@aquera/nile-elements 0.1.67-beta-1.2 → 0.1.67-beta-1.3
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/demo/index.html +9 -9
- package/dist/index.js +31 -29
- package/dist/nile-rich-text-editor/nile-rich-text-editor.cjs.js +1 -1
- package/dist/nile-rich-text-editor/nile-rich-text-editor.cjs.js.map +1 -1
- package/dist/nile-rich-text-editor/nile-rich-text-editor.css.cjs.js +1 -1
- package/dist/nile-rich-text-editor/nile-rich-text-editor.css.cjs.js.map +1 -1
- package/dist/nile-rich-text-editor/nile-rich-text-editor.css.esm.js +3 -1
- package/dist/nile-rich-text-editor/nile-rich-text-editor.esm.js +1 -1
- package/dist/nile-rich-text-editor/utils.cjs.js +1 -1
- package/dist/nile-rich-text-editor/utils.cjs.js.map +1 -1
- package/dist/nile-rich-text-editor/utils.esm.js +1 -1
- package/dist/src/nile-rich-text-editor/nile-rich-text-editor.css.js +3 -1
- package/dist/src/nile-rich-text-editor/nile-rich-text-editor.css.js.map +1 -1
- package/dist/src/nile-rich-text-editor/nile-rich-text-editor.d.ts +1 -0
- package/dist/src/nile-rich-text-editor/nile-rich-text-editor.js +64 -8
- package/dist/src/nile-rich-text-editor/nile-rich-text-editor.js.map +1 -1
- package/dist/src/nile-rich-text-editor/utils.d.ts +2 -2
- package/dist/src/nile-rich-text-editor/utils.js +181 -27
- package/dist/src/nile-rich-text-editor/utils.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/nile-rich-text-editor/nile-rich-text-editor.css.ts +3 -1
- package/src/nile-rich-text-editor/nile-rich-text-editor.ts +89 -11
- package/src/nile-rich-text-editor/utils.ts +248 -26
- package/vscode-html-custom-data.json +1 -1
package/package.json
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
"description": "Webcomponent nile-elements following open-wc recommendations",
|
4
4
|
"license": "MIT",
|
5
5
|
"author": "nile-elements",
|
6
|
-
"version": "0.1.67-beta-1.
|
6
|
+
"version": "0.1.67-beta-1.3",
|
7
7
|
"main": "dist/src/index.js",
|
8
8
|
"type": "module",
|
9
9
|
"module": "dist/src/index.js",
|
@@ -42,7 +42,9 @@ nile-rte-toolbar-item > nile-button::part(base) {
|
|
42
42
|
.editor h5 { font-size:0.83em }
|
43
43
|
.editor h6 { font-size:0.67em }
|
44
44
|
|
45
|
-
.editor { min-height:160px; padding:12px; border:1px solid #e5e7eb; border-radius:0 0 8px 8px; background:#fff; outline:none;
|
45
|
+
.editor { min-height:160px; padding:12px; border:1px solid #e5e7eb; border-radius:0 0 8px 8px; background:#fff; outline:none; white-space: pre-wrap;
|
46
|
+
tab-size: 4;
|
47
|
+
-moz-tab-size: 4; }
|
46
48
|
nile-rte-preview { display:block; margin-top:10px; padding:10px; border:1px dashed #cbd5e1; border-radius:8px; background:#fafafa; }
|
47
49
|
|
48
50
|
.rte-color-trigger {
|
@@ -159,7 +159,7 @@ private bgSwatchEl: HTMLElement | null = null;
|
|
159
159
|
private ensureEditor() {
|
160
160
|
this.editorEl = this.querySelector('.editor') as HTMLElement;
|
161
161
|
if (!this.editorEl) {
|
162
|
-
const editor = document.createElement('
|
162
|
+
const editor = document.createElement('article');
|
163
163
|
editor.className = 'editor';
|
164
164
|
editor.setAttribute('contenteditable','true');
|
165
165
|
if (this.toolbarEl?.nextSibling) {
|
@@ -178,14 +178,65 @@ private bgSwatchEl: HTMLElement | null = null;
|
|
178
178
|
}
|
179
179
|
|
180
180
|
private wireEditor() {
|
181
|
-
// Input & selection preservation
|
182
181
|
this.editorEl.addEventListener('input', () => {
|
183
182
|
this.ensureAtLeastOneParagraph();
|
184
183
|
this.updateContent();
|
185
184
|
});
|
186
185
|
this.editorEl.addEventListener('mouseup', () => this.saveSelection());
|
187
|
-
this.editorEl.addEventListener('keyup', () => this.saveSelection());
|
186
|
+
this.editorEl.addEventListener('keyup', () => this.saveSelection());
|
187
|
+
this.editorEl.addEventListener('keydown', this.onEditorKeydown);
|
188
188
|
}
|
189
|
+
|
190
|
+
|
191
|
+
private onEditorKeydown = (e: KeyboardEvent) => {
|
192
|
+
if (e.key !== 'Tab') return;
|
193
|
+
|
194
|
+
e.preventDefault();
|
195
|
+
this.focusAndRestore();
|
196
|
+
|
197
|
+
const sel = window.getSelection();
|
198
|
+
if (!sel || sel.rangeCount === 0) return;
|
199
|
+
const range = sel.getRangeAt(0);
|
200
|
+
|
201
|
+
|
202
|
+
if (e.shiftKey) {
|
203
|
+
if (range.collapsed && range.startContainer.nodeType === Node.TEXT_NODE) {
|
204
|
+
const t = range.startContainer as Text;
|
205
|
+
const off = range.startOffset;
|
206
|
+
const before = t.data.slice(0, off);
|
207
|
+
|
208
|
+
|
209
|
+
const removed = before.replace(/(\t|[ \u00a0]{2})$/, '');
|
210
|
+
if (removed.length !== before.length) {
|
211
|
+
t.data = removed + t.data.slice(off);
|
212
|
+
const r = document.createRange();
|
213
|
+
r.setStart(t, removed.length);
|
214
|
+
r.collapse(true);
|
215
|
+
sel.removeAllRanges();
|
216
|
+
sel.addRange(r);
|
217
|
+
this.updateContent();
|
218
|
+
this.updateToolbarState();
|
219
|
+
}
|
220
|
+
}
|
221
|
+
return;
|
222
|
+
}
|
223
|
+
|
224
|
+
|
225
|
+
range.deleteContents();
|
226
|
+
const tabNode = document.createTextNode('\t');
|
227
|
+
range.insertNode(tabNode);
|
228
|
+
|
229
|
+
|
230
|
+
const r = document.createRange();
|
231
|
+
r.setStartAfter(tabNode);
|
232
|
+
r.collapse(true);
|
233
|
+
sel.removeAllRanges();
|
234
|
+
sel.addRange(r);
|
235
|
+
|
236
|
+
this.updateContent();
|
237
|
+
this.updateToolbarState();
|
238
|
+
};
|
239
|
+
|
189
240
|
|
190
241
|
private wireAuthoredToolbar(tb: HTMLElement) {
|
191
242
|
|
@@ -239,7 +290,6 @@ private bgSwatchEl: HTMLElement | null = null;
|
|
239
290
|
}
|
240
291
|
child.innerHTML = '';
|
241
292
|
} else {
|
242
|
-
// Author provided custom content (could be a <nile-icon> already)
|
243
293
|
btn.innerHTML = child.innerHTML;
|
244
294
|
child.innerHTML = '';
|
245
295
|
}
|
@@ -252,12 +302,12 @@ private bgSwatchEl: HTMLElement | null = null;
|
|
252
302
|
btn.addEventListener('mousedown', e => e.preventDefault());
|
253
303
|
btn.addEventListener('click', () => this.onToolbarCommand(cmd));
|
254
304
|
|
255
|
-
|
305
|
+
|
256
306
|
const arr = this.buttonMap.get(cmd) ?? [];
|
257
307
|
arr.push(btn);
|
258
308
|
this.buttonMap.set(cmd, arr);
|
259
309
|
|
260
|
-
return;
|
310
|
+
return;
|
261
311
|
}
|
262
312
|
if (tag === 'nile-rte-select') {
|
263
313
|
const type = child.getAttribute('type') || '';
|
@@ -621,15 +671,43 @@ private ensureAtLeastOneParagraph() {
|
|
621
671
|
private updateContent() {
|
622
672
|
if (!this.editorEl) return;
|
623
673
|
this.ensureAtLeastOneParagraph();
|
624
|
-
|
625
|
-
|
626
|
-
|
674
|
+
|
675
|
+
// Clone content for safe manipulation
|
676
|
+
const clone = this.editorEl.cloneNode(true) as HTMLElement;
|
677
|
+
|
678
|
+
// Walk the original DOM and the clone in parallel
|
679
|
+
const origWalker = document.createTreeWalker(this.editorEl, NodeFilter.SHOW_ELEMENT);
|
680
|
+
const cloneWalker = document.createTreeWalker(clone, NodeFilter.SHOW_ELEMENT);
|
681
|
+
|
682
|
+
while (origWalker.nextNode() && cloneWalker.nextNode()) {
|
683
|
+
const origEl = origWalker.currentNode as HTMLElement;
|
684
|
+
const cloneEl = cloneWalker.currentNode as HTMLElement;
|
685
|
+
const computed = window.getComputedStyle(origEl);
|
686
|
+
|
687
|
+
// Dump *all* computed styles into a single inline style attribute
|
688
|
+
const cssText = Array.from(computed)
|
689
|
+
.map(prop => `${prop}:${computed.getPropertyValue(prop)}`)
|
690
|
+
.join(';');
|
691
|
+
|
692
|
+
cloneEl.setAttribute('style', cssText);
|
693
|
+
}
|
694
|
+
|
695
|
+
// Store the fully inlined HTML
|
696
|
+
this.content = clone.innerHTML;
|
697
|
+
|
698
|
+
// Live preview updates
|
627
699
|
if (this.previewEl) this.previewEl.innerHTML = this.content;
|
628
|
-
|
700
|
+
|
701
|
+
// Emit event with full inline styles
|
629
702
|
this.dispatchEvent(new CustomEvent('content-changed', {
|
630
|
-
detail: { content: this.content },
|
703
|
+
detail: { content: this.content },
|
704
|
+
bubbles: true,
|
705
|
+
composed: true
|
631
706
|
}));
|
632
707
|
}
|
708
|
+
|
709
|
+
|
710
|
+
|
633
711
|
}
|
634
712
|
|
635
713
|
declare global {
|
@@ -103,39 +103,261 @@ export function closestBlock(node: Node | null, root: HTMLElement): HTMLElement
|
|
103
103
|
surroundInline(range, 'span', { style: `font-family:${family}` });
|
104
104
|
}
|
105
105
|
|
106
|
-
|
106
|
+
|
107
|
+
function enclosingStyledSpan(
|
108
|
+
editor: HTMLElement,
|
109
|
+
node: Node | null,
|
110
|
+
dataAttr: 'data-rte-color' | 'data-rte-bg'
|
111
|
+
): HTMLSpanElement | null {
|
112
|
+
while (node && node !== editor) {
|
113
|
+
if (node instanceof HTMLSpanElement && node.hasAttribute(dataAttr)) {
|
114
|
+
return node;
|
115
|
+
}
|
116
|
+
node = node.parentNode;
|
117
|
+
}
|
118
|
+
return null;
|
119
|
+
}
|
120
|
+
|
121
|
+
|
122
|
+
function applyInlineStyle(
|
123
|
+
editor: HTMLElement,
|
124
|
+
cssProp: 'color' | 'backgroundColor',
|
125
|
+
value: string,
|
126
|
+
dataAttr: 'data-rte-color' | 'data-rte-bg'
|
127
|
+
) {
|
107
128
|
const sel = window.getSelection();
|
108
129
|
if (!sel || sel.rangeCount === 0) return;
|
109
|
-
const
|
110
|
-
if (!
|
111
|
-
|
112
|
-
const span = document.createElement('span');
|
113
|
-
span.style.backgroundColor = color;
|
114
|
-
span.appendChild(range.extractContents());
|
115
|
-
range.insertNode(span);
|
116
|
-
const after = document.createRange();
|
117
|
-
after.setStartAfter(span);
|
118
|
-
after.collapse(true);
|
119
|
-
sel.removeAllRanges();
|
120
|
-
sel.addRange(after);
|
121
|
-
}
|
130
|
+
const r0 = sel.getRangeAt(0);
|
131
|
+
if (!editor.contains(r0.commonAncestorContainer)) return;
|
122
132
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
range.insertNode(span);
|
132
|
-
const r = document.createRange(); r.setStart(span.firstChild!, 1); r.collapse(true);
|
133
|
-
sel.removeAllRanges(); sel.addRange(r);
|
133
|
+
const range = r0.cloneRange();
|
134
|
+
|
135
|
+
|
136
|
+
if (range.collapsed) {
|
137
|
+
const enclosing = enclosingStyledSpan(editor, range.startContainer, dataAttr);
|
138
|
+
if (enclosing) {
|
139
|
+
(enclosing.style as any)[cssProp] = value;
|
140
|
+
mergeAdjacentStyledSpans(editor, dataAttr, cssProp);
|
134
141
|
return;
|
135
142
|
}
|
136
|
-
|
143
|
+
|
144
|
+
const s = document.createElement('span');
|
145
|
+
s.setAttribute(dataAttr, '1');
|
146
|
+
(s.style as any)[cssProp] = value;
|
147
|
+
s.appendChild(document.createTextNode('\u200B'));
|
148
|
+
range.insertNode(s);
|
149
|
+
|
150
|
+
|
151
|
+
const caret = document.createRange();
|
152
|
+
caret.setStart(s.firstChild!, 1);
|
153
|
+
caret.collapse(true);
|
154
|
+
sel.removeAllRanges(); sel.addRange(caret);
|
155
|
+
|
156
|
+
mergeAdjacentStyledSpans(editor, dataAttr, cssProp);
|
157
|
+
return;
|
137
158
|
}
|
159
|
+
|
160
|
+
|
161
|
+
const leftEdge = enclosingStyledSpan(editor, range.startContainer, dataAttr);
|
162
|
+
const rightEdge = enclosingStyledSpan(editor, range.endContainer, dataAttr);
|
163
|
+
if (leftEdge && leftEdge === rightEdge) {
|
164
|
+
|
165
|
+
if (rangeCoversWholeNode(range, leftEdge)) {
|
166
|
+
(leftEdge.style as any)[cssProp] = value;
|
167
|
+
} else {
|
168
|
+
|
169
|
+
const mid = splitAndRecolorWithinSpan(
|
170
|
+
leftEdge,
|
171
|
+
range,
|
172
|
+
dataAttr,
|
173
|
+
cssProp,
|
174
|
+
value
|
175
|
+
);
|
138
176
|
|
177
|
+
|
178
|
+
const sel = window.getSelection();
|
179
|
+
const r = document.createRange();
|
180
|
+
r.selectNodeContents(mid);
|
181
|
+
sel?.removeAllRanges();
|
182
|
+
sel?.addRange(r);
|
183
|
+
}
|
184
|
+
mergeAdjacentStyledSpans(editor, dataAttr, cssProp);
|
185
|
+
return;
|
186
|
+
}
|
187
|
+
|
188
|
+
|
189
|
+
const commonEl = (() => {
|
190
|
+
let n: Node | null = range.commonAncestorContainer;
|
191
|
+
while (n && !(n instanceof HTMLElement)) n = n.parentNode;
|
192
|
+
return n as HTMLElement | null;
|
193
|
+
})();
|
194
|
+
|
195
|
+
const walker = document.createTreeWalker(
|
196
|
+
commonEl || editor,
|
197
|
+
NodeFilter.SHOW_TEXT,
|
198
|
+
{
|
199
|
+
acceptNode: (n) => {
|
200
|
+
if (!n.nodeValue || !n.nodeValue.trim()) return NodeFilter.FILTER_REJECT;
|
201
|
+
const nodeRange = document.createRange();
|
202
|
+
nodeRange.selectNodeContents(n);
|
203
|
+
const intersects =
|
204
|
+
range.compareBoundaryPoints(Range.END_TO_START, nodeRange) < 0 &&
|
205
|
+
range.compareBoundaryPoints(Range.START_TO_END, nodeRange) > 0;
|
206
|
+
return intersects ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
|
207
|
+
}
|
208
|
+
}
|
209
|
+
);
|
210
|
+
|
211
|
+
const toProcess: Text[] = [];
|
212
|
+
let t: Node | null;
|
213
|
+
while ((t = walker.nextNode())) toProcess.push(t as Text);
|
214
|
+
|
215
|
+
toProcess.forEach((text) => {
|
216
|
+
|
217
|
+
let start = 0, end = text.length;
|
218
|
+
if (text === range.startContainer) start = range.startOffset;
|
219
|
+
if (text === range.endContainer) end = range.endOffset;
|
220
|
+
if (start > 0) text = text.splitText(start);
|
221
|
+
if (end < text.length) text.splitText(end);
|
222
|
+
|
223
|
+
// If this slice already sits in a styled span → update it, don’t nest.
|
224
|
+
const existing = enclosingStyledSpan(editor, text, dataAttr);
|
225
|
+
if (existing) {
|
226
|
+
(existing.style as any)[cssProp] = value;
|
227
|
+
return;
|
228
|
+
}
|
229
|
+
|
230
|
+
// Create a new, single-purpose span
|
231
|
+
const span = document.createElement('span');
|
232
|
+
span.setAttribute(dataAttr, '1');
|
233
|
+
(span.style as any)[cssProp] = value;
|
234
|
+
const parent = text.parentElement!;
|
235
|
+
parent.replaceChild(span, text);
|
236
|
+
span.appendChild(text);
|
237
|
+
});
|
238
|
+
|
239
|
+
mergeAdjacentStyledSpans(editor, dataAttr, cssProp);
|
240
|
+
|
241
|
+
// restore selection
|
242
|
+
sel.removeAllRanges(); sel.addRange(range);
|
243
|
+
}
|
244
|
+
|
245
|
+
// Is the range covering the entire node's contents?
|
246
|
+
function rangeCoversWholeNode(range: Range, node: Node): boolean {
|
247
|
+
const all = document.createRange();
|
248
|
+
all.selectNodeContents(node);
|
249
|
+
return (
|
250
|
+
range.compareBoundaryPoints(Range.START_TO_START, all) <= 0 &&
|
251
|
+
range.compareBoundaryPoints(Range.END_TO_END, all) >= 0
|
252
|
+
);
|
253
|
+
}
|
254
|
+
|
255
|
+
function hasRangeContent(r: Range): boolean {
|
256
|
+
if (r.collapsed) return false;
|
257
|
+
const text = r.cloneContents().textContent || '';
|
258
|
+
return text.length > 0;
|
259
|
+
}
|
260
|
+
|
261
|
+
// Split one styled span into [left][middle][right]; recolor only middle
|
262
|
+
function splitAndRecolorWithinSpan(
|
263
|
+
span: HTMLSpanElement,
|
264
|
+
range: Range,
|
265
|
+
dataAttr: 'data-rte-color' | 'data-rte-bg',
|
266
|
+
cssProp: 'color' | 'backgroundColor',
|
267
|
+
newValue: string
|
268
|
+
): HTMLSpanElement {
|
269
|
+
const oldValue = (span.style as any)[cssProp];
|
270
|
+
|
271
|
+
const left = document.createRange();
|
272
|
+
left.setStart(span, 0);
|
273
|
+
left.setEnd(range.startContainer, range.startOffset);
|
274
|
+
|
275
|
+
const right = document.createRange();
|
276
|
+
right.setStart(range.endContainer, range.endOffset);
|
277
|
+
right.setEnd(span, span.childNodes.length);
|
278
|
+
|
279
|
+
// Build replacement fragment
|
280
|
+
const frag = document.createDocumentFragment();
|
281
|
+
|
282
|
+
// helper to make a styled clone shell
|
283
|
+
const makeShell = (val: string) => {
|
284
|
+
const s = document.createElement('span');
|
285
|
+
s.setAttribute(dataAttr, '1');
|
286
|
+
(s.style as any)[cssProp] = val;
|
287
|
+
return s;
|
288
|
+
};
|
289
|
+
|
290
|
+
if (hasRangeContent(left)) {
|
291
|
+
const sLeft = makeShell(oldValue);
|
292
|
+
sLeft.appendChild(left.cloneContents());
|
293
|
+
frag.appendChild(sLeft);
|
294
|
+
}
|
295
|
+
|
296
|
+
const mid = makeShell(newValue);
|
297
|
+
mid.appendChild(range.cloneContents());
|
298
|
+
frag.appendChild(mid);
|
299
|
+
|
300
|
+
if (hasRangeContent(right)) {
|
301
|
+
const sRight = makeShell(oldValue);
|
302
|
+
sRight.appendChild(right.cloneContents());
|
303
|
+
frag.appendChild(sRight);
|
304
|
+
}
|
305
|
+
|
306
|
+
// Replace original span
|
307
|
+
span.replaceWith(frag);
|
308
|
+
return mid; // return the middle span so caller can restore selection
|
309
|
+
}
|
310
|
+
|
311
|
+
|
312
|
+
function mergeAdjacentStyledSpans(
|
313
|
+
root: HTMLElement,
|
314
|
+
dataAttr: 'data-rte-color' | 'data-rte-bg',
|
315
|
+
cssProp: 'color' | 'backgroundColor'
|
316
|
+
) {
|
317
|
+
const spans = Array.from(root.querySelectorAll<HTMLSpanElement>(`span[${dataAttr}]`));
|
318
|
+
|
319
|
+
const valOf = (el: HTMLElement) => (el.style as any)[cssProp];
|
320
|
+
|
321
|
+
spans.forEach((s) => {
|
322
|
+
|
323
|
+
const nested = Array.from(s.querySelectorAll<HTMLSpanElement>(`span[${dataAttr}]`));
|
324
|
+
nested.forEach((child) => {
|
325
|
+
if (valOf(child) === valOf(s)) {
|
326
|
+
while (child.firstChild) s.insertBefore(child.firstChild, child);
|
327
|
+
child.remove();
|
328
|
+
}
|
329
|
+
});
|
330
|
+
|
331
|
+
|
332
|
+
const prev = s.previousSibling;
|
333
|
+
if (prev instanceof HTMLSpanElement &&
|
334
|
+
prev.hasAttribute(dataAttr) &&
|
335
|
+
valOf(prev) === valOf(s)) {
|
336
|
+
while (s.firstChild) prev.appendChild(s.firstChild);
|
337
|
+
s.remove();
|
338
|
+
return; // s is gone, next checks not needed
|
339
|
+
}
|
340
|
+
|
341
|
+
// 3) Merge with next sibling if identical
|
342
|
+
const next = s.nextSibling;
|
343
|
+
if (next instanceof HTMLSpanElement &&
|
344
|
+
next.hasAttribute(dataAttr) &&
|
345
|
+
valOf(next) === valOf(s)) {
|
346
|
+
while (next.firstChild) s.appendChild(next.firstChild);
|
347
|
+
next.remove();
|
348
|
+
}
|
349
|
+
});
|
350
|
+
}
|
351
|
+
|
352
|
+
|
353
|
+
export function setForeColor(editor: HTMLElement, hex: string) {
|
354
|
+
applyInlineStyle(editor, 'color', hex, 'data-rte-color');
|
355
|
+
}
|
356
|
+
export function setBackColor(editor: HTMLElement, hex: string) {
|
357
|
+
applyInlineStyle(editor, 'backgroundColor', hex, 'data-rte-bg');
|
358
|
+
}
|
359
|
+
|
360
|
+
|
139
361
|
export function toggleList(root: HTMLElement, kind: 'ul'|'ol') {
|
140
362
|
const sel = document.getSelection(); if (!sel || sel.rangeCount === 0) return;
|
141
363
|
const range = sel.getRangeAt(0);
|
@@ -2820,7 +2820,7 @@
|
|
2820
2820
|
},
|
2821
2821
|
{
|
2822
2822
|
"name": "nile-rich-text-editor",
|
2823
|
-
"description": "Events:\n\n * `content-changed` {`CustomEvent<{ content: string; }>`} - \n\nAttributes:\n\n * `value` {`string`} - Initial HTML content\n\n * `mentions` - Optional mentions config (can also be on <nile-rte-mentions mentions=\"...\">)\n\nProperties:\n\n * `value` {`string`} - Initial HTML content\n\n * `mentions` - Optional mentions config (can also be on <nile-rte-mentions mentions=\"...\">)\n\n * `content` {`string`} - \n\n * `editorEl` {`HTMLElement`} - \n\n * `previewEl` {`HTMLElement | null`} - \n\n * `toolbarEl` {`HTMLElement | null`} - \n\n * `lastRange` {`Range | null`} - \n\n * `buttonMap` {`Map<string, HTMLElement[]>`} - \n\n * `headingSelect` {`HTMLSelectElement | null`} - \n\n * `fontSelect` {`HTMLSelectElement | null`} - \n\n * `colorInput` {`HTMLInputElement | null`} - \n\n * `bgColorInput` {`HTMLInputElement | null`} - \n\n * `colorSwatchEl` {`HTMLElement | null`} - \n\n * `bgSwatchEl` {`HTMLElement | null`} - \n\n * `mentionsEl` {`HTMLElement | null`} - \n\n * `onSelectionChange` - ",
|
2823
|
+
"description": "Events:\n\n * `content-changed` {`CustomEvent<{ content: string; }>`} - \n\nAttributes:\n\n * `value` {`string`} - Initial HTML content\n\n * `mentions` - Optional mentions config (can also be on <nile-rte-mentions mentions=\"...\">)\n\nProperties:\n\n * `value` {`string`} - Initial HTML content\n\n * `mentions` - Optional mentions config (can also be on <nile-rte-mentions mentions=\"...\">)\n\n * `content` {`string`} - \n\n * `editorEl` {`HTMLElement`} - \n\n * `previewEl` {`HTMLElement | null`} - \n\n * `toolbarEl` {`HTMLElement | null`} - \n\n * `lastRange` {`Range | null`} - \n\n * `buttonMap` {`Map<string, HTMLElement[]>`} - \n\n * `headingSelect` {`HTMLSelectElement | null`} - \n\n * `fontSelect` {`HTMLSelectElement | null`} - \n\n * `colorInput` {`HTMLInputElement | null`} - \n\n * `bgColorInput` {`HTMLInputElement | null`} - \n\n * `colorSwatchEl` {`HTMLElement | null`} - \n\n * `bgSwatchEl` {`HTMLElement | null`} - \n\n * `mentionsEl` {`HTMLElement | null`} - \n\n * `onEditorKeydown` - \n\n * `onSelectionChange` - ",
|
2824
2824
|
"attributes": [
|
2825
2825
|
{
|
2826
2826
|
"name": "value",
|