@vaadin/rich-text-editor 25.0.0-alpha8 → 25.0.0-beta1

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.
@@ -9,40 +9,33 @@
9
9
  * license.
10
10
  */
11
11
  import '../vendor/vaadin-quill.js';
12
+ import { isKeyboardActive } from '@vaadin/a11y-base/src/focus-utils.js';
12
13
  import { timeOut } from '@vaadin/component-base/src/async.js';
13
14
  import { Debouncer } from '@vaadin/component-base/src/debounce.js';
14
15
  import { I18nMixin } from '@vaadin/component-base/src/i18n-mixin.js';
15
16
 
16
17
  const Quill = window.Quill;
17
18
 
18
- // Workaround for text disappearing when accepting spellcheck suggestion
19
- // See https://github.com/quilljs/quill/issues/2096#issuecomment-399576957
20
- const Inline = Quill.import('blots/inline');
21
-
22
- class CustomColor extends Inline {
23
- constructor(domNode, value) {
24
- super(domNode, value);
25
-
26
- // Map <font> properties
27
- domNode.style.color = domNode.color;
28
-
29
- const span = this.replaceWith(new Inline(Inline.create()));
30
-
31
- span.children.forEach((child) => {
32
- if (child.attributes) child.attributes.copy(span);
33
- if (child.unwrap) child.unwrap();
34
- });
35
-
36
- this.remove();
37
-
38
- return span; // eslint-disable-line no-constructor-return
19
+ // There are some issues e.g. `spellcheck="false"` not preserved
20
+ // See https://github.com/slab/quill/issues/4289
21
+ // Fix to add `spellcheck="false"` on the `<pre>` tag removed by Quill
22
+ const QuillCodeBlockContainer = Quill.import('formats/code-block-container');
23
+
24
+ class CodeBlockContainer extends QuillCodeBlockContainer {
25
+ html(index, length) {
26
+ const markup = super.html(index, length);
27
+ const tempDiv = document.createElement('div');
28
+ tempDiv.innerHTML = markup;
29
+ const preTag = tempDiv.querySelector('pre');
30
+ if (preTag) {
31
+ preTag.setAttribute('spellcheck', 'false');
32
+ return preTag.outerHTML;
33
+ }
34
+ return markup; // fallback
39
35
  }
40
36
  }
41
37
 
42
- CustomColor.blotName = 'customColor';
43
- CustomColor.tagName = 'FONT';
44
-
45
- Quill.register(CustomColor, true);
38
+ Quill.register('formats/code-block-container', CodeBlockContainer, true);
46
39
 
47
40
  const HANDLERS = [
48
41
  'bold',
@@ -69,8 +62,6 @@ const STATE = {
69
62
  CLICKED: 2,
70
63
  };
71
64
 
72
- const TAB_KEY = 9;
73
-
74
65
  const DEFAULT_I18N = {
75
66
  undo: 'undo',
76
67
  redo: 'redo',
@@ -87,6 +78,8 @@ const DEFAULT_I18N = {
87
78
  superscript: 'superscript',
88
79
  listOrdered: 'list ordered',
89
80
  listBullet: 'list bullet',
81
+ outdent: 'outdent',
82
+ indent: 'indent',
90
83
  alignLeft: 'align left',
91
84
  alignCenter: 'align center',
92
85
  alignRight: 'align right',
@@ -374,23 +367,21 @@ export const RichTextEditorMixin = (superClass) =>
374
367
  }
375
368
  });
376
369
 
377
- const TAB_KEY = 9;
378
-
379
370
  editorContent.addEventListener('keydown', (e) => {
380
371
  if (e.key === 'Escape') {
381
372
  if (!this.__tabBindings) {
382
- this.__tabBindings = this._editor.keyboard.bindings[TAB_KEY];
383
- this._editor.keyboard.bindings[TAB_KEY] = null;
373
+ this.__tabBindings = this._editor.keyboard.bindings.Tab;
374
+ this._editor.keyboard.bindings.Tab = null;
384
375
  }
385
376
  } else if (this.__tabBindings) {
386
- this._editor.keyboard.bindings[TAB_KEY] = this.__tabBindings;
377
+ this._editor.keyboard.bindings.Tab = this.__tabBindings;
387
378
  this.__tabBindings = null;
388
379
  }
389
380
  });
390
381
 
391
382
  editorContent.addEventListener('blur', () => {
392
383
  if (this.__tabBindings) {
393
- this._editor.keyboard.bindings[TAB_KEY] = this.__tabBindings;
384
+ this._editor.keyboard.bindings.Tab = this.__tabBindings;
394
385
  this.__tabBindings = null;
395
386
  }
396
387
  });
@@ -422,9 +413,9 @@ export const RichTextEditorMixin = (superClass) =>
422
413
  // Set up tooltip to show when hovering or focusing toolbar buttons
423
414
  this._tooltip = document.createElement('vaadin-tooltip');
424
415
  this._tooltip.slot = 'tooltip';
425
- // Create dummy aria target, as toolbar buttons already have aria-label, and also cannot be linked with the
426
- // tooltip being in the light DOM
427
- this._tooltip.ariaTarget = document.createElement('div');
416
+ // Set ariaTarget to null, as toolbar buttons already have aria-label,
417
+ // and also cannot be linked with the tooltip being in the light DOM
418
+ this._tooltip.ariaTarget = null;
428
419
  this.append(this._tooltip);
429
420
 
430
421
  const buttons = this.shadowRoot.querySelectorAll('[part~="toolbar-button"]');
@@ -435,13 +426,16 @@ export const RichTextEditorMixin = (superClass) =>
435
426
  }
436
427
 
437
428
  /** @private */
438
- __showTooltip(event) {
439
- const target = event.target;
429
+ __showTooltip({ type, target }) {
430
+ // Only show tooltip when focused with keyboard
431
+ if (type === 'focusin' && !isKeyboardActive()) {
432
+ return;
433
+ }
440
434
  this._tooltip.target = target;
441
435
  this._tooltip.text = target.ariaLabel;
442
436
  this._tooltip._stateController.open({
443
- focus: event.type === 'focusin',
444
- hover: event.type === 'mouseenter',
437
+ focus: type === 'focusin',
438
+ hover: type === 'mouseenter',
445
439
  });
446
440
  }
447
441
 
@@ -496,7 +490,7 @@ export const RichTextEditorMixin = (superClass) =>
496
490
  buttons[index].focus();
497
491
  }
498
492
  // Esc and Tab focuses the content
499
- if (e.keyCode === 27 || (e.keyCode === TAB_KEY && !e.shiftKey)) {
493
+ if (e.keyCode === 27 || (e.key === 'Tab' && !e.shiftKey)) {
500
494
  e.preventDefault();
501
495
  this._editor.focus();
502
496
  }
@@ -539,7 +533,6 @@ export const RichTextEditorMixin = (superClass) =>
539
533
  toolbar.controls.forEach((pair) => {
540
534
  const input = pair[1];
541
535
  const isActive = input.classList.contains('ql-active');
542
- input.toggleAttribute('on', isActive);
543
536
  input.part.toggle('toolbar-button-pressed', isActive);
544
537
  });
545
538
  };
@@ -552,19 +545,13 @@ export const RichTextEditorMixin = (superClass) =>
552
545
  this._toolbar.querySelector('button:not([tabindex])').focus();
553
546
  };
554
547
 
555
- const keyboard = this._editor.getModule('keyboard');
556
- const bindings = keyboard.bindings[TAB_KEY];
557
-
558
- // Exclude Quill shift-tab bindings, except for code block,
559
- // as some of those are breaking when on a newline in the list
560
- // https://github.com/vaadin/vaadin-rich-text-editor/issues/67
561
- const originalBindings = bindings.filter((b) => !b.shiftKey || (b.format && b.format['code-block']));
562
- const moveFocusBinding = { key: TAB_KEY, shiftKey: true, handler: focusToolbar };
548
+ const keyboard = this._editor.keyboard;
563
549
 
564
- keyboard.bindings[TAB_KEY] = [...originalBindings, moveFocusBinding];
550
+ // Shift + Tab focuses a toolbar button unless we are in list / code block
551
+ keyboard.addBinding({ key: 'Tab', shiftKey: true, handler: focusToolbar });
565
552
 
566
553
  // Alt-f10 focuses a toolbar button
567
- keyboard.addBinding({ key: 121, altKey: true, handler: focusToolbar });
554
+ keyboard.addBinding({ key: 'F10', altKey: true, handler: focusToolbar });
568
555
  }
569
556
 
570
557
  /** @private */
@@ -603,6 +590,7 @@ export const RichTextEditorMixin = (superClass) =>
603
590
  _applyLink(link) {
604
591
  if (link) {
605
592
  this._markToolbarClicked();
593
+ this._editor.focus();
606
594
  this._editor.format('link', link, SOURCE.USER);
607
595
  this._editor.getModule('toolbar').update(this._editor.selection.savedRange);
608
596
  }
@@ -678,6 +666,7 @@ export const RichTextEditorMixin = (superClass) =>
678
666
 
679
667
  /** @private */
680
668
  __onColorClick() {
669
+ this._tooltip.opened = false;
681
670
  this._colorEditing = true;
682
671
  }
683
672
 
@@ -686,6 +675,7 @@ export const RichTextEditorMixin = (superClass) =>
686
675
  const color = event.detail.color;
687
676
  this._colorValue = color === '#000000' ? null : color;
688
677
  this._markToolbarClicked();
678
+ this._editor.focus();
689
679
  this._editor.format('color', this._colorValue, SOURCE.USER);
690
680
  this._toolbar.style.setProperty('--_color-value', this._colorValue);
691
681
  this._colorEditing = false;
@@ -693,6 +683,7 @@ export const RichTextEditorMixin = (superClass) =>
693
683
 
694
684
  /** @private */
695
685
  __onBackgroundClick() {
686
+ this._tooltip.opened = false;
696
687
  this._backgroundEditing = true;
697
688
  }
698
689
 
@@ -701,6 +692,7 @@ export const RichTextEditorMixin = (superClass) =>
701
692
  const color = event.detail.color;
702
693
  this._backgroundValue = color === '#ffffff' ? null : color;
703
694
  this._markToolbarClicked();
695
+ this._editor.focus();
704
696
  this._editor.format('background', this._backgroundValue, SOURCE.USER);
705
697
  this._toolbar.style.setProperty('--_background-value', this._backgroundValue);
706
698
  this._backgroundEditing = false;
@@ -708,30 +700,66 @@ export const RichTextEditorMixin = (superClass) =>
708
700
 
709
701
  /** @private */
710
702
  __updateHtmlValue() {
711
- const editor = this.shadowRoot.querySelector('.ql-editor');
712
- let content = editor.innerHTML;
713
-
714
- // Remove Quill classes, e.g. ql-syntax, except for align
703
+ // We have to use this instead of `innerHTML` to get correct tags like `<pre>` etc.
704
+ let content = this._editor.getSemanticHTML();
705
+ // Remove Quill classes, e.g. ql-syntax, except for align and indent
715
706
  content = content.replace(/class="([^"]*)"/gu, (_match, group1) => {
716
707
  const classes = group1.split(' ').filter((className) => {
717
- return !className.startsWith('ql-') || className.startsWith('ql-align');
708
+ return !className.startsWith('ql-') || className.startsWith('ql-align') || className.startsWith('ql-indent');
718
709
  });
719
710
  return `class="${classes.join(' ')}"`;
720
711
  });
721
- // Remove meta spans, e.g. cursor which are empty after Quill classes removed
722
- content = content.replace(/<span[^>]*><\/span>/gu, '');
723
-
724
- // Replace Quill align classes with inline styles
725
- [this.__dir === 'rtl' ? 'left' : 'right', 'center', 'justify'].forEach((align) => {
726
- content = content.replace(
727
- new RegExp(` class=[\\\\]?"\\s?ql-align-${align}[\\\\]?"`, 'gu'),
728
- ` style="text-align: ${align}"`,
729
- );
712
+ // Process align and indent classes
713
+ content = this.__processQuillClasses(content);
714
+ this._setHtmlValue(content);
715
+ }
716
+
717
+ /** @private */
718
+ __processQuillClasses(content) {
719
+ const tempDiv = document.createElement('div');
720
+ tempDiv.innerHTML = content;
721
+ // Process only elements with align or indent classes
722
+ const elementsToProcess = tempDiv.querySelectorAll('[class*="ql-align"], [class*="ql-indent"]');
723
+ elementsToProcess.forEach((element) => {
724
+ this.__processAlignClasses(element);
725
+ this.__processIndentClasses(element);
726
+ element.removeAttribute('class');
730
727
  });
728
+ return tempDiv.innerHTML;
729
+ }
731
730
 
732
- content = content.replace(/ class=""/gu, '');
731
+ /** @private */
732
+ __processAlignClasses(element) {
733
+ let styleText = element.getAttribute('style') || '';
734
+ const alignments = [this.__dir === 'rtl' ? 'left' : 'right', 'center', 'justify'];
735
+ alignments.forEach((align) => {
736
+ if (element.classList.contains(`ql-align-${align}`)) {
737
+ const newStyle = `text-align: ${align}`;
738
+ styleText = styleText ? `${styleText}; ${newStyle}` : newStyle;
739
+ element.setAttribute('style', styleText);
740
+ element.classList.remove(`ql-align-${align}`);
741
+ }
742
+ });
743
+ }
733
744
 
734
- this._setHtmlValue(content);
745
+ /** @private */
746
+ __processIndentClasses(element) {
747
+ const indentClass = Array.from(element.classList).find((className) => className.startsWith('ql-indent-'));
748
+ if (indentClass) {
749
+ const level = parseInt(indentClass.replace('ql-indent-', '').trim(), 10);
750
+ const tabs = '\t'.repeat(level);
751
+ // Add tabs to content
752
+ const firstChild = element.firstChild;
753
+ if (firstChild && firstChild.nodeType === Node.TEXT_NODE) {
754
+ firstChild.textContent = tabs + firstChild.textContent;
755
+ } else if (element.childNodes.length > 0) {
756
+ const tabNode = document.createTextNode(tabs);
757
+ element.insertBefore(tabNode, element.firstChild);
758
+ } else {
759
+ element.textContent = tabs;
760
+ }
761
+ element.classList.remove(indentClass);
762
+ }
735
763
  }
736
764
 
737
765
  /**
@@ -778,7 +806,7 @@ export const RichTextEditorMixin = (superClass) =>
778
806
  htmlValue = htmlValue.replaceAll(/>[^<]*</gu, (match) => match.replaceAll(character, replacement)); // NOSONAR
779
807
  });
780
808
 
781
- const deltaFromHtml = this._editor.clipboard.convert(htmlValue);
809
+ const deltaFromHtml = this._editor.clipboard.convert({ html: htmlValue });
782
810
 
783
811
  // Restore whitespace characters after the conversion
784
812
  Object.entries(whitespaceCharacters).forEach(([character, replacement]) => {
@@ -16,7 +16,7 @@ import { OverlayMixin } from '@vaadin/overlay/src/vaadin-overlay-mixin.js';
16
16
  import { PositionMixin } from '@vaadin/overlay/src/vaadin-overlay-position-mixin.js';
17
17
  import { LumoInjectionMixin } from '@vaadin/vaadin-themable-mixin/lumo-injection-mixin.js';
18
18
  import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
19
- import { richTextEditorPopupOverlayStyles } from './styles/vaadin-rich-text-editor-popup-overlay-core-styles.js';
19
+ import { richTextEditorPopupOverlayStyles } from './styles/vaadin-rich-text-editor-popup-overlay-base-styles.js';
20
20
 
21
21
  /**
22
22
  * An element used internally by `<vaadin-rich-text-editor>`. Not intended to be used separately.
@@ -32,15 +32,16 @@ class RichTextEditorPopup extends PolylitMixin(LitElement) {
32
32
 
33
33
  static get styles() {
34
34
  return css`
35
- :host {
36
- display: none !important;
37
- }
38
-
39
35
  :host([opened]),
40
36
  :host([opening]),
41
37
  :host([closing]) {
42
38
  display: contents !important;
43
39
  }
40
+
41
+ :host,
42
+ :host([hidden]) {
43
+ display: none !important;
44
+ }
44
45
  `;
45
46
  }
46
47
 
@@ -70,7 +71,7 @@ class RichTextEditorPopup extends PolylitMixin(LitElement) {
70
71
  render() {
71
72
  return html`
72
73
  <vaadin-rich-text-editor-popup-overlay
73
- popover="manual"
74
+ id="overlay"
74
75
  .owner="${this}"
75
76
  .opened="${this.opened}"
76
77
  .positionTarget="${this.target}"
@@ -163,22 +164,6 @@ class RichTextEditorPopupOverlay extends PositionMixin(
163
164
  `;
164
165
  }
165
166
 
166
- /**
167
- * @protected
168
- * @override
169
- */
170
- _attachOverlay() {
171
- this.showPopover();
172
- }
173
-
174
- /**
175
- * @protected
176
- * @override
177
- */
178
- _detachOverlay() {
179
- this.hidePopover();
180
- }
181
-
182
167
  /**
183
168
  * Override method from OverlayFocusMixin to use owner as content root
184
169
  * @protected
@@ -189,11 +174,11 @@ class RichTextEditorPopupOverlay extends PositionMixin(
189
174
  }
190
175
 
191
176
  /**
192
- * Override method from OverlayFocusMixin to use owner as modal root
177
+ * Override method from OverlayFocusMixin to use owner as focus trap root
193
178
  * @protected
194
179
  * @override
195
180
  */
196
- get _modalRoot() {
181
+ get _focusTrapRoot() {
197
182
  return this.owner;
198
183
  }
199
184
  }
@@ -34,18 +34,17 @@ export interface RichTextEditorEventMap extends HTMLElementEventMap, RichTextEdi
34
34
  * ```
35
35
  *
36
36
  * Vaadin Rich Text Editor focuses on the structure, not the styling of content.
37
- * Therefore, the semantic HTML5 tags such as <h1>, <strong> and <ul> are used,
37
+ * Therefore, the semantic HTML5 tags such as `<h1>`, `<strong>` and `<ul>` are used,
38
38
  * and CSS usage is limited to most common cases, like horizontal text alignment.
39
39
  *
40
40
  * ### Styling
41
41
  *
42
42
  * The following state attributes are available for styling:
43
43
  *
44
- * Attribute | Description | Part name
45
- * -------------|-------------|------------
46
- * `disabled` | Set to a disabled text editor | :host
47
- * `readonly` | Set to a readonly text editor | :host
48
- * `on` | Set to a toolbar button applied to the selected text | toolbar-button
44
+ * Attribute | Description
45
+ * -------------|------------------------------
46
+ * `disabled` | Set to a disabled text editor
47
+ * `readonly` | Set to a readonly text editor
49
48
  *
50
49
  * The following shadow DOM parts are available for styling:
51
50
  *
@@ -54,12 +53,13 @@ export interface RichTextEditorEventMap extends HTMLElementEventMap, RichTextEdi
54
53
  * `content` | The content wrapper
55
54
  * `toolbar` | The toolbar wrapper
56
55
  * `toolbar-group` | The group for toolbar controls
57
- * `toolbar-group-history` | The group for histroy controls
56
+ * `toolbar-group-history` | The group for history controls
58
57
  * `toolbar-group-emphasis` | The group for emphasis controls
59
58
  * `toolbar-group-heading` | The group for heading controls
60
59
  * `toolbar-group-style` | The group for style controls
61
60
  * `toolbar-group-glyph-transformation` | The group for glyph transformation controls
62
- * `toolbar-group-group-list` | The group for group list controls
61
+ * `toolbar-group-list` | The group for list controls
62
+ * `toolbar-group-indent` | The group for indentation controls
63
63
  * `toolbar-group-alignment` | The group for alignment controls
64
64
  * `toolbar-group-rich-text` | The group for rich text controls
65
65
  * `toolbar-group-block` | The group for preformatted block controls
@@ -81,6 +81,8 @@ export interface RichTextEditorEventMap extends HTMLElementEventMap, RichTextEdi
81
81
  * `toolbar-button-superscript` | The "superscript" button
82
82
  * `toolbar-button-list-ordered` | The "ordered list" button
83
83
  * `toolbar-button-list-bullet` | The "bullet list" button
84
+ * `toolbar-button-outdent` | The "decrease indentation" button
85
+ * `toolbar-button-indent` | The "increase indentation" button
84
86
  * `toolbar-button-align-left` | The "left align" button
85
87
  * `toolbar-button-align-center` | The "center align" button
86
88
  * `toolbar-button-align-right` | The "right align" button
@@ -14,12 +14,13 @@ import '@vaadin/text-field/src/vaadin-text-field.js';
14
14
  import '@vaadin/tooltip/src/vaadin-tooltip.js';
15
15
  import './vaadin-rich-text-editor-popup.js';
16
16
  import { html, LitElement, render } from 'lit';
17
+ import { isKeyboardActive } from '@vaadin/a11y-base/src/focus-utils.js';
17
18
  import { defineCustomElement } from '@vaadin/component-base/src/define.js';
18
19
  import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
19
20
  import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js';
20
21
  import { LumoInjectionMixin } from '@vaadin/vaadin-themable-mixin/lumo-injection-mixin.js';
21
22
  import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
22
- import { richTextEditorStyles } from './styles/vaadin-rich-text-editor-core-styles.js';
23
+ import { richTextEditorStyles } from './styles/vaadin-rich-text-editor-base-styles.js';
23
24
  import { RichTextEditorMixin } from './vaadin-rich-text-editor-mixin.js';
24
25
 
25
26
  /**
@@ -32,18 +33,17 @@ import { RichTextEditorMixin } from './vaadin-rich-text-editor-mixin.js';
32
33
  * ```
33
34
  *
34
35
  * Vaadin Rich Text Editor focuses on the structure, not the styling of content.
35
- * Therefore, the semantic HTML5 tags such as <h1>, <strong> and <ul> are used,
36
+ * Therefore, the semantic HTML5 tags such as `<h1>`, `<strong>` and `<ul>` are used,
36
37
  * and CSS usage is limited to most common cases, like horizontal text alignment.
37
38
  *
38
39
  * ### Styling
39
40
  *
40
41
  * The following state attributes are available for styling:
41
42
  *
42
- * Attribute | Description | Part name
43
- * -------------|-------------|------------
44
- * `disabled` | Set to a disabled text editor | :host
45
- * `readonly` | Set to a readonly text editor | :host
46
- * `on` | Set to a toolbar button applied to the selected text | toolbar-button
43
+ * Attribute | Description
44
+ * -------------|------------------------------
45
+ * `disabled` | Set to a disabled text editor
46
+ * `readonly` | Set to a readonly text editor
47
47
  *
48
48
  * The following shadow DOM parts are available for styling:
49
49
  *
@@ -52,12 +52,13 @@ import { RichTextEditorMixin } from './vaadin-rich-text-editor-mixin.js';
52
52
  * `content` | The content wrapper
53
53
  * `toolbar` | The toolbar wrapper
54
54
  * `toolbar-group` | The group for toolbar controls
55
- * `toolbar-group-history` | The group for histroy controls
55
+ * `toolbar-group-history` | The group for history controls
56
56
  * `toolbar-group-emphasis` | The group for emphasis controls
57
57
  * `toolbar-group-heading` | The group for heading controls
58
58
  * `toolbar-group-style` | The group for style controls
59
59
  * `toolbar-group-glyph-transformation` | The group for glyph transformation controls
60
- * `toolbar-group-group-list` | The group for group list controls
60
+ * `toolbar-group-list` | The group for list controls
61
+ * `toolbar-group-indent` | The group for indentation controls
61
62
  * `toolbar-group-alignment` | The group for alignment controls
62
63
  * `toolbar-group-rich-text` | The group for rich text controls
63
64
  * `toolbar-group-block` | The group for preformatted block controls
@@ -79,6 +80,8 @@ import { RichTextEditorMixin } from './vaadin-rich-text-editor-mixin.js';
79
80
  * `toolbar-button-superscript` | The "superscript" button
80
81
  * `toolbar-button-list-ordered` | The "ordered list" button
81
82
  * `toolbar-button-list-bullet` | The "bullet list" button
83
+ * `toolbar-button-outdent` | The "decrease indentation" button
84
+ * `toolbar-button-indent` | The "increase indentation" button
82
85
  * `toolbar-button-align-left` | The "left align" button
83
86
  * `toolbar-button-align-center` | The "center align" button
84
87
  * `toolbar-button-align-right` | The "right align" button
@@ -115,6 +118,12 @@ class RichTextEditor extends RichTextEditorMixin(
115
118
  return richTextEditorStyles;
116
119
  }
117
120
 
121
+ static get lumoInjector() {
122
+ return {
123
+ includeBaseStyles: true,
124
+ };
125
+ }
126
+
118
127
  /** @protected */
119
128
  render() {
120
129
  return html`
@@ -259,6 +268,27 @@ class RichTextEditor extends RichTextEditorMixin(
259
268
  ></button>
260
269
  </span>
261
270
 
271
+ <span part="toolbar-group toolbar-group-indent">
272
+ <!-- Decrease -->
273
+ <button
274
+ id="btn-outdent"
275
+ type="button"
276
+ class="ql-indent"
277
+ value="-1"
278
+ part="toolbar-button toolbar-button-outdent"
279
+ aria-label="${this.__effectiveI18n.outdent}"
280
+ ></button>
281
+ <!-- Increase -->
282
+ <button
283
+ id="btn-indent"
284
+ type="button"
285
+ class="ql-indent"
286
+ value="+1"
287
+ part="toolbar-button toolbar-button-indent"
288
+ aria-label="${this.__effectiveI18n.indent}"
289
+ ></button>
290
+ </span>
291
+
262
292
  <span part="toolbar-group toolbar-group-alignment">
263
293
  <!-- Align buttons -->
264
294
  <button
@@ -438,7 +468,7 @@ class RichTextEditor extends RichTextEditorMixin(
438
468
  confirmDialog.$.overlay.addEventListener(
439
469
  'vaadin-overlay-open',
440
470
  () => {
441
- urlField.focus();
471
+ urlField.focus({ focusVisible: isKeyboardActive() });
442
472
  },
443
473
  { once: true },
444
474
  );
@@ -1,2 +1,2 @@
1
- import './theme/lumo/vaadin-rich-text-editor.js';
1
+ import './src/vaadin-rich-text-editor.js';
2
2
  export * from './src/vaadin-rich-text-editor.js';