fix: Open and highlight correct workflow form section on tab click (#2463)

Fixes https://github.com/webrecorder/browsertrix/issues/2461

## Changes

Opens workflow form section when clicking on section navigation link,
fixing issue with scroll position impacting unopened panels.
This commit is contained in:
sua yoo 2025-03-07 12:35:24 -08:00 committed by GitHub
parent 03fa00df45
commit fa05d68292
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -419,6 +419,8 @@ export class WorkflowEditor extends BtrixElement {
} }
private renderFormSections() { private renderFormSections() {
const activeTab = this.progressState?.activeTab;
const panelBody = ({ const panelBody = ({
name, name,
desc, desc,
@ -439,21 +441,27 @@ export class WorkflowEditor extends BtrixElement {
)} )}
?open=${required || hasError || tabProgress?.completed} ?open=${required || hasError || tabProgress?.completed}
@sl-focus=${() => { @sl-focus=${() => {
this.updateProgressState({ if (activeTab !== name) {
activeTab: name, this.updateProgressState({
}); activeTab: name,
});
}
}} }}
@sl-show=${this.handleCurrentTarget(() => { @sl-show=${this.handleCurrentTarget(() => {
this.pauseObserve(); if (activeTab !== name) {
this.updateProgressState({ this.pauseHandlePanelIntersect(name);
activeTab: name, this.updateProgressState({
}); activeTab: name,
});
}
track(AnalyticsTrackEvent.ExpandWorkflowFormSection, { track(AnalyticsTrackEvent.ExpandWorkflowFormSection, {
section: name, section: name,
}); });
})} })}
@sl-hide=${this.handleCurrentTarget((e: SlHideEvent) => { @sl-hide=${this.handleCurrentTarget((e: SlHideEvent) => {
this.pauseHandlePanelIntersect(name);
const el = e.currentTarget as SlDetails; const el = e.currentTarget as SlDetails;
// Check if there's any invalid elements before hiding // Check if there's any invalid elements before hiding
@ -473,8 +481,12 @@ export class WorkflowEditor extends BtrixElement {
invalidEl.checkValidity(); invalidEl.checkValidity();
} }
})} })}
@sl-after-show=${this.handleCurrentTarget(this.resumeObserve)} @sl-after-show=${this.handleCurrentTarget(
@sl-after-hide=${this.handleCurrentTarget(this.resumeObserve)} this.resumeHandlePanelIntersect,
)}
@sl-after-hide=${this.handleCurrentTarget(
this.resumeHandlePanelIntersect,
)}
> >
<div slot="expand-icon" class="flex items-center"> <div slot="expand-icon" class="flex items-center">
<sl-tooltip <sl-tooltip
@ -1652,25 +1664,22 @@ https://archiveweb.page/images/${"logo.svg"}`}
this.updateFormState(formState); this.updateFormState(formState);
} }
// Use to skip updates on intersect changes, like when scrolling // Store the panel to focus or scroll to temporarily
// an element into view on click // so that the intersection observer doesn't update
private skipIntersectUpdate = false; // the active tab on scroll
private scrollTargetTab: StepName | null = null;
private pauseObserve() { private pauseHandlePanelIntersect(targetActiveTab: StepName) {
this.onPanelIntersect.flush(); this.onPanelIntersect.flush();
this.skipIntersectUpdate = true; this.scrollTargetTab = targetActiveTab;
} }
private resumeObserve() { private resumeHandlePanelIntersect() {
this.skipIntersectUpdate = false; // Reset scroll target tab to indicate that scroll handling should continue
this.scrollTargetTab = null;
} }
private readonly onPanelIntersect = throttle(10)((e: Event) => { private readonly onPanelIntersect = throttle(10)((e: Event) => {
if (this.skipIntersectUpdate) {
this.resumeObserve();
return;
}
const { entries } = (e as IntersectEvent).detail; const { entries } = (e as IntersectEvent).detail;
entries.forEach((entry) => { entries.forEach((entry) => {
@ -1681,17 +1690,27 @@ https://archiveweb.page/images/${"logo.svg"}`}
} }
}); });
const panels = [...(this.panels ?? [])]; if (!this.scrollTargetTab) {
const activeTab = panels // Make first visible tab active
.find((panel) => this.visiblePanels.has(panel.id)) const panels = [...(this.panels ?? [])];
?.id.split(panelSuffix)[0] as StepName | undefined; const targetActiveTab = panels
.find((panel) => this.visiblePanels.has(panel.id))
?.id.split(panelSuffix)[0] as StepName | undefined;
if (!STEPS.includes(activeTab!)) { if (!targetActiveTab || !STEPS.includes(targetActiveTab)) {
console.debug("tab not in steps:", activeTab, this.visiblePanels); if (targetActiveTab) {
return; console.debug(
"tab not in steps:",
targetActiveTab,
this.visiblePanels,
);
}
return;
}
this.updateProgressState({ activeTab: targetActiveTab });
} }
this.updateProgressState({ activeTab });
}); });
private hasRequiredFields(): boolean { private hasRequiredFields(): boolean {
@ -1709,15 +1728,22 @@ https://archiveweb.page/images/${"logo.svg"}`}
return; return;
} }
this.pauseObserve(); if (this.progressState?.activeTab) {
this.pauseHandlePanelIntersect(this.progressState.activeTab);
}
// Focus on focusable element, if found, to highlight the section // Focus on focusable element, if found, to highlight the section
const summary = activeTabPanel const details = activeTabPanel.querySelector("sl-details")!;
.querySelector("sl-details") const summary = details.shadowRoot?.querySelector<HTMLElement>(
?.shadowRoot?.querySelector<HTMLElement>("summary[aria-controls]"); "summary[aria-controls]",
);
activeTabPanel.scrollIntoView({ block: "start" });
if (summary) { if (summary) {
summary.focus({ summary.focus({
// Handle scrolling into view separately
preventScroll: true,
// Prevent firefox from applying own focus styles // Prevent firefox from applying own focus styles
focusVisible: false, focusVisible: false,
} as FocusOptions & { } as FocusOptions & {
@ -1726,7 +1752,12 @@ https://archiveweb.page/images/${"logo.svg"}`}
} else { } else {
console.debug("summary not found in sl-details"); console.debug("summary not found in sl-details");
} }
activeTabPanel.scrollIntoView({ block: "start" });
if (details.open) {
this.resumeHandlePanelIntersect();
} else {
void details.show();
}
} }
private async handleRemoveRegex(e: CustomEvent) { private async handleRemoveRegex(e: CustomEvent) {