jodit 4.18.3 → 4.18.6

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 (237) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/es2015/jodit.css +72 -34
  3. package/es2015/jodit.fat.min.css +1 -1
  4. package/es2015/jodit.fat.min.js +35 -10
  5. package/es2015/jodit.js +1967 -327
  6. package/es2015/jodit.min.css +1 -1
  7. package/es2015/jodit.min.js +41 -16
  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 +35 -10
  16. package/es2018/jodit.min.css +1 -1
  17. package/es2018/jodit.min.js +35 -10
  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 +72 -34
  21. package/es2021/jodit.fat.min.css +1 -1
  22. package/es2021/jodit.fat.min.js +42 -17
  23. package/es2021/jodit.js +1949 -323
  24. package/es2021/jodit.min.css +1 -1
  25. package/es2021/jodit.min.js +42 -17
  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 +72 -34
  33. package/es2021.en/jodit.fat.min.css +1 -1
  34. package/es2021.en/jodit.fat.min.js +42 -17
  35. package/es2021.en/jodit.js +1923 -297
  36. package/es2021.en/jodit.min.css +1 -1
  37. package/es2021.en/jodit.min.js +51 -26
  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 +85 -38
  45. package/es5/jodit.fat.min.css +1 -1
  46. package/es5/jodit.fat.min.js +2 -2
  47. package/es5/jodit.js +2225 -379
  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 +28 -26
  60. package/esm/config.js +19 -21
  61. package/esm/core/constants.js +1 -1
  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/helpers/utils/align.d.ts +1 -1
  68. package/esm/core/helpers/utils/align.js +2 -3
  69. package/esm/core/selection/helpers/index.d.ts +1 -0
  70. package/esm/core/selection/helpers/index.js +1 -0
  71. package/esm/core/selection/helpers/is-moved.d.ts +15 -0
  72. package/esm/core/selection/helpers/is-moved.js +22 -0
  73. package/esm/core/selection/style/api/list/toggle-ordered-list.js +40 -3
  74. package/esm/core/selection/style/transactions.js +5 -2
  75. package/esm/jodit.js +14 -4
  76. package/esm/langs/ar.d.ts +10 -0
  77. package/esm/langs/ar.js +11 -1
  78. package/esm/langs/az.d.ts +10 -0
  79. package/esm/langs/az.js +11 -1
  80. package/esm/langs/cs_cz.d.ts +10 -0
  81. package/esm/langs/cs_cz.js +12 -1
  82. package/esm/langs/de.d.ts +10 -0
  83. package/esm/langs/de.js +11 -1
  84. package/esm/langs/es.d.ts +10 -0
  85. package/esm/langs/es.js +11 -1
  86. package/esm/langs/fa.d.ts +10 -0
  87. package/esm/langs/fa.js +11 -1
  88. package/esm/langs/fi.d.ts +10 -0
  89. package/esm/langs/fi.js +11 -1
  90. package/esm/langs/fr.d.ts +10 -0
  91. package/esm/langs/fr.js +11 -1
  92. package/esm/langs/he.d.ts +10 -0
  93. package/esm/langs/he.js +11 -1
  94. package/esm/langs/hu.d.ts +10 -0
  95. package/esm/langs/hu.js +11 -1
  96. package/esm/langs/id.d.ts +10 -0
  97. package/esm/langs/id.js +11 -1
  98. package/esm/langs/it.d.ts +10 -0
  99. package/esm/langs/it.js +11 -1
  100. package/esm/langs/ja.d.ts +10 -0
  101. package/esm/langs/ja.js +11 -1
  102. package/esm/langs/ko.d.ts +10 -0
  103. package/esm/langs/ko.js +11 -1
  104. package/esm/langs/mn.d.ts +10 -0
  105. package/esm/langs/mn.js +11 -1
  106. package/esm/langs/nl.d.ts +10 -0
  107. package/esm/langs/nl.js +11 -1
  108. package/esm/langs/no.d.ts +10 -0
  109. package/esm/langs/no.js +11 -1
  110. package/esm/langs/pl.d.ts +10 -0
  111. package/esm/langs/pl.js +11 -1
  112. package/esm/langs/pt_br.d.ts +10 -0
  113. package/esm/langs/pt_br.js +11 -1
  114. package/esm/langs/ru.d.ts +10 -0
  115. package/esm/langs/ru.js +11 -1
  116. package/esm/langs/sk.d.ts +10 -0
  117. package/esm/langs/sk.js +11 -1
  118. package/esm/langs/sv.d.ts +10 -0
  119. package/esm/langs/sv.js +11 -1
  120. package/esm/langs/tr.d.ts +10 -0
  121. package/esm/langs/tr.js +11 -1
  122. package/esm/langs/ua.d.ts +10 -0
  123. package/esm/langs/ua.js +11 -1
  124. package/esm/langs/zh_cn.d.ts +10 -0
  125. package/esm/langs/zh_cn.js +11 -1
  126. package/esm/langs/zh_tw.d.ts +10 -0
  127. package/esm/langs/zh_tw.js +11 -1
  128. package/esm/modules/dialog/dialog.d.ts +7 -0
  129. package/esm/modules/dialog/dialog.js +24 -6
  130. package/esm/modules/table/table.d.ts +6 -0
  131. package/esm/modules/table/table.js +18 -26
  132. package/esm/plugins/all.d.ts +1 -0
  133. package/esm/plugins/all.js +1 -0
  134. package/esm/plugins/backspace/cases/check-join-neighbors.js +90 -3
  135. package/esm/plugins/clean-html/clean-html.d.ts +8 -0
  136. package/esm/plugins/clean-html/clean-html.js +21 -1
  137. package/esm/plugins/clean-html/helpers/index.d.ts +1 -0
  138. package/esm/plugins/clean-html/helpers/index.js +1 -0
  139. package/esm/plugins/clean-html/helpers/remove-placeholder-br.d.ts +16 -0
  140. package/esm/plugins/clean-html/helpers/remove-placeholder-br.js +51 -0
  141. package/esm/plugins/delete/delete.d.ts +11 -1
  142. package/esm/plugins/delete/delete.js +92 -16
  143. package/esm/plugins/focus/focus.js +12 -1
  144. package/esm/plugins/iframe/config.d.ts +3 -0
  145. package/esm/plugins/iframe/config.js +0 -27
  146. package/esm/plugins/iframe/iframe.js +8 -1
  147. package/esm/plugins/image-processor/image-processor.d.ts +4 -1
  148. package/esm/plugins/image-processor/image-processor.js +15 -4
  149. package/esm/plugins/image-properties/readers/align.d.ts +2 -2
  150. package/esm/plugins/index.d.ts +1 -0
  151. package/esm/plugins/inline-popup/config/items/iframe.d.ts +9 -1
  152. package/esm/plugins/inline-popup/config/items/iframe.js +15 -0
  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/media/media.js +16 -7
  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/config.d.ts +5 -3
  161. package/esm/plugins/resizer/config.js +8 -2
  162. package/esm/plugins/resizer/resizer.d.ts +20 -0
  163. package/esm/plugins/resizer/resizer.js +140 -17
  164. package/esm/plugins/select-cells/select-cells.d.ts +23 -0
  165. package/esm/plugins/select-cells/select-cells.js +75 -1
  166. package/esm/plugins/table-keyboard-navigation/table-keyboard-navigation.js +3 -2
  167. package/esm/plugins/video/config.d.ts +6 -0
  168. package/esm/plugins/video/config.js +77 -65
  169. package/esm/plugins/video-properties/interface.d.ts +31 -0
  170. package/esm/plugins/video-properties/interface.js +6 -0
  171. package/esm/plugins/video-properties/readers.d.ts +26 -0
  172. package/esm/plugins/video-properties/readers.js +65 -0
  173. package/esm/plugins/video-properties/replace.d.ts +15 -0
  174. package/esm/plugins/video-properties/replace.js +97 -0
  175. package/esm/plugins/video-properties/ui/ui-video-form.d.ts +41 -0
  176. package/esm/plugins/video-properties/ui/ui-video-form.js +228 -0
  177. package/esm/plugins/video-properties/video-properties.d.ts +37 -0
  178. package/esm/plugins/video-properties/video-properties.js +175 -0
  179. package/esm/plugins/video-properties/writers.d.ts +15 -0
  180. package/esm/plugins/video-properties/writers.js +73 -0
  181. package/esm/types/types.d.ts +1 -1
  182. package/package.json +1 -1
  183. package/types/config.d.ts +28 -26
  184. package/types/core/helpers/html/word-lists.d.ts +29 -0
  185. package/types/core/helpers/utils/align.d.ts +1 -1
  186. package/types/core/selection/helpers/index.d.ts +1 -0
  187. package/types/core/selection/helpers/is-moved.d.ts +15 -0
  188. package/types/langs/ar.d.ts +10 -0
  189. package/types/langs/az.d.ts +10 -0
  190. package/types/langs/cs_cz.d.ts +10 -0
  191. package/types/langs/de.d.ts +10 -0
  192. package/types/langs/es.d.ts +10 -0
  193. package/types/langs/fa.d.ts +10 -0
  194. package/types/langs/fi.d.ts +10 -0
  195. package/types/langs/fr.d.ts +10 -0
  196. package/types/langs/he.d.ts +10 -0
  197. package/types/langs/hu.d.ts +10 -0
  198. package/types/langs/id.d.ts +10 -0
  199. package/types/langs/it.d.ts +10 -0
  200. package/types/langs/ja.d.ts +10 -0
  201. package/types/langs/ko.d.ts +10 -0
  202. package/types/langs/mn.d.ts +10 -0
  203. package/types/langs/nl.d.ts +10 -0
  204. package/types/langs/no.d.ts +10 -0
  205. package/types/langs/pl.d.ts +10 -0
  206. package/types/langs/pt_br.d.ts +10 -0
  207. package/types/langs/ru.d.ts +10 -0
  208. package/types/langs/sk.d.ts +10 -0
  209. package/types/langs/sv.d.ts +10 -0
  210. package/types/langs/tr.d.ts +10 -0
  211. package/types/langs/ua.d.ts +10 -0
  212. package/types/langs/zh_cn.d.ts +10 -0
  213. package/types/langs/zh_tw.d.ts +10 -0
  214. package/types/modules/dialog/dialog.d.ts +7 -0
  215. package/types/modules/table/table.d.ts +6 -0
  216. package/types/plugins/clean-html/clean-html.d.ts +8 -0
  217. package/types/plugins/clean-html/helpers/index.d.ts +1 -0
  218. package/types/plugins/clean-html/helpers/remove-placeholder-br.d.ts +16 -0
  219. package/types/plugins/delete/delete.d.ts +11 -1
  220. package/types/plugins/iframe/config.d.ts +3 -0
  221. package/types/plugins/image-processor/image-processor.d.ts +4 -1
  222. package/types/plugins/image-properties/readers/align.d.ts +2 -2
  223. package/types/plugins/index.d.ts +1 -0
  224. package/types/plugins/inline-popup/config/items/iframe.d.ts +9 -1
  225. package/types/plugins/inline-popup/inline-popup.d.ts +19 -0
  226. package/types/plugins/ordered-list/start-dialog.d.ts +19 -0
  227. package/types/plugins/resizer/config.d.ts +5 -3
  228. package/types/plugins/resizer/resizer.d.ts +20 -0
  229. package/types/plugins/select-cells/select-cells.d.ts +23 -0
  230. package/types/plugins/video/config.d.ts +6 -0
  231. package/types/plugins/video-properties/interface.d.ts +31 -0
  232. package/types/plugins/video-properties/readers.d.ts +26 -0
  233. package/types/plugins/video-properties/replace.d.ts +15 -0
  234. package/types/plugins/video-properties/ui/ui-video-form.d.ts +41 -0
  235. package/types/plugins/video-properties/video-properties.d.ts +37 -0
  236. package/types/plugins/video-properties/writers.d.ts +15 -0
  237. package/types/types/types.d.ts +1 -1
@@ -17,10 +17,13 @@ import * as consts from "../../core/constants.js";
17
17
  import { IS_ES_NEXT, IS_IE, KEY_ALT } from "../../core/constants.js";
18
18
  import { autobind, watch } from "../../core/decorators/index.js";
19
19
  import { Dom } from "../../core/dom/dom.js";
20
- import { eventEmitter, pluginSystem } from "../../core/global.js";
21
- import { $$, attr, css, dataBind, innerWidth, markOwner, offset } from "../../core/helpers/index.js";
20
+ import { eventEmitter, getContainer, pluginSystem } from "../../core/global.js";
21
+ import { $$, attr, css, dataBind, getWrappedMedia, innerWidth, markOwner, offset } from "../../core/helpers/index.js";
22
22
  import { cssInline } from "../../core/helpers/utils/css.js";
23
23
  import { Plugin } from "../../core/plugin/plugin.js";
24
+ import { isMoved } from "../../core/selection/helpers/index.js";
25
+ import { UIElement } from "../../core/ui/index.js";
26
+ import { Popup } from "../../core/ui/popup/index.js";
24
27
  import "./config.js";
25
28
  const keyBInd = '__jodit-resizer_binded';
26
29
  /**
@@ -36,6 +39,26 @@ function attrPixelSize(element, key) {
36
39
  }
37
40
  return parseFloat(value);
38
41
  }
42
+ /**
43
+ * A video with no height of its own takes it from its width and its ratio,
44
+ * so resizing it keeps the ratio without setting a height.
45
+ */
46
+ function heightFollowsWidth(element) {
47
+ return (Dom.isTag(element, 'video') &&
48
+ !attr(element, 'height') &&
49
+ !cssInline(element, 'height'));
50
+ }
51
+ const IFRAME_WRAPPER = '[data-jodit_iframe_wrapper]';
52
+ /**
53
+ * A click on an embed lands on its wrapper, so it selects the embed, until the
54
+ * wrapper is active. While the editor is locked, as during resizing, the
55
+ * wrapper takes the mouse again.
56
+ */
57
+ const EMBED_WRAPPER_STYLE = `${IFRAME_WRAPPER}{position:relative;display:block;user-select:none}` +
58
+ `${IFRAME_WRAPPER} iframe{position:relative}` +
59
+ `${IFRAME_WRAPPER}::after{position:absolute;z-index:1;display:block;background:transparent;content:'';cursor:pointer;inset:0}` +
60
+ `${IFRAME_WRAPPER}[data-jodit-wrapper_active=true] iframe{z-index:2}` +
61
+ `.jodit_lock ${IFRAME_WRAPPER}[data-jodit-wrapper_active=true] iframe{z-index:1}`;
39
62
  /**
40
63
  * The module creates a supporting frame for resizing of the elements img and table
41
64
  */
@@ -58,9 +81,13 @@ export class resizer extends Plugin {
58
81
  <div class="jodit-resizer__bottom-left"></div>
59
82
  <span>100x100</span>
60
83
  </div>`);
84
+ // Clips the frame to the workplace, without the frame making it scroll
85
+ this.layer = this.j.c.div('jodit-resizer-layer');
61
86
  this.sizeViewer = this.rect.getElementsByTagName('span')[0];
62
87
  this.pointerX = 0;
63
88
  this.pointerY = 0;
89
+ this.__keyDownRange = null;
90
+ this.__keyDownTable = null;
64
91
  this.isAltMode = false;
65
92
  this.onClickElement = (element) => {
66
93
  if (this.isResizeMode) {
@@ -72,6 +99,10 @@ export class resizer extends Plugin {
72
99
  if (Dom.isTag(this.element, 'img') && !this.element.complete) {
73
100
  this.j.e.one(this.element, 'load', this.updateSize);
74
101
  }
102
+ if (Dom.isTag(this.element, 'video') &&
103
+ this.element.readyState < HTMLMediaElement.HAVE_METADATA) {
104
+ this.j.e.one(this.element, 'loadedmetadata', this.updateSize);
105
+ }
75
106
  }
76
107
  };
77
108
  this.updateSize = () => {
@@ -108,6 +139,9 @@ export class resizer extends Plugin {
108
139
  }
109
140
  /** @override */
110
141
  afterInit(editor) {
142
+ // It goes in the editing document, so iframe mode has it whatever `iframeStyle` holds
143
+ getContainer(editor, 'resizer', 'style', true).textContent =
144
+ EMBED_WRAPPER_STYLE;
111
145
  $$('div', this.rect).forEach((resizeHandle) => {
112
146
  editor.e.on(resizeHandle, 'mousedown.resizer touchstart.resizer', this.onStartResizing.bind(this, resizeHandle));
113
147
  });
@@ -119,6 +153,9 @@ export class resizer extends Plugin {
119
153
  }
120
154
  })
121
155
  .on('afterInit changePlace', this.addEventListeners.bind(this))
156
+ // Handlers without a namespace run first, ahead of those that
157
+ // move the caret, as table-keyboard-navigation's do
158
+ .on('keydown', this.onEditorKeyDown, { top: true })
122
159
  .on('afterGetValueFromEditor.resizer', (data) => {
123
160
  const rgx = /<jodit[^>]+data-jodit_iframe_wrapper[^>]+>(.*?<iframe[^>]*>.*?<\/iframe>.*?)<\/jodit>/gi;
124
161
  if (rgx.test(data.value)) {
@@ -135,14 +172,33 @@ export class resizer extends Plugin {
135
172
  let node = e.target;
136
173
  const { editor, options: { allowResizeTags } } = this.j;
137
174
  while (node && node !== editor) {
138
- if (Dom.isTag(node, allowResizeTags)) {
139
- this.__bind(node);
140
- this.onClickElement(node);
175
+ const element = this.__resizable(node);
176
+ if (Dom.isTag(element, allowResizeTags)) {
177
+ this.__bind(element);
178
+ this.onClickElement(element);
141
179
  return;
142
180
  }
143
181
  node = node.parentNode;
144
182
  }
145
183
  }
184
+ /**
185
+ * What a click on `node` resizes: the media in it when it's a media
186
+ * wrapper (`jodit-media`, or one from `mediaWrappers`), or `node` itself
187
+ */
188
+ __resizable(node) {
189
+ var _a;
190
+ const media = (_a = getWrappedMedia(this.j, node)) !== null && _a !== void 0 ? _a : (Dom.isHTMLElement(node) && attr(node, 'data-jodit_fake_wrapper')
191
+ ? node.firstElementChild
192
+ : null);
193
+ if (!media) {
194
+ return node;
195
+ }
196
+ // An embed is resized through its own wrapper
197
+ return Dom.isHTMLElement(media.parentNode) &&
198
+ attr(media.parentNode, 'data-jodit_iframe_wrapper')
199
+ ? media.parentNode
200
+ : media;
201
+ }
146
202
  __afterInsertImage(image) {
147
203
  if (this.j.o.resizer.forImageChangeAttributes) {
148
204
  return;
@@ -169,17 +225,15 @@ export class resizer extends Plugin {
169
225
  })
170
226
  .on(editor.ow, 'resize.resizer', this.updateSize)
171
227
  .on('resize.resizer', this.updateSize)
172
- .on([editor.ow, editor.editor], 'scroll.resizer', () => {
173
- if (this.isShown && !this.isResizeMode) {
174
- this.hide();
175
- }
176
- })
228
+ // The frame is outside the content, so it's moved to follow the
229
+ // element as the content scrolls
230
+ .on([editor.ow, editor.editor], 'scroll.resizer', this.updateSize)
177
231
  .on(editor.ow, 'keydown.resizer', this.onKeyDown)
178
232
  .on(editor.ow, 'keyup.resizer', this.onKeyUp)
179
233
  .on(editor.ow, 'mouseup.resizer touchend.resizer', this.onClickOutside);
180
234
  }
181
235
  onStartResizing(resizeHandle, e) {
182
- if (!this.element || !this.element.parentNode) {
236
+ if (!this.element || !this.isElementInEditor) {
183
237
  this.hide();
184
238
  return false;
185
239
  }
@@ -233,8 +287,9 @@ export class resizer extends Plugin {
233
287
  const className = this.handle.className;
234
288
  let new_w, new_h;
235
289
  const uar = this.j.o.resizer.useAspectRatio;
236
- if (!this.isAltMode &&
237
- (uar === true || (uar && Dom.isTag(this.element, uar)))) {
290
+ const keepRatio = !this.isAltMode &&
291
+ (uar === true || Boolean(uar && Dom.isTag(this.element, uar)));
292
+ if (keepRatio) {
238
293
  if (diff_x) {
239
294
  new_w =
240
295
  this.width +
@@ -266,7 +321,8 @@ export class resizer extends Plugin {
266
321
  this.applySize(this.element, 'width', '100%');
267
322
  }
268
323
  }
269
- if (new_h > this.j.o.resizer.min_height) {
324
+ if (new_h > this.j.o.resizer.min_height &&
325
+ !(keepRatio && heightFollowsWidth(this.element))) {
270
326
  this.applySize(this.element, 'height', new_h);
271
327
  }
272
328
  this.updateSize();
@@ -274,6 +330,43 @@ export class resizer extends Plugin {
274
330
  e.stopImmediatePropagation();
275
331
  }
276
332
  }
333
+ onEditorKeyDown(event) {
334
+ // A key held down moves the caret on every repeat, with one keyup
335
+ if (event === null || event === void 0 ? void 0 : event.repeat) {
336
+ this.__followCaret();
337
+ }
338
+ this.__keyDownRange = this.j.s.range.cloneRange();
339
+ this.__keyDownTable = this.__caretTable();
340
+ }
341
+ __caretTable() {
342
+ return Dom.closest(this.j.s.range.startContainer, 'table', this.j.editor);
343
+ }
344
+ /**
345
+ * Hide the frame once a key has moved the caret off its element, and
346
+ * frame a table the caret has moved into, as clicks do
347
+ */
348
+ onEditorKeyUp() {
349
+ this.__followCaret();
350
+ }
351
+ __followCaret() {
352
+ const before = this.__keyDownRange;
353
+ this.__keyDownRange = null;
354
+ if (!isMoved(this.j, before)) {
355
+ return;
356
+ }
357
+ if (this.isShown &&
358
+ this.element &&
359
+ !this.j.s.range.intersectsNode(this.element)) {
360
+ this.hide();
361
+ }
362
+ const table = this.__caretTable();
363
+ if (table &&
364
+ table !== this.__keyDownTable &&
365
+ this.j.o.allowResizeTags.has('table')) {
366
+ this.__bind(table);
367
+ this.onClickElement(table);
368
+ }
369
+ }
277
370
  onKeyDown(e) {
278
371
  this.isAltMode = e.key === KEY_ALT;
279
372
  if (!this.isAltMode && this.isResizeMode) {
@@ -295,6 +388,12 @@ export class resizer extends Plugin {
295
388
  return;
296
389
  }
297
390
  if (!this.isResizeMode) {
391
+ // A click in the element's popup, or in a list it opens, works on
392
+ // the element, so it keeps its frame
393
+ if ((e === null || e === void 0 ? void 0 : e.target) &&
394
+ UIElement.closestElement(e.target, Popup)) {
395
+ return;
396
+ }
298
397
  return this.hide();
299
398
  }
300
399
  e.stopImmediatePropagation();
@@ -312,10 +411,20 @@ export class resizer extends Plugin {
312
411
  css(element, key, value);
313
412
  }
314
413
  }
414
+ /**
415
+ * The framed element can go with a wrapper round it, keeping its parent
416
+ */
417
+ get isElementInEditor() {
418
+ return Boolean(this.element && this.j.editor.contains(this.element));
419
+ }
315
420
  onDelete(e) {
316
421
  if (!this.element) {
317
422
  return;
318
423
  }
424
+ if (!this.isElementInEditor) {
425
+ this.hide();
426
+ return;
427
+ }
319
428
  if (this.element.tagName !== 'JODIT') {
320
429
  this.j.s.select(this.element);
321
430
  }
@@ -327,7 +436,7 @@ export class resizer extends Plugin {
327
436
  }
328
437
  __onChangeEditor() {
329
438
  if (this.isShown) {
330
- if (!this.element || !this.element.parentNode) {
439
+ if (!this.isElementInEditor) {
331
440
  this.hide();
332
441
  }
333
442
  else {
@@ -439,9 +548,13 @@ export class resizer extends Plugin {
439
548
  return;
440
549
  }
441
550
  this.isShown = true;
551
+ if (this.layer.parentNode !== this.j.workplace) {
552
+ markOwner(this.j, this.layer);
553
+ Dom.append(this.j.workplace, this.layer);
554
+ }
442
555
  if (!this.rect.parentNode) {
443
556
  markOwner(this.j, this.rect);
444
- Dom.append(this.j.workplace, this.rect);
557
+ Dom.append(this.layer, this.rect);
445
558
  }
446
559
  if (this.j.isFullSize) {
447
560
  css(this.rect, 'zIndex', css(this.j.container, 'zIndex').toString());
@@ -462,8 +575,12 @@ export class resizer extends Plugin {
462
575
  }
463
576
  beforeDestruct(jodit) {
464
577
  this.hide();
578
+ Dom.safeRemove(this.layer);
465
579
  eventEmitter.off('hideHelpers', this.hide);
466
- jodit.e.off(this.j.ow, '.resizer').off('.resizer');
580
+ jodit.e
581
+ .off(this.j.ow, '.resizer')
582
+ .off('.resizer')
583
+ .off('keydown', this.onEditorKeyDown);
467
584
  }
468
585
  }
469
586
  __decorate([
@@ -481,6 +598,12 @@ __decorate([
481
598
  __decorate([
482
599
  autobind
483
600
  ], resizer.prototype, "onResize", null);
601
+ __decorate([
602
+ autobind
603
+ ], resizer.prototype, "onEditorKeyDown", null);
604
+ __decorate([
605
+ watch(':keyup')
606
+ ], resizer.prototype, "onEditorKeyUp", null);
484
607
  __decorate([
485
608
  autobind
486
609
  ], resizer.prototype, "onKeyDown", null);
@@ -18,6 +18,29 @@ export declare class selectCells extends Plugin {
18
18
  */
19
19
  private get __tableModule();
20
20
  protected afterInit(jodit: IJodit): void;
21
+ /**
22
+ * The cell with the caret, and the selection, when a key went down
23
+ */
24
+ private __keyDownCell;
25
+ private __keyDownRange;
26
+ private __onKeyDown;
27
+ /**
28
+ * A link's popup replaces the cell's while the caret is in the link
29
+ */
30
+ private __keyDownInLink;
31
+ private __isInLink;
32
+ /**
33
+ * Where the selection starts rather than `s.current()`, which finds the
34
+ * first cell when the caret is just before the table
35
+ */
36
+ private __caretCell;
37
+ /**
38
+ * A key that moves the caret into a cell, from another cell or from
39
+ * outside the table, marks that cell and opens its popup, as a click on
40
+ * it does. One that moves the caret out of the table unmarks the cells
41
+ */
42
+ protected onKeyUp(): void;
43
+ private __followCaret;
21
44
  /**
22
45
  * First selected cell
23
46
  */
@@ -19,6 +19,7 @@ import { Dom } from "../../core/dom/dom.js";
19
19
  import { pluginSystem } from "../../core/global.js";
20
20
  import { $$, alignElement, position, toArray } from "../../core/helpers/index.js";
21
21
  import { Plugin } from "../../core/plugin/index.js";
22
+ import { isMoved } from "../../core/selection/helpers/index.js";
22
23
  import { Table } from "../../modules/table/table.js";
23
24
  import "./config.js";
24
25
  const key = 'table_processor_observer';
@@ -26,6 +27,15 @@ const MOUSE_MOVE_LABEL = 'onMoveTableSelectCell';
26
27
  export class selectCells extends Plugin {
27
28
  constructor() {
28
29
  super(...arguments);
30
+ /**
31
+ * The cell with the caret, and the selection, when a key went down
32
+ */
33
+ this.__keyDownCell = null;
34
+ this.__keyDownRange = null;
35
+ /**
36
+ * A link's popup replaces the cell's while the caret is in the link
37
+ */
38
+ this.__keyDownInLink = false;
29
39
  /**
30
40
  * First selected cell
31
41
  */
@@ -46,6 +56,9 @@ export class selectCells extends Plugin {
46
56
  return;
47
57
  }
48
58
  jodit.e
59
+ // Handlers without a namespace run first, ahead of those that
60
+ // move the caret, as table-keyboard-navigation's do
61
+ .on('keydown', this.__onKeyDown, { top: true })
49
62
  .on('keydown.select-cells', (event) => {
50
63
  if (event.key === KEY_TAB) {
51
64
  this.unselectCells();
@@ -76,6 +89,61 @@ export class selectCells extends Plugin {
76
89
  }
77
90
  });
78
91
  }
92
+ __onKeyDown(event) {
93
+ // A key held down moves the caret on every repeat, with one keyup
94
+ if (event === null || event === void 0 ? void 0 : event.repeat) {
95
+ this.__followCaret();
96
+ }
97
+ this.__keyDownCell = this.__caretCell();
98
+ this.__keyDownRange = this.j.s.range.cloneRange();
99
+ this.__keyDownInLink = this.__isInLink();
100
+ }
101
+ __isInLink() {
102
+ return Boolean(Dom.closest(this.j.s.range.startContainer, 'a', this.j.editor));
103
+ }
104
+ /**
105
+ * Where the selection starts rather than `s.current()`, which finds the
106
+ * first cell when the caret is just before the table
107
+ */
108
+ __caretCell() {
109
+ return Dom.closest(this.j.s.range.startContainer, Dom.isCell, this.j.editor);
110
+ }
111
+ /**
112
+ * A key that moves the caret into a cell, from another cell or from
113
+ * outside the table, marks that cell and opens its popup, as a click on
114
+ * it does. One that moves the caret out of the table unmarks the cells
115
+ */
116
+ onKeyUp() {
117
+ this.__followCaret();
118
+ }
119
+ __followCaret() {
120
+ const before = this.__keyDownRange;
121
+ this.__keyDownRange = null;
122
+ if (this.j.o.readonly || !isMoved(this.j, before)) {
123
+ return;
124
+ }
125
+ const cell = this.__caretCell();
126
+ const hasLeftLink = this.__keyDownInLink && !this.__isInLink();
127
+ if (cell === this.__keyDownCell && !(cell && hasLeftLink)) {
128
+ return;
129
+ }
130
+ const hadMarked = this.__tableModule.getAllSelectedCells().length > 0;
131
+ this.unselectCells();
132
+ if (!cell) {
133
+ if (hadMarked) {
134
+ this.j.e.fire('hidePopup', 'cells');
135
+ }
136
+ return;
137
+ }
138
+ const table = Dom.closest(cell, 'table', this.j.editor);
139
+ if (!table) {
140
+ return;
141
+ }
142
+ // As after a click, the first change to the cell keeps the mark
143
+ this.__selectedCell = cell;
144
+ this.__tableModule.addSelection(cell);
145
+ this.j.e.fire('showPopup', table, () => position(cell, this.j), 'cells');
146
+ }
79
147
  /**
80
148
  * Mouse click inside the table
81
149
  */
@@ -418,10 +486,16 @@ export class selectCells extends Plugin {
418
486
  /** @override */
419
487
  beforeDestruct(jodit) {
420
488
  this.__onRemoveSelection();
421
- jodit.e.off('.select-cells');
489
+ jodit.e.off('.select-cells').off('keydown', this.__onKeyDown);
422
490
  }
423
491
  }
424
492
  selectCells.requires = ['select'];
493
+ __decorate([
494
+ autobind
495
+ ], selectCells.prototype, "__onKeyDown", null);
496
+ __decorate([
497
+ watch(':keyup')
498
+ ], selectCells.prototype, "onKeyUp", null);
425
499
  __decorate([
426
500
  autobind
427
501
  ], selectCells.prototype, "onStartSelection", null);
@@ -36,7 +36,9 @@ export function tableKeyboardNavigation(editor) {
36
36
  case consts.KEY_TAB:
37
37
  case consts.KEY_LEFT: {
38
38
  next = getNextCell();
39
- if (!next) {
39
+ // Left in the first cell leaves the table, as Up in the
40
+ // top row does
41
+ if (!next && key === consts.KEY_TAB) {
40
42
  tableModule.appendRow(table, !isPrev
41
43
  ? false
42
44
  : Dom.first(table, node => Dom.isTag(node, 'tr')), !isPrev);
@@ -65,7 +67,6 @@ export function tableKeyboardNavigation(editor) {
65
67
  if (!next) {
66
68
  return;
67
69
  }
68
- editor.e.fire('hidePopup hideResizer');
69
70
  if (!next.firstChild) {
70
71
  const first = editor.createInside.element('br');
71
72
  Dom.append(next, first);
@@ -53,3 +53,9 @@ declare module 'jodit/config' {
53
53
  };
54
54
  }
55
55
  }
56
+ /**
57
+ * The video popup's tabs: Upload, when the uploader is configured, then Link
58
+ * and Code. `insert` runs what a tab inserts, which by default goes in at the
59
+ * selection the popup was opened with.
60
+ */
61
+ export declare function videoTabs(jodit: IJodit, close: () => void, insert?: (run: () => void) => void): HTMLElement;
@@ -26,73 +26,85 @@ Config.prototype.video = {
26
26
  defaultHandlerSuccess: insertVideos
27
27
  };
28
28
  Icon.set('video', videoIcon);
29
- Config.prototype.controls.video = {
30
- popup: (jodit, current, close) => {
31
- const formLink = new UIForm(jodit, [
32
- new UIBlock(jodit, [
33
- new UIInput(jodit, {
34
- name: 'url',
35
- required: true,
36
- label: 'URL',
37
- placeholder: 'https://',
38
- validators: ['url']
39
- })
40
- ]),
41
- new UIBlock(jodit, [
42
- Button(jodit, '', 'Insert', 'primary').onAction(() => formLink.submit())
43
- ])
44
- ]), formCode = new UIForm(jodit, [
45
- new UIBlock(jodit, [
46
- new UITextArea(jodit, {
47
- name: 'code',
48
- required: true,
49
- label: 'Embed code'
50
- })
51
- ]),
52
- new UIBlock(jodit, [
53
- Button(jodit, '', 'Insert', 'primary').onAction(() => formCode.submit())
54
- ])
55
- ]), tabs = [], insertCode = (code) => {
56
- jodit.s.restore();
57
- jodit.s.insertHTML(prepareEmbedCode(jodit, code));
58
- close();
59
- };
29
+ /**
30
+ * The video popup's tabs: Upload, when the uploader is configured, then Link
31
+ * and Code. `insert` runs what a tab inserts, which by default goes in at the
32
+ * selection the popup was opened with.
33
+ */
34
+ export function videoTabs(jodit, close, insert) {
35
+ const run = insert !== null && insert !== void 0 ? insert : ((fn) => {
36
+ jodit.s.restore();
37
+ fn();
38
+ });
39
+ const formLink = new UIForm(jodit, [
40
+ new UIBlock(jodit, [
41
+ new UIInput(jodit, {
42
+ name: 'url',
43
+ required: true,
44
+ label: 'URL',
45
+ placeholder: 'https://',
46
+ validators: ['url']
47
+ })
48
+ ]),
49
+ new UIBlock(jodit, [
50
+ Button(jodit, '', 'Insert', 'primary').onAction(() => formLink.submit())
51
+ ])
52
+ ]), formCode = new UIForm(jodit, [
53
+ new UIBlock(jodit, [
54
+ new UITextArea(jodit, {
55
+ name: 'code',
56
+ required: true,
57
+ label: 'Embed code'
58
+ })
59
+ ]),
60
+ new UIBlock(jodit, [
61
+ Button(jodit, '', 'Insert', 'primary').onAction(() => formCode.submit())
62
+ ])
63
+ ]), tabs = [], insertCode = (code) => {
64
+ run(() => jodit.s.insertHTML(prepareEmbedCode(jodit, code)));
65
+ close();
66
+ };
67
+ if (!insert) {
60
68
  jodit.s.save();
61
- const { uploader } = jodit.o;
62
- // Base64 alone would put whole video files into the content
63
- if (uploader.showTabInFileSelector !== false &&
64
- (uploader.url || uploader.customUploadFunction)) {
65
- tabs.push(UploadTab(jodit, data => {
69
+ }
70
+ const { uploader } = jodit.o;
71
+ // Base64 alone would put whole video files into the content
72
+ if (uploader.showTabInFileSelector !== false &&
73
+ (uploader.url || uploader.customUploadFunction)) {
74
+ tabs.push(UploadTab(jodit, data => {
75
+ run(() => {
66
76
  var _a, _b;
67
- jodit.s.restore();
68
- ((_b = (_a = jodit.o.video) === null || _a === void 0 ? void 0 : _a.defaultHandlerSuccess) !== null && _b !== void 0 ? _b : insertVideos).call(jodit, data);
69
- }, { label: 'Drop file', accept: 'video/*' }));
70
- }
71
- tabs.push({
72
- icon: 'link',
73
- name: 'Link',
74
- content: formLink.container
75
- }, {
76
- icon: 'source',
77
- name: 'Code',
78
- content: formCode.container
79
- });
80
- formLink.onSubmit(data => {
81
- var _a, _b, _c, _d;
82
- insertCode(call((_b = (_a = jodit.o.video) === null || _a === void 0 ? void 0 : _a.parseUrlToVideoEmbed) !== null && _b !== void 0 ? _b : convertMediaUrlToVideoEmbed, data.url, {
83
- width: (_c = jodit.o.video) === null || _c === void 0 ? void 0 : _c.defaultWidth,
84
- height: (_d = jodit.o.video) === null || _d === void 0 ? void 0 : _d.defaultHeight
85
- }));
86
- });
87
- formCode.onSubmit(data => {
88
- insertCode(data.code);
89
- });
90
- const box = TabsWidget(jodit, tabs);
91
- // Its tabs are sized to their labels, as the file selector's are, since
92
- // "Upload" doesn't fit a third of the popup
93
- box.classList.add('jodit-file-selector');
94
- return box;
95
- },
77
+ return ((_b = (_a = jodit.o.video) === null || _a === void 0 ? void 0 : _a.defaultHandlerSuccess) !== null && _b !== void 0 ? _b : insertVideos).call(jodit, data);
78
+ });
79
+ }, { label: 'Drop file', accept: 'video/*' }));
80
+ }
81
+ tabs.push({
82
+ icon: 'link',
83
+ name: 'Link',
84
+ content: formLink.container
85
+ }, {
86
+ icon: 'source',
87
+ name: 'Code',
88
+ content: formCode.container
89
+ });
90
+ formLink.onSubmit(data => {
91
+ var _a, _b, _c, _d;
92
+ insertCode(call((_b = (_a = jodit.o.video) === null || _a === void 0 ? void 0 : _a.parseUrlToVideoEmbed) !== null && _b !== void 0 ? _b : convertMediaUrlToVideoEmbed, data.url, {
93
+ width: (_c = jodit.o.video) === null || _c === void 0 ? void 0 : _c.defaultWidth,
94
+ height: (_d = jodit.o.video) === null || _d === void 0 ? void 0 : _d.defaultHeight
95
+ }));
96
+ });
97
+ formCode.onSubmit(data => {
98
+ insertCode(data.code);
99
+ });
100
+ const box = TabsWidget(jodit, tabs);
101
+ // Its tabs are sized to their labels, as the file selector's are, since
102
+ // "Upload" doesn't fit a third of the popup
103
+ box.classList.add('jodit-file-selector');
104
+ return box;
105
+ }
106
+ Config.prototype.controls.video = {
107
+ popup: (jodit, current, close) => videoTabs(jodit, close),
96
108
  tags: ['iframe'],
97
109
  tooltip: 'Insert youtube/vimeo video'
98
110
  };
@@ -0,0 +1,31 @@
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/video-properties
8
+ */
9
+ export type EditedMedia = HTMLVideoElement | HTMLAudioElement | HTMLIFrameElement;
10
+ export interface VideoPropertiesValues {
11
+ width: number | string;
12
+ height: number | string;
13
+ poster: string;
14
+ autoplay: boolean;
15
+ loop: boolean;
16
+ muted: boolean;
17
+ controls: boolean;
18
+ }
19
+ export interface VideoPropertiesState {
20
+ /**
21
+ * What the dialog was opened for: the media, or a wrapper round it
22
+ */
23
+ target: HTMLElement;
24
+ media: EditedMedia;
25
+ ratio: number;
26
+ sizeIsLocked: boolean;
27
+ values: VideoPropertiesValues;
28
+ }
29
+ export interface VideoPropertiesAPI {
30
+ openPosterPopup: (target: HTMLElement) => void;
31
+ }
@@ -0,0 +1,6 @@
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
+ export {};