From cfbe949ff9e46aaf60c57d26af3db603c7002f3a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Oct 2026 15:01:07 +0300 Subject: [PATCH 01/17] chore(deps): bump codeinwp/themeisle-sdk from 3.3.65 to 3.3.66 (#4655) Bumps [codeinwp/themeisle-sdk](https://github.com/Codeinwp/themeisle-sdk) from 3.3.65 to 3.3.66. - [Release notes](https://github.com/Codeinwp/themeisle-sdk/releases) - [Commits](https://github.com/Codeinwp/themeisle-sdk/compare/v3.3.65...v3.3.66) --- updated-dependencies: - dependency-name: codeinwp/themeisle-sdk dependency-version: 3.3.66 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index b6f6754f86..842291a1f7 100644 --- a/composer.lock +++ b/composer.lock @@ -8,16 +8,16 @@ "packages": [ { "name": "codeinwp/themeisle-sdk", - "version": "3.3.65", + "version": "3.3.66", "source": { "type": "git", "url": "https://github.com/Codeinwp/themeisle-sdk.git", - "reference": "f650fe856d52ce4e5754557d89ba2f3127ad54d8" + "reference": "27e095d8357e0594cd5769222af1258538832e75" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeinwp/themeisle-sdk/zipball/f650fe856d52ce4e5754557d89ba2f3127ad54d8", - "reference": "f650fe856d52ce4e5754557d89ba2f3127ad54d8", + "url": "https://api.github.com/repos/Codeinwp/themeisle-sdk/zipball/27e095d8357e0594cd5769222af1258538832e75", + "reference": "27e095d8357e0594cd5769222af1258538832e75", "shasum": "" }, "require-dev": { @@ -43,9 +43,9 @@ ], "support": { "issues": "https://github.com/Codeinwp/themeisle-sdk/issues", - "source": "https://github.com/Codeinwp/themeisle-sdk/tree/v3.3.65" + "source": "https://github.com/Codeinwp/themeisle-sdk/tree/v3.3.66" }, - "time": "2026-09-29T09:35:47+00:00" + "time": "2026-10-07T08:46:25+00:00" }, { "name": "wptt/webfont-loader", From a834ed91f8596dfb9942dfb549f0b96de23b2dd2 Mon Sep 17 00:00:00 2001 From: Girish Panchal <79647963+girishpanchal30@users.noreply.github.com> Date: Tue, 13 Oct 2026 21:34:43 +0530 Subject: [PATCH 02/17] Fixed missing woo customizer sections (#4654) * fix: guard missing woo customizer sections * test: cover shop template warning --- .../apps/customizer-controls/src/controls.js | 16 +- .../elementor-template-notices.spec.ts | 143 ++++++++++++++++++ 2 files changed, 151 insertions(+), 8 deletions(-) create mode 100644 e2e-tests/specs/customizer/general/elementor-template-notices.spec.ts diff --git a/assets/apps/customizer-controls/src/controls.js b/assets/apps/customizer-controls/src/controls.js index 2bb9ee80d6..7f7126c54a 100644 --- a/assets/apps/customizer-controls/src/controls.js +++ b/assets/apps/customizer-controls/src/controls.js @@ -316,9 +316,8 @@ const bindDataAttrQuickLinks = () => { }; const checkHasElementorTemplates = () => { if (NeveReactCustomize.elementor.hasElementorShopTemplate) { - window.wp.customize - .section('woocommerce_product_catalog') - .notifications.add( + window.wp.customize.section('woocommerce_product_catalog', (section) => + section.notifications.add( new window.wp.customize.Notification( 'neve-custom-elementor-shop-template', { @@ -329,13 +328,13 @@ const checkHasElementorTemplates = () => { ), } ) - ); + ) + ); } if (NeveReactCustomize.elementor.hasElementorProductTemplate) { - window.wp.customize - .section('neve_single_product_layout') - .notifications.add( + window.wp.customize.section('neve_single_product_layout', (section) => + section.notifications.add( new window.wp.customize.Notification( 'neve-custom-elementor-product-template', { @@ -346,7 +345,8 @@ const checkHasElementorTemplates = () => { ), } ) - ); + ) + ); } }; diff --git a/e2e-tests/specs/customizer/general/elementor-template-notices.spec.ts b/e2e-tests/specs/customizer/general/elementor-template-notices.spec.ts new file mode 100644 index 0000000000..cf1573526e --- /dev/null +++ b/e2e-tests/specs/customizer/general/elementor-template-notices.spec.ts @@ -0,0 +1,143 @@ +import { test, expect, Page } from '@playwright/test'; + +type ElementorFlags = { + hasElementorShopTemplate: boolean; + hasElementorProductTemplate: boolean; +}; + +type CustomizeSection = { + notifications: { has: (code: string) => boolean }; +}; + +type CustomizeWindow = Window & { + NeveReactCustomize: { elementor: ElementorFlags }; + wp: { + customize: { + section: { + (id: string): CustomizeSection | undefined; + has: (id: string) => boolean; + add: (section: CustomizeSection) => CustomizeSection; + }; + Section: new ( + id: string, + options: { params: { title: string } } + ) => CustomizeSection; + }; + }; +}; + +const PRODUCT_SECTION = 'neve_single_product_layout'; +const SHOP_SECTION = 'woocommerce_product_catalog'; + +/** + * Report both Elementor WooCommerce templates as present, as a site with + * leftover Elementor Pro templates would. + * + * @param {Page} page Playwright page. + */ +const forceElementorTemplates = async (page: Page): Promise => { + await page.addInitScript(() => { + let localized: Record | undefined; + Object.defineProperty(window, 'NeveReactCustomize', { + configurable: true, + get: () => localized, + set: (value: Record) => { + localized = { + ...value, + elementor: { + hasElementorShopTemplate: true, + hasElementorProductTemplate: true, + }, + }; + }, + }); + }); +}; + +const openCustomizer = async (page: Page): Promise => { + await page.goto('/wp-admin/customize.php'); + await page.waitForSelector('.wp-full-overlay-sidebar', { + state: 'visible', + }); +}; + +test.describe('Elementor WooCommerce template notices', () => { + test.beforeEach(async ({ page }) => { + await forceElementorTemplates(page); + }); + + test('customizer finishes loading when the WooCommerce sections are missing', async ({ + page, + }) => { + const notificationErrors: string[] = []; + page.on('pageerror', (error) => { + if (error.message.includes('notifications')) { + notificationErrors.push(error.message); + } + }); + + await openCustomizer(page); + + // Registered after the Elementor check, so it only appears if init got past it. + await expect(page.locator('#neve-style-book')).toBeVisible({ + timeout: 60_000, + }); + + const state = await page.evaluate( + ([shop, product]) => { + const { wp, NeveReactCustomize } = + window as unknown as CustomizeWindow; + return { + flags: NeveReactCustomize.elementor, + hasShopSection: wp.customize.section.has(shop), + hasProductSection: wp.customize.section.has(product), + }; + }, + [SHOP_SECTION, PRODUCT_SECTION] + ); + + expect(state.flags).toEqual({ + hasElementorShopTemplate: true, + hasElementorProductTemplate: true, + }); + expect(state.hasShopSection).toBe(false); + expect(state.hasProductSection).toBe(false); + expect(notificationErrors).toEqual([]); + }); + + const warnings = [ + [SHOP_SECTION, 'neve-custom-elementor-shop-template'], + [PRODUCT_SECTION, 'neve-custom-elementor-product-template'], + ] as const; + + for (const [sectionId, code] of warnings) { + test(`shows the ${code} warning once ${sectionId} is registered`, async ({ + page, + }) => { + await openCustomizer(page); + await expect(page.locator('#neve-style-book')).toBeVisible({ + timeout: 60_000, + }); + + await page.evaluate((id) => { + const { wp } = window as unknown as CustomizeWindow; + wp.customize.section.add( + new wp.customize.Section(id, { params: { title: id } }) + ); + }, sectionId); + + // The section callback resolves through a jQuery Deferred, which is async. + await expect + .poll(() => + page.evaluate( + ([id, notification]) => + (window as unknown as CustomizeWindow).wp.customize + .section(id) + ?.notifications.has(notification) ?? false, + [sectionId, code] as const + ) + ) + .toBe(true); + }); + } +}); From 74abc1bc7afb86ecb55cf835ec167605d60bc39a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 15 Oct 2026 03:01:11 +0300 Subject: [PATCH 03/17] chore(deps): bump codeinwp/themeisle-sdk from 3.3.66 to 3.3.67 (#4657) Bumps [codeinwp/themeisle-sdk](https://github.com/Codeinwp/themeisle-sdk) from 3.3.66 to 3.3.67. - [Release notes](https://github.com/Codeinwp/themeisle-sdk/releases) - [Commits](https://github.com/Codeinwp/themeisle-sdk/compare/v3.3.66...v3.3.67) --- updated-dependencies: - dependency-name: codeinwp/themeisle-sdk dependency-version: 3.3.67 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index 842291a1f7..b0cb92d373 100644 --- a/composer.lock +++ b/composer.lock @@ -8,16 +8,16 @@ "packages": [ { "name": "codeinwp/themeisle-sdk", - "version": "3.3.66", + "version": "3.3.67", "source": { "type": "git", "url": "https://github.com/Codeinwp/themeisle-sdk.git", - "reference": "27e095d8357e0594cd5769222af1258538832e75" + "reference": "e1dda44811660a65c3da6994349c054a46686d69" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeinwp/themeisle-sdk/zipball/27e095d8357e0594cd5769222af1258538832e75", - "reference": "27e095d8357e0594cd5769222af1258538832e75", + "url": "https://api.github.com/repos/Codeinwp/themeisle-sdk/zipball/e1dda44811660a65c3da6994349c054a46686d69", + "reference": "e1dda44811660a65c3da6994349c054a46686d69", "shasum": "" }, "require-dev": { @@ -43,9 +43,9 @@ ], "support": { "issues": "https://github.com/Codeinwp/themeisle-sdk/issues", - "source": "https://github.com/Codeinwp/themeisle-sdk/tree/v3.3.66" + "source": "https://github.com/Codeinwp/themeisle-sdk/tree/v3.3.67" }, - "time": "2026-10-07T08:46:25+00:00" + "time": "2026-10-13T12:16:34+00:00" }, { "name": "wptt/webfont-loader", From 646eacc4d3b757e432fe58250cf7e8c019b08c86 Mon Sep 17 00:00:00 2001 From: girishpanchal30 Date: Fri, 16 Oct 2026 11:40:27 +0530 Subject: [PATCH 04/17] fix: improve letter spacing control handling --- assets/apps/components/src/Controls/Sizing.js | 14 ++- .../src/typeface/Typeface.js | 6 +- .../typography/letter-spacing-reset.json | 26 +++++ .../typography/letter-spacing-reset.spec.ts | 94 +++++++++++++++++++ 4 files changed, 133 insertions(+), 7 deletions(-) create mode 100644 e2e-tests/fixtures/customizer/typography/letter-spacing-reset.json create mode 100644 e2e-tests/specs/customizer/typography/letter-spacing-reset.spec.ts diff --git a/assets/apps/components/src/Controls/Sizing.js b/assets/apps/components/src/Controls/Sizing.js index d4f93546af..9315af2ba3 100755 --- a/assets/apps/components/src/Controls/Sizing.js +++ b/assets/apps/components/src/Controls/Sizing.js @@ -6,6 +6,13 @@ import { __ } from '@wordpress/i18n'; import { mapValues } from 'lodash'; import { Button, Tooltip } from '@wordpress/components'; +/** + * @param {string|number|null|undefined} value Control value or default. + * @return {boolean} Whether the value is unset. + */ +const isEmptyValue = (value) => + value === '' || value === null || value === undefined; + const SizingControl = ({ noLinking, options, @@ -61,8 +68,11 @@ const SizingControl = ({ const hasSetValues = () => { if (typeof defaults !== 'object') { - // eslint-disable-next-line eqeqeq - return parseFloat(defaults) != parseFloat(options[0].value); + const current = options[0].value; + if (isEmptyValue(defaults) || isEmptyValue(current)) { + return isEmptyValue(defaults) !== isEmptyValue(current); + } + return parseFloat(defaults) !== parseFloat(current); } return ( options.filter( diff --git a/assets/apps/customizer-controls/src/typeface/Typeface.js b/assets/apps/customizer-controls/src/typeface/Typeface.js index 11c35af3b2..450e7ad3a6 100644 --- a/assets/apps/customizer-controls/src/typeface/Typeface.js +++ b/assets/apps/customizer-controls/src/typeface/Typeface.js @@ -260,11 +260,7 @@ const Typeface = (props) => { label={__('Letter Spacing', 'neve')} step={0.1} default={defaultLS[currentDevice]} - value={ - letterSpacing && letterSpacing[currentDevice] - ? letterSpacing[currentDevice] - : '' - } + value={letterSpacing[currentDevice] ?? ''} max={20} min={-5} units={['px']} diff --git a/e2e-tests/fixtures/customizer/typography/letter-spacing-reset.json b/e2e-tests/fixtures/customizer/typography/letter-spacing-reset.json new file mode 100644 index 0000000000..d3a894d983 --- /dev/null +++ b/e2e-tests/fixtures/customizer/typography/letter-spacing-reset.json @@ -0,0 +1,26 @@ +{ + "neve_typeface_general": { + "fontSize": { + "suffix": { + "mobile": "px", + "tablet": "px", + "desktop": "px" + }, + "mobile": 15, + "tablet": 16, + "desktop": 16 + }, + "lineHeight": { + "mobile": 1.6, + "tablet": 1.6, + "desktop": 1.7 + }, + "letterSpacing": { + "mobile": 0, + "tablet": "0", + "desktop": 0 + }, + "fontWeight": "400", + "textTransform": "none" + } +} diff --git a/e2e-tests/specs/customizer/typography/letter-spacing-reset.spec.ts b/e2e-tests/specs/customizer/typography/letter-spacing-reset.spec.ts new file mode 100644 index 0000000000..d9fa21c5a0 --- /dev/null +++ b/e2e-tests/specs/customizer/typography/letter-spacing-reset.spec.ts @@ -0,0 +1,94 @@ +import { test, expect, Locator, Page } from '@playwright/test'; +import { setCustomizeSettings } from '../../../utils'; +import data from '../../../fixtures/customizer/typography/letter-spacing-reset.json'; + +type Device = 'desktop' | 'tablet' | 'mobile'; + +type CustomizeWindow = Window & { + wp: { customize: { previewedDevice: (device: Device) => void } }; +}; + +/** + * Open the Customizer on a section and wait for a control to render. + * + * @param {Page} page Playwright page. + * @param {string} section Section id to autofocus. + * @param {string} control Control id to wait for. + */ +const openControl = async ( + page: Page, + section: string, + control: string +): Promise => { + await page.goto( + `/wp-admin/customize.php?test_name=letterSpacingReset&autofocus[section]=${section}` + ); + const wrap = page.locator(`#customize-control-${control}`); + await expect(wrap.locator('.letter-spacing input')).toBeVisible(); + + return wrap; +}; + +/** + * Switch the Customizer preview device. + * + * @param {Page} page Playwright page. + * @param {Device} device Device to preview. + */ +const previewDevice = (page: Page, device: Device): Promise => + page.evaluate((d) => { + (window as unknown as CustomizeWindow).wp.customize.previewedDevice(d); + }, device); + +test.describe('Typeface number controls reset state', function () { + test.beforeAll(async ({ request, baseURL }) => { + await setCustomizeSettings('letterSpacingReset', data, { + request, + baseURL, + }); + }); + + test('Zero letter spacing shows as default', async ({ page }) => { + const control = await openControl( + page, + 'neve_typography_general', + 'neve_typeface_general' + ); + const input = control.locator('.letter-spacing input'); + const reset = control.locator('.letter-spacing .reset'); + + await expect(input).toHaveValue('0'); + await expect(reset).toHaveCount(0); + + // Stored as a string, as typed values are. + await previewDevice(page, 'tablet'); + await expect(input).toHaveValue('0'); + await expect(reset).toHaveCount(0); + + await previewDevice(page, 'desktop'); + await input.fill('1'); + await expect(reset).toHaveCount(1); + + await reset.click(); + await expect(input).toHaveValue('0'); + await expect(reset).toHaveCount(0); + }); + + test('Untouched empty defaults have no reset', async ({ page }) => { + const control = await openControl( + page, + 'neve_buttons_section', + 'neve_button_typeface' + ); + + for (const field of ['font-size', 'line-height', 'letter-spacing']) { + await expect(control.locator(`.${field} input`)).toHaveValue(''); + await expect(control.locator(`.${field} .reset`)).toHaveCount(0); + } + + const input = control.locator('.letter-spacing input'); + await input.fill('0'); + await expect(input).toHaveValue('0'); + await expect(control.locator('.letter-spacing .reset')).toHaveCount(1); + }); +}); From f10d45212617f2b1b8fa5a7feab95972d6098ab4 Mon Sep 17 00:00:00 2001 From: girishpanchal30 Date: Fri, 16 Oct 2026 13:06:45 +0530 Subject: [PATCH 05/17] fix: keep partial negative input --- .../src/Common/SingleSizingInput.js | 14 ++++++--- .../typography/letter-spacing-reset.spec.ts | 31 ++++++++++++++++++- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/assets/apps/components/src/Common/SingleSizingInput.js b/assets/apps/components/src/Common/SingleSizingInput.js index f119fa1e64..2da051f193 100755 --- a/assets/apps/components/src/Common/SingleSizingInput.js +++ b/assets/apps/components/src/Common/SingleSizingInput.js @@ -1,4 +1,5 @@ import PropTypes from 'prop-types'; +import { useState } from '@wordpress/element'; const SingleSizingInput = ({ type, @@ -10,6 +11,9 @@ const SingleSizingInput = ({ label, disabled, }) => { + // Partial input like "-" reads as empty; rendering 0 would erase it. + const [isEmptyInput, setIsEmptyInput] = useState(false); + return (
- onChange(type, e.target.value === '' ? 0 : e.target.value) - } + onChange={(e) => { + setIsEmptyInput(e.target.value === ''); + onChange(type, e.target.value === '' ? 0 : e.target.value); + }} + onBlur={() => setIsEmptyInput(false)} /> {label && (