From 81b3d61a20e566bb257a9686034bf72c893aefd2 Mon Sep 17 00:00:00 2001 From: KoloMl Date: Sun, 5 Apr 2026 19:12:11 +0400 Subject: [PATCH 1/3] Fixed content layout shift caused by exclusive tags warning --- src/content/components/philomena/TagsForm.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/content/components/philomena/TagsForm.ts b/src/content/components/philomena/TagsForm.ts index 7a27cdc..606f149 100644 --- a/src/content/components/philomena/TagsForm.ts +++ b/src/content/components/philomena/TagsForm.ts @@ -172,6 +172,7 @@ export class TagsForm extends BaseComponent { } #onTagChangeRequested(event: CustomEvent) { + const targetElement = event.target instanceof HTMLElement ? event.target : null; const { addedTags = null, removedTags = null } = event.detail; let tagChangeList: string[] = []; @@ -187,20 +188,22 @@ export class TagsForm extends BaseComponent { ); } - const offsetBeforeSubmission = this.#presetsList.container.offsetTop; + const containerOffset = this.#presetsList.container.offsetTop; + const presetOffset = targetElement?.offsetTop || 0; this.#applyTagChangesWithFancyTagEditor( tagChangeList.join(',') ); - const offsetDifference = this.#presetsList.container.offsetTop - offsetBeforeSubmission; + const containerOffsetDifference = this.#presetsList.container.offsetTop - containerOffset; + const presetOffsetDifference = (targetElement?.offsetTop || 0) - presetOffset; // Compensating for the layout shift: when user clicks on a tag (or on "add/remove all tags"), tag editor might // overflow the current line and wrap tags around to the next line, causing presets section to shift. We need to // avoid that for better UX. - if (offsetDifference !== 0) { + if (containerOffsetDifference !== 0 || presetOffsetDifference !== 0) { window.scrollTo({ - top: window.scrollY + offsetDifference, + top: window.scrollY + containerOffsetDifference + presetOffsetDifference, behavior: 'instant', }); } From 7d41524b4afaa59b34a09a859b7782f2e5c237e6 Mon Sep 17 00:00:00 2001 From: KoloMl Date: Sun, 5 Apr 2026 19:21:09 +0400 Subject: [PATCH 2/3] Fixed scroll jump when preset becomes hidden --- src/content/components/philomena/TagsForm.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/content/components/philomena/TagsForm.ts b/src/content/components/philomena/TagsForm.ts index 606f149..f33d33e 100644 --- a/src/content/components/philomena/TagsForm.ts +++ b/src/content/components/philomena/TagsForm.ts @@ -196,7 +196,13 @@ export class TagsForm extends BaseComponent { ); const containerOffsetDifference = this.#presetsList.container.offsetTop - containerOffset; - const presetOffsetDifference = (targetElement?.offsetTop || 0) - presetOffset; + let presetOffsetDifference = (targetElement?.offsetTop || 0) - presetOffset; + + // If target element is no longer visible, then there is no need to apply scrolling fix, otherwise it will shift + // user up. Invisible elements are always report 0 offset. + if (targetElement?.checkVisibility() === false) { + presetOffsetDifference = 0; + } // Compensating for the layout shift: when user clicks on a tag (or on "add/remove all tags"), tag editor might // overflow the current line and wrap tags around to the next line, causing presets section to shift. We need to From 4f52906123789e0f8024d205ef9a5d1b4f5d06c7 Mon Sep 17 00:00:00 2001 From: KoloMl Date: Sun, 5 Apr 2026 19:46:34 +0400 Subject: [PATCH 3/3] Extracted CLS compensation logic into separate method --- src/content/components/philomena/TagsForm.ts | 58 ++++++++++++-------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/src/content/components/philomena/TagsForm.ts b/src/content/components/philomena/TagsForm.ts index f33d33e..8307ed8 100644 --- a/src/content/components/philomena/TagsForm.ts +++ b/src/content/components/philomena/TagsForm.ts @@ -9,8 +9,8 @@ import { EVENT_PRESET_TAG_CHANGE_APPLIED, type PresetTagChange } from "$content/ export class TagsForm extends BaseComponent { #togglePresetsButton: HTMLButtonElement = document.createElement('button'); #presetsList = EditorPresetsBlock.create(); - #plainEditorTextarea: HTMLTextAreaElement|null = null; - #fancyEditorInput: HTMLInputElement|null = null; + #plainEditorTextarea: HTMLTextAreaElement | null = null; + #fancyEditorInput: HTMLInputElement | null = null; #tagsSet: Set = new Set(); protected build() { @@ -173,7 +173,7 @@ export class TagsForm extends BaseComponent { #onTagChangeRequested(event: CustomEvent) { const targetElement = event.target instanceof HTMLElement ? event.target : null; - const { addedTags = null, removedTags = null } = event.detail; + const {addedTags = null, removedTags = null} = event.detail; let tagChangeList: string[] = []; if (addedTags) { @@ -188,31 +188,33 @@ export class TagsForm extends BaseComponent { ); } - const containerOffset = this.#presetsList.container.offsetTop; - const presetOffset = targetElement?.offsetTop || 0; - - this.#applyTagChangesWithFancyTagEditor( - tagChangeList.join(',') + this.#executeAndCompensateForLayoutShift( + () => this.#applyTagChangesWithFancyTagEditor(tagChangeList.join(',')), + [this.#presetsList.container, targetElement], ); + } - const containerOffsetDifference = this.#presetsList.container.offsetTop - containerOffset; - let presetOffsetDifference = (targetElement?.offsetTop || 0) - presetOffset; + #executeAndCompensateForLayoutShift(executeOperation: () => void, elements: (HTMLElement | null)[]) { + const offsetsListBefore = TagsForm.#gatherOffsetsFromElements(elements); + executeOperation(); + const offsetsListAfter = TagsForm.#gatherOffsetsFromElements(elements); - // If target element is no longer visible, then there is no need to apply scrolling fix, otherwise it will shift - // user up. Invisible elements are always report 0 offset. - if (targetElement?.checkVisibility() === false) { - presetOffsetDifference = 0; + const resultDifference = offsetsListAfter + .map((offsetAfter, index) => + offsetAfter !== null && offsetsListBefore[index] !== null + ? offsetAfter - offsetsListBefore[index] + : null) + .filter(difference => difference !== null) + .reduce((summary, difference) => summary + difference, 0); + + if (resultDifference === 0) { + return; } - // Compensating for the layout shift: when user clicks on a tag (or on "add/remove all tags"), tag editor might - // overflow the current line and wrap tags around to the next line, causing presets section to shift. We need to - // avoid that for better UX. - if (containerOffsetDifference !== 0 || presetOffsetDifference !== 0) { - window.scrollTo({ - top: window.scrollY + containerOffsetDifference + presetOffsetDifference, - behavior: 'instant', - }); - } + window.scrollTo({ + top: scrollY + resultDifference, + behavior: 'instant', + }) } #applyTagChangesWithFancyTagEditor(tagsListWithChanges: string): void { @@ -241,7 +243,7 @@ export class TagsForm extends BaseComponent { this.refreshTagColors(); } - #onPlainEditorReloadRequested(event: CustomEvent) { + #onPlainEditorReloadRequested(event: CustomEvent) { if (!event.detail?.skipTagColorRefresh) { this.refreshTagColors(); } @@ -251,6 +253,14 @@ export class TagsForm extends BaseComponent { } } + static #gatherOffsetsFromElements(elements: (HTMLElement | null)[]): (number | null)[] { + return elements.map( + maybeElement => maybeElement?.checkVisibility() + ? maybeElement?.offsetTop + : null + ); + } + static watchForEditors() { document.body.addEventListener('click', event => { const targetElement = event.target;