PageSourceSearch

https://golf4holland.nl/g4h_assets/application-271187c20331aeb770b…6e9f43b50138df9a77502e3f03f0ac7fdc1a9926.js

js golf4holland.nl collected 2026-09-24 10:57:33 UTC 1,833,975 bytes, 41,688 lines download raw bytes

1window.TinyMCERails = {
2  configuration: {
3    default: {}
4  },
5
6  initialize: function(config, options) {
7    if (typeof tinyMCE != 'undefined') {
8      // Merge the custom options with the given configuration
9      var configuration = TinyMCERails.configuration[config || 'default'];
10      configuration = TinyMCERails._merge(configuration, options);
11
12      tinymce.init(configuration);
13    } else {
14      // Wait until TinyMCE is loaded
15      setTimeout(function() {
16        TinyMCERails.initialize(config, options);
17      }, 50);
18    }
19  },
20
21  setupTurbolinks: function() {
22    // Remove all TinyMCE instances before rendering
23    document.addEventListener('turbolinks:before-render', function() {
24      tinymce.remove();
25    });
26  },
27
28  _merge: function() {
29    var result = {};
30
31    for (var i = 0; i < arguments.length; ++i) {
32      var source = arguments[i];
33
34      for (var key in source) {
35        if (Object.prototype.hasOwnProperty.call(source, key)) {
36          if (Object.prototype.toString.call(source[key]) === '[object Object]') {
37            result[key] = TinyMCERails._merge(result[key], source[key]);
38          } else {
39            result[key] = source[key];
40          }
41        }
42      }
43    }
44
45    return result;
46  }
47};
48
49if (typeof Turbolinks != 'undefined' && Turbolinks.supported) {
50  TinyMCERails.setupTurbolinks();
51};
52window.tinymce = window.tinymce || {
53  base:   '/g4h_assets/tinymce',
54  suffix: ''
55};
56/**
57 * TinyMCE version 8.4.0 (2026-03-31
vendor: 20,506 bytes, lines 57-631
57)
58 */
59
60(function () {
61    'use strict';
62
63    var typeOf$1 = function (x) {
64        if (x === null) {
65            return 'null';
66        }
67        if (x === undefined) {
68            return 'undefined';
69        }
70        var t = typeof x;
71        if (t === 'object' && (Array.prototype.isPrototypeOf(x) || x.constructor && x.constructor.name === 'Array')) {
72            return 'array';
73        }
74        if (t === 'object' && (String.prototype.isPrototypeOf(x) || x.constructor && x.constructor.name === 'String')) {
75            return 'string';
76        }
77        return t;
78    };
79    var isEquatableType = function (x) {
80        return ['undefined', 'boolean', 'number', 'string', 'function', 'xml', 'null'].indexOf(x) !== -1;
81    };
82
83    var sort$1 = function (xs, compareFn) {
84        var clone = Array.prototype.slice.call(xs);
85        return clone.sort(compareFn);
86    };
87
88    var contramap = function (eqa, f) {
89        return eq$2(function (x, y) { return eqa.eq(f(x), f(y)); });
90    };
91    var eq$2 = function (f) {
92        return ({ eq: f });
93    };
94    var tripleEq = eq$2(function (x, y) { return x === y; });
95    var eqString = tripleEq;
96    var eqArray = function (eqa) { return eq$2(function (x, y) {
97        if (x.length !== y.length) {
98            return false;
99        }
100        var len = x.length;
101        for (var i = 0; i < len; i++) {
102            if (!eqa.eq(x[i], y[i])) {
103                return false;
104            }
105        }
106        return true;
107    }); };
108    // TODO: Make an Ord typeclass
109    var eqSortedArray = function (eqa, compareFn) {
110        return contramap(eqArray(eqa), function (xs) { return sort$1(xs, compareFn); });
111    };
112    var eqRecord = function (eqa) { return eq$2(function (x, y) {
113        var kx = Object.keys(x);
114        var ky = Object.keys(y);
115        if (!eqSortedArray(eqString).eq(kx, ky)) {
116            return false;
117        }
118        var len = kx.length;
119        for (var i = 0; i < len; i++) {
120            var q = kx[i];
121            if (!eqa.eq(x[q], y[q])) {
122                return false;
123            }
124        }
125        return true;
126    }); };
127    var eqAny = eq$2(function (x, y) {
128        if (x === y) {
129            return true;
130        }
131        var tx = typeOf$1(x);
132        var ty = typeOf$1(y);
133        if (tx !== ty) {
134            return false;
135        }
136        if (isEquatableType(tx)) {
137            return x === y;
138        }
139        else if (tx === 'array') {
140            return eqArray(eqAny).eq(x, y);
141        }
142        else if (tx === 'object') {
143            return eqRecord(eqAny).eq(x, y);
144        }
145        return false;
146    });
147
148    /* eslint-disable @typescript-eslint/no-wrapper-object-types */
149    const getPrototypeOf$2 = Object.getPrototypeOf;
150    const hasProto = (v, constructor, predicate) => {
151        if (predicate(v, constructor.prototype)) {
152            return true;
153        }
154        else {
155            // String-based fallback time
156            return v.constructor?.name === constructor.name;
157        }
158    };
159    const typeOf = (x) => {
160        const t = typeof x;
161        if (x === null) {
162            return 'null';
163        }
164        else if (t === 'object' && Array.isArray(x)) {
165            return 'array';
166        }
167        else if (t === 'object' && hasProto(x, String, (o, proto) => proto.isPrototypeOf(o))) {
168            return 'string';
169        }
170        else {
171            return t;
172        }
173    };
174    const isType$1 = (type) => (value) => typeOf(value) === type;
175    const isSimpleType = (type) => (value) => typeof value === type;
176    const eq$1 = (t) => (a) => t === a;
177    const is$5 = (value, constructor) => isObject(value) && hasProto(value, constructor, (o, proto) => getPrototypeOf$2(o) === proto);
178    const isString = isType$1('string');
179    const isObject = isType$1('object');
180    const isPlainObject = (value) => is$5(value, Object);
181    const isArray$1 = isType$1('array');
182    const isNull = eq$1(null);
183    const isBoolean = isSimpleType('boolean');
184    const isUndefined = eq$1(undefined);
185    const isNullable = (a) => a === null || a === undefined;
186    const isNonNullable = (a) => !isNullable(a);
187    const isFunction = isSimpleType('function');
188    const isNumber = isSimpleType('number');
189    const isArrayOf = (value, pred) => {
190        if (isArray$1(value)) {
191            for (let i = 0, len = value.length; i < len; ++i) {
192                if (!(pred(value[i]))) {
193                    return false;
194                }
195            }
196            return true;
197        }
198        return false;
199    };
200
201    const noop = () => { };
202    /** Compose a unary function with an n-ary function */
203    const compose = (fa, fb) => {
204        return (...args) => {
205            return fa(fb.apply(null, args));
206        };
207    };
208    /** Compose two unary functions. Similar to compose, but avoids using Function.prototype.apply. */
209    const compose1 = (fbc, fab) => (a) => fbc(fab(a));
210    const constant = (value) => {
211        return () => {
212            return value;
213        };
214    };
215    const identity = (x) => {
216        return x;
217    };
218    const tripleEquals = (a, b) => {
219        return a === b;
220    };
221    function curry(fn, ...initialArgs) {
222        return (...restArgs) => {
223            const all = initialArgs.concat(restArgs);
224            return fn.apply(null, all);
225        };
226    }
227    const not = (f) => (t) => !f(t);
228    const die = (msg) => {
229        return () => {
230            throw new Error(msg);
231        };
232    };
233    const apply$1 = (f) => {
234        return f();
235    };
236    const call = (f) => {
237        f();
238    };
239    const never = constant(false);
240    const always = constant(true);
241
242    /**
243     * The `Optional` type represents a value (of any type) that potentially does
244     * not exist. Any `Optional<T>` can either be a `Some<T>` (in which case the
245     * value does exist) or a `None` (in which case the value does not exist). This
246     * module defines a whole lot of FP-inspired utility functions for dealing with
247     * `Optional` objects.
248     *
249     * Comparison with null or undefined:
250     * - We don't get fancy null coalescing operators with `Optional`
251     * - We do get fancy helper functions with `Optional`
252     * - `Optional` support nesting, and allow for the type to still be nullable (or
253     * another `Optional`)
254     * - There is no option to turn off strict-optional-checks like there is for
255     * strict-null-checks
256     */
257    class Optional {
258        tag;
259        value;
260        // Sneaky optimisation: every instance of Optional.none is identical, so just
261        // reuse the same object
262        static singletonNone = new Optional(false);
263        // The internal representation has a `tag` and a `value`, but both are
264        // private: able to be console.logged, but not able to be accessed by code
265        constructor(tag, value) {
266            this.tag = tag;
267            this.value = value;
268        }
269        // --- Identities ---
270        /**
271         * Creates a new `Optional<T>` that **does** contain a value.
272         */
273        static some(value) {
274            return new Optional(true, value);
275        }
276        /**
277         * Create a new `Optional<T>` that **does not** contain a value. `T` can be
278         * any type because we don't actually have a `T`.
279         */
280        static none() {
281            return Optional.singletonNone;
282        }
283        /**
284         * Perform a transform on an `Optional` type. Regardless of whether this
285         * `Optional` contains a value or not, `fold` will return a value of type `U`.
286         * If this `Optional` does not contain a value, the `U` will be created by
287         * calling `onNone`. If this `Optional` does contain a value, the `U` will be
288         * created by calling `onSome`.
289         *
290         * For the FP enthusiasts in the room, this function:
291         * 1. Could be used to implement all of the functions below
292         * 2. Forms a catamorphism
293         */
294        fold(onNone, onSome) {
295            if (this.tag) {
296                return onSome(this.value);
297            }
298            else {
299                return onNone();
300            }
301        }
302        /**
303         * Determine if this `Optional` object contains a value.
304         */
305        isSome() {
306            return this.tag;
307        }
308        /**
309         * Determine if this `Optional` object **does not** contain a value.
310         */
311        isNone() {
312            return !this.tag;
313        }
314        // --- Functor (name stolen from Haskell / maths) ---
315        /**
316         * Perform a transform on an `Optional` object, **if** there is a value. If
317         * you provide a function to turn a T into a U, this is the function you use
318         * to turn an `Optional<T>` into an `Optional<U>`. If this **does** contain
319         * a value then the output will also contain a value (that value being the
320         * output of `mapper(this.value)`), and if this **does not** contain a value
321         * then neither will the output.
322         */
323        map(mapper) {
324            if (this.tag) {
325                return Optional.some(mapper(this.value));
326            }
327            else {
328                return Optional.none();
329            }
330        }
331        // --- Monad (name stolen from Haskell / maths) ---
332        /**
333         * Perform a transform on an `Optional` object, **if** there is a value.
334         * Unlike `map`, here the transform itself also returns an `Optional`.
335         */
336        bind(binder) {
337            if (this.tag) {
338                return binder(this.value);
339            }
340            else {
341                return Optional.none();
342            }
343        }
344        // --- Traversable (name stolen from Haskell / maths) ---
345        /**
346         * For a given predicate, this function finds out if there **exists** a value
347         * inside this `Optional` object that meets the predicate. In practice, this
348         * means that for `Optional`s that do not contain a value it returns false (as
349         * no predicate-meeting value exists).
350         */
351        exists(predicate) {
352            return this.tag && predicate(this.value);
353        }
354        /**
355         * For a given predicate, this function finds out if **all** the values inside
356         * this `Optional` object meet the predicate. In practice, this means that
357         * for `Optional`s that do not contain a value it returns true (as all 0
358         * objects do meet the predicate).
359         */
360        forall(predicate) {
361            return !this.tag || predicate(this.value);
362        }
363        filter(predicate) {
364            if (!this.tag || predicate(this.value)) {
365                return this;
366            }
367            else {
368                return Optional.none();
369            }
370        }
371        // --- Getters ---
372        /**
373         * Get the value out of the inside of the `Optional` object, using a default
374         * `replacement` value if the provided `Optional` object does not contain a
375         * value.
376         */
377        getOr(replacement) {
378            return this.tag ? this.value : replacement;
379        }
380        /**
381         * Get the value out of the inside of the `Optional` object, using a default
382         * `replacement` value if the provided `Optional` object does not contain a
383         * value.  Unlike `getOr`, in this method the `replacement` object is also
384         * `Optional` - meaning that this method will always return an `Optional`.
385         */
386        or(replacement) {
387            return this.tag ? this : replacement;
388        }
389        /**
390         * Get the value out of the inside of the `Optional` object, using a default
391         * `replacement` value if the provided `Optional` object does not contain a
392         * value. Unlike `getOr`, in this method the `replacement` value is
393         * "thunked" - that is to say that you don't pass a value to `getOrThunk`, you
394         * pass a function which (if called) will **return** the `value` you want to
395         * use.
396         */
397        getOrThunk(thunk) {
398            return this.tag ? this.value : thunk();
399        }
400        /**
401         * Get the value out of the inside of the `Optional` object, using a default
402         * `replacement` value if the provided Optional object does not contain a
403         * value.
404         *
405         * Unlike `or`, in this method the `replacement` value is "thunked" - that is
406         * to say that you don't pass a value to `orThunk`, you pass a function which
407         * (if called) will **return** the `value` you want to use.
408         *
409         * Unlike `getOrThunk`, in this method the `replacement` value is also
410         * `Optional`, meaning that this method will always return an `Optional`.
411         */
412        orThunk(thunk) {
413            return this.tag ? this : thunk();
414        }
415        /**
416         * Get the value out of the inside of the `Optional` object, throwing an
417         * exception if the provided `Optional` object does not contain a value.
418         *
419         * WARNING:
420         * You should only be using this function if you know that the `Optional`
421         * object **is not** empty (otherwise you're throwing exceptions in production
422         * code, which is bad).
423         *
424         * In tests this is more acceptable.
425         *
426         * Prefer other methods to this, such as `.each`.
427         */
428        getOrDie(message) {
429            if (!this.tag) {
430                throw new Error(message ?? 'Called getOrDie on None');
431            }
432            else {
433                return this.value;
434            }
435        }
436        // --- Interop with null and undefined ---
437        /**
438         * Creates an `Optional` value from a nullable (or undefined-able) input.
439         * Null, or undefined, is converted to `None`, and anything else is converted
440         * to `Some`.
441         */
442        static from(value) {
443            return isNonNullable(value) ? Optional.some(value) : Optional.none();
444        }
445        /**
446         * Converts an `Optional` to a nullable type, by getting the value if it
447         * exists, or returning `null` if it does not.
448         */
449        getOrNull() {
450            return this.tag ? this.value : null;
451        }
452        /**
453         * Converts an `Optional` to an undefined-able type, by getting the value if
454         * it exists, or returning `undefined` if it does not.
455         */
456        getOrUndefined() {
457            return this.value;
458        }
459        // --- Utilities ---
460        /**
461         * If the `Optional` contains a value, perform an action on that value.
462         * Unlike the rest of the methods on this type, `.each` has side-effects. If
463         * you want to transform an `Optional<T>` **into** something, then this is not
464         * the method for you. If you want to use an `Optional<T>` to **do**
465         * something, then this is the method for you - provided you're okay with not
466         * doing anything in the case where the `Optional` doesn't have a value inside
467         * it. If you're not sure whether your use-case fits into transforming
468         * **into** something or **doing** something, check whether it has a return
469         * value. If it does, you should be performing a transform.
470         */
471        each(worker) {
472            if (this.tag) {
473                worker(this.value);
474            }
475        }
476        /**
477         * Turn the `Optional` object into an array that contains all of the values
478         * stored inside the `Optional`. In practice, this means the output will have
479         * either 0 or 1 elements.
480         */
481        toArray() {
482            return this.tag ? [this.value] : [];
483        }
484        /**
485         * Turn the `Optional` object into a string for debugging or printing. Not
486         * recommended for production code, but good for debugging. Also note that
487         * these days an `Optional` object can be logged to the console directly, and
488         * its inner value (if it exists) will be visible.
489         */
490        toString() {
491            return this.tag ? `some(${this.value})` : 'none()';
492        }
493    }
494
495    const nativeSlice = Array.prototype.slice;
496    const nativeIndexOf = Array.prototype.indexOf;
497    const nativePush = Array.prototype.push;
498    const rawIndexOf = (ts, t) => nativeIndexOf.call(ts, t);
499    const indexOf$1 = (xs, x) => {
500        // The rawIndexOf method does not wrap up in an option. This is for performance reasons.
501        const r = rawIndexOf(xs, x);
502        return r === -1 ? Optional.none() : Optional.some(r);
503    };
504    const contains$2 = (xs, x) => rawIndexOf(xs, x) > -1;
505    const exists = (xs, pred) => {
506        for (let i = 0, len = xs.length; i < len; i++) {
507            const x = xs[i];
508            if (pred(x, i)) {
509                return true;
510            }
511        }
512        return false;
513    };
514    const map$3 = (xs, f) => {
515        // pre-allocating array size when it's guaranteed to be known
516        // http://jsperf.com/push-allocated-vs-dynamic/22
517        const len = xs.length;
518        const r = new Array(len);
519        for (let i = 0; i < len; i++) {
520            const x = xs[i];
521            r[i] = f(x, i);
522        }
523        return r;
524    };
525    // Unwound implementing other functions in terms of each.
526    // The code size is roughly the same, and it should allow for better optimisation.
527    // const each = function<T, U>(xs: T[], f: (x: T, i?: number, xs?: T[]) => void): void {
528    const each$e = (xs, f) => {
529        for (let i = 0, len = xs.length; i < len; i++) {
530            const x = xs[i];
531            f(x, i);
532        }
533    };
534    const eachr = (xs, f) => {
535        for (let i = xs.length - 1; i >= 0; i--) {
536            const x = xs[i];
537            f(x, i);
538        }
539    };
540    const partition$2 = (xs, pred) => {
541        const pass = [];
542        const fail = [];
543        for (let i = 0, len = xs.length; i < len; i++) {
544            const x = xs[i];
545            const arr = pred(x, i) ? pass : fail;
546            arr.push(x);
547        }
548        return { pass, fail };
549    };
550    const filter$5 = (xs, pred) => {
551        const r = [];
552        for (let i = 0, len = xs.length; i < len; i++) {
553            const x = xs[i];
554            if (pred(x, i)) {
555                r.push(x);
556            }
557        }
558        return r;
559    };
560    /*
561     * Groups an array into contiguous arrays of like elements. Whether an element is like or not depends on f.
562     *
563     * f is a function that derives a value from an element - e.g. true or false, or a string.
564     * Elements are like if this function generates the same value for them (according to ===).
565     *
566     *
567     * Order of the elements is preserved. Arr.flatten() on the result will return the original list, as with Haskell groupBy function.
568     *  For a good explanation, see the group function (which is a special case of groupBy)
569     *  http://hackage.haskell.org/package/base-4.7.0.0/docs/Data-List.html#v:group
570     */
571    const groupBy = (xs, f) => {
572        if (xs.length === 0) {
573            return [];
574        }
575        else {
576            let wasType = f(xs[0]); // initial case for matching
577            const r = [];
578            let group = [];
579            for (let i = 0, len = xs.length; i < len; i++) {
580                const x = xs[i];
581                const type = f(x);
582                if (type !== wasType) {
583                    r.push(group);
584                    group = [];
585                }
586                wasType = type;
587                group.push(x);
588            }
589            if (group.length !== 0) {
590                r.push(group);
591            }
592            return r;
593        }
594    };
595    const foldr = (xs, f, acc) => {
596        eachr(xs, (x, i) => {
597            acc = f(acc, x, i);
598        });
599        return acc;
600    };
601    const foldl = (xs, f, acc) => {
602        each$e(xs, (x, i) => {
603            acc = f(acc, x, i);
604        });
605        return acc;
606    };
607    const findUntil$1 = (xs, pred, until) => {
608        for (let i = 0, len = xs.length; i < len; i++) {
609            const x = xs[i];
610            if (pred(x, i)) {
611                return Optional.some(x);
612            }
613            else if (until(x, i)) {
614                break;
615            }
616        }
617        return Optional.none();
618    };
619    const find$2 = (xs, pred) => {
620        return findUntil$1(xs, pred, never);
621    };
622    const findIndex$2 = (xs, pred) => {
623        for (let i = 0, len = xs.length; i < len; i++) {
624            const x = xs[i];
625            if (pred(x, i)) {
626                return Optional.some(i);
627            }
628        }
629        return Optional.none();
630    };
631    const findLast
631Index = (arr, pred) => {
632        for (let i = arr.length - 1; i >= 0; i--) {
633            if (pred(arr[i], i)) {
634                return Optional.some(i);
635            }
636        }
637        return Optional.none();
638    };
639    const flatten$1 = (xs) => {
640        // Note, this is possible because push supports multiple arguments:
641        // http://jsperf.com/concat-push/6
642        // Note that in the past, concat() would silently work (very slowly) for array-like objects.
vendor: 3,862 bytes, lines 643-767
643        // With this change it will throw an error.
644        const r = [];
645        for (let i = 0, len = xs.length; i < len; ++i) {
646            // Ensure that each value is an array itself
647            if (!isArray$1(xs[i])) {
648                throw new Error('Arr.flatten item ' + i + ' was not an array, input: ' + xs);
649            }
650            nativePush.apply(r, xs[i]);
651        }
652        return r;
653    };
654    const bind$3 = (xs, f) => flatten$1(map$3(xs, f));
655    const forall = (xs, pred) => {
656        for (let i = 0, len = xs.length; i < len; ++i) {
657            const x = xs[i];
658            if (pred(x, i) !== true) {
659                return false;
660            }
661        }
662        return true;
663    };
664    const reverse = (xs) => {
665        const r = nativeSlice.call(xs, 0);
666        r.reverse();
667        return r;
668    };
669    const difference = (a1, a2) => filter$5(a1, (x) => !contains$2(a2, x));
670    const mapToObject = (xs, f) => {
671        const r = {};
672        for (let i = 0, len = xs.length; i < len; i++) {
673            const x = xs[i];
674            r[String(x)] = f(x, i);
675        }
676        return r;
677    };
678    const sort = (xs, comparator) => {
679        const copy = nativeSlice.call(xs, 0);
680        copy.sort(comparator);
681        return copy;
682    };
683    const get$b = (xs, i) => i >= 0 && i < xs.length ? Optional.some(xs[i]) : Optional.none();
684    const head = (xs) => get$b(xs, 0);
685    const last$2 = (xs) => get$b(xs, xs.length - 1);
686    const from = isFunction(Array.from) ? Array.from : (x) => nativeSlice.call(x);
687    const findMap = (arr, f) => {
688        for (let i = 0; i < arr.length; i++) {
689            const r = f(arr[i], i);
690            if (r.isSome()) {
691                return r;
692            }
693        }
694        return Optional.none();
695    };
696    const unique$1 = (xs, comparator) => {
697        const r = [];
698        const isDuplicated = isFunction(comparator) ?
699            (x) => exists(r, (i) => comparator(i, x)) :
700            (x) => contains$2(r, x);
701        for (let i = 0, len = xs.length; i < len; i++) {
702            const x = xs[i];
703            if (!isDuplicated(x)) {
704                r.push(x);
705            }
706        }
707        return r;
708    };
709
710    // There are many variations of Object iteration that are faster than the 'for-in' style:
711    // http://jsperf.com/object-keys-iteration/107
712    //
713    // Use the native keys if it is available (IE9+), otherwise fall back to manually filtering
714    const keys = Object.keys;
715    const hasOwnProperty$1 = Object.hasOwnProperty;
716    const each$d = (obj, f) => {
717        const props = keys(obj);
718        for (let k = 0, len = props.length; k < len; k++) {
719            const i = props[k];
720            const x = obj[i];
721            f(x, i);
722        }
723    };
724    const map$2 = (obj, f) => {
725        return tupleMap(obj, (x, i) => ({
726            k: i,
727            v: f(x, i)
728        }));
729    };
730    const tupleMap = (obj, f) => {
731        const r = {};
732        each$d(obj, (x, i) => {
733            const tuple = f(x, i);
734            r[tuple.k] = tuple.v;
735        });
736        return r;
737    };
738    const objAcc = (r) => (x, i) => {
739        r[i] = x;
740    };
741    const internalFilter = (obj, pred, onTrue, onFalse) => {
742        each$d(obj, (x, i) => {
743            (pred(x, i) ? onTrue : onFalse)(x, i);
744        });
745    };
746    const bifilter = (obj, pred) => {
747        const t = {};
748        const f = {};
749        internalFilter(obj, pred, objAcc(t), objAcc(f));
750        return { t, f };
751    };
752    const filter$4 = (obj, pred) => {
753        const t = {};
754        internalFilter(obj, pred, objAcc(t), noop);
755        return t;
756    };
757    const mapToArray = (obj, f) => {
758        const r = [];
759        each$d(obj, (value, name) => {
760            r.push(f(value, name));
761        });
762        return r;
763    };
764    const values = (obj) => {
765        return mapToArray(obj, identity);
766    };
767    const get$a
vendor: 53,531 bytes, lines 767-2256
767 = (obj, key) => {
768        return has$2(obj, key) ? Optional.from(obj[key]) : Optional.none();
769    };
770    const has$2 = (obj, key) => hasOwnProperty$1.call(obj, key);
771    const hasNonNullableKey = (obj, key) => has$2(obj, key) && obj[key] !== undefined && obj[key] !== null;
772    const equal$1 = (a1, a2, eq = eqAny) => eqRecord(eq).eq(a1, a2);
773
774    /*
775     * Generates a church encoded ADT (https://en.wikipedia.org/wiki/Church_encoding)
776     * For syntax and use, look at the test code.
777     */
778    const generate$1 = (cases) => {
779        // validation
780        if (!isArray$1(cases)) {
781            throw new Error('cases must be an array');
782        }
783        if (cases.length === 0) {
784            throw new Error('there must be at least one case');
785        }
786        const constructors = [];
787        // adt is mutated to add the individual cases
788        const adt = {};
789        each$e(cases, (acase, count) => {
790            const keys$1 = keys(acase);
791            // validation
792            if (keys$1.length !== 1) {
793                throw new Error('one and only one name per case');
794            }
795            const key = keys$1[0];
796            const value = acase[key];
797            // validation
798            if (adt[key] !== undefined) {
799                throw new Error('duplicate key detected:' + key);
800            }
801            else if (key === 'cata') {
802                throw new Error('cannot have a case named cata (sorry)');
803            }
804            else if (!isArray$1(value)) {
805                // this implicitly checks if acase is an object
806                throw new Error('case arguments must be an array');
807            }
808            constructors.push(key);
809            //
810            // constructor for key
811            //
812            adt[key] = (...args) => {
813                const argLength = args.length;
814                // validation
815                if (argLength !== value.length) {
816                    throw new Error('Wrong number of arguments to case ' + key + '. Expected ' + value.length + ' (' + value + '), got ' + argLength);
817                }
818                const match = (branches) => {
819                    const branchKeys = keys(branches);
820                    if (constructors.length !== branchKeys.length) {
821                        throw new Error('Wrong number of arguments to match. Expected: ' + constructors.join(',') + '\nActual: ' + branchKeys.join(','));
822                    }
823                    const allReqd = forall(constructors, (reqKey) => {
824                        return contains$2(branchKeys, reqKey);
825                    });
826                    if (!allReqd) {
827                        throw new Error('Not all branches were specified when using match. Specified: ' + branchKeys.join(', ') + '\nRequired: ' + constructors.join(', '));
828                    }
829                    return branches[key].apply(null, args);
830                };
831                //
832                // the fold function for key
833                //
834                return {
835                    fold: (...foldArgs) => {
836                        // runtime validation
837                        if (foldArgs.length !== cases.length) {
838                            throw new Error('Wrong number of arguments to fold. Expected ' + cases.length + ', got ' + foldArgs.length);
839                        }
840                        const target = foldArgs[count];
841                        return target.apply(null, args);
842                    },
843                    match,
844                    // NOTE: Only for debugging.
845                    log: (label) => {
846                        // eslint-disable-next-line no-console
847                        console.log(label, {
848                            constructors,
849                            constructor: key,
850                            params: args
851                        });
852                    }
853                };
854            };
855        });
856        return adt;
857    };
858    const Adt = {
859        generate: generate$1
860    };
861
862    const Cell = (initial) => {
863        let value = initial;
864        const get = () => {
865            return value;
866        };
867        const set = (v) => {
868            value = v;
869        };
870        return {
871            get,
872            set
873        };
874    };
875
876    /**
877     * Creates a new `Result<T, E>` that **does** contain a value.
878     */
879    const value$2 = (value) => {
880        const applyHelper = (fn) => fn(value);
881        const constHelper = constant(value);
882        const outputHelper = () => output;
883        const output = {
884            // Debug info
885            tag: true,
886            inner: value,
887            // Actual Result methods
888            fold: (_onError, onValue) => onValue(value),
889            isValue: always,
890            isError: never,
891            map: (mapper) => Result.value(mapper(value)),
892            mapError: outputHelper,
893            bind: applyHelper,
894            exists: applyHelper,
895            forall: applyHelper,
896            getOr: constHelper,
897            or: outputHelper,
898            getOrThunk: constHelper,
899            orThunk: outputHelper,
900            getOrDie: constHelper,
901            each: (fn) => {
902                // Can't write the function inline because we don't want to return something by mistake
903                fn(value);
904            },
905            toOptional: () => Optional.some(value),
906        };
907        return output;
908    };
909    /**
910     * Creates a new `Result<T, E>` that **does not** contain a value, and therefore
911     * contains an error.
912     */
913    const error = (error) => {
914        const outputHelper = () => output;
915        const output = {
916            // Debug info
917            tag: false,
918            inner: error,
919            // Actual Result methods
920            fold: (onError, _onValue) => onError(error),
921            isValue: never,
922            isError: always,
923            map: outputHelper,
924            mapError: (mapper) => Result.error(mapper(error)),
925            bind: outputHelper,
926            exists: never,
927            forall: always,
928            getOr: identity,
929            or: identity,
930            getOrThunk: apply$1,
931            orThunk: apply$1,
932            getOrDie: die(String(error)),
933            each: noop,
934            toOptional: Optional.none,
935        };
936        return output;
937    };
938    /**
939     * Creates a new `Result<T, E>` from an `Optional<T>` and an `E`. If the
940     * `Optional` contains a value, so will the outputted `Result`. If it does not,
941     * the outputted `Result` will contain an error (and that error will be the
942     * error passed in).
943     */
944    const fromOption = (optional, err) => optional.fold(() => error(err), value$2);
945    const Result = {
946        value: value$2,
947        error,
948        fromOption
949    };
950
951    // Use window object as the global if it's available since CSP will block script evals
952    // eslint-disable-next-line @typescript-eslint/no-implied-eval
953    const Global = typeof window !== 'undefined' ? window : Function('return this;')();
954
955    /* eslint-disable no-bitwise */
956    const uuidV4Bytes = () => {
957        const bytes = window.crypto.getRandomValues(new Uint8Array(16));
958        // https://tools.ietf.org/html/rfc4122#section-4.1.3
959        // This will first bit mask away the most significant 4 bits (version octet)
960        // then mask in the v4 number we only care about v4 random version at this point so (byte & 0b00001111 | 0b01000000)
961        bytes[6] = bytes[6] & 15 | 64;
962        // https://tools.ietf.org/html/rfc4122#section-4.1.1
963        // This will first bit mask away the highest two bits then masks in the highest bit so (byte & 0b00111111 | 0b10000000)
964        // So it will set the Msb0=1 & Msb1=0 described by the "The variant specified in this document." row in the table
965        bytes[8] = bytes[8] & 63 | 128;
966        return bytes;
967    };
968    const uuidV4String = () => {
969        const uuid = uuidV4Bytes();
970        const getHexRange = (startIndex, endIndex) => {
971            let buff = '';
972            for (let i = startIndex; i <= endIndex; ++i) {
973                const hexByte = uuid[i].toString(16).padStart(2, '0');
974                buff += hexByte;
975            }
976            return buff;
977        };
978        // RFC 4122 UUID format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
979        return `${getHexRange(0, 3)}-${getHexRange(4, 5)}-${getHexRange(6, 7)}-${getHexRange(8, 9)}-${getHexRange(10, 15)}`;
980    };
981
982    /**
983     * Adds two numbers, and wrap to a range.
984     * If the result overflows to the right, snap to the left.
985     * If the result overflows to the left, snap to the right.
986     */
987    // ASSUMPTION: Max will always be larger than min
988    const clamp$2 = (value, min, max) => Math.min(Math.max(value, min), max);
989    // the division is meant to get a number between 0 and 1 for more information check this discussion: https://stackoverflow.com/questions/58285941/how-to-replace-math-random-with-crypto-getrandomvalues-and-keep-same-result
990    const random = () => window.crypto.getRandomValues(new Uint32Array(1))[0] / 4294967295;
991
992    /**
993     * Generate a unique identifier.
994     *
995     * The unique portion of the identifier only contains an underscore
996     * and digits, so that it may safely be used within HTML attributes.
997     *
998     * The chance of generating a non-unique identifier has been minimized
999     * by combining the current time, a random number and a one-up counter.
1000     *
1001     * generate :: String -> String
1002     */
1003    let unique = 0;
1004    const generate = (prefix) => {
1005        const date = new Date();
1006        const time = date.getTime();
1007        const random$1 = Math.floor(random() * 1000000000);
1008        unique++;
1009        return prefix + '_' + random$1 + unique + String(time);
1010    };
1011    /**
1012     * Generate a uuidv4 string
1013     * In accordance with RFC 4122 (https://datatracker.ietf.org/doc/html/rfc4122)
1014     */
1015    const uuidV4 = () => {
1016        if (window.isSecureContext) {
1017            return window.crypto.randomUUID();
1018        }
1019        else {
1020            return uuidV4String();
1021        }
1022    };
1023
1024    const shallow$1 = (old, nu) => {
1025        return nu;
1026    };
1027    const deep$1 = (old, nu) => {
1028        const bothObjects = isPlainObject(old) && isPlainObject(nu);
1029        return bothObjects ? deepMerge(old, nu) : nu;
1030    };
1031    const baseMerge = (merger) => {
1032        return (...objects) => {
1033            if (objects.length === 0) {
1034                throw new Error(`Can't merge zero objects`);
1035            }
1036            const ret = {};
1037            for (let j = 0; j < objects.length; j++) {
1038                const curObject = objects[j];
1039                for (const key in curObject) {
1040                    if (has$2(curObject, key)) {
1041                        ret[key] = merger(ret[key], curObject[key]);
1042                    }
1043                }
1044            }
1045            return ret;
1046        };
1047    };
1048    const deepMerge = baseMerge(deep$1);
1049    const merge$1 = baseMerge(shallow$1);
1050
1051    /**
1052     * **Is** the value stored inside this Optional object equal to `rhs`?
1053     */
1054    const is$4 = (lhs, rhs, comparator = tripleEquals) => lhs.exists((left) => comparator(left, rhs));
1055    /**
1056     * Are these two Optional objects equal? Equality here means either they're both
1057     * `Some` (and the values are equal under the comparator) or they're both `None`.
1058     */
1059    const equals = (lhs, rhs, comparator = tripleEquals) => lift2(lhs, rhs, comparator).getOr(lhs.isNone() && rhs.isNone());
1060    const cat = (arr) => {
1061        const r = [];
1062        const push = (x) => {
1063            r.push(x);
1064        };
1065        for (let i = 0; i < arr.length; i++) {
1066            arr[i].each(push);
1067        }
1068        return r;
1069    };
1070    /*
1071    Notes on the lift functions:
1072    - We used to have a generic liftN, but we were concerned about its type-safety, and the below variants were faster in microbenchmarks.
1073    - The getOrDie calls are partial functions, but are checked beforehand. This is faster and more convenient (but less safe) than folds.
1074    - && is used instead of a loop for simplicity and performance.
1075    */
1076    const lift2 = (oa, ob, f) => oa.isSome() && ob.isSome() ? Optional.some(f(oa.getOrDie(), ob.getOrDie())) : Optional.none();
1077    const lift3 = (oa, ob, oc, f) => oa.isSome() && ob.isSome() && oc.isSome() ? Optional.some(f(oa.getOrDie(), ob.getOrDie(), oc.getOrDie())) : Optional.none();
1078    const flatten = (oot) => oot.bind(identity);
1079    // This can help with type inference, by specifying the type param on the none case, so the caller doesn't have to.
1080    const someIf = (b, a) => b ? Optional.some(a) : Optional.none();
1081
1082    /** path :: ([String], JsObj?) -> JsObj */
1083    const path = (parts, scope) => {
1084        let o = scope !== undefined && scope !== null ? scope : Global;
1085        for (let i = 0; i < parts.length && o !== undefined && o !== null; ++i) {
1086            o = o[parts[i]];
1087        }
1088        return o;
1089    };
1090    /** resolve :: (String, JsObj?) -> JsObj */
1091    const resolve$3 = (p, scope) => {
1092        const parts = p.split('.');
1093        return path(parts, scope);
1094    };
1095
1096    Adt.generate([
1097        { bothErrors: ['error1', 'error2'] },
1098        { firstError: ['error1', 'value2'] },
1099        { secondError: ['value1', 'error2'] },
1100        { bothValues: ['value1', 'value2'] }
1101    ]);
1102    /** partition :: [Result a] -> { errors: [String], values: [a] } */
1103    const partition$1 = (results) => {
1104        const errors = [];
1105        const values = [];
1106        each$e(results, (result) => {
1107            result.fold((err) => {
1108                errors.push(err);
1109            }, (value) => {
1110                values.push(value);
1111            });
1112        });
1113        return { errors, values };
1114    };
1115
1116    const singleton = (doRevoke) => {
1117        const subject = Cell(Optional.none());
1118        const revoke = () => subject.get().each(doRevoke);
1119        const clear = () => {
1120            revoke();
1121            subject.set(Optional.none());
1122        };
1123        const isSet = () => subject.get().isSome();
1124        const get = () => subject.get();
1125        const set = (s) => {
1126            revoke();
1127            subject.set(Optional.some(s));
1128        };
1129        return {
1130            clear,
1131            isSet,
1132            get,
1133            set
1134        };
1135    };
1136    const repeatable = (delay) => {
1137        const intervalId = Cell(Optional.none());
1138        const revoke = () => intervalId.get().each((id) => clearInterval(id));
1139        const clear = () => {
1140            revoke();
1141            intervalId.set(Optional.none());
1142        };
1143        const isSet = () => intervalId.get().isSome();
1144        const get = () => intervalId.get();
1145        const set = (functionToRepeat) => {
1146            revoke();
1147            intervalId.set(Optional.some(setInterval(functionToRepeat, delay)));
1148        };
1149        return {
1150            clear,
1151            isSet,
1152            get,
1153            set,
1154        };
1155    };
1156    const value$1 = () => {
1157        const subject = singleton(noop);
1158        const on = (f) => subject.get().each(f);
1159        return {
1160            ...subject,
1161            on
1162        };
1163    };
1164
1165    const removeFromStart = (str, numChars) => {
1166        return str.substring(numChars);
1167    };
1168
1169    const checkRange = (str, substr, start) => substr === '' || str.length >= substr.length && str.substr(start, start + substr.length) === substr;
1170    const removeLeading = (str, prefix) => {
1171        return startsWith(str, prefix) ? removeFromStart(str, prefix.length) : str;
1172    };
1173    const contains$1 = (str, substr, start = 0, end) => {
1174        const idx = str.indexOf(substr, start);
1175        if (idx !== -1) {
1176            return isUndefined(end) ? true : idx + substr.length <= end;
1177        }
1178        else {
1179            return false;
1180        }
1181    };
1182    /** Does 'str' start with 'prefix'?
1183     *  Note: all strings start with the empty string.
1184     *        More formally, for all strings x, startsWith(x, "").
1185     *        This is so that for all strings x and y, startsWith(y + x, y)
1186     */
1187    const startsWith = (str, prefix) => {
1188        return checkRange(str, prefix, 0);
1189    };
1190    /** Does 'str' end with 'suffix'?
1191     *  Note: all strings end with the empty string.
1192     *        More formally, for all strings x, endsWith(x, "").
1193     *        This is so that for all strings x and y, endsWith(x + y, y)
1194     */
1195    const endsWith = (str, suffix) => {
1196        return checkRange(str, suffix, str.length - suffix.length);
1197    };
1198    const blank = (r) => (s) => s.replace(r, '');
1199    /** removes all leading and trailing spaces */
1200    const trim$4 = blank(/^\s+|\s+$/g);
1201    const lTrim = blank(/^\s+/g);
1202    const rTrim = blank(/\s+$/g);
1203    const isNotEmpty = (s) => s.length > 0;
1204    const isEmpty$5 = (s) => !isNotEmpty(s);
1205    const repeat = (s, count) => count <= 0 ? '' : new Array(count + 1).join(s);
1206    const toInt = (value, radix = 10) => {
1207        const num = parseInt(value, radix);
1208        return isNaN(num) ? Optional.none() : Optional.some(num);
1209    };
1210
1211    // Run a function fn after rate ms. If another invocation occurs
1212    // during the time it is waiting, ignore it completely.
1213    const first$1 = (fn, rate) => {
1214        let timer = null;
1215        const cancel = () => {
1216            if (!isNull(timer)) {
1217                clearTimeout(timer);
1218                timer = null;
1219            }
1220        };
1221        const throttle = (...args) => {
1222            if (isNull(timer)) {
1223                timer = setTimeout(() => {
1224                    timer = null;
1225                    fn.apply(null, args);
1226                }, rate);
1227            }
1228        };
1229        return {
1230            cancel,
1231            throttle
1232        };
1233    };
1234    // Run a function fn after rate ms. If another invocation occurs
1235    // during the time it is waiting, reschedule the function again
1236    // with the new arguments.
1237    const last$1 = (fn, rate) => {
1238        let timer = null;
1239        const cancel = () => {
1240            if (!isNull(timer)) {
1241                clearTimeout(timer);
1242                timer = null;
1243            }
1244        };
1245        const throttle = (...args) => {
1246            cancel();
1247            timer = setTimeout(() => {
1248                timer = null;
1249                fn.apply(null, args);
1250            }, rate);
1251        };
1252        return {
1253            cancel,
1254            throttle
1255        };
1256    };
1257
1258    const cached = (f) => {
1259        let called = false;
1260        let r;
1261        return (...args) => {
1262            if (!called) {
1263                called = true;
1264                r = f.apply(null, args);
1265            }
1266            return r;
1267        };
1268    };
1269
1270    const zeroWidth = '\uFEFF';
1271    const nbsp = '\u00A0';
1272    const ellipsis = '\u2026';
1273    const isZwsp$2 = (char) => char === zeroWidth;
1274    const removeZwsp = (s) => s.replace(/\uFEFF/g, '');
1275
1276    const stringArray = (a) => {
1277        const all = {};
1278        each$e(a, (key) => {
1279            all[key] = {};
1280        });
1281        return keys(all);
1282    };
1283
1284    const isArrayLike = (o) => o.length !== undefined;
1285    const isArray = Array.isArray;
1286    const toArray$1 = (obj) => {
1287        if (!isArray(obj)) {
1288            const array = [];
1289            for (let i = 0, l = obj.length; i < l; i++) {
1290                array[i] = obj[i];
1291            }
1292            return array;
1293        }
1294        else {
1295            return obj;
1296        }
1297    };
1298    const each$c = (o, cb, s) => {
1299        if (!o) {
1300            return false;
1301        }
1302        s = s || o;
1303        if (isArrayLike(o)) {
1304            // Indexed arrays, needed for Safari
1305            for (let n = 0, l = o.length; n < l; n++) {
1306                if (cb.call(s, o[n], n, o) === false) {
1307                    return false;
1308                }
1309            }
1310        }
1311        else {
1312            // Hashtables
1313            for (const n in o) {
1314                if (has$2(o, n)) {
1315                    if (cb.call(s, o[n], n, o) === false) {
1316                        return false;
1317                    }
1318                }
1319            }
1320        }
1321        return true;
1322    };
1323    const map$1 = (array, callback) => {
1324        const out = [];
1325        each$c(array, (item, index) => {
1326            out.push(callback(item, index, array));
1327        });
1328        return out;
1329    };
1330    const filter$3 = (a, f) => {
1331        const o = [];
1332        each$c(a, (v, index) => {
1333            if (!f || f(v, index, a)) {
1334                o.push(v);
1335            }
1336        });
1337        return o;
1338    };
1339    const indexOf = (a, v) => {
1340        if (a) {
1341            for (let i = 0, l = a.length; i < l; i++) {
1342                if (a[i] === v) {
1343                    return i;
1344                }
1345            }
1346        }
1347        return -1;
1348    };
1349    const reduce = (collection, iteratee, accumulator, thisArg) => {
1350        let acc = isUndefined(accumulator) ? collection[0] : accumulator;
1351        for (let i = 0; i < collection.length; i++) {
1352            acc = iteratee.call(thisArg, acc, collection[i], i);
1353        }
1354        return acc;
1355    };
1356    const findIndex$1 = (array, predicate, thisArg) => {
1357        for (let i = 0, l = array.length; i < l; i++) {
1358            if (predicate.call(thisArg, array[i], i, array)) {
1359                return i;
1360            }
1361        }
1362        return -1;
1363    };
1364    const last = (collection) => collection[collection.length - 1];
1365
1366    const DeviceType = (os, browser, userAgent, mediaMatch) => {
1367        const isiPad = os.isiOS() && /ipad/i.test(userAgent) === true;
1368        const isiPhone = os.isiOS() && !isiPad;
1369        const isMobile = os.isiOS() || os.isAndroid();
1370        const isTouch = isMobile || mediaMatch('(pointer:coarse)');
1371        const isTablet = isiPad || !isiPhone && isMobile && mediaMatch('(min-device-width:768px)');
1372        const isPhone = isiPhone || isMobile && !isTablet;
1373        const iOSwebview = browser.isSafari() && os.isiOS() && /safari/i.test(userAgent) === false;
1374        const isDesktop = !isPhone && !isTablet && !iOSwebview;
1375        return {
1376            isiPad: constant(isiPad),
1377            isiPhone: constant(isiPhone),
1378            isTablet: constant(isTablet),
1379            isPhone: constant(isPhone),
1380            isTouch: constant(isTouch),
1381            isAndroid: os.isAndroid,
1382            isiOS: os.isiOS,
1383            isWebView: constant(iOSwebview),
1384            isDesktop: constant(isDesktop)
1385        };
1386    };
1387
1388    const firstMatch = (regexes, s) => {
1389        for (let i = 0; i < regexes.length; i++) {
1390            const x = regexes[i];
1391            if (x.test(s)) {
1392                return x;
1393            }
1394        }
1395        return undefined;
1396    };
1397    const find$1 = (regexes, agent) => {
1398        const r = firstMatch(regexes, agent);
1399        if (!r) {
1400            return { major: 0, minor: 0 };
1401        }
1402        const group = (i) => {
1403            return Number(agent.replace(r, '$' + i));
1404        };
1405        return nu$3(group(1), group(2));
1406    };
1407    const detect$4 = (versionRegexes, agent) => {
1408        const cleanedAgent = String(agent).toLowerCase();
1409        if (versionRegexes.length === 0) {
1410            return unknown$2();
1411        }
1412        return find$1(versionRegexes, cleanedAgent);
1413    };
1414    const unknown$2 = () => {
1415        return nu$3(0, 0);
1416    };
1417    const nu$3 = (major, minor) => {
1418        return { major, minor };
1419    };
1420    const Version = {
1421        nu: nu$3,
1422        detect: detect$4,
1423        unknown: unknown$2
1424    };
1425
1426    const detectBrowser$1 = (browsers, userAgentData) => {
1427        return findMap(userAgentData.brands, (uaBrand) => {
1428            const lcBrand = uaBrand.brand.toLowerCase();
1429            return find$2(browsers, (browser) => lcBrand === browser.brand?.toLowerCase())
1430                .map((info) => ({
1431                current: info.name,
1432                version: Version.nu(parseInt(uaBrand.version, 10), 0)
1433            }));
1434        });
1435    };
1436
1437    const detect$3 = (candidates, userAgent) => {
1438        const agent = String(userAgent).toLowerCase();
1439        return find$2(candidates, (candidate) => {
1440            return candidate.search(agent);
1441        });
1442    };
1443    // They (browser and os) are the same at the moment, but they might
1444    // not stay that way.
1445    const detectBrowser = (browsers, userAgent) => {
1446        return detect$3(browsers, userAgent).map((browser) => {
1447            const version = Version.detect(browser.versionRegexes, userAgent);
1448            return {
1449                current: browser.name,
1450                version
1451            };
1452        });
1453    };
1454    const detectOs = (oses, userAgent) => {
1455        return detect$3(oses, userAgent).map((os) => {
1456            const version = Version.detect(os.versionRegexes, userAgent);
1457            return {
1458                current: os.name,
1459                version
1460            };
1461        });
1462    };
1463
1464    const normalVersionRegex = /.*?version\/\ ?([0-9]+)\.([0-9]+).*/;
1465    const checkContains = (target) => {
1466        return (uastring) => {
1467            return contains$1(uastring, target);
1468        };
1469    };
1470    const browsers = [
1471        // This is legacy Edge
1472        {
1473            name: 'Edge',
1474            versionRegexes: [/.*?edge\/ ?([0-9]+)\.([0-9]+)$/],
1475            search: (uastring) => {
1476                return contains$1(uastring, 'edge/') && contains$1(uastring, 'chrome') && contains$1(uastring, 'safari') && contains$1(uastring, 'applewebkit');
1477            }
1478        },
1479        // This is Google Chrome and Chromium Edge
1480        {
1481            name: 'Chromium',
1482            brand: 'Chromium',
1483            versionRegexes: [/.*?chrome\/([0-9]+)\.([0-9]+).*/, normalVersionRegex],
1484            search: (uastring) => {
1485                return contains$1(uastring, 'chrome') && !contains$1(uastring, 'chromeframe');
1486            }
1487        },
1488        {
1489            name: 'IE',
1490            versionRegexes: [/.*?msie\ ?([0-9]+)\.([0-9]+).*/, /.*?rv:([0-9]+)\.([0-9]+).*/],
1491            search: (uastring) => {
1492                return contains$1(uastring, 'msie') || contains$1(uastring, 'trident');
1493            }
1494        },
1495        // INVESTIGATE: Is this still the Opera user agent?
1496        {
1497            name: 'Opera',
1498            versionRegexes: [normalVersionRegex, /.*?opera\/([0-9]+)\.([0-9]+).*/],
1499            search: checkContains('opera')
1500        },
1501        {
1502            name: 'Firefox',
1503            versionRegexes: [/.*?firefox\/\ ?([0-9]+)\.([0-9]+).*/],
1504            search: checkContains('firefox')
1505        },
1506        {
1507            name: 'Safari',
1508            versionRegexes: [normalVersionRegex, /.*?cpu os ([0-9]+)_([0-9]+).*/],
1509            search: (uastring) => {
1510                return (contains$1(uastring, 'safari') || contains$1(uastring, 'mobile/')) && contains$1(uastring, 'applewebkit');
1511            }
1512        }
1513    ];
1514    const oses = [
1515        {
1516            name: 'Windows',
1517            search: checkContains('win'),
1518            versionRegexes: [/.*?windows\ nt\ ?([0-9]+)\.([0-9]+).*/]
1519        },
1520        {
1521            name: 'iOS',
1522            search: (uastring) => {
1523                return contains$1(uastring, 'iphone') || contains$1(uastring, 'ipad');
1524            },
1525            versionRegexes: [/.*?version\/\ ?([0-9]+)\.([0-9]+).*/, /.*cpu os ([0-9]+)_([0-9]+).*/, /.*cpu iphone os ([0-9]+)_([0-9]+).*/]
1526        },
1527        {
1528            name: 'Android',
1529            search: checkContains('android'),
1530            versionRegexes: [/.*?android\ ?([0-9]+)\.([0-9]+).*/]
1531        },
1532        {
1533            name: 'macOS',
1534            search: checkContains('mac os x'),
1535            versionRegexes: [/.*?mac\ os\ x\ ?([0-9]+)_([0-9]+).*/]
1536        },
1537        {
1538            name: 'Linux',
1539            search: checkContains('linux'),
1540            versionRegexes: []
1541        },
1542        { name: 'Solaris',
1543            search: checkContains('sunos'),
1544            versionRegexes: []
1545        },
1546        {
1547            name: 'FreeBSD',
1548            search: checkContains('freebsd'),
1549            versionRegexes: []
1550        },
1551        {
1552            name: 'ChromeOS',
1553            search: checkContains('cros'),
1554            versionRegexes: [/.*?chrome\/([0-9]+)\.([0-9]+).*/]
1555        }
1556    ];
1557    const PlatformInfo = {
1558        browsers: constant(browsers),
1559        oses: constant(oses)
1560    };
1561
1562    const edge = 'Edge';
1563    const chromium = 'Chromium';
1564    const ie = 'IE';
1565    const opera = 'Opera';
1566    const firefox = 'Firefox';
1567    const safari = 'Safari';
1568    const unknown$1 = () => {
1569        return nu$2({
1570            current: undefined,
1571            version: Version.unknown()
1572        });
1573    };
1574    const nu$2 = (info) => {
1575        const current = info.current;
1576        const version = info.version;
1577        const isBrowser = (name) => () => current === name;
1578        return {
1579            current,
1580            version,
1581            isEdge: isBrowser(edge),
1582            isChromium: isBrowser(chromium),
1583            // NOTE: isIe just looks too weird
1584            isIE: isBrowser(ie),
1585            isOpera: isBrowser(opera),
1586            isFirefox: isBrowser(firefox),
1587            isSafari: isBrowser(safari)
1588        };
1589    };
1590    const Browser = {
1591        unknown: unknown$1,
1592        nu: nu$2,
1593        edge: constant(edge),
1594        chromium: constant(chromium),
1595        ie: constant(ie),
1596        opera: constant(opera),
1597        firefox: constant(firefox),
1598        safari: constant(safari)
1599    };
1600
1601    const windows = 'Windows';
1602    const ios = 'iOS';
1603    const android = 'Android';
1604    const linux = 'Linux';
1605    const macos = 'macOS';
1606    const solaris = 'Solaris';
1607    const freebsd = 'FreeBSD';
1608    const chromeos = 'ChromeOS';
1609    // Though there is a bit of dupe with this and Browser, trying to
1610    // reuse code makes it much harder to follow and change.
1611    const unknown = () => {
1612        return nu$1({
1613            current: undefined,
1614            version: Version.unknown()
1615        });
1616    };
1617    const nu$1 = (info) => {
1618        const current = info.current;
1619        const version = info.version;
1620        const isOS = (name) => () => current === name;
1621        return {
1622            current,
1623            version,
1624            isWindows: isOS(windows),
1625            // TODO: Fix capitalisation
1626            isiOS: isOS(ios),
1627            isAndroid: isOS(android),
1628            isMacOS: isOS(macos),
1629            isLinux: isOS(linux),
1630            isSolaris: isOS(solaris),
1631            isFreeBSD: isOS(freebsd),
1632            isChromeOS: isOS(chromeos)
1633        };
1634    };
1635    const OperatingSystem = {
1636        unknown,
1637        nu: nu$1,
1638        windows: constant(windows),
1639        ios: constant(ios),
1640        android: constant(android),
1641        linux: constant(linux),
1642        macos: constant(macos),
1643        solaris: constant(solaris),
1644        freebsd: constant(freebsd),
1645        chromeos: constant(chromeos)
1646    };
1647
1648    const detect$2 = (userAgent, userAgentDataOpt, mediaMatch) => {
1649        const browsers = PlatformInfo.browsers();
1650        const oses = PlatformInfo.oses();
1651        const browser = userAgentDataOpt.bind((userAgentData) => detectBrowser$1(browsers, userAgentData))
1652            .orThunk(() => detectBrowser(browsers, userAgent))
1653            .fold(Browser.unknown, Browser.nu);
1654        const os = detectOs(oses, userAgent).fold(OperatingSystem.unknown, OperatingSystem.nu);
1655        const deviceType = DeviceType(os, browser, userAgent, mediaMatch);
1656        return {
1657            browser,
1658            os,
1659            deviceType
1660        };
1661    };
1662    const PlatformDetection = {
1663        detect: detect$2
1664    };
1665
1666    const mediaMatch = (query) => window.matchMedia(query).matches;
1667    // IMPORTANT: Must be in a thunk, otherwise rollup thinks calling this immediately
1668    // causes side effects and won't tree shake this away
1669    // Note: navigator.userAgentData is not part of the native typescript types yet
1670    let platform$4 = cached(() => PlatformDetection.detect(window.navigator.userAgent, Optional.from((window.navigator.userAgentData)), mediaMatch));
1671    const detect$1 = () => platform$4();
1672
1673    const unsafe = (name, scope) => {
1674        return resolve$3(name, scope);
1675    };
1676    const getOrDie = (name, scope) => {
1677        const actual = unsafe(name, scope);
1678        if (actual === undefined || actual === null) {
1679            throw new Error(name + ' not available on this browser');
1680        }
1681        return actual;
1682    };
1683
1684    const getPrototypeOf$1 = Object.getPrototypeOf;
1685    /*
1686     * IE9 and above
1687     *
1688     * MDN no use on this one, but here's the link anyway:
1689     * https://developer.mozilla.org/en/docs/Web/API/HTMLElement
1690     */
1691    const sandHTMLElement = (scope) => {
1692        return getOrDie('HTMLElement', scope);
1693    };
1694    const isPrototypeOf = (x) => {
1695        // use Resolve to get the window object for x and just return undefined if it can't find it.
1696        // undefined scope later triggers using the global window.
1697        const scope = resolve$3('ownerDocument.defaultView', x);
1698        // TINY-7374: We can't rely on looking at the owner window HTMLElement as the element may have
1699        // been constructed in a different window and then appended to the current window document.
1700        return isObject(x) && (sandHTMLElement(scope).prototype.isPrototypeOf(x) || /^HTML\w*Element$/.test(getPrototypeOf$1(x).constructor.name));
1701    };
1702
1703    /**
1704     * This class contains various environment constants like browser versions etc.
1705     * Normally you don't want to sniff specific browser versions but sometimes you have
1706     * to when it's impossible to feature detect. So use this with care.
1707     *
1708     * @class tinymce.Env
1709     * @static
1710     */
1711    const userAgent = window.navigator.userAgent;
1712    const platform$3 = detect$1();
1713    const browser$3 = platform$3.browser;
1714    const os$1 = platform$3.os;
1715    const deviceType = platform$3.deviceType;
1716    const windowsPhone = userAgent.indexOf('Windows Phone') !== -1;
1717    const Env = {
1718        /**
1719         * Transparent image data url.
1720         *
1721         * @property transparentSrc
1722         * @type Boolean
1723         * @final
1724         */
1725        transparentSrc: 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7',
1726        /**
1727         * Returns the IE document mode. For non IE browsers, this will fake IE 10 document mode.
1728         *
1729         * @property documentMode
1730         * @type Number
1731         */
1732        documentMode: browser$3.isIE() ? (document.documentMode || 7) : 10,
1733        cacheSuffix: null,
1734        container: null,
1735        /**
1736         * Constant if CSP mode is possible or not. Meaning we can't use script urls for the iframe.
1737         */
1738        canHaveCSP: !browser$3.isIE(),
1739        windowsPhone,
1740        /**
1741         * @include ../../../../../tools/docs/tinymce.Env.js
1742         */
1743        browser: {
1744            current: browser$3.current,
1745            version: browser$3.version,
1746            isChromium: browser$3.isChromium,
1747            isEdge: browser$3.isEdge,
1748            isFirefox: browser$3.isFirefox,
1749            isIE: browser$3.isIE,
1750            isOpera: browser$3.isOpera,
1751            isSafari: browser$3.isSafari
1752        },
1753        os: {
1754            current: os$1.current,
1755            version: os$1.version,
1756            isAndroid: os$1.isAndroid,
1757            isChromeOS: os$1.isChromeOS,
1758            isFreeBSD: os$1.isFreeBSD,
1759            isiOS: os$1.isiOS,
1760            isLinux: os$1.isLinux,
1761            isMacOS: os$1.isMacOS,
1762            isSolaris: os$1.isSolaris,
1763            isWindows: os$1.isWindows
1764        },
1765        deviceType: {
1766            isDesktop: deviceType.isDesktop,
1767            isiPad: deviceType.isiPad,
1768            isiPhone: deviceType.isiPhone,
1769            isPhone: deviceType.isPhone,
1770            isTablet: deviceType.isTablet,
1771            isTouch: deviceType.isTouch,
1772            isWebView: deviceType.isWebView
1773        }
1774    };
1775
1776    /**
1777     * This class contains various utility functions. These are also exposed
1778     * directly on the tinymce namespace.
1779     *
1780     * @class tinymce.util.Tools
1781     */
1782    /**
1783     * Removes whitespace from the beginning and end of a string.
1784     *
1785     * @method trim
1786     * @param {String} s String to remove whitespace from.
1787     * @return {String} New string with removed whitespace.
1788     */
1789    const whiteSpaceRegExp$1 = /^\s*|\s*$/g;
1790    const trim$3 = (str) => {
1791        return isNullable(str) ? '' : ('' + str).replace(whiteSpaceRegExp$1, '');
1792    };
1793    /**
1794     * Checks if a object is of a specific type for example an array.
1795     *
1796     * @method is
1797     * @param {Object} obj Object to check type of.
1798     * @param {String} type Optional type to check for.
1799     * @return {Boolean} true/false if the object is of the specified type.
1800     */
1801    const is$3 = (obj, type) => {
1802        if (!type) {
1803            return obj !== undefined;
1804        }
1805        if (type === 'array' && isArray(obj)) {
1806            return true;
1807        }
1808        return typeof obj === type;
1809    };
1810    /**
1811     * Makes a name/object map out of an array with names.
1812     *
1813     * @method makeMap
1814     * @param {Array/String} items Items to make map out of.
1815     * @param {String} delim Optional delimiter to split string by.
1816     * @param {Object} map Optional map to add items to.
1817     * @return {Object} Name/value map of items.
1818     */
1819    const makeMap$4 = (items, delim, map = {}) => {
1820        const resolvedItems = isString(items) ? items.split(delim || ',') : (items || []);
1821        let i = resolvedItems.length;
1822        while (i--) {
1823            map[resolvedItems[i]] = {};
1824        }
1825        return map;
1826    };
1827    /**
1828     * JavaScript does not protect hasOwnProperty method, so it is possible to overwrite it. This is
1829     * an object independent version.
1830     * Checks if the input object "<code>obj</code>" has the property "<code>prop</code>".
1831     *
1832     * @method hasOwnProperty
1833     * @param {Object} obj Object to check if the property exists.
1834     * @param {String} prop Name of a property on the object.
1835     * @returns {Boolean} true if the object has the specified property.
1836     */
1837    const hasOwnProperty = has$2;
1838    const extend$3 = (obj, ...exts) => {
1839        for (let i = 0; i < exts.length; i++) {
1840            const ext = exts[i];
1841            for (const name in ext) {
1842                if (has$2(ext, name)) {
1843                    const value = ext[name];
1844                    if (value !== undefined) {
1845                        obj[name] = value;
1846                    }
1847                }
1848            }
1849        }
1850        return obj;
1851    };
1852    /**
1853     * Executed the specified function for each item in a object tree.
1854     *
1855     * @method walk
1856     * @param {Object} o Object tree to walk though.
1857     * @param {Function} f Function to call for each item.
1858     * @param {String} n Optional name of collection inside the objects to walk for example childNodes.
1859     * @param {String} s Optional scope to execute the function in.
1860     */
1861    const walk$4 = function (o, f, n, s) {
1862        s = s || this;
1863        if (o) {
1864            if (n) {
1865                o = o[n];
1866            }
1867            each$c(o, (o, i) => {
1868                if (f.call(s, o, i, n) === false) {
1869                    return false;
1870                }
1871                else {
1872                    walk$4(o, f, n, s);
1873                    return true;
1874                }
1875            });
1876        }
1877    };
1878    /**
1879     * Resolves a string and returns the object from a specific structure.
1880     *
1881     * @method resolve
1882     * @param {String} n Path to resolve for example a.b.c.d.
1883     * @param {Object} o Optional object to search though, defaults to window.
1884     * @return {Object} Last object in path or null if it couldn't be resolved.
1885     * @example
1886     * // Resolve a path into an object reference
1887     * const obj = tinymce.resolve('a.b.c.d');
1888     */
1889    const resolve$2 = (n, o = window) => {
1890        const path = n.split('.');
1891        for (let i = 0, l = path.length; i < l; i++) {
1892            o = o[path[i]];
1893            if (!o) {
1894                break;
1895            }
1896        }
1897        return o;
1898    };
1899    /**
1900     * Splits a string but removes the whitespace before and after each value.
1901     *
1902     * @method explode
1903     * @param {String} s String to split.
1904     * @param {String} d Delimiter to split by.
1905     * @example
1906     * // Split a string into an array with a,b,c
1907     * const arr = tinymce.explode('a, b,   c');
1908     */
1909    const explode$3 = (s, d) => {
1910        if (isArray$1(s)) {
1911            return s;
1912        }
1913        else if (s === '') {
1914            return [];
1915        }
1916        else {
1917            return map$1(s.split(d || ','), trim$3);
1918        }
1919    };
1920    const _addCacheSuffix = (url) => {
1921        const cacheSuffix = Env.cacheSuffix;
1922        if (cacheSuffix) {
1923            url += (url.indexOf('?') === -1 ? '?' : '&') + cacheSuffix;
1924        }
1925        return url;
1926    };
1927    const Tools = {
1928        trim: trim$3,
1929        /**
1930         * Returns true/false if the object is an array or not.
1931         *
1932         * @method isArray
1933         * @param {Object} obj Object to check.
1934         * @return {Boolean} true/false state if the object is an array or not.
1935         */
1936        isArray: isArray,
1937        is: is$3,
1938        /**
1939         * Converts the specified object into a real JavaScript array.
1940         *
1941         * @method toArray
1942         * @param {Object} obj Object to convert into array.
1943         * @return {Array} Array object based in input.
1944         */
1945        toArray: toArray$1,
1946        makeMap: makeMap$4,
1947        /**
1948         * Performs an iteration of all items in a collection such as an object or array. This method will execute the
1949         * callback function for each item in the collection, if the callback returns false the iteration will terminate.
1950         * The callback has the following format: `cb(value, key_or_index)`.
1951         *
1952         * @method each
1953         * @param {Object} o Collection to iterate.
1954         * @param {Function} cb Callback function to execute for each item.
1955         * @param {Object} s Optional scope to execute the callback in.
1956         * @example
1957         * // Iterate an array
1958         * tinymce.each([ 1,2,3 ], (v, i) => {
1959         *   console.debug("Value: " + v + ", Index: " + i);
1960         * });
1961         *
1962         * // Iterate an object
1963         * tinymce.each({ a: 1, b: 2, c: 3 }, (v, k) => {
1964         *   console.debug("Value: " + v + ", Key: " + k);
1965         * });
1966         */
1967        each: each$c,
1968        /**
1969         * Creates a new array by the return value of each iteration function call. This enables you to convert
1970         * one array list into another.
1971         *
1972         * @method map
1973         * @param {Array} array Array of items to iterate.
1974         * @param {Function} callback Function to call for each item. It's return value will be the new value.
1975         * @return {Array} Array with new values based on function return values.
1976         */
1977        map: map$1,
1978        /**
1979         * Filters out items from the input array by calling the specified function for each item.
1980         * If the function returns false the item will be excluded if it returns true it will be included.
1981         *
1982         * @method grep
1983         * @param {Array} a Array of items to loop though.
1984         * @param {Function} f Function to call for each item. Include/exclude depends on it's return value.
1985         * @return {Array} New array with values imported and filtered based in input.
1986         * @example
1987         * // Filter out some items, this will return an array with 4 and 5
1988         * const items = tinymce.grep([ 1,2,3,4,5 ], (v) => v > 3);
1989         */
1990        grep: filter$3,
1991        /**
1992         * Returns an index of the item or -1 if item is not present in the array.
1993         *
1994         * @method inArray
1995         * @param {any} item Item to search for.
1996         * @param {Array} arr Array to search in.
1997         * @return {Number} index of the item or -1 if item was not found.
1998         */
1999        inArray: indexOf,
2000        hasOwn: hasOwnProperty,
2001        extend: extend$3,
2002        walk: walk$4,
2003        resolve: resolve$2,
2004        explode: explode$3,
2005        _addCacheSuffix
2006    };
2007
2008    const fromHtml$1 = (html, scope) => {
2009        const doc = scope || document;
2010        const div = doc.createElement('div');
2011        div.innerHTML = html;
2012        if (!div.hasChildNodes() || div.childNodes.length > 1) {
2013            const message = 'HTML does not have a single root node';
2014            // eslint-disable-next-line no-console
2015            console.error(message, html);
2016            throw new Error(message);
2017        }
2018        return fromDom$2(div.childNodes[0]);
2019    };
2020    const fromTag = (tag, scope) => {
2021        const doc = scope || document;
2022        const node = doc.createElement(tag);
2023        return fromDom$2(node);
2024    };
2025    const fromText = (text, scope) => {
2026        const doc = scope || document;
2027        const node = doc.createTextNode(text);
2028        return fromDom$2(node);
2029    };
2030    const fromDom$2 = (node) => {
2031        // TODO: Consider removing this check, but left atm for safety
2032        if (node === null || node === undefined) {
2033            throw new Error('Node cannot be null or undefined');
2034        }
2035        return {
2036            dom: node
2037        };
2038    };
2039    const fromPoint$2 = (docElm, x, y) => Optional.from(docElm.dom.elementFromPoint(x, y)).map(fromDom$2);
2040    // tslint:disable-next-line:variable-name
2041    const SugarElement = {
2042        fromHtml: fromHtml$1,
2043        fromTag,
2044        fromText,
2045        fromDom: fromDom$2,
2046        fromPoint: fromPoint$2
2047    };
2048
2049    // NOTE: Mutates the range.
2050    const setStart = (rng, situ) => {
2051        situ.fold((e) => {
2052            rng.setStartBefore(e.dom);
2053        }, (e, o) => {
2054            rng.setStart(e.dom, o);
2055        }, (e) => {
2056            rng.setStartAfter(e.dom);
2057        });
2058    };
2059    const setFinish = (rng, situ) => {
2060        situ.fold((e) => {
2061            rng.setEndBefore(e.dom);
2062        }, (e, o) => {
2063            rng.setEnd(e.dom, o);
2064        }, (e) => {
2065            rng.setEndAfter(e.dom);
2066        });
2067    };
2068    const relativeToNative = (win, startSitu, finishSitu) => {
2069        const range = win.document.createRange();
2070        setStart(range, startSitu);
2071        setFinish(range, finishSitu);
2072        return range;
2073    };
2074    const exactToNative = (win, start, soffset, finish, foffset) => {
2075        const rng = win.document.createRange();
2076        rng.setStart(start.dom, soffset);
2077        rng.setEnd(finish.dom, foffset);
2078        return rng;
2079    };
2080
2081    const adt$3 = Adt.generate([
2082        { ltr: ['start', 'soffset', 'finish', 'foffset'] },
2083        { rtl: ['start', 'soffset', 'finish', 'foffset'] }
2084    ]);
2085    const fromRange = (win, type, range) => type(SugarElement.fromDom(range.startContainer), range.startOffset, SugarElement.fromDom(range.endContainer), range.endOffset);
2086    const getRanges$1 = (win, selection) => selection.match({
2087        domRange: (rng) => {
2088            return {
2089                ltr: constant(rng),
2090                rtl: Optional.none
2091            };
2092        },
2093        relative: (startSitu, finishSitu) => {
2094            return {
2095                ltr: cached(() => relativeToNative(win, startSitu, finishSitu)),
2096                rtl: cached(() => Optional.some(relativeToNative(win, finishSitu, startSitu)))
2097            };
2098        },
2099        exact: (start, soffset, finish, foffset) => {
2100            return {
2101                ltr: cached(() => exactToNative(win, start, soffset, finish, foffset)),
2102                rtl: cached(() => Optional.some(exactToNative(win, finish, foffset, start, soffset)))
2103            };
2104        }
2105    });
2106    const doDiagnose = (win, ranges) => {
2107        // If we cannot create a ranged selection from start > finish, it could be RTL
2108        const rng = ranges.ltr();
2109        if (rng.collapsed) {
2110            // Let's check if it's RTL ... if it is, then reversing the direction will not be collapsed
2111            const reversed = ranges.rtl().filter((rev) => rev.collapsed === false);
2112            return reversed.map((rev) => 
2113            // We need to use "reversed" here, because the original only has one point (collapsed)
2114            adt$3.rtl(SugarElement.fromDom(rev.endContainer), rev.endOffset, SugarElement.fromDom(rev.startContainer), rev.startOffset)).getOrThunk(() => fromRange(win, adt$3.ltr, rng));
2115        }
2116        else {
2117            return fromRange(win, adt$3.ltr, rng);
2118        }
2119    };
2120    const diagnose = (win, selection) => {
2121        const ranges = getRanges$1(win, selection);
2122        return doDiagnose(win, ranges);
2123    };
2124    adt$3.ltr;
2125    adt$3.rtl;
2126
2127    const COMMENT = 8;
2128    const DOCUMENT = 9;
2129    const DOCUMENT_FRAGMENT = 11;
2130    const ELEMENT = 1;
2131    const TEXT = 3;
2132
2133    const is$2 = (element, selector) => {
2134        const dom = element.dom;
2135        if (dom.nodeType !== ELEMENT) {
2136            return false;
2137        }
2138        else {
2139            const elem = dom;
2140            if (elem.matches !== undefined) {
2141                return elem.matches(selector);
2142            }
2143            else if (elem.msMatchesSelector !== undefined) {
2144                return elem.msMatchesSelector(selector);
2145            }
2146            else if (elem.webkitMatchesSelector !== undefined) {
2147                return elem.webkitMatchesSelector(selector);
2148            }
2149            else if (elem.mozMatchesSelector !== undefined) {
2150                // cast to any as mozMatchesSelector doesn't exist in TS DOM lib
2151                return elem.mozMatchesSelector(selector);
2152            }
2153            else {
2154                throw new Error('Browser lacks native selectors');
2155            } // unfortunately we can't throw this on startup :(
2156        }
2157    };
2158    const bypassSelector = (dom) => 
2159    // Only elements, documents and shadow roots support querySelector
2160    // shadow root element type is DOCUMENT_FRAGMENT
2161    dom.nodeType !== ELEMENT && dom.nodeType !== DOCUMENT && dom.nodeType !== DOCUMENT_FRAGMENT ||
2162        // IE fix for complex queries on empty nodes: http://jsfiddle.net/spyder/fv9ptr5L/
2163        dom.childElementCount === 0;
2164    const all = (selector, scope) => {
2165        const base = scope === undefined ? document : scope.dom;
2166        return bypassSelector(base) ? [] : map$3(base.querySelectorAll(selector), SugarElement.fromDom);
2167    };
2168    const one = (selector, scope) => {
2169        const base = scope === undefined ? document : scope.dom;
2170        return bypassSelector(base) ? Optional.none() : Optional.from(base.querySelector(selector)).map(SugarElement.fromDom);
2171    };
2172
2173    const eq = (e1, e2) => e1.dom === e2.dom;
2174    // Returns: true if node e1 contains e2, otherwise false.
2175    // (returns false if e1===e2: A node does not contain itself).
2176    const contains = (e1, e2) => {
2177        const d1 = e1.dom;
2178        const d2 = e2.dom;
2179        return d1 === d2 ? false : d1.contains(d2);
2180    };
2181    const is$1 = is$2;
2182
2183    /**
2184     * Applies f repeatedly until it completes (by returning Optional.none()).
2185     *
2186     * Normally would just use recursion, but JavaScript lacks tail call optimisation.
2187     *
2188     * This is what recursion looks like when manually unravelled :)
2189     */
2190    const toArray = (target, f) => {
2191        const r = [];
2192        const recurse = (e) => {
2193            r.push(e);
2194            return f(e);
2195        };
2196        let cur = f(target);
2197        do {
2198            cur = cur.bind(recurse);
2199        } while (cur.isSome());
2200        return r;
2201    };
2202
2203    const name = (element) => {
2204        const r = element.dom.nodeName;
2205        return r.toLowerCase();
2206    };
2207    const type$1 = (element) => element.dom.nodeType;
2208    const isType = (t) => (element) => type$1(element) === t;
2209    const isComment$1 = (element) => type$1(element) === COMMENT || name(element) === '#comment';
2210    const isHTMLElement$1 = (element) => isElement$8(element) && isPrototypeOf(element.dom);
2211    const isElement$8 = isType(ELEMENT);
2212    const isText$c = isType(TEXT);
2213    const isDocument$2 = isType(DOCUMENT);
2214    const isDocumentFragment$1 = isType(DOCUMENT_FRAGMENT);
2215    const isTag = (tag) => (e) => isElement$8(e) && name(e) === tag;
2216
2217    /**
2218     * The document associated with the current element
2219     * NOTE: this will throw if the owner is null.
2220     */
2221    const owner$1 = (element) => SugarElement.fromDom(element.dom.ownerDocument);
2222    /**
2223     * If the element is a document, return it. Otherwise, return its ownerDocument.
2224     * @param dos
2225     */
2226    const documentOrOwner = (dos) => isDocument$2(dos) ? dos : owner$1(dos);
2227    const documentElement = (element) => SugarElement.fromDom(documentOrOwner(element).dom.documentElement);
2228    /**
2229     * The window element associated with the element
2230     * NOTE: this will throw if the defaultView is null.
2231     */
2232    const defaultView = (element) => SugarElement.fromDom(documentOrOwner(element).dom.defaultView);
2233    const parent = (element) => Optional.from(element.dom.parentNode).map(SugarElement.fromDom);
2234    const parentElement = (element) => Optional.from(element.dom.parentElement).map(SugarElement.fromDom);
2235    const parents$1 = (element, isRoot) => {
2236        const stop = isFunction(isRoot) ? isRoot : never;
2237        // This is used a *lot* so it needs to be performant, not recursive
2238        let dom = element.dom;
2239        const ret = [];
2240        while (dom.parentNode !== null && dom.parentNode !== undefined) {
2241            const rawParent = dom.parentNode;
2242            const p = SugarElement.fromDom(rawParent);
2243            ret.push(p);
2244            if (stop(p) === true) {
2245                break;
2246            }
2247            else {
2248                dom = rawParent;
2249            }
2250        }
2251        return ret;
2252    };
2253    const siblings = (element) => {
2254        // TODO: Refactor out children so we can just not add self instead of filtering afterwards
2255        const filterSelf = (elements) => filter$5(elements, (x) => !eq(element, x));
2256        return parent(element).map(children$
vendor: 40,845 bytes, lines 2256-3261
22561).map(filterSelf).getOr([]);
2257    };
2258    const prevSibling = (element) => Optional.from(element.dom.previousSibling).map(SugarElement.fromDom);
2259    const nextSibling = (element) => Optional.from(element.dom.nextSibling).map(SugarElement.fromDom);
2260    // This one needs to be reversed, so they're still in DOM order
2261    const prevSiblings = (element) => reverse(toArray(element, prevSibling));
2262    const nextSiblings = (element) => toArray(element, nextSibling);
2263    const children$1 = (element) => map$3(element.dom.childNodes, SugarElement.fromDom);
2264    const child$1 = (element, index) => {
2265        const cs = element.dom.childNodes;
2266        return Optional.from(cs[index]).map(SugarElement.fromDom);
2267    };
2268    const firstChild = (element) => child$1(element, 0);
2269    const lastChild = (element) => child$1(element, element.dom.childNodes.length - 1);
2270    const childNodesCount = (element) => element.dom.childNodes.length;
2271
2272    const getHead = (doc) => {
2273        /*
2274         * IE9 and above per
2275         * https://developer.mozilla.org/en-US/docs/Web/API/Document/head
2276         */
2277        const b = doc.dom.head;
2278        if (b === null || b === undefined) {
2279            throw new Error('Head is not available yet');
2280        }
2281        return SugarElement.fromDom(b);
2282    };
2283
2284    /**
2285     * Is the element a ShadowRoot?
2286     *
2287     * Note: this is insufficient to test if any element is a shadow root, but it is sufficient to differentiate between
2288     * a Document and a ShadowRoot.
2289     */
2290    const isShadowRoot = (dos) => isDocumentFragment$1(dos) && isNonNullable(dos.dom.host);
2291    const getRootNode = (e) => SugarElement.fromDom(e.dom.getRootNode());
2292    /** Where style tags need to go. ShadowRoot or document head */
2293    const getStyleContainer = (dos) => isShadowRoot(dos) ? dos : getHead(documentOrOwner(dos));
2294    /** Where content needs to go. ShadowRoot or document body */
2295    const getContentContainer = (dos) => 
2296    // Can't use SugarBody.body without causing a circular module reference (since SugarBody.inBody uses SugarShadowDom)
2297    isShadowRoot(dos) ? dos : SugarElement.fromDom(documentOrOwner(dos).dom.body);
2298    /** If this element is in a ShadowRoot, return it. */
2299    const getShadowRoot = (e) => {
2300        const r = getRootNode(e);
2301        return isShadowRoot(r) ? Optional.some(r) : Optional.none();
2302    };
2303    /** Return the host of a ShadowRoot.
2304     *
2305     * This function will throw if Shadow DOM is unsupported in the browser, or if the host is null.
2306     * If you actually have a ShadowRoot, this shouldn't happen.
2307     */
2308    const getShadowHost = (e) => SugarElement.fromDom(e.dom.host);
2309    /**
2310     * When Events bubble up through a ShadowRoot, the browser changes the target to be the shadow host.
2311     * This function gets the "original" event target if possible.
2312     * This only works if the shadow tree is open - if the shadow tree is closed, event.target is returned.
2313     * See: https://developers.google.com/web/fundamentals/web-components/shadowdom#events
2314     */
2315    const getOriginalEventTarget = (event) => {
2316        if (isNonNullable(event.target)) {
2317            const el = SugarElement.fromDom(event.target);
2318            if (isElement$8(el) && isOpenShadowHost(el)) {
2319                // When target element is inside Shadow DOM we need to take first element from composedPath
2320                // otherwise we'll get Shadow Root parent, not actual target element.
2321                if (event.composed && event.composedPath) {
2322                    const composedPath = event.composedPath();
2323                    if (composedPath) {
2324                        return head(composedPath);
2325                    }
2326                }
2327            }
2328        }
2329        return Optional.from(event.target);
2330    };
2331    /** Return true if the element is a host of an open shadow root.
2332     *  Return false if the element is a host of a closed shadow root, or if the element is not a host.
2333     */
2334    const isOpenShadowHost = (element) => isNonNullable(element.dom.shadowRoot);
2335
2336    const mkEvent = (target, x, y, stop, prevent, kill, raw) => ({
2337        target,
2338        x,
2339        y,
2340        stop,
2341        prevent,
2342        kill,
2343        raw
2344    });
2345    /** Wraps an Event in an EventArgs structure.
2346     * The returned EventArgs structure has its target set to the "original" target if possible.
2347     * See SugarShadowDom.getOriginalEventTarget
2348     */
2349    const fromRawEvent = (rawEvent) => {
2350        const target = SugarElement.fromDom(getOriginalEventTarget(rawEvent).getOr(rawEvent.target));
2351        const stop = () => rawEvent.stopPropagation();
2352        const prevent = () => rawEvent.preventDefault();
2353        const kill = compose(prevent, stop); // more of a sequence than a compose, but same effect
2354        // FIX: Don't just expose the raw event. Need to identify what needs standardisation.
2355        return mkEvent(target, rawEvent.clientX, rawEvent.clientY, stop, prevent, kill, rawEvent);
2356    };
2357    const handle$1 = (filter, handler) => (rawEvent) => {
2358        if (filter(rawEvent)) {
2359            handler(fromRawEvent(rawEvent));
2360        }
2361    };
2362    const binder = (element, event, filter, handler, useCapture) => {
2363        const wrapped = handle$1(filter, handler);
2364        // IE9 minimum
2365        element.dom.addEventListener(event, wrapped, useCapture);
2366        return {
2367            unbind: curry(unbind, element, event, wrapped, useCapture)
2368        };
2369    };
2370    const bind$2 = (element, event, filter, handler) => binder(element, event, filter, handler, false);
2371    const unbind = (element, event, handler, useCapture) => {
2372        // IE9 minimum
2373        element.dom.removeEventListener(event, handler, useCapture);
2374    };
2375
2376    const filter$2 = always; // no filter on plain DomEvents
2377    const bind$1 = (element, event, handler) => bind$2(element, event, filter$2, handler);
2378
2379    const getDocument = () => SugarElement.fromDom(document);
2380
2381    const focus$1 = (element, preventScroll = false) => element.dom.focus({ preventScroll });
2382    const hasFocus$1 = (element) => {
2383        const root = getRootNode(element).dom;
2384        return element.dom === root.activeElement;
2385    };
2386    // Note: assuming that activeElement will always be a HTMLElement (maybe we should add a runtime check?)
2387    const active = (root = getDocument()) => Optional.from(root.dom.activeElement).map(SugarElement.fromDom);
2388    /**
2389     * Return the descendant element that has focus.
2390     * Use instead of SelectorFind.descendant(container, ':focus')
2391     *  because the :focus selector relies on keyboard focus.
2392     */
2393    const search = (element) => active(getRootNode(element))
2394        .filter((e) => element.dom.contains(e.dom));
2395
2396    const before$4 = (marker, element) => {
2397        const parent$1 = parent(marker);
2398        parent$1.each((v) => {
2399            v.dom.insertBefore(element.dom, marker.dom);
2400        });
2401    };
2402    const after$4 = (marker, element) => {
2403        const sibling = nextSibling(marker);
2404        sibling.fold(() => {
2405            const parent$1 = parent(marker);
2406            parent$1.each((v) => {
2407                append$1(v, element);
2408            });
2409        }, (v) => {
2410            before$4(v, element);
2411        });
2412    };
2413    const prepend = (parent, element) => {
2414        const firstChild$1 = firstChild(parent);
2415        firstChild$1.fold(() => {
2416            append$1(parent, element);
2417        }, (v) => {
2418            parent.dom.insertBefore(element.dom, v.dom);
2419        });
2420    };
2421    const append$1 = (parent, element) => {
2422        parent.dom.appendChild(element.dom);
2423    };
2424    const wrap$2 = (element, wrapper) => {
2425        before$4(element, wrapper);
2426        append$1(wrapper, element);
2427    };
2428
2429    const before$3 = (marker, elements) => {
2430        each$e(elements, (x) => {
2431            before$4(marker, x);
2432        });
2433    };
2434    const after$3 = (marker, elements) => {
2435        each$e(elements, (x, i) => {
2436            const e = i === 0 ? marker : elements[i - 1];
2437            after$4(e, x);
2438        });
2439    };
2440    const append = (parent, elements) => {
2441        each$e(elements, (x) => {
2442            append$1(parent, x);
2443        });
2444    };
2445
2446    const rawSet = (dom, key, value) => {
2447        /*
2448         * JQuery coerced everything to a string, and silently did nothing on text node/null/undefined.
2449         *
2450         * We fail on those invalid cases, only allowing numbers and booleans.
2451         */
2452        if (isString(value) || isBoolean(value) || isNumber(value)) {
2453            dom.setAttribute(key, value + '');
2454        }
2455        else {
2456            // eslint-disable-next-line no-console
2457            console.error('Invalid call to Attribute.set. Key ', key, ':: Value ', value, ':: Element ', dom);
2458            throw new Error('Attribute value was not simple');
2459        }
2460    };
2461    const set$4 = (element, key, value) => {
2462        rawSet(element.dom, key, value);
2463    };
2464    const setAll$1 = (element, attrs) => {
2465        const dom = element.dom;
2466        each$d(attrs, (v, k) => {
2467            rawSet(dom, k, v);
2468        });
2469    };
2470    const get$9 = (element, key) => {
2471        const v = element.dom.getAttribute(key);
2472        // undefined is the more appropriate value for JS, and this matches JQuery
2473        return v === null ? undefined : v;
2474    };
2475    const getOpt = (element, key) => Optional.from(get$9(element, key));
2476    const has$1 = (element, key) => {
2477        const dom = element.dom;
2478        // return false for non-element nodes, no point in throwing an error
2479        return dom && dom.hasAttribute ? dom.hasAttribute(key) : false;
2480    };
2481    const remove$9 = (element, key) => {
2482        element.dom.removeAttribute(key);
2483    };
2484    const hasNone = (element) => {
2485        const attrs = element.dom.attributes;
2486        return attrs === undefined || attrs === null || attrs.length === 0;
2487    };
2488    const clone$4 = (element) => foldl(element.dom.attributes, (acc, attr) => {
2489        acc[attr.name] = attr.value;
2490        return acc;
2491    }, {});
2492
2493    const empty = (element) => {
2494        // shortcut "empty node" trick. Requires IE 9.
2495        element.dom.textContent = '';
2496        // If the contents was a single empty text node, the above doesn't remove it. But, it's still faster in general
2497        // than removing every child node manually.
2498        // The following is (probably) safe for performance as 99.9% of the time the trick works and
2499        // Traverse.children will return an empty array.
2500        each$e(children$1(element), (rogue) => {
2501            remove$8(rogue);
2502        });
2503    };
2504    const remove$8 = (element) => {
2505        const dom = element.dom;
2506        if (dom.parentNode !== null) {
2507            dom.parentNode.removeChild(dom);
2508        }
2509    };
2510    const unwrap = (wrapper) => {
2511        const children = children$1(wrapper);
2512        if (children.length > 0) {
2513            after$3(wrapper, children);
2514        }
2515        remove$8(wrapper);
2516    };
2517
2518    const clone$3 = (original, isDeep) => SugarElement.fromDom(original.dom.cloneNode(isDeep));
2519    /** Shallow clone - just the tag, no children */
2520    const shallow = (original) => clone$3(original, false);
2521    /** Deep clone - everything copied including children */
2522    const deep = (original) => clone$3(original, true);
2523    /** Shallow clone, with a new tag */
2524    const shallowAs = (original, tag) => {
2525        const nu = SugarElement.fromTag(tag);
2526        const attributes = clone$4(original);
2527        setAll$1(nu, attributes);
2528        return nu;
2529    };
2530    /** Change the tag name, but keep all children */
2531    const mutate = (original, tag) => {
2532        const nu = shallowAs(original, tag);
2533        after$4(original, nu);
2534        const children = children$1(original);
2535        append(nu, children);
2536        remove$8(original);
2537        return nu;
2538    };
2539
2540    const fromHtml = (html, scope) => {
2541        const doc = scope || document;
2542        const div = doc.createElement('div');
2543        div.innerHTML = html;
2544        return children$1(SugarElement.fromDom(div));
2545    };
2546    const fromDom$1 = (nodes) => map$3(nodes, SugarElement.fromDom);
2547
2548    const get$8 = (element) => element.dom.innerHTML;
2549    const set$3 = (element, content) => {
2550        const owner = owner$1(element);
2551        const docDom = owner.dom;
2552        // FireFox has *terrible* performance when using innerHTML = x
2553        const fragment = SugarElement.fromDom(docDom.createDocumentFragment());
2554        const contentElements = fromHtml(content, docDom);
2555        append(fragment, contentElements);
2556        empty(element);
2557        append$1(element, fragment);
2558    };
2559    const getOuter = (element) => {
2560        const container = SugarElement.fromTag('div');
2561        const clone = SugarElement.fromDom(element.dom.cloneNode(true));
2562        append$1(container, clone);
2563        return get$8(container);
2564    };
2565
2566    // some elements, such as mathml, don't have style attributes
2567    // others, such as angular elements, have style attributes that aren't a CSSStyleDeclaration
2568    const isSupported = (dom) => dom.style !== undefined && isFunction(dom.style.getPropertyValue);
2569
2570    // Node.contains() is very, very, very good performance
2571    // http://jsperf.com/closest-vs-contains/5
2572    const inBody = (element) => {
2573        // Technically this is only required on IE, where contains() returns false for text nodes.
2574        // But it's cheap enough to run everywhere and Sugar doesn't have platform detection (yet).
2575        const dom = isText$c(element) ? element.dom.parentNode : element.dom;
2576        // use ownerDocument.body to ensure this works inside iframes.
2577        // Normally contains is bad because an element "contains" itself, but here we want that.
2578        if (dom === undefined || dom === null || dom.ownerDocument === null) {
2579            return false;
2580        }
2581        const doc = dom.ownerDocument;
2582        return getShadowRoot(SugarElement.fromDom(dom)).fold(() => doc.body.contains(dom), compose1(inBody, getShadowHost));
2583    };
2584
2585    const internalSet = (dom, property, value) => {
2586        // This is going to hurt. Apologies.
2587        // JQuery coerces numbers to pixels for certain property names, and other times lets numbers through.
2588        // we're going to be explicit; strings only.
2589        if (!isString(value)) {
2590            // eslint-disable-next-line no-console
2591            console.error('Invalid call to CSS.set. Property ', property, ':: Value ', value, ':: Element ', dom);
2592            throw new Error('CSS value must be a string: ' + value);
2593        }
2594        // removed: support for dom().style[property] where prop is camel case instead of normal property name
2595        if (isSupported(dom)) {
2596            dom.style.setProperty(property, value);
2597        }
2598    };
2599    const internalRemove = (dom, property) => {
2600        /*
2601         * IE9 and above - MDN doesn't have details, but here's a couple of random internet claims
2602         *
2603         * http://help.dottoro.com/ljopsjck.php
2604         * http://stackoverflow.com/a/7901886/7546
2605         */
2606        if (isSupported(dom)) {
2607            dom.style.removeProperty(property);
2608        }
2609    };
2610    const set$2 = (element, property, value) => {
2611        const dom = element.dom;
2612        internalSet(dom, property, value);
2613    };
2614    const setAll = (element, css) => {
2615        const dom = element.dom;
2616        each$d(css, (v, k) => {
2617            internalSet(dom, k, v);
2618        });
2619    };
2620    /*
2621     * NOTE: For certain properties, this returns the "used value" which is subtly different to the "computed value" (despite calling getComputedStyle).
2622     * Blame CSS 2.0.
2623     *
2624     * https://developer.mozilla.org/en-US/docs/Web/CSS/used_value
2625     */
2626    const get$7 = (element, property) => {
2627        const dom = element.dom;
2628        /*
2629         * IE9 and above per
2630         * https://developer.mozilla.org/en/docs/Web/API/window.getComputedStyle
2631         *
2632         * Not in numerosity, because it doesn't memoize and looking this up dynamically in performance critical code would be horrendous.
2633         *
2634         * JQuery has some magic here for IE popups, but we don't really need that.
2635         * It also uses element.ownerDocument.defaultView to handle iframes but that hasn't been required since FF 3.6.
2636         */
2637        const styles = window.getComputedStyle(dom);
2638        const r = styles.getPropertyValue(property);
2639        // jquery-ism: If r is an empty string, check that the element is not in a document. If it isn't, return the raw value.
2640        // Turns out we do this a lot.
2641        return (r === '' && !inBody(element)) ? getUnsafeProperty(dom, property) : r;
2642    };
2643    // removed: support for dom().style[property] where prop is camel case instead of normal property name
2644    // empty string is what the browsers (IE11 and Chrome) return when the propertyValue doesn't exists.
2645    const getUnsafeProperty = (dom, property) => isSupported(dom) ? dom.style.getPropertyValue(property) : '';
2646    /*
2647     * Gets the raw value from the style attribute. Useful for retrieving "used values" from the DOM:
2648     * https://developer.mozilla.org/en-US/docs/Web/CSS/used_value
2649     *
2650     * Returns NONE if the property isn't set, or the value is an empty string.
2651     */
2652    const getRaw$1 = (element, property) => {
2653        const dom = element.dom;
2654        const raw = getUnsafeProperty(dom, property);
2655        return Optional.from(raw).filter((r) => r.length > 0);
2656    };
2657    const getAllRaw = (element) => {
2658        const css = {};
2659        const dom = element.dom;
2660        if (isSupported(dom)) {
2661            for (let i = 0; i < dom.style.length; i++) {
2662                const ruleName = dom.style.item(i);
2663                css[ruleName] = dom.style[ruleName];
2664            }
2665        }
2666        return css;
2667    };
2668    const remove$7 = (element, property) => {
2669        const dom = element.dom;
2670        internalRemove(dom, property);
2671        if (is$4(getOpt(element, 'style').map(trim$4), '')) {
2672            // No more styles left, remove the style attribute as well
2673            remove$9(element, 'style');
2674        }
2675    };
2676    /* NOTE: This function is here for the side effect it triggers.
2677    The value itself is not used.
2678    Be sure to not use the return value, and that it is not removed by a minifier.
2679     */
2680    const reflow = (e) => e.dom.offsetWidth;
2681
2682    const Dimension = (name, getOffset) => {
2683        const set = (element, h) => {
2684            if (!isNumber(h) && !h.match(/^[0-9]+$/)) {
2685                throw new Error(name + '.set accepts only positive integer values. Value was ' + h);
2686            }
2687            const dom = element.dom;
2688            if (isSupported(dom)) {
2689                dom.style[name] = h + 'px';
2690            }
2691        };
2692        /*
2693         * jQuery supports querying width and height on the document and window objects.
2694         *
2695         * TBIO doesn't do this, so the code is removed to save space, but left here just in case.
2696         */
2697        /*
2698        var getDocumentWidth = (element) => {
2699          var dom = element.dom;
2700          if (Node.isDocument(element)) {
2701            var body = dom.body;
2702            var doc = dom.documentElement;
2703            return Math.max(
2704              body.scrollHeight,
2705              doc.scrollHeight,
2706              body.offsetHeight,
2707              doc.offsetHeight,
2708              doc.clientHeight
2709            );
2710          }
2711        };
2712      
2713        var getWindowWidth = (element) => {
2714          var dom = element.dom;
2715          if (dom.window === dom) {
2716            // There is no offsetHeight on a window, so use the clientHeight of the document
2717            return dom.document.documentElement.clientHeight;
2718          }
2719        };
2720      */
2721        const get = (element) => {
2722            const r = getOffset(element);
2723            // zero or null means non-standard or disconnected, fall back to CSS
2724            if (r <= 0 || r === null) {
2725                const css = get$7(element, name);
2726                // ugh this feels dirty, but it saves cycles
2727                return parseFloat(css) || 0;
2728            }
2729            return r;
2730        };
2731        // in jQuery, getOuter replicates (or uses) box-sizing: border-box calculations
2732        // although these calculations only seem relevant for quirks mode, and edge cases TBIO doesn't rely on
2733        const getOuter = get;
2734        const aggregate = (element, properties) => foldl(properties, (acc, property) => {
2735            const val = get$7(element, property);
2736            const value = val === undefined ? 0 : parseInt(val, 10);
2737            return isNaN(value) ? acc : acc + value;
2738        }, 0);
2739        const max = (element, value, properties) => {
2740            const cumulativeInclusions = aggregate(element, properties);
2741            // if max-height is 100px and your cumulativeInclusions is 150px, there is no way max-height can be 100px, so we return 0.
2742            const absoluteMax = value > cumulativeInclusions ? value - cumulativeInclusions : 0;
2743            return absoluteMax;
2744        };
2745        return {
2746            set,
2747            get,
2748            getOuter,
2749            aggregate,
2750            max
2751        };
2752    };
2753
2754    const api$1 = Dimension('height', (element) => {
2755        // getBoundingClientRect gives better results than offsetHeight for tables with captions on Firefox
2756        const dom = element.dom;
2757        return inBody(element) ? dom.getBoundingClientRect().height : dom.offsetHeight;
2758    });
2759    const get$6 = (element) => api$1.get(element);
2760
2761    const r = (left, top) => {
2762        const translate = (x, y) => r(left + x, top + y);
2763        return {
2764            left,
2765            top,
2766            translate
2767        };
2768    };
2769    // tslint:disable-next-line:variable-name
2770    const SugarPosition = r;
2771
2772    const boxPosition = (dom) => {
2773        const box = dom.getBoundingClientRect();
2774        return SugarPosition(box.left, box.top);
2775    };
2776    // Avoids falsy false fallthrough
2777    const firstDefinedOrZero = (a, b) => {
2778        if (a !== undefined) {
2779            return a;
2780        }
2781        else {
2782            return b !== undefined ? b : 0;
2783        }
2784    };
2785    const absolute = (element) => {
2786        const doc = element.dom.ownerDocument;
2787        const body = doc.body;
2788        const win = doc.defaultView;
2789        const html = doc.documentElement;
2790        if (body === element.dom) {
2791            return SugarPosition(body.offsetLeft, body.offsetTop);
2792        }
2793        const scrollTop = firstDefinedOrZero(win?.pageYOffset, html.scrollTop);
2794        const scrollLeft = firstDefinedOrZero(win?.pageXOffset, html.scrollLeft);
2795        const clientTop = firstDefinedOrZero(html.clientTop, body.clientTop);
2796        const clientLeft = firstDefinedOrZero(html.clientLeft, body.clientLeft);
2797        return viewport(element).translate(scrollLeft - clientLeft, scrollTop - clientTop);
2798    };
2799    const viewport = (element) => {
2800        const dom = element.dom;
2801        const doc = dom.ownerDocument;
2802        const body = doc.body;
2803        if (body === dom) {
2804            return SugarPosition(body.offsetLeft, body.offsetTop);
2805        }
2806        if (!inBody(element)) {
2807            return SugarPosition(0, 0);
2808        }
2809        return boxPosition(dom);
2810    };
2811
2812    // get scroll position (x,y) relative to document _doc (or global if not supplied)
2813    const get$5 = (_DOC) => {
2814        const doc = _DOC !== undefined ? _DOC.dom : document;
2815        // ASSUMPTION: This is for cross-browser support, body works for Safari & EDGE, and when we have an iframe body scroller
2816        const x = doc.body.scrollLeft || doc.documentElement.scrollLeft;
2817        const y = doc.body.scrollTop || doc.documentElement.scrollTop;
2818        return SugarPosition(x, y);
2819    };
2820    // Scroll content to (x,y) relative to document _doc (or global if not supplied)
2821    const to = (x, y, _DOC) => {
2822        const doc = _DOC !== undefined ? _DOC.dom : document;
2823        const win = doc.defaultView;
2824        if (win) {
2825            win.scrollTo(x, y);
2826        }
2827    };
2828    // TBIO-4472 Safari 10 - Scrolling typeahead with keyboard scrolls page
2829    const intoView = (element, alignToTop) => {
2830        const isSafari = detect$1().browser.isSafari();
2831        // this method isn't in TypeScript
2832        if (isSafari && isFunction(element.dom.scrollIntoViewIfNeeded)) {
2833            element.dom.scrollIntoViewIfNeeded(false); // false=align to nearest edge
2834        }
2835        else {
2836            element.dom.scrollIntoView(alignToTop); // true=to top, false=to bottom
2837        }
2838    };
2839
2840    const NodeValue = (is, name) => {
2841        const get = (element) => {
2842            if (!is(element)) {
2843                throw new Error('Can only get ' + name + ' value of a ' + name + ' node');
2844            }
2845            return getOption(element).getOr('');
2846        };
2847        const getOption = (element) => is(element) ? Optional.from(element.dom.nodeValue) : Optional.none();
2848        const set = (element, value) => {
2849            if (!is(element)) {
2850                throw new Error('Can only set raw ' + name + ' value of a ' + name + ' node');
2851            }
2852            element.dom.nodeValue = value;
2853        };
2854        return {
2855            get,
2856            getOption,
2857            set
2858        };
2859    };
2860
2861    const fromElements = (elements, scope) => {
2862        const doc = scope || document;
2863        const fragment = doc.createDocumentFragment();
2864        each$e(elements, (element) => {
2865            fragment.appendChild(element.dom);
2866        });
2867        return SugarElement.fromDom(fragment);
2868    };
2869
2870    const api = NodeValue(isText$c, 'text');
2871    const get$4 = (element) => api.get(element);
2872    const getOption = (element) => api.getOption(element);
2873    const set$1 = (element, value) => api.set(element, value);
2874
2875    // Methods for handling attributes that contain a list of values <div foo="alpha beta theta">
2876    const read$4 = (element, attr) => {
2877        const value = get$9(element, attr);
2878        return value === undefined || value === '' ? [] : value.split(' ');
2879    };
2880    const add$4 = (element, attr, id) => {
2881        const old = read$4(element, attr);
2882        const nu = old.concat([id]);
2883        set$4(element, attr, nu.join(' '));
2884        return true;
2885    };
2886    const remove$6 = (element, attr, id) => {
2887        const nu = filter$5(read$4(element, attr), (v) => v !== id);
2888        if (nu.length > 0) {
2889            set$4(element, attr, nu.join(' '));
2890        }
2891        else {
2892            remove$9(element, attr);
2893        }
2894        return false;
2895    };
2896
2897    var ClosestOrAncestor = (is, ancestor, scope, a, isRoot) => {
2898        if (is(scope, a)) {
2899            return Optional.some(scope);
2900        }
2901        else if (isFunction(isRoot) && isRoot(scope)) {
2902            return Optional.none();
2903        }
2904        else {
2905            return ancestor(scope, a, isRoot);
2906        }
2907    };
2908
2909    const ancestor$5 = (scope, predicate, isRoot) => {
2910        let element = scope.dom;
2911        const stop = isFunction(isRoot) ? isRoot : never;
2912        while (element.parentNode) {
2913            element = element.parentNode;
2914            const el = SugarElement.fromDom(element);
2915            if (predicate(el)) {
2916                return Optional.some(el);
2917            }
2918            else if (stop(el)) {
2919                break;
2920            }
2921        }
2922        return Optional.none();
2923    };
2924    const closest$5 = (scope, predicate, isRoot) => {
2925        // This is required to avoid ClosestOrAncestor passing the predicate to itself
2926        const is = (s, test) => test(s);
2927        return ClosestOrAncestor(is, ancestor$5, scope, predicate, isRoot);
2928    };
2929    const sibling$1 = (scope, predicate) => {
2930        const element = scope.dom;
2931        if (!element.parentNode) {
2932            return Optional.none();
2933        }
2934        return child(SugarElement.fromDom(element.parentNode), (x) => !eq(scope, x) && predicate(x));
2935    };
2936    const child = (scope, predicate) => {
2937        const pred = (node) => predicate(SugarElement.fromDom(node));
2938        const result = find$2(scope.dom.childNodes, pred);
2939        return result.map(SugarElement.fromDom);
2940    };
2941    const descendant$2 = (scope, predicate) => {
2942        const descend = (node) => {
2943            // tslint:disable-next-line:prefer-for-of
2944            for (let i = 0; i < node.childNodes.length; i++) {
2945                const child = SugarElement.fromDom(node.childNodes[i]);
2946                if (predicate(child)) {
2947                    return Optional.some(child);
2948                }
2949                const res = descend(node.childNodes[i]);
2950                if (res.isSome()) {
2951                    return res;
2952                }
2953            }
2954            return Optional.none();
2955        };
2956        return descend(scope.dom);
2957    };
2958
2959    const ancestor$4 = (scope, selector, isRoot) => ancestor$5(scope, (e) => is$2(e, selector), isRoot);
2960    const descendant$1 = (scope, selector) => one(selector, scope);
2961    // Returns Some(closest ancestor element (sugared)) matching 'selector' up to isRoot, or None() otherwise
2962    const closest$4 = (scope, selector, isRoot) => {
2963        const is = (element, selector) => is$2(element, selector);
2964        return ClosestOrAncestor(is, ancestor$4, scope, selector, isRoot);
2965    };
2966
2967    // IE11 Can return undefined for a classList on elements such as math, so we make sure it's not undefined before attempting to use it.
2968    const supports = (element) => element.dom.classList !== undefined;
2969    const get$3 = (element) => read$4(element, 'class');
2970    const add$3 = (element, clazz) => add$4(element, 'class', clazz);
2971    const remove$5 = (element, clazz) => remove$6(element, 'class', clazz);
2972    const toggle$2 = (element, clazz) => {
2973        if (contains$2(get$3(element), clazz)) {
2974            return remove$5(element, clazz);
2975        }
2976        else {
2977            return add$3(element, clazz);
2978        }
2979    };
2980
2981    /*
2982     * ClassList is IE10 minimum:
2983     * https://developer.mozilla.org/en-US/docs/Web/API/Element.classList
2984     *
2985     * Note that IE doesn't support the second argument to toggle (at all).
2986     * If it did, the toggler could be better.
2987     */
2988    const add$2 = (element, clazz) => {
2989        if (supports(element)) {
2990            element.dom.classList.add(clazz);
2991        }
2992        else {
2993            add$3(element, clazz);
2994        }
2995    };
2996    const cleanClass = (element) => {
2997        const classList = supports(element) ? element.dom.classList : get$3(element);
2998        // classList is a "live list", so this is up to date already
2999        if (classList.length === 0) {
3000            // No more classes left, remove the class attribute as well
3001            remove$9(element, 'class');
3002        }
3003    };
3004    const remove$4 = (element, clazz) => {
3005        if (supports(element)) {
3006            const classList = element.dom.classList;
3007            classList.remove(clazz);
3008        }
3009        else {
3010            remove$5(element, clazz);
3011        }
3012        cleanClass(element);
3013    };
3014    const toggle$1 = (element, clazz) => {
3015        const result = supports(element) ? element.dom.classList.toggle(clazz) : toggle$2(element, clazz);
3016        cleanClass(element);
3017        return result;
3018    };
3019    const has = (element, clazz) => supports(element) && element.dom.classList.contains(clazz);
3020
3021    /*
3022     * ClassList is IE10 minimum:
3023     * https://developer.mozilla.org/en-US/docs/Web/API/Element.classList
3024     */
3025    const add$1 = (element, classes) => {
3026        each$e(classes, (x) => {
3027            add$2(element, x);
3028        });
3029    };
3030    const remove$3 = (element, classes) => {
3031        each$e(classes, (x) => {
3032            remove$4(element, x);
3033        });
3034    };
3035
3036    const closest$3 = (target) => closest$4(target, '[contenteditable]');
3037    const isEditable$2 = (element, assumeEditable = false) => {
3038        if (inBody(element)) {
3039            return element.dom.isContentEditable;
3040        }
3041        else {
3042            // Find the closest contenteditable element and check if it's editable
3043            return closest$3(element).fold(constant(assumeEditable), (editable) => getRaw(editable) === 'true');
3044        }
3045    };
3046    const getRaw = (element) => element.dom.contentEditable;
3047    const set = (element, editable) => {
3048        element.dom.contentEditable = editable ? 'true' : 'false';
3049    };
3050
3051    const ancestors$1 = (scope, predicate, isRoot) => filter$5(parents$1(scope, isRoot), predicate);
3052    const children = (scope, predicate) => filter$5(children$1(scope), predicate);
3053    const descendants$1 = (scope, predicate) => {
3054        let result = [];
3055        // Recurse.toArray() might help here
3056        each$e(children$1(scope), (x) => {
3057            if (predicate(x)) {
3058                result = result.concat([x]);
3059            }
3060            result = result.concat(descendants$1(x, predicate));
3061        });
3062        return result;
3063    };
3064
3065    // For all of the following:
3066    //
3067    // jQuery does siblings of firstChild. IE9+ supports scope.dom.children (similar to Traverse.children but elements only).
3068    // Traverse should also do this (but probably not by default).
3069    //
3070    const ancestors = (scope, selector, isRoot) => 
3071    // It may surprise you to learn this is exactly what JQuery does
3072    // TODO: Avoid all this wrapping and unwrapping
3073    ancestors$1(scope, (e) => is$2(e, selector), isRoot);
3074    const descendants = (scope, selector) => all(selector, scope);
3075
3076    const ancestor$3 = (scope, predicate, isRoot) => ancestor$5(scope, predicate, isRoot).isSome();
3077    const sibling = (scope, predicate) => sibling$1(scope, predicate).isSome();
3078    const descendant = (scope, predicate) => descendant$2(scope, predicate).isSome();
3079
3080    const ancestor$2 = (element, target) => ancestor$3(element, curry(eq, target));
3081
3082    const ancestor$1 = (scope, selector, isRoot) => ancestor$4(scope, selector, isRoot).isSome();
3083    const closest$2 = (scope, selector, isRoot) => closest$4(scope, selector, isRoot).isSome();
3084
3085    const ensureIsRoot = (isRoot) => isFunction(isRoot) ? isRoot : never;
3086    const ancestor = (scope, transform, isRoot) => {
3087        let element = scope.dom;
3088        const stop = ensureIsRoot(isRoot);
3089        while (element.parentNode) {
3090            element = element.parentNode;
3091            const el = SugarElement.fromDom(element);
3092            const transformed = transform(el);
3093            if (transformed.isSome()) {
3094                return transformed;
3095            }
3096            else if (stop(el)) {
3097                break;
3098            }
3099        }
3100        return Optional.none();
3101    };
3102    const closest$1 = (scope, transform, isRoot) => {
3103        const current = transform(scope);
3104        const stop = ensureIsRoot(isRoot);
3105        return current.orThunk(() => stop(scope) ? Optional.none() : ancestor(scope, transform, stop));
3106    };
3107
3108    const isTextNodeWithCursorPosition = (el) => getOption(el).filter((text) => 
3109    // For the purposes of finding cursor positions only allow text nodes with content,
3110    // but trim removes &nbsp; and that's allowed
3111    text.trim().length !== 0 || text.indexOf(nbsp) > -1).isSome();
3112    const isContentEditableFalse$b = (elem) => isHTMLElement$1(elem) && (get$9(elem, 'contenteditable') === 'false');
3113    const elementsWithCursorPosition = ['img', 'br'];
3114    const isCursorPosition = (elem) => {
3115        const hasCursorPosition = isTextNodeWithCursorPosition(elem);
3116        return hasCursorPosition || contains$2(elementsWithCursorPosition, name(elem)) || isContentEditableFalse$b(elem);
3117    };
3118
3119    const first = (element) => descendant$2(element, isCursorPosition);
3120
3121    const create$c = (start, soffset, finish, foffset) => ({
3122        start,
3123        soffset,
3124        finish,
3125        foffset
3126    });
3127    // tslint:disable-next-line:variable-name
3128    const SimRange = {
3129        create: create$c
3130    };
3131
3132    const adt$2 = Adt.generate([
3133        { before: ['element'] },
3134        { on: ['element', 'offset'] },
3135        { after: ['element'] }
3136    ]);
3137    // Probably don't need this given that we now have "match"
3138    const cata = (subject, onBefore, onOn, onAfter) => subject.fold(onBefore, onOn, onAfter);
3139    const getStart$2 = (situ) => situ.fold(identity, identity, identity);
3140    const before$2 = adt$2.before;
3141    const on = adt$2.on;
3142    const after$2 = adt$2.after;
3143    // tslint:disable-next-line:variable-name
3144    const Situ = {
3145        before: before$2,
3146        on,
3147        after: after$2,
3148        cata,
3149        getStart: getStart$2
3150    };
3151
3152    // Consider adding a type for "element"
3153    const adt$1 = Adt.generate([
3154        { domRange: ['rng'] },
3155        { relative: ['startSitu', 'finishSitu'] },
3156        { exact: ['start', 'soffset', 'finish', 'foffset'] }
3157    ]);
3158    const exactFromRange = (simRange) => adt$1.exact(simRange.start, simRange.soffset, simRange.finish, simRange.foffset);
3159    const getStart$1 = (selection) => selection.match({
3160        domRange: (rng) => SugarElement.fromDom(rng.startContainer),
3161        relative: (startSitu, _finishSitu) => Situ.getStart(startSitu),
3162        exact: (start, _soffset, _finish, _foffset) => start
3163    });
3164    const domRange = adt$1.domRange;
3165    const relative = adt$1.relative;
3166    const exact = adt$1.exact;
3167    const getWin = (selection) => {
3168        const start = getStart$1(selection);
3169        return defaultView(start);
3170    };
3171    // This is out of place but it's API so I can't remove it
3172    const range = SimRange.create;
3173    // tslint:disable-next-line:variable-name
3174    const SimSelection = {
3175        domRange,
3176        relative,
3177        exact,
3178        exactFromRange,
3179        getWin,
3180        range
3181    };
3182
3183    const caretPositionFromPoint = (doc, x, y) => Optional.from(doc.caretPositionFromPoint?.(x, y))
3184        .bind((pos) => {
3185        // It turns out that Firefox can return null for pos.offsetNode
3186        if (pos.offsetNode === null) {
3187            return Optional.none();
3188        }
3189        const r = doc.createRange();
3190        r.setStart(pos.offsetNode, pos.offset);
3191        r.collapse();
3192        return Optional.some(r);
3193    });
3194    const caretRangeFromPoint = (doc, x, y) => Optional.from(doc.caretRangeFromPoint?.(x, y));
3195    const availableSearch = (doc, x, y) => {
3196        if (doc.caretPositionFromPoint) {
3197            return caretPositionFromPoint(doc, x, y); // defined standard, firefox only
3198        }
3199        else if (doc.caretRangeFromPoint) {
3200            return caretRangeFromPoint(doc, x, y); // webkit/blink implementation
3201        }
3202        else {
3203            return Optional.none(); // unsupported browser
3204        }
3205    };
3206    const fromPoint$1 = (win, x, y) => {
3207        const doc = win.document;
3208        return availableSearch(doc, x, y).map((rng) => SimRange.create(SugarElement.fromDom(rng.startContainer), rng.startOffset, SugarElement.fromDom(rng.endContainer), rng.endOffset));
3209    };
3210
3211    const beforeSpecial = (element, offset) => {
3212        // From memory, we don't want to use <br> directly on Firefox because it locks the keyboard input.
3213        // It turns out that <img> directly on IE locks the keyboard as well.
3214        // If the offset is 0, use before. If the offset is 1, use after.
3215        // TBIO-3889: Firefox Situ.on <input> results in a child of the <input>; Situ.before <input> results in platform inconsistencies
3216        const name$1 = name(element);
3217        if ('input' === name$1) {
3218            return Situ.after(element);
3219        }
3220        else if (!contains$2(['br', 'img'], name$1)) {
3221            return Situ.on(element, offset);
3222        }
3223        else {
3224            return offset === 0 ? Situ.before(element) : Situ.after(element);
3225        }
3226    };
3227    const preprocessRelative = (startSitu, finishSitu) => {
3228        const start = startSitu.fold(Situ.before, beforeSpecial, Situ.after);
3229        const finish = finishSitu.fold(Situ.before, beforeSpecial, Situ.after);
3230        return SimSelection.relative(start, finish);
3231    };
3232    const preprocessExact = (start, soffset, finish, foffset) => {
3233        const startSitu = beforeSpecial(start, soffset);
3234        const finishSitu = beforeSpecial(finish, foffset);
3235        return SimSelection.relative(startSitu, finishSitu);
3236    };
3237    const preprocess = (selection) => selection.match({
3238        domRange: (rng) => {
3239            const start = SugarElement.fromDom(rng.startContainer);
3240            const finish = SugarElement.fromDom(rng.endContainer);
3241            return preprocessExact(start, rng.startOffset, finish, rng.endOffset);
3242        },
3243        relative: preprocessRelative,
3244        exact: preprocessExact
3245    });
3246
3247    const toNative = (selection) => {
3248        const win = SimSelection.getWin(selection).dom;
3249        const getDomRange = (start, soffset, finish, foffset) => exactToNative(win, start, soffset, finish, foffset);
3250        const filtered = preprocess(selection);
3251        return diagnose(win, filtered).match({
3252            ltr: getDomRange,
3253            rtl: getDomRange
3254        });
3255    };
3256    const getAtPoint = (win, x, y) => fromPoint$1(win, x, y);
3257
3258    const get$2 = (_win) => {
3259        const win = _win === undefined ? window : _win;
3260        if (detect$1().browser.isFirefox()) {
3261            // TINY-7984: Firefox 91 is returning incorrect values for visualVie
3261wport.pageTop, so disable it for now
3262            return Optional.none();
3263        }
3264        else {
3265            return Optional.from(win.visualViewport);
3266        }
3267    };
3268    const bounds = (x, y, width, height) => ({
3269        x,
3270        y,
3271        width,
3272        height,
3273        right: x + width,
3274        bottom: y + height
3275    });
3276    const getBounds = (_win) => {
3277        const win = _win === undefined ? window : _win;
3278        const doc = win.document;
3279        const scroll = get$5
vendor: 19,717 bytes, lines 3279-3713
3279(SugarElement.fromDom(doc));
3280        return get$2(win).fold(() => {
3281            const html = win.document.documentElement;
3282            // Don't use window.innerWidth/innerHeight here, as we don't want to include scrollbars
3283            // since the right/bottom position is based on the edge of the scrollbar not the window
3284            const width = html.clientWidth;
3285            const height = html.clientHeight;
3286            return bounds(scroll.left, scroll.top, width, height);
3287        }, (visualViewport) => 
3288        // iOS doesn't update the pageTop/pageLeft when element.scrollIntoView() is called, so we need to fallback to the
3289        // scroll position which will always be less than the page top/left values when page top/left are accurate/correct.
3290        bounds(Math.max(visualViewport.pageLeft, scroll.left), Math.max(visualViewport.pageTop, scroll.top), visualViewport.width, visualViewport.height));
3291    };
3292
3293    /**
3294     * TreeWalker class enables you to walk the DOM in a linear manner.
3295     *
3296     * @class tinymce.dom.TreeWalker
3297     * @example
3298     * const walker = new tinymce.dom.TreeWalker(startNode);
3299     *
3300     * do {
3301     *   console.log(walker.current());
3302     * } while (walker.next());
3303     */
3304    class DomTreeWalker {
3305        rootNode;
3306        node;
3307        constructor(startNode, rootNode) {
3308            this.node = startNode;
3309            this.rootNode = rootNode;
3310            // This is a bit hacky but needed to ensure the 'this' variable
3311            // always references the instance and not the caller scope
3312            this.current = this.current.bind(this);
3313            this.next = this.next.bind(this);
3314            this.prev = this.prev.bind(this);
3315            this.prev2 = this.prev2.bind(this);
3316        }
3317        /**
3318         * Returns the current node.
3319         *
3320         * @method current
3321         * @return {Node/undefined} Current node where the walker is, or undefined if the walker has reached the end.
3322         */
3323        current() {
3324            return this.node;
3325        }
3326        /**
3327         * Walks to the next node in tree.
3328         *
3329         * @method next
3330         * @return {Node/undefined} Current node where the walker is after moving to the next node, or undefined if the walker has reached the end.
3331         */
3332        next(shallow) {
3333            this.node = this.findSibling(this.node, 'firstChild', 'nextSibling', shallow);
3334            return this.node;
3335        }
3336        /**
3337         * Walks to the previous node in tree.
3338         *
3339         * @method prev
3340         * @return {Node/undefined} Current node where the walker is after moving to the previous node, or undefined if the walker has reached the end.
3341         */
3342        prev(shallow) {
3343            this.node = this.findSibling(this.node, 'lastChild', 'previousSibling', shallow);
3344            return this.node;
3345        }
3346        prev2(shallow) {
3347            this.node = this.findPreviousNode(this.node, shallow);
3348            return this.node;
3349        }
3350        findSibling(node, startName, siblingName, shallow) {
3351            if (node) {
3352                // Walk into nodes if it has a start
3353                if (!shallow && node[startName]) {
3354                    return node[startName];
3355                }
3356                // Return the sibling if it has one
3357                if (node !== this.rootNode) {
3358                    let sibling = node[siblingName];
3359                    if (sibling) {
3360                        return sibling;
3361                    }
3362                    // Walk up the parents to look for siblings
3363                    for (let parent = node.parentNode; parent && parent !== this.rootNode; parent = parent.parentNode) {
3364                        sibling = parent[siblingName];
3365                        if (sibling) {
3366                            return sibling;
3367                        }
3368                    }
3369                }
3370            }
3371            return undefined;
3372        }
3373        findPreviousNode(node, shallow) {
3374            if (node) {
3375                const sibling = node.previousSibling;
3376                if (this.rootNode && sibling === this.rootNode) {
3377                    return;
3378                }
3379                if (sibling) {
3380                    if (!shallow) {
3381                        // Walk down to the most distant child
3382                        for (let child = sibling.lastChild; child; child = child.lastChild) {
3383                            if (!child.lastChild) {
3384                                return child;
3385                            }
3386                        }
3387                    }
3388                    return sibling;
3389                }
3390                const parent = node.parentNode;
3391                if (parent && parent !== this.rootNode) {
3392                    return parent;
3393                }
3394            }
3395            return undefined;
3396        }
3397    }
3398
3399    const whiteSpaceRegExp = /^[ \t\r\n]*$/;
3400    const isWhitespaceText = (text) => whiteSpaceRegExp.test(text);
3401    const isZwsp$1 = (text) => {
3402        for (const c of text) {
3403            if (!isZwsp$2(c)) {
3404                return false;
3405            }
3406        }
3407        return true;
3408    };
3409    // Don't compare other unicode spaces here, as we're only concerned about whitespace the browser would collapse
3410    const isCollapsibleWhitespace$1 = (c) => ' \f\t\v'.indexOf(c) !== -1;
3411    const isNewLineChar = (c) => c === '\n' || c === '\r';
3412    const isNewline = (text, idx) => (idx < text.length && idx >= 0) ? isNewLineChar(text[idx]) : false;
3413    // Converts duplicate whitespace to alternating space/nbsps and tabs to spaces
3414    const normalize$4 = (text, tabSpaces = 4, isStartOfContent = true, isEndOfContent = true) => {
3415        // Replace tabs with a variable amount of spaces
3416        // Note: We don't use an actual tab character here, as it only works when in a "whitespace: pre" element,
3417        // which will cause other issues, such as trying to type the content will also be treated as being in a pre.
3418        const tabSpace = repeat(' ', tabSpaces);
3419        const normalizedText = text.replace(/\t/g, tabSpace);
3420        const result = foldl(normalizedText, (acc, c) => {
3421            // Are we dealing with a char other than some collapsible whitespace or nbsp? if so then just use it as is
3422            if (isCollapsibleWhitespace$1(c) || c === nbsp) {
3423                // If the previous char is a space, we are at the start or end, or if the next char is a new line char, then we need
3424                // to convert the space to a nbsp
3425                if (acc.pcIsSpace || (acc.str === '' && isStartOfContent) || (acc.str.length === normalizedText.length - 1 && isEndOfContent) || isNewline(normalizedText, acc.str.length + 1)) {
3426                    return { pcIsSpace: false, str: acc.str + nbsp };
3427                }
3428                else {
3429                    return { pcIsSpace: true, str: acc.str + ' ' };
3430                }
3431            }
3432            else {
3433                // Treat newlines as being a space, since we'll need to convert any leading spaces to nsbps
3434                return { pcIsSpace: isNewLineChar(c), str: acc.str + c };
3435            }
3436        }, { pcIsSpace: false, str: '' });
3437        return result.str;
3438    };
3439
3440    const isNodeType = (type) => {
3441        return (node) => {
3442            return !!node && node.nodeType === type;
3443        };
3444    };
3445    // Firefox can allow you to get a selection on a restricted node, such as file/number inputs. These nodes
3446    // won't implement the Object prototype, so Object.getPrototypeOf() will return null or something similar.
3447    const isRestrictedNode = (node) => !!node && !Object.getPrototypeOf(node);
3448    const isElement$7 = isNodeType(1);
3449    const isHTMLElement = (node) => isElement$7(node) && isHTMLElement$1(SugarElement.fromDom(node));
3450    const isSVGElement = (node) => isElement$7(node) && node.namespaceURI === 'http://www.w3.org/2000/svg';
3451    const matchNodeName$1 = (name) => {
3452        const lowerCasedName = name.toLowerCase();
3453        return (node) => isNonNullable(node) && node.nodeName.toLowerCase() === lowerCasedName;
3454    };
3455    const matchNodeNames$1 = (names) => {
3456        const lowerCasedNames = names.map((s) => s.toLowerCase());
3457        return (node) => {
3458            if (node && node.nodeName) {
3459                const nodeName = node.nodeName.toLowerCase();
3460                return contains$2(lowerCasedNames, nodeName);
3461            }
3462            return false;
3463        };
3464    };
3465    const matchStyleValues = (name, values) => {
3466        const items = values.toLowerCase().split(' ');
3467        return (node) => {
3468            if (isElement$7(node)) {
3469                const win = node.ownerDocument.defaultView;
3470                if (win) {
3471                    for (let i = 0; i < items.length; i++) {
3472                        const computed = win.getComputedStyle(node, null);
3473                        const cssValue = computed ? computed.getPropertyValue(name) : null;
3474                        if (cssValue === items[i]) {
3475                            return true;
3476                        }
3477                    }
3478                }
3479            }
3480            return false;
3481        };
3482    };
3483    const hasAttribute = (attrName) => {
3484        return (node) => {
3485            return isElement$7(node) && node.hasAttribute(attrName);
3486        };
3487    };
3488    const isBogus$1 = (node) => isElement$7(node) && node.hasAttribute('data-mce-bogus');
3489    const isBogusAll = (node) => isElement$7(node) && node.getAttribute('data-mce-bogus') === 'all';
3490    const isTable$2 = (node) => isElement$7(node) && node.tagName === 'TABLE';
3491    const hasContentEditableState = (value) => {
3492        return (node) => {
3493            if (isHTMLElement(node)) {
3494                if (node.contentEditable === value) {
3495                    return true;
3496                }
3497                if (node.getAttribute('data-mce-contenteditable') === value) {
3498                    return true;
3499                }
3500            }
3501            return false;
3502        };
3503    };
3504    const isTextareaOrInput = matchNodeNames$1(['textarea', 'input']);
3505    const isText$b = isNodeType(3);
3506    const isCData = isNodeType(4);
3507    const isPi = isNodeType(7);
3508    const isComment = isNodeType(8);
3509    const isDocument$1 = isNodeType(9);
3510    const isDocumentFragment = isNodeType(11);
3511    const isBr$7 = matchNodeName$1('br');
3512    const isImg = matchNodeName$1('img');
3513    const isAnchor = matchNodeName$1('a');
3514    const isContentEditableTrue$3 = hasContentEditableState('true');
3515    const isContentEditableFalse$a = hasContentEditableState('false');
3516    const isEditingHost = (node) => isHTMLElement(node) && node.isContentEditable && isNonNullable(node.parentElement) && !node.parentElement.isContentEditable;
3517    const isTableCell$3 = matchNodeNames$1(['td', 'th']);
3518    const isTableCellOrCaption = matchNodeNames$1(['td', 'th', 'caption']);
3519    const isTemplate = matchNodeName$1('template');
3520    const isMedia$2 = matchNodeNames$1(['video', 'audio', 'object', 'embed']);
3521    const isListItem$3 = matchNodeName$1('li');
3522    const isDetails = matchNodeName$1('details');
3523    const isSummary$1 = matchNodeName$1('summary');
3524    const ucVideoNodeName = 'uc-video';
3525    const isUcVideo = matchNodeName$1(ucVideoNodeName);
3526
3527    const defaultOptionValues = {
3528        skipBogus: true,
3529        includeZwsp: false,
3530        checkRootAsContent: false,
3531    };
3532    const hasWhitespacePreserveParent = (node, rootNode, schema) => {
3533        const rootElement = SugarElement.fromDom(rootNode);
3534        const startNode = SugarElement.fromDom(node);
3535        const whitespaceElements = schema.getWhitespaceElements();
3536        const predicate = (node) => has$2(whitespaceElements, name(node));
3537        return ancestor$3(startNode, predicate, curry(eq, rootElement));
3538    };
3539    const isNamedAnchor = (node) => {
3540        return isElement$7(node) && node.nodeName === 'A' && !node.hasAttribute('href') && (node.hasAttribute('name') || node.hasAttribute('id'));
3541    };
3542    const isNonEmptyElement$1 = (node, schema) => {
3543        return isElement$7(node) && has$2(schema.getNonEmptyElements(), node.nodeName);
3544    };
3545    const isBookmark = hasAttribute('data-mce-bookmark');
3546    const hasNonEditableParent = (node) => parentElement(SugarElement.fromDom(node)).exists((parent) => !isEditable$2(parent));
3547    const isWhitespace$1 = (node, rootNode, schema) => isWhitespaceText(node.data)
3548        && !hasWhitespacePreserveParent(node, rootNode, schema);
3549    const isText$a = (node, rootNode, schema, options) => isText$b(node)
3550        && !isWhitespace$1(node, rootNode, schema)
3551        && (!options.includeZwsp || !isZwsp$1(node.data));
3552    const isContentNode = (schema, node, rootNode, options) => {
3553        return isFunction(options.isContent) && options.isContent(node)
3554            || isNonEmptyElement$1(node, schema)
3555            || isBookmark(node)
3556            || isNamedAnchor(node)
3557            || isText$a(node, rootNode, schema, options)
3558            || isContentEditableFalse$a(node)
3559            || isContentEditableTrue$3(node) && hasNonEditableParent(node);
3560    };
3561    const isEmptyNode = (schema, targetNode, opts) => {
3562        const options = { ...defaultOptionValues, ...opts };
3563        if (options.checkRootAsContent) {
3564            if (isContentNode(schema, targetNode, targetNode, options)) {
3565                return false;
3566            }
3567        }
3568        let node = targetNode.firstChild;
3569        let brCount = 0;
3570        if (!node) {
3571            return true;
3572        }
3573        const walker = new DomTreeWalker(node, targetNode);
3574        do {
3575            if (options.skipBogus && isElement$7(node)) {
3576                const bogusValue = node.getAttribute('data-mce-bogus');
3577                if (bogusValue) {
3578                    node = walker.next(bogusValue === 'all');
3579                    continue;
3580                }
3581            }
3582            if (isComment(node)) {
3583                node = walker.next(true);
3584                continue;
3585            }
3586            if (isBr$7(node)) {
3587                brCount++;
3588                node = walker.next();
3589                continue;
3590            }
3591            if (isContentNode(schema, node, targetNode, options)) {
3592                return false;
3593            }
3594            node = walker.next();
3595        } while (node);
3596        return brCount <= 1;
3597    };
3598    const isEmpty$4 = (schema, elm, options) => {
3599        return isEmptyNode(schema, elm.dom, { checkRootAsContent: true, ...options });
3600    };
3601    const isContent$1 = (schema, node, options) => {
3602        return isContentNode(schema, node, node, { includeZwsp: defaultOptionValues.includeZwsp, ...options });
3603    };
3604
3605    const nodeNameToNamespaceType = (name) => {
3606        const lowerCaseName = name.toLowerCase();
3607        if (lowerCaseName === 'svg') {
3608            return 'svg';
3609        }
3610        else if (lowerCaseName === 'math') {
3611            return 'math';
3612        }
3613        else {
3614            return 'html';
3615        }
3616    };
3617    const isNonHtmlElementRootName = (name) => nodeNameToNamespaceType(name) !== 'html';
3618    const isNonHtmlElementRoot = (node) => isNonHtmlElementRootName(node.nodeName);
3619    const toScopeType = (node) => nodeNameToNamespaceType(node.nodeName);
3620    const namespaceElements = ['svg', 'math'];
3621    const createNamespaceTracker = () => {
3622        const currentScope = value$1();
3623        const current = () => currentScope.get().map(toScopeType).getOr('html');
3624        const track = (node) => {
3625            if (isNonHtmlElementRoot(node)) {
3626                currentScope.set(node);
3627            }
3628            else if (currentScope.get().exists((scopeNode) => !scopeNode.contains(node))) {
3629                currentScope.clear();
3630            }
3631            return current();
3632        };
3633        const reset = () => {
3634            currentScope.clear();
3635        };
3636        return {
3637            track,
3638            current,
3639            reset
3640        };
3641    };
3642
3643    const transparentBlockAttr = 'data-mce-block';
3644    // Returns the lowercase element names form a SchemaMap by excluding anyone that has uppercase letters.
3645    // This method is to avoid having to specify all possible valid characters other than lowercase a-z such as '-' or ':' etc.
3646    const elementNames = (map) => filter$5(keys(map), (key) => !/[A-Z]/.test(key));
3647    const makeSelectorFromSchemaMap = (map) => map$3(elementNames(map), (name) => {
3648        // Exclude namespace elements from processing
3649        const escapedName = CSS.escape(name);
3650        return `${escapedName}:` + map$3(namespaceElements, (ns) => `not(${ns} ${escapedName})`).join(':');
3651    }).join(',');
3652    const updateTransparent = (blocksSelector, transparent) => {
3653        if (isNonNullable(transparent.querySelector(blocksSelector))) {
3654            transparent.setAttribute(transparentBlockAttr, 'true');
3655            if (transparent.getAttribute('data-mce-selected') === 'inline-boundary') {
3656                transparent.removeAttribute('data-mce-selected');
3657            }
3658            return true;
3659        }
3660        else {
3661            transparent.removeAttribute(transparentBlockAttr);
3662            return false;
3663        }
3664    };
3665    const updateBlockStateOnChildren = (schema, scope) => {
3666        const transparentSelector = makeSelectorFromSchemaMap(schema.getTransparentElements());
3667        const blocksSelector = makeSelectorFromSchemaMap(schema.getBlockElements());
3668        return filter$5(scope.querySelectorAll(transparentSelector), (transparent) => updateTransparent(blocksSelector, transparent));
3669    };
3670    const trimEdge = (schema, el, leftSide) => {
3671        const childPropertyName = leftSide ? 'lastChild' : 'firstChild';
3672        for (let child = el[childPropertyName]; child; child = child[childPropertyName]) {
3673            if (isEmptyNode(schema, child, { checkRootAsContent: true })) {
3674                child.parentNode?.removeChild(child);
3675                return;
3676            }
3677        }
3678    };
3679    const split$2 = (schema, parentElm, splitElm) => {
3680        const range = document.createRange();
3681        const parentNode = parentElm.parentNode;
3682        if (parentNode) {
3683            range.setStartBefore(parentElm);
3684            range.setEndBefore(splitElm);
3685            const beforeFragment = range.extractContents();
3686            trimEdge(schema, beforeFragment, true);
3687            range.setStartAfter(splitElm);
3688            range.setEndAfter(parentElm);
3689            const afterFragment = range.extractContents();
3690            trimEdge(schema, afterFragment, false);
3691            if (!isEmptyNode(schema, beforeFragment, { checkRootAsContent: true })) {
3692                parentNode.insertBefore(beforeFragment, parentElm);
3693            }
3694            if (!isEmptyNode(schema, splitElm, { checkRootAsContent: true })) {
3695                parentNode.insertBefore(splitElm, parentElm);
3696            }
3697            if (!isEmptyNode(schema, afterFragment, { checkRootAsContent: true })) {
3698                parentNode.insertBefore(afterFragment, parentElm);
3699            }
3700            parentNode.removeChild(parentElm);
3701        }
3702    };
3703    // This will find invalid blocks wrapped in anchors and split them out so for example
3704    // <h1><a href="#"><h2>x</h2></a></h1> will find that h2 is invalid inside the H1 and split that out.
3705    // This is a simplistic apporach so it's likely not covering all the cases but it's a start.
3706    const splitInvalidChildren = (schema, scope, transparentBlocks) => {
3707        const blocksElements = schema.getBlockElements();
3708        const rootNode = SugarElement.fromDom(scope);
3709        const isBlock = (el) => name(el) in blocksElements;
3710        const isRoot = (el) => eq(el, rootNode);
3711        each$e(fromDom$1(transparentBlocks), (transparentBlock) => {
3712            ancestor$5(transparentBlock, isBlock, isRoot).each((parentBlock) => {
3713                const invalidChildren = children
vendor: 12,415 bytes, lines 3713-3984
3713(transparentBlock, (el) => isBlock(el) && !schema.isValidChild(name(parentBlock), name(el)));
3714                if (invalidChildren.length > 0) {
3715                    const stateScope = parentElement(parentBlock);
3716                    each$e(invalidChildren, (child) => {
3717                        ancestor$5(child, isBlock, isRoot).each((parentBlock) => {
3718                            split$2(schema, parentBlock.dom, child.dom);
3719                        });
3720                    });
3721                    stateScope.each((scope) => updateBlockStateOnChildren(schema, scope.dom));
3722                }
3723            });
3724        });
3725    };
3726    const unwrapInvalidChildren = (schema, scope, transparentBlocks) => {
3727        each$e([...transparentBlocks, ...(isTransparentBlock(schema, scope) ? [scope] : [])], (block) => each$e(descendants(SugarElement.fromDom(block), block.nodeName.toLowerCase()), (elm) => {
3728            if (isTransparentInline(schema, elm.dom)) {
3729                unwrap(elm);
3730            }
3731        }));
3732    };
3733    const updateChildren = (schema, scope) => {
3734        const transparentBlocks = updateBlockStateOnChildren(schema, scope);
3735        splitInvalidChildren(schema, scope, transparentBlocks);
3736        unwrapInvalidChildren(schema, scope, transparentBlocks);
3737    };
3738    const updateElement = (schema, target) => {
3739        if (isTransparentElement(schema, target)) {
3740            const blocksSelector = makeSelectorFromSchemaMap(schema.getBlockElements());
3741            updateTransparent(blocksSelector, target);
3742        }
3743    };
3744    const updateCaret = (schema, root, caretParent) => {
3745        const isRoot = (el) => eq(el, SugarElement.fromDom(root));
3746        const parents = parents$1(SugarElement.fromDom(caretParent), isRoot);
3747        // Check the element just above below the root so in if caretParent is I in this
3748        // case <body><p><b><i>|</i></b></p></body> it would use the P as the scope
3749        get$b(parents, parents.length - 2).filter(isElement$8).fold(() => updateChildren(schema, root), (scope) => updateChildren(schema, scope.dom));
3750    };
3751    const hasBlockAttr = (el) => el.hasAttribute(transparentBlockAttr);
3752    const isTransparentElementName = (schema, name) => has$2(schema.getTransparentElements(), name);
3753    const isTransparentElement = (schema, node) => isElement$7(node) && isTransparentElementName(schema, node.nodeName);
3754    const isTransparentBlock = (schema, node) => isTransparentElement(schema, node) && hasBlockAttr(node);
3755    const isTransparentInline = (schema, node) => isTransparentElement(schema, node) && !hasBlockAttr(node);
3756    const isTransparentAstBlock = (schema, node) => node.type === 1 && isTransparentElementName(schema, node.name) && isString(node.attr(transparentBlockAttr));
3757
3758    const browser$2 = detect$1().browser;
3759    const firstElement = (nodes) => find$2(nodes, isElement$8);
3760    // Firefox has a bug where caption height is not included correctly in offset calculations of tables
3761    // this tries to compensate for that by detecting if that offsets are incorrect and then remove the height
3762    const getTableCaptionDeltaY = (elm) => {
3763        if (browser$2.isFirefox() && name(elm) === 'table') {
3764            return firstElement(children$1(elm)).filter((elm) => {
3765                return name(elm) === 'caption';
3766            }).bind((caption) => {
3767                return firstElement(nextSiblings(caption)).map((body) => {
3768                    const bodyTop = body.dom.offsetTop;
3769                    const captionTop = caption.dom.offsetTop;
3770                    const captionHeight = caption.dom.offsetHeight;
3771                    return bodyTop <= captionTop ? -captionHeight : 0;
3772                });
3773            }).getOr(0);
3774        }
3775        else {
3776            return 0;
3777        }
3778    };
3779    const hasChild = (elm, child) => elm.children && contains$2(elm.children, child);
3780    const getPos = (body, elm, rootElm) => {
3781        let x = 0, y = 0;
3782        const doc = body.ownerDocument;
3783        rootElm = rootElm ? rootElm : body;
3784        if (elm) {
3785            // Use getBoundingClientRect if it exists since it's faster than looping offset nodes
3786            // Fallback to offsetParent calculations if the body isn't static better since it stops at the body root
3787            if (rootElm === body && elm.getBoundingClientRect && get$7(SugarElement.fromDom(body), 'position') === 'static') {
3788                const pos = elm.getBoundingClientRect();
3789                // Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit
3790                // Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position
3791                x = pos.left + (doc.documentElement.scrollLeft || body.scrollLeft) - doc.documentElement.clientLeft;
3792                y = pos.top + (doc.documentElement.scrollTop || body.scrollTop) - doc.documentElement.clientTop;
3793                return { x, y };
3794            }
3795            let offsetParent = elm;
3796            while (offsetParent && offsetParent !== rootElm && offsetParent.nodeType && !hasChild(offsetParent, rootElm)) {
3797                const castOffsetParent = offsetParent;
3798                x += castOffsetParent.offsetLeft || 0;
3799                y += castOffsetParent.offsetTop || 0;
3800                offsetParent = castOffsetParent.offsetParent;
3801            }
3802            offsetParent = elm.parentNode;
3803            while (offsetParent && offsetParent !== rootElm && offsetParent.nodeType && !hasChild(offsetParent, rootElm)) {
3804                x -= offsetParent.scrollLeft || 0;
3805                y -= offsetParent.scrollTop || 0;
3806                offsetParent = offsetParent.parentNode;
3807            }
3808            y += getTableCaptionDeltaY(SugarElement.fromDom(elm));
3809        }
3810        return { x, y };
3811    };
3812
3813    const getCrossOrigin$1 = (url, settings) => {
3814        const crossOriginFn = settings.crossOrigin;
3815        if (settings.contentCssCors) {
3816            return 'anonymous';
3817        }
3818        else if (isFunction(crossOriginFn)) {
3819            return crossOriginFn(url);
3820        }
3821        else {
3822            return undefined;
3823        }
3824    };
3825    const StyleSheetLoader = (documentOrShadowRoot, settings = {}) => {
3826        let idCount = 0;
3827        const loadedStates = {};
3828        const edos = SugarElement.fromDom(documentOrShadowRoot);
3829        const doc = documentOrOwner(edos);
3830        const _setReferrerPolicy = (referrerPolicy) => {
3831            settings.referrerPolicy = referrerPolicy;
3832        };
3833        const _setContentCssCors = (contentCssCors) => {
3834            settings.contentCssCors = contentCssCors;
3835        };
3836        const _setCrossOrigin = (crossOrigin) => {
3837            settings.crossOrigin = crossOrigin;
3838        };
3839        const addStyle = (element) => {
3840            append$1(getStyleContainer(edos), element);
3841        };
3842        const removeStyle = (id) => {
3843            const styleContainer = getStyleContainer(edos);
3844            descendant$1(styleContainer, '#' + id).each(remove$8);
3845        };
3846        const getOrCreateState = (url) => get$a(loadedStates, url).getOrThunk(() => ({
3847            id: 'mce-u' + (idCount++),
3848            passed: [],
3849            failed: [],
3850            count: 0
3851        }));
3852        /**
3853         * Loads the specified CSS file and returns a Promise that will resolve when the stylesheet is loaded successfully or reject if it failed to load.
3854         *
3855         * @method load
3856         * @param {String} url Url to be loaded.
3857         * @return {Promise} A Promise that will resolve or reject when the stylesheet is loaded.
3858         */
3859        const load = (url) => new Promise((success, failure) => {
3860            let link;
3861            const urlWithSuffix = Tools._addCacheSuffix(url);
3862            const state = getOrCreateState(urlWithSuffix);
3863            loadedStates[urlWithSuffix] = state;
3864            state.count++;
3865            const resolve = (callbacks, status) => {
3866                each$e(callbacks, call);
3867                state.status = status;
3868                state.passed = [];
3869                state.failed = [];
3870                if (link) {
3871                    link.onload = null;
3872                    link.onerror = null;
3873                    link = null;
3874                }
3875            };
3876            const passed = () => resolve(state.passed, 2);
3877            const failed = () => resolve(state.failed, 3);
3878            if (success) {
3879                state.passed.push(success);
3880            }
3881            if (failure) {
3882                state.failed.push(failure);
3883            }
3884            // Is loading wait for it to pass
3885            if (state.status === 1) {
3886                return;
3887            }
3888            // Has finished loading and was success
3889            if (state.status === 2) {
3890                passed();
3891                return;
3892            }
3893            // Has finished loading and was a failure
3894            if (state.status === 3) {
3895                failed();
3896                return;
3897            }
3898            // Start loading
3899            state.status = 1;
3900            const linkElem = SugarElement.fromTag('link', doc.dom);
3901            setAll$1(linkElem, {
3902                rel: 'stylesheet',
3903                type: 'text/css',
3904                id: state.id
3905            });
3906            const crossorigin = getCrossOrigin$1(url, settings);
3907            if (crossorigin !== undefined) {
3908                set$4(linkElem, 'crossOrigin', crossorigin);
3909            }
3910            if (settings.referrerPolicy) {
3911                // Note: Don't use link.referrerPolicy = ... here as it doesn't work on Safari
3912                set$4(linkElem, 'referrerpolicy', settings.referrerPolicy);
3913            }
3914            link = linkElem.dom;
3915            link.onload = passed;
3916            link.onerror = failed;
3917            addStyle(linkElem);
3918            set$4(linkElem, 'href', urlWithSuffix);
3919        });
3920        /**
3921         * Loads the specified css string in as a style element with an unique key.
3922         *
3923         * @method loadRawCss
3924         * @param {String} key Unique key for the style element.
3925         * @param {String} css Css style content to add.
3926         */
3927        const loadRawCss = (key, css) => {
3928            const state = getOrCreateState(key);
3929            loadedStates[key] = state;
3930            state.count++;
3931            // Start loading
3932            const styleElem = SugarElement.fromTag('style', doc.dom);
3933            setAll$1(styleElem, {
3934                'rel': 'stylesheet',
3935                'type': 'text/css',
3936                'id': state.id,
3937                'data-mce-key': key
3938            });
3939            styleElem.dom.innerHTML = css;
3940            addStyle(styleElem);
3941        };
3942        /**
3943         * Loads the specified CSS files and returns a Promise that is resolved when all stylesheets are loaded or rejected if any failed to load.
3944         *
3945         * @method loadAll
3946         * @param {Array} urls URLs to be loaded.
3947         * @return {Promise} A Promise that will resolve or reject when all stylesheets are loaded.
3948         */
3949        const loadAll = (urls) => {
3950            const loadedUrls = Promise.allSettled(map$3(urls, (url) => load(url).then(constant(url))));
3951            return loadedUrls.then((results) => {
3952                const parts = partition$2(results, (r) => r.status === 'fulfilled');
3953                if (parts.fail.length > 0) {
3954                    return Promise.reject(map$3(parts.fail, (result) => result.reason));
3955                }
3956                else {
3957                    return map$3(parts.pass, (result) => result.value);
3958                }
3959            });
3960        };
3961        /**
3962         * Unloads the specified CSS file if no resources currently depend on it.
3963         *
3964         * @method unload
3965         * @param {String} url URL to unload or remove.
3966         */
3967        const unload = (url) => {
3968            const urlWithSuffix = Tools._addCacheSuffix(url);
3969            get$a(loadedStates, urlWithSuffix).each((state) => {
3970                const count = --state.count;
3971                if (count === 0) {
3972                    delete loadedStates[urlWithSuffix];
3973                    removeStyle(state.id);
3974                }
3975            });
3976        };
3977        /**
3978         * Unloads the specified CSS style element by key.
3979         *
3980         * @method unloadRawCss
3981         * @param {String} key Key of CSS style resource to unload.
3982         */
3983        const unloadRawCss = (key) => {
3984            get$a
vendor: 77,521 bytes, lines 3984-5647
3984(loadedStates, key).each((state) => {
3985                const count = --state.count;
3986                if (count === 0) {
3987                    delete loadedStates[key];
3988                    removeStyle(state.id);
3989                }
3990            });
3991        };
3992        /**
3993         * Unloads each specified CSS file if no resources currently depend on it.
3994         *
3995         * @method unloadAll
3996         * @param {Array} urls URLs to unload or remove.
3997         */
3998        const unloadAll = (urls) => {
3999            each$e(urls, (url) => {
4000                unload(url);
4001            });
4002        };
4003        return {
4004            load,
4005            loadRawCss,
4006            loadAll,
4007            unload,
4008            unloadRawCss,
4009            unloadAll,
4010            _setReferrerPolicy,
4011            _setContentCssCors,
4012            _setCrossOrigin
4013        };
4014    };
4015
4016    /**
4017     * This function is exported for testing purposes only - please use StyleSheetLoader.instance in production code.
4018     */
4019    const create$b = () => {
4020        const map = new WeakMap();
4021        const forElement = (referenceElement, settings) => {
4022            const root = getRootNode(referenceElement);
4023            const rootDom = root.dom;
4024            return Optional.from(map.get(rootDom)).getOrThunk(() => {
4025                const sl = StyleSheetLoader(rootDom, settings);
4026                map.set(rootDom, sl);
4027                return sl;
4028            });
4029        };
4030        return {
4031            forElement
4032        };
4033    };
4034    const instance = create$b();
4035
4036    const isSpan = (node) => node.nodeName.toLowerCase() === 'span';
4037    const isInlineContent = (node, schema) => isNonNullable(node) && (isContent$1(schema, node) || schema.isInline(node.nodeName.toLowerCase()));
4038    const surroundedByInlineContent = (node, root, schema) => {
4039        const prev = new DomTreeWalker(node, root).prev(false);
4040        const next = new DomTreeWalker(node, root).next(false);
4041        // Check if the next/previous is either inline content or the start/end (eg is undefined)
4042        const prevIsInline = isUndefined(prev) || isInlineContent(prev, schema);
4043        const nextIsInline = isUndefined(next) || isInlineContent(next, schema);
4044        return prevIsInline && nextIsInline;
4045    };
4046    const isBookmarkNode$2 = (node) => isSpan(node) && node.getAttribute('data-mce-type') === 'bookmark';
4047    // Keep text nodes with only spaces if surrounded by spans.
4048    // eg. "<p><span>a</span> <span>b</span></p>" should keep space between a and b
4049    const isKeepTextNode = (node, root, schema) => isText$b(node) && node.data.length > 0 && surroundedByInlineContent(node, root, schema);
4050    // Keep elements as long as they have any children
4051    const isKeepElement = (node) => isElement$7(node) ? node.childNodes.length > 0 : false;
4052    const isDocument = (node) => isDocumentFragment(node) || isDocument$1(node);
4053    // W3C valid browsers tend to leave empty nodes to the left/right side of the contents - this makes sense
4054    // but we don't want that in our code since it serves no purpose for the end user
4055    // For example splitting this html at the bold element:
4056    //   <p>text 1<span><b>CHOP</b></span>text 2</p>
4057    // would produce:
4058    //   <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
4059    // this function will then trim off empty edges and produce:
4060    //   <p>text 1</p><b>CHOP</b><p>text 2</p>
4061    const trimNode = (dom, node, schema, root) => {
4062        const rootNode = root || node;
4063        if (isElement$7(node) && isBookmarkNode$2(node)) {
4064            return node;
4065        }
4066        const children = node.childNodes;
4067        for (let i = children.length - 1; i >= 0; i--) {
4068            trimNode(dom, children[i], schema, rootNode);
4069        }
4070        // If the only child is a bookmark then move it up
4071        if (isElement$7(node)) {
4072            const currentChildren = node.childNodes;
4073            if (currentChildren.length === 1 && isBookmarkNode$2(currentChildren[0])) {
4074                node.parentNode?.insertBefore(currentChildren[0], node);
4075            }
4076        }
4077        // Remove any empty nodes
4078        if (!isDocument(node) && !isContent$1(schema, node) && !isKeepElement(node) && !isKeepTextNode(node, rootNode, schema)) {
4079            dom.remove(node);
4080        }
4081        return node;
4082    };
4083
4084    /**
4085     * Entity encoder class.
4086     *
4087     * @class tinymce.html.Entities
4088     * @static
4089     * @version 3.4
4090     */
4091    const makeMap$3 = Tools.makeMap;
4092    const attrsCharsRegExp = /[&<>\"\u0060\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g;
4093    const textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g;
4094    const rawCharsRegExp = /[<>&\"\']/g;
4095    const entityRegExp = /&#([a-z0-9]+);?|&([a-z0-9]+);/gi;
4096    const asciiMap = {
4097        128: '\u20AC', 130: '\u201A', 131: '\u0192', 132: '\u201E', 133: '\u2026', 134: '\u2020',
4098        135: '\u2021', 136: '\u02C6', 137: '\u2030', 138: '\u0160', 139: '\u2039', 140: '\u0152',
4099        142: '\u017D', 145: '\u2018', 146: '\u2019', 147: '\u201C', 148: '\u201D', 149: '\u2022',
4100        150: '\u2013', 151: '\u2014', 152: '\u02DC', 153: '\u2122', 154: '\u0161', 155: '\u203A',
4101        156: '\u0153', 158: '\u017E', 159: '\u0178'
4102    };
4103    // Raw entities
4104    const baseEntities = {
4105        '\"': '&quot;', // Needs to be escaped since the YUI compressor would otherwise break the code
4106        '\'': '&#39;',
4107        '<': '&lt;',
4108        '>': '&gt;',
4109        '&': '&amp;',
4110        '\u0060': '&#96;'
4111    };
4112    // Reverse lookup table for raw entities
4113    const reverseEntities = {
4114        '&lt;': '<',
4115        '&gt;': '>',
4116        '&amp;': '&',
4117        '&quot;': '"',
4118        '&apos;': `'`
4119    };
4120    // Decodes text by using the browser
4121    const nativeDecode = (text) => {
4122        const elm = SugarElement.fromTag('div').dom;
4123        elm.innerHTML = text;
4124        return elm.textContent || elm.innerText || text;
4125    };
4126    // Build a two way lookup table for the entities
4127    const buildEntitiesLookup = (items, radix) => {
4128        const lookup = {};
4129        if (items) {
4130            const itemList = items.split(',');
4131            radix = radix || 10;
4132            // Build entities lookup table
4133            for (let i = 0; i < itemList.length; i += 2) {
4134                const chr = String.fromCharCode(parseInt(itemList[i], radix));
4135                // Only add non base entities
4136                if (!baseEntities[chr]) {
4137                    const entity = '&' + itemList[i + 1] + ';';
4138                    lookup[chr] = entity;
4139                    lookup[entity] = chr;
4140                }
4141            }
4142            return lookup;
4143        }
4144        else {
4145            return undefined;
4146        }
4147    };
4148    // Unpack entities lookup where the numbers are in radix 32 to reduce the size
4149    const namedEntities = buildEntitiesLookup('50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +
4150        '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +
4151        '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +
4152        '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +
4153        '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +
4154        '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +
4155        '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +
4156        '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +
4157        '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +
4158        '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +
4159        'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +
4160        'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +
4161        't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +
4162        'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +
4163        'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +
4164        '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +
4165        '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +
4166        '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +
4167        '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +
4168        '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' +
4169        'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +
4170        'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +
4171        'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +
4172        '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +
4173        '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro', 32);
4174    /**
4175     * Encodes the specified string using raw entities. This means only the required XML base entities will be encoded.
4176     *
4177     * @method encodeRaw
4178     * @param {String} text Text to encode.
4179     * @param {Boolean} attr Optional flag to specify if the text is attribute contents.
4180     * @return {String} Entity encoded text.
4181     */
4182    const encodeRaw = (text, attr) => text.replace(attr ? attrsCharsRegExp : textCharsRegExp, (chr) => {
4183        return baseEntities[chr] || chr;
4184    });
4185    /**
4186     * Encoded the specified text with both the attributes and text entities. This function will produce larger text contents
4187     * since it doesn't know if the context is within an attribute or text node. This was added for compatibility
4188     * and is exposed as the `DOMUtils.encode` function.
4189     *
4190     * @method encodeAllRaw
4191     * @param {String} text Text to encode.
4192     * @return {String} Entity encoded text.
4193     */
4194    const encodeAllRaw = (text) => ('' + text).replace(rawCharsRegExp, (chr) => {
4195        return baseEntities[chr] || chr;
4196    });
4197    /**
4198     * Encodes the specified string using numeric entities. The core entities will be
4199     * encoded as named ones but all non lower ascii characters will be encoded into numeric entities.
4200     *
4201     * @method encodeNumeric
4202     * @param {String} text Text to encode.
4203     * @param {Boolean} attr Optional flag to specify if the text is attribute contents.
4204     * @return {String} Entity encoded text.
4205     */
4206    const encodeNumeric = (text, attr) => text.replace(attr ? attrsCharsRegExp : textCharsRegExp, (chr) => {
4207        // Multi byte sequence convert it to a single entity
4208        if (chr.length > 1) {
4209            return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';';
4210        }
4211        return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';';
4212    });
4213    /**
4214     * Encodes the specified string using named entities. The core entities will be encoded
4215     * as named ones but all non lower ascii characters will be encoded into named entities.
4216     *
4217     * @method encodeNamed
4218     * @param {String} text Text to encode.
4219     * @param {Boolean} attr Optional flag to specify if the text is attribute contents.
4220     * @param {Object} entities Optional parameter with entities to use.
4221     * @return {String} Entity encoded text.
4222     */
4223    const encodeNamed = (text, attr, entities) => {
4224        const resolveEntities = entities || namedEntities;
4225        return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, (chr) => {
4226            return baseEntities[chr] || resolveEntities[chr] || chr;
4227        });
4228    };
4229    /**
4230     * Returns an encode function based on the name(s) and it's optional entities.
4231     *
4232     * @method getEncodeFunc
4233     * @param {String} name Comma separated list of encoders for example named,numeric.
4234     * @param {String} entities Optional parameter with entities to use instead of the built in set.
4235     * @return {Function} Encode function to be used.
4236     */
4237    const getEncodeFunc = (name, entities) => {
4238        const entitiesMap = buildEntitiesLookup(entities) || namedEntities;
4239        const encodeNamedAndNumeric = (text, attr) => text.replace(attr ? attrsCharsRegExp : textCharsRegExp, (chr) => {
4240            if (baseEntities[chr] !== undefined) {
4241                return baseEntities[chr];
4242            }
4243            if (entitiesMap[chr] !== undefined) {
4244                return entitiesMap[chr];
4245            }
4246            // Convert multi-byte sequences to a single entity.
4247            if (chr.length > 1) {
4248                return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';';
4249            }
4250            return '&#' + chr.charCodeAt(0) + ';';
4251        });
4252        const encodeCustomNamed = (text, attr) => {
4253            return encodeNamed(text, attr, entitiesMap);
4254        };
4255        // Replace + with , to be compatible with previous TinyMCE versions
4256        const nameMap = makeMap$3(name.replace(/\+/g, ','));
4257        // Named and numeric encoder
4258        if (nameMap.named && nameMap.numeric) {
4259            return encodeNamedAndNumeric;
4260        }
4261        // Named encoder
4262        if (nameMap.named) {
4263            // Custom names
4264            if (entities) {
4265                return encodeCustomNamed;
4266            }
4267            return encodeNamed;
4268        }
4269        // Numeric
4270        if (nameMap.numeric) {
4271            return encodeNumeric;
4272        }
4273        // Raw encoder
4274        return encodeRaw;
4275    };
4276    /**
4277     * Decodes the specified string, this will replace entities with raw UTF characters.
4278     *
4279     * @method decode
4280     * @param {String} text Text to entity decode.
4281     * @return {String} Entity decoded string.
4282     */
4283    const decode = (text) => text.replace(entityRegExp, (all, numeric) => {
4284        if (numeric) {
4285            if (numeric.charAt(0).toLowerCase() === 'x') {
4286                numeric = parseInt(numeric.substr(1), 16);
4287            }
4288            else {
4289                numeric = parseInt(numeric, 10);
4290            }
4291            // Support upper UTF
4292            if (numeric > 0xFFFF) {
4293                numeric -= 0x10000;
4294                // eslint-disable-next-line no-bitwise
4295                return String.fromCharCode(0xD800 + (numeric >> 10), 0xDC00 + (numeric & 0x3FF));
4296            }
4297            return asciiMap[numeric] || String.fromCharCode(numeric);
4298        }
4299        return reverseEntities[all] || namedEntities[all] || nativeDecode(all);
4300    });
4301    const Entities = {
4302        encodeRaw,
4303        encodeAllRaw,
4304        encodeNumeric,
4305        encodeNamed,
4306        getEncodeFunc,
4307        decode
4308    };
4309
4310    const split$1 = (items, delim) => {
4311        items = Tools.trim(items);
4312        return items ? items.split(delim || ' ') : [];
4313    };
4314    // Converts a wildcard expression string to a regexp for example *a will become /.*a/.
4315    const patternToRegExp = (str) => new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$');
4316    const isRegExp$1 = (obj) => isObject(obj) && obj.source && Object.prototype.toString.call(obj) === '[object RegExp]';
4317    const deepCloneElementRule = (obj) => {
4318        const helper = (value) => {
4319            if (isArray$1(value)) {
4320                return map$3(value, helper);
4321            }
4322            else if (isRegExp$1(value)) {
4323                return new RegExp(value.source, value.flags);
4324            }
4325            else if (isObject(value)) {
4326                return map$2(value, helper);
4327            }
4328            else {
4329                return value;
4330            }
4331        };
4332        return helper(obj);
4333    };
4334
4335    const parseCustomElementsRules = (value) => {
4336        const customElementRegExp = /^(~)?(.+)$/;
4337        return bind$3(split$1(value, ','), (rule) => {
4338            const matches = customElementRegExp.exec(rule);
4339            if (matches) {
4340                const inline = matches[1] === '~';
4341                const cloneName = inline ? 'span' : 'div';
4342                const name = matches[2];
4343                return [{ cloneName, name }];
4344            }
4345            else {
4346                return [];
4347            }
4348        });
4349    };
4350
4351    const getGlobalAttributeSet = (type) => {
4352        return Object.freeze([
4353            // Present on all schema types
4354            'id',
4355            'accesskey',
4356            'class',
4357            'dir',
4358            'lang',
4359            'style',
4360            'tabindex',
4361            'title',
4362            'role',
4363            // html5 and html5-strict extra attributes
4364            ...(type !== 'html4' ? ['contenteditable', 'contextmenu', 'draggable', 'dropzone', 'hidden', 'spellcheck', 'translate', 'itemprop', 'itemscope', 'itemtype'] : []),
4365            // html4 and html5 extra attributes
4366            ...(type !== 'html5-strict' ? ['xml:lang'] : [])
4367        ]);
4368    };
4369
4370    // Missing elements in `phrasing` compared to HTML5 spec at 2024-01-30 (timestamped since spec is constantly evolving)
4371    //  area - required to be inside a map element so we should not add it to all elements.
4372    //  link - required to be in the body so we should not add it to all elements.
4373    //  math - currently not supported.
4374    //  meta - Only allowed if the `itemprop` attribute is set so very special.
4375    //  slot - We only want these to be accepted in registered custom components.
4376    // Extra element in `phrasing`: command keygen
4377    //
4378    // Missing elements in `flow` compared to HTML5 spec at 2034-01-30 (timestamped since the spec is constantly evolving)
4379    //  search - Can be both in a block and inline position but is not a transparent element. So not supported at this time.
4380    const getElementSetsAsStrings = (type) => {
4381        let blockContent;
4382        let phrasingContent;
4383        // Block content elements
4384        blockContent =
4385            'address blockquote div dl fieldset form h1 h2 h3 h4 h5 h6 hr menu ol p pre table ul';
4386        // Phrasing content elements from the HTML5 spec (inline)
4387        phrasingContent =
4388            'a abbr b bdo br button cite code del dfn em embed i iframe img input ins kbd ' +
4389                'label map noscript object q s samp script select small span strong sub sup ' +
4390                'textarea u var #text #comment';
4391        // Add HTML5 items to globalAttributes, blockContent, phrasingContent
4392        if (type !== 'html4') {
4393            const transparentContent = 'a ins del canvas map';
4394            blockContent += ' article aside details dialog figure main header footer hgroup section nav ' + transparentContent;
4395            phrasingContent += ' audio canvas command data datalist mark meter output picture ' +
4396                'progress template time wbr video ruby bdi keygen svg';
4397        }
4398        // Add HTML4 elements unless it's html5-strict
4399        if (type !== 'html5-strict') {
4400            const html4PhrasingContent = 'acronym applet basefont big font strike tt';
4401            phrasingContent = [phrasingContent, html4PhrasingContent].join(' ');
4402            const html4BlockContent = 'center dir isindex noframes';
4403            blockContent = [blockContent, html4BlockContent].join(' ');
4404        }
4405        // Flow content elements from the HTML5 spec (block+inline)
4406        const flowContent = [blockContent, phrasingContent].join(' ');
4407        return { blockContent, phrasingContent, flowContent };
4408    };
4409    const getElementSets = (type) => {
4410        const { blockContent, phrasingContent, flowContent } = getElementSetsAsStrings(type);
4411        const toArr = (value) => {
4412            return Object.freeze(value.split(' '));
4413        };
4414        return Object.freeze({
4415            blockContent: toArr(blockContent),
4416            phrasingContent: toArr(phrasingContent),
4417            flowContent: toArr(flowContent)
4418        });
4419    };
4420
4421    const cachedSets = {
4422        'html4': cached(() => getElementSets('html4')),
4423        'html5': cached(() => getElementSets('html5')),
4424        'html5-strict': cached(() => getElementSets('html5-strict'))
4425    };
4426    // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
4427    const getElementsPreset = (type, name) => {
4428        const { blockContent, phrasingContent, flowContent } = cachedSets[type]();
4429        if (name === 'blocks') {
4430            return Optional.some(blockContent);
4431        }
4432        else if (name === 'phrasing') {
4433            return Optional.some(phrasingContent);
4434        }
4435        else if (name === 'flow') {
4436            return Optional.some(flowContent);
4437        }
4438        else {
4439            return Optional.none();
4440        }
4441    };
4442
4443    const makeSchema = (type) => {
4444        const globalAttributes = getGlobalAttributeSet(type);
4445        const { phrasingContent, flowContent } = getElementSetsAsStrings(type);
4446        const schema = {};
4447        const addElement = (name, attributes, children) => {
4448            schema[name] = {
4449                attributes: mapToObject(attributes, constant({})),
4450                attributesOrder: attributes,
4451                children: mapToObject(children, constant({}))
4452            };
4453        };
4454        const add = (name, attributes = '', children = '') => {
4455            const childNames = split$1(children);
4456            const names = split$1(name);
4457            let ni = names.length;
4458            const allAttributes = [...globalAttributes, ...split$1(attributes)];
4459            while (ni--) {
4460                addElement(names[ni], allAttributes.slice(), childNames);
4461            }
4462        };
4463        const addAttrs = (name, attributes) => {
4464            const names = split$1(name);
4465            const attrs = split$1(attributes);
4466            let ni = names.length;
4467            while (ni--) {
4468                const schemaItem = schema[names[ni]];
4469                for (let i = 0, l = attrs.length; i < l; i++) {
4470                    schemaItem.attributes[attrs[i]] = {};
4471                    schemaItem.attributesOrder.push(attrs[i]);
4472                }
4473            }
4474        };
4475        if (type !== 'html5-strict') {
4476            const html4PhrasingContent = 'acronym applet basefont big font strike tt';
4477            each$e(split$1(html4PhrasingContent), (name) => {
4478                add(name, '', phrasingContent);
4479            });
4480            const html4BlockContent = 'center dir isindex noframes';
4481            each$e(split$1(html4BlockContent), (name) => {
4482                add(name, '', flowContent);
4483            });
4484        }
4485        // HTML4 base schema TODO: Move HTML5 specific attributes to HTML5 specific if statement
4486        // Schema items <element name>, <specific attributes>, <children ..>
4487        add('html', 'manifest', 'head body');
4488        add('head', '', 'base command link meta noscript script style title');
4489        add('title hr noscript br');
4490        add('base', 'href target');
4491        add('link', 'href rel media hreflang type sizes hreflang');
4492        add('meta', 'name http-equiv content charset property'); // Property is an RDFa spec attribute.
4493        add('style', 'media type scoped');
4494        add('script', 'src async defer type charset');
4495        add('body', 'onafterprint onbeforeprint onbeforeunload onblur onerror onfocus ' +
4496            'onhashchange onload onmessage onoffline ononline onpagehide onpageshow ' +
4497            'onpopstate onresize onscroll onstorage onunload', flowContent);
4498        add('dd div', '', flowContent);
4499        add('address dt caption', '', type === 'html4' ? phrasingContent : flowContent);
4500        add('h1 h2 h3 h4 h5 h6 pre p abbr code var samp kbd sub sup i b u bdo span legend em strong small s cite dfn', '', phrasingContent);
4501        add('blockquote', 'cite', flowContent);
4502        add('ol', 'reversed start type', 'li');
4503        add('ul', '', 'li');
4504        add('li', 'value', flowContent);
4505        add('dl', '', 'dt dd');
4506        add('a', 'href target rel media hreflang type', type === 'html4' ? phrasingContent : flowContent);
4507        add('q', 'cite', phrasingContent);
4508        add('ins del', 'cite datetime', flowContent);
4509        add('img', 'src sizes srcset alt usemap ismap width height');
4510        add('iframe', 'src name width height', flowContent);
4511        add('embed', 'src type width height');
4512        add('object', 'data type typemustmatch name usemap form width height', [flowContent, 'param'].join(' '));
4513        add('param', 'name value');
4514        add('map', 'name', [flowContent, 'area'].join(' '));
4515        add('area', 'alt coords shape href target rel media hreflang type');
4516        add('table', 'border', 'caption colgroup thead tfoot tbody tr' + (type === 'html4' ? ' col' : ''));
4517        add('colgroup', 'span', 'col');
4518        add('col', 'span');
4519        add('tbody thead tfoot', '', 'tr');
4520        add('tr', '', 'td th');
4521        add('td', 'colspan rowspan headers', flowContent);
4522        add('th', 'colspan rowspan headers scope abbr', flowContent);
4523        add('form', 'accept-charset action autocomplete enctype method name novalidate target', flowContent);
4524        add('fieldset', 'disabled form name', [flowContent, 'legend'].join(' '));
4525        add('label', 'form for', phrasingContent);
4526        add('input', 'accept alt autocomplete checked dirname disabled form formaction formenctype formmethod formnovalidate ' +
4527            'formtarget height list max maxlength min multiple name pattern readonly required size src step type value width');
4528        add('button', 'disabled form formaction formenctype formmethod formnovalidate formtarget name type value', type === 'html4' ? flowContent : phrasingContent);
4529        add('select', 'disabled form multiple name required size', 'option optgroup');
4530        add('optgroup', 'disabled label', 'option');
4531        add('option', 'disabled label selected value');
4532        add('textarea', 'cols dirname disabled form maxlength name readonly required rows wrap');
4533        add('menu', 'type label', [flowContent, 'li'].join(' '));
4534        add('noscript', '', flowContent);
4535        // Extend with HTML5 elements
4536        if (type !== 'html4') {
4537            add('wbr');
4538            add('ruby', '', [phrasingContent, 'rt rp'].join(' '));
4539            add('figcaption', '', flowContent);
4540            add('mark rt rp bdi', '', phrasingContent);
4541            add('summary', '', [phrasingContent, 'h1 h2 h3 h4 h5 h6'].join(' '));
4542            add('canvas', 'width height', flowContent);
4543            add('data', 'value', phrasingContent);
4544            add('video', 'src crossorigin poster preload autoplay mediagroup loop ' +
4545                'controlslist disablepictureinpicture disableremoteplayback playsinline ' +
4546                'muted controls width height buffered', [flowContent, 'track source'].join(' '));
4547            add('audio', 'src crossorigin preload autoplay mediagroup loop muted controls ' +
4548                'buffered volume', [flowContent, 'track source'].join(' '));
4549            add('picture', '', 'img source');
4550            add('source', 'src srcset type media sizes');
4551            add('track', 'kind src srclang label default');
4552            add('datalist', '', [phrasingContent, 'option'].join(' '));
4553            add('article section nav aside main header footer', '', flowContent);
4554            add('hgroup', '', 'h1 h2 h3 h4 h5 h6');
4555            add('figure', '', [flowContent, 'figcaption'].join(' '));
4556            add('time', 'datetime', phrasingContent);
4557            add('dialog', 'open', flowContent);
4558            add('command', 'type label icon disabled checked radiogroup command');
4559            add('output', 'for form name', phrasingContent);
4560            add('progress', 'value max', phrasingContent);
4561            add('meter', 'value min max low high optimum', phrasingContent);
4562            add('details', 'open', [flowContent, 'summary'].join(' '));
4563            add('keygen', 'autofocus challenge disabled form keytype name');
4564            // SVGs only support a subset of the global attributes
4565            addElement('svg', 'id tabindex lang xml:space class style x y width height viewBox preserveAspectRatio zoomAndPan transform'.split(' '), []);
4566        }
4567        // Extend with HTML4 attributes unless it's html5-strict
4568        if (type !== 'html5-strict') {
4569            addAttrs('script', 'language xml:space');
4570            addAttrs('style', 'xml:space');
4571            addAttrs('object', 'declare classid code codebase codetype archive standby align border hspace vspace');
4572            addAttrs('embed', 'align name hspace vspace');
4573            addAttrs('param', 'valuetype type');
4574            addAttrs('a', 'charset name rev shape coords');
4575            addAttrs('br', 'clear');
4576            addAttrs('applet', 'codebase archive code object alt name width height align hspace vspace');
4577            addAttrs('img', 'name longdesc align border hspace vspace');
4578            addAttrs('iframe', 'longdesc frameborder marginwidth marginheight scrolling align');
4579            addAttrs('font basefont', 'size color face');
4580            addAttrs('input', 'usemap align');
4581            addAttrs('select');
4582            addAttrs('textarea');
4583            addAttrs('h1 h2 h3 h4 h5 h6 div p legend caption', 'align');
4584            addAttrs('ul', 'type compact');
4585            addAttrs('li', 'type');
4586            addAttrs('ol dl menu dir', 'compact');
4587            addAttrs('pre', 'width xml:space');
4588            addAttrs('hr', 'align noshade size width');
4589            addAttrs('isindex', 'prompt');
4590            addAttrs('table', 'summary width frame rules cellspacing cellpadding align bgcolor');
4591            addAttrs('col', 'width align char charoff valign');
4592            addAttrs('colgroup', 'width align char charoff valign');
4593            addAttrs('thead', 'align char charoff valign');
4594            addAttrs('tr', 'align char charoff valign bgcolor');
4595            addAttrs('th', 'axis align char charoff valign nowrap bgcolor width height');
4596            addAttrs('form', 'accept');
4597            addAttrs('td', 'abbr axis scope align char charoff valign nowrap bgcolor width height');
4598            addAttrs('tfoot', 'align char charoff valign');
4599            addAttrs('tbody', 'align char charoff valign');
4600            addAttrs('area', 'nohref');
4601            addAttrs('body', 'background bgcolor text link vlink alink');
4602        }
4603        // Extend with HTML5 attributes unless it's html4
4604        if (type !== 'html4') {
4605            addAttrs('input button select textarea', 'autofocus');
4606            addAttrs('input textarea', 'placeholder');
4607            addAttrs('a', 'download');
4608            addAttrs('link script img', 'crossorigin');
4609            addAttrs('img', 'loading');
4610            addAttrs('iframe', 'sandbox seamless allow allowfullscreen loading referrerpolicy'); // Excluded: srcdoc
4611        }
4612        // Special: iframe, ruby, video, audio, label
4613        if (type !== 'html4') {
4614            // Video/audio elements cannot have nested children
4615            each$e([schema.video, schema.audio], (item) => {
4616                delete item.children.audio;
4617                delete item.children.video;
4618            });
4619        }
4620        // Delete children of the same name from it's parent
4621        // For example: form can't have a child of the name form
4622        each$e(split$1('a form meter progress dfn'), (name) => {
4623            if (schema[name]) {
4624                delete schema[name].children[name];
4625            }
4626        });
4627        // Delete header, footer, sectioning and heading content descendants
4628        /* each('dt th address', function(name) {
4629         delete schema[name].children[name];
4630         });*/
4631        // Caption can't have tables
4632        delete schema.caption.children.table;
4633        // Delete scripts by default due to possible XSS
4634        delete schema.script;
4635        // TODO: LI:s can only have value if parent is OL
4636        return schema;
4637    };
4638
4639    const prefixToOperation = (prefix) => prefix === '-' ? 'remove' : 'add';
4640    const parseValidChild = (name) => {
4641        // see: https://html.spec.whatwg.org/#valid-custom-element-name
4642        const validChildRegExp = /^(@?)([A-Za-z0-9_\-.\u00b7\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u037d\u037f-\u1fff\u200c-\u200d\u203f-\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]+)$/;
4643        return Optional.from(validChildRegExp.exec(name)).map((matches) => ({
4644            preset: matches[1] === '@',
4645            name: matches[2]
4646        }));
4647    };
4648    const parseValidChildrenRules = (value) => {
4649        // see: https://html.spec.whatwg.org/#valid-custom-element-name
4650        const childRuleRegExp = /^([+\-]?)([A-Za-z0-9_\-.\u00b7\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u037d\u037f-\u1fff\u200c-\u200d\u203f-\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]+)\[([^\]]+)]$/; // from w3c's custom grammar (above)
4651        return bind$3(split$1(value, ','), (rule) => {
4652            const matches = childRuleRegExp.exec(rule);
4653            if (matches) {
4654                const prefix = matches[1];
4655                const operation = prefix ? prefixToOperation(prefix) : 'replace';
4656                const name = matches[2];
4657                const validChildren = bind$3(split$1(matches[3], '|'), (validChild) => parseValidChild(validChild).toArray());
4658                return [{ operation, name, validChildren }];
4659            }
4660            else {
4661                return [];
4662            }
4663        });
4664    };
4665
4666    const parseValidElementsAttrDataIntoElement = (attrData, targetElement) => {
4667        const attrRuleRegExp = /^([!\-])?(\w+[\\:]:\w+|[^=~<]+)?(?:([=~<])(.*))?$/;
4668        const hasPatternsRegExp = /[*?+]/;
4669        const { attributes, attributesOrder } = targetElement;
4670        return each$e(split$1(attrData, '|'), (rule) => {
4671            const matches = attrRuleRegExp.exec(rule);
4672            if (matches) {
4673                const attr = {};
4674                const attrType = matches[1];
4675                const attrName = matches[2].replace(/[\\:]:/g, ':');
4676                const attrPrefix = matches[3];
4677                const value = matches[4];
4678                // Required
4679                if (attrType === '!') {
4680                    targetElement.attributesRequired = targetElement.attributesRequired || [];
4681                    targetElement.attributesRequired.push(attrName);
4682                    attr.required = true;
4683                }
4684                // Denied from global
4685                if (attrType === '-') {
4686                    delete attributes[attrName];
4687                    attributesOrder.splice(Tools.inArray(attributesOrder, attrName), 1);
4688                    return;
4689                }
4690                // Default value
4691                if (attrPrefix) {
4692                    if (attrPrefix === '=') { // Default value
4693                        targetElement.attributesDefault = targetElement.attributesDefault || [];
4694                        targetElement.attributesDefault.push({ name: attrName, value });
4695                        attr.defaultValue = value;
4696                    }
4697                    else if (attrPrefix === '~') { // Forced value
4698                        targetElement.attributesForced = targetElement.attributesForced || [];
4699                        targetElement.attributesForced.push({ name: attrName, value });
4700                        attr.forcedValue = value;
4701                    }
4702                    else if (attrPrefix === '<') { // Required values
4703                        attr.validValues = Tools.makeMap(value, '?');
4704                    }
4705                }
4706                // Check for attribute patterns
4707                if (hasPatternsRegExp.test(attrName)) {
4708                    const attrPattern = attr;
4709                    targetElement.attributePatterns = targetElement.attributePatterns || [];
4710                    attrPattern.pattern = patternToRegExp(attrName);
4711                    targetElement.attributePatterns.push(attrPattern);
4712                }
4713                else {
4714                    // Add attribute to order list if it doesn't already exist
4715                    if (!attributes[attrName]) {
4716                        attributesOrder.push(attrName);
4717                    }
4718                    attributes[attrName] = attr;
4719                }
4720            }
4721        });
4722    };
4723    const cloneAttributesInto = (from, to) => {
4724        each$d(from.attributes, (value, key) => {
4725            to.attributes[key] = value;
4726        });
4727        to.attributesOrder.push(...from.attributesOrder);
4728    };
4729    const parseValidElementsRules = (globalElement, validElements) => {
4730        const elementRuleRegExp = /^([#+\-])?([^\[!\/]+)(?:\/([^\[!]+))?(?:(!?)\[([^\]]+)])?$/;
4731        return bind$3(split$1(validElements, ','), (rule) => {
4732            const matches = elementRuleRegExp.exec(rule);
4733            if (matches) {
4734                const prefix = matches[1];
4735                const elementName = matches[2];
4736                const outputName = matches[3];
4737                const attrsPrefix = matches[4];
4738                const attrData = matches[5];
4739                const element = {
4740                    attributes: {},
4741                    attributesOrder: []
4742                };
4743                globalElement.each((el) => cloneAttributesInto(el, element));
4744                if (prefix === '#') {
4745                    element.paddEmpty = true;
4746                }
4747                else if (prefix === '-') {
4748                    element.removeEmpty = true;
4749                }
4750                if (attrsPrefix === '!') {
4751                    element.removeEmptyAttrs = true;
4752                }
4753                if (attrData) {
4754                    parseValidElementsAttrDataIntoElement(attrData, element);
4755                }
4756                // Handle substitute elements such as b/strong
4757                if (outputName) {
4758                    element.outputName = elementName;
4759                }
4760                // Mutate the local globalElement option state if we find a global @ rule
4761                if (elementName === '@') {
4762                    // We only care about the first one
4763                    if (globalElement.isNone()) {
4764                        globalElement = Optional.some(element);
4765                    }
4766                    else {
4767                        return [];
4768                    }
4769                }
4770                return [outputName ? { name: elementName, element, aliasName: outputName } : { name: elementName, element }];
4771            }
4772            else {
4773                return [];
4774            }
4775        });
4776    };
4777
4778    /**
4779     * Schema validator class.
4780     *
4781     * @class tinymce.html.Schema
4782     * @version 3.4
4783     * @example
4784     * if (tinymce.activeEditor.schema.isValidChild('p', 'span')) {
4785     *   alert('span is valid child of p.');
4786     * }
4787     *
4788     * if (tinymce.activeEditor.schema.getElementRule('p')) {
4789     *   alert('P is a valid element.');
4790     * }
4791     */
4792    const mapCache = {};
4793    const makeMap$2 = Tools.makeMap, each$b = Tools.each, extend$2 = Tools.extend, explode$2 = Tools.explode;
4794    const createMap = (defaultValue, extendWith = {}) => {
4795        const value = makeMap$2(defaultValue, ' ', makeMap$2(defaultValue.toUpperCase(), ' '));
4796        return extend$2(value, extendWith);
4797    };
4798    // A curated list using the textBlockElements map and parts of the blockElements map from the schema
4799    // TODO: TINY-8728 Investigate if the extras can be added directly to the default text block elements
4800    const getTextRootBlockElements = (schema) => createMap('td th li dt dd figcaption caption details summary', schema.getTextBlockElements());
4801    const compileElementMap = (value, mode) => {
4802        if (value) {
4803            const styles = {};
4804            if (isString(value)) {
4805                value = {
4806                    '*': value
4807                };
4808            }
4809            // Convert styles into a rule list
4810            each$b(value, (value, key) => {
4811                styles[key] = styles[key.toUpperCase()] = mode === 'map' ? makeMap$2(value, /[, ]/) : explode$2(value, /[, ]/);
4812            });
4813            return styles;
4814        }
4815        else {
4816            return undefined;
4817        }
4818    };
4819    const Schema = (settings = {}) => {
4820        const elements = {};
4821        const children = {};
4822        let patternElements = [];
4823        const customElementsMap = {};
4824        const specialElements = {};
4825        const componentUrls = {};
4826        // Creates an lookup table map object for the specified option or the default value
4827        const createLookupTable = (option, defaultValue, extendWith) => {
4828            const value = settings[option];
4829            if (!value) {
4830                // Get cached default map or make it if needed
4831                let newValue = mapCache[option];
4832                if (!newValue) {
4833                    newValue = createMap(defaultValue, extendWith);
4834                    mapCache[option] = newValue;
4835                }
4836                return newValue;
4837            }
4838            else {
4839                // Create custom map
4840                return makeMap$2(value, /[, ]/, makeMap$2(value.toUpperCase(), /[, ]/));
4841            }
4842        };
4843        const schemaType = settings.schema ?? 'html5';
4844        const schemaItems = makeSchema(schemaType);
4845        // Allow all elements and attributes if verify_html is set to false
4846        if (settings.verify_html === false) {
4847            settings.valid_elements = '*[*]';
4848        }
4849        const validStyles = compileElementMap(settings.valid_styles);
4850        const invalidStyles = compileElementMap(settings.invalid_styles, 'map');
4851        const validClasses = compileElementMap(settings.valid_classes, 'map');
4852        // Setup map objects
4853        const whitespaceElementsMap = createLookupTable('whitespace_elements', 'pre script noscript style textarea video audio iframe object code');
4854        const selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li option p td tfoot th thead tr');
4855        const voidElementsMap = createLookupTable('void_elements', 'area base basefont br col frame hr img input isindex link ' +
4856            'meta param embed source wbr track');
4857        const boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize ' +
4858            'noshade nowrap readonly selected autoplay loop controls allowfullscreen');
4859        const nonEmptyOrMoveCaretBeforeOnEnter = 'td th iframe video audio object script code';
4860        const nonEmptyElementsMap = createLookupTable('non_empty_elements', nonEmptyOrMoveCaretBeforeOnEnter + ' pre svg textarea summary', voidElementsMap);
4861        const moveCaretBeforeOnEnterElementsMap = createLookupTable('move_caret_before_on_enter_elements', nonEmptyOrMoveCaretBeforeOnEnter + ' table', voidElementsMap);
4862        const headings = 'h1 h2 h3 h4 h5 h6';
4863        const textBlockElementsMap = createLookupTable('text_block_elements', headings + ' p div address pre form ' +
4864            'blockquote center dir fieldset header footer article section hgroup aside main nav figure');
4865        const blockElementsMap = createLookupTable('block_elements', 'hr table tbody thead tfoot ' +
4866            'th tr td li ol ul caption dl dt dd noscript menu isindex option ' +
4867            'datalist select optgroup figcaption details summary html body multicol listing colgroup col', textBlockElementsMap);
4868        const textInlineElementsMap = createLookupTable('text_inline_elements', 'span strong b em i font s strike u var cite ' +
4869            'dfn code mark q sup sub samp');
4870        const transparentElementsMap = createLookupTable('transparent_elements', 'a ins del canvas map');
4871        const wrapBlockElementsMap = createLookupTable('wrap_block_elements', 'pre ' + headings);
4872        // See https://html.spec.whatwg.org/multipage/parsing.html#parsing-html-fragments
4873        each$b(('script noscript iframe noframes noembed title style textarea xmp plaintext').split(' '), (name) => {
4874            specialElements[name] = new RegExp('<\/' + name + '[^>]*>', 'gi');
4875        });
4876        // Parses the specified valid_elements string and adds to the current rules
4877        const addValidElements = (validElements) => {
4878            const globalElement = Optional.from(elements['@']);
4879            const hasPatternsRegExp = /[*?+]/;
4880            each$e(parseValidElementsRules(globalElement, validElements ?? ''), ({ name, element, aliasName }) => {
4881                if (aliasName) {
4882                    elements[aliasName] = element;
4883                }
4884                // Add pattern or exact element
4885                if (hasPatternsRegExp.test(name)) {
4886                    const patternElement = element;
4887                    patternElement.pattern = patternToRegExp(name);
4888                    patternElements.push(patternElement);
4889                }
4890                else {
4891                    elements[name] = element;
4892                }
4893            });
4894        };
4895        const setValidElements = (validElements) => {
4896            // Clear any existing rules. Note that since `elements` is exposed we can't
4897            // overwrite it, so instead we delete all the properties
4898            patternElements = [];
4899            each$e(keys(elements), (name) => {
4900                delete elements[name];
4901            });
4902            addValidElements(validElements);
4903        };
4904        const addCustomElement = (name, spec) => {
4905            // Flush cached items since we are altering the default maps
4906            delete mapCache.text_block_elements;
4907            delete mapCache.block_elements;
4908            const inline = spec.extends ? !isBlock(spec.extends) : false;
4909            const cloneName = spec.extends;
4910            children[name] = cloneName ? children[cloneName] : {};
4911            customElementsMap[name] = cloneName ?? name;
4912            // Treat all custom elements as being non-empty by default
4913            nonEmptyElementsMap[name.toUpperCase()] = {};
4914            nonEmptyElementsMap[name] = {};
4915            // If it's not marked as inline then add it to valid block elements
4916            if (!inline) {
4917                blockElementsMap[name.toUpperCase()] = {};
4918                blockElementsMap[name] = {};
4919            }
4920            // Add elements clone if needed
4921            if (cloneName && !elements[name] && elements[cloneName]) {
4922                const customRule = deepCloneElementRule(elements[cloneName]);
4923                delete customRule.removeEmptyAttrs;
4924                delete customRule.removeEmpty;
4925                elements[name] = customRule;
4926            }
4927            else {
4928                elements[name] = { attributesOrder: [], attributes: {} };
4929            }
4930            // Add custom attributes
4931            if (isArray$1(spec.attributes)) {
4932                const processAttrName = (name) => {
4933                    customRule.attributesOrder.push(name);
4934                    customRule.attributes[name] = {};
4935                };
4936                const customRule = elements[name] ?? {};
4937                delete customRule.attributesDefault;
4938                delete customRule.attributesForced;
4939                delete customRule.attributePatterns;
4940                delete customRule.attributesRequired;
4941                customRule.attributesOrder = [];
4942                customRule.attributes = {};
4943                each$e(spec.attributes, (attrName) => {
4944                    const globalAttrs = getGlobalAttributeSet(schemaType);
4945                    parseValidChild(attrName).each(({ preset, name }) => {
4946                        if (preset) {
4947                            if (name === 'global') {
4948                                each$e(globalAttrs, processAttrName);
4949                            }
4950                        }
4951                        else {
4952                            processAttrName(name);
4953                        }
4954                    });
4955                });
4956                elements[name] = customRule;
4957            }
4958            // Add custom pad empty rule
4959            if (isBoolean(spec.padEmpty)) {
4960                const customRule = elements[name] ?? {};
4961                customRule.paddEmpty = spec.padEmpty;
4962                elements[name] = customRule;
4963            }
4964            // Add custom children
4965            if (isArray$1(spec.children)) {
4966                const customElementChildren = {};
4967                const processNodeName = (name) => {
4968                    customElementChildren[name] = {};
4969                };
4970                const processPreset = (name) => {
4971                    getElementsPreset(schemaType, name).each((names) => {
4972                        each$e(names, processNodeName);
4973                    });
4974                };
4975                each$e(spec.children, (child) => {
4976                    parseValidChild(child).each(({ preset, name }) => {
4977                        if (preset) {
4978                            processPreset(name);
4979                        }
4980                        else {
4981                            processNodeName(name);
4982                        }
4983                    });
4984                });
4985                children[name] = customElementChildren;
4986            }
4987            // Add custom elements at extends positions
4988            if (cloneName) {
4989                each$d(children, (element, elmName) => {
4990                    if (element[cloneName]) {
4991                        children[elmName] = element = extend$2({}, children[elmName]);
4992                        element[name] = element[cloneName];
4993                    }
4994                });
4995            }
4996        };
4997        const addCustomElementsFromString = (customElements) => {
4998            each$e(parseCustomElementsRules(customElements ?? ''), ({ name, cloneName }) => {
4999                addCustomElement(name, { extends: cloneName });
5000            });
5001        };
5002        const addComponentUrl = (elementName, componentUrl) => {
5003            componentUrls[elementName] = componentUrl;
5004        };
5005        const addCustomElements = (customElements) => {
5006            if (isObject(customElements)) {
5007                each$d(customElements, (spec, name) => {
5008                    const componentUrl = spec.componentUrl;
5009                    if (isString(componentUrl)) {
5010                        addComponentUrl(name, componentUrl);
5011                    }
5012                    addCustomElement(name, spec);
5013                });
5014            }
5015            else if (isString(customElements)) {
5016                addCustomElementsFromString(customElements);
5017            }
5018        };
5019        // Adds valid children to the schema object
5020        const addValidChildren = (validChildren) => {
5021            each$e(parseValidChildrenRules(validChildren ?? ''), ({ operation, name, validChildren }) => {
5022                const parent = operation === 'replace' ? { '#comment': {} } : children[name];
5023                const processNodeName = (name) => {
5024                    if (operation === 'remove') {
5025                        delete parent[name];
5026                    }
5027                    else {
5028                        parent[name] = {};
5029                    }
5030                };
5031                const processPreset = (name) => {
5032                    getElementsPreset(schemaType, name).each((names) => {
5033                        each$e(names, processNodeName);
5034                    });
5035                };
5036                each$e(validChildren, ({ preset, name }) => {
5037                    if (preset) {
5038                        processPreset(name);
5039                    }
5040                    else {
5041                        processNodeName(name);
5042                    }
5043                });
5044                children[name] = parent;
5045            });
5046        };
5047        const getElementRule = (name) => {
5048            const element = elements[name];
5049            // Exact match found
5050            if (element) {
5051                return element;
5052            }
5053            // No exact match then try the patterns
5054            let i = patternElements.length;
5055            while (i--) {
5056                const patternElement = patternElements[i];
5057                if (patternElement.pattern.test(name)) {
5058                    return patternElement;
5059                }
5060            }
5061            return undefined;
5062        };
5063        const setup = () => {
5064            if (!settings.valid_elements) {
5065                // No valid elements defined then clone the elements from the schema spec
5066                each$b(schemaItems, (element, name) => {
5067                    elements[name] = {
5068                        attributes: element.attributes,
5069                        attributesOrder: element.attributesOrder
5070                    };
5071                    children[name] = element.children;
5072                });
5073                // Prefer strong/em over b/i
5074                each$b(split$1('strong/b em/i'), (item) => {
5075                    const items = split$1(item, '/');
5076                    elements[items[1]].outputName = items[0];
5077                });
5078                // Add default alt attribute for images, removed since alt="" is treated as presentational.
5079                // elements.img.attributesDefault = [{name: 'alt', value: ''}];
5080                // By default,
5081                // - padd the text inline element if it is empty and also a child of an empty root block
5082                // - in all other cases, remove the text inline element if it is empty
5083                each$b(textInlineElementsMap, (_val, name) => {
5084                    if (elements[name]) {
5085                        if (settings.padd_empty_block_inline_children) {
5086                            elements[name].paddInEmptyBlock = true;
5087                        }
5088                        elements[name].removeEmpty = true;
5089                    }
5090                });
5091                // Remove these if they are empty by default
5092                each$b(split$1('ol ul blockquote a table tbody'), (name) => {
5093                    if (elements[name]) {
5094                        elements[name].removeEmpty = true;
5095                    }
5096                });
5097                // Padd these by default
5098                each$b(split$1('p h1 h2 h3 h4 h5 h6 th td pre div address caption li summary'), (name) => {
5099                    if (elements[name]) {
5100                        elements[name].paddEmpty = true;
5101                    }
5102                });
5103                // Remove these if they have no attributes
5104                each$b(split$1('span'), (name) => {
5105                    elements[name].removeEmptyAttrs = true;
5106                });
5107                // Remove these by default
5108                // TODO: Reenable in 4.1
5109                /* each(split('script style'), function(name) {
5110                 delete elements[name];
5111                 });*/
5112            }
5113            else {
5114                setValidElements(settings.valid_elements);
5115                each$b(schemaItems, (element, name) => {
5116                    children[name] = element.children;
5117                });
5118            }
5119            // Opt in is done with options like `extended_valid_elements`
5120            delete elements.svg;
5121            addCustomElements(settings.custom_elements);
5122            addValidChildren(settings.valid_children);
5123            addValidElements(settings.extended_valid_elements);
5124            // Todo: Remove this when we fix list handling to be valid
5125            addValidChildren('+ol[ul|ol],+ul[ul|ol]');
5126            // Some elements are not valid by themselves - require parents
5127            each$b({
5128                dd: 'dl',
5129                dt: 'dl',
5130                li: 'ul ol',
5131                td: 'tr',
5132                th: 'tr',
5133                tr: 'tbody thead tfoot',
5134                tbody: 'table',
5135                thead: 'table',
5136                tfoot: 'table',
5137                legend: 'fieldset',
5138                area: 'map',
5139                param: 'video audio object'
5140            }, (parents, item) => {
5141                if (elements[item]) {
5142                    elements[item].parentsRequired = split$1(parents);
5143                }
5144            });
5145            // Delete invalid elements
5146            if (settings.invalid_elements) {
5147                each$b(explode$2(settings.invalid_elements), (item) => {
5148                    if (elements[item]) {
5149                        delete elements[item];
5150                    }
5151                });
5152            }
5153            // If the user didn't allow span only allow internal spans
5154            if (!getElementRule('span')) {
5155                addValidElements('span[!data-mce-type|*]');
5156            }
5157        };
5158        /**
5159         * Name/value map object with valid parents and children to those parents.
5160         *
5161         * @field children
5162         * @type Object
5163         * @example
5164         * children = {
5165         *    div: { p:{}, h1:{} }
5166         * };
5167         */
5168        /**
5169         * Name/value map object with valid styles for each element.
5170         *
5171         * @method getValidStyles
5172         * @type Object
5173         */
5174        const getValidStyles = constant(validStyles);
5175        /**
5176         * Name/value map object with valid styles for each element.
5177         *
5178         * @method getInvalidStyles
5179         * @type Object
5180         */
5181        const getInvalidStyles = constant(invalidStyles);
5182        /**
5183         * Name/value map object with valid classes for each element.
5184         *
5185         * @method getValidClasses
5186         * @type Object
5187         */
5188        const getValidClasses = constant(validClasses);
5189        /**
5190         * Returns a map with boolean attributes.
5191         *
5192         * @method getBoolAttrs
5193         * @return {Object} Name/value lookup map for boolean attributes.
5194         */
5195        const getBoolAttrs = constant(boolAttrMap);
5196        /**
5197         * Returns a map with block elements.
5198         *
5199         * @method getBlockElements
5200         * @return {Object} Name/value lookup map for block elements.
5201         */
5202        const getBlockElements = constant(blockElementsMap);
5203        /**
5204         * Returns a map with text block elements. For example: <code>&#60;p&#62;</code>, <code>&#60;h1&#62;</code> to <code>&#60;h6&#62;</code>, <code>&#60;div&#62;</code> or <code>&#60;address&#62;</code>.
5205         *
5206         * @method getTextBlockElements
5207         * @return {Object} Name/value lookup map for block elements.
5208         */
5209        const getTextBlockElements = constant(textBlockElementsMap);
5210        /**
5211         * Returns a map of inline text format nodes. For example: <code>&#60;strong&#62;</code>, <code>&#60;span&#62;</code> or <code>&#60;ins&#62;</code>.
5212         *
5213         * @method getTextInlineElements
5214         * @return {Object} Name/value lookup map for text format elements.
5215         */
5216        const getTextInlineElements = constant(textInlineElementsMap);
5217        /**
5218         * Returns a map with void elements. For example: <code>&#60;br&#62;</code> or <code>&#60;img&#62;</code>.
5219         *
5220         * @method getVoidElements
5221         * @return {Object} Name/value lookup map for void elements.
5222         */
5223        const getVoidElements = constant(Object.seal(voidElementsMap));
5224        /**
5225         * Returns a map with self closing tags. For example: <code>&#60;li&#62;</code>.
5226         *
5227         * @method getSelfClosingElements
5228         * @return {Object} Name/value lookup map for self closing tags elements.
5229         */
5230        const getSelfClosingElements = constant(selfClosingElementsMap);
5231        /**
5232         * Returns a map with elements that should be treated as contents regardless if it has text
5233         * content in them or not. For example: <code>&#60;td&#62;</code>, <code>&#60;video&#62;</code> or <code>&#60;img&#62;</code>.
5234         *
5235         * @method getNonEmptyElements
5236         * @return {Object} Name/value lookup map for non empty elements.
5237         */
5238        const getNonEmptyElements = constant(nonEmptyElementsMap);
5239        /**
5240         * Returns a map with elements that the caret should be moved in front of after enter is
5241         * pressed.
5242         *
5243         * @method getMoveCaretBeforeOnEnterElements
5244         * @return {Object} Name/value lookup map for elements to place the caret in front of.
5245         */
5246        const getMoveCaretBeforeOnEnterElements = constant(moveCaretBeforeOnEnterElementsMap);
5247        /**
5248         * Returns a map with elements where white space is to be preserved. For example: <code>&#60;pre&#62;</code> or <code>&#60;script&#62;</code>.
5249         *
5250         * @method getWhitespaceElements
5251         * @return {Object} Name/value lookup map for white space elements.
5252         */
5253        const getWhitespaceElements = constant(whitespaceElementsMap);
5254        /**
5255         * Returns a map with elements that should be treated as transparent.
5256         *
5257         * @method getTransparentElements
5258         * @return {Object} Name/value lookup map for special elements.
5259         */
5260        const getTransparentElements = constant(transparentElementsMap);
5261        const getWrapBlockElements = constant(wrapBlockElementsMap);
5262        /**
5263         * Returns a map with special elements. These are elements that needs to be parsed
5264         * in a special way such as script, style, textarea etc. The map object values
5265         * are regexps used to find the end of the element.
5266         *
5267         * @method getSpecialElements
5268         * @return {Object} Name/value lookup map for special elements.
5269         */
5270        const getSpecialElements = constant(Object.seal(specialElements));
5271        /**
5272         * Returns true/false if the specified element and it's child is valid or not
5273         * according to the schema.
5274         *
5275         * @method isValidChild
5276         * @param {String} name Element name to check for.
5277         * @param {String} child Element child to verify.
5278         * @return {Boolean} True/false if the element is a valid child of the specified parent.
5279         */
5280        const isValidChild = (name, child) => {
5281            const parent = children[name.toLowerCase()];
5282            return !!(parent && parent[child.toLowerCase()]);
5283        };
5284        /**
5285         * Returns true/false if the specified element name and optional attribute is
5286         * valid according to the schema.
5287         *
5288         * @method isValid
5289         * @param {String} name Name of element to check.
5290         * @param {String} attr Optional attribute name to check for.
5291         * @return {Boolean} True/false if the element and attribute is valid.
5292         */
5293        const isValid = (name, attr) => {
5294            const rule = getElementRule(name);
5295            // Check if it's a valid element
5296            if (rule) {
5297                if (attr) {
5298                    // Check if attribute name exists
5299                    if (rule.attributes[attr]) {
5300                        return true;
5301                    }
5302                    // Check if attribute matches a regexp pattern
5303                    const attrPatterns = rule.attributePatterns;
5304                    if (attrPatterns) {
5305                        let i = attrPatterns.length;
5306                        while (i--) {
5307                            if (attrPatterns[i].pattern.test(attr)) {
5308                                return true;
5309                            }
5310                        }
5311                    }
5312                }
5313                else {
5314                    return true;
5315                }
5316            }
5317            // No match
5318            return false;
5319        };
5320        const isBlock = (name) => has$2(getBlockElements(), name);
5321        // Check if name starts with # to detect non-element node names like #text and #comment
5322        const isInline = (name) => !startsWith(name, '#') && isValid(name) && !isBlock(name);
5323        const isWrapper = (name) => has$2(getWrapBlockElements(), name) || isInline(name);
5324        /**
5325         * Returns true/false if the specified element is valid or not
5326         * according to the schema.
5327         *
5328         * @method getElementRule
5329         * @param {String} name Element name to check for.
5330         * @return {Object} Element object or undefined if the element isn't valid.
5331         */
5332        /**
5333         * Returns an map object of all custom elements.
5334         *
5335         * @method getCustomElements
5336         * @return {Object} Name/value map object of all custom elements.
5337         */
5338        const getCustomElements = constant(customElementsMap);
5339        /**
5340         * Parses a valid elements string and adds it to the schema. The valid elements
5341         * format is for example <code>element[attr=default|otherattr]</code>.
5342         * Existing rules will be replaced with the ones specified, so this extends the schema.
5343         *
5344         * @method addValidElements
5345         * @param {String} valid_elements String in the valid elements format to be parsed.
5346         */
5347        /**
5348         * Parses a valid elements string and sets it to the schema. The valid elements
5349         * format is for example <code>element[attr=default|otherattr]</code>.
5350         * Existing rules will be replaced with the ones specified, so this extends the schema.
5351         *
5352         * @method setValidElements
5353         * @param {String} valid_elements String in the valid elements format to be parsed.
5354         */
5355        /**
5356         * Adds custom non-HTML elements to the schema. For more information about adding custom elements see:
5357         * <a href="https://www.tiny.cloud/docs/tinymce/latest/content-filtering/#custom_elements">custom_elements</a>
5358         *
5359         * @method addCustomElements
5360         * @param {String/Object} custom_elements Comma separated list or record of custom elements to add.
5361         */
5362        /**
5363         * Parses a valid children string and adds them to the schema structure. The valid children
5364         * format is for example <code>element[child1|child2]</code>.
5365         *
5366         * @method addValidChildren
5367         * @param {String} valid_children Valid children elements string to parse
5368         */
5369        /**
5370         * Returns an object of all custom elements that have component URLs.
5371         *
5372         * @method getComponentUrls
5373         * @return {Object} Object with where key is the component and the value is the url for that component.
5374         */
5375        const getComponentUrls = constant(componentUrls);
5376        setup();
5377        return {
5378            type: schemaType,
5379            children,
5380            elements,
5381            getValidStyles,
5382            getValidClasses,
5383            getBlockElements,
5384            getInvalidStyles,
5385            getVoidElements,
5386            getTextBlockElements,
5387            getTextInlineElements,
5388            getBoolAttrs,
5389            getElementRule,
5390            getSelfClosingElements,
5391            getNonEmptyElements,
5392            getMoveCaretBeforeOnEnterElements,
5393            getWhitespaceElements,
5394            getTransparentElements,
5395            getSpecialElements,
5396            getComponentUrls,
5397            isValidChild,
5398            isValid,
5399            isBlock,
5400            isInline,
5401            isWrapper,
5402            getCustomElements,
5403            addValidElements,
5404            setValidElements,
5405            addCustomElements,
5406            addValidChildren,
5407        };
5408    };
5409
5410    const hexColour = (value) => ({
5411        value: normalizeHex(value)
5412    });
5413    const normalizeHex = (hex) => removeLeading(hex, '#').toUpperCase();
5414    const toHex = (component) => {
5415        const hex = component.toString(16);
5416        return (hex.length === 1 ? '0' + hex : hex).toUpperCase();
5417    };
5418    const fromRgba = (rgbaColour) => {
5419        const value = toHex(rgbaColour.red) + toHex(rgbaColour.green) + toHex(rgbaColour.blue);
5420        return hexColour(value);
5421    };
5422
5423    const rgbRegex = /^\s*rgb\s*\(\s*(\d+)\s*[,\s]\s*(\d+)\s*[,\s]\s*(\d+)\s*\)\s*$/i;
5424    // This regex will match rgba(0, 0, 0, 0.5) or rgba(0, 0, 0, 50%) , or without commas
5425    const rgbaRegex = /^\s*rgba\s*\(\s*(\d+)\s*[,\s]\s*(\d+)\s*[,\s]\s*(\d+)\s*[,\s]\s*((?:\d?\.\d+|\d+)%?)\s*\)\s*$/i;
5426    const rgbaColour = (red, green, blue, alpha) => ({
5427        red,
5428        green,
5429        blue,
5430        alpha
5431    });
5432    const fromStringValues = (red, green, blue, alpha) => {
5433        const r = parseInt(red, 10);
5434        const g = parseInt(green, 10);
5435        const b = parseInt(blue, 10);
5436        const a = parseFloat(alpha);
5437        return rgbaColour(r, g, b, a);
5438    };
5439    const getColorFormat = (colorString) => {
5440        if (rgbRegex.test(colorString)) {
5441            return 'rgb';
5442        }
5443        else if (rgbaRegex.test(colorString)) {
5444            return 'rgba';
5445        }
5446        return 'other';
5447    };
5448    const fromString = (rgbaString) => {
5449        const rgbMatch = rgbRegex.exec(rgbaString);
5450        if (rgbMatch !== null) {
5451            return Optional.some(fromStringValues(rgbMatch[1], rgbMatch[2], rgbMatch[3], '1'));
5452        }
5453        const rgbaMatch = rgbaRegex.exec(rgbaString);
5454        if (rgbaMatch !== null) {
5455            return Optional.some(fromStringValues(rgbaMatch[1], rgbaMatch[2], rgbaMatch[3], rgbaMatch[4]));
5456        }
5457        return Optional.none();
5458    };
5459    const toString = (rgba) => `rgba(${rgba.red},${rgba.green},${rgba.blue},${rgba.alpha})`;
5460
5461    const rgbaToHexString = (color) => fromString(color)
5462        .map(fromRgba)
5463        .map((h) => '#' + h.value)
5464        .getOr(color);
5465
5466    /**
5467     * This class is used to parse CSS styles. It also compresses styles to reduce the output size.
5468     *
5469     * @class tinymce.html.Styles
5470     * @version 3.4
5471     * @example
5472     * const Styles = tinymce.html.Styles({
5473     *   url_converter: (url) => {
5474     *     return url;
5475     *   }
5476     * });
5477     *
5478     * styles = Styles.parse('border: 1px solid red');
5479     * styles.color = 'red';
5480     *
5481     * console.log(tinymce.html.Styles().serialize(styles));
5482     */
5483    const Styles = (settings = {}, schema) => {
5484        /* jshint maxlen:255 */
5485        /* eslint max-len:0 */
5486        const urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi;
5487        const styleRegExp = /\s*([^:]+):\s*([^;]+);?/g;
5488        const trimRightRegExp = /\s+$/;
5489        const encodingLookup = {};
5490        let validStyles;
5491        let invalidStyles;
5492        const invisibleChar = zeroWidth;
5493        if (schema) {
5494            validStyles = schema.getValidStyles();
5495            invalidStyles = schema.getInvalidStyles();
5496        }
5497        const encodingItems = (`\\" \\' \\; \\: ; : ` + invisibleChar).split(' ');
5498        for (let i = 0; i < encodingItems.length; i++) {
5499            encodingLookup[encodingItems[i]] = invisibleChar + i;
5500            encodingLookup[invisibleChar + i] = encodingItems[i];
5501        }
5502        // eslint-disable-next-line consistent-this
5503        const self = {
5504            /**
5505             * Parses the specified style value into an object collection. This parser will also
5506             * merge and remove any redundant items that browsers might have added. URLs inside
5507             * the styles will also be converted to absolute/relative based on the settings.
5508             *
5509             * @method parse
5510             * @param {String} css Style value to parse. For example: `border:1px solid red;`
5511             * @return {Object} Object representation of that style. For example: `{ border: '1px solid red' }`
5512             */
5513            parse: (css) => {
5514                const styles = {};
5515                let isEncoded = false;
5516                const urlConverter = settings.url_converter;
5517                const urlConverterScope = settings.url_converter_scope || self;
5518                const compress = (prefix, suffix, noJoin) => {
5519                    const top = styles[prefix + '-top' + suffix];
5520                    if (!top) {
5521                        return;
5522                    }
5523                    const right = styles[prefix + '-right' + suffix];
5524                    if (!right) {
5525                        return;
5526                    }
5527                    const bottom = styles[prefix + '-bottom' + suffix];
5528                    if (!bottom) {
5529                        return;
5530                    }
5531                    const left = styles[prefix + '-left' + suffix];
5532                    if (!left) {
5533                        return;
5534                    }
5535                    const box = [top, right, bottom, left];
5536                    let i = box.length - 1;
5537                    while (i--) {
5538                        if (box[i] !== box[i + 1]) {
5539                            break;
5540                        }
5541                    }
5542                    if (i > -1 && noJoin) {
5543                        return;
5544                    }
5545                    styles[prefix + suffix] = i === -1 ? box[0] : box.join(' ');
5546                    delete styles[prefix + '-top' + suffix];
5547                    delete styles[prefix + '-right' + suffix];
5548                    delete styles[prefix + '-bottom' + suffix];
5549                    delete styles[prefix + '-left' + suffix];
5550                };
5551                /**
5552                 * Checks if the specific style can be compressed in other words if all border-width are equal.
5553                 */
5554                const canCompress = (key) => {
5555                    const value = styles[key];
5556                    if (!value) {
5557                        return;
5558                    }
5559                    // Make sure not to split values like 'rgb(100, 50, 100);
5560                    const values = value.indexOf(',') > -1 ? [value] : value.split(' ');
5561                    let i = values.length;
5562                    while (i--) {
5563                        if (values[i] !== values[0]) {
5564                            return false;
5565                        }
5566                    }
5567                    styles[key] = values[0];
5568                    return true;
5569                };
5570                /**
5571                 * Compresses multiple styles into one style.
5572                 */
5573                const compress2 = (target, a, b, c) => {
5574                    if (!canCompress(a)) {
5575                        return;
5576                    }
5577                    if (!canCompress(b)) {
5578                        return;
5579                    }
5580                    if (!canCompress(c)) {
5581                        return;
5582                    }
5583                    // Compress
5584                    styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c];
5585                    delete styles[a];
5586                    delete styles[b];
5587                    delete styles[c];
5588                };
5589                // Encodes the specified string by replacing all \" \' ; : with _<num>
5590                const encode = (str) => {
5591                    isEncoded = true;
5592                    return encodingLookup[str];
5593                };
5594                // Decodes the specified string by replacing all _<num> with it's original value \" \' etc
5595                // It will also decode the \" \' if keepSlashes is set to false or omitted
5596                const decode = (str, keepSlashes) => {
5597                    if (isEncoded) {
5598                        str = str.replace(/\uFEFF[0-9]/g, (str) => {
5599                            return encodingLookup[str];
5600                        });
5601                    }
5602                    if (!keepSlashes) {
5603                        str = str.replace(/\\([\'\";:])/g, '$1');
5604                    }
5605                    return str;
5606                };
5607                const decodeSingleHexSequence = (escSeq) => {
5608                    return String.fromCharCode(parseInt(escSeq.slice(1), 16));
5609                };
5610                const decodeHexSequences = (value) => {
5611                    return value.replace(/\\[0-9a-f]+/gi, decodeSingleHexSequence);
5612                };
5613                const processUrl = (match, url, url2, url3, str, str2) => {
5614                    str = str || str2;
5615                    if (str) {
5616                        str = decode(str);
5617                        // Force strings into single quote format
5618                        return `'` + str.replace(/\'/g, `\\'`) + `'`;
5619                    }
5620                    url = decode(url || url2 || url3 || '');
5621                    if (!settings.allow_script_urls) {
5622                        const scriptUrl = url.replace(/[\s\r\n]+/g, '');
5623                        if (/(java|vb)script:/i.test(scriptUrl)) {
5624                            return '';
5625                        }
5626                        if (!settings.allow_svg_data_urls && /^data:image\/svg/i.test(scriptUrl)) {
5627                            return '';
5628                        }
5629                    }
5630                    // Convert the URL to relative/absolute depending on config
5631                    if (urlConverter) {
5632                        url = urlConverter.call(urlConverterScope, url, 'style');
5633                    }
5634                    // Output new URL format
5635                    return `url('` + url.replace(/\'/g, `\\'`) + `')`;
5636                };
5637                if (css) {
5638                    css = css.replace(/[\u0000-\u001F]/g, '');
5639                    // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing
5640                    css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, (str) => {
5641                        return str.replace(/[;:]/g, encode);
5642                    });
5643                    // Parse styles
5644                    let matches;
5645                    while ((matches = styleRegExp.exec(css))) {
5646                        styleRegExp.lastIndex = matches.index + matches[0].length;
5647                        let name = matches[1].replace(trimRightRegExp, '')
vendor: 1,511 bytes, lines 5647-5668
5647.toLowerCase();
5648                        let value = matches[2].replace(trimRightRegExp, '');
5649                        if (name && value) {
5650                            // Decode escaped sequences like \65 -> e
5651                            name = decodeHexSequences(name);
5652                            value = decodeHexSequences(value);
5653                            // Skip properties with double quotes and sequences like \" \' in their names
5654                            // See 'mXSS Attacks: Attacking well-secured Web-Applications by using innerHTML Mutations'
5655                            // https://cure53.de/fp170.pdf
5656                            if (name.indexOf(invisibleChar) !== -1 || name.indexOf('"') !== -1) {
5657                                continue;
5658                            }
5659                            // Don't allow behavior name or expression/comments within the values
5660                            if (!settings.allow_script_urls && (name === 'behavior' || /expression\s*\(|\/\*|\*\//.test(value))) {
5661                                continue;
5662                            }
5663                            // Opera will produce 700 instead of bold in their style values
5664                            if (name === 'font-weight' && value === '700') {
5665                                value = 'bold';
5666                            }
5667                            else if (name === 'color' || name === 'background-color') { // Lowercase colors like RED
5668                                value = value.toLowerCase()
vendor: 25,113 bytes, lines 5668-6221
5668;
5669                            }
5670                            // Convert RGB colors to HEX
5671                            if (getColorFormat(value) === 'rgb') {
5672                                fromString(value).each((rgba) => {
5673                                    value = rgbaToHexString(toString(rgba)).toLowerCase();
5674                                });
5675                            }
5676                            // Convert URLs and force them into url('value') format
5677                            value = value.replace(urlOrStrRegExp, processUrl);
5678                            styles[name] = isEncoded ? decode(value, true) : value;
5679                        }
5680                    }
5681                    // Compress the styles to reduce it's size for example IE will expand styles
5682                    compress('border', '', true);
5683                    compress('border', '-width');
5684                    compress('border', '-color');
5685                    compress('border', '-style');
5686                    compress('padding', '');
5687                    compress('margin', '');
5688                    if (!/(#.* rgb(a?)\(.*)|(rgb(a?)\(.*\) )/.test(styles['border-color'])) {
5689                        compress2('border', 'border-width', 'border-style', 'border-color');
5690                    }
5691                    // Remove pointless border, IE produces these
5692                    if (styles.border === 'medium none') {
5693                        delete styles.border;
5694                    }
5695                    // IE 11 will produce a border-image: none when getting the style attribute from <p style="border: 1px solid red"></p>
5696                    // So let us assume it shouldn't be there
5697                    if (styles['border-image'] === 'none') {
5698                        delete styles['border-image'];
5699                    }
5700                }
5701                return styles;
5702            },
5703            /**
5704             * Serializes the specified style object into a string.
5705             *
5706             * @method serialize
5707             * @param {Object} styles Object to serialize as string. For example: `{ border: '1px solid red' }`
5708             * @param {String} elementName Optional element name, if specified only the styles that matches the schema will be serialized.
5709             * @return {String} String representation of the style object. For example: `border: 1px solid red`
5710             */
5711            serialize: (styles, elementName) => {
5712                let css = '';
5713                const serializeStyles = (elemName, validStyleList) => {
5714                    const styleList = validStyleList[elemName];
5715                    if (styleList) {
5716                        for (let i = 0, l = styleList.length; i < l; i++) {
5717                            const name = styleList[i];
5718                            const value = styles[name];
5719                            if (value) {
5720                                css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
5721                            }
5722                        }
5723                    }
5724                };
5725                const isValid = (name, elemName) => {
5726                    if (!invalidStyles || !elemName) {
5727                        return true;
5728                    }
5729                    let styleMap = invalidStyles['*'];
5730                    if (styleMap && styleMap[name]) {
5731                        return false;
5732                    }
5733                    styleMap = invalidStyles[elemName];
5734                    return !(styleMap && styleMap[name]);
5735                };
5736                // Serialize styles according to schema
5737                if (elementName && validStyles) {
5738                    // Serialize global styles and element specific styles
5739                    serializeStyles('*', validStyles);
5740                    serializeStyles(elementName, validStyles);
5741                }
5742                else {
5743                    // Output the styles in the order they are inside the object
5744                    each$d(styles, (value, name) => {
5745                        if (value && isValid(name, elementName)) {
5746                            css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
5747                        }
5748                    });
5749                }
5750                return css;
5751            }
5752        };
5753        return self;
5754    };
5755
5756    // Note: The values here aren't used. This is just used as a hash map to see if the key exists
5757    const deprecated = {
5758        keyLocation: true,
5759        layerX: true,
5760        layerY: true,
5761        returnValue: true,
5762        webkitMovementX: true,
5763        webkitMovementY: true,
5764        keyIdentifier: true,
5765        mozPressure: true,
5766        mozInputSource: true,
5767    };
5768    // Note: We can't rely on `instanceof` here as it won't work if the event was fired from another window.
5769    // Additionally, the constructor name might be `MouseEvent` or similar so we can't rely on the constructor name.
5770    const isNativeEvent = (event) => event instanceof Event || isFunction(event.initEvent);
5771    // Checks if it is our own isDefaultPrevented function
5772    const hasIsDefaultPrevented = (event) => event.isDefaultPrevented === always || event.isDefaultPrevented === never;
5773    // An event needs normalizing if it doesn't have the prevent default function or if it's a native event
5774    const needsNormalizing = (event) => isNullable(event.preventDefault) || isNativeEvent(event);
5775    const clone$2 = (originalEvent, data) => {
5776        const event = data ?? {};
5777        // Copy all properties from the original event
5778        for (const name in originalEvent) {
5779            // Some properties are deprecated and produces a warning so don't include them
5780            if (!has$2(deprecated, name)) {
5781                event[name] = originalEvent[name];
5782            }
5783        }
5784        // The composed path can't be cloned, so delegate instead
5785        if (isNonNullable(originalEvent.composedPath)) {
5786            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
5787            event.composedPath = () => originalEvent.composedPath();
5788        }
5789        // The getModifierState won't work when cloned, so delegate instead
5790        if (isNonNullable(originalEvent.getModifierState)) {
5791            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
5792            event.getModifierState = (keyArg) => originalEvent.getModifierState(keyArg);
5793        }
5794        // The getTargetRanges won't work when cloned, so delegate instead
5795        if (isNonNullable(originalEvent.getTargetRanges)) {
5796            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
5797            event.getTargetRanges = () => originalEvent.getTargetRanges();
5798        }
5799        return event;
5800    };
5801    const normalize$3 = (type, originalEvent, fallbackTarget, data) => {
5802        const event = clone$2(originalEvent, data);
5803        event.type = type;
5804        // Normalize target IE uses srcElement
5805        if (isNullable(event.target)) {
5806            event.target = event.srcElement ?? fallbackTarget;
5807        }
5808        if (needsNormalizing(originalEvent)) {
5809            // Add preventDefault method
5810            event.preventDefault = () => {
5811                event.defaultPrevented = true;
5812                event.isDefaultPrevented = always;
5813                // Execute preventDefault on the original event object
5814                if (isFunction(originalEvent.preventDefault)) {
5815                    originalEvent.preventDefault();
5816                }
5817            };
5818            // Add stopPropagation
5819            event.stopPropagation = () => {
5820                event.cancelBubble = true;
5821                event.isPropagationStopped = always;
5822                // Execute stopPropagation on the original event object
5823                if (isFunction(originalEvent.stopPropagation)) {
5824                    originalEvent.stopPropagation();
5825                }
5826            };
5827            // Add stopImmediatePropagation
5828            event.stopImmediatePropagation = () => {
5829                event.isImmediatePropagationStopped = always;
5830                event.stopPropagation();
5831            };
5832            // Add event delegation states
5833            if (!hasIsDefaultPrevented(event)) {
5834                event.isDefaultPrevented = event.defaultPrevented === true ? always : never;
5835                event.isPropagationStopped = event.cancelBubble === true ? always : never;
5836                event.isImmediatePropagationStopped = never;
5837            }
5838        }
5839        return event;
5840    };
5841
5842    /**
5843     * This class wraps the browsers native event logic with more convenient methods.
5844     *
5845     * @class tinymce.dom.EventUtils
5846     */
5847    const eventExpandoPrefix = 'mce-data-';
5848    const mouseEventRe = /^(?:mouse|contextmenu)|click/;
5849    /**
5850     * Binds a native event to a callback on the speified target.
5851     */
5852    const addEvent = (target, name, callback, capture) => {
5853        target.addEventListener(name, callback, capture || false);
5854    };
5855    /**
5856     * Unbinds a native event callback on the specified target.
5857     */
5858    const removeEvent = (target, name, callback, capture) => {
5859        target.removeEventListener(name, callback, capture || false);
5860    };
5861    const isMouseEvent = (event) => isNonNullable(event) && mouseEventRe.test(event.type);
5862    /**
5863     * Normalizes a native event object or just adds the event specific methods on a custom event.
5864     */
5865    const fix = (originalEvent, data) => {
5866        const event = normalize$3(originalEvent.type, originalEvent, document, data);
5867        // Calculate pageX/Y if missing and clientX/Y available
5868        if (isMouseEvent(originalEvent) && isUndefined(originalEvent.pageX) && !isUndefined(originalEvent.clientX)) {
5869            const eventDoc = event.target.ownerDocument || document;
5870            const doc = eventDoc.documentElement;
5871            const body = eventDoc.body;
5872            const mouseEvent = event;
5873            mouseEvent.pageX = originalEvent.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) -
5874                (doc && doc.clientLeft || body && body.clientLeft || 0);
5875            mouseEvent.pageY = originalEvent.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) -
5876                (doc && doc.clientTop || body && body.clientTop || 0);
5877        }
5878        return event;
5879    };
5880    /**
5881     * Bind a DOMContentLoaded event across browsers and executes the callback once the page DOM is initialized.
5882     * It will also set/check the domLoaded state of the event_utils instance so ready isn't called multiple times.
5883     */
5884    const bindOnReady = (win, callback, eventUtils) => {
5885        const doc = win.document, event = { type: 'ready' };
5886        if (eventUtils.domLoaded) {
5887            callback(event);
5888            return;
5889        }
5890        const isDocReady = () => {
5891            // Check complete or interactive state if there is a body
5892            // element on some iframes IE 8 will produce a null body
5893            return doc.readyState === 'complete' || (doc.readyState === 'interactive' && doc.body);
5894        };
5895        // Gets called when the DOM is ready
5896        const readyHandler = () => {
5897            removeEvent(win, 'DOMContentLoaded', readyHandler);
5898            removeEvent(win, 'load', readyHandler);
5899            if (!eventUtils.domLoaded) {
5900                eventUtils.domLoaded = true;
5901                callback(event);
5902            }
5903            // Clean memory for IE
5904            win = null;
5905        };
5906        if (isDocReady()) {
5907            readyHandler();
5908        }
5909        else {
5910            addEvent(win, 'DOMContentLoaded', readyHandler);
5911        }
5912        // Fallback if any of the above methods should fail for some odd reason
5913        if (!eventUtils.domLoaded) {
5914            addEvent(win, 'load', readyHandler);
5915        }
5916    };
5917    /**
5918     * This class enables you to bind/unbind native events to elements and normalize it's behavior across browsers.
5919     */
5920    class EventUtils {
5921        static Event = new EventUtils();
5922        // State if the DOMContentLoaded was executed or not
5923        domLoaded = false;
5924        events = {};
5925        expando;
5926        hasFocusIn;
5927        count = 1;
5928        constructor() {
5929            this.expando = eventExpandoPrefix + (+new Date()).toString(32);
5930            this.hasFocusIn = 'onfocusin' in document.documentElement;
5931            this.count = 1;
5932        }
5933        bind(target, names, callback, scope) {
5934            const self = this;
5935            let callbackList;
5936            const win = window;
5937            // Native event handler function patches the event and executes the callbacks for the expando
5938            const defaultNativeHandler = (evt) => {
5939                self.executeHandlers(fix(evt || win.event), id);
5940            };
5941            // Don't bind to text nodes or comments
5942            if (!target || isText$b(target) || isComment(target)) {
5943                return callback;
5944            }
5945            // Create or get events id for the target
5946            let id;
5947            if (!target[self.expando]) {
5948                id = self.count++;
5949                target[self.expando] = id;
5950                self.events[id] = {};
5951            }
5952            else {
5953                id = target[self.expando];
5954            }
5955            // Setup the specified scope or use the target as a default
5956            scope = scope || target;
5957            // Split names and bind each event, enables you to bind multiple events with one call
5958            const namesList = names.split(' ');
5959            let i = namesList.length;
5960            while (i--) {
5961                let name = namesList[i];
5962                let nativeHandler = defaultNativeHandler;
5963                let capture = false;
5964                let fakeName = false;
5965                // Use ready instead of DOMContentLoaded
5966                if (name === 'DOMContentLoaded') {
5967                    name = 'ready';
5968                }
5969                // DOM is already ready
5970                if (self.domLoaded && name === 'ready' && target.readyState === 'complete') {
5971                    callback.call(scope, fix({ type: name }));
5972                    continue;
5973                }
5974                // Fake bubbling of focusin/focusout
5975                if (!self.hasFocusIn && (name === 'focusin' || name === 'focusout')) {
5976                    capture = true;
5977                    fakeName = name === 'focusin' ? 'focus' : 'blur';
5978                    nativeHandler = (evt) => {
5979                        const event = fix(evt || win.event);
5980                        event.type = event.type === 'focus' ? 'focusin' : 'focusout';
5981                        self.executeHandlers(event, id);
5982                    };
5983                }
5984                // Setup callback list and bind native event
5985                callbackList = self.events[id][name];
5986                if (!callbackList) {
5987                    self.events[id][name] = callbackList = [{ func: callback, scope }];
5988                    callbackList.fakeName = fakeName;
5989                    callbackList.capture = capture;
5990                    // callbackList.callback = callback;
5991                    // Add the nativeHandler to the callback list so that we can later unbind it
5992                    callbackList.nativeHandler = nativeHandler;
5993                    // Check if the target has native events support
5994                    if (name === 'ready') {
5995                        bindOnReady(target, nativeHandler, self);
5996                    }
5997                    else {
5998                        addEvent(target, fakeName || name, nativeHandler, capture);
5999                    }
6000                }
6001                else {
6002                    if (name === 'ready' && self.domLoaded) {
6003                        callback(fix({ type: name }));
6004                    }
6005                    else {
6006                        // If it already has an native handler then just push the callback
6007                        callbackList.push({ func: callback, scope });
6008                    }
6009                }
6010            }
6011            target = callbackList = null; // Clean memory for IE
6012            return callback;
6013        }
6014        unbind(target, names, callback) {
6015            // Don't bind to text nodes or comments
6016            if (!target || isText$b(target) || isComment(target)) {
6017                return this;
6018            }
6019            // Unbind event or events if the target has the expando
6020            const id = target[this.expando];
6021            if (id) {
6022                let eventMap = this.events[id];
6023                // Specific callback
6024                if (names) {
6025                    const namesList = names.split(' ');
6026                    let i = namesList.length;
6027                    while (i--) {
6028                        const name = namesList[i];
6029                        const callbackList = eventMap[name];
6030                        // Unbind the event if it exists in the map
6031                        if (callbackList) {
6032                            // Remove specified callback
6033                            if (callback) {
6034                                let ci = callbackList.length;
6035                                while (ci--) {
6036                                    if (callbackList[ci].func === callback) {
6037                                        const nativeHandler = callbackList.nativeHandler;
6038                                        const fakeName = callbackList.fakeName, capture = callbackList.capture;
6039                                        // Clone callbackList since unbind inside a callback would otherwise break the handlers loop
6040                                        const newCallbackList = callbackList.slice(0, ci).concat(callbackList.slice(ci + 1));
6041                                        newCallbackList.nativeHandler = nativeHandler;
6042                                        newCallbackList.fakeName = fakeName;
6043                                        newCallbackList.capture = capture;
6044                                        eventMap[name] = newCallbackList;
6045                                    }
6046                                }
6047                            }
6048                            // Remove all callbacks if there isn't a specified callback or there is no callbacks left
6049                            if (!callback || callbackList.length === 0) {
6050                                delete eventMap[name];
6051                                removeEvent(target, callbackList.fakeName || name, callbackList.nativeHandler, callbackList.capture);
6052                            }
6053                        }
6054                    }
6055                }
6056                else {
6057                    // All events for a specific element
6058                    each$d(eventMap, (callbackList, name) => {
6059                        removeEvent(target, callbackList.fakeName || name, callbackList.nativeHandler, callbackList.capture);
6060                    });
6061                    eventMap = {};
6062                }
6063                // Check if object is empty, if it isn't then we won't remove the expando map
6064                for (const name in eventMap) {
6065                    if (has$2(eventMap, name)) {
6066                        return this;
6067                    }
6068                }
6069                // Delete event object
6070                delete this.events[id];
6071                // Remove expando from target
6072                try {
6073                    // IE will fail here since it can't delete properties from window
6074                    delete target[this.expando];
6075                }
6076                catch {
6077                    // IE will set it to null
6078                    target[this.expando] = null;
6079                }
6080            }
6081            return this;
6082        }
6083        /**
6084         * Fires the specified event on the specified target.
6085         * <br>
6086         * <em>Deprecated in TinyMCE 6.0 and has been marked for removal in TinyMCE 7.0. Use <code>dispatch</code> instead.</em>
6087         *
6088         * @method fire
6089         * @param {Object} target Target node/window or custom object.
6090         * @param {String} name Event name to fire.
6091         * @param {Object} args Optional arguments to send to the observers.
6092         * @return {EventUtils} Event utils instance.
6093         * @deprecated Use dispatch() instead
6094         */
6095        fire(target, name, args) {
6096            return this.dispatch(target, name, args);
6097        }
6098        /**
6099         * Dispatches the specified event on the specified target.
6100         *
6101         * @method dispatch
6102         * @param {Node/window} target Target node/window or custom object.
6103         * @param {String} name Event name to dispatch.
6104         * @param {Object} args Optional arguments to send to the observers.
6105         * @return {EventUtils} Event utils instance.
6106         */
6107        dispatch(target, name, args) {
6108            // Don't bind to text nodes or comments
6109            if (!target || isText$b(target) || isComment(target)) {
6110                return this;
6111            }
6112            // Build event object by patching the args
6113            const event = fix({ type: name, target }, args);
6114            do {
6115                // Found an expando that means there is listeners to execute
6116                const id = target[this.expando];
6117                if (id) {
6118                    this.executeHandlers(event, id);
6119                }
6120                // Walk up the DOM
6121                target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow;
6122            } while (target && !event.isPropagationStopped());
6123            return this;
6124        }
6125        /**
6126         * Removes all bound event listeners for the specified target. This will also remove any bound
6127         * listeners to child nodes within that target.
6128         *
6129         * @method clean
6130         * @param {Object} target Target node/window object.
6131         * @return {EventUtils} Event utils instance.
6132         */
6133        clean(target) {
6134            // Don't bind to text nodes or comments
6135            if (!target || isText$b(target) || isComment(target)) {
6136                return this;
6137            }
6138            // Unbind any element on the specified target
6139            if (target[this.expando]) {
6140                this.unbind(target);
6141            }
6142            // Target doesn't have getElementsByTagName it's probably a window object then use it's document to find the children
6143            if (!target.getElementsByTagName) {
6144                target = target.document;
6145            }
6146            // Remove events from each child element
6147            if (target && target.getElementsByTagName) {
6148                this.unbind(target);
6149                const children = target.getElementsByTagName('*');
6150                let i = children.length;
6151                while (i--) {
6152                    target = children[i];
6153                    if (target[this.expando]) {
6154                        this.unbind(target);
6155                    }
6156                }
6157            }
6158            return this;
6159        }
6160        /**
6161         * Destroys the event object. Call this to remove memory leaks.
6162         */
6163        destroy() {
6164            this.events = {};
6165        }
6166        // Legacy function for canceling events
6167        cancel(e) {
6168            if (e) {
6169                e.preventDefault();
6170                e.stopImmediatePropagation();
6171            }
6172            return false;
6173        }
6174        /**
6175         * Executes all event handler callbacks for a specific event.
6176         *
6177         * @private
6178         * @param {Event} evt Event object.
6179         * @param {String} id Expando id value to look for.
6180         */
6181        executeHandlers(evt, id) {
6182            const container = this.events[id];
6183            const callbackList = container && container[evt.type];
6184            if (callbackList) {
6185                for (let i = 0, l = callbackList.length; i < l; i++) {
6186                    const callback = callbackList[i];
6187                    // Check if callback exists might be removed if a unbind is called inside the callback
6188                    if (callback && callback.func.call(callback.scope, evt) === false) {
6189                        evt.preventDefault();
6190                    }
6191                    // Should we stop propagation to immediate listeners
6192                    if (evt.isImmediatePropagationStopped()) {
6193                        return;
6194                    }
6195                }
6196            }
6197        }
6198    }
6199
6200    /**
6201     * Utility class for various DOM manipulation and retrieval functions.
6202     *
6203     * @class tinymce.dom.DOMUtils
6204     * @example
6205     * // Add a class to an element by id in the page
6206     * tinymce.DOM.addClass('someid', 'someclass');
6207     *
6208     * // Add a class to an element by id inside the editor
6209     * tinymce.activeEditor.dom.addClass('someid', 'someclass');
6210     */
6211    // Shorten names
6212    const each$a = Tools.each;
6213    const grep = Tools.grep;
6214    const internalStyleName = 'data-mce-style';
6215    const numericalCssMap = Tools.makeMap('fill-opacity font-weight line-height opacity orphans widows z-index zoom', ' ');
6216    const legacySetAttribute = (elm, name, value) => {
6217        if (isNullable(value) || value === '') {
6218            remove$9(elm, name);
6219        }
6220        else {
6221            set$
vendor: 20,552 bytes, lines 6221-6740
62214(elm, name, value);
6222        }
6223    };
6224    // Convert camel cased names back to hyphenated names
6225    const camelCaseToHyphens = (name) => name.replace(/[A-Z]/g, (v) => '-' + v.toLowerCase());
6226    const findNodeIndex = (node, normalized) => {
6227        let idx = 0;
6228        if (node) {
6229            for (let lastNodeType = node.nodeType, tempNode = node.previousSibling; tempNode; tempNode = tempNode.previousSibling) {
6230                const nodeType = tempNode.nodeType;
6231                // Normalize text nodes
6232                if (normalized && isText$b(tempNode)) {
6233                    if (nodeType === lastNodeType || !tempNode.data.length) {
6234                        continue;
6235                    }
6236                }
6237                idx++;
6238                lastNodeType = nodeType;
6239            }
6240        }
6241        return idx;
6242    };
6243    const updateInternalStyleAttr = (styles, elm) => {
6244        const rawValue = get$9(elm, 'style');
6245        const value = styles.serialize(styles.parse(rawValue), name(elm));
6246        legacySetAttribute(elm, internalStyleName, value);
6247    };
6248    const convertStyleToString = (cssValue, cssName) => {
6249        if (isNumber(cssValue)) {
6250            return has$2(numericalCssMap, cssName) ? cssValue + '' : cssValue + 'px';
6251        }
6252        else {
6253            return cssValue;
6254        }
6255    };
6256    const applyStyle$1 = ($elm, cssName, cssValue) => {
6257        const normalizedName = camelCaseToHyphens(cssName);
6258        if (isNullable(cssValue) || cssValue === '') {
6259            remove$7($elm, normalizedName);
6260        }
6261        else {
6262            set$2($elm, normalizedName, convertStyleToString(cssValue, normalizedName));
6263        }
6264    };
6265    const setupAttrHooks = (styles, settings, getContext) => {
6266        const keepValues = settings.keep_values;
6267        const keepUrlHook = {
6268            set: (elm, value, name) => {
6269                const sugarElm = SugarElement.fromDom(elm);
6270                if (isFunction(settings.url_converter) && isNonNullable(value)) {
6271                    value = settings.url_converter.call(settings.url_converter_scope || getContext(), String(value), name, elm);
6272                }
6273                const internalName = 'data-mce-' + name;
6274                legacySetAttribute(sugarElm, internalName, value);
6275                legacySetAttribute(sugarElm, name, value);
6276            },
6277            get: (elm, name) => {
6278                const sugarElm = SugarElement.fromDom(elm);
6279                return get$9(sugarElm, 'data-mce-' + name) || get$9(sugarElm, name);
6280            }
6281        };
6282        const attrHooks = {
6283            style: {
6284                set: (elm, value) => {
6285                    const sugarElm = SugarElement.fromDom(elm);
6286                    if (keepValues) {
6287                        legacySetAttribute(sugarElm, internalStyleName, value);
6288                    }
6289                    remove$9(sugarElm, 'style');
6290                    // If setting a style then delegate to the css api, otherwise
6291                    // this will cause issues when using a content security policy
6292                    if (isString(value)) {
6293                        setAll(sugarElm, styles.parse(value));
6294                    }
6295                },
6296                get: (elm) => {
6297                    const sugarElm = SugarElement.fromDom(elm);
6298                    const value = get$9(sugarElm, internalStyleName) || get$9(sugarElm, 'style');
6299                    return styles.serialize(styles.parse(value), name(sugarElm));
6300                }
6301            }
6302        };
6303        if (keepValues) {
6304            attrHooks.href = attrHooks.src = keepUrlHook;
6305        }
6306        return attrHooks;
6307    };
6308    /**
6309     * Constructs a new DOMUtils instance. Consult the TinyMCE Documentation for more details on settings etc for this class.
6310     *
6311     * @private
6312     * @constructor
6313     * @method DOMUtils
6314     * @param {Document} doc Document reference to bind the utility class to.
6315     * @param {settings} settings Optional settings collection.
6316     */
6317    const DOMUtils = (doc, settings = {}) => {
6318        const addedStyles = {};
6319        const win = window;
6320        const files = {};
6321        let counter = 0;
6322        const stdMode = true;
6323        const boxModel = true;
6324        const styleSheetLoader = instance.forElement(SugarElement.fromDom(doc), {
6325            contentCssCors: settings.contentCssCors,
6326            referrerPolicy: settings.referrerPolicy,
6327            crossOrigin: (url) => {
6328                const crossOrigin = settings.crossOrigin;
6329                if (isFunction(crossOrigin)) {
6330                    return crossOrigin(url, 'stylesheet');
6331                }
6332                else {
6333                    return undefined;
6334                }
6335            }
6336        });
6337        const boundEvents = [];
6338        const schema = settings.schema ? settings.schema : Schema({});
6339        const styles = Styles({
6340            url_converter: settings.url_converter,
6341            url_converter_scope: settings.url_converter_scope,
6342        }, settings.schema);
6343        const events = settings.ownEvents ? new EventUtils() : EventUtils.Event;
6344        const blockElementsMap = schema.getBlockElements();
6345        /**
6346         * Returns true/false if the specified element is a block element or not.
6347         *
6348         * @method isBlock
6349         * @param {Node/String} node Element/Node to check.
6350         * @return {Boolean} True/False state if the node is a block element or not.
6351         */
6352        const isBlock = (node) => {
6353            if (isString(node)) {
6354                return has$2(blockElementsMap, node);
6355            }
6356            else {
6357                return isElement$7(node) && (has$2(blockElementsMap, node.nodeName) || isTransparentBlock(schema, node));
6358            }
6359        };
6360        const get = (elm) => elm && doc && isString(elm)
6361            ? doc.getElementById(elm)
6362            : elm;
6363        const _get = (elm) => {
6364            const value = get(elm);
6365            return isNonNullable(value) ? SugarElement.fromDom(value) : null;
6366        };
6367        const getAttrib = (elm, name, defaultVal = '') => {
6368            let value;
6369            const $elm = _get(elm);
6370            if (isNonNullable($elm) && isElement$8($elm)) {
6371                const hook = attrHooks[name];
6372                if (hook && hook.get) {
6373                    value = hook.get($elm.dom, name);
6374                }
6375                else {
6376                    value = get$9($elm, name);
6377                }
6378            }
6379            return isNonNullable(value) ? value : defaultVal;
6380        };
6381        const getAttribs = (elm) => {
6382            const node = get(elm);
6383            return isNullable(node) ? [] : node.attributes;
6384        };
6385        const setAttrib = (elm, name, value) => {
6386            run(elm, (e) => {
6387                if (isElement$7(e)) {
6388                    const $elm = SugarElement.fromDom(e);
6389                    const val = value === '' ? null : value;
6390                    const originalValue = get$9($elm, name);
6391                    const hook = attrHooks[name];
6392                    if (hook && hook.set) {
6393                        hook.set($elm.dom, val, name);
6394                    }
6395                    else {
6396                        legacySetAttribute($elm, name, val);
6397                    }
6398                    if (originalValue !== val && settings.onSetAttrib) {
6399                        settings.onSetAttrib({
6400                            attrElm: $elm.dom, // We lie here to not break backwards compatibility
6401                            attrName: name,
6402                            attrValue: val
6403                        });
6404                    }
6405                }
6406            });
6407        };
6408        const clone = (node, deep) => {
6409            return node.cloneNode(deep);
6410        };
6411        const getRoot = () => settings.root_element || doc.body;
6412        const getViewPort = (argWin) => {
6413            const vp = getBounds(argWin);
6414            // Returns viewport size excluding scrollbars
6415            return {
6416                x: vp.x,
6417                y: vp.y,
6418                w: vp.width,
6419                h: vp.height
6420            };
6421        };
6422        const getPos$1 = (elm, rootElm) => getPos(doc.body, get(elm), rootElm);
6423        const setStyle = (elm, name, value) => {
6424            run(elm, (e) => {
6425                const $elm = SugarElement.fromDom(e);
6426                applyStyle$1($elm, name, value);
6427                if (settings.update_styles) {
6428                    updateInternalStyleAttr(styles, $elm);
6429                }
6430            });
6431        };
6432        const setStyles = (elm, stylesArg) => {
6433            run(elm, (e) => {
6434                const $elm = SugarElement.fromDom(e);
6435                each$d(stylesArg, (v, n) => {
6436                    applyStyle$1($elm, n, v);
6437                });
6438                if (settings.update_styles) {
6439                    updateInternalStyleAttr(styles, $elm);
6440                }
6441            });
6442        };
6443        const getStyle = (elm, name, computed) => {
6444            const $elm = get(elm);
6445            if (isNullable($elm) || (!isHTMLElement($elm) && !isSVGElement($elm))) {
6446                return undefined;
6447            }
6448            if (computed) {
6449                return get$7(SugarElement.fromDom($elm), camelCaseToHyphens(name));
6450            }
6451            else {
6452                // Camelcase it, if needed
6453                name = name.replace(/-(\D)/g, (a, b) => b.toUpperCase());
6454                if (name === 'float') {
6455                    name = 'cssFloat';
6456                }
6457                return $elm.style ? $elm.style[name] : undefined;
6458            }
6459        };
6460        const getSize = (elm) => {
6461            const $elm = get(elm);
6462            if (!$elm) {
6463                return { w: 0, h: 0 };
6464            }
6465            let w = getStyle($elm, 'width');
6466            let h = getStyle($elm, 'height');
6467            // Non pixel value, then force offset/clientWidth
6468            if (!w || w.indexOf('px') === -1) {
6469                w = '0';
6470            }
6471            // Non pixel value, then force offset/clientWidth
6472            if (!h || h.indexOf('px') === -1) {
6473                h = '0';
6474            }
6475            return {
6476                w: parseInt(w, 10) || $elm.offsetWidth || $elm.clientWidth,
6477                h: parseInt(h, 10) || $elm.offsetHeight || $elm.clientHeight
6478            };
6479        };
6480        const getRect = (elm) => {
6481            const $elm = get(elm);
6482            const pos = getPos$1($elm);
6483            const size = getSize($elm);
6484            return {
6485                x: pos.x, y: pos.y,
6486                w: size.w, h: size.h
6487            };
6488        };
6489        const is = (elm, selector) => {
6490            if (!elm) {
6491                return false;
6492            }
6493            const elms = isArray$1(elm) ? elm : [elm];
6494            return exists(elms, (e) => {
6495                return is$2(SugarElement.fromDom(e), selector);
6496            });
6497        };
6498        const getParents = (elm, selector, root, collect) => {
6499            const result = [];
6500            let node = get(elm);
6501            collect = collect === undefined;
6502            // Default root on inline mode
6503            const resolvedRoot = root || (getRoot().nodeName !== 'BODY' ? getRoot().parentNode : null);
6504            // Wrap node name as func
6505            if (isString(selector)) {
6506                if (selector === '*') {
6507                    selector = isElement$7;
6508                }
6509                else {
6510                    const selectorVal = selector;
6511                    selector = (node) => is(node, selectorVal);
6512                }
6513            }
6514            while (node) {
6515                // TODO: Remove nullable check once TINY-6599 is complete
6516                if (node === resolvedRoot || isNullable(node.nodeType) || isDocument$1(node) || isDocumentFragment(node)) {
6517                    break;
6518                }
6519                if (!selector || selector(node)) {
6520                    if (collect) {
6521                        result.push(node);
6522                    }
6523                    else {
6524                        return [node];
6525                    }
6526                }
6527                node = node.parentNode;
6528            }
6529            return collect ? result : null;
6530        };
6531        const getParent = (node, selector, root) => {
6532            const parents = getParents(node, selector, root, false);
6533            return parents && parents.length > 0 ? parents[0] : null;
6534        };
6535        const _findSib = (node, selector, name) => {
6536            let func = selector;
6537            if (node) {
6538                // If expression make a function of it using is
6539                if (isString(selector)) {
6540                    func = (node) => {
6541                        return is(node, selector);
6542                    };
6543                }
6544                // Loop all siblings
6545                for (let tempNode = node[name]; tempNode; tempNode = tempNode[name]) {
6546                    if (isFunction(func) && func(tempNode)) {
6547                        return tempNode;
6548                    }
6549                }
6550            }
6551            return null;
6552        };
6553        const getNext = (node, selector) => _findSib(node, selector, 'nextSibling');
6554        const getPrev = (node, selector) => _findSib(node, selector, 'previousSibling');
6555        const isParentNode = (node) => isFunction(node.querySelectorAll);
6556        const select = (selector, scope) => {
6557            const elm = get(scope) ?? settings.root_element ?? doc;
6558            return isParentNode(elm) ? from(elm.querySelectorAll(selector)) : [];
6559        };
6560        const run = function (elm, func, scope) {
6561            const context = scope ?? this;
6562            if (isArray$1(elm)) {
6563                const result = [];
6564                each$a(elm, (e, i) => {
6565                    const node = get(e);
6566                    if (node) {
6567                        result.push(func.call(context, node, i));
6568                    }
6569                });
6570                return result;
6571            }
6572            else {
6573                const node = get(elm);
6574                return !node ? false : func.call(context, node);
6575            }
6576        };
6577        const setAttribs = (elm, attrs) => {
6578            run(elm, ($elm) => {
6579                each$d(attrs, (value, name) => {
6580                    setAttrib($elm, name, value);
6581                });
6582            });
6583        };
6584        const setHTML = (elm, html) => {
6585            run(elm, (e) => {
6586                const $elm = SugarElement.fromDom(e);
6587                set$3($elm, html);
6588            });
6589        };
6590        const add = (parentElm, name, attrs, html, create) => run(parentElm, (parentElm) => {
6591            const newElm = isString(name) ? doc.createElement(name) : name;
6592            if (isNonNullable(attrs)) {
6593                setAttribs(newElm, attrs);
6594            }
6595            if (html) {
6596                if (!isString(html) && html.nodeType) {
6597                    newElm.appendChild(html);
6598                }
6599                else if (isString(html)) {
6600                    setHTML(newElm, html);
6601                }
6602            }
6603            return !create ? parentElm.appendChild(newElm) : newElm;
6604        });
6605        const create = (name, attrs, html) => add(doc.createElement(name), name, attrs, html, true);
6606        const decode = Entities.decode;
6607        const encode = Entities.encodeAllRaw;
6608        const createHTML = (name, attrs, html = '') => {
6609            let outHtml = '<' + name;
6610            for (const key in attrs) {
6611                if (hasNonNullableKey(attrs, key)) {
6612                    outHtml += ' ' + key + '="' + encode(attrs[key]) + '"';
6613                }
6614            }
6615            if (isEmpty$5(html) && has$2(schema.getVoidElements(), name)) {
6616                return outHtml + ' />';
6617            }
6618            else {
6619                return outHtml + '>' + html + '</' + name + '>';
6620            }
6621        };
6622        const createFragment = (html) => {
6623            const container = doc.createElement('div');
6624            const frag = doc.createDocumentFragment();
6625            // Append the container to the fragment so as to remove it from
6626            // the current document context
6627            frag.appendChild(container);
6628            if (html) {
6629                container.innerHTML = html;
6630            }
6631            let node;
6632            while ((node = container.firstChild)) {
6633                frag.appendChild(node);
6634            }
6635            // Remove the container now that all the children have been transferred
6636            frag.removeChild(container);
6637            return frag;
6638        };
6639        const remove = (node, keepChildren) => {
6640            return run(node, (n) => {
6641                const $node = SugarElement.fromDom(n);
6642                if (keepChildren) {
6643                    // Unwrap but don't keep any empty text nodes
6644                    each$e(children$1($node), (child) => {
6645                        if (isText$c(child) && child.dom.length === 0) {
6646                            remove$8(child);
6647                        }
6648                        else {
6649                            before$4($node, child);
6650                        }
6651                    });
6652                }
6653                remove$8($node);
6654                return $node.dom;
6655            });
6656        };
6657        const removeAllAttribs = (e) => run(e, (e) => {
6658            const attrs = e.attributes;
6659            for (let i = attrs.length - 1; i >= 0; i--) {
6660                e.removeAttributeNode(attrs.item(i));
6661            }
6662        });
6663        const parseStyle = (cssText) => styles.parse(cssText);
6664        const serializeStyle = (stylesArg, name) => styles.serialize(stylesArg, name);
6665        const addStyle = (cssText) => {
6666            // Prevent inline from loading the same styles twice
6667            if (self !== DOMUtils.DOM && doc === document) {
6668                if (addedStyles[cssText]) {
6669                    return;
6670                }
6671                addedStyles[cssText] = true;
6672            }
6673            // Create style element if needed
6674            let styleElm = doc.getElementById('mceDefaultStyles');
6675            if (!styleElm) {
6676                styleElm = doc.createElement('style');
6677                styleElm.id = 'mceDefaultStyles';
6678                styleElm.type = 'text/css';
6679                const head = doc.head;
6680                if (head.firstChild) {
6681                    head.insertBefore(styleElm, head.firstChild);
6682                }
6683                else {
6684                    head.appendChild(styleElm);
6685                }
6686            }
6687            // Append style data to old or new style element
6688            if (styleElm.styleSheet) {
6689                styleElm.styleSheet.cssText += cssText;
6690            }
6691            else {
6692                styleElm.appendChild(doc.createTextNode(cssText));
6693            }
6694        };
6695        const loadCSS = (urls) => {
6696            if (!urls) {
6697                urls = '';
6698            }
6699            each$e(urls.split(','), (url) => {
6700                files[url] = true;
6701                styleSheetLoader.load(url).catch(noop);
6702            });
6703        };
6704        const toggleClass = (elm, cls, state) => {
6705            run(elm, (e) => {
6706                if (isElement$7(e)) {
6707                    const $elm = SugarElement.fromDom(e);
6708                    // TINY-4520: DomQuery used to handle specifying multiple classes and the
6709                    // formatter relies on it due to the changes made for TINY-7227
6710                    const classes = cls.split(' ');
6711                    each$e(classes, (c) => {
6712                        if (isNonNullable(state)) {
6713                            const fn = state ? add$2 : remove$4;
6714                            fn($elm, c);
6715                        }
6716                        else {
6717                            toggle$1($elm, c);
6718                        }
6719                    });
6720                }
6721            });
6722        };
6723        const addClass = (elm, cls) => {
6724            toggleClass(elm, cls, true);
6725        };
6726        const removeClass = (elm, cls) => {
6727            toggleClass(elm, cls, false);
6728        };
6729        const hasClass = (elm, cls) => {
6730            const $elm = _get(elm);
6731            // TINY-4520: DomQuery used to handle specifying multiple classes and the
6732            // formatter relies on it due to the changes made for TINY-7227
6733            const classes = cls.split(' ');
6734            return isNonNullable($elm) && forall(classes, (c) => has($elm, c));
6735        };
6736        const show = (elm) => {
6737            run(elm, (e) => remove$7(SugarElement.fromDom(e), 'display'));
6738        };
6739        const hide = (elm) => {
6740            run(elm, (e) => set$2
vendor: 48,653 bytes, lines 6740-7796
6740(SugarElement.fromDom(e), 'display', 'none'));
6741        };
6742        const isHidden = (elm) => {
6743            const $elm = _get(elm);
6744            return isNonNullable($elm) && is$4(getRaw$1($elm, 'display'), 'none');
6745        };
6746        const uniqueId = (prefix) => (!prefix ? 'mce_' : prefix) + (counter++);
6747        const getOuterHTML = (elm) => {
6748            const $elm = _get(elm);
6749            if (isNonNullable($elm)) {
6750                return isElement$7($elm.dom) ? $elm.dom.outerHTML : getOuter($elm);
6751            }
6752            else {
6753                return '';
6754            }
6755        };
6756        const setOuterHTML = (elm, html) => {
6757            run(elm, ($elm) => {
6758                if (isElement$7($elm)) {
6759                    $elm.outerHTML = html;
6760                }
6761            });
6762        };
6763        const insertAfter = (node, reference) => {
6764            const referenceNode = get(reference);
6765            return run(node, (node) => {
6766                const parent = referenceNode?.parentNode;
6767                const nextSibling = referenceNode?.nextSibling;
6768                if (parent) {
6769                    if (nextSibling) {
6770                        parent.insertBefore(node, nextSibling);
6771                    }
6772                    else {
6773                        parent.appendChild(node);
6774                    }
6775                }
6776                return node;
6777            });
6778        };
6779        const replace = (newElm, oldElm, keepChildren) => run(oldElm, (elm) => {
6780            const replacee = isArray$1(oldElm) ? newElm.cloneNode(true) : newElm;
6781            if (keepChildren) {
6782                each$a(grep(elm.childNodes), (node) => {
6783                    replacee.appendChild(node);
6784                });
6785            }
6786            elm.parentNode?.replaceChild(replacee, elm);
6787            return elm;
6788        });
6789        const rename = (elm, name) => {
6790            if (elm.nodeName !== name.toUpperCase()) {
6791                // Rename block element
6792                const newElm = create(name);
6793                // Copy attribs to new block
6794                each$a(getAttribs(elm), (attrNode) => {
6795                    setAttrib(newElm, attrNode.nodeName, getAttrib(elm, attrNode.nodeName));
6796                });
6797                // Replace block
6798                replace(newElm, elm, true);
6799                return newElm;
6800            }
6801            else {
6802                return elm;
6803            }
6804        };
6805        const findCommonAncestor = (a, b) => {
6806            let ps = a;
6807            while (ps) {
6808                let pe = b;
6809                while (pe && ps !== pe) {
6810                    pe = pe.parentNode;
6811                }
6812                if (ps === pe) {
6813                    break;
6814                }
6815                ps = ps.parentNode;
6816            }
6817            if (!ps && a.ownerDocument) {
6818                return a.ownerDocument.documentElement;
6819            }
6820            else {
6821                return ps;
6822            }
6823        };
6824        const isEmpty = (node, elements, options) => {
6825            if (isPlainObject(elements)) {
6826                const isContent = (node) => {
6827                    const name = node.nodeName.toLowerCase();
6828                    return Boolean(elements[name]);
6829                };
6830                return isEmptyNode(schema, node, { ...options, isContent });
6831            }
6832            else {
6833                return isEmptyNode(schema, node, options);
6834            }
6835        };
6836        const createRng = () => doc.createRange();
6837        const split = (parentElm, splitElm, replacementElm) => {
6838            let range = createRng();
6839            let beforeFragment;
6840            let afterFragment;
6841            if (parentElm && splitElm && parentElm.parentNode && splitElm.parentNode) {
6842                const parentNode = parentElm.parentNode;
6843                // Get before chunk
6844                range.setStart(parentNode, findNodeIndex(parentElm));
6845                range.setEnd(splitElm.parentNode, findNodeIndex(splitElm));
6846                beforeFragment = range.extractContents();
6847                // Get after chunk
6848                range = createRng();
6849                range.setStart(splitElm.parentNode, findNodeIndex(splitElm) + 1);
6850                range.setEnd(parentNode, findNodeIndex(parentElm) + 1);
6851                afterFragment = range.extractContents();
6852                // Insert before chunk
6853                parentNode.insertBefore(trimNode(self, beforeFragment, schema), parentElm);
6854                // Insert middle chunk
6855                if (replacementElm) {
6856                    parentNode.insertBefore(replacementElm, parentElm);
6857                    // pa.replaceChild(replacementElm, splitElm);
6858                }
6859                else {
6860                    parentNode.insertBefore(splitElm, parentElm);
6861                }
6862                // Insert after chunk
6863                parentNode.insertBefore(trimNode(self, afterFragment, schema), parentElm);
6864                remove(parentElm);
6865                return replacementElm || splitElm;
6866            }
6867            else {
6868                return undefined;
6869            }
6870        };
6871        const bind = (target, name, func, scope) => {
6872            if (isArray$1(target)) {
6873                let i = target.length;
6874                const rv = [];
6875                while (i--) {
6876                    rv[i] = bind(target[i], name, func, scope);
6877                }
6878                return rv;
6879            }
6880            else {
6881                // Collect all window/document events bound by editor instance
6882                if (settings.collect && (target === doc || target === win)) {
6883                    boundEvents.push([target, name, func, scope]);
6884                }
6885                return events.bind(target, name, func, scope || self);
6886            }
6887        };
6888        const unbind = (target, name, func) => {
6889            if (isArray$1(target)) {
6890                let i = target.length;
6891                const rv = [];
6892                while (i--) {
6893                    rv[i] = unbind(target[i], name, func);
6894                }
6895                return rv;
6896            }
6897            else {
6898                // Remove any bound events matching the input
6899                if (boundEvents.length > 0 && (target === doc || target === win)) {
6900                    let i = boundEvents.length;
6901                    while (i--) {
6902                        const [boundTarget, boundName, boundFunc] = boundEvents[i];
6903                        if (target === boundTarget && (!name || name === boundName) && (!func || func === boundFunc)) {
6904                            events.unbind(boundTarget, boundName, boundFunc);
6905                        }
6906                    }
6907                }
6908                return events.unbind(target, name, func);
6909            }
6910        };
6911        const dispatch = (target, name, evt) => events.dispatch(target, name, evt);
6912        const fire = (target, name, evt) => events.dispatch(target, name, evt);
6913        const getContentEditable = (node) => {
6914            if (node && isHTMLElement(node)) {
6915                // Check for fake content editable
6916                const contentEditable = node.getAttribute('data-mce-contenteditable');
6917                if (contentEditable && contentEditable !== 'inherit') {
6918                    return contentEditable;
6919                }
6920                // Check for real content editable
6921                return node.contentEditable !== 'inherit' ? node.contentEditable : null;
6922            }
6923            else {
6924                return null;
6925            }
6926        };
6927        const getContentEditableParent = (node) => {
6928            const root = getRoot();
6929            let state = null;
6930            for (let tempNode = node; tempNode && tempNode !== root; tempNode = tempNode.parentNode) {
6931                state = getContentEditable(tempNode);
6932                if (state !== null) {
6933                    break;
6934                }
6935            }
6936            return state;
6937        };
6938        const isEditable = (node) => {
6939            if (isNonNullable(node)) {
6940                const scope = isElement$7(node) ? node : node.parentElement;
6941                return isNonNullable(scope) && isHTMLElement(scope) && isEditable$2(SugarElement.fromDom(scope));
6942            }
6943            else {
6944                return false;
6945            }
6946        };
6947        const destroy = () => {
6948            // Unbind all events bound to window/document by editor instance
6949            if (boundEvents.length > 0) {
6950                let i = boundEvents.length;
6951                while (i--) {
6952                    const [boundTarget, boundName, boundFunc] = boundEvents[i];
6953                    events.unbind(boundTarget, boundName, boundFunc);
6954                }
6955            }
6956            // Remove CSS files added to the dom
6957            each$d(files, (_, url) => {
6958                styleSheetLoader.unload(url);
6959                delete files[url];
6960            });
6961        };
6962        const isChildOf = (node, parent) => {
6963            return node === parent || parent.contains(node);
6964        };
6965        const dumpRng = (r) => ('startContainer: ' + r.startContainer.nodeName +
6966            ', startOffset: ' + r.startOffset +
6967            ', endContainer: ' + r.endContainer.nodeName +
6968            ', endOffset: ' + r.endOffset);
6969        // eslint-disable-next-line consistent-this
6970        const self = {
6971            doc,
6972            settings,
6973            win,
6974            files,
6975            stdMode,
6976            boxModel,
6977            styleSheetLoader,
6978            boundEvents,
6979            styles,
6980            schema,
6981            events,
6982            isBlock: isBlock,
6983            root: null,
6984            clone,
6985            /**
6986             * Returns the root node of the document. This is normally the body but might be a DIV. Parents like getParent will not
6987             * go above the point of this root node.
6988             *
6989             * @method getRoot
6990             * @return {Element} Root element for the utility class.
6991             */
6992            getRoot,
6993            /**
6994             * Returns the viewport of the window.
6995             *
6996             * @method getViewPort
6997             * @param {Window} win Optional window to get viewport of.
6998             * @return {Object} Viewport object with fields x, y, w and h.
6999             */
7000            getViewPort,
7001            /**
7002             * Returns the rectangle for a specific element.
7003             *
7004             * @method getRect
7005             * @param {Element/String} elm Element object or element ID to get rectangle from.
7006             * @return {Object} Rectangle for specified element object with x, y, w, h fields.
7007             */
7008            getRect,
7009            /**
7010             * Returns the size dimensions of the specified element.
7011             *
7012             * @method getSize
7013             * @param {Element/String} elm Element object or element ID to get rectangle from.
7014             * @return {Object} Rectangle for specified element object with w, h fields.
7015             */
7016            getSize,
7017            /**
7018             * Returns a node by the specified selector function. This function will
7019             * loop through all parent nodes and call the specified function for each node.
7020             * If the function then returns true indicating that it has found what it was looking for, the loop execution will then end
7021             * and the node it found will be returned.
7022             *
7023             * @method getParent
7024             * @param {Node/String} node DOM node to search parents on or ID string.
7025             * @param {Function} selector Selection function or CSS selector to execute on each node.
7026             * @param {Node} root Optional root element, never go beyond this point.
7027             * @return {Node} DOM Node or null if it wasn't found.
7028             */
7029            getParent,
7030            /**
7031             * Returns a node list of all parents matching the specified selector function or pattern.
7032             * If the function then returns true indicating that it has found what it was looking for and that node will be collected.
7033             *
7034             * @method getParents
7035             * @param {Node/String} node DOM node to search parents on or ID string.
7036             * @param {Function} selector Selection function to execute on each node or CSS pattern.
7037             * @param {Node} root Optional root element, never go beyond this point.
7038             * @return {Array} Array of nodes or null if it wasn't found.
7039             */
7040            getParents: getParents,
7041            /**
7042             * Returns the specified element by ID or the input element if it isn't a string.
7043             *
7044             * @method get
7045             * @param {String/Element} n Element id to look for or element to just pass though.
7046             * @return {Element} Element matching the specified id or null if it wasn't found.
7047             */
7048            get,
7049            /**
7050             * Returns the next node that matches selector or function
7051             *
7052             * @method getNext
7053             * @param {Node} node Node to find siblings from.
7054             * @param {String/function} selector Selector CSS expression or function.
7055             * @return {Node} Next node item matching the selector or null if it wasn't found.
7056             */
7057            getNext,
7058            /**
7059             * Returns the previous node that matches selector or function
7060             *
7061             * @method getPrev
7062             * @param {Node} node Node to find siblings from.
7063             * @param {String/function} selector Selector CSS expression or function.
7064             * @return {Node} Previous node item matching the selector or null if it wasn't found.
7065             */
7066            getPrev,
7067            // #ifndef jquery
7068            /**
7069             * Returns a list of the elements specified by the given CSS selector. For example: `div#a1 p.test`
7070             *
7071             * @method select
7072             * @param {String} selector Target CSS selector.
7073             * @param {Object} scope Optional root element/scope element to search in.
7074             * @return {Array} Array with all matched elements.
7075             * @example
7076             * // Adds a class to all paragraphs in the currently active editor
7077             * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'someclass');
7078             *
7079             * // Adds a class to all spans that have the test class in the currently active editor
7080             * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('span.test'), 'someclass')
7081             */
7082            select,
7083            /**
7084             * Returns true/false if the specified element matches the specified css pattern.
7085             *
7086             * @method is
7087             * @param {Node/NodeList} elm DOM node to match or an array of nodes to match.
7088             * @param {String} selector CSS pattern to match the element against.
7089             */
7090            is,
7091            // #endif
7092            /**
7093             * Adds the specified element to another element or elements.
7094             *
7095             * @method add
7096             * @param {String/Element/Array} parentElm Element id string, DOM node element or array of ids or elements to add to.
7097             * @param {String/Element} name Name of new element to add or existing element to add.
7098             * @param {Object} attrs Optional object collection with arguments to add to the new element(s).
7099             * @param {String} html Optional inner HTML contents to add for each element.
7100             * @param {Boolean} create Optional flag if the element should be created or added.
7101             * @return {Element/Array} Element that got created, or an array of created elements if multiple input elements
7102             * were passed in.
7103             * @example
7104             * // Adds a new paragraph to the end of the active editor
7105             * tinymce.activeEditor.dom.add(tinymce.activeEditor.getBody(), 'p', { title: 'my title' }, 'Some content');
7106             */
7107            add,
7108            /**
7109             * Creates a new element.
7110             *
7111             * @method create
7112             * @param {String} name Name of new element.
7113             * @param {Object} attrs Optional object name/value collection with element attributes.
7114             * @param {String} html Optional HTML string to set as inner HTML of the element.
7115             * @return {Element} HTML DOM node element that got created.
7116             * @example
7117             * // Adds an element where the caret/selection is in the active editor
7118             * var el = tinymce.activeEditor.dom.create('div', { id: 'test', 'class': 'myclass' }, 'some content');
7119             * tinymce.activeEditor.selection.setNode(el);
7120             */
7121            create,
7122            /**
7123             * Creates HTML string for element. The element will be closed unless an empty inner HTML string is passed in.
7124             *
7125             * @method createHTML
7126             * @param {String} name Name of new element.
7127             * @param {Object} attrs Optional object name/value collection with element attributes.
7128             * @param {String} html Optional HTML string to set as inner HTML of the element.
7129             * @return {String} String with new HTML element, for example: <a href="#">test</a>.
7130             * @example
7131             * // Creates a html chunk and inserts it at the current selection/caret location
7132             * tinymce.activeEditor.insertContent(tinymce.activeEditor.dom.createHTML('a', { href: 'test.html' }, 'some line'));
7133             */
7134            createHTML,
7135            /**
7136             * Creates a document fragment out of the specified HTML string.
7137             *
7138             * @method createFragment
7139             * @param {String} html Html string to create fragment from.
7140             * @return {DocumentFragment} Document fragment node.
7141             */
7142            createFragment,
7143            /**
7144             * Removes/deletes the specified element(s) from the DOM.
7145             *
7146             * @method remove
7147             * @param {String/Element/Array} node ID of element or DOM element object or array containing multiple elements/ids.
7148             * @param {Boolean} keepChildren Optional state to keep children or not. If set to true all children will be
7149             * placed at the location of the removed element.
7150             * @return {Element/Array} HTML DOM element that got removed, or an array of removed elements if multiple input elements
7151             * were passed in.
7152             * @example
7153             * // Removes all paragraphs in the active editor
7154             * tinymce.activeEditor.dom.remove(tinymce.activeEditor.dom.select('p'));
7155             *
7156             * // Removes an element by id in the document
7157             * tinymce.DOM.remove('mydiv');
7158             */
7159            remove,
7160            /**
7161             * Sets the CSS style value on a HTML element. The name can be a camelcase string
7162             * or the CSS style name like background-color.
7163             *
7164             * @method setStyle
7165             * @param {String/Element/Array} elm HTML element/Array of elements to set CSS style value on.
7166             * @param {String} name Name of the style value to set.
7167             * @param {String} value Value to set on the style.
7168             * @example
7169             * // Sets a style value on all paragraphs in the currently active editor
7170             * tinymce.activeEditor.dom.setStyle(tinymce.activeEditor.dom.select('p'), 'background-color', 'red');
7171             *
7172             * // Sets a style value to an element by id in the current document
7173             * tinymce.DOM.setStyle('mydiv', 'background-color', 'red');
7174             */
7175            setStyle,
7176            /**
7177             * Returns the current style or runtime/computed value of an element.
7178             *
7179             * @method getStyle
7180             * @param {String/Element} elm HTML element or element id string to get style from.
7181             * @param {String} name Style name to return.
7182             * @param {Boolean} computed Computed style.
7183             * @return {String} Current style or computed style value of an element.
7184             */
7185            getStyle: getStyle,
7186            /**
7187             * Sets multiple styles on the specified element(s).
7188             *
7189             * @method setStyles
7190             * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set styles on.
7191             * @param {Object} styles Name/Value collection of style items to add to the element(s).
7192             * @example
7193             * // Sets styles on all paragraphs in the currently active editor
7194             * tinymce.activeEditor.dom.setStyles(tinymce.activeEditor.dom.select('p'), { 'background-color': 'red', 'color': 'green' });
7195             *
7196             * // Sets styles to an element by id in the current document
7197             * tinymce.DOM.setStyles('mydiv', { 'background-color': 'red', 'color': 'green' });
7198             */
7199            setStyles,
7200            /**
7201             * Removes all attributes from an element or elements.
7202             *
7203             * @method removeAllAttribs
7204             * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to remove attributes from.
7205             */
7206            removeAllAttribs,
7207            /**
7208             * Sets the specified attribute of an element or elements.
7209             *
7210             * @method setAttrib
7211             * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set attribute on.
7212             * @param {String} name Name of attribute to set.
7213             * @param {String} value Value to set on the attribute - if this value is falsy like null, 0 or '' it will remove
7214             * the attribute instead.
7215             * @example
7216             * // Sets class attribute on all paragraphs in the active editor
7217             * tinymce.activeEditor.dom.setAttrib(tinymce.activeEditor.dom.select('p'), 'class', 'myclass');
7218             *
7219             * // Sets class attribute on a specific element in the current page
7220             * tinymce.dom.setAttrib('mydiv', 'class', 'myclass');
7221             */
7222            setAttrib,
7223            /**
7224             * Sets two or more specified attributes of an element or elements.
7225             *
7226             * @method setAttribs
7227             * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set attributes on.
7228             * @param {Object} attrs Name/Value collection of attribute items to add to the element(s).
7229             * @example
7230             * // Sets class and title attributes on all paragraphs in the active editor
7231             * tinymce.activeEditor.dom.setAttribs(tinymce.activeEditor.dom.select('p'), { 'class': 'myclass', title: 'some title' });
7232             *
7233             * // Sets class and title attributes on a specific element in the current page
7234             * tinymce.DOM.setAttribs('mydiv', { 'class': 'myclass', title: 'some title' });
7235             */
7236            setAttribs,
7237            /**
7238             * Returns the specified attribute by name.
7239             *
7240             * @method getAttrib
7241             * @param {String/Element} elm Element string id or DOM element to get attribute from.
7242             * @param {String} name Name of attribute to get.
7243             * @param {String} defaultVal Optional default value to return if the attribute didn't exist.
7244             * @return {String} Attribute value string, default value or null if the attribute wasn't found.
7245             */
7246            getAttrib,
7247            /**
7248             * Returns the absolute x, y position of a node. The position will be returned in an object with x, y fields.
7249             *
7250             * @method getPos
7251             * @param {Element/String} elm HTML element or element id to get x, y position from.
7252             * @param {Element} rootElm Optional root element to stop calculations at.
7253             * @return {Object} Absolute position of the specified element object with x, y fields.
7254             */
7255            getPos: getPos$1,
7256            /**
7257             * Parses the specified style value into an object collection. This parser will also
7258             * merge and remove any redundant items that browsers might have added. It will also convert non-hex
7259             * colors to hex values. Urls inside the styles will also be converted to absolute/relative based on settings.
7260             *
7261             * @method parseStyle
7262             * @param {String} cssText Style value to parse, for example: border:1px solid red;.
7263             * @return {Object} Object representation of that style, for example: {border: '1px solid red'}
7264             */
7265            parseStyle,
7266            /**
7267             * Serializes the specified style object into a string.
7268             *
7269             * @method serializeStyle
7270             * @param {Object} styles Object to serialize as string, for example: {border: '1px solid red'}
7271             * @param {String} name Optional element name.
7272             * @return {String} String representation of the style object, for example: border: 1px solid red.
7273             */
7274            serializeStyle,
7275            /**
7276             * Adds a style element at the top of the document with the specified cssText content.
7277             *
7278             * @method addStyle
7279             * @param {String} cssText CSS Text style to add to top of head of document.
7280             */
7281            addStyle,
7282            /**
7283             * Imports/loads the specified CSS file into the document bound to the class.
7284             *
7285             * @method loadCSS
7286             * @param {String} url URL to CSS file to load.
7287             * @example
7288             * // Loads a CSS file dynamically into the current document
7289             * tinymce.DOM.loadCSS('somepath/some.css');
7290             *
7291             * // Loads a CSS file into the currently active editor instance
7292             * tinymce.activeEditor.dom.loadCSS('somepath/some.css');
7293             *
7294             * // Loads a CSS file into an editor instance by id
7295             * tinymce.get('someid').dom.loadCSS('somepath/some.css');
7296             *
7297             * // Loads multiple CSS files into the current document
7298             * tinymce.DOM.loadCSS('somepath/some.css,somepath/someother.css');
7299             */
7300            loadCSS,
7301            /**
7302             * Adds a class to the specified element or elements.
7303             *
7304             * @method addClass
7305             * @param {String/Element/Array} elm Element ID string or DOM element or array with elements or IDs.
7306             * @param {String} cls Class name to add to each element.
7307             * @return {String/Array} String with new class value or array with new class values for all elements.
7308             * @example
7309             * // Adds a class to all paragraphs in the active editor
7310             * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'myclass');
7311             *
7312             * // Adds a class to a specific element in the current page
7313             * tinymce.DOM.addClass('mydiv', 'myclass');
7314             */
7315            addClass,
7316            /**
7317             * Removes a class from the specified element or elements.
7318             *
7319             * @method removeClass
7320             * @param {String/Element/Array} elm Element ID string or DOM element or array with elements or IDs.
7321             * @param {String} cls Class name to remove from each element.
7322             * @return {String/Array} String of remaining class name(s), or an array of strings if multiple input elements
7323             * were passed in.
7324             * @example
7325             * // Removes a class from all paragraphs in the active editor
7326             * tinymce.activeEditor.dom.removeClass(tinymce.activeEditor.dom.select('p'), 'myclass');
7327             *
7328             * // Removes a class from a specific element in the current page
7329             * tinymce.DOM.removeClass('mydiv', 'myclass');
7330             */
7331            removeClass,
7332            /**
7333             * Returns true if the specified element has the specified class.
7334             *
7335             * @method hasClass
7336             * @param {String/Element} elm HTML element or element id string to check CSS class on.
7337             * @param {String} cls CSS class to check for.
7338             * @return {Boolean} true/false if the specified element has the specified class.
7339             */
7340            hasClass,
7341            /**
7342             * Toggles the specified class on/off.
7343             *
7344             * @method toggleClass
7345             * @param {Element} elm Element to toggle class on.
7346             * @param {String} cls Class to toggle on/off.
7347             * @param {Boolean} state Optional state to set.
7348             */
7349            toggleClass,
7350            /**
7351             * Shows the specified element(s) by ID by setting the "display" style.
7352             *
7353             * @method show
7354             * @param {String/Element/Array} elm ID of DOM element or DOM element or array with elements or IDs to show.
7355             */
7356            show,
7357            /**
7358             * Hides the specified element(s) by ID by setting the "display" style.
7359             *
7360             * @method hide
7361             * @param {String/Element/Array} elm ID of DOM element or DOM element or array with elements or IDs to hide.
7362             * @example
7363             * // Hides an element by id in the document
7364             * tinymce.DOM.hide('myid');
7365             */
7366            hide,
7367            /**
7368             * Returns true/false if the element is hidden or not by checking the "display" style.
7369             *
7370             * @method isHidden
7371             * @param {String/Element} elm Id or element to check display state on.
7372             * @return {Boolean} true/false if the element is hidden or not.
7373             */
7374            isHidden,
7375            /**
7376             * Returns a unique id. This can be useful when generating elements on the fly.
7377             * This method will not check if the element already exists.
7378             *
7379             * @method uniqueId
7380             * @param {String} prefix Optional prefix to add in front of all ids - defaults to "mce_".
7381             * @return {String} Unique id.
7382             */
7383            uniqueId,
7384            /**
7385             * Sets the specified HTML content inside the element or elements. The HTML will first be processed. This means
7386             * URLs will get converted, hex color values fixed etc. Check processHTML for details.
7387             *
7388             * @method setHTML
7389             * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set HTML inside of.
7390             * @param {String} html HTML content to set as inner HTML of the element.
7391             * @example
7392             * // Sets the inner HTML of all paragraphs in the active editor
7393             * tinymce.activeEditor.dom.setHTML(tinymce.activeEditor.dom.select('p'), 'some inner html');
7394             *
7395             * // Sets the inner HTML of an element by id in the document
7396             * tinymce.DOM.setHTML('mydiv', 'some inner html');
7397             */
7398            setHTML,
7399            /**
7400             * Returns the outer HTML of an element.
7401             *
7402             * @method getOuterHTML
7403             * @param {String/Element} elm Element ID or element object to get outer HTML from.
7404             * @return {String} Outer HTML string.
7405             * @example
7406             * tinymce.DOM.getOuterHTML(editorElement);
7407             * tinymce.activeEditor.getOuterHTML(tinymce.activeEditor.getBody());
7408             */
7409            getOuterHTML,
7410            /**
7411             * Sets the specified outer HTML on an element or elements.
7412             *
7413             * @method setOuterHTML
7414             * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set outer HTML on.
7415             * @param {Object} html HTML code to set as outer value for the element.
7416             * @example
7417             * // Sets the outer HTML of all paragraphs in the active editor
7418             * tinymce.activeEditor.dom.setOuterHTML(tinymce.activeEditor.dom.select('p'), '<div>some html</div>');
7419             *
7420             * // Sets the outer HTML of an element by id in the document
7421             * tinymce.DOM.setOuterHTML('mydiv', '<div>some html</div>');
7422             */
7423            setOuterHTML,
7424            /**
7425             * Entity decodes a string. This method decodes any HTML entities, such as `&amp;aring;`.
7426             *
7427             * @method decode
7428             * @param {String} s String to decode entities on.
7429             * @return {String} Entity decoded string.
7430             */
7431            decode,
7432            /**
7433             * Entity encodes a string. This method encodes the most common entities, such as `<`, `>`, `"` and `&`.
7434             *
7435             * @method encode
7436             * @param {String} text String to encode with entities.
7437             * @return {String} Entity encoded string.
7438             */
7439            encode,
7440            /**
7441             * Inserts an element after the reference element.
7442             *
7443             * @method insertAfter
7444             * @param {Element} node Element to insert after the reference.
7445             * @param {Element/String/Array} referenceNode Reference element, element id or array of elements to insert after.
7446             * @return {Element/Array} Element that got added or an array with elements.
7447             */
7448            insertAfter,
7449            /**
7450             * Replaces the specified element or elements with the new element specified. The new element will
7451             * be cloned if multiple input elements are passed in.
7452             *
7453             * @method replace
7454             * @param {Element} newElm New element to replace old ones with.
7455             * @param {Element/String/Array} oldElm Element DOM node, element id or array of elements or ids to replace.
7456             * @param {Boolean} keepChildren Optional keep children state, if set to true child nodes from the old object will be added
7457             * to new ones.
7458             */
7459            replace,
7460            /**
7461             * Renames the specified element and keeps its attributes and children.
7462             *
7463             * @method rename
7464             * @param {Element} elm Element to rename.
7465             * @param {String} name Name of the new element.
7466             * @return {Element} New element or the old element if it needed renaming.
7467             */
7468            rename,
7469            /**
7470             * Find the common ancestor of two elements. This is a shorter method than using the DOM Range logic.
7471             *
7472             * @method findCommonAncestor
7473             * @param {Element} a Element to find common ancestor of.
7474             * @param {Element} b Element to find common ancestor of.
7475             * @return {Element} Common ancestor element of the two input elements.
7476             */
7477            findCommonAncestor,
7478            /**
7479             * Executes the specified function on the element by id or dom element node or array of elements/id.
7480             *
7481             * @method run
7482             * @param {String/Element/Array} elm ID or DOM element object or array with ids or elements.
7483             * @param {Function} func Function to execute for each item.
7484             * @param {Object} scope Optional scope to execute the function in.
7485             * @return {Object/Array} Single object, or an array of objects if multiple input elements were passed in.
7486             */
7487            run,
7488            /**
7489             * Returns a NodeList with attributes for the element.
7490             *
7491             * @method getAttribs
7492             * @param {HTMLElement/string} elm Element node or string id to get attributes from.
7493             * @return {NodeList} NodeList with attributes.
7494             */
7495            getAttribs,
7496            /**
7497             * Returns true/false if the specified node is to be considered empty or not.
7498             *
7499             * @method isEmpty
7500             * @param {Node} node The target node to check if it's empty.
7501             * @param {Object} elements Optional name/value object with elements that are automatically treated as non-empty elements.
7502             * @return {Boolean} true/false if the node is empty or not.
7503             * @example
7504             * tinymce.DOM.isEmpty(node, { img: true });
7505             */
7506            isEmpty,
7507            /**
7508             * Creates a new DOM Range object. This will use the native DOM Range API if it's
7509             * available. If it's not, it will fall back to the custom TinyMCE implementation.
7510             *
7511             * @method createRng
7512             * @return {DOMRange} DOM Range object.
7513             * @example
7514             * const rng = tinymce.DOM.createRng();
7515             * alert(rng.startContainer + "," + rng.startOffset);
7516             */
7517            createRng,
7518            /**
7519             * Returns the index of the specified node within its parent.
7520             *
7521             * @method nodeIndex
7522             * @param {Node} node Node to look for.
7523             * @param {Boolean} normalized Optional true/false state if the index is what it would be after a normalization.
7524             * @return {Number} Index of the specified node.
7525             */
7526            nodeIndex: findNodeIndex,
7527            /**
7528             * Splits an element into two new elements and places the specified split
7529             * element or elements between the new ones. For example splitting the paragraph at the bold element in
7530             * this example `<p>abc<b>abc</b>123</p>` would produce `<p>abc</p><b>abc</b><p>123</p>`.
7531             *
7532             * @method split
7533             * @param {Element} parentElm Parent element to split.
7534             * @param {Element} splitElm Element to split at.
7535             * @param {Element} replacementElm Optional replacement element to replace the split element with.
7536             * @return {Element} Returns the split element or the replacement element if that is specified.
7537             */
7538            split,
7539            /**
7540             * Adds an event handler to the specified object.
7541             *
7542             * @method bind
7543             * @param {Element/Document/Window/Array} target Target element to bind events to.
7544             * handler to or an array of elements/ids/documents.
7545             * @param {String} name Name of event handler to add, for example: click.
7546             * @param {Function} func Function to execute when the event occurs.
7547             * @param {Object} scope Optional scope to execute the function in.
7548             * @return {Function} Function callback handler the same as the one passed in.
7549             */
7550            bind: bind,
7551            /**
7552             * Removes the specified event handler by name and function from an element or collection of elements.
7553             *
7554             * @method unbind
7555             * @param {Element/Document/Window/Array} target Target element to unbind events on.
7556             * @param {String} name Event handler name, for example: "click"
7557             * @param {Function} func Function to remove.
7558             * @return {Boolean/Array} Bool state of true if the handler was removed, or an array of states if multiple input elements
7559             * were passed in.
7560             */
7561            unbind: unbind,
7562            /**
7563             * Fires the specified event name and optional object on the specified target.
7564             * <br>
7565             * <em>Deprecated in TinyMCE 6.0 and has been marked for removal in TinyMCE 7.0. Use <code>dispatch</code> instead.</em>
7566             *
7567             * @method fire
7568             * @param {Node/Document/Window} target Target element or object to fire event on.
7569             * @param {String} name Event name to fire.
7570             * @param {Object} evt Event object to send.
7571             * @return {Event} Event object.
7572             * @deprecated Use dispatch() instead
7573             */
7574            fire,
7575            /**
7576             * Dispatches the specified event name and optional object on the specified target.
7577             *
7578             * @method dispatch
7579             * @param {Node/Document/Window} target Target element or object to dispatch event on.
7580             * @param {String} name Name of the event to fire.
7581             * @param {Object} evt Event object to send.
7582             * @return {Event} Event object.
7583             */
7584            dispatch,
7585            // Returns the content editable state of a node
7586            getContentEditable,
7587            getContentEditableParent,
7588            /**
7589             * Checks if the specified node is editable within the given context of its parents.
7590             *
7591             * @method isEditable
7592             * @param {Node} node Node to check if it's editable.
7593             * @return {Boolean} Will be true if the node is editable and false if it's not editable.
7594             */
7595            isEditable,
7596            /**
7597             * Destroys all internal references to the DOM to solve memory leak issues.
7598             *
7599             * @method destroy
7600             */
7601            destroy,
7602            isChildOf,
7603            dumpRng
7604        };
7605        const attrHooks = setupAttrHooks(styles, settings, constant(self));
7606        return self;
7607    };
7608    /**
7609     * Instance of DOMUtils for the current document.
7610     *
7611     * @static
7612     * @property DOM
7613     * @type tinymce.dom.DOMUtils
7614     * @example
7615     * // Example of how to add a class to some element by id
7616     * tinymce.DOM.addClass('someid', 'someclass');
7617     */
7618    DOMUtils.DOM = DOMUtils(document);
7619    DOMUtils.nodeIndex = findNodeIndex;
7620
7621    /**
7622     * This class handles asynchronous/synchronous loading of JavaScript files it will execute callbacks
7623     * when various items gets loaded. This class is useful to load external JavaScript files.
7624     *
7625     * @class tinymce.dom.ScriptLoader
7626     * @example
7627     * // Load a script from a specific URL using the global script loader
7628     * tinymce.ScriptLoader.load('somescript.js');
7629     *
7630     * // Load a script using a unique instance of the script loader
7631     * const scriptLoader = new tinymce.dom.ScriptLoader();
7632     *
7633     * scriptLoader.load('somescript.js');
7634     *
7635     * // Load multiple scripts
7636     * scriptLoader.add('somescript1.js');
7637     * scriptLoader.add('somescript2.js');
7638     * scriptLoader.add('somescript3.js');
7639     *
7640     * scriptLoader.loadQueue().then(() => {
7641     *   alert('All scripts are now loaded.');
7642     * });
7643     */
7644    const DOM$e = DOMUtils.DOM;
7645    const QUEUED = 0;
7646    const LOADING = 1;
7647    const LOADED = 2;
7648    const FAILED = 3;
7649    class ScriptLoader {
7650        static ScriptLoader = new ScriptLoader();
7651        settings;
7652        states = {};
7653        queue = [];
7654        scriptLoadedCallbacks = {};
7655        queueLoadedCallbacks = [];
7656        loading = false;
7657        constructor(settings = {}) {
7658            this.settings = settings;
7659        }
7660        _setReferrerPolicy(referrerPolicy) {
7661            this.settings.referrerPolicy = referrerPolicy;
7662        }
7663        _setCrossOrigin(crossOrigin) {
7664            this.settings.crossOrigin = crossOrigin;
7665        }
7666        /**
7667         * Loads a specific script directly without adding it to the load queue.
7668         *
7669         * @method loadScript
7670         * @param {String} url Absolute URL to script to add.
7671         * @return {Promise} A promise that will resolve when the script loaded successfully or reject if it failed to load.
7672         */
7673        loadScript(url) {
7674            return new Promise((resolve, reject) => {
7675                const dom = DOM$e;
7676                const doc = document;
7677                let elm;
7678                const cleanup = () => {
7679                    dom.remove(id);
7680                    if (elm) {
7681                        elm.onerror = elm.onload = elm = null;
7682                    }
7683                };
7684                // Execute callback when script is loaded
7685                const done = () => {
7686                    cleanup();
7687                    resolve();
7688                };
7689                const error = () => {
7690                    // We can't mark it as done if there is a load error since
7691                    // A) We don't want to produce 404 errors on the server and
7692                    // B) the onerror event won't fire on all browsers.
7693                    cleanup();
7694                    reject('Failed to load script: ' + url);
7695                };
7696                const id = dom.uniqueId();
7697                // Create new script element
7698                elm = doc.createElement('script');
7699                elm.id = id;
7700                elm.type = 'text/javascript';
7701                elm.src = Tools._addCacheSuffix(url);
7702                if (this.settings.referrerPolicy) {
7703                    // Note: Don't use elm.referrerPolicy = ... here as it doesn't work on Safari
7704                    dom.setAttrib(elm, 'referrerpolicy', this.settings.referrerPolicy);
7705                }
7706                const crossOrigin = this.settings.crossOrigin;
7707                if (isFunction(crossOrigin)) {
7708                    const resultCrossOrigin = crossOrigin(url);
7709                    if (resultCrossOrigin !== undefined) {
7710                        dom.setAttrib(elm, 'crossorigin', resultCrossOrigin);
7711                    }
7712                }
7713                elm.onload = done;
7714                // Add onerror event will get fired on some browsers but not all of them
7715                elm.onerror = error;
7716                // Add script to document
7717                (doc.head || doc.body).appendChild(elm);
7718            });
7719        }
7720        /**
7721         * Returns true/false if a script has been loaded or not.
7722         *
7723         * @method isDone
7724         * @param {String} url URL to check for.
7725         * @return {Boolean} true/false if the URL is loaded.
7726         */
7727        isDone(url) {
7728            return this.states[url] === LOADED;
7729        }
7730        /**
7731         * Marks a specific script to be loaded. This can be useful if a script got loaded outside
7732         * the script loader or to skip it from loading some script.
7733         *
7734         * @method markDone
7735         * @param {String} url Absolute URL to the script to mark as loaded.
7736         */
7737        markDone(url) {
7738            this.states[url] = LOADED;
7739        }
7740        /**
7741         * Adds a specific script to the load queue of the script loader.
7742         *
7743         * @method add
7744         * @param {String} url Absolute URL to script to add.
7745         * @return {Promise} A promise that will resolve when the script loaded successfully or reject if it failed to load.
7746         */
7747        add(url) {
7748            const self = this;
7749            self.queue.push(url);
7750            // Add url to load queue
7751            const state = self.states[url];
7752            if (state === undefined) {
7753                self.states[url] = QUEUED;
7754            }
7755            return new Promise((resolve, reject) => {
7756                // Store away callback for later execution
7757                if (!self.scriptLoadedCallbacks[url]) {
7758                    self.scriptLoadedCallbacks[url] = [];
7759                }
7760                self.scriptLoadedCallbacks[url].push({
7761                    resolve,
7762                    reject
7763                });
7764            });
7765        }
7766        load(url) {
7767            return this.add(url);
7768        }
7769        remove(url) {
7770            delete this.states[url];
7771            delete this.scriptLoadedCallbacks[url];
7772        }
7773        /**
7774         * Starts the loading of the queue.
7775         *
7776         * @method loadQueue
7777         * @return {Promise} A promise that is resolved when all queued items are loaded or rejected with the script urls that failed to load.
7778         */
7779        loadQueue() {
7780            const queue = this.queue;
7781            this.queue = [];
7782            return this.loadScripts(queue);
7783        }
7784        /**
7785         * Loads the specified queue of files and executes the callback ones they are loaded.
7786         * This method is generally not used outside this class but it might be useful in some scenarios.
7787         *
7788         * @method loadScripts
7789         * @param {Array} scripts Array of queue items to load.
7790         * @return {Promise} A promise that is resolved when all scripts are loaded or rejected with the script urls that failed to load.
7791         */
7792        loadScripts(scripts) {
7793            const self = this;
7794            const execCallbacks = (name, url) => {
7795                // Execute URL callback functions
7796                get$
vendor: 29,189 bytes, lines 7796-8503
7796a(self.scriptLoadedCallbacks, url).each((callbacks) => {
7797                    each$e(callbacks, (callback) => callback[name](url));
7798                });
7799                delete self.scriptLoadedCallbacks[url];
7800            };
7801            const processResults = (results) => {
7802                const failures = filter$5(results, (result) => result.status === 'rejected');
7803                if (failures.length > 0) {
7804                    return Promise.reject(bind$3(failures, ({ reason }) => isArray$1(reason) ? reason : [reason]));
7805                }
7806                else {
7807                    return Promise.resolve();
7808                }
7809            };
7810            const load = (urls) => Promise.allSettled(map$3(urls, (url) => {
7811                // Script is already loaded then execute script callbacks directly
7812                if (self.states[url] === LOADED) {
7813                    execCallbacks('resolve', url);
7814                    return Promise.resolve();
7815                }
7816                else if (self.states[url] === FAILED) {
7817                    execCallbacks('reject', url);
7818                    return Promise.reject(url);
7819                }
7820                else {
7821                    // Script is not already loaded, so load it
7822                    self.states[url] = LOADING;
7823                    return self.loadScript(url).then(() => {
7824                        self.states[url] = LOADED;
7825                        execCallbacks('resolve', url);
7826                        // Immediately load additional scripts if any were added to the queue while loading this script
7827                        const queue = self.queue;
7828                        if (queue.length > 0) {
7829                            self.queue = [];
7830                            return load(queue).then(processResults);
7831                        }
7832                        else {
7833                            return Promise.resolve();
7834                        }
7835                    }, () => {
7836                        self.states[url] = FAILED;
7837                        execCallbacks('reject', url);
7838                        return Promise.reject(url);
7839                    });
7840                }
7841            }));
7842            const processQueue = (urls) => {
7843                self.loading = true;
7844                return load(urls).then((results) => {
7845                    self.loading = false;
7846                    // Start loading the next queued item
7847                    const nextQueuedItem = self.queueLoadedCallbacks.shift();
7848                    Optional.from(nextQueuedItem).each(call);
7849                    return processResults(results);
7850                });
7851            };
7852            // Wait for any other scripts to finish loading first, otherwise load immediately
7853            const uniqueScripts = stringArray(scripts);
7854            if (self.loading) {
7855                return new Promise((resolve, reject) => {
7856                    self.queueLoadedCallbacks.push(() => {
7857                        processQueue(uniqueScripts).then(resolve, reject);
7858                    });
7859                });
7860            }
7861            else {
7862                return processQueue(uniqueScripts);
7863            }
7864        }
7865        /**
7866         * Returns the attributes that should be added to a script tag when loading the specified URL.
7867         *
7868         * @method getScriptAttributes
7869         * @param {String} url Url to get attributes for.
7870         * @return {Object} Object with attributes to add to the script tag.
7871         */
7872        getScriptAttributes(url) {
7873            const attrs = {};
7874            if (this.settings.referrerPolicy) {
7875                attrs.referrerpolicy = this.settings.referrerPolicy;
7876            }
7877            const crossOrigin = this.settings.crossOrigin;
7878            if (isFunction(crossOrigin)) {
7879                const resultCrossOrigin = crossOrigin(url);
7880                if (isString(resultCrossOrigin)) {
7881                    attrs.crossorigin = resultCrossOrigin;
7882                }
7883            }
7884            return attrs;
7885        }
7886    }
7887
7888    const isDuplicated = (items, item) => {
7889        const firstIndex = items.indexOf(item);
7890        return firstIndex !== -1 && items.indexOf(item, firstIndex + 1) > firstIndex;
7891    };
7892    const isRaw = (str) => isObject(str) && has$2(str, 'raw');
7893    const isTokenised = (str) => isArray$1(str) && str.length > 1;
7894    const data = {};
7895    const currentCode = Cell('en');
7896    const getLanguageData = () => get$a(data, currentCode.get());
7897    const getData$1 = () => map$2(data, (value) => ({ ...value }));
7898    /**
7899     * Sets the current language code.
7900     *
7901     * @method setCode
7902     * @param {String} newCode Current language code.
7903     */
7904    const setCode = (newCode) => {
7905        if (newCode) {
7906            currentCode.set(newCode);
7907        }
7908    };
7909    /**
7910     * Returns the current language code.
7911     *
7912     * @method getCode
7913     * @return {String} Current language code.
7914     */
7915    const getCode = () => currentCode.get();
7916    /**
7917     * Adds translations for a specific language code.
7918     * Translation keys are set to be case insensitive.
7919     *
7920     * @method add
7921     * @param {String} code Language code like sv_SE.
7922     * @param {Object} items Name/value object where key is english and value is the translation.
7923     */
7924    const add = (code, items) => {
7925        let langData = data[code];
7926        if (!langData) {
7927            data[code] = langData = {};
7928        }
7929        const lcNames = map$3(keys(items), (name) => name.toLowerCase());
7930        each$d(items, (translation, name) => {
7931            const lcName = name.toLowerCase();
7932            if (lcName !== name && isDuplicated(lcNames, lcName)) {
7933                if (!has$2(items, lcName)) {
7934                    langData[lcName] = translation;
7935                }
7936                langData[name] = translation;
7937            }
7938            else {
7939                langData[lcName] = translation;
7940            }
7941        });
7942    };
7943    /**
7944     * Translates the specified text.
7945     *
7946     * It has a few formats:
7947     * I18n.translate("Text");
7948     * I18n.translate(["Text {0}/{1}", 0, 1]);
7949     * I18n.translate({raw: "Raw string"});
7950     *
7951     * @method translate
7952     * @param {String/Object/Array} text Text to translate.
7953     * @return {String} String that got translated.
7954     */
7955    const translate = (text) => {
7956        const langData = getLanguageData().getOr({});
7957        /*
7958         * number - string
7959         * null, undefined and empty string - empty string
7960         * array - comma-delimited string
7961         * object - in [object Object]
7962         * function - in [object Function]
7963         */
7964        const toString = (obj) => {
7965            if (isFunction(obj)) {
7966                return Object.prototype.toString.call(obj);
7967            }
7968            return !isEmpty(obj) ? '' + obj : '';
7969        };
7970        const isEmpty = (text) => text === '' || text === null || text === undefined;
7971        const getLangData = (text) => {
7972            // make sure we work on a string and return a string
7973            const textStr = toString(text);
7974            return has$2(langData, textStr)
7975                ? toString(langData[textStr])
7976                : get$a(langData, textStr.toLowerCase()).map(toString).getOr(textStr);
7977        };
7978        const removeContext = (str) => str.replace(/{context:\w+}$/, '');
7979        const replaceWithEllipsisChar = (text) => text.replaceAll('...', ellipsis);
7980        // empty strings
7981        if (isEmpty(text)) {
7982            return '';
7983        }
7984        // Raw, already translated
7985        if (isRaw(text)) {
7986            return replaceWithEllipsisChar(toString(text.raw));
7987        }
7988        // Tokenised {translations}
7989        if (isTokenised(text)) {
7990            const values = text.slice(1);
7991            const substitued = getLangData(text[0]).replace(/\{([0-9]+)\}/g, ($1, $2) => has$2(values, $2) ? toString(values[$2]) : $1);
7992            return replaceWithEllipsisChar(removeContext(substitued));
7993        }
7994        // straight forward translation mapping
7995        return replaceWithEllipsisChar(removeContext(getLangData(text)));
7996    };
7997    /**
7998     * Returns true/false if the currently active language pack is rtl or not.
7999     *
8000     * @method isRtl
8001     * @return {Boolean} True if the current language pack is rtl.
8002     */
8003    const isRtl$1 = () => getLanguageData()
8004        .bind((items) => get$a(items, '_dir'))
8005        .exists((dir) => dir === 'rtl');
8006    /**
8007     * Returns true/false if specified language pack exists.
8008     *
8009     * @method hasCode
8010     * @param {String} code Code to check for.
8011     * @return {Boolean} True if the current language pack for the specified code exists.
8012     */
8013    const hasCode = (code) => has$2(data, code);
8014    const I18n = {
8015        getData: getData$1,
8016        setCode,
8017        getCode,
8018        add,
8019        translate,
8020        isRtl: isRtl$1,
8021        hasCode
8022    };
8023
8024    const AddOnManager = () => {
8025        const items = [];
8026        const urls = {};
8027        const lookup = {};
8028        const _listeners = [];
8029        const runListeners = (name, state) => {
8030            const matchedListeners = filter$5(_listeners, (listener) => listener.name === name && listener.state === state);
8031            each$e(matchedListeners, (listener) => listener.resolve());
8032        };
8033        const isLoaded = (name) => has$2(urls, name);
8034        const isAdded = (name) => has$2(lookup, name);
8035        const get = (name) => {
8036            if (lookup[name]) {
8037                return lookup[name].instance;
8038            }
8039            return undefined;
8040        };
8041        const loadLanguagePack = (name, languages) => {
8042            const language = I18n.getCode();
8043            const wrappedLanguages = ',' + (languages || '') + ',';
8044            if (!language || languages && wrappedLanguages.indexOf(',' + language + ',') === -1) {
8045                return;
8046            }
8047            // eslint-disable-next-line @typescript-eslint/no-floating-promises
8048            ScriptLoader.ScriptLoader.add(urls[name] + '/langs/' + language + '.js');
8049        };
8050        const requireLangPack = (name, languages) => {
8051            if (AddOnManager.languageLoad !== false) {
8052                if (isLoaded(name)) {
8053                    loadLanguagePack(name, languages);
8054                }
8055                else {
8056                    // eslint-disable-next-line @typescript-eslint/no-floating-promises
8057                    waitFor(name, 'loaded').then(() => loadLanguagePack(name, languages));
8058                }
8059            }
8060        };
8061        const add = (id, addOn) => {
8062            items.push(addOn);
8063            lookup[id] = { instance: addOn };
8064            runListeners(id, 'added');
8065            return addOn;
8066        };
8067        const remove = (name) => {
8068            delete urls[name];
8069            delete lookup[name];
8070        };
8071        const createUrl = (baseUrl, dep) => {
8072            if (isString(dep)) {
8073                return isString(baseUrl) ?
8074                    { prefix: '', resource: dep, suffix: '' } :
8075                    { prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix };
8076            }
8077            else {
8078                return dep;
8079            }
8080        };
8081        const load = (name, addOnUrl) => {
8082            if (urls[name]) {
8083                return Promise.resolve();
8084            }
8085            let urlString = isString(addOnUrl) ? addOnUrl : addOnUrl.prefix + addOnUrl.resource + addOnUrl.suffix;
8086            if (urlString.indexOf('/') !== 0 && urlString.indexOf('://') === -1) {
8087                urlString = AddOnManager.baseURL + '/' + urlString;
8088            }
8089            urls[name] = urlString.substring(0, urlString.lastIndexOf('/'));
8090            const done = () => {
8091                runListeners(name, 'loaded');
8092                return Promise.resolve();
8093            };
8094            if (lookup[name]) {
8095                return done();
8096            }
8097            else {
8098                return ScriptLoader.ScriptLoader.add(urlString).then(done);
8099            }
8100        };
8101        const waitFor = (name, state = 'added') => {
8102            if (state === 'added' && isAdded(name)) {
8103                return Promise.resolve();
8104            }
8105            else if (state === 'loaded' && isLoaded(name)) {
8106                return Promise.resolve();
8107            }
8108            else {
8109                return new Promise((resolve) => {
8110                    _listeners.push({ name, state, resolve });
8111                });
8112            }
8113        };
8114        return {
8115            items,
8116            urls,
8117            lookup,
8118            /**
8119             * Returns the specified add on by the short name.
8120             *
8121             * @method get
8122             * @param {String} name Add-on to look for.
8123             * @return {tinymce.Theme/tinymce.Plugin} Theme or plugin add-on instance or undefined.
8124             */
8125            get,
8126            /**
8127             * Loads a language pack for the specified add-on.
8128             *
8129             * @method requireLangPack
8130             * @param {String} name Short name of the add-on.
8131             * @param {String} languages Optional comma or space separated list of languages to check if it matches the name.
8132             */
8133            requireLangPack,
8134            /**
8135             * Adds a instance of the add-on by it's short name.
8136             *
8137             * @method add
8138             * @param {String} id Short name/id for the add-on.
8139             * @param {tinymce.Theme/tinymce.Plugin} addOn Theme or plugin to add.
8140             * @return {tinymce.Theme/tinymce.Plugin} The same theme or plugin instance that got passed in.
8141             * @example
8142             * // Create a simple plugin
8143             * const TestPlugin = (ed, url) => {
8144             *   ed.on('click', (e) => {
8145             *     ed.windowManager.alert('Hello World!');
8146             *   });
8147             * };
8148             *
8149             * // Register plugin using the add method
8150             * tinymce.PluginManager.add('test', TestPlugin);
8151             *
8152             * // Initialize TinyMCE
8153             * tinymce.init({
8154             *   ...
8155             *   plugins: '-test' // Init the plugin but don't try to load it
8156             * });
8157             */
8158            add,
8159            remove,
8160            createUrl,
8161            /**
8162             * Loads an add-on from a specific url.
8163             *
8164             * @method load
8165             * @param {String} name Short name of the add-on that gets loaded.
8166             * @param {String} addOnUrl URL to the add-on that will get loaded.
8167             * @return {Promise} A promise that will resolve when the add-on is loaded successfully or reject if it failed to load.
8168             * @example
8169             * // Loads a plugin from an external URL
8170             * tinymce.PluginManager.load('myplugin', '/some/dir/someplugin/plugin.js');
8171             *
8172             * // Initialize TinyMCE
8173             * tinymce.init({
8174             *   ...
8175             *   plugins: '-myplugin' // Don't try to load it again
8176             * });
8177             */
8178            load,
8179            waitFor
8180        };
8181    };
8182    AddOnManager.languageLoad = true;
8183    AddOnManager.baseURL = '';
8184    AddOnManager.PluginManager = AddOnManager();
8185    AddOnManager.ThemeManager = AddOnManager();
8186    AddOnManager.ModelManager = AddOnManager();
8187
8188    const annotation = constant('mce-annotation');
8189    const dataAnnotation = constant('data-mce-annotation');
8190    const dataAnnotationId = constant('data-mce-annotation-uid');
8191    const dataAnnotationActive = constant('data-mce-annotation-active');
8192    const dataAnnotationClasses = constant('data-mce-annotation-classes');
8193    const dataAnnotationAttributes = constant('data-mce-annotation-attrs');
8194
8195    const isRoot$1 = (root) => (node) => eq(node, root);
8196    // Given the current editor selection, identify the uid of any current
8197    // annotation
8198    const identify = (editor, annotationName) => {
8199        const rng = editor.selection.getRng();
8200        const start = SugarElement.fromDom(rng.startContainer);
8201        const root = SugarElement.fromDom(editor.getBody());
8202        const selector = annotationName.fold(() => '.' + annotation(), (an) => `[${dataAnnotation()}="${an}"]`);
8203        const newStart = child$1(start, rng.startOffset).getOr(start);
8204        const closest = closest$4(newStart, selector, isRoot$1(root));
8205        return closest.bind((c) => getOpt(c, `${dataAnnotationId()}`).bind((uid) => getOpt(c, `${dataAnnotation()}`).map((name) => {
8206            const elements = findMarkers(editor, uid);
8207            return {
8208                uid,
8209                name,
8210                elements
8211            };
8212        })));
8213    };
8214    const isAnnotation = (elem) => isElement$8(elem) && has(elem, annotation());
8215    const isBogusElement = (elem, root) => has$1(elem, 'data-mce-bogus') || ancestor$1(elem, '[data-mce-bogus="all"]', isRoot$1(root));
8216    const findMarkers = (editor, uid) => {
8217        const body = SugarElement.fromDom(editor.getBody());
8218        const descendants$1 = descendants(body, `[${dataAnnotationId()}="${uid}"]`);
8219        return filter$5(descendants$1, (descendant) => !isBogusElement(descendant, body));
8220    };
8221    const findAll = (editor, name) => {
8222        const body = SugarElement.fromDom(editor.getBody());
8223        const markers = descendants(body, `[${dataAnnotation()}="${name}"]`);
8224        const directory = {};
8225        each$e(markers, (m) => {
8226            if (!isBogusElement(m, body)) {
8227                const uid = get$9(m, dataAnnotationId());
8228                const nodesAlready = get$a(directory, uid).getOr([]);
8229                directory[uid] = nodesAlready.concat([m]);
8230            }
8231        });
8232        return directory;
8233    };
8234
8235    const setup$E = (editor, registry) => {
8236        const changeCallbacks = Cell({});
8237        const initData = () => ({
8238            listeners: [],
8239            previous: value$1()
8240        });
8241        const withCallbacks = (name, f) => {
8242            updateCallbacks(name, (data) => {
8243                f(data);
8244                return data;
8245            });
8246        };
8247        const updateCallbacks = (name, f) => {
8248            const callbackMap = changeCallbacks.get();
8249            const data = get$a(callbackMap, name).getOrThunk(initData);
8250            const outputData = f(data);
8251            callbackMap[name] = outputData;
8252            changeCallbacks.set(callbackMap);
8253        };
8254        const fireCallbacks = (name, uid, elements) => {
8255            withCallbacks(name, (data) => {
8256                each$e(data.listeners, (f) => f(true, name, {
8257                    uid,
8258                    nodes: map$3(elements, (elem) => elem.dom)
8259                }));
8260            });
8261        };
8262        const fireNoAnnotation = (name) => {
8263            withCallbacks(name, (data) => {
8264                each$e(data.listeners, (f) => f(false, name));
8265            });
8266        };
8267        const toggleActiveAttr = (uid, state) => {
8268            each$e(findMarkers(editor, uid), (elem) => {
8269                if (state) {
8270                    set$4(elem, dataAnnotationActive(), 'true');
8271                }
8272                else {
8273                    remove$9(elem, dataAnnotationActive());
8274                }
8275            });
8276        };
8277        // NOTE: Runs in alphabetical order.
8278        const onNodeChange = last$1(() => {
8279            const annotations = sort(registry.getNames());
8280            each$e(annotations, (name) => {
8281                updateCallbacks(name, (data) => {
8282                    const prev = data.previous.get();
8283                    identify(editor, Optional.some(name)).fold(() => {
8284                        prev.each((uid) => {
8285                            // Changed from something to nothing.
8286                            fireNoAnnotation(name);
8287                            data.previous.clear();
8288                            toggleActiveAttr(uid, false);
8289                        });
8290                    }, ({ uid, name, elements }) => {
8291                        // Changed from a different annotation (or nothing)
8292                        if (!is$4(prev, uid)) {
8293                            prev.each((uid) => toggleActiveAttr(uid, false));
8294                            fireCallbacks(name, uid, elements);
8295                            data.previous.set(uid);
8296                            toggleActiveAttr(uid, true);
8297                        }
8298                    });
8299                    return {
8300                        previous: data.previous,
8301                        listeners: data.listeners
8302                    };
8303                });
8304            });
8305        }, 30);
8306        editor.on('remove', () => {
8307            onNodeChange.cancel();
8308        });
8309        editor.on('NodeChange', () => {
8310            onNodeChange.throttle();
8311        });
8312        const addListener = (name, f) => {
8313            updateCallbacks(name, (data) => ({
8314                previous: data.previous,
8315                listeners: data.listeners.concat([f])
8316            }));
8317        };
8318        return {
8319            addListener
8320        };
8321    };
8322
8323    const setup$D = (editor, registry) => {
8324        const dataAnnotation$1 = dataAnnotation();
8325        const identifyParserNode = (node) => Optional.from(node.attr(dataAnnotation$1)).bind(registry.lookup);
8326        const removeDirectAnnotation = (node) => {
8327            node.attr(dataAnnotationId(), null);
8328            node.attr(dataAnnotation(), null);
8329            node.attr(dataAnnotationActive(), null);
8330            const customAttrNames = Optional.from(node.attr(dataAnnotationAttributes())).map((names) => names.split(',')).getOr([]);
8331            const customClasses = Optional.from(node.attr(dataAnnotationClasses())).map((names) => names.split(',')).getOr([]);
8332            each$e(customAttrNames, (name) => node.attr(name, null));
8333            const classList = node.attr('class')?.split(' ') ?? [];
8334            const newClassList = difference(classList, [annotation()].concat(customClasses));
8335            node.attr('class', newClassList.length > 0 ? newClassList.join(' ') : null);
8336            node.attr(dataAnnotationClasses(), null);
8337            node.attr(dataAnnotationAttributes(), null);
8338        };
8339        editor.serializer.addTempAttr(dataAnnotationActive());
8340        editor.serializer.addAttributeFilter(dataAnnotation$1, (nodes) => {
8341            for (const node of nodes) {
8342                identifyParserNode(node).each((settings) => {
8343                    if (settings.persistent === false) {
8344                        if (node.name === 'span') {
8345                            node.unwrap();
8346                        }
8347                        else {
8348                            removeDirectAnnotation(node);
8349                        }
8350                    }
8351                });
8352            }
8353        });
8354    };
8355
8356    const create$a = () => {
8357        const annotations = {};
8358        const register = (name, settings) => {
8359            annotations[name] = {
8360                name,
8361                settings
8362            };
8363        };
8364        const lookup = (name) => get$a(annotations, name).map((a) => a.settings);
8365        const getNames = () => keys(annotations);
8366        return {
8367            register,
8368            lookup,
8369            getNames
8370        };
8371    };
8372
8373    const TextWalker = (startNode, rootNode, isBoundary = never) => {
8374        const walker = new DomTreeWalker(startNode, rootNode);
8375        const walk = (direction) => {
8376            let next;
8377            do {
8378                next = walker[direction]();
8379            } while (next && !isText$b(next) && !isBoundary(next));
8380            return Optional.from(next).filter(isText$b);
8381        };
8382        return {
8383            current: () => Optional.from(walker.current()).filter(isText$b),
8384            next: () => walk('next'),
8385            prev: () => walk('prev'),
8386            prev2: () => walk('prev2')
8387        };
8388    };
8389
8390    /**
8391     * The TextSeeker class enables you to seek for a specific point in text across the DOM.
8392     *
8393     * @class tinymce.dom.TextSeeker
8394     * @example
8395     * const seeker = tinymce.dom.TextSeeker(editor.dom);
8396     * const startOfWord = seeker.backwards(startNode, startOffset, (textNode, offset, text) => {
8397     *   const lastSpaceCharIndex = text.lastIndexOf(' ');
8398     *   if (lastSpaceCharIndex !== -1) {
8399     *     return lastSpaceCharIndex + 1;
8400     *   } else {
8401     *     // No space found so continue searching
8402     *     return -1;
8403     *   }
8404     * });
8405     */
8406    /**
8407     * Constructs a new TextSeeker instance.
8408     *
8409     * @constructor
8410     * @param {tinymce.dom.DOMUtils} dom DOMUtils object reference.
8411     * @param {Function} isBoundary Optional function to determine if the seeker should continue to walk past the node provided. The default is to search until a block or <code>br</code> element is found.
8412     */
8413    const TextSeeker = (dom, isBoundary) => {
8414        const isBlockBoundary = isBoundary ? isBoundary : (node) => dom.isBlock(node) || isBr$7(node) || isContentEditableFalse$a(node);
8415        const walk = (node, offset, walker, process) => {
8416            if (isText$b(node)) {
8417                const newOffset = process(node, offset, node.data);
8418                if (newOffset !== -1) {
8419                    return Optional.some({ container: node, offset: newOffset });
8420                }
8421            }
8422            return walker().bind((next) => walk(next.container, next.offset, walker, process));
8423        };
8424        /**
8425         * Search backwards through text nodes until a match, boundary, or root node has been found.
8426         *
8427         * @method backwards
8428         * @param {Node} node The node to start searching from.
8429         * @param {Number} offset The offset of the node to start searching from.
8430         * @param {Function} process A function that's passed the current text node, the current offset and the text content of the node. It should return the offset of the match or -1 to continue searching.
8431         * @param {Node} root An optional root node to constrain the search to.
8432         * @return {Object} An object containing the matched text node and offset. If no match is found, null will be returned.
8433         */
8434        const backwards = (node, offset, process, root) => {
8435            const walker = TextWalker(node, root ?? dom.getRoot(), isBlockBoundary);
8436            return walk(node, offset, () => walker.prev().map((prev) => ({ container: prev, offset: prev.length })), process).getOrNull();
8437        };
8438        /**
8439         * Search forwards through text nodes until a match, boundary, or root node has been found.
8440         *
8441         * @method forwards
8442         * @param {Node} node The node to start searching from.
8443         * @param {Number} offset The offset of the node to start searching from.
8444         * @param {Function} process A function that's passed the current text node, the current offset and the text content of the node. It should return the offset of the match or -1 to continue searching.
8445         * @param {Node} root An optional root node to constrain the search to.
8446         * @return {Object} An object containing the matched text node and offset. If no match is found, null will be returned.
8447         */
8448        const forwards = (node, offset, process, root) => {
8449            const walker = TextWalker(node, root ?? dom.getRoot(), isBlockBoundary);
8450            return walk(node, offset, () => walker.next().map((next) => ({ container: next, offset: 0 })), process).getOrNull();
8451        };
8452        return {
8453            backwards,
8454            forwards
8455        };
8456    };
8457
8458    const tableCells = ['td', 'th'];
8459    const tableSections = ['thead', 'tbody', 'tfoot'];
8460    const textBlocks = [
8461        'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'div', 'address', 'pre', 'form',
8462        'blockquote', 'center', 'dir', 'fieldset', 'header', 'footer', 'article',
8463        'section', 'hgroup', 'aside', 'nav', 'figure'
8464    ];
8465    const listItems$1 = ['li', 'dd', 'dt'];
8466    const lists = ['ul', 'ol', 'dl'];
8467    const wsElements = ['pre', 'script', 'textarea', 'style'];
8468    const lazyLookup = (items) => {
8469        let lookup;
8470        return (node) => {
8471            lookup = lookup ? lookup : mapToObject(items, always);
8472            return has$2(lookup, name(node));
8473        };
8474    };
8475    // WARNING: don't add anything to this file, the intention is to move these checks into the Schema
8476    const isTable$1 = (node) => name(node) === 'table';
8477    const isBr$6 = (node) => isElement$8(node) && name(node) === 'br';
8478    const isTextBlock$3 = lazyLookup(textBlocks);
8479    const isList$1 = lazyLookup(lists);
8480    const isListItem$2 = lazyLookup(listItems$1);
8481    const isTableSection = lazyLookup(tableSections);
8482    const isTableCell$2 = lazyLookup(tableCells);
8483    const isWsPreserveElement = lazyLookup(wsElements);
8484
8485    const getLastChildren$1 = (elm) => {
8486        const children = [];
8487        let rawNode = elm.dom;
8488        while (rawNode) {
8489            children.push(SugarElement.fromDom(rawNode));
8490            rawNode = rawNode.lastChild;
8491        }
8492        return children;
8493    };
8494    const removeTrailingBr = (elm) => {
8495        const allBrs = descendants(elm, 'br');
8496        const brs = filter$5(getLastChildren$1(elm).slice(-1), isBr$6);
8497        if (allBrs.length === brs.length) {
8498            each$e(brs, remove$8);
8499        }
8500    };
8501    const createPaddingBr = () => {
8502        const br = SugarElement.fromTag('br');
8503        set$4
vendor: 49,750 bytes, lines 8503-9709
8503(br, 'data-mce-bogus', '1');
8504        return br;
8505    };
8506    const fillWithPaddingBr = (elm) => {
8507        empty(elm);
8508        append$1(elm, createPaddingBr());
8509    };
8510    const trimBlockTrailingBr = (elm, schema) => {
8511        lastChild(elm).each((lastChild) => {
8512            prevSibling(lastChild).each((lastChildPrevSibling) => {
8513                if (schema.isBlock(name(elm)) && isBr$6(lastChild) && schema.isBlock(name(lastChildPrevSibling))) {
8514                    remove$8(lastChild);
8515                }
8516            });
8517        });
8518    };
8519
8520    /**
8521     * Utility functions for working with zero width space
8522     * characters used as character containers etc.
8523     *
8524     * @private
8525     * @class tinymce.text.Zwsp
8526     * @example
8527     * const isZwsp = Zwsp.isZwsp('\uFEFF');
8528     * const abc = Zwsp.trim('a\uFEFFc');
8529     */
8530    // This is technically not a ZWSP but a ZWNBSP or a BYTE ORDER MARK it used to be a ZWSP
8531    const ZWSP$1 = zeroWidth;
8532    const isZwsp = isZwsp$2;
8533    const trim$2 = removeZwsp;
8534    const insert$5 = (editor) => editor.insertContent(ZWSP$1, { preserve_zwsp: true });
8535
8536    /**
8537     * This module handles caret containers. A caret container is a node that
8538     * holds the caret for positional purposes.
8539     *
8540     * @private
8541     * @class tinymce.caret.CaretContainer
8542     */
8543    const isElement$6 = isElement$7;
8544    const isText$9 = isText$b;
8545    const isCaretContainerBlock$1 = (node) => {
8546        if (isText$9(node)) {
8547            node = node.parentNode;
8548        }
8549        return isElement$6(node) && node.hasAttribute('data-mce-caret');
8550    };
8551    const isCaretContainerInline = (node) => isText$9(node) && isZwsp(node.data);
8552    const isCaretContainer$2 = (node) => isCaretContainerBlock$1(node) || isCaretContainerInline(node);
8553    const hasContent = (node) => node.firstChild !== node.lastChild || !isBr$7(node.firstChild);
8554    const insertInline$1 = (node, before) => {
8555        const doc = node.ownerDocument ?? document;
8556        const textNode = doc.createTextNode(ZWSP$1);
8557        const parentNode = node.parentNode;
8558        if (!before) {
8559            const sibling = node.nextSibling;
8560            if (isText$9(sibling)) {
8561                if (isCaretContainer$2(sibling)) {
8562                    return sibling;
8563                }
8564                if (startsWithCaretContainer$1(sibling)) {
8565                    sibling.splitText(1);
8566                    return sibling;
8567                }
8568            }
8569            if (node.nextSibling) {
8570                parentNode?.insertBefore(textNode, node.nextSibling);
8571            }
8572            else {
8573                parentNode?.appendChild(textNode);
8574            }
8575        }
8576        else {
8577            const sibling = node.previousSibling;
8578            if (isText$9(sibling)) {
8579                if (isCaretContainer$2(sibling)) {
8580                    return sibling;
8581                }
8582                if (endsWithCaretContainer$1(sibling)) {
8583                    return sibling.splitText(sibling.data.length - 1);
8584                }
8585            }
8586            parentNode?.insertBefore(textNode, node);
8587        }
8588        return textNode;
8589    };
8590    const isBeforeInline = (pos) => {
8591        const container = pos.container();
8592        if (!isText$b(container)) {
8593            return false;
8594        }
8595        // The text nodes may not be normalized, so check the current node and the previous one
8596        return container.data.charAt(pos.offset()) === ZWSP$1 || pos.isAtStart() && isCaretContainerInline(container.previousSibling);
8597    };
8598    const isAfterInline = (pos) => {
8599        const container = pos.container();
8600        if (!isText$b(container)) {
8601            return false;
8602        }
8603        // The text nodes may not be normalized, so check the current node and the next one
8604        return container.data.charAt(pos.offset() - 1) === ZWSP$1 || pos.isAtEnd() && isCaretContainerInline(container.nextSibling);
8605    };
8606    const insertBlock = (blockName, node, before) => {
8607        const doc = node.ownerDocument ?? document;
8608        const blockNode = doc.createElement(blockName);
8609        blockNode.setAttribute('data-mce-caret', before ? 'before' : 'after');
8610        blockNode.setAttribute('data-mce-bogus', 'all');
8611        blockNode.appendChild(createPaddingBr().dom);
8612        const parentNode = node.parentNode;
8613        if (!before) {
8614            if (node.nextSibling) {
8615                parentNode?.insertBefore(blockNode, node.nextSibling);
8616            }
8617            else {
8618                parentNode?.appendChild(blockNode);
8619            }
8620        }
8621        else {
8622            parentNode?.insertBefore(blockNode, node);
8623        }
8624        return blockNode;
8625    };
8626    const startsWithCaretContainer$1 = (node) => isText$9(node) && node.data[0] === ZWSP$1;
8627    const endsWithCaretContainer$1 = (node) => isText$9(node) && node.data[node.data.length - 1] === ZWSP$1;
8628    const trimBogusBr = (elm) => {
8629        const brs = elm.getElementsByTagName('br');
8630        const lastBr = brs[brs.length - 1];
8631        if (isBogus$1(lastBr)) {
8632            lastBr.parentNode?.removeChild(lastBr);
8633        }
8634    };
8635    const showCaretContainerBlock = (caretContainer) => {
8636        if (caretContainer && caretContainer.hasAttribute('data-mce-caret')) {
8637            trimBogusBr(caretContainer);
8638            caretContainer.removeAttribute('data-mce-caret');
8639            caretContainer.removeAttribute('data-mce-bogus');
8640            caretContainer.removeAttribute('style');
8641            caretContainer.removeAttribute('data-mce-style');
8642            caretContainer.removeAttribute('_moz_abspos');
8643            return caretContainer;
8644        }
8645        return null;
8646    };
8647    const isRangeInCaretContainerBlock = (range) => isCaretContainerBlock$1(range.startContainer);
8648
8649    const round$2 = Math.round;
8650    const clone$1 = (rect) => {
8651        if (!rect) {
8652            return { left: 0, top: 0, bottom: 0, right: 0, width: 0, height: 0 };
8653        }
8654        return {
8655            left: round$2(rect.left),
8656            top: round$2(rect.top),
8657            bottom: round$2(rect.bottom),
8658            right: round$2(rect.right),
8659            width: round$2(rect.width),
8660            height: round$2(rect.height)
8661        };
8662    };
8663    const collapse = (rect, toStart) => {
8664        rect = clone$1(rect);
8665        if (toStart) {
8666            rect.right = rect.left;
8667        }
8668        else {
8669            rect.left = rect.left + rect.width;
8670            rect.right = rect.left;
8671        }
8672        rect.width = 0;
8673        return rect;
8674    };
8675    const isEqual = (rect1, rect2) => (rect1.left === rect2.left &&
8676        rect1.top === rect2.top &&
8677        rect1.bottom === rect2.bottom &&
8678        rect1.right === rect2.right);
8679    const isValidOverflow = (overflowY, rect1, rect2) => overflowY >= 0 && overflowY <= Math.min(rect1.height, rect2.height) / 2;
8680    const isAbove$1 = (rect1, rect2) => {
8681        const halfHeight = Math.min(rect2.height / 2, rect1.height / 2);
8682        if ((rect1.bottom - halfHeight) < rect2.top) {
8683            return true;
8684        }
8685        if (rect1.top > rect2.bottom) {
8686            return false;
8687        }
8688        return isValidOverflow(rect2.top - rect1.bottom, rect1, rect2);
8689    };
8690    const isBelow$1 = (rect1, rect2) => {
8691        if (rect1.top > rect2.bottom) {
8692            return true;
8693        }
8694        if (rect1.bottom < rect2.top) {
8695            return false;
8696        }
8697        return isValidOverflow(rect2.bottom - rect1.top, rect1, rect2);
8698    };
8699    const containsXY = (rect, clientX, clientY) => (clientX >= rect.left &&
8700        clientX <= rect.right &&
8701        clientY >= rect.top &&
8702        clientY <= rect.bottom);
8703    const boundingClientRectFromRects = (rects) => {
8704        return foldl(rects, (acc, rect) => {
8705            return acc.fold(() => Optional.some(rect), (prevRect) => {
8706                const left = Math.min(rect.left, prevRect.left);
8707                const top = Math.min(rect.top, prevRect.top);
8708                const right = Math.max(rect.right, prevRect.right);
8709                const bottom = Math.max(rect.bottom, prevRect.bottom);
8710                return Optional.some({
8711                    top,
8712                    right,
8713                    bottom,
8714                    left,
8715                    width: right - left,
8716                    height: bottom - top
8717                });
8718            });
8719        }, Optional.none());
8720    };
8721    const distanceToRectEdgeFromXY = (rect, x, y) => {
8722        const cx = Math.max(Math.min(x, rect.left + rect.width), rect.left);
8723        const cy = Math.max(Math.min(y, rect.top + rect.height), rect.top);
8724        return Math.sqrt((x - cx) * (x - cx) + (y - cy) * (y - cy));
8725    };
8726    const overlapY = (r1, r2) => Math.max(0, Math.min(r1.bottom, r2.bottom) - Math.max(r1.top, r2.top));
8727
8728    const getSelectedNode = (range) => {
8729        const startContainer = range.startContainer, startOffset = range.startOffset;
8730        if (startContainer === range.endContainer && startContainer.hasChildNodes() && range.endOffset === startOffset + 1) {
8731            return startContainer.childNodes[startOffset];
8732        }
8733        return null;
8734    };
8735    const getNode$1 = (container, offset) => {
8736        if (isElement$7(container) && container.hasChildNodes()) {
8737            const childNodes = container.childNodes;
8738            const safeOffset = clamp$2(offset, 0, childNodes.length - 1);
8739            return childNodes[safeOffset];
8740        }
8741        else {
8742            return container;
8743        }
8744    };
8745    /** @deprecated Use getNode instead */
8746    const getNodeUnsafe = (container, offset) => {
8747        // If a negative offset is used on an element then `undefined` should be returned
8748        if (offset < 0 && isElement$7(container) && container.hasChildNodes()) {
8749            return undefined;
8750        }
8751        else {
8752            return getNode$1(container, offset);
8753        }
8754    };
8755
8756    /**
8757     * This class contains logic for detecting extending characters.
8758     *
8759     * @private
8760     * @class tinymce.text.ExtendingChar
8761     * @example
8762     * const isExtending = ExtendingChar.isExtendingChar('a');
8763     */
8764    // Generated from: http://www.unicode.org/Public/UNIDATA/DerivedCoreProperties.txt
8765    // Only includes the characters in that fit into UCS-2 16 bit
8766    const extendingChars = new RegExp('[\u0300-\u036F\u0483-\u0487\u0488-\u0489\u0591-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7\u0610-\u061A' +
8767        '\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7-\u06E8\u06EA-\u06ED\u0711\u0730-\u074A\u07A6-\u07B0' +
8768        '\u07EB-\u07F3\u0816-\u0819\u081B-\u0823\u0825-\u0827\u0829-\u082D\u0859-\u085B\u08E3-\u0902\u093A\u093C' +
8769        '\u0941-\u0948\u094D\u0951-\u0957\u0962-\u0963\u0981\u09BC\u09BE\u09C1-\u09C4\u09CD\u09D7\u09E2-\u09E3' +
8770        '\u0A01-\u0A02\u0A3C\u0A41-\u0A42\u0A47-\u0A48\u0A4B-\u0A4D\u0A51\u0A70-\u0A71\u0A75\u0A81-\u0A82\u0ABC' +
8771        '\u0AC1-\u0AC5\u0AC7-\u0AC8\u0ACD\u0AE2-\u0AE3\u0B01\u0B3C\u0B3E\u0B3F\u0B41-\u0B44\u0B4D\u0B56\u0B57' +
8772        '\u0B62-\u0B63\u0B82\u0BBE\u0BC0\u0BCD\u0BD7\u0C00\u0C3E-\u0C40\u0C46-\u0C48\u0C4A-\u0C4D\u0C55-\u0C56' +
8773        '\u0C62-\u0C63\u0C81\u0CBC\u0CBF\u0CC2\u0CC6\u0CCC-\u0CCD\u0CD5-\u0CD6\u0CE2-\u0CE3\u0D01\u0D3E\u0D41-\u0D44' +
8774        '\u0D4D\u0D57\u0D62-\u0D63\u0DCA\u0DCF\u0DD2-\u0DD4\u0DD6\u0DDF\u0E31\u0E34-\u0E3A\u0E47-\u0E4E\u0EB1\u0EB4-\u0EB9' +
8775        '\u0EBB-\u0EBC\u0EC8-\u0ECD\u0F18-\u0F19\u0F35\u0F37\u0F39\u0F71-\u0F7E\u0F80-\u0F84\u0F86-\u0F87\u0F8D-\u0F97' +
8776        '\u0F99-\u0FBC\u0FC6\u102D-\u1030\u1032-\u1037\u1039-\u103A\u103D-\u103E\u1058-\u1059\u105E-\u1060\u1071-\u1074' +
8777        '\u1082\u1085-\u1086\u108D\u109D\u135D-\u135F\u1712-\u1714\u1732-\u1734\u1752-\u1753\u1772-\u1773\u17B4-\u17B5' +
8778        '\u17B7-\u17BD\u17C6\u17C9-\u17D3\u17DD\u180B-\u180D\u18A9\u1920-\u1922\u1927-\u1928\u1932\u1939-\u193B\u1A17-\u1A18' +
8779        '\u1A1B\u1A56\u1A58-\u1A5E\u1A60\u1A62\u1A65-\u1A6C\u1A73-\u1A7C\u1A7F\u1AB0-\u1ABD\u1ABE\u1B00-\u1B03\u1B34' +
8780        '\u1B36-\u1B3A\u1B3C\u1B42\u1B6B-\u1B73\u1B80-\u1B81\u1BA2-\u1BA5\u1BA8-\u1BA9\u1BAB-\u1BAD\u1BE6\u1BE8-\u1BE9' +
8781        '\u1BED\u1BEF-\u1BF1\u1C2C-\u1C33\u1C36-\u1C37\u1CD0-\u1CD2\u1CD4-\u1CE0\u1CE2-\u1CE8\u1CED\u1CF4\u1CF8-\u1CF9' +
8782        '\u1DC0-\u1DF5\u1DFC-\u1DFF\u200C-\u200D\u20D0-\u20DC\u20DD-\u20E0\u20E1\u20E2-\u20E4\u20E5-\u20F0\u2CEF-\u2CF1' +
8783        '\u2D7F\u2DE0-\u2DFF\u302A-\u302D\u302E-\u302F\u3099-\u309A\uA66F\uA670-\uA672\uA674-\uA67D\uA69E-\uA69F\uA6F0-\uA6F1' +
8784        '\uA802\uA806\uA80B\uA825-\uA826\uA8C4\uA8E0-\uA8F1\uA926-\uA92D\uA947-\uA951\uA980-\uA982\uA9B3\uA9B6-\uA9B9\uA9BC' +
8785        '\uA9E5\uAA29-\uAA2E\uAA31-\uAA32\uAA35-\uAA36\uAA43\uAA4C\uAA7C\uAAB0\uAAB2-\uAAB4\uAAB7-\uAAB8\uAABE-\uAABF\uAAC1' +
8786        '\uAAEC-\uAAED\uAAF6\uABE5\uABE8\uABED\uFB1E\uFE00-\uFE0F\uFE20-\uFE2F\uFF9E-\uFF9F]');
8787    const isExtendingChar = (ch) => isString(ch) && ch.charCodeAt(0) >= 768 && extendingChars.test(ch);
8788
8789    const or = (...args) => {
8790        return (x) => {
8791            for (let i = 0; i < args.length; i++) {
8792                if (args[i](x)) {
8793                    return true;
8794                }
8795            }
8796            return false;
8797        };
8798    };
8799    const and = (...args) => {
8800        return (x) => {
8801            for (let i = 0; i < args.length; i++) {
8802                if (!args[i](x)) {
8803                    return false;
8804                }
8805            }
8806            return true;
8807        };
8808    };
8809
8810    /**
8811     * This module contains logic for handling caret candidates. A caret candidate is
8812     * for example text nodes, images, input elements, cE=false elements etc.
8813     *
8814     * @private
8815     * @class tinymce.caret.CaretCandidate
8816     */
8817    const isContentEditableTrue$2 = isContentEditableTrue$3;
8818    const isContentEditableFalse$9 = isContentEditableFalse$a;
8819    const isBr$5 = isBr$7;
8820    const isText$8 = isText$b;
8821    const isInvalidTextElement = matchNodeNames$1(['script', 'style', 'textarea']);
8822    const isAtomicInline = matchNodeNames$1(['img', 'input', 'textarea', 'hr', 'iframe', 'video', 'audio', 'object', 'embed']);
8823    const isTable = matchNodeNames$1(['table']);
8824    const isCaretContainer$1 = isCaretContainer$2;
8825    const isCaretCandidate$3 = (node) => {
8826        if (isCaretContainer$1(node)) {
8827            return false;
8828        }
8829        if (isText$8(node)) {
8830            return !isInvalidTextElement(node.parentNode);
8831        }
8832        return isAtomicInline(node) || isBr$5(node) || isTable(node) || isNonUiContentEditableFalse(node);
8833    };
8834    // UI components on IE is marked with contenteditable=false and unselectable=true so lets not handle those as real content editables
8835    const isUnselectable = (node) => isElement$7(node) && node.getAttribute('unselectable') === 'true';
8836    const isNonUiContentEditableFalse = (node) => !isUnselectable(node) && isContentEditableFalse$9(node);
8837    const isInEditable = (node, root) => {
8838        for (let tempNode = node.parentNode; tempNode && tempNode !== root; tempNode = tempNode.parentNode) {
8839            if (isNonUiContentEditableFalse(tempNode)) {
8840                return false;
8841            }
8842            if (isContentEditableTrue$2(tempNode)) {
8843                return true;
8844            }
8845        }
8846        return true;
8847    };
8848    const isAtomicContentEditableFalse = (node) => {
8849        if (!isNonUiContentEditableFalse(node)) {
8850            return false;
8851        }
8852        return !foldl(from(node.getElementsByTagName('*')), (result, elm) => {
8853            return result || isContentEditableTrue$2(elm);
8854        }, false);
8855    };
8856    const isAtomic$1 = (node) => isAtomicInline(node) || isAtomicContentEditableFalse(node);
8857    const isEditableCaretCandidate$1 = (node, root) => isCaretCandidate$3(node) && isInEditable(node, root);
8858
8859    /**
8860     * This module contains logic for creating caret positions within a document a caretposition
8861     * is similar to a DOMRange object but it doesn't have two endpoints and is also more lightweight
8862     * since it's now updated live when the DOM changes.
8863     *
8864     * @private
8865     * @class tinymce.caret.CaretPosition
8866     * @example
8867     * const caretPos1 = CaretPosition(container, offset);
8868     * const caretPos2 = CaretPosition.fromRangeStart(someRange);
8869     */
8870    const isElement$5 = isElement$7;
8871    const isCaretCandidate$2 = isCaretCandidate$3;
8872    const isBlock$3 = matchStyleValues('display', 'block table');
8873    const isFloated = matchStyleValues('float', 'left right');
8874    const isValidElementCaretCandidate = and(isElement$5, isCaretCandidate$2, not(isFloated));
8875    const isNotPre = not(matchStyleValues('white-space', 'pre pre-line pre-wrap'));
8876    const isText$7 = isText$b;
8877    const isBr$4 = isBr$7;
8878    const nodeIndex$1 = DOMUtils.nodeIndex;
8879    const resolveIndex$1 = getNodeUnsafe;
8880    const createRange$1 = (doc) => doc ? doc.createRange() : DOMUtils.DOM.createRng();
8881    const isWhiteSpace$1 = (chr) => isString(chr) && /[\r\n\t ]/.test(chr);
8882    const isRange = (rng) => !!rng.setStart && !!rng.setEnd;
8883    const isHiddenWhiteSpaceRange = (range) => {
8884        const container = range.startContainer;
8885        const offset = range.startOffset;
8886        if (isWhiteSpace$1(range.toString()) && isNotPre(container.parentNode) && isText$b(container)) {
8887            const text = container.data;
8888            if (isWhiteSpace$1(text[offset - 1]) || isWhiteSpace$1(text[offset + 1])) {
8889                return true;
8890            }
8891        }
8892        return false;
8893    };
8894    // Hack for older WebKit versions that doesn't
8895    // support getBoundingClientRect on BR elements
8896    const getBrClientRect = (brNode) => {
8897        const doc = brNode.ownerDocument;
8898        const rng = createRange$1(doc);
8899        const nbsp$1 = doc.createTextNode(nbsp);
8900        const parentNode = brNode.parentNode;
8901        parentNode.insertBefore(nbsp$1, brNode);
8902        rng.setStart(nbsp$1, 0);
8903        rng.setEnd(nbsp$1, 1);
8904        const clientRect = clone$1(rng.getBoundingClientRect());
8905        parentNode.removeChild(nbsp$1);
8906        return clientRect;
8907    };
8908    // Safari will not return a rect for <p>a<br>|b</p> for some odd reason
8909    const getBoundingClientRectWebKitText = (rng) => {
8910        const sc = rng.startContainer;
8911        const ec = rng.endContainer;
8912        const so = rng.startOffset;
8913        const eo = rng.endOffset;
8914        if (sc === ec && isText$b(ec) && so === 0 && eo === 1) {
8915            const newRng = rng.cloneRange();
8916            newRng.setEndAfter(ec);
8917            return getBoundingClientRect$1(newRng);
8918        }
8919        else {
8920            return null;
8921        }
8922    };
8923    const isZeroRect = (r) => r.left === 0 && r.right === 0 && r.top === 0 && r.bottom === 0;
8924    const getBoundingClientRect$1 = (item) => {
8925        let clientRect;
8926        const clientRects = item.getClientRects();
8927        if (clientRects.length > 0) {
8928            clientRect = clone$1(clientRects[0]);
8929        }
8930        else {
8931            clientRect = clone$1(item.getBoundingClientRect());
8932        }
8933        if (!isRange(item) && isBr$4(item) && isZeroRect(clientRect)) {
8934            return getBrClientRect(item);
8935        }
8936        if (isZeroRect(clientRect) && isRange(item)) {
8937            return getBoundingClientRectWebKitText(item) ?? clientRect;
8938        }
8939        return clientRect;
8940    };
8941    const collapseAndInflateWidth = (clientRect, toStart) => {
8942        const newClientRect = collapse(clientRect, toStart);
8943        newClientRect.width = 1;
8944        newClientRect.right = newClientRect.left + 1;
8945        return newClientRect;
8946    };
8947    const getCaretPositionClientRects = (caretPosition) => {
8948        const clientRects = [];
8949        const addUniqueAndValidRect = (clientRect) => {
8950            if (clientRect.height === 0) {
8951                return;
8952            }
8953            if (clientRects.length > 0) {
8954                if (isEqual(clientRect, clientRects[clientRects.length - 1])) {
8955                    return;
8956                }
8957            }
8958            clientRects.push(clientRect);
8959        };
8960        const addCharacterOffset = (container, offset) => {
8961            const range = createRange$1(container.ownerDocument);
8962            if (offset < container.data.length) {
8963                if (isExtendingChar(container.data[offset])) {
8964                    return;
8965                }
8966                // WebKit returns two client rects for a position after an extending
8967                // character a\uxxx|b so expand on "b" and collapse to start of "b" box
8968                if (isExtendingChar(container.data[offset - 1])) {
8969                    range.setStart(container, offset);
8970                    range.setEnd(container, offset + 1);
8971                    if (!isHiddenWhiteSpaceRange(range)) {
8972                        addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect$1(range), false));
8973                        return;
8974                    }
8975                }
8976            }
8977            if (offset > 0) {
8978                range.setStart(container, offset - 1);
8979                range.setEnd(container, offset);
8980                if (!isHiddenWhiteSpaceRange(range)) {
8981                    addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect$1(range), false));
8982                }
8983            }
8984            if (offset < container.data.length) {
8985                range.setStart(container, offset);
8986                range.setEnd(container, offset + 1);
8987                if (!isHiddenWhiteSpaceRange(range)) {
8988                    addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect$1(range), true));
8989                }
8990            }
8991        };
8992        const container = caretPosition.container();
8993        const offset = caretPosition.offset();
8994        if (isText$7(container)) {
8995            addCharacterOffset(container, offset);
8996            return clientRects;
8997        }
8998        if (isElement$5(container)) {
8999            if (caretPosition.isAtEnd()) {
9000                const node = resolveIndex$1(container, offset);
9001                if (isText$7(node)) {
9002                    addCharacterOffset(node, node.data.length);
9003                }
9004                if (isValidElementCaretCandidate(node) && !isBr$4(node)) {
9005                    addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect$1(node), false));
9006                }
9007            }
9008            else {
9009                const node = resolveIndex$1(container, offset);
9010                if (isText$7(node)) {
9011                    addCharacterOffset(node, 0);
9012                }
9013                if (isValidElementCaretCandidate(node) && caretPosition.isAtEnd()) {
9014                    addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect$1(node), false));
9015                    return clientRects;
9016                }
9017                const beforeNode = resolveIndex$1(caretPosition.container(), caretPosition.offset() - 1);
9018                if (isValidElementCaretCandidate(beforeNode) && !isBr$4(beforeNode)) {
9019                    if (isBlock$3(beforeNode) || isBlock$3(node) || !isValidElementCaretCandidate(node)) {
9020                        addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect$1(beforeNode), false));
9021                    }
9022                }
9023                if (isValidElementCaretCandidate(node)) {
9024                    addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect$1(node), true));
9025                }
9026            }
9027        }
9028        return clientRects;
9029    };
9030    /**
9031     * Represents a location within the document by a container and an offset.
9032     *
9033     * @constructor
9034     * @param {Node} container Container node.
9035     * @param {Number} offset Offset within that container node.
9036     * @param {Array} clientRects Optional client rects array for the position.
9037     */
9038    const CaretPosition = (container, offset, clientRects) => {
9039        const isAtStart = () => {
9040            if (isText$7(container)) {
9041                return offset === 0;
9042            }
9043            return offset === 0;
9044        };
9045        const isAtEnd = () => {
9046            if (isText$7(container)) {
9047                return offset >= container.data.length;
9048            }
9049            return offset >= container.childNodes.length;
9050        };
9051        const toRange = () => {
9052            const range = createRange$1(container.ownerDocument);
9053            range.setStart(container, offset);
9054            range.setEnd(container, offset);
9055            return range;
9056        };
9057        const getClientRects = () => {
9058            if (!clientRects) {
9059                clientRects = getCaretPositionClientRects(CaretPosition(container, offset));
9060            }
9061            return clientRects;
9062        };
9063        const isVisible = () => getClientRects().length > 0;
9064        const isEqual = (caretPosition) => caretPosition && container === caretPosition.container() && offset === caretPosition.offset();
9065        const getNode = (before) => resolveIndex$1(container, before ? offset - 1 : offset);
9066        return {
9067            /**
9068             * Returns the container node.
9069             *
9070             * @method container
9071             * @return {Node} Container node.
9072             */
9073            container: constant(container),
9074            /**
9075             * Returns the offset within the container node.
9076             *
9077             * @method offset
9078             * @return {Number} Offset within the container node.
9079             */
9080            offset: constant(offset),
9081            /**
9082             * Returns a range out of a the caret position.
9083             *
9084             * @method toRange
9085             * @return {DOMRange} range for the caret position.
9086             */
9087            toRange,
9088            /**
9089             * Returns the client rects for the caret position. Might be multiple rects between
9090             * block elements.
9091             *
9092             * @method getClientRects
9093             * @return {Array} Array of client rects.
9094             */
9095            getClientRects,
9096            /**
9097             * Returns true if the caret location is visible/displayed on screen.
9098             *
9099             * @method isVisible
9100             * @return {Boolean} true/false if the position is visible or not.
9101             */
9102            isVisible,
9103            /**
9104             * Returns true if the caret location is at the beginning of text node or container.
9105             *
9106             * @method isVisible
9107             * @return {Boolean} true/false if the position is at the beginning.
9108             */
9109            isAtStart,
9110            /**
9111             * Returns true if the caret location is at the end of text node or container.
9112             *
9113             * @method isVisible
9114             * @return {Boolean} true/false if the position is at the end.
9115             */
9116            isAtEnd,
9117            /**
9118             * Compares the caret position to another caret position. This will only compare the
9119             * container and offset not it's visual position.
9120             *
9121             * @method isEqual
9122             * @param {tinymce.caret.CaretPosition} caretPosition Caret position to compare with.
9123             * @return {Boolean} true if the caret positions are equal.
9124             */
9125            isEqual,
9126            /**
9127             * Returns the closest resolved node from a node index. That means if you have an offset after the
9128             * last node in a container it will return that last node.
9129             *
9130             * @method getNode
9131             * @return {Node} Node that is closest to the index.
9132             */
9133            getNode
9134        };
9135    };
9136    /**
9137     * Creates a caret position from the start of a range.
9138     *
9139     * @method fromRangeStart
9140     * @param {DOMRange} range DOM Range to create caret position from.
9141     * @return {tinymce.caret.CaretPosition} Caret position from the start of DOM range.
9142     */
9143    CaretPosition.fromRangeStart = (range) => CaretPosition(range.startContainer, range.startOffset);
9144    /**
9145     * Creates a caret position from the end of a range.
9146     *
9147     * @method fromRangeEnd
9148     * @param {DOMRange} range DOM Range to create caret position from.
9149     * @return {tinymce.caret.CaretPosition} Caret position from the end of DOM range.
9150     */
9151    CaretPosition.fromRangeEnd = (range) => CaretPosition(range.endContainer, range.endOffset);
9152    /**
9153     * Creates a caret position from a node and places the offset after it.
9154     *
9155     * @method after
9156     * @param {Node} node Node to get caret position from.
9157     * @return {tinymce.caret.CaretPosition} Caret position from the node.
9158     */
9159    // TODO: TINY-8865 - This may not be safe to cast as Node and alternative solutions need to be looked into
9160    CaretPosition.after = (node) => CaretPosition(node.parentNode, nodeIndex$1(node) + 1);
9161    /**
9162     * Creates a caret position from a node and places the offset before it.
9163     *
9164     * @method before
9165     * @param {Node} node Node to get caret position from.
9166     * @return {tinymce.caret.CaretPosition} Caret position from the node.
9167     */
9168    // TODO: TINY-8865 - This may not be safe to cast as Node and alternative solutions need to be looked into
9169    CaretPosition.before = (node) => CaretPosition(node.parentNode, nodeIndex$1(node));
9170    CaretPosition.isAbove = (pos1, pos2) => lift2(head(pos2.getClientRects()), last$2(pos1.getClientRects()), isAbove$1).getOr(false);
9171    CaretPosition.isBelow = (pos1, pos2) => lift2(last$2(pos2.getClientRects()), head(pos1.getClientRects()), isBelow$1).getOr(false);
9172    CaretPosition.isAtStart = (pos) => pos ? pos.isAtStart() : false;
9173    CaretPosition.isAtEnd = (pos) => pos ? pos.isAtEnd() : false;
9174    CaretPosition.isTextPosition = (pos) => pos ? isText$b(pos.container()) : false;
9175    CaretPosition.isElementPosition = (pos) => !CaretPosition.isTextPosition(pos);
9176
9177    const trimEmptyTextNode$1 = (dom, node) => {
9178        if (isText$b(node) && node.data.length === 0) {
9179            dom.remove(node);
9180        }
9181    };
9182    const insertNode = (dom, rng, node) => {
9183        rng.insertNode(node);
9184        trimEmptyTextNode$1(dom, node.previousSibling);
9185        trimEmptyTextNode$1(dom, node.nextSibling);
9186    };
9187    const insertFragment = (dom, rng, frag) => {
9188        const firstChild = Optional.from(frag.firstChild);
9189        const lastChild = Optional.from(frag.lastChild);
9190        rng.insertNode(frag);
9191        firstChild.each((child) => trimEmptyTextNode$1(dom, child.previousSibling));
9192        lastChild.each((child) => trimEmptyTextNode$1(dom, child.nextSibling));
9193    };
9194    // Wrapper to Range.insertNode which removes any empty text nodes created in the process.
9195    // Doesn't merge adjacent text nodes - this is according to the DOM spec.
9196    const rangeInsertNode = (dom, rng, node) => {
9197        if (isDocumentFragment(node)) {
9198            insertFragment(dom, rng, node);
9199        }
9200        else {
9201            insertNode(dom, rng, node);
9202        }
9203    };
9204
9205    /**
9206     * This module creates or resolves xpath like string representation of a CaretPositions.
9207     *
9208     * The format is a / separated list of chunks with:
9209     * <element|text()>[index|after|before]
9210     *
9211     * For example:
9212     *  p[0]/b[0]/text()[0],1 = <p><b>a|c</b></p>
9213     *  p[0]/img[0],before = <p>|<img></p>
9214     *  p[0]/img[0],after = <p><img>|</p>
9215     *
9216     * @private
9217     * @static
9218     * @class tinymce.caret.CaretBookmark
9219     * @example
9220     * const bookmark = CaretBookmark.create(rootElm, CaretPosition.before(rootElm.firstChild));
9221     * const caretPosition = CaretBookmark.resolve(bookmark);
9222     */
9223    const isText$6 = isText$b;
9224    const isBogus = isBogus$1;
9225    const nodeIndex = DOMUtils.nodeIndex;
9226    const normalizedParent = (node) => {
9227        const parentNode = node.parentNode;
9228        if (isBogus(parentNode)) {
9229            return normalizedParent(parentNode);
9230        }
9231        return parentNode;
9232    };
9233    const getChildNodes = (node) => {
9234        if (!node) {
9235            return [];
9236        }
9237        return reduce(node.childNodes, (result, node) => {
9238            if (isBogus(node) && node.nodeName !== 'BR') {
9239                result = result.concat(getChildNodes(node));
9240            }
9241            else {
9242                result.push(node);
9243            }
9244            return result;
9245        }, []);
9246    };
9247    const normalizedTextOffset = (node, offset) => {
9248        let tempNode = node;
9249        while ((tempNode = tempNode.previousSibling)) {
9250            if (!isText$6(tempNode)) {
9251                break;
9252            }
9253            offset += tempNode.data.length;
9254        }
9255        return offset;
9256    };
9257    const equal = (a) => (b) => a === b;
9258    const normalizedNodeIndex = (node) => {
9259        let nodes, index;
9260        nodes = getChildNodes(normalizedParent(node));
9261        index = findIndex$1(nodes, equal(node), node);
9262        nodes = nodes.slice(0, index + 1);
9263        const numTextFragments = reduce(nodes, (result, node, i) => {
9264            if (isText$6(node) && isText$6(nodes[i - 1])) {
9265                result++;
9266            }
9267            return result;
9268        }, 0);
9269        nodes = filter$3(nodes, matchNodeNames$1([node.nodeName]));
9270        index = findIndex$1(nodes, equal(node), node);
9271        return index - numTextFragments;
9272    };
9273    const createPathItem = (node) => {
9274        const name = isText$6(node) ? 'text()' : node.nodeName.toLowerCase();
9275        return name + '[' + normalizedNodeIndex(node) + ']';
9276    };
9277    const parentsUntil$1 = (root, node, predicate) => {
9278        const parents = [];
9279        for (let tempNode = node.parentNode; tempNode && tempNode !== root; tempNode = tempNode.parentNode) {
9280            if (predicate && predicate(tempNode)) {
9281                break;
9282            }
9283            parents.push(tempNode);
9284        }
9285        return parents;
9286    };
9287    const create$9 = (root, caretPosition) => {
9288        let path = [];
9289        let container = caretPosition.container();
9290        let offset = caretPosition.offset();
9291        let outputOffset;
9292        if (isText$6(container)) {
9293            outputOffset = normalizedTextOffset(container, offset);
9294        }
9295        else {
9296            const childNodes = container.childNodes;
9297            if (offset >= childNodes.length) {
9298                outputOffset = 'after';
9299                offset = childNodes.length - 1;
9300            }
9301            else {
9302                outputOffset = 'before';
9303            }
9304            container = childNodes[offset];
9305        }
9306        path.push(createPathItem(container));
9307        let parents = parentsUntil$1(root, container);
9308        parents = filter$3(parents, not(isBogus$1));
9309        path = path.concat(map$1(parents, (node) => {
9310            return createPathItem(node);
9311        }));
9312        return path.reverse().join('/') + ',' + outputOffset;
9313    };
9314    const resolvePathItem = (node, name, index) => {
9315        let nodes = getChildNodes(node);
9316        nodes = filter$3(nodes, (node, index) => {
9317            return !isText$6(node) || !isText$6(nodes[index - 1]);
9318        });
9319        nodes = filter$3(nodes, matchNodeNames$1([name]));
9320        return nodes[index];
9321    };
9322    const findTextPosition = (container, offset) => {
9323        let node = container;
9324        let targetOffset = 0;
9325        while (isText$6(node)) {
9326            const dataLen = node.data.length;
9327            if (offset >= targetOffset && offset <= targetOffset + dataLen) {
9328                container = node;
9329                offset = offset - targetOffset;
9330                break;
9331            }
9332            if (!isText$6(node.nextSibling)) {
9333                container = node;
9334                offset = dataLen;
9335                break;
9336            }
9337            targetOffset += dataLen;
9338            node = node.nextSibling;
9339        }
9340        if (isText$6(container) && offset > container.data.length) {
9341            offset = container.data.length;
9342        }
9343        return CaretPosition(container, offset);
9344    };
9345    const resolve$1 = (root, path) => {
9346        if (!path) {
9347            return null;
9348        }
9349        const parts = path.split(',');
9350        const paths = parts[0].split('/');
9351        const offset = parts.length > 1 ? parts[1] : 'before';
9352        const container = reduce(paths, (result, value) => {
9353            const match = /([\w\-\(\)]+)\[([0-9]+)\]/.exec(value);
9354            if (!match) {
9355                return null;
9356            }
9357            if (match[1] === 'text()') {
9358                match[1] = '#text';
9359            }
9360            return resolvePathItem(result, match[1], parseInt(match[2], 10));
9361        }, root);
9362        if (!container) {
9363            return null;
9364        }
9365        if (!isText$6(container) && container.parentNode) {
9366            let nodeOffset;
9367            if (offset === 'after') {
9368                nodeOffset = nodeIndex(container) + 1;
9369            }
9370            else {
9371                nodeOffset = nodeIndex(container);
9372            }
9373            return CaretPosition(container.parentNode, nodeOffset);
9374        }
9375        return findTextPosition(container, parseInt(offset, 10));
9376    };
9377
9378    const isContentEditableFalse$8 = isContentEditableFalse$a;
9379    const getNormalizedTextOffset$1 = (trim, container, offset) => {
9380        let trimmedOffset = trim(container.data.slice(0, offset)).length;
9381        for (let node = container.previousSibling; node && isText$b(node); node = node.previousSibling) {
9382            trimmedOffset += trim(node.data).length;
9383        }
9384        return trimmedOffset;
9385    };
9386    const getPoint = (dom, trim, normalized, rng, start) => {
9387        const container = start ? rng.startContainer : rng.endContainer;
9388        let offset = start ? rng.startOffset : rng.endOffset;
9389        const point = [];
9390        const root = dom.getRoot();
9391        if (isText$b(container)) {
9392            point.push(normalized ? getNormalizedTextOffset$1(trim, container, offset) : offset);
9393        }
9394        else {
9395            let after = 0;
9396            const childNodes = container.childNodes;
9397            if (offset >= childNodes.length && childNodes.length) {
9398                after = 1;
9399                offset = Math.max(0, childNodes.length - 1);
9400            }
9401            point.push(dom.nodeIndex(childNodes[offset], normalized) + after);
9402        }
9403        for (let node = container; node && node !== root; node = node.parentNode) {
9404            point.push(dom.nodeIndex(node, normalized));
9405        }
9406        return point;
9407    };
9408    const getLocation = (trim, selection, normalized, rng) => {
9409        const dom = selection.dom;
9410        const start = getPoint(dom, trim, normalized, rng, true);
9411        const forward = selection.isForward();
9412        const fakeCaret = isRangeInCaretContainerBlock(rng) ? { isFakeCaret: true } : {};
9413        if (!selection.isCollapsed()) {
9414            const end = getPoint(dom, trim, normalized, rng, false);
9415            return { start, end, forward, ...fakeCaret };
9416        }
9417        else {
9418            return { start, forward, ...fakeCaret };
9419        }
9420    };
9421    const findIndex = (dom, name, element) => {
9422        let count = 0;
9423        Tools.each(dom.select(name), (node) => {
9424            if (node.getAttribute('data-mce-bogus') === 'all') {
9425                return;
9426            }
9427            else if (node === element) {
9428                return false;
9429            }
9430            else {
9431                count++;
9432                return;
9433            }
9434        });
9435        return count;
9436    };
9437    const moveEndPoint$1 = (rng, start) => {
9438        let container = start ? rng.startContainer : rng.endContainer;
9439        let offset = start ? rng.startOffset : rng.endOffset;
9440        // normalize Table Cell selection
9441        if (isElement$7(container) && container.nodeName === 'TR') {
9442            const childNodes = container.childNodes;
9443            container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)];
9444            if (container) {
9445                offset = start ? 0 : container.childNodes.length;
9446                if (start) {
9447                    rng.setStart(container, offset);
9448                }
9449                else {
9450                    rng.setEnd(container, offset);
9451                }
9452            }
9453        }
9454    };
9455    const normalizeTableCellSelection = (rng) => {
9456        moveEndPoint$1(rng, true);
9457        moveEndPoint$1(rng, false);
9458        return rng;
9459    };
9460    const findSibling = (node, offset) => {
9461        if (isElement$7(node)) {
9462            node = getNode$1(node, offset);
9463            if (isContentEditableFalse$8(node)) {
9464                return node;
9465            }
9466        }
9467        if (isCaretContainer$2(node)) {
9468            if (isText$b(node) && isCaretContainerBlock$1(node)) {
9469                node = node.parentNode;
9470            }
9471            let sibling = node.previousSibling;
9472            if (isContentEditableFalse$8(sibling)) {
9473                return sibling;
9474            }
9475            sibling = node.nextSibling;
9476            if (isContentEditableFalse$8(sibling)) {
9477                return sibling;
9478            }
9479        }
9480        return undefined;
9481    };
9482    const findAdjacentContentEditableFalseElm = (rng) => {
9483        return findSibling(rng.startContainer, rng.startOffset) || findSibling(rng.endContainer, rng.endOffset);
9484    };
9485    const getOffsetBookmark = (trim, normalized, selection) => {
9486        const element = selection.getNode();
9487        const rng = selection.getRng();
9488        if (element.nodeName === 'IMG' || isContentEditableFalse$8(element)) {
9489            const name = element.nodeName;
9490            return { name, index: findIndex(selection.dom, name, element) };
9491        }
9492        const sibling = findAdjacentContentEditableFalseElm(rng);
9493        if (sibling) {
9494            const name = sibling.tagName;
9495            return { name, index: findIndex(selection.dom, name, sibling) };
9496        }
9497        return getLocation(trim, selection, normalized, rng);
9498    };
9499    const getCaretBookmark = (selection) => {
9500        const rng = selection.getRng();
9501        return {
9502            start: create$9(selection.dom.getRoot(), CaretPosition.fromRangeStart(rng)),
9503            end: create$9(selection.dom.getRoot(), CaretPosition.fromRangeEnd(rng)),
9504            forward: selection.isForward()
9505        };
9506    };
9507    const getRangeBookmark = (selection) => {
9508        return { rng: selection.getRng(), forward: selection.isForward() };
9509    };
9510    const createBookmarkSpan = (dom, id, filled) => {
9511        const args = { 'data-mce-type': 'bookmark', id, 'style': 'overflow:hidden;line-height:0px' };
9512        return filled ? dom.create('span', args, '&#xFEFF;') : dom.create('span', args);
9513    };
9514    const getPersistentBookmark = (selection, filled) => {
9515        const dom = selection.dom;
9516        let rng = selection.getRng();
9517        const id = dom.uniqueId();
9518        const collapsed = selection.isCollapsed();
9519        const element = selection.getNode();
9520        const name = element.nodeName;
9521        const forward = selection.isForward();
9522        if (name === 'IMG') {
9523            return { name, index: findIndex(dom, name, element) };
9524        }
9525        // W3C method
9526        const rng2 = normalizeTableCellSelection(rng.cloneRange());
9527        // Insert end marker
9528        if (!collapsed) {
9529            rng2.collapse(false);
9530            const endBookmarkNode = createBookmarkSpan(dom, id + '_end', filled);
9531            rangeInsertNode(dom, rng2, endBookmarkNode);
9532        }
9533        rng = normalizeTableCellSelection(rng);
9534        rng.collapse(true);
9535        const startBookmarkNode = createBookmarkSpan(dom, id + '_start', filled);
9536        rangeInsertNode(dom, rng, startBookmarkNode);
9537        selection.moveToBookmark({ id, keep: true, forward });
9538        return { id, forward };
9539    };
9540    const getBookmark$2 = (selection, type, normalized = false) => {
9541        if (type === 2) {
9542            return getOffsetBookmark(trim$2, normalized, selection);
9543        }
9544        else if (type === 3) {
9545            return getCaretBookmark(selection);
9546        }
9547        else if (type) {
9548            return getRangeBookmark(selection);
9549        }
9550        else {
9551            return getPersistentBookmark(selection, false);
9552        }
9553    };
9554    const getUndoBookmark = curry(getOffsetBookmark, identity, true);
9555
9556    /**
9557     * Checks if the direction is forwards.
9558     */
9559    const isForwards = (direction) => direction === 1 /* HDirection.Forwards */;
9560    /**
9561     * Checks if the direction is backwards.
9562     */
9563    const isBackwards = (direction) => direction === -1 /* HDirection.Backwards */;
9564
9565    var SimpleResultType;
9566    (function (SimpleResultType) {
9567        SimpleResultType[SimpleResultType["Error"] = 0] = "Error";
9568        SimpleResultType[SimpleResultType["Value"] = 1] = "Value";
9569    })(SimpleResultType || (SimpleResultType = {}));
9570    const fold$1 = (res, onError, onValue) => res.stype === SimpleResultType.Error ? onError(res.serror) : onValue(res.svalue);
9571    const partition = (results) => {
9572        const values = [];
9573        const errors = [];
9574        each$e(results, (obj) => {
9575            fold$1(obj, (err) => errors.push(err), (val) => values.push(val));
9576        });
9577        return { values, errors };
9578    };
9579    const mapError = (res, f) => {
9580        if (res.stype === SimpleResultType.Error) {
9581            return { stype: SimpleResultType.Error, serror: f(res.serror) };
9582        }
9583        else {
9584            return res;
9585        }
9586    };
9587    const map = (res, f) => {
9588        if (res.stype === SimpleResultType.Value) {
9589            return { stype: SimpleResultType.Value, svalue: f(res.svalue) };
9590        }
9591        else {
9592            return res;
9593        }
9594    };
9595    const bind = (res, f) => {
9596        if (res.stype === SimpleResultType.Value) {
9597            return f(res.svalue);
9598        }
9599        else {
9600            return res;
9601        }
9602    };
9603    const bindError = (res, f) => {
9604        if (res.stype === SimpleResultType.Error) {
9605            return f(res.serror);
9606        }
9607        else {
9608            return res;
9609        }
9610    };
9611    const svalue = (v) => ({ stype: SimpleResultType.Value, svalue: v });
9612    const serror = (e) => ({ stype: SimpleResultType.Error, serror: e });
9613    const toResult = (res) => fold$1(res, Result.error, Result.value);
9614    const fromResult = (res) => res.fold(serror, svalue);
9615    const SimpleResult = {
9616        fromResult,
9617        toResult,
9618        svalue,
9619        partition,
9620        serror,
9621        bind,
9622        bindError,
9623        map,
9624        mapError,
9625        fold: fold$1
9626    };
9627
9628    const formatObj = (input) => {
9629        return isObject(input) && keys(input).length > 100 ? ' removed due to size' : JSON.stringify(input, null, 2);
9630    };
9631    const formatErrors = (errors) => {
9632        const es = errors.length > 10 ? errors.slice(0, 10).concat([
9633            {
9634                path: [],
9635                getErrorInfo: constant('... (only showing first ten failures)')
9636            }
9637        ]) : errors;
9638        // TODO: Work out a better split between PrettyPrinter and SchemaError
9639        return map$3(es, (e) => {
9640            return 'Failed path: (' + e.path.join(' > ') + ')\n' + e.getErrorInfo();
9641        });
9642    };
9643
9644    const nu = (path, getErrorInfo) => {
9645        return SimpleResult.serror([{
9646                path,
9647                // This is lazy so that it isn't calculated unnecessarily
9648                getErrorInfo
9649            }]);
9650    };
9651    const missingRequired = (path, key, obj) => nu(path, () => 'Could not find valid *required* value for "' + key + '" in ' + formatObj(obj));
9652    const custom = (path, err) => nu(path, constant(err));
9653
9654    const value = (validator) => {
9655        const extract = (path, val) => {
9656            return SimpleResult.bindError(validator(val), (err) => custom(path, err));
9657        };
9658        const toString = constant('val');
9659        return {
9660            extract,
9661            toString
9662        };
9663    };
9664    const anyValue$1 = value(SimpleResult.svalue);
9665
9666    const anyValue = constant(anyValue$1);
9667    const typedValue = (validator, expectedType) => value((a) => {
9668        const actualType = typeof a;
9669        return validator(a) ? SimpleResult.svalue(a) : SimpleResult.serror(`Expected type: ${expectedType} but got: ${actualType}`);
9670    });
9671    const number = typedValue(isNumber, 'number');
9672    const string = typedValue(isString, 'string');
9673    const functionProcessor = typedValue(isFunction, 'function');
9674
9675    const required$1 = () => ({ tag: "required" /* FieldPresenceTag.Required */, process: {} });
9676    const defaultedThunk = (fallbackThunk) => ({ tag: "defaultedThunk" /* FieldPresenceTag.DefaultedThunk */, process: fallbackThunk });
9677    const defaulted$1 = (fallback) => defaultedThunk(constant(fallback));
9678    const asOption = () => ({ tag: "option" /* FieldPresenceTag.Option */, process: {} });
9679
9680    const field$1 = (key, newKey, presence, prop) => ({ tag: "field" /* FieldTag.Field */, key, newKey, presence, prop });
9681    const fold = (value, ifField, ifCustom) => {
9682        switch (value.tag) {
9683            case "field" /* FieldTag.Field */:
9684                return ifField(value.key, value.newKey, value.presence, value.prop);
9685            case "custom" /* FieldTag.CustomField */:
9686                return ifCustom(value.newKey, value.instantiator);
9687        }
9688    };
9689
9690    const mergeValues = (values, base) => {
9691        return SimpleResult.svalue(deepMerge(base, merge$1.apply(undefined, values)));
9692    };
9693    const mergeErrors = (errors) => compose(SimpleResult.serror, flatten$1)(errors);
9694    const consolidateObj = (objects, base) => {
9695        const partition = SimpleResult.partition(objects);
9696        return partition.errors.length > 0 ? mergeErrors(partition.errors) : mergeValues(partition.values, base);
9697    };
9698    const consolidateArr = (objects) => {
9699        const partitions = SimpleResult.partition(objects);
9700        return partitions.errors.length > 0 ? mergeErrors(partitions.errors) : SimpleResult.svalue(partitions.values);
9701    };
9702    const ResultCombine = {
9703        consolidateObj,
9704        consolidateArr
9705    };
9706
9707    const requiredAccess = (path, obj, key, bundle) => 
9708    // In required mode, if it is undefined, it is an error.
9709    get$
9709a(obj, key).fold(() => missingRequired(path, key, obj), bundle);
9710    const fallbackAccess = (obj, key, fallback, bundle) => {
9711        const v = get$a(obj, key).getOrThunk(() => fallback(obj));
9712        return bundle(v);
9713    };
9714    const optionAccess = (obj, key, bundle) => bundle(get$a(obj, key));
9715    const optionDefaultedAccess = (obj, key, fallback, bundle) => {
9716        const opt = get$a
vendor: 13,726 bytes, lines 9716-10009
9716(obj, key).map((val) => val === true ? fallback(obj) : val);
9717        return bundle(opt);
9718    };
9719    const extractField = (field, path, obj, key, prop) => {
9720        const bundle = (av) => prop.extract(path.concat([key]), av);
9721        const bundleAsOption = (optValue) => optValue.fold(() => SimpleResult.svalue(Optional.none()), (ov) => {
9722            const result = prop.extract(path.concat([key]), ov);
9723            return SimpleResult.map(result, Optional.some);
9724        });
9725        switch (field.tag) {
9726            case "required" /* FieldPresenceTag.Required */:
9727                return requiredAccess(path, obj, key, bundle);
9728            case "defaultedThunk" /* FieldPresenceTag.DefaultedThunk */:
9729                return fallbackAccess(obj, key, field.process, bundle);
9730            case "option" /* FieldPresenceTag.Option */:
9731                return optionAccess(obj, key, bundleAsOption);
9732            case "defaultedOptionThunk" /* FieldPresenceTag.DefaultedOptionThunk */:
9733                return optionDefaultedAccess(obj, key, field.process, bundleAsOption);
9734            case "mergeWithThunk" /* FieldPresenceTag.MergeWithThunk */: {
9735                return fallbackAccess(obj, key, constant({}), (v) => {
9736                    const result = deepMerge(field.process(obj), v);
9737                    return bundle(result);
9738                });
9739            }
9740        }
9741    };
9742    const extractFields = (path, obj, fields) => {
9743        const success = {};
9744        const errors = [];
9745        // PERFORMANCE: We use a for loop here instead of Arr.each as this is a hot code path
9746        for (const field of fields) {
9747            fold(field, (key, newKey, presence, prop) => {
9748                const result = extractField(presence, path, obj, key, prop);
9749                SimpleResult.fold(result, (err) => {
9750                    errors.push(...err);
9751                }, (res) => {
9752                    success[newKey] = res;
9753                });
9754            }, (newKey, instantiator) => {
9755                success[newKey] = instantiator(obj);
9756            });
9757        }
9758        return errors.length > 0 ? SimpleResult.serror(errors) : SimpleResult.svalue(success);
9759    };
9760    const objOf = (values) => {
9761        const extract = (path, o) => extractFields(path, o, values);
9762        const toString = () => {
9763            const fieldStrings = map$3(values, (value) => fold(value, (key, _okey, _presence, prop) => key + ' -> ' + prop.toString(), (newKey, _instantiator) => 'state(' + newKey + ')'));
9764            return 'obj{\n' + fieldStrings.join('\n') + '}';
9765        };
9766        return {
9767            extract,
9768            toString
9769        };
9770    };
9771    const arrOf = (prop) => {
9772        const extract = (path, array) => {
9773            const results = map$3(array, (a, i) => prop.extract(path.concat(['[' + i + ']']), a));
9774            return ResultCombine.consolidateArr(results);
9775        };
9776        const toString = () => 'array(' + prop.toString() + ')';
9777        return {
9778            extract,
9779            toString
9780        };
9781    };
9782    const arrOfObj = compose(arrOf, objOf);
9783
9784    const valueOf = (validator) => value((v) => validator(v).fold(SimpleResult.serror, SimpleResult.svalue));
9785    const extractValue = (label, prop, obj) => {
9786        const res = prop.extract([label], obj);
9787        return SimpleResult.mapError(res, (errs) => ({ input: obj, errors: errs }));
9788    };
9789    const asRaw = (label, prop, obj) => SimpleResult.toResult(extractValue(label, prop, obj));
9790    const formatError = (errInfo) => {
9791        return 'Errors: \n' + formatErrors(errInfo.errors).join('\n') +
9792            '\n\nInput object: ' + formatObj(errInfo.input);
9793    };
9794
9795    const field = field$1;
9796    const required = (key) => field(key, key, required$1(), anyValue());
9797    const requiredOf = (key, schema) => field(key, key, required$1(), schema);
9798    const requiredString = (key) => requiredOf(key, string);
9799    const requiredFunction = (key) => requiredOf(key, functionProcessor);
9800    const requiredArrayOf = (key, schema) => field(key, key, required$1(), arrOf(schema));
9801    const option$1 = (key) => field(key, key, asOption(), anyValue());
9802    const optionOf = (key, schema) => field(key, key, asOption(), schema);
9803    const optionString = (key) => optionOf(key, string);
9804    const optionFunction = (key) => optionOf(key, functionProcessor);
9805    const defaulted = (key, fallback) => field(key, key, defaulted$1(fallback), anyValue());
9806    const defaultedOf = (key, fallback, schema) => field(key, key, defaulted$1(fallback), schema);
9807    const defaultedNumber = (key, fallback) => defaultedOf(key, fallback, number);
9808    const defaultedArrayOf = (key, fallback, schema) => defaultedOf(key, fallback, arrOf(schema));
9809
9810    const isInlinePattern = (pattern) => pattern.type === 'inline-command' || pattern.type === 'inline-format';
9811    const isBlockPattern = (pattern) => pattern.type === 'block-command' || pattern.type === 'block-format';
9812    const hasBlockTrigger = (pattern, trigger) => (pattern.type === 'block-command' || pattern.type === 'block-format') && pattern.trigger === trigger;
9813    const normalizePattern = (pattern) => {
9814        const err = (message) => Result.error({ message, pattern });
9815        const formatOrCmd = (name, onFormat, onCommand) => {
9816            if (pattern.format !== undefined) {
9817                let formats;
9818                if (isArray$1(pattern.format)) {
9819                    if (!forall(pattern.format, isString)) {
9820                        return err(name + ' pattern has non-string items in the `format` array');
9821                    }
9822                    formats = pattern.format;
9823                }
9824                else if (isString(pattern.format)) {
9825                    formats = [pattern.format];
9826                }
9827                else {
9828                    return err(name + ' pattern has non-string `format` parameter');
9829                }
9830                return Result.value(onFormat(formats));
9831            }
9832            else if (pattern.cmd !== undefined) {
9833                if (!isString(pattern.cmd)) {
9834                    return err(name + ' pattern has non-string `cmd` parameter');
9835                }
9836                return Result.value(onCommand(pattern.cmd, pattern.value));
9837            }
9838            else {
9839                return err(name + ' pattern is missing both `format` and `cmd` parameters');
9840            }
9841        };
9842        if (!isObject(pattern)) {
9843            return err('Raw pattern is not an object');
9844        }
9845        if (!isString(pattern.start)) {
9846            return err('Raw pattern is missing `start` parameter');
9847        }
9848        if (pattern.end !== undefined) {
9849            // inline pattern
9850            if (!isString(pattern.end)) {
9851                return err('Inline pattern has non-string `end` parameter');
9852            }
9853            if (pattern.start.length === 0 && pattern.end.length === 0) {
9854                return err('Inline pattern has empty `start` and `end` parameters');
9855            }
9856            let start = pattern.start;
9857            let end = pattern.end;
9858            // when the end is empty swap with start as it is more efficient
9859            if (end.length === 0) {
9860                end = start;
9861                start = '';
9862            }
9863            return formatOrCmd('Inline', (format) => ({ type: 'inline-format', start, end, format }), (cmd, value) => ({ type: 'inline-command', start, end, cmd, value }));
9864        }
9865        else if (pattern.replacement !== undefined) {
9866            // replacement pattern
9867            if (!isString(pattern.replacement)) {
9868                return err('Replacement pattern has non-string `replacement` parameter');
9869            }
9870            if (pattern.start.length === 0) {
9871                return err('Replacement pattern has empty `start` parameter');
9872            }
9873            return Result.value({
9874                type: 'inline-command',
9875                start: '',
9876                end: pattern.start,
9877                cmd: 'mceInsertContent',
9878                value: pattern.replacement
9879            });
9880        }
9881        else {
9882            // block pattern
9883            const trigger = pattern.trigger ?? 'space';
9884            if (pattern.start.length === 0) {
9885                return err('Block pattern has empty `start` parameter');
9886            }
9887            return formatOrCmd('Block', (formats) => ({
9888                type: 'block-format',
9889                start: pattern.start,
9890                format: formats[0],
9891                trigger
9892            }), (command, commandValue) => ({
9893                type: 'block-command',
9894                start: pattern.start,
9895                cmd: command,
9896                value: commandValue,
9897                trigger
9898            }));
9899        }
9900    };
9901    const getBlockPatterns = (patterns) => filter$5(patterns, isBlockPattern);
9902    const getInlinePatterns = (patterns) => filter$5(patterns, isInlinePattern);
9903    const createPatternSet = (patterns, dynamicPatternsLookup) => ({
9904        inlinePatterns: getInlinePatterns(patterns),
9905        blockPatterns: getBlockPatterns(patterns),
9906        dynamicPatternsLookup
9907    });
9908    const filterByTrigger = (patterns, trigger) => {
9909        return {
9910            ...patterns,
9911            blockPatterns: filter$5(patterns.blockPatterns, (pattern) => hasBlockTrigger(pattern, trigger))
9912        };
9913    };
9914    const fromRawPatterns = (patterns) => {
9915        const normalized = partition$1(map$3(patterns, normalizePattern));
9916        // eslint-disable-next-line no-console
9917        each$e(normalized.errors, (err) => console.error(err.message, err.pattern));
9918        return normalized.values;
9919    };
9920    const fromRawPatternsLookup = (lookupFn) => {
9921        return (ctx) => {
9922            const rawPatterns = lookupFn(ctx);
9923            return fromRawPatterns(rawPatterns);
9924        };
9925    };
9926
9927    const firePreProcess = (editor, args) => editor.dispatch('PreProcess', args);
9928    const firePostProcess = (editor, args) => editor.dispatch('PostProcess', args);
9929    const fireRemove = (editor) => {
9930        editor.dispatch('remove');
9931    };
9932    const fireDetach = (editor) => {
9933        editor.dispatch('detach');
9934    };
9935    const fireSwitchMode = (editor, mode) => {
9936        editor.dispatch('SwitchMode', { mode });
9937    };
9938    const fireObjectResizeStart = (editor, target, width, height, origin) => {
9939        editor.dispatch('ObjectResizeStart', { target, width, height, origin });
9940    };
9941    const fireObjectResized = (editor, target, width, height, origin) => {
9942        editor.dispatch('ObjectResized', { target, width, height, origin });
9943    };
9944    const firePreInit = (editor) => {
9945        editor.dispatch('PreInit');
9946    };
9947    const firePostRender = (editor) => {
9948        editor.dispatch('PostRender');
9949    };
9950    const fireInit = (editor) => {
9951        editor.dispatch('Init');
9952    };
9953    const firePlaceholderToggle = (editor, state) => {
9954        editor.dispatch('PlaceholderToggle', { state });
9955    };
9956    const fireError = (editor, errorType, error) => {
9957        editor.dispatch(errorType, error);
9958    };
9959    const fireFormatApply = (editor, format, node, vars) => {
9960        editor.dispatch('FormatApply', { format, node, vars });
9961    };
9962    const fireFormatRemove = (editor, format, node, vars) => {
9963        editor.dispatch('FormatRemove', { format, node, vars });
9964    };
9965    const fireBeforeSetContent = (editor, args) => editor.dispatch('BeforeSetContent', args);
9966    const fireSetContent = (editor, args) => editor.dispatch('SetContent', args);
9967    const fireBeforeGetContent = (editor, args) => editor.dispatch('BeforeGetContent', args);
9968    const fireGetContent = (editor, args) => editor.dispatch('GetContent', args);
9969    const fireAutocompleterStart = (editor, args) => {
9970        editor.dispatch('AutocompleterStart', args);
9971    };
9972    const fireAutocompleterUpdate = (editor, args) => {
9973        editor.dispatch('AutocompleterUpdate', args);
9974    };
9975    const fireAutocompleterUpdateActiveRange = (editor, args) => {
9976        editor.dispatch('AutocompleterUpdateActiveRange', args);
9977    };
9978    const fireAutocompleterEnd = (editor) => {
9979        editor.dispatch('AutocompleterEnd');
9980    };
9981    const firePastePreProcess = (editor, html, internal) => editor.dispatch('PastePreProcess', { content: html, internal });
9982    const firePastePostProcess = (editor, node, internal) => editor.dispatch('PastePostProcess', { node, internal });
9983    const firePastePlainTextToggle = (editor, state) => editor.dispatch('PastePlainTextToggle', { state });
9984    const fireEditableRootStateChange = (editor, state) => editor.dispatch('EditableRootStateChange', { state });
9985    const fireDisabledStateChange = (editor, state) => editor.dispatch('DisabledStateChange', { state });
9986    const fireCloseTooltips = (editor) => editor.dispatch('CloseActiveTooltips');
9987
9988    const deviceDetection$1 = detect$1().deviceType;
9989    const isTouch = deviceDetection$1.isTouch();
9990    const DOM$d = DOMUtils.DOM;
9991    const getHash = (value) => {
9992        const items = value.indexOf('=') > 0 ? value.split(/[;,](?![^=;,]*(?:[;,]|$))/) : value.split(',');
9993        return foldl(items, (output, item) => {
9994            const arr = item.split('=');
9995            const key = arr[0];
9996            const val = arr.length > 1 ? arr[1] : key;
9997            output[trim$4(key)] = trim$4(val);
9998            return output;
9999        }, {});
10000    };
10001    const isRegExp = (x) => is$5(x, RegExp);
10002    const option = (name) => (editor) => editor.options.get(name);
10003    const stringOrObjectProcessor = (value) => isString(value) || isObject(value);
10004    const bodyOptionProcessor = (editor, defaultValue = '') => (value) => {
10005        const valid = isString(value);
10006        if (valid) {
10007            if (value.indexOf('=') !== -1) {
10008                const bodyObj = getHash(value);
10009                return { value: get$
vendor: 7,863 bytes, lines 10009-10238
10009a(bodyObj, editor.id).getOr(defaultValue), valid };
10010            }
10011            else {
10012                return { value, valid };
10013            }
10014        }
10015        else {
10016            return { valid: false, message: 'Must be a string.' };
10017        }
10018    };
10019    const register$7 = (editor) => {
10020        const registerOption = editor.options.register;
10021        registerOption('id', {
10022            processor: 'string',
10023            default: editor.id
10024        });
10025        registerOption('selector', {
10026            processor: 'string'
10027        });
10028        registerOption('target', {
10029            processor: 'object'
10030        });
10031        registerOption('suffix', {
10032            processor: 'string'
10033        });
10034        registerOption('cache_suffix', {
10035            processor: 'string'
10036        });
10037        registerOption('base_url', {
10038            processor: 'string'
10039        });
10040        registerOption('referrer_policy', {
10041            processor: 'string',
10042            default: ''
10043        });
10044        registerOption('crossorigin', {
10045            processor: 'function',
10046            default: constant(undefined)
10047        });
10048        registerOption('language_load', {
10049            processor: 'boolean',
10050            default: true
10051        });
10052        registerOption('inline', {
10053            processor: 'boolean',
10054            default: false
10055        });
10056        registerOption('iframe_attrs', {
10057            processor: 'object',
10058            default: {}
10059        });
10060        registerOption('doctype', {
10061            processor: 'string',
10062            default: '<!DOCTYPE html>'
10063        });
10064        registerOption('document_base_url', {
10065            processor: 'string',
10066            default: editor.editorManager.documentBaseURL
10067        });
10068        registerOption('body_id', {
10069            processor: bodyOptionProcessor(editor, 'tinymce'),
10070            default: 'tinymce'
10071        });
10072        registerOption('body_class', {
10073            processor: bodyOptionProcessor(editor),
10074            default: ''
10075        });
10076        registerOption('content_security_policy', {
10077            processor: 'string',
10078            default: ''
10079        });
10080        registerOption('br_in_pre', {
10081            processor: 'boolean',
10082            default: true
10083        });
10084        registerOption('forced_root_block', {
10085            processor: (value) => {
10086                const valid = isString(value) && isNotEmpty(value);
10087                if (valid) {
10088                    return { value, valid };
10089                }
10090                else {
10091                    return { valid: false, message: 'Must be a non-empty string.' };
10092                }
10093            },
10094            default: 'p'
10095        });
10096        registerOption('forced_root_block_attrs', {
10097            processor: 'object',
10098            default: {}
10099        });
10100        registerOption('newline_behavior', {
10101            processor: (value) => {
10102                const valid = contains$2(['block', 'linebreak', 'invert', 'default'], value);
10103                return valid ? { value, valid } : { valid: false, message: 'Must be one of: block, linebreak, invert or default.' };
10104            },
10105            default: 'default'
10106        });
10107        registerOption('br_newline_selector', {
10108            processor: 'string',
10109            default: '.mce-toc h2,figcaption,caption'
10110        });
10111        registerOption('no_newline_selector', {
10112            processor: 'string',
10113            default: ''
10114        });
10115        registerOption('keep_styles', {
10116            processor: 'boolean',
10117            default: true
10118        });
10119        registerOption('end_container_on_empty_block', {
10120            processor: (value) => {
10121                if (isBoolean(value)) {
10122                    return { valid: true, value };
10123                }
10124                else if (isString(value)) {
10125                    return { valid: true, value };
10126                }
10127                else {
10128                    return { valid: false, message: 'Must be boolean or a string' };
10129                }
10130            },
10131            default: 'blockquote'
10132        });
10133        registerOption('font_size_style_values', {
10134            processor: 'string',
10135            default: 'xx-small,x-small,small,medium,large,x-large,xx-large'
10136        });
10137        registerOption('font_size_legacy_values', {
10138            processor: 'string',
10139            // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size
10140            default: 'xx-small,small,medium,large,x-large,xx-large,300%'
10141        });
10142        registerOption('font_size_classes', {
10143            processor: 'string',
10144            default: ''
10145        });
10146        registerOption('automatic_uploads', {
10147            processor: 'boolean',
10148            default: true
10149        });
10150        registerOption('images_reuse_filename', {
10151            processor: 'boolean',
10152            default: false
10153        });
10154        registerOption('images_replace_blob_uris', {
10155            processor: 'boolean',
10156            default: true
10157        });
10158        registerOption('icons', {
10159            processor: 'string',
10160            default: ''
10161        });
10162        registerOption('icons_url', {
10163            processor: 'string',
10164            default: ''
10165        });
10166        registerOption('images_upload_url', {
10167            processor: 'string',
10168            default: ''
10169        });
10170        registerOption('images_upload_base_path', {
10171            processor: 'string',
10172            default: ''
10173        });
10174        registerOption('images_upload_credentials', {
10175            processor: 'boolean',
10176            default: false
10177        });
10178        registerOption('images_upload_handler', {
10179            processor: 'function'
10180        });
10181        registerOption('language', {
10182            processor: 'string',
10183            default: 'en'
10184        });
10185        registerOption('language_url', {
10186            processor: 'string',
10187            default: ''
10188        });
10189        registerOption('entity_encoding', {
10190            processor: 'string',
10191            default: 'named'
10192        });
10193        registerOption('indent', {
10194            processor: 'boolean',
10195            default: true
10196        });
10197        registerOption('indent_before', {
10198            processor: 'string',
10199            default: 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,th,ul,ol,li,dl,dt,dd,area,table,thead,' +
10200                'tfoot,tbody,tr,section,details,summary,article,hgroup,aside,figure,figcaption,option,optgroup,datalist'
10201        });
10202        registerOption('indent_after', {
10203            processor: 'string',
10204            default: 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,th,ul,ol,li,dl,dt,dd,area,table,thead,' +
10205                'tfoot,tbody,tr,section,details,summary,article,hgroup,aside,figure,figcaption,option,optgroup,datalist'
10206        });
10207        registerOption('indent_use_margin', {
10208            processor: 'boolean',
10209            default: false
10210        });
10211        registerOption('indentation', {
10212            processor: 'string',
10213            default: '40px'
10214        });
10215        registerOption('content_css', {
10216            processor: (value) => {
10217                const valid = value === false || isString(value) || isArrayOf(value, isString);
10218                if (valid) {
10219                    if (isString(value)) {
10220                        return { value: map$3(value.split(','), trim$4), valid };
10221                    }
10222                    else if (isArray$1(value)) {
10223                        return { value, valid };
10224                    }
10225                    else if (value === false) {
10226                        return { value: [], valid };
10227                    }
10228                    else {
10229                        return { value, valid };
10230                    }
10231                }
10232                else {
10233                    return { valid: false, message: 'Must be false, a string or an array of strings.' };
10234                }
10235            },
10236            default: isInline$2(editor) ? [] : ['default']
10237        });
10238        registerOption('content_styl
vendor: 22,488 bytes, lines 10238-10850
10238e', {
10239            processor: 'string'
10240        });
10241        registerOption('content_css_cors', {
10242            processor: 'boolean',
10243            default: false
10244        });
10245        registerOption('font_css', {
10246            processor: (value) => {
10247                const valid = isString(value) || isArrayOf(value, isString);
10248                if (valid) {
10249                    const newValue = isArray$1(value) ? value : map$3(value.split(','), trim$4);
10250                    return { value: newValue, valid };
10251                }
10252                else {
10253                    return { valid: false, message: 'Must be a string or an array of strings.' };
10254                }
10255            },
10256            default: []
10257        });
10258        registerOption('extended_mathml_attributes', {
10259            processor: 'string[]'
10260        });
10261        registerOption('extended_mathml_elements', {
10262            processor: 'string[]'
10263        });
10264        registerOption('inline_boundaries', {
10265            processor: 'boolean',
10266            default: true
10267        });
10268        registerOption('inline_boundaries_selector', {
10269            processor: 'string',
10270            default: 'a[href],code,span.mce-annotation'
10271        });
10272        registerOption('object_resizing', {
10273            processor: (value) => {
10274                const valid = isBoolean(value) || isString(value);
10275                if (valid) {
10276                    if (value === false || deviceDetection$1.isiPhone() || deviceDetection$1.isiPad()) {
10277                        return { value: '', valid };
10278                    }
10279                    else {
10280                        return { value: value === true ? 'table,img,figure.image,div,video,iframe' : value, valid };
10281                    }
10282                }
10283                else {
10284                    return { valid: false, message: 'Must be boolean or a string' };
10285                }
10286            },
10287            // No nice way to do object resizing on touch devices at this stage
10288            default: !isTouch
10289        });
10290        registerOption('resize_img_proportional', {
10291            processor: 'boolean',
10292            default: true
10293        });
10294        registerOption('event_root', {
10295            processor: 'string'
10296        });
10297        registerOption('service_message', {
10298            processor: 'string'
10299        });
10300        registerOption('onboarding', {
10301            processor: 'boolean',
10302            default: true
10303        });
10304        registerOption('tiny_cloud_entry_url', {
10305            processor: 'string'
10306        });
10307        registerOption('theme', {
10308            processor: (value) => value === false || isString(value) || isFunction(value),
10309            default: 'silver'
10310        });
10311        registerOption('theme_url', {
10312            processor: 'string'
10313        });
10314        registerOption('formats', {
10315            processor: 'object'
10316        });
10317        registerOption('format_empty_lines', {
10318            processor: 'boolean',
10319            default: false
10320        });
10321        registerOption('format_noneditable_selector', {
10322            processor: 'string',
10323            default: ''
10324        });
10325        registerOption('preview_styles', {
10326            processor: (value) => {
10327                const valid = value === false || isString(value);
10328                if (valid) {
10329                    return { value: value === false ? '' : value, valid };
10330                }
10331                else {
10332                    return { valid: false, message: 'Must be false or a string' };
10333                }
10334            },
10335            default: 'font-family font-size font-weight font-style text-decoration text-transform color background-color border border-radius outline text-shadow'
10336        });
10337        registerOption('custom_ui_selector', {
10338            processor: 'string',
10339            default: ''
10340        });
10341        registerOption('hidden_input', {
10342            processor: 'boolean',
10343            default: true
10344        });
10345        registerOption('submit_patch', {
10346            processor: 'boolean',
10347            default: true
10348        });
10349        registerOption('encoding', {
10350            processor: 'string'
10351        });
10352        registerOption('add_form_submit_trigger', {
10353            processor: 'boolean',
10354            default: true
10355        });
10356        registerOption('add_unload_trigger', {
10357            processor: 'boolean',
10358            default: true
10359        });
10360        registerOption('custom_undo_redo_levels', {
10361            processor: 'number',
10362            default: 0
10363        });
10364        registerOption('disable_nodechange', {
10365            processor: 'boolean',
10366            default: false
10367        });
10368        registerOption('disabled', {
10369            processor: (value) => {
10370                if (isBoolean(value)) {
10371                    if (editor.initialized && isDisabled$1(editor) !== value) {
10372                        // Schedules the callback to run in the next microtask queue once the option is updated
10373                        // TODO: TINY-11586 - Implement `onChange` callback when the value of an option changes
10374                        // eslint-disable-next-line @typescript-eslint/no-floating-promises
10375                        Promise.resolve().then(() => {
10376                            fireDisabledStateChange(editor, value);
10377                        });
10378                    }
10379                    return { valid: true, value };
10380                }
10381                return { valid: false, message: 'The value must be a boolean.' };
10382            },
10383            default: false
10384        });
10385        registerOption('readonly', {
10386            processor: 'boolean',
10387            default: false
10388        });
10389        registerOption('editable_root', {
10390            processor: 'boolean',
10391            default: true
10392        });
10393        registerOption('plugins', {
10394            processor: 'string[]',
10395            default: []
10396        });
10397        registerOption('external_plugins', {
10398            processor: 'object'
10399        });
10400        registerOption('forced_plugins', {
10401            processor: 'string[]'
10402        });
10403        registerOption('model', {
10404            processor: 'string',
10405            default: editor.hasPlugin('rtc') ? 'plugin' : 'dom'
10406        });
10407        registerOption('model_url', {
10408            processor: 'string'
10409        });
10410        registerOption('block_unsupported_drop', {
10411            processor: 'boolean',
10412            default: true
10413        });
10414        registerOption('visual', {
10415            processor: 'boolean',
10416            default: true
10417        });
10418        registerOption('visual_table_class', {
10419            processor: 'string',
10420            default: 'mce-item-table'
10421        });
10422        registerOption('visual_anchor_class', {
10423            processor: 'string',
10424            default: 'mce-item-anchor'
10425        });
10426        registerOption('iframe_aria_text', {
10427            processor: 'string',
10428            default: 'Rich Text Area'.concat(editor.hasPlugin('help') ? '. Press ALT-0 for help.' : '')
10429        });
10430        registerOption('setup', {
10431            processor: 'function'
10432        });
10433        registerOption('init_instance_callback', {
10434            processor: 'function'
10435        });
10436        registerOption('url_converter', {
10437            processor: 'function',
10438            // Note: Don't bind here, as the binding is handled via the `url_converter_scope`
10439            // eslint-disable-next-line @typescript-eslint/unbound-method
10440            default: editor.convertURL
10441        });
10442        registerOption('url_converter_scope', {
10443            processor: 'object',
10444            default: editor
10445        });
10446        registerOption('urlconverter_callback', {
10447            processor: 'function'
10448        });
10449        registerOption('allow_conditional_comments', {
10450            processor: 'boolean',
10451            default: false
10452        });
10453        registerOption('allow_html_data_urls', {
10454            processor: 'boolean',
10455            default: false
10456        });
10457        registerOption('allow_svg_data_urls', {
10458            processor: 'boolean'
10459        });
10460        registerOption('allow_html_in_named_anchor', {
10461            processor: 'boolean',
10462            default: false
10463        });
10464        registerOption('allow_html_in_comments', {
10465            processor: 'boolean',
10466            default: false
10467        });
10468        registerOption('allow_script_urls', {
10469            processor: 'boolean',
10470            default: false
10471        });
10472        registerOption('allow_unsafe_link_target', {
10473            processor: 'boolean',
10474            default: false
10475        });
10476        registerOption('allow_mathml_annotation_encodings', {
10477            processor: (value) => {
10478                const valid = isArrayOf(value, isString);
10479                return valid ? { value, valid } : { valid: false, message: 'Must be an array of strings.' };
10480            },
10481            default: []
10482        });
10483        registerOption('convert_fonts_to_spans', {
10484            processor: 'boolean',
10485            default: true,
10486            deprecated: true
10487        });
10488        registerOption('fix_list_elements', {
10489            processor: 'boolean',
10490            default: false
10491        });
10492        registerOption('preserve_cdata', {
10493            processor: 'boolean',
10494            default: false
10495        });
10496        registerOption('remove_trailing_brs', {
10497            processor: 'boolean',
10498            default: true
10499        });
10500        registerOption('pad_empty_with_br', {
10501            processor: 'boolean',
10502            default: false,
10503        });
10504        registerOption('inline_styles', {
10505            processor: 'boolean',
10506            default: true,
10507            deprecated: true
10508        });
10509        registerOption('element_format', {
10510            processor: 'string',
10511            default: 'html'
10512        });
10513        registerOption('entities', {
10514            processor: 'string'
10515        });
10516        registerOption('schema', {
10517            processor: 'string',
10518            default: 'html5'
10519        });
10520        registerOption('convert_urls', {
10521            processor: 'boolean',
10522            default: true
10523        });
10524        registerOption('relative_urls', {
10525            processor: 'boolean',
10526            default: true
10527        });
10528        registerOption('remove_script_host', {
10529            processor: 'boolean',
10530            default: true
10531        });
10532        registerOption('custom_elements', {
10533            processor: stringOrObjectProcessor
10534        });
10535        registerOption('extended_valid_elements', {
10536            processor: 'string'
10537        });
10538        registerOption('invalid_elements', {
10539            processor: 'string'
10540        });
10541        registerOption('invalid_styles', {
10542            processor: stringOrObjectProcessor
10543        });
10544        registerOption('valid_children', {
10545            processor: 'string'
10546        });
10547        registerOption('valid_classes', {
10548            processor: stringOrObjectProcessor
10549        });
10550        registerOption('valid_elements', {
10551            processor: 'string'
10552        });
10553        registerOption('valid_styles', {
10554            processor: stringOrObjectProcessor
10555        });
10556        registerOption('verify_html', {
10557            processor: 'boolean',
10558            default: true
10559        });
10560        registerOption('auto_focus', {
10561            processor: (value) => isString(value) || value === true
10562        });
10563        registerOption('browser_spellcheck', {
10564            processor: 'boolean',
10565            default: false
10566        });
10567        registerOption('protect', {
10568            processor: 'array'
10569        });
10570        registerOption('images_file_types', {
10571            processor: 'string',
10572            default: 'jpeg,jpg,jpe,jfi,jif,jfif,png,gif,bmp,webp'
10573        });
10574        registerOption('deprecation_warnings', {
10575            processor: 'boolean',
10576            default: true
10577        });
10578        registerOption('a11y_advanced_options', {
10579            processor: 'boolean',
10580            default: false
10581        });
10582        registerOption('api_key', {
10583            processor: 'string'
10584        });
10585        registerOption('license_key', {
10586            processor: 'string'
10587        });
10588        registerOption('paste_block_drop', {
10589            processor: 'boolean',
10590            default: false
10591        });
10592        registerOption('paste_data_images', {
10593            processor: 'boolean',
10594            default: true
10595        });
10596        registerOption('paste_preprocess', {
10597            processor: 'function'
10598        });
10599        registerOption('paste_postprocess', {
10600            processor: 'function'
10601        });
10602        registerOption('paste_webkit_styles', {
10603            processor: 'string',
10604            default: 'none'
10605        });
10606        registerOption('paste_remove_styles_if_webkit', {
10607            processor: 'boolean',
10608            default: true
10609        });
10610        registerOption('paste_merge_formats', {
10611            processor: 'boolean',
10612            default: true
10613        });
10614        registerOption('smart_paste', {
10615            processor: 'boolean',
10616            default: true
10617        });
10618        registerOption('paste_as_text', {
10619            processor: 'boolean',
10620            default: false
10621        });
10622        registerOption('paste_tab_spaces', {
10623            processor: 'number',
10624            default: 4
10625        });
10626        registerOption('text_patterns', {
10627            processor: (value) => {
10628                if (isArrayOf(value, isObject) || value === false) {
10629                    const patterns = value === false ? [] : value;
10630                    return { value: fromRawPatterns(patterns), valid: true };
10631                }
10632                else {
10633                    return { valid: false, message: 'Must be an array of objects or false.' };
10634                }
10635            },
10636            default: [
10637                { start: '*', end: '*', format: 'italic' },
10638                { start: '**', end: '**', format: 'bold' },
10639                { start: '#', format: 'h1', trigger: 'space' },
10640                { start: '##', format: 'h2', trigger: 'space' },
10641                { start: '###', format: 'h3', trigger: 'space' },
10642                { start: '####', format: 'h4', trigger: 'space' },
10643                { start: '#####', format: 'h5', trigger: 'space' },
10644                { start: '######', format: 'h6', trigger: 'space' },
10645                { start: '1.', cmd: 'InsertOrderedList', trigger: 'space' },
10646                { start: '*', cmd: 'InsertUnorderedList', trigger: 'space' },
10647                { start: '-', cmd: 'InsertUnorderedList', trigger: 'space' },
10648                { start: '>', cmd: 'mceBlockQuote', trigger: 'space' },
10649                { start: '---', cmd: 'InsertHorizontalRule', trigger: 'space' },
10650            ]
10651        });
10652        registerOption('text_patterns_lookup', {
10653            processor: (value) => {
10654                if (isFunction(value)) {
10655                    return {
10656                        value: fromRawPatternsLookup(value),
10657                        valid: true,
10658                    };
10659                }
10660                else {
10661                    return { valid: false, message: 'Must be a single function' };
10662                }
10663            },
10664            default: (_ctx) => []
10665        });
10666        registerOption('allow_noneditable', {
10667            processor: 'boolean',
10668            default: true
10669        });
10670        registerOption('noneditable_class', {
10671            processor: 'string',
10672            default: 'mceNonEditable'
10673        });
10674        registerOption('editable_class', {
10675            processor: 'string',
10676            default: 'mceEditable'
10677        });
10678        registerOption('noneditable_regexp', {
10679            processor: (value) => {
10680                if (isArrayOf(value, isRegExp)) {
10681                    return { value, valid: true };
10682                }
10683                else if (isRegExp(value)) {
10684                    return { value: [value], valid: true };
10685                }
10686                else {
10687                    return { valid: false, message: 'Must be a RegExp or an array of RegExp.' };
10688                }
10689            },
10690            default: []
10691        });
10692        registerOption('table_tab_navigation', {
10693            processor: 'boolean',
10694            default: true
10695        });
10696        registerOption('highlight_on_focus', {
10697            processor: 'boolean',
10698            default: true
10699        });
10700        registerOption('xss_sanitization', {
10701            processor: 'boolean',
10702            default: true
10703        });
10704        registerOption('details_initial_state', {
10705            processor: (value) => {
10706                const valid = contains$2(['inherited', 'collapsed', 'expanded'], value);
10707                return valid ? { value, valid } : { valid: false, message: 'Must be one of: inherited, collapsed, or expanded.' };
10708            },
10709            default: 'inherited'
10710        });
10711        registerOption('details_serialized_state', {
10712            processor: (value) => {
10713                const valid = contains$2(['inherited', 'collapsed', 'expanded'], value);
10714                return valid ? { value, valid } : { valid: false, message: 'Must be one of: inherited, collapsed, or expanded.' };
10715            },
10716            default: 'inherited'
10717        });
10718        registerOption('init_content_sync', {
10719            processor: 'boolean',
10720            default: false
10721        });
10722        registerOption('newdocument_content', {
10723            processor: 'string',
10724            default: ''
10725        });
10726        registerOption('sandbox_iframes', {
10727            processor: 'boolean',
10728            default: true
10729        });
10730        registerOption('sandbox_iframes_exclusions', {
10731            processor: 'string[]',
10732            default: [
10733                'youtube.com',
10734                'youtu.be',
10735                'vimeo.com',
10736                'player.vimeo.com',
10737                'dailymotion.com',
10738                'embed.music.apple.com',
10739                'open.spotify.com',
10740                'giphy.com',
10741                'dai.ly',
10742                'codepen.io',
10743            ]
10744        });
10745        registerOption('convert_unsafe_embeds', {
10746            processor: 'boolean',
10747            default: true
10748        });
10749        registerOption('user_id', {
10750            processor: 'string',
10751            default: 'Anonymous'
10752        });
10753        registerOption('content_id', {
10754            processor: 'string',
10755        });
10756        registerOption('fetch_users', {
10757            processor: (value) => {
10758                if (value === undefined) {
10759                    return { valid: true, value: undefined };
10760                }
10761                if (isFunction(value)) {
10762                    return { valid: true, value };
10763                }
10764                return {
10765                    valid: false,
10766                    message: 'fetch_users must be a function that returns a Promise<ExpectedUser[]>'
10767                };
10768            }
10769        });
10770        const documentsFileTypesOptionsSchema = arrOfObj([
10771            requiredString('mimeType'),
10772            requiredArrayOf('extensions', valueOf((ext) => {
10773                if (isString(ext)) {
10774                    return Result.value(ext);
10775                }
10776                else {
10777                    return Result.error('Extensions must be an array of strings');
10778                }
10779            })),
10780        ]);
10781        registerOption('documents_file_types', {
10782            processor: (value) => asRaw('documents_file_types', documentsFileTypesOptionsSchema, value).fold((_err) => ({
10783                valid: false,
10784                message: 'Must be a non-empty array of objects matching the configuration schema: https://www.tiny.cloud/docs/tinymce/latest/uploadcare-documents/#documents-file-types'
10785            }), (val) => ({ valid: true, value: val }))
10786        });
10787        // These options must be registered later in the init sequence due to their default values
10788        editor.on('ScriptsLoaded', () => {
10789            registerOption('directionality', {
10790                processor: 'string',
10791                default: I18n.isRtl() ? 'rtl' : undefined
10792            });
10793            registerOption('placeholder', {
10794                processor: 'string',
10795                // Fallback to the original elements placeholder if not set in the settings
10796                default: DOM$d.getAttrib(editor.getElement(), 'placeholder')
10797            });
10798        });
10799        registerOption('lists_indent_on_tab', {
10800            processor: 'boolean',
10801            default: true
10802        });
10803        registerOption('list_max_depth', {
10804            processor: (value) => {
10805                const valid = isNumber(value);
10806                if (valid) {
10807                    if (value < 0) {
10808                        throw new Error('list_max_depth cannot be set to lower than 0');
10809                    }
10810                    return { value, valid };
10811                }
10812                else {
10813                    return { valid: false, message: 'Must be a number' };
10814                }
10815            },
10816        });
10817    };
10818    const getIframeAttrs = option('iframe_attrs');
10819    const getDocType = option('doctype');
10820    const getDocumentBaseUrl = option('document_base_url');
10821    const getBodyId = option('body_id');
10822    const getBodyClass = option('body_class');
10823    const getContentSecurityPolicy = option('content_security_policy');
10824    const shouldPutBrInPre$1 = option('br_in_pre');
10825    const getForcedRootBlock = option('forced_root_block');
10826    const getForcedRootBlockAttrs = option('forced_root_block_attrs');
10827    const getNewlineBehavior = option('newline_behavior');
10828    const getBrNewLineSelector = option('br_newline_selector');
10829    const getNoNewLineSelector = option('no_newline_selector');
10830    const shouldKeepStyles = option('keep_styles');
10831    const shouldEndContainerOnEmptyBlock = option('end_container_on_empty_block');
10832    const isAutomaticUploadsEnabled = option('automatic_uploads');
10833    const shouldReuseFileName = option('images_reuse_filename');
10834    const shouldReplaceBlobUris = option('images_replace_blob_uris');
10835    const getIconPackName = option('icons');
10836    const getIconsUrl = option('icons_url');
10837    const getImageUploadUrl = option('images_upload_url');
10838    const getImageUploadBasePath = option('images_upload_base_path');
10839    const getImagesUploadCredentials = option('images_upload_credentials');
10840    const getImagesUploadHandler = option('images_upload_handler');
10841    const shouldUseContentCssCors = option('content_css_cors');
10842    const getReferrerPolicy = option('referrer_policy');
10843    const getCrossOrigin = option('crossorigin');
10844    const getLanguageCode = option('language');
10845    const getLanguageUrl = option('language_url');
10846    const shouldIndentUseMargin = option('indent_use_margin');
10847    const getIndentation = option('indentation');
10848    const getContentCss = option('content_css');
10849    const getContentStyle = option('content_style');
10850    const get
10850FontCss = option('font_css');
10851    const getDirectionality = option('directionality');
10852    const getInlineBoundarySelector = option('inline_boundaries_selector');
10853    const getObjectResizing = option('object_resizing');
10854    const getResizeImgProportional = option('resize_img_proportional');
10855    const getPlaceholder = option('placeholder');
10856    const getEventRoot = option('event_root');
10857    const getServiceMessage = option('service_message');
10858    const getTheme = option('theme');
10859    const getThemeUrl = option('theme_url');
10860    const getModel = option('model');
10861    const getModelUrl = option('model_url');
10862    const isInlineBoundariesEnabled = option('inline_boundaries');
10863    const getFormats = option('formats');
10864    const getPreviewStyles = option('preview_styles');
10865    const canFormatEmptyLines = option('format_empty_lines');
10866    const getFormatNoneditableSelector = option('format_noneditable_selector');
10867    const getCustomUiSelector = option('custom_ui_selector');
10868    const isInline$2 = option('inline');
10869    const hasHiddenInput = option('hidden_input');
10870    const shouldPatchSubmit = option('submit_patch');
10871    const shouldAddFormSubmitTrigger = option('add_form_submit_trigger');
10872    const shouldAddUnloadTrigger = option('add_unload_trigger');
10873    const getCustomUndoRedoLevels = option('custom_undo_redo_levels');
10874    const shouldDisableNodeChange = option('disable_nodechange');
10875    const isReadOnly$1 = option('readonly');
10876    const hasEditableRoot$1 = option('editable_root');
10877    const hasContentCssCors = option('content_css_cors');
10878    const getPlugins = option('plugins');
10879    const getExternalPlugins$1 = option('external_plugins');
10880    const shouldBlockUnsupportedDrop = option('block_unsupported_drop');
10881    const isVisualAidsEnabled = option('visual');
10882    const getVisualAidsTableClass = option('visual_table_class');
10883    const getVisualAidsAnchorClass = option('visual_anchor_class');
10884    const getIframeAriaText = option('iframe_aria_text');
10885    const getSetupCallback = option('setup');
10886    const getInitInstanceCallback = option('init_instance_callback');
10887    const getUrlConverterCallback = option('urlconverter_callback');
10888    const getAutoFocus = option('auto_focus');
10889    const shouldBrowserSpellcheck = option('browser_spellcheck');
10890    const getProtect = option('protect');
10891    const shouldPasteBlockDrop = option('paste_block_drop');
10892    const shouldPasteDataImages = option('paste_data_images');
10893    const getPastePreProcess = option('paste_preprocess');
10894    const getPastePostProcess = option('paste_postprocess');
10895    const getNewDocumentContent = option('newdocument_content');
10896    const getPasteWebkitStyles = option('paste_webkit_styles');
10897    const shouldPasteRemoveWebKitStyles = option('paste_remove_styles_if_webkit');
10898    const shouldPasteMergeFormats = option('paste_merge_formats');
10899    const isSmartPasteEnabled = option('smart_paste');
10900    const isPasteAsTextEnabled = option('paste_as_text');
vendor: 14,302 bytes, lines 10901-11228
10901    const getPasteTabSpaces = option('paste_tab_spaces');
10902    const shouldAllowHtmlDataUrls = option('allow_html_data_urls');
10903    const getTextPatterns = option('text_patterns');
10904    const getTextPatternsLookup = option('text_patterns_lookup');
10905    const shouldAllowNonEditable = option('allow_noneditable');
10906    const getNonEditableClass = option('noneditable_class');
10907    const getEditableClass = option('editable_class');
10908    const getNonEditableRegExps = option('noneditable_regexp');
10909    const shouldPreserveCData = option('preserve_cdata');
10910    const shouldHighlightOnFocus = option('highlight_on_focus');
10911    const shouldSanitizeXss = option('xss_sanitization');
10912    const shouldUseDocumentWrite = option('init_content_sync');
10913    const hasTextPatternsLookup = (editor) => editor.options.isSet('text_patterns_lookup');
10914    const getFontStyleValues = (editor) => Tools.explode(editor.options.get('font_size_style_values'));
10915    const getFontSizeClasses = (editor) => Tools.explode(editor.options.get('font_size_classes'));
10916    const isEncodingXml = (editor) => editor.options.get('encoding') === 'xml';
10917    const getAllowedImageFileTypes = (editor) => Tools.explode(editor.options.get('images_file_types'));
10918    const hasTableTabNavigation = option('table_tab_navigation');
10919    const getDetailsInitialState = option('details_initial_state');
10920    const getDetailsSerializedState = option('details_serialized_state');
10921    const shouldSandboxIframes = option('sandbox_iframes');
10922    const getSandboxIframesExclusions = (editor) => editor.options.get('sandbox_iframes_exclusions');
10923    const shouldConvertUnsafeEmbeds = option('convert_unsafe_embeds');
10924    const getLicenseKey = option('license_key');
10925    const getApiKey = option('api_key');
10926    const isDisabled$1 = option('disabled');
10927    const getUserId = option('user_id');
10928    const getFetchUsers = option('fetch_users');
10929    const shouldIndentOnTab = option('lists_indent_on_tab');
10930    const getListMaxDepth = (editor) => Optional.from(editor.options.get('list_max_depth'));
10931
10932    const isElement$4 = isElement$7;
10933    const isText$5 = isText$b;
10934    const removeNode$1 = (node) => {
10935        const parentNode = node.parentNode;
10936        if (parentNode) {
10937            parentNode.removeChild(node);
10938        }
10939    };
10940    const trimCount = (text) => {
10941        const trimmedText = trim$2(text);
10942        return {
10943            count: text.length - trimmedText.length,
10944            text: trimmedText
10945        };
10946    };
10947    const deleteZwspChars = (caretContainer) => {
10948        // We use the Text.deleteData API here so as to preserve selection offsets
10949        let idx;
10950        while ((idx = caretContainer.data.lastIndexOf(ZWSP$1)) !== -1) {
10951            caretContainer.deleteData(idx, 1);
10952        }
10953    };
10954    const removeUnchanged = (caretContainer, pos) => {
10955        remove$2(caretContainer);
10956        return pos;
10957    };
10958    const removeTextAndReposition = (caretContainer, pos) => {
10959        const before = trimCount(caretContainer.data.substr(0, pos.offset()));
10960        const after = trimCount(caretContainer.data.substr(pos.offset()));
10961        const text = before.text + after.text;
10962        if (text.length > 0) {
10963            deleteZwspChars(caretContainer);
10964            return CaretPosition(caretContainer, pos.offset() - before.count);
10965        }
10966        else {
10967            return pos;
10968        }
10969    };
10970    const removeElementAndReposition = (caretContainer, pos) => {
10971        const parentNode = pos.container();
10972        const newPosition = indexOf$1(from(parentNode.childNodes), caretContainer).map((index) => {
10973            return index < pos.offset() ? CaretPosition(parentNode, pos.offset() - 1) : pos;
10974        }).getOr(pos);
10975        remove$2(caretContainer);
10976        return newPosition;
10977    };
10978    const removeTextCaretContainer = (caretContainer, pos) => isText$5(caretContainer) && pos.container() === caretContainer ? removeTextAndReposition(caretContainer, pos) : removeUnchanged(caretContainer, pos);
10979    const removeElementCaretContainer = (caretContainer, pos) => pos.container() === caretContainer.parentNode ? removeElementAndReposition(caretContainer, pos) : removeUnchanged(caretContainer, pos);
10980    const removeAndReposition = (container, pos) => CaretPosition.isTextPosition(pos) ? removeTextCaretContainer(container, pos) : removeElementCaretContainer(container, pos);
10981    const remove$2 = (caretContainerNode) => {
10982        if (isElement$4(caretContainerNode) && isCaretContainer$2(caretContainerNode)) {
10983            if (hasContent(caretContainerNode)) {
10984                caretContainerNode.removeAttribute('data-mce-caret');
10985            }
10986            else {
10987                removeNode$1(caretContainerNode);
10988            }
10989        }
10990        if (isText$5(caretContainerNode)) {
10991            deleteZwspChars(caretContainerNode);
10992            if (caretContainerNode.data.length === 0) {
10993                removeNode$1(caretContainerNode);
10994            }
10995        }
10996    };
10997
10998    const isContentEditableFalse$7 = isContentEditableFalse$a;
10999    const isMedia$1 = isMedia$2;
11000    const isTableCell$1 = isTableCell$3;
11001    const inlineFakeCaretSelector = '*[contentEditable=false],video,audio,embed,object';
11002    const getAbsoluteClientRect = (root, element, before) => {
11003        const clientRect = collapse(element.getBoundingClientRect(), before);
11004        let scrollX;
11005        let scrollY;
11006        if (root.tagName === 'BODY') {
11007            const docElm = root.ownerDocument.documentElement;
11008            scrollX = root.scrollLeft || docElm.scrollLeft;
11009            scrollY = root.scrollTop || docElm.scrollTop;
11010        }
11011        else {
11012            const rootRect = root.getBoundingClientRect();
11013            scrollX = root.scrollLeft - rootRect.left;
11014            scrollY = root.scrollTop - rootRect.top;
11015        }
11016        clientRect.left += scrollX;
11017        clientRect.right += scrollX;
11018        clientRect.top += scrollY;
11019        clientRect.bottom += scrollY;
11020        clientRect.width = 1;
11021        let margin = element.offsetWidth - element.clientWidth;
11022        if (margin > 0) {
11023            if (before) {
11024                margin *= -1;
11025            }
11026            clientRect.left += margin;
11027            clientRect.right += margin;
11028        }
11029        return clientRect;
11030    };
11031    const trimInlineCaretContainers = (root) => {
11032        const fakeCaretTargetNodes = descendants(SugarElement.fromDom(root), inlineFakeCaretSelector);
11033        for (let i = 0; i < fakeCaretTargetNodes.length; i++) {
11034            const node = fakeCaretTargetNodes[i].dom;
11035            let sibling = node.previousSibling;
11036            if (endsWithCaretContainer$1(sibling)) {
11037                const data = sibling.data;
11038                if (data.length === 1) {
11039                    sibling.parentNode?.removeChild(sibling);
11040                }
11041                else {
11042                    sibling.deleteData(data.length - 1, 1);
11043                }
11044            }
11045            sibling = node.nextSibling;
11046            if (startsWithCaretContainer$1(sibling)) {
11047                const data = sibling.data;
11048                if (data.length === 1) {
11049                    sibling.parentNode?.removeChild(sibling);
11050                }
11051                else {
11052                    sibling.deleteData(0, 1);
11053                }
11054            }
11055        }
11056    };
11057    const FakeCaret = (editor, root, isBlock, hasFocus) => {
11058        const lastVisualCaret = value$1();
11059        let cursorInterval;
11060        let caretContainerNode;
11061        const caretBlock = getForcedRootBlock(editor);
11062        const dom = editor.dom;
11063        const show = (before, element) => {
11064            let rng;
11065            hide();
11066            if (isTableCell$1(element)) {
11067                return null;
11068            }
11069            if (isBlock(element)) {
11070                const caretContainer = insertBlock(caretBlock, element, before);
11071                const clientRect = getAbsoluteClientRect(root, element, before);
11072                dom.setStyle(caretContainer, 'top', clientRect.top);
11073                dom.setStyle(caretContainer, 'caret-color', 'transparent');
11074                caretContainerNode = caretContainer;
11075                const caret = dom.create('div', { 'class': 'mce-visual-caret', 'data-mce-bogus': 'all' });
11076                dom.setStyles(caret, { ...clientRect });
11077                dom.add(root, caret);
11078                lastVisualCaret.set({ caret, element, before });
11079                if (before) {
11080                    dom.addClass(caret, 'mce-visual-caret-before');
11081                }
11082                startBlink();
11083                rng = element.ownerDocument.createRange();
11084                rng.setStart(caretContainer, 0);
11085                rng.setEnd(caretContainer, 0);
11086            }
11087            else {
11088                caretContainerNode = insertInline$1(element, before);
11089                rng = element.ownerDocument.createRange();
11090                if (isInlineFakeCaretTarget(caretContainerNode.nextSibling)) {
11091                    rng.setStart(caretContainerNode, 0);
11092                    rng.setEnd(caretContainerNode, 0);
11093                }
11094                else {
11095                    rng.setStart(caretContainerNode, 1);
11096                    rng.setEnd(caretContainerNode, 1);
11097                }
11098                return rng;
11099            }
11100            return rng;
11101        };
11102        const hide = () => {
11103            // TODO: TINY-6015 - Ensure cleaning up the fake caret preserves the selection, as currently
11104            //  the CaretContainerRemove.remove below will change the selection in some cases
11105            trimInlineCaretContainers(root);
11106            if (caretContainerNode) {
11107                remove$2(caretContainerNode);
11108                caretContainerNode = null;
11109            }
11110            lastVisualCaret.on((caretState) => {
11111                dom.remove(caretState.caret);
11112                lastVisualCaret.clear();
11113            });
11114            if (cursorInterval) {
11115                clearInterval(cursorInterval);
11116                cursorInterval = undefined;
11117            }
11118        };
11119        const startBlink = () => {
11120            cursorInterval = window.setInterval(() => {
11121                lastVisualCaret.on((caretState) => {
11122                    if (hasFocus()) {
11123                        dom.toggleClass(caretState.caret, 'mce-visual-caret-hidden');
11124                    }
11125                    else {
11126                        dom.addClass(caretState.caret, 'mce-visual-caret-hidden');
11127                    }
11128                });
11129            }, 500);
11130        };
11131        const reposition = () => {
11132            lastVisualCaret.on((caretState) => {
11133                const clientRect = getAbsoluteClientRect(root, caretState.element, caretState.before);
11134                dom.setStyles(caretState.caret, { ...clientRect });
11135            });
11136        };
11137        const destroy = () => clearInterval(cursorInterval);
11138        const getCss = () => ('.mce-visual-caret {' +
11139            'position: absolute;' +
11140            'background-color: black;' +
11141            'background-color: currentcolor;' +
11142            // 'background-color: red;' +
11143            '}' +
11144            '.mce-visual-caret-hidden {' +
11145            'display: none;' +
11146            '}' +
11147            '*[data-mce-caret] {' +
11148            'position: absolute;' +
11149            'left: -1000px;' +
11150            'right: auto;' +
11151            'top: 0;' +
11152            'margin: 0;' +
11153            'padding: 0;' +
11154            '}');
11155        return {
11156            isShowing: lastVisualCaret.isSet,
11157            show,
11158            hide,
11159            getCss,
11160            reposition,
11161            destroy
11162        };
11163    };
11164    const isFakeCaretTableBrowser = () => Env.browser.isFirefox();
11165    const isInlineFakeCaretTarget = (node) => isContentEditableFalse$7(node) || isMedia$1(node);
11166    const isFakeCaretTarget = (node) => {
11167        const isTarget = isInlineFakeCaretTarget(node) || (isTable$2(node) && isFakeCaretTableBrowser());
11168        return isTarget && parentElement(SugarElement.fromDom(node)).exists(isEditable$2);
11169    };
11170
11171    const isContentEditableTrue$1 = isContentEditableTrue$3;
11172    const isContentEditableFalse$6 = isContentEditableFalse$a;
11173    const isMedia = isMedia$2;
11174    const isBlockLike = matchStyleValues('display', 'block table table-cell table-row table-caption list-item');
11175    const isCaretContainer = isCaretContainer$2;
11176    const isCaretContainerBlock = isCaretContainerBlock$1;
11177    const isElement$3 = isElement$7;
11178    const isText$4 = isText$b;
11179    const isCaretCandidate$1 = isCaretCandidate$3;
11180    const skipCaretContainers = (walk, shallow) => {
11181        let node;
11182        while ((node = walk(shallow))) {
11183            if (!isCaretContainerBlock(node)) {
11184                return node;
11185            }
11186        }
11187        return null;
11188    };
11189    const findNode = (node, direction, predicateFn, rootNode, shallow) => {
11190        const walker = new DomTreeWalker(node, rootNode);
11191        const isCefOrCaretContainer = isContentEditableFalse$6(node) || isCaretContainerBlock(node);
11192        let tempNode;
11193        if (isBackwards(direction)) {
11194            if (isCefOrCaretContainer) {
11195                tempNode = skipCaretContainers(walker.prev.bind(walker), true);
11196                if (predicateFn(tempNode)) {
11197                    return tempNode;
11198                }
11199            }
11200            while ((tempNode = skipCaretContainers(walker.prev.bind(walker), shallow))) {
11201                if (predicateFn(tempNode)) {
11202                    return tempNode;
11203                }
11204            }
11205        }
11206        if (isForwards(direction)) {
11207            if (isCefOrCaretContainer) {
11208                tempNode = skipCaretContainers(walker.next.bind(walker), true);
11209                if (predicateFn(tempNode)) {
11210                    return tempNode;
11211                }
11212            }
11213            while ((tempNode = skipCaretContainers(walker.next.bind(walker), shallow))) {
11214                if (predicateFn(tempNode)) {
11215                    return tempNode;
11216                }
11217            }
11218        }
11219        return null;
11220    };
11221    const getEditingHost = (node, rootNode) => {
11222        const isCETrue = (node) => isContentEditableTrue$1(node.dom);
11223        const isRoot = (node) => node.dom === rootNode;
11224        return ancestor$5(SugarElement.fromDom(node), isCETrue, isRoot)
11225            .map((elm) => elm.dom)
11226            .getOr(rootNode);
11227    };
11228    const isAbsPositionedElement = (node) => isElement$7(node) && get$7
vendor: 40,705 bytes, lines 11228-12170
11228(SugarElement.fromDom(node), 'position') === 'absolute';
11229    const isInlineBlock = (node, rootNode) => node.parentNode !== rootNode;
11230    const isInlineAbsPositionedCEF = (node, rootNode) => isContentEditableFalse$6(node) && isAbsPositionedElement(node) && isInlineBlock(node, rootNode);
11231    const getParentBlock$3 = (node, rootNode) => {
11232        while (node && node !== rootNode) {
11233            // Exclude inline absolutely positioned CEF elements since they have 'display: block'
11234            // Created TINY-12922 to improve handling non CEF elements
11235            if (isBlockLike(node) && !isInlineAbsPositionedCEF(node, rootNode)) {
11236                return node;
11237            }
11238            node = node.parentNode;
11239        }
11240        return null;
11241    };
11242    const isInSameBlock = (caretPosition1, caretPosition2, rootNode) => getParentBlock$3(caretPosition1.container(), rootNode) === getParentBlock$3(caretPosition2.container(), rootNode);
11243    const getChildNodeAtRelativeOffset = (relativeOffset, caretPosition) => {
11244        if (!caretPosition) {
11245            return Optional.none();
11246        }
11247        const container = caretPosition.container();
11248        const offset = caretPosition.offset();
11249        if (!isElement$3(container)) {
11250            return Optional.none();
11251        }
11252        return Optional.from(container.childNodes[offset + relativeOffset]);
11253    };
11254    const beforeAfter = (before, node) => {
11255        const doc = node.ownerDocument ?? document;
11256        const range = doc.createRange();
11257        if (before) {
11258            range.setStartBefore(node);
11259            range.setEndBefore(node);
11260        }
11261        else {
11262            range.setStartAfter(node);
11263            range.setEndAfter(node);
11264        }
11265        return range;
11266    };
11267    const isNodesInSameBlock = (root, node1, node2) => getParentBlock$3(node1, root) === getParentBlock$3(node2, root);
11268    const lean = (left, root, node) => {
11269        const siblingName = left ? 'previousSibling' : 'nextSibling';
11270        let tempNode = node;
11271        while (tempNode && tempNode !== root) {
11272            let sibling = tempNode[siblingName];
11273            if (sibling && isCaretContainer(sibling)) {
11274                sibling = sibling[siblingName];
11275            }
11276            if (isContentEditableFalse$6(sibling) || isMedia(sibling)) {
11277                if (isNodesInSameBlock(root, sibling, tempNode)) {
11278                    return sibling;
11279                }
11280                break;
11281            }
11282            if (isCaretCandidate$1(sibling)) {
11283                break;
11284            }
11285            tempNode = tempNode.parentNode;
11286        }
11287        return null;
11288    };
11289    const before$1 = curry(beforeAfter, true);
11290    const after$1 = curry(beforeAfter, false);
11291    const normalizeRange$2 = (direction, root, range) => {
11292        let node;
11293        const leanLeft = curry(lean, true, root);
11294        const leanRight = curry(lean, false, root);
11295        const container = range.startContainer;
11296        const offset = range.startOffset;
11297        if (isCaretContainerBlock$1(container)) {
11298            const block = isText$4(container) ? container.parentNode : container;
11299            const location = block.getAttribute('data-mce-caret');
11300            if (location === 'before') {
11301                node = block.nextSibling;
11302                if (isFakeCaretTarget(node)) {
11303                    return before$1(node);
11304                }
11305            }
11306            if (location === 'after') {
11307                node = block.previousSibling;
11308                if (isFakeCaretTarget(node)) {
11309                    return after$1(node);
11310                }
11311            }
11312        }
11313        if (!range.collapsed) {
11314            return range;
11315        }
11316        if (isText$b(container)) {
11317            if (isCaretContainer(container)) {
11318                if (direction === 1) {
11319                    node = leanRight(container);
11320                    if (node) {
11321                        return before$1(node);
11322                    }
11323                    node = leanLeft(container);
11324                    if (node) {
11325                        return after$1(node);
11326                    }
11327                }
11328                if (direction === -1) {
11329                    node = leanLeft(container);
11330                    if (node) {
11331                        return after$1(node);
11332                    }
11333                    node = leanRight(container);
11334                    if (node) {
11335                        return before$1(node);
11336                    }
11337                }
11338                return range;
11339            }
11340            if (endsWithCaretContainer$1(container) && offset >= container.data.length - 1) {
11341                if (direction === 1) {
11342                    node = leanRight(container);
11343                    if (node) {
11344                        return before$1(node);
11345                    }
11346                }
11347                return range;
11348            }
11349            if (startsWithCaretContainer$1(container) && offset <= 1) {
11350                if (direction === -1) {
11351                    node = leanLeft(container);
11352                    if (node) {
11353                        return after$1(node);
11354                    }
11355                }
11356                return range;
11357            }
11358            if (offset === container.data.length) {
11359                node = leanRight(container);
11360                if (node) {
11361                    return before$1(node);
11362                }
11363                return range;
11364            }
11365            if (offset === 0) {
11366                node = leanLeft(container);
11367                if (node) {
11368                    return after$1(node);
11369                }
11370                return range;
11371            }
11372        }
11373        return range;
11374    };
11375    const getRelativeCefElm = (forward, caretPosition) => getChildNodeAtRelativeOffset(forward ? 0 : -1, caretPosition).filter(isContentEditableFalse$6);
11376    const getNormalizedRangeEndPoint = (direction, root, range) => {
11377        const normalizedRange = normalizeRange$2(direction, root, range);
11378        return direction === -1 ? CaretPosition.fromRangeStart(normalizedRange) : CaretPosition.fromRangeEnd(normalizedRange);
11379    };
11380    const getElementFromPosition = (pos) => Optional.from(pos.getNode()).map(SugarElement.fromDom);
11381    const getElementFromPrevPosition = (pos) => Optional.from(pos.getNode(true)).map(SugarElement.fromDom);
11382    const getVisualCaretPosition = (walkFn, caretPosition) => {
11383        let pos = caretPosition;
11384        while ((pos = walkFn(pos))) {
11385            if (pos.isVisible()) {
11386                return pos;
11387            }
11388        }
11389        return pos;
11390    };
11391    const isMoveInsideSameBlock = (from, to) => {
11392        const inSameBlock = isInSameBlock(from, to);
11393        // Handle bogus BR <p>abc|<br></p>
11394        if (!inSameBlock && isBr$7(from.getNode())) {
11395            return true;
11396        }
11397        return inSameBlock;
11398    };
11399
11400    /**
11401     * This module contains logic for moving around a virtual caret in logical order within a DOM element.
11402     *
11403     * It ignores the most obvious invalid caret locations such as within a script element or within a
11404     * contentEditable=false element but it will return locations that isn't possible to render visually.
11405     *
11406     * @private
11407     * @class tinymce.caret.CaretWalker
11408     * @example
11409     * const caretWalker = CaretWalker(rootElm);
11410     *
11411     * const prevLogicalCaretPosition = caretWalker.prev(CaretPosition.fromRangeStart(range));
11412     * const nextLogicalCaretPosition = caretWalker.next(CaretPosition.fromRangeEnd(range));
11413     */
11414    const isContentEditableFalse$5 = isContentEditableFalse$a;
11415    const isText$3 = isText$b;
11416    const isElement$2 = isElement$7;
11417    const isBr$3 = isBr$7;
11418    const isCaretCandidate = isCaretCandidate$3;
11419    const isAtomic = isAtomic$1;
11420    const isEditableCaretCandidate = isEditableCaretCandidate$1;
11421    const getParents$3 = (node, root) => {
11422        const parents = [];
11423        let tempNode = node;
11424        while (tempNode && tempNode !== root) {
11425            parents.push(tempNode);
11426            tempNode = tempNode.parentNode;
11427        }
11428        return parents;
11429    };
11430    const nodeAtIndex = (container, offset) => {
11431        if (container.hasChildNodes() && offset < container.childNodes.length) {
11432            return container.childNodes[offset];
11433        }
11434        return null;
11435    };
11436    const getCaretCandidatePosition = (direction, node) => {
11437        if (isForwards(direction)) {
11438            if (isCaretCandidate(node.previousSibling) && !isText$3(node.previousSibling)) {
11439                return CaretPosition.before(node);
11440            }
11441            if (isText$3(node)) {
11442                return CaretPosition(node, 0);
11443            }
11444        }
11445        if (isBackwards(direction)) {
11446            if (isCaretCandidate(node.nextSibling) && !isText$3(node.nextSibling)) {
11447                return CaretPosition.after(node);
11448            }
11449            if (isText$3(node)) {
11450                return CaretPosition(node, node.data.length);
11451            }
11452        }
11453        if (isBackwards(direction)) {
11454            if (isBr$3(node)) {
11455                return CaretPosition.before(node);
11456            }
11457            return CaretPosition.after(node);
11458        }
11459        return CaretPosition.before(node);
11460    };
11461    const moveForwardFromBr = (root, nextNode) => {
11462        const nextSibling = nextNode.nextSibling;
11463        if (nextSibling && isCaretCandidate(nextSibling)) {
11464            if (isText$3(nextSibling)) {
11465                return CaretPosition(nextSibling, 0);
11466            }
11467            else {
11468                return CaretPosition.before(nextSibling);
11469            }
11470        }
11471        else {
11472            return findCaretPosition$1(1 /* HDirection.Forwards */, CaretPosition.after(nextNode), root);
11473        }
11474    };
11475    const findCaretPosition$1 = (direction, startPos, root) => {
11476        let node;
11477        let nextNode;
11478        let innerNode;
11479        let caretPosition;
11480        if (!isElement$2(root) || !startPos) {
11481            return null;
11482        }
11483        if (startPos.isEqual(CaretPosition.after(root)) && root.lastChild) {
11484            caretPosition = CaretPosition.after(root.lastChild);
11485            if (isBackwards(direction) && isCaretCandidate(root.lastChild) && isElement$2(root.lastChild)) {
11486                return isBr$3(root.lastChild) ? CaretPosition.before(root.lastChild) : caretPosition;
11487            }
11488        }
11489        else {
11490            caretPosition = startPos;
11491        }
11492        const container = caretPosition.container();
11493        let offset = caretPosition.offset();
11494        if (isText$3(container)) {
11495            if (isBackwards(direction) && offset > 0) {
11496                return CaretPosition(container, --offset);
11497            }
11498            if (isForwards(direction) && offset < container.length) {
11499                return CaretPosition(container, ++offset);
11500            }
11501            node = container;
11502        }
11503        else {
11504            if (isBackwards(direction) && offset > 0) {
11505                nextNode = nodeAtIndex(container, offset - 1);
11506                if (isCaretCandidate(nextNode)) {
11507                    if (!isAtomic(nextNode)) {
11508                        innerNode = findNode(nextNode, direction, isEditableCaretCandidate, nextNode);
11509                        if (innerNode) {
11510                            if (isText$3(innerNode)) {
11511                                return CaretPosition(innerNode, innerNode.data.length);
11512                            }
11513                            return CaretPosition.after(innerNode);
11514                        }
11515                    }
11516                    if (isText$3(nextNode)) {
11517                        return CaretPosition(nextNode, nextNode.data.length);
11518                    }
11519                    return CaretPosition.before(nextNode);
11520                }
11521            }
11522            if (isForwards(direction) && offset < container.childNodes.length) {
11523                nextNode = nodeAtIndex(container, offset);
11524                if (isCaretCandidate(nextNode)) {
11525                    if (isBr$3(nextNode)) {
11526                        return moveForwardFromBr(root, nextNode);
11527                    }
11528                    if (!isAtomic(nextNode)) {
11529                        innerNode = findNode(nextNode, direction, isEditableCaretCandidate, nextNode);
11530                        if (innerNode) {
11531                            if (isText$3(innerNode)) {
11532                                return CaretPosition(innerNode, 0);
11533                            }
11534                            return CaretPosition.before(innerNode);
11535                        }
11536                    }
11537                    if (isText$3(nextNode)) {
11538                        return CaretPosition(nextNode, 0);
11539                    }
11540                    return CaretPosition.after(nextNode);
11541                }
11542            }
11543            node = nextNode ? nextNode : caretPosition.getNode();
11544        }
11545        if (node && ((isForwards(direction) && caretPosition.isAtEnd()) || (isBackwards(direction) && caretPosition.isAtStart()))) {
11546            node = findNode(node, direction, always, root, true);
11547            if (isEditableCaretCandidate(node, root)) {
11548                return getCaretCandidatePosition(direction, node);
11549            }
11550        }
11551        nextNode = node ? findNode(node, direction, isEditableCaretCandidate, root) : node;
11552        const rootContentEditableFalseElm = last(filter$5(getParents$3(container, root), isContentEditableFalse$5));
11553        if (rootContentEditableFalseElm && (!nextNode || !rootContentEditableFalseElm.contains(nextNode))) {
11554            if (isForwards(direction)) {
11555                caretPosition = CaretPosition.after(rootContentEditableFalseElm);
11556            }
11557            else {
11558                caretPosition = CaretPosition.before(rootContentEditableFalseElm);
11559            }
11560            return caretPosition;
11561        }
11562        if (nextNode) {
11563            return getCaretCandidatePosition(direction, nextNode);
11564        }
11565        return null;
11566    };
11567    const CaretWalker = (root) => ({
11568        /**
11569           * Returns the next logical caret position from the specified input
11570           * caretPosition or null if there isn't any more positions left for example
11571           * at the end specified root element.
11572           *
11573           * @method next
11574           * @param {tinymce.caret.CaretPosition} caretPosition Caret position to start from.
11575           * @return {tinymce.caret.CaretPosition} CaretPosition or null if no position was found.
11576           */
11577        next: (caretPosition) => {
11578            return findCaretPosition$1(1 /* HDirection.Forwards */, caretPosition, root);
11579        },
11580        /**
11581           * Returns the previous logical caret position from the specified input
11582           * caretPosition or null if there isn't any more positions left for example
11583           * at the end specified root element.
11584           *
11585           * @method prev
11586           * @param {tinymce.caret.CaretPosition} caretPosition Caret position to start from.
11587           * @return {tinymce.caret.CaretPosition} CaretPosition or null if no position was found.
11588           */
11589        prev: (caretPosition) => {
11590            return findCaretPosition$1(-1 /* HDirection.Backwards */, caretPosition, root);
11591        }
11592    });
11593
11594    const walkToPositionIn = (forward, root, start) => {
11595        const position = forward ? CaretPosition.before(start) : CaretPosition.after(start);
11596        return fromPosition(forward, root, position);
11597    };
11598    const afterElement = (node) => isBr$7(node) ? CaretPosition.before(node) : CaretPosition.after(node);
11599    const isBeforeOrStart = (position) => {
11600        if (CaretPosition.isTextPosition(position)) {
11601            return position.offset() === 0;
11602        }
11603        else {
11604            return isCaretCandidate$3(position.getNode());
11605        }
11606    };
11607    const isAfterOrEnd = (position) => {
11608        if (CaretPosition.isTextPosition(position)) {
11609            const container = position.container();
11610            return position.offset() === container.data.length;
11611        }
11612        else {
11613            return isCaretCandidate$3(position.getNode(true));
11614        }
11615    };
11616    const isBeforeAfterSameElement = (from, to) => !CaretPosition.isTextPosition(from) && !CaretPosition.isTextPosition(to) && from.getNode() === to.getNode(true);
11617    const isAtBr = (position) => !CaretPosition.isTextPosition(position) && isBr$7(position.getNode());
11618    const shouldSkipPosition = (forward, from, to) => {
11619        if (forward) {
11620            return !isBeforeAfterSameElement(from, to) && !isAtBr(from) && isAfterOrEnd(from) && isBeforeOrStart(to);
11621        }
11622        else {
11623            return !isBeforeAfterSameElement(to, from) && isBeforeOrStart(from) && isAfterOrEnd(to);
11624        }
11625    };
11626    // Finds: <p>a|<b>b</b></p> -> <p>a<b>|b</b></p>
11627    const fromPosition = (forward, root, pos) => {
11628        const walker = CaretWalker(root);
11629        return Optional.from(forward ? walker.next(pos) : walker.prev(pos));
11630    };
11631    // Finds: <p>a|<b>b</b></p> -> <p>a<b>b|</b></p>
11632    const navigate = (forward, root, from) => fromPosition(forward, root, from).bind((to) => {
11633        if (isInSameBlock(from, to, root) && shouldSkipPosition(forward, from, to)) {
11634            return fromPosition(forward, root, to);
11635        }
11636        else {
11637            return Optional.some(to);
11638        }
11639    });
11640    const navigateIgnore = (forward, root, from, ignoreFilter) => navigate(forward, root, from)
11641        .bind((pos) => ignoreFilter(pos) ? navigateIgnore(forward, root, pos, ignoreFilter) : Optional.some(pos));
11642    const positionIn = (forward, element) => {
11643        const startNode = forward ? element.firstChild : element.lastChild;
11644        if (isText$b(startNode)) {
11645            return Optional.some(CaretPosition(startNode, forward ? 0 : startNode.data.length));
11646        }
11647        else if (startNode) {
11648            if (isCaretCandidate$3(startNode)) {
11649                return Optional.some(forward ? CaretPosition.before(startNode) : afterElement(startNode));
11650            }
11651            else {
11652                return walkToPositionIn(forward, element, startNode);
11653            }
11654        }
11655        else {
11656            return Optional.none();
11657        }
11658    };
11659    const nextPosition = curry(fromPosition, true);
11660    const prevPosition = curry(fromPosition, false);
11661    const firstPositionIn = curry(positionIn, true);
11662    const lastPositionIn = curry(positionIn, false);
11663
11664    const CARET_ID = '_mce_caret';
11665    const isCaretNode = (node) => isElement$7(node) && node.id === CARET_ID;
11666    const getParentCaretContainer = (body, node) => {
11667        let currentNode = node;
11668        while (currentNode && currentNode !== body) {
11669            if (isCaretNode(currentNode)) {
11670                return currentNode;
11671            }
11672            currentNode = currentNode.parentNode;
11673        }
11674        return null;
11675    };
11676
11677    const isStringPathBookmark = (bookmark) => isString(bookmark.start);
11678    const isRangeBookmark = (bookmark) => has$2(bookmark, 'rng');
11679    const isIdBookmark = (bookmark) => has$2(bookmark, 'id');
11680    const isIndexBookmark = (bookmark) => has$2(bookmark, 'name');
11681    const isPathBookmark = (bookmark) => Tools.isArray(bookmark.start);
11682
11683    const isForwardBookmark = (bookmark) => !isIndexBookmark(bookmark) && isBoolean(bookmark.forward) ? bookmark.forward : true;
11684    const addBogus = (dom, node) => {
11685        // Adds a bogus BR element for empty block elements
11686        if (isElement$7(node) && dom.isBlock(node) && !node.innerHTML) {
11687            node.innerHTML = '<br data-mce-bogus="1" />';
11688        }
11689        return node;
11690    };
11691    const resolveCaretPositionBookmark = (dom, bookmark) => {
11692        const startPos = Optional.from(resolve$1(dom.getRoot(), bookmark.start));
11693        const endPos = Optional.from(resolve$1(dom.getRoot(), bookmark.end));
11694        return lift2(startPos, endPos, (start, end) => {
11695            const range = dom.createRng();
11696            range.setStart(start.container(), start.offset());
11697            range.setEnd(end.container(), end.offset());
11698            return { range, forward: isForwardBookmark(bookmark) };
11699        });
11700    };
11701    const insertZwsp = (node, rng) => {
11702        const doc = node.ownerDocument ?? document;
11703        const textNode = doc.createTextNode(ZWSP$1);
11704        node.appendChild(textNode);
11705        rng.setStart(textNode, 0);
11706        rng.setEnd(textNode, 0);
11707    };
11708    const isEmpty$3 = (node) => !node.hasChildNodes();
11709    const tryFindRangePosition = (node, rng) => lastPositionIn(node).fold(never, (pos) => {
11710        rng.setStart(pos.container(), pos.offset());
11711        rng.setEnd(pos.container(), pos.offset());
11712        return true;
11713    });
11714    // Since we trim zwsp from undo levels the caret format containers
11715    // may be empty if so pad them with a zwsp and move caret there
11716    const padEmptyCaretContainer = (root, node, rng) => {
11717        if (isEmpty$3(node) && getParentCaretContainer(root, node)) {
11718            insertZwsp(node, rng);
11719            return true;
11720        }
11721        else {
11722            return false;
11723        }
11724    };
11725    const setEndPoint = (dom, start, bookmark, rng) => {
11726        const point = bookmark[start ? 'start' : 'end'];
11727        const root = dom.getRoot();
11728        if (point) {
11729            let node = root;
11730            let offset = point[0];
11731            // Find container node
11732            for (let i = point.length - 1; node && i >= 1; i--) {
11733                const children = node.childNodes;
11734                if (padEmptyCaretContainer(root, node, rng)) {
11735                    return true;
11736                }
11737                if (point[i] > children.length - 1) {
11738                    if (padEmptyCaretContainer(root, node, rng)) {
11739                        return true;
11740                    }
11741                    return tryFindRangePosition(node, rng);
11742                }
11743                node = children[point[i]];
11744            }
11745            // Move text offset to best suitable location
11746            if (isText$b(node)) {
11747                offset = Math.min(point[0], node.data.length);
11748            }
11749            // Move element offset to best suitable location
11750            if (isElement$7(node)) {
11751                offset = Math.min(point[0], node.childNodes.length);
11752            }
11753            // Set offset within container node
11754            if (start) {
11755                rng.setStart(node, offset);
11756            }
11757            else {
11758                rng.setEnd(node, offset);
11759            }
11760        }
11761        return true;
11762    };
11763    const isValidTextNode = (node) => isText$b(node) && node.data.length > 0;
11764    const restoreEndPoint$1 = (dom, suffix, bookmark) => {
11765        const marker = dom.get(bookmark.id + '_' + suffix);
11766        const markerParent = marker?.parentNode;
11767        const keep = bookmark.keep;
11768        if (marker && markerParent) {
11769            let container;
11770            let offset;
11771            if (suffix === 'start') {
11772                if (!keep) {
11773                    container = markerParent;
11774                    offset = dom.nodeIndex(marker);
11775                }
11776                else {
11777                    if (marker.hasChildNodes()) {
11778                        container = marker.firstChild;
11779                        offset = 1;
11780                    }
11781                    else if (isValidTextNode(marker.nextSibling)) {
11782                        container = marker.nextSibling;
11783                        offset = 0;
11784                    }
11785                    else if (isValidTextNode(marker.previousSibling)) {
11786                        container = marker.previousSibling;
11787                        offset = marker.previousSibling.data.length;
11788                    }
11789                    else {
11790                        container = markerParent;
11791                        offset = dom.nodeIndex(marker) + 1;
11792                    }
11793                }
11794            }
11795            else {
11796                if (!keep) {
11797                    container = markerParent;
11798                    offset = dom.nodeIndex(marker);
11799                }
11800                else {
11801                    if (marker.hasChildNodes()) {
11802                        container = marker.firstChild;
11803                        offset = 1;
11804                    }
11805                    else if (isValidTextNode(marker.previousSibling)) {
11806                        container = marker.previousSibling;
11807                        offset = marker.previousSibling.data.length;
11808                    }
11809                    else {
11810                        container = markerParent;
11811                        offset = dom.nodeIndex(marker);
11812                    }
11813                }
11814            }
11815            if (!keep) {
11816                const prev = marker.previousSibling;
11817                const next = marker.nextSibling;
11818                // Remove all marker text nodes
11819                Tools.each(Tools.grep(marker.childNodes), (node) => {
11820                    if (isText$b(node)) {
11821                        node.data = node.data.replace(/\uFEFF/g, '');
11822                    }
11823                });
11824                // Remove marker but keep children if for example contents where inserted into the marker
11825                // Also remove duplicated instances of the marker for example by a
11826                // split operation or by WebKit auto split on paste feature
11827                let otherMarker;
11828                while ((otherMarker = dom.get(bookmark.id + '_' + suffix))) {
11829                    dom.remove(otherMarker, true);
11830                }
11831                // If siblings are text nodes then merge them unless it's Opera since it some how removes the node
11832                // and we are sniffing since adding a lot of detection code for a browser with 3% of the market
11833                // isn't worth the effort. Sorry, Opera but it's just a fact
11834                if (isText$b(next) && isText$b(prev) && !Env.browser.isOpera()) {
11835                    const idx = prev.data.length;
11836                    prev.appendData(next.data);
11837                    dom.remove(next);
11838                    container = prev;
11839                    offset = idx;
11840                }
11841            }
11842            return Optional.some(CaretPosition(container, offset));
11843        }
11844        else {
11845            return Optional.none();
11846        }
11847    };
11848    const resolvePaths = (dom, bookmark) => {
11849        const range = dom.createRng();
11850        if (setEndPoint(dom, true, bookmark, range) && setEndPoint(dom, false, bookmark, range)) {
11851            return Optional.some({ range, forward: isForwardBookmark(bookmark) });
11852        }
11853        else {
11854            return Optional.none();
11855        }
11856    };
11857    const resolveId = (dom, bookmark) => {
11858        const startPos = restoreEndPoint$1(dom, 'start', bookmark);
11859        const endPos = restoreEndPoint$1(dom, 'end', bookmark);
11860        return lift2(startPos, endPos.or(startPos), (spos, epos) => {
11861            const range = dom.createRng();
11862            range.setStart(addBogus(dom, spos.container()), spos.offset());
11863            range.setEnd(addBogus(dom, epos.container()), epos.offset());
11864            return { range, forward: isForwardBookmark(bookmark) };
11865        });
11866    };
11867    const resolveIndex = (dom, bookmark) => Optional.from(dom.select(bookmark.name)[bookmark.index]).map((elm) => {
11868        const range = dom.createRng();
11869        range.selectNode(elm);
11870        return { range, forward: true };
11871    });
11872    const resolve = (selection, bookmark) => {
11873        const dom = selection.dom;
11874        if (bookmark) {
11875            if (isPathBookmark(bookmark)) {
11876                return resolvePaths(dom, bookmark);
11877            }
11878            else if (isStringPathBookmark(bookmark)) {
11879                return resolveCaretPositionBookmark(dom, bookmark);
11880            }
11881            else if (isIdBookmark(bookmark)) {
11882                return resolveId(dom, bookmark);
11883            }
11884            else if (isIndexBookmark(bookmark)) {
11885                return resolveIndex(dom, bookmark);
11886            }
11887            else if (isRangeBookmark(bookmark)) {
11888                return Optional.some({ range: bookmark.rng, forward: isForwardBookmark(bookmark) });
11889            }
11890        }
11891        return Optional.none();
11892    };
11893
11894    const getBookmark$1 = (selection, type, normalized) => {
11895        return getBookmark$2(selection, type, normalized);
11896    };
11897    const moveToBookmark = (selection, bookmark) => {
11898        resolve(selection, bookmark).each(({ range, forward }) => {
11899            selection.setRng(range, forward);
11900        });
11901    };
11902    const isBookmarkNode$1 = (node) => {
11903        return isElement$7(node) && node.tagName === 'SPAN' && node.getAttribute('data-mce-type') === 'bookmark';
11904    };
11905
11906    const is = (expected) => (actual) => expected === actual;
11907    const isNbsp = is(nbsp);
11908    const isWhiteSpace = (chr) => chr !== '' && ' \f\n\r\t\v'.indexOf(chr) !== -1;
11909    const isContent = (chr) => !isWhiteSpace(chr) && !isNbsp(chr) && !isZwsp$2(chr);
11910
11911    const getRanges = (selection) => {
11912        const ranges = [];
11913        if (selection) {
11914            for (let i = 0; i < selection.rangeCount; i++) {
11915                ranges.push(selection.getRangeAt(i));
11916            }
11917        }
11918        return ranges;
11919    };
11920    const getSelectedNodes = (ranges) => {
11921        return bind$3(ranges, (range) => {
11922            const node = getSelectedNode(range);
11923            return node ? [SugarElement.fromDom(node)] : [];
11924        });
11925    };
11926    const hasMultipleRanges = (selection) => {
11927        return getRanges(selection).length > 1;
11928    };
11929
11930    const getCellsFromRanges = (ranges) => filter$5(getSelectedNodes(ranges), isTableCell$2);
11931    const getCellsFromElement = (elm) => descendants(elm, 'td[data-mce-selected],th[data-mce-selected]');
11932    const getCellsFromElementOrRanges = (ranges, element) => {
11933        const selectedCells = getCellsFromElement(element);
11934        return selectedCells.length > 0 ? selectedCells : getCellsFromRanges(ranges);
11935    };
11936    const getCellsFromEditor = (editor) => getCellsFromElementOrRanges(getRanges(editor.selection.getSel()), SugarElement.fromDom(editor.getBody()));
11937    const getClosestTable = (cell, isRoot) => ancestor$4(cell, 'table', isRoot);
11938
11939    const getStartNode = (rng) => {
11940        const sc = rng.startContainer, so = rng.startOffset;
11941        if (isText$b(sc)) {
11942            return so === 0 ? Optional.some(SugarElement.fromDom(sc)) : Optional.none();
11943        }
11944        else {
11945            return Optional.from(sc.childNodes[so]).map(SugarElement.fromDom);
11946        }
11947    };
11948    const getEndNode = (rng) => {
11949        const ec = rng.endContainer, eo = rng.endOffset;
11950        if (isText$b(ec)) {
11951            return eo === ec.data.length ? Optional.some(SugarElement.fromDom(ec)) : Optional.none();
11952        }
11953        else {
11954            return Optional.from(ec.childNodes[eo - 1]).map(SugarElement.fromDom);
11955        }
11956    };
11957    const getFirstChildren = (node) => {
11958        return firstChild(node).fold(constant([node]), (child) => {
11959            return [node].concat(getFirstChildren(child));
11960        });
11961    };
11962    const getLastChildren = (node) => {
11963        return lastChild(node).fold(constant([node]), (child) => {
11964            if (name(child) === 'br') {
11965                return prevSibling(child).map((sibling) => {
11966                    return [node].concat(getLastChildren(sibling));
11967                }).getOr([]);
11968            }
11969            else {
11970                return [node].concat(getLastChildren(child));
11971            }
11972        });
11973    };
11974    const hasAllContentsSelected = (elm, rng) => {
11975        return lift2(getStartNode(rng), getEndNode(rng), (startNode, endNode) => {
11976            const start = find$2(getFirstChildren(elm), curry(eq, startNode));
11977            const end = find$2(getLastChildren(elm), curry(eq, endNode));
11978            return start.isSome() && end.isSome();
11979        }).getOr(false);
11980    };
11981    const moveEndPoint = (dom, rng, node, start) => {
11982        const root = node;
11983        const walker = new DomTreeWalker(node, root);
11984        const moveCaretBeforeOnEnterElementsMap = filter$4(dom.schema.getMoveCaretBeforeOnEnterElements(), (_, name) => !contains$2(['td', 'th', 'table'], name.toLowerCase()));
11985        let currentNode = node;
11986        do {
11987            if (isText$b(currentNode) && Tools.trim(currentNode.data).length !== 0) {
11988                if (start) {
11989                    rng.setStart(currentNode, 0);
11990                }
11991                else {
11992                    rng.setEnd(currentNode, currentNode.data.length);
11993                }
11994                return;
11995            }
11996            // BR/IMG/INPUT elements but not table cells
11997            if (moveCaretBeforeOnEnterElementsMap[currentNode.nodeName]) {
11998                if (start) {
11999                    rng.setStartBefore(currentNode);
12000                }
12001                else {
12002                    if (currentNode.nodeName === 'BR') {
12003                        rng.setEndBefore(currentNode);
12004                    }
12005                    else {
12006                        rng.setEndAfter(currentNode);
12007                    }
12008                }
12009                return;
12010            }
12011        } while ((currentNode = (start ? walker.next() : walker.prev())));
12012        // Failed to find any text node or other suitable location then move to the root of body
12013        if (root.nodeName === 'BODY') {
12014            if (start) {
12015                rng.setStart(root, 0);
12016            }
12017            else {
12018                rng.setEnd(root, root.childNodes.length);
12019            }
12020        }
12021    };
12022    const hasAnyRanges = (editor) => {
12023        const sel = editor.selection.getSel();
12024        return isNonNullable(sel) && sel.rangeCount > 0;
12025    };
12026    const runOnRanges = (editor, executor) => {
12027        // Check to see if a fake selection is active. If so then we are simulating a multi range
12028        // selection so we should return a range for each selected node.
12029        // Note: Currently tables are the only thing supported for fake selections.
12030        const fakeSelectionNodes = getCellsFromEditor(editor);
12031        if (fakeSelectionNodes.length > 0) {
12032            each$e(fakeSelectionNodes, (elem) => {
12033                const node = elem.dom;
12034                const fakeNodeRng = editor.dom.createRng();
12035                fakeNodeRng.setStartBefore(node);
12036                fakeNodeRng.setEndAfter(node);
12037                executor(fakeNodeRng, true);
12038            });
12039        }
12040        else {
12041            executor(editor.selection.getRng(), false);
12042        }
12043    };
12044    const preserve = (selection, fillBookmark, executor) => {
12045        const bookmark = getPersistentBookmark(selection, fillBookmark);
12046        executor(bookmark);
12047        selection.moveToBookmark(bookmark);
12048    };
12049    const isSelectionOverWholeNode = (range, nodeTypePredicate) => range.startContainer === range.endContainer
12050        && range.endOffset - range.startOffset === 1
12051        && nodeTypePredicate(range.startContainer.childNodes[range.startOffset]);
12052    const isSelectionOverWholeHTMLElement = (range) => isSelectionOverWholeNode(range, isHTMLElement);
12053    const isSelectionOverWholeTextNode = (range) => isSelectionOverWholeNode(range, isText$b);
12054    const isSelectionOverWholeAnchor = (range) => isSelectionOverWholeNode(range, isAnchor);
12055
12056    const isNode = (node) => isNumber(node?.nodeType);
12057    const isElementNode$1 = (node) => isElement$7(node) && !isBookmarkNode$1(node) && !isCaretNode(node) && !isBogus$1(node);
12058    // In TinyMCE, directly selected elements are indicated with the data-mce-selected attribute
12059    // Elements that can be directly selected include control elements such as img, media elements, noneditable elements and others
12060    const isElementDirectlySelected = (dom, node) => {
12061        // Table cells are a special case and are separately handled from native editor selection
12062        if (isElementNode$1(node) && !/^(TD|TH)$/.test(node.nodeName)) {
12063            const selectedAttr = dom.getAttrib(node, 'data-mce-selected');
12064            const value = parseInt(selectedAttr, 10);
12065            // Avoid cases where data-mce-selected is not a positive number e.g. inline-boundary
12066            return !isNaN(value) && value > 0;
12067        }
12068        else {
12069            return false;
12070        }
12071    };
12072    // TODO: TINY-9130 Look at making SelectionUtils.preserve maintain the noneditable selection instead
12073    const preserveSelection = (editor, action, shouldMoveStart) => {
12074        const { selection, dom } = editor;
12075        const selectedNodeBeforeAction = selection.getNode();
12076        const isSelectedBeforeNodeNoneditable = isContentEditableFalse$a(selectedNodeBeforeAction);
12077        preserve(selection, true, () => {
12078            action();
12079        });
12080        // Check previous selected node before the action still exists in the DOM
12081        // and is still noneditable
12082        const isBeforeNodeStillNoneditable = isSelectedBeforeNodeNoneditable && isContentEditableFalse$a(selectedNodeBeforeAction);
12083        if (isBeforeNodeStillNoneditable && dom.isChildOf(selectedNodeBeforeAction, editor.getBody())) {
12084            editor.selection.select(selectedNodeBeforeAction);
12085        }
12086        else if (shouldMoveStart(selection.getStart())) {
12087            moveStartToNearestText(dom, selection);
12088        }
12089    };
12090    // Note: The reason why we only care about moving the start is because MatchFormat and its function use the start of the selection to determine if a selection has a given format or not
12091    const moveStartToNearestText = (dom, selection) => {
12092        const rng = selection.getRng();
12093        const { startContainer, startOffset } = rng;
12094        const selectedNode = selection.getNode();
12095        if (isElementDirectlySelected(dom, selectedNode)) {
12096            return;
12097        }
12098        // Try move startContainer/startOffset to a suitable text node
12099        if (isElement$7(startContainer)) {
12100            const nodes = startContainer.childNodes;
12101            const root = dom.getRoot();
12102            let walker;
12103            if (startOffset < nodes.length) {
12104                const startNode = nodes[startOffset];
12105                walker = new DomTreeWalker(startNode, dom.getParent(startNode, dom.isBlock) ?? root);
12106            }
12107            else {
12108                const startNode = nodes[nodes.length - 1];
12109                walker = new DomTreeWalker(startNode, dom.getParent(startNode, dom.isBlock) ?? root);
12110                walker.next(true);
12111            }
12112            for (let node = walker.current(); node; node = walker.next()) {
12113                // If we have found a noneditable element before we have found any text
12114                // then we cannot move forward any further as otherwise the start could be put inside
12115                // the non-editable element which is not valid
12116                if (dom.getContentEditable(node) === 'false') {
12117                    return;
12118                }
12119                else if (isText$b(node) && !isWhiteSpaceNode$1(node)) {
12120                    rng.setStart(node, 0);
12121                    selection.setRng(rng);
12122                    return;
12123                }
12124            }
12125        }
12126    };
12127    /**
12128     * Returns the next/previous non whitespace node.
12129     *
12130     * @private
12131     * @param {Node} node Node to start at.
12132     * @param {Boolean} next (Optional) Include next or previous node defaults to previous.
12133     * @param {Boolean} inc (Optional) Include the current node in checking. Defaults to false.
12134     * @return {Node} Next or previous node or undefined if it wasn't found.
12135     */
12136    const getNonWhiteSpaceSibling = (node, next, inc) => {
12137        if (node) {
12138            const nextName = next ? 'nextSibling' : 'previousSibling';
12139            for (node = inc ? node : node[nextName]; node; node = node[nextName]) {
12140                if (isElement$7(node) || !isWhiteSpaceNode$1(node)) {
12141                    return node;
12142                }
12143            }
12144        }
12145        return undefined;
12146    };
12147    const isTextBlock$2 = (schema, node) => !!schema.getTextBlockElements()[node.nodeName.toLowerCase()] || isTransparentBlock(schema, node);
12148    const isValid = (ed, parent, child) => {
12149        return ed.schema.isValidChild(parent, child);
12150    };
12151    const isWhiteSpaceNode$1 = (node, allowSpaces = false) => {
12152        if (isNonNullable(node) && isText$b(node)) {
12153            // If spaces are allowed, treat them as a non-breaking space
12154            const data = allowSpaces ? node.data.replace(/ /g, '\u00a0') : node.data;
12155            return isWhitespaceText(data);
12156        }
12157        else {
12158            return false;
12159        }
12160    };
12161    const isEmptyTextNode$1 = (node) => {
12162        return isNonNullable(node) && isText$b(node) && node.length === 0;
12163    };
12164    const isWrapNoneditableTarget = (editor, node) => {
12165        const baseDataSelector = '[data-mce-cef-wrappable]';
12166        const formatNoneditableSelector = getFormatNoneditableSelector(editor);
12167        const selector = isEmpty$5(formatNoneditableSelector) ? baseDataSelector : `${baseDataSelector},${formatNoneditableSelector}`;
12168        return is$2(SugarElement.fromDom(node), selector);
12169    };
12170    
vendor: 5,370 bytes, lines 12170-12290
12170// A noneditable element is wrappable if it:
12171    // - is valid target (has data-mce-cef-wrappable attribute or matches selector from option)
12172    // - has no editable descendants - removing formats in the editable region can result in the wrapped noneditable being split which is undesirable
12173    const isWrappableNoneditable = (editor, node) => {
12174        const dom = editor.dom;
12175        return (isElementNode$1(node) &&
12176            dom.getContentEditable(node) === 'false' &&
12177            isWrapNoneditableTarget(editor, node) &&
12178            dom.select('[contenteditable="true"]', node).length === 0);
12179    };
12180    /**
12181     * Replaces variables in the value. The variable format is %var.
12182     *
12183     * @private
12184     * @param {String} value Value to replace variables in.
12185     * @param {Object} vars Name/value array with variables to replace.
12186     * @return {String} New value with replaced variables.
12187     */
12188    const replaceVars = (value, vars) => {
12189        if (isFunction(value)) {
12190            return value(vars);
12191        }
12192        else if (isNonNullable(vars)) {
12193            value = value.replace(/%(\w+)/g, (str, name) => {
12194                return vars[name] || str;
12195            });
12196        }
12197        return value;
12198    };
12199    /**
12200     * Compares two string/nodes regardless of their case.
12201     *
12202     * @private
12203     * @param {String/Node} str1 Node or string to compare.
12204     * @param {String/Node} str2 Node or string to compare.
12205     * @return {Boolean} True/false if they match.
12206     */
12207    const isEq$5 = (str1, str2) => {
12208        str1 = str1 || '';
12209        str2 = str2 || '';
12210        str1 = '' + (str1.nodeName || str1);
12211        str2 = '' + (str2.nodeName || str2);
12212        return str1.toLowerCase() === str2.toLowerCase();
12213    };
12214    const normalizeStyleValue = (value, name) => {
12215        if (isNullable(value)) {
12216            return null;
12217        }
12218        else {
12219            let strValue = String(value);
12220            // Force the format to hex
12221            if (name === 'color' || name === 'backgroundColor') {
12222                strValue = rgbaToHexString(strValue);
12223            }
12224            // Opera will return bold as 700
12225            if (name === 'fontWeight' && value === 700) {
12226                strValue = 'bold';
12227            }
12228            // Normalize fontFamily so "'Font name', Font" becomes: "Font name,Font"
12229            if (name === 'fontFamily') {
12230                strValue = strValue.replace(/[\'\"]/g, '').replace(/,\s+/g, ',');
12231            }
12232            return strValue;
12233        }
12234    };
12235    const getStyle = (dom, node, name) => {
12236        const style = dom.getStyle(node, name);
12237        return normalizeStyleValue(style, name);
12238    };
12239    const getTextDecoration = (dom, node) => {
12240        let decoration;
12241        dom.getParent(node, (n) => {
12242            if (isElement$7(n)) {
12243                decoration = dom.getStyle(n, 'text-decoration');
12244                return !!decoration && decoration !== 'none';
12245            }
12246            else {
12247                return false;
12248            }
12249        });
12250        return decoration;
12251    };
12252    const getParents$2 = (dom, node, selector) => {
12253        return dom.getParents(node, selector, dom.getRoot());
12254    };
12255    const isFormatPredicate = (editor, formatName, predicate) => {
12256        const formats = editor.formatter.get(formatName);
12257        return isNonNullable(formats) && exists(formats, predicate);
12258    };
12259    const isVariableFormatName = (editor, formatName) => {
12260        const hasVariableValues = (format) => {
12261            const isVariableValue = (val) => isFunction(val) || val.length > 1 && val.charAt(0) === '%';
12262            return exists(['styles', 'attributes'], (key) => get$a(format, key).exists((field) => {
12263                const fieldValues = isArray$1(field) ? field : values(field);
12264                return exists(fieldValues, isVariableValue);
12265            }));
12266        };
12267        return isFormatPredicate(editor, formatName, hasVariableValues);
12268    };
12269    /**
12270     * Checks if the two formats are similar based on the format type, attributes, styles and classes
12271     */
12272    const areSimilarFormats = (editor, formatName, otherFormatName) => {
12273        // Note: MatchFormat.matchNode() uses these parameters to check if a format matches a node
12274        // Therefore, these are ideal to check if two formats are similar
12275        const validKeys = ['inline', 'block', 'selector', 'attributes', 'styles', 'classes'];
12276        const filterObj = (format) => filter$4(format, (_, key) => exists(validKeys, (validKey) => validKey === key));
12277        return isFormatPredicate(editor, formatName, (fmt1) => {
12278            const filteredFmt1 = filterObj(fmt1);
12279            return isFormatPredicate(editor, otherFormatName, (fmt2) => {
12280                const filteredFmt2 = filterObj(fmt2);
12281                return equal$1(filteredFmt1, filteredFmt2);
12282            });
12283        });
12284    };
12285    const isBlockFormat = (format) => hasNonNullableKey(format, 'block');
12286    const isWrappingBlockFormat = (format) => isBlockFormat(format) && format.wrapper === true;
12287    const isNonWrappingBlockFormat = (format) => isBlockFormat(format) && format.wrapper !== true;
12288    const isSelectorFormat = (format) => hasNonNullableKey(format, 'selector');
12289    const isInlineFormat = (format) => hasNonNullableKey(format, 'inline');
12290    const isMixedFormat = (format) => isSelectorFormat(format) && isInlineFormat(format) && is$4(get$a
vendor: 18,594 bytes, lines 12290-12690
12290(format, 'mixed'), true);
12291    const shouldExpandToSelector = (format) => isSelectorFormat(format) && format.expand !== false && !isInlineFormat(format);
12292    const getEmptyCaretContainers = (node) => {
12293        const nodes = [];
12294        let tempNode = node;
12295        while (tempNode) {
12296            if ((isText$b(tempNode) && tempNode.data !== ZWSP$1) || tempNode.childNodes.length > 1) {
12297                return [];
12298            }
12299            // Collect nodes
12300            if (isElement$7(tempNode)) {
12301                nodes.push(tempNode);
12302            }
12303            tempNode = tempNode.firstChild;
12304        }
12305        return nodes;
12306    };
12307    const isCaretContainerEmpty = (node) => {
12308        return getEmptyCaretContainers(node).length > 0;
12309    };
12310    const isEmptyCaretFormatElement = (element) => {
12311        return isCaretNode(element.dom) && isCaretContainerEmpty(element.dom);
12312    };
12313
12314    const isBookmarkNode = isBookmarkNode$1;
12315    const getParents$1 = getParents$2;
12316    const isWhiteSpaceNode = isWhiteSpaceNode$1;
12317    const isTextBlock$1 = isTextBlock$2;
12318    const isBogusBr$1 = (node) => {
12319        return isBr$7(node) && node.getAttribute('data-mce-bogus') && !node.nextSibling;
12320    };
12321    // Expands the node to the closes contentEditable false element if it exists
12322    const findParentContentEditable = (dom, node) => {
12323        let parent = node;
12324        while (parent) {
12325            if (isElement$7(parent) && dom.getContentEditable(parent)) {
12326                return dom.getContentEditable(parent) === 'false' ? parent : node;
12327            }
12328            parent = parent.parentNode;
12329        }
12330        return node;
12331    };
12332    const walkText = (start, node, offset, predicate) => {
12333        const str = node.data;
12334        if (start) {
12335            for (let i = offset; i > 0; i--) {
12336                if (predicate(str.charAt(i - 1))) {
12337                    return i;
12338                }
12339            }
12340        }
12341        else {
12342            for (let i = offset; i < str.length; i++) {
12343                if (predicate(str.charAt(i))) {
12344                    return i;
12345                }
12346            }
12347        }
12348        return -1;
12349    };
12350    const findSpace = (start, node, offset) => walkText(start, node, offset, (c) => isNbsp(c) || isWhiteSpace(c));
12351    const findContent = (start, node, offset) => walkText(start, node, offset, isContent);
12352    const findWordEndPoint = (dom, body, container, offset, start, includeTrailingSpaces) => {
12353        let lastTextNode;
12354        const closestRoot = dom.getParent(container, (node) => isEditingHost(node) || dom.isBlock(node));
12355        const rootNode = isNonNullable(closestRoot) ? closestRoot : body;
12356        const walk = (container, offset, pred) => {
12357            const textSeeker = TextSeeker(dom);
12358            const walker = start ? textSeeker.backwards : textSeeker.forwards;
12359            return Optional.from(walker(container, offset, (text, textOffset) => {
12360                if (isBookmarkNode(text.parentNode)) {
12361                    return -1;
12362                }
12363                else {
12364                    lastTextNode = text;
12365                    return pred(start, text, textOffset);
12366                }
12367            }, rootNode));
12368        };
12369        const spaceResult = walk(container, offset, findSpace);
12370        return spaceResult.bind((result) => includeTrailingSpaces ?
12371            walk(result.container, result.offset + (start ? -1 : 0), findContent) :
12372            Optional.some(result)).orThunk(() => lastTextNode ?
12373            Optional.some({ container: lastTextNode, offset: start ? 0 : lastTextNode.length }) :
12374            Optional.none());
12375    };
12376    const findSelectorEndPoint = (dom, formatList, rng, container, siblingName) => {
12377        const sibling = container[siblingName];
12378        if (isText$b(container) && isEmpty$5(container.data) && sibling) {
12379            container = sibling;
12380        }
12381        const parents = getParents$1(dom, container);
12382        for (let i = 0; i < parents.length; i++) {
12383            for (let y = 0; y < formatList.length; y++) {
12384                const curFormat = formatList[y];
12385                // If collapsed state is set then skip formats that doesn't match that
12386                if (isNonNullable(curFormat.collapsed) && curFormat.collapsed !== rng.collapsed) {
12387                    continue;
12388                }
12389                if (isSelectorFormat(curFormat) && dom.is(parents[i], curFormat.selector)) {
12390                    return parents[i];
12391                }
12392            }
12393        }
12394        return container;
12395    };
12396    const findBlockEndPoint = (dom, formatList, container, siblingName) => {
12397        let node = container;
12398        const root = dom.getRoot();
12399        const format = formatList[0];
12400        // Expand to block of similar type
12401        if (isBlockFormat(format)) {
12402            node = format.wrapper ? null : dom.getParent(container, format.block, root);
12403        }
12404        // Expand to first wrappable block element or any block element
12405        if (!node) {
12406            const scopeRoot = dom.getParent(container, 'LI,TD,TH,SUMMARY') ?? root;
12407            node = dom.getParent(isText$b(container) ? container.parentNode : container, 
12408            // Fixes #6183 where it would expand to editable parent element in inline mode
12409            (node) => node !== root && isTextBlock$1(dom.schema, node), scopeRoot);
12410        }
12411        // Exclude inner lists from wrapping
12412        if (node && isBlockFormat(format) && format.wrapper) {
12413            node = getParents$1(dom, node, 'ul,ol').reverse()[0] || node;
12414        }
12415        // Didn't find a block element look for first/last wrappable element
12416        if (!node) {
12417            node = container;
12418            while (node && node[siblingName] && !dom.isBlock(node[siblingName])) {
12419                node = node[siblingName];
12420                // Break on BR but include it will be removed later on
12421                // we can't remove it now since we need to check if it can be wrapped
12422                if (isEq$5(node, 'br')) {
12423                    break;
12424                }
12425            }
12426        }
12427        return node || container;
12428    };
12429    // We're at the edge if the parent is a block and there's no next sibling. Alternatively,
12430    // if we reach the root or can't walk further we also consider it to be a boundary.
12431    const isAtBlockBoundary$1 = (dom, root, container, siblingName) => {
12432        const parent = container.parentNode;
12433        if (isNonNullable(container[siblingName])) {
12434            return false;
12435        }
12436        else if (parent === root || isNullable(parent) || dom.isBlock(parent)) {
12437            return true;
12438        }
12439        else {
12440            return isAtBlockBoundary$1(dom, root, parent, siblingName);
12441        }
12442    };
12443    // This function walks up the tree if there is no siblings before/after the node.
12444    // If a sibling is found then the container is returned
12445    const findParentContainer = (dom, formatList, container, offset, start, expandToBlock) => {
12446        let parent = container;
12447        const siblingName = start ? 'previousSibling' : 'nextSibling';
12448        const root = dom.getRoot();
12449        // If it's a text node and the offset is inside the text
12450        if (isText$b(container) && !isWhiteSpaceNode(container)) {
12451            if (start ? offset > 0 : offset < container.data.length) {
12452                return container;
12453            }
12454        }
12455        while (parent) {
12456            if (isEditingHost(parent)) {
12457                return container;
12458            }
12459            // Stop expanding on block elements
12460            if (!formatList[0].block_expand && dom.isBlock(parent)) {
12461                return expandToBlock ? parent : container;
12462            }
12463            // Walk left/right
12464            for (let sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) {
12465                // Allow spaces if not at the edge of a block element, as the spaces won't have been collapsed
12466                const allowSpaces = isText$b(sibling) && !isAtBlockBoundary$1(dom, root, sibling, siblingName);
12467                if (!isBookmarkNode(sibling) && !isBogusBr$1(sibling) && !isWhiteSpaceNode(sibling, allowSpaces)) {
12468                    return parent;
12469                }
12470            }
12471            // Check if we can move up are we at root level or body level
12472            if (parent === root || parent.parentNode === root) {
12473                container = parent;
12474                break;
12475            }
12476            parent = parent.parentNode;
12477        }
12478        return container;
12479    };
12480    const isSelfOrParentBookmark = (container) => isBookmarkNode(container.parentNode) || isBookmarkNode(container);
12481    const expandRng = (dom, rng, formatList, expandOptions = {}) => {
12482        const { includeTrailingSpace = false, expandToBlock = true } = expandOptions;
12483        const editableHost = dom.getParent(rng.commonAncestorContainer, (node) => isEditingHost(node));
12484        const root = isNonNullable(editableHost) ? editableHost : dom.getRoot();
12485        let { startContainer, startOffset, endContainer, endOffset } = rng;
12486        const format = formatList[0];
12487        // If index based start position then resolve it
12488        if (isElement$7(startContainer) && startContainer.hasChildNodes()) {
12489            startContainer = getNode$1(startContainer, startOffset);
12490            if (isText$b(startContainer)) {
12491                startOffset = 0;
12492            }
12493        }
12494        // If index based end position then resolve it
12495        if (isElement$7(endContainer) && endContainer.hasChildNodes()) {
12496            endContainer = getNode$1(endContainer, rng.collapsed ? endOffset : endOffset - 1);
12497            if (isText$b(endContainer)) {
12498                endOffset = endContainer.data.length;
12499            }
12500        }
12501        // Expand to closest contentEditable element
12502        startContainer = findParentContentEditable(dom, startContainer);
12503        endContainer = findParentContentEditable(dom, endContainer);
12504        // Exclude bookmark nodes if possible
12505        if (isSelfOrParentBookmark(startContainer)) {
12506            startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode;
12507            if (rng.collapsed) {
12508                startContainer = startContainer.previousSibling || startContainer;
12509            }
12510            else {
12511                startContainer = startContainer.nextSibling || startContainer;
12512            }
12513            if (isText$b(startContainer)) {
12514                startOffset = rng.collapsed ? startContainer.length : 0;
12515            }
12516        }
12517        if (isSelfOrParentBookmark(endContainer)) {
12518            endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode;
12519            if (rng.collapsed) {
12520                endContainer = endContainer.nextSibling || endContainer;
12521            }
12522            else {
12523                endContainer = endContainer.previousSibling || endContainer;
12524            }
12525            if (isText$b(endContainer)) {
12526                endOffset = rng.collapsed ? 0 : endContainer.length;
12527            }
12528        }
12529        if (rng.collapsed) {
12530            // Expand left to closest word boundary
12531            const startPoint = findWordEndPoint(dom, root, startContainer, startOffset, true, includeTrailingSpace);
12532            startPoint.each(({ container, offset }) => {
12533                startContainer = container;
12534                startOffset = offset;
12535            });
12536            // Expand right to closest word boundary
12537            const endPoint = findWordEndPoint(dom, root, endContainer, endOffset, false, includeTrailingSpace);
12538            endPoint.each(({ container, offset }) => {
12539                endContainer = container;
12540                endOffset = offset;
12541            });
12542        }
12543        // Move start/end point up the tree if the leaves are sharp and if we are in different containers
12544        // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
12545        // This will reduce the number of wrapper elements that needs to be created
12546        // Move start point up the tree
12547        if (isInlineFormat(format) || format.block_expand) {
12548            if (!isInlineFormat(format) || (!isText$b(startContainer) || startOffset === 0)) {
12549                startContainer = findParentContainer(dom, formatList, startContainer, startOffset, true, expandToBlock);
12550            }
12551            if (!isInlineFormat(format) || (!isText$b(endContainer) || endOffset === endContainer.data.length)) {
12552                endContainer = findParentContainer(dom, formatList, endContainer, endOffset, false, expandToBlock);
12553            }
12554        }
12555        // Expand start/end container to matching selector
12556        if (shouldExpandToSelector(format)) {
12557            // Find new startContainer/endContainer if there is better one
12558            startContainer = findSelectorEndPoint(dom, formatList, rng, startContainer, 'previousSibling');
12559            endContainer = findSelectorEndPoint(dom, formatList, rng, endContainer, 'nextSibling');
12560        }
12561        // Expand start/end container to matching block element or text node
12562        if (isBlockFormat(format) || isSelectorFormat(format)) {
12563            // Find new startContainer/endContainer if there is better one
12564            startContainer = findBlockEndPoint(dom, formatList, startContainer, 'previousSibling');
12565            endContainer = findBlockEndPoint(dom, formatList, endContainer, 'nextSibling');
12566            // Non block element then try to expand up the leaf
12567            if (isBlockFormat(format)) {
12568                if (!dom.isBlock(startContainer)) {
12569                    startContainer = findParentContainer(dom, formatList, startContainer, startOffset, true, expandToBlock);
12570                    if (isText$b(startContainer)) {
12571                        startOffset = 0;
12572                    }
12573                }
12574                if (!dom.isBlock(endContainer)) {
12575                    endContainer = findParentContainer(dom, formatList, endContainer, endOffset, false, expandToBlock);
12576                    if (isText$b(endContainer)) {
12577                        endOffset = endContainer.data.length;
12578                    }
12579                }
12580            }
12581        }
12582        // Setup index for startContainer
12583        if (isElement$7(startContainer) && startContainer.parentNode) {
12584            startOffset = dom.nodeIndex(startContainer);
12585            startContainer = startContainer.parentNode;
12586        }
12587        // Setup index for endContainer
12588        if (isElement$7(endContainer) && endContainer.parentNode) {
12589            endOffset = dom.nodeIndex(endContainer) + 1;
12590            endContainer = endContainer.parentNode;
12591        }
12592        // Return new range like object
12593        return {
12594            startContainer,
12595            startOffset,
12596            endContainer,
12597            endOffset
12598        };
12599    };
12600
12601    const walk$3 = (dom, rng, callback) => {
12602        const startOffset = rng.startOffset;
12603        const startContainer = getNode$1(rng.startContainer, startOffset);
12604        const endOffset = rng.endOffset;
12605        const endContainer = getNode$1(rng.endContainer, endOffset - 1);
12606        /**
12607         * Excludes start/end text node if they are out side the range
12608         *
12609         * @private
12610         * @param {Array} nodes Nodes to exclude items from.
12611         * @return {Array} Array with nodes excluding the start/end container if needed.
12612         */
12613        const exclude = (nodes) => {
12614            // First node is excluded
12615            const firstNode = nodes[0];
12616            if (isText$b(firstNode) && firstNode === startContainer && startOffset >= firstNode.data.length) {
12617                nodes.splice(0, 1);
12618            }
12619            // Last node is excluded
12620            const lastNode = nodes[nodes.length - 1];
12621            if (endOffset === 0 && nodes.length > 0 && lastNode === endContainer && isText$b(lastNode)) {
12622                nodes.splice(nodes.length - 1, 1);
12623            }
12624            return nodes;
12625        };
12626        const collectSiblings = (node, name, endNode) => {
12627            const siblings = [];
12628            for (; node && node !== endNode; node = node[name]) {
12629                siblings.push(node);
12630            }
12631            return siblings;
12632        };
12633        const findEndPoint = (node, root) => dom.getParent(node, (node) => node.parentNode === root, root);
12634        const walkBoundary = (startNode, endNode, next) => {
12635            const siblingName = next ? 'nextSibling' : 'previousSibling';
12636            for (let node = startNode, parent = node.parentNode; node && node !== endNode; node = parent) {
12637                parent = node.parentNode;
12638                const siblings = collectSiblings(node === startNode ? node : node[siblingName], siblingName);
12639                if (siblings.length) {
12640                    if (!next) {
12641                        siblings.reverse();
12642                    }
12643                    callback(exclude(siblings));
12644                }
12645            }
12646        };
12647        // Same container
12648        if (startContainer === endContainer) {
12649            return callback(exclude([startContainer]));
12650        }
12651        // Find common ancestor and end points
12652        const ancestor = dom.findCommonAncestor(startContainer, endContainer) ?? dom.getRoot();
12653        // Process left side
12654        if (dom.isChildOf(startContainer, endContainer)) {
12655            return walkBoundary(startContainer, ancestor, true);
12656        }
12657        // Process right side
12658        if (dom.isChildOf(endContainer, startContainer)) {
12659            return walkBoundary(endContainer, ancestor);
12660        }
12661        // Find start/end point
12662        const startPoint = findEndPoint(startContainer, ancestor) || startContainer;
12663        const endPoint = findEndPoint(endContainer, ancestor) || endContainer;
12664        // Walk left leaf
12665        walkBoundary(startContainer, startPoint, true);
12666        // Walk the middle from start to end point
12667        const siblings = collectSiblings(startPoint === startContainer ? startPoint : startPoint.nextSibling, 'nextSibling', endPoint === endContainer ? endPoint.nextSibling : endPoint);
12668        if (siblings.length) {
12669            callback(exclude(siblings));
12670        }
12671        // Walk right leaf
12672        walkBoundary(endContainer, endPoint);
12673    };
12674
12675    const validBlocks = [
12676        // Codesample plugin
12677        'pre[class*=language-][contenteditable="false"]',
12678        // Image plugin - captioned image
12679        'figure.image',
12680        // Mediaembed plugin
12681        'div[data-ephox-embed-iri]',
12682        // Pageembed plugin
12683        'div.tiny-pageembed',
12684        // Tableofcontents plugin
12685        'div.mce-toc',
12686        'div[data-mce-toc]',
12687        // Footnootes plugin
12688        'div.mce-footnotes'
12689    ];
12690    const isZeroWidth = (elem) => isText$c(elem) && get$
vendor: 13,672 bytes, lines 12690-13000
126904(elem) === ZWSP$1;
12691    const context = (editor, elem, wrapName, nodeName) => parentElement(elem).fold(() => "skipping" /* ChildContext.Skipping */, (parent) => {
12692        // We used to skip these, but given that they might be representing empty paragraphs, it probably
12693        // makes sense to treat them just like text nodes
12694        if (nodeName === 'br' || isZeroWidth(elem)) {
12695            return "valid" /* ChildContext.Valid */;
12696        }
12697        else if (isAnnotation(elem)) {
12698            return "existing" /* ChildContext.Existing */;
12699        }
12700        else if (isCaretNode(elem.dom)) {
12701            return "caret" /* ChildContext.Caret */;
12702        }
12703        else if (exists(validBlocks, (selector) => is$2(elem, selector))) {
12704            return "valid-block" /* ChildContext.ValidBlock */;
12705        }
12706        else if (!isValid(editor, wrapName, nodeName) || !isValid(editor, name(parent), wrapName)) {
12707            return "invalid-child" /* ChildContext.InvalidChild */;
12708        }
12709        else {
12710            return "valid" /* ChildContext.Valid */;
12711        }
12712    });
12713
12714    const applyWordGrab = (editor, rng) => {
12715        const r = expandRng(editor.dom, rng, [{ inline: 'span' }]);
12716        rng.setStart(r.startContainer, r.startOffset);
12717        rng.setEnd(r.endContainer, r.endOffset);
12718        editor.selection.setRng(rng);
12719    };
12720    const applyAnnotation = (elem, masterUId, data, annotationName, decorate, directAnnotation) => {
12721        const { uid = masterUId, ...otherData } = data;
12722        add$2(elem, annotation());
12723        set$4(elem, `${dataAnnotationId()}`, uid);
12724        set$4(elem, `${dataAnnotation()}`, annotationName);
12725        const { attributes = {}, classes = [] } = decorate(uid, otherData);
12726        setAll$1(elem, attributes);
12727        add$1(elem, classes);
12728        if (directAnnotation) {
12729            if (classes.length > 0) {
12730                set$4(elem, `${dataAnnotationClasses()}`, classes.join(','));
12731            }
12732            const attributeNames = keys(attributes);
12733            if (attributeNames.length > 0) {
12734                set$4(elem, `${dataAnnotationAttributes()}`, attributeNames.join(','));
12735            }
12736        }
12737    };
12738    const removeDirectAnnotation = (elem) => {
12739        remove$4(elem, annotation());
12740        remove$9(elem, `${dataAnnotationId()}`);
12741        remove$9(elem, `${dataAnnotation()}`);
12742        remove$9(elem, `${dataAnnotationActive()}`);
12743        const customAttrNames = getOpt(elem, `${dataAnnotationAttributes()}`).map((names) => names.split(',')).getOr([]);
12744        const customClasses = getOpt(elem, `${dataAnnotationClasses()}`).map((names) => names.split(',')).getOr([]);
12745        each$e(customAttrNames, (name) => remove$9(elem, name));
12746        remove$3(elem, customClasses);
12747        remove$9(elem, `${dataAnnotationClasses()}`);
12748        remove$9(elem, `${dataAnnotationAttributes()}`);
12749    };
12750    const makeAnnotation = (eDoc, uid, data, annotationName, decorate) => {
12751        const master = SugarElement.fromTag('span', eDoc);
12752        applyAnnotation(master, uid, data, annotationName, decorate, false);
12753        return master;
12754    };
12755    const annotate = (editor, rng, uid, annotationName, decorate, data) => {
12756        // Setup all the wrappers that are going to be used.
12757        const newWrappers = [];
12758        // Setup the spans for the comments
12759        const master = makeAnnotation(editor.getDoc(), uid, data, annotationName, decorate);
12760        // Set the current wrapping element
12761        const wrapper = value$1();
12762        // Clear the current wrapping element, so that subsequent calls to
12763        // getOrOpenWrapper spawns a new one.
12764        const finishWrapper = () => {
12765            wrapper.clear();
12766        };
12767        // Get the existing wrapper, or spawn a new one.
12768        const getOrOpenWrapper = () => wrapper.get().getOrThunk(() => {
12769            const nu = shallow(master);
12770            newWrappers.push(nu);
12771            wrapper.set(nu);
12772            return nu;
12773        });
12774        const processElements = (elems) => {
12775            each$e(elems, processElement);
12776        };
12777        const processElement = (elem) => {
12778            const ctx = context(editor, elem, 'span', name(elem));
12779            switch (ctx) {
12780                case "invalid-child" /* ChildContext.InvalidChild */: {
12781                    finishWrapper();
12782                    const children = children$1(elem);
12783                    processElements(children);
12784                    finishWrapper();
12785                    break;
12786                }
12787                case "valid-block" /* ChildContext.ValidBlock */: {
12788                    finishWrapper();
12789                    applyAnnotation(elem, uid, data, annotationName, decorate, true);
12790                    break;
12791                }
12792                case "valid" /* ChildContext.Valid */: {
12793                    const w = getOrOpenWrapper();
12794                    wrap$2(elem, w);
12795                    break;
12796                }
12797            }
12798        };
12799        const processNodes = (nodes) => {
12800            const elems = map$3(nodes, SugarElement.fromDom);
12801            processElements(elems);
12802        };
12803        walk$3(editor.dom, rng, (nodes) => {
12804            finishWrapper();
12805            processNodes(nodes);
12806        });
12807        return newWrappers;
12808    };
12809    const annotateWithBookmark = (editor, name, settings, data) => {
12810        editor.undoManager.transact(() => {
12811            const selection = editor.selection;
12812            const initialRng = selection.getRng();
12813            const hasFakeSelection = getCellsFromEditor(editor).length > 0;
12814            const masterUid = generate('mce-annotation');
12815            if (initialRng.collapsed && !hasFakeSelection) {
12816                applyWordGrab(editor, initialRng);
12817            }
12818            // Even after applying word grab, we could not find a selection. Therefore,
12819            // just make a wrapper and insert it at the current cursor
12820            if (selection.getRng().collapsed && !hasFakeSelection) {
12821                const wrapper = makeAnnotation(editor.getDoc(), masterUid, data, name, settings.decorate);
12822                // Put something visible in the marker
12823                set$3(wrapper, nbsp);
12824                selection.getRng().insertNode(wrapper.dom);
12825                selection.select(wrapper.dom);
12826            }
12827            else {
12828                // The bookmark is responsible for splitting the nodes beforehand at the selection points
12829                // The "false" here means a zero width cursor is NOT put in the bookmark. It seems to be required
12830                // to stop an empty paragraph splitting into two paragraphs. Probably a better way exists.
12831                preserve(selection, false, () => {
12832                    runOnRanges(editor, (selectionRng) => {
12833                        annotate(editor, selectionRng, masterUid, name, settings.decorate, data);
12834                    });
12835                });
12836            }
12837        });
12838    };
12839
12840    const Annotator = (editor) => {
12841        const registry = create$a();
12842        setup$D(editor, registry);
12843        const changes = setup$E(editor, registry);
12844        const isSpan = isTag('span');
12845        const removeAnnotations = (elements) => {
12846            each$e(elements, (element) => {
12847                if (isSpan(element)) {
12848                    unwrap(element);
12849                }
12850                else {
12851                    removeDirectAnnotation(element);
12852                }
12853            });
12854        };
12855        return {
12856            /**
12857             * Registers a specific annotator by name
12858             *
12859             * @method register
12860             * @param {String} name the name of the annotation
12861             * @param {Object} settings settings for the annotation (e.g. decorate)
12862             */
12863            register: (name, settings) => {
12864                registry.register(name, settings);
12865            },
12866            /**
12867             * Applies the annotation at the current selection using data
12868             *
12869             * @method annotate
12870             * @param {String} name the name of the annotation to apply
12871             * @param {Object} data information to pass through to this particular
12872             * annotation
12873             */
12874            annotate: (name, data) => {
12875                registry.lookup(name).each((settings) => {
12876                    annotateWithBookmark(editor, name, settings, data);
12877                });
12878            },
12879            /**
12880             * Executes the specified callback when the current selection matches the annotation or not.
12881             *
12882             * @method annotationChanged
12883             * @param {String} name Name of annotation to listen for
12884             * @param {Function} callback Callback with (state, name, and data) fired when the annotation
12885             * at the cursor changes. If state if false, data will not be provided.
12886             */
12887            annotationChanged: (name, callback) => {
12888                changes.addListener(name, callback);
12889            },
12890            /**
12891             * Removes any annotations from the current selection that match
12892             * the name
12893             *
12894             * @method remove
12895             * @param {String} name the name of the annotation to remove
12896             */
12897            remove: (name) => {
12898                identify(editor, Optional.some(name)).each(({ elements }) => {
12899                    /**
12900                     * TINY-9399: It is important to keep the bookmarking in the callback
12901                     * because it adjusts selection in a way that `identify` function
12902                     * cannot retain the selected word.
12903                     */
12904                    const bookmark = editor.selection.getBookmark();
12905                    removeAnnotations(elements);
12906                    editor.selection.moveToBookmark(bookmark);
12907                });
12908            },
12909            /**
12910             * Removes all annotations that match the specified name from the entire document.
12911             *
12912             * @method removeAll
12913             * @param {String} name the name of the annotation to remove
12914             */
12915            removeAll: (name) => {
12916                const bookmark = editor.selection.getBookmark();
12917                each$d(findAll(editor, name), (elements, _) => {
12918                    removeAnnotations(elements);
12919                });
12920                editor.selection.moveToBookmark(bookmark);
12921            },
12922            /**
12923             * Retrieve all the annotations for a given name
12924             *
12925             * @method getAll
12926             * @param {String} name the name of the annotations to retrieve
12927             * @return {Object} an index of annotations from uid => DOM nodes
12928             */
12929            getAll: (name) => {
12930                const directory = findAll(editor, name);
12931                return map$2(directory, (elems) => map$3(elems, (elem) => elem.dom));
12932            }
12933        };
12934    };
12935
12936    /**
12937     * Constructs a new BookmarkManager instance for a specific selection instance.
12938     *
12939     * @constructor
12940     * @method BookmarkManager
12941     * @param {tinymce.dom.Selection} selection Selection instance to handle bookmarks for.
12942     */
12943    const BookmarkManager = (selection) => {
12944        return {
12945            /**
12946             * Returns a bookmark location for the current selection. This bookmark object
12947             * can then be used to restore the selection after some content modification to the document.
12948             *
12949             * @method getBookmark
12950             * @param {Number} type Optional state if the bookmark should be simple or not. Default is complex.
12951             * @param {Boolean} normalized Optional state that enables you to get a position that it would be after normalization.
12952             * @return {Object} Bookmark object, use moveToBookmark with this object to restore the selection.
12953             * @example
12954             * // Stores a bookmark of the current selection
12955             * const bm = tinymce.activeEditor.selection.getBookmark();
12956             *
12957             * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content');
12958             *
12959             * // Restore the selection bookmark
12960             * tinymce.activeEditor.selection.moveToBookmark(bm);
12961             */
12962            getBookmark: curry(getBookmark$1, selection),
12963            /**
12964             * Restores the selection to the specified bookmark.
12965             *
12966             * @method moveToBookmark
12967             * @param {Object} bookmark Bookmark to restore selection from.
12968             * @example
12969             * // Stores a bookmark of the current selection
12970             * const bm = tinymce.activeEditor.selection.getBookmark();
12971             *
12972             * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content');
12973             *
12974             * // Restore the selection bookmark
12975             * tinymce.activeEditor.selection.moveToBookmark(bm);
12976             */
12977            moveToBookmark: curry(moveToBookmark, selection)
12978        };
12979    };
12980    /**
12981     * Returns true/false if the specified node is a bookmark node or not.
12982     *
12983     * @static
12984     * @method isBookmarkNode
12985     * @param {DOMNode} node DOM Node to check if it's a bookmark node or not.
12986     * @return {Boolean} true/false if the node is a bookmark node or not.
12987     */
12988    BookmarkManager.isBookmarkNode = isBookmarkNode$1;
12989
12990    const isXYWithinRange = (clientX, clientY, range) => {
12991        if (range.collapsed) {
12992            return false;
12993        }
12994        else {
12995            return exists(range.getClientRects(), (rect) => containsXY(rect, clientX, clientY));
12996        }
12997    };
12998
12999    const clamp$1 = (offset, element) => {
13000        const max = isText$c(element) ? get$4(element).length : children$1
vendor: 13,608 bytes, lines 13000-13309
13000(element).length + 1;
13001        if (offset > max) {
13002            return max;
13003        }
13004        else if (offset < 0) {
13005            return 0;
13006        }
13007        return offset;
13008    };
13009    const normalizeRng = (rng) => SimSelection.range(rng.start, clamp$1(rng.soffset, rng.start), rng.finish, clamp$1(rng.foffset, rng.finish));
13010    const isOrContains = (root, elm) => !isRestrictedNode(elm.dom) && (contains(root, elm) || eq(root, elm));
13011    const isRngInRoot = (root) => (rng) => isOrContains(root, rng.start) && isOrContains(root, rng.finish);
13012    // TINY-9259: We need to store the selection on Firefox since if the editor is hidden the selection.getRng() api will not work as expected.
13013    const shouldStore = (editor) => editor.inline || Env.browser.isFirefox();
13014    const nativeRangeToSelectionRange = (r) => SimSelection.range(SugarElement.fromDom(r.startContainer), r.startOffset, SugarElement.fromDom(r.endContainer), r.endOffset);
13015    const readRange = (win) => {
13016        const selection = win.getSelection();
13017        const rng = !selection || selection.rangeCount === 0 ? Optional.none() : Optional.from(selection.getRangeAt(0));
13018        return rng.map(nativeRangeToSelectionRange);
13019    };
13020    const getBookmark = (root) => {
13021        const win = defaultView(root);
13022        return readRange(win.dom)
13023            .filter(isRngInRoot(root));
13024    };
13025    const validate = (root, bookmark) => Optional.from(bookmark)
13026        .filter(isRngInRoot(root))
13027        .map(normalizeRng);
13028    const bookmarkToNativeRng = (bookmark) => {
13029        const rng = document.createRange();
13030        try {
13031            // Might throw IndexSizeError
13032            rng.setStart(bookmark.start.dom, bookmark.soffset);
13033            rng.setEnd(bookmark.finish.dom, bookmark.foffset);
13034            return Optional.some(rng);
13035        }
13036        catch {
13037            return Optional.none();
13038        }
13039    };
13040    const store = (editor) => {
13041        const newBookmark = shouldStore(editor) ? getBookmark(SugarElement.fromDom(editor.getBody())) : Optional.none();
13042        editor.bookmark = newBookmark.isSome() ? newBookmark : editor.bookmark;
13043    };
13044    const getRng = (editor) => {
13045        const bookmark = editor.bookmark ? editor.bookmark : Optional.none();
13046        return bookmark
13047            .bind((x) => validate(SugarElement.fromDom(editor.getBody()), x))
13048            .bind(bookmarkToNativeRng);
13049    };
13050    const restore = (editor) => {
13051        getRng(editor).each((rng) => editor.selection.setRng(rng));
13052    };
13053
13054    /**
13055     * This class manages the focus/blur state of the editor. This class is needed since some
13056     * browsers fire false focus/blur states when the selection is moved to a UI dialog or similar.
13057     *
13058     * This class will fire two events focus and blur on the editor instances that got affected.
13059     * It will also handle the restore of selection when the focus is lost and returned.
13060     *
13061     * @class tinymce.FocusManager
13062     * @private
13063     */
13064    /**
13065     * Returns true if the specified element is part of the UI for example an button or text input.
13066     *
13067     * @static
13068     * @method isEditorUIElement
13069     * @param  {Element} elm Element to check if it's part of the UI or not.
13070     * @return {Boolean} True/false state if the element is part of the UI or not.
13071     */
13072    const isEditorUIElement$1 = (elm) => {
13073        // Needs to be converted to string since svg can have focus: #6776
13074        const className = elm.className.toString();
13075        return className.indexOf('tox-') !== -1 || className.indexOf('mce-') !== -1;
13076    };
13077    const FocusManager = {
13078        isEditorUIElement: isEditorUIElement$1
13079    };
13080
13081    /**
13082     * Utility class for working with delayed actions like setTimeout.
13083     *
13084     * @class tinymce.util.Delay
13085     */
13086    const wrappedSetTimeout = (callback, time) => {
13087        if (!isNumber(time)) {
13088            time = 0;
13089        }
13090        return window.setTimeout(callback, time);
13091    };
13092    const wrappedSetInterval = (callback, time) => {
13093        if (!isNumber(time)) {
13094            time = 0;
13095        }
13096        return window.setInterval(callback, time);
13097    };
13098    const Delay = {
13099        /**
13100         * Sets a timeout that's similar to the native browser <a href="https://developer.mozilla.org/en-US/docs/Web/API/setTimeout">setTimeout</a>
13101         * API, except that it checks if the editor instance is still alive when the callback gets executed.
13102         *
13103         * @method setEditorTimeout
13104         * @param {tinymce.Editor} editor Editor instance to check the removed state on.
13105         * @param {Function} callback Callback to execute when timer runs out.
13106         * @param {Number} time Optional time to wait before the callback is executed, defaults to 0.
13107         * @return {Number} Timeout id number.
13108         */
13109        setEditorTimeout: (editor, callback, time) => {
13110            return wrappedSetTimeout(() => {
13111                if (!editor.removed) {
13112                    callback();
13113                }
13114            }, time);
13115        },
13116        /**
13117         * Sets an interval timer that's similar to native browser <a href="https://developer.mozilla.org/en-US/docs/Web/API/setInterval">setInterval</a>
13118         * API, except that it checks if the editor instance is still alive when the callback gets executed.
13119         *
13120         * @method setEditorInterval
13121         * @param {Function} callback Callback to execute when interval time runs out.
13122         * @param {Number} time Optional time to wait before the callback is executed, defaults to 0.
13123         * @return {Number} Timeout id number.
13124         */
13125        setEditorInterval: (editor, callback, time) => {
13126            const timer = wrappedSetInterval(() => {
13127                if (!editor.removed) {
13128                    callback();
13129                }
13130                else {
13131                    window.clearInterval(timer);
13132                }
13133            }, time);
13134            return timer;
13135        }
13136    };
13137
13138    const isManualNodeChange = (e) => {
13139        return e.type === 'nodechange' && e.selectionChange;
13140    };
13141    const registerPageMouseUp = (editor, throttledStore) => {
13142        const mouseUpPage = () => {
13143            throttledStore.throttle();
13144        };
13145        DOMUtils.DOM.bind(document, 'mouseup', mouseUpPage);
13146        editor.on('remove', () => {
13147            DOMUtils.DOM.unbind(document, 'mouseup', mouseUpPage);
13148        });
13149    };
13150    const registerMouseUp = (editor, throttledStore) => {
13151        editor.on('mouseup touchend', (_e) => {
13152            throttledStore.throttle();
13153        });
13154    };
13155    const registerEditorEvents = (editor, throttledStore) => {
13156        registerMouseUp(editor, throttledStore);
13157        editor.on('keyup NodeChange AfterSetSelectionRange', (e) => {
13158            if (!isManualNodeChange(e)) {
13159                store(editor);
13160            }
13161        });
13162    };
13163    const register$6 = (editor) => {
13164        const throttledStore = first$1(() => {
13165            store(editor);
13166        }, 0);
13167        editor.on('init', () => {
13168            if (editor.inline) {
13169                registerPageMouseUp(editor, throttledStore);
13170            }
13171            registerEditorEvents(editor, throttledStore);
13172        });
13173        editor.on('remove', () => {
13174            throttledStore.cancel();
13175        });
13176    };
13177
13178    let documentFocusInHandler;
13179    const DOM$c = DOMUtils.DOM;
13180    const isEditorUIElement = (elm) => {
13181        // Since this can be overridden by third party we need to use the API reference here
13182        return isElement$7(elm) && FocusManager.isEditorUIElement(elm);
13183    };
13184    const isEditorContentAreaElement = (elm) => {
13185        const classList = elm.classList;
13186        if (classList !== undefined) {
13187            // tox-edit-area__iframe === iframe container element
13188            // mce-content-body === inline body element
13189            return classList.contains('tox-edit-area') || classList.contains('tox-edit-area__iframe') || classList.contains('mce-content-body');
13190        }
13191        else {
13192            return false;
13193        }
13194    };
13195    const isUIElement = (editor, elm) => {
13196        const customSelector = getCustomUiSelector(editor);
13197        const parent = DOM$c.getParent(elm, (elm) => {
13198            return (isEditorUIElement(elm) ||
13199                (customSelector ? editor.dom.is(elm, customSelector) : false));
13200        });
13201        return parent !== null;
13202    };
13203    const getActiveElement = (editor) => {
13204        try {
13205            const root = getRootNode(SugarElement.fromDom(editor.getElement()));
13206            return active(root).fold(() => document.body, (x) => x.dom);
13207        }
13208        catch {
13209            // IE sometimes fails to get the activeElement when resizing table
13210            // TODO: Investigate this
13211            return document.body;
13212        }
13213    };
13214    const registerEvents$1 = (editorManager, e) => {
13215        const editor = e.editor;
13216        register$6(editor);
13217        const toggleContentAreaOnFocus = (editor, fn) => {
13218            // Inline editors have a different approach to highlight the content area on focus
13219            if (shouldHighlightOnFocus(editor) && editor.inline !== true) {
13220                const contentArea = SugarElement.fromDom(editor.getContainer());
13221                fn(contentArea, 'tox-edit-focus');
13222            }
13223        };
13224        editor.on('focusin', () => {
13225            const focusedEditor = editorManager.focusedEditor;
13226            if (isEditorContentAreaElement(getActiveElement(editor))) {
13227                toggleContentAreaOnFocus(editor, add$2);
13228            }
13229            if (focusedEditor !== editor) {
13230                if (focusedEditor) {
13231                    focusedEditor.dispatch('blur', { focusedEditor: editor });
13232                }
13233                editorManager.setActive(editor);
13234                editorManager.focusedEditor = editor;
13235                editor.dispatch('focus', { blurredEditor: focusedEditor });
13236                editor.focus(true);
13237            }
13238        });
13239        editor.on('focusout', () => {
13240            Delay.setEditorTimeout(editor, () => {
13241                const focusedEditor = editorManager.focusedEditor;
13242                // Remove focus highlight when the content area is no longer the active editor element, or if the highlighted editor is not the current focused editor
13243                if (!isEditorContentAreaElement(getActiveElement(editor)) || focusedEditor !== editor) {
13244                    toggleContentAreaOnFocus(editor, remove$4);
13245                }
13246                // Still the same editor the blur was outside any editor UI
13247                if (!isUIElement(editor, getActiveElement(editor)) && focusedEditor === editor) {
13248                    editor.dispatch('blur', { focusedEditor: null });
13249                    editorManager.focusedEditor = null;
13250                }
13251            });
13252        });
13253        // Check if focus is moved to an element outside the active editor by checking if the target node
13254        // isn't within the body of the activeEditor nor a UI element such as a dialog child control
13255        if (!documentFocusInHandler) {
13256            documentFocusInHandler = (e) => {
13257                const activeEditor = editorManager.activeEditor;
13258                if (activeEditor) {
13259                    getOriginalEventTarget(e).each((target) => {
13260                        const elem = target;
13261                        if (elem.ownerDocument === document) {
13262                            // Fire a blur event if the element isn't a UI element
13263                            if (elem !== document.body && !isUIElement(activeEditor, elem) && editorManager.focusedEditor === activeEditor) {
13264                                activeEditor.dispatch('blur', { focusedEditor: null });
13265                                editorManager.focusedEditor = null;
13266                            }
13267                        }
13268                    });
13269                }
13270            };
13271            DOM$c.bind(document, 'focusin', documentFocusInHandler);
13272        }
13273    };
13274    const unregisterDocumentEvents = (editorManager, e) => {
13275        if (editorManager.focusedEditor === e.editor) {
13276            editorManager.focusedEditor = null;
13277        }
13278        if (!editorManager.activeEditor && documentFocusInHandler) {
13279            DOM$c.unbind(document, 'focusin', documentFocusInHandler);
13280            documentFocusInHandler = null;
13281        }
13282    };
13283    const setup$C = (editorManager) => {
13284        editorManager.on('AddEditor', curry(registerEvents$1, editorManager));
13285        editorManager.on('RemoveEditor', curry(unregisterDocumentEvents, editorManager));
13286    };
13287
13288    const getContentEditableHost = (editor, node) => editor.dom.getParent(node, (node) => editor.dom.getContentEditable(node) === 'true');
13289    const hasContentEditableFalseParent$1 = (editor, node) => editor.dom.getParent(node, (node) => editor.dom.getContentEditable(node) === 'false') !== null;
13290    const getCollapsedNode = (rng) => rng.collapsed ? Optional.from(getNode$1(rng.startContainer, rng.startOffset)).map(SugarElement.fromDom) : Optional.none();
13291    const getFocusInElement = (root, rng) => getCollapsedNode(rng).bind((node) => {
13292        if (isTableSection(node)) {
13293            return Optional.some(node);
13294        }
13295        else if (!contains(root, node)) {
13296            return Optional.some(root);
13297        }
13298        else {
13299            return Optional.none();
13300        }
13301    });
13302    const normalizeSelection = (editor, rng) => {
13303        getFocusInElement(SugarElement.fromDom(editor.getBody()), rng).bind((elm) => {
13304            return firstPositionIn(elm.dom);
13305        }).fold(() => {
13306            editor.selection.normalize();
13307        }, (caretPos) => editor.selection.setRng(caretPos.toRange()));
13308    };
13309    const focusBody = (body
vendor: 2,953 bytes, lines 13309-13374
13309) => {
13310        if (body.setActive) {
13311            // IE 11 sometimes throws "Invalid function" then fallback to focus
13312            // setActive is better since it doesn't scroll to the element being focused
13313            try {
13314                body.setActive();
13315            }
13316            catch {
13317                body.focus();
13318            }
13319        }
13320        else {
13321            body.focus();
13322        }
13323    };
13324    const hasElementFocus = (elm) => hasFocus$1(elm) || search(elm).isSome();
13325    const hasIframeFocus = (editor) => isNonNullable(editor.iframeElement) && hasFocus$1(SugarElement.fromDom(editor.iframeElement));
13326    const hasInlineFocus = (editor) => {
13327        const rawBody = editor.getBody();
13328        return rawBody && hasElementFocus(SugarElement.fromDom(rawBody));
13329    };
13330    const hasUiFocus = (editor) => {
13331        const dos = getRootNode(SugarElement.fromDom(editor.getElement()));
13332        // Editor container is the obvious one (Menubar, Toolbar, Status bar, Sidebar) and dialogs and menus are in an auxiliary element (silver theme specific)
13333        // This can't use Focus.search() because only the theme has this element reference
13334        return active(dos)
13335            .filter((elem) => !isEditorContentAreaElement(elem.dom) && isUIElement(editor, elem.dom))
13336            .isSome();
13337    };
13338    const hasFocus = (editor) => editor.inline ? hasInlineFocus(editor) : hasIframeFocus(editor);
13339    const hasEditorOrUiFocus = (editor) => hasFocus(editor) || hasUiFocus(editor);
13340    const focusEditor = (editor) => {
13341        const selection = editor.selection;
13342        const body = editor.getBody();
13343        let rng = selection.getRng();
13344        editor.quirks.refreshContentEditable();
13345        const restoreBookmark = (editor) => {
13346            getRng(editor).each((bookmarkRng) => {
13347                editor.selection.setRng(bookmarkRng);
13348                rng = bookmarkRng;
13349            });
13350        };
13351        if (!hasFocus(editor) && editor.hasEditableRoot()) {
13352            restoreBookmark(editor);
13353        }
13354        // Move focus to contentEditable=true child if needed
13355        const contentEditableHost = getContentEditableHost(editor, selection.getNode());
13356        if (contentEditableHost && editor.dom.isChildOf(contentEditableHost, body)) {
13357            if (!hasContentEditableFalseParent$1(editor, contentEditableHost)) {
13358                focusBody(body);
13359            }
13360            focusBody(contentEditableHost);
13361            if (!editor.hasEditableRoot()) {
13362                restoreBookmark(editor);
13363            }
13364            normalizeSelection(editor, rng);
13365            activateEditor(editor);
13366            return;
13367        }
13368        // Focus the window iframe
13369        if (!editor.inline) {
13370            // WebKit needs this call to fire focusin event properly see #5948
13371            // But Opera pre Blink engine will produce an empty selection so skip Opera
13372            if (!Env.browser.isOpera()) {
13373                focusBody(body);
13374            }
13375            editor.getWin().focus();
13376        }
13377        // Focus the body as well since it's contentEditable
13378        if (Env.browser.isFirefox() || editor.inline) {
13379            focusBody(body);
13380            normalizeSelection(editor, rng);
13381        }
13382        activateEditor(editor);
13383    };
13384    const activateEditor = (editor) => editor.editorManager.setActive(editor);
13385    const focus = (editor, skipFocus) => {
13386        if (editor.removed) {
13387            return;
13388        }
13389        if (skipFocus) {
13390            activateEditor(editor);
13391        }
13392        else {
13393            focusEditor(editor);
13394        }
vendor: 38,924 bytes, lines 13394-14250
13394
13395    };
13396
13397    /**
13398     * This file exposes a set of the common KeyCodes for use. Please grow it as needed.
13399     */
13400    const VK = {
13401        BACKSPACE: 8,
13402        DELETE: 46,
13403        DOWN: 40,
13404        ENTER: 13,
13405        ESC: 27,
13406        LEFT: 37,
13407        RIGHT: 39,
13408        SPACEBAR: 32,
13409        TAB: 9,
13410        UP: 38,
13411        PAGE_UP: 33,
13412        PAGE_DOWN: 34,
13413        END: 35,
13414        HOME: 36,
13415        modifierPressed: (e) => {
13416            return e.shiftKey || e.ctrlKey || e.altKey || VK.metaKeyPressed(e);
13417        },
13418        metaKeyPressed: (e) => {
13419            // Check if ctrl or meta key is pressed. Edge case for AltGr on Windows where it produces ctrlKey+altKey states
13420            return Env.os.isMacOS() || Env.os.isiOS() ? e.metaKey : e.ctrlKey && !e.altKey;
13421        }
13422    };
13423
13424    const elementSelectionAttr = 'data-mce-selected';
13425    const controlElmSelector = `table,img,figure.image,hr,video,span.mce-preview-object,details,${ucVideoNodeName}`;
13426    const abs = Math.abs;
13427    const round$1 = Math.round;
13428    // Details about each resize handle how to scale etc
13429    const resizeHandles = {
13430        // Name: x multiplier, y multiplier, delta size x, delta size y
13431        nw: [0, 0, -1, -1],
13432        ne: [1, 0, 1, -1],
13433        se: [1, 1, 1, 1],
13434        sw: [0, 1, -1, 1]
13435    };
13436    const isTouchEvent = (evt) => evt.type === 'longpress' || evt.type.indexOf('touch') === 0;
13437    /**
13438     * This class handles control selection of elements. Controls are elements
13439     * that can be resized and needs to be selected as a whole. It adds custom resize handles
13440     * to all browser engines that support properly disabling the built in resize logic.
13441     *
13442     * @private
13443     * @class tinymce.dom.ControlSelection
13444     */
13445    const ControlSelection = (selection, editor) => {
13446        const dom = editor.dom;
13447        const editableDoc = editor.getDoc();
13448        const rootDocument = document;
13449        const rootElement = editor.getBody();
13450        let selectedElm, selectedElmGhost, resizeHelper, selectedHandle, resizeBackdrop;
13451        let startX, startY, startW, startH, ratio, resizeStarted;
13452        let width;
13453        let height;
13454        let startScrollWidth;
13455        let startScrollHeight;
13456        const isImage = (elm) => isNonNullable(elm) && (isImg(elm) || dom.is(elm, 'figure.image'));
13457        const isMedia = (elm) => isMedia$2(elm) || dom.hasClass(elm, 'mce-preview-object');
13458        const isEventOnImageOutsideRange = (evt, range) => {
13459            if (isTouchEvent(evt)) {
13460                const touch = evt.touches[0];
13461                return isImage(evt.target) && !isXYWithinRange(touch.clientX, touch.clientY, range);
13462            }
13463            else {
13464                return isImage(evt.target) && !isXYWithinRange(evt.clientX, evt.clientY, range);
13465            }
13466        };
13467        const contextMenuSelectImage = (evt) => {
13468            const target = evt.target;
13469            if (isEventOnImageOutsideRange(evt, editor.selection.getRng()) && !evt.isDefaultPrevented()) {
13470                editor.selection.select(target);
13471            }
13472        };
13473        const getResizeTargets = (elm) => {
13474            if (dom.hasClass(elm, 'mce-preview-object') && isNonNullable(elm.firstElementChild)) {
13475                // When resizing a preview object we need to resize both the original element and the wrapper span
13476                return [elm, elm.firstElementChild];
13477            }
13478            else if (dom.is(elm, 'figure.image')) {
13479                return [elm.querySelector('img')];
13480            }
13481            else {
13482                return [elm];
13483            }
13484        };
13485        const isResizable = (elm) => {
13486            const selector = getObjectResizing(editor);
13487            if (!selector || editor.mode.isReadOnly()) {
13488                return false;
13489            }
13490            if (elm.getAttribute('data-mce-resize') === 'false') {
13491                return false;
13492            }
13493            if (elm === editor.getBody()) {
13494                return false;
13495            }
13496            if (dom.hasClass(elm, 'mce-preview-object') && isNonNullable(elm.firstElementChild)) {
13497                return is$2(SugarElement.fromDom(elm.firstElementChild), selector);
13498            }
13499            else {
13500                return is$2(SugarElement.fromDom(elm), selector);
13501            }
13502        };
13503        const createGhostElement = (dom, elm) => {
13504            if (isMedia(elm)) {
13505                return dom.create('img', { src: Env.transparentSrc });
13506            }
13507            else if (isTable$2(elm)) {
13508                const isNorth = startsWith(selectedHandle.name, 'n');
13509                const rowSelect = isNorth ? head : last$2;
13510                const tableElm = elm.cloneNode(true);
13511                // Get row, remove all height styles
13512                rowSelect(dom.select('tr', tableElm)).each((tr) => {
13513                    const cells = dom.select('td,th', tr);
13514                    dom.setStyle(tr, 'height', null);
13515                    each$e(cells, (cell) => dom.setStyle(cell, 'height', null));
13516                });
13517                return tableElm;
13518            }
13519            else {
13520                return elm.cloneNode(true);
13521            }
13522        };
13523        const setUcVideoSizeProp = (element, name, value) => {
13524            // this is needed because otherwise the ghost for `uc-video` is not correctly rendered
13525            element[name] = value;
13526            const minimumWidth = 400;
13527            if (element.width > minimumWidth && !(name === 'width' && value < minimumWidth)) {
13528                element[name] = value;
13529                dom.setStyle(element, name, value);
13530            }
13531            else {
13532                const valueConsideringMinWidth = name === 'height' ? minimumWidth * (ratio ?? 1) : minimumWidth;
13533                element[name] = valueConsideringMinWidth;
13534                dom.setStyle(element, name, valueConsideringMinWidth);
13535            }
13536        };
13537        const setSizeProp = (element, name, value) => {
13538            if (isNonNullable(value)) {
13539                // Resize by using style or attribute
13540                const targets = getResizeTargets(element);
13541                each$e(targets, (target) => {
13542                    if (isUcVideo(target)) {
13543                        setUcVideoSizeProp(target, name, value);
13544                    }
13545                    else {
13546                        if (target.style[name] || !editor.schema.isValid(target.nodeName.toLowerCase(), name)) {
13547                            dom.setStyle(target, name, value);
13548                        }
13549                        else {
13550                            dom.setAttrib(target, name, '' + value);
13551                        }
13552                    }
13553                });
13554            }
13555        };
13556        const setGhostElmSize = (ghostElm, width, height) => {
13557            setSizeProp(ghostElm, 'width', width);
13558            setSizeProp(ghostElm, 'height', height);
13559        };
13560        const resizeGhostElement = (e) => {
13561            let deltaX, deltaY, proportional;
13562            let resizeHelperX, resizeHelperY;
13563            // Calc new width/height
13564            deltaX = e.screenX - startX;
13565            deltaY = e.screenY - startY;
13566            // Calc new size
13567            width = deltaX * selectedHandle[2] + startW;
13568            height = deltaY * selectedHandle[3] + startH;
13569            // Never scale down lower than 5 pixels
13570            width = width < 5 ? 5 : width;
13571            height = height < 5 ? 5 : height;
13572            if ((isImage(selectedElm) || isMedia(selectedElm) || isUcVideo(selectedElm)) && getResizeImgProportional(editor) !== false) {
13573                proportional = !VK.modifierPressed(e);
13574            }
13575            else {
13576                proportional = VK.modifierPressed(e);
13577            }
13578            // Constrain proportions
13579            if (proportional) {
13580                if (abs(deltaX) > abs(deltaY)) {
13581                    height = round$1(width * ratio);
13582                    width = round$1(height / ratio);
13583                }
13584                else {
13585                    width = round$1(height / ratio);
13586                    height = round$1(width * ratio);
13587                }
13588            }
13589            // Update ghost size
13590            setGhostElmSize(selectedElmGhost, width, height);
13591            // Update resize helper position
13592            resizeHelperX = selectedHandle.startPos.x + deltaX;
13593            resizeHelperY = selectedHandle.startPos.y + deltaY;
13594            resizeHelperX = resizeHelperX > 0 ? resizeHelperX : 0;
13595            resizeHelperY = resizeHelperY > 0 ? resizeHelperY : 0;
13596            dom.setStyles(resizeHelper, {
13597                left: resizeHelperX,
13598                top: resizeHelperY,
13599                display: 'block'
13600            });
13601            resizeHelper.innerHTML = width + ' &times; ' + height;
13602            /* TODO: TINY-11702 dom.setStyle() has no effect because the value is NaN
13603              // Update ghost X position if needed
13604              if (selectedHandle[2] < 0 && selectedElmGhost.clientWidth <= width) {
13605                dom.setStyle(selectedElmGhost, 'left', selectedElmX + (startW - width));
13606              }
13607        
13608              // Update ghost Y position if needed
13609              if (selectedHandle[3] < 0 && selectedElmGhost.clientHeight <= height) {
13610                dom.setStyle(selectedElmGhost, 'top', selectedElmY + (startH - height));
13611              }
13612            */
13613            // Calculate how must overflow we got
13614            deltaX = rootElement.scrollWidth - startScrollWidth;
13615            deltaY = rootElement.scrollHeight - startScrollHeight;
13616            // Re-position the resize helper based on the overflow
13617            if (deltaX + deltaY !== 0) {
13618                dom.setStyles(resizeHelper, {
13619                    left: resizeHelperX - deltaX,
13620                    top: resizeHelperY - deltaY
13621                });
13622            }
13623            if (!resizeStarted) {
13624                fireObjectResizeStart(editor, selectedElm, startW, startH, 'corner-' + selectedHandle.name);
13625                resizeStarted = true;
13626            }
13627        };
13628        const endGhostResize = () => {
13629            const wasResizeStarted = resizeStarted;
13630            resizeStarted = false;
13631            // Set width/height properties
13632            if (wasResizeStarted) {
13633                setSizeProp(selectedElm, 'width', width);
13634                setSizeProp(selectedElm, 'height', height);
13635            }
13636            dom.unbind(editableDoc, 'mousemove', resizeGhostElement);
13637            dom.unbind(editableDoc, 'mouseup', endGhostResize);
13638            if (rootDocument !== editableDoc) {
13639                dom.unbind(rootDocument, 'mousemove', resizeGhostElement);
13640                dom.unbind(rootDocument, 'mouseup', endGhostResize);
13641            }
13642            // Remove ghost/helper and update resize handle positions
13643            dom.remove(selectedElmGhost);
13644            dom.remove(resizeHelper);
13645            dom.remove(resizeBackdrop);
13646            showResizeRect(selectedElm);
13647            if (wasResizeStarted) {
13648                fireObjectResized(editor, selectedElm, width, height, 'corner-' + selectedHandle.name);
13649                dom.setAttrib(selectedElm, 'style', dom.getAttrib(selectedElm, 'style'));
13650            }
13651            editor.nodeChanged();
13652        };
13653        const showResizeRect = (targetElm) => {
13654            unbindResizeHandleEvents();
13655            // Get position and size of target
13656            const position = dom.getPos(targetElm, rootElement);
13657            const selectedElmX = position.x;
13658            const selectedElmY = position.y;
13659            const rect = targetElm.getBoundingClientRect(); // Fix for Gecko offsetHeight for table with caption
13660            const targetWidth = rect.width || (rect.right - rect.left);
13661            const targetHeight = rect.height || (rect.bottom - rect.top);
13662            // Reset width/height if user selects a new image/table
13663            if (selectedElm !== targetElm) {
13664                hideResizeRect();
13665                selectedElm = targetElm;
13666                width = height = 0;
13667            }
13668            // Makes it possible to disable resizing
13669            const e = editor.dispatch('ObjectSelected', { target: targetElm });
13670            if (isResizable(targetElm) && !e.isDefaultPrevented()) {
13671                each$d(resizeHandles, (handle, name) => {
13672                    const startDrag = (e) => {
13673                        // Note: We're guaranteed to have at least one target here
13674                        const target = getResizeTargets(selectedElm)[0];
13675                        startX = e.screenX;
13676                        startY = e.screenY;
13677                        startW = target.clientWidth;
13678                        startH = target.clientHeight;
13679                        ratio = startH / startW;
13680                        selectedHandle = handle;
13681                        selectedHandle.name = name;
13682                        selectedHandle.startPos = {
13683                            x: targetWidth * handle[0] + selectedElmX,
13684                            y: targetHeight * handle[1] + selectedElmY
13685                        };
13686                        startScrollWidth = rootElement.scrollWidth;
13687                        startScrollHeight = rootElement.scrollHeight;
13688                        resizeBackdrop = dom.add(rootElement, 'div', {
13689                            'class': 'mce-resize-backdrop',
13690                            'data-mce-bogus': 'all'
13691                        });
13692                        dom.setStyles(resizeBackdrop, {
13693                            position: 'fixed',
13694                            left: '0',
13695                            top: '0',
13696                            width: '100%',
13697                            height: '100%'
13698                        });
13699                        selectedElmGhost = createGhostElement(dom, selectedElm);
13700                        dom.addClass(selectedElmGhost, 'mce-clonedresizable');
13701                        dom.setAttrib(selectedElmGhost, 'data-mce-bogus', 'all');
13702                        selectedElmGhost.contentEditable = 'false'; // Hides IE move layer cursor
13703                        dom.setStyles(selectedElmGhost, {
13704                            left: selectedElmX,
13705                            top: selectedElmY,
13706                            margin: 0
13707                        });
13708                        // Set initial ghost size
13709                        setGhostElmSize(selectedElmGhost, targetWidth, targetHeight);
13710                        selectedElmGhost.removeAttribute(elementSelectionAttr);
13711                        rootElement.appendChild(selectedElmGhost);
13712                        dom.bind(editableDoc, 'mousemove', resizeGhostElement);
13713                        dom.bind(editableDoc, 'mouseup', endGhostResize);
13714                        if (rootDocument !== editableDoc) {
13715                            dom.bind(rootDocument, 'mousemove', resizeGhostElement);
13716                            dom.bind(rootDocument, 'mouseup', endGhostResize);
13717                        }
13718                        resizeHelper = dom.add(rootElement, 'div', {
13719                            'class': 'mce-resize-helper',
13720                            'data-mce-bogus': 'all'
13721                        }, startW + ' &times; ' + startH);
13722                    };
13723                    // Get existing or render resize handle
13724                    let handleElm = dom.get('mceResizeHandle' + name);
13725                    if (handleElm) {
13726                        dom.remove(handleElm);
13727                    }
13728                    handleElm = dom.add(rootElement, 'div', {
13729                        'id': 'mceResizeHandle' + name,
13730                        'data-mce-bogus': 'all',
13731                        'class': 'mce-resizehandle',
13732                        'unselectable': true,
13733                        'style': 'cursor:' + name + '-resize; margin:0; padding:0'
13734                    });
13735                    dom.bind(handleElm, 'mousedown', (e) => {
13736                        e.stopImmediatePropagation();
13737                        e.preventDefault();
13738                        startDrag(e);
13739                    });
13740                    handle.elm = handleElm;
13741                    // Position element
13742                    dom.setStyles(handleElm, {
13743                        left: (targetWidth * handle[0] + selectedElmX) - (handleElm.offsetWidth / 2),
13744                        top: (targetHeight * handle[1] + selectedElmY) - (handleElm.offsetHeight / 2)
13745                    });
13746                });
13747            }
13748            else {
13749                hideResizeRect(false);
13750            }
13751        };
13752        const throttledShowResizeRect = first$1(showResizeRect, 0);
13753        const hideResizeRect = (removeSelected = true) => {
13754            throttledShowResizeRect.cancel();
13755            unbindResizeHandleEvents();
13756            if (selectedElm && removeSelected) {
13757                selectedElm.removeAttribute(elementSelectionAttr);
13758            }
13759            each$d(resizeHandles, (value, name) => {
13760                const handleElm = dom.get('mceResizeHandle' + name);
13761                if (handleElm) {
13762                    dom.unbind(handleElm);
13763                    dom.remove(handleElm);
13764                }
13765            });
13766        };
13767        const isChildOrEqual = (node, parent) => dom.isChildOf(node, parent);
13768        const updateResizeRect = (e) => {
13769            // Ignore all events while resizing, if the editor instance is composing or the editor was removed
13770            if (resizeStarted || editor.removed || editor.composing) {
13771                return;
13772            }
13773            const targetElm = e.type === 'mousedown' ? e.target : selection.getNode();
13774            const controlElm = closest$4(SugarElement.fromDom(targetElm), controlElmSelector)
13775                .map((e) => e.dom)
13776                .filter((e) => dom.isEditable(e.parentElement) || (e.nodeName === 'IMG' && dom.isEditable(e)))
13777                .getOrUndefined();
13778            // Store the original data-mce-selected value or fallback to '1' if not set
13779            const selectedValue = isNonNullable(controlElm) ? dom.getAttrib(controlElm, elementSelectionAttr, '1') : '1';
13780            // Remove data-mce-selected from all elements since they might have been copied using Ctrl+c/v
13781            each$e(dom.select(`img[${elementSelectionAttr}],hr[${elementSelectionAttr}]`), (img) => {
13782                img.removeAttribute(elementSelectionAttr);
13783            });
13784            if (isNonNullable(controlElm) && isChildOrEqual(controlElm, rootElement) && hasEditorOrUiFocus(editor)) {
13785                disableGeckoResize();
13786                const startElm = selection.getStart(true);
13787                if (isChildOrEqual(startElm, controlElm) && isChildOrEqual(selection.getEnd(true), controlElm)) {
13788                    // Note: We must ensure the selected attribute is added first before showing the rect so that we don't get any selection flickering
13789                    dom.setAttrib(controlElm, elementSelectionAttr, selectedValue);
13790                    throttledShowResizeRect.throttle(controlElm);
13791                    return;
13792                }
13793            }
13794            hideResizeRect();
13795        };
13796        const unbindResizeHandleEvents = () => {
13797            each$d(resizeHandles, (handle) => {
13798                if (handle.elm) {
13799                    dom.unbind(handle.elm);
13800                    // eslint-disable-next-line @typescript-eslint/no-array-delete
13801                    delete handle.elm;
13802                }
13803            });
13804        };
13805        const disableGeckoResize = () => {
13806            try {
13807                // Disable object resizing on Gecko
13808                editor.getDoc().execCommand('enableObjectResizing', false, 'false');
13809            }
13810            catch {
13811                // Ignore
13812            }
13813        };
13814        editor.on('init', () => {
13815            disableGeckoResize();
13816            editor.on('NodeChange ResizeEditor ResizeWindow ResizeContent drop', updateResizeRect);
13817            // Update resize rect while typing in a table
13818            editor.on('keyup compositionend', (e) => {
13819                // Don't update the resize rect while composing since it blows away the IME see: #2710
13820                if (selectedElm && selectedElm.nodeName === 'TABLE') {
13821                    updateResizeRect(e);
13822                }
13823            });
13824            editor.on('hide blur', hideResizeRect);
13825            editor.on('contextmenu longpress', contextMenuSelectImage, true);
13826            // Hide rect on focusout since it would float on top of windows otherwise
13827            // editor.on('focusout', hideResizeRect);
13828        });
13829        editor.on('remove', unbindResizeHandleEvents);
13830        const destroy = () => {
13831            throttledShowResizeRect.cancel();
13832            selectedElm = selectedElmGhost = resizeBackdrop = null;
13833        };
13834        return {
13835            isResizable,
13836            showResizeRect,
13837            hideResizeRect,
13838            updateResizeRect,
13839            destroy
13840        };
13841    };
13842
13843    const fromPoint = (clientX, clientY, doc) => {
13844        const win = defaultView(SugarElement.fromDom(doc));
13845        return getAtPoint(win.dom, clientX, clientY).map((simRange) => {
13846            const rng = doc.createRange();
13847            rng.setStart(simRange.start.dom, simRange.soffset);
13848            rng.setEnd(simRange.finish.dom, simRange.foffset);
13849            return rng;
13850        }).getOrUndefined();
13851    };
13852
13853    const isEq$4 = (rng1, rng2) => {
13854        return isNonNullable(rng1) && isNonNullable(rng2) &&
13855            (rng1.startContainer === rng2.startContainer && rng1.startOffset === rng2.startOffset) &&
13856            (rng1.endContainer === rng2.endContainer && rng1.endOffset === rng2.endOffset);
13857    };
13858
13859    const findParent = (node, rootNode, predicate) => {
13860        let currentNode = node;
13861        while (currentNode && currentNode !== rootNode) {
13862            if (predicate(currentNode)) {
13863                return currentNode;
13864            }
13865            currentNode = currentNode.parentNode;
13866        }
13867        return null;
13868    };
13869    const hasParent$1 = (node, rootNode, predicate) => findParent(node, rootNode, predicate) !== null;
13870    const hasParentWithName = (node, rootNode, name) => hasParent$1(node, rootNode, (node) => node.nodeName === name);
13871    const isCeFalseCaretContainer = (node, rootNode) => isCaretContainer$2(node) && !hasParent$1(node, rootNode, isCaretNode);
13872    const hasBrBeforeAfter = (dom, node, left) => {
13873        const parentNode = node.parentNode;
13874        if (parentNode) {
13875            const walker = new DomTreeWalker(node, dom.getParent(parentNode, dom.isBlock) || dom.getRoot());
13876            let currentNode;
13877            while ((currentNode = walker[left ? 'prev' : 'next']())) {
13878                if (isBr$7(currentNode)) {
13879                    return true;
13880                }
13881            }
13882        }
13883        return false;
13884    };
13885    const isPrevNode = (node, name) => node.previousSibling?.nodeName === name;
13886    const hasContentEditableFalseParent = (root, node) => {
13887        let currentNode = node;
13888        while (currentNode && currentNode !== root) {
13889            if (isContentEditableFalse$a(currentNode)) {
13890                return true;
13891            }
13892            currentNode = currentNode.parentNode;
13893        }
13894        return false;
13895    };
13896    // Walks the dom left/right to find a suitable text node to move the endpoint into
13897    // It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG
13898    const findTextNodeRelative = (dom, isAfterNode, collapsed, left, startNode) => {
13899        const body = dom.getRoot();
13900        const nonEmptyElementsMap = dom.schema.getNonEmptyElements();
13901        const parentNode = startNode.parentNode;
13902        let lastInlineElement;
13903        let node;
13904        if (!parentNode) {
13905            return Optional.none();
13906        }
13907        const parentBlockContainer = dom.getParent(parentNode, dom.isBlock) || body;
13908        // Lean left before the BR element if it's the only BR within a block element. Gecko bug: #6680
13909        // This: <p><br>|</p> becomes <p>|<br></p>
13910        if (left && isBr$7(startNode) && isAfterNode && dom.isEmpty(parentBlockContainer)) {
13911            return Optional.some(CaretPosition(parentNode, dom.nodeIndex(startNode)));
13912        }
13913        // Walk left until we hit a text node we can move to or a block/br/img
13914        const walker = new DomTreeWalker(startNode, parentBlockContainer);
13915        while ((node = walker[left ? 'prev' : 'next']())) {
13916            // Break if we hit a non content editable node
13917            if (dom.getContentEditableParent(node) === 'false' || isCeFalseCaretContainer(node, body)) {
13918                return Optional.none();
13919            }
13920            // Found text node that has a length
13921            if (isText$b(node) && node.data.length > 0) {
13922                if (!hasParentWithName(node, body, 'A')) {
13923                    return Optional.some(CaretPosition(node, left ? node.data.length : 0));
13924                }
13925                return Optional.none();
13926            }
13927            // Break if we find a block or a BR/IMG/INPUT etc
13928            if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
13929                return Optional.none();
13930            }
13931            lastInlineElement = node;
13932        }
13933        if (isComment(lastInlineElement)) {
13934            return Optional.none();
13935        }
13936        // Only fetch the last inline element when in caret mode for now
13937        if (collapsed && lastInlineElement) {
13938            return Optional.some(CaretPosition(lastInlineElement, 0));
13939        }
13940        return Optional.none();
13941    };
13942    const normalizeEndPoint = (dom, collapsed, start, rng) => {
13943        const body = dom.getRoot();
13944        let node;
13945        let normalized = false;
13946        let container = start ? rng.startContainer : rng.endContainer;
13947        let offset = start ? rng.startOffset : rng.endOffset;
13948        const isAfterNode = isElement$7(container) && offset === container.childNodes.length;
13949        const nonEmptyElementsMap = dom.schema.getNonEmptyElements();
13950        let directionLeft = start;
13951        if (isCaretContainer$2(container)) {
13952            return Optional.none();
13953        }
13954        if (isElement$7(container) && offset > container.childNodes.length - 1) {
13955            directionLeft = false;
13956        }
13957        // If the container is a document move it to the body element
13958        if (isDocument$1(container)) {
13959            container = body;
13960            offset = 0;
13961        }
13962        // If the container is body try move it into the closest text node or position
13963        if (container === body) {
13964            // If start is before/after a image, table etc
13965            if (directionLeft) {
13966                node = container.childNodes[offset > 0 ? offset - 1 : 0];
13967                if (node) {
13968                    if (isCaretContainer$2(node)) {
13969                        return Optional.none();
13970                    }
13971                    if (nonEmptyElementsMap[node.nodeName] || isTable$2(node)) {
13972                        return Optional.none();
13973                    }
13974                }
13975            }
13976            // Resolve the index
13977            if (container.hasChildNodes()) {
13978                offset = Math.min(!directionLeft && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1);
13979                container = container.childNodes[offset];
13980                offset = isText$b(container) && isAfterNode ? container.data.length : 0;
13981                // Don't normalize non collapsed selections like <p>[a</p><table></table>]
13982                if (!collapsed && container === body.lastChild && isTable$2(container)) {
13983                    return Optional.none();
13984                }
13985                if (hasContentEditableFalseParent(body, container) || isCaretContainer$2(container)) {
13986                    return Optional.none();
13987                }
13988                if (isDetails(container)) {
13989                    return Optional.none();
13990                }
13991                // Don't walk into elements that doesn't have any child nodes like a IMG
13992                if (container.hasChildNodes() && !isTable$2(container)) {
13993                    // Walk the DOM to find a text node to place the caret at or a BR
13994                    node = container;
13995                    const walker = new DomTreeWalker(container, body);
13996                    do {
13997                        if (isContentEditableFalse$a(node) || isCaretContainer$2(node)) {
13998                            normalized = false;
13999                            break;
14000                        }
14001                        // Found a text node use that position
14002                        if (isText$b(node) && node.data.length > 0) {
14003                            offset = directionLeft ? 0 : node.data.length;
14004                            container = node;
14005                            normalized = true;
14006                            break;
14007                        }
14008                        // Found a BR/IMG/PRE element that we can place the caret before
14009                        if (nonEmptyElementsMap[node.nodeName.toLowerCase()] && !isTableCellOrCaption(node)) {
14010                            offset = dom.nodeIndex(node);
14011                            container = node.parentNode;
14012                            // Put caret after image and pre tag when moving the end point
14013                            if (!directionLeft) {
14014                                offset++;
14015                            }
14016                            normalized = true;
14017                            break;
14018                        }
14019                    } while ((node = (directionLeft ? walker.next() : walker.prev())));
14020                }
14021            }
14022        }
14023        // Lean the caret to the left if possible
14024        if (collapsed) {
14025            // So this: <b>x</b><i>|x</i>
14026            // Becomes: <b>x|</b><i>x</i>
14027            // Seems that only gecko has issues with this
14028            if (isText$b(container) && offset === 0) {
14029                findTextNodeRelative(dom, isAfterNode, collapsed, true, container).each((pos) => {
14030                    container = pos.container();
14031                    offset = pos.offset();
14032                    normalized = true;
14033                });
14034            }
14035            // Lean left into empty inline elements when the caret is before a BR
14036            // So this: <i><b></b><i>|<br></i>
14037            // Becomes: <i><b>|</b><i><br></i>
14038            // Seems that only gecko has issues with this.
14039            // Special edge case for <p><a>x</a>|<br></p> since we don't want <p><a>x|</a><br></p>
14040            if (isElement$7(container)) {
14041                node = container.childNodes[offset];
14042                // Offset is after the containers last child
14043                // then use the previous child for normalization
14044                if (!node) {
14045                    node = container.childNodes[offset - 1];
14046                }
14047                if (node && isBr$7(node) && !isPrevNode(node, 'A') &&
14048                    !hasBrBeforeAfter(dom, node, false) && !hasBrBeforeAfter(dom, node, true)) {
14049                    findTextNodeRelative(dom, isAfterNode, collapsed, true, node).each((pos) => {
14050                        container = pos.container();
14051                        offset = pos.offset();
14052                        normalized = true;
14053                    });
14054                }
14055            }
14056        }
14057        // Lean the start of the selection right if possible
14058        // So this: x[<b>x]</b>
14059        // Becomes: x<b>[x]</b>
14060        if (directionLeft && !collapsed && isText$b(container) && offset === container.data.length) {
14061            findTextNodeRelative(dom, isAfterNode, collapsed, false, container).each((pos) => {
14062                container = pos.container();
14063                offset = pos.offset();
14064                normalized = true;
14065            });
14066        }
14067        return normalized && container ? Optional.some(CaretPosition(container, offset)) : Optional.none();
14068    };
14069    const normalize$2 = (dom, rng) => {
14070        const collapsed = rng.collapsed, normRng = rng.cloneRange();
14071        const startPos = CaretPosition.fromRangeStart(rng);
14072        normalizeEndPoint(dom, collapsed, true, normRng).each((pos) => {
14073            // #TINY-1595: Do not move the caret to previous line
14074            if (!collapsed || !CaretPosition.isAbove(startPos, pos)) {
14075                normRng.setStart(pos.container(), pos.offset());
14076            }
14077        });
14078        if (!collapsed) {
14079            normalizeEndPoint(dom, collapsed, false, normRng).each((pos) => {
14080                normRng.setEnd(pos.container(), pos.offset());
14081            });
14082        }
14083        // If it was collapsed then make sure it still is
14084        if (collapsed) {
14085            normRng.collapse(true);
14086        }
14087        return isEq$4(rng, normRng) ? Optional.none() : Optional.some(normRng);
14088    };
14089
14090    const splitText = (node, offset) => {
14091        return node.splitText(offset);
14092    };
14093    const split = (rng) => {
14094        let startContainer = rng.startContainer, startOffset = rng.startOffset, endContainer = rng.endContainer, endOffset = rng.endOffset;
14095        // Handle single text node
14096        if (startContainer === endContainer && isText$b(startContainer)) {
14097            if (startOffset > 0 && startOffset < startContainer.data.length) {
14098                endContainer = splitText(startContainer, startOffset);
14099                startContainer = endContainer.previousSibling;
14100                if (endOffset > startOffset) {
14101                    endOffset = endOffset - startOffset;
14102                    const newContainer = splitText(endContainer, endOffset).previousSibling;
14103                    startContainer = endContainer = newContainer;
14104                    endOffset = newContainer.data.length;
14105                    startOffset = 0;
14106                }
14107                else {
14108                    endOffset = 0;
14109                }
14110            }
14111        }
14112        else {
14113            // Split startContainer text node if needed
14114            if (isText$b(startContainer) && startOffset > 0 && startOffset < startContainer.data.length) {
14115                startContainer = splitText(startContainer, startOffset);
14116                startOffset = 0;
14117            }
14118            // Split endContainer text node if needed
14119            if (isText$b(endContainer) && endOffset > 0 && endOffset < endContainer.data.length) {
14120                const newContainer = splitText(endContainer, endOffset).previousSibling;
14121                endContainer = newContainer;
14122                endOffset = newContainer.data.length;
14123            }
14124        }
14125        return {
14126            startContainer,
14127            startOffset,
14128            endContainer,
14129            endOffset
14130        };
14131    };
14132
14133    /**
14134     * This class contains a few utility methods for ranges.
14135     *
14136     * @class tinymce.dom.RangeUtils
14137     */
14138    const RangeUtils = (dom) => {
14139        /**
14140         * Walks the specified range like object and executes the callback for each sibling collection it finds.
14141         *
14142         * @private
14143         * @method walk
14144         * @param {RangeObject} rng Range like object.
14145         * @param {Function} callback Callback function to execute for each sibling collection.
14146         */
14147        const walk = (rng, callback) => {
14148            return walk$3(dom, rng, callback);
14149        };
14150        /**
14151         * Splits the specified range at it's start/end points.
14152         *
14153         * @private
14154         * @param {RangeObject} rng Range to split.
14155         * @return {RangeObject} Range position object.
14156         */
14157        const split$1 = split;
14158        /**
14159         * Normalizes the specified range by finding the closest best suitable caret location.
14160         *
14161         * @private
14162         * @param {Range} rng Range to normalize.
14163         * @return {Boolean} True or false if the specified range was normalized or not.
14164         */
14165        const normalize = (rng) => {
14166            return normalize$2(dom, rng).fold(never, (normalizedRng) => {
14167                rng.setStart(normalizedRng.startContainer, normalizedRng.startOffset);
14168                rng.setEnd(normalizedRng.endContainer, normalizedRng.endOffset);
14169                return true;
14170            });
14171        };
14172        /**
14173         * Returns a range expanded around the entire word the provided selection was collapsed within.
14174         *
14175         * @method expand
14176         * @param {Range} rng The initial range to work from.
14177         * @param {Object} options Optional options provided to the expansion. Defaults to { type: 'word' }
14178         * @return {Range} Returns the expanded range.
14179         */
14180        const expand = (rng, options = { type: 'word' }) => {
14181            if (options.type === 'word') {
14182                const rangeLike = expandRng(dom, rng, [{ inline: 'span' }], { includeTrailingSpace: false, expandToBlock: false });
14183                const newRange = dom.createRng();
14184                newRange.setStart(rangeLike.startContainer, rangeLike.startOffset);
14185                newRange.setEnd(rangeLike.endContainer, rangeLike.endOffset);
14186                return newRange;
14187            }
14188            return rng;
14189        };
14190        return {
14191            walk,
14192            split: split$1,
14193            expand,
14194            normalize
14195        };
14196    };
14197    /**
14198     * Compares two ranges and checks if they are equal.
14199     *
14200     * @static
14201     * @method compareRanges
14202     * @param {RangeObject} rng1 First range to compare.
14203     * @param {RangeObject} rng2 First range to compare.
14204     * @return {Boolean} True or false if the ranges are equal.
14205     */
14206    RangeUtils.compareRanges = isEq$4;
14207    /**
14208     * Gets the caret range for the given x/y location.
14209     *
14210     * @static
14211     * @method getCaretRangeFromPoint
14212     * @param {Number} clientX X coordinate for range
14213     * @param {Number} clientY Y coordinate for range
14214     * @param {Document} doc Document that the x and y coordinates are relative to
14215     * @returns {Range} Caret range
14216     */
14217    RangeUtils.getCaretRangeFromPoint = fromPoint;
14218    RangeUtils.getSelectedNode = getSelectedNode;
14219    RangeUtils.getNode = getNode$1;
14220
14221    const walkUp = (navigation, doc) => {
14222        const frame = navigation.view(doc);
14223        return frame.fold(constant([]), (f) => {
14224            const parent = navigation.owner(f);
14225            const rest = walkUp(navigation, parent);
14226            return [f].concat(rest);
14227        });
14228    };
14229    const pathTo = (element, navigation) => {
14230        const d = navigation.owner(element);
14231        return walkUp(navigation, d);
14232    };
14233
14234    const view = (doc) => {
14235        // Only walk up to the document this script is defined in.
14236        // This prevents walking up to the parent window when the editor is in an iframe.
14237        const element = doc.dom === document ? Optional.none() : Optional.from(doc.dom.defaultView?.frameElement);
14238        return element.map(SugarElement.fromDom);
14239    };
14240    const owner = (element) => documentOrOwner(element);
14241
14242    var Navigation = /*#__PURE__*/Object.freeze({
14243        __proto__: null,
14244        view: view,
14245        owner: owner
14246    });
14247
14248    const find = (element) => {
14249        const doc = getDocument();
14250        const scroll = get$
vendor: 2,893 bytes, lines 14250-14323
142505(doc);
14251        const frames = pathTo(element, Navigation);
14252        const offset = viewport(element);
14253        const r = foldr(frames, (b, a) => {
14254            const loc = viewport(a);
14255            return {
14256                left: b.left + loc.left,
14257                top: b.top + loc.top
14258            };
14259        }, { left: 0, top: 0 });
14260        return SugarPosition(r.left + offset.left + scroll.left, r.top + offset.top + scroll.top);
14261    };
14262
14263    const excludeFromDescend = (element) => name(element) === 'textarea';
14264    const fireScrollIntoViewEvent = (editor, data) => {
14265        const scrollEvent = editor.dispatch('ScrollIntoView', data);
14266        return scrollEvent.isDefaultPrevented();
14267    };
14268    const fireAfterScrollIntoViewEvent = (editor, data) => {
14269        editor.dispatch('AfterScrollIntoView', data);
14270    };
14271    const descend = (element, offset) => {
14272        const children = children$1(element);
14273        if (children.length === 0 || excludeFromDescend(element)) {
14274            return { element, offset };
14275        }
14276        else if (offset < children.length && !excludeFromDescend(children[offset])) {
14277            return { element: children[offset], offset: 0 };
14278        }
14279        else {
14280            const last = children[children.length - 1];
14281            if (excludeFromDescend(last)) {
14282                return { element, offset };
14283            }
14284            else {
14285                if (name(last) === 'img') {
14286                    return { element: last, offset: 1 };
14287                }
14288                else if (isText$c(last)) {
14289                    return { element: last, offset: get$4(last).length };
14290                }
14291                else {
14292                    return { element: last, offset: children$1(last).length };
14293                }
14294            }
14295        }
14296    };
14297    const markerInfo = (element, cleanupFun) => {
14298        const pos = absolute(element);
14299        const height = get$6(element);
14300        return {
14301            element,
14302            bottom: pos.top + height,
14303            height,
14304            pos,
14305            cleanup: cleanupFun
14306        };
14307    };
14308    const createMarker$1 = (element, offset) => {
14309        const startPoint = descend(element, offset);
14310        const span = SugarElement.fromHtml('<span data-mce-bogus="all" style="display: inline-block;">' + ZWSP$1 + '</span>');
14311        before$4(startPoint.element, span);
14312        return markerInfo(span, () => remove$8(span));
14313    };
14314    const elementMarker = (element) => markerInfo(SugarElement.fromDom(element), noop);
14315    const withMarker = (editor, f, rng, alignToTop) => {
14316        preserveWith(editor, (_s, _e) => applyWithMarker(editor, f, rng, alignToTop), rng);
14317    };
14318    const withScrollEvents = (editor, doc, f, marker, alignToTop) => {
14319        const data = { elm: marker.element.dom, alignToTop };
14320        if (fireScrollIntoViewEvent(editor, data)) {
14321            return;
14322        }
14323        const scrollTop = get$5
vendor: 27,895 bytes, lines 14323-15006
14323(doc).top;
14324        f(editor, doc, scrollTop, marker, alignToTop);
14325        fireAfterScrollIntoViewEvent(editor, data);
14326    };
14327    const applyWithMarker = (editor, f, rng, alignToTop) => {
14328        const body = SugarElement.fromDom(editor.getBody());
14329        const doc = SugarElement.fromDom(editor.getDoc());
14330        reflow(body);
14331        const marker = createMarker$1(SugarElement.fromDom(rng.startContainer), rng.startOffset);
14332        withScrollEvents(editor, doc, f, marker, alignToTop);
14333        marker.cleanup();
14334    };
14335    const withElement = (editor, element, f, alignToTop) => {
14336        const doc = SugarElement.fromDom(editor.getDoc());
14337        withScrollEvents(editor, doc, f, elementMarker(element), alignToTop);
14338    };
14339    const preserveWith = (editor, f, rng) => {
14340        const startElement = rng.startContainer;
14341        const startOffset = rng.startOffset;
14342        const endElement = rng.endContainer;
14343        const endOffset = rng.endOffset;
14344        f(SugarElement.fromDom(startElement), SugarElement.fromDom(endElement));
14345        const newRng = editor.dom.createRng();
14346        newRng.setStart(startElement, startOffset);
14347        newRng.setEnd(endElement, endOffset);
14348        editor.selection.setRng(rng);
14349    };
14350    const scrollToMarker = (editor, marker, viewHeight, alignToTop, doc) => {
14351        const pos = marker.pos;
14352        // with default font size 16px font and 1.3 line height (~21px per line),
14353        // adding roughly 50% extra space gives about 30px of breathing room ensuring comfortable spacing.
14354        const scrollMargin = 30;
14355        if (alignToTop) {
14356            // When scrolling to top, add margin to the top position
14357            to(pos.left, Math.max(0, pos.top - scrollMargin), doc);
14358        }
14359        else {
14360            // The position we want to scroll to is the...
14361            // (absolute position of the marker, minus the view height) plus (the height of the marker)
14362            // When scrolling to bottom, add margin to ensure content isn't at the very bottom
14363            const y = (pos.top - viewHeight) + marker.height + scrollMargin;
14364            to(-editor.getBody().getBoundingClientRect().left, y, doc);
14365        }
14366    };
14367    const intoWindowIfNeeded = (editor, doc, scrollTop, viewHeight, marker, alignToTop) => {
14368        const viewportBottom = viewHeight + scrollTop;
14369        const markerTop = marker.pos.top;
14370        const markerBottom = marker.bottom;
14371        const largerThanViewport = markerBottom - markerTop >= viewHeight;
14372        // above the screen, scroll to top by default
14373        if (markerTop < scrollTop) {
14374            scrollToMarker(editor, marker, viewHeight, alignToTop !== false, doc);
14375            // completely below the screen. Default scroll to the top if element height is larger
14376            // than the viewport, otherwise default to scrolling to the bottom
14377        }
14378        else if (markerTop > viewportBottom) {
14379            const align = largerThanViewport ? alignToTop !== false : alignToTop === true;
14380            scrollToMarker(editor, marker, viewHeight, align, doc);
14381            // partially below the bottom, only scroll if element height is less than viewport
14382        }
14383        else if (markerBottom > viewportBottom && !largerThanViewport) {
14384            scrollToMarker(editor, marker, viewHeight, alignToTop === true, doc);
14385        }
14386    };
14387    const intoWindow = (editor, doc, scrollTop, marker, alignToTop) => {
14388        const viewHeight = defaultView(doc).dom.innerHeight;
14389        intoWindowIfNeeded(editor, doc, scrollTop, viewHeight, marker, alignToTop);
14390    };
14391    const intoFrame = (editor, doc, scrollTop, marker, alignToTop) => {
14392        const frameViewHeight = defaultView(doc).dom.innerHeight; // height of iframe container
14393        // If the position is outside the iframe viewport, scroll to it
14394        intoWindowIfNeeded(editor, doc, scrollTop, frameViewHeight, marker, alignToTop);
14395        // If the new position is outside the window viewport, scroll to it
14396        const op = find(marker.element);
14397        const viewportBounds = getBounds(window);
14398        if (op.top < viewportBounds.y) {
14399            intoView(marker.element, alignToTop !== false);
14400        }
14401        else if (op.top > viewportBounds.bottom) {
14402            intoView(marker.element, alignToTop === true);
14403        }
14404    };
14405    const rangeIntoWindow = (editor, rng, alignToTop) => withMarker(editor, intoWindow, rng, alignToTop);
14406    const elementIntoWindow = (editor, element, alignToTop) => withElement(editor, element, intoWindow, alignToTop);
14407    const rangeIntoFrame = (editor, rng, alignToTop) => withMarker(editor, intoFrame, rng, alignToTop);
14408    const elementIntoFrame = (editor, element, alignToTop) => withElement(editor, element, intoFrame, alignToTop);
14409    const scrollElementIntoView = (editor, element, alignToTop) => {
14410        const scroller = editor.inline ? elementIntoWindow : elementIntoFrame;
14411        scroller(editor, element, alignToTop);
14412    };
14413    // This method is made to deal with the user pressing enter, it is not useful
14414    // if we want for example scroll in content after a paste event.
14415    const scrollRangeIntoView = (editor, rng, alignToTop) => {
14416        const scroller = editor.inline ? rangeIntoWindow : rangeIntoFrame;
14417        scroller(editor, rng, alignToTop);
14418    };
14419
14420    const isEditableRange = (dom, rng) => {
14421        if (rng.collapsed) {
14422            return dom.isEditable(rng.startContainer);
14423        }
14424        else {
14425            return dom.isEditable(rng.startContainer) && dom.isEditable(rng.endContainer);
14426        }
14427    };
14428
14429    const getEndpointElement = (root, rng, start, real, resolve) => {
14430        const container = start ? rng.startContainer : rng.endContainer;
14431        const offset = start ? rng.startOffset : rng.endOffset;
14432        return Optional.from(container)
14433            .map(SugarElement.fromDom)
14434            .map((elm) => !real || !rng.collapsed ? child$1(elm, resolve(elm, offset)).getOr(elm) : elm)
14435            .bind((elm) => isElement$8(elm) ? Optional.some(elm) : parent(elm).filter(isElement$8))
14436            .map((elm) => elm.dom)
14437            .getOr(root);
14438    };
14439    const getStart = (root, rng, real = false) => getEndpointElement(root, rng, true, real, (elm, offset) => Math.min(childNodesCount(elm), offset));
14440    const getEnd = (root, rng, real = false) => getEndpointElement(root, rng, false, real, (elm, offset) => offset > 0 ? offset - 1 : offset);
14441    const skipEmptyTextNodes = (node, forwards) => {
14442        const orig = node;
14443        while (node && isText$b(node) && node.length === 0) {
14444            node = forwards ? node.nextSibling : node.previousSibling;
14445        }
14446        return node || orig;
14447    };
14448    const getNode = (root, rng) => {
14449        // Range maybe lost after the editor is made visible again
14450        if (!rng) {
14451            return root;
14452        }
14453        let startContainer = rng.startContainer;
14454        let endContainer = rng.endContainer;
14455        const startOffset = rng.startOffset;
14456        const endOffset = rng.endOffset;
14457        let node = rng.commonAncestorContainer;
14458        // Handle selection a image or other control like element such as anchors
14459        if (!rng.collapsed) {
14460            if (startContainer === endContainer) {
14461                if (endOffset - startOffset < 2) {
14462                    if (startContainer.hasChildNodes()) {
14463                        node = startContainer.childNodes[startOffset];
14464                    }
14465                }
14466            }
14467            // If the anchor node is a element instead of a text node then return this element
14468            // if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1)
14469            // return sel.anchorNode.childNodes[sel.anchorOffset];
14470            // Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent.
14471            // This happens when you double click an underlined word in FireFox.
14472            if (isText$b(startContainer) && isText$b(endContainer)) {
14473                if (startContainer.length === startOffset) {
14474                    startContainer = skipEmptyTextNodes(startContainer.nextSibling, true);
14475                }
14476                else {
14477                    startContainer = startContainer.parentNode;
14478                }
14479                if (endOffset === 0) {
14480                    endContainer = skipEmptyTextNodes(endContainer.previousSibling, false);
14481                }
14482                else {
14483                    endContainer = endContainer.parentNode;
14484                }
14485                if (startContainer && startContainer === endContainer) {
14486                    node = startContainer;
14487                }
14488            }
14489        }
14490        const elm = isText$b(node) ? node.parentNode : node;
14491        return isHTMLElement(elm) ? elm : root;
14492    };
14493    const getSelectedBlocks = (dom, rng, startElm, endElm) => {
14494        const selectedBlocks = [];
14495        const root = dom.getRoot();
14496        const start = dom.getParent(startElm || getStart(root, rng, rng.collapsed), dom.isBlock);
14497        const end = dom.getParent(endElm || getEnd(root, rng, rng.collapsed), dom.isBlock);
14498        if (start && start !== root) {
14499            selectedBlocks.push(start);
14500        }
14501        if (start && end && start !== end) {
14502            let node;
14503            const walker = new DomTreeWalker(start, root);
14504            while ((node = walker.next()) && node !== end) {
14505                if (dom.isBlock(node)) {
14506                    selectedBlocks.push(node);
14507                }
14508            }
14509        }
14510        if (end && start !== end && end !== root) {
14511            selectedBlocks.push(end);
14512        }
14513        return selectedBlocks;
14514    };
14515    const select = (dom, node, content) => Optional.from(node).bind((node) => Optional.from(node.parentNode).map((parent) => {
14516        const idx = dom.nodeIndex(node);
14517        const rng = dom.createRng();
14518        rng.setStart(parent, idx);
14519        rng.setEnd(parent, idx + 1);
14520        // Find first/last text node or BR element
14521        if (content) {
14522            moveEndPoint(dom, rng, node, true);
14523            moveEndPoint(dom, rng, node, false);
14524        }
14525        return rng;
14526    }));
14527
14528    const processRanges = (editor, ranges) => map$3(ranges, (range) => {
14529        const evt = editor.dispatch('GetSelectionRange', { range });
14530        return evt.range !== range ? evt.range : range;
14531    });
14532
14533    const typeLookup = {
14534        '#text': 3,
14535        '#comment': 8,
14536        '#cdata': 4,
14537        '#pi': 7,
14538        '#doctype': 10,
14539        '#document-fragment': 11
14540    };
14541    // Walks the tree left/right
14542    const walk$2 = (node, root, prev) => {
14543        const startName = prev ? 'lastChild' : 'firstChild';
14544        const siblingName = prev ? 'prev' : 'next';
14545        // Walk into nodes if it has a start
14546        if (node[startName]) {
14547            return node[startName];
14548        }
14549        // Return the sibling if it has one
14550        if (node !== root) {
14551            let sibling = node[siblingName];
14552            if (sibling) {
14553                return sibling;
14554            }
14555            // Walk up the parents to look for siblings
14556            for (let parent = node.parent; parent && parent !== root; parent = parent.parent) {
14557                sibling = parent[siblingName];
14558                if (sibling) {
14559                    return sibling;
14560                }
14561            }
14562        }
14563        return undefined;
14564    };
14565    const isEmptyTextNode = (node) => {
14566        const text = node.value ?? '';
14567        // Non whitespace content
14568        if (!isWhitespaceText(text)) {
14569            return false;
14570        }
14571        // Parent is not a span and only spaces or is a span but has styles
14572        const parentNode = node.parent;
14573        if (parentNode && (parentNode.name !== 'span' || parentNode.attr('style')) && /^[ ]+$/.test(text)) {
14574            return false;
14575        }
14576        return true;
14577    };
14578    // Check if node contains data-bookmark attribute, name attribute, id attribute or is a named anchor
14579    const isNonEmptyElement = (node) => {
14580        const isNamedAnchor = node.name === 'a' && !node.attr('href') && node.attr('id');
14581        return (node.attr('name') || (node.attr('id') && !node.firstChild) || node.attr('data-mce-bookmark') || isNamedAnchor);
14582    };
14583    /**
14584     * This class is a minimalistic implementation of a DOM like node used by the DomParser class.
14585     *
14586     * @class tinymce.html.Node
14587     * @version 3.4
14588     * @example
14589     * const node = new tinymce.html.Node('strong', 1);
14590     * someRoot.append(node);
14591     */
14592    class AstNode {
14593        /**
14594         * Creates a node of a specific type.
14595         *
14596         * @static
14597         * @method create
14598         * @param {String} name Name of the node type to create for example "b" or "#text".
14599         * @param {Object} attrs Name/value collection of attributes that will be applied to elements.
14600         */
14601        static create(name, attrs) {
14602            // Create node
14603            const node = new AstNode(name, typeLookup[name] || 1);
14604            // Add attributes if needed
14605            if (attrs) {
14606                each$d(attrs, (value, attrName) => {
14607                    node.attr(attrName, value);
14608                });
14609            }
14610            return node;
14611        }
14612        name;
14613        type;
14614        attributes;
14615        value;
14616        parent;
14617        firstChild;
14618        lastChild;
14619        next;
14620        prev;
14621        raw;
14622        /**
14623         * Constructs a new Node instance.
14624         *
14625         * @constructor
14626         * @method Node
14627         * @param {String} name Name of the node type.
14628         * @param {Number} type Numeric type representing the node.
14629         */
14630        constructor(name, type) {
14631            this.name = name;
14632            this.type = type;
14633            if (type === 1) {
14634                this.attributes = [];
14635                this.attributes.map = {}; // Should be considered internal
14636            }
14637        }
14638        /**
14639         * Replaces the current node with the specified one.
14640         *
14641         * @method replace
14642         * @param {tinymce.html.Node} node Node to replace the current node with.
14643         * @return {tinymce.html.Node} The old node that got replaced.
14644         * @example
14645         * someNode.replace(someNewNode);
14646         */
14647        replace(node) {
14648            const self = this;
14649            if (node.parent) {
14650                node.remove();
14651            }
14652            self.insert(node, self);
14653            self.remove();
14654            return self;
14655        }
14656        attr(name, value) {
14657            const self = this;
14658            if (!isString(name)) {
14659                if (isNonNullable(name)) {
14660                    each$d(name, (value, key) => {
14661                        self.attr(key, value);
14662                    });
14663                }
14664                return self;
14665            }
14666            const attrs = self.attributes;
14667            if (attrs) {
14668                if (value !== undefined) {
14669                    // Remove attribute
14670                    if (value === null) {
14671                        if (name in attrs.map) {
14672                            delete attrs.map[name];
14673                            let i = attrs.length;
14674                            while (i--) {
14675                                if (attrs[i].name === name) {
14676                                    attrs.splice(i, 1);
14677                                    return self;
14678                                }
14679                            }
14680                        }
14681                        return self;
14682                    }
14683                    // Set attribute
14684                    if (name in attrs.map) {
14685                        // Set attribute
14686                        let i = attrs.length;
14687                        while (i--) {
14688                            if (attrs[i].name === name) {
14689                                attrs[i].value = value;
14690                                break;
14691                            }
14692                        }
14693                    }
14694                    else {
14695                        attrs.push({ name, value });
14696                    }
14697                    attrs.map[name] = value;
14698                    return self;
14699                }
14700                return attrs.map[name];
14701            }
14702            return undefined;
14703        }
14704        /**
14705         * Does a shallow clones the node into a new node. It will also exclude id attributes since
14706         * there should only be one id per document.
14707         *
14708         * @method clone
14709         * @return {tinymce.html.Node} New copy of the original node.
14710         * @example
14711         * const clonedNode = node.clone();
14712         */
14713        clone() {
14714            const self = this;
14715            const clone = new AstNode(self.name, self.type);
14716            const selfAttrs = self.attributes;
14717            // Clone element attributes
14718            if (selfAttrs) {
14719                const cloneAttrs = [];
14720                cloneAttrs.map = {};
14721                for (let i = 0, l = selfAttrs.length; i < l; i++) {
14722                    const selfAttr = selfAttrs[i];
14723                    // Clone everything except id
14724                    if (selfAttr.name !== 'id') {
14725                        cloneAttrs[cloneAttrs.length] = { name: selfAttr.name, value: selfAttr.value };
14726                        cloneAttrs.map[selfAttr.name] = selfAttr.value;
14727                    }
14728                }
14729                clone.attributes = cloneAttrs;
14730            }
14731            clone.value = self.value;
14732            return clone;
14733        }
14734        /**
14735         * Wraps the node in in another node.
14736         *
14737         * @method wrap
14738         * @example
14739         * node.wrap(wrapperNode);
14740         */
14741        wrap(wrapper) {
14742            const self = this;
14743            if (self.parent) {
14744                self.parent.insert(wrapper, self);
14745                wrapper.append(self);
14746            }
14747            return self;
14748        }
14749        /**
14750         * Unwraps the node in other words it removes the node but keeps the children.
14751         *
14752         * @method unwrap
14753         * @example
14754         * node.unwrap();
14755         */
14756        unwrap() {
14757            const self = this;
14758            for (let node = self.firstChild; node;) {
14759                const next = node.next;
14760                self.insert(node, self, true);
14761                node = next;
14762            }
14763            self.remove();
14764        }
14765        /**
14766         * Removes the node from it's parent.
14767         *
14768         * @method remove
14769         * @return {tinymce.html.Node} Current node that got removed.
14770         * @example
14771         * node.remove();
14772         */
14773        remove() {
14774            const self = this, parent = self.parent, next = self.next, prev = self.prev;
14775            if (parent) {
14776                if (parent.firstChild === self) {
14777                    parent.firstChild = next;
14778                    if (next) {
14779                        next.prev = null;
14780                    }
14781                }
14782                else if (prev) {
14783                    prev.next = next;
14784                }
14785                if (parent.lastChild === self) {
14786                    parent.lastChild = prev;
14787                    if (prev) {
14788                        prev.next = null;
14789                    }
14790                }
14791                else if (next) {
14792                    next.prev = prev;
14793                }
14794                self.parent = self.next = self.prev = null;
14795            }
14796            return self;
14797        }
14798        /**
14799         * Appends a new node as a child of the current node.
14800         *
14801         * @method append
14802         * @param {tinymce.html.Node} node Node to append as a child of the current one.
14803         * @return {tinymce.html.Node} The node that got appended.
14804         * @example
14805         * node.append(someNode);
14806         */
14807        append(node) {
14808            const self = this;
14809            if (node.parent) {
14810                node.remove();
14811            }
14812            const last = self.lastChild;
14813            if (last) {
14814                last.next = node;
14815                node.prev = last;
14816                self.lastChild = node;
14817            }
14818            else {
14819                self.lastChild = self.firstChild = node;
14820            }
14821            node.parent = self;
14822            return node;
14823        }
14824        /**
14825         * Inserts a node at a specific position as a child of this node.
14826         *
14827         * @method insert
14828         * @param {tinymce.html.Node} node Node to insert as a child of this node.
14829         * @param {tinymce.html.Node} refNode Reference node to set node before/after.
14830         * @param {Boolean} before Optional state to insert the node before the reference node.
14831         * @return {tinymce.html.Node} The node that got inserted.
14832         * @example
14833         * parentNode.insert(newChildNode, oldChildNode);
14834         */
14835        insert(node, refNode, before) {
14836            if (node.parent) {
14837                node.remove();
14838            }
14839            const parent = refNode.parent || this;
14840            if (before) {
14841                if (refNode === parent.firstChild) {
14842                    parent.firstChild = node;
14843                }
14844                else if (refNode.prev) {
14845                    refNode.prev.next = node;
14846                }
14847                node.prev = refNode.prev;
14848                node.next = refNode;
14849                refNode.prev = node;
14850            }
14851            else {
14852                if (refNode === parent.lastChild) {
14853                    parent.lastChild = node;
14854                }
14855                else if (refNode.next) {
14856                    refNode.next.prev = node;
14857                }
14858                node.next = refNode.next;
14859                node.prev = refNode;
14860                refNode.next = node;
14861            }
14862            node.parent = parent;
14863            return node;
14864        }
14865        /**
14866         * Get all descendants by name.
14867         *
14868         * @method getAll
14869         * @param {String} name Name of the descendant nodes to collect.
14870         * @return {Array} Array with descendant nodes matching the specified name.
14871         */
14872        getAll(name) {
14873            const self = this;
14874            const collection = [];
14875            for (let node = self.firstChild; node; node = walk$2(node, self)) {
14876                if (node.name === name) {
14877                    collection.push(node);
14878                }
14879            }
14880            return collection;
14881        }
14882        /**
14883         * Get all children of this node.
14884         *
14885         * @method children
14886         * @return {Array} Array containing child nodes.
14887         */
14888        children() {
14889            const self = this;
14890            const collection = [];
14891            for (let node = self.firstChild; node; node = node.next) {
14892                collection.push(node);
14893            }
14894            return collection;
14895        }
14896        /**
14897         * Removes all children of the current node.
14898         *
14899         * @method empty
14900         * @return {tinymce.html.Node} The current node that got cleared.
14901         */
14902        empty() {
14903            const self = this;
14904            // Remove all children
14905            if (self.firstChild) {
14906                const nodes = [];
14907                // Collect the children
14908                for (let node = self.firstChild; node; node = walk$2(node, self)) {
14909                    nodes.push(node);
14910                }
14911                // Remove the children
14912                let i = nodes.length;
14913                while (i--) {
14914                    const node = nodes[i];
14915                    node.parent = node.firstChild = node.lastChild = node.next = node.prev = null;
14916                }
14917            }
14918            self.firstChild = self.lastChild = null;
14919            return self;
14920        }
14921        /**
14922         * Returns true/false if the node is to be considered empty or not.
14923         *
14924         * @method isEmpty
14925         * @param {Object} elements Name/value object with elements that are automatically treated as non empty elements.
14926         * @param {Object} whitespace Name/value object with elements that are automatically treated whitespace preservables.
14927         * @param {Function} predicate Optional predicate that gets called after the other rules determine that the node is empty. Should return true if the node is a content node.
14928         * @return {Boolean} true/false if the node is empty or not.
14929         * @example
14930         * node.isEmpty({ img: true });
14931         */
14932        isEmpty(elements, whitespace = {}, predicate) {
14933            const self = this;
14934            let node = self.firstChild;
14935            if (isNonEmptyElement(self)) {
14936                return false;
14937            }
14938            if (node) {
14939                do {
14940                    if (node.type === 1) {
14941                        // Ignore bogus elements
14942                        if (node.attr('data-mce-bogus')) {
14943                            continue;
14944                        }
14945                        // Keep empty elements like <img />
14946                        if (elements[node.name]) {
14947                            return false;
14948                        }
14949                        if (isNonEmptyElement(node)) {
14950                            return false;
14951                        }
14952                    }
14953                    // Keep comments
14954                    if (node.type === 8) {
14955                        return false;
14956                    }
14957                    // Keep non whitespace text nodes
14958                    if (node.type === 3 && !isEmptyTextNode(node)) {
14959                        return false;
14960                    }
14961                    // Keep whitespace preserve elements
14962                    if (node.type === 3 && node.parent && whitespace[node.parent.name] && isWhitespaceText(node.value ?? '')) {
14963                        return false;
14964                    }
14965                    // Predicate tells that the node is contents
14966                    if (predicate && predicate(node)) {
14967                        return false;
14968                    }
14969                } while ((node = walk$2(node, self)));
14970            }
14971            return true;
14972        }
14973        /**
14974         * Walks to the next or previous node and returns that node or null if it wasn't found.
14975         *
14976         * @method walk
14977         * @param {Boolean} prev Optional previous node state defaults to false.
14978         * @return {tinymce.html.Node} Node that is next to or previous of the current node.
14979         */
14980        walk(prev) {
14981            return walk$2(this, null, prev);
14982        }
14983    }
14984
14985    // TINY-10305: Map over array for faster lookup.
14986    const unescapedTextParents = Tools.makeMap('NOSCRIPT STYLE SCRIPT XMP IFRAME NOEMBED NOFRAMES PLAINTEXT', ' ');
14987    const containsZwsp = (node) => isString(node.nodeValue) && node.nodeValue.includes(ZWSP$1);
14988    const getTemporaryNodeSelector = (tempAttrs) => `${tempAttrs.length === 0 ? '' : `${map$3(tempAttrs, (attr) => `[${attr}]`).join(',')},`}[data-mce-bogus="all"]`;
14989    const getTemporaryNodes = (tempAttrs, body) => body.querySelectorAll(getTemporaryNodeSelector(tempAttrs));
14990    const createZwspCommentWalker = (body) => document.createTreeWalker(body, NodeFilter.SHOW_COMMENT, (node) => containsZwsp(node) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP);
14991    const createUnescapedZwspTextWalker = (body) => document.createTreeWalker(body, NodeFilter.SHOW_TEXT, (node) => {
14992        if (containsZwsp(node)) {
14993            const parent = node.parentNode;
14994            return parent && has$2(unescapedTextParents, parent.nodeName) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
14995        }
14996        else {
14997            return NodeFilter.FILTER_SKIP;
14998        }
14999    });
15000    const hasZwspComment = (body) => createZwspCommentWalker(body).nextNode() !== null;
15001    const hasUnescapedZwspText = (body) => createUnescapedZwspTextWalker(body).nextNode() !== null;
15002    const hasTemporaryNode = (tempAttrs, body) => body.querySelector(getTemporaryNodeSelector(tempAttrs)) !== null;
15003    const trimTemporaryNodes = (tempAttrs, body) => {
15004        each$e(getTemporaryNodes(tempAttrs, body), (elm) => {
15005            const element = SugarElement.fromDom(elm);
15006            if (get$
vendor: 4,122 bytes, lines 15006-15117
150069(element, 'data-mce-bogus') === 'all') {
15007                remove$8(element);
15008            }
15009            else {
15010                each$e(tempAttrs, (attr) => {
15011                    if (has$1(element, attr)) {
15012                        remove$9(element, attr);
15013                    }
15014                });
15015            }
15016        });
15017    };
15018    const emptyAllNodeValuesInWalker = (walker) => {
15019        let curr = walker.nextNode();
15020        while (curr !== null) {
15021            curr.nodeValue = null;
15022            curr = walker.nextNode();
15023        }
15024    };
15025    const emptyZwspComments = compose(emptyAllNodeValuesInWalker, createZwspCommentWalker);
15026    const emptyUnescapedZwspTexts = compose(emptyAllNodeValuesInWalker, createUnescapedZwspTextWalker);
15027    const trim$1 = (body, tempAttrs) => {
15028        const conditionalTrims = [
15029            {
15030                condition: curry(hasTemporaryNode, tempAttrs),
15031                action: curry(trimTemporaryNodes, tempAttrs)
15032            },
15033            {
15034                condition: hasZwspComment,
15035                action: emptyZwspComments
15036            },
15037            {
15038                condition: hasUnescapedZwspText,
15039                action: emptyUnescapedZwspTexts
15040            }
15041        ];
15042        let trimmed = body;
15043        let cloned = false;
15044        each$e(conditionalTrims, ({ condition, action }) => {
15045            if (condition(trimmed)) {
15046                if (!cloned) {
15047                    trimmed = body.cloneNode(true);
15048                    cloned = true;
15049                }
15050                action(trimmed);
15051            }
15052        });
15053        return trimmed;
15054    };
15055
15056    const cleanupBogusElements = (parent) => {
15057        const bogusElements = descendants(parent, '[data-mce-bogus]');
15058        each$e(bogusElements, (elem) => {
15059            const bogusValue = get$9(elem, 'data-mce-bogus');
15060            if (bogusValue === 'all') {
15061                remove$8(elem);
15062            }
15063            else if (isBr$6(elem)) {
15064                // Need to keep bogus padding brs represented as a zero-width space so that they aren't collapsed by the browser
15065                before$4(elem, SugarElement.fromText(zeroWidth));
15066                remove$8(elem);
15067            }
15068            else {
15069                unwrap(elem);
15070            }
15071        });
15072    };
15073    const cleanupInputNames = (parent) => {
15074        const inputs = descendants(parent, 'input');
15075        each$e(inputs, (input) => {
15076            remove$9(input, 'name');
15077        });
15078    };
15079
15080    const trimEmptyContents = (editor, html) => {
15081        const blockName = getForcedRootBlock(editor);
15082        const emptyRegExp = new RegExp(`^(<${blockName}[^>]*>(&nbsp;|&#160;|\\s|\u00a0|<br \\/>|)<\\/${blockName}>[\r\n]*|<br \\/>[\r\n]*)$`);
15083        return html.replace(emptyRegExp, '');
15084    };
15085    const getPlainTextContent = (editor, body) => {
15086        const doc = editor.getDoc();
15087        const dos = getRootNode(SugarElement.fromDom(editor.getBody()));
15088        const offscreenDiv = SugarElement.fromTag('div', doc);
15089        set$4(offscreenDiv, 'data-mce-bogus', 'all');
15090        setAll(offscreenDiv, {
15091            position: 'fixed',
15092            left: '-9999999px',
15093            top: '0'
15094        });
15095        set$3(offscreenDiv, body.innerHTML);
15096        cleanupBogusElements(offscreenDiv);
15097        cleanupInputNames(offscreenDiv);
15098        // Append the wrapper element so that the browser will evaluate styles when getting the `innerText`
15099        const root = getContentContainer(dos);
15100        append$1(root, offscreenDiv);
15101        const content = trim$2(offscreenDiv.dom.innerText);
15102        remove$8(offscreenDiv);
15103        return content;
15104    };
15105    const getContentFromBody = (editor, args, body) => {
15106        let content;
15107        if (args.format === 'raw') {
15108            content = Tools.trim(trim$2(trim$1(body, editor.serializer.getTempAttrs()).innerHTML));
15109        }
15110        else if (args.format === 'text') {
15111            content = getPlainTextContent(editor, body);
15112        }
15113        else if (args.format === 'tree') {
15114            content = editor.serializer.serialize(body, args);
15115        }
15116        else {
15117            content = trimEmptyContents(editor
vendor: 17,073 bytes, lines 15117-15487
15117, editor.serializer.serialize(body, args));
15118        }
15119        // Trim if not using a whitespace preserve format/element
15120        const shouldTrim = args.format !== 'text' && !isWsPreserveElement(SugarElement.fromDom(body));
15121        return shouldTrim && isString(content) ? Tools.trim(content) : content;
15122    };
15123    const getContentInternal = (editor, args) => Optional.from(editor.getBody())
15124        .fold(constant(args.format === 'tree' ? new AstNode('body', 11) : ''), (body) => getContentFromBody(editor, args, body));
15125
15126    /**
15127     * This class is used to write HTML tags out it can be used with the Serializer.
15128     *
15129     * @class tinymce.html.Writer
15130     * @version 3.4
15131     * @example
15132     * const writer = tinymce.html.Writer({ indent: true });
15133     * writer.start('node', { attr: 'value' });
15134     * writer.end('node');
15135     * console.log(writer.getContent());
15136     */
15137    const makeMap$1 = Tools.makeMap;
15138    const Writer = (settings) => {
15139        const html = [];
15140        settings = settings || {};
15141        const indent = settings.indent;
15142        const indentBefore = makeMap$1(settings.indent_before || '');
15143        const indentAfter = makeMap$1(settings.indent_after || '');
15144        const encode = Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);
15145        const htmlOutput = settings.element_format !== 'xhtml';
15146        return {
15147            /**
15148             * Writes a start element, such as `<p id="a">`.
15149             *
15150             * @method start
15151             * @param {String} name Name of the element.
15152             * @param {Array} attrs Optional array of objects containing an attribute name and value, or undefined if the element has no attributes.
15153             * @param {Boolean} empty Optional empty state if the tag should serialize as a void element. For example: `<img />`
15154             */
15155            start: (name, attrs, empty) => {
15156                if (indent && indentBefore[name] && html.length > 0) {
15157                    const value = html[html.length - 1];
15158                    if (value.length > 0 && value !== '\n') {
15159                        html.push('\n');
15160                    }
15161                }
15162                html.push('<', name);
15163                if (attrs) {
15164                    for (let i = 0, l = attrs.length; i < l; i++) {
15165                        const attr = attrs[i];
15166                        html.push(' ', attr.name, '="', encode(attr.value, true), '"');
15167                    }
15168                }
15169                if (!empty || htmlOutput) {
15170                    html[html.length] = '>';
15171                }
15172                else {
15173                    html[html.length] = ' />';
15174                }
15175                if (empty && indent && indentAfter[name] && html.length > 0) {
15176                    const value = html[html.length - 1];
15177                    if (value.length > 0 && value !== '\n') {
15178                        html.push('\n');
15179                    }
15180                }
15181            },
15182            /**
15183             * Writes an end element, such as `</p>`.
15184             *
15185             * @method end
15186             * @param {String} name Name of the element.
15187             */
15188            end: (name) => {
15189                let value;
15190                /* if (indent && indentBefore[name] && html.length > 0) {
15191                  value = html[html.length - 1];
15192          
15193                  if (value.length > 0 && value !== '\n')
15194                    html.push('\n');
15195                }*/
15196                html.push('</', name, '>');
15197                if (indent && indentAfter[name] && html.length > 0) {
15198                    value = html[html.length - 1];
15199                    if (value.length > 0 && value !== '\n') {
15200                        html.push('\n');
15201                    }
15202                }
15203            },
15204            /**
15205             * Writes a text node.
15206             *
15207             * @method text
15208             * @param {String} text String to write out.
15209             * @param {Boolean} raw Optional raw state. If true, the contents won't get encoded.
15210             */
15211            text: (text, raw) => {
15212                if (text.length > 0) {
15213                    html[html.length] = raw ? text : encode(text);
15214                }
15215            },
15216            /**
15217             * Writes a cdata node, such as `<![CDATA[data]]>`.
15218             *
15219             * @method cdata
15220             * @param {String} text String to write out inside the cdata.
15221             */
15222            cdata: (text) => {
15223                html.push('<![CDATA[', text, ']]>');
15224            },
15225            /**
15226             * Writes a comment node, such as `<!-- Comment -->`.
15227             *
15228             * @method comment
15229             * @param {String} text String to write out inside the comment.
15230             */
15231            comment: (text) => {
15232                html.push('<!--', text, '-->');
15233            },
15234            /**
15235             * Writes a processing instruction (PI) node, such as `<?xml attr="value" ?>`.
15236             *
15237             * @method pi
15238             * @param {String} name Name of the pi.
15239             * @param {String} text String to write out inside the pi.
15240             */
15241            pi: (name, text) => {
15242                if (text) {
15243                    html.push('<?', name, ' ', encode(text), '?>');
15244                }
15245                else {
15246                    html.push('<?', name, '?>');
15247                }
15248                if (indent) {
15249                    html.push('\n');
15250                }
15251            },
15252            /**
15253             * Writes a doctype node, such as `<!DOCTYPE data>`.
15254             *
15255             * @method doctype
15256             * @param {String} text String to write out inside the doctype.
15257             */
15258            doctype: (text) => {
15259                html.push('<!DOCTYPE', text, '>', indent ? '\n' : '');
15260            },
15261            /**
15262             * Resets the internal buffer. For example, if one wants to reuse the writer.
15263             *
15264             * @method reset
15265             */
15266            reset: () => {
15267                html.length = 0;
15268            },
15269            /**
15270             * Returns the contents that was serialized.
15271             *
15272             * @method getContent
15273             * @return {String} HTML contents that got written down.
15274             */
15275            getContent: () => {
15276                return html.join('').replace(/\n$/, '');
15277            }
15278        };
15279    };
15280
15281    /**
15282     * This class is used to serialize down the DOM tree into a string using a Writer instance.
15283     *
15284     * @class tinymce.html.Serializer
15285     * @version 3.4
15286     * @example
15287     * tinymce.html.Serializer().serialize(tinymce.html.DomParser().parse('<p>text</p>'));
15288     */
15289    const HtmlSerializer = (settings = {}, schema = Schema()) => {
15290        const writer = Writer(settings);
15291        settings.validate = 'validate' in settings ? settings.validate : true;
15292        /**
15293         * Serializes the specified node into a string.
15294         *
15295         * @method serialize
15296         * @param {tinymce.html.Node} node Node instance to serialize.
15297         * @return {String} String with HTML based on the DOM tree.
15298         * @example
15299         * tinymce.html.Serializer().serialize(tinymce.html.DomParser().parse('<p>text</p>'));
15300         */
15301        const serialize = (node) => {
15302            const validate = settings.validate;
15303            const handlers = {
15304                // #text
15305                3: (node) => {
15306                    writer.text(node.value ?? '', node.raw);
15307                },
15308                // #comment
15309                8: (node) => {
15310                    writer.comment(node.value ?? '');
15311                },
15312                // Processing instruction
15313                7: (node) => {
15314                    writer.pi(node.name, node.value);
15315                },
15316                // Doctype
15317                10: (node) => {
15318                    writer.doctype(node.value ?? '');
15319                },
15320                // CDATA
15321                4: (node) => {
15322                    writer.cdata(node.value ?? '');
15323                },
15324                // Document fragment
15325                11: (node) => {
15326                    let tempNode = node;
15327                    if ((tempNode = tempNode.firstChild)) {
15328                        do {
15329                            walk(tempNode);
15330                        } while ((tempNode = tempNode.next));
15331                    }
15332                }
15333            };
15334            writer.reset();
15335            const walk = (node) => {
15336                const handler = handlers[node.type];
15337                if (!handler) {
15338                    const name = node.name;
15339                    const isEmpty = name in schema.getVoidElements();
15340                    let attrs = node.attributes;
15341                    // Sort attributes
15342                    if (validate && attrs && attrs.length > 1) {
15343                        const sortedAttrs = [];
15344                        sortedAttrs.map = {};
15345                        const elementRule = schema.getElementRule(node.name);
15346                        if (elementRule) {
15347                            for (let i = 0, l = elementRule.attributesOrder.length; i < l; i++) {
15348                                const attrName = elementRule.attributesOrder[i];
15349                                if (attrName in attrs.map) {
15350                                    const attrValue = attrs.map[attrName];
15351                                    sortedAttrs.map[attrName] = attrValue;
15352                                    sortedAttrs.push({ name: attrName, value: attrValue });
15353                                }
15354                            }
15355                            for (let i = 0, l = attrs.length; i < l; i++) {
15356                                const attrName = attrs[i].name;
15357                                if (!(attrName in sortedAttrs.map)) {
15358                                    const attrValue = attrs.map[attrName];
15359                                    sortedAttrs.map[attrName] = attrValue;
15360                                    sortedAttrs.push({ name: attrName, value: attrValue });
15361                                }
15362                            }
15363                            attrs = sortedAttrs;
15364                        }
15365                    }
15366                    writer.start(name, attrs, isEmpty);
15367                    if (isNonHtmlElementRootName(name)) {
15368                        if (isString(node.value)) {
15369                            writer.text(node.value, true);
15370                        }
15371                        writer.end(name);
15372                    }
15373                    else {
15374                        if (!isEmpty) {
15375                            let child = node.firstChild;
15376                            if (child) {
15377                                // Pre and textarea elements treat the first newline character as optional and will omit it. As such, if the content starts
15378                                // with a newline we need to add in an additional newline to prevent the current newline in the value being treated as optional
15379                                // See https://html.spec.whatwg.org/multipage/syntax.html#element-restrictions
15380                                if ((name === 'pre' || name === 'textarea') && child.type === 3 && child.value?.[0] === '\n') {
15381                                    writer.text('\n', true);
15382                                }
15383                                do {
15384                                    walk(child);
15385                                } while ((child = child.next));
15386                            }
15387                            writer.end(name);
15388                        }
15389                    }
15390                }
15391                else {
15392                    handler(node);
15393                }
15394            };
15395            // Serialize element or text nodes and treat all other nodes as fragments
15396            if (node.type === 1 && !settings.inner) {
15397                walk(node);
15398            }
15399            else if (node.type === 3) {
15400                handlers[3](node);
15401            }
15402            else {
15403                handlers[11](node);
15404            }
15405            return writer.getContent();
15406        };
15407        return {
15408            serialize
15409        };
15410    };
15411
15412    const nonInheritableStyles = new Set();
15413    (() => {
15414        // TODO: TINY-7326 Figure out what else should go in the nonInheritableStyles list
15415        const nonInheritableStylesArr = [
15416            'margin', 'margin-left', 'margin-right', 'margin-top', 'margin-bottom',
15417            'padding', 'padding-left', 'padding-right', 'padding-top', 'padding-bottom',
15418            'border', 'border-width', 'border-style', 'border-color',
15419            'background', 'background-attachment', 'background-clip',
15420            'background-image', 'background-origin', 'background-position', 'background-repeat', 'background-size',
15421            'float', 'position', 'left', 'right', 'top', 'bottom',
15422            'z-index', 'display', 'transform',
15423            'width', 'max-width', 'min-width', 'height', 'max-height', 'min-height',
15424            'overflow', 'overflow-x', 'overflow-y', 'text-overflow', 'vertical-align',
15425            'transition', 'transition-delay', 'transition-duration', 'transition-property', 'transition-timing-function'
15426        ];
15427        each$e(nonInheritableStylesArr, (style) => {
15428            nonInheritableStyles.add(style);
15429        });
15430    })();
15431    const conditionalNonInheritableStyles = new Set();
15432    (() => {
15433        // These styles are only noninheritable when applied to an element with a noninheritable style
15434        // For example, background-color is visible on an element with padding, even when children have background-color;
15435        // however, when the element has no padding, background-color is either visible or overridden by children
15436        const conditionalNonInheritableStylesArr = [
15437            'background-color'
15438        ];
15439        each$e(conditionalNonInheritableStylesArr, (style) => {
15440            conditionalNonInheritableStyles.add(style);
15441        });
15442    })();
15443    // TODO: TINY-7326 Figure out what else should be added to the shorthandStyleProps list
15444    // Does not include non-inherited shorthand style properties
15445    const shorthandStyleProps = ['font', 'text-decoration', 'text-emphasis'];
15446    const getStyles$1 = (dom, node) => dom.parseStyle(dom.getAttrib(node, 'style'));
15447    const getStyleProps = (dom, node) => keys(getStyles$1(dom, node));
15448    const isNonInheritableStyle = (style) => nonInheritableStyles.has(style);
15449    const isConditionalNonInheritableStyle = (style) => conditionalNonInheritableStyles.has(style);
15450    const hasNonInheritableStyles = (dom, node) => exists(getStyleProps(dom, node), (style) => isNonInheritableStyle(style));
15451    const hasConditionalNonInheritableStyles = (dom, node) => hasNonInheritableStyles(dom, node) &&
15452        exists(getStyleProps(dom, node), (style) => isConditionalNonInheritableStyle(style));
15453    const getLonghandStyleProps = (styles) => filter$5(styles, (style) => exists(shorthandStyleProps, (prop) => startsWith(style, prop)));
15454    const hasStyleConflict = (dom, node, parentNode) => {
15455        const nodeStyleProps = getStyleProps(dom, node);
15456        const parentNodeStyleProps = getStyleProps(dom, parentNode);
15457        const valueMismatch = (prop) => {
15458            const nodeValue = dom.getStyle(node, prop) ?? '';
15459            const parentValue = dom.getStyle(parentNode, prop) ?? '';
15460            return isNotEmpty(nodeValue) && isNotEmpty(parentValue) && nodeValue !== parentValue;
15461        };
15462        return exists(nodeStyleProps, (nodeStyleProp) => {
15463            const propExists = (props) => exists(props, (prop) => prop === nodeStyleProp);
15464            // If parent has a longhand property e.g. margin-left but the child (node) style is margin, need to get the margin-left value of node to be able to do a proper comparison
15465            // This is because getting the style using the key of 'margin' on a 'margin-left' parent would give a string of space separated values or empty string depending on the browser
15466            if (!propExists(parentNodeStyleProps) && propExists(shorthandStyleProps)) {
15467                const longhandProps = getLonghandStyleProps(parentNodeStyleProps);
15468                return exists(longhandProps, valueMismatch);
15469            }
15470            else {
15471                return valueMismatch(nodeStyleProp);
15472            }
15473        });
15474    };
15475
15476    const isChar = (forward, predicate, pos) => Optional.from(pos.container()).filter(isText$b).exists((text) => {
15477        const delta = forward ? 0 : -1;
15478        return predicate(text.data.charAt(pos.offset() + delta));
15479    });
15480    const isBeforeSpace = curry(isChar, true, isWhiteSpace);
15481    const isAfterSpace = curry(isChar, false, isWhiteSpace);
15482    const isEmptyText = (pos) => {
15483        const container = pos.container();
15484        return isText$b(container) && (container.data.length === 0 || isZwsp(container.data) && BookmarkManager.isBookmarkNode(container.parentNode));
15485    };
15486    const matchesElementPosition = (before, predicate) => (pos) => getChildNodeAtRelativeOffset(before ? 0 : -1, pos).filter(predicate).isSome();
15487    const isImageBlock = (node) => isImg(node) && get$
vendor: 4,368 bytes, lines 15487-15558
154877(SugarElement.fromDom(node), 'display') === 'block';
15488    const isCefNode = (node) => isContentEditableFalse$a(node) && !isBogusAll(node);
15489    const isBeforeImageBlock = matchesElementPosition(true, isImageBlock);
15490    const isAfterImageBlock = matchesElementPosition(false, isImageBlock);
15491    const isBeforeMedia = matchesElementPosition(true, isMedia$2);
15492    const isAfterMedia = matchesElementPosition(false, isMedia$2);
15493    const isBeforeTable = matchesElementPosition(true, isTable$2);
15494    const isAfterTable = matchesElementPosition(false, isTable$2);
15495    const isBeforeContentEditableFalse = matchesElementPosition(true, isCefNode);
15496    const isAfterContentEditableFalse = matchesElementPosition(false, isCefNode);
15497
15498    const dropLast = (xs) => xs.slice(0, -1);
15499    const parentsUntil = (start, root, predicate) => {
15500        if (contains(root, start)) {
15501            return dropLast(parents$1(start, (elm) => {
15502                return predicate(elm) || eq(elm, root);
15503            }));
15504        }
15505        else {
15506            return [];
15507        }
15508    };
15509    const parents = (start, root) => parentsUntil(start, root, never);
15510    const parentsAndSelf = (start, root) => [start].concat(parents(start, root));
15511
15512    const navigateIgnoreEmptyTextNodes = (forward, root, from) => navigateIgnore(forward, root, from, isEmptyText);
15513    const isBlock$2 = (schema) => (el) => schema.isBlock(name(el));
15514    const getClosestBlock$1 = (root, pos, schema) => find$2(parentsAndSelf(SugarElement.fromDom(pos.container()), root), isBlock$2(schema));
15515    const isAtBeforeAfterBlockBoundary = (forward, root, pos, schema) => navigateIgnoreEmptyTextNodes(forward, root.dom, pos)
15516        .forall((newPos) => getClosestBlock$1(root, pos, schema).fold(() => !isInSameBlock(newPos, pos, root.dom), (fromBlock) => !isInSameBlock(newPos, pos, root.dom) && contains(fromBlock, SugarElement.fromDom(newPos.container()))));
15517    const isAtBlockBoundary = (forward, root, pos, schema) => getClosestBlock$1(root, pos, schema).fold(() => navigateIgnoreEmptyTextNodes(forward, root.dom, pos).forall((newPos) => !isInSameBlock(newPos, pos, root.dom)), (parent) => navigateIgnoreEmptyTextNodes(forward, parent.dom, pos).isNone());
15518    const isAtStartOfBlock = curry(isAtBlockBoundary, false);
15519    const isAtEndOfBlock = curry(isAtBlockBoundary, true);
15520    const isBeforeBlock = curry(isAtBeforeAfterBlockBoundary, false);
15521    const isAfterBlock = curry(isAtBeforeAfterBlockBoundary, true);
15522
15523    const isBr$2 = (pos) => getElementFromPosition(pos).exists(isBr$6);
15524    const findBr = (forward, root, pos, schema) => {
15525        const parentBlocks = filter$5(parentsAndSelf(SugarElement.fromDom(pos.container()), root), (el) => schema.isBlock(name(el)));
15526        const scope = head(parentBlocks).getOr(root);
15527        return fromPosition(forward, scope.dom, pos).filter(isBr$2);
15528    };
15529    const isBeforeBr$1 = (root, pos, schema) => getElementFromPosition(pos).exists(isBr$6) || findBr(true, root, pos, schema).isSome();
15530    const isAfterBr = (root, pos, schema) => getElementFromPrevPosition(pos).exists(isBr$6) || findBr(false, root, pos, schema).isSome();
15531    const findPreviousBr = curry(findBr, false);
15532    const findNextBr = curry(findBr, true);
15533
15534    const isInMiddleOfText = (pos) => CaretPosition.isTextPosition(pos) && !pos.isAtStart() && !pos.isAtEnd();
15535    const getClosestBlock = (root, pos, schema) => {
15536        const parentBlocks = filter$5(parentsAndSelf(SugarElement.fromDom(pos.container()), root), (el) => schema.isBlock(name(el)));
15537        return head(parentBlocks).getOr(root);
15538    };
15539    const hasSpaceBefore = (root, pos, schema) => {
15540        if (isInMiddleOfText(pos)) {
15541            return isAfterSpace(pos);
15542        }
15543        else {
15544            return isAfterSpace(pos) || prevPosition(getClosestBlock(root, pos, schema).dom, pos).exists(isAfterSpace);
15545        }
15546    };
15547    const hasSpaceAfter = (root, pos, schema) => {
15548        if (isInMiddleOfText(pos)) {
15549            return isBeforeSpace(pos);
15550        }
15551        else {
15552            return isBeforeSpace(pos) || nextPosition(getClosestBlock(root, pos, schema).dom, pos).exists(isBeforeSpace);
15553        }
15554    };
15555    const isPreValue = (value) => contains$2(['pre', 'pre-wrap'], value);
15556    const isInPre = (pos) => getElementFromPosition(pos)
15557        .bind((elm) => closest$5(elm, isElement$8))
15558        .exists((elm) => isPreValue(get$7
vendor: 11,575 bytes, lines 15558-15796
15558(elm, 'white-space')));
15559    const isAtBeginningOfBody = (root, pos) => prevPosition(root.dom, pos).isNone();
15560    const isAtEndOfBody = (root, pos) => nextPosition(root.dom, pos).isNone();
15561    const isAtLineBoundary = (root, pos, schema) => (isAtBeginningOfBody(root, pos) ||
15562        isAtEndOfBody(root, pos) ||
15563        isAtStartOfBlock(root, pos, schema) ||
15564        isAtEndOfBlock(root, pos, schema) ||
15565        isAfterBr(root, pos, schema) ||
15566        isBeforeBr$1(root, pos, schema));
15567    const isCefBlock = (node) => isNonNullable(node) && isContentEditableFalse$a(node) && isBlockLike(node);
15568    // Check the next/previous element in case it is a cef and the next/previous caret position then would skip it, then check
15569    // the next next/previous caret position ( for example in case the next element is a strong, containing a cef ).
15570    const isSiblingCefBlock = (root, direction) => (container) => {
15571        return isCefBlock(new DomTreeWalker(container, root)[direction]());
15572    };
15573    const isBeforeCefBlock = (root, pos) => {
15574        const nextPos = nextPosition(root.dom, pos).getOr(pos);
15575        const isNextCefBlock = isSiblingCefBlock(root.dom, 'next');
15576        return pos.isAtEnd() && (isNextCefBlock(pos.container()) || isNextCefBlock(nextPos.container()));
15577    };
15578    const isAfterCefBlock = (root, pos) => {
15579        const prevPos = prevPosition(root.dom, pos).getOr(pos);
15580        const isPrevCefBlock = isSiblingCefBlock(root.dom, 'prev');
15581        return pos.isAtStart() && (isPrevCefBlock(pos.container()) || isPrevCefBlock(prevPos.container()));
15582    };
15583    const needsToHaveNbsp = (root, pos, schema) => {
15584        if (isInPre(pos)) {
15585            return false;
15586        }
15587        else {
15588            return isAtLineBoundary(root, pos, schema) || hasSpaceBefore(root, pos, schema) || hasSpaceAfter(root, pos, schema);
15589        }
15590    };
15591    const needsToBeNbspLeft = (root, pos, schema) => {
15592        if (isInPre(pos)) {
15593            return false;
15594        }
15595        else {
15596            return isAtStartOfBlock(root, pos, schema) || isBeforeBlock(root, pos, schema) || isAfterBr(root, pos, schema) || hasSpaceBefore(root, pos, schema) || isAfterCefBlock(root, pos);
15597        }
15598    };
15599    const leanRight = (pos) => {
15600        const container = pos.container();
15601        const offset = pos.offset();
15602        if (isText$b(container) && offset < container.data.length) {
15603            return CaretPosition(container, offset + 1);
15604        }
15605        else {
15606            return pos;
15607        }
15608    };
15609    const needsToBeNbspRight = (root, pos, schema) => {
15610        if (isInPre(pos)) {
15611            return false;
15612        }
15613        else {
15614            return isAtEndOfBlock(root, pos, schema) || isAfterBlock(root, pos, schema) || isBeforeBr$1(root, pos, schema) || hasSpaceAfter(root, pos, schema) || isBeforeCefBlock(root, pos);
15615        }
15616    };
15617    const needsToBeNbsp = (root, pos, schema) => needsToBeNbspLeft(root, pos, schema) || needsToBeNbspRight(root, leanRight(pos), schema);
15618    const isNbspAt = (text, offset) => isNbsp(text.charAt(offset));
15619    const isWhiteSpaceAt = (text, offset) => isWhiteSpace(text.charAt(offset));
15620    const hasNbsp = (pos) => {
15621        const container = pos.container();
15622        return isText$b(container) && contains$1(container.data, nbsp);
15623    };
15624    const normalizeNbspMiddle = (text) => {
15625        const chars = text.split('');
15626        return map$3(chars, (chr, i) => {
15627            if (isNbsp(chr) && i > 0 && i < chars.length - 1 && isContent(chars[i - 1]) && isContent(chars[i + 1])) {
15628                return ' ';
15629            }
15630            else {
15631                return chr;
15632            }
15633        }).join('');
15634    };
15635    const normalizeNbspAtStart = (root, node, makeNbsp, schema) => {
15636        const text = node.data;
15637        const firstPos = CaretPosition(node, 0);
15638        if (!makeNbsp && isNbspAt(text, 0) && !needsToBeNbsp(root, firstPos, schema)) {
15639            node.data = ' ' + text.slice(1);
15640            return true;
15641        }
15642        else if (makeNbsp && isWhiteSpaceAt(text, 0) && needsToBeNbspLeft(root, firstPos, schema)) {
15643            node.data = nbsp + text.slice(1);
15644            return true;
15645        }
15646        else {
15647            return false;
15648        }
15649    };
15650    const normalizeNbspInMiddleOfTextNode = (node) => {
15651        const text = node.data;
15652        const newText = normalizeNbspMiddle(text);
15653        if (newText !== text) {
15654            node.data = newText;
15655            return true;
15656        }
15657        else {
15658            return false;
15659        }
15660    };
15661    const normalizeNbspAtEnd = (root, node, makeNbsp, schema) => {
15662        const text = node.data;
15663        const lastPos = CaretPosition(node, text.length - 1);
15664        if (!makeNbsp && isNbspAt(text, text.length - 1) && !needsToBeNbsp(root, lastPos, schema)) {
15665            node.data = text.slice(0, -1) + ' ';
15666            return true;
15667        }
15668        else if (makeNbsp && isWhiteSpaceAt(text, text.length - 1) && needsToBeNbspRight(root, lastPos, schema)) {
15669            node.data = text.slice(0, -1) + nbsp;
15670            return true;
15671        }
15672        else {
15673            return false;
15674        }
15675    };
15676    const normalizeNbsps$1 = (root, pos, schema) => {
15677        const container = pos.container();
15678        if (!isText$b(container)) {
15679            return Optional.none();
15680        }
15681        if (hasNbsp(pos)) {
15682            const normalized = normalizeNbspAtStart(root, container, false, schema) || normalizeNbspInMiddleOfTextNode(container) || normalizeNbspAtEnd(root, container, false, schema);
15683            return someIf(normalized, pos);
15684        }
15685        else if (needsToBeNbsp(root, pos, schema)) {
15686            const normalized = normalizeNbspAtStart(root, container, true, schema) || normalizeNbspAtEnd(root, container, true, schema);
15687            return someIf(normalized, pos);
15688        }
15689        else {
15690            return Optional.none();
15691        }
15692    };
15693    const normalizeNbspsInEditor = (editor) => {
15694        const root = SugarElement.fromDom(editor.getBody());
15695        if (editor.selection.isCollapsed()) {
15696            normalizeNbsps$1(root, CaretPosition.fromRangeStart(editor.selection.getRng()), editor.schema).each((pos) => {
15697                editor.selection.setRng(pos.toRange());
15698            });
15699        }
15700    };
15701
15702    const normalize$1 = (node, offset, count, schema) => {
15703        if (count === 0) {
15704            return;
15705        }
15706        const elm = SugarElement.fromDom(node);
15707        const root = ancestor$5(elm, (el) => schema.isBlock(name(el))).getOr(elm);
15708        // Get the whitespace
15709        const whitespace = node.data.slice(offset, offset + count);
15710        // Determine if we're at the end or start of the content
15711        const isEndOfContent = offset + count >= node.data.length && needsToBeNbspRight(root, CaretPosition(node, node.data.length), schema);
15712        const isStartOfContent = offset === 0 && needsToBeNbspLeft(root, CaretPosition(node, 0), schema);
15713        // Replace the original whitespace with the normalized whitespace content
15714        node.replaceData(offset, count, normalize$4(whitespace, 4, isStartOfContent, isEndOfContent));
15715    };
15716    const normalizeWhitespaceAfter = (node, offset, schema) => {
15717        const content = node.data.slice(offset);
15718        const whitespaceCount = content.length - lTrim(content).length;
15719        normalize$1(node, offset, whitespaceCount, schema);
15720    };
15721    const normalizeWhitespaceBefore = (node, offset, schema) => {
15722        const content = node.data.slice(0, offset);
15723        const whitespaceCount = content.length - rTrim(content).length;
15724        normalize$1(node, offset - whitespaceCount, whitespaceCount, schema);
15725    };
15726    const mergeTextNodes = (prevNode, nextNode, schema, normalizeWhitespace, mergeToPrev = true) => {
15727        const whitespaceOffset = rTrim(prevNode.data).length;
15728        const newNode = mergeToPrev ? prevNode : nextNode;
15729        const removeNode = mergeToPrev ? nextNode : prevNode;
15730        // Merge the elements
15731        if (mergeToPrev) {
15732            newNode.appendData(removeNode.data);
15733        }
15734        else {
15735            newNode.insertData(0, removeNode.data);
15736        }
15737        remove$8(SugarElement.fromDom(removeNode));
15738        // Normalize the whitespace around the merged elements, to ensure it doesn't get lost
15739        if (normalizeWhitespace) {
15740            normalizeWhitespaceAfter(newNode, whitespaceOffset, schema);
15741        }
15742        return newNode;
15743    };
15744
15745    const needsReposition = (pos, elm) => {
15746        const container = pos.container();
15747        const offset = pos.offset();
15748        return !CaretPosition.isTextPosition(pos) && container === elm.parentNode && offset > CaretPosition.before(elm).offset();
15749    };
15750    const reposition = (elm, pos) => needsReposition(pos, elm) ? CaretPosition(pos.container(), pos.offset() - 1) : pos;
15751    const beforeOrStartOf = (node) => isText$b(node) ? CaretPosition(node, 0) : CaretPosition.before(node);
15752    const afterOrEndOf = (node) => isText$b(node) ? CaretPosition(node, node.data.length) : CaretPosition.after(node);
15753    const getPreviousSiblingCaretPosition = (elm) => {
15754        if (isCaretCandidate$3(elm.previousSibling)) {
15755            return Optional.some(afterOrEndOf(elm.previousSibling));
15756        }
15757        else {
15758            return elm.previousSibling ? lastPositionIn(elm.previousSibling) : Optional.none();
15759        }
15760    };
15761    const getNextSiblingCaretPosition = (elm) => {
15762        if (isCaretCandidate$3(elm.nextSibling)) {
15763            return Optional.some(beforeOrStartOf(elm.nextSibling));
15764        }
15765        else {
15766            return elm.nextSibling ? firstPositionIn(elm.nextSibling) : Optional.none();
15767        }
15768    };
15769    const findCaretPositionBackwardsFromElm = (rootElement, elm) => {
15770        return Optional.from(elm.previousSibling ? elm.previousSibling : elm.parentNode)
15771            .bind((node) => prevPosition(rootElement, CaretPosition.before(node)))
15772            .orThunk(() => nextPosition(rootElement, CaretPosition.after(elm)));
15773    };
15774    const findCaretPositionForwardsFromElm = (rootElement, elm) => nextPosition(rootElement, CaretPosition.after(elm)).orThunk(() => prevPosition(rootElement, CaretPosition.before(elm)));
15775    const findCaretPositionBackwards = (rootElement, elm) => getPreviousSiblingCaretPosition(elm).orThunk(() => getNextSiblingCaretPosition(elm))
15776        .orThunk(() => findCaretPositionBackwardsFromElm(rootElement, elm));
15777    const findCaretPositionForward = (rootElement, elm) => getNextSiblingCaretPosition(elm)
15778        .orThunk(() => getPreviousSiblingCaretPosition(elm))
15779        .orThunk(() => findCaretPositionForwardsFromElm(rootElement, elm));
15780    const findCaretPosition = (forward, rootElement, elm) => forward ? findCaretPositionForward(rootElement, elm) : findCaretPositionBackwards(rootElement, elm);
15781    const findCaretPosOutsideElmAfterDelete = (forward, rootElement, elm) => findCaretPosition(forward, rootElement, elm).map(curry(reposition, elm));
15782    const setSelection$1 = (editor, forward, pos) => {
15783        pos.fold(() => {
15784            editor.focus();
15785        }, (pos) => {
15786            editor.selection.setRng(pos.toRange(), forward);
15787        });
15788    };
15789    const eqRawNode = (rawNode) => (elm) => elm.dom === rawNode;
15790    const isBlock$1 = (editor, elm) => elm && has$2(editor.schema.getBlockElements(), name(elm));
15791    const paddEmptyBlock = (schema, elm, preserveEmptyCaret) => {
15792        if (isEmpty$4(schema, elm)) {
15793            const br = SugarElement.fromHtml('<br data-mce-bogus="1">');
15794            // Remove all bogus elements except caret
15795            if (preserveEmptyCaret) {
15796                each$e(children$
vendor: 8,698 bytes, lines 15796-15968
157961(elm), (node) => {
15797                    if (!isEmptyCaretFormatElement(node)) {
15798                        remove$8(node);
15799                    }
15800                });
15801            }
15802            else {
15803                empty(elm);
15804            }
15805            append$1(elm, br);
15806            return Optional.some(CaretPosition.before(br.dom));
15807        }
15808        else {
15809            return Optional.none();
15810        }
15811    };
15812    const deleteNormalized = (elm, afterDeletePosOpt, schema, normalizeWhitespace) => {
15813        const prevTextOpt = prevSibling(elm).filter(isText$c);
15814        const nextTextOpt = nextSibling(elm).filter(isText$c);
15815        // Delete the element
15816        remove$8(elm);
15817        // Merge and normalize any prev/next text nodes, so that they are merged and don't lose meaningful whitespace
15818        // eg. <p>a <span></span> b</p> -> <p>a &nsbp;b</p> or <p><span></span> a</p> -> <p>&nbsp;a</a>
15819        return lift3(prevTextOpt, nextTextOpt, afterDeletePosOpt, (prev, next, pos) => {
15820            const prevNode = prev.dom, nextNode = next.dom;
15821            const offset = prevNode.data.length;
15822            mergeTextNodes(prevNode, nextNode, schema, normalizeWhitespace);
15823            // Update the cursor position if required
15824            return pos.container() === nextNode ? CaretPosition(prevNode, offset) : pos;
15825        }).orThunk(() => {
15826            if (normalizeWhitespace) {
15827                prevTextOpt.each((elm) => normalizeWhitespaceBefore(elm.dom, elm.dom.length, schema));
15828                nextTextOpt.each((elm) => normalizeWhitespaceAfter(elm.dom, 0, schema));
15829            }
15830            return afterDeletePosOpt;
15831        });
15832    };
15833    const isInlineElement = (editor, element) => has$2(editor.schema.getTextInlineElements(), name(element));
15834    const deleteElement$2 = (editor, forward, elm, moveCaret = true, preserveEmptyCaret = false) => {
15835        // Existing delete logic
15836        const afterDeletePos = findCaretPosOutsideElmAfterDelete(forward, editor.getBody(), elm.dom);
15837        const parentBlock = ancestor$5(elm, curry(isBlock$1, editor), eqRawNode(editor.getBody()));
15838        const normalizedAfterDeletePos = deleteNormalized(elm, afterDeletePos, editor.schema, isInlineElement(editor, elm));
15839        if (editor.dom.isEmpty(editor.getBody())) {
15840            editor.setContent('');
15841            editor.selection.setCursorLocation();
15842        }
15843        else {
15844            parentBlock.bind((elm) => paddEmptyBlock(editor.schema, elm, preserveEmptyCaret)).fold(() => {
15845                if (moveCaret) {
15846                    setSelection$1(editor, forward, normalizedAfterDeletePos);
15847                }
15848            }, (paddPos) => {
15849                if (moveCaret) {
15850                    setSelection$1(editor, forward, Optional.some(paddPos));
15851                }
15852            });
15853        }
15854    };
15855
15856    const strongRtl = /[\u0591-\u07FF\uFB1D-\uFDFF\uFE70-\uFEFC]/;
15857    const hasStrongRtl = (text) => strongRtl.test(text);
15858
15859    const isInlineTarget = (editor, elm) => is$2(SugarElement.fromDom(elm), getInlineBoundarySelector(editor))
15860        && !isTransparentBlock(editor.schema, elm)
15861        && editor.dom.isEditable(elm);
15862    const isRtl = (element) => DOMUtils.DOM.getStyle(element, 'direction', true) === 'rtl' || hasStrongRtl(element.textContent ?? '');
15863    const findInlineParents = (isInlineTarget, rootNode, pos) => filter$5(DOMUtils.DOM.getParents(pos.container(), '*', rootNode), isInlineTarget);
15864    const findRootInline = (isInlineTarget, rootNode, pos) => {
15865        const parents = findInlineParents(isInlineTarget, rootNode, pos);
15866        return Optional.from(parents[parents.length - 1]);
15867    };
15868    const hasSameParentBlock = (rootNode, node1, node2) => {
15869        const block1 = getParentBlock$3(node1, rootNode);
15870        const block2 = getParentBlock$3(node2, rootNode);
15871        return isNonNullable(block1) && block1 === block2;
15872    };
15873    const isAtZwsp = (pos) => isBeforeInline(pos) || isAfterInline(pos);
15874    const normalizePosition = (forward, pos) => {
15875        const container = pos.container(), offset = pos.offset();
15876        if (forward) {
15877            if (isCaretContainerInline(container)) {
15878                if (isText$b(container.nextSibling)) {
15879                    return CaretPosition(container.nextSibling, 0);
15880                }
15881                else {
15882                    return CaretPosition.after(container);
15883                }
15884            }
15885            else {
15886                return isBeforeInline(pos) ? CaretPosition(container, offset + 1) : pos;
15887            }
15888        }
15889        else {
15890            if (isCaretContainerInline(container)) {
15891                if (isText$b(container.previousSibling)) {
15892                    return CaretPosition(container.previousSibling, container.previousSibling.data.length);
15893                }
15894                else {
15895                    return CaretPosition.before(container);
15896                }
15897            }
15898            else {
15899                return isAfterInline(pos) ? CaretPosition(container, offset - 1) : pos;
15900            }
15901        }
15902    };
15903    const normalizeForwards = curry(normalizePosition, true);
15904    const normalizeBackwards = curry(normalizePosition, false);
15905
15906    const execCommandIgnoreInputEvents = (editor, command) => {
15907        // We need to prevent the input events from being fired by execCommand when delete is used internally
15908        const inputBlocker = (e) => e.stopImmediatePropagation();
15909        editor.on('beforeinput input', inputBlocker, true);
15910        editor.getDoc().execCommand(command);
15911        editor.off('beforeinput input', inputBlocker);
15912    };
15913    // ASSUMPTION: The editor command 'delete' doesn't have any `beforeinput` and `input` trapping
15914    // because those events are only triggered by native contenteditable behaviour.
15915    const execEditorDeleteCommand = (editor) => {
15916        editor.execCommand('delete');
15917    };
15918    const execNativeDeleteCommand = (editor) => execCommandIgnoreInputEvents(editor, 'Delete');
15919    const execNativeForwardDeleteCommand = (editor) => execCommandIgnoreInputEvents(editor, 'ForwardDelete');
15920    const isBeforeRoot = (rootNode) => (elm) => is$4(parent(elm), rootNode, eq);
15921    const isTextBlockOrListItem = (element) => isTextBlock$3(element) || isListItem$2(element);
15922    const getParentBlock$2 = (rootNode, elm) => {
15923        if (contains(rootNode, elm)) {
15924            return closest$5(elm, isTextBlockOrListItem, isBeforeRoot(rootNode));
15925        }
15926        else {
15927            return Optional.none();
15928        }
15929    };
15930    const paddEmptyBody = (editor, moveSelection = true) => {
15931        if (editor.dom.isEmpty(editor.getBody())) {
15932            editor.setContent('', { no_selection: !moveSelection });
15933        }
15934    };
15935    const willDeleteLastPositionInElement = (forward, fromPos, elm) => lift2(firstPositionIn(elm), lastPositionIn(elm), (firstPos, lastPos) => {
15936        const normalizedFirstPos = normalizePosition(true, firstPos);
15937        const normalizedLastPos = normalizePosition(false, lastPos);
15938        const normalizedFromPos = normalizePosition(false, fromPos);
15939        if (forward) {
15940            return nextPosition(elm, normalizedFromPos).exists((nextPos) => nextPos.isEqual(normalizedLastPos) && fromPos.isEqual(normalizedFirstPos));
15941        }
15942        else {
15943            return prevPosition(elm, normalizedFromPos).exists((prevPos) => prevPos.isEqual(normalizedFirstPos) && fromPos.isEqual(normalizedLastPos));
15944        }
15945    }).getOr(true);
15946    const freefallRtl = (root) => {
15947        const child = isComment$1(root) ? prevSibling(root) : lastChild(root);
15948        return child.bind(freefallRtl).orThunk(() => Optional.some(root));
15949    };
15950    const deleteRangeContents = (editor, rng, root, moveSelection = true) => {
15951        rng.deleteContents();
15952        // Pad the last block node
15953        const lastNode = freefallRtl(root).getOr(root);
15954        const lastBlock = SugarElement.fromDom(editor.dom.getParent(lastNode.dom, editor.dom.isBlock) ?? root.dom);
15955        // If the block is the editor body then we need to insert the root block as well
15956        if (lastBlock.dom === editor.getBody()) {
15957            paddEmptyBody(editor, moveSelection);
15958        }
15959        else if (isEmpty$4(editor.schema, lastBlock, { checkRootAsContent: false })) {
15960            fillWithPaddingBr(lastBlock);
15961            if (moveSelection) {
15962                editor.selection.setCursorLocation(lastBlock.dom, 0);
15963            }
15964        }
15965        // Clean up any additional leftover nodes. If the last block wasn't a direct child, then we also need to clean up siblings
15966        if (!eq(root, lastBlock)) {
15967            const additionalCleanupNodes = is$4(parent(lastBlock), root) ? [] : siblings(lastBlock);
15968            each$e(additionalCleanupNodes.concat(children$1
vendor: 11,084 bytes, lines 15968-16181
15968(root)), (node) => {
15969                if (!eq(node, lastBlock) && !contains(node, lastBlock) && isEmpty$4(editor.schema, node)) {
15970                    remove$8(node);
15971                }
15972            });
15973        }
15974    };
15975
15976    const isRootFromElement = (root) => (cur) => eq(root, cur);
15977    const getTableCells = (table) => descendants(table, 'td,th');
15978    const getTable$1 = (node, isRoot) => getClosestTable(SugarElement.fromDom(node), isRoot);
15979    const selectionInTableWithNestedTable = (details) => {
15980        return lift2(details.startTable, details.endTable, (startTable, endTable) => {
15981            const isStartTableParentOfEndTable = descendant(startTable, (t) => eq(t, endTable));
15982            const isEndTableParentOfStartTable = descendant(endTable, (t) => eq(t, startTable));
15983            return !isStartTableParentOfEndTable && !isEndTableParentOfStartTable ? details : {
15984                ...details,
15985                startTable: isStartTableParentOfEndTable ? Optional.none() : details.startTable,
15986                endTable: isEndTableParentOfStartTable ? Optional.none() : details.endTable,
15987                isSameTable: false,
15988                isMultiTable: false
15989            };
15990        }).getOr(details);
15991    };
15992    const adjustQuirksInDetails = (details) => {
15993        return selectionInTableWithNestedTable(details);
15994    };
15995    const getTableDetailsFromRange = (rng, isRoot) => {
15996        const startTable = getTable$1(rng.startContainer, isRoot);
15997        const endTable = getTable$1(rng.endContainer, isRoot);
15998        const isStartInTable = startTable.isSome();
15999        const isEndInTable = endTable.isSome();
16000        // Partial selection - selection is not within the same table
16001        const isSameTable = lift2(startTable, endTable, eq).getOr(false);
16002        const isMultiTable = !isSameTable && isStartInTable && isEndInTable;
16003        return adjustQuirksInDetails({
16004            startTable,
16005            endTable,
16006            isStartInTable,
16007            isEndInTable,
16008            isSameTable,
16009            isMultiTable
16010        });
16011    };
16012
16013    const tableCellRng = (start, end) => ({
16014        start,
16015        end,
16016    });
16017    const tableSelection = (rng, table, cells) => ({
16018        rng,
16019        table,
16020        cells
16021    });
16022    const deleteAction = Adt.generate([
16023        { singleCellTable: ['rng', 'cell'] },
16024        { fullTable: ['table'] },
16025        { partialTable: ['cells', 'outsideDetails'] },
16026        { multiTable: ['startTableCells', 'endTableCells', 'betweenRng'] },
16027    ]);
16028    const getClosestCell$1 = (container, isRoot) => closest$4(SugarElement.fromDom(container), 'td,th', isRoot);
16029    const isExpandedCellRng = (cellRng) => !eq(cellRng.start, cellRng.end);
16030    const getTableFromCellRng = (cellRng, isRoot) => getClosestTable(cellRng.start, isRoot)
16031        .bind((startParentTable) => getClosestTable(cellRng.end, isRoot)
16032        .bind((endParentTable) => someIf(eq(startParentTable, endParentTable), startParentTable)));
16033    const isSingleCellTable = (cellRng, isRoot) => !isExpandedCellRng(cellRng) &&
16034        getTableFromCellRng(cellRng, isRoot).exists((table) => {
16035            const rows = table.dom.rows;
16036            return rows.length === 1 && rows[0].cells.length === 1;
16037        });
16038    const getCellRng = (rng, isRoot) => {
16039        const startCell = getClosestCell$1(rng.startContainer, isRoot);
16040        const endCell = getClosestCell$1(rng.endContainer, isRoot);
16041        return lift2(startCell, endCell, tableCellRng);
16042    };
16043    const getCellRangeFromStartTable = (isRoot) => (startCell) => getClosestTable(startCell, isRoot).bind((table) => last$2(getTableCells(table)).map((endCell) => tableCellRng(startCell, endCell)));
16044    const getCellRangeFromEndTable = (isRoot) => (endCell) => getClosestTable(endCell, isRoot).bind((table) => head(getTableCells(table)).map((startCell) => tableCellRng(startCell, endCell)));
16045    const getTableSelectionFromCellRng = (isRoot) => (cellRng) => getTableFromCellRng(cellRng, isRoot).map((table) => tableSelection(cellRng, table, getTableCells(table)));
16046    const getTableSelections = (cellRng, selectionDetails, rng, isRoot) => {
16047        if (rng.collapsed || !cellRng.forall(isExpandedCellRng)) {
16048            return Optional.none();
16049        }
16050        else if (selectionDetails.isSameTable) {
16051            const sameTableSelection = cellRng.bind(getTableSelectionFromCellRng(isRoot));
16052            return Optional.some({
16053                start: sameTableSelection,
16054                end: sameTableSelection
16055            });
16056        }
16057        else {
16058            // Covers partial table selection (either start or end will have a tableSelection) and multitable selection (both start and end will have a tableSelection)
16059            const startCell = getClosestCell$1(rng.startContainer, isRoot);
16060            const endCell = getClosestCell$1(rng.endContainer, isRoot);
16061            const startTableSelection = startCell
16062                .bind(getCellRangeFromStartTable(isRoot))
16063                .bind(getTableSelectionFromCellRng(isRoot));
16064            const endTableSelection = endCell
16065                .bind(getCellRangeFromEndTable(isRoot))
16066                .bind(getTableSelectionFromCellRng(isRoot));
16067            return Optional.some({
16068                start: startTableSelection,
16069                end: endTableSelection
16070            });
16071        }
16072    };
16073    const getCellIndex = (cells, cell) => findIndex$2(cells, (x) => eq(x, cell));
16074    const getSelectedCells = (tableSelection) => lift2(getCellIndex(tableSelection.cells, tableSelection.rng.start), getCellIndex(tableSelection.cells, tableSelection.rng.end), (startIndex, endIndex) => tableSelection.cells.slice(startIndex, endIndex + 1));
16075    const isSingleCellTableContentSelected = (optCellRng, rng, isRoot) => optCellRng.exists((cellRng) => isSingleCellTable(cellRng, isRoot) && hasAllContentsSelected(cellRng.start, rng));
16076    const unselectCells = (rng, selectionDetails) => {
16077        const { startTable, endTable } = selectionDetails;
16078        const otherContentRng = rng.cloneRange();
16079        // If the table is some, it should be unselected (works for single table and multitable cases)
16080        startTable.each((table) => otherContentRng.setStartAfter(table.dom));
16081        endTable.each((table) => otherContentRng.setEndBefore(table.dom));
16082        return otherContentRng;
16083    };
16084    const handleSingleTable = (cellRng, selectionDetails, rng, isRoot) => getTableSelections(cellRng, selectionDetails, rng, isRoot)
16085        .bind(({ start, end }) => start.or(end))
16086        .bind((tableSelection) => {
16087        const { isSameTable } = selectionDetails;
16088        const selectedCells = getSelectedCells(tableSelection).getOr([]);
16089        if (isSameTable && tableSelection.cells.length === selectedCells.length) {
16090            return Optional.some(deleteAction.fullTable(tableSelection.table));
16091        }
16092        else if (selectedCells.length > 0) {
16093            if (isSameTable) {
16094                return Optional.some(deleteAction.partialTable(selectedCells, Optional.none()));
16095            }
16096            else {
16097                const otherContentRng = unselectCells(rng, selectionDetails);
16098                return Optional.some(deleteAction.partialTable(selectedCells, Optional.some({
16099                    ...selectionDetails,
16100                    rng: otherContentRng
16101                })));
16102            }
16103        }
16104        else {
16105            return Optional.none();
16106        }
16107    });
16108    const handleMultiTable = (cellRng, selectionDetails, rng, isRoot) => getTableSelections(cellRng, selectionDetails, rng, isRoot)
16109        .bind(({ start, end }) => {
16110        const startTableSelectedCells = start.bind(getSelectedCells).getOr([]);
16111        const endTableSelectedCells = end.bind(getSelectedCells).getOr([]);
16112        if (startTableSelectedCells.length > 0 && endTableSelectedCells.length > 0) {
16113            const otherContentRng = unselectCells(rng, selectionDetails);
16114            return Optional.some(deleteAction.multiTable(startTableSelectedCells, endTableSelectedCells, otherContentRng));
16115        }
16116        else {
16117            return Optional.none();
16118        }
16119    });
16120    const getActionFromRange = (root, rng) => {
16121        const isRoot = isRootFromElement(root);
16122        const optCellRng = getCellRng(rng, isRoot);
16123        const selectionDetails = getTableDetailsFromRange(rng, isRoot);
16124        if (isSingleCellTableContentSelected(optCellRng, rng, isRoot)) {
16125            // SingleCellTable
16126            return optCellRng.map((cellRng) => deleteAction.singleCellTable(rng, cellRng.start));
16127        }
16128        else if (selectionDetails.isMultiTable) {
16129            // MultiTable
16130            return handleMultiTable(optCellRng, selectionDetails, rng, isRoot);
16131        }
16132        else {
16133            // FullTable, PartialTable with no rng or PartialTable with outside rng
16134            return handleSingleTable(optCellRng, selectionDetails, rng, isRoot);
16135        }
16136    };
16137
16138    // Reset the contenteditable state and fill the content with a padding br
16139    const cleanCells = (cells) => each$e(cells, (cell) => {
16140        remove$9(cell, 'contenteditable');
16141        fillWithPaddingBr(cell);
16142    });
16143    const getOutsideBlock = (editor, container) => Optional.from(editor.dom.getParent(container, editor.dom.isBlock)).map(SugarElement.fromDom);
16144    const handleEmptyBlock = (editor, startInTable, emptyBlock) => {
16145        emptyBlock.each((block) => {
16146            if (startInTable) {
16147                // Note that we don't need to set the selection as it'll be within the table
16148                remove$8(block);
16149            }
16150            else {
16151                // Set the cursor location as it'll move when filling with padding
16152                fillWithPaddingBr(block);
16153                editor.selection.setCursorLocation(block.dom, 0);
16154            }
16155        });
16156    };
16157    const deleteContentInsideCell = (editor, cell, rng, isFirstCellInSelection) => {
16158        const insideTableRng = rng.cloneRange();
16159        if (isFirstCellInSelection) {
16160            insideTableRng.setStart(rng.startContainer, rng.startOffset);
16161            insideTableRng.setEndAfter(cell.dom.lastChild);
16162        }
16163        else {
16164            insideTableRng.setStartBefore(cell.dom.firstChild);
16165            insideTableRng.setEnd(rng.endContainer, rng.endOffset);
16166        }
16167        deleteCellContents(editor, insideTableRng, cell, false).each((action) => action());
16168    };
16169    const collapseAndRestoreCellSelection = (editor) => {
16170        const selectedCells = getCellsFromEditor(editor);
16171        const selectedNode = SugarElement.fromDom(editor.selection.getNode());
16172        if (isTableCell$3(selectedNode.dom) && isEmpty$4(editor.schema, selectedNode)) {
16173            editor.selection.setCursorLocation(selectedNode.dom, 0);
16174        }
16175        else {
16176            editor.selection.collapse(true);
16177        }
16178        // Restore the data-mce-selected attribute if multiple cells were selected, as if it was a cef element
16179        // then selection overrides would remove it as it was using an offscreen selection clone.
16180        if (selectedCells.length > 1 && exists(selectedCells, (cell) => eq(cell, selectedNode))) {
16181            set$
161814
vendor: 23,653 bytes, lines 16181-16679
16181(selectedNode, 'data-mce-selected', '1');
16182        }
16183    };
16184    /*
16185     * Runs when
16186     * - the start and end of the selection is contained within the same table (called directly from deleteRange)
16187     * - part of a table and content outside is selected
16188     */
16189    const emptySingleTableCells = (editor, cells, outsideDetails) => Optional.some(() => {
16190        const editorRng = editor.selection.getRng();
16191        const cellsToClean = outsideDetails.bind(({ rng, isStartInTable }) => {
16192            /*
16193             * Delete all content outside of the table that is in the selection
16194             * - Get the outside block before deleting the contents
16195             * - Delete the contents outside
16196             * - Handle the block outside the table if it is empty since rng.deleteContents leaves it
16197             */
16198            const outsideBlock = getOutsideBlock(editor, isStartInTable ? rng.endContainer : rng.startContainer);
16199            rng.deleteContents();
16200            handleEmptyBlock(editor, isStartInTable, outsideBlock.filter(curry(isEmpty$4, editor.schema)));
16201            /*
16202             * The only time we can have only part of the cell contents selected is when part of the selection
16203             * is outside the table (otherwise we use the Darwin fake selection, which always selects entire cells),
16204             * in which case we need to delete the contents inside and check if the entire contents of the cell have been deleted.
16205             *
16206             * Note: The endPointCell is the only cell which may have only part of its contents selected.
16207             */
16208            const endPointCell = isStartInTable ? cells[0] : cells[cells.length - 1];
16209            deleteContentInsideCell(editor, endPointCell, editorRng, isStartInTable);
16210            if (!isEmpty$4(editor.schema, endPointCell)) {
16211                return Optional.some(isStartInTable ? cells.slice(1) : cells.slice(0, -1));
16212            }
16213            else {
16214                return Optional.none();
16215            }
16216        }).getOr(cells);
16217        // Remove content from cells we need to clean
16218        cleanCells(cellsToClean);
16219        // Collapse the original selection after deleting everything
16220        collapseAndRestoreCellSelection(editor);
16221    });
16222    /*
16223     * Runs when the start of the selection is in a table and the end of the selection is in another table
16224     */
16225    const emptyMultiTableCells = (editor, startTableCells, endTableCells, betweenRng) => Optional.some(() => {
16226        const rng = editor.selection.getRng();
16227        const startCell = startTableCells[0];
16228        const endCell = endTableCells[endTableCells.length - 1];
16229        deleteContentInsideCell(editor, startCell, rng, true);
16230        deleteContentInsideCell(editor, endCell, rng, false);
16231        // Only clean empty cells, the first and last cells have the potential to still have content
16232        const startTableCellsToClean = isEmpty$4(editor.schema, startCell) ? startTableCells : startTableCells.slice(1);
16233        const endTableCellsToClean = isEmpty$4(editor.schema, endCell) ? endTableCells : endTableCells.slice(0, -1);
16234        cleanCells(startTableCellsToClean.concat(endTableCellsToClean));
16235        // Delete all content in between the start table and end table
16236        betweenRng.deleteContents();
16237        // This will collapse the selection into the cell of the start table
16238        collapseAndRestoreCellSelection(editor);
16239    });
16240    // Delete the contents of a range inside a cell. Runs on tables that are a single cell or partial selections that need to be cleaned up.
16241    const deleteCellContents = (editor, rng, cell, moveSelection = true) => Optional.some(() => {
16242        deleteRangeContents(editor, rng, cell, moveSelection);
16243    });
16244    const deleteTableElement = (editor, table) => Optional.some(() => deleteElement$2(editor, false, table));
16245    const deleteCellRange = (editor, rootElm, rng) => getActionFromRange(rootElm, rng)
16246        .bind((action) => action.fold(curry(deleteCellContents, editor), curry(deleteTableElement, editor), curry(emptySingleTableCells, editor), curry(emptyMultiTableCells, editor)));
16247    const deleteCaptionRange = (editor, caption) => emptyElement(editor, caption);
16248    const deleteTableRange = (editor, rootElm, rng, startElm) => getParentCaption(rootElm, startElm).fold(() => deleteCellRange(editor, rootElm, rng), (caption) => deleteCaptionRange(editor, caption));
16249    const deleteRange$4 = (editor, startElm, selectedCells) => {
16250        const rootNode = SugarElement.fromDom(editor.getBody());
16251        const rng = editor.selection.getRng();
16252        return selectedCells.length !== 0 ?
16253            emptySingleTableCells(editor, selectedCells, Optional.none()) :
16254            deleteTableRange(editor, rootNode, rng, startElm);
16255    };
16256    const getParentCell = (rootElm, elm) => find$2(parentsAndSelf(elm, rootElm), isTableCell$2);
16257    const getParentCaption = (rootElm, elm) => find$2(parentsAndSelf(elm, rootElm), isTag('caption'));
16258    const deleteBetweenCells = (editor, rootElm, forward, fromCell, from) => 
16259    // TODO: TINY-8865 - This may not be safe to cast as Node below and alternative solutions need to be looked into
16260    navigate(forward, editor.getBody(), from)
16261        .bind((to) => getParentCell(rootElm, SugarElement.fromDom(to.getNode()))
16262        .bind((toCell) => eq(toCell, fromCell) ? Optional.none() : Optional.some(noop)));
16263    const emptyElement = (editor, elm) => Optional.some(() => {
16264        fillWithPaddingBr(elm);
16265        editor.selection.setCursorLocation(elm.dom, 0);
16266    });
16267    const isDeleteOfLastCharPos = (fromCaption, forward, from, to) => firstPositionIn(fromCaption.dom).bind((first) => lastPositionIn(fromCaption.dom).map((last) => forward ?
16268        from.isEqual(first) && to.isEqual(last) :
16269        from.isEqual(last) && to.isEqual(first))).getOr(true);
16270    const emptyCaretCaption = (editor, elm) => emptyElement(editor, elm);
16271    const validateCaretCaption = (rootElm, fromCaption, to) => 
16272    // TODO: TINY-8865 - This may not be safe to cast as Node below and alternative solutions need to be looked into
16273    getParentCaption(rootElm, SugarElement.fromDom(to.getNode()))
16274        .fold(() => Optional.some(noop), (toCaption) => someIf(!eq(toCaption, fromCaption), noop));
16275    const deleteCaretInsideCaption = (editor, rootElm, forward, fromCaption, from) => navigate(forward, editor.getBody(), from).fold(() => Optional.some(noop), (to) => isDeleteOfLastCharPos(fromCaption, forward, from, to) ?
16276        emptyCaretCaption(editor, fromCaption) :
16277        validateCaretCaption(rootElm, fromCaption, to));
16278    const deleteCaretCells = (editor, forward, rootElm, startElm) => {
16279        const from = CaretPosition.fromRangeStart(editor.selection.getRng());
16280        return getParentCell(rootElm, startElm).bind((fromCell) => isEmpty$4(editor.schema, fromCell, { checkRootAsContent: false }) ?
16281            emptyElement(editor, fromCell) :
16282            deleteBetweenCells(editor, rootElm, forward, fromCell, from));
16283    };
16284    const deleteCaretCaption = (editor, forward, rootElm, fromCaption) => {
16285        const from = CaretPosition.fromRangeStart(editor.selection.getRng());
16286        return isEmpty$4(editor.schema, fromCaption) ?
16287            emptyElement(editor, fromCaption) :
16288            deleteCaretInsideCaption(editor, rootElm, forward, fromCaption, from);
16289    };
16290    const isNearTable = (forward, pos) => forward ? isBeforeTable(pos) : isAfterTable(pos);
16291    const isBeforeOrAfterTable = (editor, forward) => {
16292        const fromPos = CaretPosition.fromRangeStart(editor.selection.getRng());
16293        return isNearTable(forward, fromPos) || fromPosition(forward, editor.getBody(), fromPos)
16294            .exists((pos) => isNearTable(forward, pos));
16295    };
16296    const deleteCaret$3 = (editor, forward, startElm) => {
16297        const rootElm = SugarElement.fromDom(editor.getBody());
16298        return getParentCaption(rootElm, startElm).fold(() => deleteCaretCells(editor, forward, rootElm, startElm)
16299            .orThunk(() => someIf(isBeforeOrAfterTable(editor, forward), noop)), (fromCaption) => deleteCaretCaption(editor, forward, rootElm, fromCaption));
16300    };
16301    const backspaceDelete$d = (editor, forward) => {
16302        const startElm = SugarElement.fromDom(editor.selection.getStart(true));
16303        const cells = getCellsFromEditor(editor);
16304        return editor.selection.isCollapsed() && cells.length === 0 ?
16305            deleteCaret$3(editor, forward, startElm) :
16306            deleteRange$4(editor, startElm, cells);
16307    };
16308
16309    const getContentEditableRoot$1 = (root, node) => {
16310        let tempNode = node;
16311        while (tempNode && tempNode !== root) {
16312            if (isContentEditableTrue$3(tempNode) || isContentEditableFalse$a(tempNode)) {
16313                return tempNode;
16314            }
16315            tempNode = tempNode.parentNode;
16316        }
16317        return null;
16318    };
16319
16320    const internalAttributesPrefixes = [
16321        'data-ephox-',
16322        'data-mce-',
16323        'data-alloy-',
16324        'data-snooker-',
16325        '_'
16326    ];
16327    /**
16328     * Utility class for various element specific functions.
16329     *
16330     * @private
16331     * @class tinymce.dom.ElementUtils
16332     */
16333    const each$9 = Tools.each;
16334    const ElementUtils = (editor) => {
16335        const dom = editor.dom;
16336        const internalAttributes = new Set(editor.serializer.getTempAttrs());
16337        /**
16338         * Compares two nodes and checks if it's attributes and styles matches.
16339         * This doesn't compare classes as items since their order is significant.
16340         *
16341         * @method compare
16342         * @param {Node} node1 First node to compare with.
16343         * @param {Node} node2 Second node to compare with.
16344         * @return {Boolean} True/false if the nodes are the same or not.
16345         */
16346        const compare = (node1, node2) => {
16347            // Not the same name or type
16348            if (node1.nodeName !== node2.nodeName || node1.nodeType !== node2.nodeType) {
16349                return false;
16350            }
16351            /**
16352             * Returns all the nodes attributes excluding internal ones, styles and classes.
16353             *
16354             * @private
16355             * @param {Node} node Node to get attributes from.
16356             * @return {Object} Name/value object with attributes and attribute values.
16357             */
16358            const getAttribs = (node) => {
16359                const attribs = {};
16360                each$9(dom.getAttribs(node), (attr) => {
16361                    const name = attr.nodeName.toLowerCase();
16362                    // Don't compare internal attributes or style
16363                    if (name !== 'style' && !isAttributeInternal(name)) {
16364                        attribs[name] = dom.getAttrib(node, name);
16365                    }
16366                });
16367                return attribs;
16368            };
16369            /**
16370             * Compares two objects checks if it's key + value exists in the other one.
16371             *
16372             * @private
16373             * @param {Object} obj1 First object to compare.
16374             * @param {Object} obj2 Second object to compare.
16375             * @return {Boolean} True/false if the objects matches or not.
16376             */
16377            const compareObjects = (obj1, obj2) => {
16378                for (const name in obj1) {
16379                    // Obj1 has item obj2 doesn't have
16380                    if (has$2(obj1, name)) {
16381                        const value = obj2[name];
16382                        // Obj2 doesn't have obj1 item
16383                        if (isUndefined(value)) {
16384                            return false;
16385                        }
16386                        // Obj2 item has a different value
16387                        if (obj1[name] !== value) {
16388                            return false;
16389                        }
16390                        // Delete similar value
16391                        delete obj2[name];
16392                    }
16393                }
16394                // Check if obj 2 has something obj 1 doesn't have
16395                for (const name in obj2) {
16396                    // Obj2 has item obj1 doesn't have
16397                    if (has$2(obj2, name)) {
16398                        return false;
16399                    }
16400                }
16401                return true;
16402            };
16403            if (isElement$7(node1) && isElement$7(node2)) {
16404                // Attribs are not the same
16405                if (!compareObjects(getAttribs(node1), getAttribs(node2))) {
16406                    return false;
16407                }
16408                // Styles are not the same
16409                if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style')))) {
16410                    return false;
16411                }
16412            }
16413            return !isBookmarkNode$1(node1) && !isBookmarkNode$1(node2);
16414        };
16415        const isAttributeInternal = (attributeName) => exists(internalAttributesPrefixes, (value) => startsWith(attributeName, value)) || internalAttributes.has(attributeName);
16416        return {
16417            compare,
16418            isAttributeInternal
16419        };
16420    };
16421
16422    const getNormalizedPoint$1 = (container, offset) => {
16423        if (isText$b(container)) {
16424            return { container, offset };
16425        }
16426        const node = RangeUtils.getNode(container, offset);
16427        if (isText$b(node)) {
16428            return {
16429                container: node,
16430                offset: offset >= container.childNodes.length ? node.data.length : 0
16431            };
16432        }
16433        else if (node.previousSibling && isText$b(node.previousSibling)) {
16434            return {
16435                container: node.previousSibling,
16436                offset: node.previousSibling.data.length
16437            };
16438        }
16439        else if (node.nextSibling && isText$b(node.nextSibling)) {
16440            return {
16441                container: node.nextSibling,
16442                offset: 0
16443            };
16444        }
16445        return { container, offset };
16446    };
16447    const normalizeRange$1 = (rng) => {
16448        const outRng = rng.cloneRange();
16449        const rangeStart = getNormalizedPoint$1(rng.startContainer, rng.startOffset);
16450        outRng.setStart(rangeStart.container, rangeStart.offset);
16451        const rangeEnd = getNormalizedPoint$1(rng.endContainer, rng.endOffset);
16452        outRng.setEnd(rangeEnd.container, rangeEnd.offset);
16453        return outRng;
16454    };
16455
16456    const DOM$b = DOMUtils.DOM;
16457    const defaultMarker = () => DOM$b.create('span', { 'data-mce-type': 'bookmark' });
16458    const setupEndPoint = (container, offset, createMarker) => {
16459        if (isElement$7(container)) {
16460            const offsetNode = createMarker();
16461            if (container.hasChildNodes()) {
16462                if (offset === container.childNodes.length) {
16463                    container.appendChild(offsetNode);
16464                }
16465                else {
16466                    container.insertBefore(offsetNode, container.childNodes[offset]);
16467                }
16468            }
16469            else {
16470                container.appendChild(offsetNode);
16471            }
16472            return { container: offsetNode, offset: 0 };
16473        }
16474        else {
16475            return { container, offset };
16476        }
16477    };
16478    const restoreEndPoint = (container, offset) => {
16479        const nodeIndex = (container) => {
16480            let node = container.parentNode?.firstChild;
16481            let idx = 0;
16482            while (node) {
16483                if (node === container) {
16484                    return idx;
16485                }
16486                // Skip data-mce-type=bookmark nodes
16487                if (!isElement$7(node) || node.getAttribute('data-mce-type') !== 'bookmark') {
16488                    idx++;
16489                }
16490                node = node.nextSibling;
16491            }
16492            return -1;
16493        };
16494        if (isElement$7(container) && isNonNullable(container.parentNode)) {
16495            const node = container;
16496            offset = nodeIndex(container);
16497            container = container.parentNode;
16498            DOM$b.remove(node);
16499            if (!container.hasChildNodes() && DOM$b.isBlock(container)) {
16500                container.appendChild(DOM$b.create('br'));
16501            }
16502        }
16503        return { container, offset };
16504    };
16505    const createNormalizedRange = (startContainer, startOffset, endContainer, endOffset) => {
16506        const rng = DOM$b.createRng();
16507        rng.setStart(startContainer, startOffset);
16508        rng.setEnd(endContainer, endOffset);
16509        return normalizeRange$1(rng);
16510    };
16511    /**
16512     * Returns a range bookmark. This will convert indexed bookmarks into temporary span elements with
16513     * index 0 so that they can be restored properly after the DOM has been modified. Text bookmarks will not have spans
16514     * added to them since they can be restored after a dom operation.
16515     *
16516     * So this: <p><b>|</b><b>|</b></p>
16517     * becomes: <p><b><span data-mce-type="bookmark">|</span></b><b data-mce-type="bookmark">|</span></b></p>
16518     */
16519    const createBookmark = (rng, createMarker = defaultMarker) => {
16520        const { container: startContainer, offset: startOffset } = setupEndPoint(rng.startContainer, rng.startOffset, createMarker);
16521        if (rng.collapsed) {
16522            return { startContainer, startOffset };
16523        }
16524        else {
16525            const { container: endContainer, offset: endOffset } = setupEndPoint(rng.endContainer, rng.endOffset, createMarker);
16526            return { startContainer, startOffset, endContainer, endOffset };
16527        }
16528    };
16529    const resolveBookmark = (bookmark) => {
16530        const { container: startContainer, offset: startOffset } = restoreEndPoint(bookmark.startContainer, bookmark.startOffset);
16531        if (!isUndefined(bookmark.endContainer) && !isUndefined(bookmark.endOffset)) {
16532            const { container: endContainer, offset: endOffset } = restoreEndPoint(bookmark.endContainer, bookmark.endOffset);
16533            return createNormalizedRange(startContainer, startOffset, endContainer, endOffset);
16534        }
16535        else {
16536            return createNormalizedRange(startContainer, startOffset, startContainer, startOffset);
16537        }
16538    };
16539
16540    const applyStyles = (dom, elm, format, vars) => {
16541        Tools.each(format.styles, (value, name) => {
16542            dom.setStyle(elm, name, replaceVars(value, vars));
16543        });
16544        // Needed for the WebKit span spam bug
16545        // TODO: Remove this once WebKit/Blink fixes this
16546        if (format.styles) {
16547            const styleVal = dom.getAttrib(elm, 'style');
16548            if (styleVal) {
16549                dom.setAttrib(elm, 'data-mce-style', styleVal);
16550            }
16551        }
16552    };
16553    const setElementFormat = (ed, elm, fmt, vars, node) => {
16554        const dom = ed.dom;
16555        if (isFunction(fmt.onformat)) {
16556            fmt.onformat(elm, fmt, vars, node);
16557        }
16558        applyStyles(dom, elm, fmt, vars);
16559        Tools.each(fmt.attributes, (value, name) => {
16560            dom.setAttrib(elm, name, replaceVars(value, vars));
16561        });
16562        Tools.each(fmt.classes, (value) => {
16563            const newValue = replaceVars(value, vars);
16564            if (!dom.hasClass(elm, newValue)) {
16565                dom.addClass(elm, newValue);
16566            }
16567        });
16568    };
16569
16570    const isApplyFormat = (format) => !isArray$1(format.attributes) && !isArray$1(format.styles);
16571
16572    const isEq$3 = isEq$5;
16573    const matchesUnInheritedFormatSelector = (ed, node, name) => {
16574        const formatList = ed.formatter.get(name);
16575        if (formatList) {
16576            for (let i = 0; i < formatList.length; i++) {
16577                const format = formatList[i];
16578                if (isSelectorFormat(format) && format.inherit === false && ed.dom.is(node, format.selector)) {
16579                    return true;
16580                }
16581            }
16582        }
16583        return false;
16584    };
16585    const matchParents = (editor, node, name, vars, similar) => {
16586        const root = editor.dom.getRoot();
16587        if (node === root) {
16588            return false;
16589        }
16590        // Find first node with similar format settings
16591        const matchedNode = editor.dom.getParent(node, (elm) => {
16592            if (matchesUnInheritedFormatSelector(editor, elm, name)) {
16593                return true;
16594            }
16595            return elm.parentNode === root || !!matchNode$1(editor, elm, name, vars, true);
16596        });
16597        // Do an exact check on the similar format element
16598        return !!matchNode$1(editor, matchedNode, name, vars, similar);
16599    };
16600    const matchName = (dom, node, format) => {
16601        // Check for inline match
16602        if (isInlineFormat(format) && isEq$3(node, format.inline)) {
16603            return true;
16604        }
16605        // Check for block match
16606        if (isBlockFormat(format) && isEq$3(node, format.block)) {
16607            return true;
16608        }
16609        // Check for selector match
16610        if (isSelectorFormat(format)) {
16611            return isElement$7(node) && dom.is(node, format.selector);
16612        }
16613        return false;
16614    };
16615    const matchItems = (dom, node, format, itemName, similar, vars) => {
16616        const items = format[itemName];
16617        const matchAttributes = itemName === 'attributes';
16618        // Custom match
16619        if (isFunction(format.onmatch)) {
16620            // onmatch is generic in a way that we can't really express without casting
16621            return format.onmatch(node, format, itemName);
16622        }
16623        // Check all items
16624        if (items) {
16625            // Non indexed object
16626            if (!isArrayLike(items)) {
16627                for (const key in items) {
16628                    if (has$2(items, key)) {
16629                        const value = matchAttributes ? dom.getAttrib(node, key) : getStyle(dom, node, key);
16630                        const expectedValue = replaceVars(items[key], vars);
16631                        const isEmptyValue = isNullable(value) || isEmpty$5(value);
16632                        if (isEmptyValue && isNullable(expectedValue)) {
16633                            continue;
16634                        }
16635                        if (similar && isEmptyValue && !format.exact) {
16636                            return false;
16637                        }
16638                        if ((!similar || format.exact) && !isEq$3(value, normalizeStyleValue(expectedValue, key))) {
16639                            return false;
16640                        }
16641                    }
16642                }
16643            }
16644            else {
16645                // Only one match needed for indexed arrays
16646                for (let i = 0; i < items.length; i++) {
16647                    if (matchAttributes ? dom.getAttrib(node, items[i]) : getStyle(dom, node, items[i])) {
16648                        return true;
16649                    }
16650                }
16651            }
16652        }
16653        return true;
16654    };
16655    const matchNode$1 = (ed, node, name, vars, similar) => {
16656        const formatList = ed.formatter.get(name);
16657        const dom = ed.dom;
16658        if (formatList && isElement$7(node)) {
16659            // Check each format in list
16660            for (let i = 0; i < formatList.length; i++) {
16661                const format = formatList[i];
16662                // Name name, attributes, styles and classes
16663                if (matchName(ed.dom, node, format) && matchItems(dom, node, format, 'attributes', similar, vars) && matchItems(dom, node, format, 'styles', similar, vars)) {
16664                    // Match classes
16665                    const classes = format.classes;
16666                    if (classes) {
16667                        for (let x = 0; x < classes.length; x++) {
16668                            if (!ed.dom.hasClass(node, replaceVars(classes[x], vars))) {
16669                                return;
16670                            }
16671                        }
16672                    }
16673                    return format;
16674                }
16675            }
16676        }
16677        return undefined;
16678    };
16679    const 
16679match$2 = (editor, name, vars, node, similar) => {
16680        // Check specified node
16681        if (node) {
16682            return matchParents(editor, node, name, vars, similar);
16683        }
vendor: 8,525 bytes, lines 16684-16880
16684        // Check selected node
16685        node = editor.selection.getNode();
16686        if (matchParents(editor, node, name, vars, similar)) {
16687            return true;
16688        }
16689        // Check start node if it's different
16690        const startNode = editor.selection.getStart();
16691        if (startNode !== node) {
16692            if (matchParents(editor, startNode, name, vars, similar)) {
16693                return true;
16694            }
16695        }
16696        return false;
16697    };
16698    const matchAll = (editor, names, vars) => {
16699        const matchedFormatNames = [];
16700        const checkedMap = {};
16701        // Check start of selection for formats
16702        const startElement = editor.selection.getStart();
16703        editor.dom.getParent(startElement, (node) => {
16704            for (let i = 0; i < names.length; i++) {
16705                const name = names[i];
16706                if (!checkedMap[name] && matchNode$1(editor, node, name, vars)) {
16707                    checkedMap[name] = true;
16708                    matchedFormatNames.push(name);
16709                }
16710            }
16711        }, editor.dom.getRoot());
16712        return matchedFormatNames;
16713    };
16714    const closest = (editor, names) => {
16715        const isRoot = (elm) => eq(elm, SugarElement.fromDom(editor.getBody()));
16716        const match = (elm, name) => matchNode$1(editor, elm.dom, name) ? Optional.some(name) : Optional.none();
16717        return Optional.from(editor.selection.getStart(true)).bind((rawElm) => closest$1(SugarElement.fromDom(rawElm), (elm) => findMap(names, (name) => match(elm, name)), isRoot)).getOrNull();
16718    };
16719    const canApply = (editor, name) => {
16720        const formatList = editor.formatter.get(name);
16721        const dom = editor.dom;
16722        if (formatList && editor.selection.isEditable()) {
16723            const startNode = editor.selection.getStart();
16724            const parents = getParents$2(dom, startNode);
16725            for (let x = formatList.length - 1; x >= 0; x--) {
16726                const format = formatList[x];
16727                // Format is not selector based then always return TRUE
16728                if (!isSelectorFormat(format)) {
16729                    return true;
16730                }
16731                for (let i = parents.length - 1; i >= 0; i--) {
16732                    if (dom.is(parents[i], format.selector)) {
16733                        return true;
16734                    }
16735                }
16736            }
16737        }
16738        return false;
16739    };
16740    /**
16741     *  Get all of the format names present on the specified node
16742     */
16743    const matchAllOnNode = (editor, node, formatNames) => foldl(formatNames, (acc, name) => {
16744        const matchSimilar = isVariableFormatName(editor, name);
16745        if (editor.formatter.matchNode(node, name, {}, matchSimilar)) {
16746            return acc.concat([name]);
16747        }
16748        else {
16749            return acc;
16750        }
16751    }, []);
16752
16753    const ZWSP = ZWSP$1;
16754    const importNode = (ownerDocument, node) => {
16755        return ownerDocument.importNode(node, true);
16756    };
16757    const findFirstTextNode = (node) => {
16758        if (node) {
16759            const walker = new DomTreeWalker(node, node);
16760            for (let tempNode = walker.current(); tempNode; tempNode = walker.next()) {
16761                if (isText$b(tempNode)) {
16762                    return tempNode;
16763                }
16764            }
16765        }
16766        return null;
16767    };
16768    const createCaretContainer = (fill) => {
16769        const caretContainer = SugarElement.fromTag('span');
16770        setAll$1(caretContainer, {
16771            // style: 'color:red',
16772            'id': CARET_ID,
16773            'data-mce-bogus': '1',
16774            'data-mce-type': 'format-caret'
16775        });
16776        if (fill) {
16777            append$1(caretContainer, SugarElement.fromText(ZWSP));
16778        }
16779        return caretContainer;
16780    };
16781    const trimZwspFromCaretContainer = (caretContainerNode) => {
16782        const textNode = findFirstTextNode(caretContainerNode);
16783        if (textNode && textNode.data.charAt(0) === ZWSP) {
16784            textNode.deleteData(0, 1);
16785        }
16786        return textNode;
16787    };
16788    const removeCaretContainerNode = (editor, node, moveCaret) => {
16789        const dom = editor.dom, selection = editor.selection;
16790        if (isCaretContainerEmpty(node)) {
16791            deleteElement$2(editor, false, SugarElement.fromDom(node), moveCaret, true);
16792        }
16793        else {
16794            const rng = selection.getRng();
16795            const block = dom.getParent(node, dom.isBlock);
16796            // Store the current selection offsets
16797            const startContainer = rng.startContainer;
16798            const startOffset = rng.startOffset;
16799            const endContainer = rng.endContainer;
16800            const endOffset = rng.endOffset;
16801            const textNode = trimZwspFromCaretContainer(node);
16802            dom.remove(node, true);
16803            // Restore the selection after unwrapping the node and removing the zwsp
16804            if (startContainer === textNode && startOffset > 0) {
16805                rng.setStart(textNode, startOffset - 1);
16806            }
16807            if (endContainer === textNode && endOffset > 0) {
16808                rng.setEnd(textNode, endOffset - 1);
16809            }
16810            if (block && dom.isEmpty(block)) {
16811                fillWithPaddingBr(SugarElement.fromDom(block));
16812            }
16813            selection.setRng(rng);
16814        }
16815    };
16816    // Removes the caret container for the specified node or all on the current document
16817    const removeCaretContainer = (editor, node, moveCaret) => {
16818        const dom = editor.dom, selection = editor.selection;
16819        if (!node) {
16820            node = getParentCaretContainer(editor.getBody(), selection.getStart());
16821            if (!node) {
16822                while ((node = dom.get(CARET_ID))) {
16823                    removeCaretContainerNode(editor, node, moveCaret);
16824                }
16825            }
16826        }
16827        else {
16828            removeCaretContainerNode(editor, node, moveCaret);
16829        }
16830    };
16831    const insertCaretContainerNode = (editor, caretContainer, formatNode) => {
16832        const dom = editor.dom;
16833        const block = dom.getParent(formatNode, curry(isTextBlock$2, editor.schema));
16834        if (block && dom.isEmpty(block)) {
16835            // Replace formatNode with caretContainer when removing format from empty block like <p><b>|</b></p>
16836            formatNode.parentNode?.replaceChild(caretContainer, formatNode);
16837        }
16838        else {
16839            removeTrailingBr(SugarElement.fromDom(formatNode));
16840            if (dom.isEmpty(formatNode)) {
16841                formatNode.parentNode?.replaceChild(caretContainer, formatNode);
16842            }
16843            else {
16844                dom.insertAfter(caretContainer, formatNode);
16845            }
16846        }
16847    };
16848    const appendNode = (parentNode, node) => {
16849        parentNode.appendChild(node);
16850        return node;
16851    };
16852    const insertFormatNodesIntoCaretContainer = (formatNodes, caretContainer) => {
16853        const innerMostFormatNode = foldr(formatNodes, (parentNode, formatNode) => {
16854            return appendNode(parentNode, formatNode.cloneNode(false));
16855        }, caretContainer);
16856        const doc = innerMostFormatNode.ownerDocument ?? document;
16857        return appendNode(innerMostFormatNode, doc.createTextNode(ZWSP));
16858    };
16859    const cleanFormatNode = (editor, caretContainer, formatNode, name, vars, similar) => {
16860        const formatter = editor.formatter;
16861        const dom = editor.dom;
16862        // Find all formats present on the format node
16863        const validFormats = filter$5(keys(formatter.get()), (formatName) => formatName !== name && !contains$1(formatName, 'removeformat'));
16864        const matchedFormats = matchAllOnNode(editor, formatNode, validFormats);
16865        // Filter out any matched formats that are 'visually' equivalent to the 'name' format since they are not unique formats on the node
16866        const uniqueFormats = filter$5(matchedFormats, (fmtName) => !areSimilarFormats(editor, fmtName, name));
16867        // If more than one format is present, then there's additional formats that should be retained. So clone the node,
16868        // remove the format and then return cleaned format node
16869        if (uniqueFormats.length > 0) {
16870            const clonedFormatNode = formatNode.cloneNode(false);
16871            dom.add(caretContainer, clonedFormatNode);
16872            formatter.remove(name, vars, clonedFormatNode, similar);
16873            dom.remove(clonedFormatNode);
16874            return Optional.some(clonedFormatNode);
16875        }
16876        else {
16877            return Optional.none();
16878        }
16879    };
16880    const normalizeNbsps = (node) => set$1(node, get$4
vendor: 10,188 bytes, lines 16880-17091
16880(node).replace(new RegExp(`${nbsp}$`), ' '));
16881    const normalizeNbspsBetween = (editor, caretContainer) => {
16882        const handler = () => {
16883            if (caretContainer !== null && !editor.dom.isEmpty(caretContainer)) {
16884                prevSibling(SugarElement.fromDom(caretContainer)).each((node) => {
16885                    if (isText$c(node)) {
16886                        normalizeNbsps(node);
16887                    }
16888                    else {
16889                        descendant$2(node, (e) => isText$c(e)).each((textNode) => {
16890                            if (isText$c(textNode)) {
16891                                normalizeNbsps(textNode);
16892                            }
16893                        });
16894                    }
16895                });
16896            }
16897        };
16898        editor.once('input', (e) => {
16899            if (e.data && !isWhiteSpace(e.data)) {
16900                if (!e.isComposing) {
16901                    handler();
16902                }
16903                else {
16904                    editor.once('compositionend', () => {
16905                        handler();
16906                    });
16907                }
16908            }
16909        });
16910    };
16911    const applyCaretFormat = (editor, name, vars) => {
16912        let caretContainer;
16913        const selection = editor.selection;
16914        const formatList = editor.formatter.get(name);
16915        if (!formatList) {
16916            return;
16917        }
16918        const selectionRng = selection.getRng();
16919        let offset = selectionRng.startOffset;
16920        const container = selectionRng.startContainer;
16921        const text = container.nodeValue;
16922        caretContainer = getParentCaretContainer(editor.getBody(), selection.getStart());
16923        // Expand to word if caret is in the middle of a text node and the char before/after is a alpha numeric character
16924        const wordcharRegex = /[^\s\u00a0\u00ad\u200b\ufeff]/;
16925        if (text && offset > 0 && offset < text.length &&
16926            wordcharRegex.test(text.charAt(offset)) && wordcharRegex.test(text.charAt(offset - 1))) {
16927            // Get bookmark of caret position
16928            const bookmark = selection.getBookmark();
16929            // Collapse bookmark range (WebKit)
16930            selectionRng.collapse(true);
16931            // Expand the range to the closest word and split it at those points
16932            let rng = expandRng(editor.dom, selectionRng, formatList);
16933            rng = split(rng);
16934            // Apply the format to the range
16935            editor.formatter.apply(name, vars, rng);
16936            // Move selection back to caret position
16937            selection.moveToBookmark(bookmark);
16938        }
16939        else {
16940            let textNode = caretContainer ? findFirstTextNode(caretContainer) : null;
16941            if (!caretContainer || textNode?.data !== ZWSP) {
16942                // Need to import the node into the document on IE or we get a lovely WrongDocument exception
16943                caretContainer = importNode(editor.getDoc(), createCaretContainer(true).dom);
16944                textNode = caretContainer.firstChild;
16945                selectionRng.insertNode(caretContainer);
16946                offset = 1;
16947                normalizeNbspsBetween(editor, caretContainer);
16948                editor.formatter.apply(name, vars, caretContainer);
16949            }
16950            else {
16951                editor.formatter.apply(name, vars, caretContainer);
16952            }
16953            // Move selection to text node
16954            selection.setCursorLocation(textNode, offset);
16955        }
16956    };
16957    const removeCaretFormat = (editor, name, vars, similar) => {
16958        const dom = editor.dom;
16959        const selection = editor.selection;
16960        let hasContentAfter = false;
16961        const formatList = editor.formatter.get(name);
16962        if (!formatList) {
16963            return;
16964        }
16965        const rng = selection.getRng();
16966        const container = rng.startContainer;
16967        const offset = rng.startOffset;
16968        let node = container;
16969        if (isText$b(container)) {
16970            if (offset !== container.data.length) {
16971                hasContentAfter = true;
16972            }
16973            node = node.parentNode;
16974        }
16975        const parents = [];
16976        let formatNode;
16977        while (node) {
16978            if (matchNode$1(editor, node, name, vars, similar)) {
16979                formatNode = node;
16980                break;
16981            }
16982            if (node.nextSibling) {
16983                hasContentAfter = true;
16984            }
16985            parents.push(node);
16986            node = node.parentNode;
16987        }
16988        // Node doesn't have the specified format
16989        if (!formatNode) {
16990            return;
16991        }
16992        // Is there contents after the caret then remove the format on the element
16993        if (hasContentAfter) {
16994            const bookmark = selection.getBookmark();
16995            // Collapse bookmark range (WebKit)
16996            rng.collapse(true);
16997            // Expand the range to the closest word and split it at those points
16998            let expandedRng = expandRng(dom, rng, formatList, { includeTrailingSpace: true });
16999            expandedRng = split(expandedRng);
17000            // TODO: Figure out how on earth this works, as it shouldn't since remove format
17001            //  definitely seems to require an actual Range
17002            editor.formatter.remove(name, vars, expandedRng, similar);
17003            selection.moveToBookmark(bookmark);
17004        }
17005        else {
17006            const caretContainer = getParentCaretContainer(editor.getBody(), formatNode);
17007            const parentsAfter = isNonNullable(caretContainer) ? dom.getParents(formatNode.parentNode, always, caretContainer) : [];
17008            const newCaretContainer = createCaretContainer(false).dom;
17009            insertCaretContainerNode(editor, newCaretContainer, caretContainer ?? formatNode);
17010            const cleanedFormatNode = cleanFormatNode(editor, newCaretContainer, formatNode, name, vars, similar);
17011            const caretTextNode = insertFormatNodesIntoCaretContainer([
17012                ...parents,
17013                ...cleanedFormatNode.toArray(),
17014                ...parentsAfter
17015            ], newCaretContainer);
17016            if (caretContainer) {
17017                removeCaretContainerNode(editor, caretContainer, isNonNullable(caretContainer));
17018            }
17019            selection.setCursorLocation(caretTextNode, 1);
17020            normalizeNbspsBetween(editor, newCaretContainer);
17021            if (dom.isEmpty(formatNode)) {
17022                dom.remove(formatNode);
17023            }
17024        }
17025    };
17026    const disableCaretContainer = (editor, keyCode, moveCaret) => {
17027        const selection = editor.selection, body = editor.getBody();
17028        removeCaretContainer(editor, null, moveCaret);
17029        // Remove caret container if it's empty
17030        if ((keyCode === 8 || keyCode === 46) && selection.isCollapsed() && selection.getStart().innerHTML === ZWSP) {
17031            removeCaretContainer(editor, getParentCaretContainer(body, selection.getStart()), true);
17032        }
17033        // Remove caret container on keydown and it's left/right arrow keys
17034        if (keyCode === 37 || keyCode === 39) {
17035            removeCaretContainer(editor, getParentCaretContainer(body, selection.getStart()), true);
17036        }
17037    };
17038    const endsWithNbsp = (element) => isText$b(element) && endsWith(element.data, nbsp);
17039    const setup$B = (editor) => {
17040        editor.on('mouseup keydown', (e) => {
17041            disableCaretContainer(editor, e.keyCode, endsWithNbsp(editor.selection.getRng().endContainer));
17042        });
17043    };
17044    const createCaretFormat = (formatNodes) => {
17045        const caretContainer = createCaretContainer(false);
17046        const innerMost = insertFormatNodesIntoCaretContainer(formatNodes, caretContainer.dom);
17047        return { caretContainer, caretPosition: CaretPosition(innerMost, 0) };
17048    };
17049    const replaceWithCaretFormat = (targetNode, formatNodes) => {
17050        const { caretContainer, caretPosition } = createCaretFormat(formatNodes);
17051        before$4(SugarElement.fromDom(targetNode), caretContainer);
17052        remove$8(SugarElement.fromDom(targetNode));
17053        return caretPosition;
17054    };
17055    const createCaretFormatAtStart$1 = (rng, formatNodes) => {
17056        const { caretContainer, caretPosition } = createCaretFormat(formatNodes);
17057        rng.insertNode(caretContainer.dom);
17058        return caretPosition;
17059    };
17060    const isFormatElement = (editor, element) => {
17061        if (isCaretNode(element.dom)) {
17062            return false;
17063        }
17064        const inlineElements = editor.schema.getTextInlineElements();
17065        return has$2(inlineElements, name(element)) && !isCaretNode(element.dom) && !isBogus$1(element.dom);
17066    };
17067
17068    const listItemStyles = ['fontWeight', 'fontStyle', 'color', 'fontSize', 'fontFamily'];
17069    const hasListStyles = (fmt) => isObject(fmt.styles) && exists(keys(fmt.styles), (name) => contains$2(listItemStyles, name));
17070    const findExpandedListItemFormat = (formats) => find$2(formats, (fmt) => isInlineFormat(fmt) && fmt.inline === 'span' && hasListStyles(fmt));
17071    const getExpandedListItemFormat = (formatter, format) => {
17072        const formatList = formatter.get(format);
17073        return isArray$1(formatList) ? findExpandedListItemFormat(formatList) : Optional.none();
17074    };
17075    const isRngStartAtStartOfElement = (rng, elm) => prevPosition(elm, CaretPosition.fromRangeStart(rng)).isNone();
17076    const isRngEndAtEndOfElement = (rng, elm) => {
17077        return nextPosition(elm, CaretPosition.fromRangeEnd(rng))
17078            .exists((pos) => !isBr$7(pos.getNode()) || nextPosition(elm, pos).isSome()) === false;
17079    };
17080    const isEditableListItem = (dom) => (elm) => isListItem$3(elm) && dom.isEditable(elm);
17081    // TINY-13197: If the content is wrapped inside a block element, the first block returned by getSelectedBlocks() is not LI, even when the content is fully selected.
17082    // However, the second and subsequent do return LI as the selected block so only the first block needs to be adjusted
17083    const getAndOnlyNormalizeFirstBlockIf = (selection, pred) => map$3(selection.getSelectedBlocks(), (block, i) => {
17084        if (i === 0 && pred(block)) {
17085            return selection.dom.getParent(block, isListItem$3) ?? block;
17086        }
17087        else {
17088            return block;
17089        }
17090    });
17091    
vendor: 1,100 bytes, lines 17091-17108
17091const getFullySelectedBlocks = (selection) => {
17092        if (selection.isCollapsed()) {
17093            return [];
17094        }
17095        const rng = selection.getRng();
17096        const blocks = getAndOnlyNormalizeFirstBlockIf(selection, (el) => isRngStartAtStartOfElement(rng, el) && !isListItem$3(el));
17097        if (blocks.length === 1) {
17098            return isRngStartAtStartOfElement(rng, blocks[0]) && isRngEndAtEndOfElement(rng, blocks[0]) ? blocks : [];
17099        }
17100        else {
17101            const first = head(blocks).filter((elm) => isRngStartAtStartOfElement(rng, elm)).toArray();
17102            const last = last$2(blocks).filter((elm) => isRngEndAtEndOfElement(rng, elm)).toArray();
17103            const middle = blocks.slice(1, -1);
17104            return first.concat(middle).concat(last);
17105        }
17106    };
17107    const getFullySelectedListItems = (selection) => filter$5(getFullySelectedBlocks(selection), isEditableListItem(selection.dom));
17108    const getPartiallySelectedListItems = (selection) => filter$5(getAndOnlyNormalizeFirstBlockIf(selection, (el) => !isListItem$3(el)), isEditableListItem(selection.dom))
vendor: 10,081 bytes, lines 17108-17350
17108;
17109
17110    const each$8 = Tools.each;
17111    const isElementNode = (node) => isElement$7(node) && !isBookmarkNode$1(node) && !isCaretNode(node) && !isBogus$1(node);
17112    const findElementSibling = (node, siblingName) => {
17113        for (let sibling = node; sibling; sibling = sibling[siblingName]) {
17114            if (isText$b(sibling) && isNotEmpty(sibling.data)) {
17115                return node;
17116            }
17117            if (isElement$7(sibling) && !isBookmarkNode$1(sibling)) {
17118                return sibling;
17119            }
17120        }
17121        return node;
17122    };
17123    const mergeSiblingsNodes = (editor, prev, next) => {
17124        const elementUtils = ElementUtils(editor);
17125        const isPrevEditable = isHTMLElement(prev) && editor.dom.isEditable(prev);
17126        const isNextEditable = isHTMLElement(next) && editor.dom.isEditable(next);
17127        // Check if next/prev exists and that they are elements
17128        if (isPrevEditable && isNextEditable) {
17129            // If previous sibling is empty then jump over it
17130            const prevSibling = findElementSibling(prev, 'previousSibling');
17131            const nextSibling = findElementSibling(next, 'nextSibling');
17132            // Compare next and previous nodes
17133            if (elementUtils.compare(prevSibling, nextSibling)) {
17134                // Append nodes between
17135                for (let sibling = prevSibling.nextSibling; sibling && sibling !== nextSibling;) {
17136                    const tmpSibling = sibling;
17137                    sibling = sibling.nextSibling;
17138                    prevSibling.appendChild(tmpSibling);
17139                }
17140                editor.dom.remove(nextSibling);
17141                Tools.each(Tools.grep(nextSibling.childNodes), (node) => {
17142                    prevSibling.appendChild(node);
17143                });
17144                return prevSibling;
17145            }
17146        }
17147        return next;
17148    };
17149    const mergeSiblings = (editor, format, vars, node) => {
17150        // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
17151        // Note: mergeSiblingNodes attempts to not merge sibilings if they are noneditable
17152        if (node && format.merge_siblings !== false) {
17153            // Previous sibling
17154            const newNode = mergeSiblingsNodes(editor, getNonWhiteSpaceSibling(node), node) ?? node;
17155            // Next sibling
17156            mergeSiblingsNodes(editor, newNode, getNonWhiteSpaceSibling(newNode, true));
17157        }
17158    };
17159    const clearChildStyles = (dom, format, node) => {
17160        if (format.clear_child_styles) {
17161            const selector = format.links ? '*:not(a)' : '*';
17162            each$8(dom.select(selector, node), (childNode) => {
17163                if (isElementNode(childNode) && dom.isEditable(childNode)) {
17164                    each$8(format.styles, (_value, name) => {
17165                        dom.setStyle(childNode, name, '');
17166                    });
17167                }
17168            });
17169        }
17170    };
17171    const processChildElements = (node, filter, process) => {
17172        each$8(node.childNodes, (node) => {
17173            if (isElementNode(node)) {
17174                if (filter(node)) {
17175                    process(node);
17176                }
17177                if (node.hasChildNodes()) {
17178                    processChildElements(node, filter, process);
17179                }
17180            }
17181        });
17182    };
17183    const unwrapEmptySpan = (dom, node) => {
17184        if (node.nodeName === 'SPAN' && dom.getAttribs(node).length === 0) {
17185            dom.remove(node, true);
17186        }
17187    };
17188    const hasStyle = (dom, name) => (node) => !!(node && getStyle(dom, node, name));
17189    const applyStyle = (dom, name, value) => (node) => {
17190        dom.setStyle(node, name, value);
17191        if (node.getAttribute('style') === '') {
17192            node.removeAttribute('style');
17193        }
17194        unwrapEmptySpan(dom, node);
17195    };
17196
17197    const removeResult = Adt.generate([
17198        { keep: [] },
17199        { rename: ['name'] },
17200        { removed: [] }
17201    ]);
17202    const MCE_ATTR_RE = /^(src|href|style)$/;
17203    const each$7 = Tools.each;
17204    const isEq$2 = isEq$5;
17205    const isTableCellOrRow = (node) => /^(TR|TH|TD)$/.test(node.nodeName);
17206    const isChildOfInlineParent = (dom, node, parent) => dom.isChildOf(node, parent) && node !== parent && !dom.isBlock(parent);
17207    const getContainer = (ed, rng, start) => {
17208        let container = rng[start ? 'startContainer' : 'endContainer'];
17209        let offset = rng[start ? 'startOffset' : 'endOffset'];
17210        if (isElement$7(container)) {
17211            const lastIdx = container.childNodes.length - 1;
17212            if (!start && offset) {
17213                offset--;
17214            }
17215            container = container.childNodes[offset > lastIdx ? lastIdx : offset];
17216        }
17217        // If start text node is excluded then walk to the next node
17218        if (isText$b(container) && start && offset >= container.data.length) {
17219            container = new DomTreeWalker(container, ed.getBody()).next() || container;
17220        }
17221        // If end text node is excluded then walk to the previous node
17222        if (isText$b(container) && !start && offset === 0) {
17223            container = new DomTreeWalker(container, ed.getBody()).prev() || container;
17224        }
17225        return container;
17226    };
17227    const normalizeTableSelection = (node, start) => {
17228        const prop = start ? 'firstChild' : 'lastChild';
17229        const childNode = node[prop];
17230        if (isTableCellOrRow(node) && childNode) {
17231            if (node.nodeName === 'TR') {
17232                return childNode[prop] || childNode;
17233            }
17234            else {
17235                return childNode;
17236            }
17237        }
17238        return node;
17239    };
17240    const wrap$1 = (dom, node, name, attrs) => {
17241        const wrapper = dom.create(name, attrs);
17242        node.parentNode?.insertBefore(wrapper, node);
17243        wrapper.appendChild(node);
17244        return wrapper;
17245    };
17246    const wrapWithSiblings = (dom, node, next, name, attrs) => {
17247        const start = SugarElement.fromDom(node);
17248        const wrapper = SugarElement.fromDom(dom.create(name, attrs));
17249        const siblings = next ? nextSiblings(start) : prevSiblings(start);
17250        append(wrapper, siblings);
17251        if (next) {
17252            before$4(start, wrapper);
17253            prepend(wrapper, start);
17254        }
17255        else {
17256            after$4(start, wrapper);
17257            append$1(wrapper, start);
17258        }
17259        return wrapper.dom;
17260    };
17261    const isColorFormatAndAnchor = (node, format) => format.links && node.nodeName === 'A';
17262    /**
17263     * Removes the node and wrap it's children in paragraphs before doing so or
17264     * appends BR elements to the beginning/end of the block element if forcedRootBlocks is disabled.
17265     *
17266     * If the div in the node below gets removed:
17267     *  text<div>text</div>text
17268     *
17269     * Output becomes:
17270     *  text<div><br />text<br /></div>text
17271     *
17272     * So when the div is removed the result is:
17273     *  text<br />text<br />text
17274     *
17275     * @private
17276     * @param {Node} node Node to remove + apply BR/P elements to.
17277     * @param {Object} format Format rule.
17278     * @return {Node} Input node.
17279     */
17280    const removeNode = (ed, node, format) => {
17281        const parentNode = node.parentNode;
17282        let rootBlockElm;
17283        const dom = ed.dom;
17284        const forcedRootBlock = getForcedRootBlock(ed);
17285        if (isBlockFormat(format)) {
17286            // Wrap the block in a forcedRootBlock if we are at the root of document
17287            if (parentNode === dom.getRoot()) {
17288                if (!format.list_block || !isEq$2(node, format.list_block)) {
17289                    each$e(from(node.childNodes), (node) => {
17290                        if (isValid(ed, forcedRootBlock, node.nodeName.toLowerCase())) {
17291                            if (!rootBlockElm) {
17292                                rootBlockElm = wrap$1(dom, node, forcedRootBlock);
17293                                dom.setAttribs(rootBlockElm, getForcedRootBlockAttrs(ed));
17294                            }
17295                            else {
17296                                rootBlockElm.appendChild(node);
17297                            }
17298                        }
17299                        else {
17300                            rootBlockElm = null;
17301                        }
17302                    });
17303                }
17304            }
17305        }
17306        // Never remove nodes that aren't the specified inline element if a selector is specified too
17307        if (isMixedFormat(format) && !isEq$2(format.inline, node)) {
17308            return;
17309        }
17310        dom.remove(node, true);
17311    };
17312    // Attributes or styles can be either an array of names or an object containing name/value pairs
17313    const processFormatAttrOrStyle = (name, value, vars) => {
17314        // Indexed array
17315        if (isNumber(name)) {
17316            return {
17317                name: value,
17318                value: null
17319            };
17320        }
17321        else {
17322            return {
17323                name,
17324                value: replaceVars(value, vars)
17325            };
17326        }
17327    };
17328    const removeEmptyStyleAttributeIfNeeded = (dom, elm) => {
17329        if (dom.getAttrib(elm, 'style') === '') {
17330            elm.removeAttribute('style');
17331            elm.removeAttribute('data-mce-style');
17332        }
17333    };
17334    const removeStyles$1 = (dom, elm, format, vars, compareNode) => {
17335        let stylesModified = false;
17336        each$7(format.styles, (value, name) => {
17337            const { name: styleName, value: styleValue } = processFormatAttrOrStyle(name, value, vars);
17338            const normalizedStyleValue = normalizeStyleValue(styleValue, styleName);
17339            if (format.remove_similar || isNull(styleValue) || !isElement$7(compareNode) || isEq$2(getStyle(dom, compareNode, styleName), normalizedStyleValue)) {
17340                dom.setStyle(elm, styleName, '');
17341            }
17342            stylesModified = true;
17343        });
17344        if (stylesModified) {
17345            removeEmptyStyleAttributeIfNeeded(dom, elm);
17346        }
17347    };
17348    const removeListStyleFormats = (editor, name, vars) => {
17349        if (name === 'removeformat') {
17350            each$e(getPartiallySelectedListItems(editor
17350.selection), (li) => {
17351                each$e(listItemStyles, (name) => editor.dom.setStyle(li, name, ''));
17352                removeEmptyStyleAttributeIfNeeded(editor.dom, li);
17353            });
17354        }
17355        else {
17356            getExpandedListItemFormat(editor.formatter, name).each((liFmt) => {
17357                each$e(getPartiallySelectedListItems(editor.selection
vendor: 20,508 bytes, lines 17357-17741
17357), (li) => removeStyles$1(editor.dom, li, liFmt, vars, null));
17358            });
17359        }
17360    };
17361    const removeNodeFormatInternal = (ed, format, vars, node, compareNode) => {
17362        const dom = ed.dom;
17363        const elementUtils = ElementUtils(ed);
17364        const schema = ed.schema;
17365        // Root level block transparents should get converted into regular text blocks
17366        if (isInlineFormat(format) && isTransparentElementName(schema, format.inline) && isTransparentBlock(schema, node) && node.parentElement === ed.getBody()) {
17367            removeNode(ed, node, format);
17368            return removeResult.removed();
17369        }
17370        // Check if node is noneditable and can have the format removed from it
17371        if (!format.ceFalseOverride && node && dom.getContentEditableParent(node) === 'false') {
17372            return removeResult.keep();
17373        }
17374        // Check if node matches format
17375        if (node && !matchName(dom, node, format) && !isColorFormatAndAnchor(node, format)) {
17376            return removeResult.keep();
17377        }
17378        // "matchName" will made sure we're dealing with an element, so cast as one
17379        const elm = node;
17380        // Applies to styling elements like strong, em, i, u, etc. so that if they have styling attributes, the attributes can be kept but the styling element is removed
17381        const preserveAttributes = format.preserve_attributes;
17382        if (isInlineFormat(format) && format.remove === 'all' && isArray$1(preserveAttributes)) {
17383            // Remove all attributes except for the attributes specified in preserve_attributes
17384            const attrsToPreserve = filter$5(dom.getAttribs(elm), (attr) => contains$2(preserveAttributes, attr.name.toLowerCase()));
17385            dom.removeAllAttribs(elm);
17386            each$e(attrsToPreserve, (attr) => dom.setAttrib(elm, attr.name, attr.value));
17387            // Note: If there are no attributes left, the element will be removed as normal at the end of the function
17388            if (attrsToPreserve.length > 0) {
17389                // Convert inline element to span if necessary
17390                return removeResult.rename('span');
17391            }
17392        }
17393        // Should we compare with format attribs and styles
17394        if (format.remove !== 'all') {
17395            removeStyles$1(dom, elm, format, vars, compareNode);
17396            // Remove attributes
17397            each$7(format.attributes, (value, name) => {
17398                const { name: attrName, value: attrValue } = processFormatAttrOrStyle(name, value, vars);
17399                if (format.remove_similar || isNull(attrValue) || !isElement$7(compareNode) || isEq$2(dom.getAttrib(compareNode, attrName), attrValue)) {
17400                    // Keep internal classes
17401                    if (attrName === 'class') {
17402                        const currentValue = dom.getAttrib(elm, attrName);
17403                        if (currentValue) {
17404                            // Build new class value where everything is removed except the internal prefixed classes
17405                            let valueOut = '';
17406                            each$e(currentValue.split(/\s+/), (cls) => {
17407                                if (/mce\-\w+/.test(cls)) {
17408                                    valueOut += (valueOut ? ' ' : '') + cls;
17409                                }
17410                            });
17411                            // We got some internal classes left
17412                            if (valueOut) {
17413                                dom.setAttrib(elm, attrName, valueOut);
17414                                return;
17415                            }
17416                        }
17417                    }
17418                    // Remove mce prefixed attributes (must clean before short circuit operations)
17419                    if (MCE_ATTR_RE.test(attrName)) {
17420                        elm.removeAttribute('data-mce-' + attrName);
17421                    }
17422                    // keep style="list-style-type: none" on <li>s
17423                    if (attrName === 'style' && matchNodeNames$1(['li'])(elm) && dom.getStyle(elm, 'list-style-type') === 'none') {
17424                        elm.removeAttribute(attrName);
17425                        dom.setStyle(elm, 'list-style-type', 'none');
17426                        return;
17427                    }
17428                    // IE6 has a bug where the attribute doesn't get removed correctly
17429                    if (attrName === 'class') {
17430                        elm.removeAttribute('className');
17431                    }
17432                    elm.removeAttribute(attrName);
17433                }
17434            });
17435            // Remove classes
17436            each$7(format.classes, (value) => {
17437                value = replaceVars(value, vars);
17438                if (!isElement$7(compareNode) || dom.hasClass(compareNode, value)) {
17439                    dom.removeClass(elm, value);
17440                }
17441            });
17442            // Check for non internal attributes
17443            const attrs = dom.getAttribs(elm);
17444            for (let i = 0; i < attrs.length; i++) {
17445                const attrName = attrs[i].nodeName;
17446                if (!elementUtils.isAttributeInternal(attrName)) {
17447                    return removeResult.keep();
17448                }
17449            }
17450        }
17451        // Remove the inline child if it's empty for example <b> or <span>
17452        if (format.remove !== 'none') {
17453            removeNode(ed, elm, format);
17454            return removeResult.removed();
17455        }
17456        return removeResult.keep();
17457    };
17458    const findFormatRoot = (editor, container, name, vars, similar) => {
17459        let formatRoot;
17460        if (container.parentNode) {
17461            // Find format root
17462            each$e(getParents$2(editor.dom, container.parentNode).reverse(), (parent) => {
17463                // Find format root element
17464                if (!formatRoot && isElement$7(parent) && parent.id !== '_start' && parent.id !== '_end') {
17465                    // Is the node matching the format we are looking for
17466                    const format = matchNode$1(editor, parent, name, vars, similar);
17467                    if (format && format.split !== false) {
17468                        formatRoot = parent;
17469                    }
17470                }
17471            });
17472        }
17473        return formatRoot;
17474    };
17475    const removeNodeFormatFromClone = (editor, format, vars, clone) => removeNodeFormatInternal(editor, format, vars, clone, clone).fold(constant(clone), (newName) => {
17476        // To rename a node, it needs to be a child of another node
17477        const fragment = editor.dom.createFragment();
17478        fragment.appendChild(clone);
17479        // If renaming we are guaranteed this is a Element, so cast
17480        return editor.dom.rename(clone, newName);
17481    }, constant(null));
17482    const wrapAndSplit = (editor, formatList, formatRoot, container, target, split, format, vars) => {
17483        let lastClone;
17484        let firstClone;
17485        const dom = editor.dom;
17486        // Format root found then clone formats and split it
17487        if (formatRoot) {
17488            const formatRootParent = formatRoot.parentNode;
17489            for (let parent = container.parentNode; parent && parent !== formatRootParent; parent = parent.parentNode) {
17490                let clone = dom.clone(parent, false);
17491                for (let i = 0; i < formatList.length; i++) {
17492                    clone = removeNodeFormatFromClone(editor, formatList[i], vars, clone);
17493                    if (clone === null) {
17494                        break;
17495                    }
17496                }
17497                // Build wrapper node
17498                if (clone) {
17499                    if (lastClone) {
17500                        clone.appendChild(lastClone);
17501                    }
17502                    if (!firstClone) {
17503                        firstClone = clone;
17504                    }
17505                    lastClone = clone;
17506                }
17507            }
17508            // Never split block elements if the format is mixed
17509            if (split && (!format.mixed || !dom.isBlock(formatRoot))) {
17510                container = dom.split(formatRoot, container) ?? container;
17511            }
17512            // Wrap container in cloned formats
17513            if (lastClone && firstClone) {
17514                target.parentNode?.insertBefore(lastClone, target);
17515                firstClone.appendChild(target);
17516                // After splitting the nodes may match with other siblings so we need to attempt to merge them
17517                // Note: We can't use MergeFormats, as that'd create a circular dependency
17518                if (isInlineFormat(format)) {
17519                    mergeSiblings(editor, format, vars, lastClone);
17520                }
17521            }
17522        }
17523        return container;
17524    };
17525    const removeFormatInternal = (ed, name, vars, node, similar) => {
17526        const formatList = ed.formatter.get(name);
17527        const format = formatList[0];
17528        const dom = ed.dom;
17529        const selection = ed.selection;
17530        const splitToFormatRoot = (container) => {
17531            const formatRoot = findFormatRoot(ed, container, name, vars, similar);
17532            return wrapAndSplit(ed, formatList, formatRoot, container, container, true, format, vars);
17533        };
17534        // Make sure to only check for bookmarks created here (eg _start or _end)
17535        // as there maybe nested bookmarks
17536        const isRemoveBookmarkNode = (node) => isBookmarkNode$1(node) && isElement$7(node) && (node.id === '_start' || node.id === '_end');
17537        const removeFormatOnNode = (node) => exists(formatList, (fmt) => removeNodeFormat(ed, fmt, vars, node, node));
17538        // Merges the styles for each node
17539        const process = (node) => {
17540            // Grab the children first since the nodelist might be changed
17541            const children = from(node.childNodes);
17542            // Process current node
17543            const removed = removeFormatOnNode(node);
17544            // TINY-6567/TINY-7393: Include the parent if using an expanded selector format and no match was found for the current node
17545            const currentNodeMatches = removed || exists(formatList, (f) => matchName(dom, node, f));
17546            const parentNode = node.parentNode;
17547            if (!currentNodeMatches && isNonNullable(parentNode) && shouldExpandToSelector(format)) {
17548                removeFormatOnNode(parentNode);
17549            }
17550            // Process the children
17551            if (format.deep) {
17552                if (children.length) {
17553                    for (let i = 0; i < children.length; i++) {
17554                        process(children[i]);
17555                    }
17556                }
17557            }
17558            // Note: Assists with cleaning up any stray text decorations that may been applied when text decorations
17559            // and text colors were merged together from an applied format
17560            // Remove child span if it only contains text-decoration and a parent node also has the same text decoration.
17561            const textDecorations = ['underline', 'line-through', 'overline'];
17562            each$e(textDecorations, (decoration) => {
17563                if (isElement$7(node) && ed.dom.getStyle(node, 'text-decoration') === decoration &&
17564                    node.parentNode && getTextDecoration(dom, node.parentNode) === decoration) {
17565                    removeNodeFormat(ed, {
17566                        deep: false,
17567                        exact: true,
17568                        inline: 'span',
17569                        styles: {
17570                            textDecoration: decoration
17571                        }
17572                    }, undefined, node);
17573                }
17574            });
17575        };
17576        const unwrap = (start) => {
17577            const node = dom.get(start ? '_start' : '_end');
17578            if (node) {
17579                let out = node[start ? 'firstChild' : 'lastChild'];
17580                // If the end is placed within the start the result will be removed
17581                // So this checks if the out node is a bookmark node if it is it
17582                // checks for another more suitable node
17583                if (isRemoveBookmarkNode(out)) {
17584                    out = out[start ? 'firstChild' : 'lastChild'];
17585                }
17586                // Since dom.remove removes empty text nodes then we need to try to find a better node
17587                if (isText$b(out) && out.data.length === 0) {
17588                    out = start ? node.previousSibling || node.nextSibling : node.nextSibling || node.previousSibling;
17589                }
17590                dom.remove(node, true);
17591                return out;
17592            }
17593            else {
17594                return null;
17595            }
17596        };
17597        const removeRngStyle = (rng) => {
17598            let startContainer;
17599            let endContainer;
17600            let expandedRng = expandRng(dom, rng, formatList, { includeTrailingSpace: rng.collapsed });
17601            if (format.split) {
17602                // Split text nodes
17603                expandedRng = split(expandedRng);
17604                startContainer = getContainer(ed, expandedRng, true);
17605                endContainer = getContainer(ed, expandedRng);
17606                if (startContainer !== endContainer) {
17607                    // WebKit will render the table incorrectly if we wrap a TH or TD in a SPAN
17608                    // so let's see if we can use the first/last child instead
17609                    // This will happen if you triple click a table cell and use remove formatting
17610                    startContainer = normalizeTableSelection(startContainer, true);
17611                    endContainer = normalizeTableSelection(endContainer, false);
17612                    // Wrap and split if nested
17613                    if (isChildOfInlineParent(dom, startContainer, endContainer)) {
17614                        const marker = Optional.from(startContainer.firstChild).getOr(startContainer);
17615                        splitToFormatRoot(wrapWithSiblings(dom, marker, true, 'span', { 'id': '_start', 'data-mce-type': 'bookmark' }));
17616                        unwrap(true);
17617                        return;
17618                    }
17619                    // Wrap and split if nested
17620                    if (isChildOfInlineParent(dom, endContainer, startContainer)) {
17621                        const marker = Optional.from(endContainer.lastChild).getOr(endContainer);
17622                        splitToFormatRoot(wrapWithSiblings(dom, marker, false, 'span', { 'id': '_end', 'data-mce-type': 'bookmark' }));
17623                        unwrap(false);
17624                        return;
17625                    }
17626                    // Wrap start/end nodes in span element since these might be cloned/moved
17627                    startContainer = wrap$1(dom, startContainer, 'span', { 'id': '_start', 'data-mce-type': 'bookmark' });
17628                    endContainer = wrap$1(dom, endContainer, 'span', { 'id': '_end', 'data-mce-type': 'bookmark' });
17629                    // Split start/end and anything in between
17630                    const newRng = dom.createRng();
17631                    newRng.setStartAfter(startContainer);
17632                    newRng.setEndBefore(endContainer);
17633                    walk$3(dom, newRng, (nodes) => {
17634                        each$e(nodes, (n) => {
17635                            if (!isBookmarkNode$1(n) && !isBookmarkNode$1(n.parentNode)) {
17636                                splitToFormatRoot(n);
17637                            }
17638                        });
17639                    });
17640                    splitToFormatRoot(startContainer);
17641                    splitToFormatRoot(endContainer);
17642                    // Unwrap start/end to get real elements again
17643                    // Note that the return value should always be a node since it's wrapped above
17644                    startContainer = unwrap(true);
17645                    endContainer = unwrap();
17646                }
17647                else {
17648                    startContainer = endContainer = splitToFormatRoot(startContainer);
17649                }
17650                // Update range positions since they might have changed after the split operations
17651                expandedRng.startContainer = startContainer.parentNode ? startContainer.parentNode : startContainer;
17652                expandedRng.startOffset = dom.nodeIndex(startContainer);
17653                expandedRng.endContainer = endContainer.parentNode ? endContainer.parentNode : endContainer;
17654                expandedRng.endOffset = dom.nodeIndex(endContainer) + 1;
17655            }
17656            // Remove items between start/end
17657            walk$3(dom, expandedRng, (nodes) => {
17658                each$e(nodes, process);
17659            });
17660        };
17661        // Handle node
17662        if (node) {
17663            if (isNode(node)) {
17664                const rng = dom.createRng();
17665                rng.setStartBefore(node);
17666                rng.setEndAfter(node);
17667                removeRngStyle(rng);
17668            }
17669            else {
17670                removeRngStyle(node);
17671            }
17672            fireFormatRemove(ed, name, node, vars);
17673            return;
17674        }
17675        if (!selection.isCollapsed() || !isInlineFormat(format) || getCellsFromEditor(ed).length) {
17676            // Remove formatting on the selection
17677            preserveSelection(ed, () => runOnRanges(ed, removeRngStyle), 
17678            // Before trying to move the start of the selection, check if start element still has formatting then we are at: "<b>text|</b>text"
17679            // and need to move the start into the next text node
17680            (startNode) => isInlineFormat(format) && match$2(ed, name, vars, startNode));
17681            ed.nodeChanged();
17682        }
17683        else {
17684            removeCaretFormat(ed, name, vars, similar);
17685        }
17686        removeListStyleFormats(ed, name, vars);
17687        fireFormatRemove(ed, name, node, vars);
17688    };
17689    const removeFormat$1 = (ed, name, vars, node, similar) => {
17690        if (node || ed.selection.isEditable()) {
17691            removeFormatInternal(ed, name, vars, node, similar);
17692        }
17693    };
17694    const removeFormatOnElement = (editor, format, vars, node) => {
17695        return removeNodeFormatInternal(editor, format, vars, node).fold(() => Optional.some(node), (newName) => Optional.some(editor.dom.rename(node, newName)), Optional.none);
17696    };
17697    /**
17698     * Removes the specified format for the specified node. It will also remove the node if it doesn't have
17699     * any attributes if the format specifies it to do so.
17700     *
17701     * @private
17702     * @param {Object} format Format object with items to remove from node.
17703     * @param {Object} vars Name/value object with variables to apply to format.
17704     * @param {Node} node Node to remove the format styles on.
17705     * @param {Node} compareNode Optional compare node, if specified the styles will be compared to that node.
17706     * @return {Boolean} True/false if the node was removed or not.
17707     */
17708    const removeNodeFormat = (editor, format, vars, node, compareNode) => {
17709        return removeNodeFormatInternal(editor, format, vars, node, compareNode).fold(never, (newName) => {
17710            // If renaming we are guaranteed this is a Element, so cast
17711            editor.dom.rename(node, newName);
17712            return true;
17713        }, always);
17714    };
17715
17716    const fontSizeAlteringFormats = ['fontsize', 'subscript', 'superscript'];
17717    const formatsToActOn = ['strikethrough', ...fontSizeAlteringFormats];
17718    const hasFormat = (formatter, el, format) => isNonNullable(formatter.matchNode(el.dom, format, {}, format === 'fontsize'));
17719    const isFontSizeAlteringElement = (formatter, el) => exists(fontSizeAlteringFormats, (format) => hasFormat(formatter, el, format));
17720    const isNormalizingFormat = (format) => contains$2(formatsToActOn, format);
17721    const gatherWrapperData = (isRoot, scope, hasFormat, createFormatElement, removeFormatFromElement) => {
17722        const parents = parents$1(scope, isRoot).filter(isElement$8);
17723        return findLastIndex(parents, hasFormat).map((index) => {
17724            const container = parents[index];
17725            const innerWrapper = createFormatElement(container);
17726            const outerWrappers = [
17727                ...removeFormatFromElement(shallow(container)).toArray(),
17728                ...bind$3(parents.slice(0, index), (wrapper) => {
17729                    if (hasFormat(wrapper)) {
17730                        return removeFormatFromElement(wrapper).toArray();
17731                    }
17732                    else {
17733                        return [shallow(wrapper)];
17734                    }
17735                })
17736            ];
17737            return { container, innerWrapper, outerWrappers };
17738        });
17739    };
17740    const wrapChildrenInInnerWrapper = (target, wrapper, hasFormat, removeFormatFromElement) => {
17741        each$e(children$
177411(target), (child) => {
17742            if (isElement$8(child) && hasFormat(child)) {
17743                if (removeFormatFromElement(child).isNone()) {
17744                    unwrap(child);
17745                }
17746            }
17747        });
17748        each$e(children$1
vendor: 24,488 bytes, lines 17748-18317
17748(target), (child) => append$1(wrapper, child));
17749        prepend(target, wrapper);
17750    };
17751    const wrapInOuterWrappers = (target, wrappers) => {
17752        if (wrappers.length > 0) {
17753            const outermost = wrappers[wrappers.length - 1];
17754            before$4(target, outermost);
17755            const innerMost = foldl(wrappers.slice(0, wrappers.length - 1), (acc, wrapper) => {
17756                append$1(acc, wrapper);
17757                return wrapper;
17758            }, outermost);
17759            append$1(innerMost, target);
17760        }
17761    };
17762    const normalizeFontSizeElementsInternal = (domUtils, fontSizeElements, hasFormat, createFormatElement, removeFormatFromElement) => {
17763        const isRoot = (el) => eq(SugarElement.fromDom(domUtils.getRoot()), el) || domUtils.isBlock(el.dom);
17764        each$e(fontSizeElements, (fontSizeElement) => {
17765            gatherWrapperData(isRoot, fontSizeElement, hasFormat, createFormatElement, removeFormatFromElement).each(({ container, innerWrapper, outerWrappers }) => {
17766                domUtils.split(container.dom, fontSizeElement.dom);
17767                wrapChildrenInInnerWrapper(fontSizeElement, innerWrapper, hasFormat, removeFormatFromElement);
17768                wrapInOuterWrappers(fontSizeElement, outerWrappers);
17769            });
17770        });
17771    };
17772    const normalizeFontSizeElementsWithFormat = (editor, formatName, fontSizeElements) => {
17773        const hasFormat = (el) => isNonNullable(matchNode$1(editor, el.dom, formatName));
17774        const createFormatElement = (el) => {
17775            const newEl = SugarElement.fromTag(name(el));
17776            const format = matchNode$1(editor, el.dom, formatName, {});
17777            if (isNonNullable(format) && isApplyFormat(format)) {
17778                setElementFormat(editor, newEl.dom, format);
17779            }
17780            return newEl;
17781        };
17782        const removeFormatFromElement = (el) => {
17783            const format = matchNode$1(editor, el.dom, formatName, {});
17784            if (isNonNullable(format)) {
17785                return removeFormatOnElement(editor, format, {}, el.dom).map(SugarElement.fromDom);
17786            }
17787            else {
17788                return Optional.some(el);
17789            }
17790        };
17791        const bookmark = createBookmark(editor.selection.getRng());
17792        normalizeFontSizeElementsInternal(editor.dom, fontSizeElements, hasFormat, createFormatElement, removeFormatFromElement);
17793        editor.selection.setRng(resolveBookmark(bookmark));
17794    };
17795    const collectFontSizeElements = (formatter, wrappers) => bind$3(wrappers, (wrapper) => {
17796        const fontSizeDescendants = descendants$1(wrapper, (el) => isFontSizeAlteringElement(formatter, el));
17797        return isFontSizeAlteringElement(formatter, wrapper) ? [wrapper, ...fontSizeDescendants] : fontSizeDescendants;
17798    });
17799    const normalizeFontSizeElementsAfterApply = (editor, appliedFormat, wrappers) => {
17800        if (isNormalizingFormat(appliedFormat)) {
17801            const fontSizeElements = collectFontSizeElements(editor.formatter, wrappers);
17802            normalizeFontSizeElementsWithFormat(editor, 'strikethrough', fontSizeElements);
17803        }
17804    };
17805    const normalizeElements = (editor, elements) => {
17806        const fontSizeElements = filter$5(elements, (el) => isFontSizeAlteringElement(editor.formatter, el));
17807        normalizeFontSizeElementsWithFormat(editor, 'strikethrough', fontSizeElements);
17808    };
17809
17810    const isHeading = (node) => ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(node.name);
17811    const isSummary = (node) => node.name === 'summary';
17812
17813    const traverse = (root, fn) => {
17814        let node = root;
17815        while ((node = node.walk())) {
17816            fn(node);
17817        }
17818    };
17819    // Test a single node against the current filters, and add it to any match lists if necessary
17820    const matchNode = (nodeFilters, attributeFilters, node, matches) => {
17821        const name = node.name;
17822        // Match node filters
17823        for (let ni = 0, nl = nodeFilters.length; ni < nl; ni++) {
17824            const filter = nodeFilters[ni];
17825            if (filter.name === name) {
17826                const match = matches.nodes[name];
17827                if (match) {
17828                    match.nodes.push(node);
17829                }
17830                else {
17831                    matches.nodes[name] = { filter, nodes: [node] };
17832                }
17833            }
17834        }
17835        // Match attribute filters
17836        if (node.attributes) {
17837            for (let ai = 0, al = attributeFilters.length; ai < al; ai++) {
17838                const filter = attributeFilters[ai];
17839                const attrName = filter.name;
17840                if (attrName in node.attributes.map) {
17841                    const match = matches.attributes[attrName];
17842                    if (match) {
17843                        match.nodes.push(node);
17844                    }
17845                    else {
17846                        matches.attributes[attrName] = { filter, nodes: [node] };
17847                    }
17848                }
17849            }
17850        }
17851    };
17852    const findMatchingNodes = (nodeFilters, attributeFilters, node) => {
17853        const matches = { nodes: {}, attributes: {} };
17854        if (node.firstChild) {
17855            traverse(node, (childNode) => {
17856                matchNode(nodeFilters, attributeFilters, childNode, matches);
17857            });
17858        }
17859        return matches;
17860    };
17861    // Run all necessary node filters and attribute filters, based on a match set
17862    const runFilters = (matches, args) => {
17863        const run = (matchRecord, filteringAttributes) => {
17864            each$d(matchRecord, (match) => {
17865                // in theory we don't need to copy the array, it was created purely for this filtering, but the method is exported so we can't guarantee that
17866                const nodes = from(match.nodes);
17867                each$e(match.filter.callbacks, (callback) => {
17868                    // very very carefully mutate the nodes array based on whether the filter still matches them
17869                    for (let i = nodes.length - 1; i >= 0; i--) {
17870                        const node = nodes[i];
17871                        // Remove already removed children, and nodes that no longer match the filter
17872                        const valueMatches = filteringAttributes ? node.attr(match.filter.name) !== undefined : node.name === match.filter.name;
17873                        if (!valueMatches || isNullable(node.parent)) {
17874                            nodes.splice(i, 1);
17875                        }
17876                    }
17877                    if (nodes.length > 0) {
17878                        callback(nodes, match.filter.name, args);
17879                    }
17880                });
17881            });
17882        };
17883        run(matches.nodes, false);
17884        run(matches.attributes, true);
17885    };
17886    const filter$1 = (nodeFilters, attributeFilters, node, args = {}) => {
17887        const matches = findMatchingNodes(nodeFilters, attributeFilters, node);
17888        runFilters(matches, args);
17889    };
17890
17891    const paddEmptyNode = (settings, args, isBlock, node) => {
17892        const brPreferred = settings.pad_empty_with_br || args.insert;
17893        if (brPreferred && isBlock(node)) {
17894            const astNode = new AstNode('br', 1);
17895            if (args.insert) {
17896                astNode.attr('data-mce-bogus', '1');
17897            }
17898            node.empty().append(astNode);
17899        }
17900        else {
17901            node.empty().append(new AstNode('#text', 3)).value = nbsp;
17902        }
17903    };
17904    const isPaddedWithNbsp = (node) => hasOnlyChild(node, '#text') && node?.firstChild?.value === nbsp;
17905    const hasOnlyChild = (node, name) => {
17906        const firstChild = node?.firstChild;
17907        return isNonNullable(firstChild) && firstChild === node.lastChild && firstChild.name === name;
17908    };
17909    const isPadded = (schema, node) => {
17910        const rule = schema.getElementRule(node.name);
17911        return rule?.paddEmpty === true;
17912    };
17913    const isEmpty$2 = (schema, nonEmptyElements, whitespaceElements, node) => node.isEmpty(nonEmptyElements, whitespaceElements, (node) => isPadded(schema, node));
17914    const isLineBreakNode = (node, isBlock) => isNonNullable(node) && (isBlock(node) || node.name === 'br');
17915    const findClosestEditingHost = (scope) => {
17916        let editableNode;
17917        for (let node = scope; node; node = node.parent) {
17918            const contentEditable = node.attr('contenteditable');
17919            if (contentEditable === 'false') {
17920                break;
17921            }
17922            else if (contentEditable === 'true') {
17923                editableNode = node;
17924            }
17925        }
17926        return Optional.from(editableNode);
17927    };
17928    const getAllDescendants = (scope) => {
17929        const collection = [];
17930        for (let node = scope.firstChild; isNonNullable(node); node = node.walk()) {
17931            collection.push(node);
17932        }
17933        return collection;
17934    };
17935
17936    const removeOrUnwrapInvalidNode = (node, schema, originalNodeParent = node.parent) => {
17937        if (schema.getSpecialElements()[node.name]) {
17938            node.empty().remove();
17939        }
17940        else {
17941            // are the children of `node` valid children of the top level parent?
17942            // if not, remove or unwrap them too
17943            const children = node.children();
17944            for (const childNode of children) {
17945                if (originalNodeParent && !schema.isValidChild(originalNodeParent.name, childNode.name)) {
17946                    removeOrUnwrapInvalidNode(childNode, schema, originalNodeParent);
17947                }
17948            }
17949            node.unwrap();
17950        }
17951    };
17952    const cleanInvalidNodes = (nodes, schema, rootNode, onCreate = noop) => {
17953        const textBlockElements = schema.getTextBlockElements();
17954        const nonEmptyElements = schema.getNonEmptyElements();
17955        const whitespaceElements = schema.getWhitespaceElements();
17956        const nonSplittableElements = Tools.makeMap('tr,td,th,tbody,thead,tfoot,table,summary');
17957        const fixed = new Set();
17958        const isSplittableElement = (node) => node !== rootNode && !nonSplittableElements[node.name];
17959        for (let ni = 0; ni < nodes.length; ni++) {
17960            const node = nodes[ni];
17961            let parent;
17962            let newParent;
17963            let tempNode;
17964            // Don't bother if it's detached from the tree
17965            if (!node.parent || fixed.has(node)) {
17966                continue;
17967            }
17968            // If the invalid element is a text block, and the text block is within a parent LI element
17969            // Then unwrap the first text block and convert other sibling text blocks to LI elements similar to Word/Open Office
17970            if (textBlockElements[node.name] && node.parent.name === 'li') {
17971                // Move sibling text blocks after LI element
17972                let sibling = node.next;
17973                while (sibling) {
17974                    if (textBlockElements[sibling.name]) {
17975                        sibling.name = 'li';
17976                        fixed.add(sibling);
17977                        node.parent.insert(sibling, node.parent);
17978                    }
17979                    else {
17980                        break;
17981                    }
17982                    sibling = sibling.next;
17983                }
17984                // Unwrap current text block
17985                node.unwrap();
17986                continue;
17987            }
17988            // Get list of all parent nodes until we find a valid parent to stick the child into
17989            const parents = [node];
17990            for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && isSplittableElement(parent); parent = parent.parent) {
17991                parents.push(parent);
17992            }
17993            // Found a suitable parent
17994            if (parent && parents.length > 1) {
17995                // If the node is a valid child of the parent, then try to move it. Otherwise unwrap it
17996                if (!isInvalid(schema, node, parent)) {
17997                    // Reverse the array since it makes looping easier
17998                    parents.reverse();
17999                    // Clone the related parent and insert that after the moved node
18000                    newParent = parents[0].clone();
18001                    onCreate(newParent);
18002                    // Start cloning and moving children on the left side of the target node
18003                    let currentNode = newParent;
18004                    for (let i = 0; i < parents.length - 1; i++) {
18005                        if (schema.isValidChild(currentNode.name, parents[i].name) && i > 0) {
18006                            tempNode = parents[i].clone();
18007                            onCreate(tempNode);
18008                            currentNode.append(tempNode);
18009                        }
18010                        else {
18011                            tempNode = currentNode;
18012                        }
18013                        for (let childNode = parents[i].firstChild; childNode && childNode !== parents[i + 1];) {
18014                            const nextNode = childNode.next;
18015                            tempNode.append(childNode);
18016                            childNode = nextNode;
18017                        }
18018                        currentNode = tempNode;
18019                    }
18020                    if (!isEmpty$2(schema, nonEmptyElements, whitespaceElements, newParent)) {
18021                        parent.insert(newParent, parents[0], true);
18022                        parent.insert(node, newParent);
18023                    }
18024                    else {
18025                        parent.insert(node, parents[0], true);
18026                    }
18027                    // Check if the element is empty by looking through its contents, with special treatment for <p><br /></p>
18028                    parent = parents[0];
18029                    if (isEmpty$2(schema, nonEmptyElements, whitespaceElements, parent) || hasOnlyChild(parent, 'br')) {
18030                        parent.empty().remove();
18031                    }
18032                }
18033                else {
18034                    removeOrUnwrapInvalidNode(node, schema);
18035                }
18036            }
18037            else if (node.parent) {
18038                // If it's an LI try to find a UL/OL for it or wrap it
18039                if (node.name === 'li') {
18040                    let sibling = node.prev;
18041                    if (sibling && (sibling.name === 'ul' || sibling.name === 'ol')) {
18042                        sibling.append(node);
18043                        continue;
18044                    }
18045                    sibling = node.next;
18046                    if (sibling && (sibling.name === 'ul' || sibling.name === 'ol') && sibling.firstChild) {
18047                        sibling.insert(node, sibling.firstChild, true);
18048                        continue;
18049                    }
18050                    const wrapper = new AstNode('ul', 1);
18051                    onCreate(wrapper);
18052                    node.wrap(wrapper);
18053                    continue;
18054                }
18055                // Try wrapping the element in a DIV
18056                if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) {
18057                    const wrapper = new AstNode('div', 1);
18058                    onCreate(wrapper);
18059                    node.wrap(wrapper);
18060                }
18061                else {
18062                    // We failed wrapping it, remove or unwrap it
18063                    removeOrUnwrapInvalidNode(node, schema);
18064                }
18065            }
18066        }
18067    };
18068    const hasClosest = (node, parentName) => {
18069        let tempNode = node;
18070        while (tempNode) {
18071            if (tempNode.name === parentName) {
18072                return true;
18073            }
18074            tempNode = tempNode.parent;
18075        }
18076        return false;
18077    };
18078    // The `parent` parameter of `isInvalid` function represents the closest valid parent
18079    // under which the `node` is intended to be moved.
18080    const isInvalid = (schema, node, parent = node.parent) => {
18081        if (!parent) {
18082            return false;
18083        }
18084        // Check if the node is a valid child of the parent node. If the child is
18085        // unknown we don't collect it since it's probably a custom element
18086        if (schema.children[node.name] && !schema.isValidChild(parent.name, node.name)) {
18087            return true;
18088        }
18089        // Anchors are a special case and cannot be nested
18090        if (node.name === 'a' && hasClosest(parent, 'a')) {
18091            return true;
18092        }
18093        // heading element is valid if it is the only one child of summary
18094        if (isSummary(parent) && isHeading(node)) {
18095            return !(parent?.firstChild === node && parent?.lastChild === node);
18096        }
18097        return false;
18098    };
18099
18100    const createRange = (sc, so, ec, eo) => {
18101        const rng = document.createRange();
18102        rng.setStart(sc, so);
18103        rng.setEnd(ec, eo);
18104        return rng;
18105    };
18106    // If you triple click a paragraph in this case:
18107    //   <blockquote><p>a</p></blockquote><p>b</p>
18108    // It would become this range in webkit:
18109    //   <blockquote><p>[a</p></blockquote><p>]b</p>
18110    // We would want it to be:
18111    //   <blockquote><p>[a]</p></blockquote><p>b</p>
18112    // Since it would otherwise produces spans out of thin air on insertContent for example.
18113    const normalizeBlockSelectionRange = (rng) => {
18114        const startPos = CaretPosition.fromRangeStart(rng);
18115        const endPos = CaretPosition.fromRangeEnd(rng);
18116        const rootNode = rng.commonAncestorContainer;
18117        return fromPosition(false, rootNode, endPos)
18118            .map((newEndPos) => {
18119            if (!isInSameBlock(startPos, endPos, rootNode) && isInSameBlock(startPos, newEndPos, rootNode)) {
18120                return createRange(startPos.container(), startPos.offset(), newEndPos.container(), newEndPos.offset());
18121            }
18122            else {
18123                return rng;
18124            }
18125        }).getOr(rng);
18126    };
18127    const normalize = (rng) => rng.collapsed ? rng : normalizeBlockSelectionRange(rng);
18128
18129    const explode$1 = Tools.explode;
18130    const create$8 = () => {
18131        const filters = {};
18132        const addFilter = (name, callback) => {
18133            each$e(explode$1(name), (name) => {
18134                if (!has$2(filters, name)) {
18135                    filters[name] = { name, callbacks: [] };
18136                }
18137                filters[name].callbacks.push(callback);
18138            });
18139        };
18140        const getFilters = () => values(filters);
18141        const removeFilter = (name, callback) => {
18142            each$e(explode$1(name), (name) => {
18143                if (has$2(filters, name)) {
18144                    if (isNonNullable(callback)) {
18145                        const filter = filters[name];
18146                        const newCallbacks = filter$5(filter.callbacks, (c) => c !== callback);
18147                        // If all callbacks have been removed then remove the filter reference
18148                        if (newCallbacks.length > 0) {
18149                            filter.callbacks = newCallbacks;
18150                        }
18151                        else {
18152                            delete filters[name];
18153                        }
18154                    }
18155                    else {
18156                        delete filters[name];
18157                    }
18158                }
18159            });
18160        };
18161        return {
18162            addFilter,
18163            getFilters,
18164            removeFilter
18165        };
18166    };
18167
18168    const encodeData = (data) => data.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
18169    const decodeData$1 = (data) => data.replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/g, '&');
18170
18171    const removeAttrs = (node, names) => {
18172        each$e(names, (name) => {
18173            node.attr(name, null);
18174        });
18175    };
18176    const addFontToSpansFilter = (domParser, styles, fontSizes) => {
18177        domParser.addNodeFilter('font', (nodes) => {
18178            each$e(nodes, (node) => {
18179                const props = styles.parse(node.attr('style'));
18180                const color = node.attr('color');
18181                const face = node.attr('face');
18182                const size = node.attr('size');
18183                if (color) {
18184                    props.color = color;
18185                }
18186                if (face) {
18187                    props['font-family'] = face;
18188                }
18189                if (size) {
18190                    toInt(size).each((num) => {
18191                        props['font-size'] = fontSizes[num - 1];
18192                    });
18193                }
18194                node.name = 'span';
18195                node.attr('style', styles.serialize(props));
18196                removeAttrs(node, ['color', 'face', 'size']);
18197            });
18198        });
18199    };
18200    const addStrikeFilter = (domParser, schema, styles) => {
18201        domParser.addNodeFilter('strike', (nodes) => {
18202            const convertToSTag = schema.type !== 'html4';
18203            each$e(nodes, (node) => {
18204                if (convertToSTag) {
18205                    node.name = 's';
18206                }
18207                else {
18208                    const props = styles.parse(node.attr('style'));
18209                    props['text-decoration'] = 'line-through';
18210                    node.name = 'span';
18211                    node.attr('style', styles.serialize(props));
18212                }
18213            });
18214        });
18215    };
18216    const addFilters = (domParser, settings, schema) => {
18217        const styles = Styles();
18218        if (settings.convert_fonts_to_spans) {
18219            addFontToSpansFilter(domParser, styles, Tools.explode(settings.font_size_legacy_values ?? ''));
18220        }
18221        addStrikeFilter(domParser, schema, styles);
18222    };
18223    const register$5 = (domParser, settings, schema) => {
18224        if (settings.inline_styles) {
18225            addFilters(domParser, settings, schema);
18226        }
18227    };
18228
18229    const blobUriToBlob = (url) => fetch(url)
18230        .then((res) => res.ok ? res.blob() : Promise.reject())
18231        .catch(() => Promise.reject({
18232        message: `Cannot convert ${url} to Blob. Resource might not exist or is inaccessible.`,
18233        uriType: 'blob'
18234    }));
18235    const extractBase64Data = (data) => {
18236        const matches = /([a-z0-9+\/=\s]+)/i.exec(data);
18237        return matches ? matches[1] : '';
18238    };
18239    const decodeData = (data) => {
18240        try {
18241            return decodeURIComponent(data);
18242        }
18243        catch {
18244            return data;
18245        }
18246    };
18247    const parseDataUri = (uri) => {
18248        const [type, ...rest] = uri.split(',');
18249        const data = rest.join(',');
18250        const matches = /data:([^/]+\/[^;]+)(;.+)?/.exec(type);
18251        if (matches) {
18252            const base64Encoded = matches[2] === ';base64';
18253            const decodedData = decodeData(data);
18254            const extractedData = base64Encoded ? extractBase64Data(decodedData) : decodedData;
18255            return Optional.some({
18256                type: matches[1],
18257                data: extractedData,
18258                base64Encoded
18259            });
18260        }
18261        else {
18262            return Optional.none();
18263        }
18264    };
18265    const buildBlob = (type, data, base64Encoded = true) => {
18266        let str = data;
18267        if (base64Encoded) {
18268            // Might throw error if data isn't proper base64
18269            try {
18270                str = atob(data);
18271            }
18272            catch {
18273                return Optional.none();
18274            }
18275        }
18276        const arr = new Uint8Array(str.length);
18277        for (let i = 0; i < arr.length; i++) {
18278            arr[i] = str.charCodeAt(i);
18279        }
18280        return Optional.some(new Blob([arr], { type }));
18281    };
18282    const dataUriToBlob = (uri) => {
18283        return new Promise((resolve, reject) => {
18284            parseDataUri(uri)
18285                .bind(({ type, data, base64Encoded }) => buildBlob(type, data, base64Encoded))
18286                .fold(() => reject('Invalid data URI'), resolve);
18287        });
18288    };
18289    const uriToBlob = (url) => {
18290        if (startsWith(url, 'blob:')) {
18291            return blobUriToBlob(url);
18292        }
18293        else if (startsWith(url, 'data:')) {
18294            return dataUriToBlob(url);
18295        }
18296        else {
18297            return Promise.reject('Unknown URI format');
18298        }
18299    };
18300    const blobToDataUri = (blob) => {
18301        return new Promise((resolve, reject) => {
18302            const reader = new FileReader();
18303            reader.onloadend = () => {
18304                resolve(reader.result);
18305            };
18306            reader.onerror = () => {
18307                reject(reader.error?.message);
18308            };
18309            reader.readAsDataURL(blob);
18310        });
18311    };
18312
18313    let count$1 = 0;
18314    const uniqueId$1 = (prefix) => {
18315        return (prefix || 'blobid') + (count$1++);
18316    };
18317    const 
vendor: 13,363 bytes, lines 18317-18620
18317processDataUri = (dataUri, base64Only, generateBlobInfo) => {
18318        return parseDataUri(dataUri).bind(({ data, type, base64Encoded }) => {
18319            if (base64Only && !base64Encoded) {
18320                return Optional.none();
18321            }
18322            else {
18323                const base64 = base64Encoded ? data : btoa(data);
18324                return generateBlobInfo(base64, type);
18325            }
18326        });
18327    };
18328    const createBlobInfo$1 = (blobCache, blob, base64) => {
18329        const blobInfo = blobCache.create(uniqueId$1(), blob, base64);
18330        blobCache.add(blobInfo);
18331        return blobInfo;
18332    };
18333    const dataUriToBlobInfo = (blobCache, dataUri, base64Only = false) => {
18334        return processDataUri(dataUri, base64Only, (base64, type) => Optional.from(blobCache.getByData(base64, type)).orThunk(() => buildBlob(type, base64).map((blob) => createBlobInfo$1(blobCache, blob, base64))));
18335    };
18336    const imageToBlobInfo = (blobCache, imageSrc) => {
18337        const invalidDataUri = () => Promise.reject('Invalid data URI');
18338        if (startsWith(imageSrc, 'blob:')) {
18339            const blobInfo = blobCache.getByUri(imageSrc);
18340            if (isNonNullable(blobInfo)) {
18341                return Promise.resolve(blobInfo);
18342            }
18343            else {
18344                return uriToBlob(imageSrc).then((blob) => {
18345                    return blobToDataUri(blob).then((dataUri) => {
18346                        return processDataUri(dataUri, false, (base64) => {
18347                            return Optional.some(createBlobInfo$1(blobCache, blob, base64));
18348                        }).getOrThunk(invalidDataUri);
18349                    });
18350                });
18351            }
18352        }
18353        else if (startsWith(imageSrc, 'data:')) {
18354            return dataUriToBlobInfo(blobCache, imageSrc).fold(invalidDataUri, (blobInfo) => Promise.resolve(blobInfo));
18355        }
18356        else {
18357            // Not a blob or data URI so the image isn't a local image and isn't something that can be processed
18358            return Promise.reject('Unknown image data format');
18359        }
18360    };
18361
18362    // TINY-10350: A modification of the Regexes.link regex to specifically capture host.
18363    // eslint-disable-next-line max-len
18364    const hostCaptureRegex = /^(?:(?:(?:[A-Za-z][A-Za-z\d.+-]{0,14}:\/\/(?:[-.~*+=!&;:'%@?^${}(),\w]+@)?|www\.|[-;:&=+$,.\w]+@)([A-Za-z\d-]+(?:\.[A-Za-z\d-]+)*))(?::\d+)?(?:\/(?:[-.~*+=!;:'%@$(),\/\w]*[-~*+=%@$()\/\w])?)?(?:\?(?:[-.~*+=!&;:'%@?^${}(),\/\w]+)?)?(?:#(?:[-.~*+=!&;:'%@?^${}(),\/\w]+)?)?)$/;
18365    const extractHost = (url) => Optional.from(url.match(hostCaptureRegex)).bind((ms) => get$b(ms, 1)).map((h) => startsWith(h, 'www.') ? h.substring(4) : h);
18366
18367    const sandboxIframe = (iframeNode, exclusions) => {
18368        if (Optional.from(iframeNode.attr('src')).bind(extractHost).forall((host) => !contains$2(exclusions, host))) {
18369            iframeNode.attr('sandbox', '');
18370        }
18371    };
18372    const isMimeType = (mime, type) => startsWith(mime, `${type}/`);
18373    const getEmbedType = (type) => {
18374        if (isUndefined(type)) {
18375            return 'iframe';
18376        }
18377        else if (isMimeType(type, 'image')) {
18378            return 'img';
18379        }
18380        else if (isMimeType(type, 'video')) {
18381            return 'video';
18382        }
18383        else if (isMimeType(type, 'audio')) {
18384            return 'audio';
18385        }
18386        else {
18387            return 'iframe';
18388        }
18389    };
18390    const createSafeEmbed = ({ type, src, width, height } = {}, sandboxIframes, sandboxIframesExclusions) => {
18391        const name = getEmbedType(type);
18392        const embed = new AstNode(name, 1);
18393        embed.attr(name === 'audio' ? { src } : { src, width, height });
18394        // TINY-10349: Show controls for audio and video so the replaced embed is visible in editor.
18395        if (name === 'audio' || name === 'video') {
18396            embed.attr('controls', '');
18397        }
18398        if (name === 'iframe' && sandboxIframes) {
18399            sandboxIframe(embed, sandboxIframesExclusions);
18400        }
18401        return embed;
18402    };
18403
18404    const isBogusImage = (img) => isNonNullable(img.attr('data-mce-bogus'));
18405    const isInternalImageSource = (img) => img.attr('src') === Env.transparentSrc || isNonNullable(img.attr('data-mce-placeholder'));
18406    const registerBase64ImageFilter = (parser, settings) => {
18407        const { blob_cache: blobCache } = settings;
18408        if (blobCache) {
18409            const processImage = (img) => {
18410                const inputSrc = img.attr('src');
18411                if (isInternalImageSource(img) || isBogusImage(img) || isNullable(inputSrc)) {
18412                    return;
18413                }
18414                dataUriToBlobInfo(blobCache, inputSrc, true).each((blobInfo) => {
18415                    img.attr('src', blobInfo.blobUri());
18416                });
18417            };
18418            parser.addAttributeFilter('src', (nodes) => each$e(nodes, processImage));
18419        }
18420    };
18421    const register$4 = (parser, settings) => {
18422        const schema = parser.schema;
18423        parser.addAttributeFilter('href', (nodes) => {
18424            let i = nodes.length;
18425            const appendRel = (rel) => {
18426                const parts = rel.split(' ').filter((p) => p.length > 0);
18427                return parts.concat(['noopener']).sort().join(' ');
18428            };
18429            const addNoOpener = (rel) => {
18430                const newRel = rel ? Tools.trim(rel) : '';
18431                if (!/\b(noopener)\b/g.test(newRel)) {
18432                    return appendRel(newRel);
18433                }
18434                else {
18435                    return newRel;
18436                }
18437            };
18438            if (!settings.allow_unsafe_link_target) {
18439                while (i--) {
18440                    const node = nodes[i];
18441                    if (node.name === 'a' && node.attr('target') === '_blank') {
18442                        node.attr('rel', addNoOpener(node.attr('rel')));
18443                    }
18444                }
18445            }
18446        });
18447        // Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included.
18448        if (!settings.allow_html_in_named_anchor) {
18449            parser.addAttributeFilter('id,name', (nodes) => {
18450                let i = nodes.length, sibling, prevSibling, parent, node;
18451                while (i--) {
18452                    node = nodes[i];
18453                    if (node.name === 'a' && node.firstChild && !node.attr('href')) {
18454                        parent = node.parent;
18455                        // Move children after current node
18456                        sibling = node.lastChild;
18457                        while (sibling && parent) {
18458                            prevSibling = sibling.prev;
18459                            parent.insert(sibling, node);
18460                            sibling = prevSibling;
18461                        }
18462                    }
18463                }
18464            });
18465        }
18466        if (settings.fix_list_elements) {
18467            parser.addNodeFilter('ul,ol', (nodes) => {
18468                let i = nodes.length, node, parentNode;
18469                while (i--) {
18470                    node = nodes[i];
18471                    parentNode = node.parent;
18472                    if (parentNode && (parentNode.name === 'ul' || parentNode.name === 'ol')) {
18473                        if (node.prev && node.prev.name === 'li') {
18474                            node.prev.append(node);
18475                        }
18476                        else {
18477                            const li = new AstNode('li', 1);
18478                            li.attr('style', 'list-style-type: none');
18479                            node.wrap(li);
18480                        }
18481                    }
18482                }
18483            });
18484        }
18485        const validClasses = schema.getValidClasses();
18486        if (settings.validate && validClasses) {
18487            parser.addAttributeFilter('class', (nodes) => {
18488                let i = nodes.length;
18489                while (i--) {
18490                    const node = nodes[i];
18491                    const clazz = node.attr('class') ?? '';
18492                    const classList = Tools.explode(clazz, ' ');
18493                    let classValue = '';
18494                    for (let ci = 0; ci < classList.length; ci++) {
18495                        const className = classList[ci];
18496                        let valid = false;
18497                        let validClassesMap = validClasses['*'];
18498                        if (validClassesMap && validClassesMap[className]) {
18499                            valid = true;
18500                        }
18501                        validClassesMap = validClasses[node.name];
18502                        if (!valid && validClassesMap && validClassesMap[className]) {
18503                            valid = true;
18504                        }
18505                        if (valid) {
18506                            if (classValue) {
18507                                classValue += ' ';
18508                            }
18509                            classValue += className;
18510                        }
18511                    }
18512                    if (!classValue.length) {
18513                        classValue = null;
18514                    }
18515                    node.attr('class', classValue);
18516                }
18517            });
18518        }
18519        registerBase64ImageFilter(parser, settings);
18520        const shouldSandboxIframes = settings.sandbox_iframes ?? false;
18521        const sandboxIframesExclusions = unique$1(settings.sandbox_iframes_exclusions ?? []);
18522        if (settings.convert_unsafe_embeds) {
18523            parser.addNodeFilter('object,embed', (nodes) => each$e(nodes, (node) => {
18524                node.replace(createSafeEmbed({
18525                    type: node.attr('type'),
18526                    src: node.name === 'object' ? node.attr('data') : node.attr('src'),
18527                    width: node.attr('width'),
18528                    height: node.attr('height'),
18529                }, shouldSandboxIframes, sandboxIframesExclusions));
18530            }));
18531        }
18532        if (shouldSandboxIframes) {
18533            parser.addNodeFilter('iframe', (nodes) => each$e(nodes, (node) => sandboxIframe(node, sandboxIframesExclusions)));
18534        }
18535    };
18536
18537    /*! @license DOMPurify 3.3.2 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.3.2/LICENSE */
18538
18539    const {
18540      entries,
18541      setPrototypeOf,
18542      isFrozen,
18543      getPrototypeOf,
18544      getOwnPropertyDescriptor
18545    } = Object;
18546    let {
18547      freeze,
18548      seal,
18549      create: create$7
18550    } = Object; // eslint-disable-line import/no-mutable-exports
18551    let {
18552      apply,
18553      construct
18554    } = typeof Reflect !== 'undefined' && Reflect;
18555    if (!freeze) {
18556      freeze = function freeze(x) {
18557        return x;
18558      };
18559    }
18560    if (!seal) {
18561      seal = function seal(x) {
18562        return x;
18563      };
18564    }
18565    if (!apply) {
18566      apply = function apply(func, thisArg) {
18567        for (var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
18568          args[_key - 2] = arguments[_key];
18569        }
18570        return func.apply(thisArg, args);
18571      };
18572    }
18573    if (!construct) {
18574      construct = function construct(Func) {
18575        for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
18576          args[_key2 - 1] = arguments[_key2];
18577        }
18578        return new Func(...args);
18579      };
18580    }
18581    const arrayForEach = unapply(Array.prototype.forEach);
18582    const arrayLastIndexOf = unapply(Array.prototype.lastIndexOf);
18583    const arrayPop = unapply(Array.prototype.pop);
18584    const arrayPush = unapply(Array.prototype.push);
18585    const arraySplice = unapply(Array.prototype.splice);
18586    const stringToLowerCase = unapply(String.prototype.toLowerCase);
18587    const stringToString = unapply(String.prototype.toString);
18588    const stringMatch = unapply(String.prototype.match);
18589    const stringReplace = unapply(String.prototype.replace);
18590    const stringIndexOf = unapply(String.prototype.indexOf);
18591    const stringTrim = unapply(String.prototype.trim);
18592    const objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty);
18593    const regExpTest = unapply(RegExp.prototype.test);
18594    const typeErrorCreate = unconstruct(TypeError);
18595    /**
18596     * Creates a new function that calls the given function with a specified thisArg and arguments.
18597     *
18598     * @param func - The function to be wrapped and called.
18599     * @returns A new function that calls the given function with a specified thisArg and arguments.
18600     */
18601    function unapply(func) {
18602      return function (thisArg) {
18603        if (thisArg instanceof RegExp) {
18604          thisArg.lastIndex = 0;
18605        }
18606        for (var _len3 = arguments.length, args = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) {
18607          args[_key3 - 1] = arguments[_key3];
18608        }
18609        return apply(func, thisArg, args);
18610      };
18611    }
18612    /**
18613     * Creates a new function that constructs an instance of the given constructor function with the provided arguments.
18614     *
18615     * @param func - The constructor function to be wrapped and called.
18616     * @returns A new function that constructs an instance of the given constructor function with the provided arguments.
18617     */
18618    function unconstruct(Func) {
18619      return function () {
18620        for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
vendor: 6,587 bytes, lines 18621-18733
18621          args[_key4] = arguments[_key4];
18622        }
18623        return construct(Func, args);
18624      };
18625    }
18626    /**
18627     * Add properties to a lookup table
18628     *
18629     * @param set - The set to which elements will be added.
18630     * @param array - The array containing elements to be added to the set.
18631     * @param transformCaseFunc - An optional function to transform the case of each element before adding to the set.
18632     * @returns The modified set with added elements.
18633     */
18634    function addToSet(set, array) {
18635      let transformCaseFunc = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : stringToLowerCase;
18636      if (setPrototypeOf) {
18637        // Make 'in' and truthy checks like Boolean(set.constructor)
18638        // independent of any properties defined on Object.prototype.
18639        // Prevent prototype setters from intercepting set as a this value.
18640        setPrototypeOf(set, null);
18641      }
18642      let l = array.length;
18643      while (l--) {
18644        let element = array[l];
18645        if (typeof element === 'string') {
18646          const lcElement = transformCaseFunc(element);
18647          if (lcElement !== element) {
18648            // Config presets (e.g. tags.js, attrs.js) are immutable.
18649            if (!isFrozen(array)) {
18650              array[l] = lcElement;
18651            }
18652            element = lcElement;
18653          }
18654        }
18655        set[element] = true;
18656      }
18657      return set;
18658    }
18659    /**
18660     * Clean up an array to harden against CSPP
18661     *
18662     * @param array - The array to be cleaned.
18663     * @returns The cleaned version of the array
18664     */
18665    function cleanArray(array) {
18666      for (let index = 0; index < array.length; index++) {
18667        const isPropertyExist = objectHasOwnProperty(array, index);
18668        if (!isPropertyExist) {
18669          array[index] = null;
18670        }
18671      }
18672      return array;
18673    }
18674    /**
18675     * Shallow clone an object
18676     *
18677     * @param object - The object to be cloned.
18678     * @returns A new object that copies the original.
18679     */
18680    function clone(object) {
18681      const newObject = create$7(null);
18682      for (const [property, value] of entries(object)) {
18683        const isPropertyExist = objectHasOwnProperty(object, property);
18684        if (isPropertyExist) {
18685          if (Array.isArray(value)) {
18686            newObject[property] = cleanArray(value);
18687          } else if (value && typeof value === 'object' && value.constructor === Object) {
18688            newObject[property] = clone(value);
18689          } else {
18690            newObject[property] = value;
18691          }
18692        }
18693      }
18694      return newObject;
18695    }
18696    /**
18697     * This method automatically checks if the prop is function or getter and behaves accordingly.
18698     *
18699     * @param object - The object to look up the getter function in its prototype chain.
18700     * @param prop - The property name for which to find the getter function.
18701     * @returns The getter function found in the prototype chain or a fallback function.
18702     */
18703    function lookupGetter(object, prop) {
18704      while (object !== null) {
18705        const desc = getOwnPropertyDescriptor(object, prop);
18706        if (desc) {
18707          if (desc.get) {
18708            return unapply(desc.get);
18709          }
18710          if (typeof desc.value === 'function') {
18711            return unapply(desc.value);
18712          }
18713        }
18714        object = getPrototypeOf(object);
18715      }
18716      function fallbackValue() {
18717        return null;
18718      }
18719      return fallbackValue;
18720    }
18721
18722    const html$1 = freeze(['a', 'abbr', 'acronym', 'address', 'area', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', 'big', 'blink', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'content', 'data', 'datalist', 'dd', 'decorator', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'element', 'em', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'main', 'map', 'mark', 'marquee', 'menu', 'menuitem', 'meter', 'nav', 'nobr', 'ol', 'optgroup', 'option', 'output', 'p', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'search', 'section', 'select', 'shadow', 'slot', 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr']);
18723    const svg$1 = freeze(['svg', 'a', 'altglyph', 'altglyphdef', 'altglyphitem', 'animatecolor', 'animatemotion', 'animatetransform', 'circle', 'clippath', 'defs', 'desc', 'ellipse', 'enterkeyhint', 'exportparts', 'filter', 'font', 'g', 'glyph', 'glyphref', 'hkern', 'image', 'inputmode', 'line', 'lineargradient', 'marker', 'mask', 'metadata', 'mpath', 'part', 'path', 'pattern', 'polygon', 'polyline', 'radialgradient', 'rect', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'view', 'vkern']);
18724    const svgFilters = freeze(['feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feDistantLight', 'feDropShadow', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feImage', 'feMerge', 'feMergeNode', 'feMorphology', 'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile', 'feTurbulence']);
18725    // List of SVG elements that are disallowed by default.
18726    // We still need to know them so that we can do namespace
18727    // checks properly in case one wants to add them to
18728    // allow-list.
18729    const svgDisallowed = freeze(['animate', 'color-profile', 'cursor', 'discard', 'font-face', 'font-face-format', 'font-face-name', 'font-face-src', 'font-face-uri', 'foreignobject', 'hatch', 'hatchpath', 'mesh', 'meshgradient', 'meshpatch', 'meshrow', 'missing-glyph', 'script', 'set', 'solidcolor', 'unknown', 'use']);
18730    const mathMl$1 = freeze(['math', 'menclose', 'merror', 'mfenced', 'mfrac', 'mglyph', 'mi', 'mlabeledtr', 'mmultiscripts', 'mn', 'mo', 'mover', 'mpadded', 'mphantom', 'mroot', 'mrow', 'ms', 'mspace', 'msqrt', 'mstyle', 'msub', 'msup', 'msubsup', 'mtable', 'mtd', 'mtext', 'mtr', 'munder', 'munderover', 'mprescripts']);
18731    // Similarly to SVG, we want to know all MathML elements,
18732    // even those that we disallow by default.
18733    const mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'n
18733one']);
18734    const text = freeze(['#text']);
18735
18736    const html = freeze(['accept', 'action', 'align', 'alt', 'autocapitalize', 'autocomplete', 'autopictureinpicture', 'autoplay', 'background', 'bgcolor', 'border', 'capture', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', 'controls', 'controlslist', 'coords', 'crossorigin', 'datetime', 'decoding', 'default', 'dir', 'disabled', 'disablepictureinpicture', 'disableremoteplayback', 'download', 'draggable', 'enctype', 'enterkeyhint', 'exportparts', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', 'inert', 'inputmode', 'integrity', 'ismap', 'kind', 'label', 'lang', 'list', 'loading', 'loop', 'low', 'max', 'maxlength', 'media', 'method', 'min', 'minlength', 'multiple', 'muted', 'name', 'nonce', 'noshade', 'novalidate', 'nowrap', 'open', 'optimum', 'part', 'pattern', 'placeholder', 'playsinline', 'popover', 'popovertarget', 'popovertargetaction', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'slot', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'translate', 'type', 'usemap', 'valign', 'value', 'width', 'wrap', 'xmlns', 'slot']);
18737    const svg = freeze(['accent-height', 'accumulate', 'additive', 'alignment-baseline', 'amplitude', 'ascent', 'attributename', 'attributetype', 'azimuth', 'basefrequency', 'baseline-shift', 'begin', 'bias', 'by', 'class', 'clip', 'clippathunits', 'clip-path', 'clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cx', 'cy', 'd', 'dx', 'dy', 'diffuseconstant', 'direction', 'display', 'divisor', 'dur', 'edgemode', 'elevation', 'end', 'exponent', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'filterunits', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'fx', 'fy', 'g1', 'g2', 'glyph-name', 'glyphref', 'gradientunits', 'gradienttransform', 'height', 'href', 'id', 'image-rendering', 'in', 'in2', 'intercept', 'k', 'k1', 'k2', 'k3', 'k4', 'kerning', 'keypoints', 'keysplines', 'keytimes', 'lang', 'lengthadjust', 'letter-spacing', 'kernelmatrix', 'kernelunitlength', 'lighting-color', 'local', 'marker-end', 'marker-mid', 'marker-start', 'markerheight', 'markerunits', 'markerwidth', 'maskcontentunits', 'maskunits', 'max', 'mask', 'mask-type', 'media', 'method', 'mode', 'min', 'name', 'numoctaves', 'offset', 'operator', 'opacity', 'order', 'orient', 'orientation', 'origin', 'overflow', 'paint-order', 'path', 'pathlength', 'patterncontentunits', 'patterntransform', 'patternunits', 'points', 'preservealpha', 'preserveaspectratio', 'primitiveunits', 'r', 'rx', 'ry', 'radius', 'refx', 'refy', 'repeatcount', 'repeatdur', 'restart', 'result', 'rotate', 'scale', 'seed', 'shape-rendering', 'slope', 'specularconstant', 'specularexponent', 'spreadmethod', 'startoffset', 'stddeviation', 'stitchtiles', 'stop-color', 'stop-opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke', 'stroke-width', 'style', 'surfacescale', 'systemlanguage', 'tabindex', 'tablevalues', 'targetx', 'targety', 'transform', 'transform-origin', 'text-anchor', 'text-decoration', 'text-rendering', 'textlength', 'type', 'u1', 'u2', 'unicode', 'values', 'viewbox', 'visibility', 'version', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'width', 'word-spacing', 'wrap', 'writing-mode', 'xchannelselector', 'ychannelselector', 'x', 'x1', 'x2', 'xmlns', 'y', 'y1', 'y2', 'z', 'zoomandpan']);
18738    const mathMl = freeze(['accent', 'accentunder', 'align', 'bevelled', 'close', 'columnsalign', 'columnlines', 'columnspan', 'denomalign', 'depth', 'dir', 'display', 'displaystyle', 'encoding', 'fence', 'frame', 'height', 'href', 'id', 'largeop', 'length', 'linethickness', 'lspace', 'lquote', 'mathbackground', 'mathcolor', 'mathsize', 'mathvariant', 'maxsize', 'minsize', 'movablelimits', 'n
vendor: 9,941 bytes, lines 18738-18975
18738otation', 'numalign', 'open', 'rowalign', 'rowlines', 'rowspacing', 'rowspan', 'rspace', 'rquote', 'scriptlevel', 'scriptminsize', 'scriptsizemultiplier', 'selection', 'separator', 'separators', 'stretchy', 'subscriptshift', 'supscriptshift', 'symmetric', 'voffset', 'width', 'xmlns']);
18739    const xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);
18740
18741    // eslint-disable-next-line unicorn/better-regex
18742    const MUSTACHE_EXPR = seal(/\{\{[\w\W]*|[\w\W]*\}\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode
18743    const ERB_EXPR = seal(/<%[\w\W]*|[\w\W]*%>/gm);
18744    const TMPLIT_EXPR = seal(/\$\{[\w\W]*/gm); // eslint-disable-line unicorn/better-regex
18745    const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]+$/); // eslint-disable-line no-useless-escape
18746    const ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape
18747    const IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i // eslint-disable-line no-useless-escape
18748    );
18749    const IS_SCRIPT_OR_DATA = seal(/^(?:\w+script|data):/i);
18750    const ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g // eslint-disable-line no-control-regex
18751    );
18752    const DOCTYPE_NAME = seal(/^html$/i);
18753    const CUSTOM_ELEMENT = seal(/^[a-z][.\w]*(-[.\w]+)+$/i);
18754
18755    var EXPRESSIONS = /*#__PURE__*/Object.freeze({
18756      __proto__: null,
18757      ARIA_ATTR: ARIA_ATTR,
18758      ATTR_WHITESPACE: ATTR_WHITESPACE,
18759      CUSTOM_ELEMENT: CUSTOM_ELEMENT,
18760      DATA_ATTR: DATA_ATTR,
18761      DOCTYPE_NAME: DOCTYPE_NAME,
18762      ERB_EXPR: ERB_EXPR,
18763      IS_ALLOWED_URI: IS_ALLOWED_URI,
18764      IS_SCRIPT_OR_DATA: IS_SCRIPT_OR_DATA,
18765      MUSTACHE_EXPR: MUSTACHE_EXPR,
18766      TMPLIT_EXPR: TMPLIT_EXPR
18767    });
18768
18769    /* eslint-disable @typescript-eslint/indent */
18770    // https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
18771    const NODE_TYPE = {
18772      element: 1,
18773      attribute: 2,
18774      text: 3,
18775      cdataSection: 4,
18776      entityReference: 5,
18777      // Deprecated
18778      entityNode: 6,
18779      // Deprecated
18780      progressingInstruction: 7,
18781      comment: 8,
18782      document: 9,
18783      documentType: 10,
18784      documentFragment: 11,
18785      notation: 12 // Deprecated
18786    };
18787    const getGlobal = function getGlobal() {
18788      return typeof window === 'undefined' ? null : window;
18789    };
18790    /**
18791     * Creates a no-op policy for internal use only.
18792     * Don't export this function outside this module!
18793     * @param trustedTypes The policy factory.
18794     * @param purifyHostElement The Script element used to load DOMPurify (to determine policy name suffix).
18795     * @return The policy created (or null, if Trusted Types
18796     * are not supported or creating the policy failed).
18797     */
18798    const _createTrustedTypesPolicy = function _createTrustedTypesPolicy(trustedTypes, purifyHostElement) {
18799      if (typeof trustedTypes !== 'object' || typeof trustedTypes.createPolicy !== 'function') {
18800        return null;
18801      }
18802      // Allow the callers to control the unique policy name
18803      // by adding a data-tt-policy-suffix to the script element with the DOMPurify.
18804      // Policy creation with duplicate names throws in Trusted Types.
18805      let suffix = null;
18806      const ATTR_NAME = 'data-tt-policy-suffix';
18807      if (purifyHostElement && purifyHostElement.hasAttribute(ATTR_NAME)) {
18808        suffix = purifyHostElement.getAttribute(ATTR_NAME);
18809      }
18810      const policyName = 'dompurify' + (suffix ? '#' + suffix : '');
18811      try {
18812        return trustedTypes.createPolicy(policyName, {
18813          createHTML(html) {
18814            return html;
18815          },
18816          createScriptURL(scriptUrl) {
18817            return scriptUrl;
18818          }
18819        });
18820      } catch (_) {
18821        // Policy creation failed (most likely another DOMPurify script has
18822        // already run). Skip creating the policy, as this will only cause errors
18823        // if TT are enforced.
18824        console.warn('TrustedTypes policy ' + policyName + ' could not be created.');
18825        return null;
18826      }
18827    };
18828    const _createHooksMap = function _createHooksMap() {
18829      return {
18830        afterSanitizeAttributes: [],
18831        afterSanitizeElements: [],
18832        afterSanitizeShadowDOM: [],
18833        beforeSanitizeAttributes: [],
18834        beforeSanitizeElements: [],
18835        beforeSanitizeShadowDOM: [],
18836        uponSanitizeAttribute: [],
18837        uponSanitizeElement: [],
18838        uponSanitizeShadowNode: []
18839      };
18840    };
18841    function createDOMPurify() {
18842      let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
18843      const DOMPurify = root => createDOMPurify(root);
18844      DOMPurify.version = '3.3.2';
18845      DOMPurify.removed = [];
18846      if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
18847        // Not running in a browser, provide a factory function
18848        // so that you can pass your own Window
18849        DOMPurify.isSupported = false;
18850        return DOMPurify;
18851      }
18852      let {
18853        document
18854      } = window;
18855      const originalDocument = document;
18856      const currentScript = originalDocument.currentScript;
18857      const {
18858        DocumentFragment,
18859        HTMLTemplateElement,
18860        Node,
18861        Element,
18862        NodeFilter,
18863        NamedNodeMap = window.NamedNodeMap || window.MozNamedAttrMap,
18864        HTMLFormElement,
18865        DOMParser,
18866        trustedTypes
18867      } = window;
18868      const ElementPrototype = Element.prototype;
18869      const cloneNode = lookupGetter(ElementPrototype, 'cloneNode');
18870      const remove = lookupGetter(ElementPrototype, 'remove');
18871      const getNextSibling = lookupGetter(ElementPrototype, 'nextSibling');
18872      const getChildNodes = lookupGetter(ElementPrototype, 'childNodes');
18873      const getParentNode = lookupGetter(ElementPrototype, 'parentNode');
18874      // As per issue #47, the web-components registry is inherited by a
18875      // new document created via createHTMLDocument. As per the spec
18876      // (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)
18877      // a new empty registry is used when creating a template contents owner
18878      // document, so we use that as our parent document to ensure nothing
18879      // is inherited.
18880      if (typeof HTMLTemplateElement === 'function') {
18881        const template = document.createElement('template');
18882        if (template.content && template.content.ownerDocument) {
18883          document = template.content.ownerDocument;
18884        }
18885      }
18886      let trustedTypesPolicy;
18887      let emptyHTML = '';
18888      const {
18889        implementation,
18890        createNodeIterator,
18891        createDocumentFragment,
18892        getElementsByTagName
18893      } = document;
18894      const {
18895        importNode
18896      } = originalDocument;
18897      let hooks = _createHooksMap();
18898      /**
18899       * Expose whether this browser supports running the full DOMPurify.
18900       */
18901      DOMPurify.isSupported = typeof entries === 'function' && typeof getParentNode === 'function' && implementation && implementation.createHTMLDocument !== undefined;
18902      const {
18903        MUSTACHE_EXPR,
18904        ERB_EXPR,
18905        TMPLIT_EXPR,
18906        DATA_ATTR,
18907        ARIA_ATTR,
18908        IS_SCRIPT_OR_DATA,
18909        ATTR_WHITESPACE,
18910        CUSTOM_ELEMENT
18911      } = EXPRESSIONS;
18912      let {
18913        IS_ALLOWED_URI: IS_ALLOWED_URI$1
18914      } = EXPRESSIONS;
18915      /**
18916       * We consider the elements and attributes below to be safe. Ideally
18917       * don't add any new ones but feel free to remove unwanted ones.
18918       */
18919      /* allowed element names */
18920      let ALLOWED_TAGS = null;
18921      const DEFAULT_ALLOWED_TAGS = addToSet({}, [...html$1, ...svg$1, ...svgFilters, ...mathMl$1, ...text]);
18922      /* Allowed attribute names */
18923      let ALLOWED_ATTR = null;
18924      const DEFAULT_ALLOWED_ATTR = addToSet({}, [...html, ...svg, ...mathMl, ...xml]);
18925      /*
18926       * Configure how DOMPurify should handle custom elements and their attributes as well as customized built-in elements.
18927       * @property {RegExp|Function|null} tagNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any custom elements)
18928       * @property {RegExp|Function|null} attributeNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any attributes not on the allow list)
18929       * @property {boolean} allowCustomizedBuiltInElements allow custom elements derived from built-ins if they pass CUSTOM_ELEMENT_HANDLING.tagNameCheck. Default: `false`.
18930       */
18931      let CUSTOM_ELEMENT_HANDLING = Object.seal(create$7(null, {
18932        tagNameCheck: {
18933          writable: true,
18934          configurable: false,
18935          enumerable: true,
18936          value: null
18937        },
18938        attributeNameCheck: {
18939          writable: true,
18940          configurable: false,
18941          enumerable: true,
18942          value: null
18943        },
18944        allowCustomizedBuiltInElements: {
18945          writable: true,
18946          configurable: false,
18947          enumerable: true,
18948          value: false
18949        }
18950      }));
18951      /* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */
18952      let FORBID_TAGS = null;
18953      /* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */
18954      let FORBID_ATTR = null;
18955      /* Config object to store ADD_TAGS/ADD_ATTR functions (when used as functions) */
18956      const EXTRA_ELEMENT_HANDLING = Object.seal(create$7(null, {
18957        tagCheck: {
18958          writable: true,
18959          configurable: false,
18960          enumerable: true,
18961          value: null
18962        },
18963        attributeCheck: {
18964          writable: true,
18965          configurable: false,
18966          enumerable: true,
18967          value: null
18968        }
18969      }));
18970      /* Decide if ARIA attributes are okay */
18971      let ALLOW_ARIA_ATTR = true;
18972      /* Decide if custom data attributes are okay */
18973      let ALLOW_DATA_ATTR = true;
18974      /* Decide if unknown protocols are okay */
18975      let ALLOW_UNKNOWN_PROTOCOLS = false;
18976      /* Decide if self-closing tags in attributes are allowed.
18977       * Usually removed due to a mXSS issue in jQuery 3.0 */
18978      let ALLOW_SELF_CLOSE_IN_ATTR = true;
18979      /* Output should be safe for common template engines.
18980       * This means, DOMPurify removes data attributes, mustaches and ERB
18981       */
18982      let SAFE_FOR_TEMPLATES = false;
18983      /* Output should be safe even for XML used within HTML and alike.
18984       * This means, DOMPurify removes comments when containing risky content.
18985       */
18986      let SAFE_FOR_XML = true;
18987      /* Decide if document with <html>... should be returned */
18988      let WHOLE_DOCUMENT = false;
18989      /* Track whether config is already set on this instance of DOMPurify. */
18990      let SET_CONFIG = false;
18991      /* Decide if all elements (e.g. style, script) must be children of
18992       * document.body. By default, browsers might move them to document.head */
18993      let FORCE_BODY = false;
18994      /* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html
18995       * string (or a TrustedHTML object if Trusted Types are supported).
18996       * If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead
18997       */
18998      let RETURN_DOM = false;
18999      /* Decide if a DOM `DocumentFragment` should be returned, instead of a html
19000       * string  (or a TrustedHTML object if Trusted Types are supported) */
19001      let RETURN_DOM_FRAGMENT = false;
19002      /* Try to return a Trusted Type object instead of a string, return a string in
19003       * case Trusted Types are not supported  */
19004      let RETURN_TRUSTED_TYPE = false;
19005      /* Output should be free from DOM clobbering attacks?
19006       * This sanitizes markups named with colliding, clobberable built-in DOM APIs.
19007       */
19008      let SANITIZE_DOM = true;
19009      /* Achieve full DOM Clobbering protection by isolating the namespace of named
19010       * properties and JS variables, mitigating attacks that abuse the HTML/DOM spec rules.
19011       *
19012       * HTML/DOM spec rules that enable DOM Clobbering:
19013       *   - Named Access on Window (§7.3.3)
19014       *   - DOM Tree Accessors (§3.1.5)
19015       *   - Form Element Parent-Child Relations (§4.10.3)
19016       *   - Iframe srcdoc / Nested WindowProxies (§4.8.5)
19017       *   - HTMLCollection (§4.2.10.2)
19018       *
19019       * Namespace isolation is implemented by prefixing `id` and `name` attributes
19020       * with a constant string, i.e., `user-content-`
19021       */
19022      let SANITIZE_NAMED_PROPS = false;
19023      const SANITIZE_NAMED_PROPS_PREFIX = 'user-content-';
19024      /* Keep element content when removing element? */
19025      let KEEP_CONTENT = true;
19026      /* If a `Node` is passed to sanitize(), then performs sanitization in-place instead
19027       * of importing it into a new Document and returning a sanitized copy */
19028      let IN_PLACE = false;
19029      /* Allow usage of profiles like html, svg and mathMl */
19030      let USE_PROFILES = {};
19031      /* Tags to ignore content of when KEEP_CONTENT is true */
19032      let FORBID_CONTENTS = null;
19033      const DEFAULT_FORBID_CONTENTS = addToSet({}, ['annotation-xml', 'audio', 'colgroup', 'desc', 'foreignobject', 'head', 'iframe', 'math', 'mi', 'mn', 'mo', 'ms', 'mtext', 'noembed', 'noframes', 'n
vendor: 42,766 bytes, lines 19033-19922
19033oscript', 'plaintext', 'script', 'style', 'svg', 'template', 'thead', 'title', 'video', 'xmp']);
19034      /* Tags that are safe for data: URIs */
19035      let DATA_URI_TAGS = null;
19036      const DEFAULT_DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image', 'track']);
19037      /* Attributes safe for values like "javascript:" */
19038      let URI_SAFE_ATTRIBUTES = null;
19039      const DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'role', 'summary', 'title', 'value', 'style', 'xmlns']);
19040      const MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';
19041      const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
19042      const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
19043      /* Document namespace */
19044      let NAMESPACE = HTML_NAMESPACE;
19045      let IS_EMPTY_INPUT = false;
19046      /* Allowed XHTML+XML namespaces */
19047      let ALLOWED_NAMESPACES = null;
19048      const DEFAULT_ALLOWED_NAMESPACES = addToSet({}, [MATHML_NAMESPACE, SVG_NAMESPACE, HTML_NAMESPACE], stringToString);
19049      let MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']);
19050      let HTML_INTEGRATION_POINTS = addToSet({}, ['annotation-xml']);
19051      // Certain elements are allowed in both SVG and HTML
19052      // namespace. We need to specify them explicitly
19053      // so that they don't get erroneously deleted from
19054      // HTML namespace.
19055      const COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, ['title', 'style', 'font', 'a', 'script']);
19056      /* Parsing of strict XHTML documents */
19057      let PARSER_MEDIA_TYPE = null;
19058      const SUPPORTED_PARSER_MEDIA_TYPES = ['application/xhtml+xml', 'text/html'];
19059      const DEFAULT_PARSER_MEDIA_TYPE = 'text/html';
19060      let transformCaseFunc = null;
19061      /* Keep a reference to config to pass to hooks */
19062      let CONFIG = null;
19063      /* Ideally, do not touch anything below this line */
19064      /* ______________________________________________ */
19065      const formElement = document.createElement('form');
19066      const isRegexOrFunction = function isRegexOrFunction(testValue) {
19067        return testValue instanceof RegExp || testValue instanceof Function;
19068      };
19069      /**
19070       * _parseConfig
19071       *
19072       * @param cfg optional config literal
19073       */
19074      // eslint-disable-next-line complexity
19075      const _parseConfig = function _parseConfig() {
19076        let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
19077        if (CONFIG && CONFIG === cfg) {
19078          return;
19079        }
19080        /* Shield configuration object from tampering */
19081        if (!cfg || typeof cfg !== 'object') {
19082          cfg = {};
19083        }
19084        /* Shield configuration object from prototype pollution */
19085        cfg = clone(cfg);
19086        PARSER_MEDIA_TYPE =
19087        // eslint-disable-next-line unicorn/prefer-includes
19088        SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1 ? DEFAULT_PARSER_MEDIA_TYPE : cfg.PARSER_MEDIA_TYPE;
19089        // HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.
19090        transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? stringToString : stringToLowerCase;
19091        /* Set configuration parameters */
19092        ALLOWED_TAGS = objectHasOwnProperty(cfg, 'ALLOWED_TAGS') ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc) : DEFAULT_ALLOWED_TAGS;
19093        ALLOWED_ATTR = objectHasOwnProperty(cfg, 'ALLOWED_ATTR') ? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc) : DEFAULT_ALLOWED_ATTR;
19094        ALLOWED_NAMESPACES = objectHasOwnProperty(cfg, 'ALLOWED_NAMESPACES') ? addToSet({}, cfg.ALLOWED_NAMESPACES, stringToString) : DEFAULT_ALLOWED_NAMESPACES;
19095        URI_SAFE_ATTRIBUTES = objectHasOwnProperty(cfg, 'ADD_URI_SAFE_ATTR') ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), cfg.ADD_URI_SAFE_ATTR, transformCaseFunc) : DEFAULT_URI_SAFE_ATTRIBUTES;
19096        DATA_URI_TAGS = objectHasOwnProperty(cfg, 'ADD_DATA_URI_TAGS') ? addToSet(clone(DEFAULT_DATA_URI_TAGS), cfg.ADD_DATA_URI_TAGS, transformCaseFunc) : DEFAULT_DATA_URI_TAGS;
19097        FORBID_CONTENTS = objectHasOwnProperty(cfg, 'FORBID_CONTENTS') ? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc) : DEFAULT_FORBID_CONTENTS;
19098        FORBID_TAGS = objectHasOwnProperty(cfg, 'FORBID_TAGS') ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) : clone({});
19099        FORBID_ATTR = objectHasOwnProperty(cfg, 'FORBID_ATTR') ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : clone({});
19100        USE_PROFILES = objectHasOwnProperty(cfg, 'USE_PROFILES') ? cfg.USE_PROFILES : false;
19101        ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true
19102        ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true
19103        ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false
19104        ALLOW_SELF_CLOSE_IN_ATTR = cfg.ALLOW_SELF_CLOSE_IN_ATTR !== false; // Default true
19105        SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false; // Default false
19106        SAFE_FOR_XML = cfg.SAFE_FOR_XML !== false; // Default true
19107        WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false
19108        RETURN_DOM = cfg.RETURN_DOM || false; // Default false
19109        RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false
19110        RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false; // Default false
19111        FORCE_BODY = cfg.FORCE_BODY || false; // Default false
19112        SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true
19113        SANITIZE_NAMED_PROPS = cfg.SANITIZE_NAMED_PROPS || false; // Default false
19114        KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true
19115        IN_PLACE = cfg.IN_PLACE || false; // Default false
19116        IS_ALLOWED_URI$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI;
19117        NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE;
19118        MATHML_TEXT_INTEGRATION_POINTS = cfg.MATHML_TEXT_INTEGRATION_POINTS || MATHML_TEXT_INTEGRATION_POINTS;
19119        HTML_INTEGRATION_POINTS = cfg.HTML_INTEGRATION_POINTS || HTML_INTEGRATION_POINTS;
19120        CUSTOM_ELEMENT_HANDLING = cfg.CUSTOM_ELEMENT_HANDLING || {};
19121        if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck)) {
19122          CUSTOM_ELEMENT_HANDLING.tagNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck;
19123        }
19124        if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)) {
19125          CUSTOM_ELEMENT_HANDLING.attributeNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck;
19126        }
19127        if (cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements === 'boolean') {
19128          CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements;
19129        }
19130        if (SAFE_FOR_TEMPLATES) {
19131          ALLOW_DATA_ATTR = false;
19132        }
19133        if (RETURN_DOM_FRAGMENT) {
19134          RETURN_DOM = true;
19135        }
19136        /* Parse profile info */
19137        if (USE_PROFILES) {
19138          ALLOWED_TAGS = addToSet({}, text);
19139          ALLOWED_ATTR = create$7(null);
19140          if (USE_PROFILES.html === true) {
19141            addToSet(ALLOWED_TAGS, html$1);
19142            addToSet(ALLOWED_ATTR, html);
19143          }
19144          if (USE_PROFILES.svg === true) {
19145            addToSet(ALLOWED_TAGS, svg$1);
19146            addToSet(ALLOWED_ATTR, svg);
19147            addToSet(ALLOWED_ATTR, xml);
19148          }
19149          if (USE_PROFILES.svgFilters === true) {
19150            addToSet(ALLOWED_TAGS, svgFilters);
19151            addToSet(ALLOWED_ATTR, svg);
19152            addToSet(ALLOWED_ATTR, xml);
19153          }
19154          if (USE_PROFILES.mathMl === true) {
19155            addToSet(ALLOWED_TAGS, mathMl$1);
19156            addToSet(ALLOWED_ATTR, mathMl);
19157            addToSet(ALLOWED_ATTR, xml);
19158          }
19159        }
19160        /* Prevent function-based ADD_ATTR / ADD_TAGS from leaking across calls */
19161        if (!objectHasOwnProperty(cfg, 'ADD_TAGS')) {
19162          EXTRA_ELEMENT_HANDLING.tagCheck = null;
19163        }
19164        if (!objectHasOwnProperty(cfg, 'ADD_ATTR')) {
19165          EXTRA_ELEMENT_HANDLING.attributeCheck = null;
19166        }
19167        /* Merge configuration parameters */
19168        if (cfg.ADD_TAGS) {
19169          if (typeof cfg.ADD_TAGS === 'function') {
19170            EXTRA_ELEMENT_HANDLING.tagCheck = cfg.ADD_TAGS;
19171          } else {
19172            if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {
19173              ALLOWED_TAGS = clone(ALLOWED_TAGS);
19174            }
19175            addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);
19176          }
19177        }
19178        if (cfg.ADD_ATTR) {
19179          if (typeof cfg.ADD_ATTR === 'function') {
19180            EXTRA_ELEMENT_HANDLING.attributeCheck = cfg.ADD_ATTR;
19181          } else {
19182            if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {
19183              ALLOWED_ATTR = clone(ALLOWED_ATTR);
19184            }
19185            addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);
19186          }
19187        }
19188        if (cfg.ADD_URI_SAFE_ATTR) {
19189          addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc);
19190        }
19191        if (cfg.FORBID_CONTENTS) {
19192          if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
19193            FORBID_CONTENTS = clone(FORBID_CONTENTS);
19194          }
19195          addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc);
19196        }
19197        if (cfg.ADD_FORBID_CONTENTS) {
19198          if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
19199            FORBID_CONTENTS = clone(FORBID_CONTENTS);
19200          }
19201          addToSet(FORBID_CONTENTS, cfg.ADD_FORBID_CONTENTS, transformCaseFunc);
19202        }
19203        /* Add #text in case KEEP_CONTENT is set to true */
19204        if (KEEP_CONTENT) {
19205          ALLOWED_TAGS['#text'] = true;
19206        }
19207        /* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */
19208        if (WHOLE_DOCUMENT) {
19209          addToSet(ALLOWED_TAGS, ['html', 'head', 'body']);
19210        }
19211        /* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286, #365 */
19212        if (ALLOWED_TAGS.table) {
19213          addToSet(ALLOWED_TAGS, ['tbody']);
19214          delete FORBID_TAGS.tbody;
19215        }
19216        if (cfg.TRUSTED_TYPES_POLICY) {
19217          if (typeof cfg.TRUSTED_TYPES_POLICY.createHTML !== 'function') {
19218            throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');
19219          }
19220          if (typeof cfg.TRUSTED_TYPES_POLICY.createScriptURL !== 'function') {
19221            throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');
19222          }
19223          // Overwrite existing TrustedTypes policy.
19224          trustedTypesPolicy = cfg.TRUSTED_TYPES_POLICY;
19225          // Sign local variables required by `sanitize`.
19226          emptyHTML = trustedTypesPolicy.createHTML('');
19227        } else {
19228          // Uninitialized policy, attempt to initialize the internal dompurify policy.
19229          if (trustedTypesPolicy === undefined) {
19230            trustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, currentScript);
19231          }
19232          // If creating the internal policy succeeded sign internal variables.
19233          if (trustedTypesPolicy !== null && typeof emptyHTML === 'string') {
19234            emptyHTML = trustedTypesPolicy.createHTML('');
19235          }
19236        }
19237        // Prevent further manipulation of configuration.
19238        // Not available in IE8, Safari 5, etc.
19239        if (freeze) {
19240          freeze(cfg);
19241        }
19242        CONFIG = cfg;
19243      };
19244      /* Keep track of all possible SVG and MathML tags
19245       * so that we can perform the namespace checks
19246       * correctly. */
19247      const ALL_SVG_TAGS = addToSet({}, [...svg$1, ...svgFilters, ...svgDisallowed]);
19248      const ALL_MATHML_TAGS = addToSet({}, [...mathMl$1, ...mathMlDisallowed]);
19249      /**
19250       * @param element a DOM element whose namespace is being checked
19251       * @returns Return false if the element has a
19252       *  namespace that a spec-compliant parser would never
19253       *  return. Return true otherwise.
19254       */
19255      const _checkValidNamespace = function _checkValidNamespace(element) {
19256        let parent = getParentNode(element);
19257        // In JSDOM, if we're inside shadow DOM, then parentNode
19258        // can be null. We just simulate parent in this case.
19259        if (!parent || !parent.tagName) {
19260          parent = {
19261            namespaceURI: NAMESPACE,
19262            tagName: 'template'
19263          };
19264        }
19265        const tagName = stringToLowerCase(element.tagName);
19266        const parentTagName = stringToLowerCase(parent.tagName);
19267        if (!ALLOWED_NAMESPACES[element.namespaceURI]) {
19268          return false;
19269        }
19270        if (element.namespaceURI === SVG_NAMESPACE) {
19271          // The only way to switch from HTML namespace to SVG
19272          // is via <svg>. If it happens via any other tag, then
19273          // it should be killed.
19274          if (parent.namespaceURI === HTML_NAMESPACE) {
19275            return tagName === 'svg';
19276          }
19277          // The only way to switch from MathML to SVG is via`
19278          // svg if parent is either <annotation-xml> or MathML
19279          // text integration points.
19280          if (parent.namespaceURI === MATHML_NAMESPACE) {
19281            return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
19282          }
19283          // We only allow elements that are defined in SVG
19284          // spec. All others are disallowed in SVG namespace.
19285          return Boolean(ALL_SVG_TAGS[tagName]);
19286        }
19287        if (element.namespaceURI === MATHML_NAMESPACE) {
19288          // The only way to switch from HTML namespace to MathML
19289          // is via <math>. If it happens via any other tag, then
19290          // it should be killed.
19291          if (parent.namespaceURI === HTML_NAMESPACE) {
19292            return tagName === 'math';
19293          }
19294          // The only way to switch from SVG to MathML is via
19295          // <math> and HTML integration points
19296          if (parent.namespaceURI === SVG_NAMESPACE) {
19297            return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
19298          }
19299          // We only allow elements that are defined in MathML
19300          // spec. All others are disallowed in MathML namespace.
19301          return Boolean(ALL_MATHML_TAGS[tagName]);
19302        }
19303        if (element.namespaceURI === HTML_NAMESPACE) {
19304          // The only way to switch from SVG to HTML is via
19305          // HTML integration points, and from MathML to HTML
19306          // is via MathML text integration points
19307          if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {
19308            return false;
19309          }
19310          if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
19311            return false;
19312          }
19313          // We disallow tags that are specific for MathML
19314          // or SVG and should never appear in HTML namespace
19315          return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]);
19316        }
19317        // For XHTML and XML documents that support custom namespaces
19318        if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && ALLOWED_NAMESPACES[element.namespaceURI]) {
19319          return true;
19320        }
19321        // The code should never reach this place (this means
19322        // that the element somehow got namespace that is not
19323        // HTML, SVG, MathML or allowed via ALLOWED_NAMESPACES).
19324        // Return false just in case.
19325        return false;
19326      };
19327      /**
19328       * _forceRemove
19329       *
19330       * @param node a DOM node
19331       */
19332      const _forceRemove = function _forceRemove(node) {
19333        arrayPush(DOMPurify.removed, {
19334          element: node
19335        });
19336        try {
19337          // eslint-disable-next-line unicorn/prefer-dom-node-remove
19338          getParentNode(node).removeChild(node);
19339        } catch (_) {
19340          remove(node);
19341        }
19342      };
19343      /**
19344       * _removeAttribute
19345       *
19346       * @param name an Attribute name
19347       * @param element a DOM node
19348       */
19349      const _removeAttribute = function _removeAttribute(name, element) {
19350        try {
19351          arrayPush(DOMPurify.removed, {
19352            attribute: element.getAttributeNode(name),
19353            from: element
19354          });
19355        } catch (_) {
19356          arrayPush(DOMPurify.removed, {
19357            attribute: null,
19358            from: element
19359          });
19360        }
19361        element.removeAttribute(name);
19362        // We void attribute values for unremovable "is" attributes
19363        if (name === 'is') {
19364          if (RETURN_DOM || RETURN_DOM_FRAGMENT) {
19365            try {
19366              _forceRemove(element);
19367            } catch (_) {}
19368          } else {
19369            try {
19370              element.setAttribute(name, '');
19371            } catch (_) {}
19372          }
19373        }
19374      };
19375      /**
19376       * _initDocument
19377       *
19378       * @param dirty - a string of dirty markup
19379       * @return a DOM, filled with the dirty markup
19380       */
19381      const _initDocument = function _initDocument(dirty) {
19382        /* Create a HTML document */
19383        let doc = null;
19384        let leadingWhitespace = null;
19385        if (FORCE_BODY) {
19386          dirty = '<remove></remove>' + dirty;
19387        } else {
19388          /* If FORCE_BODY isn't used, leading whitespace needs to be preserved manually */
19389          const matches = stringMatch(dirty, /^[\r\n\t ]+/);
19390          leadingWhitespace = matches && matches[0];
19391        }
19392        if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && NAMESPACE === HTML_NAMESPACE) {
19393          // Root of XHTML doc must contain xmlns declaration (see https://www.w3.org/TR/xhtml1/normative.html#strict)
19394          dirty = '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' + dirty + '</body></html>';
19395        }
19396        const dirtyPayload = trustedTypesPolicy ? trustedTypesPolicy.createHTML(dirty) : dirty;
19397        /*
19398         * Use the DOMParser API by default, fallback later if needs be
19399         * DOMParser not work for svg when has multiple root element.
19400         */
19401        if (NAMESPACE === HTML_NAMESPACE) {
19402          try {
19403            doc = new DOMParser().parseFromString(dirtyPayload, PARSER_MEDIA_TYPE);
19404          } catch (_) {}
19405        }
19406        /* Use createHTMLDocument in case DOMParser is not available */
19407        if (!doc || !doc.documentElement) {
19408          doc = implementation.createDocument(NAMESPACE, 'template', null);
19409          try {
19410            doc.documentElement.innerHTML = IS_EMPTY_INPUT ? emptyHTML : dirtyPayload;
19411          } catch (_) {
19412            // Syntax error if dirtyPayload is invalid xml
19413          }
19414        }
19415        const body = doc.body || doc.documentElement;
19416        if (dirty && leadingWhitespace) {
19417          body.insertBefore(document.createTextNode(leadingWhitespace), body.childNodes[0] || null);
19418        }
19419        /* Work on whole document or just its body */
19420        if (NAMESPACE === HTML_NAMESPACE) {
19421          return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? 'html' : 'body')[0];
19422        }
19423        return WHOLE_DOCUMENT ? doc.documentElement : body;
19424      };
19425      /**
19426       * Creates a NodeIterator object that you can use to traverse filtered lists of nodes or elements in a document.
19427       *
19428       * @param root The root element or node to start traversing on.
19429       * @return The created NodeIterator
19430       */
19431      const _createNodeIterator = function _createNodeIterator(root) {
19432        return createNodeIterator.call(root.ownerDocument || root, root,
19433        // eslint-disable-next-line no-bitwise
19434        NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_PROCESSING_INSTRUCTION | NodeFilter.SHOW_CDATA_SECTION, null);
19435      };
19436      /**
19437       * _isClobbered
19438       *
19439       * @param element element to check for clobbering attacks
19440       * @return true if clobbered, false if safe
19441       */
19442      const _isClobbered = function _isClobbered(element) {
19443        return element instanceof HTMLFormElement && (typeof element.nodeName !== 'string' || typeof element.textContent !== 'string' || typeof element.removeChild !== 'function' || !(element.attributes instanceof NamedNodeMap) || typeof element.removeAttribute !== 'function' || typeof element.setAttribute !== 'function' || typeof element.namespaceURI !== 'string' || typeof element.insertBefore !== 'function' || typeof element.hasChildNodes !== 'function');
19444      };
19445      /**
19446       * Checks whether the given object is a DOM node.
19447       *
19448       * @param value object to check whether it's a DOM node
19449       * @return true is object is a DOM node
19450       */
19451      const _isNode = function _isNode(value) {
19452        return typeof Node === 'function' && value instanceof Node;
19453      };
19454      function _executeHooks(hooks, currentNode, data) {
19455        arrayForEach(hooks, hook => {
19456          hook.call(DOMPurify, currentNode, data, CONFIG);
19457        });
19458      }
19459      /**
19460       * _sanitizeElements
19461       *
19462       * @protect nodeName
19463       * @protect textContent
19464       * @protect removeChild
19465       * @param currentNode to check for permission to exist
19466       * @return true if node was killed, false if left alive
19467       */
19468      const _sanitizeElements = function _sanitizeElements(currentNode) {
19469        let content = null;
19470        /* Execute a hook if present */
19471        _executeHooks(hooks.beforeSanitizeElements, currentNode, null);
19472        /* Check if element is clobbered or can clobber */
19473        if (_isClobbered(currentNode)) {
19474          _forceRemove(currentNode);
19475          return true;
19476        }
19477        /* Now let's check the element's type and name */
19478        const tagName = transformCaseFunc(currentNode.nodeName);
19479        /* Execute a hook if present */
19480        _executeHooks(hooks.uponSanitizeElement, currentNode, {
19481          tagName,
19482          allowedTags: ALLOWED_TAGS
19483        });
19484        /* Detect mXSS attempts abusing namespace confusion */
19485        if (SAFE_FOR_XML && currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(/<[/\w!]/g, currentNode.innerHTML) && regExpTest(/<[/\w!]/g, currentNode.textContent)) {
19486          _forceRemove(currentNode);
19487          return true;
19488        }
19489        /* Remove any occurrence of processing instructions */
19490        if (currentNode.nodeType === NODE_TYPE.progressingInstruction) {
19491          _forceRemove(currentNode);
19492          return true;
19493        }
19494        /* Remove any kind of possibly harmful comments */
19495        if (SAFE_FOR_XML && currentNode.nodeType === NODE_TYPE.comment && regExpTest(/<[/\w]/g, currentNode.data)) {
19496          _forceRemove(currentNode);
19497          return true;
19498        }
19499        /* Remove element if anything forbids its presence */
19500        if (!(EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function && EXTRA_ELEMENT_HANDLING.tagCheck(tagName)) && (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName])) {
19501          /* Check if we have a custom element to handle */
19502          if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) {
19503            if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) {
19504              return false;
19505            }
19506            if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)) {
19507              return false;
19508            }
19509          }
19510          /* Keep content except for bad-listed elements */
19511          if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
19512            const parentNode = getParentNode(currentNode) || currentNode.parentNode;
19513            const childNodes = getChildNodes(currentNode) || currentNode.childNodes;
19514            if (childNodes && parentNode) {
19515              const childCount = childNodes.length;
19516              for (let i = childCount - 1; i >= 0; --i) {
19517                const childClone = cloneNode(childNodes[i], true);
19518                childClone.__removalCount = (currentNode.__removalCount || 0) + 1;
19519                parentNode.insertBefore(childClone, getNextSibling(currentNode));
19520              }
19521            }
19522          }
19523          _forceRemove(currentNode);
19524          return true;
19525        }
19526        /* Check whether element has a valid namespace */
19527        if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) {
19528          _forceRemove(currentNode);
19529          return true;
19530        }
19531        /* Make sure that older browsers don't get fallback-tag mXSS */
19532        if ((tagName === 'noscript' || tagName === 'noembed' || tagName === 'noframes') && regExpTest(/<\/no(script|embed|frames)/i, currentNode.innerHTML)) {
19533          _forceRemove(currentNode);
19534          return true;
19535        }
19536        /* Sanitize element content to be template-safe */
19537        if (SAFE_FOR_TEMPLATES && currentNode.nodeType === NODE_TYPE.text) {
19538          /* Get the element's text content */
19539          content = currentNode.textContent;
19540          arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
19541            content = stringReplace(content, expr, ' ');
19542          });
19543          if (currentNode.textContent !== content) {
19544            arrayPush(DOMPurify.removed, {
19545              element: currentNode.cloneNode()
19546            });
19547            currentNode.textContent = content;
19548          }
19549        }
19550        /* Execute a hook if present */
19551        _executeHooks(hooks.afterSanitizeElements, currentNode, null);
19552        return false;
19553      };
19554      /**
19555       * _isValidAttribute
19556       *
19557       * @param lcTag Lowercase tag name of containing element.
19558       * @param lcName Lowercase attribute name.
19559       * @param value Attribute value.
19560       * @return Returns true if `value` is valid, otherwise false.
19561       */
19562      // eslint-disable-next-line complexity
19563      const _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) {
19564        /* FORBID_ATTR must always win, even if ADD_ATTR predicate would allow it */
19565        if (FORBID_ATTR[lcName]) {
19566          return false;
19567        }
19568        /* Make sure attribute cannot clobber */
19569        if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) {
19570          return false;
19571        }
19572        /* Allow valid data-* attributes: At least one character after "-"
19573            (https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
19574            XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
19575            We don't need to check the value; it's always URI safe. */
19576        if (ALLOW_DATA_ATTR && !FORBID_ATTR[lcName] && regExpTest(DATA_ATTR, lcName)) ; else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR, lcName)) ; else if (EXTRA_ELEMENT_HANDLING.attributeCheck instanceof Function && EXTRA_ELEMENT_HANDLING.attributeCheck(lcName, lcTag)) ; else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) {
19577          if (
19578          // First condition does a very basic check if a) it's basically a valid custom element tagname AND
19579          // b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
19580          // and c) if the attribute name passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.attributeNameCheck
19581          _isBasicCustomElement(lcTag) && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, lcTag) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(lcTag)) && (CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.attributeNameCheck, lcName) || CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.attributeNameCheck(lcName, lcTag)) ||
19582          // Alternative, second condition checks if it's an `is`-attribute, AND
19583          // the value passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
19584          lcName === 'is' && CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, value) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(value))) ; else {
19585            return false;
19586          }
19587          /* Check value is safe. First, is attr inert? If so, is safe */
19588        } else if (URI_SAFE_ATTRIBUTES[lcName]) ; else if (regExpTest(IS_ALLOWED_URI$1, stringReplace(value, ATTR_WHITESPACE, ''))) ; else if ((lcName === 'src' || lcName === 'xlink:href' || lcName === 'href') && lcTag !== 'script' && stringIndexOf(value, 'data:') === 0 && DATA_URI_TAGS[lcTag]) ; else if (ALLOW_UNKNOWN_PROTOCOLS && !regExpTest(IS_SCRIPT_OR_DATA, stringReplace(value, ATTR_WHITESPACE, ''))) ; else if (value) {
19589          return false;
19590        } else ;
19591        return true;
19592      };
19593      /**
19594       * _isBasicCustomElement
19595       * checks if at least one dash is included in tagName, and it's not the first char
19596       * for more sophisticated checking see https://github.com/sindresorhus/validate-element-name
19597       *
19598       * @param tagName name of the tag of the node to sanitize
19599       * @returns Returns true if the tag name meets the basic criteria for a custom element, otherwise false.
19600       */
19601      const _isBasicCustomElement = function _isBasicCustomElement(tagName) {
19602        return tagName !== 'annotation-xml' && stringMatch(tagName, CUSTOM_ELEMENT);
19603      };
19604      /**
19605       * _sanitizeAttributes
19606       *
19607       * @protect attributes
19608       * @protect nodeName
19609       * @protect removeAttribute
19610       * @protect setAttribute
19611       *
19612       * @param currentNode to sanitize
19613       */
19614      const _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
19615        /* Execute a hook if present */
19616        _executeHooks(hooks.beforeSanitizeAttributes, currentNode, null);
19617        const {
19618          attributes
19619        } = currentNode;
19620        /* Check if we have attributes; if not we might have a text node */
19621        if (!attributes || _isClobbered(currentNode)) {
19622          return;
19623        }
19624        const hookEvent = {
19625          attrName: '',
19626          attrValue: '',
19627          keepAttr: true,
19628          allowedAttributes: ALLOWED_ATTR,
19629          forceKeepAttr: undefined
19630        };
19631        let l = attributes.length;
19632        /* Go backwards over all attributes; safely remove bad ones */
19633        while (l--) {
19634          const attr = attributes[l];
19635          const {
19636            name,
19637            namespaceURI,
19638            value: attrValue
19639          } = attr;
19640          const lcName = transformCaseFunc(name);
19641          const initValue = attrValue;
19642          let value = name === 'value' ? initValue : stringTrim(initValue);
19643          /* Execute a hook if present */
19644          hookEvent.attrName = lcName;
19645          hookEvent.attrValue = value;
19646          hookEvent.keepAttr = true;
19647          hookEvent.forceKeepAttr = undefined; // Allows developers to see this is a property they can set
19648          _executeHooks(hooks.uponSanitizeAttribute, currentNode, hookEvent);
19649          value = hookEvent.attrValue;
19650          /* Full DOM Clobbering protection via namespace isolation,
19651           * Prefix id and name attributes with `user-content-`
19652           */
19653          if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name')) {
19654            // Remove the attribute with this value
19655            _removeAttribute(name, currentNode);
19656            // Prefix the value and later re-create the attribute with the sanitized value
19657            value = SANITIZE_NAMED_PROPS_PREFIX + value;
19658          }
19659          /* Work around a security issue with comments inside attributes */
19660          if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i, value)) {
19661            _removeAttribute(name, currentNode);
19662            continue;
19663          }
19664          /* Make sure we cannot easily use animated hrefs, even if animations are allowed */
19665          if (lcName === 'attributename' && stringMatch(value, 'href')) {
19666            _removeAttribute(name, currentNode);
19667            continue;
19668          }
19669          /* Did the hooks approve of the attribute? */
19670          if (hookEvent.forceKeepAttr) {
19671            continue;
19672          }
19673          /* Did the hooks approve of the attribute? */
19674          if (!hookEvent.keepAttr) {
19675            _removeAttribute(name, currentNode);
19676            continue;
19677          }
19678          /* Work around a security issue in jQuery 3.0 */
19679          if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(/\/>/i, value)) {
19680            _removeAttribute(name, currentNode);
19681            continue;
19682          }
19683          /* Sanitize attribute content to be template-safe */
19684          if (SAFE_FOR_TEMPLATES) {
19685            arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
19686              value = stringReplace(value, expr, ' ');
19687            });
19688          }
19689          /* Is `value` valid for this attribute? */
19690          const lcTag = transformCaseFunc(currentNode.nodeName);
19691          if (!_isValidAttribute(lcTag, lcName, value)) {
19692            _removeAttribute(name, currentNode);
19693            continue;
19694          }
19695          /* Handle attributes that require Trusted Types */
19696          if (trustedTypesPolicy && typeof trustedTypes === 'object' && typeof trustedTypes.getAttributeType === 'function') {
19697            if (namespaceURI) ; else {
19698              switch (trustedTypes.getAttributeType(lcTag, lcName)) {
19699                case 'TrustedHTML':
19700                  {
19701                    value = trustedTypesPolicy.createHTML(value);
19702                    break;
19703                  }
19704                case 'TrustedScriptURL':
19705                  {
19706                    value = trustedTypesPolicy.createScriptURL(value);
19707                    break;
19708                  }
19709              }
19710            }
19711          }
19712          /* Handle invalid data-* attribute set by try-catching it */
19713          if (value !== initValue) {
19714            try {
19715              if (namespaceURI) {
19716                currentNode.setAttributeNS(namespaceURI, name, value);
19717              } else {
19718                /* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
19719                currentNode.setAttribute(name, value);
19720              }
19721              if (_isClobbered(currentNode)) {
19722                _forceRemove(currentNode);
19723              } else {
19724                arrayPop(DOMPurify.removed);
19725              }
19726            } catch (_) {
19727              _removeAttribute(name, currentNode);
19728            }
19729          }
19730        }
19731        /* Execute a hook if present */
19732        _executeHooks(hooks.afterSanitizeAttributes, currentNode, null);
19733      };
19734      /**
19735       * _sanitizeShadowDOM
19736       *
19737       * @param fragment to iterate over recursively
19738       */
19739      const _sanitizeShadowDOM = function _sanitizeShadowDOM(fragment) {
19740        let shadowNode = null;
19741        const shadowIterator = _createNodeIterator(fragment);
19742        /* Execute a hook if present */
19743        _executeHooks(hooks.beforeSanitizeShadowDOM, fragment, null);
19744        while (shadowNode = shadowIterator.nextNode()) {
19745          /* Execute a hook if present */
19746          _executeHooks(hooks.uponSanitizeShadowNode, shadowNode, null);
19747          /* Sanitize tags and elements */
19748          _sanitizeElements(shadowNode);
19749          /* Check attributes next */
19750          _sanitizeAttributes(shadowNode);
19751          /* Deep shadow DOM detected */
19752          if (shadowNode.content instanceof DocumentFragment) {
19753            _sanitizeShadowDOM(shadowNode.content);
19754          }
19755        }
19756        /* Execute a hook if present */
19757        _executeHooks(hooks.afterSanitizeShadowDOM, fragment, null);
19758      };
19759      // eslint-disable-next-line complexity
19760      DOMPurify.sanitize = function (dirty) {
19761        let cfg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
19762        let body = null;
19763        let importedNode = null;
19764        let currentNode = null;
19765        let returnNode = null;
19766        /* Make sure we have a string to sanitize.
19767          DO NOT return early, as this will return the wrong type if
19768          the user has requested a DOM object rather than a string */
19769        IS_EMPTY_INPUT = !dirty;
19770        if (IS_EMPTY_INPUT) {
19771          dirty = '<!-->';
19772        }
19773        /* Stringify, in case dirty is an object */
19774        if (typeof dirty !== 'string' && !_isNode(dirty)) {
19775          if (typeof dirty.toString === 'function') {
19776            dirty = dirty.toString();
19777            if (typeof dirty !== 'string') {
19778              throw typeErrorCreate('dirty is not a string, aborting');
19779            }
19780          } else {
19781            throw typeErrorCreate('toString is not a function');
19782          }
19783        }
19784        /* Return dirty HTML if DOMPurify cannot run */
19785        if (!DOMPurify.isSupported) {
19786          return dirty;
19787        }
19788        /* Assign config vars */
19789        if (!SET_CONFIG) {
19790          _parseConfig(cfg);
19791        }
19792        /* Clean up removed elements */
19793        DOMPurify.removed = [];
19794        /* Check if dirty is correctly typed for IN_PLACE */
19795        if (typeof dirty === 'string') {
19796          IN_PLACE = false;
19797        }
19798        if (IN_PLACE) {
19799          /* Do some early pre-sanitization to avoid unsafe root nodes */
19800          if (dirty.nodeName) {
19801            const tagName = transformCaseFunc(dirty.nodeName);
19802            if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
19803              throw typeErrorCreate('root node is forbidden and cannot be sanitized in-place');
19804            }
19805          }
19806        } else if (dirty instanceof Node) {
19807          /* If dirty is a DOM element, append to an empty document to avoid
19808             elements being stripped by the parser */
19809          body = _initDocument('<!---->');
19810          importedNode = body.ownerDocument.importNode(dirty, true);
19811          if (importedNode.nodeType === NODE_TYPE.element && importedNode.nodeName === 'BODY') {
19812            /* Node is already a body, use as is */
19813            body = importedNode;
19814          } else if (importedNode.nodeName === 'HTML') {
19815            body = importedNode;
19816          } else {
19817            // eslint-disable-next-line unicorn/prefer-dom-node-append
19818            body.appendChild(importedNode);
19819          }
19820        } else {
19821          /* Exit directly if we have nothing to do */
19822          if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT &&
19823          // eslint-disable-next-line unicorn/prefer-includes
19824          dirty.indexOf('<') === -1) {
19825            return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(dirty) : dirty;
19826          }
19827          /* Initialize the document to work on */
19828          body = _initDocument(dirty);
19829          /* Check we have a DOM node from the data */
19830          if (!body) {
19831            return RETURN_DOM ? null : RETURN_TRUSTED_TYPE ? emptyHTML : '';
19832          }
19833        }
19834        /* Remove first element node (ours) if FORCE_BODY is set */
19835        if (body && FORCE_BODY) {
19836          _forceRemove(body.firstChild);
19837        }
19838        /* Get node iterator */
19839        const nodeIterator = _createNodeIterator(IN_PLACE ? dirty : body);
19840        /* Now start iterating over the created document */
19841        while (currentNode = nodeIterator.nextNode()) {
19842          /* Sanitize tags and elements */
19843          _sanitizeElements(currentNode);
19844          /* Check attributes next */
19845          _sanitizeAttributes(currentNode);
19846          /* Shadow DOM detected, sanitize it */
19847          if (currentNode.content instanceof DocumentFragment) {
19848            _sanitizeShadowDOM(currentNode.content);
19849          }
19850        }
19851        /* If we sanitized `dirty` in-place, return it. */
19852        if (IN_PLACE) {
19853          return dirty;
19854        }
19855        /* Return sanitized string or DOM */
19856        if (RETURN_DOM) {
19857          if (RETURN_DOM_FRAGMENT) {
19858            returnNode = createDocumentFragment.call(body.ownerDocument);
19859            while (body.firstChild) {
19860              // eslint-disable-next-line unicorn/prefer-dom-node-append
19861              returnNode.appendChild(body.firstChild);
19862            }
19863          } else {
19864            returnNode = body;
19865          }
19866          if (ALLOWED_ATTR.shadowroot || ALLOWED_ATTR.shadowrootmode) {
19867            /*
19868              AdoptNode() is not used because internal state is not reset
19869              (e.g. the past names map of a HTMLFormElement), this is safe
19870              in theory but we would rather not risk another attack vector.
19871              The state that is cloned by importNode() is explicitly defined
19872              by the specs.
19873            */
19874            returnNode = importNode.call(originalDocument, returnNode, true);
19875          }
19876          return returnNode;
19877        }
19878        let serializedHTML = WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML;
19879        /* Serialize doctype if allowed */
19880        if (WHOLE_DOCUMENT && ALLOWED_TAGS['!doctype'] && body.ownerDocument && body.ownerDocument.doctype && body.ownerDocument.doctype.name && regExpTest(DOCTYPE_NAME, body.ownerDocument.doctype.name)) {
19881          serializedHTML = '<!DOCTYPE ' + body.ownerDocument.doctype.name + '>\n' + serializedHTML;
19882        }
19883        /* Sanitize final string template-safe */
19884        if (SAFE_FOR_TEMPLATES) {
19885          arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
19886            serializedHTML = stringReplace(serializedHTML, expr, ' ');
19887          });
19888        }
19889        return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(serializedHTML) : serializedHTML;
19890      };
19891      DOMPurify.setConfig = function () {
19892        let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
19893        _parseConfig(cfg);
19894        SET_CONFIG = true;
19895      };
19896      DOMPurify.clearConfig = function () {
19897        CONFIG = null;
19898        SET_CONFIG = false;
19899      };
19900      DOMPurify.isValidAttribute = function (tag, attr, value) {
19901        /* Initialize shared config vars if necessary. */
19902        if (!CONFIG) {
19903          _parseConfig({});
19904        }
19905        const lcTag = transformCaseFunc(tag);
19906        const lcName = transformCaseFunc(attr);
19907        return _isValidAttribute(lcTag, lcName, value);
19908      };
19909      DOMPurify.addHook = function (entryPoint, hookFunction) {
19910        if (typeof hookFunction !== 'function') {
19911          return;
19912        }
19913        arrayPush(hooks[entryPoint], hookFunction);
19914      };
19915      DOMPurify.removeHook = function (entryPoint, hookFunction) {
19916        if (hookFunction !== undefined) {
19917          const index = arrayLastIndexOf(hooks[entryPoint], hookFunction);
19918          return index === -1 ? undefined : arraySplice(hooks[entryPoint], index, 1)[0];
19919        }
19920        return arrayPop(hooks[entryPoint]);
19921      };
19922      DOMPurify.removeHooks = function (entryPoint) {
vendor: 18,380 bytes, lines 19922-20388
19922
19923        hooks[entryPoint] = [];
19924      };
19925      DOMPurify.removeAllHooks = function () {
19926        hooks = _createHooksMap();
19927      };
19928      return DOMPurify;
19929    }
19930    var purify = createDOMPurify();
19931
19932    /**
19933     * This class handles parsing, modification and serialization of URI/URL strings.
19934     * @class tinymce.util.URI
19935     */
19936    const each$6 = Tools.each, trim = Tools.trim;
19937    const queryParts = [
19938        'source', 'protocol', 'authority', 'userInfo', 'user', 'password', 'host',
19939        'port', 'relative', 'path', 'directory', 'file', 'query', 'anchor'
19940    ];
19941    const DEFAULT_PORTS = {
19942        ftp: 21,
19943        http: 80,
19944        https: 443,
19945        mailto: 25
19946    };
19947    const safeSvgDataUrlElements = ['img', 'video'];
19948    const blockSvgDataUris = (allowSvgDataUrls, tagName) => {
19949        if (isNonNullable(allowSvgDataUrls)) {
19950            return !allowSvgDataUrls;
19951        }
19952        else {
19953            // Only allow SVGs by default on images/videos since the browser won't execute scripts on those elements
19954            return isNonNullable(tagName) ? !contains$2(safeSvgDataUrlElements, tagName) : true;
19955        }
19956    };
19957    const decodeUri = (encodedUri) => {
19958        try {
19959            // Might throw malformed URI sequence
19960            return decodeURIComponent(encodedUri);
19961        }
19962        catch {
19963            // Fallback to non UTF-8 decoder
19964            return unescape(encodedUri);
19965        }
19966    };
19967    const isInvalidUri = (settings, uri, tagName) => {
19968        // remove all whitespaces from decoded uri to prevent impact on regex matching
19969        const decodedUri = decodeUri(uri).replace(/\s/g, '');
19970        if (settings.allow_script_urls) {
19971            return false;
19972            // Ensure we don't have a javascript URI, as that is not safe since it allows arbitrary JavaScript execution
19973        }
19974        else if (/((java|vb)script|mhtml):/i.test(decodedUri)) {
19975            return true;
19976        }
19977        else if (settings.allow_html_data_urls) {
19978            return false;
19979        }
19980        else if (/^data:image\//i.test(decodedUri)) {
19981            return blockSvgDataUris(settings.allow_svg_data_urls, tagName) && /^data:image\/svg\+xml/i.test(decodedUri);
19982        }
19983        else {
19984            return /^data:/i.test(decodedUri);
19985        }
19986    };
19987    class URI {
19988        static parseDataUri(uri) {
19989            let type;
19990            const uriComponents = decodeURIComponent(uri).split(',');
19991            const matches = /data:([^;]+)/.exec(uriComponents[0]);
19992            if (matches) {
19993                type = matches[1];
19994            }
19995            return {
19996                type,
19997                data: uriComponents[1]
19998            };
19999        }
20000        /**
20001         * Check to see if a URI is safe to use in the Document Object Model (DOM). This will return
20002         * true if the URI can be used in the DOM without potentially triggering a security issue.
20003         *
20004         * @method isDomSafe
20005         * @static
20006         * @param {String} uri The URI to be validated.
20007         * @param {Object} context An optional HTML tag name where the element is being used.
20008         * @param {Object} options An optional set of options to use when determining if the URI is safe.
20009         * @return {Boolean} True if the URI is safe, otherwise false.
20010         */
20011        static isDomSafe(uri, context, options = {}) {
20012            if (options.allow_script_urls) {
20013                return true;
20014            }
20015            else {
20016                const decodedUri = Entities.decode(uri).replace(/[\s\u0000-\u001F]+/g, '');
20017                return !isInvalidUri(options, decodedUri, context);
20018            }
20019        }
20020        static getDocumentBaseUrl(loc) {
20021            let baseUrl;
20022            // Pass applewebdata:// and other non web protocols though
20023            if (loc.protocol.indexOf('http') !== 0 && loc.protocol !== 'file:') {
20024                baseUrl = loc.href ?? '';
20025            }
20026            else {
20027                baseUrl = loc.protocol + '//' + loc.host + loc.pathname;
20028            }
20029            if (/^[^:]+:\/\/\/?[^\/]+\//.test(baseUrl)) {
20030                baseUrl = baseUrl.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
20031                if (!/[\/\\]$/.test(baseUrl)) {
20032                    baseUrl += '/';
20033                }
20034            }
20035            return baseUrl;
20036        }
20037        source;
20038        protocol;
20039        authority;
20040        userInfo;
20041        user;
20042        password;
20043        host;
20044        port;
20045        relative;
20046        path = '';
20047        directory = '';
20048        file;
20049        query;
20050        anchor;
20051        settings;
20052        /**
20053         * Constructs a new URI instance.
20054         *
20055         * @constructor
20056         * @method URI
20057         * @param {String} url URI string to parse.
20058         * @param {Object} settings Optional settings object.
20059         */
20060        constructor(url, settings = {}) {
20061            url = trim(url);
20062            this.settings = settings;
20063            const baseUri = settings.base_uri;
20064            const self = this;
20065            // Strange app protocol that isn't http/https or local anchor
20066            // For example: mailto,skype,tel etc.
20067            if (/^([\w\-]+):([^\/]{2})/i.test(url) || /^\s*#/.test(url)) {
20068                self.source = url;
20069                return;
20070            }
20071            const isProtocolRelative = url.indexOf('//') === 0;
20072            // Absolute path with no host, fake host and protocol
20073            if (url.indexOf('/') === 0 && !isProtocolRelative) {
20074                url = (baseUri ? baseUri.protocol || 'http' : 'http') + '://mce_host' + url;
20075            }
20076            // Relative path http:// or protocol relative //path
20077            if (!/^[\w\-]*:?\/\//.test(url)) {
20078                const baseUrl = baseUri ? baseUri.path : new URI(document.location.href).directory;
20079                if (baseUri?.protocol === '') {
20080                    url = '//mce_host' + self.toAbsPath(baseUrl, url);
20081                }
20082                else {
20083                    const match = /([^#?]*)([#?]?.*)/.exec(url);
20084                    if (match) {
20085                        url = ((baseUri && baseUri.protocol) || 'http') + '://mce_host' + self.toAbsPath(baseUrl, match[1]) + match[2];
20086                    }
20087                }
20088            }
20089            // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
20090            url = url.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
20091            const urlMatch = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?(\[[a-zA-Z0-9:.%]+\]|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(url);
20092            if (urlMatch) {
20093                each$6(queryParts, (v, i) => {
20094                    let part = urlMatch[i];
20095                    // Zope 3 workaround, they use @@something
20096                    if (part) {
20097                        part = part.replace(/\(mce_at\)/g, '@@');
20098                    }
20099                    self[v] = part;
20100                });
20101            }
20102            if (baseUri) {
20103                if (!self.protocol) {
20104                    self.protocol = baseUri.protocol;
20105                }
20106                if (!self.userInfo) {
20107                    self.userInfo = baseUri.userInfo;
20108                }
20109                if (!self.port && self.host === 'mce_host') {
20110                    self.port = baseUri.port;
20111                }
20112                if (!self.host || self.host === 'mce_host') {
20113                    self.host = baseUri.host;
20114                }
20115                self.source = '';
20116            }
20117            if (isProtocolRelative) {
20118                self.protocol = '';
20119            }
20120        }
20121        /**
20122         * Sets the internal path part of the URI.
20123         *
20124         * @method setPath
20125         * @param {String} path Path string to set.
20126         */
20127        setPath(path) {
20128            const pathMatch = /^(.*?)\/?(\w+)?$/.exec(path);
20129            // Update path parts
20130            if (pathMatch) {
20131                this.path = pathMatch[0];
20132                this.directory = pathMatch[1];
20133                this.file = pathMatch[2];
20134            }
20135            // Rebuild source
20136            this.source = '';
20137            this.getURI();
20138        }
20139        /**
20140         * Converts the specified URI into a relative URI based on the current URI instance location.
20141         *
20142         * @method toRelative
20143         * @param {String} uri URI to convert into a relative path/URI.
20144         * @return {String} Relative URI from the point specified in the current URI instance.
20145         * @example
20146         * // Converts an absolute URL to an relative URL url will be somedir/somefile.htm
20147         * const url = new tinymce.util.URI('http://www.site.com/dir/').toRelative('http://www.site.com/dir/somedir/somefile.htm');
20148         */
20149        toRelative(uri) {
20150            if (uri === './') {
20151                return uri;
20152            }
20153            const relativeUri = new URI(uri, { base_uri: this });
20154            // Not on same domain/port or protocol
20155            if ((relativeUri.host !== 'mce_host' && this.host !== relativeUri.host && relativeUri.host) || this.port !== relativeUri.port ||
20156                (this.protocol !== relativeUri.protocol && relativeUri.protocol !== '')) {
20157                return relativeUri.getURI();
20158            }
20159            const tu = this.getURI(), uu = relativeUri.getURI();
20160            // Allow usage of the base_uri when relative_urls = true
20161            if (tu === uu || (tu.charAt(tu.length - 1) === '/' && tu.substr(0, tu.length - 1) === uu)) {
20162                return tu;
20163            }
20164            let output = this.toRelPath(this.path, relativeUri.path);
20165            // Add query
20166            if (relativeUri.query) {
20167                output += '?' + relativeUri.query;
20168            }
20169            // Add anchor
20170            if (relativeUri.anchor) {
20171                output += '#' + relativeUri.anchor;
20172            }
20173            return output;
20174        }
20175        /**
20176         * Converts the specified URI into a absolute URI based on the current URI instance location.
20177         *
20178         * @method toAbsolute
20179         * @param {String} uri URI to convert into a relative path/URI.
20180         * @param {Boolean} noHost No host and protocol prefix.
20181         * @return {String} Absolute URI from the point specified in the current URI instance.
20182         * @example
20183         * // Converts an relative URL to an absolute URL url will be http://www.site.com/dir/somedir/somefile.htm
20184         * const url = new tinymce.util.URI('http://www.site.com/dir/').toAbsolute('somedir/somefile.htm');
20185         */
20186        toAbsolute(uri, noHost) {
20187            const absoluteUri = new URI(uri, { base_uri: this });
20188            return absoluteUri.getURI(noHost && this.isSameOrigin(absoluteUri));
20189        }
20190        /**
20191         * Determine whether the given URI has the same origin as this URI.  Based on RFC-6454.
20192         * Supports default ports for protocols listed in DEFAULT_PORTS.  Unsupported protocols will fail safe: they
20193         * won't match, if the port specifications differ.
20194         *
20195         * @method isSameOrigin
20196         * @param {tinymce.util.URI} uri Uri instance to compare.
20197         * @returns {Boolean} True if the origins are the same.
20198         */
20199        isSameOrigin(uri) {
20200            // eslint-disable-next-line eqeqeq
20201            if (this.host == uri.host && this.protocol == uri.protocol) {
20202                // eslint-disable-next-line eqeqeq
20203                if (this.port == uri.port) {
20204                    return true;
20205                }
20206                const defaultPort = this.protocol ? DEFAULT_PORTS[this.protocol] : null;
20207                // eslint-disable-next-line eqeqeq
20208                if (defaultPort && ((this.port || defaultPort) == (uri.port || defaultPort))) {
20209                    return true;
20210                }
20211            }
20212            return false;
20213        }
20214        /**
20215         * Converts a absolute path into a relative path.
20216         *
20217         * @method toRelPath
20218         * @param {String} base Base point to convert the path from.
20219         * @param {String} path Absolute path to convert into a relative path.
20220         */
20221        toRelPath(base, path) {
20222            let breakPoint = 0, out = '', i, l;
20223            // Split the paths
20224            const normalizedBase = base.substring(0, base.lastIndexOf('/')).split('/');
20225            const items = path.split('/');
20226            if (normalizedBase.length >= items.length) {
20227                for (i = 0, l = normalizedBase.length; i < l; i++) {
20228                    if (i >= items.length || normalizedBase[i] !== items[i]) {
20229                        breakPoint = i + 1;
20230                        break;
20231                    }
20232                }
20233            }
20234            if (normalizedBase.length < items.length) {
20235                for (i = 0, l = items.length; i < l; i++) {
20236                    if (i >= normalizedBase.length || normalizedBase[i] !== items[i]) {
20237                        breakPoint = i + 1;
20238                        break;
20239                    }
20240                }
20241            }
20242            if (breakPoint === 1) {
20243                return path;
20244            }
20245            for (i = 0, l = normalizedBase.length - (breakPoint - 1); i < l; i++) {
20246                out += '../';
20247            }
20248            for (i = breakPoint - 1, l = items.length; i < l; i++) {
20249                if (i !== breakPoint - 1) {
20250                    out += '/' + items[i];
20251                }
20252                else {
20253                    out += items[i];
20254                }
20255            }
20256            return out;
20257        }
20258        /**
20259         * Converts a relative path into a absolute path.
20260         *
20261         * @method toAbsPath
20262         * @param {String} base Base point to convert the path from.
20263         * @param {String} path Relative path to convert into an absolute path.
20264         */
20265        toAbsPath(base, path) {
20266            let nb = 0;
20267            // Split paths
20268            const tr = /\/$/.test(path) ? '/' : '';
20269            const normalizedBase = base.split('/');
20270            const normalizedPath = path.split('/');
20271            // Remove empty chunks
20272            const baseParts = [];
20273            each$6(normalizedBase, (k) => {
20274                if (k) {
20275                    baseParts.push(k);
20276                }
20277            });
20278            // Merge relURLParts chunks
20279            const pathParts = [];
20280            for (let i = normalizedPath.length - 1; i >= 0; i--) {
20281                // Ignore empty or .
20282                if (normalizedPath[i].length === 0 || normalizedPath[i] === '.') {
20283                    continue;
20284                }
20285                // Is parent
20286                if (normalizedPath[i] === '..') {
20287                    nb++;
20288                    continue;
20289                }
20290                // Move up
20291                if (nb > 0) {
20292                    nb--;
20293                    continue;
20294                }
20295                pathParts.push(normalizedPath[i]);
20296            }
20297            const i = baseParts.length - nb;
20298            // If /a/b/c or /
20299            let outPath;
20300            if (i <= 0) {
20301                outPath = reverse(pathParts).join('/');
20302            }
20303            else {
20304                outPath = baseParts.slice(0, i).join('/') + '/' + reverse(pathParts).join('/');
20305            }
20306            // Add front / if it's needed
20307            if (outPath.indexOf('/') !== 0) {
20308                outPath = '/' + outPath;
20309            }
20310            // Add trailing / if it's needed
20311            if (tr && outPath.lastIndexOf('/') !== outPath.length - 1) {
20312                outPath += tr;
20313            }
20314            return outPath;
20315        }
20316        /**
20317         * Returns the full URI of the internal structure.
20318         *
20319         * @method getURI
20320         * @param {Boolean} noProtoHost Optional no host and protocol part. Defaults to false.
20321         */
20322        getURI(noProtoHost = false) {
20323            let s;
20324            // Rebuild source
20325            if (!this.source || noProtoHost) {
20326                s = '';
20327                if (!noProtoHost) {
20328                    if (this.protocol) {
20329                        s += this.protocol + '://';
20330                    }
20331                    else {
20332                        s += '//';
20333                    }
20334                    if (this.userInfo) {
20335                        s += this.userInfo + '@';
20336                    }
20337                    if (this.host) {
20338                        s += this.host;
20339                    }
20340                    if (this.port) {
20341                        s += ':' + this.port;
20342                    }
20343                }
20344                if (this.path) {
20345                    s += this.path;
20346                }
20347                if (this.query) {
20348                    s += '?' + this.query;
20349                }
20350                if (this.anchor) {
20351                    s += '#' + this.anchor;
20352                }
20353                this.source = s;
20354            }
20355            return this.source;
20356        }
20357    }
20358
20359    // A list of attributes that should be filtered further based on the parser settings
20360    const filteredUrlAttrs = Tools.makeMap('src,href,data,background,action,formaction,poster,xlink:href');
20361    const internalElementAttr = 'data-mce-type';
20362    let uid = 0;
20363    const processNode = (node, settings, schema, scope, evt) => {
20364        const validate = settings.validate;
20365        const specialElements = schema.getSpecialElements();
20366        if (node.nodeType === COMMENT) {
20367            // Pad conditional comments if they aren't allowed
20368            if (!settings.allow_conditional_comments && /^\[if/i.test(node.nodeValue ?? '')) {
20369                node.nodeValue = ' ' + node.nodeValue;
20370            }
20371            if (settings.sanitize && settings.allow_html_in_comments && isString(node.nodeValue)) {
20372                node.nodeValue = encodeData(node.nodeValue);
20373            }
20374        }
20375        const lcTagName = evt?.tagName ?? node.nodeName.toLowerCase();
20376        if (scope !== 'html' && schema.isValid(scope)) {
20377            if (isNonNullable(evt)) {
20378                evt.allowedTags[lcTagName] = true;
20379            }
20380            return;
20381        }
20382        // Just leave non-elements such as text and comments up to dompurify
20383        if (node.nodeType !== ELEMENT || lcTagName === 'body') {
20384            return;
20385        }
20386        // Construct the sugar element wrapper
20387        const element = SugarElement.fromDom(node);
20388        
vendor: 5,266 bytes, lines 20388-20492
20388// Determine if we're dealing with an internal attribute
20389        const isInternalElement = has$1(element, internalElementAttr);
20390        // Cleanup bogus elements
20391        const bogus = get$9(element, 'data-mce-bogus');
20392        if (!isInternalElement && isString(bogus)) {
20393            if (bogus === 'all') {
20394                remove$8(element);
20395            }
20396            else {
20397                unwrap(element);
20398            }
20399            return;
20400        }
20401        // Determine if the schema allows the element and either add it or remove it
20402        const rule = schema.getElementRule(lcTagName);
20403        if (validate && !rule) {
20404            // If a special element is invalid, then remove the entire element instead of unwrapping
20405            if (has$2(specialElements, lcTagName)) {
20406                remove$8(element);
20407            }
20408            else {
20409                unwrap(element);
20410            }
20411            return;
20412        }
20413        else {
20414            if (isNonNullable(evt)) {
20415                evt.allowedTags[lcTagName] = true;
20416            }
20417        }
20418        // Validate the element using the attribute rules
20419        if (validate && rule && !isInternalElement) {
20420            // Fix the attributes for the element, unwrapping it if we have to
20421            each$e(rule.attributesForced ?? [], (attr) => {
20422                set$4(element, attr.name, attr.value === '{$uid}' ? `mce_${uid++}` : attr.value);
20423            });
20424            each$e(rule.attributesDefault ?? [], (attr) => {
20425                if (!has$1(element, attr.name)) {
20426                    set$4(element, attr.name, attr.value === '{$uid}' ? `mce_${uid++}` : attr.value);
20427                }
20428            });
20429            // If none of the required attributes were found then remove
20430            if (rule.attributesRequired && !exists(rule.attributesRequired, (attr) => has$1(element, attr))) {
20431                unwrap(element);
20432                return;
20433            }
20434            // If there are no attributes then remove
20435            if (rule.removeEmptyAttrs && hasNone(element)) {
20436                unwrap(element);
20437                return;
20438            }
20439            // Change the node name if the schema says to
20440            if (rule.outputName && rule.outputName !== lcTagName) {
20441                mutate(element, rule.outputName);
20442            }
20443        }
20444    };
20445    const processAttr = (ele, settings, schema, scope, evt) => {
20446        const tagName = ele.tagName.toLowerCase();
20447        const { attrName, attrValue } = evt;
20448        evt.keepAttr = shouldKeepAttribute(settings, schema, scope, tagName, attrName, attrValue);
20449        if (evt.keepAttr) {
20450            evt.allowedAttributes[attrName] = true;
20451            if (isBooleanAttributeOfNonCustomElement(attrName, schema, ele.nodeName)) {
20452                evt.attrValue = attrName;
20453            }
20454            // We need to tell DOMPurify to forcibly keep the attribute if it's an SVG data URI and svg data URIs are allowed
20455            if (settings.allow_svg_data_urls && startsWith(attrValue, 'data:image/svg+xml')) {
20456                evt.forceKeepAttr = true;
20457            }
20458            // For internal elements always keep the attribute if the attribute name is id, class or style
20459        }
20460        else if (isRequiredAttributeOfInternalElement(ele, attrName)) {
20461            evt.forceKeepAttr = true;
20462        }
20463    };
20464    const shouldKeepAttribute = (settings, schema, scope, tagName, attrName, attrValue) => {
20465        // All attributes within non HTML namespaces elements are considered valid
20466        if (scope !== 'html' && !isNonHtmlElementRootName(tagName)) {
20467            return true;
20468        }
20469        return !(attrName in filteredUrlAttrs && isInvalidUri(settings, attrValue, tagName)) &&
20470            (!settings.validate || schema.isValid(tagName, attrName) || startsWith(attrName, 'data-') || startsWith(attrName, 'aria-'));
20471    };
20472    const isRequiredAttributeOfInternalElement = (ele, attrName) => ele.hasAttribute(internalElementAttr) && (attrName === 'id' || attrName === 'class' || attrName === 'style');
20473    const isBooleanAttributeOfNonCustomElement = (attrName, schema, nodeName) => attrName in schema.getBoolAttrs() && !has$2(schema.getCustomElements(), nodeName.toLowerCase());
20474    const filterAttributes = (ele, settings, schema, scope) => {
20475        const { attributes } = ele;
20476        for (let i = attributes.length - 1; i >= 0; i--) {
20477            const attr = attributes[i];
20478            const attrName = attr.name;
20479            const attrValue = attr.value;
20480            if (!shouldKeepAttribute(settings, schema, scope, ele.tagName.toLowerCase(), attrName, attrValue) && !isRequiredAttributeOfInternalElement(ele, attrName)) {
20481                ele.removeAttribute(attrName);
20482            }
20483            else if (isBooleanAttributeOfNonCustomElement(attrName, schema, ele.nodeName)) {
20484                ele.setAttribute(attrName, attrName);
20485            }
20486        }
20487    };
20488    const setupPurify = (settings, schema, namespaceTracker) => {
20489        const purify$1 = purify();
20490        // We use this to add new tags to the allow-list as we parse, if we notice that a tag has been banned but it's still in the schema
20491        purify$1.addHook('uponSanitizeElement', (ele, evt) => {
20492            processNode(ele, settings, schema, namespaceTracker.track(ele), evt
vendor: 67,209 bytes, lines 20492-21898
20492);
20493        });
20494        // Let's do the same thing for attributes
20495        purify$1.addHook('uponSanitizeAttribute', (ele, evt) => {
20496            processAttr(ele, settings, schema, namespaceTracker.current(), evt);
20497        });
20498        return purify$1;
20499    };
20500    const getPurifyConfig = (settings, mimeType) => {
20501        const basePurifyConfig = {
20502            IN_PLACE: true,
20503            ALLOW_UNKNOWN_PROTOCOLS: true,
20504            // Deliberately ban all tags and attributes by default, and then un-ban them on demand in hooks
20505            // #comment and #cdata-section are always allowed as they aren't controlled via the schema
20506            // body is also allowed due to the DOMPurify checking the root node before sanitizing
20507            ALLOWED_TAGS: ['#comment', '#cdata-section', 'body', 'html'],
20508            ALLOWED_ATTR: []
20509        };
20510        const config = { ...basePurifyConfig };
20511        // Set the relevant parser mimetype
20512        config.PARSER_MEDIA_TYPE = mimeType;
20513        // Allow any URI when allowing script urls
20514        if (settings.allow_script_urls) {
20515            config.ALLOWED_URI_REGEXP = /.*/;
20516            // Allow anything except javascript (or similar) URIs if all html data urls are allowed
20517        }
20518        else if (settings.allow_html_data_urls) {
20519            config.ALLOWED_URI_REGEXP = /^(?!(\w+script|mhtml):)/i;
20520        }
20521        return config;
20522    };
20523    const sanitizeSvgElement = (ele) => {
20524        // xlink:href used to be the way to do links in SVG 1.x https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/xlink:href
20525        const xlinkAttrs = ['type', 'href', 'role', 'arcrole', 'title', 'show', 'actuate', 'label', 'from', 'to'].map((name) => `xlink:${name}`);
20526        const config = {
20527            IN_PLACE: true,
20528            USE_PROFILES: {
20529                html: true,
20530                svg: true,
20531                svgFilters: true
20532            },
20533            ALLOWED_ATTR: xlinkAttrs
20534        };
20535        purify().sanitize(ele, config);
20536    };
20537    const sanitizeMathmlElement = (node, settings) => {
20538        const config = {
20539            IN_PLACE: true,
20540            USE_PROFILES: {
20541                mathMl: true
20542            },
20543        };
20544        const purify$1 = purify();
20545        const allowedEncodings = settings.allow_mathml_annotation_encodings;
20546        const hasAllowedEncodings = isArray$1(allowedEncodings) && allowedEncodings.length > 0;
20547        const hasValidEncoding = (el) => {
20548            const encoding = el.getAttribute('encoding');
20549            return hasAllowedEncodings && isString(encoding) && contains$2(allowedEncodings, encoding);
20550        };
20551        const isValidElementOpt = (node, lcTagName) => {
20552            if (hasAllowedEncodings && lcTagName === 'semantics') {
20553                return Optional.some(true);
20554            }
20555            else if (lcTagName === 'annotation') {
20556                return Optional.some(isElement$7(node) && hasValidEncoding(node));
20557            }
20558            else if (isArray$1(settings.extended_mathml_elements)) {
20559                if (settings.extended_mathml_elements.includes(lcTagName)) {
20560                    return Optional.from(true);
20561                }
20562                else {
20563                    return Optional.none();
20564                }
20565            }
20566            else {
20567                return Optional.none();
20568            }
20569        };
20570        purify$1.addHook('uponSanitizeElement', (node, evt) => {
20571            // We know the node is an element as we have
20572            // passed an element to the purify.sanitize function below
20573            const lcTagName = evt.tagName ?? node.nodeName.toLowerCase();
20574            const keepElementOpt = isValidElementOpt(node, lcTagName);
20575            keepElementOpt.each((keepElement) => {
20576                evt.allowedTags[lcTagName] = keepElement;
20577                if (!keepElement && settings.sanitize) {
20578                    if (isElement$7(node)) {
20579                        node.remove();
20580                    }
20581                }
20582            });
20583        });
20584        purify$1.addHook('uponSanitizeAttribute', (_node, event) => {
20585            if (isArray$1(settings.extended_mathml_attributes)) {
20586                const keepAttribute = settings.extended_mathml_attributes.includes(event.attrName);
20587                if (keepAttribute) {
20588                    event.forceKeepAttr = true;
20589                }
20590            }
20591        });
20592        purify$1.sanitize(node, config);
20593    };
20594    const mkSanitizeNamespaceElement = (settings) => (ele) => {
20595        const namespaceType = toScopeType(ele);
20596        if (namespaceType === 'svg') {
20597            sanitizeSvgElement(ele);
20598        }
20599        else if (namespaceType === 'math') {
20600            sanitizeMathmlElement(ele, settings);
20601        }
20602        else {
20603            throw new Error('Not a namespace element');
20604        }
20605    };
20606    const getSanitizer = (settings, schema) => {
20607        const namespaceTracker = createNamespaceTracker();
20608        if (settings.sanitize) {
20609            const purify = setupPurify(settings, schema, namespaceTracker);
20610            const sanitizeHtmlElement = (body, mimeType) => {
20611                purify.sanitize(body, getPurifyConfig(settings, mimeType));
20612                purify.removed = [];
20613                namespaceTracker.reset();
20614            };
20615            return {
20616                sanitizeHtmlElement,
20617                sanitizeNamespaceElement: mkSanitizeNamespaceElement(settings)
20618            };
20619        }
20620        else {
20621            const sanitizeHtmlElement = (body, _mimeType) => {
20622                // eslint-disable-next-line no-bitwise
20623                const nodeIterator = document.createNodeIterator(body, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT);
20624                let node;
20625                while ((node = nodeIterator.nextNode())) {
20626                    const currentScope = namespaceTracker.track(node);
20627                    processNode(node, settings, schema, currentScope);
20628                    if (isElement$7(node)) {
20629                        filterAttributes(node, settings, schema, currentScope);
20630                    }
20631                }
20632                namespaceTracker.reset();
20633            };
20634            const sanitizeNamespaceElement = noop;
20635            return {
20636                sanitizeHtmlElement,
20637                sanitizeNamespaceElement
20638            };
20639        }
20640    };
20641
20642    /**
20643     * @summary
20644     * This class parses HTML code into a DOM like structure of nodes it will remove redundant whitespace and make
20645     * sure that the node tree is valid according to the specified schema.
20646     * So for example: `<p>a<p>b</p>c</p>` will become `<p>a</p><p>b</p><p>c</p>`.
20647     *
20648     * @example
20649     * const parser = tinymce.html.DomParser({ validate: true }, schema);
20650     * const rootNode = parser.parse('<h1>content</h1>');
20651     *
20652     * @class tinymce.html.DomParser
20653     * @version 3.4
20654     */
20655    const extraBlockLikeElements = ['script', 'style', 'template', 'param', 'meta', 'title', 'link'];
20656    const makeMap = Tools.makeMap, extend$1 = Tools.extend;
20657    const transferChildren = (parent, nativeParent, specialElements, nsSanitizer, decodeComments) => {
20658        const parentName = parent.name;
20659        // Exclude the special elements where the content is RCDATA as their content needs to be parsed instead of being left as plain text
20660        // See: https://html.spec.whatwg.org/multipage/parsing.html#parsing-html-fragments
20661        const isSpecial = parentName in specialElements && parentName !== 'title' && parentName !== 'textarea' && parentName !== 'noscript';
20662        const childNodes = nativeParent.childNodes;
20663        for (let ni = 0, nl = childNodes.length; ni < nl; ni++) {
20664            const nativeChild = childNodes[ni];
20665            const child = new AstNode(nativeChild.nodeName.toLowerCase(), nativeChild.nodeType);
20666            if (isElement$7(nativeChild)) {
20667                const attributes = nativeChild.attributes;
20668                for (let ai = 0, al = attributes.length; ai < al; ai++) {
20669                    const attr = attributes[ai];
20670                    child.attr(attr.name, attr.value);
20671                }
20672                if (isNonHtmlElementRootName(child.name)) {
20673                    nsSanitizer(nativeChild);
20674                    child.value = nativeChild.innerHTML;
20675                }
20676            }
20677            else if (isText$b(nativeChild)) {
20678                child.value = nativeChild.data;
20679                if (isSpecial) {
20680                    child.raw = true;
20681                }
20682            }
20683            else if (isComment(nativeChild)) {
20684                child.value = decodeComments ? decodeData$1(nativeChild.data) : nativeChild.data;
20685            }
20686            else if (isCData(nativeChild) || isPi(nativeChild)) {
20687                child.value = nativeChild.data;
20688            }
20689            if (isTemplate(nativeChild)) {
20690                const content = AstNode.create('#text');
20691                content.value = nativeChild.innerHTML;
20692                content.raw = true;
20693                child.append(content);
20694            }
20695            else if (!isNonHtmlElementRootName(child.name)) {
20696                transferChildren(child, nativeChild, specialElements, nsSanitizer, decodeComments);
20697            }
20698            parent.append(child);
20699        }
20700    };
20701    const walkTree = (root, preprocessors, postprocessors) => {
20702        const traverseOrder = [];
20703        for (let node = root, lastNode = node; node; lastNode = node, node = node.walk()) {
20704            const tempNode = node;
20705            each$e(preprocessors, (preprocess) => preprocess(tempNode));
20706            if (isNullable(tempNode.parent) && tempNode !== root) {
20707                // The node has been detached, so rewind a little and don't add it to our traversal
20708                node = lastNode;
20709            }
20710            else {
20711                traverseOrder.push(tempNode);
20712            }
20713        }
20714        for (let i = traverseOrder.length - 1; i >= 0; i--) {
20715            const node = traverseOrder[i];
20716            each$e(postprocessors, (postprocess) => postprocess(node));
20717        }
20718    };
20719    // All the dom operations we want to perform, regardless of whether we're trying to properly validate things
20720    // e.g. removing excess whitespace
20721    // e.g. removing empty nodes (or padding them with <br>)
20722    //
20723    // Returns [ preprocess, postprocess ]
20724    const whitespaceCleaner = (root, schema, settings, args) => {
20725        const validate = settings.validate;
20726        const nonEmptyElements = schema.getNonEmptyElements();
20727        const whitespaceElements = schema.getWhitespaceElements();
20728        const blockElements = extend$1(makeMap(extraBlockLikeElements), schema.getBlockElements());
20729        const textRootBlockElements = getTextRootBlockElements(schema);
20730        const allWhiteSpaceRegExp = /[ \t\r\n]+/g;
20731        const startWhiteSpaceRegExp = /^[ \t\r\n]+/;
20732        const endWhiteSpaceRegExp = /[ \t\r\n]+$/;
20733        const hasWhitespaceParent = (node) => {
20734            let tempNode = node.parent;
20735            while (isNonNullable(tempNode)) {
20736                if (tempNode.name in whitespaceElements) {
20737                    return true;
20738                }
20739                else {
20740                    tempNode = tempNode.parent;
20741                }
20742            }
20743            return false;
20744        };
20745        const isTextRootBlockEmpty = (node) => {
20746            let tempNode = node;
20747            while (isNonNullable(tempNode)) {
20748                if (tempNode.name in textRootBlockElements) {
20749                    return isEmpty$2(schema, nonEmptyElements, whitespaceElements, tempNode);
20750                }
20751                else {
20752                    tempNode = tempNode.parent;
20753                }
20754            }
20755            return false;
20756        };
20757        const isBlock = (node) => node.name in blockElements || isTransparentAstBlock(schema, node) || (isNonHtmlElementRootName(node.name) && node.parent === root);
20758        const isAtEdgeOfBlock = (node, start) => {
20759            const neighbour = start ? node.prev : node.next;
20760            if (isNonNullable(neighbour) || isNullable(node.parent)) {
20761                return false;
20762            }
20763            // Make sure our parent is actually a block, and also make sure it isn't a temporary "context" element
20764            // that we're probably going to unwrap as soon as we insert this content into the editor
20765            return isBlock(node.parent) && (node.parent !== root || args.isRootContent === true);
20766        };
20767        const preprocess = (node) => {
20768            if (node.type === 3) {
20769                // Remove leading whitespace here, so that all whitespace in nodes to the left of us has already been fixed
20770                if (!hasWhitespaceParent(node)) {
20771                    let text = node.value ?? '';
20772                    text = text.replace(allWhiteSpaceRegExp, ' ');
20773                    if (isLineBreakNode(node.prev, isBlock) || isAtEdgeOfBlock(node, true)) {
20774                        text = text.replace(startWhiteSpaceRegExp, '');
20775                    }
20776                    if (text.length === 0) {
20777                        node.remove();
20778                    }
20779                    else if (text === ' ' && node.prev && node.prev.type === COMMENT && node.next && node.next.type === COMMENT) {
20780                        node.remove();
20781                    }
20782                    else {
20783                        node.value = text;
20784                    }
20785                }
20786            }
20787        };
20788        const postprocess = (node) => {
20789            if (node.type === 1) {
20790                // Check for empty nodes here, because children will have been processed and (if necessary) emptied / removed already
20791                const elementRule = schema.getElementRule(node.name);
20792                if (validate && elementRule) {
20793                    const isNodeEmpty = isEmpty$2(schema, nonEmptyElements, whitespaceElements, node);
20794                    if (elementRule.paddInEmptyBlock && isNodeEmpty && isTextRootBlockEmpty(node)) {
20795                        paddEmptyNode(settings, args, isBlock, node);
20796                    }
20797                    else if (elementRule.removeEmpty && isNodeEmpty) {
20798                        if (isBlock(node)) {
20799                            node.remove();
20800                        }
20801                        else {
20802                            node.unwrap();
20803                        }
20804                    }
20805                    else if (elementRule.paddEmpty && (isNodeEmpty || isPaddedWithNbsp(node))) {
20806                        paddEmptyNode(settings, args, isBlock, node);
20807                    }
20808                }
20809            }
20810            else if (node.type === 3) {
20811                // Removing trailing whitespace here, so that all whitespace in nodes to the right of us has already been fixed
20812                if (!hasWhitespaceParent(node)) {
20813                    let text = node.value ?? '';
20814                    if (node.next && isBlock(node.next) || isAtEdgeOfBlock(node, false)) {
20815                        text = text.replace(endWhiteSpaceRegExp, '');
20816                    }
20817                    if (text.length === 0) {
20818                        node.remove();
20819                    }
20820                    else {
20821                        node.value = text;
20822                    }
20823                }
20824            }
20825        };
20826        return [preprocess, postprocess];
20827    };
20828    const getRootBlockName = (settings, args) => {
20829        const name = args.forced_root_block ?? settings.forced_root_block;
20830        if (name === false) {
20831            return '';
20832        }
20833        else if (name === true) {
20834            return 'p';
20835        }
20836        else {
20837            return name;
20838        }
20839    };
20840    const xhtmlAttribte = ' xmlns="http://www.w3.org/1999/xhtml"';
20841    const DomParser = (settings = {}, schema = Schema()) => {
20842        const nodeFilterRegistry = create$8();
20843        const attributeFilterRegistry = create$8();
20844        // Apply setting defaults
20845        const defaultedSettings = {
20846            validate: true,
20847            root_name: 'body',
20848            sanitize: true,
20849            allow_html_in_comments: false,
20850            ...settings
20851        };
20852        const parser = new DOMParser();
20853        const sanitizer = getSanitizer(defaultedSettings, schema);
20854        const parseAndSanitizeWithContext = (html, rootName, format = 'html', useDocumentNotBody = false) => {
20855            const isxhtml = format === 'xhtml';
20856            const mimeType = isxhtml ? 'application/xhtml+xml' : 'text/html';
20857            // Determine the root element to wrap the HTML in when parsing. If we're dealing with a
20858            // special element then we need to wrap it so the internal content is handled appropriately.
20859            const isSpecialRoot = has$2(schema.getSpecialElements(), rootName.toLowerCase());
20860            const content = isSpecialRoot ? `<${rootName}>${html}</${rootName}>` : html;
20861            const makeWrap = () => {
20862                if (/^[\s]*<head/i.test(html) || /^[\s]*<html/i.test(html) || /^[\s]*<!DOCTYPE/i.test(html)) {
20863                    return `<html${isxhtml ? xhtmlAttribte : ''}>${content}</html>`;
20864                }
20865                else {
20866                    if (isxhtml) {
20867                        return `<html${xhtmlAttribte}><head></head><body>${content}</body></html>`;
20868                    }
20869                    else {
20870                        return `<body>${content}</body>`;
20871                    }
20872                }
20873            };
20874            const document = parser.parseFromString(makeWrap(), mimeType);
20875            const body = useDocumentNotBody ? document.documentElement : document.body;
20876            sanitizer.sanitizeHtmlElement(body, mimeType);
20877            return isSpecialRoot ? body.firstChild : body;
20878        };
20879        /**
20880         * Adds a node filter function to the parser, the parser will collect the specified nodes by name
20881         * and then execute the callback once it has finished parsing the document.
20882         *
20883         * @method addNodeFilter
20884         * @param {String} name Comma separated list of nodes to collect.
20885         * @param {Function} callback Callback function to execute once it has collected nodes.
20886         * @example
20887         * parser.addNodeFilter('p,h1', (nodes, name) => {
20888         *   for (var i = 0; i < nodes.length; i++) {
20889         *     console.log(nodes[i].name);
20890         *   }
20891         * });
20892         */
20893        const addNodeFilter = nodeFilterRegistry.addFilter;
20894        const getNodeFilters = nodeFilterRegistry.getFilters;
20895        /**
20896         * Removes a node filter function or removes all filter functions from the parser for the node names provided.
20897         *
20898         * @method removeNodeFilter
20899         * @param {String} name Comma separated list of node names to remove filters for.
20900         * @param {Function} callback Optional callback function to only remove a specific callback.
20901         * @example
20902         * // Remove a single filter
20903         * parser.removeNodeFilter('p,h1', someCallback);
20904         *
20905         * // Remove all filters
20906         * parser.removeNodeFilter('p,h1');
20907         */
20908        const removeNodeFilter = nodeFilterRegistry.removeFilter;
20909        /**
20910         * Adds an attribute filter function to the parser, the parser will collect nodes that has the specified attributes
20911         * and then execute the callback once it has finished parsing the document.
20912         *
20913         * @method addAttributeFilter
20914         * @param {String} name Comma separated list of attributes to collect.
20915         * @param {Function} callback Callback function to execute once it has collected nodes.
20916         * @example
20917         * parser.addAttributeFilter('src,href', (nodes, name) => {
20918         *   for (let i = 0; i < nodes.length; i++) {
20919         *     console.log(nodes[i].name);
20920         *   }
20921         * });
20922         */
20923        const addAttributeFilter = attributeFilterRegistry.addFilter;
20924        const getAttributeFilters = attributeFilterRegistry.getFilters;
20925        /**
20926         * Removes an attribute filter function or removes all filter functions from the parser for the attribute names provided.
20927         *
20928         * @method removeAttributeFilter
20929         * @param {String} name Comma separated list of attribute names to remove filters for.
20930         * @param {Function} callback Optional callback function to only remove a specific callback.
20931         * @example
20932         * // Remove a single filter
20933         * parser.removeAttributeFilter('src,href', someCallback);
20934         *
20935         * // Remove all filters
20936         * parser.removeAttributeFilter('src,href');
20937         */
20938        const removeAttributeFilter = attributeFilterRegistry.removeFilter;
20939        const findInvalidChildren = (node, invalidChildren) => {
20940            if (isInvalid(schema, node)) {
20941                invalidChildren.push(node);
20942            }
20943        };
20944        const isWrappableNode = (blockElements, node) => {
20945            const isInternalElement = isString(node.attr(internalElementAttr));
20946            const isInlineElement = node.type === 1 && (!has$2(blockElements, node.name) && !isTransparentAstBlock(schema, node)) && !isNonHtmlElementRootName(node.name);
20947            return node.type === 3 || (isInlineElement && !isInternalElement);
20948        };
20949        const addRootBlocks = (rootNode, rootBlockName) => {
20950            const blockElements = extend$1(makeMap(extraBlockLikeElements), schema.getBlockElements());
20951            const startWhiteSpaceRegExp = /^[ \t\r\n]+/;
20952            const endWhiteSpaceRegExp = /[ \t\r\n]+$/;
20953            let node = rootNode.firstChild, rootBlockNode = null;
20954            // Removes whitespace at beginning and end of block so:
20955            // <p> x </p> -> <p>x</p>
20956            const trim = (rootBlock) => {
20957                if (rootBlock) {
20958                    node = rootBlock.firstChild;
20959                    if (node && node.type === 3) {
20960                        node.value = node.value?.replace(startWhiteSpaceRegExp, '');
20961                    }
20962                    node = rootBlock.lastChild;
20963                    if (node && node.type === 3) {
20964                        node.value = node.value?.replace(endWhiteSpaceRegExp, '');
20965                    }
20966                }
20967            };
20968            // Check if rootBlock is valid within rootNode for example if P is valid in H1 if H1 is the contentEditable root
20969            if (!schema.isValidChild(rootNode.name, rootBlockName.toLowerCase())) {
20970                return;
20971            }
20972            while (node) {
20973                const next = node.next;
20974                if (isWrappableNode(blockElements, node)) {
20975                    if (!rootBlockNode) {
20976                        // Create a new root block element
20977                        rootBlockNode = new AstNode(rootBlockName, 1);
20978                        rootBlockNode.attr(defaultedSettings.forced_root_block_attrs);
20979                        rootNode.insert(rootBlockNode, node);
20980                        rootBlockNode.append(node);
20981                    }
20982                    else {
20983                        rootBlockNode.append(node);
20984                    }
20985                }
20986                else {
20987                    trim(rootBlockNode);
20988                    rootBlockNode = null;
20989                }
20990                node = next;
20991            }
20992            trim(rootBlockNode);
20993        };
20994        /**
20995         * Parses the specified HTML string into a DOM like node tree and returns the result.
20996         *
20997         * @method parse
20998         * @param {String} html Html string to sax parse.
20999         * @param {Object} args Optional args object that gets passed to all filter functions.
21000         * @return {tinymce.html.Node} Root node containing the tree.
21001         * @example
21002         * const rootNode = tinymce.html.DomParser({...}).parse('<b>text</b>');
21003         */
21004        const parse = (html, args = {}) => {
21005            const validate = defaultedSettings.validate;
21006            const preferFullDocument = (args.context ?? defaultedSettings.root_name) === '#document';
21007            const rootName = args.context ?? (preferFullDocument ? 'html' : defaultedSettings.root_name);
21008            // Parse and sanitize the content
21009            const element = parseAndSanitizeWithContext(html, rootName, args.format, preferFullDocument);
21010            updateChildren(schema, element);
21011            // Create the AST representation
21012            const rootNode = new AstNode(rootName, 11);
21013            transferChildren(rootNode, element, schema.getSpecialElements(), sanitizer.sanitizeNamespaceElement, defaultedSettings.sanitize && defaultedSettings.allow_html_in_comments);
21014            // This next line is needed to fix a memory leak in chrome and firefox.
21015            // For more information see TINY-9186
21016            element.innerHTML = '';
21017            // Set up whitespace fixes
21018            const [whitespacePre, whitespacePost] = whitespaceCleaner(rootNode, schema, defaultedSettings, args);
21019            // Find the invalid children in the tree
21020            const invalidChildren = [];
21021            const invalidFinder = validate ? (node) => findInvalidChildren(node, invalidChildren) : noop;
21022            // Set up attribute and node matching
21023            const matches = { nodes: {}, attributes: {} };
21024            const matchFinder = (node) => matchNode(getNodeFilters(), getAttributeFilters(), node, matches);
21025            // Walk the dom, apply all of the above things
21026            walkTree(rootNode, [whitespacePre, matchFinder], [whitespacePost, invalidFinder]);
21027            // Because we collected invalid children while walking backwards, we need to reverse the list before operating on them
21028            invalidChildren.reverse();
21029            // Fix invalid children or report invalid children in a contextual parsing
21030            if (validate && invalidChildren.length > 0) {
21031                if (args.context) {
21032                    args.invalid = true;
21033                }
21034                else {
21035                    cleanInvalidNodes(invalidChildren, schema, rootNode, matchFinder);
21036                }
21037            }
21038            // Wrap nodes in the root into block elements if the root is body
21039            const rootBlockName = getRootBlockName(defaultedSettings, args);
21040            if (rootBlockName && (rootNode.name === 'body' || args.isRootContent)) {
21041                addRootBlocks(rootNode, rootBlockName);
21042            }
21043            // Run filters only when the contents is valid
21044            if (!args.invalid) {
21045                runFilters(matches, args);
21046            }
21047            return rootNode;
21048        };
21049        const exports = {
21050            schema,
21051            addAttributeFilter,
21052            getAttributeFilters,
21053            removeAttributeFilter,
21054            addNodeFilter,
21055            getNodeFilters,
21056            removeNodeFilter,
21057            parse
21058        };
21059        register$4(exports, defaultedSettings);
21060        register$5(exports, defaultedSettings, schema);
21061        return exports;
21062    };
21063
21064    const isTreeNode = (content) => content instanceof AstNode;
21065
21066    const serializeContent = (content) => isTreeNode(content) ? HtmlSerializer({ validate: false }).serialize(content) : content;
21067    const withSerializedContent = (content, fireEvent, parserSettings) => {
21068        const serializedContent = serializeContent(content);
21069        const eventArgs = fireEvent(serializedContent);
21070        if (eventArgs.isDefaultPrevented()) {
21071            return eventArgs;
21072        }
21073        else if (isTreeNode(content)) {
21074            // Restore the content type back to being an AstNode. If the content has changed we need to
21075            // re-parse the new content, otherwise we can return the input.
21076            if (eventArgs.content !== serializedContent) {
21077                const rootNode = DomParser({ validate: false, forced_root_block: false, ...parserSettings }).parse(eventArgs.content, { context: content.name });
21078                return { ...eventArgs, content: rootNode };
21079            }
21080            else {
21081                return { ...eventArgs, content };
21082            }
21083        }
21084        else {
21085            return eventArgs;
21086        }
21087    };
21088    const makeParserSettings = (editor) => ({
21089        sanitize: shouldSanitizeXss(editor),
21090        sandbox_iframes: shouldSandboxIframes(editor),
21091        sandbox_iframes_exclusions: getSandboxIframesExclusions(editor)
21092    });
21093    const preProcessGetContent = (editor, args) => {
21094        if (args.no_events) {
21095            return Result.value(args);
21096        }
21097        else {
21098            const eventArgs = fireBeforeGetContent(editor, args);
21099            if (eventArgs.isDefaultPrevented()) {
21100                return Result.error(fireGetContent(editor, { content: '', ...eventArgs }).content);
21101            }
21102            else {
21103                return Result.value(eventArgs);
21104            }
21105        }
21106    };
21107    const postProcessGetContent = (editor, content, args) => {
21108        if (args.no_events) {
21109            return content;
21110        }
21111        else {
21112            const processedEventArgs = withSerializedContent(content, (content) => fireGetContent(editor, { ...args, content }), makeParserSettings(editor));
21113            return processedEventArgs.content;
21114        }
21115    };
21116    const preProcessSetContent = (editor, args) => {
21117        if (args.no_events) {
21118            return Result.value(args);
21119        }
21120        else {
21121            const processedEventArgs = withSerializedContent(args.content, (content) => fireBeforeSetContent(editor, { ...args, content }), makeParserSettings(editor));
21122            if (processedEventArgs.isDefaultPrevented()) {
21123                fireSetContent(editor, processedEventArgs);
21124                return Result.error(undefined);
21125            }
21126            else {
21127                return Result.value(processedEventArgs);
21128            }
21129        }
21130    };
21131    const postProcessSetContent = (editor, content, args) => {
21132        if (!args.no_events) {
21133            fireSetContent(editor, { ...args, content });
21134        }
21135    };
21136
21137    const removedOptions = ('autoresize_on_init,content_editable_state,padd_empty_with_br,block_elements,' +
21138        'boolean_attributes,editor_deselector,editor_selector,elements,file_browser_callback_types,filepicker_validator_handler,' +
21139        'force_hex_style_colors,force_p_newlines,gecko_spellcheck,images_dataimg_filter,media_scripts,mode,move_caret_before_on_enter_elements,' +
21140        'non_empty_elements,self_closing_elements,short_ended_elements,special,spellchecker_select_languages,spellchecker_whitelist,' +
21141        'tab_focus,tabfocus_elements,table_responsive_width,text_block_elements,text_inline_elements,toolbar_drawer,types,validate,whitespace_elements,' +
21142        'paste_enable_default_filters,paste_filter_drop,paste_word_valid_elements,paste_retain_style_properties,paste_convert_word_fake_lists,' +
21143        'template_cdate_classes,template_mdate_classes,template_selected_content_classes,template_preview_replace_values,template_replace_values,templates,template_cdate_format,template_mdate_format').split(',');
21144    const deprecatedOptions = ['content_css_cors'];
21145    const removedPlugins = 'bbcode,colorpicker,contextmenu,fullpage,legacyoutput,spellchecker,template,textcolor,rtc'.split(',');
21146    const deprecatedPlugins = [
21147        {
21148            name: 'export',
21149            replacedWith: 'Export to PDF'
21150        },
21151    ];
21152    const getMatchingOptions = (options, searchingFor) => {
21153        const settingNames = filter$5(searchingFor, (setting) => has$2(options, setting));
21154        return sort(settingNames);
21155    };
21156    const getRemovedOptions = (options) => {
21157        const settingNames = getMatchingOptions(options, removedOptions);
21158        // Forced root block is a special case whereby only the empty/false value is deprecated
21159        const forcedRootBlock = options.forced_root_block;
21160        // Note: This cast is required for old configurations as forced root block used to allow a boolean
21161        if (forcedRootBlock === false || forcedRootBlock === '') {
21162            settingNames.push('forced_root_block (false only)');
21163        }
21164        return sort(settingNames);
21165    };
21166    const getDeprecatedOptions = (options) => getMatchingOptions(options, deprecatedOptions);
21167    const getMatchingPlugins = (options, searchingFor) => {
21168        const plugins = Tools.makeMap(options.plugins, ' ');
21169        const hasPlugin = (plugin) => has$2(plugins, plugin);
21170        const pluginNames = filter$5(searchingFor, hasPlugin);
21171        return sort(pluginNames);
21172    };
21173    const getRemovedPlugins = (options) => getMatchingPlugins(options, removedPlugins);
21174    const getDeprecatedPlugins = (options) => getMatchingPlugins(options, deprecatedPlugins.map((entry) => entry.name));
21175    const logRemovedWarnings = (rawOptions, normalizedOptions) => {
21176        // Note: Ensure we use the original user settings, not the final when logging
21177        const removedOptions = getRemovedOptions(rawOptions);
21178        const removedPlugins = getRemovedPlugins(normalizedOptions);
21179        const hasRemovedPlugins = removedPlugins.length > 0;
21180        const hasRemovedOptions = removedOptions.length > 0;
21181        const isLegacyMobileTheme = normalizedOptions.theme === 'mobile';
21182        if (hasRemovedPlugins || hasRemovedOptions || isLegacyMobileTheme) {
21183            const listJoiner = '\n- ';
21184            const themesMessage = isLegacyMobileTheme ? `\n\nThemes:${listJoiner}mobile` : '';
21185            const pluginsMessage = hasRemovedPlugins ? `\n\nPlugins:${listJoiner}${removedPlugins.join(listJoiner)}` : '';
21186            const optionsMessage = hasRemovedOptions ? `\n\nOptions:${listJoiner}${removedOptions.join(listJoiner)}` : '';
21187            // eslint-disable-next-line no-console
21188            console.warn('The following deprecated features are currently enabled and have been removed in TinyMCE 8.0. These features will no longer work and should be removed from the TinyMCE configuration. ' +
21189                'See https://www.tiny.cloud/docs/tinymce/8/migration-from-7x/ for more information.' +
21190                themesMessage +
21191                pluginsMessage +
21192                optionsMessage);
21193        }
21194    };
21195    const getPluginDescription = (name) => find$2(deprecatedPlugins, (entry) => entry.name === name).fold(() => name, (entry) => {
21196        if (entry.replacedWith) {
21197            return `${name}, replaced by ${entry.replacedWith}`;
21198        }
21199        else {
21200            return name;
21201        }
21202    });
21203    const logDeprecatedWarnings = (rawOptions, normalizedOptions) => {
21204        // Note: Ensure we use the original user settings, not the final when logging
21205        const deprecatedOptions = getDeprecatedOptions(rawOptions);
21206        const deprecatedPlugins = getDeprecatedPlugins(normalizedOptions);
21207        const hasDeprecatedPlugins = deprecatedPlugins.length > 0;
21208        const hasDeprecatedOptions = deprecatedOptions.length > 0;
21209        if (hasDeprecatedPlugins || hasDeprecatedOptions) {
21210            const listJoiner = '\n- ';
21211            const pluginsMessage = hasDeprecatedPlugins ? `\n\nPlugins:${listJoiner}${deprecatedPlugins.map(getPluginDescription).join(listJoiner)}` : '';
21212            const optionsMessage = hasDeprecatedOptions ? `\n\nOptions:${listJoiner}${deprecatedOptions.join(listJoiner)}` : '';
21213            // eslint-disable-next-line no-console
21214            console.warn('The following deprecated features are currently enabled but will be removed soon.' +
21215                pluginsMessage +
21216                optionsMessage);
21217        }
21218    };
21219    const logWarnings = (rawOptions, normalizedOptions) => {
21220        logRemovedWarnings(rawOptions, normalizedOptions);
21221        logDeprecatedWarnings(rawOptions, normalizedOptions);
21222    };
21223    const deprecatedFeatures = {
21224        fire: 'The "fire" event api has been deprecated and will be removed in TinyMCE 9. Use "dispatch" instead.',
21225        selectionSetContent: 'The "editor.selection.setContent" method has been deprecated and will be removed in TinyMCE 9. Use "editor.insertContent" instead.'
21226    };
21227    const logFeatureDeprecationWarning = (feature) => {
21228        // eslint-disable-next-line no-console
21229        console.warn(deprecatedFeatures[feature], new Error().stack);
21230    };
21231
21232    const removeEmpty = (text) => {
21233        if (text.dom.length === 0) {
21234            remove$8(text);
21235            return Optional.none();
21236        }
21237        else {
21238            return Optional.some(text);
21239        }
21240    };
21241    const walkPastBookmark = (node, start) => node.filter((elm) => BookmarkManager.isBookmarkNode(elm.dom))
21242        .bind(start ? nextSibling : prevSibling);
21243    const merge = (outer, inner, rng, start, schema) => {
21244        const outerElm = outer.dom;
21245        const innerElm = inner.dom;
21246        const oldLength = start ? outerElm.length : innerElm.length;
21247        if (start) {
21248            mergeTextNodes(outerElm, innerElm, schema, false, !start);
21249            rng.setStart(innerElm, oldLength);
21250        }
21251        else {
21252            mergeTextNodes(innerElm, outerElm, schema, false, !start);
21253            rng.setEnd(innerElm, oldLength);
21254        }
21255    };
21256    const normalizeTextIfRequired = (inner, start, schema) => {
21257        parent(inner).each((root) => {
21258            const text = inner.dom;
21259            if (start && needsToBeNbspLeft(root, CaretPosition(text, 0), schema)) {
21260                normalizeWhitespaceAfter(text, 0, schema);
21261            }
21262            else if (!start && needsToBeNbspRight(root, CaretPosition(text, text.length), schema)) {
21263                normalizeWhitespaceBefore(text, text.length, schema);
21264            }
21265        });
21266    };
21267    const mergeAndNormalizeText = (outerNode, innerNode, rng, start, schema) => {
21268        outerNode.bind((outer) => {
21269            // Normalize the text outside the inserted content
21270            const normalizer = start ? normalizeWhitespaceBefore : normalizeWhitespaceAfter;
21271            normalizer(outer.dom, start ? outer.dom.length : 0, schema);
21272            // Merge the inserted content with other text nodes
21273            return innerNode.filter(isText$c).map((inner) => merge(outer, inner, rng, start, schema));
21274        }).orThunk(() => {
21275            // Note: Attempt to leave the inserted/inner content as is and only adjust if absolutely required
21276            const innerTextNode = walkPastBookmark(innerNode, start).or(innerNode).filter(isText$c);
21277            return innerTextNode.map((inner) => normalizeTextIfRequired(inner, start, schema));
21278        });
21279    };
21280    const rngSetContent = (rng, fragment, schema) => {
21281        const firstChild = Optional.from(fragment.firstChild).map(SugarElement.fromDom);
21282        const lastChild = Optional.from(fragment.lastChild).map(SugarElement.fromDom);
21283        rng.deleteContents();
21284        rng.insertNode(fragment);
21285        const prevText = firstChild.bind(prevSibling).filter(isText$c).bind(removeEmpty);
21286        const nextText = lastChild.bind(nextSibling).filter(isText$c).bind(removeEmpty);
21287        // Join and normalize text
21288        mergeAndNormalizeText(prevText, firstChild, rng, true, schema);
21289        mergeAndNormalizeText(nextText, lastChild, rng, false, schema);
21290        rng.collapse(false);
21291    };
21292    const setupArgs$3 = (args, content) => ({
21293        format: 'html',
21294        ...args,
21295        set: true,
21296        selection: true,
21297        content
21298    });
21299    const cleanContent = (editor, args) => {
21300        if (args.format !== 'raw') {
21301            // Find which context to parse the content in
21302            const rng = editor.selection.getRng();
21303            const contextBlock = editor.dom.getParent(rng.commonAncestorContainer, editor.dom.isBlock);
21304            const contextArgs = contextBlock ? { context: contextBlock.nodeName.toLowerCase() } : {};
21305            const node = editor.parser.parse(args.content, { forced_root_block: false, ...contextArgs, ...args });
21306            return HtmlSerializer({ validate: false }, editor.schema).serialize(node);
21307        }
21308        else {
21309            return args.content;
21310        }
21311    };
21312    const setContentInternal$1 = (editor, content, args = {}) => {
21313        const defaultedArgs = setupArgs$3(args, content);
21314        preProcessSetContent(editor, defaultedArgs).each((updatedArgs) => {
21315            // Sanitize the content
21316            const cleanedContent = cleanContent(editor, updatedArgs);
21317            const rng = editor.selection.getRng();
21318            rngSetContent(rng, rng.createContextualFragment(cleanedContent), editor.schema);
21319            editor.selection.setRng(rng);
21320            scrollRangeIntoView(editor, rng);
21321            postProcessSetContent(editor, cleanedContent, updatedArgs);
21322        });
21323    };
21324    const setContentExternal = (editor, content, args = {}) => {
21325        logFeatureDeprecationWarning('selectionSetContent');
21326        setContentInternal$1(editor, content, args);
21327    };
21328
21329    /**
21330     * Handles inserts of lists into the editor instance.
21331     *
21332     * @class tinymce.InsertList
21333     * @private
21334     */
21335    const hasOnlyOneChild$1 = (node) => {
21336        return isNonNullable(node.firstChild) && node.firstChild === node.lastChild;
21337    };
21338    const isPaddingNode = (node) => {
21339        return node.name === 'br' || node.value === nbsp;
21340    };
21341    const isPaddedEmptyBlock = (schema, node) => {
21342        const blockElements = schema.getBlockElements();
21343        return blockElements[node.name] && hasOnlyOneChild$1(node) && isPaddingNode(node.firstChild);
21344    };
21345    const isEmptyFragmentElement = (schema, node) => {
21346        const nonEmptyElements = schema.getNonEmptyElements();
21347        return isNonNullable(node) && (node.isEmpty(nonEmptyElements) || isPaddedEmptyBlock(schema, node));
21348    };
21349    const isListFragment = (schema, fragment) => {
21350        let firstChild = fragment.firstChild;
21351        let lastChild = fragment.lastChild;
21352        // Skip meta since it's likely <meta><ul>..</ul>
21353        if (firstChild && firstChild.name === 'meta') {
21354            firstChild = firstChild.next;
21355        }
21356        // Skip mce_marker since it's likely <ul>..</ul><span id="mce_marker"></span>
21357        if (lastChild && lastChild.attr('id') === 'mce_marker') {
21358            lastChild = lastChild.prev;
21359        }
21360        // Skip last child if it's an empty block
21361        if (isEmptyFragmentElement(schema, lastChild)) {
21362            lastChild = lastChild?.prev;
21363        }
21364        if (!firstChild || firstChild !== lastChild) {
21365            return false;
21366        }
21367        return firstChild.name === 'ul' || firstChild.name === 'ol';
21368    };
21369    const cleanupDomFragment = (domFragment) => {
21370        const firstChild = domFragment.firstChild;
21371        const lastChild = domFragment.lastChild;
21372        // TODO: remove the meta tag from paste logic
21373        if (firstChild && firstChild.nodeName === 'META') {
21374            firstChild.parentNode?.removeChild(firstChild);
21375        }
21376        if (lastChild && lastChild.id === 'mce_marker') {
21377            lastChild.parentNode?.removeChild(lastChild);
21378        }
21379        return domFragment;
21380    };
21381    const toDomFragment = (dom, serializer, fragment) => {
21382        const html = serializer.serialize(fragment);
21383        const domFragment = dom.createFragment(html);
21384        return cleanupDomFragment(domFragment);
21385    };
21386    const listItems = (elm) => {
21387        return filter$5(elm?.childNodes ?? [], (child) => {
21388            return child.nodeName === 'LI';
21389        });
21390    };
21391    const isPadding = (node) => {
21392        return node.data === nbsp || isBr$7(node);
21393    };
21394    const isListItemPadded = (node) => {
21395        return isNonNullable(node?.firstChild) && node.firstChild === node.lastChild && isPadding(node.firstChild);
21396    };
21397    const isEmptyOrPadded = (elm) => {
21398        return !elm.firstChild || isListItemPadded(elm);
21399    };
21400    const trimListItems = (elms) => {
21401        return elms.length > 0 && isEmptyOrPadded(elms[elms.length - 1]) ? elms.slice(0, -1) : elms;
21402    };
21403    const getParentLi = (dom, node) => {
21404        const parentBlock = dom.getParent(node, dom.isBlock);
21405        return parentBlock && parentBlock.nodeName === 'LI' ? parentBlock : null;
21406    };
21407    const isParentBlockLi = (dom, node) => {
21408        return !!getParentLi(dom, node);
21409    };
21410    const getSplit = (parentNode, rng) => {
21411        const beforeRng = rng.cloneRange();
21412        const afterRng = rng.cloneRange();
21413        beforeRng.setStartBefore(parentNode);
21414        afterRng.setEndAfter(parentNode);
21415        return [
21416            beforeRng.cloneContents(),
21417            afterRng.cloneContents()
21418        ];
21419    };
21420    const findFirstIn = (node, rootNode) => {
21421        const caretPos = CaretPosition.before(node);
21422        const caretWalker = CaretWalker(rootNode);
21423        const newCaretPos = caretWalker.next(caretPos);
21424        return newCaretPos ? newCaretPos.toRange() : null;
21425    };
21426    const findLastOf = (node, rootNode) => {
21427        const caretPos = CaretPosition.after(node);
21428        const caretWalker = CaretWalker(rootNode);
21429        const newCaretPos = caretWalker.prev(caretPos);
21430        return newCaretPos ? newCaretPos.toRange() : null;
21431    };
21432    const insertMiddle = (target, elms, rootNode, rng) => {
21433        const parts = getSplit(target, rng);
21434        const parentElm = target.parentNode;
21435        if (parentElm) {
21436            parentElm.insertBefore(parts[0], target);
21437            Tools.each(elms, (li) => {
21438                parentElm.insertBefore(li, target);
21439            });
21440            parentElm.insertBefore(parts[1], target);
21441            parentElm.removeChild(target);
21442        }
21443        return findLastOf(elms[elms.length - 1], rootNode);
21444    };
21445    const insertBefore$2 = (target, elms, rootNode) => {
21446        const parentElm = target.parentNode;
21447        if (parentElm) {
21448            Tools.each(elms, (elm) => {
21449                parentElm.insertBefore(elm, target);
21450            });
21451        }
21452        return findFirstIn(target, rootNode);
21453    };
21454    const insertAfter$2 = (target, elms, rootNode, dom) => {
21455        dom.insertAfter(elms.reverse(), target);
21456        return findLastOf(elms[0], rootNode);
21457    };
21458    const insertAtCaret$1 = (serializer, dom, rng, fragment) => {
21459        const domFragment = toDomFragment(dom, serializer, fragment);
21460        const liTarget = getParentLi(dom, rng.startContainer);
21461        const liElms = trimListItems(listItems(domFragment.firstChild));
21462        const BEGINNING = 1, END = 2;
21463        const rootNode = dom.getRoot();
21464        const isAt = (location) => {
21465            const caretPos = CaretPosition.fromRangeStart(rng);
21466            const caretWalker = CaretWalker(dom.getRoot());
21467            const newPos = location === BEGINNING ? caretWalker.prev(caretPos) : caretWalker.next(caretPos);
21468            const newPosNode = newPos?.getNode();
21469            return newPosNode ? getParentLi(dom, newPosNode) !== liTarget : true;
21470        };
21471        if (!liTarget) {
21472            return null;
21473        }
21474        else if (isAt(BEGINNING)) {
21475            return insertBefore$2(liTarget, liElms, rootNode);
21476        }
21477        else if (isAt(END)) {
21478            return insertAfter$2(liTarget, liElms, rootNode, dom);
21479        }
21480        else {
21481            return insertMiddle(liTarget, liElms, rootNode, rng);
21482        }
21483    };
21484
21485    const mergeableWrappedElements = ['pre'];
21486    const shouldPasteContentOnly = (dom, fragment, parentNode, root) => {
21487        const firstNode = fragment.firstChild;
21488        const lastNode = fragment.lastChild;
21489        const last = lastNode.attr('data-mce-type') === 'bookmark' ? lastNode.prev : lastNode;
21490        const isPastingSingleElement = firstNode === last;
21491        const isWrappedElement = contains$2(mergeableWrappedElements, firstNode.name);
21492        if (isPastingSingleElement && isWrappedElement) {
21493            const isContentEditable = firstNode.attr('contenteditable') !== 'false';
21494            const isPastingInTheSameBlockTag = dom.getParent(parentNode, dom.isBlock)?.nodeName.toLowerCase() === firstNode.name;
21495            const isPastingInContentEditable = Optional.from(getContentEditableRoot$1(root, parentNode)).forall(isContentEditableTrue$3);
21496            return isContentEditable && isPastingInTheSameBlockTag && isPastingInContentEditable;
21497        }
21498        else {
21499            return false;
21500        }
21501    };
21502    const isTableCell = isTableCell$3;
21503    const isTableCellContentSelected = (dom, rng, cell) => {
21504        if (isNonNullable(cell)) {
21505            const endCell = dom.getParent(rng.endContainer, isTableCell);
21506            return cell === endCell && hasAllContentsSelected(SugarElement.fromDom(cell), rng);
21507        }
21508        else {
21509            return false;
21510        }
21511    };
21512    const isEditableEmptyBlock = (dom, node) => {
21513        if (dom.isBlock(node) && dom.isEditable(node)) {
21514            const childNodes = node.childNodes;
21515            return (childNodes.length === 1 && isBr$7(childNodes[0])) || childNodes.length === 0;
21516        }
21517        else {
21518            return false;
21519        }
21520    };
21521    const validInsertion = (editor, value, parentNode) => {
21522        // Should never insert content into bogus elements, since these can
21523        // be resize handles or similar
21524        if (parentNode.getAttribute('data-mce-bogus') === 'all') {
21525            parentNode.parentNode?.insertBefore(editor.dom.createFragment(value), parentNode);
21526        }
21527        else {
21528            if (isEditableEmptyBlock(editor.dom, parentNode)) {
21529                editor.dom.setHTML(parentNode, value);
21530            }
21531            else {
21532                setContentInternal$1(editor, value, { no_events: true });
21533            }
21534        }
21535    };
21536    const trimBrsFromTableCell = (dom, elm, schema) => {
21537        Optional.from(dom.getParent(elm, 'td,th')).map(SugarElement.fromDom).each((el) => trimBlockTrailingBr(el, schema));
21538    };
21539    // Remove children nodes that are exactly the same as a parent node - name, attributes, styles
21540    const reduceInlineTextElements = (editor, merge) => {
21541        const textInlineElements = editor.schema.getTextInlineElements();
21542        const dom = editor.dom;
21543        if (merge) {
21544            const root = editor.getBody();
21545            const elementUtils = ElementUtils(editor);
21546            const fragmentSelector = '*[data-mce-fragment]';
21547            const fragments = dom.select(fragmentSelector);
21548            Tools.each(fragments, (node) => {
21549                const isInline = (currentNode) => isNonNullable(textInlineElements[currentNode.nodeName.toLowerCase()]);
21550                const hasOneChild = (currentNode) => currentNode.childNodes.length === 1;
21551                const hasNoNonInheritableStyles = (currentNode) => !(hasNonInheritableStyles(dom, currentNode) || hasConditionalNonInheritableStyles(dom, currentNode));
21552                if (hasNoNonInheritableStyles(node) && isInline(node) && hasOneChild(node)) {
21553                    const styles = getStyleProps(dom, node);
21554                    const isOverridden = (oldStyles, newStyles) => forall(oldStyles, (style) => contains$2(newStyles, style));
21555                    const overriddenByAllChildren = (childNode) => hasOneChild(node) && dom.is(childNode, fragmentSelector) && isInline(childNode) &&
21556                        (childNode.nodeName === node.nodeName && isOverridden(styles, getStyleProps(dom, childNode)) || overriddenByAllChildren(childNode.children[0]));
21557                    const identicalToParent = (parentNode) => isNonNullable(parentNode) && parentNode !== root
21558                        && (elementUtils.compare(node, parentNode) || identicalToParent(parentNode.parentElement));
21559                    const conflictWithInsertedParent = (parentNode) => isNonNullable(parentNode) && parentNode !== root
21560                        && dom.is(parentNode, fragmentSelector) && (hasStyleConflict(dom, node, parentNode) || conflictWithInsertedParent(parentNode.parentElement));
21561                    if (overriddenByAllChildren(node.children[0]) || (identicalToParent(node.parentElement) && !conflictWithInsertedParent(node.parentElement))) {
21562                        dom.remove(node, true);
21563                    }
21564                }
21565            });
21566            normalizeElements(editor, fromDom$1(fragments));
21567        }
21568    };
21569    const markFragmentElements = (fragment) => {
21570        let node = fragment;
21571        while ((node = node.walk())) {
21572            if (node.type === 1) {
21573                node.attr('data-mce-fragment', '1');
21574            }
21575        }
21576    };
21577    const unmarkFragmentElements = (elm) => {
21578        Tools.each(elm.getElementsByTagName('*'), (elm) => {
21579            elm.removeAttribute('data-mce-fragment');
21580        });
21581    };
21582    const isPartOfFragment = (node) => {
21583        return !!node.getAttribute('data-mce-fragment');
21584    };
21585    const canHaveChildren = (editor, node) => {
21586        return isNonNullable(node) && !editor.schema.getVoidElements()[node.nodeName];
21587    };
21588    const moveSelectionToMarker = (editor, marker) => {
21589        let nextRng;
21590        const dom = editor.dom;
21591        const selection = editor.selection;
21592        if (!marker) {
21593            return;
21594        }
21595        selection.scrollIntoView(marker);
21596        // If marker is in cE=false then move selection to that element instead
21597        const parentEditableElm = getContentEditableRoot$1(editor.getBody(), marker);
21598        if (parentEditableElm && dom.getContentEditable(parentEditableElm) === 'false') {
21599            dom.remove(marker);
21600            selection.select(parentEditableElm);
21601            return;
21602        }
21603        // Move selection before marker and remove it
21604        let rng = dom.createRng();
21605        // If previous sibling is a text node set the selection to the end of that node
21606        const node = marker.previousSibling;
21607        if (isText$b(node)) {
21608            rng.setStart(node, node.nodeValue?.length ?? 0);
21609            const node2 = marker.nextSibling;
21610            if (isText$b(node2)) {
21611                node.appendData(node2.data);
21612                node2.parentNode?.removeChild(node2);
21613            }
21614        }
21615        else {
21616            // If the previous sibling isn't a text node or doesn't exist set the selection before the marker node
21617            rng.setStartBefore(marker);
21618            rng.setEndBefore(marker);
21619        }
21620        const findNextCaretRng = (rng) => {
21621            let caretPos = CaretPosition.fromRangeStart(rng);
21622            const caretWalker = CaretWalker(editor.getBody());
21623            caretPos = caretWalker.next(caretPos);
21624            return caretPos?.toRange();
21625        };
21626        // Remove the marker node and set the new range
21627        const parentBlock = dom.getParent(marker, dom.isBlock);
21628        dom.remove(marker);
21629        if (parentBlock && dom.isEmpty(parentBlock)) {
21630            const isCell = isTableCell(parentBlock);
21631            empty(SugarElement.fromDom(parentBlock));
21632            rng.setStart(parentBlock, 0);
21633            rng.setEnd(parentBlock, 0);
21634            if (!isCell && !isPartOfFragment(parentBlock) && (nextRng = findNextCaretRng(rng))) {
21635                rng = nextRng;
21636                dom.remove(parentBlock);
21637            }
21638            else {
21639                // TINY-9860: If parentBlock is a table cell, add a br without 'data-mce-bogus' attribute.
21640                dom.add(parentBlock, dom.create('br', isCell ? {} : { 'data-mce-bogus': '1' }));
21641            }
21642        }
21643        selection.setRng(rng);
21644    };
21645    const deleteSelectedContent = (editor) => {
21646        const dom = editor.dom;
21647        // Fix for #2595 seems that delete removes one extra character on
21648        // WebKit for some odd reason if you double click select a word
21649        const rng = normalize(editor.selection.getRng());
21650        editor.selection.setRng(rng);
21651        // TINY-1044: Selecting all content in a single table cell will cause the entire table to be deleted
21652        // when using the native delete command. As such we need to manually delete the cell content instead
21653        const startCell = dom.getParent(rng.startContainer, isTableCell);
21654        if (isTableCellContentSelected(dom, rng, startCell)) {
21655            deleteCellContents(editor, rng, SugarElement.fromDom(startCell));
21656            // TINY-9193: If the selection is over the whole text node in an element then Firefox incorrectly moves the caret to the previous line
21657            // TINY-11953: If the selection is over the whole anchor node, then Chrome incorrectly removes parent node alongside with it's child - anchor
21658        }
21659        else if (isSelectionOverWholeAnchor(rng) || isSelectionOverWholeTextNode(rng)) {
21660            rng.deleteContents();
21661        }
21662        else {
21663            editor.getDoc().execCommand('Delete', false);
21664        }
21665    };
21666    const findMarkerNode = (scope) => {
21667        for (let markerNode = scope; markerNode; markerNode = markerNode.walk()) {
21668            if (markerNode.attr('id') === 'mce_marker') {
21669                return Optional.some(markerNode);
21670            }
21671        }
21672        return Optional.none();
21673    };
21674    const notHeadingsInSummary = (dom, node, fragment) => {
21675        return exists(fragment.children(), isHeading) && dom.getParent(node, dom.isBlock)?.nodeName === 'SUMMARY';
21676    };
21677    const insertHtmlAtCaret = (editor, value, details) => {
21678        const selection = editor.selection;
21679        const dom = editor.dom;
21680        // Setup parser and serializer
21681        const parser = editor.parser;
21682        const merge = details.merge;
21683        const serializer = HtmlSerializer({
21684            validate: true
21685        }, editor.schema);
21686        const bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">&#xFEFF;</span>';
21687        // TINY-10305: Remove all user-input zwsp to avoid impacting caret removal from content.
21688        if (!details.preserve_zwsp) {
21689            value = trim$2(value);
21690        }
21691        // Add caret at end of contents if it's missing
21692        if (value.indexOf('{$caret}') === -1) {
21693            value += '{$caret}';
21694        }
21695        // Replace the caret marker with a span bookmark element
21696        value = value.replace(/\{\$caret\}/, bookmarkHtml);
21697        // If selection is at <body>|<p></p> then move it into <body><p>|</p>
21698        let rng = selection.getRng();
21699        const caretElement = rng.startContainer;
21700        const body = editor.getBody();
21701        if (caretElement === body && selection.isCollapsed()) {
21702            if (dom.isBlock(body.firstChild) && canHaveChildren(editor, body.firstChild) && dom.isEmpty(body.firstChild)) {
21703                rng = dom.createRng();
21704                rng.setStart(body.firstChild, 0);
21705                rng.setEnd(body.firstChild, 0);
21706                selection.setRng(rng);
21707            }
21708        }
21709        // Insert node maker where we will insert the new HTML and get it's parent
21710        if (!selection.isCollapsed()) {
21711            deleteSelectedContent(editor);
21712        }
21713        const parentNode = selection.getNode();
21714        // Parse the fragment within the context of the parent node
21715        const parserArgs = { context: parentNode.nodeName.toLowerCase(), data: details.data, insert: true };
21716        const fragment = parser.parse(value, parserArgs);
21717        // Custom handling of lists
21718        if (details.paste === true && isListFragment(editor.schema, fragment) && isParentBlockLi(dom, parentNode)) {
21719            rng = insertAtCaret$1(serializer, dom, selection.getRng(), fragment);
21720            if (rng) {
21721                selection.setRng(rng);
21722            }
21723            return value;
21724        }
21725        if (details.paste === true && shouldPasteContentOnly(dom, fragment, parentNode, editor.getBody())) {
21726            fragment.firstChild?.unwrap();
21727        }
21728        markFragmentElements(fragment);
21729        // Move the caret to a more suitable location
21730        let node = fragment.lastChild;
21731        if (node && node.attr('id') === 'mce_marker') {
21732            const marker = node;
21733            for (node = node.prev; node; node = node.walk(true)) {
21734                if (node.name === 'table') {
21735                    break;
21736                }
21737                if (node.type === 3 || !dom.isBlock(node.name)) {
21738                    if (node.parent && editor.schema.isValidChild(node.parent.name, 'span')) {
21739                        node.parent.insert(marker, node, node.name === 'br');
21740                    }
21741                    break;
21742                }
21743            }
21744        }
21745        editor._selectionOverrides.showBlockCaretContainer(parentNode);
21746        // If parser says valid we can insert the contents into that parent
21747        if (!parserArgs.invalid && !notHeadingsInSummary(dom, parentNode, fragment)) {
21748            value = serializer.serialize(fragment);
21749            validInsertion(editor, value, parentNode);
21750        }
21751        else {
21752            // If the fragment was invalid within that context then we need
21753            // to parse and process the parent it's inserted into
21754            // Insert bookmark node and get the parent
21755            setContentInternal$1(editor, bookmarkHtml);
21756            let parentNode = selection.getNode();
21757            let tempNode;
21758            const rootNode = editor.getBody();
21759            // Opera will return the document node when selection is in root
21760            if (isDocument$1(parentNode)) {
21761                parentNode = tempNode = rootNode;
21762            }
21763            else {
21764                tempNode = parentNode;
21765            }
21766            // Find the ancestor just before the root element
21767            while (tempNode && tempNode !== rootNode) {
21768                parentNode = tempNode;
21769                tempNode = tempNode.parentNode;
21770            }
21771            // Get the outer/inner HTML depending on if we are in the root and parser and serialize that
21772            value = parentNode === rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode);
21773            const root = parser.parse(value);
21774            const markerNode = findMarkerNode(root);
21775            const editingHost = markerNode.bind(findClosestEditingHost).getOr(root);
21776            markerNode.each((marker) => marker.replace(fragment));
21777            const fragmentNodes = getAllDescendants(fragment);
21778            fragment.unwrap();
21779            const invalidChildren = filter$5(fragmentNodes, (node) => isInvalid(editor.schema, node));
21780            cleanInvalidNodes(invalidChildren, editor.schema, editingHost);
21781            filter$1(parser.getNodeFilters(), parser.getAttributeFilters(), root);
21782            value = serializer.serialize(root);
21783            // Set the inner/outer HTML depending on if we are in the root or not
21784            if (parentNode === rootNode) {
21785                dom.setHTML(rootNode, value);
21786            }
21787            else {
21788                dom.setOuterHTML(parentNode, value);
21789            }
21790        }
21791        reduceInlineTextElements(editor, merge);
21792        moveSelectionToMarker(editor, dom.get('mce_marker'));
21793        unmarkFragmentElements(editor.getBody());
21794        trimBrsFromTableCell(dom, selection.getStart(), editor.schema);
21795        updateCaret(editor.schema, editor.getBody(), selection.getStart());
21796        return value;
21797    };
21798
21799    const moveSelection = (editor) => {
21800        if (hasFocus(editor)) {
21801            firstPositionIn(editor.getBody()).each((pos) => {
21802                const node = pos.getNode();
21803                const caretPos = isTable$2(node) ? firstPositionIn(node).getOr(pos) : pos;
21804                editor.selection.setRng(caretPos.toRange());
21805            });
21806        }
21807    };
21808    const setEditorHtml = (editor, html, noSelection) => {
21809        editor.dom.setHTML(editor.getBody(), html);
21810        if (noSelection !== true) {
21811            moveSelection(editor);
21812        }
21813    };
21814    const setContentString = (editor, body, content, args) => {
21815        // TINY-10305: Remove all user-input zwsp to avoid impacting caret removal from content.
21816        content = trim$2(content);
21817        // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
21818        // It will also be impossible to place the caret in the editor unless there is a BR element present
21819        if (content.length === 0 || /^\s+$/.test(content)) {
21820            const padd = '<br data-mce-bogus="1">';
21821            // Todo: There is a lot more root elements that need special padding
21822            // so separate this and add all of them at some point.
21823            if (body.nodeName === 'TABLE') {
21824                content = '<tr><td>' + padd + '</td></tr>';
21825            }
21826            else if (/^(UL|OL)$/.test(body.nodeName)) {
21827                content = '<li>' + padd + '</li>';
21828            }
21829            const forcedRootBlockName = getForcedRootBlock(editor);
21830            // Check if forcedRootBlock is a valid child of the body
21831            if (editor.schema.isValidChild(body.nodeName.toLowerCase(), forcedRootBlockName.toLowerCase())) {
21832                content = padd;
21833                content = editor.dom.createHTML(forcedRootBlockName, getForcedRootBlockAttrs(editor), content);
21834            }
21835            else if (!content) {
21836                content = padd;
21837            }
21838            setEditorHtml(editor, content, args.no_selection);
21839            return { content, html: content };
21840        }
21841        else {
21842            if (args.format !== 'raw') {
21843                content = HtmlSerializer({ validate: false }, editor.schema).serialize(editor.parser.parse(content, { isRootContent: true, insert: true }));
21844            }
21845            const trimmedHtml = isWsPreserveElement(SugarElement.fromDom(body)) ? content : Tools.trim(content);
21846            setEditorHtml(editor, trimmedHtml, args.no_selection);
21847            return { content: trimmedHtml, html: trimmedHtml };
21848        }
21849    };
21850    const setContentTree = (editor, body, content, args) => {
21851        filter$1(editor.parser.getNodeFilters(), editor.parser.getAttributeFilters(), content);
21852        const html = HtmlSerializer({ validate: false }, editor.schema).serialize(content);
21853        // TINY-10305: Remove all user-input zwsp to avoid impacting caret removal from content.
21854        const trimmedHtml = trim$2(isWsPreserveElement(SugarElement.fromDom(body)) ? html : Tools.trim(html));
21855        setEditorHtml(editor, trimmedHtml, args.no_selection);
21856        return { content, html: trimmedHtml };
21857    };
21858    const setContentInternal = (editor, content, args) => {
21859        return Optional.from(editor.getBody()).map((body) => {
21860            if (isTreeNode(content)) {
21861                return setContentTree(editor, body, content, args);
21862            }
21863            else {
21864                return setContentString(editor, body, content, args);
21865            }
21866        }).getOr({ content, html: isTreeNode(args.content) ? '' : args.content });
21867    };
21868
21869    const postProcessHooks = {};
21870    const isPre = matchNodeNames$1(['pre']);
21871    const addPostProcessHook = (name, hook) => {
21872        const hooks = postProcessHooks[name];
21873        if (!hooks) {
21874            postProcessHooks[name] = [];
21875        }
21876        postProcessHooks[name].push(hook);
21877    };
21878    const postProcess$1 = (name, editor) => {
21879        if (has$2(postProcessHooks, name)) {
21880            each$e(postProcessHooks[name], (hook) => {
21881                hook(editor);
21882            });
21883        }
21884    };
21885    addPostProcessHook('pre', (editor) => {
21886        const rng = editor.selection.getRng();
21887        const hasPreSibling = (blocks) => (pre) => {
21888            const prev = pre.previousSibling;
21889            return isPre(prev) && contains$2(blocks, prev);
21890        };
21891        const joinPre = (pre1, pre2) => {
21892            const sPre2 = SugarElement.fromDom(pre2);
21893            const doc = documentOrOwner(sPre2).dom;
21894            remove$8(sPre2);
21895            append(SugarElement.fromDom(pre1), [
21896                SugarElement.fromTag('br', doc),
21897                SugarElement.fromTag('br', doc),
21898                ...children$
vendor: 7,543 bytes, lines 21898-22048
218981(sPre2)
21899            ]);
21900        };
21901        if (!rng.collapsed) {
21902            const blocks = editor.selection.getSelectedBlocks();
21903            const preBlocks = filter$5(filter$5(blocks, isPre), hasPreSibling(blocks));
21904            each$e(preBlocks, (pre) => {
21905                joinPre(pre.previousSibling, pre);
21906            });
21907        }
21908    });
21909
21910    const each$5 = Tools.each;
21911    const mergeTextDecorationsAndColor = (dom, format, vars, node) => {
21912        const processTextDecorationsAndColor = (n) => {
21913            if (isHTMLElement(n) && isElement$7(n.parentNode) && dom.isEditable(n)) {
21914                const parentTextDecoration = getTextDecoration(dom, n.parentNode);
21915                if (dom.getStyle(n, 'color') && parentTextDecoration) {
21916                    dom.setStyle(n, 'text-decoration', parentTextDecoration);
21917                }
21918                else if (dom.getStyle(n, 'text-decoration') === parentTextDecoration) {
21919                    dom.setStyle(n, 'text-decoration', null);
21920                }
21921            }
21922        };
21923        // Colored nodes should be underlined so that the color of the underline matches the text color.
21924        if (format.styles && (format.styles.color || format.styles.textDecoration)) {
21925            Tools.walk(node, processTextDecorationsAndColor, 'childNodes');
21926            processTextDecorationsAndColor(node);
21927        }
21928    };
21929    const mergeBackgroundColorAndFontSize = (dom, format, vars, node) => {
21930        // nodes with font-size should have their own background color as well to fit the line-height (see TINY-882)
21931        if (format.styles && format.styles.backgroundColor) {
21932            const hasFontSize = hasStyle(dom, 'fontSize');
21933            processChildElements(node, (elm) => hasFontSize(elm) && dom.isEditable(elm), applyStyle(dom, 'backgroundColor', replaceVars(format.styles.backgroundColor, vars)));
21934        }
21935    };
21936    const mergeSubSup = (dom, format, vars, node) => {
21937        // Remove font size on all descendants of a sub/sup and remove the inverse elements
21938        if (isInlineFormat(format) && (format.inline === 'sub' || format.inline === 'sup')) {
21939            const hasFontSize = hasStyle(dom, 'fontSize');
21940            processChildElements(node, (elm) => hasFontSize(elm) && dom.isEditable(elm), applyStyle(dom, 'fontSize', ''));
21941            const inverseTagDescendants = filter$5(dom.select(format.inline === 'sup' ? 'sub' : 'sup', node), dom.isEditable);
21942            dom.remove(inverseTagDescendants, true);
21943        }
21944    };
21945    const mergeWithChildren = (editor, formatList, vars, node) => {
21946        // Remove/merge children
21947        // Note: RemoveFormat.removeFormat will not remove formatting from noneditable nodes
21948        each$5(formatList, (format) => {
21949            // Merge all children of similar type will move styles from child to parent
21950            // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
21951            // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
21952            if (isInlineFormat(format)) {
21953                each$5(editor.dom.select(format.inline, node), (child) => {
21954                    if (isElementNode(child)) {
21955                        removeNodeFormat(editor, format, vars, child, format.exact ? child : null);
21956                    }
21957                });
21958            }
21959            clearChildStyles(editor.dom, format, node);
21960        });
21961    };
21962    const mergeWithParents = (editor, format, name, vars, node) => {
21963        // Remove format if direct parent already has the same format
21964        // Note: RemoveFormat.removeFormat will not remove formatting from noneditable nodes
21965        const parentNode = node.parentNode;
21966        if (matchNode$1(editor, parentNode, name, vars)) {
21967            if (removeNodeFormat(editor, format, vars, node)) {
21968                return;
21969            }
21970        }
21971        // Remove format if any ancestor already has the same format
21972        if (format.merge_with_parents && parentNode) {
21973            editor.dom.getParent(parentNode, (parent) => {
21974                if (matchNode$1(editor, parent, name, vars)) {
21975                    removeNodeFormat(editor, format, vars, node);
21976                    return true;
21977                }
21978                else {
21979                    return false;
21980                }
21981            });
21982        }
21983    };
21984
21985    const each$4 = Tools.each;
21986    const canFormatBR = (editor, format, node, parentName) => {
21987        // TINY-6483: Can format 'br' if it is contained in a valid empty block and an inline format is being applied
21988        if (canFormatEmptyLines(editor) && isInlineFormat(format) && node.parentNode) {
21989            const validBRParentElements = getTextRootBlockElements(editor.schema);
21990            // If a caret node is present, the format should apply to that, not the br (applicable to collapsed selections)
21991            const hasCaretNodeSibling = sibling(SugarElement.fromDom(node), (sibling) => isCaretNode(sibling.dom));
21992            return hasNonNullableKey(validBRParentElements, parentName) && isEmptyNode(editor.schema, node.parentNode, { skipBogus: false, includeZwsp: true }) && !hasCaretNodeSibling;
21993        }
21994        else {
21995            return false;
21996        }
21997    };
21998    const applyFormatAction = (ed, name, vars, node) => {
21999        const formatList = ed.formatter.get(name);
22000        const format = formatList[0];
22001        const isCollapsed = !node && ed.selection.isCollapsed();
22002        const dom = ed.dom;
22003        const selection = ed.selection;
22004        const applyNodeStyle = (formatList, node) => {
22005            let found = false;
22006            // Look for matching formats
22007            each$4(formatList, (format) => {
22008                if (!isSelectorFormat(format)) {
22009                    return false;
22010                }
22011                // Check if the node is nonediatble and if the format can override noneditable node
22012                if (dom.getContentEditable(node) === 'false' && !format.ceFalseOverride) {
22013                    return true;
22014                }
22015                // Check collapsed state if it exists
22016                if (isNonNullable(format.collapsed) && format.collapsed !== isCollapsed) {
22017                    return true;
22018                }
22019                if (dom.is(node, format.selector) && !isCaretNode(node)) {
22020                    setElementFormat(ed, node, format, vars, node);
22021                    found = true;
22022                    return false;
22023                }
22024                return true;
22025            });
22026            return found;
22027        };
22028        const createWrapElement = (wrapName) => {
22029            if (isString(wrapName)) {
22030                const wrapElm = dom.create(wrapName);
22031                setElementFormat(ed, wrapElm, format, vars, node);
22032                return wrapElm;
22033            }
22034            else {
22035                return null;
22036            }
22037        };
22038        const applyRngStyle = (dom, rng, nodeSpecific) => {
22039            const newWrappers = [];
22040            let contentEditable = true;
22041            // Setup wrapper element
22042            const wrapName = format.inline || format.block;
22043            const wrapElm = createWrapElement(wrapName);
22044            const isMatchingWrappingBlock = (node) => isWrappingBlockFormat(format) && matchNode$1(ed, node, name, vars);
22045            const canRenameBlock = (node, parentName, isEditableDescendant) => {
22046                const isValidBlockFormatForNode = isNonWrappingBlockFormat(format) &&
22047                    isTextBlock$2(ed.schema, node) &&
22048                    isValid(ed, parentName, wrapNam
vendor: 8,487 bytes, lines 22048-22199
22048e);
22049                return isEditableDescendant && isValidBlockFormatForNode;
22050            };
22051            const canWrapNode = (node, parentName, isEditableDescendant, isWrappableNoneditableElm) => {
22052                const nodeName = node.nodeName.toLowerCase();
22053                const isValidWrapNode = isValid(ed, wrapName, nodeName) &&
22054                    isValid(ed, parentName, wrapName);
22055                // If it is not node specific, it means that it was not passed into 'formatter.apply` and is within the editor selection
22056                const isZwsp$1 = !nodeSpecific && isText$b(node) && isZwsp(node.data);
22057                const isCaret = isCaretNode(node);
22058                const isCorrectFormatForNode = !isInlineFormat(format) || !dom.isBlock(node);
22059                return (isEditableDescendant || isWrappableNoneditableElm) && isValidWrapNode && !isZwsp$1 && !isCaret && isCorrectFormatForNode;
22060            };
22061            walk$3(dom, rng, (nodes) => {
22062                let currentWrapElm;
22063                /**
22064                 * Process a list of nodes wrap them.
22065                 */
22066                const process = (node) => {
22067                    let hasContentEditableState = false;
22068                    let lastContentEditable = contentEditable;
22069                    let isWrappableNoneditableElm = false;
22070                    const parentNode = node.parentNode;
22071                    const parentName = parentNode.nodeName.toLowerCase();
22072                    // Node has a contentEditable value
22073                    const contentEditableValue = dom.getContentEditable(node);
22074                    if (isNonNullable(contentEditableValue)) {
22075                        lastContentEditable = contentEditable;
22076                        contentEditable = contentEditableValue === 'true';
22077                        // Unless the noneditable element is wrappable, we don't want to wrap the container, only it's editable children
22078                        hasContentEditableState = true;
22079                        isWrappableNoneditableElm = isWrappableNoneditable(ed, node);
22080                    }
22081                    const isEditableDescendant = contentEditable && !hasContentEditableState;
22082                    // Stop wrapping on br elements except when valid
22083                    if (isBr$7(node) && !canFormatBR(ed, format, node, parentName)) {
22084                        currentWrapElm = null;
22085                        // Remove any br elements when we wrap things
22086                        if (isBlockFormat(format)) {
22087                            dom.remove(node);
22088                        }
22089                        return;
22090                    }
22091                    if (isMatchingWrappingBlock(node)) {
22092                        currentWrapElm = null;
22093                        return;
22094                    }
22095                    if (canRenameBlock(node, parentName, isEditableDescendant)) {
22096                        const elm = dom.rename(node, wrapName);
22097                        setElementFormat(ed, elm, format, vars, node);
22098                        newWrappers.push(elm);
22099                        currentWrapElm = null;
22100                        return;
22101                    }
22102                    if (isSelectorFormat(format)) {
22103                        let found = applyNodeStyle(formatList, node);
22104                        // TINY-6567/TINY-7393: Include the parent if using an expanded selector format and no match was found for the current node
22105                        if (!found && isNonNullable(parentNode) && shouldExpandToSelector(format)) {
22106                            found = applyNodeStyle(formatList, parentNode);
22107                        }
22108                        // Continue processing if a selector match wasn't found and a inline element is defined
22109                        if (!isInlineFormat(format) || found) {
22110                            currentWrapElm = null;
22111                            return;
22112                        }
22113                    }
22114                    if (isNonNullable(wrapElm) && canWrapNode(node, parentName, isEditableDescendant, isWrappableNoneditableElm)) {
22115                        // Start wrapping
22116                        if (!currentWrapElm) {
22117                            // Wrap the node
22118                            currentWrapElm = dom.clone(wrapElm, false);
22119                            parentNode.insertBefore(currentWrapElm, node);
22120                            newWrappers.push(currentWrapElm);
22121                        }
22122                        // Wrappable noneditable element has been handled so go back to previous state
22123                        if (isWrappableNoneditableElm && hasContentEditableState) {
22124                            contentEditable = lastContentEditable;
22125                        }
22126                        currentWrapElm.appendChild(node);
22127                    }
22128                    else {
22129                        // Start a new wrapper for possible children
22130                        currentWrapElm = null;
22131                        each$e(from(node.childNodes), process);
22132                        if (hasContentEditableState) {
22133                            contentEditable = lastContentEditable; // Restore last contentEditable state from stack
22134                        }
22135                        // End the last wrapper
22136                        currentWrapElm = null;
22137                    }
22138                };
22139                each$e(nodes, process);
22140            });
22141            // Apply formats to links as well to get the color of the underline to change as well
22142            if (format.links === true) {
22143                each$e(newWrappers, (wrapper) => {
22144                    const process = (target) => {
22145                        if (target.nodeName === 'A') {
22146                            setElementFormat(ed, target, format, vars, node);
22147                        }
22148                        each$e(from(target.childNodes), process);
22149                    };
22150                    process(wrapper);
22151                });
22152            }
22153            normalizeFontSizeElementsAfterApply(ed, name, fromDom$1(newWrappers));
22154            // Cleanup
22155            each$e(newWrappers, (node) => {
22156                const getChildCount = (node) => {
22157                    let count = 0;
22158                    each$e(node.childNodes, (node) => {
22159                        if (!isEmptyTextNode$1(node) && !isBookmarkNode$1(node)) {
22160                            count++;
22161                        }
22162                    });
22163                    return count;
22164                };
22165                const mergeStyles = (node) => {
22166                    // Check if a child was found and of the same type as the current node
22167                    const childElement = find$2(node.childNodes, isElementNode$1)
22168                        .filter((child) => dom.getContentEditable(child) !== 'false' && matchName(dom, child, format));
22169                    return childElement.map((child) => {
22170                        const clone = dom.clone(child, false);
22171                        setElementFormat(ed, clone, format, vars, node);
22172                        dom.replace(clone, node, true);
22173                        dom.remove(child, true);
22174                        return clone;
22175                    }).getOr(node);
22176                };
22177                const childCount = getChildCount(node);
22178                // Remove empty nodes but only if there is multiple wrappers and they are not block
22179                // elements so never remove single <h1></h1> since that would remove the
22180                // current empty block element where the caret is at
22181                if ((newWrappers.length > 1 || !dom.isBlock(node)) && childCount === 0) {
22182                    dom.remove(node, true);
22183                    return;
22184                }
22185                if (isInlineFormat(format) || isBlockFormat(format) && format.wrapper) {
22186                    // Merges the current node with it's children of similar type to reduce the number of elements
22187                    if (!format.exact && childCount === 1) {
22188                        node = mergeStyles(node);
22189                    }
22190                    mergeWithChildren(ed, formatList, vars, node);
22191                    mergeWithParents(ed, format, name, vars, node);
22192                    mergeBackgroundColorAndFontSize(dom, format, vars, node);
22193                    mergeTextDecorationsAndColor(dom, format, vars, node);
22194                    mergeSubSup(dom, format, vars, node);
22195                    mergeSiblings(ed, format, vars, node);
22196                }
22197            });
22198        };
22199        
vendor: 7,227 bytes, lines 22199-22358
22199// TODO: TINY-9142: Remove this to make nested noneditable formatting work
22200        const targetNode = isNode(node) ? node : selection.getNode();
22201        if (dom.getContentEditable(targetNode) === 'false' && !isWrappableNoneditable(ed, targetNode)) {
22202            // node variable is used by other functions above in the same scope so need to set it here
22203            node = targetNode;
22204            applyNodeStyle(formatList, node);
22205            fireFormatApply(ed, name, node, vars);
22206            return;
22207        }
22208        if (format) {
22209            if (node) {
22210                if (isNode(node)) {
22211                    if (!applyNodeStyle(formatList, node)) {
22212                        const rng = dom.createRng();
22213                        rng.setStartBefore(node);
22214                        rng.setEndAfter(node);
22215                        applyRngStyle(dom, expandRng(dom, rng, formatList), true);
22216                    }
22217                }
22218                else {
22219                    applyRngStyle(dom, node, true);
22220                }
22221            }
22222            else {
22223                if (!isCollapsed || !isInlineFormat(format) || getCellsFromEditor(ed).length) {
22224                    // Apply formatting to selection
22225                    selection.setRng(normalize(selection.getRng()));
22226                    preserveSelection(ed, () => {
22227                        runOnRanges(ed, (selectionRng, fake) => {
22228                            const expandedRng = fake ? selectionRng : expandRng(dom, selectionRng, formatList);
22229                            applyRngStyle(dom, expandedRng, false);
22230                        });
22231                    }, always);
22232                    ed.nodeChanged();
22233                }
22234                else {
22235                    applyCaretFormat(ed, name, vars);
22236                }
22237                getExpandedListItemFormat(ed.formatter, name).each((liFmt) => {
22238                    const list = getFullySelectedListItems(ed.selection);
22239                    each$e(list, (li) => applyStyles(dom, li, liFmt, vars));
22240                });
22241            }
22242            postProcess$1(name, ed);
22243        }
22244        fireFormatApply(ed, name, node, vars);
22245    };
22246    const applyFormat$1 = (editor, name, vars, node) => {
22247        if (node || editor.selection.isEditable()) {
22248            applyFormatAction(editor, name, vars, node);
22249        }
22250    };
22251
22252    const hasVars = (value) => has$2(value, 'vars');
22253    const setup$A = (registeredFormatListeners, editor) => {
22254        registeredFormatListeners.set({});
22255        editor.on('NodeChange', (e) => {
22256            updateAndFireChangeCallbacks(editor, e.element, registeredFormatListeners.get());
22257        });
22258        editor.on('FormatApply FormatRemove', (e) => {
22259            const element = Optional.from(e.node)
22260                .map((nodeOrRange) => isNode(nodeOrRange) ? nodeOrRange : nodeOrRange.startContainer)
22261                .bind((node) => isElement$7(node) ? Optional.some(node) : Optional.from(node.parentElement))
22262                .getOrThunk(() => fallbackElement(editor));
22263            updateAndFireChangeCallbacks(editor, element, registeredFormatListeners.get());
22264        });
22265    };
22266    const fallbackElement = (editor) => editor.selection.getStart();
22267    const matchingNode = (editor, parents, format, similar, vars) => {
22268        const isMatchingNode = (node) => {
22269            const matchingFormat = editor.formatter.matchNode(node, format, vars ?? {}, similar);
22270            return !isUndefined(matchingFormat);
22271        };
22272        const isUnableToMatch = (node) => {
22273            if (matchesUnInheritedFormatSelector(editor, node, format)) {
22274                return true;
22275            }
22276            else {
22277                if (!similar) {
22278                    // If we want to find an exact match, then finding a similar match halfway up the parents tree is bad
22279                    return isNonNullable(editor.formatter.matchNode(node, format, vars, true));
22280                }
22281                else {
22282                    return false;
22283                }
22284            }
22285        };
22286        return findUntil$1(parents, isMatchingNode, isUnableToMatch);
22287    };
22288    const getParents = (editor, elm) => {
22289        const element = elm ?? fallbackElement(editor);
22290        return filter$5(getParents$2(editor.dom, element), (node) => isElement$7(node) && !isBogus$1(node));
22291    };
22292    const updateAndFireChangeCallbacks = (editor, elm, registeredCallbacks) => {
22293        // Ignore bogus nodes like the <a> tag created by moveStart()
22294        const parents = getParents(editor, elm);
22295        each$d(registeredCallbacks, (data, format) => {
22296            const runIfChanged = (spec) => {
22297                const match = matchingNode(editor, parents, format, spec.similar, hasVars(spec) ? spec.vars : undefined);
22298                const isSet = match.isSome();
22299                if (spec.state.get() !== isSet) {
22300                    spec.state.set(isSet);
22301                    const node = match.getOr(elm);
22302                    if (hasVars(spec)) {
22303                        spec.callback(isSet, { node, format, parents });
22304                    }
22305                    else {
22306                        each$e(spec.callbacks, (callback) => callback(isSet, { node, format, parents }));
22307                    }
22308                }
22309            };
22310            each$e([data.withSimilar, data.withoutSimilar], runIfChanged);
22311            each$e(data.withVars, runIfChanged);
22312        });
22313    };
22314    const addListeners = (editor, registeredFormatListeners, formats, callback, similar, vars) => {
22315        const formatChangeItems = registeredFormatListeners.get();
22316        each$e(formats.split(','), (format) => {
22317            const group = get$a(formatChangeItems, format).getOrThunk(() => {
22318                const base = {
22319                    withSimilar: {
22320                        state: Cell(false),
22321                        similar: true,
22322                        callbacks: []
22323                    },
22324                    withoutSimilar: {
22325                        state: Cell(false),
22326                        similar: false,
22327                        callbacks: []
22328                    },
22329                    withVars: []
22330                };
22331                formatChangeItems[format] = base;
22332                return base;
22333            });
22334            const getCurrent = () => {
22335                const parents = getParents(editor);
22336                return matchingNode(editor, parents, format, similar, vars).isSome();
22337            };
22338            if (isUndefined(vars)) {
22339                const toAppendTo = similar ? group.withSimilar : group.withoutSimilar;
22340                toAppendTo.callbacks.push(callback);
22341                if (toAppendTo.callbacks.length === 1) {
22342                    toAppendTo.state.set(getCurrent());
22343                }
22344            }
22345            else {
22346                group.withVars.push({
22347                    state: Cell(getCurrent()),
22348                    similar,
22349                    vars,
22350                    callback
22351                });
22352            }
22353        });
22354        registeredFormatListeners.set(formatChangeItems);
22355    };
22356    const removeListeners = (registeredFormatListeners, formats, callback) => {
22357        const formatChangeItems = registeredFormatListeners.get();
22358        each$e(formats.split(','), (format) => get$a
vendor: 21,855 bytes, lines 22358-22882
22358(formatChangeItems, format).each((group) => {
22359            formatChangeItems[format] = {
22360                withSimilar: {
22361                    ...group.withSimilar,
22362                    callbacks: filter$5(group.withSimilar.callbacks, (cb) => cb !== callback),
22363                },
22364                withoutSimilar: {
22365                    ...group.withoutSimilar,
22366                    callbacks: filter$5(group.withoutSimilar.callbacks, (cb) => cb !== callback),
22367                },
22368                withVars: filter$5(group.withVars, (item) => item.callback !== callback),
22369            };
22370        }));
22371        registeredFormatListeners.set(formatChangeItems);
22372    };
22373    const formatChangedInternal = (editor, registeredFormatListeners, formats, callback, similar, vars) => {
22374        addListeners(editor, registeredFormatListeners, formats, callback, similar, vars);
22375        return {
22376            unbind: () => removeListeners(registeredFormatListeners, formats, callback)
22377        };
22378    };
22379
22380    const toggle = (editor, name, vars, node) => {
22381        const fmt = editor.formatter.get(name);
22382        if (fmt) {
22383            if (match$2(editor, name, vars, node) && (!('toggle' in fmt[0]) || fmt[0].toggle)) {
22384                removeFormat$1(editor, name, vars, node);
22385            }
22386            else {
22387                applyFormat$1(editor, name, vars, node);
22388            }
22389        }
22390    };
22391
22392    const tableModel = (element, width, rows) => ({
22393        element,
22394        width,
22395        rows
22396    });
22397    const tableRow = (element, cells) => ({
22398        element,
22399        cells
22400    });
22401    const cellPosition = (x, y) => ({
22402        x,
22403        y
22404    });
22405    const getSpan = (td, key) => {
22406        return getOpt(td, key).bind(toInt).getOr(1);
22407    };
22408    const fillout = (table, x, y, tr, td) => {
22409        const rowspan = getSpan(td, 'rowspan');
22410        const colspan = getSpan(td, 'colspan');
22411        const rows = table.rows;
22412        for (let y2 = y; y2 < y + rowspan; y2++) {
22413            if (!rows[y2]) {
22414                rows[y2] = tableRow(deep(tr), []);
22415            }
22416            for (let x2 = x; x2 < x + colspan; x2++) {
22417                const cells = rows[y2].cells;
22418                // not filler td:s are purposely not cloned so that we can
22419                // find cells in the model by element object references
22420                cells[x2] = y2 === y && x2 === x ? td : shallow(td);
22421            }
22422        }
22423    };
22424    const cellExists = (table, x, y) => {
22425        const rows = table.rows;
22426        const cells = rows[y] ? rows[y].cells : [];
22427        return !!cells[x];
22428    };
22429    const skipCellsX = (table, x, y) => {
22430        while (cellExists(table, x, y)) {
22431            x++;
22432        }
22433        return x;
22434    };
22435    const getWidth = (rows) => {
22436        return foldl(rows, (acc, row) => {
22437            return row.cells.length > acc ? row.cells.length : acc;
22438        }, 0);
22439    };
22440    const findElementPos = (table, element) => {
22441        const rows = table.rows;
22442        for (let y = 0; y < rows.length; y++) {
22443            const cells = rows[y].cells;
22444            for (let x = 0; x < cells.length; x++) {
22445                if (eq(cells[x], element)) {
22446                    return Optional.some(cellPosition(x, y));
22447                }
22448            }
22449        }
22450        return Optional.none();
22451    };
22452    const extractRows = (table, sx, sy, ex, ey) => {
22453        const newRows = [];
22454        const rows = table.rows;
22455        for (let y = sy; y <= ey; y++) {
22456            const cells = rows[y].cells;
22457            const slice = sx < ex ? cells.slice(sx, ex + 1) : cells.slice(ex, sx + 1);
22458            newRows.push(tableRow(rows[y].element, slice));
22459        }
22460        return newRows;
22461    };
22462    const subTable = (table, startPos, endPos) => {
22463        const sx = startPos.x, sy = startPos.y;
22464        const ex = endPos.x, ey = endPos.y;
22465        const newRows = sy < ey ? extractRows(table, sx, sy, ex, ey) : extractRows(table, sx, ey, ex, sy);
22466        return tableModel(table.element, getWidth(newRows), newRows);
22467    };
22468    const createDomTable = (table, rows) => {
22469        const tableElement = shallow(table.element);
22470        const tableBody = SugarElement.fromTag('tbody');
22471        append(tableBody, rows);
22472        append$1(tableElement, tableBody);
22473        return tableElement;
22474    };
22475    const modelRowsToDomRows = (table) => {
22476        return map$3(table.rows, (row) => {
22477            const cells = map$3(row.cells, (cell) => {
22478                const td = deep(cell);
22479                remove$9(td, 'colspan');
22480                remove$9(td, 'rowspan');
22481                return td;
22482            });
22483            const tr = shallow(row.element);
22484            append(tr, cells);
22485            return tr;
22486        });
22487    };
22488    const fromDom = (tableElm) => {
22489        const table = tableModel(shallow(tableElm), 0, []);
22490        each$e(descendants(tableElm, 'tr'), (tr, y) => {
22491            each$e(descendants(tr, 'td,th'), (td, x) => {
22492                fillout(table, skipCellsX(table, x, y), y, tr, td);
22493            });
22494        });
22495        return tableModel(table.element, getWidth(table.rows), table.rows);
22496    };
22497    const toDom = (table) => {
22498        return createDomTable(table, modelRowsToDomRows(table));
22499    };
22500    const subsection = (table, startElement, endElement) => {
22501        return findElementPos(table, startElement).bind((startPos) => {
22502            return findElementPos(table, endElement).map((endPos) => {
22503                return subTable(table, startPos, endPos);
22504            });
22505        });
22506    };
22507
22508    const findParentListContainer = (parents) => find$2(parents, (elm) => name(elm) === 'ul' || name(elm) === 'ol');
22509    const getFullySelectedListWrappers = (parents, rng) => find$2(parents, (elm) => name(elm) === 'li' && hasAllContentsSelected(elm, rng)).fold(constant([]), (_li) => findParentListContainer(parents).map((listCont) => {
22510        const listElm = SugarElement.fromTag(name(listCont));
22511        // Retain any list-style* styles when generating the new fragment
22512        const listStyles = filter$4(getAllRaw(listCont), (_style, name) => startsWith(name, 'list-style'));
22513        setAll(listElm, listStyles);
22514        return [
22515            SugarElement.fromTag('li'),
22516            listElm
22517        ];
22518    }).getOr([]));
22519    const wrap = (innerElm, elms) => {
22520        const wrapped = foldl(elms, (acc, elm) => {
22521            append$1(elm, acc);
22522            return elm;
22523        }, innerElm);
22524        return elms.length > 0 ? fromElements([wrapped]) : wrapped;
22525    };
22526    const directListWrappers = (commonAnchorContainer) => {
22527        if (isListItem$2(commonAnchorContainer)) {
22528            return parent(commonAnchorContainer).filter(isList$1).fold(constant([]), (listElm) => [commonAnchorContainer, listElm]);
22529        }
22530        else {
22531            return isList$1(commonAnchorContainer) ? [commonAnchorContainer] : [];
22532        }
22533    };
22534    const getWrapElements = (rootNode, rng, schema) => {
22535        const commonAnchorContainer = SugarElement.fromDom(rng.commonAncestorContainer);
22536        const parents = parentsAndSelf(commonAnchorContainer, rootNode);
22537        const wrapElements = filter$5(parents, (el) => schema.isWrapper(name(el)));
22538        const listWrappers = getFullySelectedListWrappers(parents, rng);
22539        const allWrappers = wrapElements.concat(listWrappers.length ? listWrappers : directListWrappers(commonAnchorContainer));
22540        return map$3(allWrappers, shallow);
22541    };
22542    const emptyFragment = () => fromElements([]);
22543    const getFragmentFromRange = (rootNode, rng, schema) => wrap(SugarElement.fromDom(rng.cloneContents()), getWrapElements(rootNode, rng, schema));
22544    const getParentTable = (rootElm, cell) => ancestor$4(cell, 'table', curry(eq, rootElm));
22545    const getTableFragment = (rootNode, selectedTableCells) => getParentTable(rootNode, selectedTableCells[0]).bind((tableElm) => {
22546        const firstCell = selectedTableCells[0];
22547        const lastCell = selectedTableCells[selectedTableCells.length - 1];
22548        const fullTableModel = fromDom(tableElm);
22549        return subsection(fullTableModel, firstCell, lastCell).map((sectionedTableModel) => fromElements([toDom(sectionedTableModel)]));
22550    }).getOrThunk(emptyFragment);
22551    const getSelectionFragment = (rootNode, ranges, schema) => ranges.length > 0 && ranges[0].collapsed ? emptyFragment() : getFragmentFromRange(rootNode, ranges[0], schema);
22552    const read$3 = (rootNode, ranges, schema) => {
22553        const selectedCells = getCellsFromElementOrRanges(ranges, rootNode);
22554        return selectedCells.length > 0 ? getTableFragment(rootNode, selectedCells) : getSelectionFragment(rootNode, ranges, schema);
22555    };
22556
22557    const isCollapsibleWhitespace = (text, index) => index >= 0 && index < text.length && isWhiteSpace(text.charAt(index));
22558    const getInnerText = (bin) => {
22559        return trim$2(bin.innerText);
22560    };
22561    const getContextNodeName = (parentBlockOpt) => parentBlockOpt.map((block) => block.nodeName).getOr('div').toLowerCase();
22562    const getTextContent = (editor) => Optional.from(editor.selection.getRng()).map((rng) => {
22563        const parentBlockOpt = Optional.from(editor.dom.getParent(rng.commonAncestorContainer, editor.dom.isBlock));
22564        const body = editor.getBody();
22565        const contextNodeName = getContextNodeName(parentBlockOpt);
22566        const rangeContentClone = SugarElement.fromDom(rng.cloneContents());
22567        cleanupBogusElements(rangeContentClone);
22568        cleanupInputNames(rangeContentClone);
22569        const bin = editor.dom.add(body, contextNodeName, {
22570            'data-mce-bogus': 'all',
22571            'style': 'overflow: hidden; opacity: 0;'
22572        }, rangeContentClone.dom);
22573        const text = getInnerText(bin);
22574        // textContent will not strip leading/trailing spaces since it doesn't consider how it'll render
22575        const nonRenderedText = trim$2(bin.textContent ?? '');
22576        editor.dom.remove(bin);
22577        if (isCollapsibleWhitespace(nonRenderedText, 0) || isCollapsibleWhitespace(nonRenderedText, nonRenderedText.length - 1)) {
22578            // If the bin contains a trailing/leading space, then we need to inspect the parent block to see if we should include the spaces
22579            const parentBlock = parentBlockOpt.getOr(body);
22580            const parentBlockText = getInnerText(parentBlock);
22581            const textIndex = parentBlockText.indexOf(text);
22582            if (textIndex === -1) {
22583                return text;
22584            }
22585            else {
22586                const hasProceedingSpace = isCollapsibleWhitespace(parentBlockText, textIndex - 1);
22587                const hasTrailingSpace = isCollapsibleWhitespace(parentBlockText, textIndex + text.length);
22588                return (hasProceedingSpace ? ' ' : '') + text + (hasTrailingSpace ? ' ' : '');
22589            }
22590        }
22591        else {
22592            return text;
22593        }
22594    }).getOr('');
22595    const getSerializedContent = (editor, args) => {
22596        const rng = editor.selection.getRng(), tmpElm = editor.dom.create('body');
22597        const sel = editor.selection.getSel();
22598        const ranges = processRanges(editor, getRanges(sel));
22599        const fragment = args.contextual ? read$3(SugarElement.fromDom(editor.getBody()), ranges, editor.schema).dom : rng.cloneContents();
22600        if (fragment) {
22601            tmpElm.appendChild(fragment);
22602        }
22603        return editor.selection.serializer.serialize(tmpElm, args);
22604    };
22605    const extractSelectedContent = (editor, args) => {
22606        if (args.format === 'text') {
22607            return getTextContent(editor);
22608        }
22609        else {
22610            const content = getSerializedContent(editor, args);
22611            if (args.format === 'tree') {
22612                return content;
22613            }
22614            else {
22615                return editor.selection.isCollapsed() ? '' : content;
22616            }
22617        }
22618    };
22619    const setupArgs$2 = (args, format) => ({
22620        ...args,
22621        format,
22622        get: true,
22623        selection: true,
22624        getInner: true
22625    });
22626    const getSelectedContentInternal = (editor, format, args = {}) => {
22627        const defaultedArgs = setupArgs$2(args, format);
22628        return preProcessGetContent(editor, defaultedArgs).fold(identity, (updatedArgs) => {
22629            const content = extractSelectedContent(editor, updatedArgs);
22630            return postProcessGetContent(editor, content, updatedArgs);
22631        });
22632    };
22633
22634    /**
22635     * JS Implementation of the O(ND) Difference Algorithm by Eugene W. Myers.
22636     *
22637     * @class tinymce.undo.Diff
22638     * @private
22639     */
22640    const KEEP = 0, INSERT = 1, DELETE = 2;
22641    const diff = (left, right) => {
22642        const size = left.length + right.length + 2;
22643        const vDown = new Array(size);
22644        const vUp = new Array(size);
22645        const snake = (start, end, diag) => {
22646            return {
22647                start,
22648                end,
22649                diag
22650            };
22651        };
22652        const buildScript = (start1, end1, start2, end2, script) => {
22653            const middle = getMiddleSnake(start1, end1, start2, end2);
22654            if (middle === null || middle.start === end1 && middle.diag === end1 - end2 ||
22655                middle.end === start1 && middle.diag === start1 - start2) {
22656                let i = start1;
22657                let j = start2;
22658                while (i < end1 || j < end2) {
22659                    if (i < end1 && j < end2 && left[i] === right[j]) {
22660                        script.push([KEEP, left[i]]);
22661                        ++i;
22662                        ++j;
22663                    }
22664                    else {
22665                        if (end1 - start1 > end2 - start2) {
22666                            script.push([DELETE, left[i]]);
22667                            ++i;
22668                        }
22669                        else {
22670                            script.push([INSERT, right[j]]);
22671                            ++j;
22672                        }
22673                    }
22674                }
22675            }
22676            else {
22677                buildScript(start1, middle.start, start2, middle.start - middle.diag, script);
22678                for (let i2 = middle.start; i2 < middle.end; ++i2) {
22679                    script.push([KEEP, left[i2]]);
22680                }
22681                buildScript(middle.end, end1, middle.end - middle.diag, end2, script);
22682            }
22683        };
22684        const buildSnake = (start, diag, end1, end2) => {
22685            let end = start;
22686            while (end - diag < end2 && end < end1 && left[end] === right[end - diag]) {
22687                ++end;
22688            }
22689            return snake(start, end, diag);
22690        };
22691        const getMiddleSnake = (start1, end1, start2, end2) => {
22692            // Myers Algorithm
22693            // Initialisations
22694            const m = end1 - start1;
22695            const n = end2 - start2;
22696            if (m === 0 || n === 0) {
22697                return null;
22698            }
22699            const delta = m - n;
22700            const sum = n + m;
22701            const offset = (sum % 2 === 0 ? sum : sum + 1) / 2;
22702            vDown[1 + offset] = start1;
22703            vUp[1 + offset] = end1 + 1;
22704            let d, k, i, x, y;
22705            for (d = 0; d <= offset; ++d) {
22706                // Down
22707                for (k = -d; k <= d; k += 2) {
22708                    // First step
22709                    i = k + offset;
22710                    if (k === -d || k !== d && vDown[i - 1] < vDown[i + 1]) {
22711                        vDown[i] = vDown[i + 1];
22712                    }
22713                    else {
22714                        vDown[i] = vDown[i - 1] + 1;
22715                    }
22716                    x = vDown[i];
22717                    y = x - start1 + start2 - k;
22718                    while (x < end1 && y < end2 && left[x] === right[y]) {
22719                        vDown[i] = ++x;
22720                        ++y;
22721                    }
22722                    // Second step
22723                    if (delta % 2 !== 0 && delta - d <= k && k <= delta + d) {
22724                        if (vUp[i - delta] <= vDown[i]) {
22725                            return buildSnake(vUp[i - delta], k + start1 - start2, end1, end2);
22726                        }
22727                    }
22728                }
22729                // Up
22730                for (k = delta - d; k <= delta + d; k += 2) {
22731                    // First step
22732                    i = k + offset - delta;
22733                    if (k === delta - d || k !== delta + d && vUp[i + 1] <= vUp[i - 1]) {
22734                        vUp[i] = vUp[i + 1] - 1;
22735                    }
22736                    else {
22737                        vUp[i] = vUp[i - 1];
22738                    }
22739                    x = vUp[i] - 1;
22740                    y = x - start1 + start2 - k;
22741                    while (x >= start1 && y >= start2 && left[x] === right[y]) {
22742                        vUp[i] = x--;
22743                        y--;
22744                    }
22745                    // Second step
22746                    if (delta % 2 === 0 && -d <= k && k <= d) {
22747                        if (vUp[i] <= vDown[i + delta]) {
22748                            return buildSnake(vUp[i], k + start1 - start2, end1, end2);
22749                        }
22750                    }
22751                }
22752            }
22753            return null;
22754        };
22755        const script = [];
22756        buildScript(0, left.length, 0, right.length, script);
22757        return script;
22758    };
22759
22760    /**
22761     * This module reads and applies html fragments from/to dom nodes.
22762     *
22763     * @class tinymce.undo.Fragments
22764     * @private
22765     */
22766    const getOuterHtml = (elm) => {
22767        if (isElement$7(elm)) {
22768            return elm.outerHTML;
22769        }
22770        else if (isText$b(elm)) {
22771            return Entities.encodeRaw(elm.data, false);
22772        }
22773        else if (isComment(elm)) {
22774            return '<!--' + elm.data + '-->';
22775        }
22776        return '';
22777    };
22778    const createFragment = (html) => {
22779        let node;
22780        const container = document.createElement('div');
22781        const frag = document.createDocumentFragment();
22782        if (html) {
22783            container.innerHTML = html;
22784        }
22785        while ((node = container.firstChild)) {
22786            frag.appendChild(node);
22787        }
22788        return frag;
22789    };
22790    const insertAt = (elm, html, index) => {
22791        const fragment = createFragment(html);
22792        if (elm.hasChildNodes() && index < elm.childNodes.length) {
22793            const target = elm.childNodes[index];
22794            elm.insertBefore(fragment, target);
22795        }
22796        else {
22797            elm.appendChild(fragment);
22798        }
22799    };
22800    const removeAt = (elm, index) => {
22801        if (elm.hasChildNodes() && index < elm.childNodes.length) {
22802            const target = elm.childNodes[index];
22803            elm.removeChild(target);
22804        }
22805    };
22806    const applyDiff = (diff, elm) => {
22807        let index = 0;
22808        each$e(diff, (action) => {
22809            if (action[0] === KEEP) {
22810                index++;
22811            }
22812            else if (action[0] === INSERT) {
22813                insertAt(elm, action[1], index);
22814                index++;
22815            }
22816            else if (action[0] === DELETE) {
22817                removeAt(elm, index);
22818            }
22819        });
22820    };
22821    const read$2 = (elm, trimZwsp) => filter$5(map$3(from(elm.childNodes), trimZwsp ? compose(trim$2, getOuterHtml) : getOuterHtml), (item) => {
22822        return item.length > 0;
22823    });
22824    const write = (fragments, elm) => {
22825        const currentFragments = map$3(from(elm.childNodes), getOuterHtml);
22826        applyDiff(diff(currentFragments, fragments), elm);
22827        return elm;
22828    };
22829
22830    // We need to create a temporary document instead of using the global document since
22831    // innerHTML on a detached element will still make http requests to the images
22832    const lazyTempDocument = cached(() => document.implementation.createHTMLDocument('undo'));
22833    const shouldBeFragmented = (body) => body.querySelector(`iframe, ${ucVideoNodeName}`) !== null;
22834    const createFragmentedLevel = (fragments) => {
22835        return {
22836            type: 'fragmented',
22837            fragments,
22838            content: '',
22839            bookmark: null,
22840            beforeBookmark: null
22841        };
22842    };
22843    const createCompleteLevel = (content) => {
22844        return {
22845            type: 'complete',
22846            fragments: null,
22847            content,
22848            bookmark: null,
22849            beforeBookmark: null
22850        };
22851    };
22852    const createFromEditor = (editor) => {
22853        const tempAttrs = editor.serializer.getTempAttrs();
22854        const body = trim$1(editor.getBody(), tempAttrs);
22855        return shouldBeFragmented(body) ? createFragmentedLevel(read$2(body, true)) : createCompleteLevel(trim$2(body.innerHTML));
22856    };
22857    const applyToEditor = (editor, level, before) => {
22858        const bookmark = before ? level.beforeBookmark : level.bookmark;
22859        if (level.type === 'fragmented') {
22860            write(level.fragments, editor.getBody());
22861        }
22862        else {
22863            editor.setContent(level.content, {
22864                format: 'raw',
22865                // If we have a path bookmark, we need to check if the bookmark location was a fake caret.
22866                // If the bookmark was not a fake caret, then we need to ensure that setContent does not move the selection
22867                // as this can create a new fake caret - particularly if the first element in the body is contenteditable=false.
22868                // The creation of this new fake caret will cause our path offset to be off by one when restoring the original selection.
22869                no_selection: isNonNullable(bookmark) && isPathBookmark(bookmark) ? !bookmark.isFakeCaret : true
22870            });
22871        }
22872        if (bookmark) {
22873            editor.selection.moveToBookmark(bookmark);
22874            editor.selection.scrollIntoView();
22875        }
22876    };
22877    const getLevelContent = (level) => {
22878        return level.type === 'fragmented' ? level.fragments.join('') : level.content;
22879    };
22880    const getCleanLevelContent = (isReadonly, level) => {
22881        const elm = SugarElement.fromTag('body', lazyTempDocument());
22882        set$
228823(elm, getLevelContent(level));
22883        each$e(descendants(elm, '*[data-mce-bogus]'), unwrap);
22884        if (isReadonly) {
22885            each$e(descendants(elm, 'details[open]'), (element) => remove$9(element, 'open'));
22886        }
22887        return get$8
vendor: 14,311 bytes, lines 22887-23228
22887(elm);
22888    };
22889    const hasEqualContent = (level1, level2) => getLevelContent(level1) === getLevelContent(level2);
22890    const hasEqualCleanedContent = (isReadonly, level1, level2) => getCleanLevelContent(isReadonly, level1) === getCleanLevelContent(isReadonly, level2);
22891    // Most of the time the contents is equal so it's faster to first check that using strings then fallback to a cleaned dom comparison
22892    const isEq$1 = (isReadonly, level1, level2) => {
22893        if (!level1 || !level2) {
22894            return false;
22895        }
22896        else if (hasEqualContent(level1, level2)) {
22897            return true;
22898        }
22899        else {
22900            return hasEqualCleanedContent(isReadonly, level1, level2);
22901        }
22902    };
22903
22904    const isUnlocked = (locks) => locks.get() === 0;
22905
22906    const setTyping = (undoManager, typing, locks) => {
22907        if (isUnlocked(locks)) {
22908            undoManager.typing = typing;
22909        }
22910    };
22911    const endTyping = (undoManager, locks) => {
22912        if (undoManager.typing) {
22913            setTyping(undoManager, false, locks);
22914            undoManager.add();
22915        }
22916    };
22917    const endTypingLevelIgnoreLocks = (undoManager) => {
22918        if (undoManager.typing) {
22919            undoManager.typing = false;
22920            undoManager.add();
22921        }
22922    };
22923
22924    const beforeChange$1 = (editor, locks, beforeBookmark) => {
22925        if (isUnlocked(locks)) {
22926            beforeBookmark.set(getUndoBookmark(editor.selection));
22927        }
22928    };
22929    const addUndoLevel$1 = (editor, undoManager, index, locks, beforeBookmark, level, event) => {
22930        const currentLevel = createFromEditor(editor);
22931        const newLevel = Tools.extend(level || {}, currentLevel);
22932        if (!isUnlocked(locks) || editor.removed) {
22933            return null;
22934        }
22935        const lastLevel = undoManager.data[index.get()];
22936        if (editor.dispatch('BeforeAddUndo', { level: newLevel, lastLevel, originalEvent: event }).isDefaultPrevented()) {
22937            return null;
22938        }
22939        // Add undo level if needed
22940        if (lastLevel && isEq$1(editor.readonly, lastLevel, newLevel)) {
22941            return null;
22942        }
22943        // Set before bookmark on previous level
22944        if (undoManager.data[index.get()]) {
22945            beforeBookmark.get().each((bm) => {
22946                undoManager.data[index.get()].beforeBookmark = bm;
22947            });
22948        }
22949        // Time to compress
22950        const customUndoRedoLevels = getCustomUndoRedoLevels(editor);
22951        if (customUndoRedoLevels) {
22952            if (undoManager.data.length > customUndoRedoLevels) {
22953                for (let i = 0; i < undoManager.data.length - 1; i++) {
22954                    undoManager.data[i] = undoManager.data[i + 1];
22955                }
22956                undoManager.data.length--;
22957                index.set(undoManager.data.length);
22958            }
22959        }
22960        // Get a non intrusive normalized bookmark
22961        newLevel.bookmark = getUndoBookmark(editor.selection);
22962        // Crop array if needed
22963        if (index.get() < undoManager.data.length - 1) {
22964            undoManager.data.length = index.get() + 1;
22965        }
22966        undoManager.data.push(newLevel);
22967        index.set(undoManager.data.length - 1);
22968        const args = { level: newLevel, lastLevel, originalEvent: event };
22969        if (index.get() > 0) {
22970            editor.setDirty(true);
22971            editor.dispatch('AddUndo', args);
22972            editor.dispatch('change', args);
22973        }
22974        else {
22975            editor.dispatch('AddUndo', args);
22976        }
22977        return newLevel;
22978    };
22979    const clear$1 = (editor, undoManager, index) => {
22980        undoManager.data = [];
22981        index.set(0);
22982        undoManager.typing = false;
22983        editor.dispatch('ClearUndos');
22984    };
22985    const extra$1 = (editor, undoManager, index, callback1, callback2) => {
22986        if (undoManager.transact(callback1)) {
22987            const bookmark = undoManager.data[index.get()].bookmark;
22988            const lastLevel = undoManager.data[index.get() - 1];
22989            applyToEditor(editor, lastLevel, true);
22990            if (undoManager.transact(callback2)) {
22991                undoManager.data[index.get() - 1].beforeBookmark = bookmark;
22992            }
22993        }
22994    };
22995    const redo$1 = (editor, index, data) => {
22996        let level;
22997        if (index.get() < data.length - 1) {
22998            index.set(index.get() + 1);
22999            level = data[index.get()];
23000            applyToEditor(editor, level, false);
23001            editor.setDirty(true);
23002            editor.dispatch('Redo', { level });
23003        }
23004        return level;
23005    };
23006    const undo$1 = (editor, undoManager, locks, index) => {
23007        let level;
23008        if (undoManager.typing) {
23009            undoManager.add();
23010            undoManager.typing = false;
23011            setTyping(undoManager, false, locks);
23012        }
23013        if (index.get() > 0) {
23014            index.set(index.get() - 1);
23015            level = undoManager.data[index.get()];
23016            applyToEditor(editor, level, true);
23017            editor.setDirty(true);
23018            editor.dispatch('Undo', { level });
23019        }
23020        return level;
23021    };
23022    const reset$1 = (undoManager) => {
23023        undoManager.clear();
23024        undoManager.add();
23025    };
23026    const hasUndo$1 = (editor, undoManager, index) => 
23027    // Has undo levels or typing and content isn't the same as the initial level
23028    index.get() > 0 || (undoManager.typing && undoManager.data[0] && !isEq$1(editor.readonly, createFromEditor(editor), undoManager.data[0]));
23029    const hasRedo$1 = (undoManager, index) => index.get() < undoManager.data.length - 1 && !undoManager.typing;
23030    const transact$1 = (undoManager, locks, callback) => {
23031        endTyping(undoManager, locks);
23032        undoManager.beforeChange();
23033        undoManager.ignore(callback);
23034        return undoManager.add();
23035    };
23036    const ignore$1 = (locks, callback) => {
23037        try {
23038            locks.set(locks.get() + 1);
23039            callback();
23040        }
23041        finally {
23042            locks.set(locks.get() - 1);
23043        }
23044    };
23045
23046    const addVisualInternal = (editor, elm) => {
23047        const dom = editor.dom;
23048        const scope = isNonNullable(elm) ? elm : editor.getBody();
23049        each$e(dom.select('table,a', scope), (matchedElm) => {
23050            switch (matchedElm.nodeName) {
23051                case 'TABLE':
23052                    const cls = getVisualAidsTableClass(editor);
23053                    const value = dom.getAttrib(matchedElm, 'border');
23054                    if ((!value || value === '0') && editor.hasVisual) {
23055                        dom.addClass(matchedElm, cls);
23056                    }
23057                    else {
23058                        dom.removeClass(matchedElm, cls);
23059                    }
23060                    break;
23061                case 'A':
23062                    if (!dom.getAttrib(matchedElm, 'href')) {
23063                        const value = dom.getAttrib(matchedElm, 'name') || matchedElm.id;
23064                        const cls = getVisualAidsAnchorClass(editor);
23065                        if (value && editor.hasVisual) {
23066                            dom.addClass(matchedElm, cls);
23067                        }
23068                        else {
23069                            dom.removeClass(matchedElm, cls);
23070                        }
23071                    }
23072                    break;
23073            }
23074        });
23075        editor.dispatch('VisualAid', { element: elm, hasVisual: editor.hasVisual });
23076    };
23077
23078    const makePlainAdaptor = (editor) => ({
23079        init: {
23080            bindEvents: noop
23081        },
23082        undoManager: {
23083            beforeChange: (locks, beforeBookmark) => beforeChange$1(editor, locks, beforeBookmark),
23084            add: (undoManager, index, locks, beforeBookmark, level, event) => addUndoLevel$1(editor, undoManager, index, locks, beforeBookmark, level, event),
23085            undo: (undoManager, locks, index) => undo$1(editor, undoManager, locks, index),
23086            redo: (index, data) => redo$1(editor, index, data),
23087            clear: (undoManager, index) => clear$1(editor, undoManager, index),
23088            reset: (undoManager) => reset$1(undoManager),
23089            hasUndo: (undoManager, index) => hasUndo$1(editor, undoManager, index),
23090            hasRedo: (undoManager, index) => hasRedo$1(undoManager, index),
23091            transact: (undoManager, locks, callback) => transact$1(undoManager, locks, callback),
23092            ignore: (locks, callback) => ignore$1(locks, callback),
23093            extra: (undoManager, index, callback1, callback2) => extra$1(editor, undoManager, index, callback1, callback2)
23094        },
23095        formatter: {
23096            match: (name, vars, node, similar) => match$2(editor, name, vars, node, similar),
23097            matchAll: (names, vars) => matchAll(editor, names, vars),
23098            matchNode: (node, name, vars, similar) => matchNode$1(editor, node, name, vars, similar),
23099            canApply: (name) => canApply(editor, name),
23100            closest: (names) => closest(editor, names),
23101            apply: (name, vars, node) => applyFormat$1(editor, name, vars, node),
23102            remove: (name, vars, node, similar) => removeFormat$1(editor, name, vars, node, similar),
23103            toggle: (name, vars, node) => toggle(editor, name, vars, node),
23104            formatChanged: (registeredFormatListeners, formats, callback, similar, vars) => formatChangedInternal(editor, registeredFormatListeners, formats, callback, similar, vars)
23105        },
23106        editor: {
23107            getContent: (args) => getContentInternal(editor, args),
23108            setContent: (content, args) => setContentInternal(editor, content, args),
23109            insertContent: (value, details) => insertHtmlAtCaret(editor, value, details),
23110            addVisual: (elm) => addVisualInternal(editor, elm)
23111        },
23112        selection: {
23113            getContent: (format, args) => getSelectedContentInternal(editor, format, args)
23114        },
23115        autocompleter: {
23116            addDecoration: noop, // This was never fully implemented in RTC
23117            removeDecoration: noop, // This was never fully implemented in RTC
23118        },
23119        raw: {
23120            getModel: () => Optional.none()
23121        }
23122    });
23123    const makeRtcAdaptor = (rtcEditor) => {
23124        const defaultVars = (vars) => isObject(vars) ? vars : {};
23125        const { init, undoManager, formatter, editor, selection, autocompleter, raw } = rtcEditor;
23126        return {
23127            init: {
23128                bindEvents: init.bindEvents
23129            },
23130            undoManager: {
23131                beforeChange: undoManager.beforeChange,
23132                add: undoManager.add,
23133                undo: undoManager.undo,
23134                redo: undoManager.redo,
23135                clear: undoManager.clear,
23136                reset: undoManager.reset,
23137                hasUndo: undoManager.hasUndo,
23138                hasRedo: undoManager.hasRedo,
23139                transact: (_undoManager, _locks, fn) => undoManager.transact(fn),
23140                ignore: (_locks, callback) => undoManager.ignore(callback),
23141                extra: (_undoManager, _index, callback1, callback2) => undoManager.extra(callback1, callback2)
23142            },
23143            formatter: {
23144                match: (name, vars, _node, similar) => formatter.match(name, defaultVars(vars), similar),
23145                matchAll: formatter.matchAll,
23146                matchNode: formatter.matchNode,
23147                canApply: (name) => formatter.canApply(name),
23148                closest: (names) => formatter.closest(names),
23149                apply: (name, vars, _node) => formatter.apply(name, defaultVars(vars)),
23150                remove: (name, vars, _node, _similar) => formatter.remove(name, defaultVars(vars)),
23151                toggle: (name, vars, _node) => formatter.toggle(name, defaultVars(vars)),
23152                formatChanged: (_rfl, formats, callback, similar, vars) => formatter.formatChanged(formats, callback, similar, vars)
23153            },
23154            editor: {
23155                getContent: (args) => editor.getContent(args),
23156                setContent: (content, args) => {
23157                    return { content: editor.setContent(content, args), html: '' };
23158                },
23159                insertContent: (content, _details) => {
23160                    editor.insertContent(content);
23161                    return '';
23162                },
23163                addVisual: editor.addVisual
23164            },
23165            selection: {
23166                getContent: (_format, args) => selection.getContent(args)
23167            },
23168            autocompleter: {
23169                addDecoration: autocompleter.addDecoration,
23170                removeDecoration: autocompleter.removeDecoration
23171            },
23172            raw: {
23173                getModel: () => Optional.some(raw.getRawModel())
23174            }
23175        };
23176    };
23177    const makeNoopAdaptor = () => {
23178        // Cast as any since this will never match the implementations
23179        const nul = constant(null);
23180        const empty = constant('');
23181        return {
23182            init: {
23183                bindEvents: noop
23184            },
23185            undoManager: {
23186                beforeChange: noop,
23187                add: nul,
23188                undo: nul,
23189                redo: nul,
23190                clear: noop,
23191                reset: noop,
23192                hasUndo: never,
23193                hasRedo: never,
23194                transact: nul,
23195                ignore: noop,
23196                extra: noop
23197            },
23198            formatter: {
23199                match: never,
23200                matchAll: constant([]),
23201                matchNode: constant(undefined),
23202                canApply: never,
23203                closest: empty,
23204                apply: noop,
23205                remove: noop,
23206                toggle: noop,
23207                formatChanged: constant({ unbind: noop })
23208            },
23209            editor: {
23210                getContent: empty,
23211                setContent: constant({ content: '', html: '' }),
23212                insertContent: constant(''),
23213                addVisual: noop
23214            },
23215            selection: {
23216                getContent: empty
23217            },
23218            autocompleter: {
23219                addDecoration: noop,
23220                removeDecoration: noop
23221            },
23222            raw: {
23223                getModel: constant(Optional.none())
23224            }
23225        };
23226    };
23227    const isRtc = (editor) => has$2(editor.plugins, 'rtc');
23228    const getRtcSetup = (editor) => get$
23228a
vendor: 38,074 bytes, lines 23228-24016
23228(editor.plugins, 'rtc').bind((rtcPlugin) => 
23229    // This might not exist if the stub plugin is loaded on cloud
23230    Optional.from(rtcPlugin.setup));
23231    const setup$z = (editor) => {
23232        const editorCast = editor;
23233        return getRtcSetup(editor).fold(() => {
23234            editorCast.rtcInstance = makePlainAdaptor(editor);
23235            return Optional.none();
23236        }, (setup) => {
23237            // We need to provide a noop adaptor while initializing since any call by the theme or plugins to say undoManager.hasUndo would throw errors
23238            editorCast.rtcInstance = makeNoopAdaptor();
23239            return Optional.some(() => setup().then((rtcEditor) => {
23240                editorCast.rtcInstance = makeRtcAdaptor(rtcEditor);
23241                return rtcEditor.rtc.isRemote;
23242            }));
23243        });
23244    };
23245    const getRtcInstanceWithFallback = (editor) => 
23246    // Calls to editor.getContent/editor.setContent should still work even if the rtcInstance is not yet available
23247    editor.rtcInstance ? editor.rtcInstance : makePlainAdaptor(editor);
23248    const getRtcInstanceWithError = (editor) => {
23249        const rtcInstance = editor.rtcInstance;
23250        if (!rtcInstance) {
23251            throw new Error('Failed to get RTC instance not yet initialized.');
23252        }
23253        else {
23254            return rtcInstance;
23255        }
23256    };
23257    /** In theory these could all be inlined but having them here makes it clear what is overridden */
23258    const beforeChange = (editor, locks, beforeBookmark) => {
23259        getRtcInstanceWithError(editor).undoManager.beforeChange(locks, beforeBookmark);
23260    };
23261    const addUndoLevel = (editor, undoManager, index, locks, beforeBookmark, level, event) => getRtcInstanceWithError(editor).undoManager.add(undoManager, index, locks, beforeBookmark, level, event);
23262    const undo = (editor, undoManager, locks, index) => getRtcInstanceWithError(editor).undoManager.undo(undoManager, locks, index);
23263    const redo = (editor, index, data) => getRtcInstanceWithError(editor).undoManager.redo(index, data);
23264    const clear = (editor, undoManager, index) => {
23265        getRtcInstanceWithError(editor).undoManager.clear(undoManager, index);
23266    };
23267    const reset = (editor, undoManager) => {
23268        getRtcInstanceWithError(editor).undoManager.reset(undoManager);
23269    };
23270    const hasUndo = (editor, undoManager, index) => getRtcInstanceWithError(editor).undoManager.hasUndo(undoManager, index);
23271    const hasRedo = (editor, undoManager, index) => getRtcInstanceWithError(editor).undoManager.hasRedo(undoManager, index);
23272    const transact = (editor, undoManager, locks, callback) => getRtcInstanceWithError(editor).undoManager.transact(undoManager, locks, callback);
23273    const ignore = (editor, locks, callback) => {
23274        getRtcInstanceWithError(editor).undoManager.ignore(locks, callback);
23275    };
23276    const extra = (editor, undoManager, index, callback1, callback2) => {
23277        getRtcInstanceWithError(editor).undoManager.extra(undoManager, index, callback1, callback2);
23278    };
23279    const matchFormat = (editor, name, vars, node, similar) => getRtcInstanceWithError(editor).formatter.match(name, vars, node, similar);
23280    const matchAllFormats = (editor, names, vars) => getRtcInstanceWithError(editor).formatter.matchAll(names, vars);
23281    const matchNodeFormat = (editor, node, name, vars, similar) => getRtcInstanceWithError(editor).formatter.matchNode(node, name, vars, similar);
23282    const canApplyFormat = (editor, name) => getRtcInstanceWithError(editor).formatter.canApply(name);
23283    const closestFormat = (editor, names) => getRtcInstanceWithError(editor).formatter.closest(names);
23284    const applyFormat = (editor, name, vars, node) => {
23285        getRtcInstanceWithError(editor).formatter.apply(name, vars, node);
23286    };
23287    const removeFormat = (editor, name, vars, node, similar) => {
23288        getRtcInstanceWithError(editor).formatter.remove(name, vars, node, similar);
23289    };
23290    const toggleFormat = (editor, name, vars, node) => {
23291        getRtcInstanceWithError(editor).formatter.toggle(name, vars, node);
23292    };
23293    const formatChanged = (editor, registeredFormatListeners, formats, callback, similar, vars) => getRtcInstanceWithError(editor).formatter.formatChanged(registeredFormatListeners, formats, callback, similar, vars);
23294    const getContent$2 = (editor, args) => getRtcInstanceWithFallback(editor).editor.getContent(args);
23295    const setContent$1 = (editor, content, args) => getRtcInstanceWithFallback(editor).editor.setContent(content, args);
23296    const insertContent$1 = (editor, value, details) => getRtcInstanceWithFallback(editor).editor.insertContent(value, details);
23297    const getSelectedContent = (editor, format, args) => getRtcInstanceWithError(editor).selection.getContent(format, args);
23298    const addVisual$1 = (editor, elm) => getRtcInstanceWithError(editor).editor.addVisual(elm);
23299    const bindEvents = (editor) => getRtcInstanceWithError(editor).init.bindEvents();
23300
23301    const getContent$1 = (editor, args = {}) => {
23302        const format = args.format ? args.format : 'html';
23303        return getSelectedContent(editor, format, args);
23304    };
23305
23306    const deleteFromCallbackMap = (callbackMap, selector, callback) => {
23307        if (has$2(callbackMap, selector)) {
23308            const newCallbacks = filter$5(callbackMap[selector], (cb) => cb !== callback);
23309            if (newCallbacks.length === 0) {
23310                delete callbackMap[selector];
23311            }
23312            else {
23313                callbackMap[selector] = newCallbacks;
23314            }
23315        }
23316    };
23317    var SelectorChanged = (dom, editor) => {
23318        let selectorChangedData;
23319        let currentSelectors;
23320        const findMatchingNode = (selector, nodes) => find$2(nodes, (node) => dom.is(node, selector));
23321        const getParents = (elem) => dom.getParents(elem, undefined, dom.getRoot());
23322        const setup = () => {
23323            selectorChangedData = {};
23324            currentSelectors = {};
23325            editor.on('NodeChange', (e) => {
23326                const node = e.element;
23327                const parents = getParents(node);
23328                const matchedSelectors = {};
23329                // Check for new matching selectors
23330                each$d(selectorChangedData, (callbacks, selector) => {
23331                    findMatchingNode(selector, parents).each((node) => {
23332                        if (!currentSelectors[selector]) {
23333                            // Execute callbacks
23334                            each$e(callbacks, (callback) => {
23335                                callback(true, { node, selector, parents });
23336                            });
23337                            currentSelectors[selector] = callbacks;
23338                        }
23339                        matchedSelectors[selector] = callbacks;
23340                    });
23341                });
23342                // Check if current selectors still match
23343                each$d(currentSelectors, (callbacks, selector) => {
23344                    if (!matchedSelectors[selector]) {
23345                        delete currentSelectors[selector];
23346                        each$e(callbacks, (callback) => {
23347                            callback(false, { node, selector, parents });
23348                        });
23349                    }
23350                });
23351            });
23352        };
23353        return {
23354            selectorChangedWithUnbind: (selector, callback) => {
23355                if (!selectorChangedData) {
23356                    setup();
23357                }
23358                // Add selector listeners
23359                if (!selectorChangedData[selector]) {
23360                    selectorChangedData[selector] = [];
23361                }
23362                selectorChangedData[selector].push(callback);
23363                // Setup the initial state if selected already
23364                findMatchingNode(selector, getParents(editor.selection.getStart())).each(() => {
23365                    currentSelectors[selector] = selectorChangedData[selector];
23366                });
23367                return {
23368                    unbind: () => {
23369                        deleteFromCallbackMap(selectorChangedData, selector, callback);
23370                        deleteFromCallbackMap(currentSelectors, selector, callback);
23371                    }
23372                };
23373            }
23374        };
23375    };
23376
23377    /**
23378     * This class handles text and control selection it's an crossbrowser utility class.
23379     * Consult the TinyMCE API Documentation for more details and examples on how to use this class.
23380     *
23381     * @class tinymce.dom.Selection
23382     * @example
23383     * // Getting the currently selected node for the active editor
23384     * alert(tinymce.activeEditor.selection.getNode().nodeName);
23385     */
23386    const isAttachedToDom = (node) => {
23387        return !!(node && node.ownerDocument) && contains(SugarElement.fromDom(node.ownerDocument), SugarElement.fromDom(node));
23388    };
23389    const isValidRange = (rng) => {
23390        if (!rng) {
23391            return false;
23392        }
23393        else {
23394            return isAttachedToDom(rng.startContainer) && isAttachedToDom(rng.endContainer);
23395        }
23396    };
23397    /**
23398     * Constructs a new selection instance.
23399     *
23400     * @constructor
23401     * @method Selection
23402     * @param {tinymce.dom.DOMUtils} dom DOMUtils object reference.
23403     * @param {Window} win Window to bind the selection object to.
23404     * @param {tinymce.dom.Serializer} serializer DOM serialization class to use for getContent.
23405     * @param {tinymce.Editor} editor Editor instance of the selection.
23406     */
23407    const EditorSelection = (dom, win, serializer, editor) => {
23408        let selectedRange;
23409        let explicitRange;
23410        const { selectorChangedWithUnbind } = SelectorChanged(dom, editor);
23411        /**
23412         * Move the selection cursor range to the specified node and offset.
23413         * If there is no node specified it will move it to the first suitable location within the body.
23414         *
23415         * @method setCursorLocation
23416         * @param {Node} node Optional node to put the cursor in.
23417         * @param {Number} offset Optional offset from the start of the node to put the cursor at.
23418         */
23419        const setCursorLocation = (node, offset) => {
23420            const rng = dom.createRng();
23421            if (isNonNullable(node) && isNonNullable(offset)) {
23422                rng.setStart(node, offset);
23423                rng.setEnd(node, offset);
23424                setRng(rng);
23425                collapse(false);
23426            }
23427            else {
23428                moveEndPoint(dom, rng, editor.getBody(), true);
23429                setRng(rng);
23430            }
23431        };
23432        /**
23433         * Returns the selected contents using the DOM serializer passed in to this class.
23434         *
23435         * @method getContent
23436         * @param {Object} args Optional settings class with for example output format text or html.
23437         * @return {String} Selected contents in for example HTML format.
23438         * @example
23439         * // Alerts the currently selected contents
23440         * alert(tinymce.activeEditor.selection.getContent());
23441         *
23442         * // Alerts the currently selected contents as plain text
23443         * alert(tinymce.activeEditor.selection.getContent({ format: 'text' }));
23444         */
23445        const getContent = (args) => getContent$1(editor, args);
23446        /**
23447         * This method has been deprecated. Use "editor.insertContent" instead.
23448         *
23449         * Sets the current selection to the specified content. If any contents is selected it will be replaced
23450         * with the contents passed in to this function. If there is no selection the contents will be inserted
23451         * where the caret is placed in the editor/page.
23452         *
23453         * @method setContent
23454         * @param {String} content HTML contents to set could also be other formats depending on settings.
23455         * @param {Object} args Optional settings object with for example data format.
23456         * @example
23457         * // Inserts some HTML contents at the current selection
23458         * tinymce.activeEditor.selection.setContent('<strong>Some contents</strong>');
23459         */
23460        const setContent = (content, args) => setContentExternal(editor, content, args);
23461        /**
23462         * Returns the start element of a selection range. If the start is in a text
23463         * node the parent element will be returned.
23464         *
23465         * @method getStart
23466         * @param {Boolean} real Optional state to get the real parent when the selection is collapsed not the closest element.
23467         * @return {Element} Start element of selection range.
23468         */
23469        const getStart$1 = (real) => getStart(editor.getBody(), getRng$1(), real);
23470        /**
23471         * Returns the end element of a selection range. If the end is in a text
23472         * node the parent element will be returned.
23473         *
23474         * @method getEnd
23475         * @param {Boolean} real Optional state to get the real parent when the selection is collapsed not the closest element.
23476         * @return {Element} End element of selection range.
23477         */
23478        const getEnd$1 = (real) => getEnd(editor.getBody(), getRng$1(), real);
23479        /**
23480         * Returns a bookmark location for the current selection. This bookmark object
23481         * can then be used to restore the selection after some content modification to the document.
23482         *
23483         * @method getBookmark
23484         * @param {Number} type Optional state if the bookmark should be simple or not. Default is complex.
23485         * @param {Boolean} normalized Optional state that enables you to get a position that it would be after normalization.
23486         * @return {Object} Bookmark object, use moveToBookmark with this object to restore the selection.
23487         * @example
23488         * // Stores a bookmark of the current selection
23489         * const bm = tinymce.activeEditor.selection.getBookmark();
23490         *
23491         * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content');
23492         *
23493         * // Restore the selection bookmark
23494         * tinymce.activeEditor.selection.moveToBookmark(bm);
23495         */
23496        const getBookmark = (type, normalized) => bookmarkManager.getBookmark(type, normalized);
23497        /**
23498         * Restores the selection to the specified bookmark.
23499         *
23500         * @method moveToBookmark
23501         * @param {Object} bookmark Bookmark to restore selection from.
23502         * @example
23503         * // Stores a bookmark of the current selection
23504         * const bm = tinymce.activeEditor.selection.getBookmark();
23505         *
23506         * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content');
23507         *
23508         * // Restore the selection bookmark
23509         * tinymce.activeEditor.selection.moveToBookmark(bm);
23510         */
23511        const moveToBookmark = (bookmark) => bookmarkManager.moveToBookmark(bookmark);
23512        /**
23513         * Selects the specified element. This will place the start and end of the selection range around the element.
23514         *
23515         * @method select
23516         * @param {Element} node HTML DOM element to select.
23517         * @param {Boolean} content Optional bool state if the contents should be selected or not on non IE browser.
23518         * @return {Element} Selected element the same element as the one that got passed in.
23519         * @example
23520         * // Select the first paragraph in the active editor
23521         * tinymce.activeEditor.selection.select(tinymce.activeEditor.dom.select('p')[0]);
23522         */
23523        const select$1 = (node, content) => {
23524            select(dom, node, content).each(setRng);
23525            return node;
23526        };
23527        /**
23528         * Returns true/false if the selection range is collapsed or not. Collapsed means if it's a caret or a larger selection.
23529         *
23530         * @method isCollapsed
23531         * @return {Boolean} true/false state if the selection range is collapsed or not.
23532         * Collapsed means if it's a caret or a larger selection.
23533         */
23534        const isCollapsed = () => {
23535            const rng = getRng$1(), sel = getSel();
23536            if (!rng || rng.item) {
23537                return false;
23538            }
23539            if (rng.compareEndPoints) {
23540                return rng.compareEndPoints('StartToEnd', rng) === 0;
23541            }
23542            return !sel || rng.collapsed;
23543        };
23544        /**
23545         * Checks if the current selection’s start and end containers are editable within their parent’s contexts.
23546         *
23547         * @method isEditable
23548         * @return {Boolean} Will be true if the selection is editable and false if it's not editable.
23549         */
23550        const isEditable = () => {
23551            if (editor.mode.isReadOnly()) {
23552                return false;
23553            }
23554            const rng = getRng$1();
23555            const fakeSelectedElements = editor.getBody().querySelectorAll('[data-mce-selected="1"]');
23556            if (fakeSelectedElements.length > 0) {
23557                return forall(fakeSelectedElements, (el) => dom.isEditable(el.parentElement));
23558            }
23559            else {
23560                return isEditableRange(dom, rng);
23561            }
23562        };
23563        /**
23564         * Collapse the selection to start or end of range.
23565         *
23566         * @method collapse
23567         * @param {Boolean} toStart Optional boolean state if to collapse to end or not. Defaults to false.
23568         */
23569        const collapse = (toStart) => {
23570            const rng = getRng$1();
23571            rng.collapse(!!toStart);
23572            setRng(rng);
23573        };
23574        /**
23575         * Returns the browsers internal selection object.
23576         *
23577         * @method getSel
23578         * @return {Selection} Internal browser selection object.
23579         */
23580        const getSel = () => win.getSelection ? win.getSelection() : win.document.selection;
23581        /**
23582         * Returns the browsers internal range object.
23583         *
23584         * @method getRng
23585         * @return {Range} Internal browser range object.
23586         * @see http://www.quirksmode.org/dom/range_intro.html
23587         * @see http://www.dotvoid.com/2001/03/using-the-range-object-in-mozilla/
23588         */
23589        const getRng$1 = () => {
23590            let rng;
23591            const tryCompareBoundaryPoints = (how, sourceRange, destinationRange) => {
23592                try {
23593                    return sourceRange.compareBoundaryPoints(how, destinationRange);
23594                }
23595                catch {
23596                    // Gecko throws wrong document exception if the range points
23597                    // to nodes that where removed from the dom #6690
23598                    // Browsers should mutate existing DOMRange instances so that they always point
23599                    // to something in the document this is not the case in Gecko works fine in IE/WebKit/Blink
23600                    // For performance reasons just return -1
23601                    return -1;
23602                }
23603            };
23604            const doc = win.document;
23605            if (isNonNullable(editor.bookmark) && !hasFocus(editor)) {
23606                const bookmark = getRng(editor);
23607                if (bookmark.isSome()) {
23608                    return bookmark.map((r) => processRanges(editor, [r])[0]).getOr(doc.createRange());
23609                }
23610            }
23611            try {
23612                const selection = getSel();
23613                if (selection && !isRestrictedNode(selection.anchorNode)) {
23614                    if (selection.rangeCount > 0) {
23615                        rng = selection.getRangeAt(0);
23616                    }
23617                    else {
23618                        rng = doc.createRange();
23619                    }
23620                    rng = processRanges(editor, [rng])[0];
23621                }
23622            }
23623            catch {
23624                // IE throws unspecified error here if TinyMCE is placed in a frame/iframe
23625            }
23626            // No range found then create an empty one
23627            // This can occur when the editor is placed in a hidden container element on Gecko
23628            if (!rng) {
23629                rng = doc.createRange();
23630            }
23631            // If range is at start of document then move it to start of body
23632            if (isDocument$1(rng.startContainer) && rng.collapsed) {
23633                const elm = dom.getRoot();
23634                rng.setStart(elm, 0);
23635                rng.setEnd(elm, 0);
23636            }
23637            if (selectedRange && explicitRange) {
23638                if (tryCompareBoundaryPoints(rng.START_TO_START, rng, selectedRange) === 0 &&
23639                    tryCompareBoundaryPoints(rng.END_TO_END, rng, selectedRange) === 0) {
23640                    // Safari, Opera and Chrome only ever select text which causes the range to change.
23641                    // This lets us use the originally set range if the selection hasn't been changed by the user.
23642                    rng = explicitRange;
23643                }
23644                else {
23645                    selectedRange = null;
23646                    explicitRange = null;
23647                }
23648            }
23649            return rng;
23650        };
23651        /**
23652         * Changes the selection to the specified DOM range.
23653         *
23654         * @method setRng
23655         * @param {Range} rng Range to select.
23656         * @param {Boolean} forward Optional boolean if the selection is forwards or backwards.
23657         */
23658        const setRng = (rng, forward) => {
23659            if (!isValidRange(rng)) {
23660                return;
23661            }
23662            const sel = getSel();
23663            const evt = editor.dispatch('SetSelectionRange', { range: rng, forward });
23664            rng = evt.range;
23665            if (sel) {
23666                explicitRange = rng;
23667                try {
23668                    sel.removeAllRanges();
23669                    sel.addRange(rng);
23670                }
23671                catch {
23672                    // IE might throw errors here if the editor is within a hidden container and selection is changed
23673                }
23674                // Forward is set to false and we have an extend function
23675                if (forward === false && sel.extend) {
23676                    sel.collapse(rng.endContainer, rng.endOffset);
23677                    sel.extend(rng.startContainer, rng.startOffset);
23678                }
23679                // adding range isn't always successful so we need to check range count otherwise an exception can occur
23680                selectedRange = sel.rangeCount > 0 ? sel.getRangeAt(0) : null;
23681            }
23682            // WebKit edge case selecting images works better using setBaseAndExtent when the image is floated
23683            if (!rng.collapsed && rng.startContainer === rng.endContainer && sel?.setBaseAndExtent) {
23684                if (rng.endOffset - rng.startOffset < 2) {
23685                    if (rng.startContainer.hasChildNodes()) {
23686                        const node = rng.startContainer.childNodes[rng.startOffset];
23687                        if (node && node.nodeName === 'IMG') {
23688                            sel.setBaseAndExtent(rng.startContainer, rng.startOffset, rng.endContainer, rng.endOffset);
23689                            // Since the setBaseAndExtent is fixed in more recent Blink versions we
23690                            // need to detect if it's doing the wrong thing and falling back to the
23691                            // crazy incorrect behavior api call since that seems to be the only way
23692                            // to get it to work on Safari WebKit as of 2017-02-23
23693                            if (sel.anchorNode !== rng.startContainer || sel.focusNode !== rng.endContainer) {
23694                                sel.setBaseAndExtent(node, 0, node, 1);
23695                            }
23696                        }
23697                    }
23698                }
23699            }
23700            editor.dispatch('AfterSetSelectionRange', { range: rng, forward });
23701        };
23702        /**
23703         * Sets the current selection to the specified DOM element.
23704         *
23705         * @method setNode
23706         * @param {Element} elm Element to set as the contents of the selection.
23707         * @return {Element} Returns the element that got passed in.
23708         * @example
23709         * // Inserts a DOM node at current selection/caret location
23710         * tinymce.activeEditor.selection.setNode(tinymce.activeEditor.dom.create('img', { src: 'some.gif', title: 'some title' }));
23711         */
23712        const setNode = (elm) => {
23713            setContentInternal$1(editor, dom.getOuterHTML(elm));
23714            return elm;
23715        };
23716        /**
23717         * Returns the currently selected element or the common ancestor element for both start and end of the selection.
23718         *
23719         * @method getNode
23720         * @return {Element} Currently selected element or common ancestor element.
23721         * @example
23722         * // Alerts the currently selected elements node name
23723         * alert(tinymce.activeEditor.selection.getNode().nodeName);
23724         */
23725        const getNode$1 = () => getNode(editor.getBody(), getRng$1());
23726        const getSelectedBlocks$1 = (startElm, endElm) => getSelectedBlocks(dom, getRng$1(), startElm, endElm);
23727        const isForward = () => {
23728            const sel = getSel();
23729            const anchorNode = sel?.anchorNode;
23730            const focusNode = sel?.focusNode;
23731            // No support for selection direction then always return true
23732            if (!sel || !anchorNode || !focusNode || isRestrictedNode(anchorNode) || isRestrictedNode(focusNode)) {
23733                return true;
23734            }
23735            const anchorRange = dom.createRng();
23736            const focusRange = dom.createRng();
23737            try {
23738                anchorRange.setStart(anchorNode, sel.anchorOffset);
23739                anchorRange.collapse(true);
23740                focusRange.setStart(focusNode, sel.focusOffset);
23741                focusRange.collapse(true);
23742            }
23743            catch {
23744                // Safari can generate an invalid selection and error. Silently handle it and default to forward.
23745                // See https://bugs.webkit.org/show_bug.cgi?id=230594.
23746                return true;
23747            }
23748            return anchorRange.compareBoundaryPoints(anchorRange.START_TO_START, focusRange) <= 0;
23749        };
23750        const normalize = () => {
23751            const rng = getRng$1();
23752            const sel = getSel();
23753            if (!hasMultipleRanges(sel) && hasAnyRanges(editor)) {
23754                const normRng = normalize$2(dom, rng);
23755                normRng.each((normRng) => {
23756                    setRng(normRng, isForward());
23757                });
23758                return normRng.getOr(rng);
23759            }
23760            return rng;
23761        };
23762        /**
23763         * Executes callback when the current selection starts/stops matching the specified selector. The current
23764         * state will be passed to the callback as it's first argument.
23765         *
23766         * @method selectorChanged
23767         * @param {String} selector CSS selector to check for.
23768         * @param {Function} callback Callback with state and args when the selector is matches or not.
23769         */
23770        // eslint-disable-next-line @typescript-eslint/no-wrapper-object-types
23771        const selectorChanged = (selector, callback) => {
23772            selectorChangedWithUnbind(selector, callback);
23773            return exports;
23774        };
23775        const getScrollContainer = () => {
23776            let scrollContainer;
23777            let node = dom.getRoot();
23778            while (node && node.nodeName !== 'BODY') {
23779                if (node.scrollHeight > node.clientHeight) {
23780                    scrollContainer = node;
23781                    break;
23782                }
23783                node = node.parentNode;
23784            }
23785            return scrollContainer;
23786        };
23787        const scrollIntoView = (elm, alignToTop) => {
23788            if (isNonNullable(elm)) {
23789                scrollElementIntoView(editor, elm, alignToTop);
23790            }
23791            else {
23792                scrollRangeIntoView(editor, getRng$1(), alignToTop);
23793            }
23794        };
23795        const placeCaretAt = (clientX, clientY) => setRng(fromPoint(clientX, clientY, editor.getDoc()));
23796        const getBoundingClientRect = () => {
23797            const rng = getRng$1();
23798            return rng.collapsed ? CaretPosition.fromRangeStart(rng).getClientRects()[0] : rng.getBoundingClientRect();
23799        };
23800        const destroy = () => {
23801            win = selectedRange = explicitRange = null;
23802            controlSelection.destroy();
23803        };
23804        /**
23805         * Expands the selection range to contain the entire word when the selection is collapsed within the word.
23806         *
23807         * @method expand
23808         * @param {Object} options Optional options provided to the expansion. Defaults to { type: 'word' }
23809         */
23810        const expand = (options = { type: 'word' }) => setRng(RangeUtils(dom).expand(getRng$1(), options));
23811        const exports = {
23812            dom,
23813            win,
23814            serializer,
23815            editor,
23816            expand,
23817            collapse,
23818            setCursorLocation,
23819            getContent,
23820            setContent,
23821            getBookmark,
23822            moveToBookmark,
23823            select: select$1,
23824            isCollapsed,
23825            isEditable,
23826            isForward,
23827            setNode,
23828            getNode: getNode$1,
23829            getSel,
23830            setRng,
23831            getRng: getRng$1,
23832            getStart: getStart$1,
23833            getEnd: getEnd$1,
23834            getSelectedBlocks: getSelectedBlocks$1,
23835            normalize,
23836            selectorChanged,
23837            selectorChangedWithUnbind,
23838            getScrollContainer,
23839            scrollIntoView,
23840            placeCaretAt,
23841            getBoundingClientRect,
23842            destroy
23843        };
23844        const bookmarkManager = BookmarkManager(exports);
23845        const controlSelection = ControlSelection(exports, editor);
23846        exports.bookmarkManager = bookmarkManager;
23847        exports.controlSelection = controlSelection;
23848        return exports;
23849    };
23850
23851    const addNodeFilter = (settings, htmlParser, schema) => {
23852        htmlParser.addNodeFilter('br', (nodes, _, args) => {
23853            const blockElements = Tools.extend({}, schema.getBlockElements());
23854            const nonEmptyElements = schema.getNonEmptyElements();
23855            const whitespaceElements = schema.getWhitespaceElements();
23856            // Remove brs from body element as well
23857            blockElements.body = 1;
23858            const isBlock = (node) => node.name in blockElements || isTransparentAstBlock(schema, node);
23859            // Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p>
23860            for (let i = 0, l = nodes.length; i < l; i++) {
23861                let node = nodes[i];
23862                let parent = node.parent;
23863                if (parent && isBlock(parent) && node === parent.lastChild) {
23864                    // Loop all nodes to the left of the current node and check for other BR elements
23865                    // excluding bookmarks since they are invisible
23866                    let prev = node.prev;
23867                    while (prev) {
23868                        const prevName = prev.name;
23869                        // Ignore bookmarks
23870                        if (prevName !== 'span' || prev.attr('data-mce-type') !== 'bookmark') {
23871                            // Found another br it's a <br><br> structure then don't remove anything
23872                            if (prevName === 'br') {
23873                                node = null;
23874                            }
23875                            break;
23876                        }
23877                        prev = prev.prev;
23878                    }
23879                    if (node) {
23880                        node.remove();
23881                        // Is the parent to be considered empty after we removed the BR
23882                        if (isEmpty$2(schema, nonEmptyElements, whitespaceElements, parent)) {
23883                            const elementRule = schema.getElementRule(parent.name);
23884                            // Remove or padd the element depending on schema rule
23885                            if (elementRule) {
23886                                if (elementRule.removeEmpty) {
23887                                    parent.remove();
23888                                }
23889                                else if (elementRule.paddEmpty) {
23890                                    paddEmptyNode(settings, args, isBlock, parent);
23891                                }
23892                            }
23893                        }
23894                    }
23895                }
23896                else {
23897                    // Replaces BR elements inside inline elements like <p><b><i><br></i></b></p>
23898                    // so they become <p><b><i>&nbsp;</i></b></p>
23899                    let lastParent = node;
23900                    while (parent && parent.firstChild === lastParent && parent.lastChild === lastParent) {
23901                        lastParent = parent;
23902                        if (blockElements[parent.name]) {
23903                            break;
23904                        }
23905                        parent = parent.parent;
23906                    }
23907                    if (lastParent === parent) {
23908                        const textNode = new AstNode('#text', 3);
23909                        textNode.value = nbsp;
23910                        node.replace(textNode);
23911                    }
23912                }
23913            }
23914        });
23915    };
23916
23917    const register$3 = (htmlParser, settings, dom) => {
23918        // Convert tabindex back to elements when serializing contents
23919        htmlParser.addAttributeFilter('data-mce-tabindex', (nodes, name) => {
23920            let i = nodes.length;
23921            while (i--) {
23922                const node = nodes[i];
23923                node.attr('tabindex', node.attr('data-mce-tabindex'));
23924                node.attr(name, null);
23925            }
23926        });
23927        // Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed
23928        htmlParser.addAttributeFilter('src,href,style', (nodes, name) => {
23929            const internalName = 'data-mce-' + name;
23930            const urlConverter = settings.url_converter;
23931            const urlConverterScope = settings.url_converter_scope;
23932            let i = nodes.length;
23933            while (i--) {
23934                const node = nodes[i];
23935                let value = node.attr(internalName);
23936                if (value !== undefined) {
23937                    // Set external name to internal value and remove internal
23938                    node.attr(name, value.length > 0 ? value : null);
23939                    node.attr(internalName, null);
23940                }
23941                else {
23942                    // No internal attribute found then convert the value we have in the DOM
23943                    value = node.attr(name);
23944                    if (name === 'style') {
23945                        value = dom.serializeStyle(dom.parseStyle(value), node.name);
23946                    }
23947                    else if (urlConverter) {
23948                        value = urlConverter.call(urlConverterScope, value, name, node.name);
23949                    }
23950                    node.attr(name, value.length > 0 ? value : null);
23951                }
23952            }
23953        });
23954        // Remove internal classes mceItem<..> or mceSelected
23955        htmlParser.addAttributeFilter('class', (nodes) => {
23956            let i = nodes.length;
23957            while (i--) {
23958                const node = nodes[i];
23959                let value = node.attr('class');
23960                if (value) {
23961                    value = value.replace(/(?:^|\s)mce-item-\w+(?!\S)/g, '');
23962                    node.attr('class', value.length > 0 ? value : null);
23963                }
23964            }
23965        });
23966        // Remove bookmark elements
23967        htmlParser.addAttributeFilter('data-mce-type', (nodes, name, args) => {
23968            let i = nodes.length;
23969            while (i--) {
23970                const node = nodes[i];
23971                if (node.attr('data-mce-type') === 'bookmark' && !args.cleanup) {
23972                    // We maybe dealing with a "filled" bookmark. If so just remove the node, otherwise unwrap it
23973                    const hasChildren = Optional.from(node.firstChild).exists((firstChild) => !isZwsp(firstChild.value ?? ''));
23974                    if (hasChildren) {
23975                        node.unwrap();
23976                    }
23977                    else {
23978                        node.remove();
23979                    }
23980                }
23981            }
23982        });
23983        // Force script into CDATA sections and remove the mce- prefix also add comments around styles
23984        htmlParser.addNodeFilter('script,style', (nodes, name) => {
23985            const trim = (value) => {
23986                /* jshint maxlen:255 */
23987                /* eslint max-len:0 */
23988                return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n')
23989                    .replace(/^[\r\n]*|[\r\n]*$/g, '')
23990                    .replace(/^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi, '')
23991                    .replace(/\s*(\/\*\s*\]\]>\s*\*\/(-->)?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g, '');
23992            };
23993            let i = nodes.length;
23994            while (i--) {
23995                const node = nodes[i];
23996                const firstChild = node.firstChild;
23997                const value = firstChild?.value ?? '';
23998                if (name === 'script') {
23999                    // Remove mce- prefix from script elements and remove default type since the user specified
24000                    // a script element without type attribute
24001                    const type = node.attr('type');
24002                    if (type) {
24003                        node.attr('type', type === 'mce-no/type' ? null : type.replace(/^mce\-/, ''));
24004                    }
24005                    if (settings.element_format === 'xhtml' && firstChild && value.length > 0) {
24006                        firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>';
24007                    }
24008                }
24009                else {
24010                    if (settings.element_format === 'xhtml' && firstChild && value.length > 0) {
24011                        firstChild.value = '<!--\n' + trim(value) + '\n-->';
24012                    }
24013                }
24014            }
24015        });
24016        // Convert comments to cdata
24016 and handle protected comments
24017        htmlParser.addNodeFilter('#comment', (nodes) => {
24018            let i = nodes.length;
24019            while (i--) {
24020                const node = nodes[i];
24021                const value = node.value;
24022                if (settings.preserve_cdata && value?.indexOf('[CDATA[') === 0) {
24023                    node.name = '#cdata';
24024                    node.type = 4;
24025                    node.value = dom.decode(value.replace(/^\[CDATA\[|\]\]$/g, ''));
24026                }
24027                else if (value?.indexOf('mce:protected ') === 0) {
24028                    node.name = '#text';
24029                    node.type = 3;
24030                    node.raw = true;
24031                    node.value = unescape(value).substr(14
vendor: 18,653 bytes, lines 24031-24479
24031);
24032                }
24033            }
24034        });
24035        htmlParser.addNodeFilter('xml:namespace,input', (nodes, name) => {
24036            let i = nodes.length;
24037            while (i--) {
24038                const node = nodes[i];
24039                if (node.type === 7) {
24040                    node.remove();
24041                }
24042                else if (node.type === 1) {
24043                    if (name === 'input' && !node.attr('type')) {
24044                        node.attr('type', 'text');
24045                    }
24046                }
24047            }
24048        });
24049        htmlParser.addAttributeFilter('data-mce-type', (nodes) => {
24050            each$e(nodes, (node) => {
24051                if (node.attr('data-mce-type') === 'format-caret') {
24052                    if (node.isEmpty(htmlParser.schema.getNonEmptyElements())) {
24053                        node.remove();
24054                    }
24055                    else {
24056                        node.unwrap();
24057                    }
24058                }
24059            });
24060        });
24061        // Remove internal data attributes
24062        htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style,' +
24063            'data-mce-selected,data-mce-expando,data-mce-block,' +
24064            'data-mce-type,data-mce-resize,data-mce-placeholder', (nodes, name) => {
24065            let i = nodes.length;
24066            while (i--) {
24067                nodes[i].attr(name, null);
24068            }
24069        });
24070        // Remove <br> at end of block elements Gecko and WebKit injects BR elements to
24071        // make it possible to place the caret inside empty blocks. This logic tries to remove
24072        // these elements and keep br elements that where intended to be there intact
24073        if (settings.remove_trailing_brs) {
24074            addNodeFilter(settings, htmlParser, htmlParser.schema);
24075        }
24076    };
24077    /**
24078     * IE 11 has a fantastic bug where it will produce two trailing BR elements to iframe bodies when
24079     * the iframe is hidden by display: none on a parent container. The DOM is actually out of sync
24080     * with innerHTML in this case. It's like IE adds shadow DOM BR elements that appears on innerHTML
24081     * but not as the lastChild of the body. So this fix simply removes the last two
24082     * BR elements at the end of the document.
24083     *
24084     * Example of what happens: <body>text</body> becomes <body>text<br><br></body>
24085     */
24086    const trimTrailingBr = (rootNode) => {
24087        const isBr = (node) => {
24088            return node?.name === 'br';
24089        };
24090        const brNode1 = rootNode.lastChild;
24091        if (isBr(brNode1)) {
24092            const brNode2 = brNode1.prev;
24093            if (isBr(brNode2)) {
24094                brNode1.remove();
24095                brNode2.remove();
24096            }
24097        }
24098    };
24099
24100    const preProcess$1 = (editor, node, args) => {
24101        let oldDoc;
24102        const dom = editor.dom;
24103        let clonedNode = node.cloneNode(true);
24104        // Nodes needs to be attached to something in WebKit/Opera
24105        // This fix will make DOM ranges and make Sizzle happy!
24106        const impl = document.implementation;
24107        if (impl.createHTMLDocument) {
24108            // Create an empty HTML document
24109            const doc = impl.createHTMLDocument('');
24110            // Add the element or it's children if it's a body element to the new document
24111            Tools.each(clonedNode.nodeName === 'BODY' ? clonedNode.childNodes : [clonedNode], (node) => {
24112                doc.body.appendChild(doc.importNode(node, true));
24113            });
24114            // Grab first child or body element for serialization
24115            if (clonedNode.nodeName !== 'BODY') {
24116                // We cast to a Element here, as this will be the cloned node imported and appended above
24117                clonedNode = doc.body.firstChild;
24118            }
24119            else {
24120                clonedNode = doc.body;
24121            }
24122            // set the new document in DOMUtils so createElement etc works
24123            oldDoc = dom.doc;
24124            dom.doc = doc;
24125        }
24126        firePreProcess(editor, { ...args, node: clonedNode });
24127        if (oldDoc) {
24128            dom.doc = oldDoc;
24129        }
24130        return clonedNode;
24131    };
24132    const shouldFireEvent = (editor, args) => {
24133        return isNonNullable(editor) && editor.hasEventListeners('PreProcess') && !args.no_events;
24134    };
24135    const process$1 = (editor, node, args) => {
24136        return shouldFireEvent(editor, args) ? preProcess$1(editor, node, args) : node;
24137    };
24138
24139    const addTempAttr = (htmlParser, tempAttrs, name) => {
24140        if (Tools.inArray(tempAttrs, name) === -1) {
24141            htmlParser.addAttributeFilter(name, (nodes, name) => {
24142                let i = nodes.length;
24143                while (i--) {
24144                    nodes[i].attr(name, null);
24145                }
24146            });
24147            tempAttrs.push(name);
24148        }
24149    };
24150    const postProcess = (editor, args, content) => {
24151        if (!args.no_events && editor) {
24152            const outArgs = firePostProcess(editor, { ...args, content });
24153            return outArgs.content;
24154        }
24155        else {
24156            return content;
24157        }
24158    };
24159    const getHtmlFromNode = (dom, node, args) => {
24160        // TODO: Investigate if using `innerHTML` is correct as DomSerializerPreProcess definitely returns a Node
24161        const html = trim$2(args.getInner ? node.innerHTML : dom.getOuterHTML(node));
24162        return args.selection || isWsPreserveElement(SugarElement.fromDom(node)) ? html : Tools.trim(html);
24163    };
24164    const parseHtml = (htmlParser, html, args) => {
24165        const parserArgs = args.selection ? { forced_root_block: false, ...args } : args;
24166        const rootNode = htmlParser.parse(html, parserArgs);
24167        trimTrailingBr(rootNode);
24168        return rootNode;
24169    };
24170    const serializeNode = (settings, schema, node) => {
24171        const htmlSerializer = HtmlSerializer(settings, schema);
24172        return htmlSerializer.serialize(node);
24173    };
24174    const toHtml = (editor, settings, schema, rootNode, args) => {
24175        const content = serializeNode(settings, schema, rootNode);
24176        return postProcess(editor, args, content);
24177    };
24178    const DomSerializerImpl = (settings, editor) => {
24179        const tempAttrs = ['data-mce-selected'];
24180        const defaultedSettings = {
24181            entity_encoding: 'named',
24182            remove_trailing_brs: true,
24183            pad_empty_with_br: false,
24184            ...settings
24185        };
24186        const dom = editor && editor.dom ? editor.dom : DOMUtils.DOM;
24187        const schema = editor && editor.schema ? editor.schema : Schema(defaultedSettings);
24188        const htmlParser = DomParser(defaultedSettings, schema);
24189        register$3(htmlParser, defaultedSettings, dom);
24190        const serialize = (node, domSerializerArgs = {}) => {
24191            const { indent, entity_encoding, ...rest } = domSerializerArgs;
24192            const parserArgs = { format: 'html', ...rest };
24193            const targetNode = process$1(editor, node, parserArgs);
24194            const html = getHtmlFromNode(dom, targetNode, parserArgs);
24195            const rootNode = parseHtml(htmlParser, html, parserArgs);
24196            if (parserArgs.format === 'tree') {
24197                return rootNode;
24198            }
24199            const serializerSettings = {
24200                ...defaultedSettings,
24201                ...(isNonNullable(indent) ? { indent } : {}),
24202                ...(isNonNullable(entity_encoding) ? { entity_encoding } : {}),
24203            };
24204            return toHtml(editor, serializerSettings, schema, rootNode, parserArgs);
24205        };
24206        return {
24207            schema,
24208            addNodeFilter: htmlParser.addNodeFilter,
24209            addAttributeFilter: htmlParser.addAttributeFilter,
24210            serialize: serialize,
24211            addRules: schema.addValidElements,
24212            setRules: schema.setValidElements,
24213            addTempAttr: curry(addTempAttr, htmlParser, tempAttrs),
24214            getTempAttrs: constant(tempAttrs),
24215            getNodeFilters: htmlParser.getNodeFilters,
24216            getAttributeFilters: htmlParser.getAttributeFilters,
24217            removeNodeFilter: htmlParser.removeNodeFilter,
24218            removeAttributeFilter: htmlParser.removeAttributeFilter
24219        };
24220    };
24221
24222    /**
24223     * This class is used to serialize DOM trees into a string. Consult the TinyMCE API Documentation for
24224     * more details and examples on how to use this class.
24225     *
24226     * @class tinymce.dom.Serializer
24227     */
24228    const DomSerializer = (settings, editor) => {
24229        const domSerializer = DomSerializerImpl(settings, editor);
24230        // Return public methods
24231        return {
24232            /**
24233             * Schema instance that was used to when the Serializer was constructed.
24234             *
24235             * @field {tinymce.html.Schema} schema
24236             */
24237            schema: domSerializer.schema,
24238            /**
24239             * Adds a node filter function to the parser used by the serializer, the parser will collect the specified nodes by name
24240             * and then execute the callback once it has finished parsing the document.
24241             *
24242             * @method addNodeFilter
24243             * @param {String} name Comma separated list of nodes to collect.
24244             * @param {Function} callback Callback function to execute once it has collected nodes.
24245             * @example
24246             * serializer.addNodeFilter('p,h1', (nodes, name) => {
24247             *   for (let i = 0; i < nodes.length; i++) {
24248             *     console.log(nodes[i].name);
24249             *   }
24250             * });
24251             */
24252            addNodeFilter: domSerializer.addNodeFilter,
24253            /**
24254             * Adds an attribute filter function to the parser used by the serializer, the parser will
24255             * collect nodes that has the specified attributes
24256             * and then execute the callback once it has finished parsing the document.
24257             *
24258             * @method addAttributeFilter
24259             * @param {String} name Comma separated list of attributes to collect.
24260             * @param {Function} callback Callback function to execute once it has collected nodes.
24261             * @example
24262             * serializer.addAttributeFilter('src,href', (nodes, name) => {
24263             *   for (let i = 0; i < nodes.length; i++) {
24264             *     console.log(nodes[i].name);
24265             *   }
24266             * });
24267             */
24268            addAttributeFilter: domSerializer.addAttributeFilter,
24269            /**
24270             * Serializes the specified browser DOM node into a HTML string.
24271             *
24272             * @method serialize
24273             * @param {DOMNode} node DOM node to serialize.
24274             * @param {Object} args Arguments option that gets passed to event handlers.
24275             */
24276            serialize: domSerializer.serialize,
24277            /**
24278             * Adds valid elements rules to the serializers schema instance this enables you to specify things
24279             * like what elements should be outputted and what attributes specific elements might have.
24280             * Consult the TinyMCE Documentation for more details on this format.
24281             *
24282             * @method addRules
24283             * @param {String} rules Valid elements rules string to add to schema.
24284             */
24285            addRules: domSerializer.addRules,
24286            /**
24287             * Sets the valid elements rules to the serializers schema instance this enables you to specify things
24288             * like what elements should be outputted and what attributes specific elements might have.
24289             * Consult the TinyMCE Documentation for more details on this format.
24290             *
24291             * @method setRules
24292             * @param {String} rules Valid elements rules string.
24293             */
24294            setRules: domSerializer.setRules,
24295            /**
24296             * Adds a temporary internal attribute these attributes will get removed on undo and
24297             * when getting contents out of the editor.
24298             *
24299             * @method addTempAttr
24300             * @param {String} name string
24301             */
24302            addTempAttr: domSerializer.addTempAttr,
24303            /**
24304             * Returns an array of all added temp attrs names.
24305             *
24306             * @method getTempAttrs
24307             * @return {String[]} Array with attribute names.
24308             */
24309            getTempAttrs: domSerializer.getTempAttrs,
24310            getNodeFilters: domSerializer.getNodeFilters,
24311            getAttributeFilters: domSerializer.getAttributeFilters,
24312            /**
24313             * Removes a node filter function or removes all filter functions from the parser used by the serializer for the node names provided.
24314             *
24315             * @method removeNodeFilter
24316             * @param {String} name Comma separated list of node names to remove filters for.
24317             * @param {Function} callback Optional callback function to only remove a specific callback.
24318             * @example
24319             * // Remove a single filter
24320             * serializer.removeNodeFilter('p,h1', someCallback);
24321             *
24322             * // Remove all filters
24323             * serializer.removeNodeFilter('p,h1');
24324             */
24325            removeNodeFilter: domSerializer.removeNodeFilter,
24326            /**
24327             * Removes an attribute filter function or removes all filter functions from the parser used by the serializer for the attribute names provided.
24328             *
24329             * @method removeAttributeFilter
24330             * @param {String} name Comma separated list of attribute names to remove filters for.
24331             * @param {Function} callback Optional callback function to only remove a specific callback.
24332             * @example
24333             * // Remove a single filter
24334             * serializer.removeAttributeFilter('src,href', someCallback);
24335             *
24336             * // Remove all filters
24337             * serializer.removeAttributeFilter('src,href');
24338             */
24339            removeAttributeFilter: domSerializer.removeAttributeFilter
24340        };
24341    };
24342
24343    const defaultFormat$1 = 'html';
24344    const setupArgs$1 = (args, format) => ({
24345        ...args,
24346        format,
24347        get: true,
24348        getInner: true
24349    });
24350    const getContent = (editor, args = {}) => {
24351        const format = args.format ? args.format : defaultFormat$1;
24352        const defaultedArgs = setupArgs$1(args, format);
24353        return preProcessGetContent(editor, defaultedArgs).fold(identity, (updatedArgs) => {
24354            const content = getContent$2(editor, updatedArgs);
24355            return postProcessGetContent(editor, content, updatedArgs);
24356        });
24357    };
24358
24359    const defaultFormat = 'html';
24360    const setupArgs = (args, content) => ({
24361        format: defaultFormat,
24362        ...args,
24363        set: true,
24364        content
24365    });
24366    const setContent = (editor, content, args = {}) => {
24367        const defaultedArgs = setupArgs(args, content);
24368        preProcessSetContent(editor, defaultedArgs).each((updatedArgs) => {
24369            const result = setContent$1(editor, updatedArgs.content, updatedArgs);
24370            postProcessSetContent(editor, result.html, updatedArgs);
24371        });
24372    };
24373
24374    const DOM$a = DOMUtils.DOM;
24375    const restoreOriginalStyles = (editor) => {
24376        DOM$a.setStyle(editor.id, 'display', editor.orgDisplay);
24377    };
24378    const safeDestroy = (x) => Optional.from(x).each((x) => x.destroy());
24379    const clearDomReferences = (editor) => {
24380        const ed = editor;
24381        ed.contentAreaContainer = ed.formElement = ed.container = ed.editorContainer = null;
24382        ed.bodyElement = ed.contentDocument = ed.contentWindow = null;
24383        ed.iframeElement = ed.targetElm = null;
24384        const selection = editor.selection;
24385        if (selection) {
24386            const dom = selection.dom;
24387            ed.selection = selection.win = selection.dom = dom.doc = null;
24388        }
24389    };
24390    const restoreForm = (editor) => {
24391        const form = editor.formElement;
24392        if (form) {
24393            if (form._mceOldSubmit) {
24394                form.submit = form._mceOldSubmit;
24395                delete form._mceOldSubmit;
24396            }
24397            DOM$a.unbind(form, 'submit reset', editor.formEventDelegate);
24398        }
24399    };
24400    const remove$1 = (editor) => {
24401        if (!editor.removed) {
24402            const { _selectionOverrides, editorUpload } = editor;
24403            const body = editor.getBody();
24404            const element = editor.getElement();
24405            if (body) {
24406                editor.save({ is_removing: true });
24407            }
24408            editor.removed = true;
24409            editor.unbindAllNativeEvents();
24410            // Remove any hidden input
24411            if (editor.hasHiddenInput && isNonNullable(element?.nextSibling)) {
24412                DOM$a.remove(element.nextSibling);
24413            }
24414            fireRemove(editor);
24415            editor.editorManager.remove(editor);
24416            if (!editor.inline && body) {
24417                restoreOriginalStyles(editor);
24418            }
24419            fireDetach(editor);
24420            DOM$a.remove(editor.getContainer());
24421            safeDestroy(_selectionOverrides);
24422            safeDestroy(editorUpload);
24423            editor.destroy();
24424        }
24425    };
24426    const destroy = (editor, automatic) => {
24427        const { selection, dom } = editor;
24428        if (editor.destroyed) {
24429            return;
24430        }
24431        // If user manually calls destroy and not remove
24432        // Users seems to have logic that calls destroy instead of remove
24433        if (!automatic && !editor.removed) {
24434            editor.remove();
24435            return;
24436        }
24437        if (!automatic) {
24438            editor.editorManager.off('beforeunload', editor._beforeUnload);
24439            // Manual destroy
24440            if (editor.theme && editor.theme.destroy) {
24441                editor.theme.destroy();
24442            }
24443            safeDestroy(selection);
24444            safeDestroy(dom);
24445        }
24446        restoreForm(editor);
24447        clearDomReferences(editor);
24448        editor.destroyed = true;
24449    };
24450
24451    const CreateIconManager = () => {
24452        const lookup = {};
24453        const add = (id, iconPack) => {
24454            lookup[id] = iconPack;
24455        };
24456        const get = (id) => {
24457            if (lookup[id]) {
24458                return lookup[id];
24459            }
24460            else {
24461                return { icons: {} };
24462            }
24463        };
24464        const has = (id) => has$2(lookup, id);
24465        return {
24466            add,
24467            get,
24468            has
24469        };
24470    };
24471    const IconManager = CreateIconManager();
24472
24473    const ModelManager = AddOnManager.ModelManager;
24474
24475    const getProp = (propName, elm) => {
24476        const rawElm = elm.dom;
24477        return rawElm[propName];
24478    };
24479    const getComputedSizeProp = (propName, elm) => parseInt(get$
244797
vendor: 25,461 bytes, lines 24479-25098
24479(elm, propName), 10);
24480    const getClientWidth = curry(getProp, 'clientWidth');
24481    const getClientHeight = curry(getProp, 'clientHeight');
24482    const getMarginTop = curry(getComputedSizeProp, 'margin-top');
24483    const getMarginLeft = curry(getComputedSizeProp, 'margin-left');
24484    const getBoundingClientRect = (elm) => elm.dom.getBoundingClientRect();
24485    const isInsideElementContentArea = (bodyElm, clientX, clientY) => {
24486        const clientWidth = getClientWidth(bodyElm);
24487        const clientHeight = getClientHeight(bodyElm);
24488        return clientX >= 0 && clientY >= 0 && clientX <= clientWidth && clientY <= clientHeight;
24489    };
24490    const transpose = (inline, elm, clientX, clientY) => {
24491        const clientRect = getBoundingClientRect(elm);
24492        const deltaX = inline ? clientRect.left + elm.dom.clientLeft + getMarginLeft(elm) : 0;
24493        const deltaY = inline ? clientRect.top + elm.dom.clientTop + getMarginTop(elm) : 0;
24494        const x = clientX - deltaX;
24495        const y = clientY - deltaY;
24496        return { x, y };
24497    };
24498    // Checks if the specified coordinate is within the visual content area excluding the scrollbars
24499    const isXYInContentArea = (editor, clientX, clientY) => {
24500        const bodyElm = SugarElement.fromDom(editor.getBody());
24501        const targetElm = editor.inline ? bodyElm : documentElement(bodyElm);
24502        const transposedPoint = transpose(editor.inline, targetElm, clientX, clientY);
24503        return isInsideElementContentArea(targetElm, transposedPoint.x, transposedPoint.y);
24504    };
24505    const fromDomSafe = (node) => Optional.from(node).map(SugarElement.fromDom);
24506    const isEditorAttachedToDom = (editor) => {
24507        const rawContainer = editor.inline ? editor.getBody() : editor.getContentAreaContainer();
24508        return fromDomSafe(rawContainer).map(inBody).getOr(false);
24509    };
24510
24511    var NotificationManagerImpl = () => {
24512        const unimplemented = () => {
24513            throw new Error('Theme did not provide a NotificationManager implementation.');
24514        };
24515        return {
24516            open: unimplemented,
24517            close: unimplemented,
24518            getArgs: unimplemented
24519        };
24520    };
24521
24522    /**
24523     * This class handles the creation of TinyMCE's notifications.
24524     *
24525     * @class tinymce.NotificationManager
24526     * @example
24527     * // Opens a new notification of type "error" with text "An error occurred."
24528     * tinymce.activeEditor.notificationManager.open({
24529     *   text: 'An error occurred.',
24530     *   type: 'error'
24531     * });
24532     */
24533    const NotificationManager = (editor) => {
24534        const notifications = [];
24535        const getImplementation = () => {
24536            const theme = editor.theme;
24537            return theme && theme.getNotificationManagerImpl ? theme.getNotificationManagerImpl() : NotificationManagerImpl();
24538        };
24539        const getTopNotification = () => {
24540            return Optional.from(notifications[0]);
24541        };
24542        const isEqual = (a, b) => {
24543            return a.type === b.type && a.text === b.text && !a.progressBar && !a.timeout && !b.progressBar && !b.timeout;
24544        };
24545        const reposition = () => {
24546            getTopNotification().each((notification) => {
24547                notification.reposition();
24548            });
24549        };
24550        const addNotification = (notification) => {
24551            notifications.push(notification);
24552        };
24553        const closeNotification = (notification) => {
24554            findIndex$2(notifications, (otherNotification) => {
24555                return otherNotification === notification;
24556            }).each((index) => {
24557                // Mutate here since third party might have stored away the window array
24558                // TODO: Consider breaking this api
24559                notifications.splice(index, 1);
24560            });
24561        };
24562        const open = (spec, fireEvent = true) => {
24563            // Never open notification if editor has been removed.
24564            if (editor.removed || !isEditorAttachedToDom(editor)) {
24565                return {};
24566            }
24567            // fire event to allow notification spec to be mutated before display
24568            if (fireEvent) {
24569                editor.dispatch('BeforeOpenNotification', { notification: spec });
24570            }
24571            return find$2(notifications, (notification) => {
24572                return isEqual(getImplementation().getArgs(notification), spec);
24573            }).getOrThunk(() => {
24574                editor.editorManager.setActive(editor);
24575                const notification = getImplementation().open(spec, () => {
24576                    closeNotification(notification);
24577                }, () => hasEditorOrUiFocus(editor));
24578                addNotification(notification);
24579                reposition();
24580                // Ensure notification is not passed by reference to prevent mutation
24581                editor.dispatch('OpenNotification', { notification: { ...notification } });
24582                return notification;
24583            });
24584        };
24585        const close = () => {
24586            getTopNotification().each((notification) => {
24587                getImplementation().close(notification);
24588                closeNotification(notification);
24589                reposition();
24590            });
24591        };
24592        const getNotifications = constant(notifications);
24593        const registerEvents = (editor) => {
24594            editor.on('SkinLoaded', () => {
24595                const serviceMessage = getServiceMessage(editor);
24596                if (serviceMessage) {
24597                    // Ensure we pass false for fireEvent so that service message cannot be altered.
24598                    open({
24599                        text: serviceMessage,
24600                        type: 'warning',
24601                        timeout: 0
24602                    }, false);
24603                }
24604                // Ensure the notifications are repositioned once the skin has loaded, as otherwise
24605                // any notifications rendered before then may have wrapped and been in the wrong place
24606                reposition();
24607            });
24608            // NodeChange is needed for inline mode and autoresize as the positioning is done
24609            // from the bottom up, which changes when the content in the editor changes.
24610            editor.on('show ResizeEditor ResizeWindow NodeChange ToggleView FullscreenStateChanged', () => {
24611                requestAnimationFrame(reposition);
24612            });
24613            editor.on('remove', () => {
24614                each$e(notifications.slice(), (notification) => {
24615                    getImplementation().close(notification);
24616                });
24617            });
24618            editor.on('keydown', (e) => {
24619                // TODO: TINY-11429 Remove this once we remove the use of keycodes
24620                const isF12 = e.key?.toLowerCase() === 'f12' || e.keyCode === 123;
24621                if (e.altKey && isF12) {
24622                    e.preventDefault();
24623                    getTopNotification()
24624                        .map((notificationApi) => SugarElement.fromDom(notificationApi.getEl()))
24625                        .each((elm) => focus$1(elm));
24626                }
24627            });
24628        };
24629        registerEvents(editor);
24630        return {
24631            /**
24632             * Opens a new notification.
24633             *
24634             * @method open
24635             * @param {Object} args A <code>name: value</code> collection containing settings such as: <code>timeout</code>, <code>type</code>, and message (<code>text</code>).
24636             * <br /><br />
24637             * For information on the available settings, see: <a href="https://www.tiny.cloud/docs/tinymce/8/creating-custom-notifications/">Create custom notifications</a>.
24638             */
24639            open,
24640            /**
24641             * Closes the top most notification.
24642             *
24643             * @method close
24644             */
24645            close,
24646            /**
24647             * Returns the currently opened notification objects.
24648             *
24649             * @method getNotifications
24650             * @return {Array} Array of the currently opened notifications.
24651             */
24652            getNotifications
24653        };
24654    };
24655
24656    const PluginManager = AddOnManager.PluginManager;
24657
24658    const ThemeManager = AddOnManager.ThemeManager;
24659
24660    var WindowManagerImpl = () => {
24661        const unimplemented = () => {
24662            throw new Error('Theme did not provide a WindowManager implementation.');
24663        };
24664        return {
24665            open: unimplemented,
24666            openUrl: unimplemented,
24667            alert: unimplemented,
24668            confirm: unimplemented,
24669            close: unimplemented
24670        };
24671    };
24672
24673    const WindowManager = (editor) => {
24674        let dialogs = [];
24675        const getImplementation = () => {
24676            const theme = editor.theme;
24677            return theme && theme.getWindowManagerImpl ? theme.getWindowManagerImpl() : WindowManagerImpl();
24678        };
24679        const funcBind = (scope, f) => {
24680            return (...args) => {
24681                return f ? f.apply(scope, args) : undefined;
24682            };
24683        };
24684        const fireOpenEvent = (dialog) => {
24685            editor.dispatch('OpenWindow', {
24686                dialog
24687            });
24688        };
24689        const fireCloseEvent = (dialog) => {
24690            editor.dispatch('CloseWindow', {
24691                dialog
24692            });
24693        };
24694        const addDialog = (dialog, triggerElement) => {
24695            dialogs.push({ instanceApi: dialog, triggerElement });
24696            fireOpenEvent(dialog);
24697        };
24698        const closeDialog = (dialog) => {
24699            fireCloseEvent(dialog);
24700            const dialogTriggerElement = findMap(dialogs, ({ instanceApi, triggerElement }) => instanceApi === dialog ? triggerElement : Optional.none());
24701            dialogs = filter$5(dialogs, ({ instanceApi }) => instanceApi !== dialog);
24702            // Move focus back to editor when the last window is closed
24703            if (dialogs.length === 0) {
24704                editor.focus();
24705            }
24706            else {
24707                // Move focus to the element that was active before the dialog was opened
24708                dialogTriggerElement.filter(inBody).each(focus$1);
24709            }
24710        };
24711        const getTopDialog = () => {
24712            return Optional.from(dialogs[dialogs.length - 1]);
24713        };
24714        const storeSelectionAndOpenDialog = (openDialog) => {
24715            editor.editorManager.setActive(editor);
24716            store(editor);
24717            const activeEl = active();
24718            editor.ui.show();
24719            const dialog = openDialog();
24720            addDialog(dialog, activeEl);
24721            return dialog;
24722        };
24723        const open = (args, params) => {
24724            return storeSelectionAndOpenDialog(() => getImplementation().open(args, params, closeDialog));
24725        };
24726        const openUrl = (args) => {
24727            return storeSelectionAndOpenDialog(() => getImplementation().openUrl(args, closeDialog));
24728        };
24729        const restoreFocus = (activeEl) => {
24730            if (dialogs.length !== 0) {
24731                // If there are some dialogs, the confirm/alert was probably triggered from the dialog
24732                // Move focus to the element that was active before the confirm/alert was opened
24733                activeEl.each((el) => focus$1(el));
24734            }
24735        };
24736        const alert = (message, callback, scope) => {
24737            const activeEl = active();
24738            const windowManagerImpl = getImplementation();
24739            windowManagerImpl.alert(message, funcBind(scope ? scope : windowManagerImpl, () => {
24740                restoreFocus(activeEl);
24741                callback?.();
24742            }));
24743        };
24744        const confirm = (message, callback, scope) => {
24745            const activeEl = active();
24746            const windowManagerImpl = getImplementation();
24747            windowManagerImpl.confirm(message, funcBind(scope ? scope : windowManagerImpl, (state) => {
24748                restoreFocus(activeEl);
24749                callback?.(state);
24750            }));
24751        };
24752        const close = () => {
24753            getTopDialog().each(({ instanceApi: dialog }) => {
24754                getImplementation().close(dialog);
24755                closeDialog(dialog);
24756            });
24757        };
24758        editor.on('remove', () => {
24759            each$e(dialogs, ({ instanceApi: dialog }) => {
24760                getImplementation().close(dialog);
24761            });
24762        });
24763        return {
24764            /**
24765             * Opens a new window.
24766             *
24767             * @method open
24768             * @param {Object} config For information on the available options, see: <a href="https://www.tiny.cloud/docs/tinymce/8/dialog-configuration/#options">Dialog - Configuration options</a>.
24769             * @param {Object} params (Optional) For information on the available options, see: <a href="https://www.tiny.cloud/docs/tinymce/8/dialog-configuration/#configuration-parameters">Dialog - Configuration parameters</a>.
24770             * @returns {WindowManager.DialogInstanceApi} A new dialog instance.
24771             */
24772            open,
24773            /**
24774             * Opens a new window for the specified url.
24775             *
24776             * @method openUrl
24777             * @param {Object} config For information on the available options, see: <a href="https://www.tiny.cloud/docs/tinymce/8/urldialog/#configuration">URL dialog - Configuration</a>.
24778             * @returns {WindowManager.UrlDialogInstanceApi} A new URL dialog instance.
24779             */
24780            openUrl,
24781            /**
24782             * Creates an alert dialog. Do not use the blocking behavior of this
24783             * native version. Use the callback method instead; then it can be extended.
24784             *
24785             * @method alert
24786             * @param {String} message Text to display in the new alert dialog.
24787             * @param {Function} callback (Optional) Callback function to be executed after the user has selected ok.
24788             * @param {Object} scope (Optional) Scope to execute the callback in.
24789             * @example
24790             * // Displays an alert box using the active editors window manager instance
24791             * tinymce.activeEditor.windowManager.alert('Hello world!');
24792             */
24793            alert,
24794            /**
24795             * Creates an alert dialog. Do not use the blocking behavior of this
24796             * native version. Use the callback method instead; then it can be extended.
24797             *
24798             * @method confirm
24799             * @param {String} message Text to display in the new confirm dialog.
24800             * @param {Function} callback (Optional) Callback function to be executed after the user has selected ok or cancel.
24801             * @param {Object} scope (Optional) Scope to execute the callback in.
24802             * @example
24803             * // Displays a confirm box and an alert message will be displayed depending on what you choose in the confirm
24804             * tinymce.activeEditor.windowManager.confirm('Do you want to do something?', (state) => {
24805             *   const message = state ? 'Ok' : 'Cancel';
24806             *   tinymce.activeEditor.windowManager.alert(message);
24807             * });
24808             */
24809            confirm,
24810            /**
24811             * Closes the top most window.
24812             *
24813             * @method close
24814             */
24815            close
24816        };
24817    };
24818
24819    const displayNotification$1 = (editor, message) => {
24820        editor.notificationManager.open({
24821            type: 'error',
24822            text: message
24823        });
24824    };
24825    const displayError = (editor, message) => {
24826        if (editor._skinLoaded) {
24827            displayNotification$1(editor, message);
24828        }
24829        else {
24830            editor.on('SkinLoaded', () => {
24831                displayNotification$1(editor, message);
24832            });
24833        }
24834    };
24835    const uploadError = (editor, message) => {
24836        displayError(editor, I18n.translate(['Failed to upload image: {0}', message]));
24837    };
24838    const logError = (editor, errorType, msg) => {
24839        fireError(editor, errorType, { message: msg });
24840        // eslint-disable-next-line no-console
24841        console.error(msg);
24842    };
24843    const createLoadError = (type, url, name) => name ?
24844        `Failed to load ${type}: ${name} from url ${url}` :
24845        `Failed to load ${type} url: ${url}`;
24846    const pluginLoadError = (editor, url, name) => {
24847        logError(editor, 'PluginLoadError', createLoadError('plugin', url, name));
24848    };
24849    const iconsLoadError = (editor, url, name) => {
24850        logError(editor, 'IconsLoadError', createLoadError('icons', url, name));
24851    };
24852    const languageLoadError = (editor, url, name) => {
24853        logError(editor, 'LanguageLoadError', createLoadError('language', url, name));
24854    };
24855    const themeLoadError = (editor, url, name) => {
24856        logError(editor, 'ThemeLoadError', createLoadError('theme', url, name));
24857    };
24858    const modelLoadError = (editor, url, name) => {
24859        logError(editor, 'ModelLoadError', createLoadError('model', url, name));
24860    };
24861    const licenseKeyManagerLoadError = (editor, url) => {
24862        logError(editor, 'LicenseKeyManagerLoadError', createLoadError('license key manager', url));
24863    };
24864    const componentLoadError = (editor, url) => {
24865        logError(editor, 'ComponentLoadError', createLoadError('component', url));
24866    };
24867    const pluginInitError = (editor, name, err) => {
24868        const message = I18n.translate(['Failed to initialize plugin: {0}', name]);
24869        fireError(editor, 'PluginLoadError', { message });
24870        initError(message, err);
24871        displayError(editor, message);
24872    };
24873    const initError = (message, ...x) => {
24874        const console = window.console;
24875        if (console) { // Skip test env
24876            if (console.error) {
24877                console.error(message, ...x);
24878            }
24879            else {
24880                console.log(message, ...x);
24881            }
24882        }
24883    };
24884
24885    // Map to track which editors have already been processed and disabled
24886    const processedEditors = new WeakMap();
24887    const forceDisable = (editor) => {
24888        // Check if we've already disabled the editor
24889        if (processedEditors.has(editor)) {
24890            return;
24891        }
24892        // Mark this editor as processed
24893        processedEditors.set(editor, true);
24894        const switchModeListener = () => {
24895            editor.on('SwitchMode', (e) => {
24896                const { mode } = e;
24897                if (mode !== 'readonly') {
24898                    editor.mode.set('readonly');
24899                }
24900            });
24901        };
24902        const disabledStateChangeListener = () => {
24903            editor.on('DisabledStateChange', (e) => {
24904                const { state } = e;
24905                if (!state) {
24906                    e.preventDefault();
24907                }
24908            }, true);
24909        };
24910        if (editor.initialized) {
24911            // Set readonly before setting disabled as disabling editor prevents mode from being changed
24912            if (!editor.removed) {
24913                editor.mode.set('readonly');
24914            }
24915            editor.options.set('disabled', true);
24916        }
24917        else {
24918            editor.on('init', () => {
24919                // Set readonly before setting disabled as disabling editor prevents mode from being changed
24920                if (!editor.removed) {
24921                    editor.mode.set('readonly');
24922                }
24923                editor.options.set('disabled', true);
24924            });
24925        }
24926        disabledStateChangeListener();
24927        switchModeListener();
24928    };
24929
24930    /* eslint-disable no-console */
24931    const displayNotification = (editor, messageData) => {
24932        const { type, message } = messageData;
24933        editor.notificationManager.open({
24934            type,
24935            text: message
24936        });
24937    };
24938    const getConsoleFn = (type) => {
24939        switch (type) {
24940            case 'error':
24941                return console.error;
24942            case 'info':
24943                return console.info;
24944            case 'warn':
24945                return console.warn;
24946            case 'log':
24947            default:
24948                return console.log;
24949        }
24950    };
24951    const displayConsoleMessage = (messageData) => {
24952        const consoleFn = getConsoleFn(messageData.type);
24953        consoleFn(messageData.message);
24954    };
24955    const reportMessage = (editor, message) => {
24956        const { console, editor: editorUi } = message;
24957        if (isNonNullable(editorUi)) {
24958            if (editor._skinLoaded) {
24959                displayNotification(editor, editorUi);
24960            }
24961            else {
24962                editor.on('SkinLoaded', () => {
24963                    displayNotification(editor, editorUi);
24964                });
24965            }
24966        }
24967        if (isNonNullable(console)) {
24968            displayConsoleMessage(console);
24969        }
24970    };
24971
24972    const DOCS_URL = 'https://www.tiny.cloud/docs/tinymce/latest/license-key/';
24973    const DOCS_URL_MESSAGE = `Read more: ${DOCS_URL}`;
24974    const PROVIDE_LICENSE_KEY_MESSAGE = `Make sure to provide a valid license key or add license_key: 'gpl' to the init config to agree to the open source license terms.`;
24975    const reportNoKeyError = (editor) => {
24976        const baseMessage = 'The editor is disabled because a TinyMCE license key has not been provided.';
24977        reportMessage(editor, {
24978            console: {
24979                type: 'error',
24980                message: [
24981                    `${baseMessage}`,
24982                    PROVIDE_LICENSE_KEY_MESSAGE,
24983                    DOCS_URL_MESSAGE
24984                ].join(' ')
24985            },
24986            editor: {
24987                type: 'warning',
24988                message: `${baseMessage}`
24989            }
24990        });
24991    };
24992    const reportLoadError = (editor, onlineStatus) => {
24993        const key = `${onlineStatus === 'online' ? 'API' : 'license'} key`;
24994        const baseMessage = `The editor is disabled because the TinyMCE ${key} could not be validated.`;
24995        reportMessage(editor, {
24996            console: {
24997                type: 'error',
24998                message: [
24999                    `${baseMessage}`,
25000                    `The TinyMCE Commercial License Key Manager plugin is required for the provided ${key} to be validated but could not be loaded.`,
25001                    DOCS_URL_MESSAGE
25002                ].join(' ')
25003            },
25004            editor: {
25005                type: 'warning',
25006                message: `${baseMessage}`
25007            }
25008        });
25009    };
25010    const reportInvalidPlugin = (editor, pluginCode, hasShownPluginNotification) => {
25011        const baseMessage = `The "${pluginCode}" plugin requires a valid TinyMCE license key.`;
25012        reportMessage(editor, {
25013            console: {
25014                type: 'error',
25015                message: [
25016                    `${baseMessage}`,
25017                    DOCS_URL_MESSAGE
25018                ].join(' ')
25019            },
25020            ...hasShownPluginNotification ? {} : {
25021                editor: {
25022                    type: 'warning',
25023                    message: `One or more premium plugins are disabled due to license key restrictions.`
25024                }
25025            }
25026        });
25027    };
25028
25029    const PLUGIN_CODE$1 = 'licensekeymanager';
25030    const getOnlineStatus = (editor) => {
25031        const hasApiKey = isString(getApiKey(editor));
25032        return hasApiKey ? 'online' : 'offline';
25033    };
25034    const getLicenseKeyType = (editor) => {
25035        const licenseKey = getLicenseKey(editor)?.toLowerCase();
25036        if (licenseKey === 'gpl') {
25037            return 'gpl';
25038        }
25039        else if (isNullable(licenseKey)) {
25040            return 'no_key';
25041        }
25042        else {
25043            return 'non_gpl';
25044        }
25045    };
25046    const determineStrategy = (editor) => {
25047        const onlineStatus = getOnlineStatus(editor);
25048        const licenseKeyType = getLicenseKeyType(editor);
25049        const forcePlugin = new Set([
25050            ...getPlugins(editor),
25051            ...keys(getExternalPlugins$1(editor)),
25052        ]).has(PLUGIN_CODE$1);
25053        if (licenseKeyType !== 'gpl' || onlineStatus === 'online' || forcePlugin) {
25054            return {
25055                type: 'use_plugin',
25056                onlineStatus,
25057                licenseKeyType,
25058                forcePlugin
25059            };
25060        }
25061        else {
25062            return {
25063                type: 'use_gpl',
25064                onlineStatus,
25065                licenseKeyType,
25066                forcePlugin
25067            };
25068        }
25069    };
25070
25071    const createFallbackLicenseKeyManager = (canValidate) => (editor) => {
25072        let hasShownPluginNotification = false;
25073        return {
25074            validate: (data) => {
25075                const { plugin } = data;
25076                const hasPlugin = isString(plugin);
25077                // Premium plugins are not allowed
25078                if (hasPlugin) {
25079                    reportInvalidPlugin(editor, plugin, hasShownPluginNotification);
25080                    hasShownPluginNotification = true;
25081                }
25082                return Promise.resolve(canValidate && !hasPlugin);
25083            },
25084        };
25085    };
25086    const NoLicenseKeyManager = createFallbackLicenseKeyManager(false);
25087    const GplLicenseKeyManager = createFallbackLicenseKeyManager(true);
25088    const ADDON_KEY = 'manager';
25089    const PLUGIN_CODE = PLUGIN_CODE$1;
25090    const setup$y = () => {
25091        const addOnManager = AddOnManager();
25092        const add = (addOn) => {
25093            addOnManager.add(ADDON_KEY, addOn);
25094        };
25095        const load = (editor, suffix) => {
25096            const strategy = determineStrategy(editor);
25097            if (strategy.type === 'use_plugin') {
25098                const externalUrl = get$
vendor: 7,945 bytes, lines 25098-25290
25098a(getExternalPlugins$1(editor), PLUGIN_CODE).map(trim$4).filter(isNotEmpty);
25099                const url = externalUrl.getOr(`plugins/${PLUGIN_CODE}/plugin${suffix}.js`);
25100                addOnManager.load(ADDON_KEY, url).catch(() => {
25101                    licenseKeyManagerLoadError(editor, url);
25102                });
25103            }
25104        };
25105        const init = (editor) => {
25106            const setLicenseKeyManager = (licenseKeyManager) => {
25107                Object.defineProperty(editor, 'licenseKeyManager', {
25108                    value: licenseKeyManager,
25109                    writable: false,
25110                    configurable: false,
25111                    enumerable: true,
25112                });
25113            };
25114            const strategy = determineStrategy(editor);
25115            const LicenseKeyManager = addOnManager.get(ADDON_KEY);
25116            // Use plugin if it is already loaded as it can handle all license key types
25117            if (isNonNullable(LicenseKeyManager)) {
25118                const licenseKeyManagerApi = LicenseKeyManager(editor, addOnManager.urls[ADDON_KEY]);
25119                setLicenseKeyManager(licenseKeyManagerApi);
25120            }
25121            else {
25122                switch (strategy.type) {
25123                    case 'use_gpl': {
25124                        setLicenseKeyManager(GplLicenseKeyManager(editor));
25125                        break;
25126                    }
25127                    case 'use_plugin': {
25128                        // We know the plugin hasn't loaded and it is required
25129                        forceDisable(editor);
25130                        setLicenseKeyManager(NoLicenseKeyManager(editor));
25131                        if (strategy.onlineStatus === 'offline' && strategy.licenseKeyType === 'no_key') {
25132                            reportNoKeyError(editor);
25133                        }
25134                        else {
25135                            reportLoadError(editor, strategy.onlineStatus);
25136                        }
25137                        break;
25138                    }
25139                }
25140            }
25141            // Validation of the license key is done asynchronously and does
25142            // not block initialization of the editor
25143            // The validate function is expected to set the editor to the correct
25144            // state depending on if the license key is valid or not
25145            // eslint-disable-next-line @typescript-eslint/no-floating-promises
25146            editor.licenseKeyManager.validate({});
25147        };
25148        return {
25149            load,
25150            add,
25151            init
25152        };
25153    };
25154    const LicenseKeyManagerLoader = setup$y();
25155
25156    const removeFakeSelection = (editor) => {
25157        Optional.from(editor.selection.getNode()).each((elm) => {
25158            elm.removeAttribute('data-mce-selected');
25159        });
25160    };
25161    const setEditorCommandState = (editor, cmd, state) => {
25162        try {
25163            // execCommand needs a string for the value, so convert the boolean to a string
25164            // See: https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand#Parameters
25165            editor.getDoc().execCommand(cmd, false, String(state));
25166        }
25167        catch {
25168            // Ignore
25169        }
25170    };
25171    const setCommonEditorCommands = (editor, state) => {
25172        setEditorCommandState(editor, 'StyleWithCSS', state);
25173        setEditorCommandState(editor, 'enableInlineTableEditing', state);
25174        setEditorCommandState(editor, 'enableObjectResizing', state);
25175    };
25176    const restoreFakeSelection = (editor) => {
25177        editor.selection.setRng(editor.selection.getRng());
25178    };
25179    // Not quite sugar Class.toggle, it's more of a Class.set
25180    const toggleClass = (elm, cls, state) => {
25181        if (has(elm, cls) && !state) {
25182            remove$4(elm, cls);
25183        }
25184        else if (state) {
25185            add$2(elm, cls);
25186        }
25187    };
25188    const disableEditor = (editor) => {
25189        const body = SugarElement.fromDom(editor.getBody());
25190        toggleClass(body, 'mce-content-readonly', true);
25191        editor.selection.controlSelection.hideResizeRect();
25192        editor._selectionOverrides.hideFakeCaret();
25193        removeFakeSelection(editor);
25194    };
25195    const enableEditor = (editor) => {
25196        const body = SugarElement.fromDom(editor.getBody());
25197        toggleClass(body, 'mce-content-readonly', false);
25198        if (editor.hasEditableRoot()) {
25199            set(body, true);
25200        }
25201        setCommonEditorCommands(editor, false);
25202        if (hasEditorOrUiFocus(editor)) {
25203            editor.focus();
25204        }
25205        restoreFakeSelection(editor);
25206        editor.nodeChanged();
25207    };
25208
25209    const isDisabled = (editor) => isDisabled$1(editor);
25210    const internalContentEditableAttr = 'data-mce-contenteditable';
25211    const switchOffContentEditableTrue = (elm) => {
25212        each$e(descendants(elm, '*[contenteditable="true"]'), (elm) => {
25213            set$4(elm, internalContentEditableAttr, 'true');
25214            set(elm, false);
25215        });
25216    };
25217    const switchOnContentEditableTrue = (elm) => {
25218        each$e(descendants(elm, `*[${internalContentEditableAttr}="true"]`), (elm) => {
25219            remove$9(elm, internalContentEditableAttr);
25220            set(elm, true);
25221        });
25222    };
25223    const toggleDisabled = (editor, state) => {
25224        const body = SugarElement.fromDom(editor.getBody());
25225        if (state) {
25226            disableEditor(editor);
25227            set(body, false);
25228            switchOffContentEditableTrue(body);
25229        }
25230        else {
25231            switchOnContentEditableTrue(body);
25232            enableEditor(editor);
25233        }
25234    };
25235    const registerDisabledContentFilters = (editor) => {
25236        if (editor.serializer) {
25237            registerFilters(editor);
25238        }
25239        else {
25240            editor.on('PreInit', () => {
25241                registerFilters(editor);
25242            });
25243        }
25244    };
25245    const registerFilters = (editor) => {
25246        editor.parser.addAttributeFilter('contenteditable', (nodes) => {
25247            if (isDisabled(editor)) {
25248                each$e(nodes, (node) => {
25249                    node.attr(internalContentEditableAttr, node.attr('contenteditable'));
25250                    node.attr('contenteditable', 'false');
25251                });
25252            }
25253        });
25254        editor.serializer.addAttributeFilter(internalContentEditableAttr, (nodes) => {
25255            if (isDisabled(editor)) {
25256                each$e(nodes, (node) => {
25257                    node.attr('contenteditable', node.attr(internalContentEditableAttr));
25258                });
25259            }
25260        });
25261        editor.serializer.addTempAttr(internalContentEditableAttr);
25262    };
25263    const isClickEvent = (e) => e.type === 'click';
25264    const allowedEvents = ['copy'];
25265    const isAllowedEventInDisabledMode = (e) => contains$2(allowedEvents, e.type);
25266    const getAnchorHrefOpt = (editor, elm) => {
25267        const isRoot = (elm) => eq(elm, SugarElement.fromDom(editor.getBody()));
25268        return closest$4(elm, 'a', isRoot).bind((a) => getOpt(a, 'href'));
25269    };
25270    const hasAccordion = (editor, elm) => {
25271        const isRoot = (elm) => eq(elm, SugarElement.fromDom(editor.getBody()));
25272        return closest$2(elm, 'details', isRoot);
25273    };
25274    const processDisabledEvents = (editor, e) => {
25275        /*
25276          If an event is a click event on or within an anchor, and the CMD/CTRL key is
25277          not held, then we want to prevent default behaviour and either:
25278            a) scroll to the relevant bookmark
25279            b) open the link using default browser behaviour
25280        */
25281        if (isClickEvent(e) && !VK.metaKeyPressed(e)) {
25282            const elm = SugarElement.fromDom(e.target);
25283            getAnchorHrefOpt(editor, elm).fold(() => {
25284                if (hasAccordion(editor, elm)) {
25285                    e.preventDefault();
25286                }
25287            }, (href) => {
25288                e.preventDefault();
25289                if (/^#/.test(href)) {
25290                    const targetEl = editor.dom.select(`${href},[name="${removeLeading(href, '#')
vendor: 29,264 bytes, lines 25290-26000
25290}"]`);
25291                    if (targetEl.length) {
25292                        editor.selection.scrollIntoView(targetEl[0], true);
25293                    }
25294                }
25295                else {
25296                    window.open(href, '_blank', 'rel=noopener noreferrer,menubar=yes,toolbar=yes,location=yes,status=yes,resizable=yes,scrollbars=yes');
25297                }
25298            });
25299        }
25300        else if (isAllowedEventInDisabledMode(e)) {
25301            editor.dispatch(e.type, e);
25302        }
25303    };
25304    const registerDisabledModeEventHandlers = (editor) => {
25305        editor.on('ShowCaret ObjectSelected', (e) => {
25306            if (isDisabled(editor)) {
25307                e.preventDefault();
25308            }
25309        });
25310        // Preprend to the handlers as this should be the first to fire
25311        editor.on('DisabledStateChange', (e) => {
25312            if (!e.isDefaultPrevented()) {
25313                toggleDisabled(editor, e.state);
25314            }
25315        });
25316    };
25317    const registerEventsAndFilters$1 = (editor) => {
25318        registerDisabledContentFilters(editor);
25319        registerDisabledModeEventHandlers(editor);
25320    };
25321
25322    const isContentCssSkinName = (url) => /^[a-z0-9\-]+$/i.test(url);
25323    const toContentSkinResourceName = (name) => 'content/' + name + '/content.css';
25324    const isBundledCssSkinName = (name) => tinymce.Resource.has(toContentSkinResourceName(name));
25325    const getContentCssUrls = (editor) => {
25326        return transformToUrls(editor, getContentCss(editor));
25327    };
25328    const getFontCssUrls = (editor) => {
25329        return transformToUrls(editor, getFontCss(editor));
25330    };
25331    const transformToUrls = (editor, cssLinks) => {
25332        const skinUrl = editor.editorManager.baseURL + '/skins/content';
25333        const suffix = editor.editorManager.suffix;
25334        const contentCssFile = `content${suffix}.css`;
25335        return map$3(cssLinks, (url) => {
25336            if (isBundledCssSkinName(url)) {
25337                return toContentSkinResourceName(url);
25338            }
25339            else if (isContentCssSkinName(url) && !editor.inline) {
25340                return `${skinUrl}/${url}/${contentCssFile}`;
25341            }
25342            else {
25343                return editor.documentBaseURI.toAbsolute(url);
25344            }
25345        });
25346    };
25347    const appendContentCssFromSettings = (editor) => {
25348        editor.contentCSS = editor.contentCSS.concat(getContentCssUrls(editor), getFontCssUrls(editor));
25349    };
25350
25351    /**
25352     * Finds images with data uris or blob uris. If data uris are found it will convert them into blob uris.
25353     *
25354     * @private
25355     * @class tinymce.file.ImageScanner
25356     */
25357    const getAllImages = (elm) => {
25358        return elm ? from(elm.getElementsByTagName('img')) : [];
25359    };
25360    const ImageScanner = (uploadStatus, blobCache) => {
25361        const cachedPromises = {};
25362        const findAll = (elm, predicate = always) => {
25363            const images = filter$5(getAllImages(elm), (img) => {
25364                const src = img.src;
25365                if (img.hasAttribute('data-mce-bogus')) {
25366                    return false;
25367                }
25368                if (img.hasAttribute('data-mce-placeholder')) {
25369                    return false;
25370                }
25371                if (!src || src === Env.transparentSrc) {
25372                    return false;
25373                }
25374                if (startsWith(src, 'blob:')) {
25375                    return !uploadStatus.isUploaded(src) && predicate(img);
25376                }
25377                if (startsWith(src, 'data:')) {
25378                    return predicate(img);
25379                }
25380                return false;
25381            });
25382            const promises = map$3(images, (img) => {
25383                const imageSrc = img.src;
25384                if (has$2(cachedPromises, imageSrc)) {
25385                    // Since the cached promise will return the cached image
25386                    // We need to wrap it and resolve with the actual image
25387                    return cachedPromises[imageSrc].then((imageInfo) => {
25388                        if (isString(imageInfo)) { // error apparently
25389                            return imageInfo;
25390                        }
25391                        else {
25392                            return {
25393                                image: img,
25394                                blobInfo: imageInfo.blobInfo
25395                            };
25396                        }
25397                    });
25398                }
25399                else {
25400                    const newPromise = imageToBlobInfo(blobCache, imageSrc)
25401                        .then((blobInfo) => {
25402                        delete cachedPromises[imageSrc];
25403                        return { image: img, blobInfo };
25404                    }).catch((error) => {
25405                        delete cachedPromises[imageSrc];
25406                        return error;
25407                    });
25408                    cachedPromises[imageSrc] = newPromise;
25409                    return newPromise;
25410                }
25411            });
25412            return Promise.all(promises);
25413        };
25414        return {
25415            findAll
25416        };
25417    };
25418
25419    /**
25420     * Holds the current status of a blob uri, if it's pending or uploaded and what the result urls was.
25421     *
25422     * @private
25423     * @class tinymce.file.UploadStatus
25424     */
25425    const UploadStatus = () => {
25426        const PENDING = 1, UPLOADED = 2;
25427        let blobUriStatuses = {};
25428        const createStatus = (status, resultUri) => {
25429            return {
25430                status,
25431                resultUri
25432            };
25433        };
25434        const hasBlobUri = (blobUri) => {
25435            return blobUri in blobUriStatuses;
25436        };
25437        const getResultUri = (blobUri) => {
25438            const result = blobUriStatuses[blobUri];
25439            return result ? result.resultUri : null;
25440        };
25441        const isPending = (blobUri) => {
25442            return hasBlobUri(blobUri) ? blobUriStatuses[blobUri].status === PENDING : false;
25443        };
25444        const isUploaded = (blobUri) => {
25445            return hasBlobUri(blobUri) ? blobUriStatuses[blobUri].status === UPLOADED : false;
25446        };
25447        const markPending = (blobUri) => {
25448            blobUriStatuses[blobUri] = createStatus(PENDING, null);
25449        };
25450        const markUploaded = (blobUri, resultUri) => {
25451            blobUriStatuses[blobUri] = createStatus(UPLOADED, resultUri);
25452        };
25453        const removeFailed = (blobUri) => {
25454            delete blobUriStatuses[blobUri];
25455        };
25456        const destroy = () => {
25457            blobUriStatuses = {};
25458        };
25459        return {
25460            hasBlobUri,
25461            getResultUri,
25462            isPending,
25463            isUploaded,
25464            markPending,
25465            markUploaded,
25466            removeFailed,
25467            destroy
25468        };
25469    };
25470
25471    /**
25472     * Generates unique ids.
25473     *
25474     * @class tinymce.util.Uuid
25475     * @private
25476     */
25477    let count = 0;
25478    const seed = () => {
25479        const rnd = () => {
25480            return Math.round(random() * 0xFFFFFFFF).toString(36);
25481        };
25482        const now = new Date().getTime();
25483        return 's' + now.toString(36) + rnd() + rnd() + rnd();
25484    };
25485    const uuid = (prefix) => {
25486        return prefix + (count++) + seed();
25487    };
25488
25489    const BlobCache = () => {
25490        let cache = [];
25491        const mimeToExt = (mime) => {
25492            const mimes = {
25493                'image/jpeg': 'jpg',
25494                'image/jpg': 'jpg',
25495                'image/gif': 'gif',
25496                'image/png': 'png',
25497                'image/apng': 'apng',
25498                'image/avif': 'avif',
25499                'image/svg+xml': 'svg',
25500                'image/webp': 'webp',
25501                'image/bmp': 'bmp',
25502                'image/tiff': 'tiff'
25503            };
25504            return mimes[mime.toLowerCase()] || 'dat';
25505        };
25506        const create = (o, blob, base64, name, filename) => {
25507            if (isString(o)) {
25508                const id = o;
25509                return toBlobInfo({
25510                    id,
25511                    name,
25512                    filename,
25513                    blob: blob,
25514                    base64: base64
25515                });
25516            }
25517            else if (isObject(o)) {
25518                return toBlobInfo(o);
25519            }
25520            else {
25521                throw new Error('Unknown input type');
25522            }
25523        };
25524        const toBlobInfo = (o) => {
25525            if (isNullable(o.blob) || isNullable(o.base64) || (o.base64 === '' && !o.allowEmptyFile)) {
25526                throw new Error('blob and base64 representations of the image are required for BlobInfo to be created');
25527            }
25528            const id = o.id || uuid('blobid');
25529            const name = o.name || id;
25530            const blob = o.blob;
25531            return {
25532                id: constant(id),
25533                name: constant(name),
25534                filename: constant(o.filename || name + '.' + mimeToExt(blob.type)),
25535                blob: constant(blob),
25536                base64: constant(o.base64),
25537                blobUri: constant(o.blobUri || URL.createObjectURL(blob)),
25538                uri: constant(o.uri)
25539            };
25540        };
25541        const add = (blobInfo) => {
25542            if (!get(blobInfo.id())) {
25543                cache.push(blobInfo);
25544            }
25545        };
25546        const findFirst = (predicate) => find$2(cache, predicate).getOrUndefined();
25547        const get = (id) => findFirst((cachedBlobInfo) => cachedBlobInfo.id() === id);
25548        const getByUri = (blobUri) => findFirst((blobInfo) => blobInfo.blobUri() === blobUri);
25549        const getByData = (base64, type) => findFirst((blobInfo) => blobInfo.base64() === base64 && blobInfo.blob().type === type);
25550        const removeByUri = (blobUri) => {
25551            cache = filter$5(cache, (blobInfo) => {
25552                if (blobInfo.blobUri() === blobUri) {
25553                    URL.revokeObjectURL(blobInfo.blobUri());
25554                    return false;
25555                }
25556                return true;
25557            });
25558        };
25559        const destroy = () => {
25560            each$e(cache, (cachedBlobInfo) => {
25561                URL.revokeObjectURL(cachedBlobInfo.blobUri());
25562            });
25563            cache = [];
25564        };
25565        return {
25566            create,
25567            add,
25568            get,
25569            getByUri,
25570            getByData,
25571            findFirst,
25572            removeByUri,
25573            destroy
25574        };
25575    };
25576
25577    const Uploader = (uploadStatus, settings) => {
25578        const pendingPromises = {};
25579        const pathJoin = (path1, path2) => {
25580            if (path1) {
25581                return path1.replace(/\/$/, '') + '/' + path2.replace(/^\//, '');
25582            }
25583            return path2;
25584        };
25585        const defaultHandler = (blobInfo, progress) => new Promise((success, failure) => {
25586            const xhr = new XMLHttpRequest();
25587            xhr.open('POST', settings.url);
25588            xhr.withCredentials = settings.credentials;
25589            xhr.upload.onprogress = (e) => {
25590                progress(e.loaded / e.total * 100);
25591            };
25592            xhr.onerror = () => {
25593                failure('Image upload failed due to a XHR Transport error. Code: ' + xhr.status);
25594            };
25595            xhr.onload = () => {
25596                if (xhr.status < 200 || xhr.status >= 300) {
25597                    failure('HTTP Error: ' + xhr.status);
25598                    return;
25599                }
25600                const json = JSON.parse(xhr.responseText);
25601                if (!json || !isString(json.location)) {
25602                    failure('Invalid JSON: ' + xhr.responseText);
25603                    return;
25604                }
25605                success(pathJoin(settings.basePath, json.location));
25606            };
25607            const formData = new FormData();
25608            formData.append('file', blobInfo.blob(), blobInfo.filename());
25609            xhr.send(formData);
25610        });
25611        const uploadHandler = isFunction(settings.handler) ? settings.handler : defaultHandler;
25612        const noUpload = () => new Promise((resolve) => {
25613            resolve([]);
25614        });
25615        const handlerSuccess = (blobInfo, url) => ({
25616            url,
25617            blobInfo,
25618            status: true
25619        });
25620        const handlerFailure = (blobInfo, error) => ({
25621            url: '',
25622            blobInfo,
25623            status: false,
25624            error
25625        });
25626        const resolvePending = (blobUri, result) => {
25627            Tools.each(pendingPromises[blobUri], (resolve) => {
25628                resolve(result);
25629            });
25630            delete pendingPromises[blobUri];
25631        };
25632        const uploadBlobInfo = (blobInfo, handler, openNotification) => {
25633            uploadStatus.markPending(blobInfo.blobUri());
25634            return new Promise((resolve) => {
25635                let notification;
25636                let progress;
25637                try {
25638                    const closeNotification = () => {
25639                        if (notification) {
25640                            notification.close();
25641                            progress = noop; // Once it's closed it's closed
25642                        }
25643                    };
25644                    const success = (data) => {
25645                        closeNotification();
25646                        const url = isString(data) ? data : data.url;
25647                        uploadStatus.markUploaded(blobInfo.blobUri(), url);
25648                        resolvePending(blobInfo.blobUri(), handlerSuccess(blobInfo, url));
25649                        resolve(handlerSuccess(blobInfo, url));
25650                    };
25651                    const failure = (error) => {
25652                        closeNotification();
25653                        uploadStatus.removeFailed(blobInfo.blobUri());
25654                        resolvePending(blobInfo.blobUri(), handlerFailure(blobInfo, error));
25655                        resolve(handlerFailure(blobInfo, error));
25656                    };
25657                    progress = (percent) => {
25658                        if (percent < 0 || percent > 100) {
25659                            return;
25660                        }
25661                        Optional.from(notification)
25662                            .orThunk(() => Optional.from(openNotification).map(apply$1))
25663                            .each((n) => {
25664                            notification = n;
25665                            n.progressBar.value(percent);
25666                        });
25667                    };
25668                    handler(blobInfo, progress).then(success, (err) => {
25669                        failure(isString(err) ? { message: err } : err);
25670                    });
25671                }
25672                catch (ex) {
25673                    resolve(handlerFailure(blobInfo, ex));
25674                }
25675            });
25676        };
25677        const isDefaultHandler = (handler) => handler === defaultHandler;
25678        const pendingUploadBlobInfo = (blobInfo) => {
25679            const blobUri = blobInfo.blobUri();
25680            return new Promise((resolve) => {
25681                pendingPromises[blobUri] = pendingPromises[blobUri] || [];
25682                pendingPromises[blobUri].push(resolve);
25683            });
25684        };
25685        const uploadBlobs = (blobInfos, openNotification) => {
25686            blobInfos = Tools.grep(blobInfos, (blobInfo) => !uploadStatus.isUploaded(blobInfo.blobUri()));
25687            return Promise.all(Tools.map(blobInfos, (blobInfo) => uploadStatus.isPending(blobInfo.blobUri()) ?
25688                pendingUploadBlobInfo(blobInfo) : uploadBlobInfo(blobInfo, uploadHandler, openNotification)));
25689        };
25690        const upload = (blobInfos, openNotification) => (!settings.url && isDefaultHandler(uploadHandler)) ? noUpload() : uploadBlobs(blobInfos, openNotification);
25691        return {
25692            upload
25693        };
25694    };
25695
25696    const openNotification = (editor) => () => editor.notificationManager.open({
25697        text: editor.translate('Image uploading...'),
25698        type: 'info',
25699        timeout: -1,
25700        progressBar: true
25701    });
25702    const createUploader = (editor, uploadStatus) => Uploader(uploadStatus, {
25703        url: getImageUploadUrl(editor),
25704        basePath: getImageUploadBasePath(editor),
25705        credentials: getImagesUploadCredentials(editor),
25706        handler: getImagesUploadHandler(editor)
25707    });
25708    /**
25709     * This class handles uploading images to a back-end server.
25710     *
25711     * @class tinymce.util.ImageUploader
25712     */
25713    const ImageUploader = (editor) => {
25714        const uploadStatus = UploadStatus();
25715        const uploader = createUploader(editor, uploadStatus);
25716        return {
25717            /**
25718             * Uploads images to the configured image upload URL (`images_upload_url`) or passes the images to the defined image upload handler function (`images_upload_handler`).
25719             *
25720             * @method upload
25721             * @param {Array} blobInfos  A BlobInfo array containing the image data to upload. A BlobInfo can be created by calling `editor.editorUpload.blobCache.create()`.
25722             * @param {Boolean} showNotification (Optional) When set to true, a notification with a progress bar will be shown during image uploads.
25723             */
25724            upload: (blobInfos, showNotification = true) => uploader.upload(blobInfos, showNotification ? openNotification(editor) : undefined)
25725        };
25726    };
25727
25728    const isEmptyForPadding = (editor, element) => editor.dom.isEmpty(element.dom) && isNonNullable(editor.schema.getTextBlockElements()[name(element)]);
25729    const addPaddingToEmpty = (editor) => (element) => {
25730        if (isEmptyForPadding(editor, element)) {
25731            append$1(element, SugarElement.fromHtml('<br data-mce-bogus="1" />'));
25732        }
25733    };
25734    const EditorUpload = (editor) => {
25735        const blobCache = BlobCache();
25736        let uploader, imageScanner;
25737        const uploadStatus = UploadStatus();
25738        const urlFilters = [];
25739        const aliveGuard = (callback) => {
25740            return (result) => {
25741                if (editor.selection) {
25742                    return callback(result);
25743                }
25744                return [];
25745            };
25746        };
25747        const cacheInvalidator = (url) => url + (url.indexOf('?') === -1 ? '?' : '&') + (new Date()).getTime();
25748        // Replaces strings without regexps to avoid FF regexp to big issue
25749        const replaceString = (content, search, replace) => {
25750            let index = 0;
25751            do {
25752                index = content.indexOf(search, index);
25753                if (index !== -1) {
25754                    content = content.substring(0, index) + replace + content.substr(index + search.length);
25755                    index += replace.length - search.length + 1;
25756                }
25757            } while (index !== -1);
25758            return content;
25759        };
25760        const replaceImageUrl = (content, targetUrl, replacementUrl) => {
25761            const replacementString = `src="${replacementUrl}"${replacementUrl === Env.transparentSrc ? ' data-mce-placeholder="1"' : ''}`;
25762            content = replaceString(content, `src="${targetUrl}"`, replacementString);
25763            content = replaceString(content, 'data-mce-src="' + targetUrl + '"', 'data-mce-src="' + replacementUrl + '"');
25764            return content;
25765        };
25766        const replaceUrlInUndoStack = (targetUrl, replacementUrl) => {
25767            each$e(editor.undoManager.data, (level) => {
25768                if (level.type === 'fragmented') {
25769                    level.fragments = map$3(level.fragments, (fragment) => replaceImageUrl(fragment, targetUrl, replacementUrl));
25770                }
25771                else {
25772                    level.content = replaceImageUrl(level.content, targetUrl, replacementUrl);
25773                }
25774            });
25775        };
25776        const replaceImageUriInView = (image, resultUri) => {
25777            const src = editor.convertURL(resultUri, 'src');
25778            replaceUrlInUndoStack(image.src, resultUri);
25779            setAll$1(SugarElement.fromDom(image), {
25780                'src': shouldReuseFileName(editor) ? cacheInvalidator(resultUri) : resultUri,
25781                'data-mce-src': src
25782            });
25783        };
25784        const uploadImages = () => {
25785            if (!uploader) {
25786                uploader = createUploader(editor, uploadStatus);
25787            }
25788            return scanForImages().then(aliveGuard((imageInfos) => {
25789                const blobInfos = map$3(imageInfos, (imageInfo) => imageInfo.blobInfo);
25790                return uploader.upload(blobInfos, openNotification(editor)).then(aliveGuard((result) => {
25791                    const imagesToRemove = [];
25792                    let shouldDispatchChange = false;
25793                    const filteredResult = map$3(result, (uploadInfo, index) => {
25794                        const { blobInfo, image } = imageInfos[index];
25795                        let removed = false;
25796                        if (uploadInfo.status && shouldReplaceBlobUris(editor)) {
25797                            if (uploadInfo.url && !contains$1(image.src, uploadInfo.url)) {
25798                                shouldDispatchChange = true;
25799                            }
25800                            blobCache.removeByUri(image.src);
25801                            if (isRtc(editor)) ;
25802                            else {
25803                                replaceImageUriInView(image, uploadInfo.url);
25804                            }
25805                        }
25806                        else if (uploadInfo.error) {
25807                            if (uploadInfo.error.remove) {
25808                                replaceUrlInUndoStack(image.src, Env.transparentSrc);
25809                                imagesToRemove.push(image);
25810                                removed = true;
25811                            }
25812                            uploadError(editor, uploadInfo.error.message);
25813                        }
25814                        return {
25815                            element: image,
25816                            status: uploadInfo.status,
25817                            uploadUri: uploadInfo.url,
25818                            blobInfo,
25819                            removed
25820                        };
25821                    });
25822                    if (imagesToRemove.length > 0 && !isRtc(editor)) {
25823                        editor.undoManager.transact(() => {
25824                            each$e(fromDom$1(imagesToRemove), (sugarElement) => {
25825                                const parentOpt = parent(sugarElement);
25826                                remove$8(sugarElement);
25827                                // This needs a more editor-wide fix, see issue TINY-9802. Short version: Removing the image resulted in empty <p> elements, which confused the editor.
25828                                parentOpt.each(addPaddingToEmpty(editor));
25829                                blobCache.removeByUri(sugarElement.dom.src);
25830                            });
25831                        });
25832                    }
25833                    else if (shouldDispatchChange) {
25834                        editor.undoManager.dispatchChange();
25835                    }
25836                    return filteredResult;
25837                }));
25838            }));
25839        };
25840        const uploadImagesAuto = () => isAutomaticUploadsEnabled(editor) ? uploadImages() : Promise.resolve([]);
25841        const isValidDataUriImage = (imgElm) => forall(urlFilters, (filter) => filter(imgElm));
25842        const addFilter = (filter) => {
25843            urlFilters.push(filter);
25844        };
25845        const scanForImages = () => {
25846            if (!imageScanner) {
25847                imageScanner = ImageScanner(uploadStatus, blobCache);
25848            }
25849            return imageScanner.findAll(editor.getBody(), isValidDataUriImage).then(aliveGuard((result) => {
25850                const filteredResult = filter$5(result, (resultItem) => {
25851                    // ImageScanner internally converts images that it finds, but it may fail to do so if image source is inaccessible.
25852                    // In such case resultItem will contain appropriate text error message, instead of image data.
25853                    if (isString(resultItem)) {
25854                        displayError(editor, resultItem);
25855                        return false;
25856                    }
25857                    else if (resultItem.uriType === 'blob') {
25858                        return false;
25859                    }
25860                    else {
25861                        return true;
25862                    }
25863                });
25864                if (isRtc(editor)) ;
25865                else {
25866                    each$e(filteredResult, (resultItem) => {
25867                        replaceUrlInUndoStack(resultItem.image.src, resultItem.blobInfo.blobUri());
25868                        resultItem.image.src = resultItem.blobInfo.blobUri();
25869                        resultItem.image.removeAttribute('data-mce-src');
25870                    });
25871                }
25872                return filteredResult;
25873            }));
25874        };
25875        const destroy = () => {
25876            blobCache.destroy();
25877            uploadStatus.destroy();
25878            imageScanner = uploader = null;
25879        };
25880        const replaceBlobUris = (content) => {
25881            return content.replace(/src="(blob:[^"]+)"/g, (match, blobUri) => {
25882                const resultUri = uploadStatus.getResultUri(blobUri);
25883                if (resultUri) {
25884                    return 'src="' + resultUri + '"';
25885                }
25886                let blobInfo = blobCache.getByUri(blobUri);
25887                if (!blobInfo) {
25888                    blobInfo = foldl(editor.editorManager.get(), (result, editor) => {
25889                        return result || editor.editorUpload && editor.editorUpload.blobCache.getByUri(blobUri);
25890                    }, undefined);
25891                }
25892                if (blobInfo) {
25893                    const blob = blobInfo.blob();
25894                    return 'src="data:' + blob.type + ';base64,' + blobInfo.base64() + '"';
25895                }
25896                return match;
25897            });
25898        };
25899        editor.on('SetContent', () => {
25900            if (isAutomaticUploadsEnabled(editor)) {
25901                // eslint-disable-next-line @typescript-eslint/no-floating-promises
25902                uploadImagesAuto();
25903            }
25904            else {
25905                // eslint-disable-next-line @typescript-eslint/no-floating-promises
25906                scanForImages();
25907            }
25908        });
25909        editor.on('RawSaveContent', (e) => {
25910            e.content = replaceBlobUris(e.content);
25911        });
25912        editor.on('GetContent', (e) => {
25913            if (e.source_view || e.format === 'raw' || e.format === 'tree') {
25914                return;
25915            }
25916            e.content = replaceBlobUris(e.content);
25917        });
25918        editor.on('PostRender', () => {
25919            editor.parser.addNodeFilter('img', (images) => {
25920                each$e(images, (img) => {
25921                    const src = img.attr('src');
25922                    if (!src || blobCache.getByUri(src)) {
25923                        return;
25924                    }
25925                    const resultUri = uploadStatus.getResultUri(src);
25926                    if (resultUri) {
25927                        img.attr('src', resultUri);
25928                    }
25929                });
25930            });
25931        });
25932        return {
25933            blobCache,
25934            addFilter,
25935            uploadImages,
25936            uploadImagesAuto,
25937            scanForImages,
25938            destroy
25939        };
25940    };
25941
25942    const get$1 = (editor) => {
25943        const dom = editor.dom;
25944        const schemaType = editor.schema.type;
25945        const formats = {
25946            valigntop: [
25947                { selector: 'td,th', styles: { verticalAlign: 'top' } }
25948            ],
25949            valignmiddle: [
25950                { selector: 'td,th', styles: { verticalAlign: 'middle' } }
25951            ],
25952            valignbottom: [
25953                { selector: 'td,th', styles: { verticalAlign: 'bottom' } }
25954            ],
25955            alignleft: [
25956                {
25957                    selector: 'figure.image',
25958                    collapsed: false,
25959                    classes: 'align-left',
25960                    ceFalseOverride: true,
25961                    preview: 'font-family font-size'
25962                },
25963                {
25964                    selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li,pre',
25965                    styles: {
25966                        textAlign: 'left'
25967                    },
25968                    inherit: false,
25969                    preview: false
25970                },
25971                {
25972                    selector: 'img,audio,video',
25973                    collapsed: false,
25974                    styles: {
25975                        float: 'left',
25976                    },
25977                    preview: 'font-family font-size'
25978                },
25979                {
25980                    selector: '.mce-placeholder',
25981                    styles: {
25982                        float: 'left',
25983                    },
25984                    ceFalseOverride: true
25985                },
25986                {
25987                    selector: 'table',
25988                    collapsed: false,
25989                    styles: {
25990                        marginLeft: '0px',
25991                        marginRight: 'auto',
25992                    },
25993                    onformat: (table) => {
25994                        // Remove conflicting float style
25995                        dom.setStyle(table, 'float', null);
25996                    },
25997                    preview: 'font-family font-size'
25998                },
25999                {
26000                    selector: '.mce-preview-object,[data-ephox-embed-iri]
26000',
26001                    ceFalseOverride: true,
26002                    styles: {
26003                        float: 'left'
26004                    }
26005                }
26006            ],
26007            aligncenter: [
26008                {
26009                    selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li,pre',
26010                    styles: {
26011                        textAlign: 'center'
26012                    },
26013                    inherit: false,
26014                    preview: 'font-family font-size'
26015                },
26016                {
26017                    selector: 'figure.image',
26018                    collapsed: false,
26019                    classes: 'align-center',
26020                    ceFalseOverride: true,
26021                    preview: 'font-family font-size'
26022                },
26023                {
26024                    selector: 'img,audio,video',
26025                    collapsed: false,
26026                    styles: {
26027                        display: 'block',
26028                        marginLeft: 'auto',
26029                        marginRight: 'auto'
26030                    },
26031                    preview: false
26032                },
26033                {
26034                    selector: '.mce-placeholder',
26035                    styles: {
26036                        display: 'block',
26037                        marginLeft: 'auto',
26038                        marginRight: 'auto',
26039                    },
26040                    ceFalseOverride: true
26041                },
26042                {
26043                    selector: 'table',
vendor: 2,652 bytes, lines 26044-26116
26044                    collapsed: false,
26045                    styles: {
26046                        marginLeft: 'auto',
26047                        marginRight: 'auto'
26048                    },
26049                    preview: 'font-family font-size'
26050                },
26051                {
26052                    selector: '.mce-preview-object',
26053                    ceFalseOverride: true,
26054                    styles: {
26055                        display: 'table', // Needs to be `table` to properly render while editing
26056                        marginLeft: 'auto',
26057                        marginRight: 'auto'
26058                    },
26059                    preview: false
26060                },
26061                {
26062                    selector: '[data-ephox-embed-iri]',
26063                    ceFalseOverride: true,
26064                    styles: {
26065                        marginLeft: 'auto',
26066                        marginRight: 'auto'
26067                    },
26068                    preview: false
26069                }
26070            ],
26071            alignright: [
26072                {
26073                    selector: 'figure.image',
26074                    collapsed: false,
26075                    classes: 'align-right',
26076                    ceFalseOverride: true,
26077                    preview: 'font-family font-size'
26078                },
26079                {
26080                    selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li,pre',
26081                    styles: {
26082                        textAlign: 'right'
26083                    },
26084                    inherit: false,
26085                    preview: 'font-family font-size'
26086                },
26087                {
26088                    selector: 'img,audio,video',
26089                    collapsed: false,
26090                    styles: {
26091                        float: 'right'
26092                    },
26093                    preview: 'font-family font-size'
26094                },
26095                {
26096                    selector: '.mce-placeholder',
26097                    styles: {
26098                        float: 'right'
26099                    },
26100                    ceFalseOverride: true
26101                },
26102                {
26103                    selector: 'table',
26104                    collapsed: false,
26105                    styles: {
26106                        marginRight: '0px',
26107                        marginLeft: 'auto',
26108                    },
26109                    onformat: (table) => {
26110                        // Remove conflicting float style
26111                        dom.setStyle(table, 'float', null);
26112                    },
26113                    preview: 'font-family font-size'
26114                },
26115                {
26116                    selector: '.mce-preview-object,[data-ephox-embed-iri]
vendor: 29,872 bytes, lines 26116-26803
26116',
26117                    ceFalseOverride: true,
26118                    styles: {
26119                        float: 'right'
26120                    },
26121                    preview: false
26122                }
26123            ],
26124            alignjustify: [
26125                {
26126                    selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li,pre',
26127                    styles: {
26128                        textAlign: 'justify'
26129                    },
26130                    inherit: false,
26131                    preview: 'font-family font-size'
26132                }
26133            ],
26134            bold: [
26135                { inline: 'strong', remove: 'all', preserve_attributes: ['class', 'style'] },
26136                { inline: 'span', styles: { fontWeight: 'bold' } },
26137                { inline: 'b', remove: 'all', preserve_attributes: ['class', 'style'] }
26138            ],
26139            italic: [
26140                { inline: 'em', remove: 'all', preserve_attributes: ['class', 'style'] },
26141                { inline: 'span', styles: { fontStyle: 'italic' } },
26142                { inline: 'i', remove: 'all', preserve_attributes: ['class', 'style'] }
26143            ],
26144            underline: [
26145                { inline: 'span', styles: { textDecoration: 'underline' }, exact: true },
26146                { inline: 'u', remove: 'all', preserve_attributes: ['class', 'style'] }
26147            ],
26148            strikethrough: (() => {
26149                const span = { inline: 'span', styles: { textDecoration: 'line-through' }, exact: true };
26150                const strike = { inline: 'strike', remove: 'all', preserve_attributes: ['class', 'style'] };
26151                const s = { inline: 's', remove: 'all', preserve_attributes: ['class', 'style'] };
26152                return schemaType !== 'html4' ? [s, span, strike] : [span, s, strike];
26153            })(),
26154            forecolor: { inline: 'span', styles: { color: '%value' }, links: true, remove_similar: true, clear_child_styles: true },
26155            hilitecolor: { inline: 'span', styles: { backgroundColor: '%value' }, links: true, remove_similar: true, clear_child_styles: true },
26156            fontname: { inline: 'span', toggle: false, styles: { fontFamily: '%value' }, clear_child_styles: true },
26157            fontsize: { inline: 'span', toggle: false, styles: { fontSize: '%value' }, clear_child_styles: true },
26158            lineheight: { selector: 'h1,h2,h3,h4,h5,h6,p,li,td,th,div', styles: { lineHeight: '%value' }, remove_similar: true },
26159            fontsize_class: { inline: 'span', attributes: { class: '%value' } },
26160            blockquote: { block: 'blockquote', wrapper: true, remove: 'all' },
26161            subscript: { inline: 'sub' },
26162            superscript: { inline: 'sup' },
26163            code: { inline: 'code' },
26164            samp: { inline: 'samp' },
26165            link: {
26166                inline: 'a', selector: 'a', remove: 'all', split: true, deep: true,
26167                onmatch: (node, _fmt, _itemName) => {
26168                    return isElement$7(node) && node.hasAttribute('href');
26169                },
26170                onformat: (elm, _fmt, vars) => {
26171                    Tools.each(vars, (value, key) => {
26172                        dom.setAttrib(elm, key, value);
26173                    });
26174                }
26175            },
26176            lang: {
26177                inline: 'span',
26178                clear_child_styles: true,
26179                remove_similar: true,
26180                attributes: {
26181                    'lang': '%value',
26182                    'data-mce-lang': (vars) => vars?.customValue ?? null
26183                }
26184            },
26185            removeformat: [
26186                {
26187                    selector: 'b,strong,em,i,font,u,strike,s,sub,sup,dfn,code,samp,kbd,var,cite,mark,q,del,ins,small',
26188                    remove: 'all',
26189                    split: true,
26190                    expand: false,
26191                    block_expand: true,
26192                    deep: true
26193                },
26194                { selector: 'span', attributes: ['style', 'class'], remove: 'empty', split: true, expand: false, deep: true },
26195                { selector: '*', attributes: ['style', 'class'], split: false, expand: false, deep: true }
26196            ]
26197        };
26198        Tools.each('p h1 h2 h3 h4 h5 h6 div address pre dt dd'.split(/\s/), (name) => {
26199            formats[name] = { block: name, remove: 'all' };
26200        });
26201        return formats;
26202    };
26203
26204    const genericBase = {
26205        remove_similar: true,
26206        inherit: false
26207    };
26208    const cellBase = {
26209        selector: 'td,th',
26210        ...genericBase
26211    };
26212    const cellFormats = {
26213        tablecellbackgroundcolor: {
26214            styles: { backgroundColor: '%value' },
26215            ...cellBase
26216        },
26217        tablecellverticalalign: {
26218            styles: {
26219                'vertical-align': '%value'
26220            },
26221            ...cellBase
26222        },
26223        tablecellbordercolor: {
26224            styles: { borderColor: '%value' },
26225            ...cellBase
26226        },
26227        tablecellclass: {
26228            classes: ['%value'],
26229            ...cellBase
26230        },
26231        tableclass: {
26232            selector: 'table',
26233            classes: ['%value'],
26234            ...genericBase
26235        },
26236        tablecellborderstyle: {
26237            styles: { borderStyle: '%value' },
26238            ...cellBase
26239        },
26240        tablecellborderwidth: {
26241            styles: { borderWidth: '%value' },
26242            ...cellBase
26243        }
26244    };
26245    const get = constant(cellFormats);
26246
26247    const FormatRegistry = (editor) => {
26248        const formats = {};
26249        const get$2 = (name) => isNonNullable(name) ? formats[name] : formats;
26250        const has = (name) => has$2(formats, name);
26251        const register = (name, format) => {
26252            if (name) {
26253                if (!isString(name)) {
26254                    each$d(name, (format, name) => {
26255                        register(name, format);
26256                    });
26257                }
26258                else {
26259                    // Force format into array and add it to internal collection
26260                    if (!isArray$1(format)) {
26261                        format = [format];
26262                    }
26263                    each$e(format, (format) => {
26264                        // Set deep to false by default on selector formats this to avoid removing
26265                        // alignment on images inside paragraphs when alignment is changed on paragraphs
26266                        if (isUndefined(format.deep)) {
26267                            format.deep = !isSelectorFormat(format);
26268                        }
26269                        // Default to true
26270                        if (isUndefined(format.split)) {
26271                            format.split = !isSelectorFormat(format) || isInlineFormat(format);
26272                        }
26273                        // Default to true
26274                        if (isUndefined(format.remove) && isSelectorFormat(format) && !isInlineFormat(format)) {
26275                            format.remove = 'none';
26276                        }
26277                        // Mark format as a mixed format inline + block level
26278                        if (isSelectorFormat(format) && isInlineFormat(format)) {
26279                            format.mixed = true;
26280                            format.block_expand = true;
26281                        }
26282                        // Split classes if needed
26283                        if (isString(format.classes)) {
26284                            format.classes = format.classes.split(/\s+/);
26285                        }
26286                    });
26287                    formats[name] = format;
26288                }
26289            }
26290        };
26291        const unregister = (name) => {
26292            if (name && formats[name]) {
26293                delete formats[name];
26294            }
26295            return formats;
26296        };
26297        register(get$1(editor));
26298        register(get());
26299        register(getFormats(editor));
26300        return {
26301            get: get$2,
26302            has,
26303            register,
26304            unregister
26305        };
26306    };
26307
26308    const each$3 = Tools.each;
26309    const dom = DOMUtils.DOM;
26310    const isPreviewItem = (item) => isNonNullable(item) && isObject(item);
26311    const parsedSelectorToHtml = (ancestry, editor) => {
26312        const schema = editor && editor.schema || Schema({});
26313        const decorate = (elm, item) => {
26314            if (item.classes.length > 0) {
26315                dom.addClass(elm, item.classes.join(' '));
26316            }
26317            dom.setAttribs(elm, item.attrs);
26318        };
26319        const createElement = (sItem) => {
26320            const item = isString(sItem) ? {
26321                name: sItem,
26322                classes: [],
26323                attrs: {}
26324            } : sItem;
26325            const elm = dom.create(item.name);
26326            decorate(elm, item);
26327            return elm;
26328        };
26329        const getRequiredParent = (elm, candidate) => {
26330            const elmRule = schema.getElementRule(elm.nodeName.toLowerCase());
26331            const parentsRequired = elmRule?.parentsRequired;
26332            if (parentsRequired && parentsRequired.length) {
26333                return candidate && contains$2(parentsRequired, candidate) ? candidate : parentsRequired[0];
26334            }
26335            else {
26336                return false;
26337            }
26338        };
26339        const wrapInHtml = (elm, ancestors, siblings) => {
26340            let parentCandidate;
26341            const ancestor = ancestors[0];
26342            const ancestorName = isPreviewItem(ancestor) ? ancestor.name : undefined;
26343            const parentRequired = getRequiredParent(elm, ancestorName);
26344            if (parentRequired) {
26345                if (ancestorName === parentRequired) {
26346                    parentCandidate = ancestor;
26347                    ancestors = ancestors.slice(1);
26348                }
26349                else {
26350                    parentCandidate = parentRequired;
26351                }
26352            }
26353            else if (ancestor) {
26354                parentCandidate = ancestor;
26355                ancestors = ancestors.slice(1);
26356            }
26357            else if (!siblings) {
26358                return elm;
26359            }
26360            // if no more ancestry, wrap in generic div
26361            const parent = parentCandidate ? createElement(parentCandidate) : dom.create('div');
26362            parent.appendChild(elm);
26363            if (siblings) {
26364                Tools.each(siblings, (sibling) => {
26365                    const siblingElm = createElement(sibling);
26366                    parent.insertBefore(siblingElm, elm);
26367                });
26368            }
26369            const parentSiblings = isPreviewItem(parentCandidate) ? parentCandidate.siblings : undefined;
26370            return wrapInHtml(parent, ancestors, parentSiblings);
26371        };
26372        const fragment = dom.create('div');
26373        if (ancestry.length > 0) {
26374            const item = ancestry[0];
26375            const elm = createElement(item);
26376            const siblings = isPreviewItem(item) ? item.siblings : undefined;
26377            fragment.appendChild(wrapInHtml(elm, ancestry.slice(1), siblings));
26378        }
26379        return fragment;
26380    };
26381    const parseSelectorItem = (item) => {
26382        item = Tools.trim(item);
26383        let tagName = 'div';
26384        const obj = {
26385            name: tagName,
26386            classes: [],
26387            attrs: {},
26388            selector: item
26389        };
26390        if (item !== '*') {
26391            // matching IDs, CLASSes, ATTRIBUTES and PSEUDOs
26392            tagName = item.replace(/(?:([#\.]|::?)([\w\-]+)|(\[)([^\]]+)\]?)/g, ($0, $1, $2, $3, $4) => {
26393                switch ($1) {
26394                    case '#':
26395                        obj.attrs.id = $2;
26396                        break;
26397                    case '.':
26398                        obj.classes.push($2);
26399                        break;
26400                    case ':':
26401                        if (Tools.inArray('checked disabled enabled read-only required'.split(' '), $2) !== -1) {
26402                            obj.attrs[$2] = $2;
26403                        }
26404                        break;
26405                }
26406                // attribute matched
26407                if ($3 === '[') {
26408                    const m = $4.match(/([\w\-]+)(?:\=\"([^\"]+))?/);
26409                    if (m) {
26410                        obj.attrs[m[1]] = m[2];
26411                    }
26412                }
26413                return '';
26414            });
26415        }
26416        obj.name = tagName || 'div';
26417        return obj;
26418    };
26419    const parseSelector = (selector) => {
26420        if (!isString(selector)) {
26421            return [];
26422        }
26423        // take into account only first one
26424        selector = selector.split(/\s*,\s*/)[0];
26425        // tighten
26426        selector = selector.replace(/\s*(~\+|~|\+|>)\s*/g, '$1');
26427        // split either on > or on space, but not the one inside brackets
26428        return Tools.map(selector.split(/(?:>|\s+(?![^\[\]]+\]))/), (item) => {
26429            // process each sibling selector separately
26430            const siblings = Tools.map(item.split(/(?:~\+|~|\+)/), parseSelectorItem);
26431            const obj = siblings.pop(); // the last one is our real target
26432            if (siblings.length) {
26433                obj.siblings = siblings;
26434            }
26435            return obj;
26436        }).reverse();
26437    };
26438    const getCssText = (editor, format) => {
26439        let previewCss = '';
26440        let previewStyles = getPreviewStyles(editor);
26441        // No preview forced
26442        if (previewStyles === '') {
26443            return '';
26444        }
26445        // Removes any variables since these can't be previewed
26446        const removeVars = (val) => {
26447            return isString(val) ? val.replace(/%(\w+)/g, '') : '';
26448        };
26449        const getComputedStyle = (name, elm) => {
26450            return dom.getStyle(elm ?? editor.getBody(), name, true);
26451        };
26452        // Create block/inline element to use for preview
26453        if (isString(format)) {
26454            const formats = editor.formatter.get(format);
26455            if (!formats) {
26456                return '';
26457            }
26458            format = formats[0];
26459        }
26460        // Format specific preview override
26461        // TODO: This should probably be further reduced by the previewStyles option
26462        if ('preview' in format) {
26463            const preview = format.preview;
26464            if (preview === false) {
26465                return '';
26466            }
26467            else {
26468                previewStyles = preview || previewStyles;
26469            }
26470        }
26471        let name = format.block || format.inline || 'span';
26472        let previewFrag;
26473        const items = parseSelector(format.selector);
26474        if (items.length > 0) {
26475            if (!items[0].name) { // e.g. something like ul > .someClass was provided
26476                items[0].name = name;
26477            }
26478            name = format.selector;
26479            previewFrag = parsedSelectorToHtml(items, editor);
26480        }
26481        else {
26482            previewFrag = parsedSelectorToHtml([name], editor);
26483        }
26484        const previewElm = dom.select(name, previewFrag)[0] || previewFrag.firstChild;
26485        // Add format styles to preview element
26486        each$3(format.styles, (value, name) => {
26487            const newValue = removeVars(value);
26488            if (newValue) {
26489                dom.setStyle(previewElm, name, newValue);
26490            }
26491        });
26492        // Add attributes to preview element
26493        each$3(format.attributes, (value, name) => {
26494            const newValue = removeVars(value);
26495            if (newValue) {
26496                dom.setAttrib(previewElm, name, newValue);
26497            }
26498        });
26499        // Add classes to preview element
26500        each$3(format.classes, (value) => {
26501            const newValue = removeVars(value);
26502            if (!dom.hasClass(previewElm, newValue)) {
26503                dom.addClass(previewElm, newValue);
26504            }
26505        });
26506        editor.dispatch('PreviewFormats');
26507        // Add the previewElm outside the visual area
26508        dom.setStyles(previewFrag, { position: 'absolute', left: -0xFFFF });
26509        editor.getBody().appendChild(previewFrag);
26510        // Get parent container font size so we can compute px values out of em/% for older IE:s
26511        const rawParentFontSize = getComputedStyle('fontSize');
26512        const parentFontSize = /px$/.test(rawParentFontSize) ? parseInt(rawParentFontSize, 10) : 0;
26513        each$3(previewStyles.split(' '), (name) => {
26514            let value = getComputedStyle(name, previewElm);
26515            // If background is transparent then check if the body has a background color we can use
26516            if (name === 'background-color' && /transparent|rgba\s*\([^)]+,\s*0\)/.test(value)) {
26517                value = getComputedStyle(name);
26518                // Ignore white since it's the default color, not the nicest fix
26519                // TODO: Fix this by detecting runtime style
26520                if (rgbaToHexString(value).toLowerCase() === '#ffffff') {
26521                    return;
26522                }
26523            }
26524            if (name === 'color') {
26525                // Ignore black since it's the default color, not the nicest fix
26526                // TODO: Fix this by detecting runtime style
26527                if (rgbaToHexString(value).toLowerCase() === '#000000') {
26528                    return;
26529                }
26530            }
26531            // Old IE won't calculate the font size so we need to do that manually
26532            if (name === 'font-size') {
26533                if (/em|%$/.test(value)) {
26534                    if (parentFontSize === 0) {
26535                        return;
26536                    }
26537                    // Convert font size from em/% to px
26538                    const numValue = parseFloat(value) / (/%$/.test(value) ? 100 : 1);
26539                    value = (numValue * parentFontSize) + 'px';
26540                }
26541            }
26542            if (name === 'border' && value) {
26543                previewCss += 'padding:0 2px;';
26544            }
26545            previewCss += name + ':' + value + ';';
26546        });
26547        editor.dispatch('AfterPreviewFormats');
26548        // previewCss += 'line-height:normal';
26549        dom.remove(previewFrag);
26550        return previewCss;
26551    };
26552
26553    const setup$x = (editor) => {
26554        // Add some inline shortcuts
26555        editor.addShortcut('meta+b', '', 'Bold');
26556        editor.addShortcut('meta+i', '', 'Italic');
26557        editor.addShortcut('meta+u', '', 'Underline');
26558        // BlockFormat shortcuts keys
26559        for (let i = 1; i <= 6; i++) {
26560            editor.addShortcut('access+' + i, '', ['FormatBlock', false, 'h' + i]);
26561        }
26562        editor.addShortcut('access+7', '', ['FormatBlock', false, 'p']);
26563        editor.addShortcut('access+8', '', ['FormatBlock', false, 'div']);
26564        editor.addShortcut('access+9', '', ['FormatBlock', false, 'address']);
26565    };
26566
26567    const Formatter = (editor) => {
26568        const formats = FormatRegistry(editor);
26569        const formatChangeState = Cell({});
26570        setup$x(editor);
26571        setup$B(editor);
26572        if (!isRtc(editor)) {
26573            setup$A(formatChangeState, editor);
26574        }
26575        return {
26576            /**
26577             * Returns the format by name or all formats if no name is specified.
26578             *
26579             * @method get
26580             * @param {String} name Optional name to retrieve by.
26581             * @return {Array/Object} Array/Object with all registered formats or a specific format.
26582             */
26583            get: formats.get,
26584            /**
26585             * Returns true or false if a format is registered for the specified name.
26586             *
26587             * @method has
26588             * @param {String} name Format name to check if a format exists.
26589             * @return {Boolean} True/False if a format for the specified name exists.
26590             */
26591            has: formats.has,
26592            /**
26593             * Registers a specific format by name.
26594             *
26595             * @method register
26596             * @param {Object/String} name Name of the format for example "bold".
26597             * @param {Object/Array} format Optional format object or array of format variants
26598             * can only be omitted if the first arg is an object.
26599             */
26600            register: formats.register,
26601            /**
26602             * Unregister a specific format by name.
26603             *
26604             * @method unregister
26605             * @param {String} name Name of the format for example "bold".
26606             */
26607            unregister: formats.unregister,
26608            /**
26609             * Applies the specified format to the current selection or specified node.
26610             *
26611             * @method apply
26612             * @param {String} name Name of format to apply.
26613             * @param {Object} vars Optional list of variables to replace within format before applying it.
26614             * @param {Node} node Optional node to apply the format to defaults to current selection.
26615             */
26616            apply: (name, vars, node) => {
26617                applyFormat(editor, name, vars, node);
26618            },
26619            /**
26620             * Removes the specified format from the current selection or specified node.
26621             *
26622             * @method remove
26623             * @param {String} name Name of format to remove.
26624             * @param {Object} vars Optional list of variables to replace within format before removing it.
26625             * @param {Node/Range} node Optional node or DOM range to remove the format from defaults to current selection.
26626             */
26627            remove: (name, vars, node, similar) => {
26628                removeFormat(editor, name, vars, node, similar);
26629            },
26630            /**
26631             * Toggles the specified format on/off.
26632             *
26633             * @method toggle
26634             * @param {String} name Name of format to apply/remove.
26635             * @param {Object} vars Optional list of variables to replace within format before applying/removing it.
26636             * @param {Node} node Optional node to apply the format to or remove from. Defaults to current selection.
26637             */
26638            toggle: (name, vars, node) => {
26639                toggleFormat(editor, name, vars, node);
26640            },
26641            /**
26642             * Matches the current selection or specified node against the specified format name.
26643             *
26644             * @method match
26645             * @param {String} name Name of format to match.
26646             * @param {Object} vars Optional list of variables to replace before checking it.
26647             * @param {Node} node Optional node to check.
26648             * @param {Boolean} similar Optional argument to specify that similar formats should be checked instead of only exact formats.
26649             * @return {Boolean} true/false if the specified selection/node matches the format.
26650             */
26651            match: (name, vars, node, similar) => matchFormat(editor, name, vars, node, similar),
26652            /**
26653             * Finds the closest matching format from a set of formats for the current selection.
26654             *
26655             * @method closest
26656             * @param {Array} names Format names to check for.
26657             * @return {String} The closest matching format name or null.
26658             */
26659            closest: (names) => closestFormat(editor, names),
26660            /**
26661             * Matches the current selection against the array of formats and returns a new array with matching formats.
26662             *
26663             * @method matchAll
26664             * @param {Array} names Name of format to match.
26665             * @param {Object} vars Optional list of variables to replace before checking it.
26666             * @return {Array} Array with matched formats.
26667             */
26668            matchAll: (names, vars) => matchAllFormats(editor, names, vars),
26669            /**
26670             * Return true/false if the specified node has the specified format.
26671             *
26672             * @method matchNode
26673             * @param {Node} node Node to check the format on.
26674             * @param {String} name Format name to check.
26675             * @param {Object} vars Optional list of variables to replace before checking it.
26676             * @param {Boolean} similar Match format that has similar properties.
26677             * @return {Object} Returns the format object it matches or undefined if it doesn't match.
26678             */
26679            matchNode: (node, name, vars, similar) => matchNodeFormat(editor, node, name, vars, similar),
26680            /**
26681             * Returns true/false if the specified format can be applied to the current selection or not. It
26682             * will currently only check the state for selector formats, it returns true on all other format types.
26683             *
26684             * @method canApply
26685             * @param {String} name Name of format to check.
26686             * @return {Boolean} true/false if the specified format can be applied to the current selection/node.
26687             */
26688            canApply: (name) => canApplyFormat(editor, name),
26689            /**
26690             * Executes the specified callback when the current selection matches the formats or not.
26691             *
26692             * @method formatChanged
26693             * @param {String} formats Comma separated list of formats to check for.
26694             * @param {Function} callback Callback with state and args when the format is changed/toggled on/off.
26695             * @param {Boolean} similar True/false state if the match should handle similar or exact formats.
26696             * @param {Object} vars Restrict the format being watched to only match if the variables applied are equal to vars.
26697             */
26698            formatChanged: (formats, callback, similar, vars) => formatChanged(editor, formatChangeState, formats, callback, similar, vars),
26699            /**
26700             * Returns a preview css text for the specified format.
26701             *
26702             * @method getCssText
26703             * @param {String/Object} format Format to generate preview css text for.
26704             * @return {String} Css text for the specified format.
26705             * @example
26706             * const cssText1 = editor.formatter.getCssText('bold');
26707             * const cssText2 = editor.formatter.getCssText({ inline: 'b' });
26708             */
26709            getCssText: curry(getCssText, editor)
26710        };
26711    };
26712
26713    // Avoid adding non-typing undo levels for commands that could cause duplicate undo levels to be created
26714    // or do not alter the editor content or selection in any way
26715    const shouldIgnoreCommand = (cmd) => {
26716        switch (cmd.toLowerCase()) {
26717            case 'undo':
26718            case 'redo':
26719            case 'mcefocus':
26720                return true;
26721            default:
26722                return false;
26723        }
26724    };
26725    const registerEvents = (editor, undoManager, locks) => {
26726        const isFirstTypedCharacter = Cell(false);
26727        const addNonTypingUndoLevel = (e) => {
26728            setTyping(undoManager, false, locks);
26729            undoManager.add({}, e);
26730        };
26731        // Add initial undo level when the editor is initialized
26732        editor.on('init', () => {
26733            undoManager.add();
26734        });
26735        // Get position before an execCommand is processed
26736        editor.on('BeforeExecCommand', (e) => {
26737            const cmd = e.command;
26738            if (!shouldIgnoreCommand(cmd)) {
26739                endTyping(undoManager, locks);
26740                undoManager.beforeChange();
26741            }
26742        });
26743        // Add undo level after an execCommand call was made
26744        editor.on('ExecCommand', (e) => {
26745            const cmd = e.command;
26746            if (!shouldIgnoreCommand(cmd)) {
26747                addNonTypingUndoLevel(e);
26748            }
26749        });
26750        editor.on('ObjectResizeStart cut', () => {
26751            undoManager.beforeChange();
26752        });
26753        editor.on('SaveContent ObjectResized blur', addNonTypingUndoLevel);
26754        editor.on('dragend', addNonTypingUndoLevel);
26755        editor.on('keyup', (e) => {
26756            const keyCode = e.keyCode;
26757            // If key is prevented then don't add undo level
26758            // This would happen on keyboard shortcuts for example
26759            if (e.isDefaultPrevented()) {
26760                return;
26761            }
26762            const isMeta = Env.os.isMacOS() && e.key === 'Meta';
26763            if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode === 45 || e.ctrlKey || isMeta) {
26764                addNonTypingUndoLevel();
26765                editor.nodeChanged();
26766            }
26767            if (keyCode === 46 || keyCode === 8) {
26768                editor.nodeChanged();
26769            }
26770            // Fire a TypingUndo event on the first character entered
26771            if (isFirstTypedCharacter.get() && undoManager.typing && !isEq$1(editor.readonly, createFromEditor(editor), undoManager.data[0])) {
26772                if (!editor.isDirty()) {
26773                    editor.setDirty(true);
26774                }
26775                editor.dispatch('TypingUndo');
26776                isFirstTypedCharacter.set(false);
26777                editor.nodeChanged();
26778            }
26779        });
26780        editor.on('keydown', (e) => {
26781            const keyCode = e.keyCode;
26782            // If key is prevented then don't add undo level
26783            // This would happen on keyboard shortcuts for example
26784            if (e.isDefaultPrevented()) {
26785                return;
26786            }
26787            // Is character position keys left,right,up,down,home,end,pgdown,pgup,enter
26788            if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode === 45) {
26789                if (undoManager.typing) {
26790                    addNonTypingUndoLevel(e);
26791                }
26792                return;
26793            }
26794            // If key isn't Ctrl+Alt/AltGr
26795            const modKey = (e.ctrlKey && !e.altKey) || e.metaKey;
26796            if ((keyCode < 16 || keyCode > 20) && keyCode !== 224 && keyCode !== 91 && !undoManager.typing && !modKey) {
26797                undoManager.beforeChange();
26798                setTyping(undoManager, true, locks);
26799                undoManager.add({}, e);
26800                isFirstTypedCharacter.set(true);
26801                return;
26802            }
26803            const has
vendor: 2,744 bytes, lines 26803-26863
26803OnlyMetaOrCtrlModifier = Env.os.isMacOS() ? e.metaKey : e.ctrlKey && !e.altKey;
26804            if (hasOnlyMetaOrCtrlModifier) {
26805                undoManager.beforeChange();
26806            }
26807        });
26808        editor.on('mousedown', (e) => {
26809            if (undoManager.typing) {
26810                addNonTypingUndoLevel(e);
26811            }
26812        });
26813        // Special inputType, currently only Chrome implements this: https://www.w3.org/TR/input-events-2/#x5.1.2-attributes
26814        const isInsertReplacementText = (event) => event.inputType === 'insertReplacementText';
26815        // Safari just shows inputType `insertText` but with data set to null so we can use that
26816        const isInsertTextDataNull = (event) => event.inputType === 'insertText' && event.data === null;
26817        const isInsertFromPasteOrDrop = (event) => event.inputType === 'insertFromPaste' || event.inputType === 'insertFromDrop';
26818        // For detecting when user has replaced text using the browser built-in spell checker or paste/drop events
26819        editor.on('input', (e) => {
26820            if (e.inputType && (isInsertReplacementText(e) || isInsertTextDataNull(e) || isInsertFromPasteOrDrop(e))) {
26821                addNonTypingUndoLevel(e);
26822            }
26823        });
26824        editor.on('AddUndo Undo Redo ClearUndos', (e) => {
26825            if (!e.isDefaultPrevented()) {
26826                editor.nodeChanged();
26827            }
26828        });
26829    };
26830    const addKeyboardShortcuts = (editor) => {
26831        editor.addShortcut('meta+z', '', 'Undo');
26832        editor.addShortcut('meta+y,meta+shift+z', '', 'Redo');
26833    };
26834
26835    /**
26836     * This class handles the undo/redo history levels for the editor. Since the built-in undo/redo has major drawbacks a custom one was needed.
26837     *
26838     * @class tinymce.UndoManager
26839     */
26840    const UndoManager = (editor) => {
26841        const beforeBookmark = value$1();
26842        const locks = Cell(0);
26843        const index = Cell(0);
26844        /* eslint consistent-this:0 */
26845        const undoManager = {
26846            data: [], // Gets mutated both internally and externally by plugins like remark, not documented
26847            /**
26848             * State if the user is currently typing or not. This will add a typing operation into one undo
26849             * level instead of one new level for each keystroke.
26850             *
26851             * @field {Boolean} typing
26852             */
26853            typing: false,
26854            /**
26855             * Stores away a bookmark to be used when performing an undo action so that the selection is before
26856             * the change has been made.
26857             *
26858             * @method beforeChange
26859             */
26860            beforeChange: () => {
26861                beforeChange(editor, locks, beforeBookmark);
26862            },
26863            /**
26864             * Adds a new undo level/snapshot to the undo list.
26865             *
26866             * @method add
26867             * @param {Object} level Optional undo level object to add.
26868             * @param {EditorEvent} event Optional event responsible for the creation of the undo level.
26869             * @return {Object} Undo level that got added or null if a level wasn't needed.
26870             */
26871            add: (level, event) => {
26872                return addUndoLevel(editor, undoManager, index, locks, beforeBookmark, level, event);
26873            },
26874            /**
26875             * Dispatch a change event with current editor status as level and current undoManager layer as lastLevel
26876             *
26877             * @method dispatchChange
26878             */
26879            dispatchChange: () => {
26880                editor.setDirty(true);
26881                const level = createFromEditor(editor);
26882                level.bookmark = getUndoBookmark(editor.selection);
26883                editor.dispatch('change', {
26884                    level,
26885                    lastLevel: get$b
vendor: 10,509 bytes, lines 26885-27127
26885(undoManager.data, index.get()).getOrUndefined()
26886                });
26887            },
26888            /**
26889             * Undoes the last action.
26890             *
26891             * @method undo
26892             * @return {Object} Undo level or null if no undo was performed.
26893             */
26894            undo: () => {
26895                return undo(editor, undoManager, locks, index);
26896            },
26897            /**
26898             * Redoes the last action.
26899             *
26900             * @method redo
26901             * @return {Object} Redo level or null if no redo was performed.
26902             */
26903            redo: () => {
26904                return redo(editor, index, undoManager.data);
26905            },
26906            /**
26907             * Removes all undo levels.
26908             *
26909             * @method clear
26910             */
26911            clear: () => {
26912                clear(editor, undoManager, index);
26913            },
26914            /**
26915             * Resets the undo manager levels by clearing all levels and then adding an initial level.
26916             *
26917             * @method reset
26918             */
26919            reset: () => {
26920                reset(editor, undoManager);
26921            },
26922            /**
26923             * Returns true/false if the undo manager has any undo levels.
26924             *
26925             * @method hasUndo
26926             * @return {Boolean} true/false if the undo manager has any undo levels.
26927             */
26928            hasUndo: () => {
26929                return hasUndo(editor, undoManager, index);
26930            },
26931            /**
26932             * Returns true/false if the undo manager has any redo levels.
26933             *
26934             * @method hasRedo
26935             * @return {Boolean} true/false if the undo manager has any redo levels.
26936             */
26937            hasRedo: () => {
26938                return hasRedo(editor, undoManager, index);
26939            },
26940            /**
26941             * Executes the specified mutator function as an undo transaction. The selection
26942             * before the modification will be stored to the undo stack and if the DOM changes
26943             * it will add a new undo level. Any logic within the translation that adds undo levels will
26944             * be ignored. So a translation can include calls to execCommand or editor.insertContent.
26945             *
26946             * @method transact
26947             * @param {Function} callback Function that gets executed and has dom manipulation logic in it.
26948             * @return {Object} Undo level that got added or null it a level wasn't needed.
26949             */
26950            transact: (callback) => {
26951                return transact(editor, undoManager, locks, callback);
26952            },
26953            /**
26954             * Executes the specified mutator function as an undo transaction. But without adding an undo level.
26955             * Any logic within the translation that adds undo levels will be ignored. So a translation can
26956             * include calls to execCommand or editor.insertContent.
26957             *
26958             * @method ignore
26959             * @param {Function} callback Function that gets executed and has dom manipulation logic in it.
26960             */
26961            ignore: (callback) => {
26962                ignore(editor, locks, callback);
26963            },
26964            /**
26965             * Adds an extra "hidden" undo level by first applying the first mutation and store that to the undo stack
26966             * then roll back that change and do the second mutation on top of the stack. This will produce an extra
26967             * undo level that the user doesn't see until they undo.
26968             *
26969             * @method extra
26970             * @param {Function} callback1 Function that does mutation but gets stored as a "hidden" extra undo level.
26971             * @param {Function} callback2 Function that does mutation but gets displayed to the user.
26972             */
26973            extra: (callback1, callback2) => {
26974                extra(editor, undoManager, index, callback1, callback2);
26975            }
26976        };
26977        if (!isRtc(editor)) {
26978            registerEvents(editor, undoManager, locks);
26979        }
26980        addKeyboardShortcuts(editor);
26981        return undoManager;
26982    };
26983
26984    const nonTypingKeycodes = [
26985        // tab, esc, home, end
26986        9, 27, VK.HOME, VK.END,
26987        // pause, capslock, print screen, numlock, scroll lock
26988        19, 20, 44, 144, 145,
26989        // page up/down, insert
26990        33, 34, 45,
26991        // alt, shift, ctrl
26992        16, 17, 18,
26993        // meta/windows key
26994        91, 92, 93,
26995        // direction
26996        VK.DOWN, VK.UP, VK.LEFT, VK.RIGHT
26997    ].concat(
26998    // Meta key on firefox is different
26999    Env.browser.isFirefox() ? [224] : []);
27000    const placeholderAttr = 'data-mce-placeholder';
27001    const isKeyboardEvent = (e) => e.type === 'keydown' || e.type === 'keyup';
27002    const isDeleteEvent = (e) => {
27003        const keyCode = e.keyCode;
27004        return keyCode === VK.BACKSPACE || keyCode === VK.DELETE;
27005    };
27006    const isNonTypingKeyboardEvent = (e) => {
27007        if (isKeyboardEvent(e)) {
27008            const keyCode = e.keyCode;
27009            // Ctrl/Meta/Alt key pressed, F1-12 or non typing keycode
27010            return !isDeleteEvent(e) && (VK.metaKeyPressed(e) || e.altKey || keyCode >= 112 && keyCode <= 123 || contains$2(nonTypingKeycodes, keyCode));
27011        }
27012        else {
27013            return false;
27014        }
27015    };
27016    const isTypingKeyboardEvent = (e) => 
27017    // 229 === Unidentified, so since we don't know what it is treat it as a non typing event on keyup but as a typing event on keydown
27018    // Android will generally always send a 229 keycode since it uses an IME to input text
27019    isKeyboardEvent(e) && !(isDeleteEvent(e) || e.type === 'keyup' && e.keyCode === 229);
27020    const isVisuallyEmpty = (dom, rootElm, forcedRootBlock) => {
27021        if (dom.isEmpty(rootElm, undefined, { skipBogus: false, includeZwsp: true })) {
27022            // Ensure the node matches the forced_root_block setting, as the content could be an empty list, etc...
27023            // and also check that the content isn't indented
27024            const firstElement = rootElm.firstElementChild;
27025            if (!firstElement) {
27026                return true;
27027            }
27028            else if (dom.getStyle(rootElm.firstElementChild, 'padding-left') || dom.getStyle(rootElm.firstElementChild, 'padding-right')) {
27029                return false;
27030            }
27031            else {
27032                return forcedRootBlock === firstElement.nodeName.toLowerCase();
27033            }
27034        }
27035        else {
27036            return false;
27037        }
27038    };
27039    const setup$w = (editor) => {
27040        const dom = editor.dom;
27041        const rootBlock = getForcedRootBlock(editor);
27042        const placeholder = getPlaceholder(editor) ?? '';
27043        const updatePlaceholder = (e, initial) => {
27044            if (isNonTypingKeyboardEvent(e)) {
27045                return;
27046            }
27047            // Check to see if we should show the placeholder
27048            const body = editor.getBody();
27049            const showPlaceholder = isTypingKeyboardEvent(e) ? false : isVisuallyEmpty(dom, body, rootBlock);
27050            // Update the attribute as required
27051            const isPlaceholderShown = dom.getAttrib(body, placeholderAttr) !== '';
27052            if (isPlaceholderShown !== showPlaceholder || initial) {
27053                dom.setAttrib(body, placeholderAttr, showPlaceholder ? placeholder : null);
27054                firePlaceholderToggle(editor, showPlaceholder);
27055                // Swap the key listener state
27056                editor.on(showPlaceholder ? 'keydown' : 'keyup', updatePlaceholder);
27057                editor.off(showPlaceholder ? 'keyup' : 'keydown', updatePlaceholder);
27058            }
27059        };
27060        if (isNotEmpty(placeholder)) {
27061            editor.on('init', (e) => {
27062                // Setup the initial state
27063                updatePlaceholder(e, true);
27064                editor.on('change SetContent ExecCommand', updatePlaceholder);
27065                // TINY-4828: Update the placeholder after pasting content. This needs to use a timeout as
27066                // the browser doesn't update the dom until after the paste event has fired
27067                editor.on('paste', (e) => Delay.setEditorTimeout(editor, () => updatePlaceholder(e)));
27068            });
27069        }
27070    };
27071
27072    const createAndFireInputEvent = (eventType) => (editor, inputType, specifics = {}) => {
27073        const target = editor.getBody();
27074        const overrides = {
27075            bubbles: true,
27076            composed: true,
27077            data: null,
27078            isComposing: false,
27079            detail: 0,
27080            view: null,
27081            target,
27082            currentTarget: target,
27083            eventPhase: Event.AT_TARGET,
27084            originalTarget: target,
27085            explicitOriginalTarget: target,
27086            isTrusted: false,
27087            srcElement: target,
27088            cancelable: false,
27089            preventDefault: noop,
27090            inputType
27091        };
27092        const input = clone$2(new InputEvent(eventType));
27093        return editor.dispatch(eventType, { ...input, ...overrides, ...specifics });
27094    };
27095    const fireInputEvent = createAndFireInputEvent('input');
27096    const fireBeforeInputEvent = createAndFireInputEvent('beforeinput');
27097
27098    const symulateDelete = (editor, isForward, deleteFun) => {
27099        // Some delete actions may prevent the input event from being fired. If we do not detect it, we fire it ourselves.
27100        let shouldFireInput = true;
27101        const inputHandler = () => shouldFireInput = false;
27102        const beforeInputEvent = fireBeforeInputEvent(editor, isForward ? 'deleteContentForward' : 'deleteContentBackward');
27103        if (beforeInputEvent.isDefaultPrevented()) {
27104            return false;
27105        }
27106        editor.on('input', inputHandler);
27107        try {
27108            deleteFun();
27109        }
27110        finally {
27111            editor.off('input', inputHandler);
27112        }
27113        if (shouldFireInput) {
27114            editor.dispatch('input');
27115        }
27116        return true;
27117    };
27118
27119    const matchNodeName = (name) => (node) => isNonNullable(node) && node.nodeName.toLowerCase() === name;
27120    const matchNodeNames = (regex) => (node) => isNonNullable(node) && regex.test(node.nodeName);
27121    const isTextNode$1 = (node) => isNonNullable(node) && node.nodeType === 3;
27122    const isElement$1 = (node) => isNonNullable(node) && node.nodeType === 1;
27123    const isListNode = matchNodeNames(/^(OL|UL|DL)$/);
27124    const isOlUlNode = matchNodeNames(/^(OL|UL)$/);
27125    const isListItemNode = matchNodeNames(/^(LI|DT|DD)$/);
27126    const isDlItemNode = matchNodeNames(/^(DT|DD)$/);
27127    const is
vendor: 19,327 bytes, lines 27127-27570
27127Br$1 = matchNodeName('br');
27128    const isFirstChild$1 = (node) => node.parentNode?.firstChild === node;
27129    const isTextBlock = (editor, node) => isNonNullable(node) && node.nodeName in editor.schema.getTextBlockElements();
27130    const isBlock = (node, blockElements) => isNonNullable(node) && node.nodeName in blockElements;
27131    const isVoid = (editor, node) => isNonNullable(node) && node.nodeName in editor.schema.getVoidElements();
27132    const isBogusBr = (dom, node) => {
27133        if (!isBr$1(node)) {
27134            return false;
27135        }
27136        return dom.isBlock(node.nextSibling) && !isBr$1(node.previousSibling);
27137    };
27138    const isEmpty$1 = (dom, elm, keepBookmarks) => {
27139        const empty = dom.isEmpty(elm);
27140        if (keepBookmarks && dom.select('span[data-mce-type=bookmark]', elm).length > 0) {
27141            return false;
27142        }
27143        return empty;
27144    };
27145    const isChildOfBody = (dom, elm) => dom.isChildOf(elm, dom.getRoot());
27146
27147    const DOM$9 = DOMUtils.DOM;
27148    const normalizeList = (dom, list) => {
27149        const parentNode = list.parentElement;
27150        // Move UL/OL to previous LI if it's the only child of a LI
27151        if (parentNode && parentNode.nodeName === 'LI' && parentNode.firstChild === list) {
27152            const sibling = parentNode.previousSibling;
27153            if (sibling && sibling.nodeName === 'LI') {
27154                sibling.appendChild(list);
27155                if (isEmpty$1(dom, parentNode)) {
27156                    DOM$9.remove(parentNode);
27157                }
27158            }
27159            else {
27160                DOM$9.setStyle(parentNode, 'listStyleType', 'none');
27161            }
27162        }
27163        // Append OL/UL to previous LI if it's in a parent OL/UL i.e. old HTML4
27164        if (isListNode(parentNode)) {
27165            const sibling = parentNode.previousSibling;
27166            if (sibling && sibling.nodeName === 'LI') {
27167                sibling.appendChild(list);
27168            }
27169        }
27170    };
27171    const normalizeLists = (dom, element) => {
27172        const lists = Tools.grep(dom.select('ol,ul', element));
27173        Tools.each(lists, (list) => {
27174            normalizeList(dom, list);
27175        });
27176    };
27177
27178    const getNormalizedPoint = (container, offset) => {
27179        if (isTextNode$1(container)) {
27180            return { container, offset };
27181        }
27182        const node = RangeUtils.getNode(container, offset);
27183        if (isTextNode$1(node)) {
27184            return {
27185                container: node,
27186                offset: offset >= container.childNodes.length ? node.data.length : 0
27187            };
27188        }
27189        else if (node.previousSibling && isTextNode$1(node.previousSibling)) {
27190            return {
27191                container: node.previousSibling,
27192                offset: node.previousSibling.data.length
27193            };
27194        }
27195        else if (node.nextSibling && isTextNode$1(node.nextSibling)) {
27196            return {
27197                container: node.nextSibling,
27198                offset: 0
27199            };
27200        }
27201        return { container, offset };
27202    };
27203    const normalizeRange = (rng) => {
27204        const outRng = rng.cloneRange();
27205        const rangeStart = getNormalizedPoint(rng.startContainer, rng.startOffset);
27206        outRng.setStart(rangeStart.container, rangeStart.offset);
27207        const rangeEnd = getNormalizedPoint(rng.endContainer, rng.endOffset);
27208        outRng.setEnd(rangeEnd.container, rangeEnd.offset);
27209        return outRng;
27210    };
27211
27212    const listNames = ['OL', 'UL', 'DL'];
27213    const listSelector = listNames.join(',');
27214    const getParentList = (editor, node) => {
27215        const selectionStart = node || editor.selection.getStart(true);
27216        return editor.dom.getParent(selectionStart, listSelector, getClosestListHost(editor, selectionStart, editor.selection.isCollapsed()));
27217    };
27218    const isParentListSelected = (parentList, selectedBlocks) => isNonNullable(parentList) && selectedBlocks.length === 1 && selectedBlocks[0] === parentList;
27219    const findSubLists = (parentList) => filter$5(parentList.querySelectorAll(listSelector), isListNode);
27220    const getSelectedSubLists = (editor) => {
27221        const parentList = getParentList(editor);
27222        const selectedBlocks = editor.selection.getSelectedBlocks();
27223        if (isParentListSelected(parentList, selectedBlocks)) {
27224            return findSubLists(parentList);
27225        }
27226        else {
27227            return filter$5(selectedBlocks, (elm) => {
27228                return isListNode(elm) && parentList !== elm;
27229            });
27230        }
27231    };
27232    const findParentListItemsNodes = (editor, elms, isCollapsed) => {
27233        const listItemsElms = Tools.map(elms, (elm) => {
27234            const parentLi = editor.dom.getParent(elm, 'li,dd,dt', getClosestListHost(editor, elm, isCollapsed));
27235            return parentLi ? parentLi : elm;
27236        });
27237        return unique$1(listItemsElms);
27238    };
27239    const getSelectedListItems = (editor) => {
27240        const selectedBlocks = editor.selection.getSelectedBlocks();
27241        return filter$5(findParentListItemsNodes(editor, selectedBlocks, editor.selection.isCollapsed()), isListItemNode);
27242    };
27243    const getSelectedDlItems = (editor) => filter$5(getSelectedListItems(editor), isDlItemNode);
27244    const getClosestEditingHost = (editor, elm) => {
27245        const parentTableCell = editor.dom.getParents(elm, 'TD,TH');
27246        return parentTableCell.length > 0 ? parentTableCell[0] : editor.getBody();
27247    };
27248    const isListHost = (schema, node) => !isListNode(node) && !isListItemNode(node) && exists(listNames, (listName) => schema.isValidChild(node.nodeName, listName));
27249    const getClosestListHost = (editor, elm, isCollapsed) => {
27250        const parentBlocks = editor.dom.getParents(elm, editor.dom.isBlock);
27251        const isNotForcedRootBlock = (elm) => elm.nodeName.toLowerCase() !== getForcedRootBlock(editor);
27252        const parentBlock = find$2(parentBlocks, (elm) => (!isCollapsed || isNotForcedRootBlock(elm)) && isListHost(editor.schema, elm));
27253        return parentBlock.getOr(editor.getBody());
27254    };
27255    const isListInsideAnLiWithFirstAndLastNotListElement = (list) => parent(list).exists((parent) => isListItemNode(parent.dom)
27256        && firstChild(parent).exists((firstChild) => !isListNode(firstChild.dom))
27257        && lastChild(parent).exists((lastChild) => !isListNode(lastChild.dom)));
27258    const findLastParentListNode = (editor, elm) => {
27259        const parentLists = editor.dom.getParents(elm, 'ol,ul', getClosestListHost(editor, elm, true));
27260        return last$2(parentLists);
27261    };
27262    const getSelectedLists = (editor) => {
27263        const firstList = findLastParentListNode(editor, editor.selection.getStart());
27264        const subsequentLists = filter$5(editor.selection.getSelectedBlocks(), isOlUlNode);
27265        return firstList.toArray().concat(subsequentLists);
27266    };
27267    const getParentLists = (editor) => {
27268        const elm = editor.selection.getStart();
27269        return editor.dom.getParents(elm, 'ol,ul', getClosestListHost(editor, elm, editor.selection.isCollapsed()));
27270    };
27271    const getSelectedListRoots = (editor) => {
27272        const selectedLists = getSelectedLists(editor);
27273        const parentLists = getParentLists(editor);
27274        return find$2(parentLists, (p) => isListInsideAnLiWithFirstAndLastNotListElement(SugarElement.fromDom(p))).fold(() => getUniqueListRoots(editor, selectedLists), (l) => [l]);
27275    };
27276    const getUniqueListRoots = (editor, lists) => {
27277        const listRoots = map$3(lists, (list) => findLastParentListNode(editor, list).getOr(list));
27278        return unique$1(listRoots);
27279    };
27280
27281    const isCustomList = (list) => /\btox\-/.test(list.className);
27282    // Advlist/core/ListUtils.ts - Duplicated in Advlist plugin
27283    const isWithinNonEditable = (editor, element) => element !== null && !editor.dom.isEditable(element);
27284    const selectionIsWithinNonEditableList = (editor) => {
27285        const parentList = getParentList(editor);
27286        return isWithinNonEditable(editor, parentList) || !editor.selection.isEditable();
27287    };
27288    const isWithinNonEditableList$1 = (editor, element) => {
27289        const parentList = editor.dom.getParent(element, 'ol,ul,dl');
27290        return isWithinNonEditable(editor, parentList) || !editor.selection.isEditable();
27291    };
27292
27293    const fireListEvent = (editor, action, element) => editor.dispatch('ListMutation', { action, element });
27294
27295    const createTextBlock$1 = (editor, contentNode, attrs = {}) => {
27296        const dom = editor.dom;
27297        const blockElements = editor.schema.getBlockElements();
27298        const fragment = dom.createFragment();
27299        const blockName = getForcedRootBlock(editor);
27300        const blockAttrs = getForcedRootBlockAttrs(editor);
27301        let node;
27302        let textBlock;
27303        let hasContentNode = false;
27304        textBlock = dom.create(blockName, {
27305            ...blockAttrs,
27306            ...(attrs.style ? { style: attrs.style } : {})
27307        });
27308        if (!isBlock(contentNode.firstChild, blockElements)) {
27309            fragment.appendChild(textBlock);
27310        }
27311        while ((node = contentNode.firstChild)) {
27312            const nodeName = node.nodeName;
27313            if (!hasContentNode && (nodeName !== 'SPAN' || node.getAttribute('data-mce-type') !== 'bookmark')) {
27314                hasContentNode = true;
27315            }
27316            if (isBlock(node, blockElements)) {
27317                fragment.appendChild(node);
27318                textBlock = null;
27319            }
27320            else {
27321                if (!textBlock) {
27322                    textBlock = dom.create(blockName, blockAttrs);
27323                    fragment.appendChild(textBlock);
27324                }
27325                textBlock.appendChild(node);
27326            }
27327        }
27328        // BR is needed in empty blocks
27329        if (!hasContentNode && textBlock) {
27330            textBlock.appendChild(dom.create('br', { 'data-mce-bogus': '1' }));
27331        }
27332        return fragment;
27333    };
27334
27335    const isList = (el) => is$1(el, 'OL,UL');
27336    const isListItem$1 = (el) => is$1(el, 'LI');
27337    const hasFirstChildList = (el) => firstChild(el).exists(isList);
27338    const hasLastChildList = (el) => lastChild(el).exists(isList);
27339    const canIncreaseDepthOfList = (editor, amount) => {
27340        return getListMaxDepth(editor).map((max) => max >= amount).getOr(true);
27341    };
27342
27343    const isEntryList = (entry) => 'listAttributes' in entry;
27344    const isEntryComment = (entry) => 'isComment' in entry;
27345    const isEntryFragment = (entry) => 'isFragment' in entry;
27346    const isIndented = (entry) => entry.depth > 0;
27347    const isSelected = (entry) => entry.isSelected;
27348    const cloneItemContent = (li) => {
27349        const children = children$1(li);
27350        const content = hasLastChildList(li) ? children.slice(0, -1) : children;
27351        return map$3(content, deep);
27352    };
27353    const createEntry = (li, depth, isSelected) => parent(li).filter(isElement$8).map((list) => ({
27354        depth,
27355        dirty: false,
27356        isSelected,
27357        content: cloneItemContent(li),
27358        itemAttributes: clone$4(li),
27359        listAttributes: clone$4(list),
27360        listType: name(list),
27361        isInPreviousLi: false
27362    }));
27363
27364    const joinSegment = (parent, child) => {
27365        append$1(parent.item, child.list);
27366    };
27367    const joinSegments = (segments) => {
27368        for (let i = 1; i < segments.length; i++) {
27369            joinSegment(segments[i - 1], segments[i]);
27370        }
27371    };
27372    const appendSegments = (head$1, tail) => {
27373        lift2(last$2(head$1), head(tail), joinSegment);
27374    };
27375    const createSegment = (scope, listType) => {
27376        const segment = {
27377            list: SugarElement.fromTag(listType, scope),
27378            item: SugarElement.fromTag('li', scope)
27379        };
27380        append$1(segment.list, segment.item);
27381        return segment;
27382    };
27383    const createSegments = (scope, entry, size) => {
27384        const segments = [];
27385        for (let i = 0; i < size; i++) {
27386            segments.push(createSegment(scope, isEntryList(entry) ? entry.listType : entry.parentListType));
27387        }
27388        return segments;
27389    };
27390    const populateSegments = (segments, entry) => {
27391        for (let i = 0; i < segments.length - 1; i++) {
27392            set$2(segments[i].item, 'list-style-type', 'none');
27393        }
27394        last$2(segments).each((segment) => {
27395            if (isEntryList(entry)) {
27396                setAll$1(segment.list, entry.listAttributes);
27397                setAll$1(segment.item, entry.itemAttributes);
27398            }
27399            append(segment.item, entry.content);
27400        });
27401    };
27402    const normalizeSegment = (segment, entry) => {
27403        if (name(segment.list) !== entry.listType) {
27404            segment.list = mutate(segment.list, entry.listType);
27405        }
27406        setAll$1(segment.list, entry.listAttributes);
27407    };
27408    const createItem = (scope, attr, content) => {
27409        const item = SugarElement.fromTag('li', scope);
27410        setAll$1(item, attr);
27411        append(item, content);
27412        return item;
27413    };
27414    const appendItem = (segment, item) => {
27415        append$1(segment.list, item);
27416        segment.item = item;
27417    };
27418    const writeShallow = (scope, cast, entry) => {
27419        const newCast = cast.slice(0, entry.depth);
27420        last$2(newCast).each((segment) => {
27421            if (isEntryList(entry)) {
27422                const item = createItem(scope, entry.itemAttributes, entry.content);
27423                appendItem(segment, item);
27424                normalizeSegment(segment, entry);
27425            }
27426            else if (isEntryFragment(entry)) {
27427                append(segment.item, entry.content);
27428            }
27429            else {
27430                const item = SugarElement.fromHtml(`<!--${entry.content}-->`);
27431                append$1(segment.list, item);
27432            }
27433        });
27434        return newCast;
27435    };
27436    const writeDeep = (scope, cast, entry) => {
27437        const segments = createSegments(scope, entry, entry.depth - cast.length);
27438        joinSegments(segments);
27439        populateSegments(segments, entry);
27440        appendSegments(cast, segments);
27441        return cast.concat(segments);
27442    };
27443    const composeList = (scope, entries) => {
27444        let firstCommentEntryOpt = Optional.none();
27445        const cast = foldl(entries, (cast, entry, i) => {
27446            if (!isEntryComment(entry)) {
27447                return entry.depth > cast.length ? writeDeep(scope, cast, entry) : writeShallow(scope, cast, entry);
27448            }
27449            else {
27450                // this is needed becuase if the first element of the list is a comment we would not have the data to create the new list
27451                if (i === 0) {
27452                    firstCommentEntryOpt = Optional.some(entry);
27453                    return cast;
27454                }
27455                return writeShallow(scope, cast, entry);
27456            }
27457        }, []);
27458        firstCommentEntryOpt.each((firstCommentEntry) => {
27459            const item = SugarElement.fromHtml(`<!--${firstCommentEntry.content}-->`);
27460            head(cast).each((fistCast) => {
27461                prepend(fistCast.list, item);
27462            });
27463        });
27464        return head(cast).map((segment) => segment.list);
27465    };
27466
27467    const indentEntry = (editor, indentation, entry) => {
27468        switch (indentation) {
27469            case "Indent" /* Indentation.Indent */:
27470                if (canIncreaseDepthOfList(editor, entry.depth)) {
27471                    entry.depth++;
27472                }
27473                else {
27474                    return;
27475                }
27476                break;
27477            case "Outdent" /* Indentation.Outdent */:
27478                entry.depth--;
27479                break;
27480            case "Flatten" /* Indentation.Flatten */:
27481                entry.depth = 0;
27482        }
27483        entry.dirty = true;
27484    };
27485
27486    const cloneListProperties = (target, source) => {
27487        if (isEntryList(target) && isEntryList(source)) {
27488            target.listType = source.listType;
27489            target.listAttributes = { ...source.listAttributes };
27490        }
27491    };
27492    const cleanListProperties = (entry) => {
27493        // Remove the start attribute if generating a new list
27494        entry.listAttributes = filter$4(entry.listAttributes, (_value, key) => key !== 'start');
27495    };
27496    // Closest entry above/below in the same list
27497    const closestSiblingEntry = (entries, start) => {
27498        const depth = entries[start].depth;
27499        // Ignore dirty items as they've been moved and won't have the right list data yet
27500        const matches = (entry) => entry.depth === depth && !entry.dirty;
27501        const until = (entry) => entry.depth < depth;
27502        // Check in reverse to see if there's an entry as the same depth before the current entry
27503        // but if not, then try to walk forwards as well
27504        return findUntil$1(reverse(entries.slice(0, start)), matches, until)
27505            .orThunk(() => findUntil$1(entries.slice(start + 1), matches, until));
27506    };
27507    const normalizeEntries = (entries) => {
27508        each$e(entries, (entry, i) => {
27509            closestSiblingEntry(entries, i).fold(() => {
27510                if (entry.dirty && isEntryList(entry)) {
27511                    cleanListProperties(entry);
27512                }
27513            }, (matchingEntry) => cloneListProperties(entry, matchingEntry));
27514        });
27515        return entries;
27516    };
27517
27518    const parseSingleItem = (depth, itemSelection, selectionState, item) => {
27519        if (isComment$1(item)) {
27520            return [{
27521                    depth: depth + 1,
27522                    content: item.dom.nodeValue ?? '',
27523                    dirty: false,
27524                    isSelected: false,
27525                    isComment: true
27526                }];
27527        }
27528        itemSelection.each((selection) => {
27529            if (eq(selection.start, item)) {
27530                selectionState.set(true);
27531            }
27532        });
27533        const currentItemEntry = createEntry(item, depth, selectionState.get());
27534        // Update selectionState (end)
27535        itemSelection.each((selection) => {
27536            if (eq(selection.end, item)) {
27537                selectionState.set(false);
27538            }
27539        });
27540        const childListEntries = lastChild(item)
27541            .filter(isList)
27542            .map((list) => parseList(depth, itemSelection, selectionState, list))
27543            .getOr([]);
27544        return currentItemEntry.toArray().concat(childListEntries);
27545    };
27546    const parseItem = (depth, itemSelection, selectionState, item) => firstChild(item).filter(isList).fold(() => parseSingleItem(depth, itemSelection, selectionState, item), (list) => {
27547        const parsedSiblings = foldl(children$1(item), (acc, liChild, i) => {
27548            if (i === 0) {
27549                return acc;
27550            }
27551            else {
27552                if (isListItem$1(liChild)) {
27553                    return acc.concat(parseSingleItem(depth, itemSelection, selectionState, liChild));
27554                }
27555                else {
27556                    const fragment = {
27557                        isFragment: true,
27558                        depth,
27559                        content: [liChild],
27560                        isSelected: false,
27561                        dirty: false,
27562                        parentListType: name(list)
27563                    };
27564                    return acc.concat(fragment);
27565                }
27566            }
27567        }, []);
27568        return parseList(depth, itemSelection, selectionState, list).concat(parsedSiblings);
27569    });
27570    const parseList = (depth, itemSelection, selectionState, list) => bind$3(children$1
vendor: 29,534 bytes, lines 27570-28233
27570(list), (element) => {
27571        const parser = isList(element) ? parseList : parseItem;
27572        const newDepth = depth + 1;
27573        return parser(newDepth, itemSelection, selectionState, element);
27574    });
27575    const parseLists = (lists, itemSelection) => {
27576        const selectionState = Cell(false);
27577        const initialDepth = 0;
27578        return map$3(lists, (list) => ({
27579            sourceList: list,
27580            entries: parseList(initialDepth, itemSelection, selectionState, list)
27581        }));
27582    };
27583
27584    const outdentedComposer = (editor, entries) => {
27585        const normalizedEntries = normalizeEntries(entries);
27586        return map$3(normalizedEntries, (entry) => {
27587            const content = !isEntryComment(entry)
27588                ? fromElements(entry.content)
27589                : fromElements([SugarElement.fromHtml(`<!--${entry.content}-->`)]);
27590            const listItemAttrs = isEntryList(entry) ? entry.itemAttributes : {};
27591            return SugarElement.fromDom(createTextBlock$1(editor, content.dom, listItemAttrs));
27592        });
27593    };
27594    const indentedComposer = (editor, entries) => {
27595        const normalizedEntries = normalizeEntries(entries);
27596        return composeList(editor.contentDocument, normalizedEntries).toArray();
27597    };
27598    const composeEntries = (editor, entries) => bind$3(groupBy(entries, isIndented), (entries) => {
27599        const groupIsIndented = head(entries).exists(isIndented);
27600        return groupIsIndented ? indentedComposer(editor, entries) : outdentedComposer(editor, entries);
27601    });
27602    const indentSelectedEntries = (editor, entries, indentation) => {
27603        each$e(filter$5(entries, isSelected), (entry) => indentEntry(editor, indentation, entry));
27604    };
27605    const getItemSelection = (editor) => {
27606        const selectedListItems = map$3(getSelectedListItems(editor), SugarElement.fromDom);
27607        return lift2(find$2(selectedListItems, not(hasFirstChildList)), find$2(reverse(selectedListItems), not(hasFirstChildList)), (start, end) => ({ start, end }));
27608    };
27609    const listIndentation = (editor, lists, indentation) => {
27610        const entrySets = parseLists(lists, getItemSelection(editor));
27611        each$e(entrySets, (entrySet) => {
27612            indentSelectedEntries(editor, entrySet.entries, indentation);
27613            const composedLists = composeEntries(editor, entrySet.entries);
27614            each$e(composedLists, (composedList) => {
27615                fireListEvent(editor, indentation === "Indent" /* Indentation.Indent */ ? "IndentList" /* ListAction.IndentList */ : "OutdentList" /* ListAction.OutdentList */, composedList.dom);
27616            });
27617            before$3(entrySet.sourceList, composedLists);
27618            remove$8(entrySet.sourceList);
27619        });
27620    };
27621    const canIndent$1 = (editor) => getListMaxDepth(editor).forall((max) => {
27622        const blocks = editor.selection.getSelectedBlocks();
27623        return exists(blocks, (element) => {
27624            return closest$4(SugarElement.fromDom(element), 'li').forall((sugarElement) => ancestors(sugarElement, 'ol,ul').length <= max);
27625        });
27626    });
27627
27628    const DOM$8 = DOMUtils.DOM;
27629    const splitList = (editor, list, li) => {
27630        const removeAndKeepBookmarks = (targetNode) => {
27631            const parent = targetNode.parentNode;
27632            if (parent) {
27633                Tools.each(bookmarks, (node) => {
27634                    parent.insertBefore(node, li.parentNode);
27635                });
27636            }
27637            DOM$8.remove(targetNode);
27638        };
27639        const bookmarks = DOM$8.select('span[data-mce-type="bookmark"]', list);
27640        const newBlock = createTextBlock$1(editor, li);
27641        const tmpRng = DOM$8.createRng();
27642        tmpRng.setStartAfter(li);
27643        tmpRng.setEndAfter(list);
27644        const fragment = tmpRng.extractContents();
27645        for (let node = fragment.firstChild; node; node = node.firstChild) {
27646            if (node.nodeName === 'LI' && editor.dom.isEmpty(node)) {
27647                DOM$8.remove(node);
27648                break;
27649            }
27650        }
27651        if (!editor.dom.isEmpty(fragment)) {
27652            DOM$8.insertAfter(fragment, list);
27653        }
27654        DOM$8.insertAfter(newBlock, list);
27655        const parent = li.parentElement;
27656        if (parent && isEmpty$1(editor.dom, parent)) {
27657            removeAndKeepBookmarks(parent);
27658        }
27659        DOM$8.remove(li);
27660        if (isEmpty$1(editor.dom, list)) {
27661            DOM$8.remove(list);
27662        }
27663    };
27664
27665    const isDescriptionDetail = isTag('dd');
27666    const isDescriptionTerm = isTag('dt');
27667    const outdentDlItem = (editor, item) => {
27668        if (isDescriptionDetail(item)) {
27669            mutate(item, 'dt');
27670        }
27671        else if (isDescriptionTerm(item)) {
27672            parentElement(item).each((dl) => splitList(editor, dl.dom, item.dom));
27673        }
27674    };
27675    const indentDlItem = (item) => {
27676        if (isDescriptionTerm(item)) {
27677            mutate(item, 'dd');
27678        }
27679    };
27680    const dlIndentation = (editor, indentation, dlItems) => {
27681        if (indentation === "Indent" /* Indentation.Indent */) {
27682            each$e(dlItems, indentDlItem);
27683        }
27684        else {
27685            each$e(dlItems, (item) => outdentDlItem(editor, item));
27686        }
27687    };
27688
27689    const selectionIndentation = (editor, indentation) => {
27690        const lists = fromDom$1(getSelectedListRoots(editor));
27691        const dlItems = fromDom$1(getSelectedDlItems(editor));
27692        let isHandled = false;
27693        if (lists.length || dlItems.length) {
27694            const bookmark = editor.selection.getBookmark();
27695            listIndentation(editor, lists, indentation);
27696            dlIndentation(editor, indentation, dlItems);
27697            editor.selection.moveToBookmark(bookmark);
27698            editor.selection.setRng(normalizeRange(editor.selection.getRng()));
27699            editor.nodeChanged();
27700            isHandled = true;
27701        }
27702        return isHandled;
27703    };
27704    const handleIndentation = (editor, indentation) => !selectionIsWithinNonEditableList(editor) && selectionIndentation(editor, indentation);
27705    const indentListSelection = (editor) => handleIndentation(editor, "Indent" /* Indentation.Indent */);
27706    const outdentListSelection = (editor) => handleIndentation(editor, "Outdent" /* Indentation.Outdent */);
27707    const flattenListSelection = (editor) => handleIndentation(editor, "Flatten" /* Indentation.Flatten */);
27708
27709    const listToggleActionFromListName = (listName) => {
27710        switch (listName) {
27711            case 'UL': return "ToggleUlList" /* ListAction.ToggleUlList */;
27712            case 'OL': return "ToggleOlList" /* ListAction.ToggleOlList */;
27713            case 'DL': return "ToggleDLList" /* ListAction.ToggleDLList */;
27714        }
27715    };
27716
27717    const updateListStyle = (dom, el, detail) => {
27718        const type = detail['list-style-type'] ? detail['list-style-type'] : null;
27719        dom.setStyle(el, 'list-style-type', type);
27720    };
27721    const setAttribs = (elm, attrs) => {
27722        Tools.each(attrs, (value, key) => {
27723            elm.setAttribute(key, value);
27724        });
27725    };
27726    const updateListAttrs = (dom, el, detail) => {
27727        setAttribs(el, detail['list-attributes']);
27728        Tools.each(dom.select('li', el), (li) => {
27729            setAttribs(li, detail['list-item-attributes']);
27730        });
27731    };
27732    const updateListWithDetails = (dom, el, detail) => {
27733        updateListStyle(dom, el, detail);
27734        updateListAttrs(dom, el, detail);
27735    };
27736    const removeStyles = (dom, element, styles) => {
27737        Tools.each(styles, (style) => dom.setStyle(element, style, ''));
27738    };
27739    const isInline$1 = (editor, node) => isNonNullable(node) && !isBlock(node, editor.schema.getBlockElements());
27740    const getEndPointNode = (editor, rng, start, root) => {
27741        let container = rng[start ? 'startContainer' : 'endContainer'];
27742        const offset = rng[start ? 'startOffset' : 'endOffset'];
27743        // Resolve node index
27744        if (isElement$1(container)) {
27745            container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
27746        }
27747        if (!start && isBr$1(container.nextSibling)) {
27748            container = container.nextSibling;
27749        }
27750        const findBlockAncestor = (node) => {
27751            while (!editor.dom.isBlock(node) && node.parentNode && root !== node) {
27752                node = node.parentNode;
27753            }
27754            return node;
27755        };
27756        // The reason why the next two if statements exist is because when the root node is a table cell (possibly some other node types)
27757        // then the highest we can go up the dom hierarchy is one level below the table cell.
27758        // So what happens when we have a bunch of inline nodes and text nodes in the table cell
27759        // and when the selection is collapsed inside one of the inline nodes then only that inline node (or text node) will be included
27760        // in the created list because that would be one level below td node and the other inline nodes won't be included.
27761        // So the fix proposed is to traverse left when looking for start node (and traverse right when looking for end node)
27762        // and keep traversing as long as we have an inline or text node (same for traversing right).
27763        // This way we end up including all the inline elements in the created list.
27764        // For more info look at #TINY-6853
27765        const findBetterContainer = (container, forward) => {
27766            const walker = new DomTreeWalker(container, findBlockAncestor(container));
27767            const dir = forward ? 'next' : 'prev';
27768            let node;
27769            while ((node = walker[dir]())) {
27770                if (!(isVoid(editor, node) || isZwsp$2(node.textContent) || node.textContent?.length === 0)) {
27771                    return Optional.some(node);
27772                }
27773            }
27774            return Optional.none();
27775        };
27776        // Traverse left to include inline/text nodes
27777        if (start && isTextNode$1(container)) {
27778            if (isZwsp$2(container.textContent)) {
27779                container = findBetterContainer(container, false).getOr(container);
27780            }
27781            else {
27782                if (container.parentNode !== null && isInline$1(editor, container.parentNode)) {
27783                    container = container.parentNode;
27784                }
27785                while (container.previousSibling !== null && (isInline$1(editor, container.previousSibling) || isTextNode$1(container.previousSibling))) {
27786                    container = container.previousSibling;
27787                }
27788            }
27789        }
27790        // Traverse right to include inline/text nodes
27791        if (!start && isTextNode$1(container)) {
27792            if (isZwsp$2(container.textContent)) {
27793                container = findBetterContainer(container, true).getOr(container);
27794            }
27795            else {
27796                if (container.parentNode !== null && isInline$1(editor, container.parentNode)) {
27797                    container = container.parentNode;
27798                }
27799                while (container.nextSibling !== null && (isInline$1(editor, container.nextSibling) || isTextNode$1(container.nextSibling))) {
27800                    container = container.nextSibling;
27801                }
27802            }
27803        }
27804        while (container.parentNode !== root) {
27805            const parent = container.parentNode;
27806            if (isTextBlock(editor, container)) {
27807                return container;
27808            }
27809            if (/^(TD|TH)$/.test(parent.nodeName)) {
27810                return container;
27811            }
27812            container = parent;
27813        }
27814        return container;
27815    };
27816    const getSelectedTextBlocks = (editor, rng, root) => {
27817        const textBlocks = [];
27818        const dom = editor.dom;
27819        const startNode = getEndPointNode(editor, rng, true, root);
27820        const endNode = getEndPointNode(editor, rng, false, root);
27821        let block;
27822        const siblings = [];
27823        for (let node = startNode; node; node = node.nextSibling) {
27824            siblings.push(node);
27825            if (node === endNode) {
27826                break;
27827            }
27828        }
27829        Tools.each(siblings, (node) => {
27830            if (isTextBlock(editor, node)) {
27831                textBlocks.push(node);
27832                block = null;
27833                return;
27834            }
27835            if (dom.isBlock(node) || isBr$1(node)) {
27836                if (isBr$1(node)) {
27837                    dom.remove(node);
27838                }
27839                block = null;
27840                return;
27841            }
27842            const nextSibling = node.nextSibling;
27843            if (BookmarkManager.isBookmarkNode(node)) {
27844                if (isListNode(nextSibling) || isTextBlock(editor, nextSibling) || (!nextSibling && node.parentNode === root)) {
27845                    block = null;
27846                    return;
27847                }
27848            }
27849            if (!block) {
27850                block = dom.create('p');
27851                node.parentNode?.insertBefore(block, node);
27852                textBlocks.push(block);
27853            }
27854            block.appendChild(node);
27855        });
27856        return textBlocks;
27857    };
27858    const hasCompatibleStyle = (dom, sib, detail) => {
27859        const sibStyle = dom.getStyle(sib, 'list-style-type');
27860        let detailStyle = detail ? detail['list-style-type'] : '';
27861        detailStyle = detailStyle === null ? '' : detailStyle;
27862        return sibStyle === detailStyle;
27863    };
27864    /*
27865      Find the first element we would transform into a li-element if given no constraints.
27866      If the common ancestor is higher up than that provide it as the starting-point for the search for the root instead of the first selected element.
27867      This helps avoid issues with divs that should become li-elements are detected as the root when they should not be.
27868    */
27869    const getRootSearchStart = (editor, range) => {
27870        const start = editor.selection.getStart(true);
27871        const startPoint = getEndPointNode(editor, range, true, editor.getBody());
27872        if (ancestor$2(SugarElement.fromDom(startPoint), SugarElement.fromDom(range.commonAncestorContainer))) {
27873            return range.commonAncestorContainer;
27874        }
27875        else {
27876            return start;
27877        }
27878    };
27879    const applyList = (editor, listName, detail) => {
27880        const rng = editor.selection.getRng();
27881        let listItemName = 'LI';
27882        const root = getClosestListHost(editor, getRootSearchStart(editor, rng), rng.collapsed);
27883        const dom = editor.dom;
27884        if (dom.getContentEditable(editor.selection.getNode()) === 'false') {
27885            return;
27886        }
27887        listName = listName.toUpperCase();
27888        if (listName === 'DL') {
27889            listItemName = 'DT';
27890        }
27891        const bookmark = createBookmark(rng);
27892        const selectedTextBlocks = filter$5(getSelectedTextBlocks(editor, rng, root), editor.dom.isEditable);
27893        Tools.each(selectedTextBlocks, (block) => {
27894            let listBlock;
27895            const sibling = block.previousSibling;
27896            const parent = block.parentNode;
27897            if (!isListItemNode(parent)) {
27898                if (sibling && isListNode(sibling) && sibling.nodeName === listName && hasCompatibleStyle(dom, sibling, detail)) {
27899                    listBlock = sibling;
27900                    block = dom.rename(block, listItemName);
27901                    sibling.appendChild(block);
27902                }
27903                else {
27904                    listBlock = dom.create(listName);
27905                    parent.insertBefore(listBlock, block);
27906                    listBlock.appendChild(block);
27907                    block = dom.rename(block, listItemName);
27908                }
27909                removeStyles(dom, block, [
27910                    'margin', 'margin-right', 'margin-bottom', 'margin-left', 'margin-top',
27911                    'padding', 'padding-right', 'padding-bottom', 'padding-left', 'padding-top'
27912                ]);
27913                updateListWithDetails(dom, listBlock, detail);
27914                mergeWithAdjacentLists(editor.dom, listBlock);
27915            }
27916        });
27917        editor.selection.setRng(resolveBookmark(bookmark));
27918    };
27919    const isValidLists = (list1, list2) => {
27920        return isListNode(list1) && list1.nodeName === list2?.nodeName;
27921    };
27922    const hasSameListStyle = (dom, list1, list2) => {
27923        const targetStyle = dom.getStyle(list1, 'list-style-type', true);
27924        const style = dom.getStyle(list2, 'list-style-type', true);
27925        return targetStyle === style;
27926    };
27927    const hasSameClasses = (elm1, elm2) => {
27928        return elm1.className === elm2.className;
27929    };
27930    const shouldMerge = (dom, list1, list2) => {
27931        return isValidLists(list1, list2) &&
27932            // Note: isValidLists will ensure list1 and list2 are a HTMLElement. Unfortunately TypeScript doesn't
27933            // support type guards on multiple variables. See https://github.com/microsoft/TypeScript/issues/26916
27934            hasSameListStyle(dom, list1, list2) &&
27935            hasSameClasses(list1, list2);
27936    };
27937    const mergeWithAdjacentLists = (dom, listBlock) => {
27938        let node;
27939        let sibling = listBlock.nextSibling;
27940        if (shouldMerge(dom, listBlock, sibling)) {
27941            const liSibling = sibling;
27942            while ((node = liSibling.firstChild)) {
27943                listBlock.appendChild(node);
27944            }
27945            dom.remove(liSibling);
27946        }
27947        sibling = listBlock.previousSibling;
27948        if (shouldMerge(dom, listBlock, sibling)) {
27949            const liSibling = sibling;
27950            while ((node = liSibling.lastChild)) {
27951                listBlock.insertBefore(node, listBlock.firstChild);
27952            }
27953            dom.remove(liSibling);
27954        }
27955    };
27956    const updateList$1 = (editor, list, listName, detail) => {
27957        if (list.nodeName !== listName) {
27958            const newList = editor.dom.rename(list, listName);
27959            updateListWithDetails(editor.dom, newList, detail);
27960            fireListEvent(editor, listToggleActionFromListName(listName), newList);
27961        }
27962        else {
27963            updateListWithDetails(editor.dom, list, detail);
27964            fireListEvent(editor, listToggleActionFromListName(listName), list);
27965        }
27966    };
27967    const updateCustomList = (editor, list, listName, detail) => {
27968        list.classList.forEach((cls, _, classList) => {
27969            if (cls.startsWith('tox-')) {
27970                classList.remove(cls);
27971                if (classList.length === 0) {
27972                    list.removeAttribute('class');
27973                }
27974            }
27975        });
27976        if (list.nodeName !== listName) {
27977            const newList = editor.dom.rename(list, listName);
27978            updateListWithDetails(editor.dom, newList, detail);
27979            fireListEvent(editor, listToggleActionFromListName(listName), newList);
27980        }
27981        else {
27982            updateListWithDetails(editor.dom, list, detail);
27983            fireListEvent(editor, listToggleActionFromListName(listName), list);
27984        }
27985    };
27986    const toggleMultipleLists = (editor, parentList, lists, listName, detail) => {
27987        const parentIsList = isListNode(parentList);
27988        if (parentIsList && parentList.nodeName === listName && !hasListStyleDetail(detail) && !isCustomList(parentList)) {
27989            flattenListSelection(editor);
27990        }
27991        else {
27992            applyList(editor, listName, detail);
27993            const bookmark = createBookmark(editor.selection.getRng());
27994            const allLists = parentIsList ? [parentList, ...lists] : lists;
27995            const updateFunction = (parentIsList && isCustomList(parentList)) ? updateCustomList : updateList$1;
27996            Tools.each(allLists, (elm) => {
27997                updateFunction(editor, elm, listName, detail);
27998            });
27999            editor.selection.setRng(resolveBookmark(bookmark));
28000        }
28001    };
28002    const hasListStyleDetail = (detail) => {
28003        return 'list-style-type' in detail;
28004    };
28005    const toggleSingleList = (editor, parentList, listName, detail) => {
28006        if (parentList === editor.getBody()) {
28007            return;
28008        }
28009        if (parentList) {
28010            if (parentList.nodeName === listName && !hasListStyleDetail(detail) && !isCustomList(parentList)) {
28011                flattenListSelection(editor);
28012            }
28013            else {
28014                const bookmark = createBookmark(editor.selection.getRng());
28015                if (isCustomList(parentList)) {
28016                    parentList.classList.forEach((cls, _, classList) => {
28017                        if (cls.startsWith('tox-')) {
28018                            classList.remove(cls);
28019                            if (classList.length === 0) {
28020                                parentList.removeAttribute('class');
28021                            }
28022                        }
28023                    });
28024                }
28025                updateListWithDetails(editor.dom, parentList, detail);
28026                const newList = editor.dom.rename(parentList, listName);
28027                mergeWithAdjacentLists(editor.dom, newList);
28028                editor.selection.setRng(resolveBookmark(bookmark));
28029                applyList(editor, listName, detail);
28030                fireListEvent(editor, listToggleActionFromListName(listName), newList);
28031            }
28032        }
28033        else {
28034            applyList(editor, listName, detail);
28035            fireListEvent(editor, listToggleActionFromListName(listName), parentList);
28036        }
28037    };
28038    const toggleList = (editor, listName, _detail) => {
28039        const parentList = getParentList(editor);
28040        if (isWithinNonEditableList$1(editor, parentList)) {
28041            return;
28042        }
28043        const selectedSubLists = getSelectedSubLists(editor);
28044        const detail = isObject(_detail) ? _detail : {};
28045        if (selectedSubLists.length > 0) {
28046            toggleMultipleLists(editor, parentList, selectedSubLists, listName, detail);
28047        }
28048        else {
28049            toggleSingleList(editor, parentList, listName, detail);
28050        }
28051    };
28052
28053    const findNextCaretContainer = (editor, rng, isForward, root) => {
28054        let node = rng.startContainer;
28055        const offset = rng.startOffset;
28056        if (isTextNode$1(node) && (isForward ? offset < node.data.length : offset > 0)) {
28057            return node;
28058        }
28059        const nonEmptyBlocks = editor.schema.getNonEmptyElements();
28060        if (isElement$1(node)) {
28061            node = RangeUtils.getNode(node, offset);
28062        }
28063        const walker = new DomTreeWalker(node, root);
28064        // Delete at <li>|<br></li> then jump over the bogus br
28065        if (isForward) {
28066            if (isBogusBr(editor.dom, node)) {
28067                walker.next();
28068            }
28069        }
28070        const walkFn = isForward ? walker.next.bind(walker) : walker.prev2.bind(walker);
28071        while ((node = walkFn())) {
28072            if (node.nodeName === 'LI' && !node.hasChildNodes()) {
28073                return node;
28074            }
28075            if (nonEmptyBlocks[node.nodeName]) {
28076                return node;
28077            }
28078            if (isTextNode$1(node) && node.data.length > 0) {
28079                return node;
28080            }
28081        }
28082        return null;
28083    };
28084    const hasOnlyOneBlockChild = (dom, elm) => {
28085        const childNodes = elm.childNodes;
28086        return childNodes.length === 1 && !isListNode(childNodes[0]) && dom.isBlock(childNodes[0]);
28087    };
28088    const isUnwrappable = (node) => Optional.from(node)
28089        .map(SugarElement.fromDom)
28090        .filter(isHTMLElement$1)
28091        .exists((el) => isEditable$2(el) && !contains$2(['details'], name(el)));
28092    const unwrapSingleBlockChild = (dom, elm) => {
28093        if (hasOnlyOneBlockChild(dom, elm) && isUnwrappable(elm.firstChild)) {
28094            dom.remove(elm.firstChild, true);
28095        }
28096    };
28097    const moveChildren = (dom, fromElm, toElm) => {
28098        let node;
28099        const targetElm = hasOnlyOneBlockChild(dom, toElm) ? toElm.firstChild : toElm;
28100        unwrapSingleBlockChild(dom, fromElm);
28101        if (!isEmpty$1(dom, fromElm, true)) {
28102            while ((node = fromElm.firstChild)) {
28103                targetElm.appendChild(node);
28104            }
28105        }
28106    };
28107    const mergeLiElements = (dom, fromElm, toElm) => {
28108        let listNode;
28109        const ul = fromElm.parentNode;
28110        if (!isChildOfBody(dom, fromElm) || !isChildOfBody(dom, toElm)) {
28111            return;
28112        }
28113        if (isListNode(toElm.lastChild)) {
28114            listNode = toElm.lastChild;
28115        }
28116        if (ul === toElm.lastChild) {
28117            if (isBr$1(ul.previousSibling)) {
28118                dom.remove(ul.previousSibling);
28119            }
28120        }
28121        const node = toElm.lastChild;
28122        if (node && isBr$1(node) && fromElm.hasChildNodes()) {
28123            dom.remove(node);
28124        }
28125        if (isEmpty$1(dom, toElm, true)) {
28126            empty(SugarElement.fromDom(toElm));
28127        }
28128        moveChildren(dom, fromElm, toElm);
28129        if (listNode) {
28130            toElm.appendChild(listNode);
28131        }
28132        const contains$1 = contains(SugarElement.fromDom(toElm), SugarElement.fromDom(fromElm));
28133        const nestedLists = contains$1 ? dom.getParents(fromElm, isListNode, toElm) : [];
28134        dom.remove(fromElm);
28135        each$e(nestedLists, (list) => {
28136            if (isEmpty$1(dom, list) && list !== dom.getRoot()) {
28137                dom.remove(list);
28138            }
28139        });
28140    };
28141    const mergeIntoEmptyLi = (editor, fromLi, toLi) => {
28142        empty(SugarElement.fromDom(toLi));
28143        mergeLiElements(editor.dom, fromLi, toLi);
28144        editor.selection.setCursorLocation(toLi, 0);
28145    };
28146    const mergeForward = (editor, rng, fromLi, toLi) => {
28147        const dom = editor.dom;
28148        if (dom.isEmpty(toLi)) {
28149            mergeIntoEmptyLi(editor, fromLi, toLi);
28150        }
28151        else {
28152            const bookmark = createBookmark(rng);
28153            mergeLiElements(dom, fromLi, toLi);
28154            editor.selection.setRng(resolveBookmark(bookmark));
28155        }
28156    };
28157    const mergeBackward = (editor, rng, fromLi, toLi) => {
28158        const bookmark = createBookmark(rng);
28159        mergeLiElements(editor.dom, fromLi, toLi);
28160        const resolvedBookmark = resolveBookmark(bookmark);
28161        editor.selection.setRng(resolvedBookmark);
28162    };
28163    const backspaceDeleteFromListToListCaret = (editor, isForward) => {
28164        const dom = editor.dom, selection = editor.selection;
28165        const selectionStartElm = selection.getStart();
28166        const root = getClosestEditingHost(editor, selectionStartElm);
28167        const li = dom.getParent(selection.getStart(), 'LI', root);
28168        if (li) {
28169            const ul = li.parentElement;
28170            if (ul === editor.getBody() && isEmpty$1(dom, ul)) {
28171                return true;
28172            }
28173            const rng = normalizeRange(selection.getRng());
28174            const otherLi = dom.getParent(findNextCaretContainer(editor, rng, isForward, root), 'LI', root);
28175            const willMergeParentIntoChild = otherLi && (isForward ? dom.isChildOf(li, otherLi) : dom.isChildOf(otherLi, li));
28176            if (otherLi && otherLi !== li && !willMergeParentIntoChild) {
28177                editor.undoManager.transact(() => {
28178                    if (isForward) {
28179                        mergeForward(editor, rng, otherLi, li);
28180                    }
28181                    else {
28182                        if (isFirstChild$1(li)) {
28183                            outdentListSelection(editor);
28184                        }
28185                        else {
28186                            mergeBackward(editor, rng, li, otherLi);
28187                        }
28188                    }
28189                });
28190                return true;
28191            }
28192            else if (willMergeParentIntoChild && !isForward && otherLi !== li) {
28193                const commonAncestorParent = rng.commonAncestorContainer.parentElement;
28194                if (!commonAncestorParent || dom.isChildOf(otherLi, commonAncestorParent)) {
28195                    return false;
28196                }
28197                editor.undoManager.transact(() => {
28198                    const bookmark = createBookmark(rng);
28199                    moveChildren(dom, commonAncestorParent, otherLi);
28200                    commonAncestorParent.remove();
28201                    const resolvedBookmark = resolveBookmark(bookmark);
28202                    editor.selection.setRng(resolvedBookmark);
28203                });
28204                return true;
28205            }
28206            else if (!otherLi) {
28207                if (!isForward && rng.startOffset === 0 && rng.endOffset === 0) {
28208                    editor.undoManager.transact(() => {
28209                        flattenListSelection(editor);
28210                    });
28211                    return true;
28212                }
28213            }
28214        }
28215        return false;
28216    };
28217    const removeBlock = (dom, block, root) => {
28218        const parentBlock = dom.getParent(block.parentNode, dom.isBlock, root);
28219        dom.remove(block);
28220        if (parentBlock && dom.isEmpty(parentBlock)) {
28221            dom.remove(parentBlock);
28222        }
28223    };
28224    const backspaceDeleteIntoListCaret = (editor, isForward) => {
28225        const dom = editor.dom;
28226        const selectionStartElm = editor.selection.getStart();
28227        const root = getClosestEditingHost(editor, selectionStartElm);
28228        const block = dom.getParent(selectionStartElm, dom.isBlock, root);
28229        if (block && dom.isEmpty(block, undefined, { checkRootAsContent: true })) {
28230            const rng = normalizeRange(editor.selection.getRng());
28231            const nextCaretContainer = findNextCaretContainer(editor, rng, isForward, root);
28232            const otherLi = dom.getParent(nextCaretContainer, 'LI', root);
28233            if (nextCaretContainer && otherLi
vendor: 5,464 bytes, lines 28233-28331
28233) {
28234                const findValidElement = (element) => contains$2(['td', 'th', 'caption'], name(element));
28235                const findRoot = (node) => node.dom === root;
28236                const otherLiCell = closest$5(SugarElement.fromDom(otherLi), findValidElement, findRoot);
28237                const caretCell = closest$5(SugarElement.fromDom(rng.startContainer), findValidElement, findRoot);
28238                if (!equals(otherLiCell, caretCell, eq)) {
28239                    return false;
28240                }
28241                editor.undoManager.transact(() => {
28242                    const parentNode = otherLi.parentNode;
28243                    removeBlock(dom, block, root);
28244                    mergeWithAdjacentLists(dom, parentNode);
28245                    editor.selection.select(nextCaretContainer, true);
28246                    editor.selection.collapse(isForward);
28247                });
28248                return true;
28249            }
28250        }
28251        return false;
28252    };
28253    const backspaceDeleteCaret$1 = (editor, isForward) => {
28254        return backspaceDeleteFromListToListCaret(editor, isForward) || backspaceDeleteIntoListCaret(editor, isForward);
28255    };
28256    const hasListSelection = (editor) => {
28257        const selectionStartElm = editor.selection.getStart();
28258        const root = getClosestEditingHost(editor, selectionStartElm);
28259        const startListParent = editor.dom.getParent(selectionStartElm, 'LI,DT,DD', root);
28260        return isNonNullable(startListParent) || getSelectedListItems(editor).length > 0;
28261    };
28262    const backspaceDeleteRange$1 = (editor, isForward) => {
28263        if (hasListSelection(editor)) {
28264            editor.undoManager.transact(() => {
28265                if (symulateDelete(editor, isForward, () => editor.execCommand('Delete'))) {
28266                    normalizeLists(editor.dom, editor.getBody());
28267                }
28268            });
28269            return true;
28270        }
28271        return false;
28272    };
28273    const backspaceDelete$c = (editor, isForward) => {
28274        const selection = editor.selection;
28275        return !isWithinNonEditableList$1(editor, selection.getNode()) && (selection.isCollapsed() ?
28276            backspaceDeleteCaret$1(editor, isForward) : backspaceDeleteRange$1(editor, isForward));
28277    };
28278
28279    const blockPosition = (block, position) => ({
28280        block,
28281        position
28282    });
28283    const blockBoundary = (from, to) => ({
28284        from,
28285        to
28286    });
28287    const getBlockPosition = (rootNode, pos) => {
28288        const rootElm = SugarElement.fromDom(rootNode);
28289        const containerElm = SugarElement.fromDom(pos.container());
28290        return getParentBlock$2(rootElm, containerElm).map((block) => blockPosition(block, pos));
28291    };
28292    const isNotAncestorial = (blockBoundary) => !(contains(blockBoundary.to.block, blockBoundary.from.block) || contains(blockBoundary.from.block, blockBoundary.to.block));
28293    const isDifferentBlocks = (blockBoundary) => !eq(blockBoundary.from.block, blockBoundary.to.block);
28294    const getClosestHost = (root, scope) => {
28295        const isRoot = (node) => eq(node, root);
28296        const isHost = (node) => isTableCell$2(node) || isContentEditableTrue$3(node.dom);
28297        return closest$5(scope, isHost, isRoot).filter(isElement$8).getOr(root);
28298    };
28299    const hasSameHost = (rootNode, blockBoundary) => {
28300        const root = SugarElement.fromDom(rootNode);
28301        return eq(getClosestHost(root, blockBoundary.from.block), getClosestHost(root, blockBoundary.to.block));
28302    };
28303    const isEditable$1 = (blockBoundary) => isContentEditableFalse$a(blockBoundary.from.block.dom) === false && isContentEditableFalse$a(blockBoundary.to.block.dom) === false;
28304    const hasValidBlocks = (blockBoundary) => {
28305        const isValidBlock = (block) => isTextBlock$3(block) || hasBlockAttr(block.dom) || isListItem$2(block);
28306        return isValidBlock(blockBoundary.from.block) && isValidBlock(blockBoundary.to.block);
28307    };
28308    const skipLastBr = (schema, rootNode, forward, blockPosition) => {
28309        if (isBr$7(blockPosition.position.getNode()) && !isEmpty$4(schema, blockPosition.block)) {
28310            return positionIn(false, blockPosition.block.dom).bind((lastPositionInBlock) => {
28311                if (lastPositionInBlock.isEqual(blockPosition.position)) {
28312                    return fromPosition(forward, rootNode, lastPositionInBlock).bind((to) => getBlockPosition(rootNode, to));
28313                }
28314                else {
28315                    return Optional.some(blockPosition);
28316                }
28317            }).getOr(blockPosition);
28318        }
28319        else {
28320            return blockPosition;
28321        }
28322    };
28323    const readFromRange = (schema, rootNode, forward, rng) => {
28324        const fromBlockPos = getBlockPosition(rootNode, CaretPosition.fromRangeStart(rng));
28325        const toBlockPos = fromBlockPos.bind((blockPos) => fromPosition(forward, rootNode, blockPos.position).bind((to) => getBlockPosition(rootNode, to).map((blockPos) => skipLastBr(schema, rootNode, forward, blockPos))));
28326        return lift2(fromBlockPos, toBlockPos, blockBoundary).filter((blockBoundary) => isDifferentBlocks(blockBoundary) && hasSameHost(rootNode, blockBoundary) && isEditable$1(blockBoundary) && hasValidBlocks(blockBoundary) && isNotAncestorial(blockBoundary));
28327    };
28328    const read$1 = (schema, rootNode, forward, rng) => rng.collapsed ? readFromRange(schema, rootNode, forward, rng) : Optional.none();
28329
28330    const getChildrenUntilBlockBoundary = (block, schema) => {
28331        const children = children$1
vendor: 67,761 bytes, lines 28331-29698
28331(block);
28332        return findIndex$2(children, (el) => schema.isBlock(name(el))).fold(constant(children), (index) => children.slice(0, index));
28333    };
28334    const extractChildren = (block, schema) => {
28335        const children = getChildrenUntilBlockBoundary(block, schema);
28336        each$e(children, remove$8);
28337        return children;
28338    };
28339    const removeEmptyRoot = (schema, rootNode, block) => {
28340        const parents = parentsAndSelf(block, rootNode);
28341        return find$2(parents.reverse(), (element) => isEmpty$4(schema, element)).each(remove$8);
28342    };
28343    const isEmptyBefore = (schema, el) => filter$5(prevSiblings(el), (el) => !isEmpty$4(schema, el)).length === 0;
28344    const nestedBlockMerge = (rootNode, fromBlock, toBlock, schema, insertionPoint) => {
28345        if (isEmpty$4(schema, toBlock)) {
28346            fillWithPaddingBr(toBlock);
28347            return firstPositionIn(toBlock.dom);
28348        }
28349        if (isEmptyBefore(schema, insertionPoint) && isEmpty$4(schema, fromBlock)) {
28350            before$4(insertionPoint, SugarElement.fromTag('br'));
28351        }
28352        const position = prevPosition(toBlock.dom, CaretPosition.before(insertionPoint.dom));
28353        each$e(extractChildren(fromBlock, schema), (child) => {
28354            before$4(insertionPoint, child);
28355        });
28356        removeEmptyRoot(schema, rootNode, fromBlock);
28357        return position;
28358    };
28359    const isInline = (schema, node) => schema.isInline(name(node));
28360    const sidelongBlockMerge = (rootNode, fromBlock, toBlock, schema) => {
28361        if (isEmpty$4(schema, toBlock)) {
28362            if (isEmpty$4(schema, fromBlock)) {
28363                const getInlineToBlockDescendants = (el) => {
28364                    const helper = (node, elements) => firstChild(node).fold(() => elements, (child) => isInline(schema, child) ? helper(child, elements.concat(shallow(child))) : elements);
28365                    return helper(el, []);
28366                };
28367                const newFromBlockDescendants = foldr(getInlineToBlockDescendants(toBlock), (element, descendant) => {
28368                    wrap$2(element, descendant);
28369                    return descendant;
28370                }, createPaddingBr());
28371                empty(fromBlock);
28372                append$1(fromBlock, newFromBlockDescendants);
28373            }
28374            remove$8(toBlock);
28375            return firstPositionIn(fromBlock.dom);
28376        }
28377        const position = lastPositionIn(toBlock.dom);
28378        each$e(extractChildren(fromBlock, schema), (child) => {
28379            append$1(toBlock, child);
28380        });
28381        removeEmptyRoot(schema, rootNode, fromBlock);
28382        return position;
28383    };
28384    const findInsertionPoint = (toBlock, block) => {
28385        const parentsAndSelf$1 = parentsAndSelf(block, toBlock);
28386        return Optional.from(parentsAndSelf$1[parentsAndSelf$1.length - 1]);
28387    };
28388    const getInsertionPoint = (fromBlock, toBlock) => contains(toBlock, fromBlock) ? findInsertionPoint(toBlock, fromBlock) : Optional.none();
28389    const trimBr = (first, block) => {
28390        positionIn(first, block.dom)
28391            .bind((position) => Optional.from(position.getNode()))
28392            .map(SugarElement.fromDom)
28393            .filter(isBr$6)
28394            .each(remove$8);
28395    };
28396    const mergeBlockInto = (rootNode, fromBlock, toBlock, schema) => {
28397        trimBr(true, fromBlock);
28398        trimBr(false, toBlock);
28399        return getInsertionPoint(fromBlock, toBlock).fold(curry(sidelongBlockMerge, rootNode, fromBlock, toBlock, schema), curry(nestedBlockMerge, rootNode, fromBlock, toBlock, schema));
28400    };
28401    const mergeBlocks = (rootNode, forward, block1, block2, schema) => forward ? mergeBlockInto(rootNode, block2, block1, schema) : mergeBlockInto(rootNode, block1, block2, schema);
28402
28403    const backspaceDelete$b = (editor, forward) => {
28404        const rootNode = SugarElement.fromDom(editor.getBody());
28405        const position = read$1(editor.schema, rootNode.dom, forward, editor.selection.getRng())
28406            .map((blockBoundary) => () => {
28407            mergeBlocks(rootNode, forward, blockBoundary.from.block, blockBoundary.to.block, editor.schema)
28408                .each((pos) => {
28409                editor.selection.setRng(pos.toRange());
28410            });
28411        });
28412        return position;
28413    };
28414
28415    const deleteRangeMergeBlocks = (rootNode, selection, schema) => {
28416        const rng = selection.getRng();
28417        return lift2(getParentBlock$2(rootNode, SugarElement.fromDom(rng.startContainer)), getParentBlock$2(rootNode, SugarElement.fromDom(rng.endContainer)), (block1, block2) => {
28418            if (!eq(block1, block2)) {
28419                return Optional.some(() => {
28420                    rng.deleteContents();
28421                    mergeBlocks(rootNode, true, block1, block2, schema).each((pos) => {
28422                        selection.setRng(pos.toRange());
28423                    });
28424                });
28425            }
28426            else {
28427                return Optional.none();
28428            }
28429        }).getOr(Optional.none());
28430    };
28431    const isRawNodeInTable = (root, rawNode) => {
28432        const node = SugarElement.fromDom(rawNode);
28433        const isRoot = curry(eq, root);
28434        return ancestor$5(node, isTableCell$2, isRoot).isSome();
28435    };
28436    const isSelectionInTable = (root, rng) => isRawNodeInTable(root, rng.startContainer) || isRawNodeInTable(root, rng.endContainer);
28437    const isEverythingSelected = (root, rng) => {
28438        const noPrevious = prevPosition(root.dom, CaretPosition.fromRangeStart(rng)).isNone();
28439        const noNext = nextPosition(root.dom, CaretPosition.fromRangeEnd(rng)).isNone();
28440        return !isSelectionInTable(root, rng) && noPrevious && noNext;
28441    };
28442    const emptyEditor = (editor) => {
28443        return Optional.some(() => {
28444            editor.setContent('');
28445            editor.selection.setCursorLocation();
28446        });
28447    };
28448    const deleteRange$3 = (editor) => {
28449        const rootNode = SugarElement.fromDom(editor.getBody());
28450        const rng = editor.selection.getRng();
28451        return isEverythingSelected(rootNode, rng) ? emptyEditor(editor) : deleteRangeMergeBlocks(rootNode, editor.selection, editor.schema);
28452    };
28453    const backspaceDelete$a = (editor, _forward) => editor.selection.isCollapsed() ? Optional.none() : deleteRange$3(editor);
28454
28455    const showCaret = (direction, editor, node, before, scrollIntoView) => 
28456    // TODO: Figure out a better way to handle this dependency
28457    Optional.from(editor._selectionOverrides.showCaret(direction, node, before, scrollIntoView));
28458    const getNodeRange = (node) => {
28459        const rng = node.ownerDocument.createRange();
28460        rng.selectNode(node);
28461        return rng;
28462    };
28463    const selectNode = (editor, node) => {
28464        const e = editor.dispatch('BeforeObjectSelected', { target: node });
28465        if (e.isDefaultPrevented()) {
28466            return Optional.none();
28467        }
28468        return Optional.some(getNodeRange(node));
28469    };
28470    const renderCaretAtRange = (editor, range, scrollIntoView) => {
28471        const normalizedRange = normalizeRange$2(1, editor.getBody(), range);
28472        const caretPosition = CaretPosition.fromRangeStart(normalizedRange);
28473        const caretPositionNode = caretPosition.getNode();
28474        if (isInlineFakeCaretTarget(caretPositionNode)) {
28475            return showCaret(1, editor, caretPositionNode, !caretPosition.isAtEnd(), false);
28476        }
28477        const caretPositionBeforeNode = caretPosition.getNode(true);
28478        if (isInlineFakeCaretTarget(caretPositionBeforeNode)) {
28479            return showCaret(1, editor, caretPositionBeforeNode, false, false);
28480        }
28481        // TODO: Should render caret before/after depending on where you click on the page forces after now
28482        const ceRoot = getContentEditableRoot$1(editor.dom.getRoot(), caretPosition.getNode());
28483        if (isInlineFakeCaretTarget(ceRoot)) {
28484            return showCaret(1, editor, ceRoot, false, scrollIntoView);
28485        }
28486        return Optional.none();
28487    };
28488    const renderRangeCaret = (editor, range, scrollIntoView) => range.collapsed ? renderCaretAtRange(editor, range, scrollIntoView).getOr(range) : range;
28489
28490    const isBeforeBoundary = (pos) => isBeforeContentEditableFalse(pos) || isBeforeMedia(pos);
28491    const isAfterBoundary = (pos) => isAfterContentEditableFalse(pos) || isAfterMedia(pos);
28492    const trimEmptyTextNode = (dom, node) => {
28493        if (isText$b(node) && node.data.length === 0) {
28494            dom.remove(node);
28495        }
28496    };
28497    const deleteContentAndShowCaret = (editor, range, node, direction, forward, peekCaretPosition) => {
28498        showCaret(direction, editor, peekCaretPosition.getNode(!forward), forward, true).each((caretRange) => {
28499            // Delete the selected content
28500            if (range.collapsed) {
28501                const deleteRange = range.cloneRange();
28502                if (forward) {
28503                    deleteRange.setEnd(caretRange.startContainer, caretRange.startOffset);
28504                }
28505                else {
28506                    deleteRange.setStart(caretRange.endContainer, caretRange.endOffset);
28507                }
28508                deleteRange.deleteContents();
28509            }
28510            else {
28511                range.deleteContents();
28512            }
28513            editor.selection.setRng(caretRange);
28514        });
28515        trimEmptyTextNode(editor.dom, node);
28516    };
28517    // If the caret position is next to a fake caret target element (eg cef/media) after a delete operation, then ensure a caret is added
28518    // eg. <span cE=false>a|b -> <span cE=false>|bc
28519    // Note: We also need to handle the actual deletion, as some browsers (eg IE) move the selection to the opposite side of the cef element
28520    const deleteBoundaryText = (editor, forward) => {
28521        const range = editor.selection.getRng();
28522        if (!isText$b(range.commonAncestorContainer)) {
28523            return Optional.none();
28524        }
28525        const direction = forward ? 1 /* HDirection.Forwards */ : -1 /* HDirection.Backwards */;
28526        const caretWalker = CaretWalker(editor.getBody());
28527        const getNextPosFn = curry(getVisualCaretPosition, forward ? caretWalker.next : caretWalker.prev);
28528        const isBeforeFn = forward ? isBeforeBoundary : isAfterBoundary;
28529        // Get the next caret position. ie where it'll be after the delete
28530        const caretPosition = getNormalizedRangeEndPoint(direction, editor.getBody(), range);
28531        const nextCaretPosition = getNextPosFn(caretPosition);
28532        const normalizedNextCaretPosition = nextCaretPosition ? normalizePosition(forward, nextCaretPosition) : nextCaretPosition;
28533        if (!normalizedNextCaretPosition || !isMoveInsideSameBlock(caretPosition, normalizedNextCaretPosition)) {
28534            return Optional.none();
28535        }
28536        else if (isBeforeFn(normalizedNextCaretPosition)) {
28537            return Optional.some(() => deleteContentAndShowCaret(editor, range, caretPosition.getNode(), direction, forward, normalizedNextCaretPosition));
28538        }
28539        // Peek ahead and see if the next element is a cef/media element
28540        const peekCaretPosition = getNextPosFn(normalizedNextCaretPosition);
28541        if (peekCaretPosition && isBeforeFn(peekCaretPosition)) {
28542            if (isMoveInsideSameBlock(normalizedNextCaretPosition, peekCaretPosition)) {
28543                return Optional.some(() => deleteContentAndShowCaret(editor, range, caretPosition.getNode(), direction, forward, peekCaretPosition));
28544            }
28545        }
28546        return Optional.none();
28547    };
28548    const backspaceDelete$9 = (editor, forward) => deleteBoundaryText(editor, forward);
28549
28550    const getEdgeCefPosition = (editor, atStart) => {
28551        const root = editor.getBody();
28552        return atStart ? firstPositionIn(root).filter(isBeforeContentEditableFalse) :
28553            lastPositionIn(root).filter(isAfterContentEditableFalse);
28554    };
28555    const isCefAtEdgeSelected = (editor) => {
28556        const rng = editor.selection.getRng();
28557        return !rng.collapsed
28558            && (getEdgeCefPosition(editor, true).exists((pos) => pos.isEqual(CaretPosition.fromRangeStart(rng)))
28559                || getEdgeCefPosition(editor, false).exists((pos) => pos.isEqual(CaretPosition.fromRangeEnd(rng))));
28560    };
28561
28562    const isCompoundElement = (node) => isNonNullable(node) && (isTableCell$2(SugarElement.fromDom(node)) || isListItem$2(SugarElement.fromDom(node)));
28563    const DeleteAction = Adt.generate([
28564        { remove: ['element'] },
28565        { moveToElement: ['element'] },
28566        { moveToPosition: ['position'] }
28567    ]);
28568    const isAtContentEditableBlockCaret = (forward, from) => {
28569        const elm = from.getNode(!forward);
28570        const caretLocation = forward ? 'after' : 'before';
28571        return isElement$7(elm) && elm.getAttribute('data-mce-caret') === caretLocation;
28572    };
28573    const isDeleteFromCefDifferentBlocks = (root, forward, from, to, schema) => {
28574        const inSameBlock = (elm) => schema.isInline(elm.nodeName.toLowerCase()) && !isInSameBlock(from, to, root);
28575        return getRelativeCefElm(!forward, from).fold(() => getRelativeCefElm(forward, to).fold(never, inSameBlock), inSameBlock);
28576    };
28577    const deleteEmptyBlockOrMoveToCef = (schema, root, forward, from, to) => {
28578        // TODO: TINY-8865 - This may not be safe to cast as Node below and alternative solutions need to be looked into
28579        const toCefElm = to.getNode(!forward);
28580        return getParentBlock$2(SugarElement.fromDom(root), SugarElement.fromDom(from.getNode())).map((blockElm) => isEmpty$4(schema, blockElm) ? DeleteAction.remove(blockElm.dom) : DeleteAction.moveToElement(toCefElm)).orThunk(() => Optional.some(DeleteAction.moveToElement(toCefElm)));
28581    };
28582    const findCefPosition = (root, forward, from, schema) => fromPosition(forward, root, from).bind((to) => {
28583        if (isCompoundElement(to.getNode())) {
28584            return Optional.none();
28585        }
28586        else if (isDeleteFromCefDifferentBlocks(root, forward, from, to, schema)) {
28587            return Optional.none();
28588        }
28589        else if (forward && isContentEditableFalse$a(to.getNode())) {
28590            return deleteEmptyBlockOrMoveToCef(schema, root, forward, from, to);
28591        }
28592        else if (!forward && isContentEditableFalse$a(to.getNode(true))) {
28593            return deleteEmptyBlockOrMoveToCef(schema, root, forward, from, to);
28594        }
28595        else if (forward && isAfterContentEditableFalse(from)) {
28596            return Optional.some(DeleteAction.moveToPosition(to));
28597        }
28598        else if (!forward && isBeforeContentEditableFalse(from)) {
28599            return Optional.some(DeleteAction.moveToPosition(to));
28600        }
28601        else {
28602            return Optional.none();
28603        }
28604    });
28605    const getContentEditableBlockAction = (forward, elm) => {
28606        if (isNullable(elm)) {
28607            return Optional.none();
28608        }
28609        else if (forward && isContentEditableFalse$a(elm.nextSibling)) {
28610            return Optional.some(DeleteAction.moveToElement(elm.nextSibling));
28611        }
28612        else if (!forward && isContentEditableFalse$a(elm.previousSibling)) {
28613            return Optional.some(DeleteAction.moveToElement(elm.previousSibling));
28614        }
28615        else {
28616            return Optional.none();
28617        }
28618    };
28619    const skipMoveToActionFromInlineCefToContent = (root, from, deleteAction) => deleteAction.fold((elm) => Optional.some(DeleteAction.remove(elm)), (elm) => Optional.some(DeleteAction.moveToElement(elm)), (to) => {
28620        if (isInSameBlock(from, to, root)) {
28621            return Optional.none();
28622        }
28623        else {
28624            return Optional.some(DeleteAction.moveToPosition(to));
28625        }
28626    });
28627    const getContentEditableAction = (root, forward, from, schema) => {
28628        if (isAtContentEditableBlockCaret(forward, from)) {
28629            return getContentEditableBlockAction(forward, from.getNode(!forward))
28630                .orThunk(() => findCefPosition(root, forward, from, schema));
28631        }
28632        else {
28633            return findCefPosition(root, forward, from, schema).bind((deleteAction) => skipMoveToActionFromInlineCefToContent(root, from, deleteAction));
28634        }
28635    };
28636    const read = (root, forward, rng, schema) => {
28637        const normalizedRange = normalizeRange$2(forward ? 1 : -1, root, rng);
28638        const from = CaretPosition.fromRangeStart(normalizedRange);
28639        const rootElement = SugarElement.fromDom(root);
28640        // TODO: TINY-8865 - This may not be safe to cast as Node below and alternative solutions need to be looked into
28641        if (!forward && isAfterContentEditableFalse(from)) {
28642            return Optional.some(DeleteAction.remove(from.getNode(true)));
28643        }
28644        else if (forward && isBeforeContentEditableFalse(from)) {
28645            return Optional.some(DeleteAction.remove(from.getNode()));
28646        }
28647        else if (!forward && isBeforeContentEditableFalse(from) && isAfterBr(rootElement, from, schema)) {
28648            return findPreviousBr(rootElement, from, schema).map((br) => DeleteAction.remove(br.getNode()));
28649        }
28650        else if (forward && isAfterContentEditableFalse(from) && isBeforeBr$1(rootElement, from, schema)) {
28651            return findNextBr(rootElement, from, schema).map((br) => DeleteAction.remove(br.getNode()));
28652        }
28653        else {
28654            return getContentEditableAction(root, forward, from, schema);
28655        }
28656    };
28657
28658    const deleteElement$1 = (editor, forward) => (element) => {
28659        editor._selectionOverrides.hideFakeCaret();
28660        deleteElement$2(editor, forward, SugarElement.fromDom(element));
28661        return true;
28662    };
28663    const moveToElement = (editor, forward) => (element) => {
28664        const pos = forward ? CaretPosition.before(element) : CaretPosition.after(element);
28665        editor.selection.setRng(pos.toRange());
28666        return true;
28667    };
28668    const moveToPosition = (editor) => (pos) => {
28669        editor.selection.setRng(pos.toRange());
28670        return true;
28671    };
28672    const getAncestorCe = (editor, node) => Optional.from(getContentEditableRoot$1(editor.getBody(), node));
28673    const backspaceDeleteCaret = (editor, forward) => {
28674        const selectedNode = editor.selection.getNode(); // is the parent node if cursor before/after cef
28675        // Cases:
28676        // 1. CEF selectedNode -> return true
28677        // 2. CET selectedNode -> try to delete, return true if possible else false
28678        // 3. CET ancestor -> try to delete, return true if possible else false
28679        // 4. no CET/CEF ancestor -> try to delete, return true if possible else false
28680        // 5. CEF ancestor -> return true
28681        return getAncestorCe(editor, selectedNode).filter(isContentEditableFalse$a).fold(() => read(editor.getBody(), forward, editor.selection.getRng(), editor.schema).map((deleteAction) => () => deleteAction.fold(deleteElement$1(editor, forward), moveToElement(editor, forward), moveToPosition(editor))), () => Optional.some(noop));
28682    };
28683    const deleteOffscreenSelection = (rootElement) => {
28684        each$e(descendants(rootElement, '.mce-offscreen-selection'), remove$8);
28685    };
28686    const backspaceDeleteRange = (editor, forward) => {
28687        const selectedNode = editor.selection.getNode(); // is the cef node if cef is selected
28688        // Cases:
28689        // 1. Table cell -> return false, as this is handled by `TableDelete` instead
28690        // 2. CEF selectedNode
28691        //    a. no ancestor CET/CEF || CET ancestor -> run delete code and return true
28692        //    b. CEF ancestor -> return true
28693        // 3. non-CEF selectedNode -> return false
28694        if (isContentEditableFalse$a(selectedNode) && !isTableCell$3(selectedNode)) {
28695            const hasCefAncestor = getAncestorCe(editor, selectedNode.parentNode).filter(isContentEditableFalse$a);
28696            return hasCefAncestor.fold(() => Optional.some(() => {
28697                deleteOffscreenSelection(SugarElement.fromDom(editor.getBody()));
28698                deleteElement$2(editor, forward, SugarElement.fromDom(editor.selection.getNode()));
28699                paddEmptyBody(editor);
28700            }), () => Optional.some(noop));
28701        }
28702        if (isCefAtEdgeSelected(editor)) {
28703            return Optional.some(() => {
28704                deleteRangeContents(editor, editor.selection.getRng(), SugarElement.fromDom(editor.getBody()));
28705            });
28706        }
28707        return Optional.none();
28708    };
28709    const paddEmptyElement = (editor) => {
28710        const dom = editor.dom, selection = editor.selection;
28711        const ceRoot = getContentEditableRoot$1(editor.getBody(), selection.getNode());
28712        if (isContentEditableTrue$3(ceRoot) && dom.isBlock(ceRoot) && dom.isEmpty(ceRoot)) {
28713            const br = dom.create('br', { 'data-mce-bogus': '1' });
28714            dom.setHTML(ceRoot, '');
28715            ceRoot.appendChild(br);
28716            selection.setRng(CaretPosition.before(br).toRange());
28717        }
28718        return true;
28719    };
28720    const backspaceDelete$8 = (editor, forward) => {
28721        if (editor.selection.isCollapsed()) {
28722            return backspaceDeleteCaret(editor, forward);
28723        }
28724        else {
28725            return backspaceDeleteRange(editor, forward);
28726        }
28727    };
28728
28729    const backspaceDelete$7 = (editor, forward) => {
28730        const dom = editor.dom;
28731        const startBlock = dom.getParent(editor.selection.getStart(), dom.isBlock);
28732        const endBlock = dom.getParent(editor.selection.getEnd(), dom.isBlock);
28733        const body = editor.getBody();
28734        const startBlockName = startBlock?.nodeName?.toLowerCase();
28735        // Only act on single root div that is not empty
28736        if (startBlockName === 'div' && startBlock && endBlock && startBlock === body.firstChild && endBlock === body.lastChild && !dom.isEmpty(body)) {
28737            const wrapper = startBlock.cloneNode(false);
28738            const deleteAction = () => {
28739                if (forward) {
28740                    execNativeForwardDeleteCommand(editor);
28741                }
28742                else {
28743                    execNativeDeleteCommand(editor);
28744                }
28745                // Div was deleted by delete operation then lets restore it
28746                if (body.firstChild !== startBlock) {
28747                    const bookmark = createBookmark(editor.selection.getRng(), () => document.createElement('span'));
28748                    Array.from(body.childNodes).forEach((node) => wrapper.appendChild(node));
28749                    body.appendChild(wrapper);
28750                    editor.selection.setRng(resolveBookmark(bookmark));
28751                }
28752            };
28753            return Optional.some(deleteAction);
28754        }
28755        return Optional.none();
28756    };
28757
28758    const deleteCaret$2 = (editor, forward) => {
28759        const fromPos = CaretPosition.fromRangeStart(editor.selection.getRng());
28760        return fromPosition(forward, editor.getBody(), fromPos)
28761            .filter((pos) => forward ? isBeforeImageBlock(pos) : isAfterImageBlock(pos))
28762            .bind((pos) => getChildNodeAtRelativeOffset(forward ? 0 : -1, pos))
28763            .map((elm) => () => editor.selection.select(elm));
28764    };
28765    const backspaceDelete$6 = (editor, forward) => editor.selection.isCollapsed() ? deleteCaret$2(editor, forward) : Optional.none();
28766
28767    const isText$2 = isText$b;
28768    const startsWithCaretContainer = (node) => isText$2(node) && node.data[0] === ZWSP$1;
28769    const endsWithCaretContainer = (node) => isText$2(node) && node.data[node.data.length - 1] === ZWSP$1;
28770    const createZwsp = (node) => {
28771        const doc = node.ownerDocument ?? document;
28772        return doc.createTextNode(ZWSP$1);
28773    };
28774    const insertBefore$1 = (node) => {
28775        if (isText$2(node.previousSibling)) {
28776            if (endsWithCaretContainer(node.previousSibling)) {
28777                return node.previousSibling;
28778            }
28779            else {
28780                node.previousSibling.appendData(ZWSP$1);
28781                return node.previousSibling;
28782            }
28783        }
28784        else if (isText$2(node)) {
28785            if (startsWithCaretContainer(node)) {
28786                return node;
28787            }
28788            else {
28789                node.insertData(0, ZWSP$1);
28790                return node;
28791            }
28792        }
28793        else {
28794            const newNode = createZwsp(node);
28795            node.parentNode?.insertBefore(newNode, node);
28796            return newNode;
28797        }
28798    };
28799    const insertAfter$1 = (node) => {
28800        if (isText$2(node.nextSibling)) {
28801            if (startsWithCaretContainer(node.nextSibling)) {
28802                return node.nextSibling;
28803            }
28804            else {
28805                node.nextSibling.insertData(0, ZWSP$1);
28806                return node.nextSibling;
28807            }
28808        }
28809        else if (isText$2(node)) {
28810            if (endsWithCaretContainer(node)) {
28811                return node;
28812            }
28813            else {
28814                node.appendData(ZWSP$1);
28815                return node;
28816            }
28817        }
28818        else {
28819            const newNode = createZwsp(node);
28820            if (node.nextSibling) {
28821                node.parentNode?.insertBefore(newNode, node.nextSibling);
28822            }
28823            else {
28824                node.parentNode?.appendChild(newNode);
28825            }
28826            return newNode;
28827        }
28828    };
28829    const insertInline = (before, node) => before ? insertBefore$1(node) : insertAfter$1(node);
28830    const insertInlineBefore = curry(insertInline, true);
28831    const insertInlineAfter = curry(insertInline, false);
28832
28833    const insertInlinePos = (pos, before) => {
28834        if (isText$b(pos.container())) {
28835            return insertInline(before, pos.container());
28836        }
28837        else {
28838            // TODO: TINY-8865 - This may not be safe to cast as Node and alternative solutions need to be looked into
28839            return insertInline(before, pos.getNode());
28840        }
28841    };
28842    const isPosCaretContainer = (pos, caret) => {
28843        const caretNode = caret.get();
28844        return caretNode && pos.container() === caretNode && isCaretContainerInline(caretNode);
28845    };
28846    const renderCaret = (caret, location) => location.fold((element) => {
28847        remove$2(caret.get());
28848        const text = insertInlineBefore(element);
28849        caret.set(text);
28850        return Optional.some(CaretPosition(text, text.length - 1));
28851    }, (element) => // Start
28852     firstPositionIn(element).map((pos) => {
28853        if (!isPosCaretContainer(pos, caret)) {
28854            remove$2(caret.get());
28855            const text = insertInlinePos(pos, true);
28856            caret.set(text);
28857            return CaretPosition(text, 1);
28858        }
28859        else {
28860            const node = caret.get();
28861            return CaretPosition(node, 1);
28862        }
28863    }), (element) => // End
28864     lastPositionIn(element).map((pos) => {
28865        if (!isPosCaretContainer(pos, caret)) {
28866            remove$2(caret.get());
28867            const text = insertInlinePos(pos, false);
28868            caret.set(text);
28869            return CaretPosition(text, text.length - 1);
28870        }
28871        else {
28872            const node = caret.get();
28873            return CaretPosition(node, node.length - 1);
28874        }
28875    }), (element) => {
28876        remove$2(caret.get());
28877        const text = insertInlineAfter(element);
28878        caret.set(text);
28879        return Optional.some(CaretPosition(text, 1));
28880    });
28881
28882    const evaluateUntil = (fns, args) => {
28883        for (let i = 0; i < fns.length; i++) {
28884            const result = fns[i].apply(null, args);
28885            if (result.isSome()) {
28886                return result;
28887            }
28888        }
28889        return Optional.none();
28890    };
28891
28892    const Location = Adt.generate([
28893        { before: ['element'] },
28894        { start: ['element'] },
28895        { end: ['element'] },
28896        { after: ['element'] }
28897    ]);
28898    const rescope$1 = (rootNode, node) => {
28899        const parentBlock = getParentBlock$3(node, rootNode);
28900        return parentBlock ? parentBlock : rootNode;
28901    };
28902    const before = (isInlineTarget, rootNode, pos) => {
28903        const nPos = normalizeForwards(pos);
28904        const scope = rescope$1(rootNode, nPos.container());
28905        return findRootInline(isInlineTarget, scope, nPos).fold(() => nextPosition(scope, nPos)
28906            .bind(curry(findRootInline, isInlineTarget, scope))
28907            .map((inline) => Location.before(inline)), Optional.none);
28908    };
28909    const isNotInsideFormatCaretContainer = (rootNode, elm) => getParentCaretContainer(rootNode, elm) === null;
28910    const findInsideRootInline = (isInlineTarget, rootNode, pos) => findRootInline(isInlineTarget, rootNode, pos).filter(curry(isNotInsideFormatCaretContainer, rootNode));
28911    const start$1 = (isInlineTarget, rootNode, pos) => {
28912        const nPos = normalizeBackwards(pos);
28913        return findInsideRootInline(isInlineTarget, rootNode, nPos).bind((inline) => {
28914            const prevPos = prevPosition(inline, nPos);
28915            return prevPos.isNone() ? Optional.some(Location.start(inline)) : Optional.none();
28916        });
28917    };
28918    const end = (isInlineTarget, rootNode, pos) => {
28919        const nPos = normalizeForwards(pos);
28920        return findInsideRootInline(isInlineTarget, rootNode, nPos).bind((inline) => {
28921            const nextPos = nextPosition(inline, nPos);
28922            return nextPos.isNone() ? Optional.some(Location.end(inline)) : Optional.none();
28923        });
28924    };
28925    const after = (isInlineTarget, rootNode, pos) => {
28926        const nPos = normalizeBackwards(pos);
28927        const scope = rescope$1(rootNode, nPos.container());
28928        return findRootInline(isInlineTarget, scope, nPos).fold(() => prevPosition(scope, nPos)
28929            .bind(curry(findRootInline, isInlineTarget, scope))
28930            .map((inline) => Location.after(inline)), Optional.none);
28931    };
28932    const isValidLocation = (location) => !isRtl(getElement(location));
28933    const readLocation = (isInlineTarget, rootNode, pos) => {
28934        const location = evaluateUntil([
28935            before,
28936            start$1,
28937            end,
28938            after
28939        ], [isInlineTarget, rootNode, pos]);
28940        return location.filter(isValidLocation);
28941    };
28942    const getElement = (location) => location.fold(identity, // Before
28943    identity, // Start
28944    identity, // End
28945    identity // After
28946    );
28947    const getName = (location) => location.fold(constant('before'), // Before
28948    constant('start'), // Start
28949    constant('end'), // End
28950    constant('after') // After
28951    );
28952    const outside = (location) => location.fold(Location.before, // Before
28953    Location.before, // Start
28954    Location.after, // End
28955    Location.after // After
28956    );
28957    const inside = (location) => location.fold(Location.start, // Before
28958    Location.start, // Start
28959    Location.end, // End
28960    Location.end // After
28961    );
28962    const isEq = (location1, location2) => getName(location1) === getName(location2) && getElement(location1) === getElement(location2);
28963    const betweenInlines = (forward, isInlineTarget, rootNode, from, to, location) => lift2(findRootInline(isInlineTarget, rootNode, from), findRootInline(isInlineTarget, rootNode, to), (fromInline, toInline) => {
28964        if (fromInline !== toInline && hasSameParentBlock(rootNode, fromInline, toInline)) {
28965            // Force after since some browsers normalize and lean left into the closest inline
28966            return Location.after(forward ? fromInline : toInline);
28967        }
28968        else {
28969            return location;
28970        }
28971    }).getOr(location);
28972    const skipNoMovement = (fromLocation, toLocation) => fromLocation.fold(always, (fromLocation) => !isEq(fromLocation, toLocation));
28973    const findLocationTraverse = (forward, isInlineTarget, rootNode, fromLocation, pos) => {
28974        const from = normalizePosition(forward, pos);
28975        const to = fromPosition(forward, rootNode, from).map(curry(normalizePosition, forward));
28976        const location = to.fold(() => fromLocation.map(outside), (to) => readLocation(isInlineTarget, rootNode, to)
28977            .map(curry(betweenInlines, forward, isInlineTarget, rootNode, from, to))
28978            .filter(curry(skipNoMovement, fromLocation)));
28979        return location.filter(isValidLocation);
28980    };
28981    const findLocationSimple = (forward, location) => {
28982        if (forward) {
28983            return location.fold(compose(Optional.some, Location.start), // Before -> Start
28984            Optional.none, compose(Optional.some, Location.after), // End -> After
28985            Optional.none);
28986        }
28987        else {
28988            return location.fold(Optional.none, compose(Optional.some, Location.before), // Before <- Start
28989            Optional.none, compose(Optional.some, Location.end) // End <- After
28990            );
28991        }
28992    };
28993    const findLocation$1 = (forward, isInlineTarget, rootNode, pos) => {
28994        const from = normalizePosition(forward, pos);
28995        const fromLocation = readLocation(isInlineTarget, rootNode, from);
28996        return readLocation(isInlineTarget, rootNode, from).bind(curry(findLocationSimple, forward))
28997            .orThunk(() => findLocationTraverse(forward, isInlineTarget, rootNode, fromLocation, pos));
28998    };
28999
29000    const hasSelectionModifyApi = (editor) => {
29001        return isFunction(editor.selection.getSel().modify);
29002    };
29003    const moveRel = (forward, selection, pos) => {
29004        const delta = forward ? 1 : -1;
29005        selection.setRng(CaretPosition(pos.container(), pos.offset() + delta).toRange());
29006        selection.getSel().modify('move', forward ? 'forward' : 'backward', 'word');
29007        return true;
29008    };
29009    const moveByWord = (forward, editor) => {
29010        const rng = editor.selection.getRng();
29011        const pos = forward ? CaretPosition.fromRangeEnd(rng) : CaretPosition.fromRangeStart(rng);
29012        if (!hasSelectionModifyApi(editor)) {
29013            return false;
29014        }
29015        else if (forward && isBeforeInline(pos)) {
29016            return moveRel(true, editor.selection, pos);
29017        }
29018        else if (!forward && isAfterInline(pos)) {
29019            return moveRel(false, editor.selection, pos);
29020        }
29021        else {
29022            return false;
29023        }
29024    };
29025
29026    var BreakType;
29027    (function (BreakType) {
29028        BreakType[BreakType["Br"] = 0] = "Br";
29029        BreakType[BreakType["Block"] = 1] = "Block";
29030        BreakType[BreakType["Wrap"] = 2] = "Wrap";
29031        BreakType[BreakType["Eol"] = 3] = "Eol";
29032    })(BreakType || (BreakType = {}));
29033    const flip = (direction, positions) => direction === -1 /* HDirection.Backwards */ ? reverse(positions) : positions;
29034    const walk$1 = (direction, caretWalker, pos) => direction === 1 /* HDirection.Forwards */ ? caretWalker.next(pos) : caretWalker.prev(pos);
29035    const getBreakType = (scope, direction, currentPos, nextPos) => {
29036        if (isBr$7(nextPos.getNode(direction === 1 /* HDirection.Forwards */))) {
29037            return BreakType.Br;
29038        }
29039        else if (isInSameBlock(currentPos, nextPos) === false) {
29040            return BreakType.Block;
29041        }
29042        else {
29043            return BreakType.Wrap;
29044        }
29045    };
29046    const getPositionsUntil = (predicate, direction, scope, start) => {
29047        const caretWalker = CaretWalker(scope);
29048        let currentPos = start;
29049        const positions = [];
29050        while (currentPos) {
29051            const nextPos = walk$1(direction, caretWalker, currentPos);
29052            if (!nextPos) {
29053                break;
29054            }
29055            if (isBr$7(nextPos.getNode(false))) {
29056                if (direction === 1 /* HDirection.Forwards */) {
29057                    return { positions: flip(direction, positions).concat([nextPos]), breakType: BreakType.Br, breakAt: Optional.some(nextPos) };
29058                }
29059                else {
29060                    return { positions: flip(direction, positions), breakType: BreakType.Br, breakAt: Optional.some(nextPos) };
29061                }
29062            }
29063            if (!nextPos.isVisible()) {
29064                currentPos = nextPos;
29065                continue;
29066            }
29067            if (predicate(currentPos, nextPos)) {
29068                const breakType = getBreakType(scope, direction, currentPos, nextPos);
29069                return { positions: flip(direction, positions), breakType, breakAt: Optional.some(nextPos) };
29070            }
29071            positions.push(nextPos);
29072            currentPos = nextPos;
29073        }
29074        return { positions: flip(direction, positions), breakType: BreakType.Eol, breakAt: Optional.none() };
29075    };
29076    const getAdjacentLinePositions = (direction, getPositionsUntilBreak, scope, start) => getPositionsUntilBreak(scope, start).breakAt.map((pos) => {
29077        const positions = getPositionsUntilBreak(scope, pos).positions;
29078        return direction === -1 /* HDirection.Backwards */ ? positions.concat(pos) : [pos].concat(positions);
29079    }).getOr([]);
29080    const findClosestHorizontalPositionFromPoint = (positions, x) => foldl(positions, (acc, newPos) => acc.fold(() => Optional.some(newPos), (lastPos) => lift2(head(lastPos.getClientRects()), head(newPos.getClientRects()), (lastRect, newRect) => {
29081        const lastDist = Math.abs(x - lastRect.left);
29082        const newDist = Math.abs(x - newRect.left);
29083        return newDist <= lastDist ? newPos : lastPos;
29084    }).or(acc)), Optional.none());
29085    const findClosestHorizontalPosition = (positions, pos) => head(pos.getClientRects()).bind((targetRect) => findClosestHorizontalPositionFromPoint(positions, targetRect.left));
29086    const getPositionsUntilPreviousLine = curry(getPositionsUntil, CaretPosition.isAbove, -1);
29087    const getPositionsUntilNextLine = curry(getPositionsUntil, CaretPosition.isBelow, 1);
29088    const getPositionsAbove = curry(getAdjacentLinePositions, -1, getPositionsUntilPreviousLine);
29089    const getPositionsBelow = curry(getAdjacentLinePositions, 1, getPositionsUntilNextLine);
29090    const isAtFirstLine = (scope, pos) => getPositionsUntilPreviousLine(scope, pos).breakAt.isNone();
29091    const isAtLastLine = (scope, pos) => getPositionsUntilNextLine(scope, pos).breakAt.isNone();
29092    const getFirstLinePositions = (scope) => firstPositionIn(scope).map((pos) => [pos].concat(getPositionsUntilNextLine(scope, pos).positions)).getOr([]);
29093    const getLastLinePositions = (scope) => lastPositionIn(scope).map((pos) => getPositionsUntilPreviousLine(scope, pos).positions.concat(pos)).getOr([]);
29094    const getClosestPositionAbove = (scope, pos) => findClosestHorizontalPosition(getPositionsAbove(scope, pos), pos);
29095    const getClosestPositionBelow = (scope, pos) => findClosestHorizontalPosition(getPositionsBelow(scope, pos), pos);
29096
29097    const isContentEditableFalse$4 = isContentEditableFalse$a;
29098    const distanceToRectLeft$1 = (clientRect, clientX) => Math.abs(clientRect.left - clientX);
29099    const distanceToRectRight$1 = (clientRect, clientX) => Math.abs(clientRect.right - clientX);
29100    const isNodeClientRect = (rect) => hasNonNullableKey(rect, 'node');
29101    const findClosestClientRect = (clientRects, clientX) => reduce(clientRects, (oldClientRect, clientRect) => {
29102        const oldDistance = Math.min(distanceToRectLeft$1(oldClientRect, clientX), distanceToRectRight$1(oldClientRect, clientX));
29103        const newDistance = Math.min(distanceToRectLeft$1(clientRect, clientX), distanceToRectRight$1(clientRect, clientX));
29104        // cE=false has higher priority
29105        if (newDistance === oldDistance && isNodeClientRect(clientRect) && isContentEditableFalse$4(clientRect.node)) {
29106            return clientRect;
29107        }
29108        if (newDistance < oldDistance) {
29109            return clientRect;
29110        }
29111        return oldClientRect;
29112    });
29113
29114    const getNodeClientRects = (node) => {
29115        const toArrayWithNode = (clientRects) => {
29116            return map$3(clientRects, (rect) => {
29117                const clientRect = clone$1(rect);
29118                clientRect.node = node;
29119                return clientRect;
29120            });
29121        };
29122        if (isElement$7(node)) {
29123            return toArrayWithNode(node.getClientRects());
29124        }
29125        else if (isText$b(node)) {
29126            const rng = node.ownerDocument.createRange();
29127            rng.setStart(node, 0);
29128            rng.setEnd(node, node.data.length);
29129            return toArrayWithNode(rng.getClientRects());
29130        }
29131        else {
29132            return [];
29133        }
29134    };
29135    const getClientRects = (nodes) => bind$3(nodes, getNodeClientRects);
29136
29137    var VDirection;
29138    (function (VDirection) {
29139        VDirection[VDirection["Up"] = -1] = "Up";
29140        VDirection[VDirection["Down"] = 1] = "Down";
29141    })(VDirection || (VDirection = {}));
29142    const findUntil = (direction, root, predicateFn, node) => {
29143        let currentNode = node;
29144        while ((currentNode = findNode(currentNode, direction, isEditableCaretCandidate$1, root))) {
29145            if (predicateFn(currentNode)) {
29146                return;
29147            }
29148        }
29149    };
29150    const walkUntil = (direction, isAboveFn, isBeflowFn, root, predicateFn, caretPosition) => {
29151        let line = 0;
29152        const result = [];
29153        const add = (node) => {
29154            let clientRects = getClientRects([node]);
29155            if (direction === VDirection.Up) {
29156                clientRects = clientRects.reverse();
29157            }
29158            for (let i = 0; i < clientRects.length; i++) {
29159                const clientRect = clientRects[i];
29160                if (isBeflowFn(clientRect, targetClientRect)) {
29161                    continue;
29162                }
29163                if (result.length > 0 && isAboveFn(clientRect, last(result))) {
29164                    line++;
29165                }
29166                clientRect.line = line;
29167                if (predicateFn(clientRect)) {
29168                    return true;
29169                }
29170                result.push(clientRect);
29171            }
29172            return false;
29173        };
29174        const targetClientRect = last(caretPosition.getClientRects());
29175        if (!targetClientRect) {
29176            return result;
29177        }
29178        const node = caretPosition.getNode();
29179        if (node) {
29180            add(node);
29181            findUntil(direction, root, add, node);
29182        }
29183        return result;
29184    };
29185    const aboveLineNumber = (lineNumber, clientRect) => clientRect.line > lineNumber;
29186    const isLineNumber = (lineNumber, clientRect) => clientRect.line === lineNumber;
29187    const upUntil = curry(walkUntil, VDirection.Up, isAbove$1, isBelow$1);
29188    const downUntil = curry(walkUntil, VDirection.Down, isBelow$1, isAbove$1);
29189    const getLastClientRect = (caretPosition) => {
29190        // ASSUMPTION: There should always be at least one client rect here
29191        return last(caretPosition.getClientRects());
29192    };
29193    const positionsUntil = (direction, root, predicateFn, node) => {
29194        const caretWalker = CaretWalker(root);
29195        let walkFn;
29196        let isBelowFn;
29197        let isAboveFn;
29198        let caretPosition;
29199        const result = [];
29200        let line = 0;
29201        if (direction === VDirection.Down) {
29202            walkFn = caretWalker.next;
29203            isBelowFn = isBelow$1;
29204            isAboveFn = isAbove$1;
29205            caretPosition = CaretPosition.after(node);
29206        }
29207        else {
29208            walkFn = caretWalker.prev;
29209            isBelowFn = isAbove$1;
29210            isAboveFn = isBelow$1;
29211            caretPosition = CaretPosition.before(node);
29212        }
29213        const targetClientRect = getLastClientRect(caretPosition);
29214        do {
29215            if (!caretPosition.isVisible()) {
29216                continue;
29217            }
29218            const rect = getLastClientRect(caretPosition);
29219            if (isAboveFn(rect, targetClientRect)) {
29220                continue;
29221            }
29222            if (result.length > 0 && isBelowFn(rect, last(result))) {
29223                line++;
29224            }
29225            const clientRect = clone$1(rect);
29226            clientRect.position = caretPosition;
29227            clientRect.line = line;
29228            if (predicateFn(clientRect)) {
29229                return result;
29230            }
29231            result.push(clientRect);
29232        } while ((caretPosition = walkFn(caretPosition)));
29233        return result;
29234    };
29235    const isAboveLine = (lineNumber) => (clientRect) => aboveLineNumber(lineNumber, clientRect);
29236    const isLine = (lineNumber) => (clientRect) => isLineNumber(lineNumber, clientRect);
29237
29238    const moveToRange = (editor, rng) => {
29239        editor.selection.setRng(rng);
29240        // Don't reuse the original range as TinyMCE will adjust it
29241        scrollRangeIntoView(editor, editor.selection.getRng());
29242    };
29243    const renderRangeCaretOpt = (editor, range, scrollIntoView) => Optional.some(renderRangeCaret(editor, range, scrollIntoView));
29244    const getAbsPositionElement = (pos, direction) => {
29245        const node = pos.getNode(direction === -1 /* HDirection.Backwards */);
29246        return isNonNullable(node) && isAbsPositionedElement(node) ? Optional.some(node) : Optional.none();
29247    };
29248    const elementToRange = (editor, node) => {
29249        const rng = editor.dom.createRng();
29250        rng.selectNode(node);
29251        return rng;
29252    };
29253    const moveHorizontally = (editor, direction, range, isBefore, isAfter, isElement) => {
29254        const forwards = direction === 1 /* HDirection.Forwards */;
29255        const caretWalker = CaretWalker(editor.getBody());
29256        const getNextPosFn = curry(getVisualCaretPosition, forwards ? caretWalker.next : caretWalker.prev);
29257        const isBeforeFn = forwards ? isBefore : isAfter;
29258        if (!range.collapsed) {
29259            const node = getSelectedNode(range);
29260            if (isElement(node)) {
29261                if (isAbsPositionedElement(node)) {
29262                    const caretPosition = getNormalizedRangeEndPoint(direction, editor.getBody(), range);
29263                    return Optional.from(getNextPosFn(caretPosition)).map((next) => next.toRange());
29264                }
29265                else {
29266                    return showCaret(direction, editor, node, direction === -1 /* HDirection.Backwards */, false);
29267                }
29268            }
29269            else if (isCefAtEdgeSelected(editor)) {
29270                const newRange = range.cloneRange();
29271                newRange.collapse(direction === -1 /* HDirection.Backwards */);
29272                return Optional.from(newRange);
29273            }
29274        }
29275        const caretPosition = getNormalizedRangeEndPoint(direction, editor.getBody(), range);
29276        if (isBeforeFn(caretPosition)) {
29277            return selectNode(editor, caretPosition.getNode(!forwards));
29278        }
29279        let nextCaretPosition = getNextPosFn(caretPosition);
29280        const rangeIsInContainerBlock = isRangeInCaretContainerBlock(range);
29281        if (!nextCaretPosition) {
29282            return rangeIsInContainerBlock ? Optional.some(range) : Optional.none();
29283        }
29284        else {
29285            nextCaretPosition = normalizePosition(forwards, nextCaretPosition);
29286        }
29287        if (isBeforeFn(nextCaretPosition)) {
29288            return getAbsPositionElement(nextCaretPosition, direction).fold(() => showCaret(direction, editor, nextCaretPosition?.getNode(!forwards), forwards, false), (el) => Optional.some(elementToRange(editor, el)));
29289        }
29290        // Peek ahead for handling of ab|c<span cE=false> -> abc|<span cE=false>
29291        const peekCaretPosition = getNextPosFn(nextCaretPosition);
29292        if (peekCaretPosition && isBeforeFn(peekCaretPosition)) {
29293            if (isMoveInsideSameBlock(nextCaretPosition, peekCaretPosition)) {
29294                return getAbsPositionElement(nextCaretPosition, direction).fold(() => showCaret(direction, editor, peekCaretPosition.getNode(!forwards), forwards, false), (el) => Optional.some(elementToRange(editor, el)));
29295            }
29296        }
29297        if (rangeIsInContainerBlock) {
29298            return renderRangeCaretOpt(editor, nextCaretPosition.toRange(), false);
29299        }
29300        return Optional.none();
29301    };
29302    const moveVertically$1 = (editor, direction, range, isBefore, isAfter, isElement) => {
29303        const caretPosition = getNormalizedRangeEndPoint(direction, editor.getBody(), range);
29304        const caretClientRect = last(caretPosition.getClientRects());
29305        const forwards = direction === VDirection.Down;
29306        const root = editor.getBody();
29307        if (!caretClientRect) {
29308            return Optional.none();
29309        }
29310        if (isCefAtEdgeSelected(editor)) {
29311            const caretPosition = forwards ? CaretPosition.fromRangeEnd(range) : CaretPosition.fromRangeStart(range);
29312            const getClosestFn = !forwards ? getClosestPositionAbove : getClosestPositionBelow;
29313            return getClosestFn(root, caretPosition)
29314                .orThunk(() => Optional.from(caretPosition))
29315                .map((pos) => pos.toRange());
29316        }
29317        const walkerFn = forwards ? downUntil : upUntil;
29318        const linePositions = walkerFn(root, isAboveLine(1), caretPosition);
29319        const nextLinePositions = filter$5(linePositions, isLine(1));
29320        const clientX = caretClientRect.left;
29321        const nextLineRect = findClosestClientRect(nextLinePositions, clientX);
29322        if (nextLineRect && isElement(nextLineRect.node)) {
29323            const dist1 = Math.abs(clientX - nextLineRect.left);
29324            const dist2 = Math.abs(clientX - nextLineRect.right);
29325            return showCaret(direction, editor, nextLineRect.node, dist1 < dist2, false);
29326        }
29327        let currentNode;
29328        if (isBefore(caretPosition)) {
29329            currentNode = caretPosition.getNode();
29330        }
29331        else if (isAfter(caretPosition)) {
29332            currentNode = caretPosition.getNode(true);
29333        }
29334        else {
29335            currentNode = getSelectedNode(range);
29336        }
29337        if (currentNode) {
29338            const caretPositions = positionsUntil(direction, root, isAboveLine(1), currentNode);
29339            let closestNextLineRect = findClosestClientRect(filter$5(caretPositions, isLine(1)), clientX);
29340            if (closestNextLineRect) {
29341                return renderRangeCaretOpt(editor, closestNextLineRect.position.toRange(), false);
29342            }
29343            closestNextLineRect = last(filter$5(caretPositions, isLine(0)));
29344            if (closestNextLineRect) {
29345                return renderRangeCaretOpt(editor, closestNextLineRect.position.toRange(), false);
29346            }
29347        }
29348        if (nextLinePositions.length === 0) {
29349            return getLineEndPoint(editor, forwards).filter(forwards ? isAfter : isBefore)
29350                .map((pos) => renderRangeCaret(editor, pos.toRange(), false));
29351        }
29352        return Optional.none();
29353    };
29354    const getLineEndPoint = (editor, forward) => {
29355        const rng = editor.selection.getRng();
29356        const from = forward ? CaretPosition.fromRangeEnd(rng) : CaretPosition.fromRangeStart(rng);
29357        const host = getEditingHost(from.container(), editor.getBody());
29358        if (forward) {
29359            const lineInfo = getPositionsUntilNextLine(host, from);
29360            return last$2(lineInfo.positions);
29361        }
29362        else {
29363            const lineInfo = getPositionsUntilPreviousLine(host, from);
29364            return head(lineInfo.positions);
29365        }
29366    };
29367    const moveToLineEndPoint$3 = (editor, forward, isElementPosition) => getLineEndPoint(editor, forward).filter(isElementPosition).exists((pos) => {
29368        editor.selection.setRng(pos.toRange());
29369        return true;
29370    });
29371
29372    const setCaretPosition = (editor, pos) => {
29373        const rng = editor.dom.createRng();
29374        rng.setStart(pos.container(), pos.offset());
29375        rng.setEnd(pos.container(), pos.offset());
29376        editor.selection.setRng(rng);
29377    };
29378    const setSelected = (state, elm) => {
29379        if (state) {
29380            elm.setAttribute('data-mce-selected', 'inline-boundary');
29381        }
29382        else {
29383            elm.removeAttribute('data-mce-selected');
29384        }
29385    };
29386    const renderCaretLocation = (editor, caret, location) => renderCaret(caret, location).map((pos) => {
29387        setCaretPosition(editor, pos);
29388        return location;
29389    });
29390    const getPositionFromRange = (range, root, forward) => {
29391        const start = CaretPosition.fromRangeStart(range);
29392        if (range.collapsed) {
29393            return start;
29394        }
29395        else {
29396            const end = CaretPosition.fromRangeEnd(range);
29397            return forward ? prevPosition(root, end).getOr(end) : nextPosition(root, start).getOr(start);
29398        }
29399    };
29400    const findLocation = (editor, caret, forward) => {
29401        const rootNode = editor.getBody();
29402        const from = getPositionFromRange(editor.selection.getRng(), rootNode, forward);
29403        const isInlineTarget$1 = curry(isInlineTarget, editor);
29404        const location = findLocation$1(forward, isInlineTarget$1, rootNode, from);
29405        return location.bind((location) => renderCaretLocation(editor, caret, location));
29406    };
29407    const toggleInlines = (isInlineTarget, dom, elms) => {
29408        const inlineBoundaries = map$3(descendants(SugarElement.fromDom(dom.getRoot()), '*[data-mce-selected="inline-boundary"]'), (e) => e.dom);
29409        const selectedInlines = filter$5(inlineBoundaries, isInlineTarget);
29410        const targetInlines = filter$5(elms, isInlineTarget);
29411        each$e(difference(selectedInlines, targetInlines), curry(setSelected, false));
29412        each$e(difference(targetInlines, selectedInlines), curry(setSelected, true));
29413    };
29414    const safeRemoveCaretContainer = (editor, caret) => {
29415        const caretValue = caret.get();
29416        if (editor.selection.isCollapsed() && !editor.composing && caretValue) {
29417            const pos = CaretPosition.fromRangeStart(editor.selection.getRng());
29418            if (CaretPosition.isTextPosition(pos) && !isAtZwsp(pos)) {
29419                setCaretPosition(editor, removeAndReposition(caretValue, pos));
29420                caret.set(null);
29421            }
29422        }
29423    };
29424    const renderInsideInlineCaret = (isInlineTarget, editor, caret, elms) => {
29425        if (editor.selection.isCollapsed()) {
29426            const inlines = filter$5(elms, isInlineTarget);
29427            each$e(inlines, (_inline) => {
29428                const pos = CaretPosition.fromRangeStart(editor.selection.getRng());
29429                readLocation(isInlineTarget, editor.getBody(), pos).bind((location) => renderCaretLocation(editor, caret, location));
29430            });
29431        }
29432    };
29433    const move$3 = (editor, caret, forward) => isInlineBoundariesEnabled(editor) ? findLocation(editor, caret, forward).isSome() : false;
29434    const moveWord = (forward, editor, _caret) => isInlineBoundariesEnabled(editor) ? moveByWord(forward, editor) : false;
29435    const setupSelectedState = (editor) => {
29436        const caret = Cell(null);
29437        const isInlineTarget$1 = curry(isInlineTarget, editor);
29438        editor.on('NodeChange', (e) => {
29439            if (isInlineBoundariesEnabled(editor)) {
29440                toggleInlines(isInlineTarget$1, editor.dom, e.parents);
29441                safeRemoveCaretContainer(editor, caret);
29442                renderInsideInlineCaret(isInlineTarget$1, editor, caret, e.parents);
29443            }
29444        });
29445        return caret;
29446    };
29447    const moveNextWord = curry(moveWord, true);
29448    const movePrevWord = curry(moveWord, false);
29449    const moveToLineEndPoint$2 = (editor, forward, caret) => {
29450        if (isInlineBoundariesEnabled(editor)) {
29451            // Try to find the line endpoint, however if one isn't found then assume we're already at the end point
29452            const linePoint = getLineEndPoint(editor, forward).getOrThunk(() => {
29453                const rng = editor.selection.getRng();
29454                return forward ? CaretPosition.fromRangeEnd(rng) : CaretPosition.fromRangeStart(rng);
29455            });
29456            return readLocation(curry(isInlineTarget, editor), editor.getBody(), linePoint).exists((loc) => {
29457                const outsideLoc = outside(loc);
29458                return renderCaret(caret, outsideLoc).exists((pos) => {
29459                    setCaretPosition(editor, pos);
29460                    return true;
29461                });
29462            });
29463        }
29464        else {
29465            return false;
29466        }
29467    };
29468
29469    const rangeFromPositions = (from, to) => {
29470        const range = document.createRange();
29471        range.setStart(from.container(), from.offset());
29472        range.setEnd(to.container(), to.offset());
29473        return range;
29474    };
29475    // Checks for delete at <code>|a</code> when there is only one item left except the zwsp caret container nodes
29476    const hasOnlyTwoOrLessPositionsLeft = (elm) => lift2(firstPositionIn(elm), lastPositionIn(elm), (firstPos, lastPos) => {
29477        const normalizedFirstPos = normalizePosition(true, firstPos);
29478        const normalizedLastPos = normalizePosition(false, lastPos);
29479        return nextPosition(elm, normalizedFirstPos).forall((pos) => pos.isEqual(normalizedLastPos));
29480    }).getOr(true);
29481    const setCaretLocation = (editor, caret) => (location) => renderCaret(caret, location).map((pos) => () => setCaretPosition(editor, pos));
29482    const deleteFromTo = (editor, caret, from, to) => {
29483        const rootNode = editor.getBody();
29484        const isInlineTarget$1 = curry(isInlineTarget, editor);
29485        editor.undoManager.ignore(() => {
29486            editor.selection.setRng(rangeFromPositions(from, to));
29487            // TODO: TINY-9120 - Investigate if this should be using our custom overrides
29488            execNativeDeleteCommand(editor);
29489            readLocation(isInlineTarget$1, rootNode, CaretPosition.fromRangeStart(editor.selection.getRng()))
29490                .map(inside)
29491                .bind(setCaretLocation(editor, caret))
29492                .each(call);
29493        });
29494        editor.nodeChanged();
29495    };
29496    const rescope = (rootNode, node) => {
29497        const parentBlock = getParentBlock$3(node, rootNode);
29498        return parentBlock ? parentBlock : rootNode;
29499    };
29500    const backspaceDeleteCollapsed = (editor, caret, forward, from) => {
29501        const rootNode = rescope(editor.getBody(), from.container());
29502        const isInlineTarget$1 = curry(isInlineTarget, editor);
29503        const fromLocation = readLocation(isInlineTarget$1, rootNode, from);
29504        const location = fromLocation.bind((location) => {
29505            if (forward) {
29506                return location.fold(constant(Optional.some(inside(location))), // Before
29507                Optional.none, // Start
29508                constant(Optional.some(outside(location))), // End
29509                Optional.none // After
29510                );
29511            }
29512            else {
29513                return location.fold(Optional.none, // Before
29514                constant(Optional.some(outside(location))), // Start
29515                Optional.none, // End
29516                constant(Optional.some(inside(location))) // After
29517                );
29518            }
29519        });
29520        return location.map(setCaretLocation(editor, caret))
29521            .getOrThunk(() => {
29522            const toPosition = navigate(forward, rootNode, from);
29523            const toLocation = toPosition.bind((pos) => readLocation(isInlineTarget$1, rootNode, pos));
29524            return lift2(fromLocation, toLocation, () => findRootInline(isInlineTarget$1, rootNode, from).bind((elm) => {
29525                if (hasOnlyTwoOrLessPositionsLeft(elm)) {
29526                    return Optional.some(() => {
29527                        deleteElement$2(editor, forward, SugarElement.fromDom(elm));
29528                    });
29529                }
29530                else {
29531                    return Optional.none();
29532                }
29533            })).getOrThunk(() => toLocation.bind(() => toPosition.map((to) => {
29534                return () => {
29535                    if (forward) {
29536                        deleteFromTo(editor, caret, from, to);
29537                    }
29538                    else {
29539                        deleteFromTo(editor, caret, to, from);
29540                    }
29541                };
29542            })));
29543        });
29544    };
29545    const backspaceDelete$5 = (editor, caret, forward) => {
29546        if (editor.selection.isCollapsed() && isInlineBoundariesEnabled(editor)) {
29547            const from = CaretPosition.fromRangeStart(editor.selection.getRng());
29548            return backspaceDeleteCollapsed(editor, caret, forward, from);
29549        }
29550        return Optional.none();
29551    };
29552
29553    const hasMultipleChildren = (elm) => childNodesCount(elm) > 1;
29554    const getParentsUntil = (editor, pred) => {
29555        const rootElm = SugarElement.fromDom(editor.getBody());
29556        const startElm = SugarElement.fromDom(editor.selection.getStart());
29557        const parents = parentsAndSelf(startElm, rootElm);
29558        return findIndex$2(parents, pred).fold(constant(parents), (index) => parents.slice(0, index));
29559    };
29560    const hasOnlyOneChild = (elm) => childNodesCount(elm) === 1;
29561    const getParentInlinesUntilMultichildInline = (editor) => getParentsUntil(editor, (elm) => editor.schema.isBlock(name(elm)) || hasMultipleChildren(elm));
29562    const getParentInlines = (editor) => getParentsUntil(editor, (el) => editor.schema.isBlock(name(el)));
29563    const getFormatNodes = (editor, parentInlines) => {
29564        const isFormatElement$1 = curry(isFormatElement, editor);
29565        return bind$3(parentInlines, (elm) => isFormatElement$1(elm) ? [elm.dom] : []);
29566    };
29567    const getFormatNodesAtStart = (editor) => {
29568        const parentInlines = getParentInlines(editor);
29569        return getFormatNodes(editor, parentInlines);
29570    };
29571    const deleteLastPosition = (forward, editor, target, parentInlines) => {
29572        const formatNodes = getFormatNodes(editor, parentInlines);
29573        if (formatNodes.length === 0) {
29574            deleteElement$2(editor, forward, target);
29575        }
29576        else {
29577            const pos = replaceWithCaretFormat(target.dom, formatNodes);
29578            editor.selection.setRng(pos.toRange());
29579        }
29580    };
29581    const deleteCaret$1 = (editor, forward) => {
29582        const parentInlines = filter$5(getParentInlinesUntilMultichildInline(editor), hasOnlyOneChild);
29583        return last$2(parentInlines).bind((target) => {
29584            const fromPos = CaretPosition.fromRangeStart(editor.selection.getRng());
29585            if (willDeleteLastPositionInElement(forward, fromPos, target.dom) && !isEmptyCaretFormatElement(target)) {
29586                return Optional.some(() => deleteLastPosition(forward, editor, target, parentInlines));
29587            }
29588            else {
29589                return Optional.none();
29590            }
29591        });
29592    };
29593    const isBrInEmptyElement = (editor, elm) => {
29594        const parentElm = elm.parentElement;
29595        return isBr$7(elm) && !isNull(parentElm) && editor.dom.isEmpty(parentElm);
29596    };
29597    const isEmptyCaret = (elm) => isEmptyCaretFormatElement(SugarElement.fromDom(elm));
29598    const createCaretFormatAtStart = (editor, formatNodes) => {
29599        const startElm = editor.selection.getStart();
29600        // replace <br> in empty node or existing caret at start if applicable
29601        // otherwise create new caret format at start
29602        const pos = isBrInEmptyElement(editor, startElm) || isEmptyCaret(startElm)
29603            ? replaceWithCaretFormat(startElm, formatNodes)
29604            : createCaretFormatAtStart$1(editor.selection.getRng(), formatNodes);
29605        editor.selection.setRng(pos.toRange());
29606    };
29607    const updateCaretFormat = (editor, updateFormats) => {
29608        // Create a caret format at cursor containing missing formats to ensure all formats
29609        // that are supposed to be retained are retained
29610        const missingFormats = difference(updateFormats, getFormatNodesAtStart(editor));
29611        if (missingFormats.length > 0) {
29612            createCaretFormatAtStart(editor, missingFormats);
29613        }
29614    };
29615    const rangeStartsAtTextContainer = (rng) => isText$b(rng.startContainer);
29616    const rangeStartsAtStartOfTextContainer = (rng) => rng.startOffset === 0 && rangeStartsAtTextContainer(rng);
29617    const rangeStartParentIsFormatElement = (editor, rng) => {
29618        const startParent = rng.startContainer.parentElement;
29619        return !isNull(startParent) && isFormatElement(editor, SugarElement.fromDom(startParent));
29620    };
29621    const rangeStartAndEndHaveSameParent = (rng) => {
29622        const startParent = rng.startContainer.parentNode;
29623        const endParent = rng.endContainer.parentNode;
29624        return !isNull(startParent) && !isNull(endParent) && startParent.isEqualNode(endParent);
29625    };
29626    const rangeEndsAtEndOfEndContainer = (rng) => {
29627        const endContainer = rng.endContainer;
29628        return rng.endOffset === (isText$b(endContainer) ? endContainer.length : endContainer.childNodes.length);
29629    };
29630    const rangeEndsAtEndOfStartContainer = (rng) => rangeStartAndEndHaveSameParent(rng) && rangeEndsAtEndOfEndContainer(rng);
29631    const rangeEndsAfterEndOfStartContainer = (rng) => !rng.endContainer.isEqualNode(rng.commonAncestorContainer);
29632    const rangeEndsAtOrAfterEndOfStartContainer = (rng) => rangeEndsAtEndOfStartContainer(rng) || rangeEndsAfterEndOfStartContainer(rng);
29633    const requiresDeleteRangeOverride = (editor) => {
29634        const rng = editor.selection.getRng();
29635        return rangeStartsAtStartOfTextContainer(rng) && rangeStartParentIsFormatElement(editor, rng) && rangeEndsAtOrAfterEndOfStartContainer(rng);
29636    };
29637    const deleteRange$2 = (editor) => {
29638        if (requiresDeleteRangeOverride(editor)) {
29639            const formatNodes = getFormatNodesAtStart(editor);
29640            return Optional.some(() => {
29641                execNativeDeleteCommand(editor);
29642                updateCaretFormat(editor, formatNodes);
29643            });
29644        }
29645        else {
29646            return Optional.none();
29647        }
29648    };
29649    const backspaceDelete$4 = (editor, forward) => editor.selection.isCollapsed() ? deleteCaret$1(editor, forward) : deleteRange$2(editor);
29650    const hasAncestorInlineCaret = (elm, schema) => ancestor$3(elm, (node) => isCaretNode(node.dom), (el) => schema.isBlock(name(el)));
29651    const hasAncestorInlineCaretAtStart = (editor) => hasAncestorInlineCaret(SugarElement.fromDom(editor.selection.getStart()), editor.schema);
29652    const requiresRefreshCaretOverride = (editor) => {
29653        const rng = editor.selection.getRng();
29654        return rng.collapsed && (rangeStartsAtTextContainer(rng) || editor.dom.isEmpty(rng.startContainer)) && !hasAncestorInlineCaretAtStart(editor);
29655    };
29656    const refreshCaret = (editor) => {
29657        if (requiresRefreshCaretOverride(editor)) {
29658            createCaretFormatAtStart(editor, []);
29659        }
29660        return true;
29661    };
29662
29663    const deleteElement = (editor, forward, element) => {
29664        if (isNonNullable(element)) {
29665            return Optional.some(() => {
29666                editor._selectionOverrides.hideFakeCaret();
29667                deleteElement$2(editor, forward, SugarElement.fromDom(element));
29668            });
29669        }
29670        else {
29671            return Optional.none();
29672        }
29673    };
29674    const deleteCaret = (editor, forward) => {
29675        const isNearMedia = forward ? isBeforeMedia : isAfterMedia;
29676        const direction = forward ? 1 /* HDirection.Forwards */ : -1 /* HDirection.Backwards */;
29677        const fromPos = getNormalizedRangeEndPoint(direction, editor.getBody(), editor.selection.getRng());
29678        if (isNearMedia(fromPos)) {
29679            return deleteElement(editor, forward, fromPos.getNode(!forward));
29680        }
29681        else {
29682            return Optional.from(normalizePosition(forward, fromPos))
29683                .filter((pos) => isNearMedia(pos) && isMoveInsideSameBlock(fromPos, pos))
29684                .bind((pos) => deleteElement(editor, forward, pos.getNode(!forward)));
29685        }
29686    };
29687    const deleteRange$1 = (editor, forward) => {
29688        const selectedNode = editor.selection.getNode();
29689        return isMedia$2(selectedNode) ? deleteElement(editor, forward, selectedNode) : Optional.none();
29690    };
29691    const backspaceDelete$3 = (editor, forward) => editor.selection.isCollapsed() ? deleteCaret(editor, forward) : deleteRange$1(editor, forward);
29692
29693    const isEditable = (target) => closest$5(target, (elm) => isContentEditableTrue$3(elm.dom) || isContentEditableFalse$a(elm.dom))
29694        .exists((elm) => isContentEditableTrue$3(elm.dom));
29695    const parseIndentValue = (value) => toInt(value ?? '').getOr(0);
29696    const getIndentStyleName = (useMargin, element) => {
29697        const indentStyleName = useMargin || isTable$1(element) ? 'margin' : 'padding';
29698        const suffix = get$
vendor: 3,375 bytes, lines 29698-29765
296987(element, 'direction') === 'rtl' ? '-right' : '-left';
29699        return indentStyleName + suffix;
29700    };
29701    const indentElement = (dom, command, useMargin, value, unit, element) => {
29702        const indentStyleName = getIndentStyleName(useMargin, SugarElement.fromDom(element));
29703        const parsedValue = parseIndentValue(dom.getStyle(element, indentStyleName));
29704        if (command === 'outdent') {
29705            const styleValue = Math.max(0, parsedValue - value);
29706            dom.setStyle(element, indentStyleName, styleValue ? styleValue + unit : '');
29707        }
29708        else {
29709            const styleValue = (parsedValue + value) + unit;
29710            dom.setStyle(element, indentStyleName, styleValue);
29711        }
29712    };
29713    const validateBlocks = (editor, blocks) => forall(blocks, (block) => {
29714        const indentStyleName = getIndentStyleName(shouldIndentUseMargin(editor), block);
29715        const intentValue = getRaw$1(block, indentStyleName).map(parseIndentValue).getOr(0);
29716        const contentEditable = editor.dom.getContentEditable(block.dom);
29717        return contentEditable !== 'false' && intentValue > 0;
29718    });
29719    const canOutdent = (editor) => {
29720        const blocks = getBlocksToIndent(editor);
29721        return !editor.mode.isReadOnly() && (blocks.length > 1 || validateBlocks(editor, blocks));
29722    };
29723    const canIndent = (editor) => !editor.mode.isReadOnly() && canIndent$1(editor);
29724    const isListComponent = (el) => isList$1(el) || isListItem$2(el);
29725    const parentIsListComponent = (el) => parent(el).exists(isListComponent);
29726    const getBlocksToIndent = (editor) => filter$5(fromDom$1(editor.selection.getSelectedBlocks()), (el) => !isListComponent(el) && !parentIsListComponent(el) && isEditable(el));
29727    const handle = (editor, command) => {
29728        if (editor.mode.isReadOnly()) {
29729            return;
29730        }
29731        const { dom } = editor;
29732        const indentation = getIndentation(editor);
29733        const indentUnit = /[a-z%]+$/i.exec(indentation)?.[0] ?? 'px';
29734        const indentValue = parseIndentValue(indentation);
29735        const useMargin = shouldIndentUseMargin(editor);
29736        each$e(getBlocksToIndent(editor), (block) => {
29737            indentElement(dom, command, useMargin, indentValue, indentUnit, block.dom);
29738        });
29739        if (command === 'indent') {
29740            indentListSelection(editor);
29741        }
29742        else {
29743            outdentListSelection(editor);
29744        }
29745    };
29746    const indent = (editor) => handle(editor, 'indent');
29747    const outdent = (editor) => handle(editor, 'outdent');
29748
29749    const backspaceDelete$2 = (editor) => {
29750        if (editor.selection.isCollapsed() && canOutdent(editor)) {
29751            const dom = editor.dom;
29752            const rng = editor.selection.getRng();
29753            const pos = CaretPosition.fromRangeStart(rng);
29754            const block = dom.getParent(rng.startContainer, dom.isBlock);
29755            if (block !== null && isAtStartOfBlock(SugarElement.fromDom(block), pos, editor.schema)) {
29756                return Optional.some(() => outdent(editor));
29757            }
29758        }
29759        return Optional.none();
29760    };
29761
29762    const deleteRange = (editor, forward) => {
29763        const rng = normalize(editor.selection.getRng());
29764        return isSelectionOverWholeHTMLElement(rng)
29765            ? Optional.some(() => deleteElement$2(editor, forward, SugarElement.fromDom(editor.selection.getNode()
vendor: 25,699 bytes, lines 29765-30361
29765)))
29766            : Optional.none();
29767    };
29768    const backspaceDelete$1 = (editor, forward) => editor.selection.isCollapsed() ? Optional.none() : deleteRange(editor, forward);
29769
29770    const findAction = (editor, caret, forward) => findMap([
29771        backspaceDelete$2,
29772        backspaceDelete$8,
29773        backspaceDelete$9,
29774        (editor, forward) => backspaceDelete$5(editor, caret, forward),
29775        backspaceDelete$b,
29776        backspaceDelete$d,
29777        backspaceDelete$6,
29778        backspaceDelete$3,
29779        backspaceDelete$a,
29780        backspaceDelete$4,
29781        backspaceDelete$7,
29782        backspaceDelete$1,
29783    ], (item) => item(editor, forward))
29784        .filter((_) => editor.selection.isEditable());
29785    const deleteCommand = (editor, caret) => {
29786        const result = findAction(editor, caret, false);
29787        result.fold(() => {
29788            // We can't use an `execEditorDeleteCommand` here, otherwise we'd get
29789            // possible infinite recursion (as it would trigger `deleteCommand` again)
29790            if (editor.selection.isEditable()) {
29791                execNativeDeleteCommand(editor);
29792                paddEmptyBody(editor);
29793            }
29794        }, call);
29795        if (hasListSelection(editor)) {
29796            normalizeLists(editor.dom, editor.getBody());
29797        }
29798    };
29799    const forwardDeleteCommand = (editor, caret) => {
29800        const result = findAction(editor, caret, true);
29801        result.fold(() => {
29802            if (editor.selection.isEditable()) {
29803                execNativeForwardDeleteCommand(editor);
29804            }
29805        }, call);
29806        if (hasListSelection(editor)) {
29807            normalizeLists(editor.dom, editor.getBody());
29808        }
29809    };
29810    const setup$v = (editor, caret) => {
29811        editor.addCommand('delete', () => {
29812            deleteCommand(editor, caret);
29813        });
29814        editor.addCommand('forwardDelete', () => {
29815            forwardDeleteCommand(editor, caret);
29816        });
29817    };
29818
29819    // This is based heavily on Alloy's TapEvent.ts, just modified to use TinyMCE's event system.
29820    const SIGNIFICANT_MOVE = 5;
29821    const LONGPRESS_DELAY = 400;
29822    const getTouch = (event) => {
29823        if (event.touches === undefined || event.touches.length !== 1) {
29824            return Optional.none();
29825        }
29826        return Optional.some(event.touches[0]);
29827    };
29828    const isFarEnough = (touch, data) => {
29829        const distX = Math.abs(touch.clientX - data.x);
29830        const distY = Math.abs(touch.clientY - data.y);
29831        return distX > SIGNIFICANT_MOVE || distY > SIGNIFICANT_MOVE;
29832    };
29833    const setup$u = (editor) => {
29834        const startData = value$1();
29835        const longpressFired = Cell(false);
29836        const debounceLongpress = last$1((e) => {
29837            editor.dispatch('longpress', { ...e, type: 'longpress' });
29838            longpressFired.set(true);
29839        }, LONGPRESS_DELAY);
29840        editor.on('touchstart', (e) => {
29841            getTouch(e).each((touch) => {
29842                debounceLongpress.cancel();
29843                const data = {
29844                    x: touch.clientX,
29845                    y: touch.clientY,
29846                    target: e.target
29847                };
29848                debounceLongpress.throttle(e);
29849                longpressFired.set(false);
29850                startData.set(data);
29851            });
29852        }, true);
29853        editor.on('touchmove', (e) => {
29854            debounceLongpress.cancel();
29855            getTouch(e).each((touch) => {
29856                startData.on((data) => {
29857                    if (isFarEnough(touch, data)) {
29858                        startData.clear();
29859                        longpressFired.set(false);
29860                        editor.dispatch('longpresscancel');
29861                    }
29862                });
29863            });
29864        }, true);
29865        editor.on('touchend touchcancel', (e) => {
29866            debounceLongpress.cancel();
29867            if (e.type === 'touchcancel') {
29868                return;
29869            }
29870            // Cancel the touchend event if a longpress was fired, otherwise fire the tap event
29871            startData.get()
29872                .filter((data) => data.target.isEqualNode(e.target))
29873                .each(() => {
29874                if (longpressFired.get()) {
29875                    e.preventDefault();
29876                }
29877                else {
29878                    editor.dispatch('tap', { ...e, type: 'tap' });
29879                }
29880            });
29881        }, true);
29882    };
29883
29884    /**
29885     * Makes sure that everything gets wrapped in paragraphs.
29886     *
29887     * @private
29888     * @class tinymce.ForceBlocks
29889     */
29890    const isBlockElement = (blockElements, node) => has$2(blockElements, node.nodeName);
29891    const isValidTarget = (schema, node) => {
29892        if (isText$b(node)) {
29893            return true;
29894        }
29895        else if (isElement$7(node)) {
29896            return !isBlockElement(schema.getBlockElements(), node) && !isBookmarkNode$1(node) &&
29897                !isTransparentBlock(schema, node) && !isNonHtmlElementRoot(node) && !isTemplate(node);
29898        }
29899        else {
29900            return false;
29901        }
29902    };
29903    const hasBlockParent = (blockElements, root, node) => {
29904        return exists(parents(SugarElement.fromDom(node), SugarElement.fromDom(root)), (elm) => {
29905            return isBlockElement(blockElements, elm.dom);
29906        });
29907    };
29908    const shouldRemoveTextNode = (blockElements, node) => {
29909        if (isText$b(node)) {
29910            if (node.data.length === 0) {
29911                return true;
29912            }
29913            else if (/^\s+$/.test(node.data)) {
29914                return !node.nextSibling || isBlockElement(blockElements, node.nextSibling) || isNonHtmlElementRoot(node.nextSibling);
29915            }
29916        }
29917        return false;
29918    };
29919    const createRootBlock = (editor) => editor.dom.create(getForcedRootBlock(editor), getForcedRootBlockAttrs(editor));
29920    const addRootBlocks = (editor) => {
29921        const dom = editor.dom, selection = editor.selection;
29922        const schema = editor.schema;
29923        const blockElements = schema.getBlockElements();
29924        const startNode = selection.getStart();
29925        const rootNode = editor.getBody();
29926        let rootBlockNode;
29927        let tempNode;
29928        let bm = null;
29929        const forcedRootBlock = getForcedRootBlock(editor);
29930        if (!startNode || !isElement$7(startNode)) {
29931            return;
29932        }
29933        const rootNodeName = rootNode.nodeName.toLowerCase();
29934        if (!schema.isValidChild(rootNodeName, forcedRootBlock.toLowerCase()) || hasBlockParent(blockElements, rootNode, startNode)) {
29935            return;
29936        }
29937        // Firefox will automatically remove the last BR if you insert nodes next to it and add a BR back if you remove those siblings
29938        // and since the bookmark code inserts temporary nodes an new BR will be constantly removed and added and triggering a selection
29939        // change causing an infinite recursion. So we treat this special case on it's own.
29940        if (rootNode.firstChild === rootNode.lastChild && isBr$7(rootNode.firstChild)) {
29941            rootBlockNode = createRootBlock(editor);
29942            rootBlockNode.appendChild(createPaddingBr().dom);
29943            rootNode.replaceChild(rootBlockNode, rootNode.firstChild);
29944            editor.selection.setCursorLocation(rootBlockNode, 0);
29945            editor.nodeChanged();
29946            return;
29947        }
29948        // Wrap non block elements and text nodes
29949        let node = rootNode.firstChild;
29950        while (node) {
29951            if (isElement$7(node)) {
29952                updateElement(schema, node);
29953            }
29954            if (isValidTarget(schema, node)) {
29955                // Remove empty text nodes and nodes containing only whitespace
29956                if (shouldRemoveTextNode(blockElements, node)) {
29957                    tempNode = node;
29958                    node = node.nextSibling;
29959                    dom.remove(tempNode);
29960                    continue;
29961                }
29962                if (!rootBlockNode) {
29963                    if (!bm && editor.hasFocus()) {
29964                        bm = createBookmark(editor.selection.getRng(), () => document.createElement('span'));
29965                    }
29966                    // Firefox will remove the last BR element if you insert nodes next to it using DOM APIs like insertBefore
29967                    // so for that weird edge case we stop processing.
29968                    if (!node.parentNode) {
29969                        node = null;
29970                        break;
29971                    }
29972                    rootBlockNode = createRootBlock(editor);
29973                    rootNode.insertBefore(rootBlockNode, node);
29974                }
29975                tempNode = node;
29976                node = node.nextSibling;
29977                rootBlockNode.appendChild(tempNode);
29978            }
29979            else {
29980                rootBlockNode = null;
29981                node = node.nextSibling;
29982            }
29983        }
29984        if (bm) {
29985            editor.selection.setRng(resolveBookmark(bm));
29986            editor.nodeChanged();
29987        }
29988    };
29989    const insertEmptyLine = (editor, root, insertBlock) => {
29990        const block = SugarElement.fromDom(createRootBlock(editor));
29991        const br = createPaddingBr();
29992        append$1(block, br);
29993        insertBlock(root, block);
29994        const rng = document.createRange();
29995        rng.setStartBefore(br.dom);
29996        rng.setEndBefore(br.dom);
29997        return rng;
29998    };
29999    const setup$t = (editor) => {
30000        editor.on('NodeChange', () => addRootBlocks(editor));
30001    };
30002
30003    const hasClass = (checkClassName) => (node) => (' ' + node.attr('class') + ' ').indexOf(checkClassName) !== -1;
30004    const replaceMatchWithSpan = (editor, content, cls) => {
30005        return function (match) {
30006            const args = arguments, index = args[args.length - 2];
30007            const prevChar = index > 0 ? content.charAt(index - 1) : '';
30008            // Is value inside an attribute then don't replace
30009            if (prevChar === '"') {
30010                return match;
30011            }
30012            // Is value inside a contentEditable='false' tag
30013            if (prevChar === '>') {
30014                const findStartTagIndex = content.lastIndexOf('<', index);
30015                if (findStartTagIndex !== -1) {
30016                    const tagHtml = content.substring(findStartTagIndex, index);
30017                    if (tagHtml.indexOf('contenteditable="false"') !== -1) {
30018                        return match;
30019                    }
30020                }
30021            }
30022            return ('<span class="' + cls + '" data-mce-content="' + editor.dom.encode(args[0]) + '">' +
30023                editor.dom.encode(typeof args[1] === 'string' ? args[1] : args[0]) + '</span>');
30024        };
30025    };
30026    const convertRegExpsToNonEditable = (editor, nonEditableRegExps, e) => {
30027        let i = nonEditableRegExps.length, content = e.content;
30028        // Don't replace the variables when raw is used for example on undo/redo
30029        if (e.format === 'raw') {
30030            return;
30031        }
30032        while (i--) {
30033            content = content.replace(nonEditableRegExps[i], replaceMatchWithSpan(editor, content, getNonEditableClass(editor)));
30034        }
30035        e.content = content;
30036    };
30037    const isValidContent = (nonEditableRegExps, content) => {
30038        return forall(nonEditableRegExps, (re) => {
30039            const matches = content.match(re);
30040            return matches !== null && matches[0].length === content.length;
30041        });
30042    };
30043    const setup$s = (editor) => {
30044        const contentEditableAttrName = 'contenteditable';
30045        const editClass = ' ' + Tools.trim(getEditableClass(editor)) + ' ';
30046        const nonEditClass = ' ' + Tools.trim(getNonEditableClass(editor)) + ' ';
30047        const hasEditClass = hasClass(editClass);
30048        const hasNonEditClass = hasClass(nonEditClass);
30049        const nonEditableRegExps = getNonEditableRegExps(editor);
30050        if (nonEditableRegExps.length > 0) {
30051            editor.on('BeforeSetContent', (e) => {
30052                convertRegExpsToNonEditable(editor, nonEditableRegExps, e);
30053            });
30054        }
30055        editor.parser.addAttributeFilter('class', (nodes) => {
30056            let i = nodes.length;
30057            while (i--) {
30058                const node = nodes[i];
30059                if (hasEditClass(node)) {
30060                    node.attr(contentEditableAttrName, 'true');
30061                }
30062                else if (hasNonEditClass(node)) {
30063                    node.attr(contentEditableAttrName, 'false');
30064                }
30065            }
30066        });
30067        editor.serializer.addAttributeFilter(contentEditableAttrName, (nodes) => {
30068            let i = nodes.length;
30069            while (i--) {
30070                const node = nodes[i];
30071                if (!hasEditClass(node) && !hasNonEditClass(node)) {
30072                    continue;
30073                }
30074                const content = node.attr('data-mce-content');
30075                if (nonEditableRegExps.length > 0 && content) {
30076                    if (isValidContent(nonEditableRegExps, content)) {
30077                        node.name = '#text';
30078                        node.type = 3;
30079                        node.raw = true;
30080                        node.value = content;
30081                    }
30082                    else {
30083                        node.remove();
30084                    }
30085                }
30086                else {
30087                    node.attr(contentEditableAttrName, null);
30088                }
30089            }
30090        });
30091    };
30092
30093    /**
30094     * This module shows the invisible block that the caret is currently in when contents is added to that block.
30095     */
30096    const findBlockCaretContainer = (editor) => descendant$1(SugarElement.fromDom(editor.getBody()), '*[data-mce-caret]')
30097        .map((elm) => elm.dom)
30098        .getOrNull();
30099    const showBlockCaretContainer = (editor, blockCaretContainer) => {
30100        if (blockCaretContainer.hasAttribute('data-mce-caret')) {
30101            showCaretContainerBlock(blockCaretContainer);
30102            editor.selection.setRng(editor.selection.getRng()); // Clears the fake caret state
30103            editor.selection.scrollIntoView(blockCaretContainer);
30104        }
30105    };
30106    const handleBlockContainer = (editor, e) => {
30107        const blockCaretContainer = findBlockCaretContainer(editor);
30108        if (!blockCaretContainer) {
30109            return;
30110        }
30111        if (e.type === 'compositionstart') {
30112            e.preventDefault();
30113            e.stopPropagation();
30114            showBlockCaretContainer(editor, blockCaretContainer);
30115            return;
30116        }
30117        if (hasContent(blockCaretContainer)) {
30118            showBlockCaretContainer(editor, blockCaretContainer);
30119            editor.undoManager.add();
30120        }
30121    };
30122    const setup$r = (editor) => {
30123        editor.on('keyup compositionstart', curry(handleBlockContainer, editor));
30124    };
30125
30126    const isContentEditableFalse$3 = isContentEditableFalse$a;
30127    const moveToCeFalseHorizontally = (direction, editor, range) => moveHorizontally(editor, direction, range, isBeforeContentEditableFalse, isAfterContentEditableFalse, isContentEditableFalse$3);
30128    const moveToCeFalseVertically = (direction, editor, range) => {
30129        const isBefore = (caretPosition) => isBeforeContentEditableFalse(caretPosition) || isBeforeTable(caretPosition);
30130        const isAfter = (caretPosition) => isAfterContentEditableFalse(caretPosition) || isAfterTable(caretPosition);
30131        return moveVertically$1(editor, direction, range, isBefore, isAfter, isContentEditableFalse$3);
30132    };
30133    const createTextBlock = (editor) => {
30134        const textBlock = editor.dom.create(getForcedRootBlock(editor));
30135        textBlock.innerHTML = '<br data-mce-bogus="1">';
30136        return textBlock;
30137    };
30138    const exitPreBlock = (editor, direction, range) => {
30139        const caretWalker = CaretWalker(editor.getBody());
30140        const getVisualCaretPosition$1 = curry(getVisualCaretPosition, direction === 1 /* HDirection.Forwards */ ? caretWalker.next : caretWalker.prev);
30141        if (range.collapsed) {
30142            const pre = editor.dom.getParent(range.startContainer, 'PRE');
30143            if (!pre) {
30144                return;
30145            }
30146            const caretPos = getVisualCaretPosition$1(CaretPosition.fromRangeStart(range));
30147            if (!caretPos) {
30148                const newBlock = SugarElement.fromDom(createTextBlock(editor));
30149                if (direction === 1 /* HDirection.Forwards */) {
30150                    after$4(SugarElement.fromDom(pre), newBlock);
30151                }
30152                else {
30153                    before$4(SugarElement.fromDom(pre), newBlock);
30154                }
30155                editor.selection.select(newBlock.dom, true);
30156                editor.selection.collapse();
30157            }
30158        }
30159    };
30160    const getHorizontalRange = (editor, forward) => {
30161        const direction = forward ? 1 /* HDirection.Forwards */ : -1 /* HDirection.Backwards */;
30162        const range = editor.selection.getRng();
30163        return moveToCeFalseHorizontally(direction, editor, range).orThunk(() => {
30164            exitPreBlock(editor, direction, range);
30165            return Optional.none();
30166        });
30167    };
30168    const getVerticalRange = (editor, down) => {
30169        const direction = down ? 1 : -1;
30170        const range = editor.selection.getRng();
30171        return moveToCeFalseVertically(direction, editor, range).orThunk(() => {
30172            exitPreBlock(editor, direction, range);
30173            return Optional.none();
30174        });
30175    };
30176    const flipDirection = (selection, forward) => {
30177        const elm = forward ? selection.getEnd(true) : selection.getStart(true);
30178        return isRtl(elm) ? !forward : forward;
30179    };
30180    const moveH$2 = (editor, forward) => getHorizontalRange(editor, flipDirection(editor.selection, forward)).exists((newRange) => {
30181        moveToRange(editor, newRange);
30182        return true;
30183    });
30184    const moveV$5 = (editor, down) => getVerticalRange(editor, down).exists((newRange) => {
30185        moveToRange(editor, newRange);
30186        return true;
30187    });
30188    const moveToLineEndPoint$1 = (editor, forward) => {
30189        const isCefPosition = forward ? isAfterContentEditableFalse : isBeforeContentEditableFalse;
30190        return moveToLineEndPoint$3(editor, forward, isCefPosition);
30191    };
30192    const selectToEndPoint = (editor, forward) => getEdgeCefPosition(editor, !forward)
30193        .map((pos) => {
30194        const rng = pos.toRange();
30195        const curRng = editor.selection.getRng();
30196        if (forward) {
30197            rng.setStart(curRng.startContainer, curRng.startOffset);
30198        }
30199        else {
30200            rng.setEnd(curRng.endContainer, curRng.endOffset);
30201        }
30202        return rng;
30203    })
30204        .exists((rng) => {
30205        moveToRange(editor, rng);
30206        return true;
30207    });
30208
30209    const getClosestCetBlock = (position, root) => {
30210        const isRoot = (el) => eq(el, root);
30211        const isCet = (el) => isContentEditableTrue$3(el.dom);
30212        const startNode = SugarElement.fromDom(position.container());
30213        const closestCetBlock = closest$5(startNode, isCet, isRoot);
30214        return closestCetBlock.filter((b) => !isRoot(b));
30215    };
30216    const moveVertically = (editor, position, down) => {
30217        const getNextPosition = down ? getClosestPositionBelow : getClosestPositionAbove;
30218        return getNextPosition(editor.getBody(), position).map((nextPosition) => nextPosition.toRange());
30219    };
30220    const moveToNextOrPreviousLine = (editor, down) => {
30221        const startPosition = CaretPosition.fromRangeStart(editor.selection.getRng());
30222        const endPosition = CaretPosition.fromRangeEnd(editor.selection.getRng());
30223        const root = SugarElement.fromDom(editor.getBody());
30224        /* I wasn't able to find a way to create a selection between two different contenteditable elements.
30225          However I can't rule out that it is possible. So I am checking if both positions are in the same contenteditable element.
30226          This is a defensive check to ensure selection integrity.
30227        */
30228        const closestCetBlock = flatten(lift2(getClosestCetBlock(startPosition, root), getClosestCetBlock(endPosition, root), (c1, c2) => eq(c1, c2) ? Optional.some(c1) : Optional.none()));
30229        return closestCetBlock.fold(never, (cetBlock) => {
30230            if ((down && isAtLastLine(cetBlock.dom, endPosition)) ||
30231                (!down && isAtFirstLine(cetBlock.dom, startPosition))) {
30232                return moveVertically(editor, down ? endPosition : startPosition, down).exists((newRange) => {
30233                    moveToRange(editor, newRange);
30234                    return true;
30235                });
30236            }
30237            return false;
30238        });
30239    };
30240    const moveV$4 = (editor, down) => moveToNextOrPreviousLine(editor, down);
30241
30242    const isTarget = (node) => contains$2(['figcaption'], name(node));
30243    const getClosestTargetBlock = (pos, root, schema) => {
30244        const isRoot = curry(eq, root);
30245        return closest$5(SugarElement.fromDom(pos.container()), (el) => schema.isBlock(name(el)), isRoot).filter(isTarget);
30246    };
30247    const isAtFirstOrLastLine = (root, forward, pos) => forward ? isAtLastLine(root.dom, pos) : isAtFirstLine(root.dom, pos);
30248    const moveCaretToNewEmptyLine = (editor, forward) => {
30249        const root = SugarElement.fromDom(editor.getBody());
30250        const pos = CaretPosition.fromRangeStart(editor.selection.getRng());
30251        return getClosestTargetBlock(pos, root, editor.schema).exists(() => {
30252            if (isAtFirstOrLastLine(root, forward, pos)) {
30253                const insertFn = forward ? append$1 : prepend;
30254                const rng = insertEmptyLine(editor, root, insertFn);
30255                editor.selection.setRng(rng);
30256                return true;
30257            }
30258            else {
30259                return false;
30260            }
30261        });
30262    };
30263    const moveV$3 = (editor, forward) => {
30264        if (editor.selection.isCollapsed()) {
30265            return moveCaretToNewEmptyLine(editor, forward);
30266        }
30267        else {
30268            return false;
30269        }
30270    };
30271
30272    const moveUp = (editor, details, summary) => {
30273        const rng = editor.selection.getRng();
30274        const pos = CaretPosition.fromRangeStart(rng);
30275        const root = editor.getBody();
30276        if (root.firstChild === details && isAtFirstLine(summary, pos)) {
30277            editor.execCommand('InsertNewBlockBefore');
30278            return true;
30279        }
30280        else {
30281            return false;
30282        }
30283    };
30284    const moveDown = (editor, details) => {
30285        const rng = editor.selection.getRng();
30286        const pos = CaretPosition.fromRangeStart(rng);
30287        const root = editor.getBody();
30288        if (root.lastChild === details && isAtLastLine(details, pos)) {
30289            editor.execCommand('InsertNewBlockAfter');
30290            return true;
30291        }
30292        else {
30293            return false;
30294        }
30295    };
30296    const move$2 = (editor, forward) => {
30297        if (forward) {
30298            return Optional.from(editor.dom.getParent(editor.selection.getNode(), 'details'))
30299                .map((details) => moveDown(editor, details))
30300                .getOr(false);
30301        }
30302        else {
30303            return Optional.from(editor.dom.getParent(editor.selection.getNode(), 'summary'))
30304                .bind((summary) => Optional.from(editor.dom.getParent(summary, 'details'))
30305                .map((details) => moveUp(editor, details, summary))).getOr(false);
30306        }
30307    };
30308    const moveV$2 = (editor, forward) => move$2(editor, forward);
30309
30310    const baseKeyPattern = {
30311        shiftKey: false,
30312        altKey: false,
30313        ctrlKey: false,
30314        metaKey: false,
30315        keyCode: 0
30316    };
30317    const defaultPatterns = (patterns) => map$3(patterns, (pattern) => ({
30318        ...baseKeyPattern,
30319        ...pattern
30320    }));
30321    const defaultDelayedPatterns = (patterns) => map$3(patterns, (pattern) => ({
30322        ...baseKeyPattern,
30323        ...pattern
30324    }));
30325    const matchesEvent = (pattern, evt) => (evt.keyCode === pattern.keyCode &&
30326        evt.shiftKey === pattern.shiftKey &&
30327        evt.altKey === pattern.altKey &&
30328        evt.ctrlKey === pattern.ctrlKey &&
30329        evt.metaKey === pattern.metaKey);
30330    const match$1 = (patterns, evt) => bind$3(defaultPatterns(patterns), (pattern) => matchesEvent(pattern, evt) ? [pattern] : []);
30331    const matchDelayed = (patterns, evt) => bind$3(defaultDelayedPatterns(patterns), (pattern) => matchesEvent(pattern, evt) ? [pattern] : []);
30332    const action = (f, ...x) => () => f.apply(null, x);
30333    const execute = (patterns, evt) => find$2(match$1(patterns, evt), (pattern) => pattern.action());
30334    const executeWithDelayedAction = (patterns, evt) => findMap(matchDelayed(patterns, evt), (pattern) => pattern.action());
30335
30336    const moveH$1 = (editor, forward) => {
30337        const direction = forward ? 1 /* HDirection.Forwards */ : -1 /* HDirection.Backwards */;
30338        const range = editor.selection.getRng();
30339        return moveHorizontally(editor, direction, range, isBeforeMedia, isAfterMedia, isMedia$2).exists((newRange) => {
30340            moveToRange(editor, newRange);
30341            return true;
30342        });
30343    };
30344    const moveV$1 = (editor, down) => {
30345        const direction = down ? 1 : -1;
30346        const range = editor.selection.getRng();
30347        return moveVertically$1(editor, direction, range, isBeforeMedia, isAfterMedia, isMedia$2).exists((newRange) => {
30348            moveToRange(editor, newRange);
30349            return true;
30350        });
30351    };
30352    const moveToLineEndPoint = (editor, forward) => {
30353        const isNearMedia = forward ? isAfterMedia : isBeforeMedia;
30354        return moveToLineEndPoint$3(editor, forward, isNearMedia);
30355    };
30356
30357    const firstLayer = (scope, selector) => {
30358        return filterFirstLayer(scope, selector, always);
30359    };
30360    const filterFirstLayer = (scope, selector, predicate) => {
30361        return bind$3(children$
vendor: 10,021 bytes, lines 30361-30664
303611(scope), (x) => {
30362            if (is$2(x, selector)) {
30363                return predicate(x) ? [x] : [];
30364            }
30365            else {
30366                return filterFirstLayer(x, selector, predicate);
30367            }
30368        });
30369    };
30370
30371    // lookup inside this table
30372    const lookup$1 = (tags, element, isRoot = never) => {
30373        // If the element we're inspecting is the root, we definitely don't want it.
30374        if (isRoot(element)) {
30375            return Optional.none();
30376        }
30377        // This looks a lot like SelectorFind.closest, with one big exception - the isRoot check.
30378        // The code here will look for parents if passed a table, SelectorFind.closest with that specific isRoot check won't.
30379        if (contains$2(tags, name(element))) {
30380            return Optional.some(element);
30381        }
30382        const isRootOrUpperTable = (elm) => is$2(elm, 'table') || isRoot(elm);
30383        return ancestor$4(element, tags.join(','), isRootOrUpperTable);
30384    };
30385    /*
30386     * Identify the optional cell that element represents.
30387     */
30388    const cell = (element, isRoot) => lookup$1(['td', 'th'], element, isRoot);
30389    const cells = (ancestor) => firstLayer(ancestor, 'th,td');
30390    const table = (element, isRoot) => closest$4(element, 'table', isRoot);
30391
30392    const adt = Adt.generate([
30393        { none: ['current'] },
30394        { first: ['current'] },
30395        { middle: ['current', 'target'] },
30396        { last: ['current'] }
30397    ]);
30398    const none = (current) => adt.none(current);
30399    const CellLocation = {
30400        ...adt,
30401        none
30402    };
30403
30404    /*
30405     *  Walk until the next eligible cell location is found, or the start/end of the table is found.
30406     */
30407    const walk = (all, current, index, direction, isEligible = always) => {
30408        const forwards = direction === 1 /* Direction.Forwards */;
30409        if (!forwards && index <= 0) {
30410            return CellLocation.first(all[0]);
30411        }
30412        else if (forwards && index >= all.length - 1) {
30413            return CellLocation.last(all[all.length - 1]);
30414        }
30415        else {
30416            const newIndex = index + direction;
30417            const elem = all[newIndex];
30418            return isEligible(elem) ? CellLocation.middle(current, elem) : walk(all, current, newIndex, direction, isEligible);
30419        }
30420    };
30421    /*
30422     * Identify the index of the current cell within all the cells, and
30423     * a list of the cells within its table.
30424     */
30425    const detect = (current, isRoot) => {
30426        return table(current, isRoot).bind((table) => {
30427            const all = cells(table);
30428            const index = findIndex$2(all, (x) => eq(current, x));
30429            return index.map((index) => ({ index, all }));
30430        });
30431    };
30432    /*
30433     * Identify the CellLocation of the cell when navigating forward from current
30434     */
30435    const next = (current, isEligible, isRoot) => {
30436        const detection = detect(current, isRoot);
30437        return detection.fold(() => {
30438            return CellLocation.none(current);
30439        }, (info) => {
30440            return walk(info.all, current, info.index, 1 /* Direction.Forwards */, isEligible);
30441        });
30442    };
30443    /*
30444     * Identify the CellLocation of the cell when navigating back from current
30445     */
30446    const prev = (current, isEligible, isRoot) => {
30447        const detection = detect(current, isRoot);
30448        return detection.fold(() => {
30449            return CellLocation.none();
30450        }, (info) => {
30451            return walk(info.all, current, info.index, -1 /* Direction.Backwards */, isEligible);
30452        });
30453    };
30454
30455    var TagBoundaries = [
30456        'body',
30457        'p',
30458        'div',
30459        'article',
30460        'aside',
30461        'figcaption',
30462        'figure',
30463        'footer',
30464        'header',
30465        'nav',
30466        'section',
30467        'ol',
30468        'ul',
30469        'li',
30470        'table',
30471        'thead',
30472        'tbody',
30473        'tfoot',
30474        'caption',
30475        'tr',
30476        'td',
30477        'th',
30478        'h1',
30479        'h2',
30480        'h3',
30481        'h4',
30482        'h5',
30483        'h6',
30484        'blockquote',
30485        'pre',
30486        'address'
30487    ];
30488
30489    var DomUniverse = () => {
30490        const clone = (element) => {
30491            return SugarElement.fromDom(element.dom.cloneNode(false));
30492        };
30493        const document = (element) => documentOrOwner(element).dom;
30494        const isBoundary = (element) => {
30495            if (!isElement$8(element)) {
30496                return false;
30497            }
30498            if (name(element) === 'body') {
30499                return true;
30500            }
30501            return contains$2(TagBoundaries, name(element));
30502        };
30503        const isEmptyTag = (element) => {
30504            if (!isElement$8(element)) {
30505                return false;
30506            }
30507            return contains$2(['br', 'img', 'hr', 'input'], name(element));
30508        };
30509        const isNonEditable = (element) => isElement$8(element) && get$9(element, 'contenteditable') === 'false';
30510        const comparePosition = (element, other) => {
30511            return element.dom.compareDocumentPosition(other.dom);
30512        };
30513        const copyAttributesTo = (source, destination) => {
30514            const as = clone$4(source);
30515            setAll$1(destination, as);
30516        };
30517        const isSpecial = (element) => {
30518            const tag = name(element);
30519            return contains$2([
30520                'script', 'noscript', 'iframe', 'noframes', 'noembed', 'title', 'style', 'textarea', 'xmp'
30521            ], tag);
30522        };
30523        const getLanguage = (element) => isElement$8(element) ? getOpt(element, 'lang') : Optional.none();
30524        return {
30525            up: constant({
30526                selector: ancestor$4,
30527                closest: closest$4,
30528                predicate: ancestor$5,
30529                all: parents$1
30530            }),
30531            down: constant({
30532                selector: descendants,
30533                predicate: descendants$1
30534            }),
30535            styles: constant({
30536                get: get$7,
30537                getRaw: getRaw$1,
30538                set: set$2,
30539                remove: remove$7
30540            }),
30541            attrs: constant({
30542                get: get$9,
30543                set: set$4,
30544                remove: remove$9,
30545                copyTo: copyAttributesTo
30546            }),
30547            insert: constant({
30548                before: before$4,
30549                after: after$4,
30550                afterAll: after$3,
30551                append: append$1,
30552                appendAll: append,
30553                prepend: prepend,
30554                wrap: wrap$2
30555            }),
30556            remove: constant({
30557                unwrap: unwrap,
30558                remove: remove$8
30559            }),
30560            create: constant({
30561                nu: SugarElement.fromTag,
30562                clone,
30563                text: SugarElement.fromText
30564            }),
30565            query: constant({
30566                comparePosition,
30567                prevSibling: prevSibling,
30568                nextSibling: nextSibling
30569            }),
30570            property: constant({
30571                children: children$1,
30572                name: name,
30573                parent: parent,
30574                document,
30575                isText: isText$c,
30576                isComment: isComment$1,
30577                isElement: isElement$8,
30578                isSpecial,
30579                getLanguage,
30580                getText: get$4,
30581                setText: set$1,
30582                isBoundary,
30583                isEmptyTag,
30584                isNonEditable
30585            }),
30586            eq: eq,
30587            is: is$1
30588        };
30589    };
30590
30591    const point$1 = (element, offset) => ({
30592        element,
30593        offset
30594    });
30595
30596    /**
30597     * Return the last available cursor position in the node.
30598     */
30599    const toLast$1 = (universe, node) => {
30600        if (universe.property().isText(node)) {
30601            return point$1(node, universe.property().getText(node).length);
30602        }
30603        else {
30604            const children = universe.property().children(node);
30605            // keep descending if there are children.
30606            return children.length > 0 ? toLast$1(universe, children[children.length - 1]) : point$1(node, children.length);
30607        }
30608    };
30609    /**
30610     * Descend down to a leaf node at the given offset.
30611     */
30612    const toLeaf$3 = (universe, element, offset) => {
30613        const children = universe.property().children(element);
30614        if (children.length > 0 && offset < children.length) {
30615            return toLeaf$3(universe, children[offset], 0);
30616        }
30617        else if (children.length > 0 && universe.property().isElement(element) && children.length === offset) {
30618            return toLast$1(universe, children[children.length - 1]);
30619        }
30620        else {
30621            return point$1(element, offset);
30622        }
30623    };
30624
30625    const toLeaf$2 = toLeaf$3;
30626
30627    const universe = DomUniverse();
30628    const toLeaf$1 = (element, offset) => {
30629        return toLeaf$2(universe, element, offset);
30630    };
30631
30632    const imageId = generate('image');
30633    const getDragImage = (transfer) => {
30634        const dt = transfer;
30635        return Optional.from(dt[imageId]);
30636    };
30637    const setDragImage = (transfer, imageData) => {
30638        const dt = transfer;
30639        dt[imageId] = imageData;
30640    };
30641
30642    const eventId = generate('event');
30643    const getEvent = (transfer) => {
30644        const dt = transfer;
30645        return Optional.from(dt[eventId]);
30646    };
30647    const mkSetEventFn = (type) => (transfer) => {
30648        const dt = transfer;
30649        dt[eventId] = type;
30650    };
30651    const setEvent = (transfer, type) => mkSetEventFn(type)(transfer);
30652    const setDragstartEvent = mkSetEventFn(0 /* Event.Dragstart */);
30653    const setDropEvent = mkSetEventFn(2 /* Event.Drop */);
30654    const setDragendEvent = mkSetEventFn(1 /* Event.Dragend */);
30655    const checkEvent = (expectedType) => (transfer) => {
30656        const dt = transfer;
30657        return Optional.from(dt[eventId]).exists((type) => type === expectedType);
30658    };
30659    const isInDragStartEvent = checkEvent(0 /* Event.Dragstart */);
30660
30661    const createEmptyFileList = () => Object.freeze({
30662        length: 0,
30663        item: (_) => null
30664    })
vendor: 141,153 bytes, lines 30664-33678
30664;
30665
30666    const modeId = generate('mode');
30667    const getMode = (transfer) => {
30668        const dt = transfer;
30669        return Optional.from(dt[modeId]);
30670    };
30671    const mkSetModeFn = (mode) => (transfer) => {
30672        const dt = transfer;
30673        dt[modeId] = mode;
30674    };
30675    const setMode$1 = (transfer, mode) => mkSetModeFn(mode)(transfer);
30676    const setReadWriteMode = mkSetModeFn(0 /* Mode.ReadWrite */);
30677    const setReadOnlyMode = mkSetModeFn(2 /* Mode.ReadOnly */);
30678    const setProtectedMode = mkSetModeFn(1 /* Mode.Protected */);
30679    const checkMode = (expectedMode) => (transfer) => {
30680        const dt = transfer;
30681        return Optional.from(dt[modeId]).exists((mode) => mode === expectedMode);
30682    };
30683    const isInReadWriteMode = checkMode(0 /* Mode.ReadWrite */);
30684    const isInProtectedMode = checkMode(1 /* Mode.Protected */);
30685
30686    const normalizeItems = (dataTransfer, itemsImpl) => ({
30687        ...itemsImpl,
30688        get length() {
30689            return itemsImpl.length;
30690        },
30691        add: (data, type) => {
30692            if (isInReadWriteMode(dataTransfer)) {
30693                if (isString(data)) {
30694                    if (!isUndefined(type)) {
30695                        return itemsImpl.add(data, type);
30696                    }
30697                }
30698                else {
30699                    return itemsImpl.add(data);
30700                }
30701            }
30702            return null;
30703        },
30704        remove: (idx) => {
30705            if (isInReadWriteMode(dataTransfer)) {
30706                itemsImpl.remove(idx);
30707            }
30708        },
30709        clear: () => {
30710            if (isInReadWriteMode(dataTransfer)) {
30711                itemsImpl.clear();
30712            }
30713        }
30714    });
30715
30716    const validDropEffects = ['none', 'copy', 'link', 'move'];
30717    const validEffectAlloweds = ['none', 'copy', 'copyLink', 'copyMove', 'link', 'linkMove', 'move', 'all', 'uninitialized'];
30718    const createDataTransfer = () => {
30719        const dataTransferImpl = new window.DataTransfer();
30720        let dropEffect = 'move';
30721        let effectAllowed = 'all';
30722        const dataTransfer = {
30723            get dropEffect() {
30724                return dropEffect;
30725            },
30726            set dropEffect(effect) {
30727                if (contains$2(validDropEffects, effect)) {
30728                    dropEffect = effect;
30729                }
30730            },
30731            get effectAllowed() {
30732                return effectAllowed;
30733            },
30734            set effectAllowed(allowed) {
30735                // TINY-9601: Only allow setting effectAllowed to a valid value in a dragstart event
30736                // https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/effectAllowed
30737                if (isInDragStartEvent(dataTransfer) && contains$2(validEffectAlloweds, allowed)) {
30738                    effectAllowed = allowed;
30739                }
30740            },
30741            get items() {
30742                return normalizeItems(dataTransfer, dataTransferImpl.items);
30743            },
30744            get files() {
30745                if (isInProtectedMode(dataTransfer)) {
30746                    return createEmptyFileList();
30747                }
30748                else {
30749                    return dataTransferImpl.files;
30750                }
30751            },
30752            get types() {
30753                return dataTransferImpl.types;
30754            },
30755            setDragImage: (image, x, y) => {
30756                if (isInReadWriteMode(dataTransfer)) {
30757                    setDragImage(dataTransfer, { image, x, y });
30758                    dataTransferImpl.setDragImage(image, x, y);
30759                }
30760            },
30761            getData: (format) => {
30762                if (isInProtectedMode(dataTransfer)) {
30763                    return '';
30764                }
30765                else {
30766                    return dataTransferImpl.getData(format);
30767                }
30768            },
30769            setData: (format, data) => {
30770                if (isInReadWriteMode(dataTransfer)) {
30771                    dataTransferImpl.setData(format, data);
30772                }
30773            },
30774            clearData: (format) => {
30775                if (isInReadWriteMode(dataTransfer)) {
30776                    dataTransferImpl.clearData(format);
30777                }
30778            }
30779        };
30780        setReadWriteMode(dataTransfer);
30781        return dataTransfer;
30782    };
30783    const cloneDataTransfer = (original) => {
30784        // Create new DataTransfer object to ensure scope is not shared between original and clone
30785        const clone = createDataTransfer();
30786        const originalMode = getMode(original);
30787        // Set original to read-only to ensure data can be copied
30788        setReadOnlyMode(original);
30789        // Set clone event to dragstart to ensure effectAllowed can be set
30790        setDragstartEvent(clone);
30791        clone.dropEffect = original.dropEffect;
30792        clone.effectAllowed = original.effectAllowed;
30793        getDragImage(original).each((imageData) => clone.setDragImage(imageData.image, imageData.x, imageData.y));
30794        each$e(original.types, (type) => {
30795            if (type !== 'Files') {
30796                clone.setData(type, original.getData(type));
30797            }
30798        });
30799        each$e(original.files, (file) => clone.items.add(file));
30800        getEvent(original).each((type) => {
30801            setEvent(clone, type);
30802        });
30803        originalMode.each((mode) => {
30804            // Reset original mode since it was set to read-only earlier
30805            setMode$1(original, mode);
30806            setMode$1(clone, mode);
30807        });
30808        return clone;
30809    };
30810
30811    const getHtmlData = (dataTransfer) => {
30812        const html = dataTransfer.getData('text/html');
30813        return html === '' ? Optional.none() : Optional.some(html);
30814    };
30815    const setHtmlData = (dataTransfer, html) => dataTransfer.setData('text/html', html);
30816
30817    const deflate = (rect, delta) => ({
30818        left: rect.left - delta,
30819        top: rect.top - delta,
30820        right: rect.right + delta * 2,
30821        bottom: rect.bottom + delta * 2,
30822        width: rect.width + delta,
30823        height: rect.height + delta
30824    });
30825    const getCorners = (getYAxisValue, tds) => bind$3(tds, (td) => {
30826        const rect = deflate(clone$1(td.getBoundingClientRect()), -1);
30827        return [
30828            { x: rect.left, y: getYAxisValue(rect), cell: td },
30829            { x: rect.right, y: getYAxisValue(rect), cell: td }
30830        ];
30831    });
30832    const findClosestCorner = (corners, x, y) => foldl(corners, (acc, newCorner) => acc.fold(() => Optional.some(newCorner), (oldCorner) => {
30833        const oldDist = Math.sqrt(Math.abs(oldCorner.x - x) + Math.abs(oldCorner.y - y));
30834        const newDist = Math.sqrt(Math.abs(newCorner.x - x) + Math.abs(newCorner.y - y));
30835        return Optional.some(newDist < oldDist ? newCorner : oldCorner);
30836    }), Optional.none());
30837    const getClosestCell = (getYAxisValue, isTargetCorner, table, x, y) => {
30838        const cells = descendants(SugarElement.fromDom(table), 'td,th,caption').map((e) => e.dom);
30839        const corners = filter$5(getCorners(getYAxisValue, cells), (corner) => isTargetCorner(corner, y));
30840        return findClosestCorner(corners, x, y).map((corner) => corner.cell);
30841    };
30842    const getBottomValue = (rect) => rect.bottom;
30843    const getTopValue = (rect) => rect.top;
30844    const isAbove = (corner, y) => corner.y < y;
30845    const isBelow = (corner, y) => corner.y > y;
30846    const getClosestCellAbove = curry(getClosestCell, getBottomValue, isAbove);
30847    const getClosestCellBelow = curry(getClosestCell, getTopValue, isBelow);
30848    const findClosestPositionInAboveCell = (table, pos) => head(pos.getClientRects())
30849        .bind((rect) => getClosestCellAbove(table, rect.left, rect.top))
30850        .bind((cell) => findClosestHorizontalPosition(getLastLinePositions(cell), pos));
30851    const findClosestPositionInBelowCell = (table, pos) => last$2(pos.getClientRects())
30852        .bind((rect) => getClosestCellBelow(table, rect.left, rect.top))
30853        .bind((cell) => findClosestHorizontalPosition(getFirstLinePositions(cell), pos));
30854
30855    const hasNextBreak = (getPositionsUntil, scope, lineInfo) => lineInfo.breakAt.exists((breakPos) => getPositionsUntil(scope, breakPos).breakAt.isSome());
30856    const startsWithWrapBreak = (lineInfo) => lineInfo.breakType === BreakType.Wrap && lineInfo.positions.length === 0;
30857    const startsWithBrBreak = (lineInfo) => lineInfo.breakType === BreakType.Br && lineInfo.positions.length === 1;
30858    const isAtTableCellLine = (getPositionsUntil, scope, pos) => {
30859        const lineInfo = getPositionsUntil(scope, pos);
30860        // Since we can't determine if the caret is on the above or below line in a word wrap break we asume it's always
30861        // on the below/above line based on direction. This will make the caret jump one line if you are at the end of the last
30862        // line and moving down or at the beginning of the second line moving up.
30863        if (startsWithWrapBreak(lineInfo) || (!isBr$7(pos.getNode()) && startsWithBrBreak(lineInfo))) {
30864            return !hasNextBreak(getPositionsUntil, scope, lineInfo);
30865        }
30866        else {
30867            return lineInfo.breakAt.isNone();
30868        }
30869    };
30870    const isAtFirstTableCellLine = curry(isAtTableCellLine, getPositionsUntilPreviousLine);
30871    const isAtLastTableCellLine = curry(isAtTableCellLine, getPositionsUntilNextLine);
30872    const isCaretAtStartOrEndOfTable = (forward, rng, table) => {
30873        const caretPos = CaretPosition.fromRangeStart(rng);
30874        return positionIn(!forward, table).exists((pos) => pos.isEqual(caretPos));
30875    };
30876    const navigateHorizontally = (editor, forward, table, _td) => {
30877        const rng = editor.selection.getRng();
30878        const direction = forward ? 1 : -1;
30879        if (isFakeCaretTableBrowser() && isCaretAtStartOrEndOfTable(forward, rng, table)) {
30880            showCaret(direction, editor, table, !forward, false).each((newRng) => {
30881                moveToRange(editor, newRng);
30882            });
30883            return true;
30884        }
30885        return false;
30886    };
30887    const getClosestAbovePosition = (root, table, start) => findClosestPositionInAboveCell(table, start).orThunk(() => head(start.getClientRects()).bind((rect) => findClosestHorizontalPositionFromPoint(getPositionsAbove(root, CaretPosition.before(table)), rect.left))).getOr(CaretPosition.before(table));
30888    const getClosestBelowPosition = (root, table, start) => findClosestPositionInBelowCell(table, start).orThunk(() => head(start.getClientRects()).bind((rect) => findClosestHorizontalPositionFromPoint(getPositionsBelow(root, CaretPosition.after(table)), rect.left))).getOr(CaretPosition.after(table));
30889    const getTable = (previous, pos) => {
30890        const node = pos.getNode(previous);
30891        return isTable$2(node) ? Optional.some(node) : Optional.none();
30892    };
30893    const renderBlock = (down, editor, table) => {
30894        editor.undoManager.transact(() => {
30895            const insertFn = down ? after$4 : before$4;
30896            const rng = insertEmptyLine(editor, SugarElement.fromDom(table), insertFn);
30897            moveToRange(editor, rng);
30898        });
30899    };
30900    const moveCaret = (editor, down, pos) => {
30901        const table = down ? getTable(true, pos) : getTable(false, pos);
30902        const last = down === false;
30903        table.fold(() => moveToRange(editor, pos.toRange()), (table) => positionIn(last, editor.getBody()).filter((lastPos) => lastPos.isEqual(pos)).fold(() => moveToRange(editor, pos.toRange()), (_) => renderBlock(down, editor, table)));
30904    };
30905    const navigateVertically = (editor, down, table, td) => {
30906        const rng = editor.selection.getRng();
30907        const pos = CaretPosition.fromRangeStart(rng);
30908        const root = editor.getBody();
30909        if (!down && isAtFirstTableCellLine(td, pos)) {
30910            const newPos = getClosestAbovePosition(root, table, pos);
30911            moveCaret(editor, down, newPos);
30912            return true;
30913        }
30914        else if (down && isAtLastTableCellLine(td, pos)) {
30915            const newPos = getClosestBelowPosition(root, table, pos);
30916            moveCaret(editor, down, newPos);
30917            return true;
30918        }
30919        else {
30920            return false;
30921        }
30922    };
30923    const move$1 = (editor, forward, mover) => Optional.from(editor.dom.getParent(editor.selection.getNode(), 'td,th'))
30924        .bind((td) => Optional.from(editor.dom.getParent(td, 'table'))
30925        .map((table) => mover(editor, forward, table, td))).getOr(false);
30926    const moveH = (editor, forward) => move$1(editor, forward, navigateHorizontally);
30927    const moveV = (editor, forward) => move$1(editor, forward, navigateVertically);
30928    const getCellFirstCursorPosition = (cell) => {
30929        const selection = SimSelection.exact(cell, 0, cell, 0);
30930        return toNative(selection);
30931    };
30932    const rowHasEditableCell = (cell) => {
30933        return isCellEditable(cell) || siblings(cell).some((element) => isHTMLElement$1(element) && isCellEditable(element));
30934    };
30935    const tabGo = (editor, isRoot, cell) => {
30936        return cell.fold(Optional.none, Optional.none, (_current, next) => {
30937            return first(next).map((cell) => {
30938                return getCellFirstCursorPosition(cell);
30939            });
30940        }, (current) => {
30941            if (editor.mode.isReadOnly() || !isCellInEditableTable(current) || !rowHasEditableCell(current)) {
30942                return Optional.none();
30943            }
30944            editor.execCommand('mceTableInsertRowAfter');
30945            // Move forward from the last cell so that we move into the first valid position in the new row
30946            return tabForward(editor, isRoot, current);
30947        });
30948    };
30949    const isCellInEditableTable = (cell) => closest$5(cell, isTag('table')).exists(isEditable$2);
30950    const tabForward = (editor, isRoot, cell) => tabGo(editor, isRoot, next(cell, isCellEditable));
30951    const tabBackward = (editor, isRoot, cell) => tabGo(editor, isRoot, prev(cell, isCellEditable));
30952    const isCellEditable = (cell) => isEditable$2(cell) || descendant(cell, isEditableHTMLElement);
30953    const isEditableHTMLElement = (node) => isHTMLElement$1(node) && isEditable$2(node);
30954    const handleTab = (editor, forward) => {
30955        const rootElements = ['table', 'li', 'dl'];
30956        const body = SugarElement.fromDom(editor.getBody());
30957        const isRoot = (element) => {
30958            const name$1 = name(element);
30959            return eq(element, body) || contains$2(rootElements, name$1);
30960        };
30961        const rng = editor.selection.getRng();
30962        // If navigating backwards, use the start of the ranged selection
30963        const container = SugarElement.fromDom(!forward ? rng.startContainer : rng.endContainer);
30964        return cell(container, isRoot).map((cell) => {
30965            // Clear fake ranged selection because our new selection will always be collapsed
30966            table(cell, isRoot).each((table) => {
30967                editor.model.table.clearSelectedCells(table.dom);
30968            });
30969            // Collapse selection to start or end based on shift key
30970            editor.selection.collapse(!forward);
30971            const navigation = !forward ? tabBackward : tabForward;
30972            const rng = navigation(editor, isRoot, cell);
30973            rng.each((range) => {
30974                editor.selection.setRng(range);
30975            });
30976            return true;
30977        }).getOr(false);
30978    };
30979
30980    const executeKeydownOverride$4 = (editor, caret, evt) => {
30981        const isMac = Env.os.isMacOS() || Env.os.isiOS();
30982        const isFirefox = Env.browser.isFirefox();
30983        execute([
30984            { keyCode: VK.RIGHT, action: action(moveH$2, editor, true) },
30985            { keyCode: VK.LEFT, action: action(moveH$2, editor, false) },
30986            { keyCode: VK.UP, action: action(moveV$5, editor, false) },
30987            { keyCode: VK.DOWN, action: action(moveV$5, editor, true) },
30988            ...(isMac ? [
30989                { keyCode: VK.UP, action: action(selectToEndPoint, editor, false), metaKey: true, shiftKey: true },
30990                { keyCode: VK.DOWN, action: action(selectToEndPoint, editor, true), metaKey: true, shiftKey: true }
30991            ] : []),
30992            { keyCode: VK.RIGHT, action: action(moveH, editor, true) },
30993            { keyCode: VK.LEFT, action: action(moveH, editor, false) },
30994            { keyCode: VK.UP, action: action(moveV, editor, false) },
30995            { keyCode: VK.DOWN, action: action(moveV, editor, true) },
30996            { keyCode: VK.UP, action: action(moveV, editor, false) },
30997            { keyCode: VK.UP, action: action(moveV$2, editor, false) },
30998            { keyCode: VK.DOWN, action: action(moveV$2, editor, true) },
30999            { keyCode: VK.RIGHT, action: action(moveH$1, editor, true) },
31000            { keyCode: VK.LEFT, action: action(moveH$1, editor, false) },
31001            { keyCode: VK.UP, action: action(moveV$1, editor, false) },
31002            { keyCode: VK.DOWN, action: action(moveV$1, editor, true) },
31003            { keyCode: VK.RIGHT, action: action(move$3, editor, caret, true) },
31004            { keyCode: VK.LEFT, action: action(move$3, editor, caret, false) },
31005            { keyCode: VK.RIGHT, ctrlKey: !isMac, altKey: isMac, action: action(moveNextWord, editor, caret) },
31006            { keyCode: VK.LEFT, ctrlKey: !isMac, altKey: isMac, action: action(movePrevWord, editor, caret) },
31007            { keyCode: VK.UP, action: action(moveV$3, editor, false) },
31008            { keyCode: VK.DOWN, action: action(moveV$3, editor, true) },
31009            ...(isFirefox ? [
31010                { keyCode: VK.UP, action: action(moveV$4, editor, false) },
31011                { keyCode: VK.DOWN, action: action(moveV$4, editor, true) }
31012            ] : [])
31013        ], evt).each((_) => {
31014            evt.preventDefault();
31015        });
31016    };
31017    const setup$q = (editor, caret) => {
31018        editor.on('keydown', (evt) => {
31019            if (!evt.isDefaultPrevented()) {
31020                executeKeydownOverride$4(editor, caret, evt);
31021            }
31022        });
31023    };
31024
31025    const point = (container, offset) => ({
31026        container,
31027        offset
31028    });
31029
31030    const DOM$7 = DOMUtils.DOM;
31031    const alwaysNext = (startNode) => (node) => startNode === node ? -1 : 0;
31032    // This largely is derived from robins isBoundary check, however it also treats contenteditable=false elements as a boundary
31033    // See robins `Structure.isEmptyTag` for the list of quasi block elements
31034    const isBoundary = (dom) => (node) => dom.isBlock(node) || contains$2(['BR', 'IMG', 'HR', 'INPUT'], node.nodeName) || dom.getContentEditable(node) === 'false';
31035    // Finds the text node before the specified node, or just returns the node if it's already on a text node
31036    const textBefore = (node, offset, rootNode) => {
31037        if (isText$b(node) && offset >= 0) {
31038            return Optional.some(point(node, offset));
31039        }
31040        else {
31041            const textSeeker = TextSeeker(DOM$7);
31042            return Optional.from(textSeeker.backwards(node, offset, alwaysNext(node), rootNode)).map((prev) => point(prev.container, prev.container.data.length));
31043        }
31044    };
31045    const textAfter = (node, offset, rootNode) => {
31046        if (isText$b(node) && offset >= node.length) {
31047            return Optional.some(point(node, offset));
31048        }
31049        else {
31050            const textSeeker = TextSeeker(DOM$7);
31051            return Optional.from(textSeeker.forwards(node, offset, alwaysNext(node), rootNode)).map((prev) => point(prev.container, 0));
31052        }
31053    };
31054    const scanLeft = (node, offset, rootNode) => {
31055        if (!isText$b(node)) {
31056            return Optional.none();
31057        }
31058        const text = node.data;
31059        if (offset >= 0 && offset <= text.length) {
31060            return Optional.some(point(node, offset));
31061        }
31062        else {
31063            const textSeeker = TextSeeker(DOM$7);
31064            return Optional.from(textSeeker.backwards(node, offset, alwaysNext(node), rootNode)).bind((prev) => {
31065                const prevText = prev.container.data;
31066                return scanLeft(prev.container, offset + prevText.length, rootNode);
31067            });
31068        }
31069    };
31070    const scanRight = (node, offset, rootNode) => {
31071        if (!isText$b(node)) {
31072            return Optional.none();
31073        }
31074        const text = node.data;
31075        if (offset <= text.length) {
31076            return Optional.some(point(node, offset));
31077        }
31078        else {
31079            const textSeeker = TextSeeker(DOM$7);
31080            return Optional.from(textSeeker.forwards(node, offset, alwaysNext(node), rootNode)).bind((next) => scanRight(next.container, offset - text.length, rootNode));
31081        }
31082    };
31083    const repeatLeft = (dom, node, offset, process, rootNode) => {
31084        const search = TextSeeker(dom, isBoundary(dom));
31085        return Optional.from(search.backwards(node, offset, process, rootNode));
31086    };
31087
31088    const isValidTextRange = (rng) => rng.collapsed && isText$b(rng.startContainer);
31089    // Normalize the text by replacing non-breaking spaces with regular spaces and stripping zero-width spaces (fake carets).
31090    const getText = (rng) => trim$2(rng.toString().replace(/\u00A0/g, ' '));
31091    const isWhitespace = (chr) => chr !== '' && ' \u00a0\ufeff\f\n\r\t\v'.indexOf(chr) !== -1;
31092
31093    const stripTrigger = (text, trigger) => text.substring(trigger.length);
31094    const findTrigger = (text, index, trigger, includeWhitespace = false) => {
31095        // Identify the `char` in, and start the text from that point forward. If there is ever any whitespace, fail
31096        let i;
31097        const firstChar = trigger.charAt(0);
31098        for (i = index - 1; i >= 0; i--) {
31099            const char = text.charAt(i);
31100            if (!includeWhitespace && isWhitespace(char)) {
31101                return Optional.none();
31102            }
31103            if (firstChar === char && contains$1(text, trigger, i, index)) {
31104                break;
31105            }
31106        }
31107        return Optional.some(i);
31108    };
31109    const getContext = (dom, initRange, trigger, includeWhitespace = false) => {
31110        if (!isValidTextRange(initRange)) {
31111            return Optional.none();
31112        }
31113        const buffer = { text: '', offset: 0 };
31114        const findTriggerIndex = (element, offset, text) => {
31115            buffer.text = text + buffer.text;
31116            buffer.offset += offset;
31117            // Stop searching by just returning the current offset if whitespace was found (eg Optional.none())
31118            // and we'll handle the final checks below instead
31119            return findTrigger(buffer.text, buffer.offset, trigger, includeWhitespace).getOr(offset);
31120        };
31121        const root = dom.getParent(initRange.startContainer, dom.isBlock) || dom.getRoot();
31122        return repeatLeft(dom, initRange.startContainer, initRange.startOffset, findTriggerIndex, root).bind((spot) => {
31123            const range = initRange.cloneRange();
31124            range.setStart(spot.container, spot.offset);
31125            range.setEnd(initRange.endContainer, initRange.endOffset);
31126            // If the range is collapsed then we didn't find a match so abort
31127            if (range.collapsed) {
31128                return Optional.none();
31129            }
31130            const text = getText(range);
31131            const triggerIndex = text.lastIndexOf(trigger);
31132            // If the match doesn't start with the trigger (eg whitespace found)
31133            if (triggerIndex !== 0) {
31134                return Optional.none();
31135            }
31136            else {
31137                return Optional.some({ text: stripTrigger(text, trigger), range, trigger });
31138            }
31139        });
31140    };
31141
31142    const isText$1 = (node) => node.nodeType === TEXT;
31143    const isElement = (node) => node.nodeType === ELEMENT;
31144    const toLast = (node) => {
31145        if (isText$1(node)) {
31146            return point(node, node.data.length);
31147        }
31148        else {
31149            const children = node.childNodes;
31150            // keep descending if there are children.
31151            return children.length > 0 ? toLast(children[children.length - 1]) : point(node, children.length);
31152        }
31153    };
31154    const toLeaf = (node, offset) => {
31155        const children = node.childNodes;
31156        if (children.length > 0 && offset < children.length) {
31157            return toLeaf(children[offset], 0);
31158        }
31159        else if (children.length > 0 && isElement(node) && children.length === offset) {
31160            return toLast(children[children.length - 1]);
31161        }
31162        else {
31163            return point(node, offset);
31164        }
31165    };
31166
31167    const isPreviousCharContent = (dom, leaf) => {
31168        // If at the start of the range, then we need to look backwards one more place. Otherwise we just need to look at the current text
31169        const root = dom.getParent(leaf.container, dom.isBlock) ?? dom.getRoot();
31170        return repeatLeft(dom, leaf.container, leaf.offset, (_element, offset) => offset === 0 ? -1 : offset, root).filter((spot) => {
31171            const char = spot.container.data.charAt(spot.offset - 1);
31172            return !isWhitespace(char);
31173        }).isSome();
31174    };
31175    const isStartOfWord = (dom) => (rng) => {
31176        const leaf = toLeaf(rng.startContainer, rng.startOffset);
31177        return !isPreviousCharContent(dom, leaf);
31178    };
31179    const getTriggerContext = (dom, initRange, database) => findMap(database.triggers, (trigger) => getContext(dom, initRange, trigger));
31180    const lookup = (editor, getDatabase) => {
31181        const database = getDatabase();
31182        const rng = editor.selection.getRng();
31183        return getTriggerContext(editor.dom, rng, database).bind((context) => lookupWithContext(editor, getDatabase, context));
31184    };
31185    const lookupWithContext = (editor, getDatabase, context, fetchOptions = {}) => {
31186        const database = getDatabase();
31187        const rng = editor.selection.getRng();
31188        const startText = rng.startContainer.nodeValue ?? '';
31189        const autocompleters = filter$5(database.lookupByTrigger(context.trigger), (autocompleter) => context.text.length >= autocompleter.minChars && autocompleter.matches.getOrThunk(() => isStartOfWord(editor.dom))(context.range, startText, context.text));
31190        if (autocompleters.length === 0) {
31191            return Optional.none();
31192        }
31193        const lookupData = Promise.all(map$3(autocompleters, (ac) => {
31194            // TODO: Find a sensible way to do maxResults
31195            const fetchResult = ac.fetch(context.text, ac.maxResults, fetchOptions);
31196            return fetchResult.then((results) => ({
31197                matchText: context.text,
31198                items: results,
31199                columns: ac.columns,
31200                onAction: ac.onAction,
31201                highlightOn: ac.highlightOn
31202            }));
31203        }));
31204        return Optional.some({
31205            lookupData,
31206            context
31207        });
31208    };
31209
31210    const type = requiredString('type');
31211    const fetch$1 = requiredFunction('fetch');
31212    const onAction = requiredFunction('onAction');
31213    optionString('name');
31214    optionString('text');
31215    optionString('role');
31216    optionString('icon');
31217    optionString('url');
31218    optionString('tooltip');
31219    optionString('chevronTooltip');
31220    optionString('label');
31221    optionString('shortcut');
31222    const defaultedColumns = (num) => defaulted('columns', num);
31223
31224    const autocompleterSchema = objOf([
31225        type,
31226        requiredString('trigger'),
31227        defaultedNumber('minChars', 1),
31228        defaultedColumns(1),
31229        defaultedNumber('maxResults', 10),
31230        optionFunction('matches'),
31231        fetch$1,
31232        onAction,
31233        defaultedArrayOf('highlightOn', [], string)
31234    ]);
31235    const createAutocompleter = (spec) => asRaw('Autocompleter', autocompleterSchema, spec);
31236
31237    const create$6 = () => {
31238        const buttons = {};
31239        const menuItems = {};
31240        const popups = {};
31241        const icons = {};
31242        const contextMenus = {};
31243        const contextToolbars = {};
31244        const contexts = {};
31245        const sidebars = {};
31246        const views = {};
31247        const add = (collection, type) => (name, spec) => {
31248            collection[name.toLowerCase()] = { ...spec, type };
31249        };
31250        const addDefaulted = (collection, type) => (name, spec) => {
31251            collection[name.toLowerCase()] = { type, ...spec };
31252        };
31253        const addIcon = (name, svgData) => icons[name.toLowerCase()] = svgData;
31254        const addContext = (name, pred) => contexts[name.toLowerCase()] = pred;
31255        return {
31256            addButton: add(buttons, 'button'),
31257            addGroupToolbarButton: add(buttons, 'grouptoolbarbutton'),
31258            addToggleButton: add(buttons, 'togglebutton'),
31259            addMenuButton: add(buttons, 'menubutton'),
31260            addSplitButton: add(buttons, 'splitbutton'),
31261            addMenuItem: add(menuItems, 'menuitem'),
31262            addNestedMenuItem: add(menuItems, 'nestedmenuitem'),
31263            addToggleMenuItem: add(menuItems, 'togglemenuitem'),
31264            addAutocompleter: add(popups, 'autocompleter'),
31265            addContextMenu: add(contextMenus, 'contextmenu'),
31266            addContextToolbar: add(contextToolbars, 'contexttoolbar'),
31267            addContextForm: addDefaulted(contextToolbars, 'contextform'),
31268            addSidebar: add(sidebars, 'sidebar'),
31269            addView: add(views, 'views'),
31270            addIcon,
31271            addContext,
31272            getAll: () => ({
31273                buttons,
31274                menuItems,
31275                icons,
31276                // TODO: should popups be combined with context menus? We'd need to make a new add function.
31277                // Right now using `add` shares the key namespace, which prevents registering both
31278                // a completer and a context menu with the same name
31279                popups,
31280                contextMenus,
31281                contextToolbars,
31282                sidebars,
31283                views,
31284                contexts
31285            })
31286        };
31287    };
31288
31289    const register$2 = (editor) => {
31290        const popups = editor.ui.registry.getAll().popups;
31291        const dataset = map$2(popups, (popup) => createAutocompleter(popup).fold((err) => {
31292            throw new Error(formatError(err));
31293        }, identity));
31294        const triggers = stringArray(mapToArray(dataset, (v) => v.trigger));
31295        const datasetValues = values(dataset);
31296        const lookupByTrigger = (trigger) => filter$5(datasetValues, (dv) => dv.trigger === trigger);
31297        return {
31298            dataset,
31299            triggers,
31300            lookupByTrigger
31301        };
31302    };
31303
31304    const setupEditorInput = (editor, api) => {
31305        const update = last$1(api.load, 50);
31306        editor.on('input', (e) => {
31307            // TINY-10715: Firefox on Android using Korean Gboard will produce stray composition events when you move the caret by tapping inside the composed text
31308            if (e.inputType === 'insertCompositionText' && !editor.composing) {
31309                return;
31310            }
31311            update.throttle();
31312        });
31313        editor.on('keydown', (e) => {
31314            const keyCode = e.which;
31315            // Pressing <backspace> updates the autocompleter
31316            if (keyCode === 8) {
31317                update.throttle();
31318                // Pressing <esc> closes the autocompleter
31319            }
31320            else if (keyCode === 27) {
31321                update.cancel(); // We need to cancel here since Esc cancels the IME composition and triggers an input event
31322                api.cancelIfNecessary();
31323            }
31324            else if (keyCode === 38 || keyCode === 40) {
31325                // Arrow up and down keys needs to cancel the update since while composing arrow up or down will end the compose and issue a input event
31326                // that causes the list to update and then the focus moves up to the first item in the auto completer list.
31327                update.cancel();
31328            }
31329        }, true); // Need to add this to the top so that it exectued before the silver keyboard event
31330        editor.on('remove', update.cancel);
31331    };
31332    const setup$p = (editor) => {
31333        const activeAutocompleter = value$1();
31334        const uiActive = Cell(false);
31335        const isActive = activeAutocompleter.isSet;
31336        const cancelIfNecessary = () => {
31337            if (isActive()) {
31338                fireAutocompleterEnd(editor);
31339                uiActive.set(false);
31340                activeAutocompleter.clear();
31341            }
31342        };
31343        const commenceIfNecessary = (context) => {
31344            if (!isActive()) {
31345                // store the element/context
31346                activeAutocompleter.set({
31347                    trigger: context.trigger,
31348                    matchLength: context.text.length
31349                });
31350            }
31351        };
31352        // This needs to be calculated once things are ready, but the key events must be bound
31353        // before `init` or other keydown / keypress listeners will fire first. Therefore,
31354        // this is a thunk so that its value is calculated just once when it is used for the
31355        // first time, and after that it's value is stored.
31356        const getAutocompleters = cached(() => register$2(editor));
31357        const doLookup = (fetchOptions) => activeAutocompleter.get().map((ac) => getContext(editor.dom, editor.selection.getRng(), ac.trigger, true)
31358            .bind((newContext) => lookupWithContext(editor, getAutocompleters, newContext, fetchOptions))).getOrThunk(() => lookup(editor, getAutocompleters));
31359        const load = (fetchOptions) => {
31360            doLookup(fetchOptions).fold(cancelIfNecessary, (lookupInfo) => {
31361                commenceIfNecessary(lookupInfo.context);
31362                // Wait for the results to return and then display the menu
31363                // eslint-disable-next-line @typescript-eslint/no-floating-promises
31364                lookupInfo.lookupData.then((lookupData) => {
31365                    // Lookup the active autocompleter to make sure it's still active, if it isn't then do nothing
31366                    activeAutocompleter.get().map((ac) => {
31367                        const context = lookupInfo.context;
31368                        // Ensure the active autocompleter trigger matches, as the old one may have closed
31369                        // and a new one may have opened. If it doesn't match, then do nothing.
31370                        if (ac.trigger !== context.trigger) {
31371                            return;
31372                        }
31373                        activeAutocompleter.set({
31374                            ...ac,
31375                            matchLength: context.text.length
31376                        });
31377                        if (uiActive.get()) {
31378                            fireAutocompleterUpdateActiveRange(editor, { range: context.range });
31379                            fireAutocompleterUpdate(editor, { lookupData });
31380                        }
31381                        else {
31382                            uiActive.set(true);
31383                            fireAutocompleterUpdateActiveRange(editor, { range: context.range });
31384                            fireAutocompleterStart(editor, { lookupData });
31385                        }
31386                    });
31387                });
31388            });
31389        };
31390        const isRangeInsideOrEqual = (innerRange, outerRange) => {
31391            const startComparison = innerRange.compareBoundaryPoints(window.Range.START_TO_START, outerRange);
31392            const endComparison = innerRange.compareBoundaryPoints(window.Range.END_TO_END, outerRange);
31393            return startComparison >= 0 && endComparison <= 0;
31394        };
31395        const readActiveRange = () => {
31396            return activeAutocompleter.get().bind(({ trigger }) => {
31397                const selRange = editor.selection.getRng();
31398                return getContext(editor.dom, selRange, trigger, uiActive.get())
31399                    .filter(({ range }) => isRangeInsideOrEqual(selRange, range))
31400                    .map(({ range }) => range);
31401            });
31402        };
31403        editor.addCommand('mceAutocompleterReload', (_ui, value) => {
31404            const fetchOptions = isObject(value) ? value.fetchOptions : {};
31405            load(fetchOptions);
31406        });
31407        editor.addCommand('mceAutocompleterClose', cancelIfNecessary);
31408        editor.addCommand('mceAutocompleterRefreshActiveRange', () => {
31409            readActiveRange().each((range) => {
31410                fireAutocompleterUpdateActiveRange(editor, { range });
31411            });
31412        });
31413        editor.editorCommands.addQueryStateHandler('mceAutoCompleterInRange', () => readActiveRange().isSome());
31414        setupEditorInput(editor, {
31415            cancelIfNecessary,
31416            load
31417        });
31418    };
31419
31420    const browser$1 = detect$1().browser;
31421    const isSafari = browser$1.isSafari();
31422    const emptyNodeContents = (node) => fillWithPaddingBr(SugarElement.fromDom(node));
31423    const isEntireNodeSelected = (rng, node) => rng.startOffset === 0 && rng.endOffset === node.textContent?.length;
31424    const getParentDetailsElementAtPos = (dom, pos) => Optional.from(dom.getParent(pos.container(), 'details'));
31425    const isInDetailsElement = (dom, pos) => getParentDetailsElementAtPos(dom, pos).isSome();
31426    const getDetailsElements = (dom, rng) => {
31427        const startDetails = Optional.from(dom.getParent(rng.startContainer, 'details'));
31428        const endDetails = Optional.from(dom.getParent(rng.endContainer, 'details'));
31429        if (startDetails.isSome() || endDetails.isSome()) {
31430            const startSummary = startDetails.bind((details) => Optional.from(dom.select('summary', details)[0]));
31431            return Optional.some({ startSummary, startDetails, endDetails });
31432        }
31433        else {
31434            return Optional.none();
31435        }
31436    };
31437    const isCaretInTheBeginningOf = (caretPos, element) => firstPositionIn(element).exists((pos) => pos.isEqual(caretPos));
31438    const isCaretInTheEndOf = (caretPos, element) => {
31439        return lastPositionIn(element).exists((pos) => {
31440            // Summary may or may not have a trailing BR
31441            if (isBr$7(pos.getNode())) {
31442                return prevPosition(element, pos).exists((pos2) => pos2.isEqual(caretPos)) || pos.isEqual(caretPos);
31443            }
31444            else {
31445                return pos.isEqual(caretPos);
31446            }
31447        });
31448    };
31449    const isCaretAtStartOfSummary = (caretPos, detailsElements) => detailsElements.startSummary.exists((summary) => isCaretInTheBeginningOf(caretPos, summary));
31450    const isCaretAtEndOfSummary = (caretPos, detailsElements) => detailsElements.startSummary.exists((summary) => isCaretInTheEndOf(caretPos, summary));
31451    const isCaretInFirstPositionInBody = (caretPos, detailsElements) => detailsElements.startDetails.exists((details) => prevPosition(details, caretPos).forall((pos) => detailsElements.startSummary.exists((summary) => !summary.contains(caretPos.container()) && summary.contains(pos.container()))));
31452    const isCaretInLastPositionInBody = (root, caretPos, detailsElements) => detailsElements.startDetails.exists((details) => nextPosition(root, caretPos).forall((pos) => !details.contains(pos.container())));
31453    const setCaretToPosition = (editor, position) => {
31454        const node = position.getNode();
31455        if (!isUndefined(node)) {
31456            editor.selection.setCursorLocation(node, position.offset());
31457        }
31458    };
31459    const moveCaretToDetailsPos = (editor, pos, forward) => {
31460        const details = editor.dom.getParent(pos.container(), 'details');
31461        if (details && !details.open) {
31462            const summary = editor.dom.select('summary', details)[0];
31463            if (summary) {
31464                const newPos = forward ? firstPositionIn(summary) : lastPositionIn(summary);
31465                newPos.each((pos) => setCaretToPosition(editor, pos));
31466            }
31467        }
31468        else {
31469            setCaretToPosition(editor, pos);
31470        }
31471    };
31472    const isPartialDelete = (rng, detailsElements) => {
31473        const containsStart = (element) => element.contains(rng.startContainer);
31474        const containsEnd = (element) => element.contains(rng.endContainer);
31475        const startInSummary = detailsElements.startSummary.exists(containsStart);
31476        const endInSummary = detailsElements.startSummary.exists(containsEnd);
31477        const isPartiallySelectedDetailsElements = detailsElements.startDetails.forall((startDetails) => detailsElements.endDetails.forall((endDetails) => startDetails !== endDetails));
31478        const isInPartiallySelectedSummary = (startInSummary || endInSummary) && !(startInSummary && endInSummary);
31479        return isInPartiallySelectedSummary || isPartiallySelectedDetailsElements;
31480    };
31481    const shouldPreventDeleteIntoDetails = (editor, forward, granularity) => {
31482        const { dom, selection } = editor;
31483        const root = editor.getBody();
31484        if (granularity === 'character') {
31485            const caretPos = CaretPosition.fromRangeStart(selection.getRng());
31486            const parentBlock = dom.getParent(caretPos.container(), dom.isBlock);
31487            const parentDetailsAtCaret = getParentDetailsElementAtPos(dom, caretPos);
31488            const inEmptyParentBlock = parentBlock && dom.isEmpty(parentBlock);
31489            const isFirstBlock = isNull(parentBlock?.previousSibling);
31490            const isLastBlock = isNull(parentBlock?.nextSibling);
31491            // Pressing backspace or delete in an first or last empty block before or after details
31492            if (inEmptyParentBlock) {
31493                const firstOrLast = forward ? isLastBlock : isFirstBlock;
31494                if (firstOrLast) {
31495                    const isBeforeAfterDetails = navigate(!forward, root, caretPos).exists((pos) => {
31496                        return isInDetailsElement(dom, pos) && !equals(parentDetailsAtCaret, getParentDetailsElementAtPos(dom, pos));
31497                    });
31498                    if (isBeforeAfterDetails) {
31499                        return true;
31500                    }
31501                }
31502            }
31503            return navigate(forward, root, caretPos).fold(never, (pos) => {
31504                const parentDetailsAtNewPos = getParentDetailsElementAtPos(dom, pos);
31505                if (isInDetailsElement(dom, pos) && !equals(parentDetailsAtCaret, parentDetailsAtNewPos)) {
31506                    if (!forward) {
31507                        moveCaretToDetailsPos(editor, pos, false);
31508                    }
31509                    if (parentBlock && inEmptyParentBlock) {
31510                        if (forward && isFirstBlock) {
31511                            return true;
31512                        }
31513                        else if (!forward && isLastBlock) {
31514                            return true;
31515                        }
31516                        moveCaretToDetailsPos(editor, pos, forward);
31517                        editor.dom.remove(parentBlock);
31518                    }
31519                    return true;
31520                }
31521                else {
31522                    return false;
31523                }
31524            });
31525        }
31526        else {
31527            return false;
31528        }
31529    };
31530    const shouldPreventDeleteSummaryAction = (editor, detailElements, forward, granularity) => {
31531        const selection = editor.selection;
31532        const rng = selection.getRng();
31533        const caretPos = CaretPosition.fromRangeStart(rng);
31534        const root = editor.getBody();
31535        if (granularity === 'selection') {
31536            return isPartialDelete(rng, detailElements);
31537        }
31538        else if (forward) {
31539            return isCaretAtEndOfSummary(caretPos, detailElements) || isCaretInLastPositionInBody(root, caretPos, detailElements);
31540        }
31541        else {
31542            return isCaretAtStartOfSummary(caretPos, detailElements) || isCaretInFirstPositionInBody(caretPos, detailElements);
31543        }
31544    };
31545    const shouldPreventDeleteAction = (editor, forward, granularity) => getDetailsElements(editor.dom, editor.selection.getRng()).fold(() => shouldPreventDeleteIntoDetails(editor, forward, granularity), (detailsElements) => shouldPreventDeleteSummaryAction(editor, detailsElements, forward, granularity) || shouldPreventDeleteIntoDetails(editor, forward, granularity));
31546    const handleDeleteActionSafari = (editor, forward, granularity) => {
31547        const selection = editor.selection;
31548        const node = selection.getNode();
31549        const rng = selection.getRng();
31550        const caretPos = CaretPosition.fromRangeStart(rng);
31551        if (isSummary$1(node)) {
31552            // TINY-9951: Safari bug, deleting within the summary causes all content to be removed and no caret position to be left
31553            // https://bugs.webkit.org/show_bug.cgi?id=257745
31554            if (granularity === 'selection' && isEntireNodeSelected(rng, node) || willDeleteLastPositionInElement(forward, caretPos, node)) {
31555                emptyNodeContents(node);
31556            }
31557            else {
31558                editor.undoManager.transact(() => {
31559                    // Wrap all summary children in a temporary container to execute Backspace/Delete there, then unwrap
31560                    const sel = selection.getSel();
31561                    let { anchorNode, anchorOffset, focusNode, focusOffset } = sel ?? {};
31562                    const applySelection = () => {
31563                        if (isNonNullable(anchorNode) && isNonNullable(anchorOffset) && isNonNullable(focusNode) && isNonNullable(focusOffset)) {
31564                            sel?.setBaseAndExtent(anchorNode, anchorOffset, focusNode, focusOffset);
31565                        }
31566                    };
31567                    const updateSelection = () => {
31568                        anchorNode = sel?.anchorNode;
31569                        anchorOffset = sel?.anchorOffset;
31570                        focusNode = sel?.focusNode;
31571                        focusOffset = sel?.focusOffset;
31572                    };
31573                    const appendAllChildNodes = (from, to) => {
31574                        each$e(from.childNodes, (child) => {
31575                            if (isNode(child)) {
31576                                to.appendChild(child);
31577                            }
31578                        });
31579                    };
31580                    const container = editor.dom.create('span', { 'data-mce-bogus': '1' });
31581                    appendAllChildNodes(node, container);
31582                    node.appendChild(container);
31583                    applySelection();
31584                    // Manually perform deletion with modified granularities
31585                    if (granularity === 'word' || granularity === 'line') {
31586                        sel?.modify('extend', forward ? 'right' : 'left', granularity);
31587                    }
31588                    if (!selection.isCollapsed() && isEntireNodeSelected(selection.getRng(), container)) {
31589                        emptyNodeContents(node);
31590                    }
31591                    else {
31592                        editor.execCommand(forward ? 'ForwardDelete' : 'Delete');
31593                        updateSelection();
31594                        appendAllChildNodes(container, node);
31595                        applySelection();
31596                    }
31597                    editor.dom.remove(container);
31598                });
31599            }
31600            return true;
31601        }
31602        else {
31603            return false;
31604        }
31605    };
31606    const backspaceDelete = (editor, forward, granularity) => shouldPreventDeleteAction(editor, forward, granularity) || isSafari && handleDeleteActionSafari(editor, forward, granularity)
31607        ? Optional.some(noop) : Optional.none();
31608
31609    const platform$2 = detect$1();
31610    const os = platform$2.os;
31611    const isMacOSOriOS = os.isMacOS() || os.isiOS();
31612    const browser = platform$2.browser;
31613    const isFirefox = browser.isFirefox();
31614    const executeKeydownOverride$3 = (editor, caret, evt) => {
31615        const inputType = evt.keyCode === VK.BACKSPACE ? 'deleteContentBackward' : 'deleteContentForward';
31616        const isCollapsed = editor.selection.isCollapsed();
31617        const unmodifiedGranularity = isCollapsed ? 'character' : 'selection';
31618        const getModifiedGranularity = (isWord) => {
31619            if (isCollapsed) {
31620                return isWord ? 'word' : 'line';
31621            }
31622            else {
31623                return 'selection';
31624            }
31625        };
31626        executeWithDelayedAction([
31627            { keyCode: VK.BACKSPACE, action: action(backspaceDelete$2, editor) },
31628            { keyCode: VK.BACKSPACE, action: action(backspaceDelete$8, editor, false) },
31629            { keyCode: VK.DELETE, action: action(backspaceDelete$8, editor, true) },
31630            { keyCode: VK.BACKSPACE, action: action(backspaceDelete$9, editor, false) },
31631            { keyCode: VK.DELETE, action: action(backspaceDelete$9, editor, true) },
31632            { keyCode: VK.BACKSPACE, action: action(backspaceDelete$5, editor, caret, false) },
31633            { keyCode: VK.DELETE, action: action(backspaceDelete$5, editor, caret, true) },
31634            { keyCode: VK.BACKSPACE, action: action(backspaceDelete$d, editor, false) },
31635            { keyCode: VK.DELETE, action: action(backspaceDelete$d, editor, true) },
31636            { keyCode: VK.BACKSPACE, action: action(backspaceDelete, editor, false, unmodifiedGranularity) },
31637            { keyCode: VK.DELETE, action: action(backspaceDelete, editor, true, unmodifiedGranularity) },
31638            ...isMacOSOriOS ? [
31639                { keyCode: VK.BACKSPACE, altKey: true, action: action(backspaceDelete, editor, false, getModifiedGranularity(true)) },
31640                { keyCode: VK.DELETE, altKey: true, action: action(backspaceDelete, editor, true, getModifiedGranularity(true)) },
31641                { keyCode: VK.BACKSPACE, metaKey: true, action: action(backspaceDelete, editor, false, getModifiedGranularity(false)) },
31642            ] : [
31643                { keyCode: VK.BACKSPACE, ctrlKey: true, action: action(backspaceDelete, editor, false, getModifiedGranularity(true)) },
31644                { keyCode: VK.DELETE, ctrlKey: true, action: action(backspaceDelete, editor, true, getModifiedGranularity(true)) }
31645            ],
31646            { keyCode: VK.BACKSPACE, action: action(backspaceDelete$6, editor, false) },
31647            { keyCode: VK.DELETE, action: action(backspaceDelete$6, editor, true) },
31648            { keyCode: VK.BACKSPACE, action: action(backspaceDelete$3, editor, false) },
31649            { keyCode: VK.DELETE, action: action(backspaceDelete$3, editor, true) },
31650            { keyCode: VK.BACKSPACE, action: action(backspaceDelete$a, editor, false) },
31651            { keyCode: VK.DELETE, action: action(backspaceDelete$a, editor, true) },
31652            { keyCode: VK.BACKSPACE, action: action(backspaceDelete$b, editor, false) },
31653            { keyCode: VK.DELETE, action: action(backspaceDelete$b, editor, true) },
31654            { keyCode: VK.BACKSPACE, action: action(backspaceDelete$4, editor, false) },
31655            { keyCode: VK.DELETE, action: action(backspaceDelete$4, editor, true) },
31656            { keyCode: VK.BACKSPACE, action: action(backspaceDelete$7, editor, false) },
31657            { keyCode: VK.DELETE, action: action(backspaceDelete$7, editor, true) },
31658            { keyCode: VK.BACKSPACE, action: action(backspaceDelete$1, editor, false) },
31659            { keyCode: VK.DELETE, action: action(backspaceDelete$1, editor, true) },
31660        ], evt)
31661            .filter((_) => editor.selection.isEditable())
31662            .each((applyAction) => {
31663            evt.preventDefault();
31664            const beforeInput = fireBeforeInputEvent(editor, inputType);
31665            if (!beforeInput.isDefaultPrevented()) {
31666                applyAction();
31667                fireInputEvent(editor, inputType);
31668            }
31669        });
31670    };
31671    const executeKeyupOverride = (editor, evt, isBackspaceKeydown, formatNodes) => execute([
31672        { keyCode: VK.BACKSPACE, action: action(paddEmptyElement, editor) },
31673        { keyCode: VK.DELETE, action: action(paddEmptyElement, editor) },
31674        ...isMacOSOriOS ? [
31675            { keyCode: VK.BACKSPACE, altKey: true, action: action(refreshCaret, editor) },
31676            { keyCode: VK.DELETE, altKey: true, action: action(refreshCaret, editor) },
31677            // macOS surpresses keyup events for most keys including Backspace when Meta key is engaged
31678            // To emulate Meta + Backspace on macOS, add a pattern for the meta key when backspace was
31679            // detected on keydown
31680            ...isBackspaceKeydown ? [{
31681                    // Firefox detects macOS Command key code as "Command" not "Meta"
31682                    keyCode: isFirefox ? 224 : 91,
31683                    action: action(() => {
31684                        updateCaretFormat(editor, formatNodes);
31685                        return refreshCaret(editor);
31686                    })
31687                }] : []
31688        ] : [
31689            { keyCode: VK.BACKSPACE, ctrlKey: true, action: action(refreshCaret, editor) },
31690            { keyCode: VK.DELETE, ctrlKey: true, action: action(refreshCaret, editor) }
31691        ]
31692    ], evt);
31693    const setup$o = (editor, caret) => {
31694        // track backspace keydown state for emulating Meta + Backspace keyup detection on macOS
31695        let isBackspaceKeydown = false;
31696        let formatNodes = [];
31697        editor.on('init', () => {
31698            editor.on('keydown', (evt) => {
31699                isBackspaceKeydown = evt.keyCode === VK.BACKSPACE;
31700                formatNodes = getFormatNodesAtStart(editor);
31701                if (!evt.isDefaultPrevented()) {
31702                    executeKeydownOverride$3(editor, caret, evt);
31703                }
31704            });
31705            editor.on('keyup', (evt) => {
31706                if (!evt.isDefaultPrevented()) {
31707                    executeKeyupOverride(editor, evt, isBackspaceKeydown, formatNodes);
31708                    formatNodes.length = 0;
31709                }
31710                isBackspaceKeydown = false;
31711            });
31712        });
31713    };
31714
31715    const reduceFontStyleNesting = (block, node) => {
31716        const blockSugar = SugarElement.fromDom(block);
31717        const nodeSugar = SugarElement.fromDom(node);
31718        const isSpan = isTag('span');
31719        const isEndBlock = curry(eq, blockSugar);
31720        const hasFontSize = (element) => isElement$8(element) && getRaw$1(element, 'font-size').isSome();
31721        const elementsWithFontSize = [
31722            ...(hasFontSize(nodeSugar) ? [nodeSugar] : []),
31723            ...ancestors$1(nodeSugar, hasFontSize, isEndBlock)
31724        ];
31725        each$e(elementsWithFontSize.slice(1), (element) => {
31726            remove$7(element, 'font-size');
31727            remove$9(element, 'data-mce-style');
31728            if (isSpan(element) && hasNone(element)) {
31729                unwrap(element);
31730            }
31731        });
31732    };
31733
31734    const firstNonWhiteSpaceNodeSibling = (node) => {
31735        while (node) {
31736            if (isElement$7(node) || (isText$b(node) && node.data && /[\r\n\s]/.test(node.data))) {
31737                return Optional.from(SugarElement.fromDom(node));
31738            }
31739            node = node.nextSibling;
31740        }
31741        return Optional.none();
31742    };
31743    const moveToCaretPosition = (editor, root) => {
31744        const dom = editor.dom;
31745        const moveCaretBeforeOnEnterElementsMap = editor.schema.getMoveCaretBeforeOnEnterElements();
31746        if (!root) {
31747            return;
31748        }
31749        if (isListItem$2(SugarElement.fromDom(root))) {
31750            const findFirstList = (e) => isList$1(e) ? Optional.from(e) : descendant$2(e, isList$1);
31751            const isEmpty = (e) => dom.isEmpty(e.dom);
31752            firstNonWhiteSpaceNodeSibling(root.firstChild).each((firstChild) => {
31753                findFirstList(firstChild).fold(() => {
31754                    if (isEmpty(firstChild)) {
31755                        const element = toLeaf$1(firstChild, 0).element;
31756                        if (isElement$8(element) && !isBr$6(element)) {
31757                            append$1(element, SugarElement.fromHtml('<br data-mce-bogus="1" />'));
31758                        }
31759                    }
31760                }, (firstList) => {
31761                    before$4(firstList, SugarElement.fromText(nbsp));
31762                });
31763            });
31764        }
31765        const rng = dom.createRng();
31766        root.normalize();
31767        if (root.hasChildNodes()) {
31768            const walker = new DomTreeWalker(root, root);
31769            let lastNode = root;
31770            let node;
31771            while ((node = walker.current())) {
31772                if (isText$b(node)) {
31773                    rng.setStart(node, 0);
31774                    rng.setEnd(node, 0);
31775                    break;
31776                }
31777                if (moveCaretBeforeOnEnterElementsMap[node.nodeName.toLowerCase()]) {
31778                    rng.setStartBefore(node);
31779                    rng.setEndBefore(node);
31780                    break;
31781                }
31782                lastNode = node;
31783                node = walker.next();
31784            }
31785            if (!node) {
31786                rng.setStart(lastNode, 0);
31787                rng.setEnd(lastNode, 0);
31788            }
31789        }
31790        else {
31791            if (isBr$7(root)) {
31792                if (root.nextSibling && dom.isBlock(root.nextSibling)) {
31793                    rng.setStartBefore(root);
31794                    rng.setEndBefore(root);
31795                }
31796                else {
31797                    rng.setStartAfter(root);
31798                    rng.setEndAfter(root);
31799                }
31800            }
31801            else {
31802                rng.setStart(root, 0);
31803                rng.setEnd(root, 0);
31804            }
31805        }
31806        editor.selection.setRng(rng);
31807        scrollRangeIntoView(editor, rng);
31808    };
31809    const getEditableRoot = (dom, node) => {
31810        const root = dom.getRoot();
31811        let editableRoot;
31812        // Get all parents until we hit a non editable parent or the root
31813        let parent = node;
31814        while (parent !== root && parent && dom.getContentEditable(parent) !== 'false') {
31815            if (dom.getContentEditable(parent) === 'true') {
31816                editableRoot = parent;
31817                break;
31818            }
31819            parent = parent.parentNode;
31820        }
31821        return parent !== root ? editableRoot : root;
31822    };
31823    const getParentBlock$1 = (editor) => {
31824        return Optional.from(editor.dom.getParent(editor.selection.getStart(true), editor.dom.isBlock));
31825    };
31826    const getParentBlockName = (editor) => {
31827        return getParentBlock$1(editor).fold(constant(''), (parentBlock) => {
31828            return parentBlock.nodeName.toUpperCase();
31829        });
31830    };
31831    const isListItemParentBlock = (editor) => {
31832        return getParentBlock$1(editor).filter((elm) => {
31833            return isListItem$2(SugarElement.fromDom(elm));
31834        }).isSome();
31835    };
31836    const emptyBlock = (elm) => {
31837        elm.innerHTML = '<br data-mce-bogus="1">';
31838    };
31839    const applyAttributes = (editor, node, forcedRootBlockAttrs) => {
31840        const dom = editor.dom;
31841        // Merge and apply style attribute
31842        Optional.from(forcedRootBlockAttrs.style)
31843            .map(dom.parseStyle)
31844            .each((attrStyles) => {
31845            const currentStyles = getAllRaw(SugarElement.fromDom(node));
31846            const newStyles = { ...currentStyles, ...attrStyles };
31847            dom.setStyles(node, newStyles);
31848        });
31849        // Merge and apply class attribute
31850        const attrClassesOpt = Optional.from(forcedRootBlockAttrs.class).map((attrClasses) => attrClasses.split(/\s+/));
31851        const currentClassesOpt = Optional.from(node.className).map((currentClasses) => filter$5(currentClasses.split(/\s+/), (clazz) => clazz !== ''));
31852        lift2(attrClassesOpt, currentClassesOpt, (attrClasses, currentClasses) => {
31853            const filteredClasses = filter$5(currentClasses, (clazz) => !contains$2(attrClasses, clazz));
31854            const newClasses = [...attrClasses, ...filteredClasses];
31855            dom.setAttrib(node, 'class', newClasses.join(' '));
31856        });
31857        // Apply any remaining forced root block attributes
31858        const appliedAttrs = ['style', 'class'];
31859        const remainingAttrs = filter$4(forcedRootBlockAttrs, (_, attrs) => !contains$2(appliedAttrs, attrs));
31860        dom.setAttribs(node, remainingAttrs);
31861    };
31862    const setForcedBlockAttrs = (editor, node) => {
31863        const forcedRootBlockName = getForcedRootBlock(editor);
31864        if (forcedRootBlockName.toLowerCase() === node.tagName.toLowerCase()) {
31865            const forcedRootBlockAttrs = getForcedRootBlockAttrs(editor);
31866            applyAttributes(editor, node, forcedRootBlockAttrs);
31867        }
31868    };
31869    // Creates a new block element by cloning the current one or creating a new one if the name is specified
31870    // This function will also copy any text formatting from the parent block and add it to the new one
31871    const createNewBlock = (editor, container, parentBlock, editableRoot, keepStyles = true, name, styles) => {
31872        const dom = editor.dom;
31873        const schema = editor.schema;
31874        const newBlockName = getForcedRootBlock(editor);
31875        const parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
31876        let node = container;
31877        const textInlineElements = schema.getTextInlineElements();
31878        let block;
31879        if (name || parentBlockName === 'TABLE' || parentBlockName === 'HR') {
31880            block = dom.create(name || newBlockName, styles || {});
31881        }
31882        else {
31883            block = parentBlock.cloneNode(false);
31884        }
31885        let caretNode = block;
31886        if (!keepStyles) {
31887            dom.setAttrib(block, 'style', null); // wipe out any styles that came over with the block
31888            dom.setAttrib(block, 'class', null);
31889        }
31890        else {
31891            // Clone any parent styles
31892            do {
31893                if (textInlineElements[node.nodeName]) {
31894                    // Ignore caret or bookmark nodes when cloning
31895                    if (isCaretNode(node) || isBookmarkNode$1(node)) {
31896                        continue;
31897                    }
31898                    const clonedNode = node.cloneNode(false);
31899                    dom.setAttrib(clonedNode, 'id', ''); // Remove ID since it needs to be document unique
31900                    if (block.hasChildNodes()) {
31901                        clonedNode.appendChild(block.firstChild);
31902                        block.appendChild(clonedNode);
31903                    }
31904                    else {
31905                        caretNode = clonedNode;
31906                        block.appendChild(clonedNode);
31907                    }
31908                }
31909            } while ((node = node.parentNode) && node !== editableRoot);
31910            // Not omitting font sizes of list items otherwise their font size doesn't match its content
31911            if (block.nodeName !== 'LI') {
31912                reduceFontStyleNesting(block, caretNode);
31913            }
31914        }
31915        setForcedBlockAttrs(editor, block);
31916        emptyBlock(caretNode);
31917        return block;
31918    };
31919
31920    const getDetailsRoot = (editor, element) => editor.dom.getParent(element, isDetails);
31921    const isAtDetailsEdge = (root, element, isTextBlock) => {
31922        let node = element;
31923        while (node && node !== root && isNull(node.nextSibling)) {
31924            const parent = node.parentElement;
31925            if (!parent || !isTextBlock(parent)) {
31926                return isDetails(parent);
31927            }
31928            node = parent;
31929        }
31930        return false;
31931    };
31932    const isLastEmptyBlockInDetails = (editor, shiftKey, element) => !shiftKey &&
31933        element.nodeName.toLowerCase() === getForcedRootBlock(editor) &&
31934        editor.dom.isEmpty(element) &&
31935        isAtDetailsEdge(editor.getBody(), element, (el) => has$2(editor.schema.getTextBlockElements(), el.nodeName.toLowerCase()));
31936    const insertNewLine = (editor, createNewBlock, parentBlock) => {
31937        const newBlock = createNewBlock(getForcedRootBlock(editor));
31938        const root = getDetailsRoot(editor, parentBlock);
31939        if (!root) {
31940            return;
31941        }
31942        editor.dom.insertAfter(newBlock, root);
31943        moveToCaretPosition(editor, newBlock);
31944        // TODO: This now only works with our Accordions not details with multiple root level children should we support that
31945        if ((parentBlock.parentElement?.childNodes?.length ?? 0) > 1) {
31946            editor.dom.remove(parentBlock);
31947        }
31948    };
31949
31950    const hasFirstChild = (elm, name) => {
31951        return elm.firstChild && elm.firstChild.nodeName === name;
31952    };
31953    const isFirstChild = (elm) => {
31954        return elm.parentNode?.firstChild === elm;
31955    };
31956    const hasParent = (elm, parentName) => {
31957        const parentNode = elm?.parentNode;
31958        return isNonNullable(parentNode) && parentNode.nodeName === parentName;
31959    };
31960    const isListBlock = (elm) => {
31961        return isNonNullable(elm) && /^(OL|UL|LI)$/.test(elm.nodeName);
31962    };
31963    const isListItem = (elm) => {
31964        return isNonNullable(elm) && /^(LI|DT|DD)$/.test(elm.nodeName);
31965    };
31966    const isNestedList = (elm) => {
31967        return isListBlock(elm) && isListBlock(elm.parentNode);
31968    };
31969    const getContainerBlock = (containerBlock) => {
31970        const containerBlockParent = containerBlock.parentNode;
31971        return isListItem(containerBlockParent) ? containerBlockParent : containerBlock;
31972    };
31973    const isFirstOrLastLi = (containerBlock, parentBlock, first) => {
31974        let node = containerBlock[first ? 'firstChild' : 'lastChild'];
31975        // Find first/last element since there might be whitespace there
31976        while (node) {
31977            if (isElement$7(node)) {
31978                break;
31979            }
31980            node = node[first ? 'nextSibling' : 'previousSibling'];
31981        }
31982        return node === parentBlock;
31983    };
31984    const getStyles = (elm) => foldl(mapToArray(getAllRaw(SugarElement.fromDom(elm)), (style, styleName) => `${styleName}: ${style};`), (acc, s) => acc + s, '');
31985    // Inserts a block or br before/after or in the middle of a split list of the LI is empty
31986    const insert$4 = (editor, createNewBlock, containerBlock, parentBlock, newBlockName) => {
31987        const dom = editor.dom;
31988        const rng = editor.selection.getRng();
31989        const containerParent = containerBlock.parentNode;
31990        if (containerBlock === editor.getBody() || !containerParent) {
31991            return;
31992        }
31993        if (isNestedList(containerBlock)) {
31994            newBlockName = 'LI';
31995        }
31996        const parentBlockStyles = isListItem(parentBlock) ? getStyles(parentBlock) : undefined;
31997        let newBlock = isListItem(parentBlock) && parentBlockStyles
31998            ? createNewBlock(newBlockName, { style: getStyles(parentBlock) })
31999            : createNewBlock(newBlockName);
32000        if (isFirstOrLastLi(containerBlock, parentBlock, true) && isFirstOrLastLi(containerBlock, parentBlock, false)) {
32001            if (hasParent(containerBlock, 'LI')) {
32002                // Nested list is inside a LI
32003                const containerBlockParent = getContainerBlock(containerBlock);
32004                dom.insertAfter(newBlock, containerBlockParent);
32005                if (isFirstChild(containerBlock)) {
32006                    dom.remove(containerBlockParent);
32007                }
32008                else {
32009                    dom.remove(containerBlock);
32010                }
32011            }
32012            else {
32013                // Is first and last list item then replace the OL/UL with a text block
32014                dom.replace(newBlock, containerBlock);
32015            }
32016        }
32017        else if (isFirstOrLastLi(containerBlock, parentBlock, true)) {
32018            if (hasParent(containerBlock, 'LI')) {
32019                // List nested in an LI then move the list to a new sibling LI
32020                dom.insertAfter(newBlock, getContainerBlock(containerBlock));
32021                newBlock.appendChild(dom.doc.createTextNode(' ')); // Needed for IE so the caret can be placed
32022                newBlock.appendChild(containerBlock);
32023            }
32024            else {
32025                // First LI in list then remove LI and add text block before list
32026                containerParent.insertBefore(newBlock, containerBlock);
32027            }
32028            dom.remove(parentBlock);
32029        }
32030        else if (isFirstOrLastLi(containerBlock, parentBlock, false)) {
32031            // Last LI in list then remove LI and add text block after list
32032            dom.insertAfter(newBlock, getContainerBlock(containerBlock));
32033            dom.remove(parentBlock);
32034        }
32035        else {
32036            // Middle LI in list then split the list and insert a text block in the middle
32037            // Extract after fragment and insert it after the current block
32038            containerBlock = getContainerBlock(containerBlock);
32039            const tmpRng = rng.cloneRange();
32040            tmpRng.setStartAfter(parentBlock);
32041            tmpRng.setEndAfter(containerBlock);
32042            const fragment = tmpRng.extractContents();
32043            if (newBlockName === 'LI' && hasFirstChild(fragment, 'LI')) {
32044                const previousChildren = filter$5(map$3(newBlock.children, SugarElement.fromDom), not(isTag('br')));
32045                newBlock = fragment.firstChild;
32046                dom.insertAfter(fragment, containerBlock);
32047                each$e(previousChildren, (child) => prepend(SugarElement.fromDom(newBlock), child));
32048                if (parentBlockStyles) {
32049                    newBlock.setAttribute('style', parentBlockStyles);
32050                }
32051            }
32052            else {
32053                dom.insertAfter(fragment, containerBlock);
32054                dom.insertAfter(newBlock, containerBlock);
32055            }
32056            dom.remove(parentBlock);
32057        }
32058        moveToCaretPosition(editor, newBlock);
32059    };
32060
32061    const trimZwsp = (fragment) => {
32062        each$e(descendants$1(SugarElement.fromDom(fragment), isText$c), (text) => {
32063            const rawNode = text.dom;
32064            rawNode.nodeValue = trim$2(rawNode.data);
32065        });
32066    };
32067    const isWithinNonEditableList = (editor, node) => {
32068        const parentList = editor.dom.getParent(node, 'ol,ul,dl');
32069        return parentList !== null && editor.dom.getContentEditableParent(parentList) === 'false';
32070    };
32071    const isEmptyAnchor = (dom, elm) => {
32072        return elm && elm.nodeName === 'A' && dom.isEmpty(elm);
32073    };
32074    const containerAndPreviousSiblingName = (container, nodeName) => {
32075        return container.nodeName === nodeName || (container.previousSibling && container.previousSibling.nodeName === nodeName);
32076    };
32077    const containerAndNextSiblingName = (container, nodeName) => {
32078        return container.nodeName === nodeName || (container.nextSibling && container.nextSibling.nodeName === nodeName);
32079    };
32080    // Returns true if the block can be split into two blocks or not
32081    const canSplitBlock = (dom, node) => {
32082        return isNonNullable(node) &&
32083            dom.isBlock(node) &&
32084            !/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) &&
32085            !/^(fixed|absolute)/i.test(node.style.position) &&
32086            dom.isEditable(node.parentNode) && dom.getContentEditable(node) !== 'false';
32087    };
32088    // Remove the first empty inline element of the block so this: <p><b><em></em></b>x</p> becomes this: <p>x</p>
32089    const trimInlineElementsOnLeftSideOfBlock = (dom, nonEmptyElementsMap, block) => {
32090        const firstChilds = [];
32091        if (!block) {
32092            return;
32093        }
32094        // Find inner most first child ex: <p><i><b>*</b></i></p>
32095        let currentNode = block;
32096        while ((currentNode = currentNode.firstChild)) {
32097            if (dom.isBlock(currentNode)) {
32098                return;
32099            }
32100            if (isElement$7(currentNode) && !nonEmptyElementsMap[currentNode.nodeName.toLowerCase()]) {
32101                firstChilds.push(currentNode);
32102            }
32103        }
32104        let i = firstChilds.length;
32105        while (i--) {
32106            currentNode = firstChilds[i];
32107            if (!currentNode.hasChildNodes() || (currentNode.firstChild === currentNode.lastChild && currentNode.firstChild?.nodeValue === '')) {
32108                dom.remove(currentNode);
32109            }
32110            else {
32111                if (isEmptyAnchor(dom, currentNode)) {
32112                    dom.remove(currentNode);
32113                }
32114            }
32115        }
32116    };
32117    const normalizeZwspOffset = (start, container, offset) => {
32118        if (!isText$b(container)) {
32119            return offset;
32120        }
32121        else if (start) {
32122            return offset === 1 && container.data.charAt(offset - 1) === ZWSP$1 ? 0 : offset;
32123        }
32124        else {
32125            return offset === container.data.length - 1 && container.data.charAt(offset) === ZWSP$1 ? container.data.length : offset;
32126        }
32127    };
32128    const includeZwspInRange = (rng) => {
32129        const newRng = rng.cloneRange();
32130        newRng.setStart(rng.startContainer, normalizeZwspOffset(true, rng.startContainer, rng.startOffset));
32131        newRng.setEnd(rng.endContainer, normalizeZwspOffset(false, rng.endContainer, rng.endOffset));
32132        return newRng;
32133    };
32134    // Trims any linebreaks at the beginning of node user for example when pressing enter in a PRE element
32135    const trimLeadingLineBreaks = (node) => {
32136        let currentNode = node;
32137        do {
32138            if (isText$b(currentNode)) {
32139                currentNode.data = currentNode.data.replace(/^[\r\n]+/, '');
32140            }
32141            currentNode = currentNode.firstChild;
32142        } while (currentNode);
32143    };
32144    // Wraps any text nodes or inline elements in the specified forced root block name
32145    const wrapSelfAndSiblingsInDefaultBlock = (editor, newBlockName, rng, container, offset) => {
32146        const dom = editor.dom;
32147        const editableRoot = getEditableRoot(dom, container) ?? dom.getRoot();
32148        // Not in a block element or in a table cell or caption
32149        let parentBlock = dom.getParent(container, dom.isBlock);
32150        if (!parentBlock || !canSplitBlock(dom, parentBlock)) {
32151            parentBlock = parentBlock || editableRoot;
32152            if (!parentBlock.hasChildNodes()) {
32153                const newBlock = dom.create(newBlockName);
32154                setForcedBlockAttrs(editor, newBlock);
32155                parentBlock.appendChild(newBlock);
32156                rng.setStart(newBlock, 0);
32157                rng.setEnd(newBlock, 0);
32158                return newBlock;
32159            }
32160            // Find parent that is the first child of parentBlock
32161            let node = container;
32162            while (node && node.parentNode !== parentBlock) {
32163                node = node.parentNode;
32164            }
32165            // Loop left to find start node start wrapping at
32166            let startNode;
32167            while (node && !dom.isBlock(node)) {
32168                startNode = node;
32169                node = node.previousSibling;
32170            }
32171            const startNodeName = startNode?.parentElement?.nodeName;
32172            if (startNode && startNodeName && editor.schema.isValidChild(startNodeName, newBlockName.toLowerCase())) {
32173                // This should never be null since we check it above
32174                const startNodeParent = startNode.parentNode;
32175                const newBlock = dom.create(newBlockName);
32176                setForcedBlockAttrs(editor, newBlock);
32177                startNodeParent.insertBefore(newBlock, startNode);
32178                // Start wrapping until we hit a block
32179                node = startNode;
32180                while (node && !dom.isBlock(node)) {
32181                    const next = node.nextSibling;
32182                    newBlock.appendChild(node);
32183                    node = next;
32184                }
32185                // Restore range to it's past location
32186                rng.setStart(container, offset);
32187                rng.setEnd(container, offset);
32188            }
32189        }
32190        return container;
32191    };
32192    // Adds a BR at the end of blocks that only contains an IMG or INPUT since
32193    // these might be floated and then they won't expand the block
32194    const addBrToBlockIfNeeded = (dom, block) => {
32195        // IE will render the blocks correctly other browsers needs a BR
32196        block.normalize(); // Remove empty text nodes that got left behind by the extract
32197        // Check if the block is empty or contains a floated last child
32198        const lastChild = block.lastChild;
32199        if (!lastChild || isElement$7(lastChild) && (/^(left|right)$/gi.test(dom.getStyle(lastChild, 'float', true)))) {
32200            dom.add(block, 'br');
32201        }
32202    };
32203    const shouldEndContainer = (editor, container) => {
32204        const optionValue = shouldEndContainerOnEmptyBlock(editor);
32205        if (isNullable(container)) {
32206            return false;
32207        }
32208        else if (isString(optionValue)) {
32209            return contains$2(Tools.explode(optionValue), container.nodeName.toLowerCase());
32210        }
32211        else {
32212            return optionValue;
32213        }
32214    };
32215    const insert$3 = (editor, evt) => {
32216        let container;
32217        let offset;
32218        let parentBlockName;
32219        let containerBlock;
32220        let isAfterLastNodeInContainer = false;
32221        const dom = editor.dom;
32222        const schema = editor.schema, nonEmptyElementsMap = schema.getNonEmptyElements();
32223        const rng = editor.selection.getRng();
32224        const newBlockName = getForcedRootBlock(editor);
32225        const start = SugarElement.fromDom(rng.startContainer);
32226        const child = child$1(start, rng.startOffset);
32227        const isCef = child.exists((element) => isHTMLElement$1(element) && !isEditable$2(element));
32228        const collapsedAndCef = rng.collapsed && isCef;
32229        const createNewBlock$1 = (name, styles) => {
32230            return createNewBlock(editor, container, parentBlock, editableRoot, shouldKeepStyles(editor), name, styles);
32231        };
32232        // Returns true/false if the caret is at the start/end of the parent block element
32233        const isCaretAtStartOrEndOfBlock = (start) => {
32234            const normalizedOffset = normalizeZwspOffset(start, container, offset);
32235            // Caret is in the middle of a text node like "a|b"
32236            if (isText$b(container) && (start ? normalizedOffset > 0 : normalizedOffset < container.data.length)) {
32237                return false;
32238            }
32239            // If after the last element in block node edge case for #5091
32240            if ((container.parentNode === parentBlock || container === parentBlock) && isAfterLastNodeInContainer && !start) {
32241                return true;
32242            }
32243            // If the caret if before the first element in parentBlock
32244            if (start && isElement$7(container) && container === parentBlock.firstChild) {
32245                return true;
32246            }
32247            // Caret can be before/after a table or a hr
32248            if (containerAndPreviousSiblingName(container, 'TABLE') || containerAndPreviousSiblingName(container, 'HR')) {
32249                if (containerAndNextSiblingName(container, 'BR')) {
32250                    return !start;
32251                }
32252                return (isAfterLastNodeInContainer && !start) || (!isAfterLastNodeInContainer && start);
32253            }
32254            // Walk the DOM and look for text nodes or non empty elements
32255            const walker = new DomTreeWalker(container, parentBlock);
32256            // If caret is in beginning or end of a text block then jump to the next/previous node
32257            if (isText$b(container)) {
32258                if (start && normalizedOffset === 0) {
32259                    walker.prev();
32260                }
32261                else if (!start && normalizedOffset === container.data.length) {
32262                    walker.next();
32263                }
32264            }
32265            let node;
32266            while ((node = walker.current())) {
32267                if (isElement$7(node)) {
32268                    // Ignore bogus elements
32269                    if (!node.getAttribute('data-mce-bogus')) {
32270                        // Keep empty elements like <img /> <input /> but not trailing br:s like <p>text|<br></p>
32271                        const name = node.nodeName.toLowerCase();
32272                        if (nonEmptyElementsMap[name] && name !== 'br') {
32273                            return false;
32274                        }
32275                    }
32276                }
32277                else if (isText$b(node) && !isWhitespaceText(node.data)) {
32278                    return false;
32279                }
32280                if (start) {
32281                    walker.prev();
32282                }
32283                else {
32284                    walker.next();
32285                }
32286            }
32287            return true;
32288        };
32289        const isInsideLiBeforeAList = (newBlock) => {
32290            const nextSibling$1 = firstChild(newBlock).bind(nextSibling);
32291            return isListItem$2(newBlock) && nextSibling$1.exists(isList$1);
32292        };
32293        const trimEmptySpacesInLeftLeaf = (newBlock) => {
32294            const leaf = toLeaf$1(SugarElement.fromDom(newBlock), 0).element;
32295            if (isText$c(leaf) && dom.isEmpty(leaf.dom)) {
32296                leaf.dom.remove();
32297            }
32298        };
32299        const insertNewBlockAfter = () => {
32300            let block;
32301            // If the caret is at the end of a header we produce a P tag after it similar to Word unless we are in a hgroup
32302            if (/^(H[1-6]|PRE|FIGURE)$/.test(parentBlockName) && containerBlockName !== 'HGROUP') {
32303                block = createNewBlock$1(newBlockName);
32304            }
32305            else {
32306                block = createNewBlock$1();
32307            }
32308            // Split the current container block element if enter is pressed inside an empty inner block element
32309            if (shouldEndContainer(editor, containerBlock) && canSplitBlock(dom, containerBlock) && dom.isEmpty(parentBlock, undefined, { includeZwsp: true })) {
32310                // Split container block for example a BLOCKQUOTE at the current blockParent location for example a P
32311                block = dom.split(containerBlock, parentBlock);
32312            }
32313            else {
32314                dom.insertAfter(block, parentBlock);
32315            }
32316            moveToCaretPosition(editor, block);
32317            return block;
32318        };
32319        // Setup range items and newBlockName
32320        normalize$2(dom, rng).each((normRng) => {
32321            rng.setStart(normRng.startContainer, normRng.startOffset);
32322            rng.setEnd(normRng.endContainer, normRng.endOffset);
32323        });
32324        container = rng.startContainer;
32325        offset = rng.startOffset;
32326        const shiftKey = !!(evt && evt.shiftKey);
32327        const ctrlKey = !!(evt && evt.ctrlKey);
32328        // Resolve node index
32329        if (isElement$7(container) && container.hasChildNodes() && !collapsedAndCef) {
32330            isAfterLastNodeInContainer = offset > container.childNodes.length - 1;
32331            container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
32332            if (isAfterLastNodeInContainer && isText$b(container)) {
32333                offset = container.data.length;
32334            }
32335            else {
32336                offset = 0;
32337            }
32338        }
32339        // Get editable root node, normally the body element but sometimes a div or span
32340        const editableRoot = getEditableRoot(dom, container);
32341        // If there is no editable root then enter is done inside a contentEditable false element
32342        if (!editableRoot || isWithinNonEditableList(editor, container)) {
32343            return;
32344        }
32345        // Wrap the current node and it's sibling in a default block if it's needed.
32346        // for example this <td>text|<b>text2</b></td> will become this <td><p>text|<b>text2</p></b></td>
32347        // This won't happen if root blocks are disabled or the shiftKey is pressed
32348        if (!shiftKey) {
32349            container = wrapSelfAndSiblingsInDefaultBlock(editor, newBlockName, rng, container, offset);
32350        }
32351        // Find parent block and setup empty block paddings
32352        let parentBlock = dom.getParent(container, dom.isBlock) || dom.getRoot();
32353        containerBlock = isNonNullable(parentBlock?.parentNode) ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null;
32354        // Setup block names
32355        parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
32356        const containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
32357        // Enter inside block contained within a LI then split or insert before/after LI
32358        if (containerBlockName === 'LI' && !ctrlKey) {
32359            const liBlock = containerBlock;
32360            parentBlock = liBlock;
32361            containerBlock = liBlock.parentNode;
32362            parentBlockName = containerBlockName;
32363        }
32364        if (isElement$7(containerBlock) && isLastEmptyBlockInDetails(editor, shiftKey, parentBlock)) {
32365            return insertNewLine(editor, createNewBlock$1, parentBlock);
32366        }
32367        // Handle enter in list item
32368        if (/^(LI|DT|DD)$/.test(parentBlockName) && isElement$7(containerBlock)) {
32369            // Handle enter inside an empty list item
32370            if (dom.isEmpty(parentBlock)) {
32371                insert$4(editor, createNewBlock$1, containerBlock, parentBlock, newBlockName);
32372                return;
32373            }
32374        }
32375        // Never split the body or blocks that we can't split like noneditable host elements
32376        if (!collapsedAndCef && (parentBlock === editor.getBody() || !canSplitBlock(dom, parentBlock))) {
32377            return;
32378        }
32379        const parentBlockParent = parentBlock.parentNode;
32380        // Insert new block before/after the parent block depending on caret location
32381        let newBlock;
32382        if (collapsedAndCef) {
32383            newBlock = createNewBlock$1(newBlockName);
32384            child.fold(() => {
32385                append$1(start, SugarElement.fromDom(newBlock));
32386            }, (child) => {
32387                before$4(child, SugarElement.fromDom(newBlock));
32388            });
32389            editor.selection.setCursorLocation(newBlock, 0);
32390        }
32391        else if (isCaretContainerBlock$1(parentBlock)) {
32392            // TODO: TINY-10384 NOTE: Added logic to make sure pressing enter is consistent between browsers.
32393            // As an example a fake caret is used before/after tables on Firefox but not on Chrome. So different behaviour could occur
32394            newBlock = showCaretContainerBlock(parentBlock);
32395            if (dom.isEmpty(parentBlock)) {
32396                emptyBlock(parentBlock);
32397            }
32398            setForcedBlockAttrs(editor, newBlock);
32399            moveToCaretPosition(editor, newBlock);
32400        }
32401        else if (isCaretAtStartOrEndOfBlock(false)) {
32402            // Caret is moved to the new block in the insertNewBlockAfter fn
32403            newBlock = insertNewBlockAfter();
32404        }
32405        else if (isCaretAtStartOrEndOfBlock(true) && parentBlockParent) {
32406            // Check where caret is positioned before it is potentially moved by 'insertBefore' fn
32407            const caretPos = CaretPosition.fromRangeStart(rng);
32408            const afterTable = isAfterTable(caretPos);
32409            const parentBlockSugar = SugarElement.fromDom(parentBlock);
32410            const afterBr = isAfterBr(parentBlockSugar, caretPos, editor.schema);
32411            const prevBrOpt = afterBr
32412                ? findPreviousBr(parentBlockSugar, caretPos, editor.schema).bind((pos) => Optional.from(pos.getNode()))
32413                : Optional.none();
32414            newBlock = parentBlockParent.insertBefore(createNewBlock$1(), parentBlock);
32415            const root = containerAndPreviousSiblingName(parentBlock, 'HR') || afterTable ? newBlock : prevBrOpt.getOr(parentBlock);
32416            moveToCaretPosition(editor, root);
32417        }
32418        else {
32419            // Extract after fragment and insert it after the current block
32420            const tmpRng = includeZwspInRange(rng).cloneRange();
32421            tmpRng.setEndAfter(parentBlock);
32422            const fragment = tmpRng.extractContents();
32423            trimZwsp(fragment);
32424            trimLeadingLineBreaks(fragment);
32425            newBlock = fragment.firstChild;
32426            if (parentBlock === newBlock) { // Can't add yourself to yourself. Additionally the newBlock is removed from the DOM earlier, so even if you could, it'd still not work.
32427                if (isNonNullable(parentBlockParent)) {
32428                    dom.insertAfter(fragment, parentBlockParent);
32429                }
32430            }
32431            else {
32432                dom.insertAfter(fragment, parentBlock);
32433            }
32434            if (!isInsideLiBeforeAList(SugarElement.fromDom(newBlock))) {
32435                trimInlineElementsOnLeftSideOfBlock(dom, nonEmptyElementsMap, newBlock);
32436                addBrToBlockIfNeeded(dom, parentBlock);
32437            }
32438            else {
32439                trimEmptySpacesInLeftLeaf(newBlock);
32440            }
32441            if (dom.isEmpty(parentBlock)) {
32442                emptyBlock(parentBlock);
32443            }
32444            newBlock.normalize();
32445            // New block might become empty if it's <p><b>a |</b></p>
32446            if (dom.isEmpty(newBlock)) {
32447                dom.remove(newBlock);
32448                insertNewBlockAfter();
32449            }
32450            else {
32451                setForcedBlockAttrs(editor, newBlock);
32452                moveToCaretPosition(editor, newBlock);
32453            }
32454        }
32455        dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique
32456        // Allow custom handling of new blocks
32457        editor.dispatch('NewBlock', { newBlock });
32458    };
32459    const fakeEventName$1 = 'insertParagraph';
32460    const blockbreak = {
32461        insert: insert$3,
32462        fakeEventName: fakeEventName$1
32463    };
32464
32465    // Walks the parent block to the right and look for BR elements
32466    const hasRightSideContent = (schema, container, parentBlock) => {
32467        const walker = new DomTreeWalker(container, parentBlock);
32468        let node;
32469        const nonEmptyElementsMap = schema.getNonEmptyElements();
32470        while ((node = walker.next())) {
32471            if (nonEmptyElementsMap[node.nodeName.toLowerCase()] || isText$b(node) && node.length > 0) {
32472                return true;
32473            }
32474        }
32475        return false;
32476    };
32477    const moveSelectionToBr = (editor, brElm, extraBr) => {
32478        const rng = editor.dom.createRng();
32479        if (!extraBr) {
32480            rng.setStartAfter(brElm);
32481            rng.setEndAfter(brElm);
32482        }
32483        else {
32484            rng.setStartBefore(brElm);
32485            rng.setEndBefore(brElm);
32486        }
32487        editor.selection.setRng(rng);
32488        scrollRangeIntoView(editor, rng);
32489    };
32490    const insertBrAtCaret = (editor, evt) => {
32491        // We load the current event in from EnterKey.js when appropriate to heed
32492        // certain event-specific variations such as ctrl-enter in a list
32493        const selection = editor.selection;
32494        const dom = editor.dom;
32495        const rng = selection.getRng();
32496        let brElm;
32497        let extraBr = false;
32498        normalize$2(dom, rng).each((normRng) => {
32499            rng.setStart(normRng.startContainer, normRng.startOffset);
32500            rng.setEnd(normRng.endContainer, normRng.endOffset);
32501        });
32502        let offset = rng.startOffset;
32503        let container = rng.startContainer;
32504        // Resolve node index
32505        if (isElement$7(container) && container.hasChildNodes()) {
32506            const isAfterLastNodeInContainer = offset > container.childNodes.length - 1;
32507            container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
32508            if (isAfterLastNodeInContainer && isText$b(container)) {
32509                offset = container.data.length;
32510            }
32511            else {
32512                offset = 0;
32513            }
32514        }
32515        let parentBlock = dom.getParent(container, dom.isBlock);
32516        const containerBlock = parentBlock && parentBlock.parentNode ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null;
32517        const containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
32518        // Enter inside block contained within a LI then split or insert before/after LI
32519        const isControlKey = !!(evt && evt.ctrlKey);
32520        if (containerBlockName === 'LI' && !isControlKey) {
32521            parentBlock = containerBlock;
32522        }
32523        if (isText$b(container) && offset >= container.data.length) {
32524            // Insert extra BR element at the end block elements
32525            if (!hasRightSideContent(editor.schema, container, parentBlock || dom.getRoot())) {
32526                brElm = dom.create('br');
32527                rng.insertNode(brElm);
32528                rng.setStartAfter(brElm);
32529                rng.setEndAfter(brElm);
32530                extraBr = true;
32531            }
32532        }
32533        brElm = dom.create('br');
32534        rangeInsertNode(dom, rng, brElm);
32535        moveSelectionToBr(editor, brElm, extraBr);
32536        editor.undoManager.add();
32537    };
32538    const insertBrBefore = (editor, inline) => {
32539        const br = SugarElement.fromTag('br');
32540        before$4(SugarElement.fromDom(inline), br);
32541        editor.undoManager.add();
32542    };
32543    const insertBrAfter = (editor, inline) => {
32544        if (!hasBrAfter(editor.getBody(), inline)) {
32545            after$4(SugarElement.fromDom(inline), SugarElement.fromTag('br'));
32546        }
32547        const br = SugarElement.fromTag('br');
32548        after$4(SugarElement.fromDom(inline), br);
32549        moveSelectionToBr(editor, br.dom, false);
32550        editor.undoManager.add();
32551    };
32552    const isBeforeBr = (pos) => {
32553        return isBr$7(pos.getNode());
32554    };
32555    const hasBrAfter = (rootNode, startNode) => {
32556        if (isBeforeBr(CaretPosition.after(startNode))) {
32557            return true;
32558        }
32559        else {
32560            return nextPosition(rootNode, CaretPosition.after(startNode)).map((pos) => {
32561                return isBr$7(pos.getNode());
32562            }).getOr(false);
32563        }
32564    };
32565    const isAnchorLink = (elm) => {
32566        return elm && elm.nodeName === 'A' && 'href' in elm;
32567    };
32568    const isInsideAnchor = (location) => {
32569        return location.fold(never, isAnchorLink, isAnchorLink, never);
32570    };
32571    const readInlineAnchorLocation = (editor) => {
32572        const isInlineTarget$1 = curry(isInlineTarget, editor);
32573        const position = CaretPosition.fromRangeStart(editor.selection.getRng());
32574        return readLocation(isInlineTarget$1, editor.getBody(), position).filter(isInsideAnchor);
32575    };
32576    const insertBrOutsideAnchor = (editor, location) => {
32577        location.fold(noop, curry(insertBrBefore, editor), curry(insertBrAfter, editor), noop);
32578    };
32579    const insert$2 = (editor, evt) => {
32580        const anchorLocation = readInlineAnchorLocation(editor);
32581        if (anchorLocation.isSome()) {
32582            anchorLocation.each(curry(insertBrOutsideAnchor, editor));
32583        }
32584        else {
32585            insertBrAtCaret(editor, evt);
32586        }
32587    };
32588    const fakeEventName = 'insertLineBreak';
32589    const linebreak = {
32590        insert: insert$2,
32591        fakeEventName
32592    };
32593
32594    const matchesSelector = (editor, selector) => {
32595        return getParentBlock$1(editor).filter((parentBlock) => {
32596            return selector.length > 0 && is$2(SugarElement.fromDom(parentBlock), selector);
32597        }).isSome();
32598    };
32599    const shouldInsertBr = (editor) => {
32600        return matchesSelector(editor, getBrNewLineSelector(editor));
32601    };
32602    const shouldBlockNewLine$1 = (editor) => {
32603        return matchesSelector(editor, getNoNewLineSelector(editor));
32604    };
32605
32606    const newLineAction = Adt.generate([
32607        { br: [] },
32608        { block: [] },
32609        { none: [] }
32610    ]);
32611    const shouldBlockNewLine = (editor, _shiftKey) => {
32612        return shouldBlockNewLine$1(editor);
32613    };
32614    const inListBlock = (requiredState) => {
32615        return (editor, _shiftKey) => {
32616            return isListItemParentBlock(editor) === requiredState;
32617        };
32618    };
32619    const inBlock = (blockName, requiredState) => (editor, _shiftKey) => {
32620        const state = getParentBlockName(editor) === blockName.toUpperCase();
32621        return state === requiredState;
32622    };
32623    const inCefBlock = (editor) => {
32624        const editableRoot = getEditableRoot(editor.dom, editor.selection.getStart());
32625        return isNullable(editableRoot);
32626    };
32627    const inPreBlock = (requiredState) => inBlock('pre', requiredState);
32628    const inSummaryBlock = () => inBlock('summary', true);
32629    const shouldPutBrInPre = (requiredState) => {
32630        return (editor, _shiftKey) => {
32631            return shouldPutBrInPre$1(editor) === requiredState;
32632        };
32633    };
32634    const inBrContext = (editor, _shiftKey) => {
32635        return shouldInsertBr(editor);
32636    };
32637    const hasShiftKey = (_editor, shiftKey) => {
32638        return shiftKey;
32639    };
32640    const canInsertIntoEditableRoot = (editor) => {
32641        const forcedRootBlock = getForcedRootBlock(editor);
32642        const rootEditable = getEditableRoot(editor.dom, editor.selection.getStart());
32643        return isNonNullable(rootEditable) && editor.schema.isValidChild(rootEditable.nodeName, forcedRootBlock);
32644    };
32645    const isInRootWithEmptyOrCEF = (editor) => {
32646        const rng = editor.selection.getRng();
32647        const start = SugarElement.fromDom(rng.startContainer);
32648        const child = child$1(start, rng.startOffset);
32649        const isCefOpt = child.map((element) => isHTMLElement$1(element) && !isEditable$2(element));
32650        return rng.collapsed && isCefOpt.getOr(true);
32651    };
32652    const match = (predicates, action) => {
32653        return (editor, shiftKey) => {
32654            const isMatch = foldl(predicates, (res, p) => {
32655                return res && p(editor, shiftKey);
32656            }, true);
32657            return isMatch ? Optional.some(action) : Optional.none();
32658        };
32659    };
32660    const getAction = (editor, evt) => {
32661        return evaluateUntil([
32662            match([shouldBlockNewLine], newLineAction.none()),
32663            // If the pre block is cef, do not try to insert a new line (or delete contents)
32664            match([inPreBlock(true), inCefBlock], newLineAction.none()),
32665            match([inSummaryBlock()], newLineAction.br()),
32666            match([inPreBlock(true), shouldPutBrInPre(false), hasShiftKey], newLineAction.br()),
32667            match([inPreBlock(true), shouldPutBrInPre(false)], newLineAction.block()),
32668            match([inPreBlock(true), shouldPutBrInPre(true), hasShiftKey], newLineAction.block()),
32669            match([inPreBlock(true), shouldPutBrInPre(true)], newLineAction.br()),
32670            // TODO: TINY-9127 investigate if the list handling (and pre) is correct here.
32671            match([inListBlock(true), hasShiftKey], newLineAction.br()),
32672            match([inListBlock(true)], newLineAction.block()),
32673            match([inBrContext], newLineAction.br()),
32674            match([hasShiftKey], newLineAction.br()),
32675            match([canInsertIntoEditableRoot], newLineAction.block()),
32676            match([isInRootWithEmptyOrCEF], newLineAction.block())
32677        ], [editor, !!(evt && evt.shiftKey)]).getOr(newLineAction.none());
32678    };
32679
32680    const insertBreak = (breakType, editor, evt) => {
32681        if (editor.mode.isReadOnly()) {
32682            return;
32683        }
32684        if (!editor.selection.isCollapsed()) {
32685            // IMPORTANT: We want to use the editor execCommand here, so that our `delete` execCommand
32686            // overrides will be considered.
32687            execEditorDeleteCommand(editor);
32688        }
32689        if (isNonNullable(evt)) {
32690            const event = fireBeforeInputEvent(editor, breakType.fakeEventName);
32691            if (event.isDefaultPrevented()) {
32692                return;
32693            }
32694        }
32695        breakType.insert(editor, evt);
32696        if (isNonNullable(evt)) {
32697            fireInputEvent(editor, breakType.fakeEventName);
32698        }
32699    };
32700    const insert$1 = (editor, evt) => {
32701        if (editor.mode.isReadOnly()) {
32702            return;
32703        }
32704        const br = () => insertBreak(linebreak, editor, evt);
32705        const block = () => insertBreak(blockbreak, editor, evt);
32706        const logicalAction = getAction(editor, evt);
32707        switch (getNewlineBehavior(editor)) {
32708            case 'linebreak':
32709                logicalAction.fold(br, br, noop);
32710                break;
32711            case 'block':
32712                logicalAction.fold(block, block, noop);
32713                break;
32714            case 'invert':
32715                logicalAction.fold(block, br, noop);
32716                break;
32717            // implied by the options processor, unnecessary
32718            // case 'default':
32719            default:
32720                logicalAction.fold(br, block, noop);
32721                break;
32722        }
32723    };
32724
32725    const platform$1 = detect$1();
32726    const isIOSSafari = platform$1.os.isiOS() && platform$1.browser.isSafari();
32727    const handleEnterKeyEvent = (editor, event) => {
32728        if (event.isDefaultPrevented()) {
32729            return;
32730        }
32731        event.preventDefault();
32732        endTypingLevelIgnoreLocks(editor.undoManager);
32733        editor.undoManager.transact(() => {
32734            insert$1(editor, event);
32735        });
32736    };
32737    const isCaretAfterKoreanCharacter = (rng) => {
32738        if (!rng.collapsed) {
32739            return false;
32740        }
32741        const startContainer = rng.startContainer;
32742        if (isText$b(startContainer)) {
32743            // Hangul: \uAC00-\uD7AF
32744            // Hangul Jamo: \u1100-\u11FF
32745            // Hangul Compatibility Jamo: \u3130-\u318F
32746            // Hangul Jamo Extended-A: \uA960-\uA97F
32747            // Hangul Jamo Extended-B: \uD7B0-\uD7FF
32748            const koreanCharRegex = /^[\uAC00-\uD7AF\u1100-\u11FF\u3130-\u318F\uA960-\uA97F\uD7B0-\uD7FF]$/;
32749            const char = startContainer.data.charAt(rng.startOffset - 1);
32750            return koreanCharRegex.test(char);
32751        }
32752        else {
32753            return false;
32754        }
32755    };
32756    const setup$n = (editor) => {
32757        let iOSSafariKeydownBookmark = Optional.none();
32758        const iOSSafariKeydownOverride = (editor) => {
32759            iOSSafariKeydownBookmark = Optional.some(editor.selection.getBookmark());
32760            editor.undoManager.add();
32761        };
32762        const iOSSafariKeyupOverride = (editor, event) => {
32763            editor.undoManager.undo();
32764            iOSSafariKeydownBookmark.fold(noop, (b) => editor.selection.moveToBookmark(b));
32765            handleEnterKeyEvent(editor, event);
32766            iOSSafariKeydownBookmark = Optional.none();
32767        };
32768        editor.on('keydown', (event) => {
32769            if (event.keyCode === VK.ENTER) {
32770                if (isIOSSafari && isCaretAfterKoreanCharacter(editor.selection.getRng())) {
32771                    // TINY-9746: iOS Safari composes Korean characters by deleting the previous partial character and inserting
32772                    // the composed character. If the native Enter keypress event is not fired, iOS Safari will continue to compose across
32773                    // our custom newline by deleting it and inserting the composed character on the previous line, causing a bug. The workaround
32774                    // is to save a bookmark and an undo level on keydown while not preventing default to allow the native Enter keypress.
32775                    // Then on keyup, the effects of the native Enter keypress is undone and our own Enter key handler is called.
32776                    iOSSafariKeydownOverride(editor);
32777                }
32778                else {
32779                    handleEnterKeyEvent(editor, event);
32780                }
32781            }
32782        });
32783        editor.on('keyup', (event) => {
32784            if (event.keyCode === VK.ENTER) {
32785                iOSSafariKeydownBookmark.each(() => iOSSafariKeyupOverride(editor, event));
32786            }
32787        });
32788    };
32789
32790    const executeKeydownOverride$2 = (editor, caret, evt) => {
32791        const isMac = Env.os.isMacOS() || Env.os.isiOS();
32792        execute([
32793            { keyCode: VK.END, action: action(moveToLineEndPoint$1, editor, true) },
32794            { keyCode: VK.HOME, action: action(moveToLineEndPoint$1, editor, false) },
32795            ...(!isMac ? [
32796                { keyCode: VK.HOME, action: action(selectToEndPoint, editor, false), ctrlKey: true, shiftKey: true },
32797                { keyCode: VK.END, action: action(selectToEndPoint, editor, true), ctrlKey: true, shiftKey: true }
32798            ] : []),
32799            { keyCode: VK.END, action: action(moveToLineEndPoint, editor, true) },
32800            { keyCode: VK.HOME, action: action(moveToLineEndPoint, editor, false) },
32801            { keyCode: VK.END, action: action(moveToLineEndPoint$2, editor, true, caret) },
32802            { keyCode: VK.HOME, action: action(moveToLineEndPoint$2, editor, false, caret) }
32803        ], evt).each((_) => {
32804            evt.preventDefault();
32805        });
32806    };
32807    const setup$m = (editor, caret) => {
32808        editor.on('keydown', (evt) => {
32809            if (!evt.isDefaultPrevented()) {
32810                executeKeydownOverride$2(editor, caret, evt);
32811            }
32812        });
32813    };
32814
32815    const setup$l = (editor) => {
32816        editor.on('input', (e) => {
32817            // We only care about non composing inputs since moving the caret or modifying the text node will blow away the IME
32818            if (!e.isComposing) {
32819                normalizeNbspsInEditor(editor);
32820            }
32821        });
32822    };
32823
32824    const platform = detect$1();
32825    const executeKeyupAction = (editor, caret, evt) => {
32826        execute([
32827            { keyCode: VK.PAGE_UP, action: action(moveToLineEndPoint$2, editor, false, caret) },
32828            { keyCode: VK.PAGE_DOWN, action: action(moveToLineEndPoint$2, editor, true, caret) }
32829        ], evt);
32830    };
32831    const stopImmediatePropagation = (e) => e.stopImmediatePropagation();
32832    const isPageUpDown = (evt) => evt.keyCode === VK.PAGE_UP || evt.keyCode === VK.PAGE_DOWN;
32833    const setNodeChangeBlocker = (blocked, editor, block) => {
32834        // Node change event is only blocked while the user is holding down the page up/down key it would have limited effects on other things
32835        // Prevents a flickering UI while caret move in and out of the inline boundary element
32836        if (block && !blocked.get()) {
32837            editor.on('NodeChange', stopImmediatePropagation, true);
32838        }
32839        else if (!block && blocked.get()) {
32840            editor.off('NodeChange', stopImmediatePropagation);
32841        }
32842        blocked.set(block);
32843    };
32844    // Determining the correct placement on key up/down is very complicated and would require handling many edge cases,
32845    // which we don't have the resources to handle currently. As such, we allow the browser to change the selection and then make adjustments later.
32846    const setup$k = (editor, caret) => {
32847        // Mac OS doesn't move the selection when pressing page up/down and as such TinyMCE shouldn't be moving it either
32848        if (platform.os.isMacOS()) {
32849            return;
32850        }
32851        const blocked = Cell(false);
32852        editor.on('keydown', (evt) => {
32853            if (isPageUpDown(evt)) {
32854                setNodeChangeBlocker(blocked, editor, true);
32855            }
32856        });
32857        editor.on('keyup', (evt) => {
32858            if (!evt.isDefaultPrevented()) {
32859                executeKeyupAction(editor, caret, evt);
32860            }
32861            if (isPageUpDown(evt) && blocked.get()) {
32862                setNodeChangeBlocker(blocked, editor, false);
32863                editor.nodeChanged();
32864            }
32865        });
32866    };
32867
32868    const isValidContainer = (root, container) => root === container || root.contains(container);
32869    const isInEditableRange = (editor, range) => {
32870        // If the range is not in the body then it's in a shadow root and we should allow that more details in: TINY-11446
32871        if (!isValidContainer(editor.getBody(), range.startContainer) || !isValidContainer(editor.getBody(), range.endContainer)) {
32872            return true;
32873        }
32874        return isEditableRange(editor.dom, range);
32875    };
32876    const setup$j = (editor) => {
32877        editor.on('beforeinput', (e) => {
32878            // Normally input is blocked on non-editable elements that have contenteditable="false" however we are also treating
32879            // SVG elements as non-editable and deleting inside or into is possible in some browsers so we need to detect that and prevent that.
32880            if (!editor.selection.isEditable() || exists(e.getTargetRanges(), (rng) => !isInEditableRange(editor, rng))) {
32881                e.preventDefault();
32882            }
32883        });
32884    };
32885
32886    const insertTextAtPosition = (text, pos) => {
32887        const container = pos.container();
32888        const offset = pos.offset();
32889        if (isText$b(container)) {
32890            container.insertData(offset, text);
32891            return Optional.some(CaretPosition(container, offset + text.length));
32892        }
32893        else {
32894            return getElementFromPosition(pos).map((elm) => {
32895                const textNode = SugarElement.fromText(text);
32896                if (pos.isAtEnd()) {
32897                    after$4(elm, textNode);
32898                }
32899                else {
32900                    before$4(elm, textNode);
32901                }
32902                return CaretPosition(textNode.dom, text.length);
32903            });
32904        }
32905    };
32906    const insertNbspAtPosition = curry(insertTextAtPosition, nbsp);
32907    const insertSpaceAtPosition = curry(insertTextAtPosition, ' ');
32908
32909    const insertSpaceOrNbspAtPosition = (root, pos, schema) => needsToHaveNbsp(root, pos, schema) ? insertNbspAtPosition(pos) : insertSpaceAtPosition(pos);
32910    const locationToCaretPosition = (root) => (location) => location.fold((element) => prevPosition(root.dom, CaretPosition.before(element)), (element) => firstPositionIn(element), (element) => lastPositionIn(element), (element) => nextPosition(root.dom, CaretPosition.after(element)));
32911    const insertInlineBoundarySpaceOrNbsp = (root, pos, schema) => (checkPos) => needsToHaveNbsp(root, checkPos, schema) ? insertNbspAtPosition(pos) : insertSpaceAtPosition(pos);
32912    const setSelection = (editor) => (pos) => {
32913        editor.selection.setRng(pos.toRange());
32914        editor.nodeChanged();
32915    };
32916    const isInsideSummary = (domUtils, node) => domUtils.isEditable(domUtils.getParent(node, 'summary'));
32917    const insertSpaceOrNbspAtSelection = (editor) => {
32918        const pos = CaretPosition.fromRangeStart(editor.selection.getRng());
32919        const root = SugarElement.fromDom(editor.getBody());
32920        if (editor.selection.isCollapsed()) {
32921            const isInlineTarget$1 = curry(isInlineTarget, editor);
32922            const caretPosition = CaretPosition.fromRangeStart(editor.selection.getRng());
32923            return readLocation(isInlineTarget$1, editor.getBody(), caretPosition)
32924                .bind(locationToCaretPosition(root))
32925                .map((checkPos) => () => insertInlineBoundarySpaceOrNbsp(root, pos, editor.schema)(checkPos).each(setSelection(editor)));
32926        }
32927        else {
32928            return Optional.none();
32929        }
32930    };
32931    // TINY-9964: Firefox has a bug where the space key is toggling the open state instead of inserting a space in a summary element
32932    const insertSpaceInSummaryAtSelectionOnFirefox = (editor) => {
32933        const insertSpaceThunk = () => {
32934            const root = SugarElement.fromDom(editor.getBody());
32935            if (!editor.selection.isCollapsed()) {
32936                editor.getDoc().execCommand('Delete');
32937            }
32938            const pos = CaretPosition.fromRangeStart(editor.selection.getRng());
32939            insertSpaceOrNbspAtPosition(root, pos, editor.schema).each(setSelection(editor));
32940        };
32941        return someIf(Env.browser.isFirefox() && editor.selection.isEditable() && isInsideSummary(editor.dom, editor.selection.getRng().startContainer), insertSpaceThunk);
32942    };
32943
32944    const executeKeydownOverride$1 = (editor, evt) => {
32945        executeWithDelayedAction([
32946            { keyCode: VK.SPACEBAR, action: action(insertSpaceOrNbspAtSelection, editor) },
32947            { keyCode: VK.SPACEBAR, action: action(insertSpaceInSummaryAtSelectionOnFirefox, editor) }
32948        ], evt).each((applyAction) => {
32949            evt.preventDefault();
32950            const event = fireBeforeInputEvent(editor, 'insertText', { data: ' ' });
32951            if (!event.isDefaultPrevented()) {
32952                applyAction();
32953                // Browsers sends space in data even if the dom ends up with a nbsp so we should always be sending a space
32954                fireInputEvent(editor, 'insertText', { data: ' ' });
32955            }
32956        });
32957    };
32958    const setup$i = (editor) => {
32959        editor.on('keydown', (evt) => {
32960            if (!evt.isDefaultPrevented()) {
32961                executeKeydownOverride$1(editor, evt);
32962            }
32963        });
32964    };
32965
32966    const tableTabNavigation = (editor) => {
32967        if (hasTableTabNavigation(editor)) {
32968            return [
32969                { keyCode: VK.TAB, action: action(handleTab, editor, true) },
32970                { keyCode: VK.TAB, shiftKey: true, action: action(handleTab, editor, false) },
32971            ];
32972        }
32973        else {
32974            return [];
32975        }
32976    };
32977    const executeKeydownOverride = (editor, evt) => {
32978        execute([
32979            ...tableTabNavigation(editor)
32980        ], evt).each((_) => {
32981            evt.preventDefault();
32982        });
32983    };
32984    const setup$h = (editor) => {
32985        editor.on('keydown', (evt) => {
32986            if (!evt.isDefaultPrevented()) {
32987                executeKeydownOverride(editor, evt);
32988            }
32989        });
32990    };
32991
32992    const setup$g = (editor) => {
32993        editor.addShortcut('Meta+P', '', 'mcePrint');
32994        setup$p(editor);
32995        if (isRtc(editor)) {
32996            return Cell(null);
32997        }
32998        else {
32999            const caret = setupSelectedState(editor);
33000            setup$j(editor);
33001            setup$r(editor);
33002            setup$q(editor, caret);
33003            setup$o(editor, caret);
33004            setup$n(editor);
33005            setup$i(editor);
33006            setup$l(editor);
33007            setup$h(editor);
33008            setup$m(editor, caret);
33009            setup$k(editor, caret);
33010            return caret;
33011        }
33012    };
33013
33014    const updateList = (editor, update) => {
33015        const parentList = getParentList(editor);
33016        if (parentList === null || isWithinNonEditableList$1(editor, parentList)) {
33017            return;
33018        }
33019        editor.undoManager.transact(() => {
33020            if (isObject(update.styles)) {
33021                editor.dom.setStyles(parentList, update.styles);
33022            }
33023            if (isObject(update.attrs)) {
33024                each$d(update.attrs, (v, k) => editor.dom.setAttrib(parentList, k, v));
33025            }
33026        });
33027    };
33028
33029    const queryListCommandState = (editor, listName) => () => {
33030        const parentList = getParentList(editor);
33031        return isNonNullable(parentList) && parentList.nodeName === listName;
33032    };
33033    const setup$f = (editor) => {
33034        editor.addCommand('InsertUnorderedList', (ui, detail) => {
33035            toggleList(editor, 'UL', detail);
33036        });
33037        editor.addCommand('InsertOrderedList', (ui, detail) => {
33038            toggleList(editor, 'OL', detail);
33039        });
33040        editor.addCommand('InsertDefinitionList', (ui, detail) => {
33041            toggleList(editor, 'DL', detail);
33042        });
33043        editor.addCommand('RemoveList', () => {
33044            flattenListSelection(editor);
33045        });
33046        editor.addCommand('mceListUpdate', (ui, detail) => {
33047            if (isObject(detail)) {
33048                updateList(editor, detail);
33049            }
33050        });
33051        editor.addCommand('mceListBackspaceDelete', (_ui, forward) => {
33052            backspaceDelete$c(editor, forward);
33053        });
33054        editor.addQueryStateHandler('InsertUnorderedList', queryListCommandState(editor, 'UL'));
33055        editor.addQueryStateHandler('InsertOrderedList', queryListCommandState(editor, 'OL'));
33056        editor.addQueryStateHandler('InsertDefinitionList', queryListCommandState(editor, 'DL'));
33057    };
33058
33059    const setup$e = (editor) => {
33060        editor.on('init', () => {
33061            // Init is done after the editor's setup. This places our keydown after any keydowns registered in the setup function, so they can do default prevent.
33062            editor.on('keydown', (e) => {
33063                if (e.defaultPrevented) {
33064                    return;
33065                }
33066                if (e.keyCode === VK.BACKSPACE) {
33067                    if (backspaceDelete$c(editor, false)) {
33068                        e.preventDefault();
33069                    }
33070                }
33071                else if (e.keyCode === VK.DELETE) {
33072                    if (backspaceDelete$c(editor, true)) {
33073                        e.preventDefault();
33074                    }
33075                }
33076            });
33077        });
33078    };
33079
33080    const isTextNode = (node) => node.type === 3;
33081    const isEmpty = (nodeBuffer) => nodeBuffer.length === 0;
33082    const wrapInvalidChildren = (list) => {
33083        const insertListItem = (buffer, refNode) => {
33084            const li = AstNode.create('li');
33085            each$e(buffer, (node) => li.append(node));
33086            if (refNode) {
33087                list.insert(li, refNode, true);
33088            }
33089            else {
33090                list.append(li);
33091            }
33092        };
33093        const reducer = (buffer, node) => {
33094            if (isTextNode(node)) {
33095                return [...buffer, node];
33096            }
33097            else if (!isEmpty(buffer) && !isTextNode(node)) {
33098                insertListItem(buffer, node);
33099                return [];
33100            }
33101            else {
33102                return buffer;
33103            }
33104        };
33105        const restBuffer = foldl(list.children(), reducer, []);
33106        if (!isEmpty(restBuffer)) {
33107            insertListItem(restBuffer);
33108        }
33109    };
33110    const setup$d = (editor) => {
33111        editor.on('PreInit', () => {
33112            const { parser } = editor;
33113            parser.addNodeFilter('ul,ol', (nodes) => each$e(nodes, wrapInvalidChildren));
33114        });
33115    };
33116
33117    const setupTabKey = (editor) => {
33118        editor.on('keydown', (e) => {
33119            // Check for tab but not ctrl/cmd+tab since it switches browser tabs
33120            if (e.keyCode !== VK.TAB || VK.metaKeyPressed(e)) {
33121                return;
33122            }
33123            editor.undoManager.transact(() => {
33124                if (e.shiftKey ? outdentListSelection(editor) : indentListSelection(editor)) {
33125                    e.preventDefault();
33126                }
33127            });
33128        });
33129    };
33130    const setup$c = (editor) => {
33131        if (shouldIndentOnTab(editor)) {
33132            setupTabKey(editor);
33133        }
33134    };
33135
33136    const setup$b = (editor) => {
33137        setup$e(editor);
33138        setup$f(editor);
33139        setup$d(editor);
33140        setup$c(editor);
33141    };
33142
33143    /**
33144     * This class handles the nodechange event dispatching both manual and through selection change events.
33145     *
33146     * @class tinymce.NodeChange
33147     * @private
33148     */
33149    class NodeChange {
33150        editor;
33151        lastPath = [];
33152        constructor(editor) {
33153            this.editor = editor;
33154            let lastRng;
33155            const self = this;
33156            // Gecko doesn't support the "selectionchange" event
33157            if (!('onselectionchange' in editor.getDoc())) {
33158                editor.on('NodeChange click mouseup keyup focus', (e) => {
33159                    // Since DOM Ranges mutate on modification
33160                    // of the DOM we need to clone it's contents
33161                    const nativeRng = editor.selection.getRng();
33162                    const fakeRng = {
33163                        startContainer: nativeRng.startContainer,
33164                        startOffset: nativeRng.startOffset,
33165                        endContainer: nativeRng.endContainer,
33166                        endOffset: nativeRng.endOffset
33167                    };
33168                    // Always treat nodechange as a selectionchange since applying
33169                    // formatting to the current range wouldn't update the range but it's parent
33170                    if (e.type === 'nodechange' || !isEq$4(fakeRng, lastRng)) {
33171                        editor.dispatch('SelectionChange');
33172                    }
33173                    lastRng = fakeRng;
33174                });
33175            }
33176            // IE has a bug where it fires a selectionchange on right click that has a range at the start of the body
33177            // When the contextmenu event fires the selection is located at the right location
33178            editor.on('contextmenu', () => {
33179                store(editor);
33180                editor.dispatch('SelectionChange');
33181            });
33182            // Selection change is delayed ~200ms on IE when you click inside the current range
33183            editor.on('SelectionChange', () => {
33184                const startElm = editor.selection.getStart(true);
33185                // When focusout from after cef element to other input element the startelm can be undefined
33186                if (!startElm) {
33187                    return;
33188                }
33189                if (hasAnyRanges(editor) && !self.isSameElementPath(startElm) && editor.dom.isChildOf(startElm, editor.getBody())) {
33190                    editor.nodeChanged({ selectionChange: true });
33191                }
33192            });
33193            // Fire an extra nodeChange on mouseup for compatibility reasons
33194            editor.on('mouseup', (e) => {
33195                if (!e.isDefaultPrevented() && hasAnyRanges(editor)) {
33196                    // Delay nodeChanged call for WebKit edge case issue where the range
33197                    // isn't updated until after you click outside a selected image
33198                    if (editor.selection.getNode().nodeName === 'IMG') {
33199                        Delay.setEditorTimeout(editor, () => {
33200                            editor.nodeChanged();
33201                        });
33202                    }
33203                    else {
33204                        editor.nodeChanged();
33205                    }
33206                }
33207            });
33208        }
33209        /**
33210         * Dispatches out a onNodeChange event to all observers. This method should be called when you
33211         * need to update the UI states or element path etc.
33212         *
33213         * @method nodeChanged
33214         * @param {Object} args Optional args to pass to NodeChange event handlers.
33215         */
33216        nodeChanged(args = {}) {
33217            const editor = this.editor;
33218            const selection = editor.selection;
33219            let node;
33220            // Fix for bug #1896577 it seems that this can not be fired while the editor is loading
33221            if (editor.initialized && selection && !shouldDisableNodeChange(editor) && !isDisabled$1(editor)) {
33222                // Get start node
33223                const root = editor.getBody();
33224                node = selection.getStart(true) || root;
33225                // Make sure the node is within the editor root or is the editor root
33226                if (node.ownerDocument !== editor.getDoc() || !editor.dom.isChildOf(node, root)) {
33227                    node = root;
33228                }
33229                // Get parents and add them to object
33230                const parents = [];
33231                editor.dom.getParent(node, (node) => {
33232                    if (node === root) {
33233                        return true;
33234                    }
33235                    else {
33236                        parents.push(node);
33237                        return false;
33238                    }
33239                });
33240                editor.dispatch('NodeChange', {
33241                    ...args,
33242                    element: node,
33243                    parents
33244                });
33245            }
33246        }
33247        /**
33248         * Returns true/false if the current element path has been changed or not.
33249         *
33250         * @private
33251         * @return {Boolean} True if the element path is the same false if it's not.
33252         */
33253        isSameElementPath(startElm) {
33254            let i;
33255            const editor = this.editor;
33256            const currentPath = reverse(editor.dom.getParents(startElm, always, editor.getBody()));
33257            if (currentPath.length === this.lastPath.length) {
33258                for (i = currentPath.length; i >= 0; i--) {
33259                    if (currentPath[i] !== this.lastPath[i]) {
33260                        break;
33261                    }
33262                }
33263                if (i === -1) {
33264                    this.lastPath = currentPath;
33265                    return true;
33266                }
33267            }
33268            this.lastPath = currentPath;
33269            return false;
33270        }
33271    }
33272
33273    const internalMimeType = 'x-tinymce/html';
33274    const internalHtmlMime = constant(internalMimeType);
33275    const internalMark = '<!-- ' + internalMimeType + ' -->';
33276    const mark = (html) => internalMark + html;
33277    const unmark = (html) => html.replace(internalMark, '');
33278    const isMarked = (html) => html.indexOf(internalMark) !== -1;
33279
33280    /*
33281     * This module contains utilities to convert newlines (\n or \r\n) to BRs or to a combination of the specified block element and BRs
33282     */
33283    const isPlainText = (text) => {
33284        // so basically any tag that is not one of the "p, div, span, br", or is one of them, but is followed
33285        // by some additional characters qualifies the text as not a plain text (having some HTML tags)
33286        // <span style="white-space:pre"> and <br /> are added as separate exceptions to the rule
33287        return !/<(?:\/?(?!(?:div|p|br|span)>)\w+|(?:(?!(?:span style="white-space:\s?pre;?">)|br\s?\/>))\w+\s[^>]+)>/i.test(text);
33288    };
33289    const openContainer = (rootTag, rootAttrs) => {
33290        let tag = '<' + rootTag;
33291        const attrs = mapToArray(rootAttrs, (value, key) => key + '="' + Entities.encodeAllRaw(value) + '"');
33292        if (attrs.length) {
33293            tag += ' ' + attrs.join(' ');
33294        }
33295        return tag + '>';
33296    };
33297    const toBlockElements = (text, rootTag, rootAttrs) => {
33298        const blocks = text.split(/\n\n/);
33299        const tagOpen = openContainer(rootTag, rootAttrs);
33300        const tagClose = '</' + rootTag + '>';
33301        const paragraphs = map$3(blocks, (p) => {
33302            return p.split(/\n/).join('<br />');
33303        });
33304        const stitch = (p) => {
33305            return tagOpen + p + tagClose;
33306        };
33307        return paragraphs.length === 1 ? paragraphs[0] : map$3(paragraphs, stitch).join('');
33308    };
33309
33310    const pasteBinDefaultContent = '%MCEPASTEBIN%';
33311    /*
33312     * Creates a paste bin element as close as possible to the current caret location and places the focus inside that element
33313     * so that when the real paste event occurs the contents gets inserted into this element
33314     * instead of the current editor selection element.
33315     */
33316    const create$5 = (editor, lastRngCell) => {
33317        const { dom, selection } = editor;
33318        const body = editor.getBody();
33319        lastRngCell.set(selection.getRng());
33320        // Create a pastebin
33321        const pasteBinElm = dom.add(editor.getBody(), 'div', {
33322            'id': 'mcepastebin',
33323            'class': 'mce-pastebin',
33324            'contentEditable': true,
33325            'data-mce-bogus': 'all',
33326            'style': 'position: fixed; top: 50%; width: 10px; height: 10px; overflow: hidden; opacity: 0'
33327        }, pasteBinDefaultContent);
33328        // Move paste bin out of sight since the controlSelection rect gets displayed otherwise on Gecko
33329        if (Env.browser.isFirefox()) {
33330            dom.setStyle(pasteBinElm, 'left', dom.getStyle(body, 'direction', true) === 'rtl' ? 0xFFFF : -0xFFFF);
33331        }
33332        // Prevent focus events from bubbling fixed FocusManager issues
33333        dom.bind(pasteBinElm, 'beforedeactivate focusin focusout', (e) => {
33334            e.stopPropagation();
33335        });
33336        pasteBinElm.focus();
33337        selection.select(pasteBinElm, true);
33338    };
33339    /*
33340     * Removes the paste bin if it exists.
33341     */
33342    const remove = (editor, lastRngCell) => {
33343        const dom = editor.dom;
33344        if (getEl(editor)) {
33345            let pasteBinClone;
33346            const lastRng = lastRngCell.get();
33347            // WebKit/Blink might clone the div so
33348            // lets make sure we remove all clones
33349            // TODO: Man o man is this ugly. WebKit is the new IE! Remove this if they ever fix it!
33350            while ((pasteBinClone = getEl(editor))) {
33351                dom.remove(pasteBinClone);
33352                dom.unbind(pasteBinClone);
33353            }
33354            if (lastRng) {
33355                editor.selection.setRng(lastRng);
33356            }
33357        }
33358        lastRngCell.set(null);
33359    };
33360    const getEl = (editor) => editor.dom.get('mcepastebin');
33361    const isPasteBin = (elm) => isNonNullable(elm) && elm.id === 'mcepastebin';
33362    /*
33363     * Returns the contents of the paste bin as a HTML string.
33364     */
33365    const getHtml = (editor) => {
33366        const dom = editor.dom;
33367        // Since WebKit/Chrome might clone the paste bin when pasting
33368        // for example: <img style="float: right"> we need to check if any of them contains some useful html.
33369        // TODO: Man o man is this ugly. WebKit is the new IE! Remove this if they ever fix it!
33370        const copyAndRemove = (toElm, fromElm) => {
33371            toElm.appendChild(fromElm);
33372            dom.remove(fromElm, true); // remove, but keep children
33373        };
33374        // find only top level elements (there might be more nested inside them as well, see TINY-1162)
33375        const [pasteBinElm, ...pasteBinClones] = filter$5(editor.getBody().childNodes, isPasteBin);
33376        // if clones were found, move their content into the first bin
33377        each$e(pasteBinClones, (pasteBinClone) => {
33378            copyAndRemove(pasteBinElm, pasteBinClone);
33379        });
33380        // TINY-1162: when copying plain text (from notepad for example) WebKit clones
33381        // paste bin (with styles and attributes) and uses it as a default  wrapper for
33382        // the chunks of the content, here we cycle over the whole paste bin and replace
33383        // those wrappers with a basic div
33384        const dirtyWrappers = dom.select('div[id=mcepastebin]', pasteBinElm);
33385        for (let i = dirtyWrappers.length - 1; i >= 0; i--) {
33386            const cleanWrapper = dom.create('div');
33387            pasteBinElm.insertBefore(cleanWrapper, dirtyWrappers[i]);
33388            copyAndRemove(cleanWrapper, dirtyWrappers[i]);
33389        }
33390        return pasteBinElm ? pasteBinElm.innerHTML : '';
33391    };
33392    const isDefaultPasteBinContent = (content) => content === pasteBinDefaultContent;
33393    const PasteBin = (editor) => {
33394        const lastRng = Cell(null);
33395        return {
33396            create: () => create$5(editor, lastRng),
33397            remove: () => remove(editor, lastRng),
33398            getEl: () => getEl(editor),
33399            getHtml: () => getHtml(editor),
33400            getLastRng: lastRng.get
33401        };
33402    };
33403
33404    /*
33405     * This module contains various utility functions for the paste logic.
33406     */
33407    const filter = (content, items) => {
33408        Tools.each(items, (v) => {
33409            if (is$5(v, RegExp)) {
33410                content = content.replace(v, '');
33411            }
33412            else {
33413                content = content.replace(v[0], v[1]);
33414            }
33415        });
33416        return content;
33417    };
33418    /*
33419     * Gets the innerText of the specified element. It will handle edge cases
33420     * and works better than textContent on Gecko.
33421     */
33422    const innerText = (html) => {
33423        const schema = Schema();
33424        const domParser = DomParser({}, schema);
33425        let text = '';
33426        const voidElements = schema.getVoidElements();
33427        const ignoreElements = Tools.makeMap('script noscript style textarea video audio iframe object', ' ');
33428        const blockElements = schema.getBlockElements();
33429        const walk = (node) => {
33430            const name = node.name, currentNode = node;
33431            if (name === 'br') {
33432                text += '\n';
33433                return;
33434            }
33435            // Ignore wbr, to replicate innerText on Chrome/Firefox
33436            if (name === 'wbr') {
33437                return;
33438            }
33439            // img/input/hr but ignore wbr as it's just a potential word break
33440            if (voidElements[name]) {
33441                text += ' ';
33442            }
33443            // Ignore script, video contents
33444            if (ignoreElements[name]) {
33445                text += ' ';
33446                return;
33447            }
33448            if (node.type === 3) {
33449                text += node.value;
33450            }
33451            // Walk all children
33452            if (!(node.name in schema.getVoidElements())) {
33453                let currentNode = node.firstChild;
33454                if (currentNode) {
33455                    do {
33456                        walk(currentNode);
33457                    } while ((currentNode = currentNode.next));
33458                }
33459            }
33460            // Add \n or \n\n for blocks or P
33461            if (blockElements[name] && currentNode.next) {
33462                text += '\n';
33463                if (name === 'p') {
33464                    text += '\n';
33465                }
33466            }
33467        };
33468        html = filter(html, [
33469            /<!\[[^\]]+\]>/g // Conditional comments
33470        ]);
33471        walk(domParser.parse(html));
33472        return text;
33473    };
33474    /*
33475     * Trims the specified HTML by removing all WebKit fragments, all elements wrapping the body trailing BR elements etc.
33476     */
33477    const trimHtml = (html) => {
33478        const trimSpaces = (all, s1, s2) => {
33479            // WebKit &nbsp; meant to preserve multiple spaces but instead inserted around all inline tags,
33480            // including the spans with inline styles created on paste
33481            if (!s1 && !s2) {
33482                return ' ';
33483            }
33484            return nbsp;
33485        };
33486        html = filter(html, [
33487            /^[\s\S]*<body[^>]*>\s*|\s*<\/body[^>]*>[\s\S]*$/ig, // Remove anything but the contents within the BODY element
33488            /<!--StartFragment-->|<!--EndFragment-->/g, // Inner fragments (tables from excel on mac)
33489            [/( ?)<span class="Apple-converted-space">\u00a0<\/span>( ?)/g, trimSpaces],
33490            /<br class="Apple-interchange-newline">/g,
33491            /<br>$/i // Trailing BR elements
33492        ]);
33493        return html;
33494    };
33495    // TODO: Should be in some global class
33496    const createIdGenerator = (prefix) => {
33497        let count = 0;
33498        return () => {
33499            return prefix + (count++);
33500        };
33501    };
33502    const getImageMimeType = (ext) => {
33503        const lowerExt = ext.toLowerCase();
33504        const mimeOverrides = {
33505            jpg: 'jpeg',
33506            jpe: 'jpeg',
33507            jfi: 'jpeg',
33508            jif: 'jpeg',
33509            jfif: 'jpeg',
33510            pjpeg: 'jpeg',
33511            pjp: 'jpeg',
33512            svg: 'svg+xml'
33513        };
33514        return Tools.hasOwn(mimeOverrides, lowerExt) ? 'image/' + mimeOverrides[lowerExt] : 'image/' + lowerExt;
33515    };
33516
33517    const preProcess = (editor, html) => {
33518        const parser = DomParser({
33519            sanitize: shouldSanitizeXss(editor),
33520            sandbox_iframes: shouldSandboxIframes(editor),
33521            sandbox_iframes_exclusions: getSandboxIframesExclusions(editor),
33522            convert_unsafe_embeds: shouldConvertUnsafeEmbeds(editor)
33523        }, editor.schema);
33524        // Strip meta elements
33525        parser.addNodeFilter('meta', (nodes) => {
33526            Tools.each(nodes, (node) => {
33527                node.remove();
33528            });
33529        });
33530        const fragment = parser.parse(html, { forced_root_block: false, isRootContent: true });
33531        return HtmlSerializer({ validate: true }, editor.schema).serialize(fragment);
33532    };
33533    const processResult = (content, cancelled) => ({ content, cancelled });
33534    const postProcessFilter = (editor, html, internal) => {
33535        const tempBody = editor.dom.create('div', { style: 'display:none' }, html);
33536        const postProcessArgs = firePastePostProcess(editor, tempBody, internal);
33537        return processResult(postProcessArgs.node.innerHTML, postProcessArgs.isDefaultPrevented());
33538    };
33539    const filterContent = (editor, content, internal) => {
33540        const preProcessArgs = firePastePreProcess(editor, content, internal);
33541        // Filter the content to remove potentially dangerous content (eg scripts)
33542        const filteredContent = preProcess(editor, preProcessArgs.content);
33543        if (editor.hasEventListeners('PastePostProcess') && !preProcessArgs.isDefaultPrevented()) {
33544            return postProcessFilter(editor, filteredContent, internal);
33545        }
33546        else {
33547            return processResult(filteredContent, preProcessArgs.isDefaultPrevented());
33548        }
33549    };
33550    const process = (editor, html, internal) => {
33551        return filterContent(editor, html, internal);
33552    };
33553
33554    const pasteHtml$1 = (editor, html) => {
33555        editor.insertContent(html, {
33556            merge: shouldPasteMergeFormats(editor),
33557            paste: true
33558        });
33559        return true;
33560    };
33561    const isAbsoluteUrl = (url) => /^https?:\/\/[\w\-\/+=.,!;:&%@^~(){}?#]+$/i.test(url);
33562    const isImageUrl = (editor, url) => {
33563        return isAbsoluteUrl(url) && exists(getAllowedImageFileTypes(editor), (type) => endsWith(url.toLowerCase(), `.${type.toLowerCase()}`));
33564    };
33565    const createImage = (editor, url, pasteHtmlFn) => {
33566        editor.undoManager.extra(() => {
33567            pasteHtmlFn(editor, url);
33568        }, () => {
33569            editor.insertContent('<img src="' + url + '">');
33570        });
33571        return true;
33572    };
33573    const createLink = (editor, url, pasteHtmlFn) => {
33574        editor.undoManager.extra(() => {
33575            pasteHtmlFn(editor, url);
33576        }, () => {
33577            editor.execCommand('mceInsertLink', false, url);
33578        });
33579        return true;
33580    };
33581    const linkSelection = (editor, html, pasteHtmlFn) => !editor.selection.isCollapsed() && isAbsoluteUrl(html) ? createLink(editor, html, pasteHtmlFn) : false;
33582    const insertImage = (editor, html, pasteHtmlFn) => isImageUrl(editor, html) ? createImage(editor, html, pasteHtmlFn) : false;
33583    const smartInsertContent = (editor, html) => {
33584        Tools.each([
33585            linkSelection,
33586            insertImage,
33587            pasteHtml$1
33588        ], (action) => {
33589            return !action(editor, html, pasteHtml$1);
33590        });
33591    };
33592    const insertContent = (editor, html, pasteAsText) => {
33593        if (pasteAsText || !isSmartPasteEnabled(editor)) {
33594            pasteHtml$1(editor, html);
33595        }
33596        else {
33597            smartInsertContent(editor, html);
33598        }
33599    };
33600
33601    const uniqueId = createIdGenerator('mceclip');
33602    const createPasteDataTransfer = (html) => {
33603        const dataTransfer = createDataTransfer();
33604        setHtmlData(dataTransfer, html);
33605        // TINY-9829: Set to read-only mode as per https://www.w3.org/TR/input-events-2/
33606        setReadOnlyMode(dataTransfer);
33607        return dataTransfer;
33608    };
33609    const doPaste = (editor, content, internal, pasteAsText, shouldSimulateInputEvent) => {
33610        const res = process(editor, content, internal);
33611        if (!res.cancelled) {
33612            const content = res.content;
33613            const doPasteAction = () => insertContent(editor, content, pasteAsText);
33614            if (shouldSimulateInputEvent) {
33615                const args = fireBeforeInputEvent(editor, 'insertFromPaste', { dataTransfer: createPasteDataTransfer(content) });
33616                if (!args.isDefaultPrevented()) {
33617                    doPasteAction();
33618                    fireInputEvent(editor, 'insertFromPaste');
33619                }
33620            }
33621            else {
33622                doPasteAction();
33623            }
33624        }
33625    };
33626    /*
33627     * Pastes the specified HTML. This means that the HTML is filtered and then
33628     * inserted at the current selection in the editor. It will also fire paste events
33629     * for custom user filtering.
33630     */
33631    const pasteHtml = (editor, html, internalFlag, shouldSimulateInputEvent) => {
33632        const internal = internalFlag ? internalFlag : isMarked(html);
33633        doPaste(editor, unmark(html), internal, false, shouldSimulateInputEvent);
33634    };
33635    /*
33636     * Pastes the specified text. This means that the plain text is processed
33637     * and converted into BR and P elements. It will fire paste events for custom filtering.
33638     */
33639    const pasteText = (editor, text, shouldSimulateInputEvent) => {
33640        const encodedText = editor.dom.encode(text).replace(/\r\n/g, '\n');
33641        const normalizedText = normalize$4(encodedText, getPasteTabSpaces(editor));
33642        const html = toBlockElements(normalizedText, getForcedRootBlock(editor), getForcedRootBlockAttrs(editor));
33643        doPaste(editor, html, false, true, shouldSimulateInputEvent);
33644    };
33645    /*
33646     * Gets various content types out of a datatransfer object.
33647     */
33648    const getDataTransferItems = (dataTransfer) => {
33649        const items = {};
33650        if (dataTransfer && dataTransfer.types) {
33651            for (let i = 0; i < dataTransfer.types.length; i++) {
33652                const contentType = dataTransfer.types[i];
33653                try { // IE11 throws exception when contentType is Files (type is present but data cannot be retrieved via getData())
33654                    items[contentType] = dataTransfer.getData(contentType);
33655                }
33656                catch {
33657                    items[contentType] = ''; // useless in general, but for consistency across browsers
33658                }
33659            }
33660        }
33661        return items;
33662    };
33663    const hasContentType = (clipboardContent, mimeType) => mimeType in clipboardContent && clipboardContent[mimeType].length > 0;
33664    const hasHtmlOrText = (content) => hasContentType(content, 'text/html') || hasContentType(content, 'text/plain');
33665    const extractFilename = (editor, str) => {
33666        const m = str.match(/([\s\S]+?)(?:\.[a-z0-9.]+)$/i);
33667        return isNonNullable(m) ? editor.dom.encode(m[1]) : undefined;
33668    };
33669    const createBlobInfo = (editor, blobCache, file, base64) => {
33670        const id = uniqueId();
33671        const useFileName = shouldReuseFileName(editor) && isNonNullable(file.name);
33672        const name = useFileName ? extractFilename(editor, file.name) : id;
33673        const filename = useFileName ? file.name : undefined;
33674        const blobInfo = blobCache.create(id, file, base64, name, filename);
33675        blobCache.add(blobInfo);
33676        return blobInfo;
33677    };
33678    const pasteImage = 
33678(editor, imageItem) => {
33679        parseDataUri(imageItem.uri).each(({ data, type, base64Encoded }) => {
33680            const base64 = base64Encoded ? data : btoa(data);
33681            const file = imageItem.file;
33682            // TODO: Move the bulk of the cache logic to EditorUpload
33683            const blobCache = editor.editorUpload.blobCache;
33684            const existingBlobInfo = blobCache.getByData(base64, type);
33685            const blobInfo = existingBlobInfo ?? createBlobInfo(editor, blobCache, file, base64);
33686            pasteHtml(editor, `<img src="${blobInfo.blobUri()}">`, false, true);
33687        });
33688    };
33689    const isClipboardEvent = (event) => event.type === 'paste';
vendor: 1,757 bytes, lines 33690-33723
33690    const readFilesAsDataUris = (items) => Promise.all(map$3(items, (file) => {
33691        return blobToDataUri(file).then((uri) => ({ file, uri }));
33692    }));
33693    const isImage = (editor) => {
33694        const allowedExtensions = getAllowedImageFileTypes(editor);
33695        return (file) => startsWith(file.type, 'image/') && exists(allowedExtensions, (extension) => {
33696            return getImageMimeType(extension) === file.type;
33697        });
33698    };
33699    const getImagesFromDataTransfer = (editor, dataTransfer) => {
33700        const items = dataTransfer.items ? bind$3(from(dataTransfer.items), (item) => {
33701            return item.kind === 'file' ? [item.getAsFile()] : [];
33702        }) : [];
33703        const files = dataTransfer.files ? from(dataTransfer.files) : [];
33704        return filter$5(items.length > 0 ? items : files, isImage(editor));
33705    };
33706    /*
33707     * Checks if the clipboard contains image data if it does it will take that data
33708     * and convert it into a data url image and paste that image at the caret location.
33709     */
33710    const pasteImageData = (editor, e, rng) => {
33711        const dataTransfer = isClipboardEvent(e) ? e.clipboardData : e.dataTransfer;
33712        if (shouldPasteDataImages(editor) && dataTransfer) {
33713            const images = getImagesFromDataTransfer(editor, dataTransfer);
33714            if (images.length > 0) {
33715                e.preventDefault();
33716                // eslint-disable-next-line @typescript-eslint/no-floating-promises
33717                readFilesAsDataUris(images).then((fileResults) => {
33718                    if (rng) {
33719                        editor.selection.setRng(rng);
33720                    }
33721                    each$e(fileResults, (result) => {
33722                        pasteImage(editor, result);
33723                    });
vendor: 67,018 bytes, lines 33723-35106
33723
33724                });
33725                return true;
33726            }
33727        }
33728        return false;
33729    };
33730    // Chrome on Android doesn't support proper clipboard access so we have no choice but to allow the browser default behavior.
33731    const isBrokenAndroidClipboardEvent = (e) => Env.os.isAndroid() && e.clipboardData?.items?.length === 0;
33732    // Ctrl+V or Shift+Insert
33733    const isKeyboardPasteEvent = (e) => (VK.metaKeyPressed(e) && e.keyCode === 86) || (e.shiftKey && e.keyCode === 45);
33734    const insertClipboardContent = (editor, clipboardContent, html, plainTextMode, shouldSimulateInputEvent) => {
33735        let content = trimHtml(html);
33736        const isInternal = hasContentType(clipboardContent, internalHtmlMime()) || isMarked(html);
33737        const isPlainTextHtml = !isInternal && isPlainText(content);
33738        const isAbsoluteUrl$1 = isAbsoluteUrl(content);
33739        // If the paste bin is empty try using plain text mode since that is better than nothing right?
33740        // Also if we got nothing from clipboard API/pastebin or the content is a plain text (with only
33741        // some BRs, Ps or DIVs as newlines) then we fallback to plain/text
33742        if (isDefaultPasteBinContent(content) || !content.length || (isPlainTextHtml && !isAbsoluteUrl$1)) {
33743            plainTextMode = true;
33744        }
33745        // Grab plain text from Clipboard API or convert existing HTML to plain text
33746        if (plainTextMode || isAbsoluteUrl$1) {
33747            // Use plain text contents from Clipboard API unless the HTML contains paragraphs then
33748            // we should convert the HTML to plain text since works better when pasting HTML/Word contents as plain text
33749            if (hasContentType(clipboardContent, 'text/plain') && isPlainTextHtml) {
33750                content = clipboardContent['text/plain'];
33751            }
33752            else {
33753                content = innerText(content);
33754            }
33755        }
33756        // If the content is the paste bin default HTML then it was impossible to get the clipboard data out.
33757        if (isDefaultPasteBinContent(content)) {
33758            return;
33759        }
33760        if (plainTextMode) {
33761            pasteText(editor, content, shouldSimulateInputEvent);
33762        }
33763        else {
33764            pasteHtml(editor, content, isInternal, shouldSimulateInputEvent);
33765        }
33766    };
33767    const registerEventHandlers = (editor, pasteBin, pasteFormat) => {
33768        let keyboardPastePlainTextState;
33769        const getLastRng = () => pasteBin.getLastRng() || editor.selection.getRng();
33770        editor.on('keydown', (e) => {
33771            if (isKeyboardPasteEvent(e) && !e.isDefaultPrevented()) {
33772                keyboardPastePlainTextState = e.shiftKey && e.keyCode === 86;
33773            }
33774        });
33775        editor.on('paste', (e) => {
33776            if (e.isDefaultPrevented() || isBrokenAndroidClipboardEvent(e)) {
33777                return;
33778            }
33779            const plainTextMode = pasteFormat.get() === 'text' || keyboardPastePlainTextState;
33780            keyboardPastePlainTextState = false;
33781            const clipboardContent = getDataTransferItems(e.clipboardData);
33782            if (!hasHtmlOrText(clipboardContent) && pasteImageData(editor, e, getLastRng())) {
33783                return;
33784            }
33785            // If the clipboard API has HTML then use that directly
33786            if (hasContentType(clipboardContent, 'text/html')) {
33787                e.preventDefault();
33788                insertClipboardContent(editor, clipboardContent, clipboardContent['text/html'], plainTextMode, true);
33789            }
33790            else if (hasContentType(clipboardContent, 'text/plain') && hasContentType(clipboardContent, 'text/uri-list')) {
33791                /*
33792                Safari adds the uri-list attribute to links copied within it.
33793                When pasting something with the url-list within safari using the default functionality it will convert it from www.example.com to <a href="www.example.com">www.example.com</a> when pasting into the pasteBin-div.
33794                This causes issues. To solve this we bypass the default paste functionality for this situation.
33795                 */
33796                e.preventDefault();
33797                insertClipboardContent(editor, clipboardContent, clipboardContent['text/plain'], plainTextMode, true);
33798            }
33799            else {
33800                // We can't extract the HTML content from the clipboard so we need to allow the paste
33801                // to run via the pastebin and then extract from there
33802                pasteBin.create();
33803                Delay.setEditorTimeout(editor, () => {
33804                    // Get the pastebin content and then remove it so the selection is restored
33805                    const html = pasteBin.getHtml();
33806                    pasteBin.remove();
33807                    insertClipboardContent(editor, clipboardContent, html, plainTextMode, false);
33808                }, 0);
33809            }
33810        });
33811    };
33812    const registerDataImageFilter = (editor) => {
33813        const isWebKitFakeUrl = (src) => startsWith(src, 'webkit-fake-url');
33814        const isDataUri = (src) => startsWith(src, 'data:');
33815        const isPasteInsert = (args) => args.data?.paste === true;
33816        // Remove all data images from paste for example from Gecko
33817        // except internal images like video elements
33818        editor.parser.addNodeFilter('img', (nodes, name, args) => {
33819            if (!shouldPasteDataImages(editor) && isPasteInsert(args)) {
33820                for (const node of nodes) {
33821                    const src = node.attr('src');
33822                    if (isString(src) && !node.attr('data-mce-object') && src !== Env.transparentSrc) {
33823                        // Safari on Mac produces webkit-fake-url see: https://bugs.webkit.org/show_bug.cgi?id=49141
33824                        if (isWebKitFakeUrl(src)) {
33825                            node.remove();
33826                        }
33827                        else if (!shouldAllowHtmlDataUrls(editor) && isDataUri(src)) {
33828                            node.remove();
33829                        }
33830                    }
33831                }
33832            }
33833        });
33834    };
33835    /*
33836     * This class contains logic for getting HTML contents out of the clipboard.
33837     *
33838     * This by default will attempt to use the W3C clipboard API to get HTML content.
33839     * If that can't be used then fallback to letting the browser paste natively with
33840     * some logic to clean up what the browser generated, as it can mutate the content.
33841     *
33842     * Current implementation steps:
33843     *  1. On keydown determine if we should paste as plain text.
33844     *  2. Wait for the browser to fire a "paste" event and get the contents out of clipboard.
33845     *  3. If no content is available, then attach the paste bin and change the selection to be inside the bin.
33846     *  4. Extract the contents from the bin in the next event loop.
33847     *  5. If no HTML is found or we're using plain text paste mode then convert the HTML or lookup the clipboard to get the plain text.
33848     *  6. Process the content from the clipboard or pastebin and insert it into the editor.
33849     */
33850    const registerEventsAndFilters = (editor, pasteBin, pasteFormat) => {
33851        registerEventHandlers(editor, pasteBin, pasteFormat);
33852        registerDataImageFilter(editor);
33853    };
33854
33855    const togglePlainTextPaste = (editor, pasteFormat) => {
33856        if (pasteFormat.get() === 'text') {
33857            pasteFormat.set('html');
33858            firePastePlainTextToggle(editor, false);
33859        }
33860        else {
33861            pasteFormat.set('text');
33862            firePastePlainTextToggle(editor, true);
33863        }
33864        editor.focus();
33865    };
33866    const register$1 = (editor, pasteFormat) => {
33867        editor.addCommand('mceTogglePlainTextPaste', () => {
33868            togglePlainTextPaste(editor, pasteFormat);
33869        });
33870        editor.addCommand('mceInsertClipboardContent', (ui, value) => {
33871            if (value.html) {
33872                // TINY-9997: Input events are not simulated when using paste commands, similar to how the 'mceInsertContent'
33873                // and 'Delete' commands work.
33874                pasteHtml(editor, value.html, value.internal, false);
33875            }
33876            if (value.text) {
33877                pasteText(editor, value.text, false);
33878            }
33879        });
33880    };
33881
33882    const setHtml5Clipboard = (clipboardData, html, text) => {
33883        if (clipboardData) {
33884            try {
33885                clipboardData.clearData();
33886                clipboardData.setData('text/html', html);
33887                clipboardData.setData('text/plain', text);
33888                clipboardData.setData(internalHtmlMime(), html);
33889                return true;
33890            }
33891            catch {
33892                return false;
33893            }
33894        }
33895        else {
33896            return false;
33897        }
33898    };
33899    const setClipboardData = (evt, data, fallback, done) => {
33900        if (setHtml5Clipboard(evt.clipboardData, data.html, data.text)) {
33901            evt.preventDefault();
33902            done();
33903        }
33904        else {
33905            fallback(data.html, done);
33906        }
33907    };
33908    const fallback = (editor) => (html, done) => {
33909        const { dom, selection } = editor;
33910        const outer = dom.create('div', { 'contenteditable': 'false', 'data-mce-bogus': 'all' });
33911        const inner = dom.create('div', { contenteditable: 'true' }, html);
33912        dom.setStyles(outer, {
33913            position: 'fixed',
33914            top: '0',
33915            left: '-3000px',
33916            width: '1000px',
33917            overflow: 'hidden'
33918        });
33919        outer.appendChild(inner);
33920        dom.add(editor.getBody(), outer);
33921        const range = selection.getRng();
33922        inner.focus();
33923        const offscreenRange = dom.createRng();
33924        offscreenRange.selectNodeContents(inner);
33925        selection.setRng(offscreenRange);
33926        Delay.setEditorTimeout(editor, () => {
33927            selection.setRng(range);
33928            dom.remove(outer);
33929            done();
33930        }, 0);
33931    };
33932    const getData = (editor) => ({
33933        html: mark(editor.selection.getContent({ contextual: true })),
33934        text: editor.selection.getContent({ format: 'text' })
33935    });
33936    const isTableSelection = (editor) => !!editor.dom.getParent(editor.selection.getStart(), 'td[data-mce-selected],th[data-mce-selected]', editor.getBody());
33937    const hasSelectedContent = (editor) => !editor.selection.isCollapsed() || isTableSelection(editor);
33938    const cut = (editor, caret) => (evt) => {
33939        if (!evt.isDefaultPrevented() && hasSelectedContent(editor) && editor.selection.isEditable()) {
33940            setClipboardData(evt, getData(editor), fallback(editor), () => {
33941                if (Env.browser.isChromium() || Env.browser.isFirefox()) {
33942                    const rng = editor.selection.getRng();
33943                    // Chrome fails to execCommand from another execCommand with this message:
33944                    // "We don't execute document.execCommand() this time, because it is called recursively.""
33945                    // Firefox 82 now also won't run recursive commands, but it doesn't log an error
33946                    Delay.setEditorTimeout(editor, () => {
33947                        // Restore the range before deleting, as Chrome on Android will
33948                        // collapse the selection after a cut event has fired.
33949                        editor.selection.setRng(rng);
33950                        // Delete command is called directly without using editor.execCommand to avoid running editor.focus() which side effect was selection normalization and additional undo level
33951                        deleteCommand(editor, caret);
33952                    }, 0);
33953                }
33954                else {
33955                    // Delete command is called directly without using editor.execCommand to avoid running editor.focus() which side effect was selection normalization and additional undo level
33956                    deleteCommand(editor, caret);
33957                }
33958            });
33959        }
33960    };
33961    const copy = (editor) => (evt) => {
33962        if (!evt.isDefaultPrevented() && hasSelectedContent(editor)) {
33963            setClipboardData(evt, getData(editor), fallback(editor), noop);
33964        }
33965    };
33966    const register = (editor, caret) => {
33967        editor.on('cut', cut(editor, caret));
33968        editor.on('copy', copy(editor));
33969    };
33970
33971    const getCaretRangeFromEvent = (editor, e) => 
33972    // TODO: TINY-7075 Remove the "?? 0" here when agar passes valid client coords
33973    RangeUtils.getCaretRangeFromPoint(e.clientX ?? 0, e.clientY ?? 0, editor.getDoc());
33974    const isPlainTextFileUrl = (content) => {
33975        const plainTextContent = content['text/plain'];
33976        return plainTextContent ? plainTextContent.indexOf('file://') === 0 : false;
33977    };
33978    const setFocusedRange = (editor, rng) => {
33979        if (rng) {
33980            editor.selection.setRng(rng);
33981        }
33982        editor.focus();
33983    };
33984    const hasImage = (dataTransfer) => exists(dataTransfer.files, (file) => /^image\//.test(file.type));
33985    const needsCustomInternalDrop = (dom, schema, target, dropContent) => {
33986        const parentTransparent = dom.getParent(target, (node) => isTransparentBlock(schema, node));
33987        const inSummary = !isNull(dom.getParent(target, 'summary'));
33988        if (inSummary) {
33989            return true;
33990        }
33991        else if (parentTransparent && has$2(dropContent, 'text/html')) {
33992            const fragment = new DOMParser().parseFromString(dropContent['text/html'], 'text/html').body;
33993            return !isNull(fragment.querySelector(parentTransparent.nodeName.toLowerCase()));
33994        }
33995        else {
33996            return false;
33997        }
33998    };
33999    const setupSummaryDeleteByDragFix = (editor) => {
34000        editor.on('input', (e) => {
34001            const hasNoSummary = (el) => isNull(el.querySelector('summary'));
34002            if (e.inputType === 'deleteByDrag') {
34003                const brokenDetailElements = filter$5(editor.dom.select('details'), hasNoSummary);
34004                each$e(brokenDetailElements, (details) => {
34005                    // Firefox leaves a BR
34006                    if (isBr$7(details.firstChild)) {
34007                        details.firstChild.remove();
34008                    }
34009                    const summary = editor.dom.create('summary');
34010                    summary.appendChild(createPaddingBr().dom);
34011                    details.prepend(summary);
34012                });
34013            }
34014        });
34015    };
34016    const setup$a = (editor, draggingInternallyState) => {
34017        // Block all drag/drop events
34018        if (shouldPasteBlockDrop(editor)) {
34019            editor.on('dragend dragover draggesture dragdrop drop drag', (e) => {
34020                e.preventDefault();
34021                e.stopPropagation();
34022            });
34023        }
34024        // Prevent users from dropping data images on Gecko
34025        if (!shouldPasteDataImages(editor)) {
34026            editor.on('drop', (e) => {
34027                const dataTransfer = e.dataTransfer;
34028                if (dataTransfer && hasImage(dataTransfer)) {
34029                    e.preventDefault();
34030                }
34031            });
34032        }
34033        editor.on('drop', (e) => {
34034            if (e.isDefaultPrevented()) {
34035                return;
34036            }
34037            const rng = getCaretRangeFromEvent(editor, e);
34038            if (isNullable(rng)) {
34039                return;
34040            }
34041            const dropContent = getDataTransferItems(e.dataTransfer);
34042            const internal = hasContentType(dropContent, internalHtmlMime());
34043            if ((!hasHtmlOrText(dropContent) || isPlainTextFileUrl(dropContent)) && pasteImageData(editor, e, rng)) {
34044                return;
34045            }
34046            const internalContent = dropContent[internalHtmlMime()];
34047            const content = internalContent || dropContent['text/html'] || dropContent['text/plain'];
34048            const needsInternalDrop = needsCustomInternalDrop(editor.dom, editor.schema, rng.startContainer, dropContent);
34049            const isInternalDrop = draggingInternallyState.get();
34050            if (isInternalDrop && !needsInternalDrop) {
34051                return;
34052            }
34053            if (content) {
34054                e.preventDefault();
34055                // FF 45 doesn't paint a caret when dragging in text in due to focus call by execCommand
34056                Delay.setEditorTimeout(editor, () => {
34057                    editor.undoManager.transact(() => {
34058                        if (internalContent || (isInternalDrop && needsInternalDrop)) {
34059                            editor.execCommand('Delete');
34060                        }
34061                        setFocusedRange(editor, rng);
34062                        const trimmedContent = trimHtml(content);
34063                        if (dropContent['text/html']) {
34064                            pasteHtml(editor, trimmedContent, internal, true);
34065                        }
34066                        else {
34067                            pasteText(editor, trimmedContent, true);
34068                        }
34069                    });
34070                });
34071            }
34072        });
34073        editor.on('dragstart', (_e) => {
34074            draggingInternallyState.set(true);
34075        });
34076        editor.on('dragover dragend', (e) => {
34077            if (shouldPasteDataImages(editor) && !draggingInternallyState.get()) {
34078                e.preventDefault();
34079                setFocusedRange(editor, getCaretRangeFromEvent(editor, e));
34080            }
34081            if (e.type === 'dragend') {
34082                draggingInternallyState.set(false);
34083            }
34084        });
34085        setupSummaryDeleteByDragFix(editor);
34086    };
34087
34088    const setup$9 = (editor) => {
34089        const processEvent = (f) => (e) => {
34090            f(editor, e);
34091        };
34092        const preProcess = getPastePreProcess(editor);
34093        if (isFunction(preProcess)) {
34094            editor.on('PastePreProcess', processEvent(preProcess));
34095        }
34096        const postProcess = getPastePostProcess(editor);
34097        if (isFunction(postProcess)) {
34098            editor.on('PastePostProcess', processEvent(postProcess));
34099        }
34100    };
34101
34102    /*
34103     * This module contains various fixes for browsers. These issues can not be feature
34104     * detected since we have no direct control over the clipboard. However we might be able
34105     * to remove some of these fixes once the browsers gets updated/fixed.
34106     */
34107    const addPreProcessFilter = (editor, filterFunc) => {
34108        editor.on('PastePreProcess', (e) => {
34109            e.content = filterFunc(editor, e.content, e.internal);
34110        });
34111    };
34112    const rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi;
34113    const rgbToHex = (value) => Tools.trim(value).replace(rgbRegExp, rgbaToHexString).toLowerCase();
34114    /*
34115     * WebKit has a nasty quirk where the all computed styles gets added to style attributes when copy/pasting contents.
34116     * This fix solves that by simply removing the whole style attribute.
34117     *
34118     * The paste_webkit_styles option can be set to specify what to keep:
34119     *  paste_webkit_styles: "none" // Keep no styles
34120     *  paste_webkit_styles: "all", // Keep all of them
34121     *  paste_webkit_styles: "font-weight color" // Keep specific ones
34122     */
34123    const removeWebKitStyles = (editor, content, internal) => {
34124        const webKitStylesOption = getPasteWebkitStyles(editor);
34125        // If the content is internal or if we're keeping all styles then we don't need any processing
34126        if (internal || webKitStylesOption === 'all' || !shouldPasteRemoveWebKitStyles(editor)) {
34127            return content;
34128        }
34129        const webKitStyles = webKitStylesOption ? webKitStylesOption.split(/[, ]/) : [];
34130        // Keep specific styles that don't match the current node computed style
34131        if (webKitStyles && webKitStylesOption !== 'none') {
34132            const dom = editor.dom, node = editor.selection.getNode();
34133            content = content.replace(/(<[^>]+) style="([^"]*)"([^>]*>)/gi, (all, before, value, after) => {
34134                const inputStyles = dom.parseStyle(dom.decode(value));
34135                const outputStyles = {};
34136                for (let i = 0; i < webKitStyles.length; i++) {
34137                    const inputValue = inputStyles[webKitStyles[i]];
34138                    let compareInput = inputValue;
34139                    let currentValue = dom.getStyle(node, webKitStyles[i], true);
34140                    if (/color/.test(webKitStyles[i])) {
34141                        compareInput = rgbToHex(compareInput);
34142                        currentValue = rgbToHex(currentValue);
34143                    }
34144                    if (currentValue !== compareInput) {
34145                        outputStyles[webKitStyles[i]] = inputValue;
34146                    }
34147                }
34148                const outputStyle = dom.serializeStyle(outputStyles, 'span');
34149                if (outputStyle) {
34150                    return before + ' style="' + outputStyle + '"' + after;
34151                }
34152                return before + after;
34153            });
34154        }
34155        else {
34156            // Remove all external styles
34157            content = content.replace(/(<[^>]+) style="([^"]*)"([^>]*>)/gi, '$1$3');
34158        }
34159        // Keep internal styles
34160        content = content.replace(/(<[^>]+) data-mce-style="([^"]+)"([^>]*>)/gi, (all, before, value, after) => {
34161            return before + ' style="' + value + '"' + after;
34162        });
34163        return content;
34164    };
34165    const setup$8 = (editor) => {
34166        if (Env.browser.isChromium() || Env.browser.isSafari()) {
34167            addPreProcessFilter(editor, removeWebKitStyles);
34168        }
34169    };
34170
34171    const setup$7 = (editor, caret) => {
34172        const draggingInternallyState = Cell(false);
34173        const pasteFormat = Cell(isPasteAsTextEnabled(editor) ? 'text' : 'html');
34174        const pasteBin = PasteBin(editor);
34175        setup$8(editor);
34176        register$1(editor, pasteFormat);
34177        setup$9(editor);
34178        editor.addQueryStateHandler('mceTogglePlainTextPaste', () => pasteFormat.get() === 'text');
34179        // IMPORTANT: The following event hooks need to be setup later so that other things
34180        // can hook in and prevent the event so core paste doesn't handle them.
34181        editor.on('PreInit', () => {
34182            register(editor, caret);
34183            setup$a(editor, draggingInternallyState);
34184            registerEventsAndFilters(editor, pasteBin, pasteFormat);
34185        });
34186    };
34187
34188    const preventSummaryToggle = (editor) => {
34189        editor.on('click', (e) => {
34190            if (editor.dom.getParent(e.target, 'details')) {
34191                e.preventDefault();
34192            }
34193        });
34194    };
34195    const filterDetails = (editor) => {
34196        editor.parser.addNodeFilter('details', (elms) => {
34197            const initialStateOption = getDetailsInitialState(editor);
34198            each$e(elms, (details) => {
34199                if (initialStateOption === 'expanded') {
34200                    details.attr('open', 'open');
34201                }
34202                else if (initialStateOption === 'collapsed') {
34203                    details.attr('open', null);
34204                }
34205            });
34206        });
34207        editor.serializer.addNodeFilter('details', (elms) => {
34208            const serializedStateOption = getDetailsSerializedState(editor);
34209            each$e(elms, (details) => {
34210                if (serializedStateOption === 'expanded') {
34211                    details.attr('open', 'open');
34212                }
34213                else if (serializedStateOption === 'collapsed') {
34214                    details.attr('open', null);
34215                }
34216            });
34217        });
34218    };
34219    const setup$6 = (editor) => {
34220        preventSummaryToggle(editor);
34221        filterDetails(editor);
34222    };
34223
34224    const isBr = isBr$7;
34225    const isText = isText$b;
34226    const isContentEditableFalse$2 = (elm) => isContentEditableFalse$a(elm.dom);
34227    const isContentEditableTrue = (elm) => isContentEditableTrue$3(elm.dom);
34228    const isRoot = (rootNode) => (elm) => eq(SugarElement.fromDom(rootNode), elm);
34229    const getClosestScope = (node, rootNode, schema) => closest$5(SugarElement.fromDom(node), (elm) => isContentEditableTrue(elm) || schema.isBlock(name(elm)), isRoot(rootNode))
34230        .getOr(SugarElement.fromDom(rootNode)).dom;
34231    const getClosestCef = (node, rootNode) => closest$5(SugarElement.fromDom(node), isContentEditableFalse$2, isRoot(rootNode));
34232    const findEdgeCaretCandidate = (startNode, scope, forward) => {
34233        const walker = new DomTreeWalker(startNode, scope);
34234        const next = forward ? walker.next.bind(walker) : walker.prev.bind(walker);
34235        let result = startNode;
34236        for (let current = forward ? startNode : next(); current && !isBr(current); current = next()) {
34237            if (isCaretCandidate$3(current)) {
34238                result = current;
34239            }
34240        }
34241        return result;
34242    };
34243    const findClosestBlockRange = (startRng, rootNode, schema) => {
34244        const startPos = CaretPosition.fromRangeStart(startRng);
34245        // TODO: TINY-8865 - This may not be safe to cast as Node and alternative solutions need to be looked into
34246        const clickNode = startPos.getNode();
34247        const scope = getClosestScope(clickNode, rootNode, schema);
34248        const startNode = findEdgeCaretCandidate(clickNode, scope, false);
34249        const endNode = findEdgeCaretCandidate(clickNode, scope, true);
34250        const rng = document.createRange();
34251        getClosestCef(startNode, scope).fold(() => {
34252            if (isText(startNode)) {
34253                rng.setStart(startNode, 0);
34254            }
34255            else {
34256                rng.setStartBefore(startNode);
34257            }
34258        }, (cef) => rng.setStartBefore(cef.dom));
34259        getClosestCef(endNode, scope).fold(() => {
34260            if (isText(endNode)) {
34261                rng.setEnd(endNode, endNode.data.length);
34262            }
34263            else {
34264                rng.setEndAfter(endNode);
34265            }
34266        }, (cef) => rng.setEndAfter(cef.dom));
34267        return rng;
34268    };
34269    const onTripleClickSelect = (editor) => {
34270        const rng = findClosestBlockRange(editor.selection.getRng(), editor.getBody(), editor.schema);
34271        editor.selection.setRng(normalize(rng));
34272    };
34273    const setup$5 = (editor) => {
34274        editor.on('mousedown', (e) => {
34275            if (e.detail >= 3) {
34276                e.preventDefault();
34277                onTripleClickSelect(editor);
34278            }
34279        });
34280    };
34281
34282    var FakeCaretPosition;
34283    (function (FakeCaretPosition) {
34284        FakeCaretPosition["Before"] = "before";
34285        FakeCaretPosition["After"] = "after";
34286    })(FakeCaretPosition || (FakeCaretPosition = {}));
34287    const distanceToRectLeft = (clientRect, clientX) => Math.abs(clientRect.left - clientX);
34288    const distanceToRectRight = (clientRect, clientX) => Math.abs(clientRect.right - clientX);
34289    const isInsideY = (clientY, clientRect) => clientY >= clientRect.top && clientY <= clientRect.bottom;
34290    const collidesY = (r1, r2) => r1.top < r2.bottom && r1.bottom > r2.top;
34291    const isOverlapping = (r1, r2) => {
34292        // Rectangles might overlap a bit so this checks if the overlap is more than 50% then we count that as on the same line
34293        const overlap = overlapY(r1, r2) / Math.min(r1.height, r2.height);
34294        return collidesY(r1, r2) && overlap > 0.5;
34295    };
34296    const splitRectsPerAxis = (rects, y) => {
34297        const intersectingRects = filter$5(rects, (rect) => isInsideY(y, rect));
34298        return boundingClientRectFromRects(intersectingRects).fold(() => ([[], rects]), (boundingRect) => {
34299            const { pass: horizontal, fail: vertical } = partition$2(rects, (rect) => isOverlapping(rect, boundingRect));
34300            return [horizontal, vertical];
34301        });
34302    };
34303    const clientInfo = (rect, clientX) => {
34304        return {
34305            node: rect.node,
34306            position: distanceToRectLeft(rect, clientX) < distanceToRectRight(rect, clientX) ? FakeCaretPosition.Before : FakeCaretPosition.After
34307        };
34308    };
34309    // Measure the distance between the x and the closest edge of the rect.
34310    // If the x is inside the rect then always return 0.
34311    const horizontalDistance = (rect, x, _y) => x > rect.left && x < rect.right ? 0 : Math.min(Math.abs(rect.left - x), Math.abs(rect.right - x));
34312    const closestChildCaretCandidateNodeRect = (children, clientX, clientY, findCloserTextNode) => {
34313        const caretCandidateRect = (rect) => {
34314            if (isCaretCandidate$3(rect.node)) {
34315                return Optional.some(rect);
34316            }
34317            else if (isElement$7(rect.node)) {
34318                return closestChildCaretCandidateNodeRect(from(rect.node.childNodes), clientX, clientY, false);
34319            }
34320            else {
34321                return Optional.none();
34322            }
34323        };
34324        // If an element and a text node has nearly equal distance then favor the text node over the element to make it easier to select text
34325        // since setting the selection range will cancel any text select operation.
34326        const tryFindSecondBestTextNode = (closest, sndClosest, distance) => {
34327            return caretCandidateRect(sndClosest).filter((rect) => {
34328                const deltaDistance = Math.abs(distance(closest, clientX, clientY) - distance(rect, clientX, clientY));
34329                return deltaDistance < 2 && isText$b(rect.node);
34330            });
34331        };
34332        const findClosestCaretCandidateNodeRect = (rects, distance) => {
34333            const sortedRects = sort(rects, (r1, r2) => distance(r1, clientX, clientY) - distance(r2, clientX, clientY));
34334            return findMap(sortedRects, caretCandidateRect).map((closest) => {
34335                // If the closest rect is not a text node then lets try to see if the second rect has a text node that is close enough
34336                if (findCloserTextNode && !isText$b(closest.node) && sortedRects.length > 1) {
34337                    return tryFindSecondBestTextNode(closest, sortedRects[1], distance).getOr(closest);
34338                }
34339                else {
34340                    return closest;
34341                }
34342            });
34343        };
34344        const [horizontalRects, verticalRects] = splitRectsPerAxis(getClientRects(children), clientY);
34345        const { pass: above, fail: below } = partition$2(verticalRects, (rect) => rect.top < clientY);
34346        return findClosestCaretCandidateNodeRect(horizontalRects, horizontalDistance)
34347            .orThunk(() => findClosestCaretCandidateNodeRect(below, distanceToRectEdgeFromXY))
34348            .orThunk(() => findClosestCaretCandidateNodeRect(above, distanceToRectEdgeFromXY));
34349    };
34350    const traverseUp = (rootElm, scope, clientX, clientY) => {
34351        const helper = (scope, prevScope) => {
34352            const isDragGhostContainer = (node) => isElement$7(node) && node.classList.contains('mce-drag-container');
34353            const childNodesWithoutGhost = filter$5(scope.dom.childNodes, not(isDragGhostContainer));
34354            return prevScope.fold(() => closestChildCaretCandidateNodeRect(childNodesWithoutGhost, clientX, clientY, true), (prevScope) => {
34355                const uncheckedChildren = filter$5(childNodesWithoutGhost, (node) => node !== prevScope.dom);
34356                return closestChildCaretCandidateNodeRect(uncheckedChildren, clientX, clientY, true);
34357            }).orThunk(() => {
34358                const parent = eq(scope, rootElm) ? Optional.none() : parentElement(scope);
34359                return parent.bind((newScope) => helper(newScope, Optional.some(scope)));
34360            });
34361        };
34362        return helper(scope, Optional.none());
34363    };
34364    // Rough description of how this algorithm works:
34365    // 1. It starts by finding the element at the specified X, Y coordinate.
34366    // 2. Then it checks its children for the closest one and traverses down into those repeating step 2, 3 until it finds a caret candidate.
34367    // 3. If no caret candidate is found in the closest child node then it checks the second closest and so on until all decendants have been checked.
34368    // 4. If no caret candidate is found, it traverses up skips the element it already checked and checks its siblings using steps 2, 3.
34369    // 5. If no caret candidate is found, it continues step 4 until it finds the root. Then we have checked all the nodes in the document.
34370    //
34371    // This is less accurate but more performant, since for the common case you are likely to find a caret candidate close to where you are clicking.
34372    // The more accurate algorithm would be to read all caret candidates rects in the whole document in and in one big step to find the closest one, but that is just too slow for bigger documents.
34373    const closestCaretCandidateNodeRect = (root, clientX, clientY) => {
34374        const rootElm = SugarElement.fromDom(root);
34375        const ownerDoc = documentOrOwner(rootElm);
34376        const elementAtPoint = SugarElement.fromPoint(ownerDoc, clientX, clientY).filter((elm) => contains(rootElm, elm));
34377        const element = elementAtPoint.getOr(rootElm);
34378        return traverseUp(rootElm, element, clientX, clientY);
34379    };
34380    const closestFakeCaretCandidate = (root, clientX, clientY) => closestCaretCandidateNodeRect(root, clientX, clientY)
34381        .filter((rect) => isFakeCaretTarget(rect.node))
34382        .map((rect) => clientInfo(rect, clientX));
34383
34384    const getAbsolutePosition = (elm) => {
34385        const clientRect = elm.getBoundingClientRect();
34386        const doc = elm.ownerDocument;
34387        const docElem = doc.documentElement;
34388        const win = doc.defaultView;
34389        return {
34390            top: clientRect.top + (win?.scrollY ?? 0) - docElem.clientTop,
34391            left: clientRect.left + (win?.scrollX ?? 0) - docElem.clientLeft
34392        };
34393    };
34394    const getBodyPosition = (editor) => editor.inline ? getAbsolutePosition(editor.getBody()) : { left: 0, top: 0 };
34395    const getScrollPosition = (editor) => {
34396        const body = editor.getBody();
34397        return editor.inline ? { left: body.scrollLeft, top: body.scrollTop } : { left: 0, top: 0 };
34398    };
34399    const getBodyScroll = (editor) => {
34400        const body = editor.getBody(), docElm = editor.getDoc().documentElement;
34401        const inlineScroll = { left: body.scrollLeft, top: body.scrollTop };
34402        const iframeScroll = { left: body.scrollLeft || docElm.scrollLeft, top: body.scrollTop || docElm.scrollTop };
34403        return editor.inline ? inlineScroll : iframeScroll;
34404    };
34405    const getMousePosition = (editor, event) => {
34406        if (event.target.ownerDocument !== editor.getDoc()) {
34407            const iframePosition = getAbsolutePosition(editor.getContentAreaContainer());
34408            const scrollPosition = getBodyScroll(editor);
34409            return {
34410                left: event.pageX - iframePosition.left + scrollPosition.left,
34411                top: event.pageY - iframePosition.top + scrollPosition.top
34412            };
34413        }
34414        return {
34415            left: event.pageX,
34416            top: event.pageY
34417        };
34418    };
34419    const calculatePosition = (bodyPosition, scrollPosition, mousePosition) => ({
34420        pageX: (mousePosition.left - bodyPosition.left) + scrollPosition.left,
34421        pageY: (mousePosition.top - bodyPosition.top) + scrollPosition.top
34422    });
34423    const calc = (editor, event) => calculatePosition(getBodyPosition(editor), getScrollPosition(editor), getMousePosition(editor, event));
34424
34425    const getTargetProps = (target) => ({ target, srcElement: target });
34426    const makeDndEventFromMouseEvent = (type, mouseEvent, target, dataTransfer) => ({
34427        ...mouseEvent,
34428        dataTransfer,
34429        type,
34430        ...getTargetProps(target)
34431    });
34432    const makeDndEvent = (type, target, dataTransfer) => {
34433        const fail = die('Function not supported on simulated event.');
34434        const event = {
34435            // Event
34436            bubbles: true,
34437            cancelBubble: false,
34438            cancelable: true,
34439            composed: false,
34440            currentTarget: null,
34441            defaultPrevented: false,
34442            eventPhase: 0,
34443            isTrusted: true,
34444            returnValue: false,
34445            timeStamp: 0,
34446            type,
34447            composedPath: fail,
34448            initEvent: fail,
34449            preventDefault: noop,
34450            stopImmediatePropagation: noop,
34451            stopPropagation: noop,
34452            AT_TARGET: window.Event.AT_TARGET,
34453            BUBBLING_PHASE: window.Event.BUBBLING_PHASE,
34454            CAPTURING_PHASE: window.Event.CAPTURING_PHASE,
34455            NONE: window.Event.NONE,
34456            // UIEvent
34457            altKey: false,
34458            button: 0,
34459            buttons: 0,
34460            clientX: 0,
34461            clientY: 0,
34462            ctrlKey: false,
34463            layerX: 0,
34464            layerY: 0,
34465            metaKey: false,
34466            movementX: 0,
34467            movementY: 0,
34468            offsetX: 0,
34469            offsetY: 0,
34470            pageX: 0,
34471            pageY: 0,
34472            relatedTarget: null,
34473            screenX: 0,
34474            screenY: 0,
34475            shiftKey: false,
34476            x: 0,
34477            y: 0,
34478            detail: 0,
34479            view: null,
34480            which: 0,
34481            initUIEvent: fail,
34482            initMouseEvent: fail,
34483            getModifierState: fail,
34484            // DragEvent
34485            dataTransfer,
34486            ...getTargetProps(target)
34487        };
34488        return event;
34489    };
34490    const makeDataTransferCopyForDragEvent = (dataTransfer, eventType) => {
34491        const copy = cloneDataTransfer(dataTransfer);
34492        // TINY-9601: Set mode as per https://html.spec.whatwg.org/dev/dnd.html#concept-dnd-rw
34493        if (eventType === 'dragstart') {
34494            setDragstartEvent(copy);
34495            setReadWriteMode(copy);
34496        }
34497        else if (eventType === 'drop') {
34498            setDropEvent(copy);
34499            setReadOnlyMode(copy);
34500        }
34501        else {
34502            setDragendEvent(copy);
34503            setProtectedMode(copy);
34504        }
34505        return copy;
34506    };
34507    const makeDragEvent = (type, target, dataTransfer, mouseEvent) => {
34508        // TINY-9601: Get copy for each new event to prevent undesired mutations on dispatched DataTransfer objects
34509        const dataTransferForDispatch = makeDataTransferCopyForDragEvent(dataTransfer, type);
34510        return isUndefined(mouseEvent) ? makeDndEvent(type, target, dataTransferForDispatch) : makeDndEventFromMouseEvent(type, mouseEvent, target, dataTransferForDispatch);
34511    };
34512
34513    /**
34514     * This module contains logic overriding the drag/drop logic of the editor.
34515     *
34516     * @private
34517     * @class tinymce.DragDropOverrides
34518     */
34519    // Arbitrary values needed when scrolling CEF elements
34520    const scrollPixelsPerInterval = 32;
34521    const scrollIntervalValue = 100;
34522    const mouseRangeToTriggerScrollInsideEditor = 8;
34523    const mouseRangeToTriggerScrollOutsideEditor = 16;
34524    const isContentEditableFalse$1 = isContentEditableFalse$a;
34525    const isContentEditable = or(isContentEditableFalse$1, isContentEditableTrue$3);
34526    const isDraggable = (dom, rootElm, elm) => isContentEditableFalse$1(elm) && elm !== rootElm && dom.isEditable(elm.parentElement);
34527    const isValidDropTarget = (editor, targetElement, dragElement) => {
34528        if (isNullable(targetElement)) {
34529            return false;
34530        }
34531        else if (targetElement === dragElement || editor.dom.isChildOf(targetElement, dragElement)) {
34532            return false;
34533        }
34534        else {
34535            return editor.dom.isEditable(targetElement);
34536        }
34537    };
34538    const createGhost = (editor, elm, width, height) => {
34539        const dom = editor.dom;
34540        const clonedElm = elm.cloneNode(true);
34541        dom.setStyles(clonedElm, { width, height });
34542        dom.setAttrib(clonedElm, 'data-mce-selected', null);
34543        const ghostElm = dom.create('div', {
34544            'class': 'mce-drag-container',
34545            'data-mce-bogus': 'all',
34546            'unselectable': 'on',
34547            'contenteditable': 'false'
34548        });
34549        dom.setStyles(ghostElm, {
34550            position: 'absolute',
34551            opacity: 0.5,
34552            overflow: 'hidden',
34553            border: 0,
34554            padding: 0,
34555            margin: 0,
34556            width,
34557            height
34558        });
34559        dom.setStyles(clonedElm, {
34560            margin: 0,
34561            boxSizing: 'border-box'
34562        });
34563        ghostElm.appendChild(clonedElm);
34564        return ghostElm;
34565    };
34566    const appendGhostToBody = (ghostElm, bodyElm) => {
34567        if (ghostElm.parentNode !== bodyElm) {
34568            bodyElm.appendChild(ghostElm);
34569        }
34570    };
34571    // Helper function needed for scrolling the editor inside moveGhost function
34572    const scrollEditor = (direction, amount) => (win) => () => {
34573        const current = direction === 'left' ? win.scrollX : win.scrollY;
34574        win.scroll({
34575            [direction]: current + amount,
34576            behavior: 'smooth',
34577        });
34578    };
34579    const scrollLeft = scrollEditor('left', -scrollPixelsPerInterval);
34580    const scrollRight = scrollEditor('left', scrollPixelsPerInterval);
34581    const scrollUp = scrollEditor('top', -scrollPixelsPerInterval);
34582    const scrollDown = scrollEditor('top', scrollPixelsPerInterval);
34583    const moveGhost = (ghostElm, position, width, height, maxX, maxY, mouseY, mouseX, contentAreaContainer, win, state, mouseEventOriginatedFromWithinTheEditor) => {
34584        let overflowX = 0, overflowY = 0;
34585        ghostElm.style.left = position.pageX + 'px';
34586        ghostElm.style.top = position.pageY + 'px';
34587        if (position.pageX + width > maxX) {
34588            overflowX = (position.pageX + width) - maxX;
34589        }
34590        if (position.pageY + height > maxY) {
34591            overflowY = (position.pageY + height) - maxY;
34592        }
34593        ghostElm.style.width = (width - overflowX) + 'px';
34594        ghostElm.style.height = (height - overflowY) + 'px';
34595        // Code needed for dragging CEF elements (specifically fixing TINY-8874)
34596        // The idea behind the algorithm is that the user will start dragging the
34597        // CEF element to the edge of the editor and that would cause scrolling.
34598        // The way that happens is that the user will trigger a mousedown event,
34599        // then a mousemove event until they reach the edge of the editor. Then
34600        // no event triggers. That's when I set an interval to keep scrolling the editor.
34601        // Once a new event triggers I clear the existing interval and set it back to none.
34602        const clientHeight = contentAreaContainer.clientHeight;
34603        const clientWidth = contentAreaContainer.clientWidth;
34604        const outerMouseY = mouseY + contentAreaContainer.getBoundingClientRect().top;
34605        const outerMouseX = mouseX + contentAreaContainer.getBoundingClientRect().left;
34606        state.on((state) => {
34607            state.intervalId.clear();
34608            if (state.dragging && mouseEventOriginatedFromWithinTheEditor) {
34609                // This basically means that the mouse is close to the bottom edge
34610                // (within MouseRange pixels of the bottom edge)
34611                if (mouseY + mouseRangeToTriggerScrollInsideEditor >= clientHeight) {
34612                    state.intervalId.set(scrollDown(win));
34613                    // This basically means that the mouse is close to the top edge
34614                    // (within MouseRange pixels)
34615                }
34616                else if (mouseY - mouseRangeToTriggerScrollInsideEditor <= 0) {
34617                    state.intervalId.set(scrollUp(win));
34618                    // This basically means that the mouse is close to the right edge
34619                    // (within MouseRange pixels of the right edge)
34620                }
34621                else if (mouseX + mouseRangeToTriggerScrollInsideEditor >= clientWidth) {
34622                    state.intervalId.set(scrollRight(win));
34623                    // This basically means that the mouse is close to the left edge
34624                    // (within MouseRange pixels of the left edge)
34625                }
34626                else if (mouseX - mouseRangeToTriggerScrollInsideEditor <= 0) {
34627                    state.intervalId.set(scrollLeft(win));
34628                    // This basically means that the mouse is close to the bottom edge
34629                    // of the page (within MouseRange pixels) when the bottom of
34630                    // the editor is offscreen
34631                }
34632                else if (outerMouseY + mouseRangeToTriggerScrollOutsideEditor >= window.innerHeight) {
34633                    state.intervalId.set(scrollDown(window));
34634                    // This basically means that the mouse is close to the upper edge
34635                    // of the page (within MouseRange pixels) when the top of
34636                    // the editor is offscreen
34637                }
34638                else if (outerMouseY - mouseRangeToTriggerScrollOutsideEditor <= 0) {
34639                    state.intervalId.set(scrollUp(window));
34640                    // This basically means that the mouse is close to the right edge
34641                    // of the page (within MouseRange pixels) when the right edge of
34642                    // the editor is offscreen
34643                }
34644                else if (outerMouseX + mouseRangeToTriggerScrollOutsideEditor >= window.innerWidth) {
34645                    state.intervalId.set(scrollRight(window));
34646                    // This basically means that the mouse is close to the left edge
34647                    // of the page (within MouseRange pixels) when the left edge of
34648                    // the editor is offscreen
34649                }
34650                else if (outerMouseX - mouseRangeToTriggerScrollOutsideEditor <= 0) {
34651                    state.intervalId.set(scrollLeft(window));
34652                }
34653            }
34654        });
34655    };
34656    const removeElement = (elm) => {
34657        if (elm && elm.parentNode) {
34658            elm.parentNode.removeChild(elm);
34659        }
34660    };
34661    const removeElementWithPadding = (dom, elm) => {
34662        const parentBlock = dom.getParent(elm.parentNode, dom.isBlock);
34663        removeElement(elm);
34664        if (parentBlock && parentBlock !== dom.getRoot() && dom.isEmpty(parentBlock)) {
34665            fillWithPaddingBr(SugarElement.fromDom(parentBlock));
34666        }
34667    };
34668    const isLeftMouseButtonPressed = (e) => e.button === 0;
34669    const applyRelPos = (state, position) => ({
34670        pageX: position.pageX - state.relX,
34671        pageY: position.pageY + 5
34672    });
34673    const start = (state, editor) => (e) => {
34674        if (isLeftMouseButtonPressed(e)) {
34675            const ceElm = find$2(editor.dom.getParents(e.target), isContentEditable).getOr(null);
34676            if (isNonNullable(ceElm) && isDraggable(editor.dom, editor.getBody(), ceElm)) {
34677                const elmPos = editor.dom.getPos(ceElm);
34678                const bodyElm = editor.getBody();
34679                const docElm = editor.getDoc().documentElement;
34680                state.set({
34681                    element: ceElm,
34682                    dataTransfer: createDataTransfer(),
34683                    dragging: false,
34684                    screenX: e.screenX,
34685                    screenY: e.screenY,
34686                    maxX: (editor.inline ? bodyElm.scrollWidth : docElm.offsetWidth) - 2,
34687                    maxY: (editor.inline ? bodyElm.scrollHeight : docElm.offsetHeight) - 2,
34688                    relX: e.pageX - elmPos.x,
34689                    relY: e.pageY - elmPos.y,
34690                    width: ceElm.offsetWidth,
34691                    height: ceElm.offsetHeight,
34692                    ghost: createGhost(editor, ceElm, ceElm.offsetWidth, ceElm.offsetHeight),
34693                    intervalId: repeatable(scrollIntervalValue)
34694                });
34695            }
34696        }
34697    };
34698    const placeCaretAt = (editor, clientX, clientY) => {
34699        editor._selectionOverrides.hideFakeCaret();
34700        closestFakeCaretCandidate(editor.getBody(), clientX, clientY).fold(() => editor.selection.placeCaretAt(clientX, clientY), (caretInfo) => {
34701            const range = editor._selectionOverrides.showCaret(1, caretInfo.node, caretInfo.position === FakeCaretPosition.Before, false);
34702            if (range) {
34703                editor.selection.setRng(range);
34704            }
34705            else {
34706                editor.selection.placeCaretAt(clientX, clientY);
34707            }
34708        });
34709    };
34710    const dispatchDragEvent = (editor, type, target, dataTransfer, mouseEvent) => {
34711        if (type === 'dragstart') {
34712            setHtmlData(dataTransfer, editor.dom.getOuterHTML(target));
34713        }
34714        const event = makeDragEvent(type, target, dataTransfer, mouseEvent);
34715        const args = editor.dispatch(type, event);
34716        return args;
34717    };
34718    const move = (state, editor) => {
34719        // Reduces laggy drag behavior on Gecko
34720        const throttledPlaceCaretAt = first$1((clientX, clientY) => placeCaretAt(editor, clientX, clientY), 0);
34721        editor.on('remove', throttledPlaceCaretAt.cancel);
34722        const state_ = state;
34723        return (e) => state.on((state) => {
34724            const movement = Math.max(Math.abs(e.screenX - state.screenX), Math.abs(e.screenY - state.screenY));
34725            if (!state.dragging && movement > 10) {
34726                const args = dispatchDragEvent(editor, 'dragstart', state.element, state.dataTransfer, e);
34727                // TINY-9601: dataTransfer is writable in dragstart, so keep it up-to-date
34728                if (isNonNullable(args.dataTransfer)) {
34729                    state.dataTransfer = args.dataTransfer;
34730                }
34731                if (args.isDefaultPrevented()) {
34732                    return;
34733                }
34734                state.dragging = true;
34735                editor.focus();
34736            }
34737            if (state.dragging) {
34738                const mouseEventOriginatedFromWithinTheEditor = e.currentTarget === editor.getDoc().documentElement;
34739                const targetPos = applyRelPos(state, calc(editor, e));
34740                appendGhostToBody(state.ghost, editor.getBody());
34741                moveGhost(state.ghost, targetPos, state.width, state.height, state.maxX, state.maxY, e.clientY, e.clientX, editor.getContentAreaContainer(), editor.getWin(), state_, mouseEventOriginatedFromWithinTheEditor);
34742                throttledPlaceCaretAt.throttle(e.clientX, e.clientY);
34743            }
34744        });
34745    };
34746    // Returns the raw element instead of the fake cE=false element
34747    const getRawTarget = (selection) => {
34748        const sel = selection.getSel();
34749        if (isNonNullable(sel)) {
34750            const rng = sel.getRangeAt(0);
34751            const startContainer = rng.startContainer;
34752            return isText$b(startContainer) ? startContainer.parentNode : startContainer;
34753        }
34754        else {
34755            return null;
34756        }
34757    };
34758    const drop = (state, editor) => (e) => {
34759        state.on((state) => {
34760            state.intervalId.clear();
34761            if (state.dragging) {
34762                if (isValidDropTarget(editor, getRawTarget(editor.selection), state.element)) {
34763                    const dropTarget = editor.getDoc().elementFromPoint(e.clientX, e.clientY) ?? editor.getBody();
34764                    const args = dispatchDragEvent(editor, 'drop', dropTarget, state.dataTransfer, e);
34765                    if (!args.isDefaultPrevented()) {
34766                        editor.undoManager.transact(() => {
34767                            removeElementWithPadding(editor.dom, state.element);
34768                            // TINY-9601: Use dataTransfer property to determine inserted content on drop. This allows users to
34769                            // manipulate drop content by modifying dataTransfer in the dragstart event.
34770                            getHtmlData(state.dataTransfer).each((content) => editor.insertContent(content));
34771                            editor._selectionOverrides.hideFakeCaret();
34772                        });
34773                    }
34774                }
34775                // Use body as the target since the element we are dragging no longer exists. Native drag/drop works in a similar way.
34776                dispatchDragEvent(editor, 'dragend', editor.getBody(), state.dataTransfer, e);
34777            }
34778        });
34779        removeDragState(state);
34780    };
34781    const stopDragging = (state, editor, e) => {
34782        state.on((state) => {
34783            state.intervalId.clear();
34784            if (state.dragging) {
34785                e.fold(() => dispatchDragEvent(editor, 'dragend', state.element, state.dataTransfer), (mouseEvent) => dispatchDragEvent(editor, 'dragend', state.element, state.dataTransfer, mouseEvent));
34786            }
34787        });
34788        removeDragState(state);
34789    };
34790    const stop = (state, editor) => (e) => stopDragging(state, editor, Optional.some(e));
34791    const removeDragState = (state) => {
34792        state.on((state) => {
34793            state.intervalId.clear();
34794            removeElement(state.ghost);
34795        });
34796        state.clear();
34797    };
34798    const bindFakeDragEvents = (editor) => {
34799        const state = value$1();
34800        const pageDom = DOMUtils.DOM;
34801        const rootDocument = document;
34802        const dragStartHandler = start(state, editor);
34803        const dragHandler = move(state, editor);
34804        const dropHandler = drop(state, editor);
34805        const dragEndHandler = stop(state, editor);
34806        editor.on('mousedown', dragStartHandler);
34807        editor.on('mousemove', dragHandler);
34808        editor.on('mouseup', dropHandler);
34809        pageDom.bind(rootDocument, 'mousemove', dragHandler);
34810        pageDom.bind(rootDocument, 'mouseup', dragEndHandler);
34811        editor.on('remove', () => {
34812            pageDom.unbind(rootDocument, 'mousemove', dragHandler);
34813            pageDom.unbind(rootDocument, 'mouseup', dragEndHandler);
34814        });
34815        editor.on('keydown', (e) => {
34816            // Fire 'dragend' when the escape key is pressed
34817            if (e.keyCode === VK.ESC) {
34818                stopDragging(state, editor, Optional.none());
34819            }
34820        });
34821    };
34822    // Block files being dropped within the editor to prevent accidentally navigating away
34823    // while editing. Note that we can't use the `editor.on` API here, as we want these
34824    // to run after the editor event handlers have run. We also bind to the document
34825    // so that it'll try to ensure it's the last thing that runs, as it bubbles up the dom.
34826    const blockUnsupportedFileDrop = (editor) => {
34827        const preventFileDrop = (e) => {
34828            if (!e.isDefaultPrevented()) {
34829                // Prevent file drop events within the editor, as they'll cause the browser to navigate away
34830                const dataTransfer = e.dataTransfer;
34831                if (dataTransfer && (contains$2(dataTransfer.types, 'Files') || dataTransfer.files.length > 0)) {
34832                    e.preventDefault();
34833                    if (e.type === 'drop') {
34834                        displayError(editor, 'Dropped file type is not supported');
34835                    }
34836                }
34837            }
34838        };
34839        const preventFileDropIfUIElement = (e) => {
34840            if (isUIElement(editor, e.target)) {
34841                preventFileDrop(e);
34842            }
34843        };
34844        const setup = () => {
34845            const pageDom = DOMUtils.DOM;
34846            const dom = editor.dom;
34847            const doc = document;
34848            const editorRoot = editor.inline ? editor.getBody() : editor.getDoc();
34849            const eventNames = ['drop', 'dragover'];
34850            each$e(eventNames, (name) => {
34851                pageDom.bind(doc, name, preventFileDropIfUIElement);
34852                dom.bind(editorRoot, name, preventFileDrop);
34853            });
34854            editor.on('remove', () => {
34855                each$e(eventNames, (name) => {
34856                    pageDom.unbind(doc, name, preventFileDropIfUIElement);
34857                    dom.unbind(editorRoot, name, preventFileDrop);
34858                });
34859            });
34860        };
34861        editor.on('init', () => {
34862            // Use a timeout to ensure this fires after all other init callbacks
34863            Delay.setEditorTimeout(editor, setup, 0);
34864        });
34865    };
34866    const init$2 = (editor) => {
34867        bindFakeDragEvents(editor);
34868        if (shouldBlockUnsupportedDrop(editor)) {
34869            blockUnsupportedFileDrop(editor);
34870        }
34871    };
34872
34873    const setup$4 = (editor) => {
34874        const renderFocusCaret = first$1(() => {
34875            // AP-24 Added the second condition in this if because of a race condition with setting focus on the PowerPaste
34876            // remove/keep formatting dialog on paste in IE11. Without this, because we paste twice on IE11, focus ends up set
34877            // in the editor, not the dialog buttons. Specifically, we focus, blur, focus, blur, focus then enter this throttled
34878            // code before the next blur has been able to run. With this check, this function doesn't run at all in this case,
34879            // so focus goes to the dialog's buttons correctly.
34880            if (!editor.removed && editor.getBody().contains(document.activeElement)) {
34881                const rng = editor.selection.getRng();
34882                if (rng.collapsed) { // see TINY-1479
34883                    const caretRange = renderRangeCaret(editor, rng, false);
34884                    editor.selection.setRng(caretRange);
34885                }
34886            }
34887        }, 0);
34888        editor.on('focus', () => {
34889            renderFocusCaret.throttle();
34890        });
34891        editor.on('blur', () => {
34892            renderFocusCaret.cancel();
34893        });
34894    };
34895
34896    const setup$3 = (editor) => {
34897        editor.on('init', () => {
34898            // Audio elements don't fire mousedown/click events and only fire a focus event so
34899            // we need to capture that event being fired and use it to update the selection.
34900            editor.on('focusin', (e) => {
34901                const target = e.target;
34902                if (isMedia$2(target)) {
34903                    const ceRoot = getContentEditableRoot$1(editor.getBody(), target);
34904                    const node = isContentEditableFalse$a(ceRoot) ? ceRoot : target;
34905                    if (editor.selection.getNode() !== node) {
34906                        selectNode(editor, node).each((rng) => editor.selection.setRng(rng));
34907                    }
34908                }
34909            });
34910        });
34911    };
34912
34913    const isContentEditableFalse = isContentEditableFalse$a;
34914    const getContentEditableRoot = (editor, node) => getContentEditableRoot$1(editor.getBody(), node);
34915    const SelectionOverrides = (editor) => {
34916        const selection = editor.selection, dom = editor.dom;
34917        const rootNode = editor.getBody();
34918        const fakeCaret = FakeCaret(editor, rootNode, dom.isBlock, () => hasFocus(editor));
34919        const realSelectionId = 'sel-' + dom.uniqueId();
34920        const elementSelectionAttr = 'data-mce-selected';
34921        let selectedElement;
34922        const isFakeSelectionElement = (node) => isNonNullable(node) && dom.hasClass(node, 'mce-offscreen-selection');
34923        // Note: isChildOf will return true if node === rootNode, so we need an additional check for that
34924        const isFakeSelectionTargetElement = (node) => node !== rootNode && (isContentEditableFalse(node) || isMedia$2(node)) && dom.isChildOf(node, rootNode) && dom.isEditable(node.parentNode);
34925        const setRange = (range) => {
34926            if (range) {
34927                selection.setRng(range);
34928            }
34929        };
34930        const showCaret = (direction, node, before, scrollIntoView = true) => {
34931            const e = editor.dispatch('ShowCaret', {
34932                target: node,
34933                direction,
34934                before
34935            });
34936            if (e.isDefaultPrevented()) {
34937                return null;
34938            }
34939            if (scrollIntoView) {
34940                selection.scrollIntoView(node, direction === -1);
34941            }
34942            return fakeCaret.show(before, node);
34943        };
34944        const showBlockCaretContainer = (blockCaretContainer) => {
34945            if (blockCaretContainer.hasAttribute('data-mce-caret')) {
34946                showCaretContainerBlock(blockCaretContainer);
34947                selection.scrollIntoView(blockCaretContainer);
34948            }
34949        };
34950        const registerEvents = () => {
34951            editor.on('click', (e) => {
34952                // Prevent clicks on links in a cE=false element
34953                if (!dom.isEditable(e.target)) {
34954                    e.preventDefault();
34955                    editor.focus();
34956                }
34957            });
34958            editor.on('blur NewBlock', removeElementSelection);
34959            editor.on('ResizeWindow FullscreenStateChanged', fakeCaret.reposition);
34960            editor.on('tap', (e) => {
34961                const targetElm = e.target;
34962                const contentEditableRoot = getContentEditableRoot(editor, targetElm);
34963                if (isContentEditableFalse(contentEditableRoot)) {
34964                    e.preventDefault();
34965                    selectNode(editor, contentEditableRoot).each(setElementSelection);
34966                }
34967                else if (isFakeSelectionTargetElement(targetElm)) {
34968                    selectNode(editor, targetElm).each(setElementSelection);
34969                }
34970            }, true);
34971            editor.on('mousedown', (e) => {
34972                const targetElm = e.target;
34973                if (targetElm !== rootNode && targetElm.nodeName !== 'HTML' && !dom.isChildOf(targetElm, rootNode)) {
34974                    return;
34975                }
34976                if (!isXYInContentArea(editor, e.clientX, e.clientY)) {
34977                    return;
34978                }
34979                // Remove needs to be called here since the mousedown might alter the selection without calling selection.setRng
34980                // and therefore not fire the AfterSetSelectionRange event.
34981                removeElementSelection();
34982                hideFakeCaret();
34983                const closestContentEditable = getContentEditableRoot(editor, targetElm);
34984                if (isContentEditableFalse(closestContentEditable)) {
34985                    e.preventDefault();
34986                    selectNode(editor, closestContentEditable).each(setElementSelection);
34987                }
34988                else {
34989                    closestFakeCaretCandidate(rootNode, e.clientX, e.clientY).each((caretInfo) => {
34990                        e.preventDefault();
34991                        const range = showCaret(1, caretInfo.node, caretInfo.position === FakeCaretPosition.Before, false);
34992                        setRange(range);
34993                        // Set the focus after the range has been set to avoid potential issues where the body has no selection
34994                        if (isHTMLElement(closestContentEditable)) {
34995                            closestContentEditable.focus();
34996                        }
34997                        else {
34998                            editor.getBody().focus();
34999                        }
35000                    });
35001                }
35002            });
35003            editor.on('keypress', (e) => {
35004                if (VK.modifierPressed(e)) {
35005                    return;
35006                }
35007                if (isContentEditableFalse(selection.getNode())) {
35008                    e.preventDefault();
35009                }
35010            });
35011            editor.on('GetSelectionRange', (e) => {
35012                let rng = e.range;
35013                if (selectedElement) {
35014                    if (!selectedElement.parentNode) {
35015                        selectedElement = null;
35016                        return;
35017                    }
35018                    rng = rng.cloneRange();
35019                    rng.selectNode(selectedElement);
35020                    e.range = rng;
35021                }
35022            });
35023            editor.on('focusin', (e) => {
35024                // for medias the selection is already managed in `MediaFocus.ts`
35025                if (isMedia$2(e.target)) {
35026                    return;
35027                }
35028                if (editor.getBody().contains(e.target) && e.target !== editor.getBody() && !editor.dom.isEditable(e.target.parentNode)) {
35029                    if (fakeCaret.isShowing()) {
35030                        fakeCaret.hide();
35031                    }
35032                    if (!e.target.contains(editor.selection.getNode())) {
35033                        editor.selection.select(e.target, true);
35034                        editor.selection.collapse(true);
35035                    }
35036                    const rng = setElementSelection(editor.selection.getRng(), true);
35037                    if (rng) {
35038                        editor.selection.setRng(rng);
35039                    }
35040                }
35041            });
35042            editor.on('SetSelectionRange', (e) => {
35043                // If the range is set inside a short ended element, then move it
35044                // to the side as IE for example will try to add content inside
35045                e.range = normalizeVoidElementSelection(e.range);
35046                const rng = setElementSelection(e.range, e.forward);
35047                if (rng) {
35048                    e.range = rng;
35049                }
35050            });
35051            const isPasteBin = (node) => isElement$7(node) && node.id === 'mcepastebin';
35052            editor.on('AfterSetSelectionRange', (e) => {
35053                const rng = e.range;
35054                const parent = rng.startContainer.parentElement;
35055                if (!isRangeInCaretContainer(rng) && !isPasteBin(parent)) {
35056                    hideFakeCaret();
35057                }
35058                if (!isFakeSelectionElement(parent)) {
35059                    removeElementSelection();
35060                }
35061            });
35062            init$2(editor);
35063            setup$4(editor);
35064            setup$3(editor);
35065        };
35066        const isWithinCaretContainer = (node) => (isCaretContainer$2(node) ||
35067            startsWithCaretContainer$1(node) ||
35068            endsWithCaretContainer$1(node));
35069        const isRangeInCaretContainer = (rng) => isWithinCaretContainer(rng.startContainer) || isWithinCaretContainer(rng.endContainer);
35070        const normalizeVoidElementSelection = (rng) => {
35071            const voidElements = editor.schema.getVoidElements();
35072            const newRng = dom.createRng();
35073            const startContainer = rng.startContainer;
35074            const startOffset = rng.startOffset;
35075            const endContainer = rng.endContainer;
35076            const endOffset = rng.endOffset;
35077            if (has$2(voidElements, startContainer.nodeName.toLowerCase())) {
35078                if (startOffset === 0) {
35079                    newRng.setStartBefore(startContainer);
35080                }
35081                else {
35082                    newRng.setStartAfter(startContainer);
35083                }
35084            }
35085            else {
35086                newRng.setStart(startContainer, startOffset);
35087            }
35088            if (has$2(voidElements, endContainer.nodeName.toLowerCase())) {
35089                if (endOffset === 0) {
35090                    newRng.setEndBefore(endContainer);
35091                }
35092                else {
35093                    newRng.setEndAfter(endContainer);
35094                }
35095            }
35096            else {
35097                newRng.setEnd(endContainer, endOffset);
35098            }
35099            return newRng;
35100        };
35101        const setupOffscreenSelection = (node, targetClone) => {
35102            const body = SugarElement.fromDom(editor.getBody());
35103            const doc = editor.getDoc();
35104            const realSelectionContainer = descendant$1(body, '#' + realSelectionId).getOrThunk(() => {
35105                const newContainer = SugarElement.fromHtml('<div data-mce-bogus="all" class="mce-offscreen-selection"></div>', doc);
35106                set$
351064
vendor: 28,469 bytes, lines 35106-35698
35106(newContainer, 'id', realSelectionId);
35107                append$1(body, newContainer);
35108                return newContainer;
35109            });
35110            const newRange = dom.createRng();
35111            empty(realSelectionContainer);
35112            append(realSelectionContainer, [
35113                SugarElement.fromText(nbsp, doc),
35114                SugarElement.fromDom(targetClone),
35115                SugarElement.fromText(nbsp, doc)
35116            ]);
35117            newRange.setStart(realSelectionContainer.dom.firstChild, 1);
35118            newRange.setEnd(realSelectionContainer.dom.lastChild, 0);
35119            setAll(realSelectionContainer, {
35120                top: dom.getPos(node, editor.getBody()).y + 'px'
35121            });
35122            focus$1(realSelectionContainer);
35123            const sel = selection.getSel();
35124            if (sel) {
35125                sel.removeAllRanges();
35126                sel.addRange(newRange);
35127            }
35128            return newRange;
35129        };
35130        const getUcVideoClone = (ucVideo) => {
35131            const newElm = editor.getDoc().createElement('div');
35132            newElm.style.width = ucVideo.style.width;
35133            newElm.style.height = ucVideo.style.height;
35134            const ucVideoWidth = ucVideo.getAttribute('width');
35135            if (ucVideoWidth) {
35136                newElm.setAttribute('width', ucVideoWidth);
35137            }
35138            const ucVideoHeight = ucVideo.getAttribute('height');
35139            if (ucVideoHeight) {
35140                newElm.setAttribute('height', ucVideoHeight);
35141            }
35142            return newElm;
35143        };
35144        const selectElement = (elm) => {
35145            const targetClone = isUcVideo(elm) ? getUcVideoClone(elm) : elm.cloneNode(true);
35146            const e = editor.dispatch('ObjectSelected', { target: elm, targetClone });
35147            if (e.isDefaultPrevented()) {
35148                return null;
35149            }
35150            // Setup the offscreen selection
35151            const range = setupOffscreenSelection(elm, e.targetClone);
35152            // We used to just remove all data-mce-selected values and set 1 on node.
35153            // But data-mce-selected can be values other than 1 so keep existing value if
35154            // node has one, and remove data-mce-selected from everything else
35155            const nodeElm = SugarElement.fromDom(elm);
35156            each$e(descendants(SugarElement.fromDom(editor.getBody()), `*[${elementSelectionAttr}]`), (elm) => {
35157                if (!eq(nodeElm, elm)) {
35158                    remove$9(elm, elementSelectionAttr);
35159                }
35160            });
35161            if (!dom.getAttrib(elm, elementSelectionAttr)) {
35162                elm.setAttribute(elementSelectionAttr, '1');
35163            }
35164            selectedElement = elm;
35165            hideFakeCaret();
35166            return range;
35167        };
35168        const setElementSelection = (range, forward) => {
35169            if (!range) {
35170                return null;
35171            }
35172            if (range.collapsed) {
35173                if (!isRangeInCaretContainer(range)) {
35174                    const dir = forward ? 1 : -1;
35175                    const caretPosition = getNormalizedRangeEndPoint(dir, rootNode, range);
35176                    const beforeNode = caretPosition.getNode(!forward);
35177                    if (isNonNullable(beforeNode)) {
35178                        if (isFakeCaretTarget(beforeNode)) {
35179                            return showCaret(dir, beforeNode, forward ? !caretPosition.isAtEnd() : false, false);
35180                        }
35181                        if (isCaretContainerInline(beforeNode) && isContentEditableFalse$a(beforeNode.nextSibling)) {
35182                            const rng = dom.createRng();
35183                            rng.setStart(beforeNode, 0);
35184                            rng.setEnd(beforeNode, 0);
35185                            return rng;
35186                        }
35187                    }
35188                    const afterNode = caretPosition.getNode(forward);
35189                    if (isNonNullable(afterNode)) {
35190                        if (isFakeCaretTarget(afterNode)) {
35191                            return showCaret(dir, afterNode, forward ? false : !caretPosition.isAtEnd(), false);
35192                        }
35193                        if (isCaretContainerInline(afterNode) && isContentEditableFalse$a(afterNode.previousSibling)) {
35194                            const rng = dom.createRng();
35195                            rng.setStart(afterNode, 1);
35196                            rng.setEnd(afterNode, 1);
35197                            return rng;
35198                        }
35199                    }
35200                }
35201                return null;
35202            }
35203            let startContainer = range.startContainer;
35204            let startOffset = range.startOffset;
35205            const endOffset = range.endOffset;
35206            // Normalizes <span cE=false>[</span>] to [<span cE=false></span>]
35207            if (isText$b(startContainer) && startOffset === 0 && isContentEditableFalse(startContainer.parentNode)) {
35208                startContainer = startContainer.parentNode;
35209                startOffset = dom.nodeIndex(startContainer);
35210                startContainer = startContainer.parentNode;
35211            }
35212            if (!isElement$7(startContainer)) {
35213                return null;
35214            }
35215            if (endOffset === startOffset + 1 && startContainer === range.endContainer) {
35216                const node = startContainer.childNodes[startOffset];
35217                if (isFakeSelectionTargetElement(node)) {
35218                    return selectElement(node);
35219                }
35220            }
35221            return null;
35222        };
35223        const removeElementSelection = () => {
35224            if (selectedElement) {
35225                selectedElement.removeAttribute(elementSelectionAttr);
35226            }
35227            descendant$1(SugarElement.fromDom(editor.getBody()), '#' + realSelectionId).each(remove$8);
35228            selectedElement = null;
35229        };
35230        const destroy = () => {
35231            fakeCaret.destroy();
35232            selectedElement = null;
35233        };
35234        const hideFakeCaret = () => {
35235            fakeCaret.hide();
35236        };
35237        if (!isRtc(editor)) {
35238            registerEvents();
35239        }
35240        return {
35241            showCaret,
35242            showBlockCaretContainer,
35243            hideFakeCaret,
35244            destroy
35245        };
35246    };
35247
35248    const getNormalizedTextOffset = (container, offset) => {
35249        let normalizedOffset = offset;
35250        for (let node = container.previousSibling; isText$b(node); node = node.previousSibling) {
35251            normalizedOffset += node.data.length;
35252        }
35253        return normalizedOffset;
35254    };
35255    const generatePath = (dom, root, node, offset, normalized) => {
35256        if (isText$b(node) && (offset < 0 || offset > node.data.length)) {
35257            return [];
35258        }
35259        const p = normalized && isText$b(node) ? [getNormalizedTextOffset(node, offset)] : [offset];
35260        let current = node;
35261        while (current !== root && current.parentNode) {
35262            p.push(dom.nodeIndex(current, normalized));
35263            current = current.parentNode;
35264        }
35265        return current === root ? p.reverse() : [];
35266    };
35267    const generatePathRange = (dom, root, startNode, startOffset, endNode, endOffset, normalized = false) => {
35268        const start = generatePath(dom, root, startNode, startOffset, normalized);
35269        const end = generatePath(dom, root, endNode, endOffset, normalized);
35270        return { start, end };
35271    };
35272    const resolvePath = (root, path) => {
35273        const nodePath = path.slice();
35274        const offset = nodePath.pop();
35275        if (!isNumber(offset)) {
35276            return Optional.none();
35277        }
35278        else {
35279            const resolvedNode = foldl(nodePath, (optNode, index) => optNode.bind((node) => Optional.from(node.childNodes[index])), Optional.some(root));
35280            return resolvedNode.bind((node) => {
35281                if (isText$b(node) && (offset < 0 || offset > node.data.length)) {
35282                    return Optional.none();
35283                }
35284                else {
35285                    return Optional.some({ node, offset });
35286                }
35287            });
35288        }
35289    };
35290    const resolvePathRange = (root, range) => resolvePath(root, range.start)
35291        .bind(({ node: startNode, offset: startOffset }) => resolvePath(root, range.end).map(({ node: endNode, offset: endOffset }) => {
35292        const rng = document.createRange();
35293        rng.setStart(startNode, startOffset);
35294        rng.setEnd(endNode, endOffset);
35295        return rng;
35296    }));
35297    const generatePathRangeFromRange = (dom, root, range, normalized = false) => generatePathRange(dom, root, range.startContainer, range.startOffset, range.endContainer, range.endOffset, normalized);
35298
35299    const cleanEmptyNodes = (dom, node, isRoot) => {
35300        // Recursively walk up the tree while we have a parent and the node is empty. If the node is empty, then remove it.
35301        if (node && dom.isEmpty(node) && !isRoot(node)) {
35302            const parent = node.parentNode;
35303            dom.remove(node, isText$b(node.firstChild) && isWhitespaceText(node.firstChild.data));
35304            cleanEmptyNodes(dom, parent, isRoot);
35305        }
35306    };
35307    const deleteRng = (dom, rng, isRoot, clean = true) => {
35308        const startParent = rng.startContainer.parentNode;
35309        const endParent = rng.endContainer.parentNode;
35310        rng.deleteContents();
35311        // Clean up any empty nodes if required
35312        if (clean && !isRoot(rng.startContainer)) {
35313            if (isText$b(rng.startContainer) && rng.startContainer.data.length === 0) {
35314                dom.remove(rng.startContainer);
35315            }
35316            if (isText$b(rng.endContainer) && rng.endContainer.data.length === 0) {
35317                dom.remove(rng.endContainer);
35318            }
35319            cleanEmptyNodes(dom, startParent, isRoot);
35320            if (startParent !== endParent) {
35321                cleanEmptyNodes(dom, endParent, isRoot);
35322            }
35323        }
35324    };
35325    const getParentBlock = (editor, rng) => Optional.from(editor.dom.getParent(rng.startContainer, editor.dom.isBlock));
35326    const resolveFromDynamicPatterns = (patternSet, block, beforeText) => {
35327        const dynamicPatterns = patternSet.dynamicPatternsLookup({
35328            text: beforeText,
35329            block
35330        });
35331        // dynamic patterns take precedence here
35332        return {
35333            ...patternSet,
35334            blockPatterns: getBlockPatterns(dynamicPatterns).concat(patternSet.blockPatterns),
35335            inlinePatterns: getInlinePatterns(dynamicPatterns).concat(patternSet.inlinePatterns)
35336        };
35337    };
35338    const getBeforeText = (dom, block, node, offset) => {
35339        const rng = dom.createRng();
35340        rng.setStart(block, 0);
35341        rng.setEnd(node, offset);
35342        return rng.toString();
35343    };
35344
35345    const newMarker = (dom, id) => dom.create('span', { 'data-mce-type': 'bookmark', id });
35346    const rangeFromMarker = (dom, marker) => {
35347        const rng = dom.createRng();
35348        rng.setStartAfter(marker.start);
35349        rng.setEndBefore(marker.end);
35350        return rng;
35351    };
35352    const createMarker = (dom, markerPrefix, pathRange) => {
35353        // Resolve the path range
35354        const rng = resolvePathRange(dom.getRoot(), pathRange).getOrDie('Unable to resolve path range');
35355        const startNode = rng.startContainer;
35356        const endNode = rng.endContainer;
35357        // Create the marker
35358        const textEnd = rng.endOffset === 0 ? endNode : endNode.splitText(rng.endOffset);
35359        const textStart = rng.startOffset === 0 ? startNode : startNode.splitText(rng.startOffset);
35360        const startParentNode = textStart.parentNode;
35361        const endParentNode = textEnd.parentNode;
35362        return {
35363            prefix: markerPrefix,
35364            end: endParentNode.insertBefore(newMarker(dom, markerPrefix + '-end'), textEnd),
35365            start: startParentNode.insertBefore(newMarker(dom, markerPrefix + '-start'), textStart)
35366        };
35367    };
35368    const removeMarker = (dom, marker, isRoot) => {
35369        // Note: Use dom.get() here instead of marker.end/start, as applying the format/command can
35370        // clone the nodes meaning the old reference isn't usable
35371        cleanEmptyNodes(dom, dom.get(marker.prefix + '-end'), isRoot);
35372        cleanEmptyNodes(dom, dom.get(marker.prefix + '-start'), isRoot);
35373    };
35374
35375    const isReplacementPattern = (pattern) => pattern.start.length === 0;
35376    const matchesPattern = (patternContent) => (element, offset) => {
35377        const text = element.data;
35378        const searchText = text.substring(0, offset);
35379        const startEndIndex = searchText.lastIndexOf(patternContent.charAt(patternContent.length - 1));
35380        const startIndex = searchText.lastIndexOf(patternContent);
35381        if (startIndex !== -1) {
35382            // Complete string found
35383            return startIndex + patternContent.length;
35384        }
35385        else if (startEndIndex !== -1) {
35386            // Potential partial string found
35387            return startEndIndex + 1;
35388        }
35389        else {
35390            // No match in current node, so continue
35391            return -1;
35392        }
35393    };
35394    const findPatternStartFromSpot = (dom, pattern, block, spot) => {
35395        const startPattern = pattern.start;
35396        const startSpot = repeatLeft(dom, spot.container, spot.offset, matchesPattern(startPattern), block);
35397        return startSpot.bind((spot) => {
35398            const startPatternIndex = block.textContent?.indexOf(startPattern) ?? -1;
35399            const isCompleteMatch = startPatternIndex !== -1 && spot.offset >= startPatternIndex + startPattern.length;
35400            if (isCompleteMatch) {
35401                // Complete match
35402                const rng = dom.createRng();
35403                rng.setStart(spot.container, spot.offset - startPattern.length);
35404                rng.setEnd(spot.container, spot.offset);
35405                return Optional.some(rng);
35406            }
35407            else {
35408                // Partial match so lean left to see if the string exists over fragmented text nodes
35409                const offset = spot.offset - startPattern.length;
35410                return scanLeft(spot.container, offset, block).map((nextSpot) => {
35411                    // Build up the range between the last char and the first char
35412                    const rng = dom.createRng();
35413                    rng.setStart(nextSpot.container, nextSpot.offset);
35414                    rng.setEnd(spot.container, spot.offset);
35415                    return rng;
35416                }).filter((rng) => 
35417                // Ensure the range content matches the start
35418                rng.toString() === startPattern).orThunk(() => 
35419                // No match found, so continue searching
35420                findPatternStartFromSpot(dom, pattern, block, point(spot.container, 0)));
35421            }
35422        });
35423    };
35424    const findPatternStart = (dom, pattern, node, offset, block, requireGap = false) => {
35425        if (pattern.start.length === 0 && !requireGap) {
35426            const rng = dom.createRng();
35427            rng.setStart(node, offset);
35428            rng.setEnd(node, offset);
35429            return Optional.some(rng);
35430        }
35431        return textBefore(node, offset, block).bind((spot) => {
35432            const start = findPatternStartFromSpot(dom, pattern, block, spot);
35433            return start.bind((startRange) => {
35434                if (requireGap) {
35435                    if (startRange.endContainer === spot.container && startRange.endOffset === spot.offset) {
35436                        return Optional.none();
35437                    }
35438                    else if (spot.offset === 0 && startRange.endContainer.textContent?.length === startRange.endOffset) {
35439                        return Optional.none();
35440                    }
35441                }
35442                return Optional.some(startRange);
35443            });
35444        });
35445    };
35446    const findPattern$3 = (editor, block, details, normalizedMatches) => {
35447        const dom = editor.dom;
35448        const root = dom.getRoot();
35449        const pattern = details.pattern;
35450        const endNode = details.position.container;
35451        const endOffset = details.position.offset;
35452        // Lean left to find the start of the end pattern, as it could be across fragmented nodes
35453        return scanLeft(endNode, endOffset - details.pattern.end.length, block).bind((spot) => {
35454            const endPathRng = generatePathRange(dom, root, spot.container, spot.offset, endNode, endOffset, normalizedMatches);
35455            // If we have a replacement pattern, then it can't have nested patterns so just return immediately
35456            if (isReplacementPattern(pattern)) {
35457                return Optional.some({
35458                    matches: [{
35459                            pattern,
35460                            startRng: endPathRng,
35461                            endRng: endPathRng
35462                        }],
35463                    position: spot
35464                });
35465            }
35466            else {
35467                // Find any nested patterns, making sure not to process the current pattern again
35468                const resultsOpt = findPatternsRec(editor, details.remainingPatterns, spot.container, spot.offset, block, normalizedMatches);
35469                const results = resultsOpt.getOr({ matches: [], position: spot });
35470                const pos = results.position;
35471                // Find the start of the matched pattern
35472                const start = findPatternStart(dom, pattern, pos.container, pos.offset, block, resultsOpt.isNone());
35473                return start.map((startRng) => {
35474                    const startPathRng = generatePathRangeFromRange(dom, root, startRng, normalizedMatches);
35475                    return {
35476                        matches: results.matches.concat([{
35477                                pattern,
35478                                startRng: startPathRng,
35479                                endRng: endPathRng
35480                            }]),
35481                        position: point(startRng.startContainer, startRng.startOffset)
35482                    };
35483                });
35484            }
35485        });
35486    };
35487    // Assumptions:
35488    // 0. Patterns are sorted by priority so we should preferentially match earlier entries
35489    // 1. Patterns may be nested but may only occur once
35490    // 2. Patterns will not have matching prefixes which contain space or standard punctuation ',', '.', ';', ':', '!', '?'
35491    // 3. Patterns will not extend outside of the root element
35492    // 4. All pattern ends must be directly before the cursor (represented by node + offset)
35493    // 5. Only text nodes matter
35494    const findPatternsRec = (editor, patterns, node, offset, block, normalizedMatches) => {
35495        const dom = editor.dom;
35496        return textBefore(node, offset, dom.getRoot()).bind((endSpot) => {
35497            const text = getBeforeText(dom, block, node, offset);
35498            for (let i = 0; i < patterns.length; i++) {
35499                const pattern = patterns[i];
35500                // If the text does not end with the same string as the pattern, then we can exit
35501                // early, because this pattern isn't going to match this text. This saves us doing more
35502                // expensive matching calls.
35503                if (!endsWith(text, pattern.end)) {
35504                    continue;
35505                }
35506                // Generate a new array without the current pattern
35507                const patternsWithoutCurrent = patterns.slice();
35508                patternsWithoutCurrent.splice(i, 1);
35509                // Try to find the current pattern
35510                const result = findPattern$3(editor, block, {
35511                    pattern,
35512                    remainingPatterns: patternsWithoutCurrent,
35513                    position: endSpot
35514                }, normalizedMatches);
35515                if (result.isNone() && offset > 0) {
35516                    return findPatternsRec(editor, patterns, node, offset - 1, block, normalizedMatches);
35517                }
35518                // If a match was found then return that
35519                if (result.isSome()) {
35520                    return result;
35521                }
35522            }
35523            return Optional.none();
35524        });
35525    };
35526    const applyPattern$2 = (editor, pattern, patternRange) => {
35527        editor.selection.setRng(patternRange);
35528        if (pattern.type === 'inline-format') {
35529            each$e(pattern.format, (format) => {
35530                editor.formatter.apply(format);
35531            });
35532        }
35533        else {
35534            editor.execCommand(pattern.cmd, false, pattern.value);
35535        }
35536    };
35537    const applyReplacementPattern = (editor, pattern, marker, isRoot) => {
35538        // Remove the original text
35539        const markerRange = rangeFromMarker(editor.dom, marker);
35540        deleteRng(editor.dom, markerRange, isRoot);
35541        // Apply the replacement
35542        applyPattern$2(editor, pattern, markerRange);
35543    };
35544    const applyPatternWithContent = (editor, pattern, startMarker, endMarker, isRoot) => {
35545        const dom = editor.dom;
35546        // Create the marker ranges for the patterns start/end content
35547        const markerEndRange = rangeFromMarker(dom, endMarker);
35548        const markerStartRange = rangeFromMarker(dom, startMarker);
35549        // Clean up the pattern start/end content
35550        deleteRng(dom, markerStartRange, isRoot);
35551        deleteRng(dom, markerEndRange, isRoot);
35552        // Apply the pattern
35553        const patternMarker = { prefix: startMarker.prefix, start: startMarker.end, end: endMarker.start };
35554        const patternRange = rangeFromMarker(dom, patternMarker);
35555        applyPattern$2(editor, pattern, patternRange);
35556    };
35557    const addMarkers = (dom, matches) => {
35558        const markerPrefix = generate('mce_textpattern');
35559        // Add end markers
35560        const matchesWithEnds = foldr(matches, (acc, match) => {
35561            const endMarker = createMarker(dom, markerPrefix + `_end${acc.length}`, match.endRng);
35562            return acc.concat([{
35563                    ...match,
35564                    endMarker
35565                }]);
35566        }, []);
35567        // Add start markers
35568        return foldr(matchesWithEnds, (acc, match) => {
35569            const idx = matchesWithEnds.length - acc.length - 1;
35570            const startMarker = isReplacementPattern(match.pattern) ? match.endMarker : createMarker(dom, markerPrefix + `_start${idx}`, match.startRng);
35571            return acc.concat([{
35572                    ...match,
35573                    startMarker
35574                }]);
35575        }, []);
35576    };
35577    const sortPatterns$1 = (patterns) => sort(patterns, (a, b) => b.end.length - a.end.length);
35578    const getBestMatches = (matches, matchesWithSortedPatterns) => {
35579        const hasSameMatches = forall(matches, (match) => exists(matchesWithSortedPatterns, (sortedMatch) => match.pattern.start === sortedMatch.pattern.start && match.pattern.end === sortedMatch.pattern.end));
35580        if (matches.length === matchesWithSortedPatterns.length) {
35581            if (hasSameMatches) {
35582                return matches;
35583            }
35584            else {
35585                return matchesWithSortedPatterns;
35586            }
35587        }
35588        return matches.length > matchesWithSortedPatterns.length ? matches : matchesWithSortedPatterns;
35589    };
35590    const findPatterns$2 = (editor, block, node, offset, patternSet, normalizedMatches) => {
35591        const matches = findPatternsRec(editor, patternSet.inlinePatterns, node, offset, block, normalizedMatches).fold(() => [], (result) => result.matches);
35592        const matchesWithSortedPatterns = findPatternsRec(editor, sortPatterns$1(patternSet.inlinePatterns), node, offset, block, normalizedMatches).fold(() => [], (result) => result.matches);
35593        return getBestMatches(matches, matchesWithSortedPatterns);
35594    };
35595    const applyMatches$2 = (editor, matches) => {
35596        if (matches.length === 0) {
35597            return;
35598        }
35599        // Store the current selection
35600        const dom = editor.dom;
35601        const bookmark = editor.selection.getBookmark();
35602        // Add markers for the matched patterns
35603        const matchesWithMarkers = addMarkers(dom, matches);
35604        // Do the replacements
35605        each$e(matchesWithMarkers, (match) => {
35606            const block = dom.getParent(match.startMarker.start, dom.isBlock);
35607            const isRoot = (node) => node === block;
35608            if (isReplacementPattern(match.pattern)) {
35609                applyReplacementPattern(editor, match.pattern, match.endMarker, isRoot);
35610            }
35611            else {
35612                applyPatternWithContent(editor, match.pattern, match.startMarker, match.endMarker, isRoot);
35613            }
35614            // Remove the markers
35615            removeMarker(dom, match.endMarker, isRoot);
35616            removeMarker(dom, match.startMarker, isRoot);
35617        });
35618        // Restore the selection
35619        editor.selection.moveToBookmark(bookmark);
35620    };
35621
35622    const stripPattern$1 = (dom, block, pattern) => {
35623        // The pattern could be across fragmented text nodes, so we need to find the end
35624        // of the pattern and then remove all elements between the start/end range
35625        return textAfter(block, 0, block).map((spot) => {
35626            const node = spot.container;
35627            scanRight(node, pattern.start.length, block).each((end) => {
35628                const rng = dom.createRng();
35629                rng.setStart(node, 0);
35630                rng.setEnd(end.container, end.offset);
35631                deleteRng(dom, rng, (e) => e === block);
35632            });
35633            return node;
35634        });
35635    };
35636    const createApplyPattern = (stripPattern) => (editor, match) => {
35637        const dom = editor.dom;
35638        const pattern = match.pattern;
35639        const rng = resolvePathRange(dom.getRoot(), match.range).getOrDie('Unable to resolve path range');
35640        const isBlockFormatName = (name, formatter) => {
35641            const formatSet = formatter.get(name);
35642            return isArray$1(formatSet) && head(formatSet).exists((format) => has$2(format, 'block'));
35643        };
35644        getParentBlock(editor, rng).each((block) => {
35645            if (pattern.type === 'block-format') {
35646                if (isBlockFormatName(pattern.format, editor.formatter)) {
35647                    editor.undoManager.transact(() => {
35648                        stripPattern(editor.dom, block, pattern);
35649                        editor.formatter.apply(pattern.format);
35650                    });
35651                }
35652            }
35653            else if (pattern.type === 'block-command') {
35654                editor.undoManager.transact(() => {
35655                    stripPattern(editor.dom, block, pattern);
35656                    editor.execCommand(pattern.cmd, false, pattern.value);
35657                });
35658            }
35659        });
35660        return true;
35661    };
35662    const sortPatterns = (patterns) => sort(patterns, (a, b) => b.start.length - a.start.length);
35663    // Finds a matching pattern to the specified text
35664    const findPattern$2 = (predicate) => (patterns, text) => {
35665        const sortedPatterns = sortPatterns(patterns);
35666        const nuText = text.replace(nbsp, ' ');
35667        return find$2(sortedPatterns, (pattern) => predicate(pattern, text, nuText));
35668    };
35669    const createFindPatterns = (findPattern, skipFullMatch) => (editor, block, patternSet, normalizedMatches, text = block.textContent ?? '') => {
35670        const dom = editor.dom;
35671        const forcedRootBlock = getForcedRootBlock(editor);
35672        if (!dom.is(block, forcedRootBlock)) {
35673            return [];
35674        }
35675        return findPattern(patternSet.blockPatterns, text).map((pattern) => {
35676            if (skipFullMatch && Tools.trim(text).length === pattern.start.length) {
35677                return [];
35678            }
35679            return [{
35680                    pattern,
35681                    range: generatePathRange(dom, dom.getRoot(), block, 0, block, 0, normalizedMatches)
35682                }];
35683        }).getOr([]);
35684    };
35685
35686    const startsWithSingleSpace = (s) => /^\s[^\s]/.test(s);
35687    const stripPattern = (dom, block, pattern) => {
35688        stripPattern$1(dom, block, pattern).each((node) => {
35689            /**
35690             * TINY-9603: If there is a single space between pattern.start and text (e.g. # 1)
35691             * then it will be left in the text content and then can appear in certain circumstances.
35692             * This is not an issue with multiple spaces because they are transformed to non-breaking ones.
35693             *
35694             * In this specific case we've decided to remove this single space whatsoever
35695             * as it feels to be the expected behavior.
35696             */
35697            const text = SugarElement.fromDom(node);
35698            const textContent = get$
vendor: 8,569 bytes, lines 35698-35870
356984(text);
35699            if (startsWithSingleSpace(textContent)) {
35700                set$1(text, textContent.slice(1));
35701            }
35702        });
35703    };
35704    const applyPattern$1 = createApplyPattern(stripPattern);
35705    const findPattern$1 = findPattern$2((pattern, text, nuText) => text.indexOf(pattern.start) === 0 || nuText.indexOf(pattern.start) === 0);
35706    const findPatterns$1 = createFindPatterns(findPattern$1, true);
35707    const getMatches$1 = (editor, patternSet) => {
35708        const rng = editor.selection.getRng();
35709        return getParentBlock(editor, rng).map((block) => {
35710            const offset = Math.max(0, rng.startOffset);
35711            const dynamicPatternSet = resolveFromDynamicPatterns(patternSet, block, block.textContent ?? '');
35712            // IMPORTANT: We need to get normalized match results since undoing and redoing the editor state
35713            // via undoManager.extra() will result in the DOM being normalized.
35714            const inlineMatches = findPatterns$2(editor, block, rng.startContainer, offset, dynamicPatternSet, true);
35715            const blockMatches = findPatterns$1(editor, block, dynamicPatternSet, true);
35716            return { inlineMatches, blockMatches };
35717        }).filter(({ inlineMatches, blockMatches }) => blockMatches.length > 0 || inlineMatches.length > 0);
35718    };
35719    const applyMatches$1 = (editor, matches) => {
35720        if (matches.length === 0) {
35721            return;
35722        }
35723        // Store the current selection and then apply the matched patterns
35724        const bookmark = editor.selection.getBookmark();
35725        each$e(matches, (match) => applyPattern$1(editor, match));
35726        editor.selection.moveToBookmark(bookmark);
35727    };
35728
35729    const applyPattern = createApplyPattern(stripPattern$1);
35730    const findPattern = findPattern$2((pattern, text, nuText) => text === pattern.start || nuText === pattern.start);
35731    const findPatterns = createFindPatterns(findPattern, false);
35732    const getMatches = (editor, patternSet) => {
35733        const rng = editor.selection.getRng();
35734        return getParentBlock(editor, rng).map((block) => {
35735            const offset = Math.max(0, rng.startOffset);
35736            const beforeText = getBeforeText(editor.dom, block, rng.startContainer, offset);
35737            const dynamicPatternSet = resolveFromDynamicPatterns(patternSet, block, beforeText);
35738            return findPatterns(editor, block, dynamicPatternSet, false, beforeText);
35739        }).filter((matches) => matches.length > 0);
35740    };
35741    const applyMatches = (editor, matches) => {
35742        each$e(matches, (match) => applyPattern(editor, match));
35743    };
35744
35745    const handleEnter = (editor, patternSet) => getMatches$1(editor, patternSet).fold(never, ({ inlineMatches, blockMatches }) => {
35746        editor.undoManager.add();
35747        editor.undoManager.extra(() => {
35748            editor.execCommand('mceInsertNewLine');
35749        }, () => {
35750            // create a cursor position that we can move to avoid the inline formats
35751            insert$5(editor);
35752            applyMatches$2(editor, inlineMatches);
35753            applyMatches$1(editor, blockMatches);
35754            // find the spot before the cursor position
35755            const range = editor.selection.getRng();
35756            const spot = textBefore(range.startContainer, range.startOffset, editor.dom.getRoot());
35757            editor.execCommand('mceInsertNewLine');
35758            // clean up the cursor position we used to preserve the format
35759            spot.each((s) => {
35760                const node = s.container;
35761                if (node.data.charAt(s.offset - 1) === zeroWidth) {
35762                    node.deleteData(s.offset - 1, 1);
35763                    cleanEmptyNodes(editor.dom, node.parentNode, (e) => e === editor.dom.getRoot());
35764                }
35765            });
35766        });
35767        return true;
35768    });
35769    const handleInlineKey = (editor, patternSet) => {
35770        const rng = editor.selection.getRng();
35771        getParentBlock(editor, rng).map((block) => {
35772            const offset = Math.max(0, rng.startOffset - 1);
35773            const beforeText = getBeforeText(editor.dom, block, rng.startContainer, offset);
35774            const dynamicPatternSet = resolveFromDynamicPatterns(patternSet, block, beforeText);
35775            const inlineMatches = findPatterns$2(editor, block, rng.startContainer, offset, dynamicPatternSet, false);
35776            if (inlineMatches.length > 0) {
35777                editor.undoManager.transact(() => {
35778                    applyMatches$2(editor, inlineMatches);
35779                });
35780            }
35781        });
35782    };
35783    const handleBlockPatternOnSpace = (editor, patternSet) => getMatches(editor, patternSet).fold(never, (matches) => {
35784        editor.undoManager.transact(() => {
35785            applyMatches(editor, matches);
35786        });
35787        return true;
35788    });
35789    const checkKeyEvent = (codes, event, predicate) => {
35790        for (let i = 0; i < codes.length; i++) {
35791            if (predicate(codes[i], event)) {
35792                return true;
35793            }
35794        }
35795        return false;
35796    };
35797    const checkKeyCode = (codes, event) => checkKeyEvent(codes, event, (code, event) => {
35798        return code === event.keyCode && !VK.modifierPressed(event);
35799    });
35800    const checkCharCode = (chars, event) => checkKeyEvent(chars, event, (chr, event) => {
35801        return chr.charCodeAt(0) === event.charCode;
35802    });
35803
35804    const setup$2 = (editor) => {
35805        const charCodes = [',', '.', ';', ':', '!', '?'];
35806        const keyCodes = [32];
35807        // This is a thunk so that they reflect changes in the underlying options each time they are requested.
35808        const getPatternSet = () => createPatternSet(getTextPatterns(editor)
35809            .filter((pattern) => {
35810            if (pattern.type === 'inline-command' || pattern.type === 'block-command') {
35811                return editor.queryCommandSupported(pattern.cmd);
35812            }
35813            return true;
35814        }), getTextPatternsLookup(editor));
35815        // Only used for skipping text pattern matching altogether if nothing has been defined.
35816        const hasDynamicPatterns = () => hasTextPatternsLookup(editor);
35817        editor.on('keydown', (e) => {
35818            if (e.keyCode === 13 && !VK.modifierPressed(e) && editor.selection.isCollapsed() && editor.selection.isEditable()) {
35819                const patternSet = filterByTrigger(getPatternSet(), 'enter');
35820                // Do not process anything if we don't have any inline patterns, block patterns,
35821                // or dynamic lookup defined
35822                const hasPatterns = patternSet.inlinePatterns.length > 0 ||
35823                    patternSet.blockPatterns.length > 0 ||
35824                    hasDynamicPatterns();
35825                if (hasPatterns && handleEnter(editor, patternSet)) {
35826                    e.preventDefault();
35827                }
35828            }
35829        }, true);
35830        editor.on('keydown', (e) => {
35831            if (e.keyCode === 32 && editor.selection.isCollapsed() && editor.selection.isEditable()) {
35832                const patternSet = filterByTrigger(getPatternSet(), 'space');
35833                const hasPatterns = patternSet.blockPatterns.length > 0 || hasDynamicPatterns();
35834                if (hasPatterns && handleBlockPatternOnSpace(editor, patternSet)) {
35835                    e.preventDefault();
35836                }
35837            }
35838        }, true);
35839        const handleInlineTrigger = () => {
35840            if (editor.selection.isCollapsed() && editor.selection.isEditable()) {
35841                const patternSet = filterByTrigger(getPatternSet(), 'space');
35842                // Do not process anything if we don't have any inline patterns or dynamic lookup defined
35843                const hasPatterns = patternSet.inlinePatterns.length > 0 || hasDynamicPatterns();
35844                if (hasPatterns) {
35845                    handleInlineKey(editor, patternSet);
35846                }
35847            }
35848        };
35849        editor.on('keyup', (e) => {
35850            if (checkKeyCode(keyCodes, e)) {
35851                handleInlineTrigger();
35852            }
35853        });
35854        editor.on('keypress', (e) => {
35855            if (checkCharCode(charCodes, e)) {
35856                Delay.setEditorTimeout(editor, handleInlineTrigger);
35857            }
35858        });
35859    };
35860
35861    const setup$1 = (editor) => {
35862        setup$2(editor);
35863    };
35864
35865    const Quirks = (editor) => {
35866        const each = Tools.each;
35867        const BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE, dom = editor.dom, selection = editor.selection, parser = editor.parser;
35868        const browser = Env.browser;
35869        const isGecko = browser.isFirefox();
35870        const isWebKit = browser.isChromium() ||
vendor: 28,234 bytes, lines 35870-36477
35870 browser.isSafari();
35871        const isiOS = Env.deviceType.isiPhone() || Env.deviceType.isiPad();
35872        const isMac = Env.os.isMacOS() || Env.os.isiOS();
35873        /**
35874         * Executes a command with a specific state this can be to enable/disable browser editing features.
35875         */
35876        const setEditorCommandState = (cmd, state) => {
35877            try {
35878                editor.getDoc().execCommand(cmd, false, String(state));
35879            }
35880            catch {
35881                // Ignore
35882            }
35883        };
35884        /**
35885         * Returns true/false if the event is prevented or not.
35886         *
35887         * @private
35888         * @param {Event} e Event object.
35889         * @return {Boolean} true/false if the event is prevented or not.
35890         */
35891        const isDefaultPrevented = (e) => {
35892            return e.isDefaultPrevented();
35893        };
35894        /**
35895         * Makes sure that the editor body becomes empty when backspace or delete is pressed in empty editors.
35896         *
35897         * For example:
35898         * <p><b>|</b></p>
35899         *
35900         * Or:
35901         * <h1>|</h1>
35902         *
35903         * Or:
35904         * [<h1></h1>]
35905         */
35906        const emptyEditorWhenDeleting = () => {
35907            const serializeRng = (rng) => {
35908                const body = dom.create('body');
35909                const contents = rng.cloneContents();
35910                body.appendChild(contents);
35911                return selection.serializer.serialize(body, { format: 'html' });
35912            };
35913            const allContentsSelected = (rng) => {
35914                const selection = serializeRng(rng);
35915                const allRng = dom.createRng();
35916                allRng.selectNode(editor.getBody());
35917                const allSelection = serializeRng(allRng);
35918                return selection === allSelection;
35919            };
35920            editor.on('keydown', (e) => {
35921                const keyCode = e.keyCode;
35922                // Empty the editor if it's needed for example backspace at <p><b>|</b></p>
35923                if (!isDefaultPrevented(e) && (keyCode === DELETE || keyCode === BACKSPACE) && editor.selection.isEditable()) {
35924                    const isCollapsed = editor.selection.isCollapsed();
35925                    const body = editor.getBody();
35926                    // Selection is collapsed but the editor isn't empty
35927                    if (isCollapsed && !isEmptyNode(editor.schema, body)) {
35928                        return;
35929                    }
35930                    // Selection isn't collapsed but not all the contents is selected
35931                    if (!isCollapsed && !allContentsSelected(editor.selection.getRng())) {
35932                        return;
35933                    }
35934                    // Manually empty the editor
35935                    e.preventDefault();
35936                    if (symulateDelete(editor, keyCode === DELETE, () => editor.setContent(''))) {
35937                        if (body.firstChild && dom.isBlock(body.firstChild)) {
35938                            editor.selection.setCursorLocation(body.firstChild, 0);
35939                        }
35940                        else {
35941                            editor.selection.setCursorLocation(body, 0);
35942                        }
35943                    }
35944                }
35945            });
35946        };
35947        /**
35948         * WebKit doesn't select all the nodes in the body when you press Ctrl+A.
35949         * IE selects more than the contents <body>[<p>a</p>]</body> instead of <body><p>[a]</p]</body> see bug #6438
35950         * This selects the whole body so that backspace/delete logic will delete everything
35951         */
35952        const selectAll = () => {
35953            editor.shortcuts.add('meta+a', null, 'SelectAll');
35954        };
35955        /**
35956         * It seems that Chrome doesn't place the caret if you click on the documentElement in iframe mode
35957         * something that is very easy to do by accident so this problem is now more generic than the original issue.
35958         *
35959         * Original IME specific issue:
35960         * WebKit has a weird issue where it some times fails to properly convert keypresses to input method keystrokes.
35961         * The IME on Mac doesn't initialize when it doesn't fire a proper focus event.
35962         *
35963         * This seems to happen when the user manages to click the documentElement element then the window doesn't get proper focus until
35964         * you enter a character into the editor.
35965         *
35966         * See: https://bugs.webkit.org/show_bug.cgi?id=83566
35967         */
35968        const documentElementEditingFocus = () => {
35969            if (!editor.inline) {
35970                // Needs to be both down/up due to weird rendering bug on Chrome Windows
35971                dom.bind(editor.getDoc(), 'mousedown mouseup', (e) => {
35972                    let rng;
35973                    if (e.target === editor.getDoc().documentElement) {
35974                        rng = selection.getRng();
35975                        // TINY-12245: this is needed to avoid the scroll back to the top when the content is scrolled, there is no selection and the user is clicking on a non selectable editor element
35976                        // example content scrolled by browser search and user click on the horizontal scroll bar
35977                        if (editor.getDoc().getSelection()?.anchorNode !== null) {
35978                            editor.getBody().focus();
35979                        }
35980                        if (e.type === 'mousedown') {
35981                            if (isCaretContainer$2(rng.startContainer)) {
35982                                return;
35983                            }
35984                            // Edge case for mousedown, drag select and mousedown again within selection on Chrome Windows to render caret
35985                            selection.placeCaretAt(e.clientX, e.clientY);
35986                        }
35987                        else {
35988                            selection.setRng(rng);
35989                        }
35990                    }
35991                });
35992            }
35993        };
35994        /**
35995         * Backspacing in FireFox/IE from a paragraph into a horizontal rule results in a floating text node because the
35996         * browser just deletes the paragraph - the browser fails to merge the text node with a horizontal rule so it is
35997         * left there. TinyMCE sees a floating text node and wraps it in a paragraph on the key up event (ForceBlocks.js
35998         * addRootBlocks), meaning the action does nothing. With this code, FireFox/IE matche the behaviour of other
35999         * browsers.
36000         *
36001         * It also fixes a bug on Firefox where it's impossible to delete HR elements.
36002         */
36003        const removeHrOnBackspace = () => {
36004            editor.on('keydown', (e) => {
36005                if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) {
36006                    // Check if there is any HR elements this is faster since getRng on IE 7 & 8 is slow
36007                    if (!editor.getBody().getElementsByTagName('hr').length) {
36008                        return;
36009                    }
36010                    if (selection.isCollapsed() && selection.getRng().startOffset === 0) {
36011                        const node = selection.getNode();
36012                        const previousSibling = node.previousSibling;
36013                        if (node.nodeName === 'HR') {
36014                            dom.remove(node);
36015                            e.preventDefault();
36016                            return;
36017                        }
36018                        if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === 'hr') {
36019                            dom.remove(previousSibling);
36020                            e.preventDefault();
36021                        }
36022                    }
36023                }
36024            });
36025        };
36026        /**
36027         * Firefox 3.x has an issue where the body element won't get proper focus if you click out
36028         * side it's rectangle.
36029         */
36030        const focusBody = () => {
36031            // Fix for a focus bug in FF 3.x where the body element
36032            // wouldn't get proper focus if the user clicked on the HTML element
36033            if (!Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4
36034                editor.on('mousedown', (e) => {
36035                    if (!isDefaultPrevented(e) && e.target.nodeName === 'HTML') {
36036                        const body = editor.getBody();
36037                        // Blur the body it's focused but not correctly focused
36038                        body.blur();
36039                        // Refocus the body after a little while
36040                        Delay.setEditorTimeout(editor, () => {
36041                            body.focus();
36042                        });
36043                    }
36044                });
36045            }
36046        };
36047        /**
36048         * WebKit has a bug where it isn't possible to select image, hr or anchor elements
36049         * by clicking on them so we need to fake that.
36050         */
36051        const selectControlElements = () => {
36052            const visualAidsAnchorClass = getVisualAidsAnchorClass(editor);
36053            editor.on('click', (e) => {
36054                const target = e.target;
36055                // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
36056                // WebKit can't even do simple things like selecting an image
36057                // Needs to be the setBaseAndExtend or it will fail to select floated images
36058                if (/^(IMG|HR)$/.test(target.nodeName) && dom.isEditable(target)) {
36059                    e.preventDefault();
36060                    editor.selection.select(target);
36061                    editor.nodeChanged();
36062                }
36063                if (target.nodeName === 'A' && dom.hasClass(target, visualAidsAnchorClass) && target.childNodes.length === 0 && dom.isEditable(target.parentNode)) {
36064                    e.preventDefault();
36065                    selection.select(target);
36066                }
36067            });
36068        };
36069        /**
36070         * Fixes a Gecko a selection bug where if there is a floating image
36071         * more details here: https://bugzilla.mozilla.org/show_bug.cgi?id=1959606
36072         */
36073        const fixFirefoxImageSelection = () => {
36074            const isEditableImage = (node) => node.nodeName === 'IMG' && editor.dom.isEditable(node);
36075            editor.on('mousedown', (e) => {
36076                lift2(Optional.from(e.clientX), Optional.from(e.clientY), (clientX, clientY) => {
36077                    const caretPos = editor.getDoc().caretPositionFromPoint(clientX, clientY);
36078                    const img = caretPos?.offsetNode?.childNodes[caretPos.offset - (caretPos.offset > 0 ? 1 : 0)] || caretPos?.offsetNode;
36079                    if (isNonNullable(img) && isEditableImage(img)) {
36080                        const rect = img.getBoundingClientRect();
36081                        e.preventDefault();
36082                        if (!editor.hasFocus()) {
36083                            editor.focus();
36084                        }
36085                        editor.selection.select(img);
36086                        if (e.clientX < rect.left || e.clientY < rect.top) {
36087                            editor.selection.collapse(true);
36088                        }
36089                        else if (e.clientX > rect.right || e.clientY > rect.bottom) {
36090                            editor.selection.collapse(false);
36091                        }
36092                    }
36093                });
36094            });
36095        };
36096        /**
36097         * Fixes a Gecko bug where the style attribute gets added to the wrong element when deleting between two block elements.
36098         *
36099         * Fixes do backspace/delete on this:
36100         * <p>bla[ck</p><p style="color:red">r]ed</p>
36101         *
36102         * Would become:
36103         * <p>bla|ed</p>
36104         *
36105         * Instead of:
36106         * <p style="color:red">bla|ed</p>
36107         */
36108        const removeStylesWhenDeletingAcrossBlockElements = () => {
36109            const getAttributeApplyFunction = () => {
36110                const template = dom.getAttribs(selection.getStart().cloneNode(false));
36111                return () => {
36112                    const target = selection.getStart();
36113                    if (target !== editor.getBody()) {
36114                        dom.setAttrib(target, 'style', null);
36115                        each(template, (attr) => {
36116                            target.setAttributeNode(attr.cloneNode(true));
36117                        });
36118                    }
36119                };
36120            };
36121            const isSelectionAcrossElements = () => {
36122                return !selection.isCollapsed() &&
36123                    dom.getParent(selection.getStart(), dom.isBlock) !== dom.getParent(selection.getEnd(), dom.isBlock);
36124            };
36125            editor.on('keypress', (e) => {
36126                let applyAttributes;
36127                if (!isDefaultPrevented(e) && (e.keyCode === 8 || e.keyCode === 46) && isSelectionAcrossElements()) {
36128                    applyAttributes = getAttributeApplyFunction();
36129                    editor.getDoc().execCommand('delete', false);
36130                    applyAttributes();
36131                    e.preventDefault();
36132                    return false;
36133                }
36134                else {
36135                    return true;
36136                }
36137            });
36138            dom.bind(editor.getDoc(), 'cut', (e) => {
36139                if (!isDefaultPrevented(e) && isSelectionAcrossElements()) {
36140                    const applyAttributes = getAttributeApplyFunction();
36141                    Delay.setEditorTimeout(editor, () => {
36142                        applyAttributes();
36143                    });
36144                }
36145            });
36146        };
36147        /**
36148         * Backspacing into a table behaves differently depending upon browser type.
36149         * Therefore, disable Backspace when cursor immediately follows a table.
36150         */
36151        const disableBackspaceIntoATable = () => {
36152            editor.on('keydown', (e) => {
36153                if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) {
36154                    if (selection.isCollapsed() && selection.getRng().startOffset === 0) {
36155                        const previousSibling = selection.getNode().previousSibling;
36156                        if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === 'table') {
36157                            e.preventDefault();
36158                            return false;
36159                        }
36160                    }
36161                }
36162                return true;
36163            });
36164        };
36165        /**
36166         * Removes a blockquote when backspace is pressed at the beginning of it.
36167         *
36168         * For example:
36169         * <blockquote><p>|x</p></blockquote>
36170         *
36171         * Becomes:
36172         * <p>|x</p>
36173         */
36174        const removeBlockQuoteOnBackSpace = () => {
36175            // Add block quote deletion handler
36176            editor.on('keydown', (e) => {
36177                if (isDefaultPrevented(e) || e.keyCode !== VK.BACKSPACE) {
36178                    return;
36179                }
36180                let rng = selection.getRng();
36181                const container = rng.startContainer;
36182                const offset = rng.startOffset;
36183                const root = dom.getRoot();
36184                let parent = container;
36185                if (!rng.collapsed || offset !== 0) {
36186                    return;
36187                }
36188                while (parent.parentNode && parent.parentNode.firstChild === parent && parent.parentNode !== root) {
36189                    parent = parent.parentNode;
36190                }
36191                // Is the cursor at the beginning of a blockquote?
36192                if (parent.nodeName === 'BLOCKQUOTE') {
36193                    // Remove the blockquote
36194                    editor.formatter.toggle('blockquote', undefined, parent);
36195                    // Move the caret to the beginning of container
36196                    rng = dom.createRng();
36197                    rng.setStart(container, 0);
36198                    rng.setEnd(container, 0);
36199                    selection.setRng(rng);
36200                }
36201            });
36202        };
36203        /*
36204         * Firefox-specific fix for arrow key navigation. In Firefox, users can't move the caret out of a
36205         * `<figcaption>` element using the left and right arrow keys. This function handles those keystrokes
36206         * to allow navigation to the previous/next sibling of the figure element.
36207        */
36208        const arrowInFigcaption = () => {
36209            const isFigcaption = isTag('figcaption');
36210            editor.on('keydown', (e) => {
36211                if (e.keyCode === VK.LEFT || e.keyCode === VK.RIGHT) {
36212                    const currentNode = SugarElement.fromDom(editor.selection.getNode());
36213                    if (isFigcaption(currentNode) && editor.selection.isCollapsed()) {
36214                        parent(currentNode).bind((parent) => {
36215                            if (editor.selection.getRng().startOffset === 0 && e.keyCode === VK.LEFT) {
36216                                return prevSibling(parent);
36217                            }
36218                            else if (editor.selection.getRng().endOffset === currentNode.dom.textContent?.length && e.keyCode === VK.RIGHT) {
36219                                return nextSibling(parent);
36220                            }
36221                            else {
36222                                return Optional.none();
36223                            }
36224                        }).each((targetSibling) => {
36225                            editor.selection.setCursorLocation(targetSibling.dom, 0);
36226                        });
36227                    }
36228                }
36229            });
36230        };
36231        /**
36232         * Sets various Gecko editing options on mouse down and before a execCommand to disable inline table editing that is broken etc.
36233         */
36234        const setGeckoEditingOptions = () => {
36235            const setOpts = () => {
36236                setEditorCommandState('StyleWithCSS', false);
36237                setEditorCommandState('enableInlineTableEditing', false);
36238                if (!getObjectResizing(editor)) {
36239                    setEditorCommandState('enableObjectResizing', false);
36240                }
36241            };
36242            if (!isReadOnly$1(editor)) {
36243                editor.on('BeforeExecCommand mousedown', setOpts);
36244            }
36245        };
36246        /**
36247         * Fixes a gecko link bug, when a link is placed at the end of block elements there is
36248         * no way to move the caret behind the link. This fix adds a bogus br element after the link.
36249         *
36250         * For example this:
36251         * <p><b><a href="#">x</a></b></p>
36252         *
36253         * Becomes this:
36254         * <p><b><a href="#">x</a></b><br></p>
36255         */
36256        const addBrAfterLastLinks = () => {
36257            const fixLinks = () => {
36258                each(dom.select('a:not([data-mce-block])'), (node) => {
36259                    let parentNode = node.parentNode;
36260                    const root = dom.getRoot();
36261                    if (parentNode?.lastChild === node) {
36262                        while (parentNode && !dom.isBlock(parentNode)) {
36263                            if (parentNode.parentNode?.lastChild !== parentNode || parentNode === root) {
36264                                return;
36265                            }
36266                            parentNode = parentNode.parentNode;
36267                        }
36268                        dom.add(parentNode, 'br', { 'data-mce-bogus': 1 });
36269                    }
36270                });
36271            };
36272            editor.on('SetContent ExecCommand', (e) => {
36273                if (e.type === 'setcontent' || e.command === 'mceInsertLink') {
36274                    fixLinks();
36275                }
36276            });
36277        };
36278        /**
36279         * WebKit will produce DIV elements here and there by default. But since TinyMCE uses paragraphs by
36280         * default we want to change that behavior.
36281         */
36282        const setDefaultBlockType = () => {
36283            editor.on('init', () => {
36284                setEditorCommandState('DefaultParagraphSeparator', getForcedRootBlock(editor));
36285            });
36286        };
36287        const isAllContentSelected = (editor) => {
36288            const body = editor.getBody();
36289            const rng = editor.selection.getRng();
36290            return rng.startContainer === rng.endContainer && rng.startContainer === body && rng.startOffset === 0 && rng.endOffset === body.childNodes.length;
36291        };
36292        /**
36293         * Fixes selection issues where the caret can be placed between two inline elements like <b>a</b>|<b>b</b>
36294         * this fix will lean the caret right into the closest inline element.
36295         */
36296        const normalizeSelection = () => {
36297            // Normalize selection for example <b>a</b><i>|a</i> becomes <b>a|</b><i>a</i>
36298            editor.on('keyup focusin mouseup', (e) => {
36299                // no point to exclude Ctrl+A, since normalization will still run after Ctrl will be unpressed
36300                // better exclude any key combinations with the modifiers to avoid double normalization
36301                // (also addresses TINY-1130)
36302                // The use of isAllContentSelected addresses TINY-4550
36303                if (!VK.modifierPressed(e) && !isAllContentSelected(editor)) {
36304                    selection.normalize();
36305                }
36306            }, true);
36307        };
36308        /**
36309         * Forces Gecko to render a broken image icon if it fails to load an image.
36310         */
36311        const showBrokenImageIcon = () => {
36312            editor.contentStyles.push('img:-moz-broken {' +
36313                '-moz-force-broken-image-icon:1;' +
36314                'min-width:24px;' +
36315                'min-height:24px' +
36316                '}');
36317        };
36318        /**
36319         * iOS has a bug where it's impossible to type if the document has a touchstart event
36320         * bound and the user touches the document while having the on screen keyboard visible.
36321         *
36322         * The touch event moves the focus to the parent document while having the caret inside the iframe
36323         * this fix moves the focus back into the iframe document.
36324         */
36325        const restoreFocusOnKeyDown = () => {
36326            if (!editor.inline) {
36327                editor.on('keydown', () => {
36328                    if (document.activeElement === document.body) {
36329                        editor.getWin().focus();
36330                    }
36331                });
36332            }
36333        };
36334        /**
36335         * IE 11 has an annoying issue where you can't move focus into the editor
36336         * by clicking on the white area HTML element. We used to be able to fix this with
36337         * the fixCaretSelectionOfDocumentElementOnIe fix. But since M$ removed the selection
36338         * object it's not possible anymore. So we need to hack in a ungly CSS to force the
36339         * body to be at least 150px. If the user clicks the HTML element out side this 150px region
36340         * we simply move the focus into the first paragraph. Not ideal since you loose the
36341         * positioning of the caret but goot enough for most cases.
36342         */
36343        const bodyHeight = () => {
36344            if (!editor.inline) {
36345                editor.contentStyles.push('body {min-height: 150px}');
36346                editor.on('click', (e) => {
36347                    let rng;
36348                    if (e.target.nodeName === 'HTML') {
36349                        // Need to store away non collapsed ranges since the focus call will mess that up see #7382
36350                        rng = editor.selection.getRng();
36351                        editor.getBody().focus();
36352                        editor.selection.setRng(rng);
36353                        editor.selection.normalize();
36354                        editor.nodeChanged();
36355                    }
36356                });
36357            }
36358        };
36359        /**
36360         * Firefox on Mac OS will move the browser back to the previous page if you press CMD+Left arrow.
36361         * You might then loose all your work so we need to block that behavior and replace it with our own.
36362         */
36363        const blockCmdArrowNavigation = () => {
36364            if (isMac) {
36365                editor.on('keydown', (e) => {
36366                    if (VK.metaKeyPressed(e) && !e.shiftKey && (e.keyCode === 37 || e.keyCode === 39)) {
36367                        e.preventDefault();
36368                        // The modify component isn't part of the standard spec, so we need to add the type here
36369                        const selection = editor.selection.getSel();
36370                        selection.modify('move', e.keyCode === 37 ? 'backward' : 'forward', 'lineboundary');
36371                    }
36372                });
36373            }
36374        };
36375        /**
36376         * iOS 7.1 introduced two new bugs:
36377         * 1) It's possible to open links within a contentEditable area by clicking on them.
36378         * 2) If you hold down the finger it will display the link/image touch callout menu.
36379         */
36380        const tapLinksAndImages = () => {
36381            editor.on('click', (e) => {
36382                let elm = e.target;
36383                do {
36384                    if (elm.tagName === 'A') {
36385                        e.preventDefault();
36386                        return;
36387                    }
36388                } while ((elm = elm.parentNode));
36389            });
36390            editor.contentStyles.push('.mce-content-body {-webkit-touch-callout: none}');
36391        };
36392        /**
36393         * iOS Safari and possible other browsers have a bug where it won't fire
36394         * a click event when a contentEditable is focused. This function fakes click events
36395         * by using touchstart/touchend and measuring the time and distance travelled.
36396         */
36397        /*
36398        function touchClickEvent() {
36399          editor.on('touchstart', function(e) {
36400            var elm, time, startTouch, changedTouches;
36401      
36402            elm = e.target;
36403            time = new Date().getTime();
36404            changedTouches = e.changedTouches;
36405      
36406            if (!changedTouches || changedTouches.length > 1) {
36407              return;
36408            }
36409      
36410            startTouch = changedTouches[0];
36411      
36412            editor.once('touchend', function(e) {
36413              var endTouch = e.changedTouches[0], args;
36414      
36415              if (new Date().getTime() - time > 500) {
36416                return;
36417              }
36418      
36419              if (Math.abs(startTouch.clientX - endTouch.clientX) > 5) {
36420                return;
36421              }
36422      
36423              if (Math.abs(startTouch.clientY - endTouch.clientY) > 5) {
36424                return;
36425              }
36426      
36427              args = {
36428                target: elm
36429              };
36430      
36431              each('pageX pageY clientX clientY screenX screenY'.split(' '), function(key) {
36432                args[key] = endTouch[key];
36433              });
36434      
36435              args = editor.dispatch('click', args);
36436      
36437              if (!args.isDefaultPrevented()) {
36438                // iOS WebKit can't place the caret properly once
36439                // you bind touch events so we need to do this manually
36440                // TODO: Expand to the closest word? Touble tap still works.
36441                editor.selection.placeCaretAt(endTouch.clientX, endTouch.clientY);
36442                editor.nodeChanged();
36443              }
36444            });
36445          });
36446        }
36447        */
36448        /**
36449         * WebKit has a bug where it will allow forms to be submitted if they are inside a contentEditable element.
36450         * For example this: <form><button></form>
36451         */
36452        const blockFormSubmitInsideEditor = () => {
36453            editor.on('init', () => {
36454                editor.dom.bind(editor.getBody(), 'submit', (e) => {
36455                    e.preventDefault();
36456                });
36457            });
36458        };
36459        /**
36460         * Sometimes WebKit/Blink generates BR elements with the Apple-interchange-newline class.
36461         *
36462         * Scenario:
36463         *  1) Create a table 2x2.
36464         *  2) Select and copy cells A2-B2.
36465         *  3) Paste and it will add BR element to table cell.
36466         */
36467        const removeAppleInterchangeBrs = () => {
36468            parser.addNodeFilter('br', (nodes) => {
36469                let i = nodes.length;
36470                while (i--) {
36471                    if (nodes[i].attr('class') === 'Apple-interchange-newline') {
36472                        nodes[i].remove();
36473                    }
36474                }
36475            });
36476        };
36477        /
36477/ No-op since Mozilla seems to have fixed the caret repaint issues
36478        const refreshContentEditable = noop;
36479        const isHidden = () => {
36480            if (!isGecko || editor.removed) {
36481                return false;
vendor: 28,648 bytes, lines 36482-37164
36482            }
36483            // Weird, wheres that cursor selection?
36484            const sel = editor.selection.getSel();
36485            return (!sel || !sel.rangeCount || sel.rangeCount === 0);
36486        };
36487        const setupRtc = () => {
36488            if (isWebKit) {
36489                documentElementEditingFocus();
36490                selectControlElements();
36491                blockFormSubmitInsideEditor();
36492                selectAll();
36493                if (isiOS) {
36494                    restoreFocusOnKeyDown();
36495                    bodyHeight();
36496                    tapLinksAndImages();
36497                }
36498            }
36499            if (isGecko) {
36500                focusBody();
36501                setGeckoEditingOptions();
36502                showBrokenImageIcon();
36503                blockCmdArrowNavigation();
36504            }
36505        };
36506        const setup = () => {
36507            // All browsers
36508            removeBlockQuoteOnBackSpace();
36509            emptyEditorWhenDeleting();
36510            // Windows phone will return a range like [body, 0] on mousedown so
36511            // it will always normalize to the wrong location
36512            if (!Env.windowsPhone) {
36513                normalizeSelection();
36514            }
36515            // WebKit
36516            if (isWebKit) {
36517                documentElementEditingFocus();
36518                selectControlElements();
36519                setDefaultBlockType();
36520                blockFormSubmitInsideEditor();
36521                disableBackspaceIntoATable();
36522                removeAppleInterchangeBrs();
36523                // touchClickEvent();
36524                // iOS
36525                if (isiOS) {
36526                    restoreFocusOnKeyDown();
36527                    bodyHeight();
36528                    tapLinksAndImages();
36529                }
36530                else {
36531                    selectAll();
36532                }
36533            }
36534            // Gecko
36535            if (isGecko) {
36536                arrowInFigcaption();
36537                fixFirefoxImageSelection();
36538                removeHrOnBackspace();
36539                focusBody();
36540                removeStylesWhenDeletingAcrossBlockElements();
36541                setGeckoEditingOptions();
36542                addBrAfterLastLinks();
36543                showBrokenImageIcon();
36544                blockCmdArrowNavigation();
36545                disableBackspaceIntoATable();
36546            }
36547        };
36548        if (isRtc(editor)) {
36549            setupRtc();
36550        }
36551        else {
36552            setup();
36553        }
36554        return {
36555            refreshContentEditable,
36556            isHidden
36557        };
36558    };
36559
36560    class ComponentLoadError extends Error {
36561        url;
36562        constructor(message, url) {
36563            super(message);
36564            this.url = url;
36565        }
36566    }
36567    const hostWindowComponentScripts = {};
36568    const loadScript = (url, doc, extraAtts) => {
36569        return new Promise((resolve, reject) => {
36570            const script = SugarElement.fromTag('script');
36571            setAll$1(script, {
36572                type: 'text/javascript',
36573                src: url,
36574                ...extraAtts
36575            });
36576            const clean = () => {
36577                remove$8(script);
36578            };
36579            bind$1(script, 'load', () => {
36580                clean();
36581                resolve();
36582            });
36583            bind$1(script, 'error', () => {
36584                clean();
36585                reject(new Error(`Failed to load script url: ${url}`));
36586            });
36587            append$1(getHead(doc), script);
36588        });
36589    };
36590    const loadComponent = async (url, doc) => {
36591        const extraAtts = ScriptLoader.ScriptLoader.getScriptAttributes(url);
36592        await loadScript(url, doc, extraAtts).catch(() => Promise.reject(new ComponentLoadError(`Failed to load component url: ${url}`, url)));
36593        return url;
36594    };
36595    const loadComponentsForInlineEditor = (componentUrls) => {
36596        return mapToArray(componentUrls, (url, elementName) => {
36597            return get$a(hostWindowComponentScripts, url).getOrThunk(() => {
36598                // Only load the component if it hasn't already been loaded in inline mode the page might have already loaded it
36599                if (isNullable(window.customElements.get(elementName))) {
36600                    const loadPromise = loadComponent(url, getDocument());
36601                    hostWindowComponentScripts[url] = loadPromise;
36602                    return loadPromise;
36603                }
36604                else {
36605                    return Promise.resolve(url);
36606                }
36607            }).catch((err) => {
36608                // Remove from cache if the component failed to load so we can try again later
36609                delete hostWindowComponentScripts[url];
36610                return Promise.reject(err);
36611            });
36612        });
36613    };
36614    const loadComponentsForIframeEditor = (componentUrls, doc) => {
36615        const urls = unique$1(values(componentUrls));
36616        return map$3(urls, (url) => loadComponent(url, SugarElement.fromDom(doc)));
36617    };
36618    const loadComponentsForEditor = (editor) => {
36619        const componentUrls = editor.schema.getComponentUrls();
36620        if (editor.inline) {
36621            return loadComponentsForInlineEditor(componentUrls);
36622        }
36623        else {
36624            return loadComponentsForIframeEditor(componentUrls, editor.getDoc());
36625        }
36626    };
36627    const loadComponentsAsync = async (editor) => {
36628        const loadPromises = loadComponentsForEditor(editor);
36629        const rejected = filter$5(await Promise.allSettled(loadPromises), (r) => r.status === 'rejected');
36630        if (rejected.length > 0) {
36631            each$e(rejected, (rejection) => {
36632                if (rejection.reason instanceof ComponentLoadError) {
36633                    const { url } = rejection.reason;
36634                    componentLoadError(editor, url);
36635                }
36636            });
36637        }
36638    };
36639    const loadComponents = (editor) => {
36640        // Since we are handling the errors in the promise rejections inside the loadComponentsAsync we can ignore the errors here
36641        // eslint-disable-next-line @typescript-eslint/no-floating-promises
36642        loadComponentsAsync(editor);
36643    };
36644
36645    const DOM$6 = DOMUtils.DOM;
36646    const appendStyle = (editor, text) => {
36647        const body = SugarElement.fromDom(editor.getBody());
36648        const container = getStyleContainer(getRootNode(body));
36649        const style = SugarElement.fromTag('style');
36650        set$4(style, 'type', 'text/css');
36651        append$1(style, SugarElement.fromText(text));
36652        append$1(container, style);
36653        editor.on('remove', () => {
36654            remove$8(style);
36655        });
36656    };
36657    const getRootName = (editor) => editor.inline ? editor.getElement().nodeName.toLowerCase() : undefined;
36658    const removeUndefined = (obj) => filter$4(obj, (v) => isUndefined(v) === false);
36659    const mkParserSettings = (editor) => {
36660        const getOption = editor.options.get;
36661        const blobCache = editor.editorUpload.blobCache;
36662        return removeUndefined({
36663            allow_conditional_comments: getOption('allow_conditional_comments'),
36664            allow_html_data_urls: getOption('allow_html_data_urls'),
36665            allow_svg_data_urls: getOption('allow_svg_data_urls'),
36666            allow_html_in_named_anchor: getOption('allow_html_in_named_anchor'),
36667            allow_script_urls: getOption('allow_script_urls'),
36668            allow_html_in_comments: getOption('allow_html_in_comments'),
36669            allow_mathml_annotation_encodings: getOption('allow_mathml_annotation_encodings'),
36670            allow_unsafe_link_target: getOption('allow_unsafe_link_target'),
36671            convert_unsafe_embeds: getOption('convert_unsafe_embeds'),
36672            convert_fonts_to_spans: getOption('convert_fonts_to_spans'),
36673            extended_mathml_attributes: getOption('extended_mathml_attributes'),
36674            extended_mathml_elements: getOption('extended_mathml_elements'),
36675            fix_list_elements: getOption('fix_list_elements'),
36676            font_size_legacy_values: getOption('font_size_legacy_values'),
36677            forced_root_block: getOption('forced_root_block'),
36678            forced_root_block_attrs: getOption('forced_root_block_attrs'),
36679            preserve_cdata: getOption('preserve_cdata'),
36680            inline_styles: getOption('inline_styles'),
36681            root_name: getRootName(editor),
36682            sandbox_iframes: getOption('sandbox_iframes'),
36683            sandbox_iframes_exclusions: getSandboxIframesExclusions(editor),
36684            sanitize: getOption('xss_sanitization'),
36685            validate: true,
36686            blob_cache: blobCache,
36687            document: editor.getDoc()
36688        });
36689    };
36690    const mkSchemaSettings = (editor) => {
36691        const getOption = editor.options.get;
36692        return removeUndefined({
36693            custom_elements: getOption('custom_elements'),
36694            extended_valid_elements: getOption('extended_valid_elements'),
36695            invalid_elements: getOption('invalid_elements'),
36696            invalid_styles: getOption('invalid_styles'),
36697            schema: getOption('schema'),
36698            valid_children: getOption('valid_children'),
36699            valid_classes: getOption('valid_classes'),
36700            valid_elements: getOption('valid_elements'),
36701            valid_styles: getOption('valid_styles'),
36702            verify_html: getOption('verify_html'),
36703            padd_empty_block_inline_children: getOption('format_empty_lines')
36704        });
36705    };
36706    const mkSerializerSettings = (editor) => {
36707        const getOption = editor.options.get;
36708        return {
36709            ...mkParserSettings(editor),
36710            ...mkSchemaSettings(editor),
36711            ...removeUndefined({
36712                // SerializerSettings
36713                remove_trailing_brs: getOption('remove_trailing_brs'),
36714                pad_empty_with_br: getOption('pad_empty_with_br'),
36715                url_converter: getOption('url_converter'),
36716                url_converter_scope: getOption('url_converter_scope'),
36717                // Writer settings
36718                element_format: getOption('element_format'),
36719                entities: getOption('entities'),
36720                entity_encoding: getOption('entity_encoding'),
36721                indent: getOption('indent'),
36722                indent_after: getOption('indent_after'),
36723                indent_before: getOption('indent_before')
36724            })
36725        };
36726    };
36727    const createParser = (editor) => {
36728        const parser = DomParser(mkParserSettings(editor), editor.schema);
36729        // Convert src and href into data-mce-src, data-mce-href and data-mce-style
36730        parser.addAttributeFilter('src,href,style,tabindex', (nodes, name) => {
36731            const dom = editor.dom;
36732            const internalName = 'data-mce-' + name;
36733            let i = nodes.length;
36734            while (i--) {
36735                const node = nodes[i];
36736                let value = node.attr(name);
36737                // Add internal attribute if we need to we don't on a refresh of the document
36738                if (value && !node.attr(internalName)) {
36739                    // Don't duplicate these since they won't get modified by any browser
36740                    if (value.indexOf('data:') === 0 || value.indexOf('blob:') === 0) {
36741                        continue;
36742                    }
36743                    if (name === 'style') {
36744                        value = dom.serializeStyle(dom.parseStyle(value), node.name);
36745                        if (!value.length) {
36746                            value = null;
36747                        }
36748                        node.attr(internalName, value);
36749                        node.attr(name, value);
36750                    }
36751                    else if (name === 'tabindex') {
36752                        node.attr(internalName, value);
36753                        node.attr(name, null);
36754                    }
36755                    else {
36756                        node.attr(internalName, editor.convertURL(value, name, node.name));
36757                    }
36758                }
36759            }
36760        });
36761        // Keep scripts from executing
36762        parser.addNodeFilter('script', (nodes) => {
36763            let i = nodes.length;
36764            while (i--) {
36765                const node = nodes[i];
36766                const type = node.attr('type') || 'no/type';
36767                if (type.indexOf('mce-') !== 0) {
36768                    node.attr('type', 'mce-' + type);
36769                }
36770            }
36771        });
36772        if (shouldPreserveCData(editor)) {
36773            parser.addNodeFilter('#cdata', (nodes) => {
36774                let i = nodes.length;
36775                while (i--) {
36776                    const node = nodes[i];
36777                    node.type = 8;
36778                    node.name = '#comment';
36779                    node.value = '[CDATA[' + editor.dom.encode(node.value ?? '') + ']]';
36780                }
36781            });
36782        }
36783        parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', (nodes) => {
36784            let i = nodes.length;
36785            const nonEmptyElements = editor.schema.getNonEmptyElements();
36786            while (i--) {
36787                const node = nodes[i];
36788                if (node.isEmpty(nonEmptyElements) && node.getAll('br').length === 0) {
36789                    node.append(new AstNode('br', 1));
36790                }
36791            }
36792        });
36793        return parser;
36794    };
36795    const autoFocus = (editor) => {
36796        const autoFocus = getAutoFocus(editor);
36797        if (autoFocus) {
36798            Delay.setEditorTimeout(editor, () => {
36799                let focusEditor;
36800                if (autoFocus === true) {
36801                    focusEditor = editor;
36802                }
36803                else {
36804                    focusEditor = editor.editorManager.get(autoFocus);
36805                }
36806                if (focusEditor && !focusEditor.destroyed) {
36807                    focusEditor.focus();
36808                    focusEditor.selection.scrollIntoView();
36809                }
36810            }, 100);
36811        }
36812    };
36813    const moveSelectionToFirstCaretPosition = (editor) => {
36814        // If not inline and no useful selection, we want to set selection to the first valid cursor position
36815        // We don't do this on inline because then it selects the editor container
36816        // This must run AFTER editor.focus!
36817        const root = editor.dom.getRoot();
36818        if (!editor.inline && (!hasAnyRanges(editor) || editor.selection.getStart(true) === root)) {
36819            firstPositionIn(root).each((pos) => {
36820                const node = pos.getNode();
36821                // If a table is the first caret pos, then walk down one more level
36822                const caretPos = isTable$2(node) ? firstPositionIn(node).getOr(pos) : pos;
36823                editor.selection.setRng(caretPos.toRange());
36824            });
36825        }
36826    };
36827    const initEditor = (editor) => {
36828        editor.bindPendingEventDelegates();
36829        editor.initialized = true;
36830        fireInit(editor);
36831        editor.focus(true);
36832        moveSelectionToFirstCaretPosition(editor);
36833        editor.nodeChanged({ initial: true });
36834        const initInstanceCallback = getInitInstanceCallback(editor);
36835        if (isFunction(initInstanceCallback)) {
36836            initInstanceCallback.call(editor, editor);
36837        }
36838        autoFocus(editor);
36839        if (isDisabled(editor)) {
36840            toggleDisabled(editor, true);
36841        }
36842    };
36843    const getStyleSheetLoader$1 = (editor) => editor.inline ? editor.ui.styleSheetLoader : editor.dom.styleSheetLoader;
36844    const makeStylesheetLoadingPromises = (editor, css, framedFonts) => {
36845        const { pass: bundledCss, fail: normalCss } = partition$2(css, (key) => tinymce.Resource.has(key));
36846        const bundledPromises = bundledCss.map((key) => {
36847            const css = tinymce.Resource.get(key);
36848            if (isString(css)) {
36849                return Promise.resolve(getStyleSheetLoader$1(editor).loadRawCss(key, css));
36850            }
36851            return Promise.resolve();
36852        });
36853        const promises = [...bundledPromises,
36854            getStyleSheetLoader$1(editor).loadAll(normalCss),
36855        ];
36856        if (editor.inline) {
36857            return promises;
36858        }
36859        else {
36860            return promises.concat([
36861                editor.ui.styleSheetLoader.loadAll(framedFonts)
36862            ]);
36863        }
36864    };
36865    const loadContentCss = (editor) => {
36866        const styleSheetLoader = getStyleSheetLoader$1(editor);
36867        const fontCss = getFontCss(editor);
36868        const css = editor.contentCSS;
36869        const removeCss = () => {
36870            styleSheetLoader.unloadAll(css);
36871            if (!editor.inline) {
36872                editor.ui.styleSheetLoader.unloadAll(fontCss);
36873            }
36874        };
36875        const loaded = () => {
36876            if (editor.removed) {
36877                removeCss();
36878            }
36879            else {
36880                editor.on('remove', removeCss);
36881            }
36882        };
36883        // Add editor specific CSS styles
36884        if (editor.contentStyles.length > 0) {
36885            let contentCssText = '';
36886            Tools.each(editor.contentStyles, (style) => {
36887                contentCssText += style + '\r\n';
36888            });
36889            editor.dom.addStyle(contentCssText);
36890        }
36891        // Load all stylesheets
36892        const allStylesheets = Promise.all(makeStylesheetLoadingPromises(editor, css, fontCss)).then(loaded).catch(loaded);
36893        // Append specified content CSS last
36894        const contentStyle = getContentStyle(editor);
36895        if (contentStyle) {
36896            appendStyle(editor, contentStyle);
36897        }
36898        return allStylesheets;
36899    };
36900    const preInit = (editor) => {
36901        const doc = editor.getDoc(), body = editor.getBody();
36902        firePreInit(editor);
36903        if (!shouldBrowserSpellcheck(editor)) {
36904            doc.body.spellcheck = false; // Gecko
36905            DOM$6.setAttrib(body, 'spellcheck', 'false');
36906        }
36907        editor.quirks = Quirks(editor);
36908        firePostRender(editor);
36909        const directionality = getDirectionality(editor);
36910        if (directionality !== undefined) {
36911            body.dir = directionality;
36912        }
36913        const protect = getProtect(editor);
36914        if (protect) {
36915            editor.on('BeforeSetContent', (e) => {
36916                Tools.each(protect, (pattern) => {
36917                    e.content = e.content.replace(pattern, (str) => {
36918                        return '<!--mce:protected ' + escape(str) + '-->';
36919                    });
36920                });
36921            });
36922        }
36923        editor.on('SetContent', () => {
36924            editor.addVisual(editor.getBody());
36925        });
36926        editor.on('compositionstart compositionend', (e) => {
36927            editor.composing = e.type === 'compositionstart';
36928        });
36929    };
36930    const loadInitialContent = (editor) => {
36931        if (!isRtc(editor)) {
36932            editor.load({ initial: true, format: 'html' });
36933        }
36934        editor.startContent = editor.getContent({ format: 'raw' });
36935    };
36936    const initEditorWithInitialContent = (editor) => {
36937        if (editor.removed !== true) {
36938            loadInitialContent(editor);
36939            initEditor(editor);
36940        }
36941    };
36942    const startProgress = (editor) => {
36943        let canceled = false;
36944        const progressTimeout = setTimeout(() => {
36945            if (!canceled) {
36946                editor.setProgressState(true);
36947            }
36948        }, 500);
36949        return () => {
36950            clearTimeout(progressTimeout);
36951            canceled = true;
36952            editor.setProgressState(false);
36953        };
36954    };
36955    const contentBodyLoaded = (editor) => {
36956        const targetElm = editor.getElement();
36957        let doc = editor.getDoc();
36958        if (editor.inline) {
36959            DOM$6.addClass(targetElm, 'mce-content-body');
36960            editor.contentDocument = doc = document;
36961            editor.contentWindow = window;
36962            editor.bodyElement = targetElm;
36963            editor.contentAreaContainer = targetElm;
36964        }
36965        // It will not steal focus while setting contentEditable
36966        const body = editor.getBody();
36967        // disabled isn't valid on all body elements, so need to cast here
36968        // TODO: See if we actually need to disable/re-enable here
36969        body.disabled = true;
36970        editor.readonly = isReadOnly$1(editor);
36971        editor._editableRoot = hasEditableRoot$1(editor);
36972        if (!isDisabled$1(editor) && editor.hasEditableRoot()) {
36973            if (editor.inline && DOM$6.getStyle(body, 'position', true) === 'static') {
36974                body.style.position = 'relative';
36975            }
36976            body.contentEditable = 'true';
36977        }
36978        body.disabled = false;
36979        editor.editorUpload = EditorUpload(editor);
36980        editor.schema = Schema(mkSchemaSettings(editor));
36981        editor.dom = DOMUtils(doc, {
36982            keep_values: true,
36983            // Note: Don't bind here, as the binding is handled via the `url_converter_scope`
36984            // eslint-disable-next-line @typescript-eslint/unbound-method
36985            url_converter: editor.convertURL,
36986            url_converter_scope: editor,
36987            update_styles: true,
36988            root_element: editor.inline ? editor.getBody() : null,
36989            collect: editor.inline,
36990            schema: editor.schema,
36991            contentCssCors: shouldUseContentCssCors(editor),
36992            referrerPolicy: getReferrerPolicy(editor),
36993            crossOrigin: getCrossOrigin(editor),
36994            onSetAttrib: (e) => {
36995                editor.dispatch('SetAttrib', e);
36996            },
36997        });
36998        editor.parser = createParser(editor);
36999        editor.serializer = DomSerializer(mkSerializerSettings(editor), editor);
37000        editor.selection = EditorSelection(editor.dom, editor.getWin(), editor.serializer, editor);
37001        editor.annotator = Annotator(editor);
37002        editor.formatter = Formatter(editor);
37003        editor.undoManager = UndoManager(editor);
37004        editor._nodeChangeDispatcher = new NodeChange(editor);
37005        editor._selectionOverrides = SelectionOverrides(editor);
37006        setup$b(editor);
37007        setup$u(editor);
37008        setup$6(editor);
37009        if (shouldAllowNonEditable(editor)) {
37010            setup$s(editor);
37011        }
37012        if (!isRtc(editor)) {
37013            setup$5(editor);
37014            setup$1(editor);
37015        }
37016        const caret = setup$g(editor);
37017        setup$v(editor, caret);
37018        setup$t(editor);
37019        setup$w(editor);
37020        setup$7(editor, caret);
37021        const setupRtcThunk = setup$z(editor);
37022        preInit(editor);
37023        loadComponents(editor);
37024        setupRtcThunk.fold(() => {
37025            const cancelProgress = startProgress(editor);
37026            // eslint-disable-next-line @typescript-eslint/no-floating-promises
37027            loadContentCss(editor).then(() => {
37028                initEditorWithInitialContent(editor);
37029                cancelProgress();
37030            });
37031        }, (setupRtc) => {
37032            editor.setProgressState(true);
37033            // eslint-disable-next-line @typescript-eslint/no-floating-promises
37034            loadContentCss(editor).then(() => {
37035                setupRtc().then((_rtcMode) => {
37036                    editor.setProgressState(false);
37037                    initEditorWithInitialContent(editor);
37038                    bindEvents(editor);
37039                }, (err) => {
37040                    editor.notificationManager.open({ type: 'error', text: String(err) });
37041                    initEditorWithInitialContent(editor);
37042                    bindEvents(editor);
37043                });
37044            });
37045        });
37046    };
37047
37048    const DOM$5 = DOMUtils.DOM;
37049    const createIframeElement = (id, title, customAttrs, tabindex) => {
37050        const iframe = SugarElement.fromTag('iframe');
37051        // This can also be explicitly set by customAttrs, so do this first
37052        tabindex.each((t) => set$4(iframe, 'tabindex', t));
37053        setAll$1(iframe, customAttrs);
37054        setAll$1(iframe, {
37055            id: id + '_ifr',
37056            frameBorder: '0',
37057            allowTransparency: 'true',
37058            ...Env.browser.isFirefox() ? { title } : {}
37059        });
37060        add$2(iframe, 'tox-edit-area__iframe');
37061        return iframe;
37062    };
37063    const getIframeHtml = (editor) => {
37064        let iframeHTML = getDocType(editor) + '<html><head>';
37065        // We only need to override paths if we have to
37066        // IE has a bug where it remove site absolute urls to relative ones if this is specified
37067        if (getDocumentBaseUrl(editor) !== editor.editorManager.documentBaseURL) {
37068            iframeHTML += '<base href="' + editor.documentBaseURI.getURI() + '" />';
37069        }
37070        iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
37071        const bodyId = getBodyId(editor);
37072        const bodyClass = getBodyClass(editor);
37073        const translatedAriaText = editor.translate(getIframeAriaText(editor));
37074        const iframeBodyAriaLabel = Env.browser.isFirefox() ? '' : `aria-label="${translatedAriaText}"`;
37075        if (getContentSecurityPolicy(editor)) {
37076            iframeHTML += '<meta http-equiv="Content-Security-Policy" content="' + getContentSecurityPolicy(editor) + '" />';
37077        }
37078        iframeHTML += '</head>' +
37079            `<body id="${bodyId}" class="mce-content-body ${bodyClass}" data-id="${editor.id}" ${iframeBodyAriaLabel}>` +
37080            '<br>' +
37081            '</body></html>';
37082        return iframeHTML;
37083    };
37084    const createIframe = (editor, boxInfo) => {
37085        const iframeTitle = getIframeAriaText(editor);
37086        const translatedTitle = editor.translate(iframeTitle);
37087        const tabindex = getOpt(SugarElement.fromDom(editor.getElement()), 'tabindex').bind(toInt);
37088        const ifr = createIframeElement(editor.id, translatedTitle, getIframeAttrs(editor), tabindex).dom;
37089        ifr.onload = () => {
37090            ifr.onload = null;
37091            editor.dispatch('load');
37092        };
37093        editor.contentAreaContainer = boxInfo.iframeContainer;
37094        editor.iframeElement = ifr;
37095        editor.iframeHTML = getIframeHtml(editor);
37096        DOM$5.add(boxInfo.iframeContainer, ifr);
37097    };
37098    const setupIframeBody = (editor) => {
37099        // Setup iframe body
37100        const iframe = editor.iframeElement;
37101        const ready = () => {
37102            // Set the content document, now that it is available
37103            editor.contentDocument = iframe.contentDocument;
37104            // Continue to init the editor
37105            contentBodyLoaded(editor);
37106        };
37107        // TINY-8916: Firefox has a bug in its srcdoc implementation that prevents cookies being sent so unfortunately we need
37108        // to fallback to legacy APIs to load the iframe content. See https://bugzilla.mozilla.org/show_bug.cgi?id=1741489
37109        if (shouldUseDocumentWrite(editor) || Env.browser.isFirefox()) {
37110            const doc = editor.getDoc();
37111            doc.open();
37112            doc.write(editor.iframeHTML);
37113            doc.close();
37114            ready();
37115        }
37116        else {
37117            const binder = bind$1(SugarElement.fromDom(iframe), 'load', () => {
37118                binder.unbind();
37119                ready();
37120            });
37121            iframe.srcdoc = editor.iframeHTML;
37122        }
37123    };
37124    const init$1 = (editor, boxInfo) => {
37125        createIframe(editor, boxInfo);
37126        if (boxInfo.editorContainer) {
37127            boxInfo.editorContainer.style.display = editor.orgDisplay;
37128            editor.hidden = DOM$5.isHidden(boxInfo.editorContainer);
37129        }
37130        editor.getElement().style.display = 'none';
37131        DOM$5.setAttrib(editor.id, 'aria-hidden', 'true');
37132        // Restore visibility on target element
37133        editor.getElement().style.visibility = editor.orgVisibility;
37134        setupIframeBody(editor);
37135    };
37136
37137    const DOM$4 = DOMUtils.DOM;
37138    const initPlugin = (editor, initializedPlugins, plugin) => {
37139        const Plugin = PluginManager.get(plugin);
37140        const pluginUrl = PluginManager.urls[plugin] || editor.editorManager.documentBaseURL.replace(/\/$/, '');
37141        plugin = Tools.trim(plugin);
37142        if (Plugin && Tools.inArray(initializedPlugins, plugin) === -1) {
37143            if (editor.plugins[plugin]) {
37144                return;
37145            }
37146            try {
37147                const pluginInstance = Plugin(editor, pluginUrl) || {};
37148                editor.plugins[plugin] = pluginInstance;
37149                if (isFunction(pluginInstance.init)) {
37150                    pluginInstance.init(editor, pluginUrl);
37151                    initializedPlugins.push(plugin);
37152                }
37153            }
37154            catch (e) {
37155                pluginInitError(editor, plugin, e);
37156            }
37157        }
37158    };
37159    const initTooltipClosing = (editor) => {
37160        const closeTooltipsListener = (event) => {
37161            if (event.keyCode === VK.ESC && !event.defaultPrevented) {
37162                if (fireCloseTooltips(editor).isDefaultPrevented()) {
37163                    event.preventDefault();
37164                }
vendor: 23,922 bytes, lines 37164-37742
37164
37165            }
37166        };
37167        document.addEventListener('keyup', closeTooltipsListener);
37168        if (!editor.inline) {
37169            editor.on('keyup', closeTooltipsListener);
37170        }
37171        editor.on('remove', () => {
37172            document.removeEventListener('keyup', closeTooltipsListener);
37173            if (!editor.inline) {
37174                editor.off('keyup', closeTooltipsListener);
37175            }
37176        });
37177    };
37178    const trimLegacyPrefix = (name) => {
37179        // Themes and plugins can be prefixed with - to prevent them from being lazy loaded
37180        return name.replace(/^\-/, '');
37181    };
37182    const initPlugins = (editor) => {
37183        const initializedPlugins = [];
37184        each$e(getPlugins(editor), (name) => {
37185            initPlugin(editor, initializedPlugins, trimLegacyPrefix(name));
37186        });
37187    };
37188    const initIcons = (editor) => {
37189        const iconPackName = Tools.trim(getIconPackName(editor));
37190        const currentIcons = editor.ui.registry.getAll().icons;
37191        const loadIcons = {
37192            ...IconManager.get('default').icons,
37193            ...IconManager.get(iconPackName).icons
37194        };
37195        each$d(loadIcons, (svgData, icon) => {
37196            // Don't override an icon registered manually
37197            if (!has$2(currentIcons, icon)) {
37198                editor.ui.registry.addIcon(icon, svgData);
37199            }
37200        });
37201    };
37202    const initTheme = (editor) => {
37203        const theme = getTheme(editor);
37204        if (isString(theme)) {
37205            const Theme = ThemeManager.get(theme);
37206            editor.theme = Theme(editor, ThemeManager.urls[theme]) || {};
37207            if (isFunction(editor.theme.init)) {
37208                editor.theme.init(editor, ThemeManager.urls[theme] || editor.editorManager.documentBaseURL.replace(/\/$/, ''));
37209            }
37210        }
37211        else {
37212            // Theme set to false or null doesn't produce a theme api
37213            editor.theme = {};
37214        }
37215    };
37216    const initModel = (editor) => {
37217        const model = getModel(editor);
37218        const Model = ModelManager.get(model);
37219        editor.model = Model(editor, ModelManager.urls[model]);
37220    };
37221    const initLicenseKeyManager = (editor) => {
37222        LicenseKeyManagerLoader.init(editor);
37223    };
37224    const renderFromLoadedTheme = (editor) => {
37225        // Render UI
37226        const render = editor.theme.renderUI;
37227        return render ? render() : renderThemeFalse(editor);
37228    };
37229    const renderFromThemeFunc = (editor) => {
37230        const elm = editor.getElement();
37231        const theme = getTheme(editor);
37232        const info = theme(editor, elm);
37233        if (info.editorContainer.nodeType) {
37234            info.editorContainer.id = info.editorContainer.id || editor.id + '_parent';
37235        }
37236        if (info.iframeContainer && info.iframeContainer.nodeType) {
37237            info.iframeContainer.id = info.iframeContainer.id || editor.id + '_iframecontainer';
37238        }
37239        info.height = info.iframeHeight ? info.iframeHeight : elm.offsetHeight;
37240        return info;
37241    };
37242    const createThemeFalseResult = (element, iframe) => {
37243        return {
37244            editorContainer: element,
37245            iframeContainer: iframe,
37246            api: {}
37247        };
37248    };
37249    const renderThemeFalseIframe = (targetElement) => {
37250        const iframeContainer = DOM$4.create('div');
37251        DOM$4.insertAfter(iframeContainer, targetElement);
37252        return createThemeFalseResult(iframeContainer, iframeContainer);
37253    };
37254    const renderThemeFalse = (editor) => {
37255        const targetElement = editor.getElement();
37256        return editor.inline ? createThemeFalseResult(null) : renderThemeFalseIframe(targetElement);
37257    };
37258    const renderThemeUi = (editor) => {
37259        const elm = editor.getElement();
37260        editor.orgDisplay = elm.style.display;
37261        if (isString(getTheme(editor))) {
37262            return renderFromLoadedTheme(editor);
37263        }
37264        else if (isFunction(getTheme(editor))) {
37265            return renderFromThemeFunc(editor);
37266        }
37267        else {
37268            return renderThemeFalse(editor);
37269        }
37270    };
37271    const augmentEditorUiApi = (editor, api) => {
37272        const uiApiFacade = {
37273            show: Optional.from(api.show).getOr(noop),
37274            hide: Optional.from(api.hide).getOr(noop),
37275            isEnabled: Optional.from(api.isEnabled).getOr(always),
37276            setEnabled: (state) => {
37277                const shouldSkip = state && (editor.mode.get() === 'readonly' || isDisabled(editor));
37278                if (!shouldSkip) {
37279                    Optional.from(api.setEnabled).each((f) => f(state));
37280                }
37281            }
37282        };
37283        editor.ui = { ...editor.ui, ...uiApiFacade };
37284    };
37285    const init = async (editor) => {
37286        editor.dispatch('ScriptsLoaded');
37287        initIcons(editor);
37288        initTooltipClosing(editor);
37289        initTheme(editor);
37290        initModel(editor);
37291        initLicenseKeyManager(editor);
37292        initPlugins(editor);
37293        const renderInfo = await renderThemeUi(editor);
37294        augmentEditorUiApi(editor, Optional.from(renderInfo.api).getOr({}));
37295        editor.editorContainer = renderInfo.editorContainer;
37296        appendContentCssFromSettings(editor);
37297        if (editor.inline) {
37298            contentBodyLoaded(editor);
37299        }
37300        else {
37301            init$1(editor, {
37302                editorContainer: renderInfo.editorContainer,
37303                iframeContainer: renderInfo.iframeContainer
37304            });
37305        }
37306    };
37307
37308    const DOM$3 = DOMUtils.DOM;
37309    const hasSkipLoadPrefix = (name) => name.charAt(0) === '-';
37310    const loadLanguage = (scriptLoader, editor) => {
37311        const languageCode = getLanguageCode(editor);
37312        const languageUrl = getLanguageUrl(editor);
37313        if (!I18n.hasCode(languageCode) && languageCode !== 'en') {
37314            const url = isNotEmpty(languageUrl) ? languageUrl : `${editor.editorManager.baseURL}/langs/${languageCode}.js`;
37315            scriptLoader.add(url).catch(() => {
37316                languageLoadError(editor, url, languageCode);
37317            });
37318        }
37319    };
37320    const loadTheme = (editor, suffix) => {
37321        const theme = getTheme(editor);
37322        if (isString(theme) && !hasSkipLoadPrefix(theme) && !has$2(ThemeManager.urls, theme)) {
37323            const themeUrl = getThemeUrl(editor);
37324            const url = themeUrl ? editor.documentBaseURI.toAbsolute(themeUrl) : `themes/${theme}/theme${suffix}.js`;
37325            ThemeManager.load(theme, url).catch(() => {
37326                themeLoadError(editor, url, theme);
37327            });
37328        }
37329    };
37330    const loadModel = (editor, suffix) => {
37331        // Special case the 'wait for model' code if a plugin is responsible for it
37332        // as the plugin will provide the instance instead
37333        const model = getModel(editor);
37334        if (model !== 'plugin' && !has$2(ModelManager.urls, model)) {
37335            const modelUrl = getModelUrl(editor);
37336            const url = isString(modelUrl) ? editor.documentBaseURI.toAbsolute(modelUrl) : `models/${model}/model${suffix}.js`;
37337            ModelManager.load(model, url).catch(() => {
37338                modelLoadError(editor, url, model);
37339            });
37340        }
37341    };
37342    const loadLicenseKeyManager = (editor, suffix) => {
37343        LicenseKeyManagerLoader.load(editor, suffix);
37344    };
37345    const getIconsUrlMetaFromUrl = (editor) => Optional.from(getIconsUrl(editor))
37346        .filter(isNotEmpty)
37347        .map((url) => ({
37348        url,
37349        name: Optional.none()
37350    }));
37351    const getIconsUrlMetaFromName = (editor, name, suffix) => Optional.from(name)
37352        .filter((name) => isNotEmpty(name) && !IconManager.has(name))
37353        .map((name) => ({
37354        url: `${editor.editorManager.baseURL}/icons/${name}/icons${suffix}.js`,
37355        name: Optional.some(name)
37356    }));
37357    const loadIcons = (scriptLoader, editor, suffix) => {
37358        const defaultIconsUrl = getIconsUrlMetaFromName(editor, 'default', suffix);
37359        const customIconsUrl = getIconsUrlMetaFromUrl(editor).orThunk(() => getIconsUrlMetaFromName(editor, getIconPackName(editor), ''));
37360        each$e(cat([defaultIconsUrl, customIconsUrl]), (urlMeta) => {
37361            scriptLoader.add(urlMeta.url).catch(() => {
37362                iconsLoadError(editor, urlMeta.url, urlMeta.name.getOrUndefined());
37363            });
37364        });
37365    };
37366    const loadPlugins = (editor, suffix) => {
37367        const loadPlugin = (name, url) => {
37368            // If licensekeymanager is included in the plugins list
37369            // or through external_plugins, skip it
37370            if (name === 'licensekeymanager') {
37371                return;
37372            }
37373            PluginManager.load(name, url).catch(() => {
37374                pluginLoadError(editor, url, name);
37375            });
37376        };
37377        each$d(getExternalPlugins$1(editor), (url, name) => {
37378            loadPlugin(name, url);
37379            editor.options.set('plugins', getPlugins(editor).concat(name));
37380        });
37381        each$e(getPlugins(editor), (plugin) => {
37382            plugin = Tools.trim(plugin);
37383            if (plugin && !PluginManager.urls[plugin] && !hasSkipLoadPrefix(plugin)) {
37384                loadPlugin(plugin, `plugins/${plugin}/plugin${suffix}.js`);
37385            }
37386        });
37387    };
37388    const isThemeLoaded = (editor) => {
37389        const theme = getTheme(editor);
37390        return !isString(theme) || isNonNullable(ThemeManager.get(theme));
37391    };
37392    const isModelLoaded = (editor) => {
37393        const model = getModel(editor);
37394        return isNonNullable(ModelManager.get(model));
37395    };
37396    const loadScripts = (editor, suffix) => {
37397        const scriptLoader = ScriptLoader.ScriptLoader;
37398        const initEditor = () => {
37399            // If the editor has been destroyed or the theme, model haven't loaded then
37400            // don't continue to load the editor
37401            if (!editor.removed &&
37402                isThemeLoaded(editor) &&
37403                isModelLoaded(editor)) {
37404                // eslint-disable-next-line @typescript-eslint/no-floating-promises
37405                init(editor);
37406            }
37407        };
37408        loadTheme(editor, suffix);
37409        loadModel(editor, suffix);
37410        loadLicenseKeyManager(editor, suffix);
37411        loadLanguage(scriptLoader, editor);
37412        loadIcons(scriptLoader, editor, suffix);
37413        loadPlugins(editor, suffix);
37414        scriptLoader.loadQueue().then(initEditor, initEditor);
37415    };
37416    const getStyleSheetLoader = (element, editor) => instance.forElement(element, {
37417        contentCssCors: hasContentCssCors(editor),
37418        referrerPolicy: getReferrerPolicy(editor)
37419    });
37420    const render = (editor) => {
37421        const id = editor.id;
37422        // The user might have bundled multiple language packs so we need to switch the active code to the user specified language
37423        I18n.setCode(getLanguageCode(editor));
37424        const readyHandler = () => {
37425            DOM$3.unbind(window, 'ready', readyHandler);
37426            editor.render();
37427        };
37428        // Page is not loaded yet, wait for it
37429        if (!EventUtils.Event.domLoaded) {
37430            DOM$3.bind(window, 'ready', readyHandler);
37431            return;
37432        }
37433        // Element not found, then skip initialization
37434        if (!editor.getElement()) {
37435            return;
37436        }
37437        // snapshot the element we're going to render to
37438        const element = SugarElement.fromDom(editor.getElement());
37439        const snapshot = clone$4(element);
37440        editor.on('remove', () => {
37441            eachr(element.dom.attributes, (attr) => remove$9(element, attr.name));
37442            setAll$1(element, snapshot);
37443        });
37444        editor.ui.styleSheetLoader = getStyleSheetLoader(element, editor);
37445        // Hide target element early to prevent content flashing
37446        if (!isInline$2(editor)) {
37447            editor.orgVisibility = editor.getElement().style.visibility;
37448            editor.getElement().style.visibility = 'hidden';
37449        }
37450        else {
37451            editor.inline = true;
37452        }
37453        // TODO: Investigate the types here
37454        const form = editor.getElement().form || DOM$3.getParent(id, 'form');
37455        if (form) {
37456            editor.formElement = form;
37457            // Add hidden input for non input elements inside form elements
37458            if (hasHiddenInput(editor) && !isTextareaOrInput(editor.getElement())) {
37459                DOM$3.insertAfter(DOM$3.create('input', { type: 'hidden', name: id }), id);
37460                editor.hasHiddenInput = true;
37461            }
37462            // Pass submit/reset from form to editor instance
37463            editor.formEventDelegate = (e) => {
37464                editor.dispatch(e.type, e);
37465            };
37466            DOM$3.bind(form, 'submit reset', editor.formEventDelegate);
37467            // Reset contents in editor when the form is reset
37468            editor.on('reset', () => {
37469                editor.resetContent();
37470            });
37471            // Check page uses id="submit" or name="submit" for it's submit button
37472            if (shouldPatchSubmit(editor) && !form.submit.nodeType && !form.submit.length && !form._mceOldSubmit) {
37473                form._mceOldSubmit = form.submit;
37474                form.submit = () => {
37475                    editor.editorManager.triggerSave();
37476                    editor.setDirty(false);
37477                    return form._mceOldSubmit(form);
37478                };
37479            }
37480        }
37481        editor.windowManager = WindowManager(editor);
37482        editor.notificationManager = NotificationManager(editor);
37483        if (isEncodingXml(editor)) {
37484            editor.on('GetContent', (e) => {
37485                if (e.save) {
37486                    e.content = DOM$3.encode(e.content);
37487                }
37488            });
37489        }
37490        if (shouldAddFormSubmitTrigger(editor)) {
37491            editor.on('submit', () => {
37492                if (editor.initialized) {
37493                    editor.save();
37494                }
37495            });
37496        }
37497        if (shouldAddUnloadTrigger(editor)) {
37498            editor._beforeUnload = () => {
37499                if (editor.initialized && !editor.destroyed && !editor.isHidden()) {
37500                    editor.save({ format: 'raw', no_events: true, set_dirty: false });
37501                }
37502            };
37503            editor.editorManager.on('BeforeUnload', editor._beforeUnload);
37504        }
37505        editor.editorManager.add(editor);
37506        loadScripts(editor, editor.suffix);
37507    };
37508
37509    const AvatarColors = [
37510        '#E41B60', // Pink
37511        '#AD1457', // Dark Pink
37512        '#1939EC', // Indigo
37513        '#001CB5', // Dark Indigo
37514        '#648000', // Lime
37515        '#465B00', // Dark Lime
37516        '#006CE7', // Blue
37517        '#0054B4', // Dark Blue
37518        '#00838F', // Cyan
37519        '#006064', // Dark Cyan
37520        '#00866F', // Turquoise
37521        '#004D40', // Dark Turquoise
37522        '#51742F', // Green
37523        '#385021', // Dark Green
37524        '#CF4900', // Orange
37525        '#A84600', // Dark Orange
37526        '#CC0000', // Red
37527        '#6A1B9A', // Dark Red
37528        '#9C27B0', // Purple
37529        '#6A00AB', // Dark Purple
37530        '#3041BA', // Navy Blue
37531        '#0A1877', // Dark Navy Blue
37532        '#774433', // Brown
37533        '#452B24', // Dark Brown
37534        '#607D8B', // Blue Gray
37535        '#455A64', // Dark Blue Gray
37536    ];
37537    const getFirstChar = (name) => {
37538        if (Intl.Segmenter) {
37539            const segmenter = new Intl.Segmenter();
37540            const iterator = segmenter.segment(name)[Symbol.iterator]();
37541            return `${iterator.next().value?.segment}`;
37542        }
37543        else {
37544            return name.trim()[0];
37545        }
37546    };
37547    /* For a given string returns integer between 0 and maxValue (inclusive).
37548      This function is based on the djb2 hash algorithm reported by Dan Bernstein.
37549      You can find more informations here: http://www.cse.yorku.ca/~oz/hash.html
37550      The hashing algorithm is using bitwise operators to multiply by 32, and later to ensure a positive integer.
37551      The result is then reduced to the range of 0 to maxValue using modulo operation.
37552    */
37553    const djb2Hash = (key, maxValue) => {
37554        let hash = 5381;
37555        for (let i = 0; i < key.length; i++) {
37556            // eslint-disable-next-line no-bitwise
37557            hash = ((hash << 5) + hash) + key.charCodeAt(i);
37558        }
37559        // eslint-disable-next-line no-bitwise
37560        return (hash >>> 0) % (maxValue + 1);
37561    };
37562    const getColor = (id) => {
37563        const colorIdx = djb2Hash(id ?? '', AvatarColors.length - 1);
37564        return AvatarColors[colorIdx];
37565    };
37566    const generateAvatarSvg = (content, color, size) => {
37567        const halfSize = size / 2;
37568        return `<svg height="${size}" width="${size}" xmlns="http://www.w3.org/2000/svg">` +
37569            `<circle cx="${halfSize}" cy="${halfSize}" r="${halfSize}" fill="${color}"/>` +
37570            `<text x="50%" y="50%" text-anchor="middle" dominant-baseline="central" fill="#FFF" font-family="sans-serif" font-size="${halfSize}">` +
37571            content +
37572            `</text>` +
37573            '</svg>';
37574    };
37575    /**
37576     * Generates a data URL for an SVG avatar with the specified content, color, and size.
37577     *
37578     * @param content The text content to display in the avatar (typically a single character)
37579     * @param color The background color of the avatar (hex color string)
37580     * @param size The size of the avatar in pixels (width and height)
37581     * @returns A data URL string containing the encoded SVG avatar
37582     */
37583    const generateAvatar = (content, color, size) => 'data:image/svg+xml,' + encodeURIComponent(generateAvatarSvg(content, color, size));
37584    /**
37585     * Generates a user avatar based on the user's name and ID.
37586     *
37587     * @param user User object containing id and name properties
37588     * @param user.id Unique identifier used to determine the avatar color
37589     * @param user.name User's name, first character will be displayed in the avatar
37590     * @param config Configuration object for the avatar
37591     * @param config.size The size of the avatar in pixels (defaults to 36)
37592     * @returns A data URL string containing the encoded SVG avatar
37593     */
37594    const generateUserAvatar = (user, config = { size: 36 }) => generateAvatar(getFirstChar(user.name), getColor(user.id), config.size);
37595
37596    const userSchema = objOf([
37597        required('id'),
37598        optionString('name'),
37599        optionString('avatar'),
37600        option$1('custom')
37601    ]);
37602    const objectCat = (obj) => {
37603        const result = {};
37604        each$d(obj, (value, key) => {
37605            value.each((v) => {
37606                result[key] = v;
37607            });
37608        });
37609        return result;
37610    };
37611    const validateResponse = (items) => {
37612        if (!Array.isArray(items)) {
37613            throw new Error('fetch_users must return an array');
37614        }
37615        const results = map$3(items, (item) => asRaw('Invalid user object', userSchema, item));
37616        const { errors, values } = partition$1(results);
37617        if (errors.length > 0) {
37618            const formattedErrors = map$3(errors, (error, idx) => `User at index ${idx}: ${formatError(error)}`);
37619            // eslint-disable-next-line no-console
37620            console.warn('User validation errors:\n' + formattedErrors.join('\n'));
37621        }
37622        return map$3(values, (user) => {
37623            const { id, name, avatar, ...rest } = user;
37624            return {
37625                id,
37626                name: name.getOr(id),
37627                avatar: avatar.getOr(generateUserAvatar({ id, name: name.getOr(id) })),
37628                ...objectCat(rest),
37629            };
37630        });
37631    };
37632    const UserLookup = (editor) => {
37633        const userCache = new Map();
37634        const pendingResolvers = new Map();
37635        const lookup = (userId) => Optional.from(userCache.get(userId));
37636        const store = (user, userId) => {
37637            userCache.set(userId, user);
37638        };
37639        const finallyReject = (userId, error) => Optional
37640            .from(pendingResolvers.get(userId))
37641            .each(({ reject }) => {
37642            reject(error);
37643            pendingResolvers.delete(userId);
37644        });
37645        const finallyResolve = (userId, user) => Optional
37646            .from(pendingResolvers.get(userId))
37647            .each(({ resolve }) => {
37648            resolve(user);
37649            pendingResolvers.delete(userId);
37650        });
37651        const fetchUsers = (userIds) => {
37652            const fetchUsersFn = getFetchUsers(editor);
37653            if (!Array.isArray(userIds)) {
37654                return {};
37655            }
37656            else if (!fetchUsersFn) {
37657                return mapToObject(userIds, (userId) => Promise.resolve({
37658                    id: userId,
37659                    name: userId,
37660                    avatar: generateUserAvatar({ id: userId, name: userId })
37661                }));
37662            }
37663            const uncachedIds = unique$1(filter$5((userIds), (userId) => !lookup(userId).isSome()));
37664            each$e(uncachedIds, (userId) => {
37665                const newPromise = new Promise((resolve, reject) => {
37666                    pendingResolvers.set(userId, { resolve, reject });
37667                });
37668                store(newPromise, userId);
37669            });
37670            if (uncachedIds.length > 0) {
37671                fetchUsersFn(uncachedIds)
37672                    .then(validateResponse)
37673                    .then((users) => {
37674                    const foundUserIds = new Set(map$3(users, (user) => user.id));
37675                    // Resolve found users
37676                    each$e(users, (user) => finallyResolve(user.id, user));
37677                    // Reject promises for users not found in the response
37678                    each$e(uncachedIds, (userId) => {
37679                        if (!foundUserIds.has(userId)) {
37680                            finallyReject(userId, new Error(`User ${userId} not found`));
37681                        }
37682                    });
37683                })
37684                    .catch((error) => {
37685                    each$e(uncachedIds, (userId) => finallyReject(userId, error instanceof Error ? error : new Error('Network error')));
37686                });
37687            }
37688            return foldl(userIds, (acc, userId) => {
37689                acc[userId] = lookup(userId).getOr(Promise.resolve({
37690                    id: userId,
37691                    name: userId,
37692                    avatar: generateUserAvatar({ id: userId, name: userId })
37693                }));
37694                return acc;
37695            }, {});
37696        };
37697        const userId = getUserId(editor);
37698        return Object.freeze({
37699            userId,
37700            fetchUsers,
37701        });
37702    };
37703    const createUserLookup = (editor) => UserLookup(editor);
37704
37705    const setEditableRoot = (editor, state) => {
37706        if (editor._editableRoot !== state) {
37707            editor._editableRoot = state;
37708            if (!isDisabled(editor)) {
37709                editor.getBody().contentEditable = String(editor.hasEditableRoot());
37710                editor.nodeChanged();
37711            }
37712            fireEditableRootStateChange(editor, state);
37713        }
37714    };
37715    const hasEditableRoot = (editor) => editor._editableRoot;
37716
37717    const sectionResult = (sections, settings) => ({
37718        sections: constant(sections),
37719        options: constant(settings)
37720    });
37721    const deviceDetection = detect$1().deviceType;
37722    const isPhone = deviceDetection.isPhone();
37723    const isTablet = deviceDetection.isTablet();
37724    const normalizePlugins = (plugins) => {
37725        if (isNullable(plugins)) {
37726            return [];
37727        }
37728        else {
37729            const pluginNames = isArray$1(plugins) ? plugins : plugins.split(/[ ,]/);
37730            const trimmedPlugins = map$3(pluginNames, trim$4);
37731            return filter$5(trimmedPlugins, isNotEmpty);
37732        }
37733    };
37734    const extractSections = (keys, options) => {
37735        const result = bifilter(options, (value, key) => {
37736            return contains$2(keys, key);
37737        });
37738        return sectionResult(result.t, result.f);
37739    };
37740    const getSection = (sectionResult, name, defaults = {}) => {
37741        const sections = sectionResult.sections();
37742        const sectionOptions = get$
37742a(sections, name).getOr({});
37743        return Tools.extend({}, defaults, sectionOptions);
37744    };
37745    const hasSection = (sectionResult, name) => {
37746        return has$2(sectionResult.sections(), name);
37747    };
37748    const getSectionConfig = (sectionResult, name) => {
37749        return hasSection(sectionResult, name) ? sectionResult.sections()[name] : {};
37750    };
vendor: 15,316 bytes, lines 37751-38097
37751    // Get a list of options to override any desktop options
37752    const getMobileOverrideOptions = (mobileOptions, isPhone) => {
37753        const defaultMobileOptions = {
37754            table_grid: false, // Table grid relies on hover, which isn't available for touch devices so use the dialog instead
37755            object_resizing: false, // No nice way to do object resizing at this stage
37756            resize: false, // Editor resize doesn't make sense on mobile
37757            toolbar_mode: get$a(mobileOptions, 'toolbar_mode').getOr('scrolling'), // Use the default side-scrolling toolbar for tablets/phones
37758            toolbar_sticky: false // Only enable sticky toolbar on desktop by default
37759        };
37760        const defaultPhoneOptions = {
37761            menubar: false // Phones don't have a lot of screen space, so disable the menubar
37762        };
37763        return {
37764            ...defaultMobileOptions,
37765            ...isPhone ? defaultPhoneOptions : {}
37766        };
37767    };
37768    const getExternalPlugins = (overrideOptions, options) => {
37769        const userDefinedExternalPlugins = options.external_plugins ?? {};
37770        if (overrideOptions && overrideOptions.external_plugins) {
37771            return Tools.extend({}, overrideOptions.external_plugins, userDefinedExternalPlugins);
37772        }
37773        else {
37774            return userDefinedExternalPlugins;
37775        }
37776    };
37777    const combinePlugins = (forcedPlugins, plugins) => [
37778        ...normalizePlugins(forcedPlugins),
37779        ...normalizePlugins(plugins)
37780    ];
37781    const getPlatformPlugins = (isMobileDevice, sectionResult, desktopPlugins, mobilePlugins) => {
37782        // is a mobile device with any mobile options
37783        if (isMobileDevice && hasSection(sectionResult, 'mobile')) {
37784            return mobilePlugins;
37785            // is desktop
37786        }
37787        else {
37788            return desktopPlugins;
37789        }
37790    };
37791    const processPlugins = (isMobileDevice, sectionResult, defaultOverrideOptions, options) => {
37792        const forcedPlugins = normalizePlugins(defaultOverrideOptions.forced_plugins);
37793        const desktopPlugins = normalizePlugins(options.plugins);
37794        const mobileConfig = getSectionConfig(sectionResult, 'mobile');
37795        const mobilePlugins = mobileConfig.plugins ? normalizePlugins(mobileConfig.plugins) : desktopPlugins;
37796        const platformPlugins = getPlatformPlugins(isMobileDevice, sectionResult, desktopPlugins, mobilePlugins);
37797        const combinedPlugins = combinePlugins(forcedPlugins, platformPlugins);
37798        return Tools.extend(options, {
37799            forced_plugins: forcedPlugins,
37800            plugins: combinedPlugins
37801        });
37802    };
37803    const isOnMobile = (isMobileDevice, sectionResult) => {
37804        return isMobileDevice && hasSection(sectionResult, 'mobile');
37805    };
37806    const combineOptions = (isMobileDevice, isPhone, defaultOptions, defaultOverrideOptions, options) => {
37807        // Use mobile mode by default on phones, so patch in the mobile override options
37808        const deviceOverrideOptions = isMobileDevice ? { mobile: getMobileOverrideOptions(options.mobile ?? {}, isPhone) } : {};
37809        const sectionResult = extractSections(['mobile'], deepMerge(deviceOverrideOptions, options));
37810        const extendedOptions = Tools.extend(
37811        // Default options
37812        defaultOptions, 
37813        // tinymce.overrideOptions options
37814        defaultOverrideOptions, 
37815        // User options
37816        sectionResult.options(), 
37817        // Sections
37818        isOnMobile(isMobileDevice, sectionResult) ? getSection(sectionResult, 'mobile') : {}, 
37819        // Forced options
37820        {
37821            external_plugins: getExternalPlugins(defaultOverrideOptions, sectionResult.options())
37822        });
37823        return processPlugins(isMobileDevice, sectionResult, defaultOverrideOptions, extendedOptions);
37824    };
37825    const normalizeOptions = (defaultOverrideOptions, options) => {
37826        const copiedOptions = merge$1(options);
37827        return combineOptions(isPhone || isTablet, isPhone, copiedOptions, defaultOverrideOptions, copiedOptions);
37828    };
37829
37830    const addVisual = (editor, elm) => addVisual$1(editor, elm);
37831
37832    const registerExecCommands$2 = (editor) => {
37833        const toggleFormat = (name, value) => {
37834            editor.formatter.toggle(name, value);
37835            editor.nodeChanged();
37836        };
37837        const toggleAlign = (align) => () => {
37838            // Remove all other alignments first
37839            each$e('left,center,right,justify'.split(','), (name) => {
37840                if (align !== name) {
37841                    editor.formatter.remove('align' + name);
37842                }
37843            });
37844            if (align !== 'none') {
37845                toggleFormat('align' + align);
37846            }
37847        };
37848        editor.editorCommands.addCommands({
37849            JustifyLeft: toggleAlign('left'),
37850            JustifyCenter: toggleAlign('center'),
37851            JustifyRight: toggleAlign('right'),
37852            JustifyFull: toggleAlign('justify'),
37853            JustifyNone: toggleAlign('none')
37854        });
37855    };
37856    const registerQueryStateCommands = (editor) => {
37857        const alignStates = (name) => () => {
37858            const selection = editor.selection;
37859            const nodes = selection.isCollapsed() ? [editor.dom.getParent(selection.getNode(), editor.dom.isBlock)] : selection.getSelectedBlocks();
37860            return exists(nodes, (node) => isNonNullable(editor.formatter.matchNode(node, name)));
37861        };
37862        editor.editorCommands.addCommands({
37863            JustifyLeft: alignStates('alignleft'),
37864            JustifyCenter: alignStates('aligncenter'),
37865            JustifyRight: alignStates('alignright'),
37866            JustifyFull: alignStates('alignjustify')
37867        }, 'state');
37868    };
37869    const registerCommands$a = (editor) => {
37870        registerExecCommands$2(editor);
37871        registerQueryStateCommands(editor);
37872    };
37873
37874    const registerCommands$9 = (editor) => {
37875        editor.editorCommands.addCommands({
37876            'Cut,Copy,Paste': (command) => {
37877                const doc = editor.getDoc();
37878                let failed;
37879                // Try executing the native command
37880                try {
37881                    doc.execCommand(command);
37882                }
37883                catch {
37884                    // Command failed
37885                    failed = true;
37886                }
37887                // Chrome reports the paste command as supported however older IE:s will return false for cut/paste
37888                if (command === 'paste' && !doc.queryCommandEnabled(command)) {
37889                    failed = true;
37890                }
37891                // Present alert message about clipboard access not being available
37892                if (failed || !doc.queryCommandSupported(command)) {
37893                    let msg = editor.translate(`Your browser doesn't support direct access to the clipboard. ` +
37894                        'Please use the Ctrl+X/C/V keyboard shortcuts instead.');
37895                    if (Env.os.isMacOS() || Env.os.isiOS()) {
37896                        msg = msg.replace(/Ctrl\+/g, '\u2318+');
37897                    }
37898                    editor.notificationManager.open({ text: msg, type: 'error' });
37899                }
37900            }
37901        });
37902    };
37903
37904    const trimOrPadLeftRight = (dom, rng, html, schema) => {
37905        const root = SugarElement.fromDom(dom.getRoot());
37906        // Adjust the start if it needs to be an nbsp
37907        if (needsToBeNbspLeft(root, CaretPosition.fromRangeStart(rng), schema)) {
37908            html = html.replace(/^ /, '&nbsp;');
37909        }
37910        else {
37911            html = html.replace(/^&nbsp;/, ' ');
37912        }
37913        // Adjust the end if it needs to be an nbsp
37914        if (needsToBeNbspRight(root, CaretPosition.fromRangeEnd(rng), schema)) {
37915            html = html.replace(/(&nbsp;| )(<br( \/)>)?$/, '&nbsp;');
37916        }
37917        else {
37918            html = html.replace(/&nbsp;(<br( \/)?>)?$/, ' ');
37919        }
37920        return html;
37921    };
37922
37923    const processValue$1 = (value) => {
37924        if (typeof value !== 'string') {
37925            const details = Tools.extend({
37926                paste: value.paste,
37927                data: {
37928                    paste: value.paste
37929                }
37930            }, value);
37931            return {
37932                content: value.content,
37933                details
37934            };
37935        }
37936        return {
37937            content: value,
37938            details: {}
37939        };
37940    };
37941    const trimOrPad = (editor, value) => {
37942        const selection = editor.selection;
37943        const dom = editor.dom;
37944        // Check for whitespace before/after value
37945        if (/^ | $/.test(value)) {
37946            return trimOrPadLeftRight(dom, selection.getRng(), value, editor.schema);
37947        }
37948        else {
37949            return value;
37950        }
37951    };
37952    const insertAtCaret = (editor, value) => {
37953        if (editor.selection.isEditable()) {
37954            const { content, details } = processValue$1(value);
37955            preProcessSetContent(editor, { ...details, content: trimOrPad(editor, content), format: 'html', set: false, selection: true }).each((args) => {
37956                const insertedContent = insertContent$1(editor, args.content, details);
37957                postProcessSetContent(editor, insertedContent, args);
37958                editor.addVisual();
37959            });
37960        }
37961    };
37962
37963    const registerCommands$8 = (editor) => {
37964        editor.editorCommands.addCommands({
37965            mceCleanup: () => {
37966                const bm = editor.selection.getBookmark();
37967                editor.setContent(editor.getContent());
37968                editor.selection.moveToBookmark(bm);
37969            },
37970            insertImage: (_command, _ui, value) => {
37971                insertAtCaret(editor, editor.dom.createHTML('img', { src: value }));
37972            },
37973            insertHorizontalRule: () => {
37974                editor.execCommand('mceInsertContent', false, '<hr>');
37975            },
37976            insertText: (_command, _ui, value) => {
37977                insertAtCaret(editor, editor.dom.encode(value));
37978            },
37979            insertHTML: (_command, _ui, value) => {
37980                insertAtCaret(editor, value);
37981            },
37982            mceInsertContent: (_command, _ui, value) => {
37983                insertAtCaret(editor, value);
37984            },
37985            mceSetContent: (_command, _ui, value) => {
37986                editor.setContent(value);
37987            },
37988            mceReplaceContent: (_command, _ui, value) => {
37989                editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, editor.selection.getContent({ format: 'text' })));
37990            },
37991            mceNewDocument: () => {
37992                editor.setContent(getNewDocumentContent(editor));
37993            }
37994        });
37995    };
37996
37997    const legacyPropNames = {
37998        'font-size': 'size',
37999        'font-family': 'face'
38000    };
38001    const isFont = isTag('font');
38002    const getSpecifiedFontProp = (propName, rootElm, elm) => {
38003        const getProperty = (elm) => getRaw$1(elm, propName).orThunk(() => {
38004            if (isFont(elm)) {
38005                return get$a(legacyPropNames, propName).bind((legacyPropName) => getOpt(elm, legacyPropName));
38006            }
38007            else {
38008                return Optional.none();
38009            }
38010        });
38011        const isRoot = (elm) => eq(SugarElement.fromDom(rootElm), elm);
38012        return closest$1(SugarElement.fromDom(elm), (elm) => getProperty(elm), isRoot);
38013    };
38014    const normalizeFontFamily = (fontFamily) => 
38015    // 'Font name', Font -> Font name,Font
38016    fontFamily.replace(/[\'\"\\]/g, '').replace(/,\s+/g, ',');
38017    const getComputedFontProp = (propName, elm) => Optional.from(DOMUtils.DOM.getStyle(elm, propName, true));
38018    const getFontProp = (propName) => (rootElm, elm) => Optional.from(elm)
38019        .map(SugarElement.fromDom)
38020        .filter(isElement$8)
38021        .bind((element) => getSpecifiedFontProp(propName, rootElm, element.dom)
38022        .or(getComputedFontProp(propName, element.dom)))
38023        .getOr('');
38024    const getFontSize = getFontProp('font-size');
38025    const getFontFamily = compose(normalizeFontFamily, getFontProp('font-family'));
38026
38027    const findFirstCaretElement = (editor) => firstPositionIn(editor.getBody())
38028        .bind((caret) => {
38029        const container = caret.container();
38030        return Optional.from(isText$b(container) ? container.parentNode : container);
38031    });
38032    const getCaretElement = (editor) => Optional.from(editor.selection.getRng())
38033        .bind((rng) => {
38034        const root = editor.getBody();
38035        const atStartOfNode = rng.startContainer === root && rng.startOffset === 0;
38036        return atStartOfNode ? Optional.none() : Optional.from(editor.selection.getStart(true));
38037    });
38038    const bindRange = (editor, binder) => getCaretElement(editor)
38039        .orThunk(curry(findFirstCaretElement, editor))
38040        .map(SugarElement.fromDom)
38041        .filter(isElement$8)
38042        .bind(binder);
38043    const mapRange = (editor, mapper) => bindRange(editor, compose1(Optional.some, mapper));
38044
38045    const fromFontSizeNumber = (editor, value) => {
38046        if (/^[0-9.]+$/.test(value)) {
38047            const fontSizeNumber = parseInt(value, 10);
38048            // Convert font size 1-7 to styles
38049            if (fontSizeNumber >= 1 && fontSizeNumber <= 7) {
38050                const fontSizes = getFontStyleValues(editor);
38051                const fontClasses = getFontSizeClasses(editor);
38052                if (fontClasses.length > 0) {
38053                    return fontClasses[fontSizeNumber - 1] || value;
38054                }
38055                else {
38056                    return fontSizes[fontSizeNumber - 1] || value;
38057                }
38058            }
38059            else {
38060                return value;
38061            }
38062        }
38063        else {
38064            return value;
38065        }
38066    };
38067    const normalizeFontNames = (font) => {
38068        const fonts = font.split(/\s*,\s*/);
38069        return map$3(fonts, (font) => {
38070            if (font.indexOf(' ') !== -1 && !(startsWith(font, '"') || startsWith(font, `'`))) {
38071                // TINY-3801: The font has spaces, so need to wrap with quotes as the browser sometimes automatically handles this, but not always
38072                return `'${font}'`;
38073            }
38074            else {
38075                return font;
38076            }
38077        }).join(',');
38078    };
38079    const fontNameAction = (editor, value) => {
38080        const font = fromFontSizeNumber(editor, value);
38081        editor.formatter.toggle('fontname', { value: normalizeFontNames(font) });
38082        editor.nodeChanged();
38083    };
38084    const fontNameQuery = (editor) => mapRange(editor, (elm) => getFontFamily(editor.getBody(), elm.dom)).getOr('');
38085    const fontSizeAction = (editor, value) => {
38086        editor.formatter.toggle('fontsize', { value: fromFontSizeNumber(editor, value) });
38087        editor.nodeChanged();
38088    };
38089    const fontSizeQuery = (editor) => mapRange(editor, (elm) => getFontSize(editor.getBody(), elm.dom)).getOr('');
38090
38091    const lineHeightQuery = (editor) => mapRange(editor, (elm) => {
38092        const root = SugarElement.fromDom(editor.getBody());
38093        const specifiedStyle = closest$1(elm, (elm) => getRaw$1(elm, 'line-height'), curry(eq, root));
38094        const computedStyle = () => {
38095            // Css.get returns computed values (in px), and parseFloat will strip any non-number suffix
38096            const lineHeight = parseFloat(get$7(elm, 'line-height'));
38097            const fontSize = parseFloat(get$7
vendor: 9,363 bytes, lines 38097-38332
38097(elm, 'font-size'));
38098            return String(lineHeight / fontSize);
38099        };
38100        return specifiedStyle.getOrThunk(computedStyle);
38101    }).getOr('');
38102    const lineHeightAction = (editor, lineHeight) => {
38103        editor.formatter.toggle('lineheight', { value: String(lineHeight) });
38104        editor.nodeChanged();
38105    };
38106
38107    const registerExecCommands$1 = (editor) => {
38108        const toggleFormat = (name, value) => {
38109            editor.formatter.toggle(name, value);
38110            editor.nodeChanged();
38111        };
38112        editor.editorCommands.addCommands({
38113            'Bold,Italic,Underline,Strikethrough,Superscript,Subscript': (command) => {
38114                toggleFormat(command);
38115            },
38116            'ForeColor,HiliteColor': (command, _ui, value) => {
38117                toggleFormat(command, { value });
38118            },
38119            'BackColor': (_command, _ui, value) => {
38120                toggleFormat('hilitecolor', { value });
38121            },
38122            'FontName': (_command, _ui, value) => {
38123                fontNameAction(editor, value);
38124            },
38125            'FontSize': (_command, _ui, value) => {
38126                fontSizeAction(editor, value);
38127            },
38128            'LineHeight': (_command, _ui, value) => {
38129                lineHeightAction(editor, value);
38130            },
38131            'Lang': (command, _ui, lang) => {
38132                toggleFormat(command, { value: lang.code, customValue: lang.customCode ?? null });
38133            },
38134            'RemoveFormat': (command) => {
38135                editor.formatter.remove(command);
38136            },
38137            'mceBlockQuote': () => {
38138                toggleFormat('blockquote');
38139            },
38140            'FormatBlock': (_command, _ui, value) => {
38141                toggleFormat(isString(value) ? value : 'p');
38142            },
38143            'mceToggleFormat': (_command, _ui, value) => {
38144                toggleFormat(value);
38145            }
38146        });
38147    };
38148    const registerQueryValueCommands = (editor) => {
38149        const isFormatMatch = (name) => editor.formatter.match(name);
38150        editor.editorCommands.addCommands({
38151            'Bold,Italic,Underline,Strikethrough,Superscript,Subscript': (command) => isFormatMatch(command),
38152            'mceBlockQuote': () => isFormatMatch('blockquote')
38153        }, 'state');
38154        editor.editorCommands.addQueryValueHandler('FontName', () => fontNameQuery(editor));
38155        editor.editorCommands.addQueryValueHandler('FontSize', () => fontSizeQuery(editor));
38156        editor.editorCommands.addQueryValueHandler('LineHeight', () => lineHeightQuery(editor));
38157    };
38158    const registerCommands$7 = (editor) => {
38159        registerExecCommands$1(editor);
38160        registerQueryValueCommands(editor);
38161    };
38162
38163    const registerCommands$6 = (editor) => {
38164        editor.editorCommands.addCommands({
38165            mceAddUndoLevel: () => {
38166                editor.undoManager.add();
38167            },
38168            mceEndUndoLevel: () => {
38169                editor.undoManager.add();
38170            },
38171            Undo: () => {
38172                editor.undoManager.undo();
38173            },
38174            Redo: () => {
38175                editor.undoManager.redo();
38176            }
38177        });
38178    };
38179
38180    const registerCommands$5 = (editor) => {
38181        editor.editorCommands.addCommands({
38182            Indent: () => {
38183                indent(editor);
38184            },
38185            Outdent: () => {
38186                outdent(editor);
38187            },
38188        });
38189        editor.editorCommands.addCommands({
38190            Outdent: () => canOutdent(editor),
38191            Indent: () => canIndent(editor),
38192        }, 'state');
38193    };
38194
38195    const registerCommands$4 = (editor) => {
38196        const applyLinkToSelection = (_command, _ui, value) => {
38197            if (editor.mode.isReadOnly()) {
38198                return;
38199            }
38200            const linkDetails = isString(value) ? { href: value } : value;
38201            const anchor = editor.dom.getParent(editor.selection.getNode(), 'a');
38202            if (isObject(linkDetails) && isString(linkDetails.href)) {
38203                // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.
38204                linkDetails.href = linkDetails.href.replace(/ /g, '%20').replace(/&amp;/g, '&');
38205                // Remove existing links if there could be child links or that the href isn't specified
38206                if (!anchor || !linkDetails.href) {
38207                    editor.formatter.remove('link');
38208                }
38209                // Apply new link to selection
38210                if (linkDetails.href) {
38211                    editor.formatter.apply('link', linkDetails, anchor);
38212                }
38213            }
38214        };
38215        editor.editorCommands.addCommands({
38216            unlink: () => {
38217                if (editor.selection.isEditable()) {
38218                    if (editor.selection.isCollapsed()) {
38219                        const elm = editor.dom.getParent(editor.selection.getStart(), 'a');
38220                        if (elm) {
38221                            editor.dom.remove(elm, true);
38222                        }
38223                        return;
38224                    }
38225                    editor.formatter.remove('link');
38226                }
38227            },
38228            mceInsertLink: applyLinkToSelection,
38229            createLink: applyLinkToSelection
38230        });
38231    };
38232
38233    const getTopParentBlock = (editor, node, root, container) => {
38234        const dom = editor.dom;
38235        const selector = (node) => dom.isBlock(node) && node.parentElement === root;
38236        const topParentBlock = selector(node) ? node : dom.getParent(container, selector, root);
38237        return Optional.from(topParentBlock).map(SugarElement.fromDom);
38238    };
38239    const insert = (editor, before) => {
38240        if (editor.mode.isReadOnly()) {
38241            return;
38242        }
38243        const dom = editor.dom;
38244        const rng = editor.selection.getRng();
38245        const node = before ? editor.selection.getStart() : editor.selection.getEnd();
38246        const container = before ? rng.startContainer : rng.endContainer;
38247        const root = getEditableRoot(dom, container);
38248        if (!root || !root.isContentEditable) {
38249            return;
38250        }
38251        const insertFn = before ? before$4 : after$4;
38252        const newBlockName = getForcedRootBlock(editor);
38253        getTopParentBlock(editor, node, root, container).each((parentBlock) => {
38254            const newBlock = createNewBlock(editor, container, parentBlock.dom, root, false, newBlockName);
38255            insertFn(parentBlock, SugarElement.fromDom(newBlock));
38256            editor.selection.setCursorLocation(newBlock, 0);
38257            editor.dispatch('NewBlock', { newBlock });
38258            fireInputEvent(editor, 'insertParagraph');
38259        });
38260    };
38261    const insertBefore = (editor) => insert(editor, true);
38262    const insertAfter = (editor) => insert(editor, false);
38263
38264    const registerCommands$3 = (editor) => {
38265        editor.editorCommands.addCommands({
38266            InsertNewBlockBefore: () => {
38267                insertBefore(editor);
38268            },
38269            InsertNewBlockAfter: () => {
38270                insertAfter(editor);
38271            },
38272        });
38273    };
38274
38275    const registerCommands$2 = (editor) => {
38276        editor.editorCommands.addCommands({
38277            insertParagraph: () => {
38278                insertBreak(blockbreak, editor);
38279            },
38280            mceInsertNewLine: (_command, _ui, value) => {
38281                insert$1(editor, value);
38282            },
38283            InsertLineBreak: (_command, _ui, _value) => {
38284                insertBreak(linebreak, editor);
38285            }
38286        });
38287    };
38288
38289    const registerCommands$1 = (editor) => {
38290        editor.editorCommands.addCommands({
38291            mceSelectNodeDepth: (_command, _ui, value) => {
38292                let counter = 0;
38293                editor.dom.getParent(editor.selection.getNode(), (node) => {
38294                    if (isElement$7(node) && counter++ === value) {
38295                        editor.selection.select(node);
38296                        return false;
38297                    }
38298                    else {
38299                        return true;
38300                    }
38301                }, editor.getBody());
38302            },
38303            mceSelectNode: (_command, _ui, value) => {
38304                editor.selection.select(value);
38305            },
38306            selectAll: () => {
38307                const editingHost = editor.dom.getParent(editor.selection.getStart(), isContentEditableTrue$3);
38308                if (editingHost) {
38309                    const rng = editor.dom.createRng();
38310                    rng.selectNodeContents(editingHost);
38311                    editor.selection.setRng(rng);
38312                }
38313            }
38314        });
38315    };
38316
38317    const registerExecCommands = (editor) => {
38318        editor.editorCommands.addCommands({
38319            mceRemoveNode: (_command, _ui, value) => {
38320                const node = value ?? editor.selection.getNode();
38321                // Make sure that the body node isn't removed
38322                if (node !== editor.getBody()) {
38323                    const bm = editor.selection.getBookmark();
38324                    editor.dom.remove(node, true);
38325                    editor.selection.moveToBookmark(bm);
38326                }
38327            },
38328            mcePrint: () => {
38329                editor.getWin().print();
38330            },
38331            mceFocus: (_command, _ui, value) => {
38332                focus(editor, value
38332 === tr
vendor: 32,332 bytes, lines 38332-39125
38332ue);
38333            },
38334            mceToggleVisualAid: () => {
38335                editor.hasVisual = !editor.hasVisual;
38336                editor.addVisual();
38337            }
38338        });
38339    };
38340    const registerCommands = (editor) => {
38341        registerCommands$a(editor);
38342        registerCommands$9(editor);
38343        registerCommands$6(editor);
38344        registerCommands$1(editor);
38345        registerCommands$8(editor);
38346        registerCommands$4(editor);
38347        registerCommands$5(editor);
38348        registerCommands$3(editor);
38349        registerCommands$2(editor);
38350        registerCommands$7(editor);
38351        registerExecCommands(editor);
38352    };
38353
38354    // List of commands that are considered safe even if the editor has no selection when the iframe is hidden in Firefox. See TINY-9210 for details.
38355    const selectionSafeCommands = ['toggleview'];
38356    const isSelectionSafeCommand = (command) => contains$2(selectionSafeCommands, command.toLowerCase());
38357    class EditorCommands {
38358        editor;
38359        commands = { state: {}, exec: {}, value: {} };
38360        constructor(editor) {
38361            this.editor = editor;
38362        }
38363        /**
38364         * Executes a registered command on the current instance. A list of available commands can be found in
38365         * the tinymce command identifiers documentation.
38366         *
38367         * @method execCommand
38368         * @param {String} command Command name to execute, for example mceLink or Bold.
38369         * @param {Boolean} ui Specifies if a UI (dialog) should be presented or not.
38370         * @param {Object/Array/String/Number/Boolean} value Optional command value, this can be anything.
38371         * @param {Object} args Optional arguments object.
38372         * @return {Boolean} true or false if the command was supported or not.
38373         */
38374        execCommand(command, ui = false, value, args) {
38375            const editor = this.editor;
38376            const lowerCaseCommand = command.toLowerCase();
38377            const skipFocus = args?.skip_focus;
38378            if (editor.removed) {
38379                return false;
38380            }
38381            if (lowerCaseCommand !== 'mcefocus') {
38382                if (!/^(mceAddUndoLevel|mceEndUndoLevel)$/i.test(lowerCaseCommand) && !skipFocus) {
38383                    editor.focus();
38384                }
38385                else {
38386                    restore(editor);
38387                }
38388            }
38389            const eventArgs = editor.dispatch('BeforeExecCommand', { command, ui, value });
38390            if (eventArgs.isDefaultPrevented()) {
38391                return false;
38392            }
38393            const func = this.commands.exec[lowerCaseCommand];
38394            if (isFunction(func)) {
38395                func(lowerCaseCommand, ui, value, args);
38396                editor.dispatch('ExecCommand', { command, ui, value, args });
38397                return true;
38398            }
38399            return false;
38400        }
38401        /**
38402         * Queries the current state for a command. For example: If the current selection is "bold".
38403         *
38404         * @method queryCommandState
38405         * @param {String} command Command to check the state of.
38406         * @return {Boolean} true/false - For example: If the selected contents is bold or not.
38407         */
38408        queryCommandState(command) {
38409            if ((!isSelectionSafeCommand(command) && this.editor.quirks.isHidden()) || this.editor.removed) {
38410                return false;
38411            }
38412            const lowerCaseCommand = command.toLowerCase();
38413            const func = this.commands.state[lowerCaseCommand];
38414            if (isFunction(func)) {
38415                return func(lowerCaseCommand);
38416            }
38417            return false;
38418        }
38419        /**
38420         * Returns a command specific value, for example the current font size.
38421         *
38422         * @method queryCommandValue
38423         * @param {String} command Command to query value from.
38424         * @return {String} Command value, for example the current font size or an empty string (`""`) if the query command is not found.
38425         */
38426        queryCommandValue(command) {
38427            if ((!isSelectionSafeCommand(command) && this.editor.quirks.isHidden()) || this.editor.removed) {
38428                return '';
38429            }
38430            const lowerCaseCommand = command.toLowerCase();
38431            const func = this.commands.value[lowerCaseCommand];
38432            if (isFunction(func)) {
38433                return func(lowerCaseCommand);
38434            }
38435            return '';
38436        }
38437        addCommands(commandList, type = 'exec') {
38438            const commands = this.commands;
38439            each$d(commandList, (callback, command) => {
38440                each$e(command.toLowerCase().split(','), (command) => {
38441                    commands[type][command] = callback;
38442                });
38443            });
38444        }
38445        addCommand(command, callback, scope) {
38446            const lowerCaseCommand = command.toLowerCase();
38447            this.commands.exec[lowerCaseCommand] = (_command, ui, value, args) => callback.call(scope ?? this.editor, ui, value, args);
38448        }
38449        /**
38450         * Removes a command from the command collection.
38451         *
38452         * @method removeCommand
38453         * @param {String} command Command name to remove.
38454         * @param {String} type Optional type to remove, defaults to removing all types. Can be 'exec', 'state', 'value', or omitted for all.
38455         */
38456        removeCommand(command, type) {
38457            const lowerCaseCommand = command.toLowerCase();
38458            if (type) {
38459                delete this.commands[type][lowerCaseCommand];
38460            }
38461            else {
38462                delete this.commands.exec[lowerCaseCommand];
38463                delete this.commands.state[lowerCaseCommand];
38464                delete this.commands.value[lowerCaseCommand];
38465            }
38466        }
38467        /**
38468         * Returns true/false if the command is supported or not.
38469         *
38470         * @method queryCommandSupported
38471         * @param {String} command Command that we check support for.
38472         * @return {Boolean} true/false if the command is supported or not.
38473         */
38474        queryCommandSupported(command) {
38475            const lowerCaseCommand = command.toLowerCase();
38476            if (this.commands.exec[lowerCaseCommand]) {
38477                return true;
38478            }
38479            else {
38480                return false;
38481            }
38482        }
38483        addQueryStateHandler(command, callback, scope) {
38484            this.commands.state[command.toLowerCase()] = () => callback.call(scope ?? this.editor);
38485        }
38486        addQueryValueHandler(command, callback, scope) {
38487            this.commands.value[command.toLowerCase()] = () => callback.call(scope ?? this.editor);
38488        }
38489    }
38490
38491    /**
38492     * This class lets you add/remove and dispatch events by name on the specified scope. This makes
38493     * it easy to add event listener logic to any class.
38494     *
38495     * @class tinymce.util.EventDispatcher
38496     * @example
38497     * const eventDispatcher = new EventDispatcher();
38498     *
38499     * eventDispatcher.on('click', () => console.log('data'));
38500     * eventDispatcher.dispatch('click', { data: 123 });
38501     */
38502    const nativeEvents = Tools.makeMap('focus blur focusin focusout click dblclick mousedown mouseup mousemove mouseover beforepaste paste cut copy selectionchange ' +
38503        'mouseout mouseenter mouseleave wheel keydown keypress keyup input beforeinput contextmenu dragstart dragend dragover ' +
38504        'draggesture dragdrop drop drag submit ' +
38505        'compositionstart compositionend compositionupdate touchstart touchmove touchend touchcancel', ' ');
38506    class EventDispatcher {
38507        /**
38508         * Returns true/false if the specified event name is a native browser event or not.
38509         *
38510         * @method isNative
38511         * @param {String} name Name to check if it's native.
38512         * @return {Boolean} true/false if the event is native or not.
38513         * @static
38514         */
38515        static isNative(name) {
38516            return !!nativeEvents[name.toLowerCase()];
38517        }
38518        settings;
38519        scope;
38520        toggleEvent;
38521        bindings = {};
38522        constructor(settings) {
38523            this.settings = settings || {};
38524            this.scope = this.settings.scope || this;
38525            this.toggleEvent = this.settings.toggleEvent || never;
38526        }
38527        /**
38528         * Fires the specified event by name.
38529         * <br>
38530         * <em>Marked for removal in TinyMCE 8.0. Use <code>dispatch</code> instead.</em>
38531         *
38532         * @method fire
38533         * @param {String} name Name of the event to fire.
38534         * @param {Object?} args Event arguments.
38535         * @return {Object} Event args instance passed in.
38536         * @deprecated Use dispatch() instead
38537         * @example
38538         * instance.fire('event', {...});
38539         */
38540        fire(name, args) {
38541            return this.dispatch(name, args);
38542        }
38543        /**
38544         * Dispatches the specified event by name.
38545         *
38546         * @method dispatch
38547         * @param {String} name Name of the event to dispatch
38548         * @param {Object?} args Event arguments.
38549         * @return {Object} Event args instance passed in.
38550         * @example
38551         * instance.dispatch('event', {...});
38552         */
38553        dispatch(name, args) {
38554            const lcName = name.toLowerCase();
38555            const event = normalize$3(lcName, args ?? {}, this.scope);
38556            if (this.settings.beforeFire) {
38557                this.settings.beforeFire(event);
38558            }
38559            // Don't clone the array here as this is a hot code path, so instead the handlers
38560            // array is recreated and the this.bindings[name] reference is updated in the `on`
38561            // and `off` functions. This is done to avoid the handlers array being mutated while
38562            // we're iterating over it below.
38563            const handlers = this.bindings[lcName];
38564            if (handlers) {
38565                for (let i = 0, l = handlers.length; i < l; i++) {
38566                    const callback = handlers[i];
38567                    // The handler was removed by an earlier handler in this loop so skip it.
38568                    if (callback.removed) {
38569                        continue;
38570                    }
38571                    // Unbind handlers marked with "once"
38572                    if (callback.once) {
38573                        this.off(lcName, callback.func);
38574                    }
38575                    // Stop immediate propagation if needed
38576                    if (event.isImmediatePropagationStopped()) {
38577                        return event;
38578                    }
38579                    // If callback returns false then prevent default and stop all propagation
38580                    if (callback.func.call(this.scope, event) === false) {
38581                        event.preventDefault();
38582                        return event;
38583                    }
38584                }
38585            }
38586            return event;
38587        }
38588        /**
38589         * Binds an event listener to a specific event by name.
38590         *
38591         * @method on
38592         * @param {String} name Event name or space separated list of events to bind.
38593         * @param {Function} callback Callback to be executed when the event occurs.
38594         * @param {Boolean} prepend Optional flag if the event should be prepended. Use this with care.
38595         * @return {Object} Current class instance.
38596         * @example
38597         * instance.on('event', (e) => {
38598         *   // Callback logic
38599         * });
38600         */
38601        on(name, callback, prepend, extra) {
38602            if (callback === false) {
38603                callback = never;
38604            }
38605            if (callback) {
38606                const wrappedCallback = {
38607                    func: callback,
38608                    removed: false
38609                };
38610                if (extra) {
38611                    Tools.extend(wrappedCallback, extra);
38612                }
38613                const names = name.toLowerCase().split(' ');
38614                let i = names.length;
38615                while (i--) {
38616                    const currentName = names[i];
38617                    let handlers = this.bindings[currentName];
38618                    if (!handlers) {
38619                        handlers = [];
38620                        this.toggleEvent(currentName, true);
38621                    }
38622                    if (prepend) {
38623                        handlers = [wrappedCallback, ...handlers];
38624                    }
38625                    else {
38626                        handlers = [...handlers, wrappedCallback];
38627                    }
38628                    this.bindings[currentName] = handlers;
38629                }
38630            }
38631            return this;
38632        }
38633        /**
38634         * Unbinds an event listener to a specific event by name.
38635         *
38636         * @method off
38637         * @param {String?} name Name of the event to unbind.
38638         * @param {Function?} callback Callback to unbind.
38639         * @return {Object} Current class instance.
38640         * @example
38641         * // Unbind specific callback
38642         * instance.off('event', handler);
38643         *
38644         * // Unbind all listeners by name
38645         * instance.off('event');
38646         *
38647         * // Unbind all events
38648         * instance.off();
38649         */
38650        off(name, callback) {
38651            if (name) {
38652                const names = name.toLowerCase().split(' ');
38653                let i = names.length;
38654                while (i--) {
38655                    const currentName = names[i];
38656                    let handlers = this.bindings[currentName];
38657                    // Unbind all handlers
38658                    if (!currentName) {
38659                        each$d(this.bindings, (_value, bindingName) => {
38660                            this.toggleEvent(bindingName, false);
38661                            delete this.bindings[bindingName];
38662                        });
38663                        return this;
38664                    }
38665                    if (handlers) {
38666                        // Unbind all by name
38667                        if (!callback) {
38668                            handlers.length = 0;
38669                        }
38670                        else {
38671                            // Unbind specific handlers
38672                            const filteredHandlers = partition$2(handlers, (handler) => handler.func === callback);
38673                            handlers = filteredHandlers.fail;
38674                            this.bindings[currentName] = handlers;
38675                            // Mark the removed handlers in case this event is already being processed in `fire`
38676                            each$e(filteredHandlers.pass, (handler) => {
38677                                handler.removed = true;
38678                            });
38679                        }
38680                        if (!handlers.length) {
38681                            this.toggleEvent(name, false);
38682                            delete this.bindings[currentName];
38683                        }
38684                    }
38685                }
38686            }
38687            else {
38688                each$d(this.bindings, (_value, name) => {
38689                    this.toggleEvent(name, false);
38690                });
38691                this.bindings = {};
38692            }
38693            return this;
38694        }
38695        /**
38696         * Binds an event listener to a specific event by name
38697         * and automatically unbind the event once the callback fires.
38698         *
38699         * @method once
38700         * @param {String} name Event name or space separated list of events to bind.
38701         * @param {Function} callback Callback to be executed when the event occurs.
38702         * @param {Boolean} prepend Optional flag if the event should be prepended. Use this with care.
38703         * @return {Object} Current class instance.
38704         * @example
38705         * instance.once('event', (e) => {
38706         *   // Callback logic
38707         * });
38708         */
38709        once(name, callback, prepend) {
38710            return this.on(name, callback, prepend, { once: true });
38711        }
38712        /**
38713         * Returns true/false if the dispatcher has a event of the specified name.
38714         *
38715         * @method has
38716         * @param {String} name Name of the event to check for.
38717         * @return {Boolean} true/false if the event exists or not.
38718         */
38719        has(name) {
38720            name = name.toLowerCase();
38721            const binding = this.bindings[name];
38722            return !(!binding || binding.length === 0);
38723        }
38724    }
38725
38726    /**
38727     * This mixin adds event binding logic to classes. Adapts the EventDispatcher class.
38728     *
38729     * @mixin tinymce.util.Observable
38730     */
38731    const getEventDispatcher = (obj) => {
38732        if (!obj._eventDispatcher) {
38733            obj._eventDispatcher = new EventDispatcher({
38734                scope: obj,
38735                toggleEvent: (name, state) => {
38736                    if (EventDispatcher.isNative(name) && obj.toggleNativeEvent) {
38737                        obj.toggleNativeEvent(name, state);
38738                    }
38739                }
38740            });
38741        }
38742        return obj._eventDispatcher;
38743    };
38744    const Observable = {
38745        /**
38746         * Fires the specified event by name. Consult the
38747         * <a href="https://www.tiny.cloud/docs/tinymce/8/events/">event reference</a> for more details on each event.
38748         * <br>
38749         * <em>Deprecated in TinyMCE 6.0 and has been marked for removal in TinyMCE 9.0. Use <code>dispatch</code> instead.</em>
38750         *
38751         * @method fire
38752         * @param {String} name Name of the event to fire.
38753         * @param {Object?} args Event arguments.
38754         * @param {Boolean?} bubble True/false if the event is to be bubbled.
38755         * @return {Object} Event args instance passed in.
38756         * @deprecated Use dispatch() instead
38757         * @example
38758         * instance.fire('event', {...});
38759         */
38760        fire(name, args, bubble) {
38761            logFeatureDeprecationWarning('fire');
38762            return this.dispatch(name, args, bubble);
38763        },
38764        /**
38765         * Dispatches the specified event by name. Consult the
38766         * <a href="https://www.tiny.cloud/docs/tinymce/8/events/">event reference</a> for more details on each event.
38767         *
38768         * @method dispatch
38769         * @param {String} name Name of the event to dispatch.
38770         * @param {Object?} args Event arguments.
38771         * @param {Boolean?} bubble True/false if the event is to be bubbled.
38772         * @return {Object} Event args instance passed in.
38773         * @example
38774         * instance.dispatch('event', {...});
38775         */
38776        dispatch(name, args, bubble) {
38777            const self = this;
38778            // Prevent all events except the remove/detach event after the instance has been removed
38779            if (self.removed && name !== 'remove' && name !== 'detach') {
38780                return normalize$3(name.toLowerCase(), args ?? {}, self);
38781            }
38782            const dispatcherArgs = getEventDispatcher(self).dispatch(name, args);
38783            // Bubble event up to parents
38784            if (bubble !== false && self.parent) {
38785                let parent = self.parent();
38786                while (parent && !dispatcherArgs.isPropagationStopped()) {
38787                    parent.dispatch(name, dispatcherArgs, false);
38788                    parent = parent.parent ? parent.parent() : undefined;
38789                }
38790            }
38791            return dispatcherArgs;
38792        },
38793        /**
38794         * Binds an event listener to a specific event by name. Consult the
38795         * <a href="https://www.tiny.cloud/docs/tinymce/8/events/">event reference</a> for more details on each event.
38796         *
38797         * @method on
38798         * @param {String} name Event name or space separated list of events to bind.
38799         * @param {Function} callback Callback to be executed when the event occurs.
38800         * @param {Boolean} prepend Optional flag if the event should be prepended. Use this with care.
38801         * @return {Object} Current class instance.
38802         * @example
38803         * instance.on('event', (e) => {
38804         *   // Callback logic
38805         * });
38806         */
38807        on(name, callback, prepend) {
38808            return getEventDispatcher(this).on(name, callback, prepend);
38809        },
38810        /**
38811         * Unbinds an event listener to a specific event by name. Consult the
38812         * <a href="https://www.tiny.cloud/docs/tinymce/8/events/">event reference</a> for more details on each event.
38813         *
38814         * @method off
38815         * @param {String?} name Name of the event to unbind.
38816         * @param {Function?} callback Callback to unbind.
38817         * @return {Object} Current class instance.
38818         * @example
38819         * // Unbind specific callback
38820         * instance.off('event', handler);
38821         *
38822         * // Unbind all listeners by name
38823         * instance.off('event');
38824         *
38825         * // Unbind all events
38826         * instance.off();
38827         */
38828        off(name, callback) {
38829            return getEventDispatcher(this).off(name, callback);
38830        },
38831        /**
38832         * Bind the event callback and once it fires the callback is removed. Consult the
38833         * <a href="https://www.tiny.cloud/docs/tinymce/8/events/">event reference</a> for more details on each event.
38834         *
38835         * @method once
38836         * @param {String} name Name of the event to bind.
38837         * @param {Function} callback Callback to bind only once.
38838         * @return {Object} Current class instance.
38839         */
38840        once(name, callback) {
38841            return getEventDispatcher(this).once(name, callback);
38842        },
38843        /**
38844         * Returns true/false if the object has a event of the specified name.
38845         *
38846         * @method hasEventListeners
38847         * @param {String} name Name of the event to check for.
38848         * @return {Boolean} true/false if the event exists or not.
38849         */
38850        hasEventListeners(name) {
38851            return getEventDispatcher(this).has(name);
38852        }
38853    };
38854
38855    /**
38856     * This mixin contains the event logic for the tinymce.Editor class.
38857     *
38858     * @mixin tinymce.EditorObservable
38859     * @extends tinymce.util.Observable
38860     * @private
38861     */
38862    const DOM$2 = DOMUtils.DOM;
38863    let customEventRootDelegates;
38864    /**
38865     * Returns the event target for the specified event. Some events fire
38866     * only on document, some fire on documentElement etc. This also handles the
38867     * custom event root setting where it returns that element instead of the body.
38868     *
38869     * @private
38870     * @param {tinymce.Editor} editor Editor instance to get event target from.
38871     * @param {String} eventName Name of the event for example "click".
38872     * @return {Element/Document} HTML Element or document target to bind on.
38873     */
38874    const getEventTarget = (editor, eventName) => {
38875        if (eventName === 'selectionchange') {
38876            return editor.getDoc();
38877        }
38878        // Need to bind mousedown/mouseup etc to document not body in iframe mode
38879        // Since the user might click on the HTML element not the BODY
38880        if (!editor.inline && /^(?:mouse|touch|click|contextmenu|drop|dragover|dragend)/.test(eventName)) {
38881            return editor.getDoc().documentElement;
38882        }
38883        // Bind to event root instead of body if it's defined
38884        const eventRoot = getEventRoot(editor);
38885        if (eventRoot) {
38886            if (!editor.eventRoot) {
38887                editor.eventRoot = DOM$2.select(eventRoot)[0];
38888            }
38889            return editor.eventRoot;
38890        }
38891        return editor.getBody();
38892    };
38893    const isListening = (editor) => !editor.hidden && !isDisabled(editor);
38894    const fireEvent = (editor, eventName, e) => {
38895        if (isListening(editor)) {
38896            editor.dispatch(eventName, e);
38897        }
38898        else if (isDisabled(editor)) {
38899            processDisabledEvents(editor, e);
38900        }
38901    };
38902    /**
38903     * Binds a event delegate for the specified name this delegate will fire
38904     * the event to the editor dispatcher.
38905     *
38906     * @private
38907     * @param {tinymce.Editor} editor Editor instance to get event target from.
38908     * @param {String} eventName Name of the event for example "click".
38909     */
38910    const bindEventDelegate = (editor, eventName) => {
38911        if (!editor.delegates) {
38912            editor.delegates = {};
38913        }
38914        if (editor.delegates[eventName] || editor.removed) {
38915            return;
38916        }
38917        const eventRootElm = getEventTarget(editor, eventName);
38918        if (getEventRoot(editor)) {
38919            if (!customEventRootDelegates) {
38920                customEventRootDelegates = {};
38921                editor.editorManager.on('removeEditor', () => {
38922                    if (!editor.editorManager.activeEditor) {
38923                        if (customEventRootDelegates) {
38924                            each$d(customEventRootDelegates, (_value, name) => {
38925                                editor.dom.unbind(getEventTarget(editor, name));
38926                            });
38927                            customEventRootDelegates = null;
38928                        }
38929                    }
38930                });
38931            }
38932            if (customEventRootDelegates[eventName]) {
38933                return;
38934            }
38935            const delegate = (e) => {
38936                const target = e.target;
38937                const editors = editor.editorManager.get();
38938                let i = editors.length;
38939                while (i--) {
38940                    const body = editors[i].getBody();
38941                    if (body === target || DOM$2.isChildOf(target, body)) {
38942                        fireEvent(editors[i], eventName, e);
38943                    }
38944                }
38945            };
38946            customEventRootDelegates[eventName] = delegate;
38947            DOM$2.bind(eventRootElm, eventName, delegate);
38948        }
38949        else {
38950            const delegate = (e) => {
38951                fireEvent(editor, eventName, e);
38952            };
38953            DOM$2.bind(eventRootElm, eventName, delegate);
38954            editor.delegates[eventName] = delegate;
38955        }
38956    };
38957    const EditorObservable = {
38958        ...Observable,
38959        /**
38960         * Bind any pending event delegates. This gets executed after the target body/document is created.
38961         *
38962         * @private
38963         */
38964        bindPendingEventDelegates() {
38965            const self = this;
38966            Tools.each(self._pendingNativeEvents, (name) => {
38967                bindEventDelegate(self, name);
38968            });
38969        },
38970        /**
38971         * Toggles a native event on/off this is called by the EventDispatcher when
38972         * the first native event handler is added and when the last native event handler is removed.
38973         *
38974         * @private
38975         */
38976        toggleNativeEvent(name, state) {
38977            const self = this;
38978            // Never bind focus/blur since the FocusManager fakes those
38979            if (name === 'focus' || name === 'blur') {
38980                return;
38981            }
38982            // If the editor has been removed, `unbindAllNativeEvents` has already deleted all native event delegates
38983            if (self.removed) {
38984                return;
38985            }
38986            if (state) {
38987                if (self.initialized) {
38988                    bindEventDelegate(self, name);
38989                }
38990                else {
38991                    if (!self._pendingNativeEvents) {
38992                        self._pendingNativeEvents = [name];
38993                    }
38994                    else {
38995                        self._pendingNativeEvents.push(name);
38996                    }
38997                }
38998            }
38999            else if (self.initialized && self.delegates) {
39000                self.dom.unbind(getEventTarget(self, name), name, self.delegates[name]);
39001                delete self.delegates[name];
39002            }
39003        },
39004        /**
39005         * Unbinds all native event handlers that means delegates, custom events bound using the Events API etc.
39006         *
39007         * @private
39008         */
39009        unbindAllNativeEvents() {
39010            const self = this;
39011            const body = self.getBody();
39012            const dom = self.dom;
39013            if (self.delegates) {
39014                each$d(self.delegates, (value, name) => {
39015                    self.dom.unbind(getEventTarget(self, name), name, value);
39016                });
39017                delete self.delegates;
39018            }
39019            if (!self.inline && body && dom) {
39020                body.onload = null;
39021                dom.unbind(self.getWin());
39022                dom.unbind(self.getDoc());
39023            }
39024            if (dom) {
39025                dom.unbind(body);
39026                dom.unbind(self.getContainer());
39027            }
39028        }
39029    };
39030
39031    // A string array allows comma/space separated values as well for ease of use
39032    const stringListProcessor = (value) => {
39033        if (isString(value)) {
39034            return { value: value.split(/[ ,]/), valid: true };
39035        }
39036        else if (isArrayOf(value, isString)) {
39037            return { value, valid: true };
39038        }
39039        else {
39040            return { valid: false, message: `The value must be a string[] or a comma/space separated string.` };
39041        }
39042    };
39043    const getBuiltInProcessor = (type) => {
39044        const validator = (() => {
39045            switch (type) {
39046                case 'array':
39047                    return isArray$1;
39048                case 'boolean':
39049                    return isBoolean;
39050                case 'function':
39051                    return isFunction;
39052                case 'number':
39053                    return isNumber;
39054                case 'object':
39055                    return isObject;
39056                case 'string':
39057                    return isString;
39058                case 'string[]':
39059                    return stringListProcessor;
39060                case 'object[]':
39061                    return (val) => isArrayOf(val, isObject);
39062                case 'regexp':
39063                    return (val) => is$5(val, RegExp);
39064                default:
39065                    return always;
39066            }
39067        })();
39068        return (value) => processValue(value, validator, `The value must be a ${type}.`);
39069    };
39070    const isBuiltInSpec = (spec) => isString(spec.processor);
39071    const getErrorMessage = (message, result) => {
39072        const additionalText = isEmpty$5(result.message) ? '' : `. ${result.message}`;
39073        return message + additionalText;
39074    };
39075    const isValidResult = (result) => result.valid;
39076    const processValue = (value, processor, message = '') => {
39077        const result = processor(value);
39078        if (isBoolean(result)) {
39079            // Note: Need to cast here as if a boolean is returned then we're guaranteed to be returning the same value
39080            return result ? { value: value, valid: true } : { valid: false, message };
39081        }
39082        else {
39083            return result;
39084        }
39085    };
39086    const processDefaultValue = (name, defaultValue, processor) => {
39087        if (!isUndefined(defaultValue)) {
39088            const result = processValue(defaultValue, processor);
39089            if (isValidResult(result)) {
39090                return result.value;
39091            }
39092            else {
39093                // eslint-disable-next-line no-console
39094                console.error(getErrorMessage(`Invalid default value passed for the "${name}" option`, result));
39095            }
39096        }
39097        return undefined;
39098    };
39099    const create$4 = (editor, initialOptions, rawInitialOptions = initialOptions) => {
39100        const registry = {};
39101        const values = {};
39102        const setValue = (name, value, processor) => {
39103            const result = processValue(value, processor);
39104            if (isValidResult(result)) {
39105                values[name] = result.value;
39106                return true;
39107            }
39108            else {
39109                // eslint-disable-next-line no-console
39110                console.warn(getErrorMessage(`Invalid value passed for the ${name} option`, result));
39111                return false;
39112            }
39113        };
39114        const register = (name, spec) => {
39115            const processor = isBuiltInSpec(spec) ? getBuiltInProcessor(spec.processor) : spec.processor;
39116            // Process and validate the default value
39117            const defaultValue = processDefaultValue(name, spec.default, processor);
39118            // Register the spec with the validated default and normalized processor
39119            registry[name] = {
39120                ...spec,
39121                default: defaultValue,
39122                processor
39123            };
39124            // Setup the initial values
39125            const initValue = get$
39125a(values, name).orThunk(() => get$a(initialOptions, name));
39126            initValue.each((value) => setValue(name, value, processor));
39127        };
39128        const isRegistered = (name) => has$2(registry, name);
39129        const get = (name) => get$a(values, name)
39130            .orThunk(() => get$a
vendor: 37,194 bytes, lines 39130-40058
39130(registry, name).map((spec) => spec.default))
39131            .getOrUndefined();
39132        const set = (name, value) => {
39133            if (!isRegistered(name)) {
39134                // eslint-disable-next-line no-console
39135                console.warn(`"${name}" is not a registered option. Ensure the option has been registered before setting a value.`);
39136                return false;
39137            }
39138            else {
39139                const spec = registry[name];
39140                if (spec.immutable) {
39141                    // eslint-disable-next-line no-console
39142                    console.error(`"${name}" is an immutable option and cannot be updated`);
39143                    return false;
39144                }
39145                else {
39146                    return setValue(name, value, spec.processor);
39147                }
39148            }
39149        };
39150        const unset = (name) => {
39151            const registered = isRegistered(name);
39152            if (registered) {
39153                delete values[name];
39154            }
39155            return registered;
39156        };
39157        const isSet = (name) => has$2(values, name);
39158        const debug = () => {
39159            try {
39160                // eslint-disable-next-line no-console
39161                console.log(JSON.parse(JSON.stringify(rawInitialOptions, (_key, value) => {
39162                    if (isBoolean(value) ||
39163                        isNumber(value) ||
39164                        isString(value) ||
39165                        isNull(value) ||
39166                        isArray$1(value) ||
39167                        isPlainObject(value)) {
39168                        return value;
39169                    }
39170                    return Object.prototype.toString.call(value);
39171                })));
39172            }
39173            catch (error) {
39174                // eslint-disable-next-line no-console
39175                console.error(error);
39176            }
39177        };
39178        return {
39179            register,
39180            isRegistered,
39181            get,
39182            set,
39183            unset,
39184            isSet,
39185            debug,
39186        };
39187    };
39188
39189    const setContentEditable = (elm, state) => {
39190        elm.dom.contentEditable = state ? 'true' : 'false';
39191    };
39192    const toggleReadOnly = (editor, state) => {
39193        const body = SugarElement.fromDom(editor.getBody());
39194        if (state) {
39195            editor.readonly = true;
39196            if (editor.hasEditableRoot()) {
39197                setContentEditable(body, true);
39198            }
39199            disableEditor(editor);
39200        }
39201        else {
39202            editor.readonly = false;
39203            enableEditor(editor);
39204        }
39205    };
39206    const isReadOnly = (editor) => editor.readonly;
39207    const rollbackChange = (editor) => {
39208        const undoLevel = editor.undoManager.add();
39209        if (isNonNullable(undoLevel)) {
39210            editor.undoManager.undo();
39211            editor.undoManager.reset();
39212        }
39213    };
39214    const hasContentMutations = (mutations) => exists(mutations, (mutation) => mutation.type === 'characterData' || mutation.type === 'childList');
39215    const registerReadOnlyInputBlockers = (editor) => {
39216        const handleMutations = (mutations) => {
39217            if (isReadOnly(editor) && hasContentMutations(mutations)) {
39218                rollbackChange(editor);
39219            }
39220        };
39221        // Set up mutation observer to detect and revert unintended changes
39222        const observer = new MutationObserver(handleMutations);
39223        editor.on('beforeinput paste cut dragend dragover draggesture dragdrop drop drag', (e) => {
39224            if (isReadOnly(editor)) {
39225                e.preventDefault();
39226            }
39227        });
39228        editor.on('BeforeExecCommand', (e) => {
39229            if ((e.command === 'Undo' || e.command === 'Redo') && isReadOnly(editor)) {
39230                e.preventDefault();
39231            }
39232        });
39233        editor.on('compositionstart', () => {
39234            if (isReadOnly(editor)) {
39235                observer.observe(editor.getBody(), {
39236                    characterData: true,
39237                    childList: true,
39238                    subtree: true
39239                });
39240            }
39241        });
39242        editor.on('compositionend', () => {
39243            if (isReadOnly(editor)) {
39244                const records = observer.takeRecords();
39245                handleMutations(records);
39246            }
39247            observer.disconnect();
39248        });
39249    };
39250
39251    const defaultModes = ['design', 'readonly'];
39252    const switchToMode = (editor, activeMode, availableModes, mode) => {
39253        const oldMode = availableModes[activeMode.get()];
39254        const newMode = availableModes[mode];
39255        // if activate fails, hope nothing bad happened and abort
39256        try {
39257            newMode.activate();
39258        }
39259        catch (e) {
39260            // eslint-disable-next-line no-console
39261            console.error(`problem while activating editor mode ${mode}:`, e);
39262            return;
39263        }
39264        oldMode.deactivate();
39265        if (oldMode.editorReadOnly !== newMode.editorReadOnly) {
39266            toggleReadOnly(editor, newMode.editorReadOnly);
39267        }
39268        activeMode.set(mode);
39269        fireSwitchMode(editor, mode);
39270    };
39271    const setMode = (editor, availableModes, activeMode, mode) => {
39272        if (mode === activeMode.get() || (editor.initialized && isDisabled(editor))) {
39273            return;
39274        }
39275        else if (!has$2(availableModes, mode)) {
39276            throw new Error(`Editor mode '${mode}' is invalid`);
39277        }
39278        if (editor.initialized) {
39279            switchToMode(editor, activeMode, availableModes, mode);
39280        }
39281        else {
39282            editor.on('init', () => switchToMode(editor, activeMode, availableModes, mode));
39283        }
39284    };
39285    const registerMode = (availableModes, mode, api) => {
39286        if (contains$2(defaultModes, mode)) {
39287            throw new Error(`Cannot override default mode ${mode}`);
39288        }
39289        return {
39290            ...availableModes,
39291            [mode]: {
39292                ...api,
39293                deactivate: () => {
39294                    // wrap custom deactivate APIs so they can't break the editor
39295                    try {
39296                        api.deactivate();
39297                    }
39298                    catch (e) {
39299                        // eslint-disable-next-line no-console
39300                        console.error(`problem while deactivating editor mode ${mode}:`, e);
39301                    }
39302                }
39303            }
39304        };
39305    };
39306
39307    const create$3 = (editor) => {
39308        const activeMode = Cell('design');
39309        const availableModes = Cell({
39310            design: {
39311                activate: noop,
39312                deactivate: noop,
39313                editorReadOnly: false
39314            },
39315            readonly: {
39316                activate: noop,
39317                deactivate: noop,
39318                editorReadOnly: true
39319            }
39320        });
39321        registerReadOnlyInputBlockers(editor);
39322        registerEventsAndFilters$1(editor);
39323        return {
39324            isReadOnly: () => isReadOnly(editor),
39325            set: (mode) => setMode(editor, availableModes.get(), activeMode, mode),
39326            get: () => activeMode.get(),
39327            register: (mode, api) => {
39328                availableModes.set(registerMode(availableModes.get(), mode, api));
39329            }
39330        };
39331    };
39332
39333    /**
39334     * Contains logic for handling keyboard shortcuts.
39335     *
39336     * @class tinymce.Shortcuts
39337     * @example
39338     * editor.shortcuts.add('ctrl+a', 'description of the shortcut', () => {});
39339     * editor.shortcuts.add('ctrl+alt+a', 'description of the shortcut', () => {});
39340     * // "meta" maps to Command on Mac and Ctrl on PC
39341     * editor.shortcuts.add('meta+a', 'description of the shortcut', () => {});
39342     * // "access" maps to Control+Option on Mac and shift+alt on PC
39343     * editor.shortcuts.add('access+a', 'description of the shortcut', () => {});
39344     *
39345     * editor.shortcuts.add('meta+access+c', 'Opens the code editor dialog.', () => {
39346     *   editor.execCommand('mceCodeEditor');
39347     * });
39348     *
39349     * editor.shortcuts.add('meta+shift+32', 'Inserts "Hello, World!" for meta+shift+space', () => {
39350     *   editor.execCommand('mceInsertContent', false, 'Hello, World!');
39351     * });
39352     */
39353    const each$2 = Tools.each, explode = Tools.explode;
39354    const keyCodeLookup = {
39355        f1: 112,
39356        f2: 113,
39357        f3: 114,
39358        f4: 115,
39359        f5: 116,
39360        f6: 117,
39361        f7: 118,
39362        f8: 119,
39363        f9: 120,
39364        f10: 121,
39365        f11: 122,
39366        f12: 123
39367    };
39368    const modifierNames = Tools.makeMap('alt,ctrl,shift,meta,access');
39369    const isModifier = (key) => key in modifierNames;
39370    const parseShortcut = (pattern) => {
39371        const shortcut = {};
39372        const isMac = Env.os.isMacOS() || Env.os.isiOS();
39373        // Parse modifiers and keys ctrl+alt+b for example
39374        each$2(explode(pattern.toLowerCase(), '+'), (value) => {
39375            if (isModifier(value)) {
39376                shortcut[value] = true;
39377            }
39378            else {
39379                // Allow numeric keycodes like ctrl+219 for ctrl+[
39380                if (/^[0-9]{2,}$/.test(value)) {
39381                    shortcut.keyCode = parseInt(value, 10);
39382                }
39383                else {
39384                    shortcut.charCode = value.charCodeAt(0);
39385                    shortcut.keyCode = keyCodeLookup[value] || value.toUpperCase().charCodeAt(0);
39386                }
39387            }
39388        });
39389        // Generate unique id for modifier combination and set default state for unused modifiers
39390        const id = [shortcut.keyCode];
39391        let key;
39392        for (key in modifierNames) {
39393            if (shortcut[key]) {
39394                id.push(key);
39395            }
39396            else {
39397                shortcut[key] = false;
39398            }
39399        }
39400        shortcut.id = id.join(',');
39401        // Handle special access modifier differently depending on Mac/Win
39402        if (shortcut.access) {
39403            shortcut.alt = true;
39404            if (isMac) {
39405                shortcut.ctrl = true;
39406            }
39407            else {
39408                shortcut.shift = true;
39409            }
39410        }
39411        // Handle special meta modifier differently depending on Mac/Win
39412        if (shortcut.meta) {
39413            if (isMac) {
39414                shortcut.meta = true;
39415            }
39416            else {
39417                shortcut.ctrl = true;
39418                shortcut.meta = false;
39419            }
39420        }
39421        return shortcut;
39422    };
39423    class Shortcuts {
39424        editor;
39425        shortcuts = {};
39426        pendingPatterns = [];
39427        constructor(editor) {
39428            this.editor = editor;
39429            const self = this;
39430            editor.on('keyup keypress keydown', (e) => {
39431                if ((self.hasModifier(e) || self.isFunctionKey(e)) && !e.isDefaultPrevented()) {
39432                    each$2(self.shortcuts, (shortcut) => {
39433                        if (self.matchShortcut(e, shortcut)) {
39434                            self.pendingPatterns = shortcut.subpatterns.slice(0);
39435                            if (e.type === 'keydown') {
39436                                self.executeShortcutAction(shortcut);
39437                            }
39438                        }
39439                    });
39440                    if (self.matchShortcut(e, self.pendingPatterns[0])) {
39441                        if (self.pendingPatterns.length === 1) {
39442                            if (e.type === 'keydown') {
39443                                self.executeShortcutAction(self.pendingPatterns[0]);
39444                            }
39445                        }
39446                        self.pendingPatterns.shift();
39447                    }
39448                }
39449            });
39450        }
39451        /**
39452         * Adds a keyboard shortcut for some command or function.
39453         *
39454         * @method add
39455         * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o.
39456         * @param {String} desc Text description for the command.
39457         * @param {String/Function} cmdFunc Command name string or function to execute when the key is pressed.
39458         * @param {Object} scope Optional scope to execute the function in.
39459         * @return {Boolean} true/false state if the shortcut was added or not.
39460         */
39461        add(pattern, desc, cmdFunc, scope) {
39462            const self = this;
39463            const func = self.normalizeCommandFunc(cmdFunc);
39464            each$2(explode(Tools.trim(pattern)), (pattern) => {
39465                const shortcut = self.createShortcut(pattern, desc, func, scope);
39466                self.shortcuts[shortcut.id] = shortcut;
39467            });
39468            return true;
39469        }
39470        /**
39471         * Remove a keyboard shortcut by pattern.
39472         *
39473         * @method remove
39474         * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o.
39475         * @return {Boolean} true/false state if the shortcut was removed or not.
39476         */
39477        remove(pattern) {
39478            const shortcut = this.createShortcut(pattern);
39479            if (this.shortcuts[shortcut.id]) {
39480                delete this.shortcuts[shortcut.id];
39481                return true;
39482            }
39483            return false;
39484        }
39485        normalizeCommandFunc(cmdFunc) {
39486            const self = this;
39487            const cmd = cmdFunc;
39488            if (typeof cmd === 'string') {
39489                return () => {
39490                    self.editor.execCommand(cmd, false, null);
39491                };
39492            }
39493            else if (Tools.isArray(cmd)) {
39494                return () => {
39495                    self.editor.execCommand(cmd[0], cmd[1], cmd[2]);
39496                };
39497            }
39498            else {
39499                return cmd;
39500            }
39501        }
39502        createShortcut(pattern, desc, cmdFunc, scope) {
39503            const shortcuts = Tools.map(explode(pattern, '>'), parseShortcut);
39504            shortcuts[shortcuts.length - 1] = Tools.extend(shortcuts[shortcuts.length - 1], {
39505                func: cmdFunc,
39506                scope: scope || this.editor
39507            });
39508            return Tools.extend(shortcuts[0], {
39509                desc: this.editor.translate(desc),
39510                subpatterns: shortcuts.slice(1)
39511            });
39512        }
39513        hasModifier(e) {
39514            return e.altKey || e.ctrlKey || e.metaKey;
39515        }
39516        isFunctionKey(e) {
39517            return e.type === 'keydown' && e.keyCode >= 112 && e.keyCode <= 123;
39518        }
39519        matchShortcut(e, shortcut) {
39520            if (!shortcut) {
39521                return false;
39522            }
39523            if (shortcut.ctrl !== e.ctrlKey || shortcut.meta !== e.metaKey) {
39524                return false;
39525            }
39526            if (shortcut.alt !== e.altKey || shortcut.shift !== e.shiftKey) {
39527                return false;
39528            }
39529            if (e.keyCode === shortcut.keyCode || (e.charCode && e.charCode === shortcut.charCode)) {
39530                e.preventDefault();
39531                return true;
39532            }
39533            return false;
39534        }
39535        executeShortcutAction(shortcut) {
39536            return shortcut.func ? shortcut.func.call(shortcut.scope) : null;
39537        }
39538    }
39539
39540    /**
39541     * TinyMCE UI registration API.
39542     *
39543     * @class tinymce.editor.ui.Registry
39544     */
39545    const registry = () => {
39546        const bridge = create$6();
39547        return {
39548            /**
39549             * Registers a new auto completer component. When a configured string pattern
39550             * is matched in the content while typing, the autocompleter will be triggered.
39551             * Emoticons and Charmap use an autocompleter.
39552             * <br>
39553             * For information on creating an autocompleter, see:
39554             * <a href="https://www.tiny.cloud/docs/tinymce/8/autocompleter/">
39555             * UI Components - Autocompleter</a>.
39556             *
39557             * @method addAutocompleter
39558             * @param {String} name Unique name identifying this autocomplete configuration.
39559             * @param {InlineContent.AutocompleterSpec} obj The autocomplete configuration object.
39560             */
39561            addAutocompleter: bridge.addAutocompleter,
39562            /**
39563             * Registers a new toolbar button that executes a command when clicked or activated
39564             * via keyboard navigation controls.
39565             * <br>
39566             * For information on creating a basic toolbar button, see:
39567             * <a href="https://www.tiny.cloud/docs/tinymce/8/custom-basic-toolbar-button/">
39568             * UI Components - Types of toolbar buttons: Basic button</a>.
39569             *
39570             * @method addButton
39571             * @param {String} name Unique name identifying the button, this button name will be used in the toolbar configuration to reference the button.
39572             * @param {Toolbar.ToolbarButtonSpec} obj the button configuration object.
39573             */
39574            addButton: bridge.addButton,
39575            /**
39576             * Registers a new contextual form item.
39577             * Similar to a context menu item, a contextual form is an item with an input
39578             * form element appearing when a content predicate is matched. An example
39579             * of a contextual form is the link plugin when the configuration
39580             * { link_context_toolbar: true } is used. When the cursor is on a link, a
39581             * contextual input form appears allowing for quick changes to the url field.
39582             * <br>
39583             * For information on creating context forms, see:
39584             * <a href="https://www.tiny.cloud/docs/tinymce/8/contextform/">
39585             * UI Components - Context forms</a>.
39586             *
39587             * @method addContextForm
39588             * @param {String} name Unique name identifying the new contextual form item.
39589             * @param {Toolbar.ContextFormSpec} obj the context form configuration object.
39590             */
39591            addContextForm: bridge.addContextForm,
39592            /**
39593             * Registers a new context menu section that only appears when a content predicate is matched,
39594             * for example, the cursor is inside a table.
39595             * <br>
39596             * For information on creating context menus, see:
39597             * <a href="https://www.tiny.cloud/docs/tinymce/8/contextmenu/">
39598             * UI Components - Context Menu</a>.
39599             *
39600             * @method addContextMenu
39601             * @param {String} name Unique name identifying the new context menu.
39602             * @param {Menu.ContextMenuSpec} obj The context menu configuration object.
39603             */
39604            addContextMenu: bridge.addContextMenu,
39605            /**
39606             * Registers a new context toolbar that only appears when a content predicate is matched for example
39607             * the cursor is on an image element.
39608             * <br>
39609             * For information on creating context toolbars, see:
39610             * <a href="https://www.tiny.cloud/docs/tinymce/8/contexttoolbar/">
39611             * UI Components - Context Toolbar</a>.
39612             *
39613             * @method addContextToolbar
39614             * @param {String} name Unique name identifying the new context toolbar.
39615             * @param {Toolbar.ContextToolbarSpec} obj The context menu configuration object.
39616             */
39617            addContextToolbar: bridge.addContextToolbar,
39618            /**
39619             * Registers a new SVG icon. The icon name reference can be configured by any
39620             * TinyMCE UI components that can display an icon. The icon is only available
39621             * to the editor instance it was configured for.
39622             *
39623             * @method addIcon
39624             * @param {String} name Unique name identifying the new icon.
39625             * @param {String} svgData The SVG data string the browser will use to render the SVG icon.
39626             * @example
39627             * //To add a simple triangle icon:
39628             * editor.ui.registry.addIcon('triangleUp', '<svg height="24" width="24"><path d="M12 0 L24 24 L0 24 Z" /></svg>');
39629             */
39630            addIcon: bridge.addIcon,
39631            /**
39632             * Registers a new menu button. Adds a toolbar button that opens a menu when
39633             * clicked. The menu can be populated by items created by addMenuItem,
39634             * addNestedMenuItem or addToggleMenuItem.
39635             * <br>
39636             * For information on creating a toolbar menu button, see:
39637             * <a href="https://www.tiny.cloud/docs/tinymce/8/custom-menu-toolbar-button/">
39638             * UI Components - Types of toolbar buttons: Menu button</a>.
39639             *
39640             * @method addMenuButton
39641             * @param {String} name Unique name identifying the new menu button.
39642             * @param {Toolbar.ToolbarMenuButtonSpec} obj The menu button configuration object.
39643             */
39644            addMenuButton: bridge.addMenuButton,
39645            /**
39646             * Registers a new menu item that executes a command when clicked or activated
39647             * via keyboard navigation controls.
39648             * <br>
39649             * For information on creating a basic menu item, see:
39650             * <a href="https://www.tiny.cloud/docs/tinymce/8/creating-custom-menu-items/">
39651             * UI Components - Custom menu items: Basic menu items</a>.
39652             *
39653             * @method addMenuItem
39654             * @param {String} name Unique name identifying the new menu item.
39655             * @param {Menu.MenuItemSpec} obj The menu item configuration object.
39656             */
39657            addMenuItem: bridge.addMenuItem,
39658            /**
39659             * Registers a new menu item that reveals a submenu when clicked or activated
39660             * by keyboard navigation controls.The submenu can be populated by items
39661             * created by addMenuItem, addNestedMenuItem or addToggleMenuItem.
39662             * <br>
39663             * For information on creating a nested menu item, see:
39664             * <a href="https://www.tiny.cloud/docs/tinymce/8/custom-nested-menu-items/">
39665             * UI Components - Custom menu items: Nested menu items</a>.
39666             *
39667             * @method addNestedMenuItem
39668             * @param {String} name Unique name identifying the new nested menu item.
39669             * @param {Menu.NestedMenuItemSpec} obj The nested menu item configuration object.
39670             */
39671            addNestedMenuItem: bridge.addNestedMenuItem,
39672            /**
39673             * Registers a new sidebar container.
39674             * This sidebar container is attached to the right side of the editor and
39675             * can be toggled open or closed. When registered, a new toolbar toggle
39676             * button with the same sidebar name is created. Additionally there is a
39677             * ToggleSidebar command and a 'ToggleSidebar' event that can used to
39678             * manage the sidebar open/closed state. The tinycomments plugin uses a
39679             * sidebar for its Ui components.
39680             * <br>
39681             * For information on creating a custom sidebar, see:
39682             * <a href="https://www.tiny.cloud/docs/tinymce/8/customsidebar/">
39683             * UI Components - Custom sidebar</a>.
39684             *
39685             * @method addSidebar
39686             * @param {String} name Unique name identifying the new sidebar.
39687             * @param {Sidebar.SidebarSpec} obj The sidebar configuration object.
39688             */
39689            addSidebar: bridge.addSidebar,
39690            /**
39691             * Registers a new split button for the toolbar. The list styles plugin uses
39692             * a split button to simplify its functionality.
39693             * <br>
39694             * For information on creating a split toolbar button, see:
39695             * <a href="https://www.tiny.cloud/docs/tinymce/8/custom-split-toolbar-button/">
39696             * UI Components - Types of toolbar buttons: Split button</a>.
39697             *
39698             * @method addSplitButton
39699             * @param {String} name Unique name identifying the new split button.
39700             * @param {Toolbar.ToolbarSplitButtonSpec} obj The split button configuration object.
39701             */
39702            addSplitButton: bridge.addSplitButton,
39703            /**
39704             * Registers a new toggle button for the toolbar. A toggle buttons state can
39705             * be set in the configuration.
39706             * <br>
39707             * For information on creating a toggle toolbar button, see:
39708             * <a href="https://www.tiny.cloud/docs/tinymce/8/custom-toggle-toolbar-button/">
39709             * UI Components - Types of toolbar buttons: Toggle button</a>.
39710             *
39711             * @method addToggleButton
39712             * @param {String} name Unique name identifying the new split button.
39713             * @param {Toolbar.ToolbarToggleButtonSpec} obj The toggle button configuration object.
39714             */
39715            addToggleButton: bridge.addToggleButton,
39716            /**
39717             * Registers a new group toolbar button for the toolbar. Renders a toolbar button that opens a floating toolbar when
39718             * clicked.
39719             * <br>
39720             * <strong>Note:</strong> Group toolbar buttons can only be used when using the floating toolbar mode.
39721             * <br>
39722             * For information on creating a group toolbar button, see:
39723             * <a href="https://www.tiny.cloud/docs/tinymce/8/custom-group-toolbar-button/">
39724             * UI Components - Types of toolbar buttons: Group toolbar button</a>.
39725             *
39726             * @method addGroupToolbarButton
39727             * @param {String} name Unique name identifying the new group toolbar button.
39728             * @param {Toolbar.GroupToolbarButtonSpec} obj The group toolbar button configuration object.
39729             */
39730            addGroupToolbarButton: bridge.addGroupToolbarButton,
39731            /**
39732             * Registers a new menu item that will act like a toggle button,
39733             * showing a tick in the menu item to represent state.
39734             * <br>
39735             * For information on creating a toggle menu item, see:
39736             * <a href="https://www.tiny.cloud/docs/tinymce/8/custom-toggle-menu-items/">
39737             * UI Components - Custom menu items: Toggle menu items</a>.
39738             *
39739             * @method addToggleMenuItem
39740             * @param {String} name Unique name identifying the new menu item.
39741             * @param {Menu.ToggleMenuItemSpec} obj The menu item configuration object.
39742             */
39743            addToggleMenuItem: bridge.addToggleMenuItem,
39744            /**
39745             * Registers a new view container.
39746             * This view container is hidden (off) by default and attached next to the main view.
39747             * It can be toggled on or off.
39748             * When it is on, the main editor view is hidden and the specific view is shown.
39749             * When it is off, the specific view is hidden and the main view is shown.
39750             * There is also a ToggleView command.
39751             * The ToggleView command can toggle the view visibility.
39752             * The ToggleView command can be queried for its current state.
39753             * <br>
39754             * For information on creating a custom view, see:
39755             * <a href="https://www.tiny.cloud/docs/tinymce/8/custom-view/">
39756             * UI Components - Custom view</a>.
39757             *
39758             * @method addView
39759             * @param {String} name Unique name identifying the new view.
39760             * @param {View.ViewSpec} obj The view configuration object.
39761             */
39762            addView: bridge.addView,
39763            /**
39764             * Registers a new context configuration in the registry.
39765             * The registry stores all context configurations.
39766             * The buttons in editor configuration object can contain a context property.
39767             * These button specifications can use the registered contexts to determine
39768             * whether to enable or disable the buttons based on the current context.
39769             *
39770             * @method addContext
39771             * @param {String} name Unique name identifying the new context configuration.
39772             * @param {Function} pred A predicate function that determines if the context is active
39773             */
39774            addContext: bridge.addContext,
39775            /* note getAll is an internal method and may not be supported in future revisions */
39776            getAll: bridge.getAll
39777        };
39778    };
39779
39780    // Shorten these names
39781    const DOM$1 = DOMUtils.DOM;
39782    const extend = Tools.extend, each$1 = Tools.each;
39783    /**
39784     * Include Editor API docs.
39785     *
39786     * @include ../../../../../tools/docs/tinymce.Editor.js
39787     */
39788    class Editor {
39789        baseUri;
39790        /**
39791         * Editor instance id, normally the same as the div/textarea that was replaced.
39792         *
39793         * @property id
39794         * @type String
39795         */
39796        id;
39797        /**
39798         * A uuid string to uniquely identify an editor across any page.
39799         *
39800         * @property editorUid
39801         * @type String
39802         */
39803        editorUid;
39804        /**
39805         * Name/Value object containing plugin instances.
39806         *
39807         * @property plugins
39808         * @type Object
39809         * @example
39810         * // Execute a method inside a plugin directly
39811         * tinymce.activeEditor.plugins.someplugin.someMethod();
39812         */
39813        plugins = {};
39814        /**
39815         * URI object to document configured for the TinyMCE instance.
39816         *
39817         * @property documentBaseURI
39818         * @type tinymce.util.URI
39819         * @example
39820         * // Get relative URL from the location of document_base_url
39821         * tinymce.activeEditor.documentBaseURI.toRelative('/somedir/somefile.htm');
39822         *
39823         * // Get absolute URL from the location of document_base_url
39824         * tinymce.activeEditor.documentBaseURI.toAbsolute('somefile.htm');
39825         */
39826        documentBaseURI;
39827        /**
39828         * URI object to current document that holds the TinyMCE editor instance.
39829         *
39830         * @property baseURI
39831         * @type tinymce.util.URI
39832         * @example
39833         * // Get relative URL from the location of the API
39834         * tinymce.activeEditor.baseURI.toRelative('/somedir/somefile.htm');
39835         *
39836         * // Get absolute URL from the location of the API
39837         * tinymce.activeEditor.baseURI.toAbsolute('somefile.htm');
39838         */
39839        baseURI;
39840        /**
39841         * Array with CSS files to load into the iframe.
39842         *
39843         * @property contentCSS
39844         * @type Array
39845         */
39846        contentCSS = [];
39847        /**
39848         * Array of CSS styles to add to head of document when the editor loads.
39849         *
39850         * @property contentStyles
39851         * @type Array
39852         */
39853        contentStyles = [];
39854        /**
39855         * Editor ui components
39856         *
39857         * @property ui
39858         * @type tinymce.editor.ui.Ui
39859         */
39860        ui;
39861        /**
39862         * Editor mode API
39863         *
39864         * @property mode
39865         * @type tinymce.EditorMode
39866         */
39867        mode;
39868        /**
39869         * Editor options API
39870         *
39871         * @property options
39872         * @type tinymce.EditorOptions
39873         */
39874        options;
39875        /**
39876         * Editor upload API
39877         *
39878         * @property editorUpload
39879         * @type tinymce.EditorUpload
39880         */
39881        editorUpload;
39882        /**
39883         * Editor user lookup API
39884         *
39885         * @property userLookup
39886         * @type tinymce.UserLookup
39887         */
39888        userLookup;
39889        shortcuts;
39890        loadedCSS = {};
39891        editorCommands;
39892        suffix;
39893        editorManager;
39894        hidden;
39895        inline;
39896        hasVisual;
39897        isNotDirty = false;
39898        // Arguments set later, for example by InitContentBody.ts
39899        // Note that these may technically be undefined up until PreInit (or similar) has fired,
39900        // however the types are aimed at an initialised editor for ease of use.
39901        annotator;
39902        bodyElement;
39903        bookmark; // Note: Intentionally any so as to not expose Optional
39904        composing = false;
39905        container;
39906        contentAreaContainer;
39907        contentDocument;
39908        contentWindow;
39909        delegates;
39910        destroyed = false;
39911        dom;
39912        editorContainer;
39913        eventRoot;
39914        formatter;
39915        formElement;
39916        formEventDelegate;
39917        hasHiddenInput = false;
39918        iframeElement = null;
39919        iframeHTML;
39920        initialized = false;
39921        notificationManager;
39922        orgDisplay;
39923        orgVisibility;
39924        parser;
39925        quirks;
39926        readonly = false;
39927        removed = false;
39928        schema;
39929        selection;
39930        serializer;
39931        startContent = '';
39932        targetElm;
39933        theme;
39934        model;
39935        undoManager;
39936        windowManager;
39937        licenseKeyManager;
39938        _beforeUnload;
39939        _eventDispatcher;
39940        _nodeChangeDispatcher;
39941        _pendingNativeEvents = [];
39942        _selectionOverrides;
39943        _skinLoaded = false;
39944        _editableRoot = true;
39945        // EditorObservable patches
39946        bindPendingEventDelegates;
39947        toggleNativeEvent;
39948        unbindAllNativeEvents;
39949        fire;
39950        dispatch;
39951        on;
39952        off;
39953        once;
39954        hasEventListeners;
39955        /**
39956         * Constructs a editor instance by id.
39957         *
39958         * @constructor
39959         * @method Editor
39960         * @param {String} id Unique id for the editor.
39961         * @param {Object} options Options for the editor.
39962         * @param {tinymce.EditorManager} editorManager EditorManager instance.
39963         */
39964        constructor(id, options, editorManager) {
39965            this.editorManager = editorManager;
39966            // Patch in the EditorObservable functions
39967            extend(this, EditorObservable);
39968            const self = this;
39969            this.id = id;
39970            this.editorUid = uuidV4();
39971            this.hidden = false;
39972            const normalizedOptions = normalizeOptions(editorManager.defaultOptions, options);
39973            this.options = create$4(self, normalizedOptions, options);
39974            register$7(self);
39975            this.userLookup = createUserLookup(this);
39976            const getOption = this.options.get;
39977            if (getOption('deprecation_warnings')) {
39978                logWarnings(options, normalizedOptions);
39979            }
39980            const suffix = getOption('suffix');
39981            if (suffix) {
39982                editorManager.suffix = suffix;
39983            }
39984            this.suffix = editorManager.suffix;
39985            const baseUrl = getOption('base_url');
39986            if (baseUrl) {
39987                editorManager._setBaseUrl(baseUrl);
39988            }
39989            this.baseUri = editorManager.baseURI;
39990            const referrerPolicy = getReferrerPolicy(self);
39991            if (referrerPolicy) {
39992                ScriptLoader.ScriptLoader._setReferrerPolicy(referrerPolicy);
39993                DOMUtils.DOM.styleSheetLoader._setReferrerPolicy(referrerPolicy);
39994            }
39995            ScriptLoader.ScriptLoader._setCrossOrigin((url) => {
39996                const crossOrigin = getCrossOrigin(self);
39997                return crossOrigin(url, 'script');
39998            });
39999            DOMUtils.DOM.styleSheetLoader._setCrossOrigin((url) => {
40000                const crossOrigin = getCrossOrigin(self);
40001                return crossOrigin(url, 'stylesheet');
40002            });
40003            const contentCssCors = hasContentCssCors(self);
40004            if (isNonNullable(contentCssCors)) {
40005                DOMUtils.DOM.styleSheetLoader._setContentCssCors(contentCssCors);
40006            }
40007            AddOnManager.languageLoad = getOption('language_load');
40008            AddOnManager.baseURL = editorManager.baseURL;
40009            this.setDirty(false);
40010            this.documentBaseURI = new URI(getDocumentBaseUrl(self), {
40011                base_uri: this.baseUri
40012            });
40013            this.baseURI = this.baseUri;
40014            this.inline = isInline$2(self);
40015            this.hasVisual = isVisualAidsEnabled(self);
40016            this.shortcuts = new Shortcuts(this);
40017            this.editorCommands = new EditorCommands(this);
40018            registerCommands(this);
40019            const cacheSuffix = getOption('cache_suffix');
40020            if (cacheSuffix) {
40021                Env.cacheSuffix = cacheSuffix.replace(/^[\?\&]+/, '');
40022            }
40023            this.ui = {
40024                registry: registry(),
40025                styleSheetLoader: undefined,
40026                show: noop,
40027                hide: noop,
40028                setEnabled: noop,
40029                isEnabled: always
40030            };
40031            this.mode = create$3(self);
40032            // Lock certain properties to reduce misuse
40033            Object.defineProperty(this, 'editorUid', {
40034                writable: false,
40035                configurable: false,
40036                enumerable: true,
40037            });
40038            // Call setup
40039            editorManager.dispatch('SetupEditor', { editor: this });
40040            const setupCallback = getSetupCallback(self);
40041            if (isFunction(setupCallback)) {
40042                setupCallback.call(self, self);
40043            }
40044        }
40045        /**
40046         * Renders the editor/adds it to the page.
40047         *
40048         * @method render
40049         */
40050        render() {
40051            render(this);
40052        }
40053        /**
40054         * Focuses/activates the editor. This will set this editor as the activeEditor in the tinymce collection
40055         * it will also place DOM focus inside the editor.
40056         *
40057         * @method focus
40058         * @param {Boolean
40058} skipFocus Skip DOM focus. Just set is as the active editor.
vendor: 22,938 bytes, lines 40058-40616
40058
40059         */
40060        focus(skipFocus) {
40061            this.execCommand('mceFocus', false, skipFocus);
40062        }
40063        /**
40064         * Returns true/false if the editor has real keyboard focus.
40065         *
40066         * @method hasFocus
40067         * @return {Boolean} Current focus state of the editor.
40068         */
40069        hasFocus() {
40070            return hasFocus(this);
40071        }
40072        /**
40073         * Translates the specified string by replacing variables with language pack items it will also check if there is
40074         * a key matching the input.
40075         *
40076         * @method translate
40077         * @param {String} text String to translate by the language pack data.
40078         * @return {String} Translated string.
40079         */
40080        translate(text) {
40081            return I18n.translate(text);
40082        }
40083        getParam(name, defaultVal, type) {
40084            const options = this.options;
40085            // To keep the legacy API we need to register the option if it's not already been registered
40086            if (!options.isRegistered(name)) {
40087                if (isNonNullable(type)) {
40088                    options.register(name, { processor: type, default: defaultVal });
40089                }
40090                else {
40091                    options.register(name, { processor: always, default: defaultVal });
40092                }
40093            }
40094            // Attempt to use the passed default value if nothing has been set already
40095            return !options.isSet(name) && !isUndefined(defaultVal) ? defaultVal : options.get(name);
40096        }
40097        /**
40098         * Checks that the plugin is in the editor configuration and can optionally check if the plugin has been loaded.
40099         *
40100         * @method hasPlugin
40101         * @param {String} name The name of the plugin, as specified for the TinyMCE `plugins` option.
40102         * @param {Boolean} loaded If `true`, will also check that the plugin has been loaded.
40103         * @return {Boolean} If `loaded` is `true`, returns `true` if the plugin is in the configuration and has been loaded. If `loaded` is `false`, returns `true` if the plugin is in the configuration, regardless of plugin load status.
40104         * @example
40105         * // Returns `true` if the Comments plugin is in the editor configuration and has loaded successfully:
40106         * tinymce.activeEditor.hasPlugin('tinycomments', true);
40107         * // Returns `true` if the Table plugin is in the editor configuration, regardless of whether or not it loads:
40108         * tinymce.activeEditor.hasPlugin('table');
40109         */
40110        hasPlugin(name, loaded) {
40111            const hasPlugin = contains$2(getPlugins(this), name);
40112            if (hasPlugin) {
40113                return loaded ? PluginManager.get(name) !== undefined : true;
40114            }
40115            else {
40116                return false;
40117            }
40118        }
40119        /**
40120         * Dispatches out a onNodeChange event to all observers. This method should be called when you
40121         * need to update the UI states or element path etc.
40122         *
40123         * @method nodeChanged
40124         * @param {Object} args Optional args to pass to NodeChange event handlers.
40125         */
40126        nodeChanged(args) {
40127            this._nodeChangeDispatcher.nodeChanged(args);
40128        }
40129        addCommand(name, callback, scope) {
40130            /**
40131             * Callback function that gets called when a command is executed.
40132             *
40133             * @callback addCommandCallback
40134             * @param {Boolean} ui Display UI state true/false.
40135             * @param {Object} value Optional value for command.
40136             * @return {Boolean} True/false state if the command was handled or not.
40137             */
40138            this.editorCommands.addCommand(name, callback, scope);
40139        }
40140        addQueryStateHandler(name, callback, scope) {
40141            /**
40142             * Callback function that gets called when a queryCommandState is executed.
40143             *
40144             * @callback addQueryStateHandlerCallback
40145             * @return {Boolean} True/false state if the command is enabled or not like is it bold.
40146             */
40147            this.editorCommands.addQueryStateHandler(name, callback, scope);
40148        }
40149        addQueryValueHandler(name, callback, scope) {
40150            /**
40151             * Callback function that gets called when a queryCommandValue is executed.
40152             *
40153             * @callback addQueryValueHandlerCallback
40154             * @return {Object} Value of the command or undefined.
40155             */
40156            this.editorCommands.addQueryValueHandler(name, callback, scope);
40157        }
40158        /**
40159         * Adds a keyboard shortcut for some command or function.
40160         *
40161         * @method addShortcut
40162         * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o.
40163         * @param {String} desc Text description for the command.
40164         * @param {String/Function} cmdFunc Command name string or function to execute when the key is pressed.
40165         * @param {Object} scope Optional scope to execute the function in.
40166         * @return {Boolean} true/false state if the shortcut was added or not.
40167         * @example
40168         * editor.addShortcut('ctrl+a', 'description of the shortcut', () => {});
40169         * editor.addShortcut('ctrl+alt+a', 'description of the shortcut', () => {});
40170         * // "meta" maps to Command on Mac and Ctrl on PC
40171         * editor.addShortcut('meta+a', 'description of the shortcut', () => {});
40172         * // "access" maps to Control+Option on Mac and shift+alt on PC
40173         * editor.addShortcut('access+a', 'description of the shortcut', () => {});
40174         *
40175         * editor.addShortcut('meta+access+c', 'Opens the code editor dialog.', () => {
40176         *   editor.execCommand('mceCodeEditor');
40177         * });
40178         *
40179         * editor.addShortcut('meta+shift+32', 'Inserts "Hello, World!" for meta+shift+space', () => {
40180         *   editor.execCommand('mceInsertContent', false, 'Hello, World!');
40181         * });
40182         */
40183        addShortcut(pattern, desc, cmdFunc, scope) {
40184            this.shortcuts.add(pattern, desc, cmdFunc, scope);
40185        }
40186        /**
40187         * Executes a registered command on the current instance. A list of available commands can be found in
40188         * the tinymce command identifiers documentation.
40189         *
40190         * @method execCommand
40191         * @param {String} cmd Command name to execute, for example mceLink or Bold.
40192         * @param {Boolean} ui Specifies if a UI (dialog) should be presented or not.
40193         * @param {Object/Array/String/Number/Boolean} value Optional command value, this can be anything.
40194         * @param {Object} args Optional arguments object.
40195         * @return {Boolean} true or false if the command was supported or not.
40196         */
40197        execCommand(cmd, ui, value, args) {
40198            return this.editorCommands.execCommand(cmd, ui, value, args);
40199        }
40200        /**
40201         * Returns a command specific state, for example if bold is enabled or not.
40202         *
40203         * @method queryCommandState
40204         * @param {String} cmd Command to query state from.
40205         * @return {Boolean} Command specific state, for example if bold is enabled or not.
40206         */
40207        queryCommandState(cmd) {
40208            return this.editorCommands.queryCommandState(cmd);
40209        }
40210        /**
40211         * Returns a command specific value, for example the current font size.
40212         *
40213         * @method queryCommandValue
40214         * @param {String} cmd Command to query value from.
40215         * @return {String} Command value, for example the current font size or an empty string (`""`) if the query command is not found.
40216         */
40217        queryCommandValue(cmd) {
40218            return this.editorCommands.queryCommandValue(cmd);
40219        }
40220        /**
40221         * Returns true/false if the command is supported or not.
40222         *
40223         * @method queryCommandSupported
40224         * @param {String} cmd Command that we check support for.
40225         * @return {Boolean} true/false if the command is supported or not.
40226         */
40227        queryCommandSupported(cmd) {
40228            return this.editorCommands.queryCommandSupported(cmd);
40229        }
40230        /**
40231         * Shows the editor and hides any textarea/div that the editor is supposed to replace.
40232         *
40233         * @method show
40234         */
40235        show() {
40236            const self = this;
40237            if (self.hidden) {
40238                self.hidden = false;
40239                if (self.inline) {
40240                    self.getBody().contentEditable = 'true';
40241                }
40242                else {
40243                    DOM$1.show(self.getContainer());
40244                    DOM$1.hide(self.id);
40245                }
40246                self.load();
40247                self.dispatch('show');
40248            }
40249        }
40250        /**
40251         * Hides the editor and shows any textarea/div that the editor is supposed to replace.
40252         *
40253         * @method hide
40254         */
40255        hide() {
40256            const self = this;
40257            if (!self.hidden) {
40258                // We must save before we hide so Safari doesn't crash
40259                self.save();
40260                if (self.inline) {
40261                    self.getBody().contentEditable = 'false';
40262                    // Make sure the editor gets blurred
40263                    if (self === self.editorManager.focusedEditor) {
40264                        self.editorManager.focusedEditor = null;
40265                    }
40266                }
40267                else {
40268                    DOM$1.hide(self.getContainer());
40269                    DOM$1.setStyle(self.id, 'display', self.orgDisplay);
40270                }
40271                self.hidden = true;
40272                self.dispatch('hide');
40273            }
40274        }
40275        /**
40276         * Returns true/false if the editor is hidden or not.
40277         *
40278         * @method isHidden
40279         * @return {Boolean} True/false if the editor is hidden or not.
40280         */
40281        isHidden() {
40282            return this.hidden;
40283        }
40284        /**
40285         * Sets the progress state, this will display a throbber/progess for the editor.
40286         * This is ideal for asynchronous operations like an AJAX save call.
40287         *
40288         * @method setProgressState
40289         * @param {Boolean} state Boolean state if the progress should be shown or hidden.
40290         * @param {Number} time Optional time to wait before the progress gets shown.
40291         * @return {Boolean} Same as the input state.
40292         * @example
40293         * // Show progress for the active editor
40294         * tinymce.activeEditor.setProgressState(true);
40295         *
40296         * // Hide progress for the active editor
40297         * tinymce.activeEditor.setProgressState(false);
40298         *
40299         * // Show progress after 3 seconds
40300         * tinymce.activeEditor.setProgressState(true, 3000);
40301         */
40302        setProgressState(state, time) {
40303            this.dispatch('ProgressState', { state, time });
40304        }
40305        /**
40306         * Loads contents from the textarea, input or other element that got converted into an editor instance.
40307         * This method will move the contents from that textarea, input or other element into the editor by using setContent
40308         * so all events etc that method has will get dispatched as well.
40309         *
40310         * @method load
40311         * @param {Object} args Optional content object, this gets passed around through the whole load process.
40312         */
40313        load(args = {}) {
40314            const self = this;
40315            const elm = self.getElement();
40316            if (self.removed) {
40317                return;
40318            }
40319            if (elm) {
40320                const loadArgs = {
40321                    ...args,
40322                    load: true
40323                };
40324                const value = isTextareaOrInput(elm) ? elm.value : elm.innerHTML;
40325                self.setContent(value, loadArgs);
40326                if (!loadArgs.no_events) {
40327                    self.dispatch('LoadContent', {
40328                        ...loadArgs,
40329                        element: elm
40330                    });
40331                }
40332            }
40333        }
40334        /**
40335         * Saves the contents from an editor out to the textarea or div element that got converted into an editor instance.
40336         * This method will move the HTML contents from the editor into that textarea or div by getContent
40337         * so all events etc that method has will get dispatched as well.
40338         *
40339         * @method save
40340         * @param {Object} args Optional content object, this gets passed around through the whole save process.
40341         * @return {String} HTML string that got set into the textarea/div.
40342         */
40343        save(args = {}) {
40344            const self = this;
40345            let elm = self.getElement();
40346            if (!elm || !self.initialized || self.removed) {
40347                return '';
40348            }
40349            const getArgs = {
40350                ...args,
40351                save: true,
40352                element: elm
40353            };
40354            let html = self.getContent(getArgs);
40355            const saveArgs = { ...getArgs, content: html };
40356            if (!saveArgs.no_events) {
40357                self.dispatch('SaveContent', saveArgs);
40358            }
40359            // Always run this internal event
40360            if (saveArgs.format === 'raw') {
40361                self.dispatch('RawSaveContent', saveArgs);
40362            }
40363            html = saveArgs.content;
40364            if (!isTextareaOrInput(elm)) {
40365                if (args.is_removing || !self.inline) {
40366                    elm.innerHTML = html;
40367                }
40368                // Update hidden form element
40369                const form = DOM$1.getParent(self.id, 'form');
40370                if (form) {
40371                    each$1(form.elements, (elm) => {
40372                        if (elm.name === self.id) {
40373                            elm.value = html;
40374                            return false;
40375                        }
40376                        else {
40377                            return true;
40378                        }
40379                    });
40380                }
40381            }
40382            else {
40383                elm.value = html;
40384            }
40385            saveArgs.element = getArgs.element = elm = null;
40386            if (saveArgs.set_dirty !== false) {
40387                self.setDirty(false);
40388            }
40389            return html;
40390        }
40391        setContent(content, args) {
40392            setContent(this, content, args);
40393        }
40394        getContent(args) {
40395            return getContent(this, args);
40396        }
40397        /**
40398         * Inserts content at caret position.
40399         *
40400         * @method insertContent
40401         * @param {String} content Content to insert.
40402         * @param {Object} args Optional args to pass to insert call.
40403         */
40404        insertContent(content, args) {
40405            if (args) {
40406                content = extend({ content }, args);
40407            }
40408            this.execCommand('mceInsertContent', false, content);
40409        }
40410        /**
40411         * Resets the editors content, undo/redo history and dirty state. If `initialContent` isn't specified, then
40412         * the editor is reset back to the initial start content.
40413         *
40414         * @method resetContent
40415         * @param {String} initialContent An optional string to use as the initial content of the editor.
40416         */
40417        resetContent(initialContent) {
40418            // Set the editor content
40419            if (initialContent === undefined) {
40420                // editor.startContent is generated by using the `raw` format, so we should set it the same way
40421                setContent(this, this.startContent, { initial: true, format: 'raw' });
40422            }
40423            else {
40424                setContent(this, initialContent, { initial: true });
40425            }
40426            // Update the editor/undo manager state
40427            this.undoManager.reset();
40428            this.setDirty(false);
40429            // Fire a node change event
40430            this.nodeChanged();
40431        }
40432        /**
40433         * Returns true/false if the editor is dirty or not. It will get dirty if the user has made modifications to the contents.
40434         *
40435         * The dirty state is automatically set to `true` when the user modifies editor content after initialization or the
40436         * last `editor.save()` call. This includes changes made using undo or redo.
40437         *
40438         * @method isDirty
40439         * @return {Boolean} True/false if the editor is dirty or not. It will get dirty if the user has made modifications to the contents.
40440         * @example
40441         * if (tinymce.activeEditor.isDirty()) {
40442         *   alert("You must save your contents.");
40443         * }
40444         */
40445        isDirty() {
40446            return !this.isNotDirty;
40447        }
40448        /**
40449         * Explicitly sets the dirty state. This will fire the dirty event if the editor dirty state is changed from false to true
40450         * by invoking this method.
40451         *
40452         * @method setDirty
40453         * @param {Boolean} state True/false if the editor is considered dirty.
40454         * @example
40455         * const ajaxSave = () => {
40456         *   const editor = tinymce.get('elm1');
40457         *
40458         *   // Save contents using some XHR call
40459         *   alert(editor.getContent());
40460         *
40461         *   editor.setDirty(false); // Force not dirty state
40462         * }
40463         */
40464        setDirty(state) {
40465            const oldState = !this.isNotDirty;
40466            this.isNotDirty = !state;
40467            if (state && state !== oldState) {
40468                this.dispatch('dirty');
40469            }
40470        }
40471        /**
40472         * Returns the container element of the editor. The container element includes
40473         * all the elements added to the page for the editor. Such as UI, iframe, etc.
40474         *
40475         * @method getContainer
40476         * @return {Element} HTML DOM element for the editor container.
40477         */
40478        getContainer() {
40479            const self = this;
40480            if (!self.container) {
40481                self.container = self.editorContainer || DOM$1.get(self.id + '_parent');
40482            }
40483            return self.container;
40484        }
40485        /**
40486         * Returns the content area container element of the editor. This element
40487         * holds the iframe or the editable element.
40488         *
40489         * @method getContentAreaContainer
40490         * @return {Element} HTML DOM element for the editor area container.
40491         */
40492        getContentAreaContainer() {
40493            return this.contentAreaContainer;
40494        }
40495        /**
40496         * Returns the target element/textarea that got replaced with a TinyMCE editor instance.
40497         *
40498         * @method getElement
40499         * @return {Element} HTML DOM element for the replaced element.
40500         */
40501        getElement() {
40502            if (!this.targetElm) {
40503                this.targetElm = DOM$1.get(this.id);
40504            }
40505            return this.targetElm;
40506        }
40507        /**
40508         * Returns the iframes window object.
40509         *
40510         * @method getWin
40511         * @return {Window} Iframe DOM window object.
40512         */
40513        getWin() {
40514            const self = this;
40515            if (!self.contentWindow) {
40516                const elm = self.iframeElement;
40517                if (elm) {
40518                    self.contentWindow = elm.contentWindow;
40519                }
40520            }
40521            return self.contentWindow;
40522        }
40523        /**
40524         * Returns the iframes document object.
40525         *
40526         * @method getDoc
40527         * @return {Document} Iframe DOM document object.
40528         */
40529        getDoc() {
40530            const self = this;
40531            if (!self.contentDocument) {
40532                const win = self.getWin();
40533                if (win) {
40534                    self.contentDocument = win.document;
40535                }
40536            }
40537            return self.contentDocument;
40538        }
40539        /**
40540         * Returns the root element of the editable area.
40541         * For a non-inline iframe-based editor, returns the iframe's body element.
40542         *
40543         * @method getBody
40544         * @return {Element} The root element of the editable area.
40545         */
40546        getBody() {
40547            const doc = this.getDoc();
40548            return this.bodyElement ?? doc?.body ?? null;
40549        }
40550        /**
40551         * URL converter function this gets executed each time a user adds an img, a or
40552         * any other element that has a URL in it. This will be called both by the DOM and HTML
40553         * manipulation functions.
40554         *
40555         * @method convertURL
40556         * @param {String} url URL to convert.
40557         * @param {String} name Attribute name src, href etc.
40558         * @param {String/HTMLElement} elm Tag name or HTML DOM element depending on HTML or DOM insert.
40559         * @return {String} Converted URL string.
40560         */
40561        convertURL(url, name, elm) {
40562            const self = this, getOption = self.options.get;
40563            // Use callback instead
40564            const urlConverterCallback = getUrlConverterCallback(self);
40565            if (isFunction(urlConverterCallback)) {
40566                return urlConverterCallback.call(self, url, elm, true, name);
40567            }
40568            // Don't convert link href since that's the CSS files that gets loaded into the editor also skip local file URLs
40569            if (!getOption('convert_urls') ||
40570                elm === 'link' ||
40571                (isObject(elm) && elm.nodeName === 'LINK') ||
40572                url.indexOf('file:') === 0 ||
40573                url.length === 0) {
40574                return url;
40575            }
40576            const urlObject = new URI(url);
40577            if (urlObject.protocol !== 'http' && urlObject.protocol !== 'https' && urlObject.protocol !== '') {
40578                return url;
40579            }
40580            // Convert to relative
40581            if (getOption('relative_urls')) {
40582                return self.documentBaseURI.toRelative(url);
40583            }
40584            // Convert to absolute
40585            url = self.documentBaseURI.toAbsolute(url, getOption('remove_script_host'));
40586            return url;
40587        }
40588        /**
40589         * Adds visual aids for tables, anchors, etc so they can be more easily edited inside the editor.
40590         *
40591         * @method addVisual
40592         * @param {Element} elm Optional root element to loop though to find tables, etc that needs the visual aid.
40593         */
40594        addVisual(elm) {
40595            addVisual(this, elm);
40596        }
40597        /**
40598         * Changes the editable state of the editor's root element.
40599         *
40600         * @method setEditableRoot
40601         * @param {Boolean} state State to set true for editable and false for non-editable.
40602         */
40603        setEditableRoot(state) {
40604            setEditableRoot(this, state);
40605        }
40606        /**
40607         * Returns the current editable state of the editor's root element.
40608         *
40609         * @method hasEditableRoot
40610         * @return {Boolean} True if the root element is editable, false if it is not editable.
40611         */
40612        hasEditableRoot() {
40613            return hasEditableRoot(this);
40614        }
40615        /**
40616         * 
40616Removes the editor from the dom and tinymce collection.
40617         *
40618         * @method remove
40619         */
40620        remove() {
40621            remove$1(this);
40622        }
40623        /**
40624         * Destroys the editor instance by removing all events, element references or other resources
40625         * that could leak memory. This method will be called automatically when the page is unloaded
vendor: 3,611 bytes, lines 40626-40722
40626         * but you can also call it directly if you know what you are doing.
40627         *
40628         * @method destroy
40629         * @param {Boolean} automatic Optional state if the destroy is an automatic destroy or user called one.
40630         */
40631        destroy(automatic) {
40632            destroy(this, automatic);
40633        }
40634        /**
40635         * Uploads all data uri/blob uri images in the editor contents to server.
40636         *
40637         * @method uploadImages
40638         * @return {Promise} Promise instance with images and status for each image.
40639         */
40640        uploadImages() {
40641            return this.editorUpload.uploadImages();
40642        }
40643        // Internal functions
40644        _scanForImages() {
40645            return this.editorUpload.scanForImages();
40646        }
40647    }
40648
40649    const DOM = DOMUtils.DOM;
40650    const each = Tools.each;
40651    let boundGlobalEvents = false;
40652    let beforeUnloadDelegate;
40653    let editors = [];
40654    const globalEventDelegate = (e) => {
40655        const type = e.type;
40656        each(EditorManager.get(), (editor) => {
40657            switch (type) {
40658                case 'scroll':
40659                    editor.dispatch('ScrollWindow', e);
40660                    break;
40661                case 'resize':
40662                    editor.dispatch('ResizeWindow', e);
40663                    break;
40664            }
40665        });
40666    };
40667    const toggleGlobalEvents = (state) => {
40668        if (state !== boundGlobalEvents) {
40669            const DOM = DOMUtils.DOM;
40670            if (state) {
40671                DOM.bind(window, 'resize', globalEventDelegate);
40672                DOM.bind(window, 'scroll', globalEventDelegate);
40673            }
40674            else {
40675                DOM.unbind(window, 'resize', globalEventDelegate);
40676                DOM.unbind(window, 'scroll', globalEventDelegate);
40677            }
40678            boundGlobalEvents = state;
40679        }
40680    };
40681    const removeEditorFromList = (targetEditor) => {
40682        const oldEditors = editors;
40683        editors = filter$5(editors, (editor) => {
40684            return targetEditor !== editor;
40685        });
40686        // Select another editor since the active one was removed
40687        if (EditorManager.activeEditor === targetEditor) {
40688            EditorManager.activeEditor = editors.length > 0 ? editors[0] : null;
40689        }
40690        // Clear focusedEditor if necessary, so that we don't try to blur the destroyed editor
40691        if (EditorManager.focusedEditor === targetEditor) {
40692            EditorManager.focusedEditor = null;
40693        }
40694        return oldEditors.length !== editors.length;
40695    };
40696    const purgeDestroyedEditor = (editor) => {
40697        // User has manually destroyed the editor lets clean up the mess
40698        if (editor && editor.initialized && !(editor.getContainer() || editor.getBody()).parentNode) {
40699            removeEditorFromList(editor);
40700            editor.unbindAllNativeEvents();
40701            editor.destroy(true);
40702            editor.removed = true;
40703        }
40704    };
40705    const isQuirksMode = document.compatMode !== 'CSS1Compat';
40706    const EditorManager = {
40707        ...Observable,
40708        baseURI: null,
40709        baseURL: null,
40710        /**
40711         * Object containing the options that will be passed by default to the <code>init</code> method upon each initialization of an editor. These defaults will be shallow merged with other options passed to <code>init</code>.
40712         *
40713         * @property defaultOptions
40714         * @type Object
40715         */
40716        defaultOptions: {},
40717        documentBaseURL: null,
40718        suffix: null,
40719        /**
40720         * A uuid string to anonymously identify the page tinymce is loaded in
40721         *
40722         * @property pageUid
40723         * @type String
40724         */
40725        pageUid: uuidV4(),
40726        /**
40727         * Major version of TinyMCE build.
40728         *
40729         * @property majorVersion
40730         * @type String
40731         */
40732        majorVersion: '8',
40733        /**
40734         * Minor version of TinyMCE build.
40735         *
40736         * @property minorVersion
40737         * @type String
40738         */
40739        minorVersion: '4.0',
40740        /**
40741         * Release date of TinyMCE build.
40742         *
40743         * @property releaseDate
40744         * @type String
40745         */
40746        releaseDate: '2026-03-31
vendor: 7,621 bytes, lines 40746-40918
40746',
40747        /**
40748         * Collection of language pack data.
40749         *
40750         * @property i18n
40751         * @type Object
40752         */
40753        i18n: I18n,
40754        /**
40755         * Currently active editor instance.
40756         *
40757         * @property activeEditor
40758         * @type tinymce.Editor
40759         * @example
40760         * tinyMCE.activeEditor.selection.getContent();
40761         * tinymce.EditorManager.activeEditor.selection.getContent();
40762         */
40763        activeEditor: null,
40764        focusedEditor: null,
40765        setup() {
40766            const self = this;
40767            let baseURL = '';
40768            let suffix = '';
40769            // Get base URL for the current document
40770            let documentBaseURL = URI.getDocumentBaseUrl(document.location);
40771            // Check if the URL is a document based format like: http://site/dir/file and file:///
40772            // leave other formats like applewebdata://... intact
40773            if (/^[^:]+:\/\/\/?[^\/]+\//.test(documentBaseURL)) {
40774                documentBaseURL = documentBaseURL.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
40775                if (!/[\/\\]$/.test(documentBaseURL)) {
40776                    documentBaseURL += '/';
40777                }
40778            }
40779            // If tinymce is defined and has a base use that or use the old tinyMCEPreInit
40780            const preInit = window.tinymce || window.tinyMCEPreInit;
40781            if (preInit) {
40782                baseURL = preInit.base || preInit.baseURL;
40783                suffix = preInit.suffix;
40784            }
40785            else {
40786                // Get base where the tinymce script is located
40787                const scripts = document.getElementsByTagName('script');
40788                for (let i = 0; i < scripts.length; i++) {
40789                    const src = scripts[i].src || '';
40790                    if (src === '') {
40791                        continue;
40792                    }
40793                    // Script types supported:
40794                    // tinymce.js tinymce.min.js tinymce.dev.js
40795                    // tinymce.jquery.js tinymce.jquery.min.js tinymce.jquery.dev.js
40796                    // tinymce.full.js tinymce.full.min.js tinymce.full.dev.js
40797                    const srcScript = src.substring(src.lastIndexOf('/'));
40798                    if (/tinymce(\.full|\.jquery|)(\.min|\.dev|)\.js/.test(src)) {
40799                        if (srcScript.indexOf('.min') !== -1) {
40800                            suffix = '.min';
40801                        }
40802                        baseURL = src.substring(0, src.lastIndexOf('/'));
40803                        break;
40804                    }
40805                }
40806                // We didn't find any baseURL by looking at the script elements
40807                // Try to use the document.currentScript as a fallback
40808                if (!baseURL && document.currentScript) {
40809                    const src = document.currentScript.src;
40810                    if (src.indexOf('.min') !== -1) {
40811                        suffix = '.min';
40812                    }
40813                    baseURL = src.substring(0, src.lastIndexOf('/'));
40814                }
40815            }
40816            /**
40817             * Base URL where the root directory if TinyMCE is located.
40818             *
40819             * @property baseURL
40820             * @type String
40821             */
40822            self.baseURL = new URI(documentBaseURL).toAbsolute(baseURL);
40823            /**
40824             * Document base URL where the current document is located.
40825             *
40826             * @property documentBaseURL
40827             * @type String
40828             */
40829            self.documentBaseURL = documentBaseURL;
40830            /**
40831             * Absolute baseURI for the installation path of TinyMCE.
40832             *
40833             * @property baseURI
40834             * @type tinymce.util.URI
40835             */
40836            self.baseURI = new URI(self.baseURL);
40837            /**
40838             * Current suffix to add to each plugin/theme that gets loaded for example ".min".
40839             *
40840             * @property suffix
40841             * @type String
40842             */
40843            self.suffix = suffix;
40844            setup$C(self);
40845            // Lock certain properties to reduce misuse
40846            each$e(['majorVersion', 'minorVersion', 'releaseDate', 'pageUid', '_addLicenseKeyManager'], (property) => Object.defineProperty(self, property, {
40847                writable: false,
40848                configurable: false,
40849                enumerable: true,
40850            }));
40851        },
40852        /**
40853         * Overrides the default options for editor instances. The <code>overrideDefaults</code> method replaces the <code>defaultOptions</code>, including any set by a previous call to the <code>overrideDefaults</code> method.
40854         * <br /><br />
40855         * When using the Tiny Cloud, some of these defaults are required for the cloud-based editor to function.
40856         * <br /><br />
40857         * Therefore, when using <code>overrideDefaults</code> with the cloud-based editor, any newly integrated options must be combined with the options in <code>tinymce.defaultOptions</code>.
40858         *
40859         * @method overrideDefaults
40860         * @param {Object} defaultOptions Default options object.
40861         * @example
40862         * const customOptions = {
40863         *   toolbar_sticky: true
40864         * };
40865         *
40866         * tinymce.overrideDefaults({
40867         *   ...tinymce.defaultOptions,
40868         *   ...customOptions
40869         * });
40870         */
40871        overrideDefaults(defaultOptions) {
40872            const baseUrl = defaultOptions.base_url;
40873            if (baseUrl) {
40874                this._setBaseUrl(baseUrl);
40875            }
40876            const suffix = defaultOptions.suffix;
40877            if (suffix) {
40878                this.suffix = suffix;
40879            }
40880            this.defaultOptions = defaultOptions;
40881            const pluginBaseUrls = defaultOptions.plugin_base_urls;
40882            if (pluginBaseUrls !== undefined) {
40883                each$d(pluginBaseUrls, (pluginBaseUrl, pluginName) => {
40884                    AddOnManager.PluginManager.urls[pluginName] = pluginBaseUrl;
40885                });
40886            }
40887        },
40888        /**
40889         * Initializes a set of editors. This method will create editors based on various settings.
40890         * <br /><br />
40891         * For information on basic usage of <code>init</code>, see: <a href="https://www.tiny.cloud/docs/tinymce/8/basic-setup/">Basic setup</a>.
40892         *
40893         * @method init
40894         * @param {Object} options Options object to be passed to each editor instance.
40895         * @return {Promise} Promise that gets resolved with an array of editors when all editor instances are initialized.
40896         * @example
40897         * // Initializes a editor using the longer method
40898         * tinymce.EditorManager.init({
40899         *    some_settings : 'some value'
40900         * });
40901         *
40902         * // Initializes a editor instance using the shorter version and with a promise
40903         * tinymce.init({
40904         *   some_settings : 'some value'
40905         * }).then((editors) => {
40906         *   ...
40907         * });
40908         */
40909        init(options) {
40910            const self = this;
40911            let result;
40912            const invalidInlineTargets = Tools.makeMap('area base basefont br col frame hr img input isindex link meta param embed source wbr track ' +
40913                'colgroup option table tbody tfoot thead tr th td script noscript style textarea video audio iframe object menu', ' ');
40914            const isInvalidInlineTarget = (options, elm) => options.inline && elm.tagName.toLowerCase() in invalidInlineTargets;
40915            const createId = (elm) => {
40916                let id = elm.id;
40917                if (!id) {
40918                    id = get$
40918a
vendor: 13,282 bytes, lines 40918-41258
40918(elm, 'name').filter((name) => !DOM.get(name)).getOrThunk(DOM.uniqueId);
40919                    elm.setAttribute('id', id);
40920                }
40921                return id;
40922            };
40923            const execCallback = (name) => {
40924                const callback = options[name];
40925                if (!callback) {
40926                    return;
40927                }
40928                return callback.apply(self, []);
40929            };
40930            const findTargets = (options) => {
40931                if (Env.browser.isIE() || Env.browser.isEdge()) {
40932                    initError('TinyMCE does not support the browser you are using. For a list of supported' +
40933                        ' browsers please see: https://www.tiny.cloud/docs/tinymce/8/support/#supportedwebbrowsers');
40934                    return [];
40935                }
40936                else if (isQuirksMode) {
40937                    initError('Failed to initialize the editor as the document is not in standards mode. ' +
40938                        'TinyMCE requires standards mode.');
40939                    return [];
40940                }
40941                else if (isString(options.selector)) {
40942                    return DOM.select(options.selector);
40943                }
40944                else if (isNonNullable(options.target)) {
40945                    return [options.target];
40946                }
40947                else {
40948                    return [];
40949                }
40950            };
40951            let provideResults = (editors) => {
40952                result = editors;
40953            };
40954            const initEditors = () => {
40955                let initCount = 0;
40956                const editors = [];
40957                let targets;
40958                const createEditor = (id, options, targetElm) => {
40959                    const editor = new Editor(id, options, self);
40960                    editors.push(editor);
40961                    editor.on('init', () => {
40962                        if (++initCount === targets.length) {
40963                            provideResults(editors);
40964                        }
40965                    });
40966                    editor.targetElm = editor.targetElm || targetElm;
40967                    editor.render();
40968                };
40969                DOM.unbind(window, 'ready', initEditors);
40970                execCallback('onpageload');
40971                targets = unique$1(findTargets(options));
40972                Tools.each(targets, (elm) => {
40973                    purgeDestroyedEditor(self.get(elm.id));
40974                });
40975                targets = Tools.grep(targets, (elm) => {
40976                    return !self.get(elm.id);
40977                });
40978                if (targets.length === 0) {
40979                    provideResults([]);
40980                }
40981                else {
40982                    each(targets, (elm) => {
40983                        if (isInvalidInlineTarget(options, elm)) {
40984                            initError('Could not initialize inline editor on invalid inline target element', elm);
40985                        }
40986                        else {
40987                            createEditor(createId(elm), options, elm);
40988                        }
40989                    });
40990                }
40991            };
40992            DOM.bind(window, 'ready', initEditors);
40993            return new Promise((resolve) => {
40994                if (result) {
40995                    resolve(result);
40996                }
40997                else {
40998                    provideResults = (editors) => {
40999                        resolve(editors);
41000                    };
41001                }
41002            });
41003        },
41004        /**
41005         * Returns an editor instance for a given id.
41006         *
41007         * @method get
41008         * @param {String/Number} id The id or index of the editor instance to return.
41009         * @return {tinymce.Editor/Array} Editor instance or an array of editor instances.
41010         * @example
41011         * // Adds an onclick event to an editor by id
41012         * tinymce.get('mytextbox').on('click', (e) => {
41013         *   ed.windowManager.alert('Hello world!');
41014         * });
41015         *
41016         * // Adds an onclick event to an editor by index
41017         * tinymce.get(0).on('click', (e) => {
41018         *   ed.windowManager.alert('Hello world!');
41019         * });
41020         *
41021         * // Adds an onclick event to an editor by id (longer version)
41022         * tinymce.EditorManager.get('mytextbox').on('click', (e) => {
41023         *   ed.windowManager.alert('Hello world!');
41024         * });
41025         */
41026        get(id) {
41027            if (arguments.length === 0) {
41028                return editors.slice(0);
41029            }
41030            else if (isString(id)) {
41031                return find$2(editors, (editor) => {
41032                    return editor.id === id;
41033                }).getOr(null);
41034            }
41035            else if (isNumber(id)) {
41036                return editors[id] ? editors[id] : null;
41037            }
41038            else {
41039                return null;
41040            }
41041        },
41042        /**
41043         * Adds an editor instance to the editor collection. This will also set it as the active editor.
41044         *
41045         * @method add
41046         * @param {tinymce.Editor} editor Editor instance to add to the collection.
41047         * @return {tinymce.Editor} The same instance that got passed in.
41048         */
41049        add(editor) {
41050            const self = this;
41051            // Prevent existing editors from being added again this could happen
41052            // if a user calls createEditor then render or add multiple times.
41053            const existingEditor = self.get(editor.id);
41054            if (existingEditor === editor) {
41055                return editor;
41056            }
41057            if (existingEditor === null) {
41058                editors.push(editor);
41059            }
41060            toggleGlobalEvents(true);
41061            // Doesn't call setActive method since we don't want
41062            // to fire a bunch of activate/deactivate calls while initializing
41063            self.activeEditor = editor;
41064            self.dispatch('AddEditor', { editor });
41065            if (!beforeUnloadDelegate) {
41066                beforeUnloadDelegate = (e) => {
41067                    const event = self.dispatch('BeforeUnload');
41068                    if (event.returnValue) {
41069                        // browsers are all a little bit special about this: https://developer.mozilla.org/en-US/docs/Web/API/BeforeUnloadEvent
41070                        e.preventDefault();
41071                        e.returnValue = event.returnValue;
41072                        return event.returnValue;
41073                    }
41074                };
41075                window.addEventListener('beforeunload', beforeUnloadDelegate);
41076            }
41077            return editor;
41078        },
41079        /**
41080         * Creates an editor instance and adds it to the EditorManager collection.
41081         *
41082         * @method createEditor
41083         * @param {String} id Instance id to use for editor.
41084         * @param {Object} options Editor instance options.
41085         * @return {tinymce.Editor} Editor instance that got created.
41086         */
41087        createEditor(id, options) {
41088            return this.add(new Editor(id, options, this));
41089        },
41090        /**
41091         * Removes a editor or editors form page.
41092         *
41093         * @example
41094         * // Remove all editors bound to divs
41095         * tinymce.remove('div');
41096         *
41097         * // Remove all editors bound to textareas
41098         * tinymce.remove('textarea');
41099         *
41100         * // Remove all editors
41101         * tinymce.remove();
41102         *
41103         * // Remove specific instance by id
41104         * tinymce.remove('#id');
41105         *
41106         * @method remove
41107         * @param {tinymce.Editor/String/Object} [selector] CSS selector or editor instance to remove.
41108         * @return {tinymce.Editor} The editor that got passed in will be return if it was found otherwise null.
41109         */
41110        remove(selector) {
41111            const self = this;
41112            let editor;
41113            // Remove all editors
41114            if (!selector) {
41115                for (let i = editors.length - 1; i >= 0; i--) {
41116                    self.remove(editors[i]);
41117                }
41118                return;
41119            }
41120            // Remove editors by selector
41121            if (isString(selector)) {
41122                each(DOM.select(selector), (elm) => {
41123                    editor = self.get(elm.id);
41124                    if (editor) {
41125                        self.remove(editor);
41126                    }
41127                });
41128                return;
41129            }
41130            // Remove specific editor
41131            editor = selector;
41132            // Not in the collection
41133            if (isNull(self.get(editor.id))) {
41134                return null;
41135            }
41136            if (removeEditorFromList(editor)) {
41137                self.dispatch('RemoveEditor', { editor });
41138            }
41139            if (editors.length === 0) {
41140                window.removeEventListener('beforeunload', beforeUnloadDelegate);
41141            }
41142            editor.remove();
41143            toggleGlobalEvents(editors.length > 0);
41144            return editor;
41145        },
41146        /**
41147         * Executes a specific command on the currently active editor.
41148         *
41149         * @method execCommand
41150         * @param {String} cmd Command to perform for example Bold.
41151         * @param {Boolean} ui Optional boolean state if a UI should be presented for the command or not.
41152         * @param {Object/String/Number/Boolean} value Optional value parameter like for example an URL to a link.
41153         * @return {Boolean} true/false if the command was executed or not.
41154         */
41155        execCommand(cmd, ui, value) {
41156            const self = this;
41157            const editorId = isObject(value) ? value.id ?? value.index : value;
41158            // Manager commands
41159            switch (cmd) {
41160                case 'mceAddEditor': {
41161                    if (!self.get(editorId)) {
41162                        const editorOptions = value.options;
41163                        new Editor(editorId, editorOptions, self).render();
41164                    }
41165                    return true;
41166                }
41167                case 'mceRemoveEditor': {
41168                    const editor = self.get(editorId);
41169                    if (editor) {
41170                        editor.remove();
41171                    }
41172                    return true;
41173                }
41174                case 'mceToggleEditor': {
41175                    const editor = self.get(editorId);
41176                    if (!editor) {
41177                        self.execCommand('mceAddEditor', false, value);
41178                        return true;
41179                    }
41180                    if (editor.isHidden()) {
41181                        editor.show();
41182                    }
41183                    else {
41184                        editor.hide();
41185                    }
41186                    return true;
41187                }
41188            }
41189            // Run command on active editor
41190            if (self.activeEditor) {
41191                return self.activeEditor.execCommand(cmd, ui, value);
41192            }
41193            return false;
41194        },
41195        /**
41196         * Calls the save method on all editor instances in the collection. This can be useful when a form is to be submitted.
41197         *
41198         * @method triggerSave
41199         * @example
41200         * // Saves all contents
41201         * tinyMCE.triggerSave();
41202         */
41203        triggerSave: () => {
41204            each(editors, (editor) => {
41205                editor.save();
41206            });
41207        },
41208        /**
41209         * Adds a language pack, this gets called by the loaded language files like en.js.
41210         *
41211         * @method addI18n
41212         * @param {String} code Optional language code.
41213         * @param {Object} items Name/value object with translations.
41214         */
41215        addI18n: (code, items) => {
41216            I18n.add(code, items);
41217        },
41218        /**
41219         * Translates the specified string using the language pack items.
41220         *
41221         * @method translate
41222         * @param {String/Array/Object} text String to translate
41223         * @return {String} Translated string.
41224         */
41225        translate: (text) => {
41226            return I18n.translate(text);
41227        },
41228        /**
41229         * Sets the active editor instance and fires the deactivate/activate events.
41230         *
41231         * @method setActive
41232         * @param {tinymce.Editor} editor Editor instance to set as the active instance.
41233         */
41234        setActive(editor) {
41235            const activeEditor = this.activeEditor;
41236            if (this.activeEditor !== editor) {
41237                if (activeEditor) {
41238                    activeEditor.dispatch('deactivate', { relatedTarget: editor });
41239                }
41240                editor.dispatch('activate', { relatedTarget: activeEditor });
41241            }
41242            this.activeEditor = editor;
41243        },
41244        _setBaseUrl(baseUrl) {
41245            this.baseURL = new URI(this.documentBaseURL).toAbsolute(baseUrl.replace(/\/+$/, ''));
41246            this.baseURI = new URI(this.baseURL);
41247        },
41248        _addLicenseKeyManager: (addOn) => LicenseKeyManagerLoader.add(addOn),
41249    };
41250    EditorManager.setup();
41251
41252    // The FakeClipboard has been designed to match the native Clipboard API as closely as possible
41253    const setup = () => {
41254        const dataValue = value$1();
41255        const FakeClipboardItem = (items) => ({
41256            items,
41257            types: keys(items),
41258            getType: (type) => get$
41258a
vendor: 10,911 bytes, lines 41258-41604
41258(items, type).getOrUndefined()
41259        });
41260        const write = (data) => {
41261            dataValue.set(data);
41262        };
41263        const read = () => dataValue.get().getOrUndefined();
41264        const clear = dataValue.clear;
41265        return {
41266            FakeClipboardItem,
41267            write,
41268            read,
41269            clear
41270        };
41271    };
41272    const FakeClipboard = setup();
41273
41274    /**
41275     * Contains various tools for rect/position calculation.
41276     *
41277     * @class tinymce.geom.Rect
41278     */
41279    const min = Math.min, max = Math.max, round = Math.round;
41280    /**
41281     * Returns the rect positioned based on the relative position name
41282     * to the target rect.
41283     *
41284     * @method relativePosition
41285     * @param {Rect} rect Source rect to modify into a new rect.
41286     * @param {Rect} targetRect Rect to move relative to based on the rel option.
41287     * @param {String} rel Relative position. For example: tr-bl.
41288     */
41289    const relativePosition = (rect, targetRect, rel) => {
41290        let x = targetRect.x;
41291        let y = targetRect.y;
41292        const w = rect.w;
41293        const h = rect.h;
41294        const targetW = targetRect.w;
41295        const targetH = targetRect.h;
41296        const relChars = (rel || '').split('');
41297        if (relChars[0] === 'b') {
41298            y += targetH;
41299        }
41300        if (relChars[1] === 'r') {
41301            x += targetW;
41302        }
41303        if (relChars[0] === 'c') {
41304            y += round(targetH / 2);
41305        }
41306        if (relChars[1] === 'c') {
41307            x += round(targetW / 2);
41308        }
41309        if (relChars[3] === 'b') {
41310            y -= h;
41311        }
41312        if (relChars[4] === 'r') {
41313            x -= w;
41314        }
41315        if (relChars[3] === 'c') {
41316            y -= round(h / 2);
41317        }
41318        if (relChars[4] === 'c') {
41319            x -= round(w / 2);
41320        }
41321        return create$2(x, y, w, h);
41322    };
41323    /**
41324     * Tests various positions to get the most suitable one.
41325     *
41326     * @method findBestRelativePosition
41327     * @param {Rect} rect Rect to use as source.
41328     * @param {Rect} targetRect Rect to move relative to.
41329     * @param {Rect} constrainRect Rect to constrain within.
41330     * @param {Array} rels Array of relative positions to test against.
41331     */
41332    const findBestRelativePosition = (rect, targetRect, constrainRect, rels) => {
41333        for (let i = 0; i < rels.length; i++) {
41334            const pos = relativePosition(rect, targetRect, rels[i]);
41335            if (pos.x >= constrainRect.x && pos.x + pos.w <= constrainRect.w + constrainRect.x &&
41336                pos.y >= constrainRect.y && pos.y + pos.h <= constrainRect.h + constrainRect.y) {
41337                return rels[i];
41338            }
41339        }
41340        return null;
41341    };
41342    /**
41343     * Inflates the rect in all directions.
41344     *
41345     * @method inflate
41346     * @param {Rect} rect Rect to expand.
41347     * @param {Number} w Relative width to expand by.
41348     * @param {Number} h Relative height to expand by.
41349     * @return {Rect} New expanded rect.
41350     */
41351    const inflate = (rect, w, h) => {
41352        return create$2(rect.x - w, rect.y - h, rect.w + w * 2, rect.h + h * 2);
41353    };
41354    /**
41355     * Returns the intersection of the specified rectangles.
41356     *
41357     * @method intersect
41358     * @param {Rect} rect The first rectangle to compare.
41359     * @param {Rect} cropRect The second rectangle to compare.
41360     * @return {Rect} The intersection of the two rectangles or null if they don't intersect.
41361     */
41362    const intersect = (rect, cropRect) => {
41363        const x1 = max(rect.x, cropRect.x);
41364        const y1 = max(rect.y, cropRect.y);
41365        const x2 = min(rect.x + rect.w, cropRect.x + cropRect.w);
41366        const y2 = min(rect.y + rect.h, cropRect.y + cropRect.h);
41367        if (x2 - x1 < 0 || y2 - y1 < 0) {
41368            return null;
41369        }
41370        return create$2(x1, y1, x2 - x1, y2 - y1);
41371    };
41372    /**
41373     * Returns a rect clamped within the specified clamp rect. This forces the
41374     * rect to be inside the clamp rect.
41375     *
41376     * @method clamp
41377     * @param {Rect} rect Rectangle to force within clamp rect.
41378     * @param {Rect} clampRect Rectable to force within.
41379     * @param {Boolean} fixedSize True/false if size should be fixed.
41380     * @return {Rect} Clamped rect.
41381     */
41382    const clamp = (rect, clampRect, fixedSize) => {
41383        let x1 = rect.x;
41384        let y1 = rect.y;
41385        let x2 = rect.x + rect.w;
41386        let y2 = rect.y + rect.h;
41387        const cx2 = clampRect.x + clampRect.w;
41388        const cy2 = clampRect.y + clampRect.h;
41389        const underflowX1 = max(0, clampRect.x - x1);
41390        const underflowY1 = max(0, clampRect.y - y1);
41391        const overflowX2 = max(0, x2 - cx2);
41392        const overflowY2 = max(0, y2 - cy2);
41393        x1 += underflowX1;
41394        y1 += underflowY1;
41395        if (fixedSize) {
41396            x2 += underflowX1;
41397            y2 += underflowY1;
41398            x1 -= overflowX2;
41399            y1 -= overflowY2;
41400        }
41401        x2 -= overflowX2;
41402        y2 -= overflowY2;
41403        return create$2(x1, y1, x2 - x1, y2 - y1);
41404    };
41405    /**
41406     * Creates a new rectangle object.
41407     *
41408     * @method create
41409     * @param {Number} x Rectangle x location.
41410     * @param {Number} y Rectangle y location.
41411     * @param {Number} w Rectangle width.
41412     * @param {Number} h Rectangle height.
41413     * @return {Rect} New rectangle object.
41414     */
41415    const create$2 = (x, y, w, h) => {
41416        return { x, y, w, h };
41417    };
41418    /**
41419     * Creates a new rectangle object form a clientRects object.
41420     *
41421     * @method fromClientRect
41422     * @param {ClientRect} clientRect DOM ClientRect object.
41423     * @return {Rect} New rectangle object.
41424     */
41425    const fromClientRect = (clientRect) => {
41426        return create$2(clientRect.left, clientRect.top, clientRect.width, clientRect.height);
41427    };
41428    const Rect = {
41429        inflate,
41430        relativePosition,
41431        findBestRelativePosition,
41432        intersect,
41433        clamp,
41434        create: create$2,
41435        fromClientRect
41436    };
41437
41438    const awaiter = (resolveCb, rejectCb, timeout = 1000) => {
41439        let done = false;
41440        let timer = null;
41441        const complete = (completer) => (...args) => {
41442            if (!done) {
41443                done = true;
41444                if (timer !== null) {
41445                    window.clearTimeout(timer);
41446                    timer = null;
41447                }
41448                completer.apply(null, args);
41449            }
41450        };
41451        const resolve = complete(resolveCb);
41452        const reject = complete(rejectCb);
41453        const start = (...args) => {
41454            if (!done && timer === null) {
41455                timer = window.setTimeout(() => reject.apply(null, args), timeout);
41456            }
41457        };
41458        return {
41459            start,
41460            resolve,
41461            reject
41462        };
41463    };
41464    const create$1 = () => {
41465        const tasks = {};
41466        const resultFns = {};
41467        const resources = {};
41468        const load = (id, url) => {
41469            const loadErrMsg = `Script at URL "${url}" failed to load`;
41470            const runErrMsg = `Script at URL "${url}" did not call \`tinymce.Resource.add('${id}', data)\` within 1 second`;
41471            if (tasks[id] !== undefined) {
41472                return tasks[id];
41473            }
41474            else {
41475                const task = new Promise((resolve, reject) => {
41476                    const waiter = awaiter(resolve, reject);
41477                    resultFns[id] = waiter.resolve;
41478                    ScriptLoader.ScriptLoader.loadScript(url).then(() => waiter.start(runErrMsg), () => waiter.reject(loadErrMsg));
41479                });
41480                tasks[id] = task;
41481                return task;
41482            }
41483        };
41484        const add = (id, data) => {
41485            if (resultFns[id] !== undefined) {
41486                resultFns[id](data);
41487                delete resultFns[id];
41488            }
41489            tasks[id] = Promise.resolve(data);
41490            resources[id] = data;
41491        };
41492        const has = (id) => {
41493            return id in resources;
41494        };
41495        const unload = (id) => {
41496            delete tasks[id];
41497            delete resources[id];
41498        };
41499        const get = (id) => resources[id];
41500        return {
41501            load,
41502            add,
41503            has,
41504            get,
41505            unload
41506        };
41507    };
41508    const Resource = create$1();
41509
41510    // Simple stub of localstorage with strict security settings #TINY-1782
41511    const create = () => (() => {
41512        let data = {};
41513        let keys = [];
41514        const storage = {
41515            getItem: (key) => {
41516                const item = data[key];
41517                return item ? item : null;
41518            },
41519            setItem: (key, value) => {
41520                keys.push(key);
41521                data[key] = String(value);
41522            },
41523            key: (index) => {
41524                return keys[index];
41525            },
41526            removeItem: (key) => {
41527                keys = keys.filter((k) => k === key);
41528                delete data[key];
41529            },
41530            clear: () => {
41531                keys = [];
41532                data = {};
41533            },
41534            length: 0
41535        };
41536        Object.defineProperty(storage, 'length', {
41537            get: () => keys.length,
41538            configurable: false,
41539            enumerable: false
41540        });
41541        return storage;
41542    })();
41543
41544    /**
41545     * @class tinymce.util.LocalStorage
41546     * @static
41547     * @version 4.0
41548     * @private
41549     * @example
41550     * tinymce.util.LocalStorage.setItem('key', 'value');
41551     * const value = tinymce.util.LocalStorage.getItem('key');
41552     */
41553    let localStorage;
41554    // Browsers with certain strict security settings will explode when trying to access localStorage
41555    // so we need to do a try/catch and a simple stub here. #TINY-1782 & #TINY-5935
41556    try {
41557        const test = '__storage_test__';
41558        localStorage = window.localStorage;
41559        // Make sure we can set a value, as storage may also be full
41560        localStorage.setItem(test, test);
41561        localStorage.removeItem(test);
41562    }
41563    catch {
41564        localStorage = create();
41565    }
41566    var LocalStorage = localStorage;
41567
41568    /**
41569     * @include ../../../../../tools/docs/tinymce.js
41570     */
41571    /**
41572     * @include ../../../../../tools/docs/tinymce.Event.js
41573     */
41574    /**
41575     * @include ../../../../../tools/docs/tinymce.editor.ui.Ui.js
41576     */
41577    const publicApi = {
41578        geom: {
41579            Rect
41580        },
41581        util: {
41582            Delay,
41583            Tools,
41584            VK,
41585            URI,
41586            EventDispatcher,
41587            Observable,
41588            I18n,
41589            LocalStorage,
41590            ImageUploader
41591        },
41592        dom: {
41593            EventUtils,
41594            TreeWalker: DomTreeWalker,
41595            TextSeeker,
41596            DOMUtils,
41597            ScriptLoader,
41598            RangeUtils,
41599            Serializer: DomSerializer,
41600            StyleSheetLoader,
41601            ControlSelection,
41602            BookmarkManager,
41603            Selection: EditorSelection,
41604            
vendor: 2,037 bytes, lines 41604-41682
41604Event: EventUtils.Event
41605        },
41606        html: {
41607            Styles,
41608            Entities,
41609            Node: AstNode,
41610            Schema,
41611            DomParser,
41612            Writer,
41613            Serializer: HtmlSerializer
41614        },
41615        Env,
41616        AddOnManager,
41617        Annotator,
41618        Formatter,
41619        UndoManager,
41620        EditorCommands,
41621        WindowManager,
41622        NotificationManager,
41623        EditorObservable,
41624        Shortcuts,
41625        Editor,
41626        FocusManager,
41627        EditorManager,
41628        // Global instances
41629        DOM: DOMUtils.DOM,
41630        ScriptLoader: ScriptLoader.ScriptLoader,
41631        PluginManager,
41632        ThemeManager,
41633        ModelManager,
41634        IconManager,
41635        Resource,
41636        FakeClipboard,
41637        // Global utility functions
41638        trim: Tools.trim,
41639        isArray: Tools.isArray,
41640        is: Tools.is,
41641        toArray: Tools.toArray,
41642        makeMap: Tools.makeMap,
41643        each: Tools.each,
41644        map: Tools.map,
41645        grep: Tools.grep,
41646        inArray: Tools.inArray,
41647        extend: Tools.extend,
41648        walk: Tools.walk,
41649        resolve: Tools.resolve,
41650        explode: Tools.explode,
41651        _addCacheSuffix: Tools._addCacheSuffix
41652    };
41653    const tinymce$1 = Tools.extend(EditorManager, publicApi);
41654
41655    const exportToModuleLoaders = (tinymce) => {
41656        if (typeof module === 'object') {
41657            try {
41658                module.exports = tinymce;
41659            }
41660            catch {
41661                // It will thrown an error when running this module
41662                // within webpack where the module.exports object is sealed
41663            }
41664        }
41665    };
41666    const exportToWindowGlobal = (tinymce) => {
41667        window.tinymce = tinymce;
41668        window.tinyMCE = tinymce;
41669    };
41670    exportToWindowGlobal(tinymce$1);
41671    exportToModuleLoaders(tinymce$1);
41672
41673})();
41674
41675
41676
41677// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
41678
41679import "controllers";
41680import "trix";
41681import "@rails/actiontext";
41682import Alpine from "alpinejs";
41683import collapse from '@alpinejs/collapse'
41684import "@hotwired/turbo-rails"
41685
41686window.Alpine = Alpine;
41687Alpine.plugin(collapse)
41688Alpine.start();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.