Skip to content

Commit 45001eb

Browse files
authored
refactor!: update rich-text-editor to render link dialog in light DOM (#9772)
1 parent 7fdc69a commit 45001eb

File tree

3 files changed

+95
-64
lines changed

3 files changed

+95
-64
lines changed

packages/rich-text-editor/src/vaadin-rich-text-editor-mixin.js

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -419,12 +419,6 @@ export const RichTextEditorMixin = (superClass) =>
419419
this.$.backgroundPopup.target = this.shadowRoot.querySelector('#btn-background');
420420
this.$.colorPopup.target = this.shadowRoot.querySelector('#btn-color');
421421

422-
requestAnimationFrame(() => {
423-
this.$.linkDialog.$.overlay.addEventListener('vaadin-overlay-open', () => {
424-
this.$.linkUrl.focus();
425-
});
426-
});
427-
428422
// Set up tooltip to show when hovering or focusing toolbar buttons
429423
this._tooltip = document.createElement('vaadin-tooltip');
430424
this._tooltip.slot = 'tooltip';
@@ -677,7 +671,8 @@ export const RichTextEditorMixin = (superClass) =>
677671
if (e.keyCode === 13) {
678672
e.preventDefault();
679673
e.stopPropagation();
680-
this.$.confirmLink.click();
674+
this._onLinkEditConfirm();
675+
this._closeLinkDialog();
681676
}
682677
}
683678

packages/rich-text-editor/src/vaadin-rich-text-editor.js

Lines changed: 58 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import '@vaadin/confirm-dialog/src/vaadin-confirm-dialog.js';
1313
import '@vaadin/text-field/src/vaadin-text-field.js';
1414
import '@vaadin/tooltip/src/vaadin-tooltip.js';
1515
import './vaadin-rich-text-editor-popup.js';
16-
import { html, LitElement } from 'lit';
16+
import { html, LitElement, render } from 'lit';
1717
import { defineCustomElement } from '@vaadin/component-base/src/define.js';
1818
import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
1919
import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js';
@@ -350,36 +350,6 @@ class RichTextEditor extends RichTextEditorMixin(
350350
<div class="announcer" aria-live="polite"></div>
351351
</div>
352352
353-
<vaadin-confirm-dialog
354-
id="linkDialog"
355-
.opened="${this._linkEditing}"
356-
.header="${this.__effectiveI18n.linkDialogTitle}"
357-
@opened-changed="${this._onLinkEditingChanged}"
358-
>
359-
<vaadin-text-field
360-
id="linkUrl"
361-
.value="${this._linkUrl}"
362-
style="width: 100%;"
363-
@keydown="${this._onLinkKeydown}"
364-
@value-changed="${this._onLinkUrlChanged}"
365-
></vaadin-text-field>
366-
<vaadin-button id="confirmLink" slot="confirm-button" theme="primary" @click="${this._onLinkEditConfirm}">
367-
${this.__effectiveI18n.ok}
368-
</vaadin-button>
369-
<vaadin-button
370-
id="removeLink"
371-
slot="reject-button"
372-
theme="error"
373-
@click="${this._onLinkEditRemove}"
374-
?hidden="${!this._linkRange}"
375-
>
376-
${this.__effectiveI18n.remove}
377-
</vaadin-button>
378-
<vaadin-button id="cancelLink" slot="cancel-button" @click="${this._onLinkEditCancel}">
379-
${this.__effectiveI18n.cancel}
380-
</vaadin-button>
381-
</vaadin-confirm-dialog>
382-
383353
<vaadin-rich-text-editor-popup
384354
id="colorPopup"
385355
.colors="${this.colorOptions}"
@@ -397,9 +367,54 @@ class RichTextEditor extends RichTextEditorMixin(
397367
></vaadin-rich-text-editor-popup>
398368
399369
<slot name="tooltip"></slot>
370+
371+
<slot name="link-dialog"></slot>
400372
`;
401373
}
402374

375+
/**
376+
* Override update to render slotted link dialog into light DOM after rendering shadow DOM.
377+
* @param changedProperties
378+
* @protected
379+
*/
380+
update(changedProperties) {
381+
super.update(changedProperties);
382+
383+
this.__renderLinkDialog();
384+
}
385+
386+
/** @private */
387+
__renderLinkDialog() {
388+
render(
389+
html`
390+
<vaadin-confirm-dialog
391+
slot="link-dialog"
392+
cancel-button-visible
393+
reject-theme="error"
394+
.opened="${this._linkEditing}"
395+
.header="${this.__effectiveI18n.linkDialogTitle}"
396+
.confirmText="${this.__effectiveI18n.ok}"
397+
.rejectText="${this.__effectiveI18n.remove}"
398+
.cancelText="${this.__effectiveI18n.cancel}"
399+
.rejectButtonVisible="${!!this._linkRange}"
400+
@confirm="${this._onLinkEditConfirm}"
401+
@cancel="${this._onLinkEditCancel}"
402+
@reject="${this._onLinkEditRemove}"
403+
@opened-changed="${this._onLinkEditingChanged}"
404+
>
405+
<vaadin-text-field
406+
.value="${this._linkUrl}"
407+
style="width: 100%;"
408+
@keydown="${this._onLinkKeydown}"
409+
@value-changed="${this._onLinkUrlChanged}"
410+
></vaadin-text-field>
411+
</vaadin-confirm-dialog>
412+
`,
413+
this,
414+
{ host: this },
415+
);
416+
}
417+
403418
/** @private */
404419
__onBackgroundEditingChanged(event) {
405420
this._backgroundEditing = event.detail.value;
@@ -412,6 +427,18 @@ class RichTextEditor extends RichTextEditorMixin(
412427

413428
/** @private */
414429
_onLinkEditingChanged(event) {
430+
// Autofocus the URL field when the dialog opens
431+
if (event.detail.value) {
432+
const confirmDialog = event.target;
433+
const urlField = confirmDialog.querySelector('vaadin-text-field');
434+
confirmDialog.$.overlay.addEventListener(
435+
'vaadin-overlay-open',
436+
() => {
437+
urlField.focus();
438+
},
439+
{ once: true },
440+
);
441+
}
415442
this._linkEditing = event.detail.value;
416443
}
417444

packages/rich-text-editor/test/toolbar.test.js

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { expect } from '@vaadin/chai-plugins';
22
import {
3+
enter,
34
esc,
45
fire,
56
fixtureSync,
@@ -362,12 +363,16 @@ describe('toolbar controls', () => {
362363

363364
describe('hyperlink', () => {
364365
const url = 'https://vaadin.com';
365-
let dialog, overlay;
366+
let dialog, overlay, urlField, cancelButton, confirmButton, removeButton;
366367

367368
beforeEach(() => {
368369
btn = getButton('link');
369-
dialog = rte.$.linkDialog;
370+
dialog = rte.querySelector('[slot="link-dialog"]');
370371
overlay = dialog._overlayElement;
372+
urlField = dialog.querySelector('vaadin-text-field');
373+
cancelButton = dialog.querySelector('[slot="cancel-button"]');
374+
confirmButton = dialog.querySelector('[slot="confirm-button"]');
375+
removeButton = dialog.querySelector('[slot="reject-button"]');
371376
});
372377

373378
describe('dialog', () => {
@@ -377,34 +382,38 @@ describe('toolbar controls', () => {
377382
expect(dialog.opened).to.be.false;
378383
});
379384

380-
it('should focus whe text field when the dialog is opened', async () => {
381-
const spy = sinon.spy(rte.$.linkUrl, 'focus');
385+
it('should focus the text field when the dialog is opened', async () => {
382386
editor.focus();
383387
btn.click();
384388
await oneEvent(overlay, 'vaadin-overlay-open');
385-
expect(spy.calledOnce).to.be.true;
389+
expect(urlField.inputElement.matches(':focus')).to.be.true;
386390
});
387391

388392
it('should confirm the dialog by pressing enter in the focused text field', async () => {
389-
const spy = sinon.spy(rte.$.confirmLink, 'click');
393+
rte.value = JSON.stringify([{ insert: 'Vaadin' }]);
390394
editor.focus();
395+
editor.setSelection(0, 6);
396+
flushValueDebouncer();
397+
391398
btn.click();
392399
await oneEvent(overlay, 'vaadin-overlay-open');
393-
const evt = new CustomEvent('keydown');
394-
evt.keyCode = 13;
395-
rte.$.linkUrl.dispatchEvent(evt);
396-
expect(spy.calledOnce).to.be.true;
400+
401+
urlField.value = url;
402+
enter(urlField);
403+
flushValueDebouncer();
404+
405+
expect(rte.value).to.equal(`[{"attributes":{"link":"${url}"},"insert":"Vaadin"},{"insert":"\\n"}]`);
406+
expect(dialog.opened).to.be.false;
397407
});
398408

399-
it('should focus whe editor when the dialog is cancelled', async () => {
409+
it('should focus the editor when the dialog is cancelled', async () => {
400410
editor.focus();
401411
btn.click();
402412
await oneEvent(overlay, 'vaadin-overlay-open');
403413

404414
const spy = sinon.spy(editor, 'focus');
405-
rte.addEventListener('change', spy);
406415

407-
rte.$.cancelLink.click();
416+
cancelButton.click();
408417
expect(spy.calledOnce).to.be.true;
409418
});
410419
});
@@ -426,8 +435,8 @@ describe('toolbar controls', () => {
426435
flushValueDebouncer();
427436
btn.click();
428437
await oneEvent(overlay, 'vaadin-overlay-open');
429-
rte.$.linkUrl.value = url;
430-
rte.$.confirmLink.click();
438+
urlField.value = url;
439+
confirmButton.click();
431440
flushValueDebouncer();
432441
expect(rte.value).to.equal(`[{"attributes":{"link":"${url}"},"insert":"Vaadin"},{"insert":"\\n"}]`);
433442
});
@@ -439,8 +448,8 @@ describe('toolbar controls', () => {
439448
editor.setSelection(0, 6);
440449
btn.click();
441450
await oneEvent(overlay, 'vaadin-overlay-open');
442-
rte.$.linkUrl.value = url;
443-
rte.$.confirmLink.click();
451+
urlField.value = url;
452+
confirmButton.click();
444453
flushValueDebouncer();
445454
expect(rte.value).to.equal(`[{"attributes":{"link":"${url}"},"insert":"Vaadin"},{"insert":"\\n"}]`);
446455
});
@@ -452,7 +461,7 @@ describe('toolbar controls', () => {
452461
editor.setSelection(0, 6);
453462
btn.click();
454463
await oneEvent(overlay, 'vaadin-overlay-open');
455-
rte.$.removeLink.click();
464+
removeButton.click();
456465
flushValueDebouncer();
457466
expect(rte.value).to.equal('[{"insert":"Vaadin\\n"}]');
458467
});
@@ -471,8 +480,8 @@ describe('toolbar controls', () => {
471480
editor.setSelection(0, 0);
472481
btn.click();
473482
await oneEvent(overlay, 'vaadin-overlay-open');
474-
rte.$.linkUrl.value = url;
475-
rte.$.confirmLink.click();
483+
urlField.value = url;
484+
confirmButton.click();
476485
flushValueDebouncer();
477486
expect(rte.value).to.equal(`[{"attributes":{"link":"${url}"},"insert":"${url}"},{"insert":"\\n"}]`);
478487
});
@@ -484,8 +493,8 @@ describe('toolbar controls', () => {
484493
editor.setSelection(1, 0);
485494
btn.click();
486495
await oneEvent(overlay, 'vaadin-overlay-open');
487-
rte.$.linkUrl.value = url;
488-
rte.$.confirmLink.click();
496+
urlField.value = url;
497+
confirmButton.click();
489498
flushValueDebouncer();
490499
expect(rte.value).to.equal(`[{"attributes":{"link":"${url}"},"insert":"Vaadin"},{"insert":"\\n"}]`);
491500
});
@@ -497,7 +506,7 @@ describe('toolbar controls', () => {
497506
editor.setSelection(1, 0);
498507
btn.click();
499508
await oneEvent(overlay, 'vaadin-overlay-open');
500-
rte.$.removeLink.click();
509+
removeButton.click();
501510
flushValueDebouncer();
502511
expect(rte.value).to.equal('[{"insert":"Vaadin\\n"}]');
503512
});
@@ -512,12 +521,12 @@ describe('toolbar controls', () => {
512521
btn.click();
513522
await oneEvent(overlay, 'vaadin-overlay-open');
514523

515-
rte.$.linkUrl.value = url;
524+
urlField.value = url;
516525

517526
const spy = sinon.spy();
518527
rte.addEventListener('change', spy);
519528

520-
rte.$.confirmLink.click();
529+
confirmButton.click();
521530
await nextRender();
522531
flushValueDebouncer();
523532

@@ -536,7 +545,7 @@ describe('toolbar controls', () => {
536545
const spy = sinon.spy();
537546
rte.addEventListener('change', spy);
538547

539-
rte.$.cancelLink.click();
548+
cancelButton.click();
540549
await nextRender();
541550
flushValueDebouncer();
542551
expect(rte.value).to.equal(value);

0 commit comments

Comments
 (0)