plugin.js 61 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606
  1. /**
  2. * TinyMCE version 8.0.2 (2025-08-14)
  3. */
  4. (function () {
  5. 'use strict';
  6. /* eslint-disable @typescript-eslint/no-wrapper-object-types */
  7. const hasProto = (v, constructor, predicate) => {
  8. var _a;
  9. if (predicate(v, constructor.prototype)) {
  10. return true;
  11. }
  12. else {
  13. // String-based fallback time
  14. return ((_a = v.constructor) === null || _a === void 0 ? void 0 : _a.name) === constructor.name;
  15. }
  16. };
  17. const typeOf = (x) => {
  18. const t = typeof x;
  19. if (x === null) {
  20. return 'null';
  21. }
  22. else if (t === 'object' && Array.isArray(x)) {
  23. return 'array';
  24. }
  25. else if (t === 'object' && hasProto(x, String, (o, proto) => proto.isPrototypeOf(o))) {
  26. return 'string';
  27. }
  28. else {
  29. return t;
  30. }
  31. };
  32. const isType$1 = (type) => (value) => typeOf(value) === type;
  33. const isSimpleType = (type) => (value) => typeof value === type;
  34. const eq$1 = (t) => (a) => t === a;
  35. const isString = isType$1('string');
  36. const isObject = isType$1('object');
  37. const isArray = isType$1('array');
  38. const isNull = eq$1(null);
  39. const isBoolean = isSimpleType('boolean');
  40. const isUndefined = eq$1(undefined);
  41. const isNullable = (a) => a === null || a === undefined;
  42. const isNonNullable = (a) => !isNullable(a);
  43. const isFunction = isSimpleType('function');
  44. const isNumber = isSimpleType('number');
  45. const noop = () => { };
  46. /** Compose a unary function with an n-ary function */
  47. const compose = (fa, fb) => {
  48. return (...args) => {
  49. return fa(fb.apply(null, args));
  50. };
  51. };
  52. /** Compose two unary functions. Similar to compose, but avoids using Function.prototype.apply. */
  53. const compose1 = (fbc, fab) => (a) => fbc(fab(a));
  54. const constant = (value) => {
  55. return () => {
  56. return value;
  57. };
  58. };
  59. function curry(fn, ...initialArgs) {
  60. return (...restArgs) => {
  61. const all = initialArgs.concat(restArgs);
  62. return fn.apply(null, all);
  63. };
  64. }
  65. const never = constant(false);
  66. const always = constant(true);
  67. /**
  68. * The `Optional` type represents a value (of any type) that potentially does
  69. * not exist. Any `Optional<T>` can either be a `Some<T>` (in which case the
  70. * value does exist) or a `None` (in which case the value does not exist). This
  71. * module defines a whole lot of FP-inspired utility functions for dealing with
  72. * `Optional` objects.
  73. *
  74. * Comparison with null or undefined:
  75. * - We don't get fancy null coalescing operators with `Optional`
  76. * - We do get fancy helper functions with `Optional`
  77. * - `Optional` support nesting, and allow for the type to still be nullable (or
  78. * another `Optional`)
  79. * - There is no option to turn off strict-optional-checks like there is for
  80. * strict-null-checks
  81. */
  82. class Optional {
  83. // The internal representation has a `tag` and a `value`, but both are
  84. // private: able to be console.logged, but not able to be accessed by code
  85. constructor(tag, value) {
  86. this.tag = tag;
  87. this.value = value;
  88. }
  89. // --- Identities ---
  90. /**
  91. * Creates a new `Optional<T>` that **does** contain a value.
  92. */
  93. static some(value) {
  94. return new Optional(true, value);
  95. }
  96. /**
  97. * Create a new `Optional<T>` that **does not** contain a value. `T` can be
  98. * any type because we don't actually have a `T`.
  99. */
  100. static none() {
  101. return Optional.singletonNone;
  102. }
  103. /**
  104. * Perform a transform on an `Optional` type. Regardless of whether this
  105. * `Optional` contains a value or not, `fold` will return a value of type `U`.
  106. * If this `Optional` does not contain a value, the `U` will be created by
  107. * calling `onNone`. If this `Optional` does contain a value, the `U` will be
  108. * created by calling `onSome`.
  109. *
  110. * For the FP enthusiasts in the room, this function:
  111. * 1. Could be used to implement all of the functions below
  112. * 2. Forms a catamorphism
  113. */
  114. fold(onNone, onSome) {
  115. if (this.tag) {
  116. return onSome(this.value);
  117. }
  118. else {
  119. return onNone();
  120. }
  121. }
  122. /**
  123. * Determine if this `Optional` object contains a value.
  124. */
  125. isSome() {
  126. return this.tag;
  127. }
  128. /**
  129. * Determine if this `Optional` object **does not** contain a value.
  130. */
  131. isNone() {
  132. return !this.tag;
  133. }
  134. // --- Functor (name stolen from Haskell / maths) ---
  135. /**
  136. * Perform a transform on an `Optional` object, **if** there is a value. If
  137. * you provide a function to turn a T into a U, this is the function you use
  138. * to turn an `Optional<T>` into an `Optional<U>`. If this **does** contain
  139. * a value then the output will also contain a value (that value being the
  140. * output of `mapper(this.value)`), and if this **does not** contain a value
  141. * then neither will the output.
  142. */
  143. map(mapper) {
  144. if (this.tag) {
  145. return Optional.some(mapper(this.value));
  146. }
  147. else {
  148. return Optional.none();
  149. }
  150. }
  151. // --- Monad (name stolen from Haskell / maths) ---
  152. /**
  153. * Perform a transform on an `Optional` object, **if** there is a value.
  154. * Unlike `map`, here the transform itself also returns an `Optional`.
  155. */
  156. bind(binder) {
  157. if (this.tag) {
  158. return binder(this.value);
  159. }
  160. else {
  161. return Optional.none();
  162. }
  163. }
  164. // --- Traversable (name stolen from Haskell / maths) ---
  165. /**
  166. * For a given predicate, this function finds out if there **exists** a value
  167. * inside this `Optional` object that meets the predicate. In practice, this
  168. * means that for `Optional`s that do not contain a value it returns false (as
  169. * no predicate-meeting value exists).
  170. */
  171. exists(predicate) {
  172. return this.tag && predicate(this.value);
  173. }
  174. /**
  175. * For a given predicate, this function finds out if **all** the values inside
  176. * this `Optional` object meet the predicate. In practice, this means that
  177. * for `Optional`s that do not contain a value it returns true (as all 0
  178. * objects do meet the predicate).
  179. */
  180. forall(predicate) {
  181. return !this.tag || predicate(this.value);
  182. }
  183. filter(predicate) {
  184. if (!this.tag || predicate(this.value)) {
  185. return this;
  186. }
  187. else {
  188. return Optional.none();
  189. }
  190. }
  191. // --- Getters ---
  192. /**
  193. * Get the value out of the inside of the `Optional` object, using a default
  194. * `replacement` value if the provided `Optional` object does not contain a
  195. * value.
  196. */
  197. getOr(replacement) {
  198. return this.tag ? this.value : replacement;
  199. }
  200. /**
  201. * Get the value out of the inside of the `Optional` object, using a default
  202. * `replacement` value if the provided `Optional` object does not contain a
  203. * value. Unlike `getOr`, in this method the `replacement` object is also
  204. * `Optional` - meaning that this method will always return an `Optional`.
  205. */
  206. or(replacement) {
  207. return this.tag ? this : replacement;
  208. }
  209. /**
  210. * Get the value out of the inside of the `Optional` object, using a default
  211. * `replacement` value if the provided `Optional` object does not contain a
  212. * value. Unlike `getOr`, in this method the `replacement` value is
  213. * "thunked" - that is to say that you don't pass a value to `getOrThunk`, you
  214. * pass a function which (if called) will **return** the `value` you want to
  215. * use.
  216. */
  217. getOrThunk(thunk) {
  218. return this.tag ? this.value : thunk();
  219. }
  220. /**
  221. * Get the value out of the inside of the `Optional` object, using a default
  222. * `replacement` value if the provided Optional object does not contain a
  223. * value.
  224. *
  225. * Unlike `or`, in this method the `replacement` value is "thunked" - that is
  226. * to say that you don't pass a value to `orThunk`, you pass a function which
  227. * (if called) will **return** the `value` you want to use.
  228. *
  229. * Unlike `getOrThunk`, in this method the `replacement` value is also
  230. * `Optional`, meaning that this method will always return an `Optional`.
  231. */
  232. orThunk(thunk) {
  233. return this.tag ? this : thunk();
  234. }
  235. /**
  236. * Get the value out of the inside of the `Optional` object, throwing an
  237. * exception if the provided `Optional` object does not contain a value.
  238. *
  239. * WARNING:
  240. * You should only be using this function if you know that the `Optional`
  241. * object **is not** empty (otherwise you're throwing exceptions in production
  242. * code, which is bad).
  243. *
  244. * In tests this is more acceptable.
  245. *
  246. * Prefer other methods to this, such as `.each`.
  247. */
  248. getOrDie(message) {
  249. if (!this.tag) {
  250. throw new Error(message !== null && message !== void 0 ? message : 'Called getOrDie on None');
  251. }
  252. else {
  253. return this.value;
  254. }
  255. }
  256. // --- Interop with null and undefined ---
  257. /**
  258. * Creates an `Optional` value from a nullable (or undefined-able) input.
  259. * Null, or undefined, is converted to `None`, and anything else is converted
  260. * to `Some`.
  261. */
  262. static from(value) {
  263. return isNonNullable(value) ? Optional.some(value) : Optional.none();
  264. }
  265. /**
  266. * Converts an `Optional` to a nullable type, by getting the value if it
  267. * exists, or returning `null` if it does not.
  268. */
  269. getOrNull() {
  270. return this.tag ? this.value : null;
  271. }
  272. /**
  273. * Converts an `Optional` to an undefined-able type, by getting the value if
  274. * it exists, or returning `undefined` if it does not.
  275. */
  276. getOrUndefined() {
  277. return this.value;
  278. }
  279. // --- Utilities ---
  280. /**
  281. * If the `Optional` contains a value, perform an action on that value.
  282. * Unlike the rest of the methods on this type, `.each` has side-effects. If
  283. * you want to transform an `Optional<T>` **into** something, then this is not
  284. * the method for you. If you want to use an `Optional<T>` to **do**
  285. * something, then this is the method for you - provided you're okay with not
  286. * doing anything in the case where the `Optional` doesn't have a value inside
  287. * it. If you're not sure whether your use-case fits into transforming
  288. * **into** something or **doing** something, check whether it has a return
  289. * value. If it does, you should be performing a transform.
  290. */
  291. each(worker) {
  292. if (this.tag) {
  293. worker(this.value);
  294. }
  295. }
  296. /**
  297. * Turn the `Optional` object into an array that contains all of the values
  298. * stored inside the `Optional`. In practice, this means the output will have
  299. * either 0 or 1 elements.
  300. */
  301. toArray() {
  302. return this.tag ? [this.value] : [];
  303. }
  304. /**
  305. * Turn the `Optional` object into a string for debugging or printing. Not
  306. * recommended for production code, but good for debugging. Also note that
  307. * these days an `Optional` object can be logged to the console directly, and
  308. * its inner value (if it exists) will be visible.
  309. */
  310. toString() {
  311. return this.tag ? `some(${this.value})` : 'none()';
  312. }
  313. }
  314. // Sneaky optimisation: every instance of Optional.none is identical, so just
  315. // reuse the same object
  316. Optional.singletonNone = new Optional(false);
  317. const nativePush = Array.prototype.push;
  318. const map = (xs, f) => {
  319. // pre-allocating array size when it's guaranteed to be known
  320. // http://jsperf.com/push-allocated-vs-dynamic/22
  321. const len = xs.length;
  322. const r = new Array(len);
  323. for (let i = 0; i < len; i++) {
  324. const x = xs[i];
  325. r[i] = f(x, i);
  326. }
  327. return r;
  328. };
  329. // Unwound implementing other functions in terms of each.
  330. // The code size is roughly the same, and it should allow for better optimisation.
  331. // const each = function<T, U>(xs: T[], f: (x: T, i?: number, xs?: T[]) => void): void {
  332. const each$1 = (xs, f) => {
  333. for (let i = 0, len = xs.length; i < len; i++) {
  334. const x = xs[i];
  335. f(x, i);
  336. }
  337. };
  338. const filter$1 = (xs, pred) => {
  339. const r = [];
  340. for (let i = 0, len = xs.length; i < len; i++) {
  341. const x = xs[i];
  342. if (pred(x, i)) {
  343. r.push(x);
  344. }
  345. }
  346. return r;
  347. };
  348. const findUntil = (xs, pred, until) => {
  349. for (let i = 0, len = xs.length; i < len; i++) {
  350. const x = xs[i];
  351. if (pred(x, i)) {
  352. return Optional.some(x);
  353. }
  354. else if (until(x, i)) {
  355. break;
  356. }
  357. }
  358. return Optional.none();
  359. };
  360. const find$1 = (xs, pred) => {
  361. return findUntil(xs, pred, never);
  362. };
  363. const flatten = (xs) => {
  364. // Note, this is possible because push supports multiple arguments:
  365. // http://jsperf.com/concat-push/6
  366. // Note that in the past, concat() would silently work (very slowly) for array-like objects.
  367. // With this change it will throw an error.
  368. const r = [];
  369. for (let i = 0, len = xs.length; i < len; ++i) {
  370. // Ensure that each value is an array itself
  371. if (!isArray(xs[i])) {
  372. throw new Error('Arr.flatten item ' + i + ' was not an array, input: ' + xs);
  373. }
  374. nativePush.apply(r, xs[i]);
  375. }
  376. return r;
  377. };
  378. const bind$3 = (xs, f) => flatten(map(xs, f));
  379. const get$5 = (xs, i) => i >= 0 && i < xs.length ? Optional.some(xs[i]) : Optional.none();
  380. const head = (xs) => get$5(xs, 0);
  381. const findMap = (arr, f) => {
  382. for (let i = 0; i < arr.length; i++) {
  383. const r = f(arr[i], i);
  384. if (r.isSome()) {
  385. return r;
  386. }
  387. }
  388. return Optional.none();
  389. };
  390. // There are many variations of Object iteration that are faster than the 'for-in' style:
  391. // http://jsperf.com/object-keys-iteration/107
  392. //
  393. // Use the native keys if it is available (IE9+), otherwise fall back to manually filtering
  394. const keys = Object.keys;
  395. const each = (obj, f) => {
  396. const props = keys(obj);
  397. for (let k = 0, len = props.length; k < len; k++) {
  398. const i = props[k];
  399. const x = obj[i];
  400. f(x, i);
  401. }
  402. };
  403. const Cell = (initial) => {
  404. let value = initial;
  405. const get = () => {
  406. return value;
  407. };
  408. const set = (v) => {
  409. value = v;
  410. };
  411. return {
  412. get,
  413. set
  414. };
  415. };
  416. // Use window object as the global if it's available since CSP will block script evals
  417. // eslint-disable-next-line @typescript-eslint/no-implied-eval
  418. const Global = typeof window !== 'undefined' ? window : Function('return this;')();
  419. /*
  420. Notes on the lift functions:
  421. - We used to have a generic liftN, but we were concerned about its type-safety, and the below variants were faster in microbenchmarks.
  422. - The getOrDie calls are partial functions, but are checked beforehand. This is faster and more convenient (but less safe) than folds.
  423. - && is used instead of a loop for simplicity and performance.
  424. */
  425. const lift2 = (oa, ob, f) => oa.isSome() && ob.isSome() ? Optional.some(f(oa.getOrDie(), ob.getOrDie())) : Optional.none();
  426. /** path :: ([String], JsObj?) -> JsObj */
  427. const path = (parts, scope) => {
  428. let o = scope !== undefined && scope !== null ? scope : Global;
  429. for (let i = 0; i < parts.length && o !== undefined && o !== null; ++i) {
  430. o = o[parts[i]];
  431. }
  432. return o;
  433. };
  434. /** resolve :: (String, JsObj?) -> JsObj */
  435. const resolve = (p, scope) => {
  436. const parts = p.split('.');
  437. return path(parts, scope);
  438. };
  439. const singleton = (doRevoke) => {
  440. const subject = Cell(Optional.none());
  441. const revoke = () => subject.get().each(doRevoke);
  442. const clear = () => {
  443. revoke();
  444. subject.set(Optional.none());
  445. };
  446. const isSet = () => subject.get().isSome();
  447. const get = () => subject.get();
  448. const set = (s) => {
  449. revoke();
  450. subject.set(Optional.some(s));
  451. };
  452. return {
  453. clear,
  454. isSet,
  455. get,
  456. set
  457. };
  458. };
  459. const unbindable = () => singleton((s) => s.unbind());
  460. const value = () => {
  461. const subject = singleton(noop);
  462. const on = (f) => subject.get().each(f);
  463. return {
  464. ...subject,
  465. on
  466. };
  467. };
  468. const contains = (str, substr, start = 0, end) => {
  469. const idx = str.indexOf(substr, start);
  470. if (idx !== -1) {
  471. return isUndefined(end) ? true : idx + substr.length <= end;
  472. }
  473. else {
  474. return false;
  475. }
  476. };
  477. // Run a function fn after rate ms. If another invocation occurs
  478. // during the time it is waiting, ignore it completely.
  479. const first = (fn, rate) => {
  480. let timer = null;
  481. const cancel = () => {
  482. if (!isNull(timer)) {
  483. clearTimeout(timer);
  484. timer = null;
  485. }
  486. };
  487. const throttle = (...args) => {
  488. if (isNull(timer)) {
  489. timer = setTimeout(() => {
  490. timer = null;
  491. fn.apply(null, args);
  492. }, rate);
  493. }
  494. };
  495. return {
  496. cancel,
  497. throttle
  498. };
  499. };
  500. const cached = (f) => {
  501. let called = false;
  502. let r;
  503. return (...args) => {
  504. if (!called) {
  505. called = true;
  506. r = f.apply(null, args);
  507. }
  508. return r;
  509. };
  510. };
  511. var global$3 = tinymce.util.Tools.resolve('tinymce.PluginManager');
  512. const get$4 = (fullscreenState) => ({
  513. isFullscreen: () => fullscreenState.get() !== null
  514. });
  515. const fromHtml = (html, scope) => {
  516. const doc = scope || document;
  517. const div = doc.createElement('div');
  518. div.innerHTML = html;
  519. if (!div.hasChildNodes() || div.childNodes.length > 1) {
  520. const message = 'HTML does not have a single root node';
  521. // eslint-disable-next-line no-console
  522. console.error(message, html);
  523. throw new Error(message);
  524. }
  525. return fromDom(div.childNodes[0]);
  526. };
  527. const fromTag = (tag, scope) => {
  528. const doc = scope || document;
  529. const node = doc.createElement(tag);
  530. return fromDom(node);
  531. };
  532. const fromText = (text, scope) => {
  533. const doc = scope || document;
  534. const node = doc.createTextNode(text);
  535. return fromDom(node);
  536. };
  537. const fromDom = (node) => {
  538. // TODO: Consider removing this check, but left atm for safety
  539. if (node === null || node === undefined) {
  540. throw new Error('Node cannot be null or undefined');
  541. }
  542. return {
  543. dom: node
  544. };
  545. };
  546. const fromPoint = (docElm, x, y) => Optional.from(docElm.dom.elementFromPoint(x, y)).map(fromDom);
  547. // tslint:disable-next-line:variable-name
  548. const SugarElement = {
  549. fromHtml,
  550. fromTag,
  551. fromText,
  552. fromDom,
  553. fromPoint
  554. };
  555. const DOCUMENT = 9;
  556. const DOCUMENT_FRAGMENT = 11;
  557. const ELEMENT = 1;
  558. const TEXT = 3;
  559. const is = (element, selector) => {
  560. const dom = element.dom;
  561. if (dom.nodeType !== ELEMENT) {
  562. return false;
  563. }
  564. else {
  565. const elem = dom;
  566. if (elem.matches !== undefined) {
  567. return elem.matches(selector);
  568. }
  569. else if (elem.msMatchesSelector !== undefined) {
  570. return elem.msMatchesSelector(selector);
  571. }
  572. else if (elem.webkitMatchesSelector !== undefined) {
  573. return elem.webkitMatchesSelector(selector);
  574. }
  575. else if (elem.mozMatchesSelector !== undefined) {
  576. // cast to any as mozMatchesSelector doesn't exist in TS DOM lib
  577. return elem.mozMatchesSelector(selector);
  578. }
  579. else {
  580. throw new Error('Browser lacks native selectors');
  581. } // unfortunately we can't throw this on startup :(
  582. }
  583. };
  584. const bypassSelector = (dom) =>
  585. // Only elements, documents and shadow roots support querySelector
  586. // shadow root element type is DOCUMENT_FRAGMENT
  587. dom.nodeType !== ELEMENT && dom.nodeType !== DOCUMENT && dom.nodeType !== DOCUMENT_FRAGMENT ||
  588. // IE fix for complex queries on empty nodes: http://jsfiddle.net/spyder/fv9ptr5L/
  589. dom.childElementCount === 0;
  590. const all$1 = (selector, scope) => {
  591. const base = scope === undefined ? document : scope.dom;
  592. return bypassSelector(base) ? [] : map(base.querySelectorAll(selector), SugarElement.fromDom);
  593. };
  594. const eq = (e1, e2) => e1.dom === e2.dom;
  595. const DeviceType = (os, browser, userAgent, mediaMatch) => {
  596. const isiPad = os.isiOS() && /ipad/i.test(userAgent) === true;
  597. const isiPhone = os.isiOS() && !isiPad;
  598. const isMobile = os.isiOS() || os.isAndroid();
  599. const isTouch = isMobile || mediaMatch('(pointer:coarse)');
  600. const isTablet = isiPad || !isiPhone && isMobile && mediaMatch('(min-device-width:768px)');
  601. const isPhone = isiPhone || isMobile && !isTablet;
  602. const iOSwebview = browser.isSafari() && os.isiOS() && /safari/i.test(userAgent) === false;
  603. const isDesktop = !isPhone && !isTablet && !iOSwebview;
  604. return {
  605. isiPad: constant(isiPad),
  606. isiPhone: constant(isiPhone),
  607. isTablet: constant(isTablet),
  608. isPhone: constant(isPhone),
  609. isTouch: constant(isTouch),
  610. isAndroid: os.isAndroid,
  611. isiOS: os.isiOS,
  612. isWebView: constant(iOSwebview),
  613. isDesktop: constant(isDesktop)
  614. };
  615. };
  616. const firstMatch = (regexes, s) => {
  617. for (let i = 0; i < regexes.length; i++) {
  618. const x = regexes[i];
  619. if (x.test(s)) {
  620. return x;
  621. }
  622. }
  623. return undefined;
  624. };
  625. const find = (regexes, agent) => {
  626. const r = firstMatch(regexes, agent);
  627. if (!r) {
  628. return { major: 0, minor: 0 };
  629. }
  630. const group = (i) => {
  631. return Number(agent.replace(r, '$' + i));
  632. };
  633. return nu$2(group(1), group(2));
  634. };
  635. const detect$3 = (versionRegexes, agent) => {
  636. const cleanedAgent = String(agent).toLowerCase();
  637. if (versionRegexes.length === 0) {
  638. return unknown$2();
  639. }
  640. return find(versionRegexes, cleanedAgent);
  641. };
  642. const unknown$2 = () => {
  643. return nu$2(0, 0);
  644. };
  645. const nu$2 = (major, minor) => {
  646. return { major, minor };
  647. };
  648. const Version = {
  649. nu: nu$2,
  650. detect: detect$3,
  651. unknown: unknown$2
  652. };
  653. const detectBrowser$1 = (browsers, userAgentData) => {
  654. return findMap(userAgentData.brands, (uaBrand) => {
  655. const lcBrand = uaBrand.brand.toLowerCase();
  656. return find$1(browsers, (browser) => { var _a; return lcBrand === ((_a = browser.brand) === null || _a === void 0 ? void 0 : _a.toLowerCase()); })
  657. .map((info) => ({
  658. current: info.name,
  659. version: Version.nu(parseInt(uaBrand.version, 10), 0)
  660. }));
  661. });
  662. };
  663. const detect$2 = (candidates, userAgent) => {
  664. const agent = String(userAgent).toLowerCase();
  665. return find$1(candidates, (candidate) => {
  666. return candidate.search(agent);
  667. });
  668. };
  669. // They (browser and os) are the same at the moment, but they might
  670. // not stay that way.
  671. const detectBrowser = (browsers, userAgent) => {
  672. return detect$2(browsers, userAgent).map((browser) => {
  673. const version = Version.detect(browser.versionRegexes, userAgent);
  674. return {
  675. current: browser.name,
  676. version
  677. };
  678. });
  679. };
  680. const detectOs = (oses, userAgent) => {
  681. return detect$2(oses, userAgent).map((os) => {
  682. const version = Version.detect(os.versionRegexes, userAgent);
  683. return {
  684. current: os.name,
  685. version
  686. };
  687. });
  688. };
  689. const normalVersionRegex = /.*?version\/\ ?([0-9]+)\.([0-9]+).*/;
  690. const checkContains = (target) => {
  691. return (uastring) => {
  692. return contains(uastring, target);
  693. };
  694. };
  695. const browsers = [
  696. // This is legacy Edge
  697. {
  698. name: 'Edge',
  699. versionRegexes: [/.*?edge\/ ?([0-9]+)\.([0-9]+)$/],
  700. search: (uastring) => {
  701. return contains(uastring, 'edge/') && contains(uastring, 'chrome') && contains(uastring, 'safari') && contains(uastring, 'applewebkit');
  702. }
  703. },
  704. // This is Google Chrome and Chromium Edge
  705. {
  706. name: 'Chromium',
  707. brand: 'Chromium',
  708. versionRegexes: [/.*?chrome\/([0-9]+)\.([0-9]+).*/, normalVersionRegex],
  709. search: (uastring) => {
  710. return contains(uastring, 'chrome') && !contains(uastring, 'chromeframe');
  711. }
  712. },
  713. {
  714. name: 'IE',
  715. versionRegexes: [/.*?msie\ ?([0-9]+)\.([0-9]+).*/, /.*?rv:([0-9]+)\.([0-9]+).*/],
  716. search: (uastring) => {
  717. return contains(uastring, 'msie') || contains(uastring, 'trident');
  718. }
  719. },
  720. // INVESTIGATE: Is this still the Opera user agent?
  721. {
  722. name: 'Opera',
  723. versionRegexes: [normalVersionRegex, /.*?opera\/([0-9]+)\.([0-9]+).*/],
  724. search: checkContains('opera')
  725. },
  726. {
  727. name: 'Firefox',
  728. versionRegexes: [/.*?firefox\/\ ?([0-9]+)\.([0-9]+).*/],
  729. search: checkContains('firefox')
  730. },
  731. {
  732. name: 'Safari',
  733. versionRegexes: [normalVersionRegex, /.*?cpu os ([0-9]+)_([0-9]+).*/],
  734. search: (uastring) => {
  735. return (contains(uastring, 'safari') || contains(uastring, 'mobile/')) && contains(uastring, 'applewebkit');
  736. }
  737. }
  738. ];
  739. const oses = [
  740. {
  741. name: 'Windows',
  742. search: checkContains('win'),
  743. versionRegexes: [/.*?windows\ nt\ ?([0-9]+)\.([0-9]+).*/]
  744. },
  745. {
  746. name: 'iOS',
  747. search: (uastring) => {
  748. return contains(uastring, 'iphone') || contains(uastring, 'ipad');
  749. },
  750. versionRegexes: [/.*?version\/\ ?([0-9]+)\.([0-9]+).*/, /.*cpu os ([0-9]+)_([0-9]+).*/, /.*cpu iphone os ([0-9]+)_([0-9]+).*/]
  751. },
  752. {
  753. name: 'Android',
  754. search: checkContains('android'),
  755. versionRegexes: [/.*?android\ ?([0-9]+)\.([0-9]+).*/]
  756. },
  757. {
  758. name: 'macOS',
  759. search: checkContains('mac os x'),
  760. versionRegexes: [/.*?mac\ os\ x\ ?([0-9]+)_([0-9]+).*/]
  761. },
  762. {
  763. name: 'Linux',
  764. search: checkContains('linux'),
  765. versionRegexes: []
  766. },
  767. { name: 'Solaris',
  768. search: checkContains('sunos'),
  769. versionRegexes: []
  770. },
  771. {
  772. name: 'FreeBSD',
  773. search: checkContains('freebsd'),
  774. versionRegexes: []
  775. },
  776. {
  777. name: 'ChromeOS',
  778. search: checkContains('cros'),
  779. versionRegexes: [/.*?chrome\/([0-9]+)\.([0-9]+).*/]
  780. }
  781. ];
  782. const PlatformInfo = {
  783. browsers: constant(browsers),
  784. oses: constant(oses)
  785. };
  786. const edge = 'Edge';
  787. const chromium = 'Chromium';
  788. const ie = 'IE';
  789. const opera = 'Opera';
  790. const firefox = 'Firefox';
  791. const safari = 'Safari';
  792. const unknown$1 = () => {
  793. return nu$1({
  794. current: undefined,
  795. version: Version.unknown()
  796. });
  797. };
  798. const nu$1 = (info) => {
  799. const current = info.current;
  800. const version = info.version;
  801. const isBrowser = (name) => () => current === name;
  802. return {
  803. current,
  804. version,
  805. isEdge: isBrowser(edge),
  806. isChromium: isBrowser(chromium),
  807. // NOTE: isIe just looks too weird
  808. isIE: isBrowser(ie),
  809. isOpera: isBrowser(opera),
  810. isFirefox: isBrowser(firefox),
  811. isSafari: isBrowser(safari)
  812. };
  813. };
  814. const Browser = {
  815. unknown: unknown$1,
  816. nu: nu$1,
  817. edge: constant(edge),
  818. chromium: constant(chromium),
  819. ie: constant(ie),
  820. opera: constant(opera),
  821. firefox: constant(firefox),
  822. safari: constant(safari)
  823. };
  824. const windows = 'Windows';
  825. const ios = 'iOS';
  826. const android = 'Android';
  827. const linux = 'Linux';
  828. const macos = 'macOS';
  829. const solaris = 'Solaris';
  830. const freebsd = 'FreeBSD';
  831. const chromeos = 'ChromeOS';
  832. // Though there is a bit of dupe with this and Browser, trying to
  833. // reuse code makes it much harder to follow and change.
  834. const unknown = () => {
  835. return nu({
  836. current: undefined,
  837. version: Version.unknown()
  838. });
  839. };
  840. const nu = (info) => {
  841. const current = info.current;
  842. const version = info.version;
  843. const isOS = (name) => () => current === name;
  844. return {
  845. current,
  846. version,
  847. isWindows: isOS(windows),
  848. // TODO: Fix capitalisation
  849. isiOS: isOS(ios),
  850. isAndroid: isOS(android),
  851. isMacOS: isOS(macos),
  852. isLinux: isOS(linux),
  853. isSolaris: isOS(solaris),
  854. isFreeBSD: isOS(freebsd),
  855. isChromeOS: isOS(chromeos)
  856. };
  857. };
  858. const OperatingSystem = {
  859. unknown,
  860. nu,
  861. windows: constant(windows),
  862. ios: constant(ios),
  863. android: constant(android),
  864. linux: constant(linux),
  865. macos: constant(macos),
  866. solaris: constant(solaris),
  867. freebsd: constant(freebsd),
  868. chromeos: constant(chromeos)
  869. };
  870. const detect$1 = (userAgent, userAgentDataOpt, mediaMatch) => {
  871. const browsers = PlatformInfo.browsers();
  872. const oses = PlatformInfo.oses();
  873. const browser = userAgentDataOpt.bind((userAgentData) => detectBrowser$1(browsers, userAgentData))
  874. .orThunk(() => detectBrowser(browsers, userAgent))
  875. .fold(Browser.unknown, Browser.nu);
  876. const os = detectOs(oses, userAgent).fold(OperatingSystem.unknown, OperatingSystem.nu);
  877. const deviceType = DeviceType(os, browser, userAgent, mediaMatch);
  878. return {
  879. browser,
  880. os,
  881. deviceType
  882. };
  883. };
  884. const PlatformDetection = {
  885. detect: detect$1
  886. };
  887. const mediaMatch = (query) => window.matchMedia(query).matches;
  888. // IMPORTANT: Must be in a thunk, otherwise rollup thinks calling this immediately
  889. // causes side effects and won't tree shake this away
  890. // Note: navigator.userAgentData is not part of the native typescript types yet
  891. let platform = cached(() => PlatformDetection.detect(window.navigator.userAgent, Optional.from((window.navigator.userAgentData)), mediaMatch));
  892. const detect = () => platform();
  893. const unsafe = (name, scope) => {
  894. return resolve(name, scope);
  895. };
  896. const getOrDie = (name, scope) => {
  897. const actual = unsafe(name, scope);
  898. if (actual === undefined || actual === null) {
  899. throw new Error(name + ' not available on this browser');
  900. }
  901. return actual;
  902. };
  903. const getPrototypeOf = Object.getPrototypeOf;
  904. /*
  905. * IE9 and above
  906. *
  907. * MDN no use on this one, but here's the link anyway:
  908. * https://developer.mozilla.org/en/docs/Web/API/HTMLElement
  909. */
  910. const sandHTMLElement = (scope) => {
  911. return getOrDie('HTMLElement', scope);
  912. };
  913. const isPrototypeOf = (x) => {
  914. // use Resolve to get the window object for x and just return undefined if it can't find it.
  915. // undefined scope later triggers using the global window.
  916. const scope = resolve('ownerDocument.defaultView', x);
  917. // TINY-7374: We can't rely on looking at the owner window HTMLElement as the element may have
  918. // been constructed in a different window and then appended to the current window document.
  919. return isObject(x) && (sandHTMLElement(scope).prototype.isPrototypeOf(x) || /^HTML\w*Element$/.test(getPrototypeOf(x).constructor.name));
  920. };
  921. const type = (element) => element.dom.nodeType;
  922. const isType = (t) => (element) => type(element) === t;
  923. const isHTMLElement = (element) => isElement(element) && isPrototypeOf(element.dom);
  924. const isElement = isType(ELEMENT);
  925. const isText = isType(TEXT);
  926. const isDocumentFragment = isType(DOCUMENT_FRAGMENT);
  927. /**
  928. * The document associated with the current element
  929. * NOTE: this will throw if the owner is null.
  930. */
  931. const owner = (element) => SugarElement.fromDom(element.dom.ownerDocument);
  932. const parent = (element) => Optional.from(element.dom.parentNode).map(SugarElement.fromDom);
  933. const parents = (element, isRoot) => {
  934. const stop = isFunction(isRoot) ? isRoot : never;
  935. // This is used a *lot* so it needs to be performant, not recursive
  936. let dom = element.dom;
  937. const ret = [];
  938. while (dom.parentNode !== null && dom.parentNode !== undefined) {
  939. const rawParent = dom.parentNode;
  940. const p = SugarElement.fromDom(rawParent);
  941. ret.push(p);
  942. if (stop(p) === true) {
  943. break;
  944. }
  945. else {
  946. dom = rawParent;
  947. }
  948. }
  949. return ret;
  950. };
  951. const siblings$2 = (element) => {
  952. // TODO: Refactor out children so we can just not add self instead of filtering afterwards
  953. const filterSelf = (elements) => filter$1(elements, (x) => !eq(element, x));
  954. return parent(element).map(children).map(filterSelf).getOr([]);
  955. };
  956. const nextSibling = (element) => Optional.from(element.dom.nextSibling).map(SugarElement.fromDom);
  957. const children = (element) => map(element.dom.childNodes, SugarElement.fromDom);
  958. /**
  959. * Is the element a ShadowRoot?
  960. *
  961. * Note: this is insufficient to test if any element is a shadow root, but it is sufficient to differentiate between
  962. * a Document and a ShadowRoot.
  963. */
  964. const isShadowRoot = (dos) => isDocumentFragment(dos) && isNonNullable(dos.dom.host);
  965. const getRootNode = (e) => SugarElement.fromDom(e.dom.getRootNode());
  966. /** If this element is in a ShadowRoot, return it. */
  967. const getShadowRoot = (e) => {
  968. const r = getRootNode(e);
  969. return isShadowRoot(r) ? Optional.some(r) : Optional.none();
  970. };
  971. /** Return the host of a ShadowRoot.
  972. *
  973. * This function will throw if Shadow DOM is unsupported in the browser, or if the host is null.
  974. * If you actually have a ShadowRoot, this shouldn't happen.
  975. */
  976. const getShadowHost = (e) => SugarElement.fromDom(e.dom.host);
  977. /**
  978. * When Events bubble up through a ShadowRoot, the browser changes the target to be the shadow host.
  979. * This function gets the "original" event target if possible.
  980. * This only works if the shadow tree is open - if the shadow tree is closed, event.target is returned.
  981. * See: https://developers.google.com/web/fundamentals/web-components/shadowdom#events
  982. */
  983. const getOriginalEventTarget = (event) => {
  984. if (isNonNullable(event.target)) {
  985. const el = SugarElement.fromDom(event.target);
  986. if (isElement(el) && isOpenShadowHost(el)) {
  987. // When target element is inside Shadow DOM we need to take first element from composedPath
  988. // otherwise we'll get Shadow Root parent, not actual target element.
  989. if (event.composed && event.composedPath) {
  990. const composedPath = event.composedPath();
  991. if (composedPath) {
  992. return head(composedPath);
  993. }
  994. }
  995. }
  996. }
  997. return Optional.from(event.target);
  998. };
  999. /** Return true if the element is a host of an open shadow root.
  1000. * Return false if the element is a host of a closed shadow root, or if the element is not a host.
  1001. */
  1002. const isOpenShadowHost = (element) => isNonNullable(element.dom.shadowRoot);
  1003. const mkEvent = (target, x, y, stop, prevent, kill, raw) => ({
  1004. target,
  1005. x,
  1006. y,
  1007. stop,
  1008. prevent,
  1009. kill,
  1010. raw
  1011. });
  1012. /** Wraps an Event in an EventArgs structure.
  1013. * The returned EventArgs structure has its target set to the "original" target if possible.
  1014. * See SugarShadowDom.getOriginalEventTarget
  1015. */
  1016. const fromRawEvent = (rawEvent) => {
  1017. const target = SugarElement.fromDom(getOriginalEventTarget(rawEvent).getOr(rawEvent.target));
  1018. const stop = () => rawEvent.stopPropagation();
  1019. const prevent = () => rawEvent.preventDefault();
  1020. const kill = compose(prevent, stop); // more of a sequence than a compose, but same effect
  1021. // FIX: Don't just expose the raw event. Need to identify what needs standardisation.
  1022. return mkEvent(target, rawEvent.clientX, rawEvent.clientY, stop, prevent, kill, rawEvent);
  1023. };
  1024. const handle = (filter, handler) => (rawEvent) => {
  1025. if (filter(rawEvent)) {
  1026. handler(fromRawEvent(rawEvent));
  1027. }
  1028. };
  1029. const binder = (element, event, filter, handler, useCapture) => {
  1030. const wrapped = handle(filter, handler);
  1031. // IE9 minimum
  1032. element.dom.addEventListener(event, wrapped, useCapture);
  1033. return {
  1034. unbind: curry(unbind, element, event, wrapped, useCapture)
  1035. };
  1036. };
  1037. const bind$2 = (element, event, filter, handler) => binder(element, event, filter, handler, false);
  1038. const unbind = (element, event, handler, useCapture) => {
  1039. // IE9 minimum
  1040. element.dom.removeEventListener(event, handler, useCapture);
  1041. };
  1042. const filter = always; // no filter on plain DomEvents
  1043. const bind$1 = (element, event, handler) => bind$2(element, event, filter, handler);
  1044. const rawSet = (dom, key, value) => {
  1045. /*
  1046. * JQuery coerced everything to a string, and silently did nothing on text node/null/undefined.
  1047. *
  1048. * We fail on those invalid cases, only allowing numbers and booleans.
  1049. */
  1050. if (isString(value) || isBoolean(value) || isNumber(value)) {
  1051. dom.setAttribute(key, value + '');
  1052. }
  1053. else {
  1054. // eslint-disable-next-line no-console
  1055. console.error('Invalid call to Attribute.set. Key ', key, ':: Value ', value, ':: Element ', dom);
  1056. throw new Error('Attribute value was not simple');
  1057. }
  1058. };
  1059. const set$1 = (element, key, value) => {
  1060. rawSet(element.dom, key, value);
  1061. };
  1062. const get$3 = (element, key) => {
  1063. const v = element.dom.getAttribute(key);
  1064. // undefined is the more appropriate value for JS, and this matches JQuery
  1065. return v === null ? undefined : v;
  1066. };
  1067. const remove = (element, key) => {
  1068. element.dom.removeAttribute(key);
  1069. };
  1070. // some elements, such as mathml, don't have style attributes
  1071. // others, such as angular elements, have style attributes that aren't a CSSStyleDeclaration
  1072. const isSupported = (dom) => dom.style !== undefined && isFunction(dom.style.getPropertyValue);
  1073. // Node.contains() is very, very, very good performance
  1074. // http://jsperf.com/closest-vs-contains/5
  1075. const inBody = (element) => {
  1076. // Technically this is only required on IE, where contains() returns false for text nodes.
  1077. // But it's cheap enough to run everywhere and Sugar doesn't have platform detection (yet).
  1078. const dom = isText(element) ? element.dom.parentNode : element.dom;
  1079. // use ownerDocument.body to ensure this works inside iframes.
  1080. // Normally contains is bad because an element "contains" itself, but here we want that.
  1081. if (dom === undefined || dom === null || dom.ownerDocument === null) {
  1082. return false;
  1083. }
  1084. const doc = dom.ownerDocument;
  1085. return getShadowRoot(SugarElement.fromDom(dom)).fold(() => doc.body.contains(dom), compose1(inBody, getShadowHost));
  1086. };
  1087. const getBody = (doc) => {
  1088. const b = doc.dom.body;
  1089. if (b === null || b === undefined) {
  1090. throw new Error('Body is not available yet');
  1091. }
  1092. return SugarElement.fromDom(b);
  1093. };
  1094. const internalSet = (dom, property, value) => {
  1095. // This is going to hurt. Apologies.
  1096. // JQuery coerces numbers to pixels for certain property names, and other times lets numbers through.
  1097. // we're going to be explicit; strings only.
  1098. if (!isString(value)) {
  1099. // eslint-disable-next-line no-console
  1100. console.error('Invalid call to CSS.set. Property ', property, ':: Value ', value, ':: Element ', dom);
  1101. throw new Error('CSS value must be a string: ' + value);
  1102. }
  1103. // removed: support for dom().style[property] where prop is camel case instead of normal property name
  1104. if (isSupported(dom)) {
  1105. dom.style.setProperty(property, value);
  1106. }
  1107. };
  1108. const set = (element, property, value) => {
  1109. const dom = element.dom;
  1110. internalSet(dom, property, value);
  1111. };
  1112. const setAll = (element, css) => {
  1113. const dom = element.dom;
  1114. each(css, (v, k) => {
  1115. internalSet(dom, k, v);
  1116. });
  1117. };
  1118. /*
  1119. * NOTE: For certain properties, this returns the "used value" which is subtly different to the "computed value" (despite calling getComputedStyle).
  1120. * Blame CSS 2.0.
  1121. *
  1122. * https://developer.mozilla.org/en-US/docs/Web/CSS/used_value
  1123. */
  1124. const get$2 = (element, property) => {
  1125. const dom = element.dom;
  1126. /*
  1127. * IE9 and above per
  1128. * https://developer.mozilla.org/en/docs/Web/API/window.getComputedStyle
  1129. *
  1130. * Not in numerosity, because it doesn't memoize and looking this up dynamically in performance critical code would be horrendous.
  1131. *
  1132. * JQuery has some magic here for IE popups, but we don't really need that.
  1133. * It also uses element.ownerDocument.defaultView to handle iframes but that hasn't been required since FF 3.6.
  1134. */
  1135. const styles = window.getComputedStyle(dom);
  1136. const r = styles.getPropertyValue(property);
  1137. // 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.
  1138. // Turns out we do this a lot.
  1139. return (r === '' && !inBody(element)) ? getUnsafeProperty(dom, property) : r;
  1140. };
  1141. // removed: support for dom().style[property] where prop is camel case instead of normal property name
  1142. // empty string is what the browsers (IE11 and Chrome) return when the propertyValue doesn't exists.
  1143. const getUnsafeProperty = (dom, property) => isSupported(dom) ? dom.style.getPropertyValue(property) : '';
  1144. const r = (left, top) => {
  1145. const translate = (x, y) => r(left + x, top + y);
  1146. return {
  1147. left,
  1148. top,
  1149. translate
  1150. };
  1151. };
  1152. // tslint:disable-next-line:variable-name
  1153. const SugarPosition = r;
  1154. // get scroll position (x,y) relative to document _doc (or global if not supplied)
  1155. const get$1 = (_DOC) => {
  1156. const doc = _DOC !== undefined ? _DOC.dom : document;
  1157. // ASSUMPTION: This is for cross-browser support, body works for Safari & EDGE, and when we have an iframe body scroller
  1158. const x = doc.body.scrollLeft || doc.documentElement.scrollLeft;
  1159. const y = doc.body.scrollTop || doc.documentElement.scrollTop;
  1160. return SugarPosition(x, y);
  1161. };
  1162. // 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.
  1163. const supports = (element) => element.dom.classList !== undefined;
  1164. const has = (element, clazz) => supports(element) && element.dom.classList.contains(clazz);
  1165. const ancestors$1 = (scope, predicate, isRoot) => filter$1(parents(scope, isRoot), predicate);
  1166. const siblings$1 = (scope, predicate) => filter$1(siblings$2(scope), predicate);
  1167. const all = (selector) => all$1(selector);
  1168. // For all of the following:
  1169. //
  1170. // jQuery does siblings of firstChild. IE9+ supports scope.dom.children (similar to Traverse.children but elements only).
  1171. // Traverse should also do this (but probably not by default).
  1172. //
  1173. const ancestors = (scope, selector, isRoot) =>
  1174. // It may surprise you to learn this is exactly what JQuery does
  1175. // TODO: Avoid all this wrapping and unwrapping
  1176. ancestors$1(scope, (e) => is(e, selector), isRoot);
  1177. const siblings = (scope, selector) =>
  1178. // It may surprise you to learn this is exactly what JQuery does
  1179. // TODO: Avoid all the wrapping and unwrapping
  1180. siblings$1(scope, (e) => is(e, selector));
  1181. const get = (_win) => {
  1182. const win = _win === undefined ? window : _win;
  1183. if (detect().browser.isFirefox()) {
  1184. // TINY-7984: Firefox 91 is returning incorrect values for visualViewport.pageTop, so disable it for now
  1185. return Optional.none();
  1186. }
  1187. else {
  1188. return Optional.from(win.visualViewport);
  1189. }
  1190. };
  1191. const bounds = (x, y, width, height) => ({
  1192. x,
  1193. y,
  1194. width,
  1195. height,
  1196. right: x + width,
  1197. bottom: y + height
  1198. });
  1199. const getBounds = (_win) => {
  1200. const win = _win === undefined ? window : _win;
  1201. const doc = win.document;
  1202. const scroll = get$1(SugarElement.fromDom(doc));
  1203. return get(win).fold(() => {
  1204. const html = win.document.documentElement;
  1205. // Don't use window.innerWidth/innerHeight here, as we don't want to include scrollbars
  1206. // since the right/bottom position is based on the edge of the scrollbar not the window
  1207. const width = html.clientWidth;
  1208. const height = html.clientHeight;
  1209. return bounds(scroll.left, scroll.top, width, height);
  1210. }, (visualViewport) =>
  1211. // iOS doesn't update the pageTop/pageLeft when element.scrollIntoView() is called, so we need to fallback to the
  1212. // scroll position which will always be less than the page top/left values when page top/left are accurate/correct.
  1213. bounds(Math.max(visualViewport.pageLeft, scroll.left), Math.max(visualViewport.pageTop, scroll.top), visualViewport.width, visualViewport.height));
  1214. };
  1215. const bind = (name, callback, _win) => get(_win).map((visualViewport) => {
  1216. const handler = (e) => callback(fromRawEvent(e));
  1217. visualViewport.addEventListener(name, handler);
  1218. return {
  1219. unbind: () => visualViewport.removeEventListener(name, handler)
  1220. };
  1221. }).getOrThunk(() => ({
  1222. unbind: noop
  1223. }));
  1224. var global$2 = tinymce.util.Tools.resolve('tinymce.dom.DOMUtils');
  1225. var global$1 = tinymce.util.Tools.resolve('tinymce.Env');
  1226. const fireFullscreenStateChanged = (editor, state) => {
  1227. editor.dispatch('FullscreenStateChanged', { state });
  1228. editor.dispatch('ResizeEditor');
  1229. };
  1230. const option = (name) => (editor) => editor.options.get(name);
  1231. const register$2 = (editor) => {
  1232. const registerOption = editor.options.register;
  1233. registerOption('fullscreen_native', {
  1234. processor: 'boolean',
  1235. default: false
  1236. });
  1237. };
  1238. const getFullscreenNative = option('fullscreen_native');
  1239. const getFullscreenRoot = (editor) => {
  1240. const elem = SugarElement.fromDom(editor.getElement());
  1241. return getShadowRoot(elem).map(getShadowHost)
  1242. .getOrThunk(() => getBody(owner(elem)));
  1243. };
  1244. const getFullscreenElement = (root) => {
  1245. if (root.fullscreenElement !== undefined) {
  1246. return root.fullscreenElement;
  1247. }
  1248. else if (root.msFullscreenElement !== undefined) {
  1249. return root.msFullscreenElement;
  1250. }
  1251. else if (root.webkitFullscreenElement !== undefined) {
  1252. return root.webkitFullscreenElement;
  1253. }
  1254. else {
  1255. return null;
  1256. }
  1257. };
  1258. const getFullscreenchangeEventName = () => {
  1259. if (document.fullscreenElement !== undefined) {
  1260. return 'fullscreenchange';
  1261. }
  1262. else if (document.msFullscreenElement !== undefined) {
  1263. return 'MSFullscreenChange'; // warning, seems to be case sensitive
  1264. }
  1265. else if (document.webkitFullscreenElement !== undefined) {
  1266. return 'webkitfullscreenchange';
  1267. }
  1268. else {
  1269. return 'fullscreenchange';
  1270. }
  1271. };
  1272. const requestFullscreen = (sugarElem) => {
  1273. const elem = sugarElem.dom;
  1274. if (elem.requestFullscreen) {
  1275. // eslint-disable-next-line @typescript-eslint/no-floating-promises
  1276. elem.requestFullscreen();
  1277. }
  1278. else if (elem.msRequestFullscreen) {
  1279. elem.msRequestFullscreen();
  1280. }
  1281. else if (elem.webkitRequestFullScreen) {
  1282. elem.webkitRequestFullScreen();
  1283. }
  1284. };
  1285. const exitFullscreen = (sugarDoc) => {
  1286. const doc = sugarDoc.dom;
  1287. if (doc.exitFullscreen) {
  1288. // eslint-disable-next-line @typescript-eslint/no-floating-promises
  1289. doc.exitFullscreen();
  1290. }
  1291. else if (doc.msExitFullscreen) {
  1292. doc.msExitFullscreen();
  1293. }
  1294. else if (doc.webkitCancelFullScreen) {
  1295. doc.webkitCancelFullScreen();
  1296. }
  1297. };
  1298. const isFullscreenElement = (elem) => elem.dom === getFullscreenElement(owner(elem).dom);
  1299. const attr = 'data-ephox-mobile-fullscreen-style';
  1300. const siblingStyles = 'display:none!important;';
  1301. const ancestorPosition = 'position:absolute!important;';
  1302. // TINY-3407 ancestors need 'height:100%!important;overflow:visible!important;' to prevent collapsed ancestors hiding the editor
  1303. const ancestorStyles = 'top:0!important;left:0!important;margin:0!important;padding:0!important;width:100%!important;height:100%!important;overflow:visible!important;';
  1304. const bgFallback = 'background-color:rgb(255,255,255)!important;';
  1305. const isAndroid = global$1.os.isAndroid();
  1306. const matchColor = (editorBody) => {
  1307. // in iOS you can overscroll, sometimes when you overscroll you can reveal the bgcolor of an element beneath,
  1308. // by matching the bg color and clobbering ensures any reveals are 'camouflaged' the same color
  1309. const color = get$2(editorBody, 'background-color');
  1310. return (color !== undefined && color !== '') ? 'background-color:' + color + '!important' : bgFallback;
  1311. };
  1312. // We clobber all tags, direct ancestors to the editorBody get ancestorStyles, everything else gets siblingStyles
  1313. const clobberStyles = (dom, container, editorBody) => {
  1314. const gatherSiblings = (element) => {
  1315. return siblings(element, '*:not(.tox-silver-sink)');
  1316. };
  1317. const clobber = (clobberStyle) => (element) => {
  1318. const styles = get$3(element, 'style');
  1319. const backup = styles === undefined ? 'no-styles' : styles.trim();
  1320. if (backup === clobberStyle) {
  1321. return;
  1322. }
  1323. else {
  1324. set$1(element, attr, backup);
  1325. setAll(element, dom.parseStyle(clobberStyle));
  1326. }
  1327. };
  1328. const ancestors$1 = ancestors(container, '*');
  1329. const siblings$1 = bind$3(ancestors$1, gatherSiblings);
  1330. const bgColor = matchColor(editorBody);
  1331. /* NOTE: This assumes that container has no siblings itself */
  1332. each$1(siblings$1, clobber(siblingStyles));
  1333. each$1(ancestors$1, clobber(ancestorPosition + ancestorStyles + bgColor));
  1334. // position absolute on the outer-container breaks Android flex layout
  1335. const containerStyles = isAndroid === true ? '' : ancestorPosition;
  1336. clobber(containerStyles + ancestorStyles + bgColor)(container);
  1337. };
  1338. const restoreStyles = (dom) => {
  1339. const clobberedEls = all('[' + attr + ']');
  1340. each$1(clobberedEls, (element) => {
  1341. const restore = get$3(element, attr);
  1342. if (restore && restore !== 'no-styles') {
  1343. setAll(element, dom.parseStyle(restore));
  1344. }
  1345. else {
  1346. remove(element, 'style');
  1347. }
  1348. remove(element, attr);
  1349. });
  1350. };
  1351. const DOM = global$2.DOM;
  1352. const getScrollPos = () => getBounds(window);
  1353. const setScrollPos = (pos) => window.scrollTo(pos.x, pos.y);
  1354. const viewportUpdate = get().fold(() => ({ bind: noop, unbind: noop }), (visualViewport) => {
  1355. const editorContainer = value();
  1356. const resizeBinder = unbindable();
  1357. const scrollBinder = unbindable();
  1358. const refreshScroll = () => {
  1359. document.body.scrollTop = 0;
  1360. document.documentElement.scrollTop = 0;
  1361. };
  1362. const refreshVisualViewport = () => {
  1363. window.requestAnimationFrame(() => {
  1364. editorContainer.on((container) => setAll(container, {
  1365. top: visualViewport.offsetTop + 'px',
  1366. left: visualViewport.offsetLeft + 'px',
  1367. height: visualViewport.height + 'px',
  1368. width: visualViewport.width + 'px'
  1369. }));
  1370. });
  1371. };
  1372. const update = first(() => {
  1373. refreshScroll();
  1374. refreshVisualViewport();
  1375. }, 50);
  1376. const bind$1 = (element) => {
  1377. editorContainer.set(element);
  1378. update.throttle();
  1379. resizeBinder.set(bind('resize', update.throttle));
  1380. scrollBinder.set(bind('scroll', update.throttle));
  1381. };
  1382. const unbind = () => {
  1383. editorContainer.on(() => {
  1384. resizeBinder.clear();
  1385. scrollBinder.clear();
  1386. });
  1387. editorContainer.clear();
  1388. };
  1389. return {
  1390. bind: bind$1,
  1391. unbind
  1392. };
  1393. });
  1394. const toggleFullscreen = (editor, fullscreenState) => {
  1395. const body = document.body;
  1396. const documentElement = document.documentElement;
  1397. const editorContainer = editor.getContainer();
  1398. const editorContainerS = SugarElement.fromDom(editorContainer);
  1399. const sinkContainerS = nextSibling(editorContainerS)
  1400. .filter((elm) => isHTMLElement(elm) && has(elm, 'tox-silver-sink'));
  1401. const fullscreenRoot = getFullscreenRoot(editor);
  1402. const fullscreenInfo = fullscreenState.get();
  1403. const editorBody = SugarElement.fromDom(editor.getBody());
  1404. const isTouch = global$1.deviceType.isTouch();
  1405. const editorContainerStyle = editorContainer.style;
  1406. const iframe = editor.iframeElement;
  1407. const iframeStyle = iframe === null || iframe === void 0 ? void 0 : iframe.style;
  1408. const handleClasses = (handler) => {
  1409. handler(body, 'tox-fullscreen');
  1410. handler(documentElement, 'tox-fullscreen');
  1411. handler(editorContainer, 'tox-fullscreen');
  1412. getShadowRoot(editorContainerS)
  1413. .map((root) => getShadowHost(root).dom)
  1414. .each((host) => {
  1415. handler(host, 'tox-fullscreen');
  1416. handler(host, 'tox-shadowhost');
  1417. });
  1418. };
  1419. const cleanup = () => {
  1420. if (isTouch) {
  1421. restoreStyles(editor.dom);
  1422. }
  1423. handleClasses(DOM.removeClass);
  1424. viewportUpdate.unbind();
  1425. Optional.from(fullscreenState.get()).each((info) => info.fullscreenChangeHandler.unbind());
  1426. };
  1427. if (!fullscreenInfo) {
  1428. const fullscreenChangeHandler = bind$1(owner(fullscreenRoot), getFullscreenchangeEventName(), (_evt) => {
  1429. if (getFullscreenNative(editor)) {
  1430. // if we have exited browser fullscreen with Escape then exit editor fullscreen too
  1431. if (!isFullscreenElement(fullscreenRoot) && fullscreenState.get() !== null) {
  1432. toggleFullscreen(editor, fullscreenState);
  1433. }
  1434. }
  1435. });
  1436. const newFullScreenInfo = {
  1437. scrollPos: getScrollPos(),
  1438. containerWidth: editorContainerStyle.width,
  1439. containerHeight: editorContainerStyle.height,
  1440. containerTop: editorContainerStyle.top,
  1441. containerLeft: editorContainerStyle.left,
  1442. iframeWidth: iframeStyle.width,
  1443. iframeHeight: iframeStyle.height,
  1444. fullscreenChangeHandler,
  1445. sinkCssPosition: sinkContainerS.map((elm) => get$2(elm, 'position'))
  1446. };
  1447. if (isTouch) {
  1448. clobberStyles(editor.dom, editorContainerS, editorBody);
  1449. }
  1450. iframeStyle.width = iframeStyle.height = '100%';
  1451. editorContainerStyle.width = editorContainerStyle.height = '';
  1452. handleClasses(DOM.addClass);
  1453. sinkContainerS.each((elm) => {
  1454. set(elm, 'position', 'fixed');
  1455. });
  1456. viewportUpdate.bind(editorContainerS);
  1457. editor.on('remove', cleanup);
  1458. fullscreenState.set(newFullScreenInfo);
  1459. if (getFullscreenNative(editor)) {
  1460. requestFullscreen(fullscreenRoot);
  1461. }
  1462. fireFullscreenStateChanged(editor, true);
  1463. }
  1464. else {
  1465. fullscreenInfo.fullscreenChangeHandler.unbind();
  1466. if (getFullscreenNative(editor) && isFullscreenElement(fullscreenRoot)) {
  1467. exitFullscreen(owner(fullscreenRoot));
  1468. }
  1469. iframeStyle.width = fullscreenInfo.iframeWidth;
  1470. iframeStyle.height = fullscreenInfo.iframeHeight;
  1471. editorContainerStyle.width = fullscreenInfo.containerWidth;
  1472. editorContainerStyle.height = fullscreenInfo.containerHeight;
  1473. editorContainerStyle.top = fullscreenInfo.containerTop;
  1474. editorContainerStyle.left = fullscreenInfo.containerLeft;
  1475. lift2(sinkContainerS, fullscreenInfo.sinkCssPosition, (elm, val) => {
  1476. set(elm, 'position', val);
  1477. });
  1478. cleanup();
  1479. setScrollPos(fullscreenInfo.scrollPos);
  1480. fullscreenState.set(null);
  1481. fireFullscreenStateChanged(editor, false);
  1482. editor.off('remove', cleanup);
  1483. }
  1484. };
  1485. const register$1 = (editor, fullscreenState) => {
  1486. editor.addCommand('mceFullScreen', () => {
  1487. toggleFullscreen(editor, fullscreenState);
  1488. });
  1489. };
  1490. var global = tinymce.util.Tools.resolve('tinymce.util.VK');
  1491. const setup = (editor, fullscreenState) => {
  1492. editor.on('init', () => {
  1493. editor.on('keydown', (e) => {
  1494. if (e.keyCode === global.TAB && !(e.metaKey || e.ctrlKey) && fullscreenState.get()) {
  1495. e.preventDefault();
  1496. }
  1497. });
  1498. });
  1499. };
  1500. const makeSetupHandler = (editor, fullscreenState) => (api) => {
  1501. api.setActive(fullscreenState.get() !== null);
  1502. const editorEventCallback = (e) => api.setActive(e.state);
  1503. editor.on('FullscreenStateChanged', editorEventCallback);
  1504. return () => editor.off('FullscreenStateChanged', editorEventCallback);
  1505. };
  1506. const register = (editor, fullscreenState) => {
  1507. const onAction = () => editor.execCommand('mceFullScreen');
  1508. editor.ui.registry.addToggleMenuItem('fullscreen', {
  1509. text: 'Fullscreen',
  1510. icon: 'fullscreen',
  1511. shortcut: 'Meta+Shift+F',
  1512. onAction,
  1513. onSetup: makeSetupHandler(editor, fullscreenState),
  1514. context: 'any'
  1515. });
  1516. editor.ui.registry.addToggleButton('fullscreen', {
  1517. tooltip: 'Fullscreen',
  1518. icon: 'fullscreen',
  1519. onAction,
  1520. onSetup: makeSetupHandler(editor, fullscreenState),
  1521. shortcut: 'Meta+Shift+F',
  1522. context: 'any'
  1523. });
  1524. };
  1525. var Plugin = () => {
  1526. global$3.add('fullscreen', (editor) => {
  1527. const fullscreenState = Cell(null);
  1528. if (editor.inline) {
  1529. return get$4(fullscreenState);
  1530. }
  1531. register$2(editor);
  1532. register$1(editor, fullscreenState);
  1533. register(editor, fullscreenState);
  1534. setup(editor, fullscreenState);
  1535. editor.addShortcut('Meta+Shift+F', '', 'mceFullScreen');
  1536. return get$4(fullscreenState);
  1537. });
  1538. };
  1539. Plugin();
  1540. /** *****
  1541. * DO NOT EXPORT ANYTHING
  1542. *
  1543. * IF YOU DO ROLLUP WILL LEAVE A GLOBAL ON THE PAGE
  1544. *******/
  1545. })();