tinymce-rails 8.6.0 → 8.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/source/tinymce/tinymce.js +269 -50
  3. data/lib/tinymce/rails/version.rb +2 -2
  4. data/vendor/assets/javascripts/tinymce/icons/default/icons.js +1 -1
  5. data/vendor/assets/javascripts/tinymce/plugins/autolink/plugin.js +1 -1
  6. data/vendor/assets/javascripts/tinymce/plugins/autosave/plugin.js +1 -1
  7. data/vendor/assets/javascripts/tinymce/plugins/codesample/plugin.js +2 -2
  8. data/vendor/assets/javascripts/tinymce/plugins/emoticons/plugin.js +1 -1
  9. data/vendor/assets/javascripts/tinymce/plugins/help/plugin.js +1 -1
  10. data/vendor/assets/javascripts/tinymce/plugins/image/plugin.js +1 -1
  11. data/vendor/assets/javascripts/tinymce/plugins/preview/plugin.js +1 -1
  12. data/vendor/assets/javascripts/tinymce/plugins/table/plugin.js +1 -1
  13. data/vendor/assets/javascripts/tinymce/skins/content/dark/content.css +1 -1
  14. data/vendor/assets/javascripts/tinymce/skins/content/dark/content.js +1 -1
  15. data/vendor/assets/javascripts/tinymce/skins/content/dark/content.min.css +1 -1
  16. data/vendor/assets/javascripts/tinymce/skins/ui/oxide/content.css +1 -1
  17. data/vendor/assets/javascripts/tinymce/skins/ui/oxide/content.inline.css +1 -1
  18. data/vendor/assets/javascripts/tinymce/skins/ui/oxide/content.inline.js +1 -1
  19. data/vendor/assets/javascripts/tinymce/skins/ui/oxide/content.inline.min.css +1 -1
  20. data/vendor/assets/javascripts/tinymce/skins/ui/oxide/content.js +1 -1
  21. data/vendor/assets/javascripts/tinymce/skins/ui/oxide/content.min.css +1 -1
  22. data/vendor/assets/javascripts/tinymce/skins/ui/oxide/skin.css +1 -1
  23. data/vendor/assets/javascripts/tinymce/skins/ui/oxide/skin.js +1 -1
  24. data/vendor/assets/javascripts/tinymce/skins/ui/oxide/skin.min.css +1 -1
  25. data/vendor/assets/javascripts/tinymce/skins/ui/oxide-dark/content.css +1 -1
  26. data/vendor/assets/javascripts/tinymce/skins/ui/oxide-dark/content.inline.css +1 -1
  27. data/vendor/assets/javascripts/tinymce/skins/ui/oxide-dark/content.inline.js +1 -1
  28. data/vendor/assets/javascripts/tinymce/skins/ui/oxide-dark/content.inline.min.css +1 -1
  29. data/vendor/assets/javascripts/tinymce/skins/ui/oxide-dark/content.js +1 -1
  30. data/vendor/assets/javascripts/tinymce/skins/ui/oxide-dark/content.min.css +1 -1
  31. data/vendor/assets/javascripts/tinymce/skins/ui/oxide-dark/skin.css +1 -1
  32. data/vendor/assets/javascripts/tinymce/skins/ui/oxide-dark/skin.js +1 -1
  33. data/vendor/assets/javascripts/tinymce/skins/ui/oxide-dark/skin.min.css +1 -1
  34. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5/content.css +1 -1
  35. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5/content.inline.css +1 -1
  36. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5/content.inline.js +1 -1
  37. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5/content.inline.min.css +1 -1
  38. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5/content.js +1 -1
  39. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5/content.min.css +1 -1
  40. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5/skin.css +1 -1
  41. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5/skin.js +1 -1
  42. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5/skin.min.css +1 -1
  43. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5-dark/content.css +1 -1
  44. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5-dark/content.inline.css +1 -1
  45. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5-dark/content.inline.js +1 -1
  46. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5-dark/content.inline.min.css +1 -1
  47. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5-dark/content.js +1 -1
  48. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5-dark/content.min.css +1 -1
  49. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5-dark/skin.css +1 -1
  50. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5-dark/skin.js +1 -1
  51. data/vendor/assets/javascripts/tinymce/skins/ui/tinymce-5-dark/skin.min.css +1 -1
  52. data/vendor/assets/javascripts/tinymce/themes/silver/theme.js +2 -2
  53. data/vendor/assets/javascripts/tinymce/tinymce.d.ts +11 -1
  54. data/vendor/assets/javascripts/tinymce/tinymce.js +3 -3
  55. metadata +1 -1
@@ -1,5 +1,5 @@
1
1
  /**
2
- * TinyMCE version 8.6.0 (2026-06-03)
2
+ * TinyMCE version 8.7.0 (2026-07-01)
3
3
  */
4
4
 
5
5
  (function () {
@@ -573,14 +573,16 @@
573
573
  }
574
574
  return Optional.none();
575
575
  };
576
- const findLastIndex = (arr, pred) => {
576
+ const findLastByPredicate = (arr, pred) => {
577
577
  for (let i = arr.length - 1; i >= 0; i--) {
578
578
  if (pred(arr[i], i)) {
579
- return Optional.some(i);
579
+ return Optional.some({ v: arr[i], i });
580
580
  }
581
581
  }
582
582
  return Optional.none();
583
583
  };
584
+ const findLast = (arr, pred) => findLastByPredicate(arr, pred).map((r) => r.v);
585
+ const findLastIndex = (arr, pred) => findLastByPredicate(arr, pred).map((r) => r.i);
584
586
  const flatten$1 = (xs) => {
585
587
  // Note, this is possible because push supports multiple arguments:
586
588
  // http://jsperf.com/concat-push/6
@@ -2198,14 +2200,14 @@
2198
2200
  const siblings = (element) => {
2199
2201
  // TODO: Refactor out children so we can just not add self instead of filtering afterwards
2200
2202
  const filterSelf = (elements) => filter$5(elements, (x) => !eq(element, x));
2201
- return parent(element).map(children$1).map(filterSelf).getOr([]);
2203
+ return parent(element).map(children$2).map(filterSelf).getOr([]);
2202
2204
  };
2203
2205
  const prevSibling = (element) => Optional.from(element.dom.previousSibling).map(SugarElement.fromDom);
2204
2206
  const nextSibling = (element) => Optional.from(element.dom.nextSibling).map(SugarElement.fromDom);
2205
2207
  // This one needs to be reversed, so they're still in DOM order
2206
2208
  const prevSiblings = (element) => reverse(toArray(element, prevSibling));
2207
2209
  const nextSiblings = (element) => toArray(element, nextSibling);
2208
- const children$1 = (element) => map$3(element.dom.childNodes, SugarElement.fromDom);
2210
+ const children$2 = (element) => map$3(element.dom.childNodes, SugarElement.fromDom);
2209
2211
  const child$1 = (element, index) => {
2210
2212
  const cs = element.dom.childNodes;
2211
2213
  return Optional.from(cs[index]).map(SugarElement.fromDom);
@@ -2442,7 +2444,7 @@
2442
2444
  // than removing every child node manually.
2443
2445
  // The following is (probably) safe for performance as 99.9% of the time the trick works and
2444
2446
  // Traverse.children will return an empty array.
2445
- each$e(children$1(element), (rogue) => {
2447
+ each$e(children$2(element), (rogue) => {
2446
2448
  remove$8(rogue);
2447
2449
  });
2448
2450
  };
@@ -2453,7 +2455,7 @@
2453
2455
  }
2454
2456
  };
2455
2457
  const unwrap = (wrapper) => {
2456
- const children = children$1(wrapper);
2458
+ const children = children$2(wrapper);
2457
2459
  if (children.length > 0) {
2458
2460
  after$3(wrapper, children);
2459
2461
  }
@@ -2476,7 +2478,7 @@
2476
2478
  const mutate = (original, tag) => {
2477
2479
  const nu = shallowAs(original, tag);
2478
2480
  after$4(original, nu);
2479
- const children = children$1(original);
2481
+ const children = children$2(original);
2480
2482
  append(nu, children);
2481
2483
  remove$8(original);
2482
2484
  return nu;
@@ -2486,7 +2488,7 @@
2486
2488
  const doc = scope || document;
2487
2489
  const div = doc.createElement('div');
2488
2490
  div.innerHTML = html;
2489
- return children$1(SugarElement.fromDom(div));
2491
+ return children$2(SugarElement.fromDom(div));
2490
2492
  };
2491
2493
  const fromDom$1 = (nodes) => map$3(nodes, SugarElement.fromDom);
2492
2494
 
@@ -2526,6 +2528,14 @@
2526
2528
  const doc = dom.ownerDocument;
2527
2529
  return getShadowRoot(SugarElement.fromDom(dom)).fold(() => doc.body.contains(dom), compose1(inBody, getShadowHost));
2528
2530
  };
2531
+ const body = () => getBody(SugarElement.fromDom(document));
2532
+ const getBody = (doc) => {
2533
+ const b = doc.dom.body;
2534
+ if (b === null || b === undefined) {
2535
+ throw new Error('Body is not available yet');
2536
+ }
2537
+ return SugarElement.fromDom(b);
2538
+ };
2529
2539
 
2530
2540
  const internalSet = (dom, property, value) => {
2531
2541
  // This is going to hurt. Apologies.
@@ -2999,11 +3009,11 @@
2999
3009
  };
3000
3010
 
3001
3011
  const ancestors$1 = (scope, predicate, isRoot) => filter$5(parents$1(scope, isRoot), predicate);
3002
- const children = (scope, predicate) => filter$5(children$1(scope), predicate);
3012
+ const children$1 = (scope, predicate) => filter$5(children$2(scope), predicate);
3003
3013
  const descendants$1 = (scope, predicate) => {
3004
3014
  let result = [];
3005
3015
  // Recurse.toArray() might help here
3006
- each$e(children$1(scope), (x) => {
3016
+ each$e(children$2(scope), (x) => {
3007
3017
  if (predicate(x)) {
3008
3018
  result = result.concat([x]);
3009
3019
  }
@@ -3021,6 +3031,10 @@
3021
3031
  // It may surprise you to learn this is exactly what JQuery does
3022
3032
  // TODO: Avoid all this wrapping and unwrapping
3023
3033
  ancestors$1(scope, (e) => is$2(e, selector), isRoot);
3034
+ const children = (scope, selector) =>
3035
+ // It may surprise you to learn this is exactly what JQuery does
3036
+ // TODO: Avoid all the wrapping and unwrapping
3037
+ children$1(scope, (e) => is$2(e, selector));
3024
3038
  const descendants = (scope, selector) => all(selector, scope);
3025
3039
 
3026
3040
  const ancestor$3 = (scope, predicate, isRoot) => ancestor$5(scope, predicate, isRoot).isSome();
@@ -3205,6 +3219,20 @@
3205
3219
  };
3206
3220
  const getAtPoint = (win, x, y) => fromPoint$1(win, x, y);
3207
3221
 
3222
+ const getImageSize = (url) => new Promise((resolve, reject) => {
3223
+ const img = document.createElement('img');
3224
+ img.addEventListener('load', () => {
3225
+ resolve({
3226
+ width: img.naturalWidth,
3227
+ height: img.naturalHeight
3228
+ });
3229
+ });
3230
+ img.addEventListener('error', () => {
3231
+ reject(`Failed to get image dimensions for: ${url}`);
3232
+ });
3233
+ img.src = url;
3234
+ });
3235
+
3208
3236
  const get$2 = (_win) => {
3209
3237
  const win = _win === undefined ? window : _win;
3210
3238
  if (detect$1().browser.isFirefox()) {
@@ -3660,7 +3688,7 @@
3660
3688
  const isRoot = (el) => eq(el, rootNode);
3661
3689
  each$e(fromDom$1(transparentBlocks), (transparentBlock) => {
3662
3690
  ancestor$5(transparentBlock, isBlock, isRoot).each((parentBlock) => {
3663
- const invalidChildren = children(transparentBlock, (el) => isBlock(el) && !schema.isValidChild(name(parentBlock), name(el)));
3691
+ const invalidChildren = children$1(transparentBlock, (el) => isBlock(el) && !schema.isValidChild(name(parentBlock), name(el)));
3664
3692
  if (invalidChildren.length > 0) {
3665
3693
  const stateScope = parentElement(parentBlock);
3666
3694
  each$e(invalidChildren, (child) => {
@@ -3711,7 +3739,7 @@
3711
3739
  // this tries to compensate for that by detecting if that offsets are incorrect and then remove the height
3712
3740
  const getTableCaptionDeltaY = (elm) => {
3713
3741
  if (browser$2.isFirefox() && name(elm) === 'table') {
3714
- return firstElement(children$1(elm)).filter((elm) => {
3742
+ return firstElement(children$2(elm)).filter((elm) => {
3715
3743
  return name(elm) === 'caption';
3716
3744
  }).bind((caption) => {
3717
3745
  return firstElement(nextSiblings(caption)).map((body) => {
@@ -5594,12 +5622,16 @@
5594
5622
  let matches;
5595
5623
  while ((matches = styleRegExp.exec(css))) {
5596
5624
  styleRegExp.lastIndex = matches.index + matches[0].length;
5597
- let name = matches[1].replace(trimRightRegExp, '').toLowerCase();
5625
+ let name = matches[1].replace(trimRightRegExp, '');
5598
5626
  let value = matches[2].replace(trimRightRegExp, '');
5599
5627
  if (name && value) {
5600
5628
  // Decode escaped sequences like \65 -> e
5601
5629
  name = decodeHexSequences(name);
5602
5630
  value = decodeHexSequences(value);
5631
+ // Custom properties (--*) keep user case; standard names normalize to lowercase
5632
+ if (!name.startsWith('--')) {
5633
+ name = name.toLowerCase();
5634
+ }
5603
5635
  // Skip properties with double quotes and sequences like \" \' in their names
5604
5636
  // See 'mXSS Attacks: Attacking well-secured Web-Applications by using innerHTML Mutations'
5605
5637
  // https://cure53.de/fp170.pdf
@@ -5614,9 +5646,6 @@
5614
5646
  if (name === 'font-weight' && value === '700') {
5615
5647
  value = 'bold';
5616
5648
  }
5617
- else if (name === 'color' || name === 'background-color') { // Lowercase colors like RED
5618
- value = value.toLowerCase();
5619
- }
5620
5649
  // Convert RGB colors to HEX
5621
5650
  if (getColorFormat(value) === 'rgb') {
5622
5651
  fromString(value).each((rgba) => {
@@ -6591,7 +6620,7 @@
6591
6620
  const $node = SugarElement.fromDom(n);
6592
6621
  if (keepChildren) {
6593
6622
  // Unwrap but don't keep any empty text nodes
6594
- each$e(children$1($node), (child) => {
6623
+ each$e(children$2($node), (child) => {
6595
6624
  if (isText$c(child) && child.dom.length === 0) {
6596
6625
  remove$8(child);
6597
6626
  }
@@ -12736,7 +12765,7 @@
12736
12765
  switch (ctx) {
12737
12766
  case "invalid-child" /* ChildContext.InvalidChild */: {
12738
12767
  finishWrapper();
12739
- const children = children$1(elem);
12768
+ const children = children$2(elem);
12740
12769
  processElements(children);
12741
12770
  finishWrapper();
12742
12771
  break;
@@ -12890,6 +12919,113 @@
12890
12919
  };
12891
12920
  };
12892
12921
 
12922
+ const announcerContainerId = generate('tiny-aria-announcer');
12923
+ const POLITE_MESSAGE_TTL_MS = 600000; // 10 minutes
12924
+ const CREATE_DELAY_MS = 100; // Delay before creating announcer regions to avoid interfering with screen readers initial announcements.
12925
+ const politeTimestampAttr = 'data-mce-announced-at';
12926
+ const OFFSCREEN_STYLES = {
12927
+ position: 'absolute',
12928
+ left: '-9999px',
12929
+ width: '1px',
12930
+ height: '1px',
12931
+ overflow: 'hidden'
12932
+ };
12933
+ const createRegion = (live) => {
12934
+ const region = SugarElement.fromTag('div');
12935
+ setAll$1(region, {
12936
+ 'aria-live': live,
12937
+ 'aria-atomic': 'false',
12938
+ 'aria-relevant': 'additions'
12939
+ });
12940
+ return region;
12941
+ };
12942
+ const isConnected = (element) => element.dom.isConnected;
12943
+ const createNewState = () => {
12944
+ const container = SugarElement.fromTag('div');
12945
+ const politeRegion = createRegion('polite');
12946
+ const assertiveRegion = createRegion('assertive');
12947
+ set$5(container, 'id', announcerContainerId);
12948
+ setAll(container, OFFSCREEN_STYLES);
12949
+ append$1(container, politeRegion);
12950
+ append$1(container, assertiveRegion);
12951
+ append$1(body(), container);
12952
+ return { container, politeRegion, assertiveRegion };
12953
+ };
12954
+ const cleanupExpiredMessages = (polite, now) => {
12955
+ each$e(children(polite, `div[${politeTimestampAttr}]`), (messageDiv) => {
12956
+ getOpt(messageDiv, politeTimestampAttr)
12957
+ .bind((value) => toInt(value))
12958
+ .filter((announcedAt) => now - announcedAt > POLITE_MESSAGE_TTL_MS)
12959
+ .each(() => remove$8(messageDiv));
12960
+ });
12961
+ };
12962
+ const createAnnouncer = () => {
12963
+ const state = value$1();
12964
+ const mountRegions = async () => {
12965
+ const createNewPendingState = async () => {
12966
+ const promise = new Promise((resolve) => {
12967
+ const newState = createNewState();
12968
+ setTimeout(() => resolve(newState), CREATE_DELAY_MS);
12969
+ });
12970
+ state.set(promise);
12971
+ return promise;
12972
+ };
12973
+ return state.get().fold(createNewPendingState, async (existingPromise) => {
12974
+ const { container } = await existingPromise;
12975
+ if (isConnected(container)) {
12976
+ return existingPromise;
12977
+ }
12978
+ else {
12979
+ // A concurrent caller may have already replaced the stale state while we awaited.
12980
+ // The block after the await runs atomically, so reuse that state if present and
12981
+ // only create a fresh one when the state is still the stale promise we observed.
12982
+ return state.get().filter((current) => current !== existingPromise).getOrThunk(createNewPendingState);
12983
+ }
12984
+ });
12985
+ };
12986
+ const addMessage = (region, message) => {
12987
+ const now = Date.now();
12988
+ cleanupExpiredMessages(region, now);
12989
+ const messageDiv = SugarElement.fromTag('div');
12990
+ set$5(messageDiv, politeTimestampAttr, String(now));
12991
+ append$1(messageDiv, SugarElement.fromText(message));
12992
+ append$1(region, messageDiv);
12993
+ };
12994
+ const polite = async (message) => {
12995
+ const { politeRegion } = await mountRegions();
12996
+ addMessage(politeRegion, message);
12997
+ };
12998
+ const assertive = async (message) => {
12999
+ const { assertiveRegion } = await mountRegions();
13000
+ addMessage(assertiveRegion, message);
13001
+ };
13002
+ return { polite, assertive };
13003
+ };
13004
+
13005
+ const announcer = createAnnouncer();
13006
+ /**
13007
+ * Announces a message to screen readers via an aria-live region, without shifting focus.
13008
+ *
13009
+ * @method announce
13010
+ * @param {String} message The message to announce to screen readers.
13011
+ * @param {Object} options Optional settings.
13012
+ * @param {Boolean} options.assertive If true, uses aria-live="assertive" instead of polite.
13013
+ * @example
13014
+ * tinymce.dom.AriaAnnouncer.announce('Bold on');
13015
+ * tinymce.dom.AriaAnnouncer.announce('Error occurred', { assertive: true });
13016
+ */
13017
+ const announce = (message, options) => {
13018
+ if (options?.assertive === true) {
13019
+ announcer.assertive(message).catch(noop);
13020
+ }
13021
+ else {
13022
+ announcer.polite(message).catch(noop);
13023
+ }
13024
+ };
13025
+ const AriaAnnouncer = {
13026
+ announce
13027
+ };
13028
+
12893
13029
  /**
12894
13030
  * Constructs a new BookmarkManager instance for a specific selection instance.
12895
13031
  *
@@ -12954,7 +13090,7 @@
12954
13090
  };
12955
13091
 
12956
13092
  const clamp$1 = (offset, element) => {
12957
- const max = isText$c(element) ? get$5(element).length : children$1(element).length + 1;
13093
+ const max = isText$c(element) ? get$5(element).length : children$2(element).length + 1;
12958
13094
  if (offset > max) {
12959
13095
  return max;
12960
13096
  }
@@ -14226,7 +14362,7 @@
14226
14362
  editor.dispatch('AfterScrollIntoView', data);
14227
14363
  };
14228
14364
  const descend = (element, offset) => {
14229
- const children = children$1(element);
14365
+ const children = children$2(element);
14230
14366
  if (children.length === 0 || excludeFromDescend(element)) {
14231
14367
  return { element, offset };
14232
14368
  }
@@ -14246,7 +14382,7 @@
14246
14382
  return { element: last, offset: get$5(last).length };
14247
14383
  }
14248
14384
  else {
14249
- return { element: last, offset: children$1(last).length };
14385
+ return { element: last, offset: children$2(last).length };
14250
14386
  }
14251
14387
  }
14252
14388
  }
@@ -15750,7 +15886,7 @@
15750
15886
  const br = SugarElement.fromHtml('<br data-mce-bogus="1">');
15751
15887
  // Remove all bogus elements except caret
15752
15888
  if (preserveEmptyCaret) {
15753
- each$e(children$1(elm), (node) => {
15889
+ each$e(children$2(elm), (node) => {
15754
15890
  if (!isEmptyCaretFormatElement(node)) {
15755
15891
  remove$8(node);
15756
15892
  }
@@ -15922,7 +16058,7 @@
15922
16058
  // Clean up any additional leftover nodes. If the last block wasn't a direct child, then we also need to clean up siblings
15923
16059
  if (!eq(root, lastBlock)) {
15924
16060
  const additionalCleanupNodes = is$4(parent(lastBlock), root) ? [] : siblings(lastBlock);
15925
- each$e(additionalCleanupNodes.concat(children$1(root)), (node) => {
16061
+ each$e(additionalCleanupNodes.concat(children$2(root)), (node) => {
15926
16062
  if (!eq(node, lastBlock) && !contains(node, lastBlock) && isEmpty$4(editor.schema, node)) {
15927
16063
  remove$8(node);
15928
16064
  }
@@ -17695,14 +17831,14 @@
17695
17831
  });
17696
17832
  };
17697
17833
  const wrapChildrenInInnerWrapper = (target, wrapper, hasFormat, removeFormatFromElement) => {
17698
- each$e(children$1(target), (child) => {
17834
+ each$e(children$2(target), (child) => {
17699
17835
  if (isElement$8(child) && hasFormat(child)) {
17700
17836
  if (removeFormatFromElement(child).isNone()) {
17701
17837
  unwrap(child);
17702
17838
  }
17703
17839
  }
17704
17840
  });
17705
- each$e(children$1(target), (child) => append$1(wrapper, child));
17841
+ each$e(children$2(target), (child) => append$1(wrapper, child));
17706
17842
  prepend(target, wrapper);
17707
17843
  };
17708
17844
  const wrapInOuterWrappers = (target, wrappers) => {
@@ -22068,7 +22204,7 @@
22068
22204
  append(SugarElement.fromDom(pre1), [
22069
22205
  SugarElement.fromTag('br', doc),
22070
22206
  SugarElement.fromTag('br', doc),
22071
- ...children$1(sPre2)
22207
+ ...children$2(sPre2)
22072
22208
  ]);
22073
22209
  };
22074
22210
  if (!rng.collapsed) {
@@ -22215,10 +22351,12 @@
22215
22351
  const wrapName = format.inline || format.block;
22216
22352
  const wrapElm = createWrapElement(wrapName);
22217
22353
  const isMatchingWrappingBlock = (node) => isWrappingBlockFormat(format) && matchNode$1(ed, node, name, vars);
22354
+ const canRenameChildBlocks = (node) => forall(node.childNodes, (child) => !isTextBlock$2(ed.schema, child) || isValid(ed, wrapName, child.nodeName.toLowerCase()));
22218
22355
  const canRenameBlock = (node, parentName, isEditableDescendant) => {
22219
22356
  const isValidBlockFormatForNode = isNonWrappingBlockFormat(format) &&
22220
22357
  isTextBlock$2(ed.schema, node) &&
22221
- isValid(ed, parentName, wrapName);
22358
+ isValid(ed, parentName, wrapName) &&
22359
+ canRenameChildBlocks(node);
22222
22360
  return isEditableDescendant && isValidBlockFormatForNode;
22223
22361
  };
22224
22362
  const canWrapNode = (node, parentName, isEditableDescendant, isWrappableNoneditableElm) => {
@@ -22375,6 +22513,19 @@
22375
22513
  // node variable is used by other functions above in the same scope so need to set it here
22376
22514
  node = targetNode;
22377
22515
  applyNodeStyle(formatList, node);
22516
+ if (isBlockFormat(format) && !dom.isBlock(targetNode)) {
22517
+ const parentBlock = dom.getParent(targetNode, dom.isBlock);
22518
+ if (dom.isEditable(parentBlock)) {
22519
+ const wrapperElementName = format.block;
22520
+ if (parentBlock.nodeName.toLowerCase() === wrapperElementName.toLowerCase()) {
22521
+ setElementFormat(ed, parentBlock, format, vars, node);
22522
+ }
22523
+ else if (!isWrappingBlockFormat(format)) {
22524
+ const elm = dom.rename(parentBlock, wrapperElementName);
22525
+ setElementFormat(ed, elm, format, vars, node);
22526
+ }
22527
+ }
22528
+ }
22378
22529
  fireFormatApply(ed, name, node, vars);
22379
22530
  return;
22380
22531
  }
@@ -26164,7 +26315,7 @@
26164
26315
  preview: 'font-family font-size'
26165
26316
  },
26166
26317
  {
26167
- selector: '.mce-preview-object,[data-ephox-embed-iri]',
26318
+ selector: '.mce-preview-object,[data-ephox-embed-iri],.tiny-pageembed',
26168
26319
  ceFalseOverride: true,
26169
26320
  styles: {
26170
26321
  float: 'left'
@@ -26216,7 +26367,7 @@
26216
26367
  preview: 'font-family font-size'
26217
26368
  },
26218
26369
  {
26219
- selector: '.mce-preview-object',
26370
+ selector: '.mce-preview-object,.tiny-pageembed',
26220
26371
  ceFalseOverride: true,
26221
26372
  styles: {
26222
26373
  display: 'table', // Needs to be `table` to properly render while editing
@@ -26280,7 +26431,7 @@
26280
26431
  preview: 'font-family font-size'
26281
26432
  },
26282
26433
  {
26283
- selector: '.mce-preview-object,[data-ephox-embed-iri]',
26434
+ selector: '.mce-preview-object,[data-ephox-embed-iri],.tiny-pageembed',
26284
26435
  ceFalseOverride: true,
26285
26436
  styles: {
26286
26437
  float: 'right'
@@ -26967,8 +27118,8 @@
26967
27118
  isFirstTypedCharacter.set(true);
26968
27119
  return;
26969
27120
  }
26970
- const hasOnlyMetaOrCtrlModifier = Env.os.isMacOS() ? e.metaKey : e.ctrlKey && !e.altKey;
26971
- if (hasOnlyMetaOrCtrlModifier) {
27121
+ const hasMetaOrCtrlModifier = Env.os.isMacOS() ? e.metaKey : e.ctrlKey && !e.altKey;
27122
+ if (hasMetaOrCtrlModifier && (e.key === 'Backspace' || e.key === 'Delete')) {
26972
27123
  undoManager.beforeChange();
26973
27124
  }
26974
27125
  });
@@ -27533,7 +27684,7 @@
27533
27684
  const isIndented = (entry) => entry.depth > 0;
27534
27685
  const isSelected = (entry) => entry.isSelected;
27535
27686
  const cloneItemContent = (li) => {
27536
- const children = children$1(li);
27687
+ const children = children$2(li);
27537
27688
  const content = hasLastChildList(li) ? children.slice(0, -1) : children;
27538
27689
  return map$3(content, deep);
27539
27690
  };
@@ -27731,7 +27882,7 @@
27731
27882
  return currentItemEntry.toArray().concat(childListEntries);
27732
27883
  };
27733
27884
  const parseItem = (depth, itemSelection, selectionState, item) => firstChild(item).filter(isList).fold(() => parseSingleItem(depth, itemSelection, selectionState, item), (list) => {
27734
- const parsedSiblings = foldl(children$1(item), (acc, liChild, i) => {
27885
+ const parsedSiblings = foldl(children$2(item), (acc, liChild, i) => {
27735
27886
  if (i === 0) {
27736
27887
  return acc;
27737
27888
  }
@@ -27754,7 +27905,7 @@
27754
27905
  }, []);
27755
27906
  return parseList(depth, itemSelection, selectionState, list).concat(parsedSiblings);
27756
27907
  });
27757
- const parseList = (depth, itemSelection, selectionState, list) => bind$3(children$1(list), (element) => {
27908
+ const parseList = (depth, itemSelection, selectionState, list) => bind$3(children$2(list), (element) => {
27758
27909
  const parser = isList(element) ? parseList : parseItem;
27759
27910
  const newDepth = depth + 1;
27760
27911
  return parser(newDepth, itemSelection, selectionState, element);
@@ -28417,7 +28568,7 @@
28417
28568
  const rng = normalizeRange(editor.selection.getRng());
28418
28569
  const nextCaretContainer = findNextCaretContainer(editor, rng, isForward, root);
28419
28570
  const otherLi = dom.getParent(nextCaretContainer, 'LI', root);
28420
- if (nextCaretContainer && otherLi) {
28571
+ if (nextCaretContainer && otherLi && (isForward || !dom.isChildOf(nextCaretContainer, block))) {
28421
28572
  const findValidElement = (element) => contains$2(['td', 'th', 'caption'], name(element));
28422
28573
  const findRoot = (node) => node.dom === root;
28423
28574
  const otherLiCell = closest$5(SugarElement.fromDom(otherLi), findValidElement, findRoot);
@@ -28515,7 +28666,7 @@
28515
28666
  const read$1 = (schema, rootNode, forward, rng) => rng.collapsed ? readFromRange(schema, rootNode, forward, rng) : Optional.none();
28516
28667
 
28517
28668
  const getChildrenUntilBlockBoundary = (block, schema) => {
28518
- const children = children$1(block);
28669
+ const children = children$2(block);
28519
28670
  return findIndex$2(children, (el) => schema.isBlock(name(el))).fold(constant(children), (index) => children.slice(0, index));
28520
28671
  };
28521
28672
  const extractChildren = (block, schema) => {
@@ -29910,7 +30061,11 @@
29910
30061
  const canIndent = (editor) => !editor.mode.isReadOnly() && canIndent$1(editor);
29911
30062
  const isListComponent = (el) => isList$1(el) || isListItem$2(el);
29912
30063
  const parentIsListComponent = (el) => parent(el).exists(isListComponent);
29913
- const getBlocksToIndent = (editor) => filter$5(fromDom$1(editor.selection.getSelectedBlocks()), (el) => !isListComponent(el) && !parentIsListComponent(el) && isEditable(el));
30064
+ const getBlocksToIndent = (editor) => {
30065
+ const selectedCells = getCellsFromEditor(editor);
30066
+ return selectedCells.length === 0 ?
30067
+ filter$5(fromDom$1(editor.selection.getSelectedBlocks()), (el) => !isListComponent(el) && !parentIsListComponent(el) && isEditable(el)) : selectedCells;
30068
+ };
29914
30069
  const handle = (editor, command) => {
29915
30070
  if (editor.mode.isReadOnly()) {
29916
30071
  return;
@@ -30585,7 +30740,7 @@
30585
30740
  return filterFirstLayer(scope, selector, always);
30586
30741
  };
30587
30742
  const filterFirstLayer = (scope, selector, predicate) => {
30588
- return bind$3(children$1(scope), (x) => {
30743
+ return bind$3(children$2(scope), (x) => {
30589
30744
  if (is$2(x, selector)) {
30590
30745
  return predicate(x) ? [x] : [];
30591
30746
  }
@@ -30795,7 +30950,7 @@
30795
30950
  nextSibling: nextSibling
30796
30951
  }),
30797
30952
  property: constant({
30798
- children: children$1,
30953
+ children: children$2,
30799
30954
  name: name,
30800
30955
  parent: parent,
30801
30956
  document,
@@ -33902,15 +34057,20 @@
33902
34057
  blobCache.add(blobInfo);
33903
34058
  return blobInfo;
33904
34059
  };
33905
- const pasteImage = (editor, imageItem) => {
33906
- parseDataUri(imageItem.uri).each(({ data, type, base64Encoded }) => {
34060
+ const pasteImage = async (editor, imageItem) => {
34061
+ return parseDataUri(imageItem.uri).fold(() => Promise.resolve(), ({ data, type, base64Encoded }) => {
33907
34062
  const base64 = base64Encoded ? data : btoa(data);
33908
34063
  const file = imageItem.file;
33909
34064
  // TODO: Move the bulk of the cache logic to EditorUpload
33910
34065
  const blobCache = editor.editorUpload.blobCache;
33911
34066
  const existingBlobInfo = blobCache.getByData(base64, type);
33912
34067
  const blobInfo = existingBlobInfo ?? createBlobInfo(editor, blobCache, file, base64);
33913
- pasteHtml(editor, `<img src="${blobInfo.blobUri()}">`, false, true);
34068
+ const imgUrl = blobInfo.blobUri();
34069
+ return getImageSize(imgUrl).then(({ width, height }) => {
34070
+ pasteHtml(editor, `<img width="${width}" height="${height}" src="${imgUrl}">`, false, true);
34071
+ }).catch(() => {
34072
+ pasteHtml(editor, `<img src="${imgUrl}">`, false, true);
34073
+ });
33914
34074
  });
33915
34075
  };
33916
34076
  const isClipboardEvent = (event) => event.type === 'paste';
@@ -33941,13 +34101,13 @@
33941
34101
  if (images.length > 0) {
33942
34102
  e.preventDefault();
33943
34103
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
33944
- readFilesAsDataUris(images).then((fileResults) => {
34104
+ readFilesAsDataUris(images).then(async (fileResults) => {
33945
34105
  if (rng) {
33946
34106
  editor.selection.setRng(rng);
33947
34107
  }
33948
- each$e(fileResults, (result) => {
33949
- pasteImage(editor, result);
33950
- });
34108
+ for (const result of fileResults) {
34109
+ await pasteImage(editor, result);
34110
+ }
33951
34111
  });
33952
34112
  return true;
33953
34113
  }
@@ -36095,6 +36255,7 @@
36095
36255
  const browser = Env.browser;
36096
36256
  const isGecko = browser.isFirefox();
36097
36257
  const isWebKit = browser.isChromium() || browser.isSafari();
36258
+ const isSafari = browser.isSafari();
36098
36259
  const isiOS = Env.deviceType.isiPhone() || Env.deviceType.isiPad();
36099
36260
  const isMac = Env.os.isMacOS() || Env.os.isiOS();
36100
36261
  /**
@@ -36701,6 +36862,45 @@
36701
36862
  }
36702
36863
  });
36703
36864
  };
36865
+ /**
36866
+ * this is needed to manage the difference between
36867
+ * ```
36868
+ * <li><span class="fake">a</span><div>b</div></li>
36869
+ * ```
36870
+ * and
36871
+ * ```
36872
+ * <li><span class="fake">a</span> <div>b</div></li>
36873
+ * ```
36874
+ * since if the indentation of the HTML has a new line it creates a fake child in the `li` that is an empty text
36875
+ * it's check it trying to get the rects and if it can't it means that it's the false unwanted new line
36876
+ **/
36877
+ const isValidSibling = (el) => getClientRects([el.dom]).length > 0;
36878
+ const firstBlockChildOrNewLine = (target) => child(target, (child) => isBr$6(child) || isElement$8(child) && get$8(child, 'display') === 'block');
36879
+ const clickAfterEl = (clientX, clientY, rect) => clientX >= rect.right && clientY >= rect.top && clientY <= rect.bottom;
36880
+ /**
36881
+ * In Chrome in a `LI` that contains a block element and where the first child is an inline element
36882
+ * clicking on the right side of the first child the carret goes at the start of the element instead that in the end of it
36883
+ * issue: https://issues.chromium.org/issues/40767343
36884
+ **/
36885
+ const fixInLISelection = () => {
36886
+ editor.on('mousedown', (e) => {
36887
+ const target = SugarElement.fromDom(e.target);
36888
+ if (isListItem$2(target)) {
36889
+ firstBlockChildOrNewLine(target).each((firstBlock) => {
36890
+ const prevSiblings$1 = prevSiblings(firstBlock);
36891
+ findLast(prevSiblings$1, isValidSibling).each((lastInlineBeforeBlock) => {
36892
+ if (get$c(getClientRects([lastInlineBeforeBlock.dom]), 0).exists((rect) => clickAfterEl(e.clientX, e.clientY, rect))) {
36893
+ prevPosition(target.dom, CaretPosition(firstBlock.dom, 0)).each((pos) => {
36894
+ e.preventDefault();
36895
+ editor.focus();
36896
+ editor.selection.setRng(pos.toRange());
36897
+ });
36898
+ }
36899
+ });
36900
+ });
36901
+ }
36902
+ });
36903
+ };
36704
36904
  // No-op since Mozilla seems to have fixed the caret repaint issues
36705
36905
  const refreshContentEditable = noop;
36706
36906
  const isHidden = () => {
@@ -36747,6 +36947,9 @@
36747
36947
  blockFormSubmitInsideEditor();
36748
36948
  disableBackspaceIntoATable();
36749
36949
  removeAppleInterchangeBrs();
36950
+ if (!isSafari) {
36951
+ fixInLISelection();
36952
+ }
36750
36953
  // touchClickEvent();
36751
36954
  // iOS
36752
36955
  if (isiOS) {
@@ -40843,6 +41046,21 @@
40843
41046
  hasEditableRoot() {
40844
41047
  return hasEditableRoot(this);
40845
41048
  }
41049
+ /**
41050
+ * Announces a message to screen readers via the page-wide aria-live region, without shifting focus.
41051
+ * Delegates to {@link tinymce.dom.AriaAnnouncer#announce}.
41052
+ *
41053
+ * @method announce
41054
+ * @param {String} message The message to announce to screen readers.
41055
+ * @param {Object} options Optional settings.
41056
+ * @param {Boolean} options.assertive If true, uses aria-live="assertive" (role="alert") instead of polite.
41057
+ * @example
41058
+ * tinymce.activeEditor.announce('Bold on');
41059
+ * tinymce.activeEditor.announce('Error occurred', { assertive: true });
41060
+ */
41061
+ announce(message, options) {
41062
+ AriaAnnouncer.announce(message, options);
41063
+ }
40846
41064
  /**
40847
41065
  * Removes the editor from the dom and tinymce collection.
40848
41066
  *
@@ -40967,14 +41185,14 @@
40967
41185
  * @property minorVersion
40968
41186
  * @type String
40969
41187
  */
40970
- minorVersion: '6.0',
41188
+ minorVersion: '7.0',
40971
41189
  /**
40972
41190
  * Release date of TinyMCE build.
40973
41191
  *
40974
41192
  * @property releaseDate
40975
41193
  * @type String
40976
41194
  */
40977
- releaseDate: '2026-06-03',
41195
+ releaseDate: '2026-07-01',
40978
41196
  /**
40979
41197
  * Collection of language pack data.
40980
41198
  *
@@ -41832,6 +42050,7 @@
41832
42050
  ControlSelection,
41833
42051
  BookmarkManager,
41834
42052
  Selection: EditorSelection,
42053
+ AriaAnnouncer,
41835
42054
  Event: EventUtils.Event
41836
42055
  },
41837
42056
  html: {