@akilli/editor-src 5.1.5
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.
- package/abbreviation/Abbreviation.js +32 -0
- package/abbreviation/AbbreviationDialog.js +21 -0
- package/audio/Audio.js +47 -0
- package/audio/AudioDialog.js +18 -0
- package/audio/AudioListener.js +50 -0
- package/base/AlignCommand.js +34 -0
- package/base/AlignableListener.js +45 -0
- package/base/Alignment.js +36 -0
- package/base/BarListener.js +95 -0
- package/base/Base.js +127 -0
- package/base/Command.js +139 -0
- package/base/CommandManager.js +60 -0
- package/base/ContentFilter.js +109 -0
- package/base/DeletableListener.js +36 -0
- package/base/DeleteCommand.js +18 -0
- package/base/Dialog.js +153 -0
- package/base/DialogManager.js +44 -0
- package/base/Dispatcher.js +88 -0
- package/base/Dom.js +790 -0
- package/base/EditableListener.js +82 -0
- package/base/Editor.js +448 -0
- package/base/Filter.js +35 -0
- package/base/FilterManager.js +44 -0
- package/base/FocusableListener.js +22 -0
- package/base/FocusbarListener.js +99 -0
- package/base/FormCreator.js +162 -0
- package/base/FormatbarListener.js +32 -0
- package/base/Key.js +258 -0
- package/base/Listener.js +51 -0
- package/base/NavigableListener.js +81 -0
- package/base/Plugin.js +176 -0
- package/base/PluginManager.js +51 -0
- package/base/SlotableListener.js +40 -0
- package/base/SortCommand.js +30 -0
- package/base/SortableListener.js +135 -0
- package/base/Sorting.js +36 -0
- package/base/Tag.js +113 -0
- package/base/TagGroup.js +183 -0
- package/base/TagListener.js +34 -0
- package/base/TagManager.js +61 -0
- package/base/TagName.js +470 -0
- package/base/ToolbarListener.js +11 -0
- package/base/util.js +59 -0
- package/block/Block.js +51 -0
- package/block/BlockDialog.js +11 -0
- package/block/BlockElement.js +21 -0
- package/block/BlockListener.js +60 -0
- package/blockquote/Blockquote.js +43 -0
- package/blockquote/BlockquoteFilter.js +22 -0
- package/blockquote/BlockquoteListener.js +34 -0
- package/bold/Bold.js +30 -0
- package/break/Break.js +33 -0
- package/break/BreakFilter.js +24 -0
- package/build/BuildEditor.js +97 -0
- package/build/editor.css +548 -0
- package/build/editor.woff2 +0 -0
- package/cite/Cite.js +30 -0
- package/code/Code.js +30 -0
- package/data/Data.js +32 -0
- package/data/DataDialog.js +13 -0
- package/definition/Definition.js +32 -0
- package/definition/DefinitionDialog.js +13 -0
- package/deletion/Deletion.js +30 -0
- package/details/Details.js +63 -0
- package/details/DetailsFilter.js +17 -0
- package/details/DetailsListener.js +102 -0
- package/division/Division.js +53 -0
- package/division/DivisionDialog.js +13 -0
- package/emphasis/Emphasis.js +30 -0
- package/figure/Figure.js +58 -0
- package/figure/FigureFilter.js +14 -0
- package/figure/FigureListener.js +23 -0
- package/heading/Heading.js +38 -0
- package/horizontalrule/HorizontalRule.js +37 -0
- package/i18n/I18n.js +26 -0
- package/i18n/de.js +167 -0
- package/iframe/Iframe.js +49 -0
- package/iframe/IframeDialog.js +20 -0
- package/iframe/IframeListener.js +48 -0
- package/image/Image.js +47 -0
- package/image/ImageDialog.js +23 -0
- package/image/ImageListener.js +47 -0
- package/insertion/Insertion.js +30 -0
- package/italic/Italic.js +30 -0
- package/keyboard/Keyboard.js +30 -0
- package/link/Link.js +34 -0
- package/link/LinkDialog.js +14 -0
- package/link/LinkListener.js +45 -0
- package/list/List.js +40 -0
- package/list/ListListener.js +91 -0
- package/mark/Mark.js +30 -0
- package/orderedlist/OrderedList.js +39 -0
- package/package.json +24 -0
- package/paragraph/Paragraph.js +42 -0
- package/paragraph/ParagraphListener.js +40 -0
- package/preformat/Preformat.js +43 -0
- package/preformat/PreformatFilter.js +22 -0
- package/preformat/PreformatListener.js +34 -0
- package/quote/Quote.js +30 -0
- package/sample/Sample.js +30 -0
- package/section/Section.js +55 -0
- package/section/SectionDialog.js +13 -0
- package/small/Small.js +30 -0
- package/strikethrough/Strikethrough.js +30 -0
- package/strong/Strong.js +30 -0
- package/subheading/Subheading.js +38 -0
- package/subscript/Subscript.js +30 -0
- package/superscript/Superscript.js +30 -0
- package/table/Table.js +113 -0
- package/table/TableCellListener.js +125 -0
- package/table/TableColumnAddCommand.js +19 -0
- package/table/TableColumnListener.js +34 -0
- package/table/TableCommand.js +23 -0
- package/table/TableDialog.js +14 -0
- package/table/TableFilter.js +35 -0
- package/table/TableListener.js +39 -0
- package/table/TableRowAddCommand.js +18 -0
- package/table/TableRowListener.js +34 -0
- package/time/Time.js +32 -0
- package/time/TimeDialog.js +13 -0
- package/underline/Underline.js +30 -0
- package/unorderedlist/UnorderedList.js +39 -0
- package/variable/Variable.js +30 -0
- package/video/Video.js +47 -0
- package/video/VideoDialog.js +20 -0
- package/video/VideoListener.js +48 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import Command from './Command.js';
|
|
2
|
+
|
|
3
|
+
export default class CommandManager {
|
|
4
|
+
/**
|
|
5
|
+
* @type {Map<string, Command>}
|
|
6
|
+
*/
|
|
7
|
+
#items = new Map();
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {Command[]} [commands = []]
|
|
11
|
+
*/
|
|
12
|
+
constructor(commands = []) {
|
|
13
|
+
commands.forEach((command) => this.set(command));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {string} name
|
|
18
|
+
* @return {Command|undefined}
|
|
19
|
+
*/
|
|
20
|
+
get(name) {
|
|
21
|
+
return this.#items.get(name);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @param {Command} command
|
|
26
|
+
* @return {void}
|
|
27
|
+
*/
|
|
28
|
+
set(command) {
|
|
29
|
+
if (!(command instanceof Command)) {
|
|
30
|
+
throw new TypeError('Invalid argument');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
this.#items.set(command.name, command);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param {string} tagName
|
|
38
|
+
* @return {Command|undefined}
|
|
39
|
+
*/
|
|
40
|
+
findByTagName(tagName) {
|
|
41
|
+
return Array.from(this.#items.values()).find((command) => command.tag?.name === tagName);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @param {string} name
|
|
46
|
+
* @return {void}
|
|
47
|
+
*/
|
|
48
|
+
execute(name) {
|
|
49
|
+
this.get(name).execute();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @return {void}
|
|
54
|
+
*/
|
|
55
|
+
freeze() {
|
|
56
|
+
this.#items.forEach((command) => Object.freeze(command));
|
|
57
|
+
Object.freeze(this.#items);
|
|
58
|
+
Object.freeze(this);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import Filter from './Filter.js';
|
|
2
|
+
import TagGroup from './TagGroup.js';
|
|
3
|
+
import TagName from './TagName.js';
|
|
4
|
+
|
|
5
|
+
export default class ContentFilter extends Filter {
|
|
6
|
+
/**
|
|
7
|
+
* @param {HTMLElement} element
|
|
8
|
+
* @return {void}
|
|
9
|
+
*/
|
|
10
|
+
filter(element) {
|
|
11
|
+
const tag = this.editor.tags.get(element);
|
|
12
|
+
const allowedParagraph = this.editor.tags.allowed(element, TagName.P);
|
|
13
|
+
const allowedText = tag.editable || tag.group === TagGroup.FORMAT;
|
|
14
|
+
let p = [];
|
|
15
|
+
const wrap = (ref = null) => {
|
|
16
|
+
if (allowedParagraph && p.length > 0) {
|
|
17
|
+
const newP = this.editor.dom.createElement(TagName.P, { html: p.join(' ') });
|
|
18
|
+
|
|
19
|
+
if (ref) {
|
|
20
|
+
this.editor.dom.insertBefore(newP, ref);
|
|
21
|
+
} else {
|
|
22
|
+
this.editor.dom.insertLastChild(newP, element);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
p = [];
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
Array.from(element.childNodes).forEach((child) => {
|
|
30
|
+
const text = child.textContent.trim();
|
|
31
|
+
|
|
32
|
+
if (child instanceof HTMLElement) {
|
|
33
|
+
const realChild = this.#convert(child);
|
|
34
|
+
const childTag = this.editor.tags.get(realChild);
|
|
35
|
+
|
|
36
|
+
if (childTag && this.editor.tags.allowed(element, realChild)) {
|
|
37
|
+
wrap(realChild);
|
|
38
|
+
this.#filter(realChild, childTag);
|
|
39
|
+
} else if (childTag && childTag.group === TagGroup.FORMAT && allowedParagraph) {
|
|
40
|
+
const filteredChild = this.#filter(realChild, childTag);
|
|
41
|
+
|
|
42
|
+
if (filteredChild) {
|
|
43
|
+
p.push(filteredChild.outerHTML);
|
|
44
|
+
element.removeChild(filteredChild);
|
|
45
|
+
}
|
|
46
|
+
} else if (!allowedText && text && allowedParagraph) {
|
|
47
|
+
p.push(text);
|
|
48
|
+
element.removeChild(realChild);
|
|
49
|
+
} else if (allowedText && text) {
|
|
50
|
+
element.replaceChild(this.editor.dom.createText(text), realChild);
|
|
51
|
+
} else {
|
|
52
|
+
element.removeChild(realChild);
|
|
53
|
+
}
|
|
54
|
+
} else if (child instanceof Text) {
|
|
55
|
+
if (!allowedText && text && allowedParagraph) {
|
|
56
|
+
p.push(text);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!allowedText || !text) {
|
|
60
|
+
element.removeChild(child);
|
|
61
|
+
}
|
|
62
|
+
} else if (child instanceof Node) {
|
|
63
|
+
element.removeChild(child);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
wrap();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @param {HTMLElement} element
|
|
72
|
+
* @return {HTMLElement}
|
|
73
|
+
*/
|
|
74
|
+
#convert(element) {
|
|
75
|
+
const name = this.editor.config.base.filter[element.localName];
|
|
76
|
+
|
|
77
|
+
if (!name) {
|
|
78
|
+
return element;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const convert = this.editor.dom.createElement(name, { html: element.innerHTML });
|
|
82
|
+
element.parentElement.replaceChild(convert, element);
|
|
83
|
+
|
|
84
|
+
return convert;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @param {HTMLElement} element
|
|
89
|
+
* @param {Tag} tag
|
|
90
|
+
* @return {HTMLElement|undefined}
|
|
91
|
+
*/
|
|
92
|
+
#filter(element, tag) {
|
|
93
|
+
Array.from(element.attributes).forEach(
|
|
94
|
+
(item) => !tag.attributes.includes(item.name) && element.removeAttribute(item.name)
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
if (element.hasChildNodes()) {
|
|
98
|
+
this.editor.filters.filter(element);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (element.hasChildNodes() || tag.empty) {
|
|
102
|
+
return element;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
element.parentElement?.removeChild(element);
|
|
106
|
+
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import Key from './Key.js';
|
|
2
|
+
import Listener from './Listener.js';
|
|
3
|
+
|
|
4
|
+
export default class DeletableListener extends Listener {
|
|
5
|
+
/**
|
|
6
|
+
* @param {Editor} editor
|
|
7
|
+
*/
|
|
8
|
+
constructor(editor) {
|
|
9
|
+
super(editor);
|
|
10
|
+
this.editor.root.addEventListener('insert', this);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {CustomEvent} event
|
|
15
|
+
* @param {HTMLElement} event.detail.element
|
|
16
|
+
* @return {void}
|
|
17
|
+
*/
|
|
18
|
+
insert({ detail: { element } }) {
|
|
19
|
+
if (element.hasAttribute('data-deletable')) {
|
|
20
|
+
element.addEventListener('keydown', this);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @param {KeyboardEvent} event
|
|
26
|
+
* @param {HTMLElement} event.target
|
|
27
|
+
* @return {void}
|
|
28
|
+
*/
|
|
29
|
+
keydown(event) {
|
|
30
|
+
if (event.target === event.currentTarget && Key.isEventFor(event, Key.DEL, { ctrl: true })) {
|
|
31
|
+
this.editor.dom.delete(event.target);
|
|
32
|
+
event.preventDefault();
|
|
33
|
+
event.stopPropagation();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import Command from './Command.js';
|
|
2
|
+
|
|
3
|
+
export default class DeleteCommand extends Command {
|
|
4
|
+
/**
|
|
5
|
+
* @param {Editor} editor
|
|
6
|
+
*/
|
|
7
|
+
constructor(editor) {
|
|
8
|
+
super(editor, 'delete');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @return {void}
|
|
13
|
+
*/
|
|
14
|
+
execute() {
|
|
15
|
+
const element = this.editor.dom.getActiveElement();
|
|
16
|
+
element?.hasAttribute('data-deletable') && this.editor.dom.delete(element);
|
|
17
|
+
}
|
|
18
|
+
}
|
package/base/Dialog.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import Editor from './Editor.js';
|
|
2
|
+
import FormCreator from './FormCreator.js';
|
|
3
|
+
import TagName from './TagName.js';
|
|
4
|
+
import { is, isOptString, isString } from './util.js';
|
|
5
|
+
|
|
6
|
+
export default class Dialog {
|
|
7
|
+
/**
|
|
8
|
+
* @type {Editor}
|
|
9
|
+
*/
|
|
10
|
+
#editor;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @return {Editor}
|
|
14
|
+
*/
|
|
15
|
+
get editor() {
|
|
16
|
+
return this.#editor;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @type {string}
|
|
21
|
+
*/
|
|
22
|
+
#name;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @return {string}
|
|
26
|
+
*/
|
|
27
|
+
get name() {
|
|
28
|
+
return this.#name;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @type {string|undefined}
|
|
33
|
+
*/
|
|
34
|
+
#browserUrl;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @return {string|undefined}
|
|
38
|
+
*/
|
|
39
|
+
get browserUrl() {
|
|
40
|
+
return this.#browserUrl;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @type {FormCreator|undefined}
|
|
45
|
+
*/
|
|
46
|
+
#formCreator;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @type {FormCreator|undefined}
|
|
50
|
+
*/
|
|
51
|
+
get formCreator() {
|
|
52
|
+
return this.#formCreator;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @param {Editor} editor
|
|
57
|
+
* @param {string} name
|
|
58
|
+
* @param {string|undefined} browserUrl
|
|
59
|
+
*/
|
|
60
|
+
constructor(editor, name, browserUrl = undefined) {
|
|
61
|
+
if (!(editor instanceof Editor) || !isString(name) || !isOptString(browserUrl)) {
|
|
62
|
+
throw new TypeError('Invalid argument');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
this.#editor = editor;
|
|
66
|
+
this.#name = name;
|
|
67
|
+
this.#browserUrl = browserUrl;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Opens a dialog or a browser window if browser URL is set
|
|
72
|
+
*
|
|
73
|
+
* @param {function} save
|
|
74
|
+
* @param {Object} [attributes = {}]
|
|
75
|
+
* @return {void}
|
|
76
|
+
*/
|
|
77
|
+
open(save, attributes = {}) {
|
|
78
|
+
this.browserUrl ? this.#openBrowser(save, attributes) : this.#openDialog(save, attributes);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Translates given string
|
|
83
|
+
*
|
|
84
|
+
* @protected
|
|
85
|
+
* @param {string} key
|
|
86
|
+
* @return {string}
|
|
87
|
+
*/
|
|
88
|
+
_(key) {
|
|
89
|
+
return this.editor.translate(key);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @protected
|
|
94
|
+
* @return {void}
|
|
95
|
+
*/
|
|
96
|
+
_prepareForm() {
|
|
97
|
+
throw new Error('Not implemented');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Opens a browser window and registers a listener for communication between editor and browser windows
|
|
102
|
+
*
|
|
103
|
+
* @param {function} save
|
|
104
|
+
* @param {Object} [attributes = {}]
|
|
105
|
+
* @return {void}
|
|
106
|
+
*/
|
|
107
|
+
#openBrowser(save, attributes = {}) {
|
|
108
|
+
this.editor.dom.open({ url: this.browserUrl, name: this.name, call: save, params: attributes });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Opens a dialog and executes given callback on save
|
|
113
|
+
*
|
|
114
|
+
* @param {function} save
|
|
115
|
+
* @param {Object} [attributes = {}]
|
|
116
|
+
* @return {void}
|
|
117
|
+
*/
|
|
118
|
+
#openDialog(save, attributes = {}) {
|
|
119
|
+
const range = this.editor.dom.getRange();
|
|
120
|
+
this.#cleanup();
|
|
121
|
+
|
|
122
|
+
/** @type {HTMLDialogElement} */
|
|
123
|
+
const dialog = this.editor.dom.createElement(TagName.DIALOG);
|
|
124
|
+
dialog.addEventListener('click', (event) => event.target === dialog && dialog.close());
|
|
125
|
+
dialog.addEventListener('close', () => {
|
|
126
|
+
range && this.editor.dom.setRange(range);
|
|
127
|
+
dialog.returnValue && save(JSON.parse(dialog.returnValue));
|
|
128
|
+
this.#cleanup();
|
|
129
|
+
});
|
|
130
|
+
this.editor.dom.insertLastChild(dialog, this.editor.element);
|
|
131
|
+
|
|
132
|
+
this.#formCreator = new FormCreator(this.editor);
|
|
133
|
+
this._prepareForm();
|
|
134
|
+
const form = this.formCreator.form;
|
|
135
|
+
form.addEventListener('submit', () => dialog.close(JSON.stringify(Object.fromEntries(new FormData(form)))));
|
|
136
|
+
form.addEventListener('reset', () => dialog.close());
|
|
137
|
+
new FormData(form).forEach((val, key) => is(attributes[key]) && (form.elements[key].value = attributes[key]));
|
|
138
|
+
this.editor.dom.insertLastChild(form, dialog);
|
|
139
|
+
|
|
140
|
+
dialog.showModal();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Removes all existing editor dialogs
|
|
145
|
+
*
|
|
146
|
+
* @return {void}
|
|
147
|
+
*/
|
|
148
|
+
#cleanup() {
|
|
149
|
+
Array.from(this.editor.element.getElementsByTagName(TagName.DIALOG)).forEach((item) => {
|
|
150
|
+
item.parentElement.removeChild(item);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import Dialog from './Dialog.js';
|
|
2
|
+
|
|
3
|
+
export default class DialogManager {
|
|
4
|
+
/**
|
|
5
|
+
* @type {Map<string, Dialog>}
|
|
6
|
+
*/
|
|
7
|
+
#items = new Map();
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {Dialog[]} [dialogs = []]
|
|
11
|
+
*/
|
|
12
|
+
constructor(dialogs = []) {
|
|
13
|
+
dialogs.forEach((dialog) => this.set(dialog));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {string} name
|
|
18
|
+
* @return {Dialog|undefined}
|
|
19
|
+
*/
|
|
20
|
+
get(name) {
|
|
21
|
+
return this.#items.get(name);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @param {Dialog} dialog
|
|
26
|
+
* @return {void}
|
|
27
|
+
*/
|
|
28
|
+
set(dialog) {
|
|
29
|
+
if (!(dialog instanceof Dialog)) {
|
|
30
|
+
throw new TypeError('Invalid argument');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
this.#items.set(dialog.name, dialog);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @return {void}
|
|
38
|
+
*/
|
|
39
|
+
freeze() {
|
|
40
|
+
this.#items.forEach((dialog) => Object.freeze(dialog));
|
|
41
|
+
Object.freeze(this.#items);
|
|
42
|
+
Object.freeze(this);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import TagName from './TagName.js';
|
|
2
|
+
import { isFunction, isOptHtml, isString } from './util.js';
|
|
3
|
+
|
|
4
|
+
export default class Dispatcher {
|
|
5
|
+
/**
|
|
6
|
+
* @type {HTMLElement}
|
|
7
|
+
*/
|
|
8
|
+
#element;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param {HTMLElement} element
|
|
12
|
+
*/
|
|
13
|
+
constructor(element) {
|
|
14
|
+
if (!(element instanceof HTMLElement)) {
|
|
15
|
+
throw new TypeError('Invalid argument');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
this.#element = element;
|
|
19
|
+
this.register(this.#observe.bind(this));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {string} type
|
|
24
|
+
* @param {HTMLElement|undefined} [element = undefined]
|
|
25
|
+
* @param {HTMLElement|undefined} [target = undefined]
|
|
26
|
+
* @return {void}
|
|
27
|
+
*/
|
|
28
|
+
dispatch(type, element = undefined, target = undefined) {
|
|
29
|
+
if (!isString(type) || !isOptHtml(element) || !isOptHtml(target)) {
|
|
30
|
+
throw new TypeError('Invalid argument');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
this.#element.dispatchEvent(new CustomEvent(type, { detail: { element, target } }));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Registers a mutation observer on managed element
|
|
38
|
+
*
|
|
39
|
+
* @param {function} call
|
|
40
|
+
* @param {MutationObserverInit} [opts = {childList: true, subtree: true}]
|
|
41
|
+
* @return {void}
|
|
42
|
+
*/
|
|
43
|
+
register(call, opts = { childList: true, subtree: true }) {
|
|
44
|
+
if (!isFunction(call)) {
|
|
45
|
+
throw new TypeError('Invalid argument');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const observer = new MutationObserver(call);
|
|
49
|
+
observer.observe(this.#element, opts);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Dispatches a mutation event on managed element
|
|
54
|
+
*
|
|
55
|
+
* @param {string} type
|
|
56
|
+
* @param {Element} element
|
|
57
|
+
* @param {Node} target
|
|
58
|
+
* @return {void}
|
|
59
|
+
*/
|
|
60
|
+
#dispatch(type, element, target) {
|
|
61
|
+
if (element instanceof HTMLElement && target instanceof HTMLElement) {
|
|
62
|
+
this.dispatch(type, element, target);
|
|
63
|
+
this.dispatch(`${type}${element.localName.replace('-', '')}`, element, target);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Observes mutations on managed element
|
|
69
|
+
*
|
|
70
|
+
* @param {MutationRecord[]} records
|
|
71
|
+
* @return {void}
|
|
72
|
+
*/
|
|
73
|
+
#observe(records) {
|
|
74
|
+
records.forEach((record) => {
|
|
75
|
+
record.addedNodes.forEach((element) => {
|
|
76
|
+
if (element instanceof HTMLElement) {
|
|
77
|
+
this.#dispatch('insert', element, record.target);
|
|
78
|
+
Array.from(element.getElementsByTagName(TagName.ALL)).forEach((item) =>
|
|
79
|
+
this.#dispatch('insert', item, record.target)
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
record.removedNodes.forEach(
|
|
84
|
+
(element) => element instanceof HTMLElement && this.#dispatch('delete', element, record.target)
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|