Tiptap RTE: Reduce loading layout shift (#19860)
* Tiptap RTE: Set row/group min-height to prevent layout shift * Added `box-sizing: border-box` * Adds loaded state to the editor so that the border only appears once it's ready. * Refactored toolbar to reduce the number of re-renders * Refactored statusbar to reduce the number of re-renders
This commit is contained in:
@@ -193,7 +193,7 @@ export class UmbInputTiptapElement extends UmbFormControlMixin<string, typeof Um
|
||||
return html`
|
||||
${when(loading, () => html`<div id="loader"><uui-loader></uui-loader></div>`)}
|
||||
${when(!loading, () => html`${this.#renderStyles()}${this.#renderToolbar()}`)}
|
||||
<div id="editor" data-mark="input:tiptap-rte"></div>
|
||||
<div id="editor" data-mark="input:tiptap-rte" ?data-loaded=${!loading}></div>
|
||||
${when(!loading, () => this.#renderStatusbar())}
|
||||
`;
|
||||
}
|
||||
@@ -277,7 +277,7 @@ export class UmbInputTiptapElement extends UmbFormControlMixin<string, typeof Um
|
||||
display: flex;
|
||||
overflow: auto;
|
||||
border-radius: var(--uui-border-radius);
|
||||
border: 1px solid var(--umb-tiptap-edge-border-color, var(--uui-color-border));
|
||||
border: 1px solid transparent;
|
||||
padding: 1rem;
|
||||
box-sizing: border-box;
|
||||
|
||||
@@ -288,6 +288,10 @@ export class UmbInputTiptapElement extends UmbFormControlMixin<string, typeof Um
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
|
||||
&[data-loaded] {
|
||||
border-color: var(--umb-tiptap-edge-border-color, var(--uui-color-border));
|
||||
}
|
||||
|
||||
> .tiptap {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
@@ -1,18 +1,26 @@
|
||||
import type { UmbTiptapStatusbarValue } from '../types.js';
|
||||
import { css, customElement, html, map, nothing, property, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { css, customElement, html, nothing, property, repeat } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { debounce } from '@umbraco-cms/backoffice/utils';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { UmbExtensionsElementInitializer } from '@umbraco-cms/backoffice/extension-api';
|
||||
import type { Editor } from '@umbraco-cms/backoffice/external/tiptap';
|
||||
import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor';
|
||||
|
||||
/**
|
||||
* Provides a status bar for the {@link UmbInputTiptapElement}
|
||||
* @element umb-tiptap-statusbar
|
||||
* @cssprop --umb-tiptap-edge-border-color - Defines the edge border color
|
||||
*/
|
||||
@customElement('umb-tiptap-statusbar')
|
||||
export class UmbTiptapStatusbarElement extends UmbLitElement {
|
||||
#attached = false;
|
||||
|
||||
#debouncer = debounce(() => this.requestUpdate(), 100);
|
||||
|
||||
#extensionsController?: UmbExtensionsElementInitializer;
|
||||
|
||||
@state()
|
||||
private _lookup?: Map<string, unknown>;
|
||||
#lookup: Map<string, unknown> = new Map();
|
||||
|
||||
@property({ type: Boolean, reflect: true })
|
||||
readonly = false;
|
||||
@@ -61,12 +69,13 @@ export class UmbTiptapStatusbarElement extends UmbLitElement {
|
||||
'tiptapStatusbarExtension',
|
||||
(manifest) => this.statusbar.flat().includes(manifest.alias),
|
||||
(extensionControllers) => {
|
||||
this._lookup = new Map(
|
||||
extensionControllers.map((ext) => {
|
||||
extensionControllers.forEach((ext) => {
|
||||
if (!this.#lookup.has(ext.alias)) {
|
||||
(ext.component as HTMLElement)?.setAttribute('data-mark', `action:tiptap-statusbar:${ext.alias}`);
|
||||
return [ext.alias, ext.component];
|
||||
}),
|
||||
);
|
||||
this.#lookup.set(ext.alias, ext.component);
|
||||
this.#debouncer();
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
@@ -75,10 +84,15 @@ export class UmbTiptapStatusbarElement extends UmbLitElement {
|
||||
|
||||
override render() {
|
||||
if (!this.statusbar.flat().length) return nothing;
|
||||
return map(
|
||||
this.statusbar,
|
||||
(area) => html`<div class="area">${map(area, (alias) => this._lookup?.get(alias) ?? nothing)}</div>`,
|
||||
);
|
||||
return this.#renderAreas(this.statusbar);
|
||||
}
|
||||
|
||||
#renderAreas(statusbar: UmbTiptapStatusbarValue) {
|
||||
return repeat(statusbar, (area) => html`<div class="area">${this.#renderActions(area)}</div>`);
|
||||
}
|
||||
|
||||
#renderActions(aliases: Array<string>) {
|
||||
return repeat(aliases, (alias) => this.#lookup?.get(alias) ?? nothing);
|
||||
}
|
||||
|
||||
static override readonly styles = css`
|
||||
@@ -95,10 +109,11 @@ export class UmbTiptapStatusbarElement extends UmbLitElement {
|
||||
justify-content: space-between;
|
||||
|
||||
border-radius: var(--uui-border-radius);
|
||||
border: 1px solid var(--uui-color-border);
|
||||
border: 1px solid var(--umb-tiptap-edge-border-color, var(--uui-color-border));
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
border-top: 0;
|
||||
box-sizing: border-box;
|
||||
|
||||
min-height: var(--uui-size-layout-1);
|
||||
max-height: var(--uui-size-layout-2);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { UmbTiptapToolbarValue } from '../types.js';
|
||||
import { css, customElement, html, map, nothing, property, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { css, customElement, html, nothing, property, repeat } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { debounce } from '@umbraco-cms/backoffice/utils';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { UmbExtensionsElementAndApiInitializer } from '@umbraco-cms/backoffice/extension-api';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
@@ -9,18 +10,20 @@ import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/
|
||||
import '../cascading-menu-popover/cascading-menu-popover.element.js';
|
||||
|
||||
/**
|
||||
* Provides a sticky toolbar for the {@link UmbInputTiptapElement}
|
||||
* @element umb-tiptap-toolbar
|
||||
* @cssprop --umb-tiptap-edge-border-color - Defines the edge border color
|
||||
* @cssprop --umb-tiptap-top - Defines the top value for the sticky toolbar
|
||||
*/
|
||||
* Provides a sticky toolbar for the {@link UmbInputTiptapElement}
|
||||
* @element umb-tiptap-toolbar
|
||||
* @cssprop --umb-tiptap-edge-border-color - Defines the edge border color
|
||||
* @cssprop --umb-tiptap-top - Defines the top value for the sticky toolbar
|
||||
*/
|
||||
@customElement('umb-tiptap-toolbar')
|
||||
export class UmbTiptapToolbarElement extends UmbLitElement {
|
||||
#attached = false;
|
||||
|
||||
#debouncer = debounce(() => this.requestUpdate(), 100);
|
||||
|
||||
#extensionsController?: UmbExtensionsElementAndApiInitializer;
|
||||
|
||||
@state()
|
||||
private _lookup?: Map<string, unknown>;
|
||||
#lookup: Map<string, unknown> = new Map();
|
||||
|
||||
@property({ type: Boolean, reflect: true })
|
||||
readonly = false;
|
||||
@@ -58,12 +61,13 @@ export class UmbTiptapToolbarElement extends UmbLitElement {
|
||||
[],
|
||||
(manifest) => this.toolbar.flat(2).includes(manifest.alias),
|
||||
(extensionControllers) => {
|
||||
this._lookup = new Map(
|
||||
extensionControllers.map((ext) => {
|
||||
extensionControllers.forEach((ext) => {
|
||||
if (!this.#lookup.has(ext.alias)) {
|
||||
(ext.component as HTMLElement)?.setAttribute('data-mark', `action:tiptap-toolbar:${ext.alias}`);
|
||||
return [ext.alias, ext.component];
|
||||
}),
|
||||
);
|
||||
this.#lookup.set(ext.alias, ext.component);
|
||||
this.#debouncer();
|
||||
}
|
||||
});
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
@@ -76,18 +80,23 @@ export class UmbTiptapToolbarElement extends UmbLitElement {
|
||||
|
||||
override render() {
|
||||
if (!this.toolbar.flat(2).length) return nothing;
|
||||
return this.#renderRows(this.toolbar);
|
||||
}
|
||||
|
||||
return map(
|
||||
this.toolbar,
|
||||
(row) => html`
|
||||
<div class="row">
|
||||
${map(
|
||||
row,
|
||||
(group) => html`<div class="group">${map(group, (alias) => this._lookup?.get(alias) ?? nothing)}</div>`,
|
||||
)}
|
||||
</div>
|
||||
`,
|
||||
);
|
||||
#renderRows(rows: UmbTiptapToolbarValue) {
|
||||
return repeat(rows, (row) => html`<div class="row">${this.#renderGroups(row)}</div>`);
|
||||
}
|
||||
|
||||
#renderGroups(groups: Array<Array<string>>) {
|
||||
return repeat(groups, (group) => html`<div class="group">${this.#renderActions(group)}</div>`);
|
||||
}
|
||||
|
||||
#renderActions(aliases: Array<string>) {
|
||||
return repeat(aliases, (alias) => this.#lookup?.get(alias) ?? this.#renderActionPlaceholder());
|
||||
}
|
||||
|
||||
#renderActionPlaceholder() {
|
||||
return html`<span class="skeleton" role="none"></span>`;
|
||||
}
|
||||
|
||||
static override readonly styles = css`
|
||||
@@ -105,6 +114,7 @@ export class UmbTiptapToolbarElement extends UmbLitElement {
|
||||
border-top-color: var(--umb-tiptap-edge-border-color, var(--uui-color-border));
|
||||
border-left-color: var(--umb-tiptap-edge-border-color, var(--uui-color-border));
|
||||
border-right-color: var(--umb-tiptap-edge-border-color, var(--uui-color-border));
|
||||
box-sizing: border-box;
|
||||
|
||||
background-color: var(--uui-color-surface);
|
||||
color: var(--color-text);
|
||||
@@ -114,7 +124,7 @@ export class UmbTiptapToolbarElement extends UmbLitElement {
|
||||
flex-direction: column;
|
||||
|
||||
position: sticky;
|
||||
top: var(--umb-tiptap-top,-25px);
|
||||
top: var(--umb-tiptap-top, -25px);
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: var(--uui-size-3);
|
||||
@@ -130,21 +140,32 @@ export class UmbTiptapToolbarElement extends UmbLitElement {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
|
||||
min-height: var(--uui-size-12, 36px);
|
||||
|
||||
.group {
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: stretch;
|
||||
|
||||
min-height: var(--uui-size-12, 36px);
|
||||
|
||||
&:not(:last-child)::after {
|
||||
content: '';
|
||||
background-color: var(--uui-color-border);
|
||||
width: 1px;
|
||||
place-self: center;
|
||||
height: 22px;
|
||||
height: var(--uui-size-7, 21px);
|
||||
margin: 0 var(--uui-size-3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.skeleton {
|
||||
background-color: var(--uui-color-background);
|
||||
height: var(--uui-size-12, 36px);
|
||||
width: var(--uui-size-10, 30px);
|
||||
margin-left: 1px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user