jodit 4.13.26 → 4.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/es2015/jodit.css +1 -1
  3. package/es2015/jodit.fat.min.js +19 -19
  4. package/es2015/jodit.js +1104 -884
  5. package/es2015/jodit.min.js +20 -20
  6. package/es2015/plugins/debug/debug.css +1 -1
  7. package/es2015/plugins/debug/debug.js +10 -10
  8. package/es2015/plugins/debug/debug.min.js +4 -4
  9. package/es2015/plugins/speech-recognize/speech-recognize.css +1 -1
  10. package/es2015/plugins/speech-recognize/speech-recognize.js +2 -2
  11. package/es2015/plugins/speech-recognize/speech-recognize.min.js +2 -2
  12. package/es2018/jodit.fat.min.js +19 -19
  13. package/es2018/jodit.min.js +19 -19
  14. package/es2018/plugins/debug/debug.min.js +4 -4
  15. package/es2018/plugins/speech-recognize/speech-recognize.min.js +2 -2
  16. package/es2021/jodit.css +1 -1
  17. package/es2021/jodit.fat.min.js +29 -29
  18. package/es2021/jodit.js +1097 -875
  19. package/es2021/jodit.min.js +29 -29
  20. package/es2021/plugins/debug/debug.css +1 -1
  21. package/es2021/plugins/debug/debug.js +10 -10
  22. package/es2021/plugins/debug/debug.min.js +4 -4
  23. package/es2021/plugins/speech-recognize/speech-recognize.css +1 -1
  24. package/es2021/plugins/speech-recognize/speech-recognize.js +2 -2
  25. package/es2021/plugins/speech-recognize/speech-recognize.min.js +2 -2
  26. package/es2021.en/jodit.css +1 -1
  27. package/es2021.en/jodit.fat.min.js +29 -29
  28. package/es2021.en/jodit.js +1097 -875
  29. package/es2021.en/jodit.min.js +29 -29
  30. package/es2021.en/plugins/debug/debug.css +1 -1
  31. package/es2021.en/plugins/debug/debug.js +10 -10
  32. package/es2021.en/plugins/debug/debug.min.js +4 -4
  33. package/es2021.en/plugins/speech-recognize/speech-recognize.css +1 -1
  34. package/es2021.en/plugins/speech-recognize/speech-recognize.js +2 -2
  35. package/es2021.en/plugins/speech-recognize/speech-recognize.min.js +2 -2
  36. package/es5/jodit.css +2 -2
  37. package/es5/jodit.fat.min.js +2 -2
  38. package/es5/jodit.js +1221 -925
  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 +10 -10
  43. package/es5/plugins/debug/debug.min.js +2 -2
  44. package/es5/plugins/speech-recognize/speech-recognize.css +1 -1
  45. package/es5/plugins/speech-recognize/speech-recognize.js +2 -2
  46. package/es5/plugins/speech-recognize/speech-recognize.min.js +2 -2
  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/constants.js +1 -1
  51. package/esm/core/create/create.js +2 -2
  52. package/esm/core/dom/dom.d.ts +21 -13
  53. package/esm/core/dom/dom.js +58 -87
  54. package/esm/core/global.js +3 -2
  55. package/esm/core/helpers/html/apply-styles.js +14 -10
  56. package/esm/core/helpers/html/clean-from-word.js +8 -4
  57. package/esm/core/helpers/html/safe-html.js +23 -14
  58. package/esm/core/helpers/html/strip-tags.js +9 -4
  59. package/esm/core/helpers/utils/align.d.ts +0 -3
  60. package/esm/core/helpers/utils/align.js +15 -18
  61. package/esm/core/helpers/utils/append-script.js +13 -10
  62. package/esm/core/helpers/utils/attr.d.ts +3 -1
  63. package/esm/core/helpers/utils/attr.js +18 -0
  64. package/esm/core/helpers/utils/css.d.ts +18 -1
  65. package/esm/core/helpers/utils/css.js +15 -0
  66. package/esm/core/helpers/utils/print.js +17 -14
  67. package/esm/core/helpers/utils/reset.js +3 -0
  68. package/esm/core/helpers/utils/selector.js +11 -7
  69. package/esm/core/selection/helpers/move-node-inside-start.js +1 -1
  70. package/esm/core/selection/selection.js +29 -26
  71. package/esm/core/selection/style/api/get-suit-parent.js +10 -0
  72. package/esm/core/selection/style/api/is-same-attributes.js +5 -2
  73. package/esm/core/selection/style/api/toggle-attributes.js +8 -9
  74. package/esm/core/selection/style/api/wrap-unwrapped-text.js +2 -2
  75. package/esm/core/traits/elms.js +11 -3
  76. package/esm/core/ui/button/button/button.js +3 -3
  77. package/esm/core/ui/button/group/group.js +2 -1
  78. package/esm/core/ui/button/tooltip/tooltip.js +2 -2
  79. package/esm/core/ui/element.js +1 -1
  80. package/esm/core/ui/form/inputs/area/area.js +3 -2
  81. package/esm/core/ui/form/inputs/file/file.js +2 -1
  82. package/esm/core/ui/form/inputs/input/input.js +5 -5
  83. package/esm/core/ui/group/group.js +7 -7
  84. package/esm/core/ui/icon.js +2 -2
  85. package/esm/core/ui/popup/popup.js +10 -8
  86. package/esm/core/ui/progress-bar/progress-bar.js +3 -4
  87. package/esm/core/view/view-with-toolbar.js +1 -1
  88. package/esm/core/view/view.js +1 -1
  89. package/esm/jodit.js +30 -28
  90. package/esm/modules/dialog/alert.js +1 -1
  91. package/esm/modules/dialog/confirm.js +3 -2
  92. package/esm/modules/dialog/dialog.d.ts +6 -0
  93. package/esm/modules/dialog/dialog.js +40 -22
  94. package/esm/modules/dialog/prompt.js +4 -3
  95. package/esm/modules/file-browser/builders/context-menu.js +5 -5
  96. package/esm/modules/file-browser/config.js +3 -1
  97. package/esm/modules/file-browser/file-browser.js +7 -4
  98. package/esm/modules/file-browser/listeners/state-listeners.js +6 -6
  99. package/esm/modules/image-editor/image-editor.js +31 -17
  100. package/esm/modules/messages/messages.js +2 -1
  101. package/esm/modules/status-bar/status-bar.js +3 -4
  102. package/esm/modules/table/table.js +22 -18
  103. package/esm/modules/toolbar/button/button.js +4 -4
  104. package/esm/modules/toolbar/button/content.js +12 -4
  105. package/esm/modules/toolbar/collection/collection.js +4 -2
  106. package/esm/modules/uploader/config.js +3 -2
  107. package/esm/modules/uploader/helpers/process-old-browser-drag.js +1 -1
  108. package/esm/modules/uploader/uploader.js +7 -2
  109. package/esm/modules/widget/color-picker/color-picker.js +4 -4
  110. package/esm/modules/widget/file-selector/file-selector.js +6 -6
  111. package/esm/modules/widget/tabs/tabs.js +5 -5
  112. package/esm/plugins/add-new-line/add-new-line.js +8 -7
  113. package/esm/plugins/ai-assistant/ui/ui-ai-assistant.js +4 -4
  114. package/esm/plugins/backspace/cases/check-join-neighbors.js +4 -1
  115. package/esm/plugins/clean-html/helpers/visitor/filters/allow-attributes.js +5 -1
  116. package/esm/plugins/clean-html/helpers/visitor/filters/fill-empty-paragraph.js +1 -1
  117. package/esm/plugins/color/config.js +4 -4
  118. package/esm/plugins/copy-format/copy-format.js +1 -1
  119. package/esm/plugins/delete/delete.js +9 -2
  120. package/esm/plugins/drag-and-drop/drag-and-drop.js +1 -1
  121. package/esm/plugins/drag-and-drop-element/drag-and-drop-element.js +1 -1
  122. package/esm/plugins/enter/helpers/insert-paragraph.js +1 -1
  123. package/esm/plugins/enter/helpers/process-empty-li-leaf.js +2 -2
  124. package/esm/plugins/enter/helpers/wrap-text.js +1 -1
  125. package/esm/plugins/fullsize/fullsize.js +3 -2
  126. package/esm/plugins/iframe/iframe.js +6 -5
  127. package/esm/plugins/image-properties/readers/align.js +6 -5
  128. package/esm/plugins/image-properties/readers/index.js +3 -1
  129. package/esm/plugins/image-properties/ui/ui-image-form.js +2 -1
  130. package/esm/plugins/image-properties/writers/index.js +2 -1
  131. package/esm/plugins/image-properties/writers/link.js +1 -1
  132. package/esm/plugins/indent/config.js +3 -2
  133. package/esm/plugins/indent/indent.js +4 -2
  134. package/esm/plugins/media/media.js +10 -7
  135. package/esm/plugins/mobile/config.js +2 -2
  136. package/esm/plugins/paste-storage/paste-storage.js +5 -5
  137. package/esm/plugins/placeholder/placeholder.js +13 -11
  138. package/esm/plugins/print/print.js +2 -2
  139. package/esm/plugins/resize-cells/resize-cells.js +13 -15
  140. package/esm/plugins/resize-handler/resize-handler.js +3 -4
  141. package/esm/plugins/resizer/resizer.js +11 -10
  142. package/esm/plugins/search/helpers/highlight-text-ranges.js +1 -1
  143. package/esm/plugins/search/ui/search.js +1 -1
  144. package/esm/plugins/select/select.js +2 -2
  145. package/esm/plugins/select-cells/select-cells.js +5 -6
  146. package/esm/plugins/source/editor/engines/ace.js +2 -1
  147. package/esm/plugins/source/editor/engines/area.js +3 -3
  148. package/esm/plugins/source/source.js +2 -2
  149. package/esm/plugins/speech-recognize/speech-recognize.js +1 -1
  150. package/esm/plugins/sticky/sticky.js +1 -1
  151. package/esm/plugins/symbols/config.js +3 -2
  152. package/esm/plugins/symbols/symbols.js +6 -5
  153. package/esm/plugins/tab/cases/on-tab-inside-li.js +2 -2
  154. package/esm/plugins/table/config.js +18 -10
  155. package/esm/plugins/table-keyboard-navigation/table-keyboard-navigation.js +2 -2
  156. package/esm/plugins/wrap-nodes/wrap-nodes.js +2 -2
  157. package/esm/plugins/xpath/xpath.js +2 -3
  158. package/esm/types/index.d.ts +1 -0
  159. package/esm/types/vdom.d.ts +96 -0
  160. package/package.json +1 -1
  161. package/types/core/dom/dom.d.ts +21 -13
  162. package/types/core/helpers/utils/align.d.ts +0 -3
  163. package/types/core/helpers/utils/attr.d.ts +3 -1
  164. package/types/core/helpers/utils/css.d.ts +18 -1
  165. package/types/modules/dialog/dialog.d.ts +6 -0
  166. package/types/types/index.d.ts +1 -0
  167. package/types/types/vdom.d.ts +96 -0
@@ -7,6 +7,7 @@ import { isJoditObject } from "./helpers/checker/is-jodit-object.js";
7
7
  import { isString } from "./helpers/checker/is-string.js";
8
8
  import { isViewObject } from "./helpers/checker/is-view-object.js";
9
9
  import { kebabCase } from "./helpers/string/kebab-case.js";
10
+ import { attr } from "./helpers/utils/attr.js";
10
11
  import { css } from "./helpers/utils/css.js";
11
12
  import { getClassName } from "./helpers/utils/get-class-name.js";
12
13
  import { PluginSystem } from "./plugin/plugin-system.js";
@@ -76,10 +77,10 @@ export function getContainer(jodit, classFunc, tag = 'div', createInsideEditor =
76
77
  });
77
78
  // Let a strict CSP accept the injected <style>/<script> box
78
79
  if (view.o.nonce && (tag === 'style' || tag === 'script')) {
79
- box.setAttribute('nonce', view.o.nonce);
80
+ attr(box, 'nonce', view.o.nonce);
80
81
  }
81
82
  box.classList.add(`jodit_theme_${view.o.theme || 'default'}`);
82
- body.appendChild(box);
83
+ Dom.append(body, box);
83
84
  data[key] = box;
84
85
  jodit.hookStatus('beforeDestruct', () => {
85
86
  view.events.off(box);
@@ -9,6 +9,8 @@
9
9
  import { globalDocument, IS_PROD } from "../../constants.js";
10
10
  import { Dom } from "../../dom/dom.js";
11
11
  import { trim } from "../string/trim.js";
12
+ import { attr } from "../utils/attr.js";
13
+ import { css } from "../utils/css.js";
12
14
  import { $$ } from "../utils/selector.js";
13
15
  function normalizeCSS(s) {
14
16
  return s
@@ -48,8 +50,8 @@ export function applyStyles(html) {
48
50
  html = html.substring(0, closeIndex + '</html>'.length);
49
51
  }
50
52
  const iframe = globalDocument.createElement('iframe');
51
- iframe.style.display = 'none';
52
- globalDocument.body.appendChild(iframe);
53
+ css(iframe, 'display', 'none');
54
+ Dom.append(globalDocument.body, iframe);
53
55
  let convertedString = '', collection = [];
54
56
  try {
55
57
  const iframeDoc = iframe.contentDocument ||
@@ -65,7 +67,7 @@ export function applyStyles(html) {
65
67
  // style normalization strips the `mso-list` hint. See #948
66
68
  Dom.each(iframeDoc.body, (node) => {
67
69
  if (Dom.isElement(node) &&
68
- /mso-list:\s*ignore/i.test(node.getAttribute('style') || '')) {
70
+ /mso-list:\s*ignore/i.test(attr(node, 'style') || '')) {
69
71
  Dom.safeRemove(node);
70
72
  }
71
73
  });
@@ -77,6 +79,9 @@ export function applyStyles(html) {
77
79
  continue;
78
80
  }
79
81
  collection = $$(rules[idx].selectorText, iframeDoc.body);
82
+ // `style.cssText` on purpose: the CSSOM parses the text,
83
+ // drops invalid declarations and re-serializes it. The raw
84
+ // `style` attribute would keep Word-only junk verbatim
80
85
  collection.forEach((elm) => {
81
86
  elm.style.cssText = normalizeCSS(rules[idx].style.cssText +
82
87
  ';' +
@@ -92,14 +97,13 @@ export function applyStyles(html) {
92
97
  }
93
98
  Dom.each(iframeDoc.body, node => {
94
99
  if (Dom.isElement(node)) {
95
- const elm = node;
96
- const css = elm.getAttribute('style');
97
- if (css) {
98
- elm.style.cssText = normalizeCSS(css);
100
+ const cssText = attr(node, 'style');
101
+ if (cssText) {
102
+ node.style.cssText =
103
+ normalizeCSS(cssText);
99
104
  }
100
- if (elm.hasAttribute('style') &&
101
- !elm.getAttribute('style')) {
102
- elm.removeAttribute('style');
105
+ if (!attr(node, 'style')) {
106
+ attr(node, 'style', null);
103
107
  }
104
108
  }
105
109
  });
@@ -10,6 +10,7 @@ import { globalDocument } from "../../constants.js";
10
10
  import { Dom } from "../../dom/dom.js";
11
11
  import { toArray } from "../array/to-array.js";
12
12
  import { trim } from "../string/trim.js";
13
+ import { attr, attrRaw } from "../utils/attr.js";
13
14
  /**
14
15
  * The method automatically cleans up content from Microsoft Word and other HTML sources to ensure clean, compliant
15
16
  * content that matches the look and feel of the site.
@@ -48,18 +49,21 @@ export function cleanFromWord(html) {
48
49
  // with `mso-list:Ignore`. They are display-only
49
50
  // and must not be imported, otherwise the marker
50
51
  // text leaks into the content. See #948
51
- if (/mso-list:\s*ignore/i.test(node.getAttribute('style') || '')) {
52
+ if (/mso-list:\s*ignore/i.test(attr(node, 'style') || '')) {
52
53
  marks.push(node);
53
54
  break;
54
55
  }
55
- toArray(node.attributes).forEach((attr) => {
56
+ toArray(node.attributes).forEach((attribute) => {
56
57
  if ([
57
58
  'src',
58
59
  'href',
59
60
  'rel',
60
61
  'content'
61
- ].indexOf(attr.name.toLowerCase()) === -1) {
62
- node.removeAttribute(attr.name);
62
+ ].indexOf(attribute.name.toLowerCase()) === -1) {
63
+ // `attrRaw`: SVG keeps camelCase names
64
+ // (`viewBox`), `attr()` would kebab-case
65
+ // them and silently skip the removal
66
+ attrRaw(node, attribute.name, null);
63
67
  }
64
68
  });
65
69
  }
@@ -3,11 +3,9 @@
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
- /**
7
- * @module helpers/html
8
- */
9
6
  import { Dom } from "../../dom/dom.js";
10
7
  import { attr } from "../utils/index.js";
8
+ import { attrRaw } from "../utils/attr.js";
11
9
  const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
12
10
  /**
13
11
  * Integration points where HTML is legitimately allowed inside MathML/SVG foreign content.
@@ -23,9 +21,10 @@ const HTML_INTEGRATION_POINTS = new Set([
23
21
  * HTML (e.g. `mglyph` / `style` under `<math>`) that a reparse can hoist into a live node. Legitimate
24
22
  * MathML/SVG children and HTML below an integration point are kept.
25
23
  */
24
+ const isMathOrSvg = (node) => Boolean(node && /^(math|svg)$/i.test(node.nodeName));
26
25
  function isSmuggledForeignHtml(elm) {
27
26
  if (elm.namespaceURI !== HTML_NAMESPACE ||
28
- elm.closest('math,svg') == null) {
27
+ Dom.up(elm, isMathOrSvg) == null) {
29
28
  return false;
30
29
  }
31
30
  for (let parent = elm.parentElement; parent; parent = parent.parentElement) {
@@ -48,11 +47,17 @@ export function safeHTML(box, options) {
48
47
  return;
49
48
  }
50
49
  // Drop HTML smuggled into MathML/SVG before the walk: a reparse would otherwise hoist its hidden
51
- // markup into a live node.
52
- const foreign = box.querySelectorAll('math *, svg *');
53
- for (let i = 0; i < foreign.length; i++) {
54
- if (box.contains(foreign[i]) && isSmuggledForeignHtml(foreign[i])) {
55
- Dom.safeRemove(foreign[i]);
50
+ // markup into a live node. Collect the candidates first (like the static
51
+ // `querySelectorAll` snapshot did) so removals don't affect the traversal.
52
+ const foreign = [];
53
+ Dom.each(box, node => {
54
+ if (Dom.isElement(node) && Dom.up(node.parentNode, isMathOrSvg)) {
55
+ foreign.push(node);
56
+ }
57
+ });
58
+ for (const elm of foreign) {
59
+ if (Dom.isOrContains(box, elm) && isSmuggledForeignHtml(elm)) {
60
+ Dom.safeRemove(elm);
56
61
  }
57
62
  }
58
63
  const removeEvents = (_a = options.removeEventAttributes) !== null && _a !== void 0 ? _a : options.removeOnError;
@@ -70,8 +75,8 @@ export function safeHTML(box, options) {
70
75
  sanitizeHTMLElement(node, options);
71
76
  if (options.safeLinksTarget &&
72
77
  node.nodeName === 'A' &&
73
- node.getAttribute('target') === '_blank') {
74
- const rel = node.getAttribute('rel') || '';
78
+ attr(node, 'target') === '_blank') {
79
+ const rel = attr(node, 'rel') || '';
75
80
  const parts = rel.split(/\s+/).filter(Boolean);
76
81
  if (!parts.includes('noopener')) {
77
82
  parts.push('noopener');
@@ -100,7 +105,9 @@ function removeAllEventAttributes(elm) {
100
105
  }
101
106
  }
102
107
  for (const name of toRemove) {
103
- elm.removeAttribute(name);
108
+ // `attrRaw` on purpose: `attr()` kebab-cases the key
109
+ // (`onLoad` → `on-load`), which must not happen in a sanitizer
110
+ attrRaw(elm, name, null);
104
111
  effected = true;
105
112
  }
106
113
  return effected;
@@ -151,7 +158,7 @@ export function sanitizeHTMLElement(elm, { safeJavaScriptLink, removeOnError } =
151
158
  effected = true;
152
159
  }
153
160
  const tagName = elm.nodeName.toLowerCase();
154
- const href = elm.getAttribute('href');
161
+ const href = attr(elm, 'href');
155
162
  // Neutralize executable-scheme `href`s with the same normalization used for
156
163
  // every other URL attribute (`isDangerousUrl`), which strips control bytes,
157
164
  // tabs and newlines and lowercases before matching the scheme. The previous
@@ -171,8 +178,10 @@ export function sanitizeHTMLElement(elm, { safeJavaScriptLink, removeOnError } =
171
178
  effected = true;
172
179
  }
173
180
  // Strip executable schemes from any other URL-bearing attribute.
181
+ // `attrRaw`: the list contains `xlink:href` and the sanitizer must
182
+ // read exactly the attribute it will remove.
174
183
  for (const name of URL_ATTRIBUTES) {
175
- const value = elm.getAttribute(name);
184
+ const value = attrRaw(elm, name);
176
185
  if (value && isDangerousUrl(value, tagName)) {
177
186
  attr(elm, name, null);
178
187
  effected = true;
@@ -6,7 +6,6 @@
6
6
  import { Dom } from "../../dom/dom.js";
7
7
  import { isString } from "../checker/is-string.js";
8
8
  import { trim } from "../string/trim.js";
9
- import { $$ } from "../utils/index.js";
10
9
  const NEW_LINE_TAGS = new Set([
11
10
  'div',
12
11
  'p',
@@ -30,9 +29,15 @@ export function stripTags(html, doc = document, exclude = null, blockBr = false)
30
29
  tmp.innerHTML = html;
31
30
  }
32
31
  else {
33
- tmp.appendChild(html);
32
+ Dom.append(tmp, html);
34
33
  }
35
- $$('*', tmp).forEach(p => {
34
+ // Snapshot of all elements (like `querySelectorAll('*')` did) the loop
35
+ // below removes and unwraps nodes while iterating
36
+ const all = [];
37
+ Dom.each(tmp, node => {
38
+ Dom.isElement(node) && all.push(node);
39
+ });
40
+ all.forEach(p => {
36
41
  const pr = p.parentNode;
37
42
  if (!pr) {
38
43
  return;
@@ -62,7 +67,7 @@ export function stripTags(html, doc = document, exclude = null, blockBr = false)
62
67
  // plain text). When `blockBr` is set, separate them with a line
63
68
  // break instead, so paragraph structure survives — e.g. the
64
69
  // "Insert only Text" paste option. See #1232
65
- pr.insertBefore(doc.createTextNode(blockBr ? '%%%jodit-single-br%%%' : ' '), nx);
70
+ Dom.before(nx, doc.createTextNode(blockBr ? '%%%jodit-single-br%%%' : ' '));
66
71
  }
67
72
  });
68
73
  return restoreTags(trim(tmp.innerText));
@@ -15,7 +15,4 @@ export declare function hAlignElement(image: HTMLElement, align: ImageHAlign): v
15
15
  * Remove text-align style for all selected children
16
16
  */
17
17
  export declare function clearAlign(node: Node): void;
18
- /**
19
- * Apply align for element
20
- */
21
18
  export declare function alignElement(command: string, box: HTMLElement): void;
@@ -4,7 +4,8 @@
4
4
  * Copyright (c) 2013-2026 Valerii Chupurnov. All rights reserved. https://xdsoft.net
5
5
  */
6
6
  import { Dom } from "../../dom/index.js";
7
- import { clearCenterAlign, css } from "./css.js";
7
+ import { attr } from "./attr.js";
8
+ import { clearCenterAlign, css, cssInline } from "./css.js";
8
9
  /**
9
10
  * Align image
10
11
  */
@@ -37,10 +38,10 @@ export function hAlignElement(image, align) {
37
38
  export function clearAlign(node) {
38
39
  Dom.each(node, elm => {
39
40
  if (Dom.isHTMLElement(elm)) {
40
- if (elm.style.textAlign) {
41
- elm.style.textAlign = '';
42
- if (!elm.style.cssText.trim().length) {
43
- elm.removeAttribute('style');
41
+ if (cssInline(elm, 'textAlign')) {
42
+ css(elm, 'textAlign', '');
43
+ if (!(attr(elm, 'style') || '').trim().length) {
44
+ attr(elm, 'style', null);
44
45
  }
45
46
  }
46
47
  }
@@ -49,22 +50,18 @@ export function clearAlign(node) {
49
50
  /**
50
51
  * Apply align for element
51
52
  */
53
+ const ALIGN_BY_COMMAND = new Map([
54
+ ['justifyfull', 'justify'],
55
+ ['justifyright', 'right'],
56
+ ['justifyleft', 'left'],
57
+ ['justifycenter', 'center']
58
+ ]);
52
59
  export function alignElement(command, box) {
53
60
  if (Dom.isNode(box) && Dom.isElement(box)) {
54
61
  clearAlign(box);
55
- switch (command.toLowerCase()) {
56
- case 'justifyfull':
57
- box.style.textAlign = 'justify';
58
- break;
59
- case 'justifyright':
60
- box.style.textAlign = 'right';
61
- break;
62
- case 'justifyleft':
63
- box.style.textAlign = 'left';
64
- break;
65
- case 'justifycenter':
66
- box.style.textAlign = 'center';
67
- break;
62
+ const align = ALIGN_BY_COMMAND.get(command.toLowerCase());
63
+ if (align) {
64
+ css(box, 'textAlign', align);
68
65
  }
69
66
  }
70
67
  }
@@ -3,7 +3,9 @@
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 { Dom } from "../../dom/dom.js";
6
7
  import { isString } from "../checker/is-string.js";
8
+ import { attr } from "./attr.js";
7
9
  import { completeUrl } from "./complete-url.js";
8
10
  export const alreadyLoadedList = new Map();
9
11
  const cacheLoaders = (loader) => {
@@ -32,10 +34,10 @@ export const appendScriptAsync = cacheLoaders((jodit, url) => {
32
34
  src: completeUrl(url)
33
35
  });
34
36
  if (jodit.o.nonce) {
35
- script.setAttribute('nonce', jodit.o.nonce);
37
+ attr(script, 'nonce', jodit.o.nonce);
36
38
  }
37
39
  jodit.e.one(script, 'error', reject).one(script, 'load', resolve);
38
- jodit.od.body.appendChild(script);
40
+ Dom.append(jodit.od.body, script);
39
41
  });
40
42
  });
41
43
  /**
@@ -46,22 +48,23 @@ export const appendStyleAsync = cacheLoaders((jodit, url) => {
46
48
  if (jodit.isInDestruct) {
47
49
  return reject();
48
50
  }
49
- const link = jodit.c.element('link');
50
- link.rel = 'stylesheet';
51
- link.media = 'all';
52
- link.crossOrigin = 'anonymous';
51
+ const link = jodit.c.element('link', {
52
+ rel: 'stylesheet',
53
+ media: 'all',
54
+ crossorigin: 'anonymous'
55
+ });
53
56
  if (jodit.o.nonce) {
54
- link.setAttribute('nonce', jodit.o.nonce);
57
+ attr(link, 'nonce', jodit.o.nonce);
55
58
  }
56
59
  const callback = () => resolve(link);
57
60
  !jodit.isInDestruct &&
58
61
  jodit.e.on(link, 'load', callback).on(link, 'error', reject);
59
- link.href = completeUrl(url);
62
+ attr(link, 'href', completeUrl(url));
60
63
  if (jodit.o.shadowRoot) {
61
- jodit.o.shadowRoot.appendChild(link);
64
+ Dom.append(jodit.o.shadowRoot, link);
62
65
  }
63
66
  else {
64
- jodit.od.body.appendChild(link);
67
+ Dom.append(jodit.od.body, link);
65
68
  }
66
69
  });
67
70
  });
@@ -6,7 +6,7 @@
6
6
  /**
7
7
  * @module helpers/utils
8
8
  */
9
- import type { IDictionary } from "../../../types/index";
9
+ import type { IDictionary, VElement } from "../../../types/index";
10
10
  /**
11
11
  * Get attribute
12
12
  */
@@ -23,3 +23,5 @@ export declare function attr(elm: Element, key: string, value: string | number |
23
23
  * Set or remove several attributes
24
24
  */
25
25
  export declare function attr(elm: Element, attributes: IDictionary<string | number | boolean | null>): null;
26
+ export declare function attrRaw(elm: VElement, name: string): null | string;
27
+ export declare function attrRaw(elm: VElement, name: string, value: string | null): void;
@@ -56,3 +56,21 @@ export function attr(elm, keyOrAttributes, value) {
56
56
  }
57
57
  return elm.getAttribute(key);
58
58
  }
59
+ /**
60
+ * Exact-name attribute access: no camelCase → kebab-case conversion, no
61
+ * `data-` fallback and no `px` stripping. Use it where the attribute name must
62
+ * be taken verbatim — sanitizers and attribute comparison (`onLoad`, `viewBox`,
63
+ * `xlink:href`). `null` removes the attribute.
64
+ */
65
+ export function attrRaw(elm, name, value) {
66
+ if (value === undefined) {
67
+ return elm.getAttribute(name);
68
+ }
69
+ if (value == null) {
70
+ if (elm.hasAttribute(name)) {
71
+ elm.removeAttribute(name);
72
+ }
73
+ return;
74
+ }
75
+ elm.setAttribute(name, value);
76
+ }
@@ -6,13 +6,30 @@
6
6
  /**
7
7
  * @module helpers/utils
8
8
  */
9
- import type { IStyle, StyleValue } from "../../../types/index";
9
+ import type { IStyle, StyleValue, VHTMLElement } from "../../../types/index";
10
10
  export declare function css(element: HTMLElement, key: 'left' | 'top' | 'bottom' | 'right' | 'width' | 'min' | 'max' | 'height' | 'margin' | 'padding' | 'fontsize' | 'font-size'): number;
11
11
  export declare function css(element: HTMLElement, key: keyof CSSStyleDeclaration): string | number;
12
12
  export declare function css(element: HTMLElement, key: string | IStyle): string | number;
13
13
  export declare function css(element: HTMLElement, key: string | IStyle, value: StyleValue): string | number;
14
+ /**
15
+ * @deprecated Use `cssInline(element, key)` for an exact inline read.
16
+ * The boolean mode still normalizes the value (`'0px'` → `0`, `bold` → `700`).
17
+ */
14
18
  export declare function css(element: HTMLElement, key: string | IStyle, onlyStyleMode: boolean): string | number;
15
19
  /**
16
20
  * Clear center align
17
21
  */
22
+ /**
23
+ * Read the exact inline value of a CSS property (the `style` attribute).
24
+ * Unlike `css()` it never touches computed styles (no layout access) and does
25
+ * not normalize the result, so it is safe for exact string comparisons and
26
+ * save/restore. Returns an empty string when the property is not set inline.
27
+ *
28
+ * @example
29
+ * ```js
30
+ * cssInline(elm, 'zIndex'); // '' or '100'
31
+ * cssInline(elm, 'width'); // '0px' (css(elm, 'width', true) would return 0)
32
+ * ```
33
+ */
34
+ export declare function cssInline(element: VHTMLElement, key: string): string;
18
35
  export declare const clearCenterAlign: (image: HTMLElement) => void;
@@ -62,6 +62,21 @@ export function css(element, key, value, onlyStyleMode = false) {
62
62
  /**
63
63
  * Clear center align
64
64
  */
65
+ /**
66
+ * Read the exact inline value of a CSS property (the `style` attribute).
67
+ * Unlike `css()` it never touches computed styles (no layout access) and does
68
+ * not normalize the result, so it is safe for exact string comparisons and
69
+ * save/restore. Returns an empty string when the property is not set inline.
70
+ *
71
+ * @example
72
+ * ```js
73
+ * cssInline(elm, 'zIndex'); // '' or '100'
74
+ * cssInline(elm, 'width'); // '0px' (css(elm, 'width', true) would return 0)
75
+ * ```
76
+ */
77
+ export function cssInline(element, key) {
78
+ return element.style.getPropertyValue(kebabCase(key));
79
+ }
65
80
  export const clearCenterAlign = (image) => {
66
81
  if (css(image, 'display') === 'block') {
67
82
  css(image, 'display', '');
@@ -7,32 +7,35 @@ import { Dom } from "../../dom/dom.js";
7
7
  import { isString } from "../checker/is-string.js";
8
8
  import { attr } from "./attr.js";
9
9
  import { css } from "./css.js";
10
- import { $$ } from "./selector.js";
11
10
  /**
12
11
  * Fixes image sizes and sets absolute paths to images
13
12
  */
14
13
  function fixedAssetsSizeAndAbsoluteLinks(editor, points) {
15
14
  const restoreAttributes = [];
15
+ const images = [];
16
+ Dom.each(editor.editor, node => {
17
+ Dom.isTag(node, 'img') && images.push(node);
18
+ });
16
19
  try {
17
- $$('img', editor.editor).forEach(item => {
20
+ images.forEach(item => {
18
21
  const previousAttrs = [
19
22
  attr(item, 'width'),
20
23
  attr(item, 'height'),
21
- item.src
24
+ attr(item, 'src')
22
25
  ];
23
26
  attr(item, {
24
27
  width: item.offsetWidth + points,
25
28
  height: item.offsetHeight + points
26
29
  });
27
30
  const a = editor.createInside.a();
28
- editor.ed.body.appendChild(a);
29
- a.href = item.src;
30
- item.src = a.href;
31
+ Dom.append(editor.ed.body, a);
32
+ attr(a, 'href', attr(item, 'src') || '');
33
+ // reading the `href` property resolves the URL to an absolute one
34
+ attr(item, 'src', a.href);
31
35
  Dom.safeRemove(a);
32
36
  restoreAttributes.push(() => {
33
- var _a;
34
- item.src = (_a = previousAttrs[2]) !== null && _a !== void 0 ? _a : '';
35
37
  attr(item, {
38
+ src: previousAttrs[2],
36
39
  width: previousAttrs[0] || null,
37
40
  height: previousAttrs[1] || null
38
41
  });
@@ -62,7 +65,7 @@ export function previewBox(editor, defaultValue, points = 'px', container = null
62
65
  }
63
66
  let div = editor.c.div('jodit__preview-box jodit-context');
64
67
  if (container) {
65
- container.appendChild(div);
68
+ Dom.append(container, div);
66
69
  }
67
70
  css(div, {
68
71
  position: 'relative',
@@ -77,7 +80,7 @@ export function previewBox(editor, defaultValue, points = 'px', container = null
77
80
  minHeight: 600,
78
81
  border: 0
79
82
  });
80
- div.appendChild(iframe);
83
+ Dom.append(div, iframe);
81
84
  const myWindow = iframe.contentWindow;
82
85
  if (myWindow) {
83
86
  editor.e.fire('generateDocumentStructure.iframe', myWindow.document, editor);
@@ -87,7 +90,7 @@ export function previewBox(editor, defaultValue, points = 'px', container = null
87
90
  const elm = myWindow.document.body;
88
91
  const resizeObserver = new ResizeObserver(editor.async.debounce(() => {
89
92
  resizeObserver.unobserve(elm);
90
- iframe.style.height = `${elm.offsetHeight + 20}px`;
93
+ css(iframe, 'height', elm.offsetHeight + 20);
91
94
  editor.async.requestAnimationFrame(() => {
92
95
  !destructed && resizeObserver.observe(elm);
93
96
  });
@@ -120,7 +123,7 @@ export function previewBox(editor, defaultValue, points = 'px', container = null
120
123
  if (Dom.isElement(c)) {
121
124
  const newNode = box.ownerDocument.createElement(c.nodeName);
122
125
  for (let j = 0; j < c.attributes.length; j += 1) {
123
- attr(newNode, c.attributes[j].nodeName, c.attributes[j].nodeValue);
126
+ attr(newNode, c.attributes[j].name, c.attributes[j].value);
124
127
  }
125
128
  if (c.childNodes.length === 0 || Dom.isTag(c, 'table')) {
126
129
  switch (c.nodeName) {
@@ -140,13 +143,13 @@ export function previewBox(editor, defaultValue, points = 'px', container = null
140
143
  setHTML(newNode, c);
141
144
  }
142
145
  try {
143
- box.appendChild(newNode);
146
+ Dom.append(box, newNode);
144
147
  }
145
148
  catch (_a) { }
146
149
  }
147
150
  else {
148
151
  try {
149
- box.appendChild(c.cloneNode(true));
152
+ Dom.append(box, c.cloneNode(true));
150
153
  }
151
154
  catch (_b) { }
152
155
  }
@@ -18,6 +18,9 @@ const map = {};
18
18
  export function reset(key) {
19
19
  var _a, _b;
20
20
  if (!(key in map)) {
21
+ // Raw DOM API on purpose: this bootstrap helper is imported by
22
+ // `to-array`, which the `Dom` module itself depends on — importing
23
+ // `Dom` here would create a circular dependency
21
24
  const iframe = globalDocument.createElement('iframe');
22
25
  try {
23
26
  iframe.src = 'about:blank';
@@ -66,14 +66,18 @@ export const refs = (root) => {
66
66
  if ('container' in root) {
67
67
  root = root.container;
68
68
  }
69
- return $$('[ref],[data-ref]', root).reduce((def, child) => {
70
- const key = attr(child, '-ref');
71
- if (key && isString(key)) {
72
- def[camelCase(key)] = child;
73
- def[key] = child;
69
+ const def = {};
70
+ Dom.each(root, child => {
71
+ if (Dom.isElement(child) &&
72
+ (child.hasAttribute('ref') || child.hasAttribute('data-ref'))) {
73
+ const key = attr(child, '-ref');
74
+ if (key && isString(key)) {
75
+ def[camelCase(key)] = child;
76
+ def[key] = child;
77
+ }
74
78
  }
75
- return def;
76
- }, {});
79
+ });
80
+ return def;
77
81
  };
78
82
  /**
79
83
  * Calculate full CSS selector
@@ -17,7 +17,7 @@ export function moveNodeInsideStart(j, node, start) {
17
17
  Dom.isContentEditable(sibling, j.editor) &&
18
18
  (!anotherSibling || !Dom.closest(node, Dom.isElement, j.editor))) {
19
19
  if (start || !sibling.firstChild) {
20
- sibling.appendChild(node);
20
+ Dom.append(sibling, node);
21
21
  }
22
22
  else {
23
23
  Dom.before(sibling.firstChild, node);