From 2f03d6d8f54b2574ad66ac7d2af23dc3f6497e5e Mon Sep 17 00:00:00 2001 From: Ivan Bochkarev Date: Tue, 17 Feb 2026 16:19:59 +0600 Subject: [PATCH 1/3] =?UTF-8?q?=D0=92=D1=8B=D0=BD=D0=BE=D1=81=20=D1=81?= =?UTF-8?q?=D0=B5=D0=BB=D0=B5=D0=BA=D1=82=D0=BE=D1=80=D0=BE=D0=B2=20=D0=B2?= =?UTF-8?q?=20=D0=BE=D0=B1=D1=89=D0=B8=D0=B9=20=D0=BA=D0=BE=D0=BD=D1=84?= =?UTF-8?q?=D0=B8=D0=B3=20(Issue=20#18)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Добавлен Selectors.js с дефолтными селекторами и getSelectors() - Конфиг переопределяется через ms3Config.selectors - UI-классы (CartUI, OrderUI, QuantityUI, CustomerUI, ProductCardUI) используют селекторы из конфига - Fallback на CSS-классы для обратной совместимости - Миграция для добавления Selectors.js в ms3_frontend_assets Co-authored-by: Cursor --- .eslintrc.json | 11 +- _build/elements/settings.php | 1 + .../minishop3/js/web/core/Selectors.js | 36 +++++++ assets/components/minishop3/js/web/ms3.js | 32 ++++-- .../components/minishop3/js/web/ui/CartUI.js | 20 ++-- .../minishop3/js/web/ui/CustomerUI.js | 8 +- .../components/minishop3/js/web/ui/OrderUI.js | 19 ++-- .../minishop3/js/web/ui/ProductCardUI.js | 7 +- .../minishop3/js/web/ui/QuantityUI.js | 46 ++++---- ...20000_add_selectors_to_frontend_assets.php | 102 ++++++++++++++++++ core/components/minishop3/src/MiniShop3.php | 3 +- 11 files changed, 220 insertions(+), 65 deletions(-) create mode 100644 assets/components/minishop3/js/web/core/Selectors.js create mode 100644 core/components/minishop3/migrations/20260217120000_add_selectors_to_frontend_assets.php diff --git a/.eslintrc.json b/.eslintrc.json index 819db436..f6173df0 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,14 +1,11 @@ { "root": true, - "extends": [ - "standard" - ], - "env": { - "browser": true - }, + "extends": ["standard"], + "env": { "browser": true }, "globals": { "ms3": "readonly", - "ms3Config": "readonly" + "ms3Config": "readonly", + "getSelectors": "readonly" }, "ignorePatterns": ["**/node_modules/**", "**/mgr/**"] } diff --git a/_build/elements/settings.php b/_build/elements/settings.php index 195d6023..e7a50e49 100644 --- a/_build/elements/settings.php +++ b/_build/elements/settings.php @@ -233,6 +233,7 @@ "[[+jsUrl]]web\/lib\/izitoast\/iziToast.js", "[[+jsUrl]]web\/modules\/hooks.js", "[[+jsUrl]]web\/modules\/message.js", + "[[+jsUrl]]web\/core\/Selectors.js", "[[+jsUrl]]web\/core\/ApiClient.js", "[[+jsUrl]]web\/core\/TokenManager.js", "[[+jsUrl]]web\/core\/CartAPI.js", diff --git a/assets/components/minishop3/js/web/core/Selectors.js b/assets/components/minishop3/js/web/core/Selectors.js new file mode 100644 index 00000000..adde8796 --- /dev/null +++ b/assets/components/minishop3/js/web/core/Selectors.js @@ -0,0 +1,36 @@ +/** + * Default selectors for MiniShop3 UI + * Overridable via ms3Config.selectors + * Priority: data-* attributes, fallback: CSS classes (backward compatibility) + * + * @see https://github.com/modx-pro/MiniShop3/issues/18 + */ +const defaultSelectors = { + form: '[data-ms3-form], .ms3_form', + formOrder: '[data-ms3-form="order"], .ms3_order_form', + formCustomer: '[data-ms3-form="customer"], .ms3_customer_form', + cartOptions: '[data-ms3-cart-options], .ms3_cart_options', + qtyInput: '[data-ms3-qty="input"], .qty-input', + qtyInc: '[data-ms3-qty="inc"], .inc-qty', + qtyDec: '[data-ms3-qty="dec"], .dec-qty', + productCard: '[data-ms3-product-card], .ms3-product-card', + fieldError: '[data-ms3-error], .ms3_field_error', + orderCost: '#ms3_order_cost', + orderCartCost: '#ms3_order_cart_cost', + orderDeliveryCost: '#ms3_order_delivery_cost', + link: '.ms3_link' +} + +/** + * Get merged selectors (defaults + ms3Config.selectors overrides) + * @returns {Object} Selectors object + */ +function getSelectors () { + const custom = (typeof window !== 'undefined' && window.ms3Config && window.ms3Config.selectors) || {} + return { ...defaultSelectors, ...custom } +} + +// Expose for fallback when Selectors.js loads but getSelectors fails (e.g. in unit tests) +if (typeof window !== 'undefined') { + window.Ms3DefaultSelectors = defaultSelectors +} diff --git a/assets/components/minishop3/js/web/ms3.js b/assets/components/minishop3/js/web/ms3.js index 3899d0de..bc004324 100644 --- a/assets/components/minishop3/js/web/ms3.js +++ b/assets/components/minishop3/js/web/ms3.js @@ -22,7 +22,7 @@ * - data-ms3-qty="input"|"inc"|"dec" — quantity control * - data-ms3-cart-options — cart options select */ -/* global TokenManager, ApiClient, CartAPI, OrderAPI, CustomerAPI, CartUI, OrderUI, CustomerUI, ProductCardUI */ +/* global TokenManager, ApiClient, CartAPI, OrderAPI, CustomerAPI, CartUI, OrderUI, CustomerUI, QuantityUI, ProductCardUI, getSelectors */ const ms3 = { config: {}, @@ -48,6 +48,12 @@ const ms3 = { async init () { this.config = window.ms3Config || {} + // Merge selectors from Selectors.js (overridable via ms3Config.selectors) + const selectors = typeof getSelectors === 'function' + ? getSelectors() + : (window.Ms3DefaultSelectors || {}) + this.config = { ...this.config, selectors } + this.hooks = window.ms3Hooks || this.createFallbackHooks() this.message = window.ms3Message || this.createFallbackMessage() @@ -95,7 +101,7 @@ const ms3 = { }, /** - * [data-ms3-form] submit handler (fallback: .ms3_form deprecated) + * Form submit handler (uses sel.form from config) * * Automatically calls appropriate API method based on ms3_action: * - cart/add → cartUI.handleAdd() @@ -103,9 +109,13 @@ const ms3 = { * - etc. */ initFormHandler () { + const sel = this.config?.selectors || {} + const formSel = sel.form || '[data-ms3-form], .ms3_form' + document.addEventListener('submit', async (event) => { const form = event.target - if (!form.hasAttribute('data-ms3-form') && !form.classList.contains('ms3_form')) { + const matches = form.matches || form.msMatchesSelector || form.webkitMatchesSelector + if (!matches || !matches.call(form, formSel)) { return } @@ -126,21 +136,25 @@ const ms3 = { }, /** - * .ms3_link click handler + * Link click handler (uses sel.link, sel.form from config) * - * Handles clicks on buttons/links with .ms3_link class - * inside .ms3_form forms. Triggers form submit. + * Handles clicks on buttons/links with sel.link + * inside forms matching sel.form. Triggers form submit. */ initLinkHandler () { + const sel = this.config?.selectors || {} + const linkSel = sel.link || '.ms3_link' + const formSel = sel.form || '[data-ms3-form], .ms3_form' + document.addEventListener('click', async (event) => { - const link = event.target.closest('.ms3_link') + const link = event.target.closest(linkSel) if (!link) { return } - const form = link.closest('[data-ms3-form]') || link.closest('.ms3_form') + const form = link.closest(formSel) if (!form) { - console.warn('.ms3_link must be inside a form with data-ms3-form or .ms3_form') + console.warn('ms3_link must be inside a form matching sel.form') return } diff --git a/assets/components/minishop3/js/web/ui/CartUI.js b/assets/components/minishop3/js/web/ui/CartUI.js index 67b68122..e6cbe763 100644 --- a/assets/components/minishop3/js/web/ui/CartUI.js +++ b/assets/components/minishop3/js/web/ui/CartUI.js @@ -40,22 +40,16 @@ class CartUI { } /** - * Product option selects: [data-ms3-cart-options] or .ms3_cart_options (fallback) + * Product option selects: uses sel.cartOptions from config */ initOptionSelects () { - const byData = document.querySelectorAll('[data-ms3-cart-options]') - const byClass = document.querySelectorAll('.ms3_cart_options') - const seen = new Set() - const selects = [] - ;[...byData, ...byClass].forEach(select => { - if (!seen.has(select)) { - seen.add(select) - selects.push(select) - } - }) - selects.forEach(select => { + const sel = this.config?.selectors || {} + const cartOptionsSel = sel.cartOptions || '[data-ms3-cart-options], .ms3_cart_options' + const formSel = sel.form || '[data-ms3-form], .ms3_form' + + document.querySelectorAll(cartOptionsSel).forEach(select => { select.addEventListener('change', async (e) => { - const form = e.target.closest('[data-ms3-form]') || e.target.closest('.ms3_form') + const form = e.target.closest(formSel) if (!form) return console.log('Option changed:', e.target.name, e.target.value) diff --git a/assets/components/minishop3/js/web/ui/CustomerUI.js b/assets/components/minishop3/js/web/ui/CustomerUI.js index 57f23eef..3557c66f 100644 --- a/assets/components/minishop3/js/web/ui/CustomerUI.js +++ b/assets/components/minishop3/js/web/ui/CustomerUI.js @@ -47,7 +47,10 @@ class CustomerUI { * Initialize UI handlers */ init () { - document.querySelectorAll('[data-ms3-form="customer"], .ms3_customer_form').forEach(form => { + const sel = this.config?.selectors || {} + const formCustomerSel = sel.formCustomer || '[data-ms3-form="customer"], .ms3_customer_form' + + document.querySelectorAll(formCustomerSel).forEach(form => { this.initForm(form) }) } @@ -72,7 +75,8 @@ class CustomerUI { */ initInput (input) { input.addEventListener('change', async () => { - const form = input.closest('[data-ms3-form="customer"], .ms3_customer_form') + const formCustomerSel = (this.config?.selectors || {}).formCustomer || '[data-ms3-form="customer"], .ms3_customer_form' + const form = input.closest(formCustomerSel) if (!form) return const parent = input.closest('div') diff --git a/assets/components/minishop3/js/web/ui/OrderUI.js b/assets/components/minishop3/js/web/ui/OrderUI.js index 6e9678d4..9a78e75b 100644 --- a/assets/components/minishop3/js/web/ui/OrderUI.js +++ b/assets/components/minishop3/js/web/ui/OrderUI.js @@ -21,7 +21,10 @@ class OrderUI { * Initialize UI handlers */ init () { - document.querySelectorAll('[data-ms3-form="order"], .ms3_order_form').forEach(form => { + const sel = this.config?.selectors || {} + const formOrderSel = sel.formOrder || '[data-ms3-form="order"], .ms3_order_form' + + document.querySelectorAll(formOrderSel).forEach(form => { this.initForm(form) }) @@ -157,21 +160,22 @@ class OrderUI { if (response.success && response.data) { const { cost, cart_cost: cartCost, delivery_cost: deliveryCost } = response.data + const sel = this.config?.selectors || {} // Update cart cost - const cartCostEl = document.getElementById('ms3_order_cart_cost') + const cartCostEl = document.querySelector(sel.orderCartCost || '#ms3_order_cart_cost') if (cartCostEl && cartCost !== undefined) { cartCostEl.textContent = this.formatPrice(cartCost) } // Update delivery cost - const deliveryCostEl = document.getElementById('ms3_order_delivery_cost') + const deliveryCostEl = document.querySelector(sel.orderDeliveryCost || '#ms3_order_delivery_cost') if (deliveryCostEl && deliveryCost !== undefined) { deliveryCostEl.textContent = this.formatPrice(deliveryCost) } // Update total cost - const totalCostEl = document.getElementById('ms3_order_cost') + const totalCostEl = document.querySelector(sel.orderCost || '#ms3_order_cost') if (totalCostEl && cost !== undefined) { totalCostEl.textContent = this.formatPrice(cost) } @@ -264,7 +268,8 @@ class OrderUI { } if (response.success) { - document.querySelectorAll('[data-ms3-form="order"], .ms3_order_form').forEach(form => { + const formOrderSel = (this.config?.selectors || {}).formOrder || '[data-ms3-form="order"], .ms3_order_form' + document.querySelectorAll(formOrderSel).forEach(form => { form.reset() }) document.dispatchEvent(new CustomEvent('ms3:cart:updated')) @@ -283,7 +288,9 @@ class OrderUI { * @param {Array} errors - Array of field names with errors */ highlightErrors (errors) { - document.querySelectorAll('[data-ms3-error], .ms3_field_error').forEach(el => { + const fieldErrorSel = (this.config?.selectors || {}).fieldError || '[data-ms3-error], .ms3_field_error' + + document.querySelectorAll(fieldErrorSel).forEach(el => { el.removeAttribute('data-ms3-error') el.classList.remove('ms3_field_error') }) diff --git a/assets/components/minishop3/js/web/ui/ProductCardUI.js b/assets/components/minishop3/js/web/ui/ProductCardUI.js index c2481678..6553517e 100644 --- a/assets/components/minishop3/js/web/ui/ProductCardUI.js +++ b/assets/components/minishop3/js/web/ui/ProductCardUI.js @@ -127,7 +127,9 @@ class ProductCardUI { * Update all product cards on page */ updateAllCards () { - const cards = document.querySelectorAll('[data-ms3-product-card], .ms3-product-card') + const sel = this.config?.selectors || {} + const productCardSel = sel.productCard || '[data-ms3-product-card], .ms3-product-card' + const cards = document.querySelectorAll(productCardSel) cards.forEach(card => { this.updateCard(card) @@ -161,7 +163,8 @@ class ProductCardUI { changeForm.style.display = 'flex' // Update quantity input - const countInput = changeForm.querySelector('[data-ms3-qty="input"]') || changeForm.querySelector('.qty-input') + const qtyInputSel = (this.config?.selectors || {}).qtyInput || '[data-ms3-qty="input"], .qty-input' + const countInput = changeForm.querySelector(qtyInputSel) if (countInput) { countInput.value = this.cartState[productId].totalCount } diff --git a/assets/components/minishop3/js/web/ui/QuantityUI.js b/assets/components/minishop3/js/web/ui/QuantityUI.js index dddd72f5..f954fac6 100644 --- a/assets/components/minishop3/js/web/ui/QuantityUI.js +++ b/assets/components/minishop3/js/web/ui/QuantityUI.js @@ -49,19 +49,14 @@ class QuantityUI { } /** - * Initialize quantity buttons: [data-ms3-qty="inc"]/[data-ms3-qty="dec"] or .inc-qty/.dec-qty (fallback) + * Initialize quantity buttons: uses sel.qtyInc, sel.qtyDec from config */ initButtons () { - const byData = document.querySelectorAll('[data-ms3-qty="inc"], [data-ms3-qty="dec"]') - const byClass = document.querySelectorAll('.inc-qty, .dec-qty') - const seen = new Set() - const buttons = [] - ;[...byData, ...byClass].forEach(btn => { - if (!seen.has(btn)) { - seen.add(btn) - buttons.push(btn) - } - }) + const sel = this.config?.selectors || {} + const qtyIncSel = sel.qtyInc || '[data-ms3-qty="inc"], .inc-qty' + const qtyDecSel = sel.qtyDec || '[data-ms3-qty="dec"], .dec-qty' + const buttons = document.querySelectorAll([qtyIncSel, qtyDecSel].join(', ')) + buttons.forEach(btn => { const newBtn = btn.cloneNode(true) btn.parentNode.replaceChild(newBtn, btn) @@ -70,19 +65,13 @@ class QuantityUI { } /** - * Initialize quantity inputs: [data-ms3-qty="input"] or .qty-input (fallback) + * Initialize quantity inputs: uses sel.qtyInput from config */ initInputs () { - const byData = document.querySelectorAll('[data-ms3-qty="input"]') - const byClass = document.querySelectorAll('.qty-input') - const seen = new Set() - const inputs = [] - ;[...byData, ...byClass].forEach(input => { - if (!seen.has(input)) { - seen.add(input) - inputs.push(input) - } - }) + const sel = this.config?.selectors || {} + const qtyInputSel = sel.qtyInput || '[data-ms3-qty="input"], .qty-input' + const inputs = document.querySelectorAll(qtyInputSel) + inputs.forEach(input => { const newInput = input.cloneNode(true) input.parentNode.replaceChild(newInput, input) @@ -98,10 +87,14 @@ class QuantityUI { async handleButtonClick (e) { e.preventDefault() - const form = e.target.closest('[data-ms3-form]') || e.target.closest('.ms3_form') + const sel = this.config?.selectors || {} + const formSel = sel.form || '[data-ms3-form], .ms3_form' + const qtyInputSel = sel.qtyInput || '[data-ms3-qty="input"], .qty-input' + + const form = e.target.closest(formSel) if (!form) return - const input = form.querySelector('[data-ms3-qty="input"]') || form.querySelector('.qty-input') + const input = form.querySelector(qtyInputSel) if (!input) return let qty = parseInt(input.value) || 0 @@ -124,7 +117,10 @@ class QuantityUI { * @param {Event} e - Change event */ async handleInputChange (e) { - const form = e.target.closest('[data-ms3-form]') || e.target.closest('.ms3_form') + const sel = this.config?.selectors || {} + const formSel = sel.form || '[data-ms3-form], .ms3_form' + + const form = e.target.closest(formSel) if (!form) return const qty = Math.max(0, parseInt(e.target.value) || 0) diff --git a/core/components/minishop3/migrations/20260217120000_add_selectors_to_frontend_assets.php b/core/components/minishop3/migrations/20260217120000_add_selectors_to_frontend_assets.php new file mode 100644 index 00000000..3cc3063d --- /dev/null +++ b/core/components/minishop3/migrations/20260217120000_add_selectors_to_frontend_assets.php @@ -0,0 +1,102 @@ +getAdapter()->getOption('table_prefix'); + $table = $prefix . 'system_settings'; + + $row = $this->fetchRow( + "SELECT `value` FROM `{$table}` WHERE `key` = 'ms3_frontend_assets'" + ); + + if (!$row) { + return; + } + + $assets = json_decode($row['value'], true); + if (!is_array($assets)) { + return; + } + + $normalizedAssets = array_map(function ($path) { + return str_replace('\\/', '/', $path); + }, $assets); + + $newFileNormalized = str_replace('\\/', '/', self::NEW_FILE); + $insertAfterNormalized = str_replace('\\/', '/', self::INSERT_AFTER); + + if (in_array($newFileNormalized, $normalizedAssets, true)) { + return; + } + + $insertPosition = array_search($insertAfterNormalized, $normalizedAssets, true); + + if ($insertPosition !== false) { + array_splice($assets, $insertPosition + 1, 0, [self::NEW_FILE]); + } else { + $assets[] = self::NEW_FILE; + } + + $newValue = json_encode($assets, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT); + + $this->execute( + "UPDATE `{$table}` SET `value` = " . $this->getAdapter()->getConnection()->quote($newValue) . + " WHERE `key` = 'ms3_frontend_assets'" + ); + } + + public function down(): void + { + $prefix = $this->getAdapter()->getOption('table_prefix'); + $table = $prefix . 'system_settings'; + + $row = $this->fetchRow( + "SELECT `value` FROM `{$table}` WHERE `key` = 'ms3_frontend_assets'" + ); + + if (!$row) { + return; + } + + $assets = json_decode($row['value'], true); + if (!is_array($assets)) { + return; + } + + $assets = array_values(array_filter($assets, function ($path) { + $normalized = str_replace('\\/', '/', $path); + return $normalized !== str_replace('\\/', '/', self::NEW_FILE); + })); + + $newValue = json_encode($assets, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT); + + $this->execute( + "UPDATE `{$table}` SET `value` = " . $this->getAdapter()->getConnection()->quote($newValue) . + " WHERE `key` = 'ms3_frontend_assets'" + ); + } +} diff --git a/core/components/minishop3/src/MiniShop3.php b/core/components/minishop3/src/MiniShop3.php index 7f15bce8..62aa8a0d 100644 --- a/core/components/minishop3/src/MiniShop3.php +++ b/core/components/minishop3/src/MiniShop3.php @@ -158,7 +158,8 @@ public function registerFrontend($ctx = 'web') 'tokenName' => $tokenName, 'render' => [ 'cart' => [] - ] + ], + 'selectors' => [] ]; $data = json_encode($js_setting, JSON_UNESCAPED_UNICODE); From dce2e479cae7c059e21cefac6f633c125f3e9107 Mon Sep 17 00:00:00 2001 From: Ivan Bochkarev Date: Wed, 18 Feb 2026 10:08:13 +0600 Subject: [PATCH 2/3] ref(selectors): Address PR #97 review comments for selector config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - QuantityUI: use e.target.matches() with quantityIncreaseSelector/quantityDecreaseSelector instead of hardcoded data-ms3-qty for custom selector support - ms3.js: remove IE11 polyfill; use form.matches(formSelector) - Add selectors getter to all UI classes and ms3; replace config?.selectors||{} with this.selectors - Rename shortened vars: sel→selectors, qtyInputSel→quantityInputSelector, formSel→formSelector, linkSel→linkSelector, cartCostEl→cartCostElement, etc. - Improve console.warn to show actual formSelector value Refs #18 Co-authored-by: Cursor --- assets/components/minishop3/js/web/ms3.js | 23 ++++++---- .../components/minishop3/js/web/ui/CartUI.js | 14 ++++-- .../minishop3/js/web/ui/CustomerUI.js | 14 ++++-- .../components/minishop3/js/web/ui/OrderUI.js | 46 ++++++++++--------- .../minishop3/js/web/ui/ProductCardUI.js | 14 ++++-- .../minishop3/js/web/ui/QuantityUI.js | 40 +++++++++------- 6 files changed, 88 insertions(+), 63 deletions(-) diff --git a/assets/components/minishop3/js/web/ms3.js b/assets/components/minishop3/js/web/ms3.js index bc004324..175306cb 100644 --- a/assets/components/minishop3/js/web/ms3.js +++ b/assets/components/minishop3/js/web/ms3.js @@ -26,6 +26,10 @@ const ms3 = { config: {}, + get selectors () { + return this.config?.selectors || {} + }, + tokenManager: null, apiClient: null, @@ -109,13 +113,12 @@ const ms3 = { * - etc. */ initFormHandler () { - const sel = this.config?.selectors || {} - const formSel = sel.form || '[data-ms3-form], .ms3_form' + const selectors = this.selectors + const formSelector = selectors.form || '[data-ms3-form], .ms3_form' document.addEventListener('submit', async (event) => { const form = event.target - const matches = form.matches || form.msMatchesSelector || form.webkitMatchesSelector - if (!matches || !matches.call(form, formSel)) { + if (!form.matches(formSelector)) { return } @@ -142,19 +145,19 @@ const ms3 = { * inside forms matching sel.form. Triggers form submit. */ initLinkHandler () { - const sel = this.config?.selectors || {} - const linkSel = sel.link || '.ms3_link' - const formSel = sel.form || '[data-ms3-form], .ms3_form' + const selectors = this.selectors + const linkSelector = selectors.link || '.ms3_link' + const formSelector = selectors.form || '[data-ms3-form], .ms3_form' document.addEventListener('click', async (event) => { - const link = event.target.closest(linkSel) + const link = event.target.closest(linkSelector) if (!link) { return } - const form = link.closest(formSel) + const form = link.closest(formSelector) if (!form) { - console.warn('ms3_link must be inside a form matching sel.form') + console.warn(`ms3_link must be inside a form matching: ${formSelector}`) return } diff --git a/assets/components/minishop3/js/web/ui/CartUI.js b/assets/components/minishop3/js/web/ui/CartUI.js index e6cbe763..6506bfcb 100644 --- a/assets/components/minishop3/js/web/ui/CartUI.js +++ b/assets/components/minishop3/js/web/ui/CartUI.js @@ -32,6 +32,10 @@ class CartUI { this.config = config } + get selectors () { + return this.config?.selectors || {} + } + /** * Initialize UI handlers */ @@ -43,13 +47,13 @@ class CartUI { * Product option selects: uses sel.cartOptions from config */ initOptionSelects () { - const sel = this.config?.selectors || {} - const cartOptionsSel = sel.cartOptions || '[data-ms3-cart-options], .ms3_cart_options' - const formSel = sel.form || '[data-ms3-form], .ms3_form' + const selectors = this.selectors + const cartOptionsSelector = selectors.cartOptions || '[data-ms3-cart-options], .ms3_cart_options' + const formSelector = selectors.form || '[data-ms3-form], .ms3_form' - document.querySelectorAll(cartOptionsSel).forEach(select => { + document.querySelectorAll(cartOptionsSelector).forEach(select => { select.addEventListener('change', async (e) => { - const form = e.target.closest(formSel) + const form = e.target.closest(formSelector) if (!form) return console.log('Option changed:', e.target.name, e.target.value) diff --git a/assets/components/minishop3/js/web/ui/CustomerUI.js b/assets/components/minishop3/js/web/ui/CustomerUI.js index 3557c66f..ff35936e 100644 --- a/assets/components/minishop3/js/web/ui/CustomerUI.js +++ b/assets/components/minishop3/js/web/ui/CustomerUI.js @@ -31,6 +31,10 @@ class CustomerUI { this.config = config } + get selectors () { + return this.config?.selectors || {} + } + /** * Get lexicon string (window.ms3Lexicon, then fallback, then key) * @param {string} key - Lexicon key @@ -47,10 +51,10 @@ class CustomerUI { * Initialize UI handlers */ init () { - const sel = this.config?.selectors || {} - const formCustomerSel = sel.formCustomer || '[data-ms3-form="customer"], .ms3_customer_form' + const selectors = this.selectors + const formCustomerSelector = selectors.formCustomer || '[data-ms3-form="customer"], .ms3_customer_form' - document.querySelectorAll(formCustomerSel).forEach(form => { + document.querySelectorAll(formCustomerSelector).forEach(form => { this.initForm(form) }) } @@ -75,8 +79,8 @@ class CustomerUI { */ initInput (input) { input.addEventListener('change', async () => { - const formCustomerSel = (this.config?.selectors || {}).formCustomer || '[data-ms3-form="customer"], .ms3_customer_form' - const form = input.closest(formCustomerSel) + const formCustomerSelector = this.selectors.formCustomer || '[data-ms3-form="customer"], .ms3_customer_form' + const form = input.closest(formCustomerSelector) if (!form) return const parent = input.closest('div') diff --git a/assets/components/minishop3/js/web/ui/OrderUI.js b/assets/components/minishop3/js/web/ui/OrderUI.js index 9a78e75b..c5aa62ab 100644 --- a/assets/components/minishop3/js/web/ui/OrderUI.js +++ b/assets/components/minishop3/js/web/ui/OrderUI.js @@ -17,14 +17,18 @@ class OrderUI { this.config = config } + get selectors () { + return this.config?.selectors || {} + } + /** * Initialize UI handlers */ init () { - const sel = this.config?.selectors || {} - const formOrderSel = sel.formOrder || '[data-ms3-form="order"], .ms3_order_form' + const selectors = this.selectors + const formOrderSelector = selectors.formOrder || '[data-ms3-form="order"], .ms3_order_form' - document.querySelectorAll(formOrderSel).forEach(form => { + document.querySelectorAll(formOrderSelector).forEach(form => { this.initForm(form) }) @@ -160,24 +164,24 @@ class OrderUI { if (response.success && response.data) { const { cost, cart_cost: cartCost, delivery_cost: deliveryCost } = response.data - const sel = this.config?.selectors || {} + const selectors = this.selectors // Update cart cost - const cartCostEl = document.querySelector(sel.orderCartCost || '#ms3_order_cart_cost') - if (cartCostEl && cartCost !== undefined) { - cartCostEl.textContent = this.formatPrice(cartCost) + const cartCostElement = document.querySelector(selectors.orderCartCost || '#ms3_order_cart_cost') + if (cartCostElement && cartCost !== undefined) { + cartCostElement.textContent = this.formatPrice(cartCost) } // Update delivery cost - const deliveryCostEl = document.querySelector(sel.orderDeliveryCost || '#ms3_order_delivery_cost') - if (deliveryCostEl && deliveryCost !== undefined) { - deliveryCostEl.textContent = this.formatPrice(deliveryCost) + const deliveryCostElement = document.querySelector(selectors.orderDeliveryCost || '#ms3_order_delivery_cost') + if (deliveryCostElement && deliveryCost !== undefined) { + deliveryCostElement.textContent = this.formatPrice(deliveryCost) } // Update total cost - const totalCostEl = document.querySelector(sel.orderCost || '#ms3_order_cost') - if (totalCostEl && cost !== undefined) { - totalCostEl.textContent = this.formatPrice(cost) + const totalCostElement = document.querySelector(selectors.orderCost || '#ms3_order_cost') + if (totalCostElement && cost !== undefined) { + totalCostElement.textContent = this.formatPrice(cost) } await this.hooks.runHooks('afterUpdateOrderCosts', { cost, cart_cost: cartCost, delivery_cost: deliveryCost }) @@ -268,8 +272,8 @@ class OrderUI { } if (response.success) { - const formOrderSel = (this.config?.selectors || {}).formOrder || '[data-ms3-form="order"], .ms3_order_form' - document.querySelectorAll(formOrderSel).forEach(form => { + const formOrderSelector = this.selectors.formOrder || '[data-ms3-form="order"], .ms3_order_form' + document.querySelectorAll(formOrderSelector).forEach(form => { form.reset() }) document.dispatchEvent(new CustomEvent('ms3:cart:updated')) @@ -288,21 +292,21 @@ class OrderUI { * @param {Array} errors - Array of field names with errors */ highlightErrors (errors) { - const fieldErrorSel = (this.config?.selectors || {}).fieldError || '[data-ms3-error], .ms3_field_error' + const fieldErrorSelector = this.selectors.fieldError || '[data-ms3-error], .ms3_field_error' - document.querySelectorAll(fieldErrorSel).forEach(el => { - el.removeAttribute('data-ms3-error') - el.classList.remove('ms3_field_error') + document.querySelectorAll(fieldErrorSelector).forEach(element => { + element.removeAttribute('data-ms3-error') + element.classList.remove('ms3_field_error') }) errors.forEach(fieldName => { - const selectors = [ + const selectorList = [ `[name="${fieldName}"]`, `[name="address_${fieldName}"]`, `[name="order_${fieldName}"]` ] - selectors.forEach(selector => { + selectorList.forEach(selector => { const field = document.querySelector(selector) if (field) { field.setAttribute('data-ms3-error', '') diff --git a/assets/components/minishop3/js/web/ui/ProductCardUI.js b/assets/components/minishop3/js/web/ui/ProductCardUI.js index 6553517e..15485fcc 100644 --- a/assets/components/minishop3/js/web/ui/ProductCardUI.js +++ b/assets/components/minishop3/js/web/ui/ProductCardUI.js @@ -33,6 +33,10 @@ class ProductCardUI { this.cartState = {} } + get selectors () { + return this.config?.selectors || {} + } + /** * Initialize product card UI */ @@ -127,9 +131,9 @@ class ProductCardUI { * Update all product cards on page */ updateAllCards () { - const sel = this.config?.selectors || {} - const productCardSel = sel.productCard || '[data-ms3-product-card], .ms3-product-card' - const cards = document.querySelectorAll(productCardSel) + const selectors = this.selectors + const productCardSelector = selectors.productCard || '[data-ms3-product-card], .ms3-product-card' + const cards = document.querySelectorAll(productCardSelector) cards.forEach(card => { this.updateCard(card) @@ -163,8 +167,8 @@ class ProductCardUI { changeForm.style.display = 'flex' // Update quantity input - const qtyInputSel = (this.config?.selectors || {}).qtyInput || '[data-ms3-qty="input"], .qty-input' - const countInput = changeForm.querySelector(qtyInputSel) + const quantityInputSelector = this.selectors.qtyInput || '[data-ms3-qty="input"], .qty-input' + const countInput = changeForm.querySelector(quantityInputSelector) if (countInput) { countInput.value = this.cartState[productId].totalCount } diff --git a/assets/components/minishop3/js/web/ui/QuantityUI.js b/assets/components/minishop3/js/web/ui/QuantityUI.js index f954fac6..b19a79ae 100644 --- a/assets/components/minishop3/js/web/ui/QuantityUI.js +++ b/assets/components/minishop3/js/web/ui/QuantityUI.js @@ -32,6 +32,10 @@ class QuantityUI { this.config = config } + get selectors () { + return this.config?.selectors || {} + } + /** * Initialize quantity controls */ @@ -52,10 +56,10 @@ class QuantityUI { * Initialize quantity buttons: uses sel.qtyInc, sel.qtyDec from config */ initButtons () { - const sel = this.config?.selectors || {} - const qtyIncSel = sel.qtyInc || '[data-ms3-qty="inc"], .inc-qty' - const qtyDecSel = sel.qtyDec || '[data-ms3-qty="dec"], .dec-qty' - const buttons = document.querySelectorAll([qtyIncSel, qtyDecSel].join(', ')) + const selectors = this.selectors + const quantityIncreaseSelector = selectors.qtyInc || '[data-ms3-qty="inc"], .inc-qty' + const quantityDecreaseSelector = selectors.qtyDec || '[data-ms3-qty="dec"], .dec-qty' + const buttons = document.querySelectorAll([quantityIncreaseSelector, quantityDecreaseSelector].join(', ')) buttons.forEach(btn => { const newBtn = btn.cloneNode(true) @@ -68,9 +72,9 @@ class QuantityUI { * Initialize quantity inputs: uses sel.qtyInput from config */ initInputs () { - const sel = this.config?.selectors || {} - const qtyInputSel = sel.qtyInput || '[data-ms3-qty="input"], .qty-input' - const inputs = document.querySelectorAll(qtyInputSel) + const selectors = this.selectors + const quantityInputSelector = selectors.qtyInput || '[data-ms3-qty="input"], .qty-input' + const inputs = document.querySelectorAll(quantityInputSelector) inputs.forEach(input => { const newInput = input.cloneNode(true) @@ -87,20 +91,22 @@ class QuantityUI { async handleButtonClick (e) { e.preventDefault() - const sel = this.config?.selectors || {} - const formSel = sel.form || '[data-ms3-form], .ms3_form' - const qtyInputSel = sel.qtyInput || '[data-ms3-qty="input"], .qty-input' + const selectors = this.selectors + const formSelector = selectors.form || '[data-ms3-form], .ms3_form' + const quantityInputSelector = selectors.qtyInput || '[data-ms3-qty="input"], .qty-input' + const quantityIncreaseSelector = selectors.qtyInc || '[data-ms3-qty="inc"], .inc-qty' + const quantityDecreaseSelector = selectors.qtyDec || '[data-ms3-qty="dec"], .dec-qty' - const form = e.target.closest(formSel) + const form = e.target.closest(formSelector) if (!form) return - const input = form.querySelector(qtyInputSel) + const input = form.querySelector(quantityInputSelector) if (!input) return let qty = parseInt(input.value) || 0 - const isInc = e.target.getAttribute('data-ms3-qty') === 'inc' || e.target.classList.contains('inc-qty') - const isDec = e.target.getAttribute('data-ms3-qty') === 'dec' || e.target.classList.contains('dec-qty') + const isInc = e.target.matches(quantityIncreaseSelector) + const isDec = e.target.matches(quantityDecreaseSelector) if (isInc) qty++ if (isDec) qty-- @@ -117,10 +123,10 @@ class QuantityUI { * @param {Event} e - Change event */ async handleInputChange (e) { - const sel = this.config?.selectors || {} - const formSel = sel.form || '[data-ms3-form], .ms3_form' + const selectors = this.selectors + const formSelector = selectors.form || '[data-ms3-form], .ms3_form' - const form = e.target.closest(formSel) + const form = e.target.closest(formSelector) if (!form) return const qty = Math.max(0, parseInt(e.target.value) || 0) From 8024b1444a71a77518a88af4edfbd0ca38931db8 Mon Sep 17 00:00:00 2001 From: Ivan Bochkarev Date: Sat, 21 Feb 2026 00:24:58 +0600 Subject: [PATCH 3/3] refactor: streamline selector usage across UI components - Updated UI classes (CartUI, CustomerUI, OrderUI, ProductCardUI, QuantityUI) to directly use this.selectors for improved readability and maintainability. - Enhanced ms3.js to merge default selectors with custom ones from ms3Config. - Reformatted .eslintrc.json for consistency and added a rule to ignore specific unused variables. Co-authored-by: Cursor --- .eslintrc.json | 13 ++++++--- assets/components/minishop3/js/web/ms3.js | 27 ++++++++++++++----- .../components/minishop3/js/web/ui/CartUI.js | 4 +-- .../minishop3/js/web/ui/CustomerUI.js | 8 ++---- .../components/minishop3/js/web/ui/OrderUI.js | 20 +++++--------- .../minishop3/js/web/ui/ProductCardUI.js | 7 ++--- .../minishop3/js/web/ui/QuantityUI.js | 18 +++---------- core/components/minishop3/src/MiniShop3.php | 2 +- 8 files changed, 46 insertions(+), 53 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index f6173df0..a2413385 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,11 +1,18 @@ { "root": true, - "extends": ["standard"], - "env": { "browser": true }, + "extends": [ + "standard" + ], + "env": { + "browser": true + }, "globals": { "ms3": "readonly", "ms3Config": "readonly", "getSelectors": "readonly" }, - "ignorePatterns": ["**/node_modules/**", "**/mgr/**"] + "ignorePatterns": ["**/node_modules/**", "**/mgr/**"], + "rules": { + "no-unused-vars": ["error", { "varsIgnorePattern": "^(CartUI|CustomerUI|OrderUI|ProductCardUI|QuantityUI)$" }] + } } diff --git a/assets/components/minishop3/js/web/ms3.js b/assets/components/minishop3/js/web/ms3.js index 175306cb..423614a1 100644 --- a/assets/components/minishop3/js/web/ms3.js +++ b/assets/components/minishop3/js/web/ms3.js @@ -53,10 +53,25 @@ const ms3 = { this.config = window.ms3Config || {} // Merge selectors from Selectors.js (overridable via ms3Config.selectors) - const selectors = typeof getSelectors === 'function' + const rawSelectors = typeof getSelectors === 'function' ? getSelectors() : (window.Ms3DefaultSelectors || {}) - this.config = { ...this.config, selectors } + const selectorDefaults = window.Ms3DefaultSelectors || { + form: '[data-ms3-form], .ms3_form', + formOrder: '[data-ms3-form="order"], .ms3_order_form', + formCustomer: '[data-ms3-form="customer"], .ms3_customer_form', + cartOptions: '[data-ms3-cart-options], .ms3_cart_options', + qtyInput: '[data-ms3-qty="input"], .qty-input', + qtyInc: '[data-ms3-qty="inc"], .inc-qty', + qtyDec: '[data-ms3-qty="dec"], .dec-qty', + productCard: '[data-ms3-product-card], .ms3-product-card', + fieldError: '[data-ms3-error], .ms3_field_error', + orderCost: '#ms3_order_cost', + orderCartCost: '#ms3_order_cart_cost', + orderDeliveryCost: '#ms3_order_delivery_cost', + link: '.ms3_link' + } + this.config = { ...this.config, selectors: { ...selectorDefaults, ...rawSelectors } } this.hooks = window.ms3Hooks || this.createFallbackHooks() this.message = window.ms3Message || this.createFallbackMessage() @@ -113,8 +128,7 @@ const ms3 = { * - etc. */ initFormHandler () { - const selectors = this.selectors - const formSelector = selectors.form || '[data-ms3-form], .ms3_form' + const formSelector = this.selectors.form document.addEventListener('submit', async (event) => { const form = event.target @@ -145,9 +159,8 @@ const ms3 = { * inside forms matching sel.form. Triggers form submit. */ initLinkHandler () { - const selectors = this.selectors - const linkSelector = selectors.link || '.ms3_link' - const formSelector = selectors.form || '[data-ms3-form], .ms3_form' + const linkSelector = this.selectors.link + const formSelector = this.selectors.form document.addEventListener('click', async (event) => { const link = event.target.closest(linkSelector) diff --git a/assets/components/minishop3/js/web/ui/CartUI.js b/assets/components/minishop3/js/web/ui/CartUI.js index 6506bfcb..acacffe4 100644 --- a/assets/components/minishop3/js/web/ui/CartUI.js +++ b/assets/components/minishop3/js/web/ui/CartUI.js @@ -47,9 +47,7 @@ class CartUI { * Product option selects: uses sel.cartOptions from config */ initOptionSelects () { - const selectors = this.selectors - const cartOptionsSelector = selectors.cartOptions || '[data-ms3-cart-options], .ms3_cart_options' - const formSelector = selectors.form || '[data-ms3-form], .ms3_form' + const { cartOptions: cartOptionsSelector, form: formSelector } = this.selectors document.querySelectorAll(cartOptionsSelector).forEach(select => { select.addEventListener('change', async (e) => { diff --git a/assets/components/minishop3/js/web/ui/CustomerUI.js b/assets/components/minishop3/js/web/ui/CustomerUI.js index ff35936e..420324ed 100644 --- a/assets/components/minishop3/js/web/ui/CustomerUI.js +++ b/assets/components/minishop3/js/web/ui/CustomerUI.js @@ -51,10 +51,7 @@ class CustomerUI { * Initialize UI handlers */ init () { - const selectors = this.selectors - const formCustomerSelector = selectors.formCustomer || '[data-ms3-form="customer"], .ms3_customer_form' - - document.querySelectorAll(formCustomerSelector).forEach(form => { + document.querySelectorAll(this.selectors.formCustomer).forEach(form => { this.initForm(form) }) } @@ -79,8 +76,7 @@ class CustomerUI { */ initInput (input) { input.addEventListener('change', async () => { - const formCustomerSelector = this.selectors.formCustomer || '[data-ms3-form="customer"], .ms3_customer_form' - const form = input.closest(formCustomerSelector) + const form = input.closest(this.selectors.formCustomer) if (!form) return const parent = input.closest('div') diff --git a/assets/components/minishop3/js/web/ui/OrderUI.js b/assets/components/minishop3/js/web/ui/OrderUI.js index c5aa62ab..03dc4dac 100644 --- a/assets/components/minishop3/js/web/ui/OrderUI.js +++ b/assets/components/minishop3/js/web/ui/OrderUI.js @@ -25,10 +25,7 @@ class OrderUI { * Initialize UI handlers */ init () { - const selectors = this.selectors - const formOrderSelector = selectors.formOrder || '[data-ms3-form="order"], .ms3_order_form' - - document.querySelectorAll(formOrderSelector).forEach(form => { + document.querySelectorAll(this.selectors.formOrder).forEach(form => { this.initForm(form) }) @@ -164,22 +161,20 @@ class OrderUI { if (response.success && response.data) { const { cost, cart_cost: cartCost, delivery_cost: deliveryCost } = response.data - const selectors = this.selectors - // Update cart cost - const cartCostElement = document.querySelector(selectors.orderCartCost || '#ms3_order_cart_cost') + const cartCostElement = document.querySelector(this.selectors.orderCartCost) if (cartCostElement && cartCost !== undefined) { cartCostElement.textContent = this.formatPrice(cartCost) } // Update delivery cost - const deliveryCostElement = document.querySelector(selectors.orderDeliveryCost || '#ms3_order_delivery_cost') + const deliveryCostElement = document.querySelector(this.selectors.orderDeliveryCost) if (deliveryCostElement && deliveryCost !== undefined) { deliveryCostElement.textContent = this.formatPrice(deliveryCost) } // Update total cost - const totalCostElement = document.querySelector(selectors.orderCost || '#ms3_order_cost') + const totalCostElement = document.querySelector(this.selectors.orderCost) if (totalCostElement && cost !== undefined) { totalCostElement.textContent = this.formatPrice(cost) } @@ -272,8 +267,7 @@ class OrderUI { } if (response.success) { - const formOrderSelector = this.selectors.formOrder || '[data-ms3-form="order"], .ms3_order_form' - document.querySelectorAll(formOrderSelector).forEach(form => { + document.querySelectorAll(this.selectors.formOrder).forEach(form => { form.reset() }) document.dispatchEvent(new CustomEvent('ms3:cart:updated')) @@ -292,9 +286,7 @@ class OrderUI { * @param {Array} errors - Array of field names with errors */ highlightErrors (errors) { - const fieldErrorSelector = this.selectors.fieldError || '[data-ms3-error], .ms3_field_error' - - document.querySelectorAll(fieldErrorSelector).forEach(element => { + document.querySelectorAll(this.selectors.fieldError).forEach(element => { element.removeAttribute('data-ms3-error') element.classList.remove('ms3_field_error') }) diff --git a/assets/components/minishop3/js/web/ui/ProductCardUI.js b/assets/components/minishop3/js/web/ui/ProductCardUI.js index 15485fcc..7c3c573d 100644 --- a/assets/components/minishop3/js/web/ui/ProductCardUI.js +++ b/assets/components/minishop3/js/web/ui/ProductCardUI.js @@ -131,9 +131,7 @@ class ProductCardUI { * Update all product cards on page */ updateAllCards () { - const selectors = this.selectors - const productCardSelector = selectors.productCard || '[data-ms3-product-card], .ms3-product-card' - const cards = document.querySelectorAll(productCardSelector) + const cards = document.querySelectorAll(this.selectors.productCard) cards.forEach(card => { this.updateCard(card) @@ -167,8 +165,7 @@ class ProductCardUI { changeForm.style.display = 'flex' // Update quantity input - const quantityInputSelector = this.selectors.qtyInput || '[data-ms3-qty="input"], .qty-input' - const countInput = changeForm.querySelector(quantityInputSelector) + const countInput = changeForm.querySelector(this.selectors.qtyInput) if (countInput) { countInput.value = this.cartState[productId].totalCount } diff --git a/assets/components/minishop3/js/web/ui/QuantityUI.js b/assets/components/minishop3/js/web/ui/QuantityUI.js index b19a79ae..fd865291 100644 --- a/assets/components/minishop3/js/web/ui/QuantityUI.js +++ b/assets/components/minishop3/js/web/ui/QuantityUI.js @@ -56,9 +56,7 @@ class QuantityUI { * Initialize quantity buttons: uses sel.qtyInc, sel.qtyDec from config */ initButtons () { - const selectors = this.selectors - const quantityIncreaseSelector = selectors.qtyInc || '[data-ms3-qty="inc"], .inc-qty' - const quantityDecreaseSelector = selectors.qtyDec || '[data-ms3-qty="dec"], .dec-qty' + const { qtyInc: quantityIncreaseSelector, qtyDec: quantityDecreaseSelector } = this.selectors const buttons = document.querySelectorAll([quantityIncreaseSelector, quantityDecreaseSelector].join(', ')) buttons.forEach(btn => { @@ -72,8 +70,7 @@ class QuantityUI { * Initialize quantity inputs: uses sel.qtyInput from config */ initInputs () { - const selectors = this.selectors - const quantityInputSelector = selectors.qtyInput || '[data-ms3-qty="input"], .qty-input' + const quantityInputSelector = this.selectors.qtyInput const inputs = document.querySelectorAll(quantityInputSelector) inputs.forEach(input => { @@ -91,11 +88,7 @@ class QuantityUI { async handleButtonClick (e) { e.preventDefault() - const selectors = this.selectors - const formSelector = selectors.form || '[data-ms3-form], .ms3_form' - const quantityInputSelector = selectors.qtyInput || '[data-ms3-qty="input"], .qty-input' - const quantityIncreaseSelector = selectors.qtyInc || '[data-ms3-qty="inc"], .inc-qty' - const quantityDecreaseSelector = selectors.qtyDec || '[data-ms3-qty="dec"], .dec-qty' + const { form: formSelector, qtyInput: quantityInputSelector, qtyInc: quantityIncreaseSelector, qtyDec: quantityDecreaseSelector } = this.selectors const form = e.target.closest(formSelector) if (!form) return @@ -123,10 +116,7 @@ class QuantityUI { * @param {Event} e - Change event */ async handleInputChange (e) { - const selectors = this.selectors - const formSelector = selectors.form || '[data-ms3-form], .ms3_form' - - const form = e.target.closest(formSelector) + const form = e.target.closest(this.selectors.form) if (!form) return const qty = Math.max(0, parseInt(e.target.value) || 0) diff --git a/core/components/minishop3/src/MiniShop3.php b/core/components/minishop3/src/MiniShop3.php index 62aa8a0d..980301b4 100644 --- a/core/components/minishop3/src/MiniShop3.php +++ b/core/components/minishop3/src/MiniShop3.php @@ -159,7 +159,7 @@ public function registerFrontend($ctx = 'web') 'render' => [ 'cart' => [] ], - 'selectors' => [] + 'selectors' => (object)[] ]; $data = json_encode($js_setting, JSON_UNESCAPED_UNICODE);