@vaadin/rich-text-editor 25.0.0-alpha2 → 25.0.0-alpha21

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,41 +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
- import { isFirefox } from '@vaadin/component-base/src/browser-utils.js';
14
14
  import { Debouncer } from '@vaadin/component-base/src/debounce.js';
15
15
  import { I18nMixin } from '@vaadin/component-base/src/i18n-mixin.js';
16
16
 
17
17
  const Quill = window.Quill;
18
18
 
19
- // Workaround for text disappearing when accepting spellcheck suggestion
20
- // See https://github.com/quilljs/quill/issues/2096#issuecomment-399576957
21
- const Inline = Quill.import('blots/inline');
22
-
23
- class CustomColor extends Inline {
24
- constructor(domNode, value) {
25
- super(domNode, value);
26
-
27
- // Map <font> properties
28
- domNode.style.color = domNode.color;
29
-
30
- const span = this.replaceWith(new Inline(Inline.create()));
31
-
32
- span.children.forEach((child) => {
33
- if (child.attributes) child.attributes.copy(span);
34
- if (child.unwrap) child.unwrap();
35
- });
36
-
37
- this.remove();
38
-
39
- 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
40
35
  }
41
36
  }
42
37
 
43
- CustomColor.blotName = 'customColor';
44
- CustomColor.tagName = 'FONT';
45
-
46
- Quill.register(CustomColor, true);
38
+ Quill.register('formats/code-block-container', CodeBlockContainer, true);
47
39
 
48
40
  const HANDLERS = [
49
41
  'bold',
@@ -70,8 +62,6 @@ const STATE = {
70
62
  CLICKED: 2,
71
63
  };
72
64
 
73
- const TAB_KEY = 9;
74
-
75
65
  const DEFAULT_I18N = {
76
66
  undo: 'undo',
77
67
  redo: 'redo',
@@ -88,6 +78,8 @@ const DEFAULT_I18N = {
88
78
  superscript: 'superscript',
89
79
  listOrdered: 'list ordered',
90
80
  listBullet: 'list bullet',
81
+ outdent: 'outdent',
82
+ indent: 'indent',
91
83
  alignLeft: 'align left',
92
84
  alignCenter: 'align center',
93
85
  alignRight: 'align right',
@@ -177,7 +169,7 @@ export const RichTextEditorMixin = (superClass) =>
177
169
  '#ffffff', '#facccc', '#ffebcc', '#ffffcc', '#cce8cc', '#cce0f5', '#ebd6ff',
178
170
  '#bbbbbb', '#f06666', '#ffc266', '#ffff66', '#66b966', '#66a3e0', '#c285ff',
179
171
  '#888888', '#a10000', '#b26b00', '#b2b200', '#006100', '#0047b2', '#6b24b2',
180
- '#444444', '#5c0000', '#663d00', '#666600', '#003700', '#002966', '#3d1466'
172
+ '#444444', '#5c0000', '#663d00', '#666600', '#003700', '#002966', '#3d1466',
181
173
  ];
182
174
  },
183
175
  },
@@ -352,11 +344,6 @@ export const RichTextEditorMixin = (superClass) =>
352
344
  this.__patchToolbar();
353
345
  this.__patchKeyboard();
354
346
 
355
- /* c8 ignore next 3 */
356
- if (isFirefox) {
357
- this.__patchFirefoxFocus();
358
- }
359
-
360
347
  this.__setDirection(this.__dir);
361
348
 
362
349
  const editorContent = editor.querySelector('.ql-editor');
@@ -380,23 +367,21 @@ export const RichTextEditorMixin = (superClass) =>
380
367
  }
381
368
  });
382
369
 
383
- const TAB_KEY = 9;
384
-
385
370
  editorContent.addEventListener('keydown', (e) => {
386
371
  if (e.key === 'Escape') {
387
372
  if (!this.__tabBindings) {
388
- this.__tabBindings = this._editor.keyboard.bindings[TAB_KEY];
389
- this._editor.keyboard.bindings[TAB_KEY] = null;
373
+ this.__tabBindings = this._editor.keyboard.bindings.Tab;
374
+ this._editor.keyboard.bindings.Tab = null;
390
375
  }
391
376
  } else if (this.__tabBindings) {
392
- this._editor.keyboard.bindings[TAB_KEY] = this.__tabBindings;
377
+ this._editor.keyboard.bindings.Tab = this.__tabBindings;
393
378
  this.__tabBindings = null;
394
379
  }
395
380
  });
396
381
 
397
382
  editorContent.addEventListener('blur', () => {
398
383
  if (this.__tabBindings) {
399
- this._editor.keyboard.bindings[TAB_KEY] = this.__tabBindings;
384
+ this._editor.keyboard.bindings.Tab = this.__tabBindings;
400
385
  this.__tabBindings = null;
401
386
  }
402
387
  });
@@ -422,13 +407,35 @@ export const RichTextEditorMixin = (superClass) =>
422
407
  // Flush pending htmlValue only once the editor is fully initialized
423
408
  this.__flushPendingHtmlValue();
424
409
 
425
- this.$.backgroundPopup.target = this.shadowRoot.querySelector('#btn-background');
426
- this.$.colorPopup.target = this.shadowRoot.querySelector('#btn-color');
410
+ this.querySelector('[slot="color-popup"]').target = this.shadowRoot.querySelector('#btn-color');
411
+ this.querySelector('[slot="background-popup"]').target = this.shadowRoot.querySelector('#btn-background');
412
+
413
+ // Set up tooltip to show when hovering or focusing toolbar buttons
414
+ this._tooltip = document.createElement('vaadin-tooltip');
415
+ this._tooltip.slot = 'tooltip';
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;
419
+ this.append(this._tooltip);
420
+
421
+ const buttons = this.shadowRoot.querySelectorAll('[part~="toolbar-button"]');
422
+ buttons.forEach((button) => {
423
+ button.addEventListener('mouseenter', this.__showTooltip.bind(this));
424
+ button.addEventListener('focusin', this.__showTooltip.bind(this));
425
+ });
426
+ }
427
427
 
428
- requestAnimationFrame(() => {
429
- this.$.linkDialog.$.dialog.$.overlay.addEventListener('vaadin-overlay-open', () => {
430
- this.$.linkUrl.focus();
431
- });
428
+ /** @private */
429
+ __showTooltip({ type, target }) {
430
+ // Only show tooltip when focused with keyboard
431
+ if (type === 'focusin' && !isKeyboardActive()) {
432
+ return;
433
+ }
434
+ this._tooltip.target = target;
435
+ this._tooltip.text = target.ariaLabel;
436
+ this._tooltip._stateController.open({
437
+ focus: type === 'focusin',
438
+ hover: type === 'mouseenter',
432
439
  });
433
440
  }
434
441
 
@@ -483,7 +490,7 @@ export const RichTextEditorMixin = (superClass) =>
483
490
  buttons[index].focus();
484
491
  }
485
492
  // Esc and Tab focuses the content
486
- if (e.keyCode === 27 || (e.keyCode === TAB_KEY && !e.shiftKey)) {
493
+ if (e.keyCode === 27 || (e.key === 'Tab' && !e.shiftKey)) {
487
494
  e.preventDefault();
488
495
  this._editor.focus();
489
496
  }
@@ -512,69 +519,6 @@ export const RichTextEditorMixin = (superClass) =>
512
519
  this._toolbarState = STATE.DEFAULT;
513
520
  }
514
521
 
515
- /** @private */
516
- __createFakeFocusTarget() {
517
- const isRTL = document.documentElement.getAttribute('dir') === 'rtl';
518
- const elem = document.createElement('textarea');
519
- // Reset box model
520
- elem.style.border = '0';
521
- elem.style.padding = '0';
522
- elem.style.margin = '0';
523
- // Move element out of screen horizontally
524
- elem.style.position = 'absolute';
525
- elem.style[isRTL ? 'right' : 'left'] = '-9999px';
526
- // Move element to the same position vertically
527
- const yPosition = window.pageYOffset || document.documentElement.scrollTop;
528
- elem.style.top = `${yPosition}px`;
529
- return elem;
530
- }
531
-
532
- /** @private */
533
- __patchFirefoxFocus() {
534
- // In Firefox 63+ with native Shadow DOM, when moving focus out of
535
- // contenteditable and back again within same shadow root, cursor
536
- // disappears. See https://bugzilla.mozilla.org/show_bug.cgi?id=1496769
537
- const editorContent = this.shadowRoot.querySelector('.ql-editor');
538
- let isFake = false;
539
-
540
- const focusFake = () => {
541
- isFake = true;
542
- this.__fakeTarget = this.__createFakeFocusTarget();
543
- document.body.appendChild(this.__fakeTarget);
544
- // Let the focus step out of shadow root!
545
- this.__fakeTarget.focus();
546
- return new Promise((resolve) => {
547
- setTimeout(resolve);
548
- });
549
- };
550
-
551
- const focusBack = (offsetNode, offset) => {
552
- this._editor.focus();
553
- if (offsetNode) {
554
- this._editor.selection.setNativeRange(offsetNode, offset);
555
- }
556
- document.body.removeChild(this.__fakeTarget);
557
- delete this.__fakeTarget;
558
- isFake = false;
559
- };
560
-
561
- editorContent.addEventListener('mousedown', (e) => {
562
- if (!this._editor.hasFocus()) {
563
- const { x, y } = e;
564
- const { offset, offsetNode } = document.caretPositionFromPoint(x, y);
565
- focusFake().then(() => {
566
- focusBack(offsetNode, offset);
567
- });
568
- }
569
- });
570
-
571
- editorContent.addEventListener('focusin', () => {
572
- if (isFake === false) {
573
- focusFake().then(() => focusBack());
574
- }
575
- });
576
- }
577
-
578
522
  /** @private */
579
523
  __patchToolbar() {
580
524
  const toolbar = this._editor.getModule('toolbar');
@@ -589,7 +533,6 @@ export const RichTextEditorMixin = (superClass) =>
589
533
  toolbar.controls.forEach((pair) => {
590
534
  const input = pair[1];
591
535
  const isActive = input.classList.contains('ql-active');
592
- input.toggleAttribute('on', isActive);
593
536
  input.part.toggle('toolbar-button-pressed', isActive);
594
537
  });
595
538
  };
@@ -602,19 +545,13 @@ export const RichTextEditorMixin = (superClass) =>
602
545
  this._toolbar.querySelector('button:not([tabindex])').focus();
603
546
  };
604
547
 
605
- const keyboard = this._editor.getModule('keyboard');
606
- const bindings = keyboard.bindings[TAB_KEY];
607
-
608
- // Exclude Quill shift-tab bindings, except for code block,
609
- // as some of those are breaking when on a newline in the list
610
- // https://github.com/vaadin/vaadin-rich-text-editor/issues/67
611
- const originalBindings = bindings.filter((b) => !b.shiftKey || (b.format && b.format['code-block']));
612
- const moveFocusBinding = { key: TAB_KEY, shiftKey: true, handler: focusToolbar };
548
+ const keyboard = this._editor.keyboard;
613
549
 
614
- 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 });
615
552
 
616
553
  // Alt-f10 focuses a toolbar button
617
- keyboard.addBinding({ key: 121, altKey: true, handler: focusToolbar });
554
+ keyboard.addBinding({ key: 'F10', altKey: true, handler: focusToolbar });
618
555
  }
619
556
 
620
557
  /** @private */
@@ -653,6 +590,7 @@ export const RichTextEditorMixin = (superClass) =>
653
590
  _applyLink(link) {
654
591
  if (link) {
655
592
  this._markToolbarClicked();
593
+ this._editor.focus();
656
594
  this._editor.format('link', link, SOURCE.USER);
657
595
  this._editor.getModule('toolbar').update(this._editor.selection.savedRange);
658
596
  }
@@ -721,12 +659,14 @@ export const RichTextEditorMixin = (superClass) =>
721
659
  if (e.keyCode === 13) {
722
660
  e.preventDefault();
723
661
  e.stopPropagation();
724
- this.$.confirmLink.click();
662
+ this._onLinkEditConfirm();
663
+ this._closeLinkDialog();
725
664
  }
726
665
  }
727
666
 
728
667
  /** @private */
729
668
  __onColorClick() {
669
+ this._tooltip.opened = false;
730
670
  this._colorEditing = true;
731
671
  }
732
672
 
@@ -735,6 +675,7 @@ export const RichTextEditorMixin = (superClass) =>
735
675
  const color = event.detail.color;
736
676
  this._colorValue = color === '#000000' ? null : color;
737
677
  this._markToolbarClicked();
678
+ this._editor.focus();
738
679
  this._editor.format('color', this._colorValue, SOURCE.USER);
739
680
  this._toolbar.style.setProperty('--_color-value', this._colorValue);
740
681
  this._colorEditing = false;
@@ -742,6 +683,7 @@ export const RichTextEditorMixin = (superClass) =>
742
683
 
743
684
  /** @private */
744
685
  __onBackgroundClick() {
686
+ this._tooltip.opened = false;
745
687
  this._backgroundEditing = true;
746
688
  }
747
689
 
@@ -750,6 +692,7 @@ export const RichTextEditorMixin = (superClass) =>
750
692
  const color = event.detail.color;
751
693
  this._backgroundValue = color === '#ffffff' ? null : color;
752
694
  this._markToolbarClicked();
695
+ this._editor.focus();
753
696
  this._editor.format('background', this._backgroundValue, SOURCE.USER);
754
697
  this._toolbar.style.setProperty('--_background-value', this._backgroundValue);
755
698
  this._backgroundEditing = false;
@@ -757,30 +700,66 @@ export const RichTextEditorMixin = (superClass) =>
757
700
 
758
701
  /** @private */
759
702
  __updateHtmlValue() {
760
- const editor = this.shadowRoot.querySelector('.ql-editor');
761
- let content = editor.innerHTML;
762
-
763
- // 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
764
706
  content = content.replace(/class="([^"]*)"/gu, (_match, group1) => {
765
707
  const classes = group1.split(' ').filter((className) => {
766
- return !className.startsWith('ql-') || className.startsWith('ql-align');
708
+ return !className.startsWith('ql-') || className.startsWith('ql-align') || className.startsWith('ql-indent');
767
709
  });
768
710
  return `class="${classes.join(' ')}"`;
769
711
  });
770
- // Remove meta spans, e.g. cursor which are empty after Quill classes removed
771
- content = content.replace(/<span[^>]*><\/span>/gu, '');
772
-
773
- // Replace Quill align classes with inline styles
774
- [this.__dir === 'rtl' ? 'left' : 'right', 'center', 'justify'].forEach((align) => {
775
- content = content.replace(
776
- new RegExp(` class=[\\\\]?"\\s?ql-align-${align}[\\\\]?"`, 'gu'),
777
- ` style="text-align: ${align}"`,
778
- );
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');
779
727
  });
728
+ return tempDiv.innerHTML;
729
+ }
780
730
 
781
- 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
+ }
782
744
 
783
- 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
+ }
784
763
  }
785
764
 
786
765
  /**
@@ -827,7 +806,7 @@ export const RichTextEditorMixin = (superClass) =>
827
806
  htmlValue = htmlValue.replaceAll(/>[^<]*</gu, (match) => match.replaceAll(character, replacement)); // NOSONAR
828
807
  });
829
808
 
830
- const deltaFromHtml = this._editor.clipboard.convert(htmlValue);
809
+ const deltaFromHtml = this._editor.clipboard.convert({ html: htmlValue });
831
810
 
832
811
  // Restore whitespace characters after the conversion
833
812
  Object.entries(whitespaceCharacters).forEach(([character, replacement]) => {
@@ -868,10 +847,7 @@ export const RichTextEditorMixin = (superClass) =>
868
847
  timeOut.after(timeout),
869
848
  () => {
870
849
  const formatting = Array.from(this.shadowRoot.querySelectorAll('[part="toolbar"] .ql-active'))
871
- .map((button) => {
872
- const tooltip = this.shadowRoot.querySelector(`[for="${button.id}"]`);
873
- return tooltip.text;
874
- })
850
+ .map((button) => button.getAttribute('aria-label'))
875
851
  .join(', ');
876
852
  announcer.textContent = formatting;
877
853
  },
@@ -8,51 +8,83 @@
8
8
  * See https://vaadin.com/commercial-license-and-service-terms for the full
9
9
  * license.
10
10
  */
11
- import { css, html, LitElement } from 'lit';
11
+ import { css, html, LitElement, render } from 'lit';
12
12
  import { defineCustomElement } from '@vaadin/component-base/src/define.js';
13
13
  import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js';
14
14
  import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js';
15
15
  import { OverlayMixin } from '@vaadin/overlay/src/vaadin-overlay-mixin.js';
16
16
  import { PositionMixin } from '@vaadin/overlay/src/vaadin-overlay-position-mixin.js';
17
- import { overlayStyles } from '@vaadin/overlay/src/vaadin-overlay-styles.js';
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 { RichTextEditorPopupMixin } from './vaadin-rich-text-editor-popup-mixin.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.
23
23
  *
24
24
  * @customElement
25
25
  * @extends HTMLElement
26
- * @mixes RichTextEditorPopupMixin
27
26
  * @private
28
27
  */
29
- class RichTextEditorPopup extends RichTextEditorPopupMixin(PolylitMixin(LitElement)) {
28
+ class RichTextEditorPopup extends PolylitMixin(LitElement) {
30
29
  static get is() {
31
30
  return 'vaadin-rich-text-editor-popup';
32
31
  }
33
32
 
34
33
  static get styles() {
35
34
  return css`
36
- :host {
37
- display: none;
35
+ :host([opened]),
36
+ :host([opening]),
37
+ :host([closing]) {
38
+ display: contents !important;
39
+ }
40
+
41
+ :host,
42
+ :host([hidden]) {
43
+ display: none !important;
38
44
  }
39
45
  `;
40
46
  }
41
47
 
48
+ static get properties() {
49
+ return {
50
+ target: {
51
+ type: Object,
52
+ },
53
+
54
+ opened: {
55
+ type: Boolean,
56
+ reflectToAttribute: true,
57
+ notify: true,
58
+ },
59
+
60
+ colors: {
61
+ type: Array,
62
+ },
63
+ };
64
+ }
65
+
66
+ static get observers() {
67
+ return ['__openedOrTargetChanged(opened, target)', '__colorsChanged(colors)'];
68
+ }
69
+
42
70
  /** @protected */
43
71
  render() {
44
72
  return html`
45
73
  <vaadin-rich-text-editor-popup-overlay
46
- .renderer="${this.renderer}"
74
+ id="overlay"
75
+ .owner="${this}"
47
76
  .opened="${this.opened}"
48
77
  .positionTarget="${this.target}"
49
78
  no-vertical-overlap
50
79
  horizontal-align="start"
51
80
  vertical-align="top"
52
81
  focus-trap
82
+ exportparts="overlay, content"
53
83
  @opened-changed="${this._onOpenedChanged}"
54
84
  @vaadin-overlay-escape-press="${this._onOverlayEscapePress}"
55
- ></vaadin-rich-text-editor-popup-overlay>
85
+ >
86
+ <slot></slot>
87
+ </vaadin-rich-text-editor-popup-overlay>
56
88
  `;
57
89
  }
58
90
 
@@ -60,6 +92,39 @@ class RichTextEditorPopup extends RichTextEditorPopupMixin(PolylitMixin(LitEleme
60
92
  _onOpenedChanged(event) {
61
93
  this.opened = event.detail.value;
62
94
  }
95
+
96
+ /** @private */
97
+ _onOverlayEscapePress() {
98
+ this.target.focus();
99
+ }
100
+
101
+ /** @private */
102
+ _onColorClick(e) {
103
+ const { color } = e.target.dataset;
104
+ this.dispatchEvent(new CustomEvent('color-selected', { detail: { color } }));
105
+ }
106
+
107
+ /** @private */
108
+ __colorsChanged(colors) {
109
+ render(
110
+ html`
111
+ ${colors.map(
112
+ (color) => html`
113
+ <button data-color="${color}" style="background: ${color}" @click="${this._onColorClick}"></button>
114
+ `,
115
+ )}
116
+ `,
117
+ this,
118
+ { host: this },
119
+ );
120
+ }
121
+
122
+ /** @private */
123
+ __openedOrTargetChanged(opened, target) {
124
+ if (target) {
125
+ target.setAttribute('aria-expanded', opened ? 'true' : 'false');
126
+ }
127
+ }
63
128
  }
64
129
 
65
130
  defineCustomElement(RichTextEditorPopup);
@@ -78,25 +143,44 @@ export { RichTextEditorPopup };
78
143
  * @private
79
144
  */
80
145
  class RichTextEditorPopupOverlay extends PositionMixin(
81
- OverlayMixin(DirMixin(ThemableMixin(PolylitMixin(LitElement)))),
146
+ OverlayMixin(DirMixin(ThemableMixin(PolylitMixin(LumoInjectionMixin(LitElement))))),
82
147
  ) {
83
148
  static get is() {
84
149
  return 'vaadin-rich-text-editor-popup-overlay';
85
150
  }
86
151
 
87
152
  static get styles() {
88
- return overlayStyles;
153
+ return richTextEditorPopupOverlayStyles;
89
154
  }
90
155
 
91
156
  /** @protected */
92
157
  render() {
93
158
  return html`
94
- <div id="backdrop" part="backdrop" hidden></div>
95
159
  <div part="overlay" id="overlay">
96
- <div part="content" id="content"><slot></slot></div>
160
+ <div part="content" id="content">
161
+ <slot></slot>
162
+ </div>
97
163
  </div>
98
164
  `;
99
165
  }
166
+
167
+ /**
168
+ * Override method from OverlayFocusMixin to use owner as content root
169
+ * @protected
170
+ * @override
171
+ */
172
+ get _contentRoot() {
173
+ return this.owner;
174
+ }
175
+
176
+ /**
177
+ * Override method from OverlayFocusMixin to use owner as focus trap root
178
+ * @protected
179
+ * @override
180
+ */
181
+ get _focusTrapRoot() {
182
+ return this.owner;
183
+ }
100
184
  }
101
185
 
102
186
  defineCustomElement(RichTextEditorPopupOverlay);
@@ -29,23 +29,22 @@ export interface RichTextEditorEventMap extends HTMLElementEventMap, RichTextEdi
29
29
  * It provides a set of toolbar controls to apply formatting on the content,
30
30
  * which is stored and can be accessed as HTML5 or JSON string.
31
31
  *
32
- * ```
32
+ * ```html
33
33
  * <vaadin-rich-text-editor></vaadin-rich-text-editor>
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