Skip to content

feat(bulk-editor): row-level Save/Cancel and unsaved-changes tab-switch modal#23375

Open
JorPV wants to merge 8 commits into
feature/bulk-editorfrom
bulk-editor/inline-edit-ux-redesign
Open

feat(bulk-editor): row-level Save/Cancel and unsaved-changes tab-switch modal#23375
JorPV wants to merge 8 commits into
feature/bulk-editorfrom
bulk-editor/inline-edit-ux-redesign

Conversation

@JorPV

@JorPV JorPV commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Context

Updates the inline-edit row UX to the latest design. The issue bundles two related changes; this PR covers the two Free-side ones:

  1. Row-level Save/Cancel — the per-field Apply/Discard buttons are replaced by a single Save and Cancel in the Actions column (desing).
  2. Unsaved-changes tab-switch modal — switching the Search/Social tab while rows have unsaved edits now confirms via a modal instead of silently discarding (design).

Summary

This PR can be summarized in the following changelog entry:

  • Replaces the bulk editor's per-field inline-edit buttons with a row-level Save and Cancel, and confirms before discarding unsaved edits on a tab switch.

Relevant technical choices:

  • Row Save = save-all — Save fires the existing per-field save for every open field on the row; the row locks while any field is saving.
  • Tab-switch guardbulk-editor-content gates the switch on hasUnsavedEdits via a local pendingTab state; the modal lives alongside it (no store change).
  • Synchronous capture — Save-changes reads each field's draft before its first await, so clearing the edit state right after still posts the captured values.
  • Modal initial focus — focus opens on Save changes (the safe, non-destructive action) per the ARIA dialog pattern.

Test instructions

Test instructions for the acceptance test before the PR gets merged

This PR can be acceptance tested by following these steps:

  • Check out this branch, run yarn install + grunt build:js, and open SEO → Bulk editor (admin.php?page=wpseo_page_bulk_edit).

Row-level Save/Cancel

  • Click Edit on a row — the field cells become editable textareas, and the Actions column shows Save and Cancel (both magenta tertiary buttons) split by a thin separator line. There are no per-field Apply/Discard buttons.
  • Change a field and click Save — the row's edits are saved and the row returns to read-only with the new values.
  • Edit another row and click Cancel — the edits are discarded and the row returns to read-only.
  • While a row is saving, its inputs and the Save/Cancel buttons are disabled.

Unsaved-changes tab-switch modal

  • Click Edit on a row (don't save), then click the Social appearance (or Search appearance) tab.
  • A modal appears — "Unsaved changes" — with Save changes, Continue without saving, and Cancel; the tab does not switch yet.
  • Cancel → the modal closes, you stay on the current tab, and the edit is preserved.
  • Re-trigger, then Continue without saving → the edits are discarded and the tab switches.
  • Re-trigger, then make changes -> Save changes → the open edits are saved and the tab switches. Check also the changes are correctly saved in the Post/Page in the editor.

Accessibility / focus checks

  • Modal layout matches the design: the icon sits to the left of a left-aligned title + description; the three buttons are stacked full-width.
  • Initial focus lands on Save changes when the modal opens (not on the destructive "Continue without saving", and not the close X).
  • Keyboard: with a row in edit mode, moving to another tab with the arrow keys (ArrowLeft/Right on the focused tab) also opens the modal. Tab cycles within the modal; Esc and the close X both dismiss it (same as Cancel).
  • Screen reader (VoiceOver/NVDA): the modal is announced as a dialog named "Unsaved changes" with its description; the warning icon is not announced (decorative). The row Save/Cancel buttons expose per-row accessible names ("Save <title>" / "Cancel editing <title>").
  • Known minor: after dismissing via an arrow-key-triggered switch, focus returns to the adjacent (still-inactive) tab — focus is not lost; out of scope to refactor the tablist focus model here.

Relevant test scenarios

  • Changes should be tested with the browser console open
  • Changes should be tested on different posts/pages/taxonomies/custom post types/custom taxonomies
  • Changes should be tested on different editors (Default Block/Gutenberg/Classic/Elementor/other)
  • Changes should be tested on different browsers
  • Changes should be tested on multisite

Test instructions for QA when the code is in the RC

  • QA should use the same steps as above.

Impact check

This PR affects the following parts of the plugin, which may require extra testing:

  • The bulk editor inline-edit row and the Search/Social tab switching.

Other environments

  • This PR also affects Shopify.
  • This PR also affects Yoast SEO for Google Docs.

Documentation

  • I have written documentation for this change. (Code comments.)

Quality assurance

  • I have tested this code to the best of my abilities.
  • During testing, I had activated all plugins that Yoast SEO provides integrations for.
  • I have added unit tests to verify the code works as intended.
  • If any part of the code is behind a feature flag, my test instructions also cover cases where the feature flag is switched off.
  • I have written this PR in accordance with my team's definition of done.
  • I have checked that the base branch is correctly set.
  • I have run grunt build:images and committed the results, if my PR introduces or edits images or SVGs.

Innovation

  • No innovation project is applicable for this PR.
  • This PR falls under an innovation project. I have attached the innovation label.
  • I have added my hours to the WBSO document.

Notes for the reviewer / merger

  • EditableFieldCell conflicts with Adds selection and bulk-action bar #23367. This PR rewrites EditableFieldCell on feature/bulk-editor, which doesn't yet have Adds selection and bulk-action bar #23367's gradient-border / AnimateHeight tweaks to the same component. Whichever lands second will conflict — take care not to silently drop those styles or re-introduce the removed per-field Apply/Discard.
  • Save-changes failure path: the modal's "Save changes" clears the edit state and switches regardless of save outcome, so a rejected save is dropped silently (unlike the normal row Save, which keeps the field open on failure). Acceptable for a "you're leaving anyway" decision; flagged as a deliberate choice.

Fixes Yoast/reserved-tasks#1310

JorPV and others added 2 commits June 19, 2026 10:39
Switching the Search/Social tab while rows have unsaved inline edits now opens a
confirmation modal (Save changes / Continue without saving / Cancel) instead of
silently discarding the edits. Save fires the row saves and switches; Continue
discards and switches; Cancel stays on the current tab.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@JorPV JorPV added innovation Innovative issue. Relating to performance, memory or data-flow. changelog: enhancement Needs to be included in the 'Enhancements' category in the changelog changelog: non-user-facing Needs to be included in the 'Non-userfacing' category in the changelog and removed changelog: enhancement Needs to be included in the 'Enhancements' category in the changelog labels Jun 19, 2026
@JorPV JorPV added this to the feature/bulk-editor milestone Jun 19, 2026
@coveralls

coveralls commented Jun 19, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 0

Coverage decreased (-0.005%) to 57.932%

Details

  • Coverage decreased (-0.005%) from the base build.
  • Patch coverage: 1 uncovered change across 1 file (28 of 29 lines covered, 96.55%).
  • No coverage regressions found.

Uncovered Changes

File Changed Covered %
packages/js/src/bulk-editor/components/bulk-editor-content.js 18 17 94.44%
Total (4 files) 29 28 96.55%

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 28852
Covered Lines: 17139
Line Coverage: 59.4%
Relevant Branches: 18257
Covered Branches: 10152
Branch Coverage: 55.61%
Branches in Coverage %: Yes
Coverage Strength: 104295.7 hits per line

💛 - Coveralls

@github-actions

Copy link
Copy Markdown

A merge conflict has been detected for the proposed code changes in this PR. Please resolve the conflict by either rebasing the PR or merging in changes from the base branch.

Comment thread packages/js/src/bulk-editor/components/table/table-cells.js Outdated
Comment thread packages/js/src/bulk-editor/components/unsaved-changes-modal.js
Comment thread packages/js/src/bulk-editor/components/unsaved-changes-modal.js Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates the Bulk Editor inline-edit UX to match the latest design by moving from per-field Apply/Discard actions to row-level Save/Cancel, and by preventing accidental loss of in-progress edits when switching between the Search/Social appearance tabs.

Changes:

  • Replaces per-field Apply/Discard buttons with row-level Save/Cancel actions in the table’s Actions column.
  • Adds an “Unsaved changes” confirmation modal that gates tab switching when rows are in edit mode.
  • Updates and expands Jest coverage for the new row-level actions and tab-switch confirmation flows.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated no comments.

Show a summary per file
File Description
packages/js/tests/bulk-editor/bulk-editor-table.test.js Updates table tests to assert row-level Save/Cancel behavior and row-level disablement while saving.
packages/js/tests/bulk-editor/app.test.js Adds coverage for the unsaved-changes tab-switch modal and updates save flows from “Apply” to “Save”.
packages/js/src/bulk-editor/hooks/use-inline-edit.js Removes the per-field discard seam from the inline-edit hook API while keeping per-field save.
packages/js/src/bulk-editor/components/unsaved-changes-modal.js Introduces the new confirmation modal used when switching tabs with in-progress edits.
packages/js/src/bulk-editor/components/table/table-row.js Implements row-level Save/Cancel actions and locks the entire row while any field is saving.
packages/js/src/bulk-editor/components/table/table-cells.js Simplifies editable cells to just the textarea; actions move to the row-level controls.
packages/js/src/bulk-editor/components/table/bulk-editor-table.js Updates inline-edit typedef/defaults to remove the per-field discard seam.
packages/js/src/bulk-editor/components/bulk-editor-content.js Adds tab-switch guarding with pendingTab state and wires up the UnsavedChangesModal actions.

Comment thread packages/js/src/bulk-editor/components/bulk-editor-content.js
Comment thread packages/js/src/bulk-editor/components/bulk-editor-content.js
Object.entries( editing.editingRows ).forEach( ( [ id, row ] ) =>
row.openFields.forEach( ( key ) => editing.onApplyField( { id: Number( id ), key } ) )
);
stopEditing();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged in the PR notes, flagging here for visibility: stopEditing() clears the local edit state and the tab switch happens regardless of whether the onApplyField promises resolve or reject. A save failure on this path is silently dropped — the user won't know a field didn't save. Acceptable for a "leaving anyway" flow, but worth a follow-up to at least surface a toast/notice if any promise rejects.

vraja-pro and others added 3 commits June 22, 2026 16:45
…r, and AI upsell into the row-level Save/Cancel redesign

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@vraja-pro vraja-pro left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CR & AC ✅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog: non-user-facing Needs to be included in the 'Non-userfacing' category in the changelog innovation Innovative issue. Relating to performance, memory or data-flow.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants