jodit 4.18.4 → 4.18.7

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 (209) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/es2015/jodit.css +13 -34
  3. package/es2015/jodit.fat.min.css +1 -1
  4. package/es2015/jodit.fat.min.js +12 -12
  5. package/es2015/jodit.js +1130 -225
  6. package/es2015/jodit.min.css +1 -1
  7. package/es2015/jodit.min.js +12 -12
  8. package/es2015/plugins/debug/debug.css +1 -1
  9. package/es2015/plugins/debug/debug.js +1 -1
  10. package/es2015/plugins/debug/debug.min.js +1 -1
  11. package/es2015/plugins/speech-recognize/speech-recognize.css +1 -1
  12. package/es2015/plugins/speech-recognize/speech-recognize.js +1 -1
  13. package/es2015/plugins/speech-recognize/speech-recognize.min.js +1 -1
  14. package/es2018/jodit.fat.min.css +1 -1
  15. package/es2018/jodit.fat.min.js +12 -12
  16. package/es2018/jodit.min.css +1 -1
  17. package/es2018/jodit.min.js +12 -12
  18. package/es2018/plugins/debug/debug.min.js +1 -1
  19. package/es2018/plugins/speech-recognize/speech-recognize.min.js +1 -1
  20. package/es2021/jodit.css +13 -34
  21. package/es2021/jodit.fat.min.css +1 -1
  22. package/es2021/jodit.fat.min.js +16 -16
  23. package/es2021/jodit.js +1121 -223
  24. package/es2021/jodit.min.css +1 -1
  25. package/es2021/jodit.min.js +16 -16
  26. package/es2021/plugins/debug/debug.css +1 -1
  27. package/es2021/plugins/debug/debug.js +1 -1
  28. package/es2021/plugins/debug/debug.min.js +1 -1
  29. package/es2021/plugins/speech-recognize/speech-recognize.css +1 -1
  30. package/es2021/plugins/speech-recognize/speech-recognize.js +1 -1
  31. package/es2021/plugins/speech-recognize/speech-recognize.min.js +1 -1
  32. package/es2021.en/jodit.css +13 -34
  33. package/es2021.en/jodit.fat.min.css +1 -1
  34. package/es2021.en/jodit.fat.min.js +16 -16
  35. package/es2021.en/jodit.js +1095 -197
  36. package/es2021.en/jodit.min.css +1 -1
  37. package/es2021.en/jodit.min.js +16 -16
  38. package/es2021.en/plugins/debug/debug.css +1 -1
  39. package/es2021.en/plugins/debug/debug.js +1 -1
  40. package/es2021.en/plugins/debug/debug.min.js +1 -1
  41. package/es2021.en/plugins/speech-recognize/speech-recognize.css +1 -1
  42. package/es2021.en/plugins/speech-recognize/speech-recognize.js +1 -1
  43. package/es2021.en/plugins/speech-recognize/speech-recognize.min.js +1 -1
  44. package/es5/jodit.css +14 -41
  45. package/es5/jodit.fat.min.css +1 -1
  46. package/es5/jodit.fat.min.js +2 -2
  47. package/es5/jodit.js +1260 -273
  48. package/es5/jodit.min.css +3 -3
  49. package/es5/jodit.min.js +2 -2
  50. package/es5/plugins/debug/debug.css +1 -1
  51. package/es5/plugins/debug/debug.js +1 -1
  52. package/es5/plugins/debug/debug.min.js +1 -1
  53. package/es5/plugins/speech-recognize/speech-recognize.css +1 -1
  54. package/es5/plugins/speech-recognize/speech-recognize.js +1 -1
  55. package/es5/plugins/speech-recognize/speech-recognize.min.js +1 -1
  56. package/es5/polyfills.fat.min.js +1 -1
  57. package/es5/polyfills.js +1 -1
  58. package/es5/polyfills.min.js +1 -1
  59. package/esm/config.d.ts +5 -0
  60. package/esm/core/constants.js +1 -1
  61. package/esm/core/decorators/component/component.js +12 -0
  62. package/esm/core/helpers/html/apply-styles.js +3 -0
  63. package/esm/core/helpers/html/clean-from-word.js +3 -0
  64. package/esm/core/helpers/html/strip-tags.js +24 -6
  65. package/esm/core/helpers/html/word-lists.d.ts +29 -0
  66. package/esm/core/helpers/html/word-lists.js +158 -0
  67. package/esm/core/selection/helpers/index.d.ts +1 -0
  68. package/esm/core/selection/helpers/index.js +1 -0
  69. package/esm/core/selection/helpers/is-moved.d.ts +15 -0
  70. package/esm/core/selection/helpers/is-moved.js +22 -0
  71. package/esm/core/selection/style/api/list/toggle-ordered-list.js +40 -3
  72. package/esm/core/selection/style/transactions.js +5 -2
  73. package/esm/core/ui/popup/popup.js +14 -9
  74. package/esm/jodit.js +5 -1
  75. package/esm/langs/ar.d.ts +3 -0
  76. package/esm/langs/ar.js +4 -1
  77. package/esm/langs/az.d.ts +3 -0
  78. package/esm/langs/az.js +4 -1
  79. package/esm/langs/cs_cz.d.ts +3 -0
  80. package/esm/langs/cs_cz.js +4 -1
  81. package/esm/langs/de.d.ts +3 -0
  82. package/esm/langs/de.js +4 -1
  83. package/esm/langs/es.d.ts +3 -0
  84. package/esm/langs/es.js +4 -1
  85. package/esm/langs/fa.d.ts +3 -0
  86. package/esm/langs/fa.js +4 -1
  87. package/esm/langs/fi.d.ts +3 -0
  88. package/esm/langs/fi.js +4 -1
  89. package/esm/langs/fr.d.ts +3 -0
  90. package/esm/langs/fr.js +4 -1
  91. package/esm/langs/he.d.ts +3 -0
  92. package/esm/langs/he.js +4 -1
  93. package/esm/langs/hu.d.ts +3 -0
  94. package/esm/langs/hu.js +4 -1
  95. package/esm/langs/id.d.ts +3 -0
  96. package/esm/langs/id.js +4 -1
  97. package/esm/langs/it.d.ts +3 -0
  98. package/esm/langs/it.js +4 -1
  99. package/esm/langs/ja.d.ts +3 -0
  100. package/esm/langs/ja.js +4 -1
  101. package/esm/langs/ko.d.ts +3 -0
  102. package/esm/langs/ko.js +4 -1
  103. package/esm/langs/mn.d.ts +3 -0
  104. package/esm/langs/mn.js +4 -1
  105. package/esm/langs/nl.d.ts +3 -0
  106. package/esm/langs/nl.js +4 -1
  107. package/esm/langs/no.d.ts +3 -0
  108. package/esm/langs/no.js +4 -1
  109. package/esm/langs/pl.d.ts +3 -0
  110. package/esm/langs/pl.js +4 -1
  111. package/esm/langs/pt_br.d.ts +3 -0
  112. package/esm/langs/pt_br.js +4 -1
  113. package/esm/langs/ru.d.ts +3 -0
  114. package/esm/langs/ru.js +4 -1
  115. package/esm/langs/sk.d.ts +3 -0
  116. package/esm/langs/sk.js +4 -1
  117. package/esm/langs/sv.d.ts +3 -0
  118. package/esm/langs/sv.js +4 -1
  119. package/esm/langs/tr.d.ts +3 -0
  120. package/esm/langs/tr.js +4 -1
  121. package/esm/langs/ua.d.ts +3 -0
  122. package/esm/langs/ua.js +4 -1
  123. package/esm/langs/zh_cn.d.ts +3 -0
  124. package/esm/langs/zh_cn.js +4 -1
  125. package/esm/langs/zh_tw.d.ts +3 -0
  126. package/esm/langs/zh_tw.js +4 -1
  127. package/esm/modules/dialog/dialog.d.ts +7 -0
  128. package/esm/modules/dialog/dialog.js +24 -6
  129. package/esm/modules/file-browser/ui/files/files.js +17 -2
  130. package/esm/modules/file-browser/ui/tree/tree.js +17 -2
  131. package/esm/modules/table/table.d.ts +6 -0
  132. package/esm/modules/table/table.js +18 -26
  133. package/esm/plugins/backspace/cases/check-join-neighbors.js +90 -3
  134. package/esm/plugins/backspace/cases/check-remove-char.d.ts +2 -1
  135. package/esm/plugins/backspace/cases/check-remove-char.js +25 -4
  136. package/esm/plugins/backspace/config.d.ts +2 -0
  137. package/esm/plugins/backspace/config.js +6 -3
  138. package/esm/plugins/clean-html/clean-html.d.ts +8 -0
  139. package/esm/plugins/clean-html/clean-html.js +21 -1
  140. package/esm/plugins/clean-html/helpers/index.d.ts +1 -0
  141. package/esm/plugins/clean-html/helpers/index.js +1 -0
  142. package/esm/plugins/clean-html/helpers/remove-placeholder-br.d.ts +16 -0
  143. package/esm/plugins/clean-html/helpers/remove-placeholder-br.js +51 -0
  144. package/esm/plugins/delete/delete.d.ts +11 -1
  145. package/esm/plugins/delete/delete.js +92 -16
  146. package/esm/plugins/focus/focus.js +12 -1
  147. package/esm/plugins/iframe/config.d.ts +3 -0
  148. package/esm/plugins/iframe/config.js +0 -27
  149. package/esm/plugins/iframe/iframe.js +8 -1
  150. package/esm/plugins/image-processor/image-processor.js +6 -1
  151. package/esm/plugins/image-properties/image-properties.d.ts +5 -0
  152. package/esm/plugins/image-properties/image-properties.js +30 -5
  153. package/esm/plugins/inline-popup/inline-popup.d.ts +19 -0
  154. package/esm/plugins/inline-popup/inline-popup.js +70 -1
  155. package/esm/plugins/mobile/mobile.js +7 -4
  156. package/esm/plugins/ordered-list/config.js +13 -2
  157. package/esm/plugins/ordered-list/start-dialog.d.ts +19 -0
  158. package/esm/plugins/ordered-list/start-dialog.js +58 -0
  159. package/esm/plugins/placeholder/placeholder.js +7 -1
  160. package/esm/plugins/resizer/resizer.d.ts +10 -0
  161. package/esm/plugins/resizer/resizer.js +76 -2
  162. package/esm/plugins/select-cells/select-cells.d.ts +23 -0
  163. package/esm/plugins/select-cells/select-cells.js +75 -1
  164. package/esm/plugins/table-keyboard-navigation/table-keyboard-navigation.js +3 -2
  165. package/package.json +1 -1
  166. package/types/config.d.ts +5 -0
  167. package/types/core/helpers/html/word-lists.d.ts +29 -0
  168. package/types/core/selection/helpers/index.d.ts +1 -0
  169. package/types/core/selection/helpers/is-moved.d.ts +15 -0
  170. package/types/langs/ar.d.ts +3 -0
  171. package/types/langs/az.d.ts +3 -0
  172. package/types/langs/cs_cz.d.ts +3 -0
  173. package/types/langs/de.d.ts +3 -0
  174. package/types/langs/es.d.ts +3 -0
  175. package/types/langs/fa.d.ts +3 -0
  176. package/types/langs/fi.d.ts +3 -0
  177. package/types/langs/fr.d.ts +3 -0
  178. package/types/langs/he.d.ts +3 -0
  179. package/types/langs/hu.d.ts +3 -0
  180. package/types/langs/id.d.ts +3 -0
  181. package/types/langs/it.d.ts +3 -0
  182. package/types/langs/ja.d.ts +3 -0
  183. package/types/langs/ko.d.ts +3 -0
  184. package/types/langs/mn.d.ts +3 -0
  185. package/types/langs/nl.d.ts +3 -0
  186. package/types/langs/no.d.ts +3 -0
  187. package/types/langs/pl.d.ts +3 -0
  188. package/types/langs/pt_br.d.ts +3 -0
  189. package/types/langs/ru.d.ts +3 -0
  190. package/types/langs/sk.d.ts +3 -0
  191. package/types/langs/sv.d.ts +3 -0
  192. package/types/langs/tr.d.ts +3 -0
  193. package/types/langs/ua.d.ts +3 -0
  194. package/types/langs/zh_cn.d.ts +3 -0
  195. package/types/langs/zh_tw.d.ts +3 -0
  196. package/types/modules/dialog/dialog.d.ts +7 -0
  197. package/types/modules/table/table.d.ts +6 -0
  198. package/types/plugins/backspace/cases/check-remove-char.d.ts +2 -1
  199. package/types/plugins/backspace/config.d.ts +2 -0
  200. package/types/plugins/clean-html/clean-html.d.ts +8 -0
  201. package/types/plugins/clean-html/helpers/index.d.ts +1 -0
  202. package/types/plugins/clean-html/helpers/remove-placeholder-br.d.ts +16 -0
  203. package/types/plugins/delete/delete.d.ts +11 -1
  204. package/types/plugins/iframe/config.d.ts +3 -0
  205. package/types/plugins/image-properties/image-properties.d.ts +5 -0
  206. package/types/plugins/inline-popup/inline-popup.d.ts +19 -0
  207. package/types/plugins/ordered-list/start-dialog.d.ts +19 -0
  208. package/types/plugins/resizer/resizer.d.ts +10 -0
  209. package/types/plugins/select-cells/select-cells.d.ts +23 -0
@@ -217,5 +217,8 @@ declare const _exports: {
217
217
  Loop: string;
218
218
  Muted: string;
219
219
  Controls: string;
220
+ 'Start at...': string;
221
+ 'Start numbering at': string;
222
+ 'Number for the first item': string;
220
223
  };
221
224
  export = _exports;
@@ -229,5 +229,8 @@ export default {
229
229
  Autoplay: '自動播放',
230
230
  Loop: '循環播放',
231
231
  Muted: '靜音',
232
- Controls: '播放控制項'
232
+ Controls: '播放控制項',
233
+ 'Start at...': '起始於...',
234
+ 'Start numbering at': '編號起始值',
235
+ 'Number for the first item': '第一項的編號'
233
236
  };
@@ -48,6 +48,13 @@ export declare class Dialog extends ViewWithToolbar implements IDialog {
48
48
  */
49
49
  private minSize;
50
50
  private __onResizerMouseDown;
51
+ /**
52
+ * The width `box` takes with its content on one line. The header and
53
+ * footer are flex rows as wide as the dialog, so the footer's
54
+ * `scrollWidth` was the dialog's width and kept it from getting any
55
+ * narrower
56
+ */
57
+ private __contentWidth;
51
58
  private __addGlobalResizeListeners;
52
59
  private __removeGlobalResizeListeners;
53
60
  OPTIONS: IDialogOptions;
@@ -179,7 +179,12 @@ let Dialog = Dialog_1 = class Dialog extends ViewWithToolbar {
179
179
  }
180
180
  }
181
181
  __onResizerMouseDown(e) {
182
- var _a, _b, _c;
182
+ var _a, _b;
183
+ // As for the header: otherwise Safari selects the dialog's text as
184
+ // the pointer moves
185
+ if (e.cancelable) {
186
+ e.preventDefault();
187
+ }
183
188
  this.resizable = true;
184
189
  this.startX = e.clientX;
185
190
  this.startY = e.clientY;
@@ -195,11 +200,11 @@ let Dialog = Dialog_1 = class Dialog extends ViewWithToolbar {
195
200
  : 0;
196
201
  this.minSize.w = isNumber(this.o.minWidth)
197
202
  ? this.o.minWidth
198
- : Math.max(100, (_a = footer === null || footer === void 0 ? void 0 : footer.scrollWidth) !== null && _a !== void 0 ? _a : 0);
203
+ : Math.max(100, header ? this.__contentWidth(header) : 0, footer ? this.__contentWidth(footer) : 0);
199
204
  this.minSize.h = isNumber(this.o.minHeight)
200
205
  ? this.o.minHeight
201
- : ((_b = header === null || header === void 0 ? void 0 : header.offsetHeight) !== null && _b !== void 0 ? _b : 0) +
202
- ((_c = footer === null || footer === void 0 ? void 0 : footer.offsetHeight) !== null && _c !== void 0 ? _c : 0) +
206
+ : ((_a = header === null || header === void 0 ? void 0 : header.offsetHeight) !== null && _a !== void 0 ? _a : 0) +
207
+ ((_b = footer === null || footer === void 0 ? void 0 : footer.offsetHeight) !== null && _b !== void 0 ? _b : 0) +
203
208
  contentMinHeight +
204
209
  this.resizer.offsetHeight;
205
210
  this.lockSelect();
@@ -211,6 +216,19 @@ let Dialog = Dialog_1 = class Dialog extends ViewWithToolbar {
211
216
  this.e.fire(this, 'startResize');
212
217
  }
213
218
  }
219
+ /**
220
+ * The width `box` takes with its content on one line. The header and
221
+ * footer are flex rows as wide as the dialog, so the footer's
222
+ * `scrollWidth` was the dialog's width and kept it from getting any
223
+ * narrower
224
+ */
225
+ __contentWidth(box) {
226
+ const { width } = box.style;
227
+ box.style.width = 'max-content';
228
+ const contentWidth = box.offsetWidth;
229
+ box.style.width = width;
230
+ return contentWidth;
231
+ }
214
232
  __addGlobalResizeListeners() {
215
233
  const self = this;
216
234
  self.e
@@ -220,8 +238,8 @@ let Dialog = Dialog_1 = class Dialog extends ViewWithToolbar {
220
238
  __removeGlobalResizeListeners() {
221
239
  const self = this;
222
240
  self.e
223
- .off(self.ow, 'mousemove pointermove', self.__onMouseMove)
224
- .off(self.ow, 'mouseup pointerup', self.__onMouseUp);
241
+ .off(self.ow, 'pointermove touchmove', self.__onMouseMove)
242
+ .off(self.ow, 'pointerup touchend', self.__onMouseUp);
225
243
  }
226
244
  /**
227
245
  * Specifies the size of the window
@@ -3,9 +3,24 @@
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
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
7
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
8
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
9
+ r = Reflect.decorate(decorators, target, key, desc);
10
+ else
11
+ for (var i = decorators.length - 1; i >= 0; i--)
12
+ if (d = decorators[i])
13
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
14
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
15
+ };
16
+ import { component } from "../../../../core/decorators/index.js";
6
17
  import { UIGroup } from "../../../../core/ui/index.js";
7
- export class FileBrowserFiles extends UIGroup {
18
+ let FileBrowserFiles = class FileBrowserFiles extends UIGroup {
8
19
  className() {
9
20
  return 'FileBrowserFiles';
10
21
  }
11
- }
22
+ };
23
+ FileBrowserFiles = __decorate([
24
+ component
25
+ ], FileBrowserFiles);
26
+ export { FileBrowserFiles };
@@ -3,9 +3,24 @@
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
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
7
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
8
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
9
+ r = Reflect.decorate(decorators, target, key, desc);
10
+ else
11
+ for (var i = decorators.length - 1; i >= 0; i--)
12
+ if (d = decorators[i])
13
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
14
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
15
+ };
16
+ import { component } from "../../../../core/decorators/index.js";
6
17
  import { UIGroup } from "../../../../core/ui/index.js";
7
- export class FileBrowserTree extends UIGroup {
18
+ let FileBrowserTree = class FileBrowserTree extends UIGroup {
8
19
  className() {
9
20
  return 'FileBrowserTree';
10
21
  }
11
- }
22
+ };
23
+ FileBrowserTree = __decorate([
24
+ component
25
+ ], FileBrowserTree);
26
+ export { FileBrowserTree };
@@ -15,6 +15,12 @@ export declare class Table extends ViewComponent<IJodit> {
15
15
  className(): string;
16
16
  private selected;
17
17
  private static __selectedByTable;
18
+ /**
19
+ * Rewrite the `<style>` that marks the selected cells once for all the
20
+ * cells (un)selected in one go: a drag over a block of cells unmarks and
21
+ * marks every one of them on each move. `@debounce()` was never applied,
22
+ * as a Table never reaches `ready`
23
+ */
18
24
  private __recalculateStyles;
19
25
  addSelection(td: HTMLTableCellElement): void;
20
26
  removeSelection(td: HTMLTableCellElement): void;
@@ -3,19 +3,8 @@
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
- var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
7
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
8
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
9
- r = Reflect.decorate(decorators, target, key, desc);
10
- else
11
- for (var i = decorators.length - 1; i >= 0; i--)
12
- if (d = decorators[i])
13
- r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
14
- return c > 3 && r && Object.defineProperty(target, key, r), r;
15
- };
16
6
  import { ViewComponent } from "../../core/component/index.js";
17
7
  import * as consts from "../../core/constants.js";
18
- import { debounce } from "../../core/decorators/index.js";
19
8
  import { Dom } from "../../core/dom/index.js";
20
9
  import { getContainer } from "../../core/global.js";
21
10
  import { attr, css, cssPath, isNumber, toArray, trim } from "../../core/helpers/index.js";
@@ -25,23 +14,29 @@ export class Table extends ViewComponent {
25
14
  constructor() {
26
15
  super(...arguments);
27
16
  this.selected = new Set();
17
+ /**
18
+ * Rewrite the `<style>` that marks the selected cells once for all the
19
+ * cells (un)selected in one go: a drag over a block of cells unmarks and
20
+ * marks every one of them on each move. `@debounce()` was never applied,
21
+ * as a Table never reaches `ready`
22
+ */
23
+ this.__recalculateStyles = this.async.microDebounce(() => {
24
+ const style = getContainer(this.j, Table, 'style', true);
25
+ const selectors = [];
26
+ this.selected.forEach(td => {
27
+ const selector = cssPath(td);
28
+ selector && selectors.push(selector);
29
+ });
30
+ style.innerHTML = selectors.length
31
+ ? selectors.join(',') +
32
+ `{${this.jodit.options.table.selectionCellStyle}}`
33
+ : '';
34
+ });
28
35
  }
29
36
  /** @override */
30
37
  className() {
31
38
  return 'Table';
32
39
  }
33
- __recalculateStyles() {
34
- const style = getContainer(this.j, Table, 'style', true);
35
- const selectors = [];
36
- this.selected.forEach(td => {
37
- const selector = cssPath(td);
38
- selector && selectors.push(selector);
39
- });
40
- style.innerHTML = selectors.length
41
- ? selectors.join(',') +
42
- `{${this.jodit.options.table.selectionCellStyle}}`
43
- : '';
44
- }
45
40
  addSelection(td) {
46
41
  this.selected.add(td);
47
42
  this.__recalculateStyles();
@@ -701,7 +696,4 @@ export class Table extends ViewComponent {
701
696
  }
702
697
  }
703
698
  Table.__selectedByTable = new WeakMap();
704
- __decorate([
705
- debounce()
706
- ], Table.prototype, "__recalculateStyles", null);
707
699
  const instance = (j) => j.getInstance('Table', j.o);
@@ -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 { LIST_TAGS } from "../../../core/constants.js";
6
+ import { INSEPARABLE_TAGS, LIST_TAGS } from "../../../core/constants.js";
7
7
  import { Dom } from "../../../core/dom/dom.js";
8
8
  import { getMoveFilter } from "../helpers.js";
9
9
  /**
@@ -21,6 +21,10 @@ export function checkJoinNeighbors(jodit, fakeNode, backspace) {
21
21
  }
22
22
  if (Dom.isElement(mainClosestBox) &&
23
23
  Dom.isContentEditable(mainClosestBox, jodit.editor)) {
24
+ // A table cell is never joined with its neighbour
25
+ if (Dom.up(fakeNode, Dom.isCell, mainClosestBox, true)) {
26
+ return false;
27
+ }
24
28
  const sibling = Dom.findNotEmptySibling(mainClosestBox, backspace);
25
29
  // A line break before the cursor is a single unit: remove it instead of
26
30
  // merging the current element into the (void) `<br>`, which used to move
@@ -30,15 +34,60 @@ export function checkJoinNeighbors(jodit, fakeNode, backspace) {
30
34
  jodit.s.setCursorBefore(fakeNode);
31
35
  return true;
32
36
  }
37
+ // Backspace leaves a block after a table alone, as Word does, rather
38
+ // than moving its text into the last cell
39
+ if (backspace && Dom.isTag(sibling, 'table')) {
40
+ jodit.s.setCursorBefore(fakeNode);
41
+ return true;
42
+ }
43
+ const box = findCaretBox(fakeNode, mainClosestBox);
33
44
  if (sibling &&
34
- (checkMoveListContent(jodit, mainClosestBox, sibling, backspace) ||
35
- moveContentAndRemoveEmpty(jodit, mainClosestBox, resolveTableSibling(sibling, backspace), backspace))) {
45
+ (checkPullNextLine(jodit, fakeNode, box, sibling, backspace) ||
46
+ checkMoveListContent(jodit, box, sibling, backspace) ||
47
+ moveContentAndRemoveEmpty(jodit, box, findEdgeBlock(resolveTableSibling(sibling, backspace), backspace, jodit.editor), backspace))) {
36
48
  jodit.s.setCursorBefore(fakeNode);
37
49
  return true;
38
50
  }
39
51
  }
40
52
  return false;
41
53
  }
54
+ /**
55
+ * The block holding the caret inside the box that has the sibling, so
56
+ * `<p>one</p><div><p>|two</p></div>` joins the two paragraphs instead of
57
+ * moving the whole `<div>` into the first one. For a list item that is its
58
+ * list, which the list cases handle.
59
+ */
60
+ function findCaretBox(fakeNode, mainClosestBox) {
61
+ const block = Dom.up(fakeNode, Dom.isBlock, mainClosestBox);
62
+ if (!block) {
63
+ return mainClosestBox;
64
+ }
65
+ return Dom.isLeaf(block) && block.parentElement
66
+ ? block.parentElement
67
+ : block;
68
+ }
69
+ /**
70
+ * The innermost block on the sibling's edge facing the caret, so the
71
+ * content is joined to text: `<div><p>one</p></div><p>|two</p>` gives
72
+ * `<div><p>one|two</p></div>`
73
+ */
74
+ function findEdgeBlock(sibling, backspace, root) {
75
+ let block = sibling;
76
+ while (block) {
77
+ const edge = backspace ? block.lastChild : block.firstChild;
78
+ const child = edge && Dom.isEmptyTextNode(edge)
79
+ ? Dom.findNotEmptySibling(edge, backspace)
80
+ : edge;
81
+ if (!Dom.isBlock(child) ||
82
+ Dom.isTag(child, INSEPARABLE_TAGS) ||
83
+ Dom.isTag(child, 'table') ||
84
+ !Dom.isContentEditable(child, root)) {
85
+ return block;
86
+ }
87
+ block = child;
88
+ }
89
+ return block;
90
+ }
42
91
  /**
43
92
  * Content cannot be merged into the `<table>` element itself — it would land
44
93
  * between the table sections (after `</tbody>`), which is invalid HTML and
@@ -56,6 +105,44 @@ function resolveTableSibling(sibling, backspace) {
56
105
  });
57
106
  return cells.length ? cells[backspace ? cells.length - 1 : 0] : null;
58
107
  }
108
+ /**
109
+ * Delete at the edge of a list pulls the next line up to the caret, as the
110
+ * browsers do, instead of moving the caret's line into the next block:
111
+ * `<ul><li>one|</li></ul><p>two</p>` gives `<ul><li>one|two</li></ul>`, and
112
+ * `<p>one|</p><ul><li>two</li></ul>` gives `<p>one|two</p>`
113
+ * @private
114
+ */
115
+ function checkPullNextLine(jodit, fakeNode, box, sibling, backspace) {
116
+ if (backspace || !(Dom.isList(box) || Dom.isList(sibling))) {
117
+ return false;
118
+ }
119
+ const target = Dom.up(fakeNode, Dom.isBlock, box, true);
120
+ const source = Dom.isList(sibling) ? sibling.firstElementChild : sibling;
121
+ if (!target ||
122
+ !source ||
123
+ Dom.isTag(source, INSEPARABLE_TAGS) ||
124
+ Dom.isTag(source, 'table')) {
125
+ return false;
126
+ }
127
+ if (!Dom.isEmpty(source)) {
128
+ const line = [];
129
+ for (let node = source.firstChild; node && !Dom.isBlock(node); node = node.nextSibling) {
130
+ line.push(node);
131
+ }
132
+ if (!line.length) {
133
+ return false;
134
+ }
135
+ const filter = getMoveFilter(jodit);
136
+ line.forEach(node => filter(node) ? Dom.append(target, node) : Dom.safeRemove(node));
137
+ }
138
+ let remove = source;
139
+ while (remove && remove !== jodit.editor && Dom.isEmpty(remove)) {
140
+ const parent = remove.parentElement;
141
+ Dom.safeRemove(remove);
142
+ remove = parent;
143
+ }
144
+ return true;
145
+ }
59
146
  function checkMoveListContent(jodit, mainClosestBox, sibling, backspace) {
60
147
  // Process UL/LI/OL cases
61
148
  const siblingIsList = Dom.isTag(sibling, LIST_TAGS);
@@ -19,6 +19,7 @@ import type { DeleteMode } from "../interface";
19
19
  * ```html
20
20
  * t|st
21
21
  * ```
22
+ * With `stopBeforeSpace`, nothing is removed when the next char is a space.
22
23
  * @private
23
24
  */
24
- export declare function checkRemoveChar(jodit: IJodit, fakeNode: Node, backspace: boolean, mode: DeleteMode): boolean;
25
+ export declare function checkRemoveChar(jodit: IJodit, fakeNode: Node, backspace: boolean, mode: DeleteMode, stopBeforeSpace?: boolean): boolean;
@@ -18,10 +18,11 @@ import { findMostNestedNeighbor } from "../helpers.js";
18
18
  * ```html
19
19
  * t|st
20
20
  * ```
21
+ * With `stopBeforeSpace`, nothing is removed when the next char is a space.
21
22
  * @private
22
23
  */
23
24
  // eslint-disable-next-line complexity
24
- export function checkRemoveChar(jodit, fakeNode, backspace, mode) {
25
+ export function checkRemoveChar(jodit, fakeNode, backspace, mode, stopBeforeSpace = false) {
25
26
  var _a, _b;
26
27
  const step = backspace ? -1 : 1;
27
28
  const anotherSibling = Dom.sibling(fakeNode, !backspace);
@@ -40,6 +41,10 @@ export function checkRemoveChar(jodit, fakeNode, backspace, mode) {
40
41
  break;
41
42
  }
42
43
  if ((_a = sibling.nodeValue) === null || _a === void 0 ? void 0 : _a.length) {
44
+ if (stopBeforeSpace &&
45
+ isSpace(nextChar(sibling.nodeValue, backspace))) {
46
+ break;
47
+ }
43
48
  removed = tryRemoveChar(sibling, backspace, step, anotherSibling);
44
49
  if (!sibling.nodeValue.length &&
45
50
  Dom.isInlineBlock(sibling.parentNode)) {
@@ -160,8 +165,24 @@ function replaceSpaceOnNBSP(anotherSibling, backspace, value) {
160
165
  }
161
166
  function checkRepeatRemoveCharAction(backspace, sibling, fakeNode, mode, removed, jodit) {
162
167
  call(backspace ? Dom.after : Dom.before, sibling, fakeNode);
163
- if (mode === 'sentence' ||
164
- (mode === 'word' && removed !== ' ' && removed !== NBSP_SPACE)) {
165
- checkRemoveChar(jodit, fakeNode, backspace, mode);
168
+ const wordChar = mode === 'word' && !isSpace(removed);
169
+ // Backspace by word removes the spaces before the caret, then the word, and keeps the space in front of it
170
+ if (mode === 'sentence' || wordChar || (mode === 'word' && backspace)) {
171
+ checkRemoveChar(jodit, fakeNode, backspace, mode, wordChar && backspace);
166
172
  }
167
173
  }
174
+ /**
175
+ * The char tryRemoveChar would remove next
176
+ */
177
+ function nextChar(value, backspace) {
178
+ const chars = toArray(value);
179
+ const step = backspace ? -1 : 1;
180
+ let index = backspace ? chars.length - 1 : 0;
181
+ while (chars[index] === INVISIBLE_SPACE) {
182
+ index += step;
183
+ }
184
+ return chars[index];
185
+ }
186
+ function isSpace(char) {
187
+ return char === ' ' || char === NBSP_SPACE;
188
+ }
@@ -7,6 +7,8 @@ declare module 'jodit/config' {
7
7
  interface Config {
8
8
  /**
9
9
  * Keyboard hotkey mappings for delete and backspace operations (character, word, sentence).
10
+ * On macOS the defaults leave out Cmd+Backspace, Ctrl+Backspace, Ctrl+Option+Backspace
11
+ * and Cmd+Option+Backspace, so the browser applies the system's own bindings to them.
10
12
  */
11
13
  delete: {
12
14
  hotkeys: {
@@ -6,14 +6,17 @@
6
6
  /**
7
7
  * @module plugins/backspace
8
8
  */
9
+ import { IS_MAC } from "../../core/constants.js";
9
10
  import { Config } from "../../config.js";
10
11
  Config.prototype.delete = {
11
12
  hotkeys: {
12
- delete: ['delete', 'cmd+backspace'],
13
- deleteWord: ['ctrl+delete', 'cmd+alt+backspace', 'ctrl+alt+backspace'],
13
+ delete: IS_MAC ? ['delete'] : ['delete', 'cmd+backspace'],
14
+ deleteWord: IS_MAC
15
+ ? ['ctrl+delete']
16
+ : ['ctrl+delete', 'cmd+alt+backspace', 'ctrl+alt+backspace'],
14
17
  deleteSentence: ['ctrl+shift+delete', 'cmd+shift+delete'],
15
18
  backspace: ['backspace'],
16
- backspaceWord: ['ctrl+backspace'],
19
+ backspaceWord: IS_MAC ? [] : ['ctrl+backspace'],
17
20
  backspaceSentence: ['ctrl+shift+backspace', 'cmd+shift+backspace']
18
21
  }
19
22
  };
@@ -23,6 +23,14 @@ export declare class cleanHtml extends Plugin {
23
23
  */
24
24
  protected onChangeCleanHTML(): void;
25
25
  private currentSelectionNode;
26
+ /**
27
+ * Typing into an empty block (`<p><br></p>`) replaces its `<br>`, but the
28
+ * browser keeps the `<br>` when the caret is in an invisible-space text
29
+ * node in front of it, as Jodit leaves the caret in many of the empty
30
+ * blocks it makes (a cleared editor, a heading in an empty table cell).
31
+ * See #1632
32
+ */
33
+ protected onInputRemovePlaceholderBr(event: InputEvent | CompositionEvent): void;
26
34
  private walker;
27
35
  protected startWalker(): void;
28
36
  protected beforeCommand(command: string): void | false;
@@ -21,7 +21,7 @@ import { safeHTML } from "../../core/helpers/html/safe-html.js";
21
21
  import { Plugin } from "../../core/plugin/plugin.js";
22
22
  import "./config.js";
23
23
  import { replaceOldTags } from "./helpers/visitor/filters/replace-old-tags.js";
24
- import { getHash, removeFormatForCollapsedSelection, removeFormatForSelection, visitNodeWalker } from "./helpers/index.js";
24
+ import { getHash, removeFormatForCollapsedSelection, removeFormatForSelection, removePlaceholderBr, visitNodeWalker } from "./helpers/index.js";
25
25
  /**
26
26
  * Clean HTML after removeFormat and insertHorizontalRule command
27
27
  */
@@ -72,6 +72,23 @@ export class cleanHtml extends Plugin {
72
72
  this.walker.setWork(editor.editor);
73
73
  this.currentSelectionNode = editor.s.current();
74
74
  }
75
+ /**
76
+ * Typing into an empty block (`<p><br></p>`) replaces its `<br>`, but the
77
+ * browser keeps the `<br>` when the caret is in an invisible-space text
78
+ * node in front of it, as Jodit leaves the caret in many of the empty
79
+ * blocks it makes (a cleared editor, a heading in an empty table cell).
80
+ * See #1632
81
+ */
82
+ onInputRemovePlaceholderBr(event) {
83
+ var _a;
84
+ if (!this.isEditMode ||
85
+ (event.type === 'input' &&
86
+ (event.isComposing ||
87
+ !((_a = event.inputType) === null || _a === void 0 ? void 0 : _a.startsWith('insert'))))) {
88
+ return;
89
+ }
90
+ removePlaceholderBr(this.j);
91
+ }
75
92
  startWalker() {
76
93
  const { jodit } = this;
77
94
  const allow = getHash(this.j.o.cleanHTML.allowTags);
@@ -239,6 +256,9 @@ export class cleanHtml extends Plugin {
239
256
  __decorate([
240
257
  watch([':change', ':afterSetMode', ':afterInit', ':mousedown', ':keydown'])
241
258
  ], cleanHtml.prototype, "onChangeCleanHTML", null);
259
+ __decorate([
260
+ watch([':input', 'jodit.editor:compositionend'])
261
+ ], cleanHtml.prototype, "onInputRemovePlaceholderBr", null);
242
262
  __decorate([
243
263
  hook('ready')
244
264
  ], cleanHtml.prototype, "startWalker", null);
@@ -10,4 +10,5 @@ export * from "./get-hash";
10
10
  export * from "./is-allowed-media-embed";
11
11
  export * from "./remove-format/remove-format-for-collapsed-selection";
12
12
  export * from "./remove-format/remove-format-for-selection";
13
+ export * from "./remove-placeholder-br";
13
14
  export * from "./visitor/visit-node-walker";
@@ -10,4 +10,5 @@ export * from "./get-hash.js";
10
10
  export * from "./is-allowed-media-embed.js";
11
11
  export * from "./remove-format/remove-format-for-collapsed-selection.js";
12
12
  export * from "./remove-format/remove-format-for-selection.js";
13
+ export * from "./remove-placeholder-br.js";
13
14
  export * from "./visitor/visit-node-walker.js";
@@ -0,0 +1,16 @@
1
+ /*!
2
+ * Jodit Editor (https://xdsoft.net/jodit/)
3
+ * Released under MIT see LICENSE.txt in the project root for license information.
4
+ * Copyright (c) 2013-2026 Valerii Chupurnov. All rights reserved. https://xdsoft.net
5
+ */
6
+ /**
7
+ * @module plugins/clean-html
8
+ */
9
+ import type { IJodit } from "../../../types/index";
10
+ /**
11
+ * Remove the `<br>` that kept the block with the caret open while it was
12
+ * empty, once text has gone into the block: when the block is one line of
13
+ * text that ends in that `<br>`, the `<br>` adds nothing to it
14
+ * @private
15
+ */
16
+ export declare function removePlaceholderBr(jodit: IJodit): void;
@@ -0,0 +1,51 @@
1
+ /*!
2
+ * Jodit Editor (https://xdsoft.net/jodit/)
3
+ * Released under MIT see LICENSE.txt in the project root for license information.
4
+ * Copyright (c) 2013-2026 Valerii Chupurnov. All rights reserved. https://xdsoft.net
5
+ */
6
+ import { INSEPARABLE_TAGS } from "../../../core/constants.js";
7
+ import { Dom } from "../../../core/dom/dom.js";
8
+ import { trim } from "../../../core/helpers/string/trim.js";
9
+ function lastNode(node) {
10
+ let last = node.lastChild;
11
+ while (Dom.isText(last) && !last.nodeValue) {
12
+ last = last.previousSibling;
13
+ }
14
+ return last;
15
+ }
16
+ /**
17
+ * Remove the `<br>` that kept the block with the caret open while it was
18
+ * empty, once text has gone into the block: when the block is one line of
19
+ * text that ends in that `<br>`, the `<br>` adds nothing to it
20
+ * @private
21
+ */
22
+ export function removePlaceholderBr(jodit) {
23
+ var _a;
24
+ const block = Dom.closest(jodit.s.current(), Dom.isBlock, jodit.editor);
25
+ if (!block || !trim((_a = block.textContent) !== null && _a !== void 0 ? _a : '')) {
26
+ return;
27
+ }
28
+ let br = lastNode(block);
29
+ while (Dom.isElement(br) &&
30
+ !Dom.isTag(br, INSEPARABLE_TAGS) &&
31
+ !Dom.isBlock(br)) {
32
+ br = lastNode(br);
33
+ }
34
+ if (!Dom.isTag(br, 'br')) {
35
+ return;
36
+ }
37
+ // Another line break, a block, an image or an embed means the `<br>` may
38
+ // end an empty line, or give the caret a place after the embed
39
+ const isOneLineOfText = Dom.each(block, (node) => {
40
+ if (node !== br &&
41
+ Dom.isElement(node) &&
42
+ (Dom.isTag(node, INSEPARABLE_TAGS) ||
43
+ Dom.isBlock(node) ||
44
+ (Dom.isHTMLElement(node) && !node.isContentEditable))) {
45
+ return false;
46
+ }
47
+ });
48
+ if (isOneLineOfText) {
49
+ Dom.safeRemove(br);
50
+ }
51
+ }
@@ -20,13 +20,23 @@ export declare class deleteCommand extends Plugin {
20
20
  */
21
21
  private __afterDeleteCommand;
22
22
  private __onDeleteCommand;
23
+ /**
24
+ * A selection that starts or ends in a table cell clears the selected part
25
+ * of each cell it covers and keeps the cells, as the browsers do.
26
+ * `deleteContents` would remove the cells it fully covers, and the cells
27
+ * it cuts would then be merged as blocks. The range is moved out of those
28
+ * tables, so only the content between them is left to delete.
29
+ *
30
+ * Returns where the caret goes, or null when no table is involved
31
+ */
32
+ private __clearSelectedCells;
23
33
  private __moveContentInLeftSibling;
24
34
  /**
25
35
  * If left sibling is list - return last leaf
26
36
  */
27
37
  private __defineRightLeftBox;
28
38
  /**
29
- * Add BR in empty blocks left and right(for table cell)
39
+ * Add BR in the empty block left at the caret
30
40
  */
31
41
  private __addBrInEmptyBlock;
32
42
  private __moveCursorInEditableSibling;