Commit b638729fadd for woocommerce
commit b638729fadddfef936a32a05829d1268b4cae1a3
Author: Jana Hernandez <71137829+JanaMW27@users.noreply.github.com>
Date: Wed Jul 29 15:58:33 2026 +0200
Fix keyboard flow when adding products to an order (rogue search box) + clear/chevron overlap (#66381)
* docs(DSGWOO-1422): add design spec for order add-products keyboard fix
Design spec for fixing the legacy admin Add item(s) -> Add product(s)
modal: keep the select2 search dropdown inside the popup (dropdownParent),
stop the modal Enter handler from hijacking select2, and fix the
clear/chevron icon overlap. Includes grep-verified blast radius and a
keyboard e2e regression test plan.
Ref: https://github.com/woocommerce/woocommerce/issues/33517
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(DSGWOO-1422): add implementation plan for add-products keyboard fix
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(DSGWOO-1422): add keyboard-flow regression for add-products modal
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(DSGWOO-1422): render add-products search dropdown inside the modal
Set selectWoo dropdownParent to the enclosing .wc-backbone-modal-content so
the product-search dropdown no longer detaches to <body> and strands a rogue
search box in the page corner. Only applied when the field is inside a modal.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(DSGWOO-1422): don't submit backbone modal on Enter inside a select
keyboardActions no longer fires the OK/Next button when focus is on a
selectWoo/select2 control or a dropdown is open within the modal, so Enter
operates the dropdown instead of submitting and closing the modal. Shared by
all admin backbone modals; the guard only backs off for enhanced-select.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(DSGWOO-1422): stop clear/chevron overlap in add-products modal
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(DSGWOO-1422): add changelog entry for add-products keyboard fix
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(DSGWOO-1422): remove position:relative that broke modal centering
The earlier position:relative safeguard on .wc-backbone-modal-content
overrode the modal's own position:fixed centering, dropping the Add
products modal into normal flow (rendered low/off-centre) and
mispositioning the search dropdown. position:fixed already provides the
containing block the dropdown needs, so the safeguard was unnecessary.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(DSGWOO-1422): record position:relative regression in plan
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(DSGWOO-1422): drop dropdownParent; let search dropdown attach to body
selectWoo's _positionDropdown adjusts the dropdown's left for the parent
offset but never its top, so pointing dropdownParent at the position:fixed
modal placed the results panel ~parent-top px down the page (bottom of the
viewport). The default <body> attachment positions correctly under the
field, exactly like every other .wc-product-search in admin. The ticket's
actual keyboard bug is fixed by the Enter guard in backbone-modal.js alone,
so dropdownParent is both broken here and unnecessary. Widen the guard's
open-dropdown check to a document-wide lookup since the dropdown now lives
on body, and drop the test's now-invalid 'no dropdown on body' assertions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(DSGWOO-1422): mark dropdownParent approach abandoned (selectWoo positioning)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore(DSGWOO-1422): remove internal planning docs from branch
These brainstorming/plan artifacts are not WooCommerce core content and
should not ship in the PR; kept locally in the workspace .context.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(DSGWOO-1422): locate select2 search field on body, not in modal
The keyboard e2e located the search field with modal.locator(), but selectWoo
attaches its search dropdown to <body>, so the locator never resolved and the
test timed out in CI. Use the same page-level 'span > .select2-search__field'
locator the passing mouse-driven helper uses.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(DSGWOO-1422): assert on rendered selection, not ambiguous getByText
The keyboard flow works in CI (product is selected), but the intermediate
assertion modal.getByText(name) matched both the visible selection and the
hidden <option>, tripping Playwright strict mode. Assert on the specific
.select2-selection__rendered element instead.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Address review: remove dead open-dropdown check, trim Enter guard comment (cc @jorgeatorres on PR #66381)
* Address review: bump modal select2 padding specificity above global rule (cc coderabbit on PR #66381)
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
diff --git a/plugins/woocommerce/changelog/33517-fix-add-products-modal-keyboard b/plugins/woocommerce/changelog/33517-fix-add-products-modal-keyboard
new file mode 100644
index 00000000000..c94e7c8da42
--- /dev/null
+++ b/plugins/woocommerce/changelog/33517-fix-add-products-modal-keyboard
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Fix keyboard flow in the order Add products modal so products can be added without a rogue search box, and fix the clear/chevron icon overlap.
diff --git a/plugins/woocommerce/client/legacy/css/admin.scss b/plugins/woocommerce/client/legacy/css/admin.scss
index fccd54424f6..0d252ec412f 100644
--- a/plugins/woocommerce/client/legacy/css/admin.scss
+++ b/plugins/woocommerce/client/legacy/css/admin.scss
@@ -8050,6 +8050,17 @@ table.bar_chart {
.select2-container {
width: 100% !important;
}
+
+ // Reserve room for the clear (x) and chevron icons; .select2-container keeps this rule above the later global 24px padding.
+ .select2-container .select2-selection--single {
+ .select2-selection__rendered {
+ padding-right: 40px;
+ }
+
+ .select2-selection__clear {
+ margin-right: 18px;
+ }
+ }
}
.wc-backbone-modal-backdrop {
diff --git a/plugins/woocommerce/client/legacy/js/admin/backbone-modal.js b/plugins/woocommerce/client/legacy/js/admin/backbone-modal.js
index afce07540b0..85aa0d3f336 100644
--- a/plugins/woocommerce/client/legacy/js/admin/backbone-modal.js
+++ b/plugins/woocommerce/client/legacy/js/admin/backbone-modal.js
@@ -149,14 +149,22 @@
var button = e.keyCode || e.which;
// Enter key
- if (
- 13 === button &&
- ! ( e.target.tagName && ( e.target.tagName.toLowerCase() === 'input' || e.target.tagName.toLowerCase() === 'textarea' ) )
- ) {
- if ( $( '#btn-ok' ).length ) {
- this.addButton( e );
- } else if ( $( '#btn-next' ).length ) {
- this.nextButton( e );
+ if ( 13 === button ) {
+ var isFormField = e.target.tagName &&
+ ( e.target.tagName.toLowerCase() === 'input' ||
+ e.target.tagName.toLowerCase() === 'textarea' );
+
+ // Let selectWoo handle Enter on an enhanced-select control instead of submitting the modal.
+ var inEnhancedSelect = $( e.target ).closest(
+ '.select2-container, .select2-selection, .select2-search__field, [role="combobox"]'
+ ).length > 0;
+
+ if ( ! isFormField && ! inEnhancedSelect ) {
+ if ( $( '#btn-ok' ).length ) {
+ this.addButton( e );
+ } else if ( $( '#btn-next' ).length ) {
+ this.nextButton( e );
+ }
}
}
diff --git a/plugins/woocommerce/tests/e2e/tests/order/create-order.spec.ts b/plugins/woocommerce/tests/e2e/tests/order/create-order.spec.ts
index 130fbf22dca..7a5f868c666 100644
--- a/plugins/woocommerce/tests/e2e/tests/order/create-order.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/order/create-order.spec.ts
@@ -421,6 +421,65 @@ test.describe(
).toBeVisible();
} );
+ test( 'can add a product using the keyboard without a rogue search box', async ( {
+ page,
+ simpleProduct,
+ } ) => {
+ await page.goto( 'wp-admin/admin.php?page=wc-orders&action=new' );
+
+ // Open the Add products modal.
+ await page.getByRole( 'button', { name: 'Add item(s)' } ).click();
+ await page
+ .getByRole( 'button', { name: 'Add product(s)' } )
+ .click();
+
+ const modal = page.locator( '.wc-backbone-modal-content' );
+ await expect( modal ).toBeVisible();
+
+ // Focus the (closed) product-search control and press Enter.
+ // Before the fix this submitted the modal, closing it and
+ // stranding a detached selectWoo dropdown at the top-left of the
+ // page.
+ await modal.locator( '.select2-selection' ).first().focus();
+ await page.keyboard.press( 'Enter' );
+
+ // The modal must still be open: Enter on the search control must
+ // not submit/close the modal.
+ await expect( modal ).toBeVisible();
+
+ // selectWoo attaches its search dropdown to <body> (not inside the
+ // modal), so target the search field at the page level — the same
+ // locator the mouse-driven helper uses. Enter (above) opened the
+ // dropdown; type the query, wait for the result, then press Enter to
+ // choose it with the keyboard (results are loaded first, so this is
+ // not the premature-Enter path).
+ await page
+ .locator( 'span > .select2-search__field' )
+ .fill( simpleProduct.name );
+ await page
+ .getByRole( 'option', { name: simpleProduct.name } )
+ .first()
+ .waitFor();
+ await page.keyboard.press( 'Enter' );
+
+ // The product is selected in the modal's search control; commit.
+ // Assert on the rendered selection specifically — a bare getByText
+ // also matches the hidden <option>, which trips strict mode.
+ await expect(
+ modal
+ .locator( '.select2-selection__rendered' )
+ .filter( { hasText: simpleProduct.name } )
+ ).toBeVisible();
+ await page.locator( '#btn-ok' ).click();
+
+ // Line item lands on the order.
+ await expect(
+ page
+ .locator( 'td.name > a' )
+ .filter( { hasText: simpleProduct.name } )
+ ).toBeVisible();
+ } );
+
test( 'can create an order for an existing customer', async ( {
page,
simpleProduct,