Skip to content

Commit b3d5744

Browse files
authored
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
1 parent 5f1ecba commit b3d5744

File tree

3 files changed

+81
-41
lines changed

3 files changed

+81
-41
lines changed

src/Umbraco.Web.UI.Client/src/packages/tiptap/components/input-tiptap/input-tiptap.element.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ export class UmbInputTiptapElement extends UmbFormControlMixin<string, typeof Um
193193
return html`
194194
${when(loading, () => html`<div id="loader"><uui-loader></uui-loader></div>`)}
195195
${when(!loading, () => html`${this.#renderStyles()}${this.#renderToolbar()}`)}
196-
<div id="editor" data-mark="input:tiptap-rte"></div>
196+
<div id="editor" data-mark="input:tiptap-rte" ?data-loaded=${!loading}></div>
197197
${when(!loading, () => this.#renderStatusbar())}
198198
`;
199199
}
@@ -277,7 +277,7 @@ export class UmbInputTiptapElement extends UmbFormControlMixin<string, typeof Um
277277
display: flex;
278278
overflow: auto;
279279
border-radius: var(--uui-border-radius);
280-
border: 1px solid var(--umb-tiptap-edge-border-color, var(--uui-color-border));
280+
border: 1px solid transparent;
281281
padding: 1rem;
282282
box-sizing: border-box;
283283
@@ -288,6 +288,10 @@ export class UmbInputTiptapElement extends UmbFormControlMixin<string, typeof Um
288288
width: 100%;
289289
max-width: 100%;
290290
291+
&[data-loaded] {
292+
border-color: var(--umb-tiptap-edge-border-color, var(--uui-color-border));
293+
}
294+
291295
> .tiptap {
292296
height: 100%;
293297
width: 100%;

src/Umbraco.Web.UI.Client/src/packages/tiptap/components/input-tiptap/tiptap-statusbar.element.ts

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,26 @@
11
import type { UmbTiptapStatusbarValue } from '../types.js';
2-
import { css, customElement, html, map, nothing, property, state } from '@umbraco-cms/backoffice/external/lit';
2+
import { css, customElement, html, nothing, property, repeat } from '@umbraco-cms/backoffice/external/lit';
3+
import { debounce } from '@umbraco-cms/backoffice/utils';
34
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
45
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
56
import { UmbExtensionsElementInitializer } from '@umbraco-cms/backoffice/extension-api';
67
import type { Editor } from '@umbraco-cms/backoffice/external/tiptap';
78
import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor';
89

10+
/**
11+
* Provides a status bar for the {@link UmbInputTiptapElement}
12+
* @element umb-tiptap-statusbar
13+
* @cssprop --umb-tiptap-edge-border-color - Defines the edge border color
14+
*/
915
@customElement('umb-tiptap-statusbar')
1016
export class UmbTiptapStatusbarElement extends UmbLitElement {
1117
#attached = false;
18+
19+
#debouncer = debounce(() => this.requestUpdate(), 100);
20+
1221
#extensionsController?: UmbExtensionsElementInitializer;
1322

14-
@state()
15-
private _lookup?: Map<string, unknown>;
23+
#lookup: Map<string, unknown> = new Map();
1624

1725
@property({ type: Boolean, reflect: true })
1826
readonly = false;
@@ -61,12 +69,13 @@ export class UmbTiptapStatusbarElement extends UmbLitElement {
6169
'tiptapStatusbarExtension',
6270
(manifest) => this.statusbar.flat().includes(manifest.alias),
6371
(extensionControllers) => {
64-
this._lookup = new Map(
65-
extensionControllers.map((ext) => {
72+
extensionControllers.forEach((ext) => {
73+
if (!this.#lookup.has(ext.alias)) {
6674
(ext.component as HTMLElement)?.setAttribute('data-mark', `action:tiptap-statusbar:${ext.alias}`);
67-
return [ext.alias, ext.component];
68-
}),
69-
);
75+
this.#lookup.set(ext.alias, ext.component);
76+
this.#debouncer();
77+
}
78+
});
7079
},
7180
);
7281

@@ -75,10 +84,15 @@ export class UmbTiptapStatusbarElement extends UmbLitElement {
7584

7685
override render() {
7786
if (!this.statusbar.flat().length) return nothing;
78-
return map(
79-
this.statusbar,
80-
(area) => html`<div class="area">${map(area, (alias) => this._lookup?.get(alias) ?? nothing)}</div>`,
81-
);
87+
return this.#renderAreas(this.statusbar);
88+
}
89+
90+
#renderAreas(statusbar: UmbTiptapStatusbarValue) {
91+
return repeat(statusbar, (area) => html`<div class="area">${this.#renderActions(area)}</div>`);
92+
}
93+
94+
#renderActions(aliases: Array<string>) {
95+
return repeat(aliases, (alias) => this.#lookup?.get(alias) ?? nothing);
8296
}
8397

8498
static override readonly styles = css`
@@ -95,10 +109,11 @@ export class UmbTiptapStatusbarElement extends UmbLitElement {
95109
justify-content: space-between;
96110
97111
border-radius: var(--uui-border-radius);
98-
border: 1px solid var(--uui-color-border);
112+
border: 1px solid var(--umb-tiptap-edge-border-color, var(--uui-color-border));
99113
border-top-left-radius: 0;
100114
border-top-right-radius: 0;
101115
border-top: 0;
116+
box-sizing: border-box;
102117
103118
min-height: var(--uui-size-layout-1);
104119
max-height: var(--uui-size-layout-2);

src/Umbraco.Web.UI.Client/src/packages/tiptap/components/input-tiptap/tiptap-toolbar.element.ts

Lines changed: 47 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { UmbTiptapToolbarValue } from '../types.js';
2-
import { css, customElement, html, map, nothing, property, state } from '@umbraco-cms/backoffice/external/lit';
2+
import { css, customElement, html, nothing, property, repeat } from '@umbraco-cms/backoffice/external/lit';
3+
import { debounce } from '@umbraco-cms/backoffice/utils';
34
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
45
import { UmbExtensionsElementAndApiInitializer } from '@umbraco-cms/backoffice/extension-api';
56
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
@@ -9,18 +10,20 @@ import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/
910
import '../cascading-menu-popover/cascading-menu-popover.element.js';
1011

1112
/**
12-
* Provides a sticky toolbar for the {@link UmbInputTiptapElement}
13-
* @element umb-tiptap-toolbar
14-
* @cssprop --umb-tiptap-edge-border-color - Defines the edge border color
15-
* @cssprop --umb-tiptap-top - Defines the top value for the sticky toolbar
16-
*/
13+
* Provides a sticky toolbar for the {@link UmbInputTiptapElement}
14+
* @element umb-tiptap-toolbar
15+
* @cssprop --umb-tiptap-edge-border-color - Defines the edge border color
16+
* @cssprop --umb-tiptap-top - Defines the top value for the sticky toolbar
17+
*/
1718
@customElement('umb-tiptap-toolbar')
1819
export class UmbTiptapToolbarElement extends UmbLitElement {
1920
#attached = false;
21+
22+
#debouncer = debounce(() => this.requestUpdate(), 100);
23+
2024
#extensionsController?: UmbExtensionsElementAndApiInitializer;
2125

22-
@state()
23-
private _lookup?: Map<string, unknown>;
26+
#lookup: Map<string, unknown> = new Map();
2427

2528
@property({ type: Boolean, reflect: true })
2629
readonly = false;
@@ -58,12 +61,13 @@ export class UmbTiptapToolbarElement extends UmbLitElement {
5861
[],
5962
(manifest) => this.toolbar.flat(2).includes(manifest.alias),
6063
(extensionControllers) => {
61-
this._lookup = new Map(
62-
extensionControllers.map((ext) => {
64+
extensionControllers.forEach((ext) => {
65+
if (!this.#lookup.has(ext.alias)) {
6366
(ext.component as HTMLElement)?.setAttribute('data-mark', `action:tiptap-toolbar:${ext.alias}`);
64-
return [ext.alias, ext.component];
65-
}),
66-
);
67+
this.#lookup.set(ext.alias, ext.component);
68+
this.#debouncer();
69+
}
70+
});
6771
},
6872
undefined,
6973
undefined,
@@ -76,18 +80,23 @@ export class UmbTiptapToolbarElement extends UmbLitElement {
7680

7781
override render() {
7882
if (!this.toolbar.flat(2).length) return nothing;
83+
return this.#renderRows(this.toolbar);
84+
}
7985

80-
return map(
81-
this.toolbar,
82-
(row) => html`
83-
<div class="row">
84-
${map(
85-
row,
86-
(group) => html`<div class="group">${map(group, (alias) => this._lookup?.get(alias) ?? nothing)}</div>`,
87-
)}
88-
</div>
89-
`,
90-
);
86+
#renderRows(rows: UmbTiptapToolbarValue) {
87+
return repeat(rows, (row) => html`<div class="row">${this.#renderGroups(row)}</div>`);
88+
}
89+
90+
#renderGroups(groups: Array<Array<string>>) {
91+
return repeat(groups, (group) => html`<div class="group">${this.#renderActions(group)}</div>`);
92+
}
93+
94+
#renderActions(aliases: Array<string>) {
95+
return repeat(aliases, (alias) => this.#lookup?.get(alias) ?? this.#renderActionPlaceholder());
96+
}
97+
98+
#renderActionPlaceholder() {
99+
return html`<span class="skeleton" role="none"></span>`;
91100
}
92101

93102
static override readonly styles = css`
@@ -105,6 +114,7 @@ export class UmbTiptapToolbarElement extends UmbLitElement {
105114
border-top-color: var(--umb-tiptap-edge-border-color, var(--uui-color-border));
106115
border-left-color: var(--umb-tiptap-edge-border-color, var(--uui-color-border));
107116
border-right-color: var(--umb-tiptap-edge-border-color, var(--uui-color-border));
117+
box-sizing: border-box;
108118
109119
background-color: var(--uui-color-surface);
110120
color: var(--color-text);
@@ -114,7 +124,7 @@ export class UmbTiptapToolbarElement extends UmbLitElement {
114124
flex-direction: column;
115125
116126
position: sticky;
117-
top: var(--umb-tiptap-top,-25px);
127+
top: var(--umb-tiptap-top, -25px);
118128
left: 0;
119129
right: 0;
120130
padding: var(--uui-size-3);
@@ -130,21 +140,32 @@ export class UmbTiptapToolbarElement extends UmbLitElement {
130140
flex-direction: row;
131141
flex-wrap: wrap;
132142
143+
min-height: var(--uui-size-12, 36px);
144+
133145
.group {
134146
display: inline-flex;
135147
flex-wrap: wrap;
136148
align-items: stretch;
137149
150+
min-height: var(--uui-size-12, 36px);
151+
138152
&:not(:last-child)::after {
139153
content: '';
140154
background-color: var(--uui-color-border);
141155
width: 1px;
142156
place-self: center;
143-
height: 22px;
157+
height: var(--uui-size-7, 21px);
144158
margin: 0 var(--uui-size-3);
145159
}
146160
}
147161
}
162+
163+
.skeleton {
164+
background-color: var(--uui-color-background);
165+
height: var(--uui-size-12, 36px);
166+
width: var(--uui-size-10, 30px);
167+
margin-left: 1px;
168+
}
148169
`;
149170
}
150171

0 commit comments

Comments
 (0)