roosterjs-editor-adapter 0.26.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 (102) hide show
  1. package/LICENSE +25 -0
  2. package/README.md +262 -0
  3. package/lib/corePlugins/BridgePlugin.d.ts +73 -0
  4. package/lib/corePlugins/BridgePlugin.js +124 -0
  5. package/lib/corePlugins/BridgePlugin.js.map +1 -0
  6. package/lib/corePlugins/EditPlugin.d.ts +6 -0
  7. package/lib/corePlugins/EditPlugin.js +89 -0
  8. package/lib/corePlugins/EditPlugin.js.map +1 -0
  9. package/lib/editor/DarkColorHandlerImpl.d.ts +6 -0
  10. package/lib/editor/DarkColorHandlerImpl.js +134 -0
  11. package/lib/editor/DarkColorHandlerImpl.js.map +1 -0
  12. package/lib/editor/EditorAdapter.d.ts +341 -0
  13. package/lib/editor/EditorAdapter.js +843 -0
  14. package/lib/editor/EditorAdapter.js.map +1 -0
  15. package/lib/editor/utils/buildRangeEx.d.ts +5 -0
  16. package/lib/editor/utils/buildRangeEx.js +81 -0
  17. package/lib/editor/utils/buildRangeEx.js.map +1 -0
  18. package/lib/editor/utils/eventConverter.d.ts +15 -0
  19. package/lib/editor/utils/eventConverter.js +463 -0
  20. package/lib/editor/utils/eventConverter.js.map +1 -0
  21. package/lib/editor/utils/insertNode.d.ts +7 -0
  22. package/lib/editor/utils/insertNode.js +147 -0
  23. package/lib/editor/utils/insertNode.js.map +1 -0
  24. package/lib/editor/utils/selectionConverter.d.ts +10 -0
  25. package/lib/editor/utils/selectionConverter.js +79 -0
  26. package/lib/editor/utils/selectionConverter.js.map +1 -0
  27. package/lib/index.d.ts +3 -0
  28. package/lib/index.js +6 -0
  29. package/lib/index.js.map +1 -0
  30. package/lib/publicTypes/BeforePasteAdapterEvent.d.ts +15 -0
  31. package/lib/publicTypes/BeforePasteAdapterEvent.js +3 -0
  32. package/lib/publicTypes/BeforePasteAdapterEvent.js.map +1 -0
  33. package/lib/publicTypes/EditorAdapterOptions.d.ts +20 -0
  34. package/lib/publicTypes/EditorAdapterOptions.js +3 -0
  35. package/lib/publicTypes/EditorAdapterOptions.js.map +1 -0
  36. package/lib-amd/corePlugins/BridgePlugin.d.ts +73 -0
  37. package/lib-amd/corePlugins/BridgePlugin.js +122 -0
  38. package/lib-amd/corePlugins/BridgePlugin.js.map +1 -0
  39. package/lib-amd/corePlugins/EditPlugin.d.ts +6 -0
  40. package/lib-amd/corePlugins/EditPlugin.js +90 -0
  41. package/lib-amd/corePlugins/EditPlugin.js.map +1 -0
  42. package/lib-amd/editor/DarkColorHandlerImpl.d.ts +6 -0
  43. package/lib-amd/editor/DarkColorHandlerImpl.js +135 -0
  44. package/lib-amd/editor/DarkColorHandlerImpl.js.map +1 -0
  45. package/lib-amd/editor/EditorAdapter.d.ts +341 -0
  46. package/lib-amd/editor/EditorAdapter.js +836 -0
  47. package/lib-amd/editor/EditorAdapter.js.map +1 -0
  48. package/lib-amd/editor/utils/buildRangeEx.d.ts +5 -0
  49. package/lib-amd/editor/utils/buildRangeEx.js +82 -0
  50. package/lib-amd/editor/utils/buildRangeEx.js.map +1 -0
  51. package/lib-amd/editor/utils/eventConverter.d.ts +15 -0
  52. package/lib-amd/editor/utils/eventConverter.js +463 -0
  53. package/lib-amd/editor/utils/eventConverter.js.map +1 -0
  54. package/lib-amd/editor/utils/insertNode.d.ts +7 -0
  55. package/lib-amd/editor/utils/insertNode.js +148 -0
  56. package/lib-amd/editor/utils/insertNode.js.map +1 -0
  57. package/lib-amd/editor/utils/selectionConverter.d.ts +10 -0
  58. package/lib-amd/editor/utils/selectionConverter.js +79 -0
  59. package/lib-amd/editor/utils/selectionConverter.js.map +1 -0
  60. package/lib-amd/index.d.ts +3 -0
  61. package/lib-amd/index.js +7 -0
  62. package/lib-amd/index.js.map +1 -0
  63. package/lib-amd/publicTypes/BeforePasteAdapterEvent.d.ts +15 -0
  64. package/lib-amd/publicTypes/BeforePasteAdapterEvent.js +5 -0
  65. package/lib-amd/publicTypes/BeforePasteAdapterEvent.js.map +1 -0
  66. package/lib-amd/publicTypes/EditorAdapterOptions.d.ts +20 -0
  67. package/lib-amd/publicTypes/EditorAdapterOptions.js +5 -0
  68. package/lib-amd/publicTypes/EditorAdapterOptions.js.map +1 -0
  69. package/lib-mjs/corePlugins/BridgePlugin.d.ts +73 -0
  70. package/lib-mjs/corePlugins/BridgePlugin.js +120 -0
  71. package/lib-mjs/corePlugins/BridgePlugin.js.map +1 -0
  72. package/lib-mjs/corePlugins/EditPlugin.d.ts +6 -0
  73. package/lib-mjs/corePlugins/EditPlugin.js +85 -0
  74. package/lib-mjs/corePlugins/EditPlugin.js.map +1 -0
  75. package/lib-mjs/editor/DarkColorHandlerImpl.d.ts +6 -0
  76. package/lib-mjs/editor/DarkColorHandlerImpl.js +130 -0
  77. package/lib-mjs/editor/DarkColorHandlerImpl.js.map +1 -0
  78. package/lib-mjs/editor/EditorAdapter.d.ts +341 -0
  79. package/lib-mjs/editor/EditorAdapter.js +840 -0
  80. package/lib-mjs/editor/EditorAdapter.js.map +1 -0
  81. package/lib-mjs/editor/utils/buildRangeEx.d.ts +5 -0
  82. package/lib-mjs/editor/utils/buildRangeEx.js +77 -0
  83. package/lib-mjs/editor/utils/buildRangeEx.js.map +1 -0
  84. package/lib-mjs/editor/utils/eventConverter.d.ts +15 -0
  85. package/lib-mjs/editor/utils/eventConverter.js +458 -0
  86. package/lib-mjs/editor/utils/eventConverter.js.map +1 -0
  87. package/lib-mjs/editor/utils/insertNode.d.ts +7 -0
  88. package/lib-mjs/editor/utils/insertNode.js +143 -0
  89. package/lib-mjs/editor/utils/insertNode.js.map +1 -0
  90. package/lib-mjs/editor/utils/selectionConverter.d.ts +10 -0
  91. package/lib-mjs/editor/utils/selectionConverter.js +74 -0
  92. package/lib-mjs/editor/utils/selectionConverter.js.map +1 -0
  93. package/lib-mjs/index.d.ts +3 -0
  94. package/lib-mjs/index.js +2 -0
  95. package/lib-mjs/index.js.map +1 -0
  96. package/lib-mjs/publicTypes/BeforePasteAdapterEvent.d.ts +15 -0
  97. package/lib-mjs/publicTypes/BeforePasteAdapterEvent.js +2 -0
  98. package/lib-mjs/publicTypes/BeforePasteAdapterEvent.js.map +1 -0
  99. package/lib-mjs/publicTypes/EditorAdapterOptions.d.ts +20 -0
  100. package/lib-mjs/publicTypes/EditorAdapterOptions.js +2 -0
  101. package/lib-mjs/publicTypes/EditorAdapterOptions.js.map +1 -0
  102. package/package.json +21 -0
@@ -0,0 +1,840 @@
1
+ var _a;
2
+ import { __assign, __extends, __read, __spreadArray } from "tslib";
3
+ import { BridgePlugin } from '../corePlugins/BridgePlugin';
4
+ import { buildRangeEx } from './utils/buildRangeEx';
5
+ import { getObjectKeys } from 'roosterjs-content-model-dom';
6
+ import { insertNode } from './utils/insertNode';
7
+ import { newEventToOldEvent, oldEventToNewEvent, OldEventTypeToNewEventType, } from './utils/eventConverter';
8
+ import { createModelFromHtml, exportContent, isBold, redo, retrieveModelFormatState, StandaloneEditor, transformColor, undo, } from 'roosterjs-content-model-core';
9
+ import { convertDomSelectionToRangeEx, convertRangeExToDomSelection, } from './utils/selectionConverter';
10
+ import { ContentTraverser, Position, PositionContentSearcher, cacheGetEventData, collapseNodes, contains, deleteSelectedContent, findClosestElementAncestor, getBlockElementAtNode, getRegionsFromRange, getSelectionPath, isNodeEmpty, isPositionAtBeginningOf, queryElements, toArray, wrap, } from 'roosterjs-editor-dom';
11
+ var GetContentModeMap = (_a = {},
12
+ _a[0 /* CleanHTML */] = 'HTML',
13
+ _a[3 /* PlainText */] = 'PlainText',
14
+ _a[4 /* PlainTextFast */] = 'PlainTextFast',
15
+ _a[1 /* RawHTMLOnly */] = 'HTML',
16
+ _a[2 /* RawHTMLWithSelection */] = 'HTML',
17
+ _a);
18
+ /**
19
+ * Editor for Content Model.
20
+ * (This class is still under development, and may still be changed in the future with some breaking changes)
21
+ */
22
+ var EditorAdapter = /** @class */ (function (_super) {
23
+ __extends(EditorAdapter, _super);
24
+ /**
25
+ * Creates an instance of Editor
26
+ * @param contentDiv The DIV HTML element which will be the container element of editor
27
+ * @param options An optional options object to customize the editor
28
+ */
29
+ function EditorAdapter(contentDiv, options) {
30
+ if (options === void 0) { options = {}; }
31
+ var _a, _b;
32
+ var _this = this;
33
+ var bridgePlugin = new BridgePlugin(function (core) {
34
+ _this.contentModelEditorCore = core;
35
+ return _this;
36
+ }, options.legacyPlugins, options.experimentalFeatures);
37
+ var plugins = __spreadArray([bridgePlugin], __read(((_a = options.plugins) !== null && _a !== void 0 ? _a : [])), false);
38
+ var initContent = (_b = options.initialContent) !== null && _b !== void 0 ? _b : contentDiv.innerHTML;
39
+ var initialModel = initContent && !options.initialModel
40
+ ? createModelFromHtml(initContent, options.defaultDomToModelOptions, options.trustedHTMLHandler, options.defaultSegmentFormat)
41
+ : options.initialModel;
42
+ var standaloneEditorOptions = __assign(__assign({}, options), { plugins: plugins, initialModel: initialModel });
43
+ _this = _super.call(this, contentDiv, standaloneEditorOptions) || this;
44
+ return _this;
45
+ }
46
+ /**
47
+ * Dispose this editor, dispose all plugins and custom data
48
+ */
49
+ EditorAdapter.prototype.dispose = function () {
50
+ var core = this.contentModelEditorCore;
51
+ if (core) {
52
+ getObjectKeys(core.customData).forEach(function (key) {
53
+ var data = core.customData[key];
54
+ if (data && data.disposer) {
55
+ data.disposer(data.value);
56
+ }
57
+ delete core.customData[key];
58
+ });
59
+ this.contentModelEditorCore = undefined;
60
+ }
61
+ _super.prototype.dispose.call(this);
62
+ };
63
+ /**
64
+ * Get whether this editor is disposed
65
+ * @returns True if editor is disposed, otherwise false
66
+ */
67
+ EditorAdapter.prototype.isDisposed = function () {
68
+ return _super.prototype.isDisposed.call(this) || !this.contentModelEditorCore;
69
+ };
70
+ /**
71
+ * Insert node into editor
72
+ * @param node The node to insert
73
+ * @param option Insert options. Default value is:
74
+ * position: ContentPosition.SelectionStart
75
+ * updateCursor: true
76
+ * replaceSelection: true
77
+ * insertOnNewLine: false
78
+ * @returns true if node is inserted. Otherwise false
79
+ */
80
+ EditorAdapter.prototype.insertNode = function (node, option) {
81
+ var _a;
82
+ if (node) {
83
+ option = option || {
84
+ position: 3 /* SelectionStart */,
85
+ insertOnNewLine: false,
86
+ updateCursor: true,
87
+ replaceSelection: true,
88
+ insertToRegionRoot: false,
89
+ };
90
+ var contentDiv = this.getCore().contentDiv;
91
+ if (option.updateCursor) {
92
+ this.focus();
93
+ }
94
+ if (option.position == 4 /* Outside */) {
95
+ (_a = contentDiv.parentNode) === null || _a === void 0 ? void 0 : _a.insertBefore(node, contentDiv.nextSibling);
96
+ }
97
+ else {
98
+ if (this.isDarkMode()) {
99
+ transformColor(node, true /*includeSelf*/, 'lightToDark', this.getColorManager());
100
+ }
101
+ var selection = insertNode(contentDiv, this.getDOMSelection(), node, option);
102
+ if (selection) {
103
+ this.setDOMSelection(selection);
104
+ }
105
+ }
106
+ return true;
107
+ }
108
+ else {
109
+ return false;
110
+ }
111
+ };
112
+ /**
113
+ * Delete a node from editor content
114
+ * @param node The node to delete
115
+ * @returns true if node is deleted. Otherwise false
116
+ */
117
+ EditorAdapter.prototype.deleteNode = function (node) {
118
+ // Only remove the node when it falls within editor
119
+ if (node && this.contains(node) && node.parentNode) {
120
+ node.parentNode.removeChild(node);
121
+ return true;
122
+ }
123
+ return false;
124
+ };
125
+ /**
126
+ * Replace a node in editor content with another node
127
+ * @param existingNode The existing node to be replaced
128
+ * @param toNode node to replace to
129
+ * @param transformColorForDarkMode (optional) Whether to transform new node to dark mode. Default is false
130
+ * @returns true if node is replaced. Otherwise false
131
+ */
132
+ EditorAdapter.prototype.replaceNode = function (existingNode, toNode, transformColorForDarkMode) {
133
+ var _a;
134
+ var core = this.getCore();
135
+ // Only replace the node when it falls within editor
136
+ if (this.contains(existingNode) && toNode) {
137
+ if (core.lifecycle.isDarkMode && transformColorForDarkMode) {
138
+ this.transformToDarkColor(toNode, 0 /* LightToDark */);
139
+ }
140
+ (_a = existingNode.parentNode) === null || _a === void 0 ? void 0 : _a.replaceChild(toNode, existingNode);
141
+ return true;
142
+ }
143
+ return false;
144
+ };
145
+ /**
146
+ * Get BlockElement at given node
147
+ * @param node The node to create InlineElement
148
+ * @returns The BlockElement result
149
+ */
150
+ EditorAdapter.prototype.getBlockElementAtNode = function (node) {
151
+ return getBlockElementAtNode(this.getCore().contentDiv, node);
152
+ };
153
+ EditorAdapter.prototype.contains = function (arg) {
154
+ if (!arg) {
155
+ return false;
156
+ }
157
+ return contains(this.getCore().contentDiv, arg);
158
+ };
159
+ EditorAdapter.prototype.queryElements = function (selector, scopeOrCallback, callback) {
160
+ if (scopeOrCallback === void 0) { scopeOrCallback = 0 /* Body */; }
161
+ var core = this.getCore();
162
+ var result = [];
163
+ var scope = scopeOrCallback instanceof Function ? 0 /* Body */ : scopeOrCallback;
164
+ callback = scopeOrCallback instanceof Function ? scopeOrCallback : callback;
165
+ var selectionEx = scope == 0 /* Body */ ? null : this.getSelectionRangeEx();
166
+ if (selectionEx) {
167
+ selectionEx.ranges.forEach(function (range) {
168
+ result.push.apply(result, __spreadArray([], __read(queryElements(core.contentDiv, selector, callback, scope, range)), false));
169
+ });
170
+ }
171
+ else {
172
+ return queryElements(core.contentDiv, selector, callback, scope, undefined /* range */);
173
+ }
174
+ return result;
175
+ };
176
+ /**
177
+ * Collapse nodes within the given start and end nodes to their common ancestor node,
178
+ * split parent nodes if necessary
179
+ * @param start The start node
180
+ * @param end The end node
181
+ * @param canSplitParent True to allow split parent node there are nodes before start or after end under the same parent
182
+ * and the returned nodes will be all nodes from start through end after splitting
183
+ * False to disallow split parent
184
+ * @returns When canSplitParent is true, returns all node from start through end after splitting,
185
+ * otherwise just return start and end
186
+ */
187
+ EditorAdapter.prototype.collapseNodes = function (start, end, canSplitParent) {
188
+ return collapseNodes(this.getCore().contentDiv, start, end, canSplitParent);
189
+ };
190
+ //#endregion
191
+ //#region Content API
192
+ /**
193
+ * Check whether the editor contains any visible content
194
+ * @param trim Whether trim the content string before check. Default is false
195
+ * @returns True if there's no visible content, otherwise false
196
+ */
197
+ EditorAdapter.prototype.isEmpty = function (trim) {
198
+ return isNodeEmpty(this.getCore().contentDiv, trim);
199
+ };
200
+ /**
201
+ * Get current editor content as HTML string
202
+ * @param mode specify what kind of HTML content to retrieve
203
+ * @returns HTML string representing current editor content
204
+ */
205
+ EditorAdapter.prototype.getContent = function (mode) {
206
+ if (mode === void 0) { mode = 0 /* CleanHTML */; }
207
+ return exportContent(this, GetContentModeMap[mode]);
208
+ };
209
+ /**
210
+ * Set HTML content to this editor. All existing content will be replaced. A ContentChanged event will be triggered
211
+ * @param content HTML content to set in
212
+ * @param triggerContentChangedEvent True to trigger a ContentChanged event. Default value is true
213
+ */
214
+ EditorAdapter.prototype.setContent = function (content, triggerContentChangedEvent) {
215
+ if (triggerContentChangedEvent === void 0) { triggerContentChangedEvent = true; }
216
+ var core = this.getCore();
217
+ var contentDiv = core.contentDiv, api = core.api, trustedHTMLHandler = core.trustedHTMLHandler, lifecycle = core.lifecycle, darkColorHandler = core.darkColorHandler;
218
+ api.triggerEvent(core, {
219
+ eventType: 'beforeSetContent',
220
+ newContent: content,
221
+ }, true /*broadcast*/);
222
+ var newModel = createModelFromHtml(content, core.domToModelSettings.customized, trustedHTMLHandler, core.format.defaultFormat);
223
+ api.setContentModel(core, newModel);
224
+ if (triggerContentChangedEvent) {
225
+ api.triggerEvent(core, {
226
+ eventType: 'contentChanged',
227
+ source: "SetContent" /* SetContent */,
228
+ }, false /*broadcast*/);
229
+ }
230
+ else if (lifecycle.isDarkMode) {
231
+ transformColor(contentDiv, false /*includeSelf*/, 'lightToDark', darkColorHandler);
232
+ }
233
+ };
234
+ /**
235
+ * Insert HTML content into editor
236
+ * @param HTML content to insert
237
+ * @param option Insert options. Default value is:
238
+ * position: ContentPosition.SelectionStart
239
+ * updateCursor: true
240
+ * replaceSelection: true
241
+ * insertOnNewLine: false
242
+ */
243
+ EditorAdapter.prototype.insertContent = function (content, option) {
244
+ var _a;
245
+ if (content) {
246
+ var doc = this.getDocument();
247
+ var body = (_a = new DOMParser().parseFromString(this.getCore().trustedHTMLHandler(content), 'text/html')) === null || _a === void 0 ? void 0 : _a.body;
248
+ var allNodes = (body === null || body === void 0 ? void 0 : body.childNodes) ? toArray(body.childNodes) : [];
249
+ // If it is to insert on new line, and there are more than one node in the collection, wrap all nodes with
250
+ // a parent DIV before calling insertNode on each top level sub node. Otherwise, every sub node may get wrapped
251
+ // separately to show up on its own line
252
+ if (option && option.insertOnNewLine && allNodes.length > 1) {
253
+ allNodes = [wrap(allNodes)];
254
+ }
255
+ var fragment_1 = doc.createDocumentFragment();
256
+ allNodes.forEach(function (node) { return fragment_1.appendChild(node); });
257
+ this.insertNode(fragment_1, option);
258
+ }
259
+ };
260
+ /**
261
+ * Delete selected content
262
+ */
263
+ EditorAdapter.prototype.deleteSelectedContent = function () {
264
+ var range = this.getSelectionRange();
265
+ if (range && !range.collapsed) {
266
+ return deleteSelectedContent(this.getCore().contentDiv, range);
267
+ }
268
+ return null;
269
+ };
270
+ /**
271
+ * Paste into editor using a clipboardData object
272
+ * @param clipboardData Clipboard data retrieved from clipboard
273
+ * @param pasteAsText Force pasting as plain text. Default value is false
274
+ * @param applyCurrentStyle True if apply format of current selection to the pasted content,
275
+ * false to keep original format. Default value is false. When pasteAsText is true, this parameter is ignored
276
+ * @param pasteAsImage: When set to true, if the clipboardData contains a imageDataUri will paste the image to the editor
277
+ */
278
+ EditorAdapter.prototype.paste = function (clipboardData, pasteAsText, applyCurrentFormat, pasteAsImage) {
279
+ if (pasteAsText === void 0) { pasteAsText = false; }
280
+ if (applyCurrentFormat === void 0) { applyCurrentFormat = false; }
281
+ if (pasteAsImage === void 0) { pasteAsImage = false; }
282
+ this.pasteFromClipboard(clipboardData, pasteAsText
283
+ ? 'asPlainText'
284
+ : applyCurrentFormat
285
+ ? 'mergeFormat'
286
+ : pasteAsImage
287
+ ? 'asImage'
288
+ : 'normal');
289
+ };
290
+ //#endregion
291
+ //#region Focus and Selection
292
+ /**
293
+ * Get current selection range from Editor.
294
+ * It does a live pull on the selection, if nothing retrieved, return whatever we have in cache.
295
+ * @param tryGetFromCache Set to true to retrieve the selection range from cache if editor doesn't own the focus now.
296
+ * Default value is true
297
+ * @returns current selection range, or null if editor never got focus before
298
+ */
299
+ EditorAdapter.prototype.getSelectionRange = function (tryGetFromCache) {
300
+ if (tryGetFromCache === void 0) { tryGetFromCache = true; }
301
+ var selection = this.getDOMSelection();
302
+ return (selection === null || selection === void 0 ? void 0 : selection.type) == 'range' ? selection.range : null;
303
+ };
304
+ /**
305
+ * Get current selection range from Editor.
306
+ * It does a live pull on the selection, if nothing retrieved, return whatever we have in cache.
307
+ * @param tryGetFromCache Set to true to retrieve the selection range from cache if editor doesn't own the focus now.
308
+ * Default value is true
309
+ * @returns current selection range, or null if editor never got focus before
310
+ */
311
+ EditorAdapter.prototype.getSelectionRangeEx = function () {
312
+ var selection = this.getDOMSelection();
313
+ return convertDomSelectionToRangeEx(selection);
314
+ };
315
+ /**
316
+ * Get current selection in a serializable format
317
+ * It does a live pull on the selection, if nothing retrieved, return whatever we have in cache.
318
+ * @returns current selection path, or null if editor never got focus before
319
+ */
320
+ EditorAdapter.prototype.getSelectionPath = function () {
321
+ var range = this.getSelectionRange();
322
+ return range && getSelectionPath(this.getCore().contentDiv, range);
323
+ };
324
+ EditorAdapter.prototype.select = function (arg1, arg2, arg3, arg4) {
325
+ var core = this.getCore();
326
+ var rangeEx = buildRangeEx(core.contentDiv, arg1, arg2, arg3, arg4);
327
+ var selection = convertRangeExToDomSelection(rangeEx);
328
+ this.setDOMSelection(selection);
329
+ return true;
330
+ };
331
+ /**
332
+ * Get current focused position. Return null if editor doesn't have focus at this time.
333
+ */
334
+ EditorAdapter.prototype.getFocusedPosition = function () {
335
+ var _a;
336
+ var sel = (_a = this.getDocument().defaultView) === null || _a === void 0 ? void 0 : _a.getSelection();
337
+ if ((sel === null || sel === void 0 ? void 0 : sel.focusNode) && this.contains(sel.focusNode)) {
338
+ return new Position(sel.focusNode, sel.focusOffset);
339
+ }
340
+ var range = this.getSelectionRange();
341
+ if (range) {
342
+ return Position.getStart(range);
343
+ }
344
+ return null;
345
+ };
346
+ /**
347
+ * Get an HTML element from current cursor position.
348
+ * When expectedTags is not specified, return value is the current node (if it is HTML element)
349
+ * or its parent node (if current node is a Text node).
350
+ * When expectedTags is specified, return value is the first ancestor of current node which has
351
+ * one of the expected tags.
352
+ * If no element found within editor by the given tag, return null.
353
+ * @param selector Optional, an HTML selector to find HTML element with.
354
+ * @param startFrom Start search from this node. If not specified, start from current focused position
355
+ * @param event Optional, if specified, editor will try to get cached result from the event object first.
356
+ * If it is not cached before, query from DOM and cache the result into the event object
357
+ */
358
+ EditorAdapter.prototype.getElementAtCursor = function (selector, startFrom, event) {
359
+ var _this = this;
360
+ var _a;
361
+ event = startFrom ? undefined : event; // Only use cache when startFrom is not specified, for different start position can have different result
362
+ return ((_a = cacheGetEventData(event !== null && event !== void 0 ? event : null, 'GET_ELEMENT_AT_CURSOR_' + selector, function () {
363
+ if (!startFrom) {
364
+ var position = _this.getFocusedPosition();
365
+ startFrom = position === null || position === void 0 ? void 0 : position.node;
366
+ }
367
+ return (startFrom &&
368
+ findClosestElementAncestor(startFrom, _this.getCore().contentDiv, selector));
369
+ })) !== null && _a !== void 0 ? _a : null);
370
+ };
371
+ /**
372
+ * Check if this position is at beginning of the editor.
373
+ * This will return true if all nodes between the beginning of target node and the position are empty.
374
+ * @param position The position to check
375
+ * @returns True if position is at beginning of the editor, otherwise false
376
+ */
377
+ EditorAdapter.prototype.isPositionAtBeginning = function (position) {
378
+ return isPositionAtBeginningOf(position, this.getCore().contentDiv);
379
+ };
380
+ /**
381
+ * Get impacted regions from selection
382
+ */
383
+ EditorAdapter.prototype.getSelectedRegions = function (type) {
384
+ if (type === void 0) { type = 0 /* Table */; }
385
+ var selection = this.getSelectionRangeEx();
386
+ var result = [];
387
+ var contentDiv = this.getCore().contentDiv;
388
+ selection.ranges.forEach(function (range) {
389
+ result.push.apply(result, __spreadArray([], __read((range ? getRegionsFromRange(contentDiv, range, type) : [])), false));
390
+ });
391
+ return result.filter(function (value, index, self) {
392
+ return self.indexOf(value) === index;
393
+ });
394
+ };
395
+ //#endregion
396
+ //#region EVENT API
397
+ EditorAdapter.prototype.addDomEventHandler = function (nameOrMap, handler) {
398
+ var _a;
399
+ var eventsMap = typeof nameOrMap == 'string' ? (_a = {}, _a[nameOrMap] = handler, _a) : nameOrMap;
400
+ var eventsMapResult = {};
401
+ getObjectKeys(eventsMap).forEach(function (key) {
402
+ var handlerObj = eventsMap[key];
403
+ var result = {
404
+ pluginEventType: null,
405
+ beforeDispatch: null,
406
+ };
407
+ if (typeof handlerObj === 'number') {
408
+ result.pluginEventType = OldEventTypeToNewEventType[handlerObj];
409
+ }
410
+ else if (typeof handlerObj === 'function') {
411
+ result.beforeDispatch = handlerObj;
412
+ }
413
+ else if (typeof handlerObj === 'object') {
414
+ var record = handlerObj;
415
+ result = {
416
+ beforeDispatch: record.beforeDispatch,
417
+ pluginEventType: typeof record.pluginEventType == 'number'
418
+ ? OldEventTypeToNewEventType[record.pluginEventType]
419
+ : undefined,
420
+ };
421
+ }
422
+ eventsMapResult[key] = result;
423
+ });
424
+ return this.attachDomEvent(eventsMapResult);
425
+ };
426
+ /**
427
+ * Trigger an event to be dispatched to all plugins
428
+ * @param eventType Type of the event
429
+ * @param data data of the event with given type, this is the rest part of PluginEvent with the given type
430
+ * @param broadcast indicates if the event needs to be dispatched to all plugins
431
+ * True means to all, false means to allow exclusive handling from one plugin unless no one wants that
432
+ * @returns the event object which is really passed into plugins. Some plugin may modify the event object so
433
+ * the result of this function provides a chance to read the modified result
434
+ */
435
+ EditorAdapter.prototype.triggerPluginEvent = function (eventType, data, broadcast) {
436
+ var _a;
437
+ if (broadcast === void 0) { broadcast = false; }
438
+ var oldEvent = __assign({ eventType: eventType }, data);
439
+ var newEvent = oldEventToNewEvent(oldEvent);
440
+ var core = this.getCore();
441
+ if (newEvent) {
442
+ core.api.triggerEvent(core, newEvent, broadcast);
443
+ return ((_a = newEventToOldEvent(newEvent, oldEvent)) !== null && _a !== void 0 ? _a : oldEvent);
444
+ }
445
+ else {
446
+ return oldEvent;
447
+ }
448
+ };
449
+ /**
450
+ * Trigger a ContentChangedEvent
451
+ * @param source Source of this event, by default is 'SetContent'
452
+ * @param data additional data for this event
453
+ */
454
+ EditorAdapter.prototype.triggerContentChangedEvent = function (source, data) {
455
+ if (source === void 0) { source = "SetContent" /* SetContent */; }
456
+ this.triggerPluginEvent(7 /* ContentChanged */, {
457
+ source: source,
458
+ data: data,
459
+ });
460
+ };
461
+ //#endregion
462
+ //#region Undo API
463
+ /**
464
+ * Undo last edit operation
465
+ */
466
+ EditorAdapter.prototype.undo = function () {
467
+ undo(this);
468
+ };
469
+ /**
470
+ * Redo next edit operation
471
+ */
472
+ EditorAdapter.prototype.redo = function () {
473
+ redo(this);
474
+ };
475
+ /**
476
+ * Add undo snapshot, and execute a format callback function, then add another undo snapshot, then trigger
477
+ * ContentChangedEvent with given change source.
478
+ * If this function is called nested, undo snapshot will only be added in the outside one
479
+ * @param callback The callback function to perform formatting, returns a data object which will be used as
480
+ * the data field in ContentChangedEvent if changeSource is not null.
481
+ * @param changeSource The change source to use when fire ContentChangedEvent. When the value is not null,
482
+ * a ContentChangedEvent will be fired with change source equal to this value
483
+ * @param canUndoByBackspace True if this action can be undone when user press Backspace key (aka Auto Complete).
484
+ */
485
+ EditorAdapter.prototype.addUndoSnapshot = function (callback, changeSource, canUndoByBackspace, additionalData) {
486
+ var _a, _b;
487
+ var core = this.getCore();
488
+ var undoState = core.undo;
489
+ var isNested = undoState.isNested;
490
+ var data;
491
+ if (!isNested) {
492
+ undoState.isNested = true;
493
+ // When there is getEntityState, it means this is triggered by an entity change.
494
+ // So if HTML content is not changed (hasNewContent is false), no need to add another snapshot before change
495
+ if (core.undo.snapshotsManager.hasNewContent ||
496
+ !(additionalData === null || additionalData === void 0 ? void 0 : additionalData.getEntityState) ||
497
+ !callback) {
498
+ core.api.addUndoSnapshot(core, !!canUndoByBackspace, (_a = additionalData === null || additionalData === void 0 ? void 0 : additionalData.getEntityState) === null || _a === void 0 ? void 0 : _a.call(additionalData));
499
+ }
500
+ }
501
+ try {
502
+ if (callback) {
503
+ var selection = core.api.getDOMSelection(core);
504
+ var range = (selection === null || selection === void 0 ? void 0 : selection.type) == 'range' ? selection.range : null;
505
+ data = callback(range && Position.getStart(range).normalize(), range && Position.getEnd(range).normalize());
506
+ if (!isNested) {
507
+ var entityStates = (_b = additionalData === null || additionalData === void 0 ? void 0 : additionalData.getEntityState) === null || _b === void 0 ? void 0 : _b.call(additionalData);
508
+ core.api.addUndoSnapshot(core, false /*isAutoCompleteSnapshot*/, entityStates);
509
+ }
510
+ }
511
+ }
512
+ finally {
513
+ if (!isNested) {
514
+ undoState.isNested = false;
515
+ }
516
+ }
517
+ if (callback && changeSource) {
518
+ var event_1 = {
519
+ eventType: 7 /* ContentChanged */,
520
+ source: changeSource,
521
+ data: data,
522
+ additionalData: additionalData,
523
+ };
524
+ this.triggerPluginEvent(7 /* ContentChanged */, event_1, true /*broadcast*/);
525
+ }
526
+ if (canUndoByBackspace) {
527
+ var selection = core.api.getDOMSelection(core);
528
+ if ((selection === null || selection === void 0 ? void 0 : selection.type) == 'range') {
529
+ core.undo.snapshotsManager.hasNewContent = false;
530
+ core.undo.posContainer = selection.range.startContainer;
531
+ core.undo.posOffset = selection.range.startOffset;
532
+ }
533
+ }
534
+ };
535
+ /**
536
+ * Whether there is an available undo/redo snapshot
537
+ */
538
+ EditorAdapter.prototype.getUndoState = function () {
539
+ var snapshotsManager = this.getCore().undo.snapshotsManager;
540
+ return {
541
+ canUndo: snapshotsManager.hasNewContent || snapshotsManager.canMove(-1 /*previousSnapshot*/),
542
+ canRedo: snapshotsManager.canMove(1 /*nextSnapshot*/),
543
+ };
544
+ };
545
+ //#endregion
546
+ //#region Misc
547
+ /**
548
+ * Get custom data related to this editor
549
+ * @param key Key of the custom data
550
+ * @param getter Getter function. If custom data for the given key doesn't exist,
551
+ * call this function to get one and store it if it is specified. Otherwise return undefined
552
+ * @param disposer An optional disposer function to dispose this custom data when
553
+ * dispose editor.
554
+ */
555
+ EditorAdapter.prototype.getCustomData = function (key, getter, disposer) {
556
+ var core = this.getContentModelEditorCore();
557
+ return (core.customData[key] = core.customData[key] || {
558
+ value: getter ? getter() : undefined,
559
+ disposer: disposer,
560
+ }).value;
561
+ };
562
+ /**
563
+ * Get default format of this editor
564
+ * @returns Default format object of this editor
565
+ */
566
+ EditorAdapter.prototype.getDefaultFormat = function () {
567
+ var format = this.getCore().format.defaultFormat;
568
+ return {
569
+ bold: isBold(format.fontWeight),
570
+ italic: format.italic,
571
+ underline: format.underline,
572
+ fontFamily: format.fontFamily,
573
+ fontSize: format.fontSize,
574
+ textColor: format.textColor,
575
+ backgroundColor: format.backgroundColor,
576
+ };
577
+ };
578
+ /**
579
+ * Get a content traverser for the whole editor
580
+ * @param startNode The node to start from. If not passed, it will start from the beginning of the body
581
+ */
582
+ EditorAdapter.prototype.getBodyTraverser = function (startNode) {
583
+ return ContentTraverser.createBodyTraverser(this.getCore().contentDiv, startNode);
584
+ };
585
+ /**
586
+ * Get a content traverser for current selection
587
+ * @returns A content traverser, or null if editor never got focus before
588
+ */
589
+ EditorAdapter.prototype.getSelectionTraverser = function (range) {
590
+ var _a;
591
+ range = (_a = range !== null && range !== void 0 ? range : this.getSelectionRange()) !== null && _a !== void 0 ? _a : undefined;
592
+ return range
593
+ ? ContentTraverser.createSelectionTraverser(this.getCore().contentDiv, range)
594
+ : null;
595
+ };
596
+ /**
597
+ * Get a content traverser for current block element start from specified position
598
+ * @param startFrom Start position of the traverser. Default value is ContentPosition.SelectionStart
599
+ * @returns A content traverser, or null if editor never got focus before
600
+ */
601
+ EditorAdapter.prototype.getBlockTraverser = function (startFrom) {
602
+ if (startFrom === void 0) { startFrom = 3 /* SelectionStart */; }
603
+ var range = this.getSelectionRange();
604
+ return range
605
+ ? ContentTraverser.createBlockTraverser(this.getCore().contentDiv, range, startFrom)
606
+ : null;
607
+ };
608
+ /**
609
+ * Get a text traverser of current selection
610
+ * @param event Optional, if specified, editor will try to get cached result from the event object first.
611
+ * If it is not cached before, query from DOM and cache the result into the event object
612
+ * @returns A content traverser, or null if editor never got focus before
613
+ */
614
+ EditorAdapter.prototype.getContentSearcherOfCursor = function (event) {
615
+ var _this = this;
616
+ return cacheGetEventData(event !== null && event !== void 0 ? event : null, 'ContentSearcher', function () {
617
+ var range = _this.getSelectionRange();
618
+ return (range &&
619
+ new PositionContentSearcher(_this.getCore().contentDiv, Position.getStart(range)));
620
+ });
621
+ };
622
+ /**
623
+ * Run a callback function asynchronously
624
+ * @param callback The callback function to run
625
+ * @returns a function to cancel this async run
626
+ */
627
+ EditorAdapter.prototype.runAsync = function (callback) {
628
+ var _this = this;
629
+ var win = this.getCore().contentDiv.ownerDocument.defaultView || window;
630
+ var handle = win.requestAnimationFrame(function () {
631
+ if (!_this.isDisposed() && callback) {
632
+ callback(_this);
633
+ }
634
+ });
635
+ return function () {
636
+ win.cancelAnimationFrame(handle);
637
+ };
638
+ };
639
+ /**
640
+ * Set DOM attribute of editor content DIV
641
+ * @param name Name of the attribute
642
+ * @param value Value of the attribute
643
+ */
644
+ EditorAdapter.prototype.setEditorDomAttribute = function (name, value) {
645
+ this.getDOMHelper().setDomAttribute(name, value);
646
+ };
647
+ /**
648
+ * Get DOM attribute of editor content DIV, null if there is no such attribute.
649
+ * @param name Name of the attribute
650
+ */
651
+ EditorAdapter.prototype.getEditorDomAttribute = function (name) {
652
+ return this.getDOMHelper().getDomAttribute(name);
653
+ };
654
+ /**
655
+ * @deprecated Use getVisibleViewport() instead.
656
+ *
657
+ * Get current relative distance from top-left corner of the given element to top-left corner of editor content DIV.
658
+ * @param element The element to calculate from. If the given element is not in editor, return value will be null
659
+ * @param addScroll When pass true, The return value will also add scrollLeft and scrollTop if any. So the value
660
+ * may be different than what user is seeing from the view. When pass false, scroll position will be ignored.
661
+ * @returns An [x, y] array which contains the left and top distances, or null if the given element is not in editor.
662
+ */
663
+ EditorAdapter.prototype.getRelativeDistanceToEditor = function (element, addScroll) {
664
+ if (this.contains(element)) {
665
+ var contentDiv = this.getCore().contentDiv;
666
+ var editorRect = contentDiv.getBoundingClientRect();
667
+ var elementRect = element.getBoundingClientRect();
668
+ if (editorRect && elementRect) {
669
+ var x = elementRect.left - (editorRect === null || editorRect === void 0 ? void 0 : editorRect.left);
670
+ var y = elementRect.top - (editorRect === null || editorRect === void 0 ? void 0 : editorRect.top);
671
+ if (addScroll) {
672
+ x += contentDiv.scrollLeft;
673
+ y += contentDiv.scrollTop;
674
+ }
675
+ return [x, y];
676
+ }
677
+ }
678
+ return null;
679
+ };
680
+ /**
681
+ * Add a Content Edit feature.
682
+ * @param feature The feature to add
683
+ */
684
+ EditorAdapter.prototype.addContentEditFeature = function (feature) {
685
+ var core = this.getContentModelEditorCore();
686
+ feature === null || feature === void 0 ? void 0 : feature.keys.forEach(function (key) {
687
+ var array = core.edit.features[key] || [];
688
+ array.push(feature);
689
+ core.edit.features[key] = array;
690
+ });
691
+ };
692
+ /**
693
+ * Remove a Content Edit feature.
694
+ * @param feature The feature to remove
695
+ */
696
+ EditorAdapter.prototype.removeContentEditFeature = function (feature) {
697
+ var core = this.getContentModelEditorCore();
698
+ feature === null || feature === void 0 ? void 0 : feature.keys.forEach(function (key) {
699
+ var _a;
700
+ var featureSet = core.edit.features[key];
701
+ var index = (_a = featureSet === null || featureSet === void 0 ? void 0 : featureSet.indexOf(feature)) !== null && _a !== void 0 ? _a : -1;
702
+ if (index >= 0) {
703
+ core.edit.features[key].splice(index, 1);
704
+ if (core.edit.features[key].length < 1) {
705
+ delete core.edit.features[key];
706
+ }
707
+ }
708
+ });
709
+ };
710
+ /**
711
+ * @deprecated
712
+ * Get style based format state from current selection, including font name/size and colors
713
+ */
714
+ EditorAdapter.prototype.getStyleBasedFormatState = function () {
715
+ var format = this.retrieveFormatState();
716
+ return {
717
+ backgroundColor: format.backgroundColor,
718
+ direction: format.direction,
719
+ fontName: format.fontName,
720
+ fontSize: format.fontSize,
721
+ fontWeight: format.fontWeight,
722
+ lineHeight: format.lineHeight,
723
+ marginBottom: format.marginBottom,
724
+ marginTop: format.marginTop,
725
+ textAlign: format.textAlign,
726
+ textColor: format.textColor,
727
+ };
728
+ };
729
+ /**
730
+ * @deprecated
731
+ * Get the pendable format such as underline and bold
732
+ * @returns The pending format state
733
+ */
734
+ EditorAdapter.prototype.getPendableFormatState = function () {
735
+ var format = this.retrieveFormatState();
736
+ return {
737
+ isBold: format.isBold,
738
+ isItalic: format.isItalic,
739
+ isStrikeThrough: format.isStrikeThrough,
740
+ isSubscript: format.isSubscript,
741
+ isSuperscript: format.isSubscript,
742
+ isUnderline: format.isUnderline,
743
+ };
744
+ };
745
+ /**
746
+ * @deprecated
747
+ * Ensure user will type into a container element rather than into the editor content DIV directly
748
+ * @param position The position that user is about to type to
749
+ * @param keyboardEvent Optional keyboard event object
750
+ */
751
+ EditorAdapter.prototype.ensureTypeInContainer = function (position, keyboardEvent) {
752
+ // No OP
753
+ };
754
+ //#endregion
755
+ //#region Dark mode APIs
756
+ /**
757
+ * Transform the given node and all its child nodes to dark mode color if editor is in dark mode
758
+ * @param node The node to transform
759
+ * @param direction The transform direction. @default ColorTransformDirection.LightToDark
760
+ */
761
+ EditorAdapter.prototype.transformToDarkColor = function (node, direction) {
762
+ if (direction === void 0) { direction = 0 /* LightToDark */; }
763
+ var core = this.getCore();
764
+ if (core.lifecycle.isDarkMode) {
765
+ transformColor(node, true /*includeSelf*/, direction == 1 /* DarkToLight */ ? 'darkToLight' : 'lightToDark', core.darkColorHandler);
766
+ }
767
+ };
768
+ /**
769
+ * Check if the given experimental feature is enabled
770
+ * @param feature The feature to check
771
+ */
772
+ EditorAdapter.prototype.isFeatureEnabled = function (feature) {
773
+ return (this.getContentModelEditorCore().experimentalFeatures.indexOf(feature) >= 0);
774
+ };
775
+ /**
776
+ * Get current zoom scale, default value is 1
777
+ * When editor is put under a zoomed container, need to pass the zoom scale number using EditorOptions.zoomScale
778
+ * to let editor behave correctly especially for those mouse drag/drop behaviors
779
+ * @returns current zoom scale number
780
+ */
781
+ EditorAdapter.prototype.getZoomScale = function () {
782
+ return this.getDOMHelper().calculateZoomScale();
783
+ };
784
+ /**
785
+ * Set current zoom scale, default value is 1
786
+ * When editor is put under a zoomed container, need to pass the zoom scale number using EditorOptions.zoomScale
787
+ * to let editor behave correctly especially for those mouse drag/drop behaviors
788
+ * @param scale The new scale number to set. It should be positive number and no greater than 10, otherwise it will be ignored.
789
+ */
790
+ EditorAdapter.prototype.setZoomScale = function (scale) {
791
+ if (scale > 0 && scale <= 10) {
792
+ var oldValue = this.getZoomScale();
793
+ if (oldValue != scale) {
794
+ this.triggerEvent('zoomChanged', {
795
+ newZoomScale: scale,
796
+ }, true /*broadcast*/);
797
+ }
798
+ }
799
+ };
800
+ /**
801
+ * @deprecated Use getZoomScale() instead
802
+ */
803
+ EditorAdapter.prototype.getSizeTransformer = function () {
804
+ return this.getContentModelEditorCore().sizeTransformer;
805
+ };
806
+ /**
807
+ * Get a darkColorHandler object for this editor.
808
+ */
809
+ EditorAdapter.prototype.getDarkColorHandler = function () {
810
+ var core = this.getContentModelEditorCore();
811
+ return core.darkColorHandler;
812
+ };
813
+ /**
814
+ * Check if editor is in IME input sequence
815
+ * @returns True if editor is in IME input sequence, otherwise false
816
+ */
817
+ EditorAdapter.prototype.isInIME = function () {
818
+ return this.getCore().domEvent.isInIME;
819
+ };
820
+ EditorAdapter.prototype.retrieveFormatState = function () {
821
+ var pendingFormat = this.getPendingFormat();
822
+ var result = {};
823
+ var model = this.getContentModelCopy('reduced');
824
+ retrieveModelFormatState(model, pendingFormat, result);
825
+ return result;
826
+ };
827
+ /**
828
+ * @returns the current EditorAdapterCore object
829
+ * @throws a standard Error if there's no core object
830
+ */
831
+ EditorAdapter.prototype.getContentModelEditorCore = function () {
832
+ if (!this.contentModelEditorCore) {
833
+ throw new Error('Editor is already disposed');
834
+ }
835
+ return this.contentModelEditorCore;
836
+ };
837
+ return EditorAdapter;
838
+ }(StandaloneEditor));
839
+ export { EditorAdapter };
840
+ //# sourceMappingURL=EditorAdapter.js.map