jodit 4.12.22 → 4.12.23

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 (71) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/es2015/jodit.css +1 -1
  3. package/es2015/jodit.fat.min.js +10 -10
  4. package/es2015/jodit.js +211 -69
  5. package/es2015/jodit.min.js +10 -10
  6. package/es2015/plugins/debug/debug.css +1 -1
  7. package/es2015/plugins/debug/debug.js +1 -1
  8. package/es2015/plugins/debug/debug.min.js +1 -1
  9. package/es2015/plugins/speech-recognize/speech-recognize.css +1 -1
  10. package/es2015/plugins/speech-recognize/speech-recognize.js +1 -1
  11. package/es2015/plugins/speech-recognize/speech-recognize.min.js +1 -1
  12. package/es2018/jodit.fat.min.js +10 -10
  13. package/es2018/jodit.min.js +15 -15
  14. package/es2018/plugins/debug/debug.min.js +1 -1
  15. package/es2018/plugins/speech-recognize/speech-recognize.min.js +1 -1
  16. package/es2021/jodit.css +1 -1
  17. package/es2021/jodit.fat.min.js +12 -12
  18. package/es2021/jodit.js +202 -68
  19. package/es2021/jodit.min.js +12 -12
  20. package/es2021/plugins/debug/debug.css +1 -1
  21. package/es2021/plugins/debug/debug.js +1 -1
  22. package/es2021/plugins/debug/debug.min.js +1 -1
  23. package/es2021/plugins/speech-recognize/speech-recognize.css +1 -1
  24. package/es2021/plugins/speech-recognize/speech-recognize.js +1 -1
  25. package/es2021/plugins/speech-recognize/speech-recognize.min.js +1 -1
  26. package/es2021.en/jodit.css +1 -1
  27. package/es2021.en/jodit.fat.min.js +13 -13
  28. package/es2021.en/jodit.js +180 -46
  29. package/es2021.en/jodit.min.js +12 -12
  30. package/es2021.en/plugins/debug/debug.css +1 -1
  31. package/es2021.en/plugins/debug/debug.js +1 -1
  32. package/es2021.en/plugins/debug/debug.min.js +1 -1
  33. package/es2021.en/plugins/speech-recognize/speech-recognize.css +1 -1
  34. package/es2021.en/plugins/speech-recognize/speech-recognize.js +1 -1
  35. package/es2021.en/plugins/speech-recognize/speech-recognize.min.js +1 -1
  36. package/es5/jodit.css +2 -2
  37. package/es5/jodit.fat.min.js +2 -2
  38. package/es5/jodit.js +255 -104
  39. package/es5/jodit.min.css +2 -2
  40. package/es5/jodit.min.js +2 -2
  41. package/es5/plugins/debug/debug.css +1 -1
  42. package/es5/plugins/debug/debug.js +1 -1
  43. package/es5/plugins/debug/debug.min.js +1 -1
  44. package/es5/plugins/speech-recognize/speech-recognize.css +1 -1
  45. package/es5/plugins/speech-recognize/speech-recognize.js +1 -1
  46. package/es5/plugins/speech-recognize/speech-recognize.min.js +1 -1
  47. package/es5/polyfills.fat.min.js +1 -1
  48. package/es5/polyfills.js +1 -1
  49. package/es5/polyfills.min.js +1 -1
  50. package/esm/core/component/view-component.js +8 -0
  51. package/esm/core/constants.js +1 -1
  52. package/esm/core/helpers/checker/is-html-from-word.d.ts +1 -1
  53. package/esm/core/helpers/checker/is-html-from-word.js +12 -1
  54. package/esm/core/selection/style/api/wrap.js +18 -3
  55. package/esm/core/ui/popup/popup.js +4 -1
  56. package/esm/jodit.js +12 -0
  57. package/esm/modules/toolbar/button/content.d.ts +6 -0
  58. package/esm/modules/toolbar/button/content.js +26 -1
  59. package/esm/plugins/add-new-line/add-new-line.js +4 -1
  60. package/esm/plugins/ai-assistant/ai-assistant.js +7 -1
  61. package/esm/plugins/backspace/cases/check-join-neighbors.js +15 -1
  62. package/esm/plugins/color/color.js +21 -21
  63. package/esm/plugins/inline-popup/inline-popup.d.ts +7 -0
  64. package/esm/plugins/inline-popup/inline-popup.js +18 -2
  65. package/esm/plugins/paste/config.js +11 -2
  66. package/esm/plugins/search/ui/search.js +22 -12
  67. package/esm/plugins/select-cells/select-cells.js +10 -6
  68. package/package.json +1 -1
  69. package/types/core/helpers/checker/is-html-from-word.d.ts +1 -1
  70. package/types/modules/toolbar/button/content.d.ts +6 -0
  71. package/types/plugins/inline-popup/inline-popup.d.ts +7 -0
@@ -1,7 +1,7 @@
1
1
  /*!
2
2
  * jodit - Jodit is an awesome and useful wysiwyg editor with filebrowser
3
3
  * Author: Chupurnov <chupurnov@gmail.com> (https://xdsoft.net/jodit/)
4
- * Version: v4.12.22
4
+ * Version: v4.12.23
5
5
  * Url: https://xdsoft.net/jodit/
6
6
  * License(s): MIT
7
7
  */
@@ -1,7 +1,7 @@
1
1
  /*!
2
2
  * jodit - Jodit is an awesome and useful wysiwyg editor with filebrowser
3
3
  * Author: Chupurnov <chupurnov@gmail.com> (https://xdsoft.net/jodit/)
4
- * Version: v4.12.22
4
+ * Version: v4.12.23
5
5
  * Url: https://xdsoft.net/jodit/
6
6
  * License(s): MIT
7
7
  */
@@ -1,7 +1,7 @@
1
1
  /*!
2
2
  * jodit - Jodit is an awesome and useful wysiwyg editor with filebrowser
3
3
  * Author: Chupurnov <chupurnov@gmail.com> (https://xdsoft.net/jodit/)
4
- * Version: v4.12.22
4
+ * Version: v4.12.23
5
5
  * Url: https://xdsoft.net/jodit/
6
6
  * License(s): MIT
7
7
  */
@@ -1,7 +1,7 @@
1
1
  /*!
2
2
  * jodit - Jodit is an awesome and useful wysiwyg editor with filebrowser
3
3
  * Author: Chupurnov <chupurnov@gmail.com> (https://xdsoft.net/jodit/)
4
- * Version: v4.12.22
4
+ * Version: v4.12.23
5
5
  * Url: https://xdsoft.net/jodit/
6
6
  * License(s): MIT
7
7
  */
@@ -1,7 +1,7 @@
1
1
  /*!
2
2
  * jodit - Jodit is an awesome and useful wysiwyg editor with filebrowser
3
3
  * Author: Chupurnov <chupurnov@gmail.com> (https://xdsoft.net/jodit/)
4
- * Version: v4.12.22
4
+ * Version: v4.12.23
5
5
  * Url: https://xdsoft.net/jodit/
6
6
  * License(s): MIT
7
7
  */
@@ -1,7 +1,7 @@
1
1
  /*!
2
2
  * jodit - Jodit is an awesome and useful wysiwyg editor with filebrowser
3
3
  * Author: Chupurnov <chupurnov@gmail.com> (https://xdsoft.net/jodit/)
4
- * Version: v4.12.22
4
+ * Version: v4.12.23
5
5
  * Url: https://xdsoft.net/jodit/
6
6
  * License(s): MIT
7
7
  */
@@ -1,7 +1,7 @@
1
1
  /*!
2
2
  * jodit - Jodit is an awesome and useful wysiwyg editor with filebrowser
3
3
  * Author: Chupurnov <chupurnov@gmail.com> (https://xdsoft.net/jodit/)
4
- * Version: v4.12.22
4
+ * Version: v4.12.23
5
5
  * Url: https://xdsoft.net/jodit/
6
6
  * License(s): MIT
7
7
  */
package/es5/polyfills.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /*!
2
2
  * jodit - Jodit is an awesome and useful wysiwyg editor with filebrowser
3
3
  * Author: Chupurnov <chupurnov@gmail.com> (https://xdsoft.net/jodit/)
4
- * Version: v4.12.22
4
+ * Version: v4.12.23
5
5
  * Url: https://xdsoft.net/jodit/
6
6
  * License(s): MIT
7
7
  */
@@ -1,7 +1,7 @@
1
1
  /*!
2
2
  * jodit - Jodit is an awesome and useful wysiwyg editor with filebrowser
3
3
  * Author: Chupurnov <chupurnov@gmail.com> (https://xdsoft.net/jodit/)
4
- * Version: v4.12.22
4
+ * Version: v4.12.23
5
5
  * Url: https://xdsoft.net/jodit/
6
6
  * License(s): MIT
7
7
  */
@@ -22,6 +22,14 @@ export class ViewComponent extends Component {
22
22
  */
23
23
  setParentView(jodit) {
24
24
  this.jodit = jodit;
25
+ // Inherit the owner window from the parent view — for an editor
26
+ // created with a custom `ownerWindow` (e.g. inside an iframe) the
27
+ // component default (the global `window`) is wrong: outside-click
28
+ // handlers of dropdowns/popups listened to the wrong window. See
29
+ // https://github.com/xdan/jodit/issues/965
30
+ if (jodit.ow) {
31
+ this.ownerWindow = jodit.ow;
32
+ }
25
33
  jodit.components.add(this);
26
34
  return this;
27
35
  }
@@ -3,7 +3,7 @@
3
3
  * Released under MIT see LICENSE.txt in the project root for license information.
4
4
  * Copyright (c) 2013-2026 Valerii Chupurnov. All rights reserved. https://xdsoft.net
5
5
  */
6
- export const APP_VERSION = "4.12.22";
6
+ export const APP_VERSION = "4.12.23";
7
7
  // prettier-ignore
8
8
  export const ES = "es2020";
9
9
  export const IS_ES_MODERN = true;
@@ -7,6 +7,6 @@
7
7
  * @module helpers/checker
8
8
  */
9
9
  /**
10
- * Detect if string is HTML from MS Word or Excel
10
+ * Detect if string is HTML from MS Word, Excel or LibreOffice/OpenOffice
11
11
  */
12
12
  export declare function isHtmlFromWord(data: string): boolean;
@@ -7,10 +7,21 @@
7
7
  * @module helpers/checker
8
8
  */
9
9
  /**
10
- * Detect if string is HTML from MS Word or Excel
10
+ * Detect if string is HTML from MS Word, Excel or LibreOffice/OpenOffice
11
11
  */
12
12
  export function isHtmlFromWord(data) {
13
13
  return (data.search(/<meta.*?Microsoft Excel\s[\d].*?>/) !== -1 ||
14
14
  data.search(/<meta.*?Microsoft Word\s[\d].*?>/) !== -1 ||
15
+ // `<meta name=ProgId content=Word.Document>` — attribute values are
16
+ // unquoted in the raw Word clipboard fragment
17
+ data.search(/<meta[^>]*?ProgId[^>]*?(Word|Excel)\./i) !== -1 ||
18
+ // LibreOffice/OpenOffice Writer & Calc
19
+ data.search(/<meta[^>]*?(LibreOffice|OpenOffice)/i) !== -1 ||
20
+ // Office namespaces on the root element of the clipboard fragment
21
+ data.search(/urn:schemas-microsoft-com:office:(word|excel)/) !== -1 ||
22
+ // `class=MsoNormal` and friends (unquoted/quoted)
23
+ data.search(/<\w[^>]*\sclass=("|')?Mso/) !== -1 ||
24
+ // the raw Word clipboard uses SINGLE quotes: style='mso-…'
25
+ data.search(/style='[^']*mso-/) !== -1 ||
15
26
  (data.search(/style="[^"]*mso-/) !== -1 && data.search(/<font/) !== -1));
16
27
  }
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import { Dom } from "../../../dom/index.js";
7
7
  import { attr } from "../../../helpers/utils/attr.js";
8
+ import { css } from "../../../helpers/utils/css.js";
8
9
  import { wrapList } from "./list/wrap-list.js";
9
10
  import { wrapUnwrappedText } from "./wrap-unwrapped-text.js";
10
11
  /**
@@ -13,9 +14,23 @@ import { wrapUnwrappedText } from "./wrap-unwrapped-text.js";
13
14
  */
14
15
  export function wrap(commitStyle, font, jodit) {
15
16
  const wrapper = findOrCreateWrapper(commitStyle, font, jodit);
16
- return commitStyle.elementIsList
17
- ? wrapList(commitStyle, wrapper, jodit)
18
- : Dom.replace(wrapper, commitStyle.element, jodit.createInside, true);
17
+ if (commitStyle.elementIsList) {
18
+ return wrapList(commitStyle, wrapper, jodit);
19
+ }
20
+ const newWrapper = Dom.replace(wrapper, commitStyle.element, jodit.createInside, true);
21
+ if (commitStyle.elementIsBlock) {
22
+ // Inline font styles left over from pasted content visually override
23
+ // the new block format — e.g. an h2 with `font-weight: normal;
24
+ // font-size: 24px` does not look like a heading at all, so the
25
+ // command seems to do nothing. See
26
+ // https://github.com/xdan/jodit/issues/1063
27
+ css(newWrapper, 'fontSize', null);
28
+ css(newWrapper, 'fontWeight', null);
29
+ if (!attr(newWrapper, 'style')) {
30
+ attr(newWrapper, 'style', null);
31
+ }
32
+ }
33
+ return newWrapper;
19
34
  }
20
35
  const WRAP_NODES = new Set([
21
36
  'td',
@@ -137,8 +137,11 @@ export class Popup extends UIGroup {
137
137
  * Calculate static bound for point
138
138
  */
139
139
  getKeepBound(getBound) {
140
+ var _a;
140
141
  const oldBound = getBound();
141
- const elmUnderCursor = this.od.elementFromPoint(oldBound.left, oldBound.top);
142
+ // Inside Shadow DOM `document.elementFromPoint` returns the shadow
143
+ // host, so the lookup must start from the shadow root
144
+ const elmUnderCursor = ((_a = this.j.o.shadowRoot) !== null && _a !== void 0 ? _a : this.od).elementFromPoint(oldBound.left, oldBound.top);
142
145
  if (!elmUnderCursor) {
143
146
  return getBound;
144
147
  }
package/esm/jodit.js CHANGED
@@ -1073,6 +1073,18 @@ let Jodit = Jodit_1 = class Jodit extends ViewWithToolbar {
1073
1073
  (opt.uploader.url || opt.uploader.insertImageAsBase64URI)) {
1074
1074
  this.uploader.bind(this.editor);
1075
1075
  }
1076
+ else if (!opt.enableDragAndDropFileToEditor) {
1077
+ // Without a bound uploader nobody cancels a file drop, and
1078
+ // Firefox inserts the dropped image natively — the option
1079
+ // must mean "do nothing". See
1080
+ // https://github.com/xdan/jodit/issues/1077
1081
+ this.e.on(editor, 'drop', (e) => {
1082
+ var _a, _b;
1083
+ if ((_b = (_a = e.dataTransfer) === null || _a === void 0 ? void 0 : _a.files) === null || _b === void 0 ? void 0 : _b.length) {
1084
+ e.preventDefault();
1085
+ }
1086
+ });
1087
+ }
1076
1088
  // in initEditor - the editor could change
1077
1089
  if (!this.__elementToPlace.get(this.editor)) {
1078
1090
  this.__elementToPlace.set(this.editor, currentPlace);
@@ -15,6 +15,12 @@ export declare class ToolbarContent<T extends IViewBased = IViewBased> extends U
15
15
  className(): string;
16
16
  /** @override */
17
17
  update(): void;
18
+ /**
19
+ * The content is arbitrary HTML — propagate the disabled state to the
20
+ * nested form controls (e.g. the file input of the Upload button),
21
+ * otherwise they stay interactive.
22
+ */
23
+ protected onChangeDisabled(): void;
18
24
  /** @override */
19
25
  protected createContainer(): HTMLElement;
20
26
  constructor(jodit: T, control: IControlTypeContent, target?: Nullable<HTMLElement>);
@@ -24,13 +24,38 @@ let ToolbarContent = class ToolbarContent extends UIButton {
24
24
  }
25
25
  /** @override */
26
26
  update() {
27
- const content = this.control.getContent(this.j, this);
27
+ var _a, _b, _c;
28
+ const { control } = this;
29
+ const content = control.getContent(this.j, this);
28
30
  if (isString(content) || content.parentNode !== this.container) {
29
31
  Dom.detach(this.container);
30
32
  this.container.appendChild(isString(content) ? this.j.create.fromHTML(content) : content);
31
33
  }
34
+ // Content controls never went through the ToolbarButton status
35
+ // calculation, so `isDisabled`/`isActive`/`update` declared on the
36
+ // control were silently ignored (e.g. the FileBrowser Upload button
37
+ // ignored the backend permissions). See
38
+ // https://github.com/xdan/jodit/issues/1094
39
+ this.state.disabled = Boolean((_a = control.isDisabled) === null || _a === void 0 ? void 0 : _a.call(control, this.j, this));
40
+ this.state.activated = Boolean((_b = control.isActive) === null || _b === void 0 ? void 0 : _b.call(control, this.j, this));
41
+ (_c = control.update) === null || _c === void 0 ? void 0 : _c.call(control, this.j, this);
42
+ // The first update() runs before the state watchers are attached, so
43
+ // apply the calculated state explicitly (the calls are idempotent)
44
+ this.onChangeDisabled();
45
+ this.onChangeActivated();
32
46
  super.update();
33
47
  }
48
+ /**
49
+ * The content is arbitrary HTML — propagate the disabled state to the
50
+ * nested form controls (e.g. the file input of the Upload button),
51
+ * otherwise they stay interactive.
52
+ */
53
+ onChangeDisabled() {
54
+ super.onChangeDisabled();
55
+ this.container
56
+ .querySelectorAll('input,button,select,textarea')
57
+ .forEach(elm => attr(elm, 'disabled', this.state.disabled || null));
58
+ }
34
59
  /** @override */
35
60
  createContainer() {
36
61
  return this.j.c.span(this.componentName);
@@ -151,8 +151,11 @@ export class addNewLine extends Plugin {
151
151
  }
152
152
  }
153
153
  __onMouseMove(e) {
154
+ var _a;
154
155
  const editor = this.j;
155
- let currentElement = editor.ed.elementFromPoint(e.clientX, e.clientY);
156
+ // Inside Shadow DOM `document.elementFromPoint` returns the shadow
157
+ // host, so the lookup must start from the shadow root
158
+ let currentElement = ((_a = editor.o.shadowRoot) !== null && _a !== void 0 ? _a : editor.ed).elementFromPoint(e.clientX, e.clientY);
156
159
  if (!Dom.isHTMLElement(currentElement) ||
157
160
  !Dom.isOrContains(editor.editor, currentElement)) {
158
161
  return;
@@ -40,7 +40,13 @@ export class aiAssistant extends Plugin {
40
40
  return new UiAiAssistant(jodit, {
41
41
  onInsertAfter(html) {
42
42
  jodit.s.focus();
43
- jodit.s.setCursorAfter(jodit.s.current());
43
+ // `current()` returns a node of the selection START, so with
44
+ // several selected paragraphs the result landed after the
45
+ // first one — collapse the range to the END of the selection
46
+ // instead. See https://github.com/xdan/jodit/issues/1263
47
+ const range = jodit.s.range;
48
+ range.collapse(false);
49
+ jodit.s.selectRange(range);
44
50
  jodit.s.insertHTML(html);
45
51
  __dialog.close();
46
52
  },
@@ -32,13 +32,27 @@ export function checkJoinNeighbors(jodit, fakeNode, backspace) {
32
32
  }
33
33
  if (sibling &&
34
34
  (checkMoveListContent(jodit, mainClosestBox, sibling, backspace) ||
35
- moveContentAndRemoveEmpty(jodit, mainClosestBox, sibling, backspace))) {
35
+ moveContentAndRemoveEmpty(jodit, mainClosestBox, resolveTableSibling(sibling, backspace), backspace))) {
36
36
  jodit.s.setCursorBefore(fakeNode);
37
37
  return true;
38
38
  }
39
39
  }
40
40
  return false;
41
41
  }
42
+ /**
43
+ * Content cannot be merged into the `<table>` element itself — it would land
44
+ * between the table sections (after `</tbody>`), which is invalid HTML and
45
+ * gets foster-parented out of the table on the next parse. Merge into the
46
+ * edge cell instead. See https://github.com/xdan/jodit/issues/1064
47
+ * @private
48
+ */
49
+ function resolveTableSibling(sibling, backspace) {
50
+ if (!Dom.isTag(sibling, 'table')) {
51
+ return sibling;
52
+ }
53
+ const cells = sibling.querySelectorAll('td,th');
54
+ return cells.length ? cells[backspace ? cells.length - 1 : 0] : null;
55
+ }
42
56
  function checkMoveListContent(jodit, mainClosestBox, sibling, backspace) {
43
57
  // Process UL/LI/OL cases
44
58
  const siblingIsList = Dom.isTag(sibling, LIST_TAGS);
@@ -15,28 +15,28 @@ export function color(editor) {
15
15
  group: 'color'
16
16
  });
17
17
  const callback = (command, second, third) => {
18
+ var _a;
18
19
  const colorHEX = normalizeColor(third);
19
- switch (command) {
20
- case 'background':
21
- editor.s.commitStyle({
22
- attributes: {
23
- style: {
24
- backgroundColor: !colorHEX
25
- ? ''
26
- : colorHEX
27
- }
28
- }
29
- });
30
- break;
31
- case 'forecolor':
32
- editor.s.commitStyle({
33
- attributes: {
34
- style: {
35
- color: !colorHEX ? '' : colorHEX
36
- }
37
- }
38
- });
39
- break;
20
+ const value = !colorHEX ? '' : colorHEX;
21
+ const style = command === 'background'
22
+ ? { backgroundColor: value }
23
+ : { color: value };
24
+ // Cells selected with the `select-cells` plugin drop or collapse the
25
+ // native range, so `commitStyle` would paint a pending caret format
26
+ // outside the table instead of the selection the user sees. Apply the
27
+ // style to the content of every selected cell instead. See #1250
28
+ const selectedCells = editor
29
+ .getInstance('Table', editor.o)
30
+ .getAllSelectedCells();
31
+ if (selectedCells.length && editor.s.isCollapsed()) {
32
+ selectedCells.forEach(cell => {
33
+ editor.s.select(cell, true);
34
+ editor.s.commitStyle({ attributes: { style } });
35
+ });
36
+ (_a = editor.s.sel) === null || _a === void 0 ? void 0 : _a.removeAllRanges();
37
+ }
38
+ else {
39
+ editor.s.commitStyle({ attributes: { style } });
40
40
  }
41
41
  editor.synchronizeValues();
42
42
  return false;
@@ -50,6 +50,13 @@ export declare class inlinePopup extends Plugin {
50
50
  private __reopenSelectionPopup;
51
51
  private __onDocumentMouseDown;
52
52
  private __onCloseAllPopups;
53
+ /**
54
+ * The selection rect comes from the editor document — in iframe mode its
55
+ * coordinates are iframe-local, while the popup lives in the host
56
+ * document, so the iframe offset must be added. See
57
+ * https://github.com/xdan/jodit/issues/1058
58
+ */
59
+ private __selectionBound;
53
60
  private snapRange;
54
61
  private onSelectionStart;
55
62
  private onSelectionEnd;
@@ -176,10 +176,26 @@ export class inlinePopup extends Plugin {
176
176
  this.j.async.setTimeout(() => {
177
177
  const sel = this.j.s.sel;
178
178
  if (sel && !sel.isCollapsed) {
179
- this.showPopup(() => this.j.s.range.getBoundingClientRect(), 'selection');
179
+ this.showPopup(() => this.__selectionBound(), 'selection');
180
180
  }
181
181
  }, 1);
182
182
  }
183
+ /**
184
+ * The selection rect comes from the editor document — in iframe mode its
185
+ * coordinates are iframe-local, while the popup lives in the host
186
+ * document, so the iframe offset must be added. See
187
+ * https://github.com/xdan/jodit/issues/1058
188
+ */
189
+ __selectionBound() {
190
+ const rect = this.j.s.range.getBoundingClientRect();
191
+ let { left, top } = rect;
192
+ if (this.j.iframe) {
193
+ const offset = position(this.j.iframe, this.j, true);
194
+ left += offset.left;
195
+ top += offset.top;
196
+ }
197
+ return { left, top, width: rect.width, height: rect.height };
198
+ }
183
199
  onSelectionStart() {
184
200
  this.snapRange = this.j.s.range.cloneRange();
185
201
  }
@@ -219,7 +235,7 @@ export class inlinePopup extends Plugin {
219
235
  if (!node) {
220
236
  return;
221
237
  }
222
- this.showPopup(() => range.getBoundingClientRect(), type);
238
+ this.showPopup(() => this.__selectionBound(), type);
223
239
  }
224
240
  /**
225
241
  * In not collapsed selection - only one image
@@ -3,7 +3,7 @@
3
3
  * Released under MIT see LICENSE.txt in the project root for license information.
4
4
  * Copyright (c) 2013-2026 Valerii Chupurnov. All rights reserved. https://xdsoft.net
5
5
  */
6
- import { CLIPBOARD_ID, INSERT_AS_HTML, INSERT_AS_TEXT, INSERT_ONLY_TEXT, IS_PROD, TEXT_PLAIN } from "../../core/constants.js";
6
+ import { CLIPBOARD_ID, INSERT_AS_HTML, INSERT_AS_TEXT, INSERT_ONLY_TEXT, IS_PROD, TEXT_HTML, TEXT_PLAIN } from "../../core/constants.js";
7
7
  import { Config } from "../../config.js";
8
8
  import { pasteInsertHtml } from "./helpers.js";
9
9
  Config.prototype.askBeforePasteHTML = true;
@@ -21,6 +21,7 @@ const psKey = 'pasteStorage';
21
21
  Config.prototype.controls.paste = {
22
22
  tooltip: 'Paste from clipboard',
23
23
  async exec(editor, _, { control }) {
24
+ var _a;
24
25
  if (control.name === psKey) {
25
26
  editor.execCommand('showPasteStorage');
26
27
  return;
@@ -31,7 +32,15 @@ Config.prototype.controls.paste = {
31
32
  try {
32
33
  const items = await navigator.clipboard.read();
33
34
  if (items && items.length) {
34
- const textBlob = await items[0].getType(TEXT_PLAIN);
35
+ const item = items[0];
36
+ // Prefer the HTML flavor so the button behaves like the
37
+ // Ctrl+V shortcut (which receives text/html from the
38
+ // native paste event). See
39
+ // https://github.com/xdan/jodit/issues/1061
40
+ const type = ((_a = item.types) === null || _a === void 0 ? void 0 : _a.includes(TEXT_HTML))
41
+ ? TEXT_HTML
42
+ : TEXT_PLAIN;
43
+ const textBlob = await item.getType(type);
35
44
  text = await new Response(textBlob).text();
36
45
  }
37
46
  error = false;
@@ -101,20 +101,30 @@ let UISearch = class UISearch extends UIElement {
101
101
  .on(this.queryInput, 'input', () => {
102
102
  this.setMod('empty-query', !trim(this.queryInput.value).length);
103
103
  })
104
- .on(this.queryInput, 'keydown', this.j.async.debounce(async (e) => {
105
- switch (e.key) {
106
- case consts.KEY_ENTER:
104
+ .on(this.queryInput, 'keydown', (() => {
105
+ const debounced = this.j.async.debounce(async (e) => {
106
+ switch (e.key) {
107
+ case consts.KEY_ENTER:
108
+ if (await jodit.e.fire('searchNext')) {
109
+ this.close();
110
+ }
111
+ break;
112
+ default:
113
+ jodit.e.fire(this, 'needUpdateCounters');
114
+ break;
115
+ }
116
+ }, this.j.defaultTimeout);
117
+ return (e) => {
118
+ // Must be canceled synchronously — inside the debounced
119
+ // handler the browser has already submitted the parent
120
+ // form. See https://github.com/xdan/jodit/issues/918
121
+ if (e.key === consts.KEY_ENTER) {
107
122
  e.preventDefault();
108
123
  e.stopImmediatePropagation();
109
- if (await jodit.e.fire('searchNext')) {
110
- this.close();
111
- }
112
- break;
113
- default:
114
- jodit.e.fire(this, 'needUpdateCounters');
115
- break;
116
- }
117
- }, this.j.defaultTimeout));
124
+ }
125
+ debounced(e);
126
+ };
127
+ })());
118
128
  }
119
129
  onEditorKeyDown(e) {
120
130
  if (!this.isOpened) {
@@ -120,14 +120,16 @@ export class selectCells extends Plugin {
120
120
  * Mouse move inside the table
121
121
  */
122
122
  __onMove(table, e) {
123
- var _a;
123
+ var _a, _b;
124
124
  if (this.j.o.readonly && !this.j.isLocked) {
125
125
  return;
126
126
  }
127
127
  if (this.j.isLockedNotBy(key)) {
128
128
  return;
129
129
  }
130
- const node = this.j.ed.elementFromPoint(e.clientX, e.clientY);
130
+ // Inside Shadow DOM `document.elementFromPoint` returns the shadow
131
+ // host, so the lookup must start from the shadow root
132
+ const node = ((_a = this.j.o.shadowRoot) !== null && _a !== void 0 ? _a : this.j.ed).elementFromPoint(e.clientX, e.clientY);
131
133
  if (!node) {
132
134
  return;
133
135
  }
@@ -150,7 +152,7 @@ export class selectCells extends Plugin {
150
152
  }
151
153
  const cellsCount = this.__tableModule.getAllSelectedCells().length;
152
154
  if (cellsCount > 1) {
153
- (_a = this.j.s.sel) === null || _a === void 0 ? void 0 : _a.removeAllRanges();
155
+ (_b = this.j.s.sel) === null || _b === void 0 ? void 0 : _b.removeAllRanges();
154
156
  }
155
157
  this.j.e.fire('hidePopup');
156
158
  e.stopPropagation();
@@ -184,13 +186,15 @@ export class selectCells extends Plugin {
184
186
  * Stop a selection process
185
187
  */
186
188
  __onStopSelection(table, e) {
187
- var _a, _b;
189
+ var _a, _b, _c;
188
190
  if (!this.__selectedCell) {
189
191
  return;
190
192
  }
191
193
  this.__isSelectionMode = false;
192
194
  this.j.unlock();
193
- const node = this.j.ed.elementFromPoint(e.clientX, e.clientY);
195
+ // Inside Shadow DOM `document.elementFromPoint` returns the shadow
196
+ // host, so the lookup must start from the shadow root
197
+ const node = ((_a = this.j.o.shadowRoot) !== null && _a !== void 0 ? _a : this.j.ed).elementFromPoint(e.clientX, e.clientY);
194
198
  if (!node) {
195
199
  return;
196
200
  }
@@ -206,7 +210,7 @@ export class selectCells extends Plugin {
206
210
  cell,
207
211
  this.__selectedCell
208
212
  ]), box = this.__tableModule.formalMatrix(table);
209
- const max = (_a = box[bound[1][0]]) === null || _a === void 0 ? void 0 : _a[bound[1][1]], min = (_b = box[bound[0][0]]) === null || _b === void 0 ? void 0 : _b[bound[0][1]];
213
+ const max = (_b = box[bound[1][0]]) === null || _b === void 0 ? void 0 : _b[bound[1][1]], min = (_c = box[bound[0][0]]) === null || _c === void 0 ? void 0 : _c[bound[0][1]];
210
214
  // `getSelectedBound` keeps its `Infinity` sentinel when none of the
211
215
  // selected cells belong to this table's matrix — e.g. after a
212
216
  // drag-and-drop that moved/removed the cells, leaving a stale anchor and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jodit",
3
- "version": "4.12.22",
3
+ "version": "4.12.23",
4
4
  "description": "Jodit is an awesome and useful wysiwyg editor with filebrowser",
5
5
  "main": "esm/index.js",
6
6
  "types": "types/index.d.ts",
@@ -7,6 +7,6 @@
7
7
  * @module helpers/checker
8
8
  */
9
9
  /**
10
- * Detect if string is HTML from MS Word or Excel
10
+ * Detect if string is HTML from MS Word, Excel or LibreOffice/OpenOffice
11
11
  */
12
12
  export declare function isHtmlFromWord(data: string): boolean;
@@ -15,6 +15,12 @@ export declare class ToolbarContent<T extends IViewBased = IViewBased> extends U
15
15
  className(): string;
16
16
  /** @override */
17
17
  update(): void;
18
+ /**
19
+ * The content is arbitrary HTML — propagate the disabled state to the
20
+ * nested form controls (e.g. the file input of the Upload button),
21
+ * otherwise they stay interactive.
22
+ */
23
+ protected onChangeDisabled(): void;
18
24
  /** @override */
19
25
  protected createContainer(): HTMLElement;
20
26
  constructor(jodit: T, control: IControlTypeContent, target?: Nullable<HTMLElement>);
@@ -50,6 +50,13 @@ export declare class inlinePopup extends Plugin {
50
50
  private __reopenSelectionPopup;
51
51
  private __onDocumentMouseDown;
52
52
  private __onCloseAllPopups;
53
+ /**
54
+ * The selection rect comes from the editor document — in iframe mode its
55
+ * coordinates are iframe-local, while the popup lives in the host
56
+ * document, so the iframe offset must be added. See
57
+ * https://github.com/xdan/jodit/issues/1058
58
+ */
59
+ private __selectionBound;
53
60
  private snapRange;
54
61
  private onSelectionStart;
55
62
  private onSelectionEnd;