From dcf20ed58f51669bfef392e7d501af79e2a85c03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ce=CC=81dric=20Andrietti?= Date: Tue, 27 Oct 2026 16:40:50 +0100 Subject: [PATCH] feat(slider): add accessible Swiper carousel foundation Align BBT with BFF slider base: Slider class, controls partial, styles, and script translations for a11y strings. Co-authored-by: Cursor --- .eslintrc | 3 + components/parts/common/swiper-controls.php | 17 + inc/Services/Assets.php | 8 +- ...r_FR-a442c66c97675e8bda3e3b62b3a8c8ec.json | 1 + languages/beapi-blocks-theme-fr_FR.mo | Bin 1841 -> 2116 bytes languages/beapi-blocks-theme-fr_FR.po | 26 ++ languages/beapi-blocks-theme.pot | 26 ++ package-lock.json | 322 ++++++++++-------- package.json | 4 +- src/js/common/classes/Slider.js | 156 +++++++++ src/js/common/utils/beapi.js | 2 + src/scss/common/11-vendor/_swiper.scss | 56 +++ src/scss/common/editor.scss | 2 + src/scss/common/style.scss | 1 + 14 files changed, 476 insertions(+), 148 deletions(-) create mode 100644 components/parts/common/swiper-controls.php create mode 100644 languages/beapi-blocks-theme-fr_FR-a442c66c97675e8bda3e3b62b3a8c8ec.json create mode 100644 src/js/common/classes/Slider.js create mode 100644 src/scss/common/11-vendor/_swiper.scss diff --git a/.eslintrc b/.eslintrc index e3c8e4c..1ba96a6 100644 --- a/.eslintrc +++ b/.eslintrc @@ -6,6 +6,9 @@ "plugins": [ "prettier" ], + "env": { + "browser": true + }, "globals": { "jQuery": true, "wp": true diff --git a/components/parts/common/swiper-controls.php b/components/parts/common/swiper-controls.php new file mode 100644 index 0000000..5c2515a --- /dev/null +++ b/components/parts/common/swiper-controls.php @@ -0,0 +1,17 @@ + + +
+
+
+
+
+
diff --git a/inc/Services/Assets.php b/inc/Services/Assets.php index db49859..1403f29 100644 --- a/inc/Services/Assets.php +++ b/inc/Services/Assets.php @@ -88,7 +88,7 @@ public function register_assets(): void { $this->assets_tools->register_script( 'scripts', $asset['file'], - array_merge( [ 'jquery' ], $asset['dependencies'] ), // ensure jQuery dependency is set even if not declared explicitly in the JS + array_merge( [ 'jquery', 'wp-i18n' ], $asset['dependencies'] ), // ensure jQuery dependency is set even if not declared explicitly in the JS $asset['version'], [ 'strategy' => 'defer' ] ); @@ -127,6 +127,12 @@ public function register_assets(): void { public function enqueue_scripts(): void { // JS $this->assets_tools->enqueue_script( 'scripts' ); + + wp_set_script_translations( + 'scripts', + 'beapi-blocks-theme', + \get_theme_file_path( 'languages' ) + ); } /** diff --git a/languages/beapi-blocks-theme-fr_FR-a442c66c97675e8bda3e3b62b3a8c8ec.json b/languages/beapi-blocks-theme-fr_FR-a442c66c97675e8bda3e3b62b3a8c8ec.json new file mode 100644 index 0000000..b02897e --- /dev/null +++ b/languages/beapi-blocks-theme-fr_FR-a442c66c97675e8bda3e3b62b3a8c8ec.json @@ -0,0 +1 @@ +{"translation-revision-date":"2026-04-10 16:29+0200","generator":"WP-CLI\/2.12.0","source":"dist\/app.js","domain":"messages","locale_data":{"messages":{"":{"domain":"messages","lang":"fr_FR","plural-forms":"nplurals=2; plural=(n > 1);"},"Previous slide":["Diapositive pr\u00e9c\u00e9dente"],"Next slide":["Diapositive suivante"],"This is the first slide":["C'est la premi\u00e8re diapositive"],"This is the last slide":["C'est la derni\u00e8re diapositive"],"Go to slide {{index}}":["Aller \u00e0 la diapositive {{index}}"],"{{index}} of {{slidesLength}}":["{{index}} sur {{slidesLength}}"]}}} \ No newline at end of file diff --git a/languages/beapi-blocks-theme-fr_FR.mo b/languages/beapi-blocks-theme-fr_FR.mo index 9f0fed129fe5fe6fa2eee7733dcd54cece54ed8d..567779f3313d0afdc9bea0f539a9a36d819703db 100644 GIT binary patch delta 999 zcmZY6K}Zx)7{Kw#T~{+T6|D_Q#>|A)g;{qK*;Anzgju8{VG%?7bSKuCWoCA>M7(wA zAi>a0s8c0&2_l0o!PYUlcFIc;M0D_wP#yYzI_QGS?EK#MzPIoD-rJYW?^_nXv_&6j ze7gA_<(t_jk*1B->{`q4B5@s#VR#KZsLCC|DhC!MYbDgMVUW>_1GQ8eJF(oPzs+5 zG&G2f)YmRBkRMEjiR;)!Y-0~T4D+u;-=b{%4%_h)N&{cR`JX6%(O*1>^(5mMM_Hdj zxrq@Rla8-3kP|v6@qLs6%P1#Y#Z&kSd9-z2o%lU8#v+M(>Vu`4*^cv$?mN0yuyRVD zpSSFsnp;>1{?ud}#?+j@oe0vAlTDXhRkoaxw>1?^MwUk>@|LIb_<5yITdo(b)~e?V z=Fara?z%HgsjZUUsIq7LJQX)0cY`2$JopfuN?e*q4!O$oEyqp{o4(5G{YL*l(#RxJ z#zZQU?H|mh(+R`K8b)wCnrO0TO6Cl!rrnz(SAx#kA6;h)1?B3MCwjrubCy|jJj=Jr z)hDcrooOGg%6KKKY}&pGMq;Pq)rq28S-DkN$tg+Ri>=)l>Q$VHjf<|DwJOhDRb4{c TN#~Sn?@jOCf>(0)T!Z!>7Sh7Q delta 728 zcmXxi%_~Gv7{~Er=FWH@#`|l{!Ym9au|UcSC7QBRA`29;P>L*OCpDYVNy>tSvSGnS zd07yaO8x*FlO!dHjgs$g98aD5x#!+j%=P*gnTbykeBERY2MhW}%0OP0+jbRbaVi~TW zDoddjwu=>b;`(=}`A?`qJ~4(rsP8v&(W%zZ^j@L-rcf6`+phoMKT6$=CMzmwO03Ws zYB5@vs^=6l+YFg1Wfe_3ME=Y_RE_$_v`7U-mS!qcN0o2_rq=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", @@ -8423,9 +8435,9 @@ "license": "MIT" }, "node_modules/@tannin/sprintf": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@tannin/sprintf/-/sprintf-1.3.3.tgz", - "integrity": "sha512-RwARl+hFwhzy0tg9atWcchLFvoQiOh4rrP7uG2N5E4W80BPCUX0ElcUR9St43fxB9EfjsW2df9Qp+UsTbvQDjA==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@tannin/sprintf/-/sprintf-1.3.4.tgz", + "integrity": "sha512-Kypdet5L0Hhkfbdkn2QREczNTjCXhiAnouRINyAePy1lZKHD/zcwiqN93HC7i/vVCAuzNAlp+wuT4yOZ5icOSQ==", "dev": true, "license": "MIT" }, @@ -9786,6 +9798,25 @@ "npm": ">=8.19.2" } }, + "node_modules/@wordpress/a11y/node_modules/@wordpress/i18n": { + "version": "6.30.0", + "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-6.30.0.tgz", + "integrity": "sha512-Q8ivagc2aG+Qe5dTzmiohR+pDo4xDcD746fGI2dq+JIbTibYRnkFoN1VyGFZX/Q+g98I80ZJLJ5Vk3svSXb9ag==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@tannin/sprintf": "^1.3.2", + "@wordpress/hooks": "^4.57.0", + "gettext-parser": "^1.3.1", + "tannin": "^1.2.0" + }, + "bin": { + "pot-to-php": "tools/pot-to-php.js" + }, + "engines": { + "node": ">=18.12.0" + } + }, "node_modules/@wordpress/autop": { "version": "4.53.0", "resolved": "https://registry.npmjs.org/@wordpress/autop/-/autop-4.53.0.tgz", @@ -9915,6 +9946,25 @@ } } }, + "node_modules/@wordpress/blocks/node_modules/@wordpress/i18n": { + "version": "6.30.0", + "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-6.30.0.tgz", + "integrity": "sha512-Q8ivagc2aG+Qe5dTzmiohR+pDo4xDcD746fGI2dq+JIbTibYRnkFoN1VyGFZX/Q+g98I80ZJLJ5Vk3svSXb9ag==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@tannin/sprintf": "^1.3.2", + "@wordpress/hooks": "^4.57.0", + "gettext-parser": "^1.3.1", + "tannin": "^1.2.0" + }, + "bin": { + "pot-to-php": "tools/pot-to-php.js" + }, + "engines": { + "node": ">=18.12.0" + } + }, "node_modules/@wordpress/browserslist-config": { "version": "6.53.0", "resolved": "https://registry.npmjs.org/@wordpress/browserslist-config/-/browserslist-config-6.53.0.tgz", @@ -10224,14 +10274,13 @@ } }, "node_modules/@wordpress/hooks": { - "version": "4.53.0", - "resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-4.53.0.tgz", - "integrity": "sha512-YU30rweJCTu5g7X8bc+w6QyoxJBYLEHH++Wwo/IbbROHUC/4I7e+ZtxnPWls8BTtMP4d28JJ74s+UoKpzd2J7w==", + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-4.57.0.tgz", + "integrity": "sha512-vRJxRhOj+a19RvdW6u0ZBmiQc/t/ueVGsIP3IbS9zuvfLJILtvMSWXU8Fe3m/W+mBK5ZEOfm72LYBhWGPse0DA==", "dev": true, "license": "GPL-2.0-or-later", "engines": { - "node": ">=18.12.0", - "npm": ">=8.19.2" + "node": ">=18.12.0" } }, "node_modules/@wordpress/html-entities": { @@ -10246,25 +10295,46 @@ } }, "node_modules/@wordpress/i18n": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-6.26.0.tgz", - "integrity": "sha512-v/LzeB7T5YKIfBgmLM0jYG8zOuu81biZ90j8RY9pHIVu+jdRE+DSmt55S3FvtXRo5J2X9FoigFRzYPKXLbitWA==", + "version": "4.58.0", + "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-4.58.0.tgz", + "integrity": "sha512-VfvS3BWv/RDjRKD6PscIcvYfWKnGJcI/DEqyDgUMhxCM6NRwoL478CsUKTiGJIymeyRodNRfprdcF086DpGKYw==", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { - "@tannin/sprintf": "^1.3.2", - "@wordpress/hooks": "^4.53.0", + "@babel/runtime": "^7.16.0", + "@wordpress/hooks": "^3.58.0", "gettext-parser": "^1.3.1", + "memize": "^2.1.0", + "sprintf-js": "^1.1.1", "tannin": "^1.2.0" }, "bin": { "pot-to-php": "tools/pot-to-php.js" }, "engines": { - "node": ">=18.12.0", - "npm": ">=8.19.2" + "node": ">=12" + } + }, + "node_modules/@wordpress/i18n/node_modules/@wordpress/hooks": { + "version": "3.58.0", + "resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-3.58.0.tgz", + "integrity": "sha512-9LB0ZHnZRQlORttux9t/xbAskF+dk2ujqzPGsVzc92mSKpQP3K2a5Wy74fUnInguB1vLUNHT6nrNdkVom5qX1Q==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@babel/runtime": "^7.16.0" + }, + "engines": { + "node": ">=12" } }, + "node_modules/@wordpress/i18n/node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/@wordpress/is-shallow-equal": { "version": "5.53.0", "resolved": "https://registry.npmjs.org/@wordpress/is-shallow-equal/-/is-shallow-equal-5.53.0.tgz", @@ -10336,6 +10406,25 @@ } } }, + "node_modules/@wordpress/keycodes/node_modules/@wordpress/i18n": { + "version": "6.30.0", + "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-6.30.0.tgz", + "integrity": "sha512-Q8ivagc2aG+Qe5dTzmiohR+pDo4xDcD746fGI2dq+JIbTibYRnkFoN1VyGFZX/Q+g98I80ZJLJ5Vk3svSXb9ag==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@tannin/sprintf": "^1.3.2", + "@wordpress/hooks": "^4.57.0", + "gettext-parser": "^1.3.1", + "tannin": "^1.2.0" + }, + "bin": { + "pot-to-php": "tools/pot-to-php.js" + }, + "engines": { + "node": ">=18.12.0" + } + }, "node_modules/@wordpress/npm-package-json-lint-config": { "version": "5.53.0", "resolved": "https://registry.npmjs.org/@wordpress/npm-package-json-lint-config/-/npm-package-json-lint-config-5.53.0.tgz", @@ -10461,6 +10550,25 @@ } } }, + "node_modules/@wordpress/rich-text/node_modules/@wordpress/i18n": { + "version": "6.30.0", + "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-6.30.0.tgz", + "integrity": "sha512-Q8ivagc2aG+Qe5dTzmiohR+pDo4xDcD746fGI2dq+JIbTibYRnkFoN1VyGFZX/Q+g98I80ZJLJ5Vk3svSXb9ag==", + "dev": true, + "license": "GPL-2.0-or-later", + "dependencies": { + "@tannin/sprintf": "^1.3.2", + "@wordpress/hooks": "^4.57.0", + "gettext-parser": "^1.3.1", + "tannin": "^1.2.0" + }, + "bin": { + "pot-to-php": "tools/pot-to-php.js" + }, + "engines": { + "node": ">=18.12.0" + } + }, "node_modules/@wordpress/scripts": { "version": "34.1.0", "resolved": "https://registry.npmjs.org/@wordpress/scripts/-/scripts-34.1.0.tgz", @@ -10561,85 +10669,15 @@ "node": ">=18" } }, - "node_modules/@wordpress/scripts/node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.72.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.72.0.tgz", - "integrity": "sha512-aysKgysPX9XOv/dk95UkvUDe++Q3n3dJPG9CEWT3RUccxfkPJ+Qn6nhKPDhZXU6Ofo9kgwGWwKyQCnZIXTgngg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.72.0", - "@typescript-eslint/type-utils": "8.72.0", - "@typescript-eslint/utils": "8.72.0", - "@typescript-eslint/visitor-keys": "8.72.0", - "ignore": "^7.0.5", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.72.0", - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@wordpress/scripts/node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.13", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.13.tgz", - "integrity": "sha512-2E8Q6rh+1RRSq/YdaGsNfXsEZy9FVqv6BSSrkmZJP7BnaoPnRfKL+YaofPvP+5rzdk144qSm6i4+hvPgUcbV4Q==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/@wordpress/scripts/node_modules/@typescript-eslint/parser": { - "version": "8.72.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.72.0.tgz", - "integrity": "sha512-0O+x3ynciD6zuXb1CKSu165haQV6ey53DKhHHUnujP7U+XlltJG+/keT0Xhd1HXCze+IHf7Tvt39ovyepX/LNw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@typescript-eslint/scope-manager": "8.72.0", - "@typescript-eslint/types": "8.72.0", - "@typescript-eslint/typescript-estree": "8.72.0", - "@typescript-eslint/visitor-keys": "8.72.0", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, "node_modules/@wordpress/scripts/node_modules/@typescript-eslint/project-service": { - "version": "8.72.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.72.0.tgz", - "integrity": "sha512-bzHngBbmIgzj6TsTjVUCntGx6FFLgE7mPjpO0PzHycOaZHXqS43XnrkFIHRS16LLr/xO6FHSXG3GJFo/F4bSBQ==", + "version": "8.72.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.72.1.tgz", + "integrity": "sha512-fAuVMkwdO/huI8nyo/WKnNKf6Gag93S56Rh3Ts5mGcavwSdaN8tRg1gCeIGX8PF+nzp1BjGfO4gHhGzW1J6jTg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.72.0", - "@typescript-eslint/types": "^8.72.0", + "@typescript-eslint/tsconfig-utils": "^8.72.1", + "@typescript-eslint/types": "^8.72.1", "debug": "^4.4.3" }, "engines": { @@ -10654,14 +10692,14 @@ } }, "node_modules/@wordpress/scripts/node_modules/@typescript-eslint/scope-manager": { - "version": "8.72.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.72.0.tgz", - "integrity": "sha512-JewPyJOYcXBSAS7PIgufscHOwZqhTGyBRiQJhhuDln66tMchxvvt7nJ90JKKIoOelmdHA3kAQbm9cEUHc4JSEA==", + "version": "8.72.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.72.1.tgz", + "integrity": "sha512-lC/t6eMCZZQAAKZEYGL6RDVSuFehpdmMT6h+nJIjIa6oylN/PwDgDVtEIbB7O9xFfZYtZAYLoLlDNWKJhtF+uQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.72.0", - "@typescript-eslint/visitor-keys": "8.72.0" + "@typescript-eslint/types": "8.72.1", + "@typescript-eslint/visitor-keys": "8.72.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -10672,9 +10710,9 @@ } }, "node_modules/@wordpress/scripts/node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.72.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.72.0.tgz", - "integrity": "sha512-0vbxCf7xLVPSjcOdm6eQbgViH8EDJ8xnl1laLT9yoqSz2YmcSfSg5Vdl0JUvZf6rcuJ0ZivJIuMD4edTg8obaA==", + "version": "8.72.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.72.1.tgz", + "integrity": "sha512-e4D5R5vpeAEPBxT2BAzsFQL3sbwyeeeo5+aczgNSRK1V734jE7pTqaGFU+fXapWEQwnZbpFaTn32+Z8DwS6OTg==", "dev": true, "license": "MIT", "engines": { @@ -10688,37 +10726,10 @@ "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@wordpress/scripts/node_modules/@typescript-eslint/type-utils": { - "version": "8.72.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.72.0.tgz", - "integrity": "sha512-gEXxmmbSTSOVTC37IYYvremOM31v+UN2W772bJxmVYPhAOPSUxtacyb7wkm1EhkYKU6KzFJsdU44DCV4to0LWw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@typescript-eslint/types": "8.72.0", - "@typescript-eslint/typescript-estree": "8.72.0", - "@typescript-eslint/utils": "8.72.0", - "debug": "^4.4.3", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, "node_modules/@wordpress/scripts/node_modules/@typescript-eslint/types": { - "version": "8.72.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.72.0.tgz", - "integrity": "sha512-Adfoy59kxNEjT5E5fnyrp7GpHYuWs8ONbdohu4ez6IU46qDq8LltG90tLmkucZB2uhYL8mqBjtXqGd8yhfjXqQ==", + "version": "8.72.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.72.1.tgz", + "integrity": "sha512-LmE5IhQet1BWOWEXqBrVtBPsMAOQ6Nzk35d+WLh3mk4EUF33sqlfxjio1MdYeSvKSG5skactKKuixBIKXhL4Rw==", "dev": true, "license": "MIT", "engines": { @@ -10730,16 +10741,16 @@ } }, "node_modules/@wordpress/scripts/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.72.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.72.0.tgz", - "integrity": "sha512-gwLfFgWcw15tN2KXDCJvxEOQjPn+P3j/n3eSqkZ5sX4VpW9Cosodr+tWwBZRvJT3TcKx6HnPRGZMCNAiP1Xhug==", + "version": "8.72.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.72.1.tgz", + "integrity": "sha512-DAlUoUV93hIvrtIQLwCbmZQYVZwOMhiHJrOVUUIWicFZaIYjTRqlY3I/NZevfkLmSqkNHRu2qYDcq9lrn16oOQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.72.0", - "@typescript-eslint/tsconfig-utils": "8.72.0", - "@typescript-eslint/types": "8.72.0", - "@typescript-eslint/visitor-keys": "8.72.0", + "@typescript-eslint/project-service": "8.72.1", + "@typescript-eslint/tsconfig-utils": "8.72.1", + "@typescript-eslint/types": "8.72.1", + "@typescript-eslint/visitor-keys": "8.72.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -10758,16 +10769,16 @@ } }, "node_modules/@wordpress/scripts/node_modules/@typescript-eslint/utils": { - "version": "8.72.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.72.0.tgz", - "integrity": "sha512-0up3oArJZBYnzOaEXKo8DmI43QvMZyPoFEt9LWzRJcyl/RcJjfKKPebknN1VbTgXBf3M/KggUh5uUMdURLnseg==", + "version": "8.72.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.72.1.tgz", + "integrity": "sha512-5v5liypurJBFHCBNqh/RjsymAD+VWcE0djuGY/BUqp5fC3Qn+luWDEksjuTtWjPn6i5zQlUtkZCRG/CYG0yCkA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.72.0", - "@typescript-eslint/types": "8.72.0", - "@typescript-eslint/typescript-estree": "8.72.0" + "@typescript-eslint/scope-manager": "8.72.1", + "@typescript-eslint/types": "8.72.1", + "@typescript-eslint/typescript-estree": "8.72.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -10782,13 +10793,13 @@ } }, "node_modules/@wordpress/scripts/node_modules/@typescript-eslint/visitor-keys": { - "version": "8.72.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.72.0.tgz", - "integrity": "sha512-E2mroviasaWecDzTGiGMtuEguuLVOeRggnrIn93CJuxYJzQBHbyymlT08aVQydbBal5538o+JkBKXh2sRuCyIA==", + "version": "8.72.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.72.1.tgz", + "integrity": "sha512-SB8xqipSzZHUePCCLNeYxqnxq83X61/qzNhjQGbTfjxGZIeOEP7tBgWYX05Tcd6CgE02WdTJnh5b8kMZJ/+bnQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.72.0", + "@typescript-eslint/types": "8.72.1", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -34167,6 +34178,25 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/swiper": { + "version": "14.3.0", + "resolved": "https://registry.npmjs.org/swiper/-/swiper-14.3.0.tgz", + "integrity": "sha512-qw0Xv1+sylM8KCQxWxV2iYAK2G6XjFGPZ/8F1baEsY8DK4A3sTnLx6jUJ0G71rDbVQ05JHMWkcWmJ5NvlxZO0w==", + "funding": [ + { + "type": "custom", + "url": "https://sponsors.nolimits4web.com" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nolimits4web" + } + ], + "license": "MIT", + "engines": { + "node": ">= 4.7.0" + } + }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", diff --git a/package.json b/package.json index ed0874b..71a78cb 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "license": "GPL-2.0", "dependencies": { "@beapi/be-a11y": "^2.0.1", - "oneloop.js": "^5.3.1" + "oneloop.js": "^5.3.1", + "swiper": "^14.3.0" }, "devDependencies": { "@stylistic/stylelint-plugin": "^4.0.1", @@ -32,6 +33,7 @@ "@wordpress/env": "^11.13.0", "@wordpress/eslint-plugin": "^22.22.0", "@wordpress/hooks": "^4.53.0", + "@wordpress/i18n": "^4.58.0", "@wordpress/scripts": "^34.1.0", "@wordpress/stylelint-config": "^24.2.0", "chalk": "^4.1.2", diff --git a/src/js/common/classes/Slider.js b/src/js/common/classes/Slider.js new file mode 100644 index 0000000..05ac7b4 --- /dev/null +++ b/src/js/common/classes/Slider.js @@ -0,0 +1,156 @@ +import AbstractDomElement from './AbstractDomElement'; +import Swiper from 'swiper'; +import { A11y, Navigation } from 'swiper/modules'; +import { __ } from '@wordpress/i18n'; +import noop from '../utils/noop'; + +/** + * Thin wrapper around Swiper with project defaults (A11y + Navigation). + * + * @augments AbstractDomElement + */ +export default class Slider extends AbstractDomElement { + /** + * @param {HTMLElement} element - Root `.swiper` element + * @param {SliderOptions} [options] + */ + constructor(element, options) { + const instance = super(element, options); + + // avoid double init : + if (!instance.isNewInstance()) { + return instance; + } + + this.init(); + } + + init() { + const el = this._element; + const { + options, + onChange, + hideNavigationFromA11y, + fixLastSlideActiveOnEnd, + } = this._settings; + + const defaultModules = Slider.defaults.options.modules; + const userModules = options.modules || []; + const allModules = [ + ...defaultModules, + ...userModules.filter((mod) => !defaultModules.includes(mod)), + ]; + + const slidesAreListItems = + el.querySelector('.swiper-slide')?.tagName === 'LI'; + + const hideNavigationButtons = (swiper) => { + if (!hideNavigationFromA11y) { + return; + } + + swiper.navigation.prevEl?.setAttribute('tabindex', '-1'); + swiper.navigation.prevEl?.setAttribute('aria-hidden', 'true'); + swiper.navigation.nextEl?.setAttribute('tabindex', '-1'); + swiper.navigation.nextEl?.setAttribute('aria-hidden', 'true'); + }; + + const applyLastSlideActiveFix = (swiper) => { + if (!swiper.isEnd || swiper.isBeginning) { + return; + } + + swiper.activeIndex = swiper.slides.length - 1; + swiper.updateSlidesClasses(); + }; + + const mergedOptions = { + ...options, + modules: allModules, + a11y: { + ...(slidesAreListItems ? { slideRole: '' } : {}), + ...options.a11y, + }, + on: { + ...options.on, + afterInit(swiper) { + hideNavigationButtons(swiper); + + if (hideNavigationFromA11y) { + swiper.el + .querySelector('.swiper-notification') + ?.remove(); + } + + options.on?.afterInit?.call(this, swiper); + }, + transitionEnd(swiper) { + hideNavigationButtons(swiper); + options.on?.transitionEnd?.call(this, swiper); + }, + progress(swiper, progress) { + if (fixLastSlideActiveOnEnd) { + applyLastSlideActiveFix(swiper); + } + + options.on?.progress?.call(this, swiper, progress); + }, + slideChange(swiper) { + if (fixLastSlideActiveOnEnd) { + applyLastSlideActiveFix(swiper); + } + + options.on?.slideChange?.call(this, swiper); + }, + }, + }; + + this.swiper = new Swiper(el, mergedOptions); + + this.swiper.on('slideChange', onChange.bind(this)); + } + + /** + * @return {import('swiper').Swiper} Native Swiper instance. + */ + getInstance() { + return this.swiper; + } +} + +/** + * @typedef {Object} SliderOptions + * @property {Function} [onChange] - Called on slideChange (`this` = Slider instance) + * @property {boolean} [hideNavigationFromA11y=false] - Hide prev/next from AT when slides are already focusable; also removes `.swiper-notification` + * @property {boolean} [fixLastSlideActiveOnEnd=false] - Fix `.swiper-slide-active` at the end with `slidesPerView: 'auto'` or fractional values other than halves (e.g. 2.75) + * @property {import('swiper').SwiperOptions} [options] - Native Swiper options (merged with defaults) + */ + +Slider.defaults = { + onChange: noop, + hideNavigationFromA11y: false, + fixLastSlideActiveOnEnd: false, + options: { + modules: [A11y, Navigation], + a11y: { + prevSlideMessage: __('Previous slide', 'beapi-blocks-theme'), + nextSlideMessage: __('Next slide', 'beapi-blocks-theme'), + firstSlideMessage: __( + 'This is the first slide', + 'beapi-blocks-theme' + ), + lastSlideMessage: __( + 'This is the last slide', + 'beapi-blocks-theme' + ), + paginationBulletMessage: __( + 'Go to slide {{index}}', + 'beapi-blocks-theme' + ), + slideLabelMessage: __( + '{{index}} of {{slidesLength}}', + 'beapi-blocks-theme' + ), + }, + }, +}; diff --git a/src/js/common/utils/beapi.js b/src/js/common/utils/beapi.js index 01b46f7..25f7b51 100644 --- a/src/js/common/utils/beapi.js +++ b/src/js/common/utils/beapi.js @@ -1,4 +1,5 @@ import AbstractDomElement from '../classes/AbstractDomElement'; +import Slider from '../classes/Slider'; import scrollDirection from '../classes/ScrollDirection'; import * as oneloop from 'oneloop.js'; import extend from './extend'; @@ -15,6 +16,7 @@ window.beapi = window.beapi || {}; extend(true, window.beapi, { classes: { AbstractDomElement, + Slider, }, libraries: { oneloop, diff --git a/src/scss/common/11-vendor/_swiper.scss b/src/scss/common/11-vendor/_swiper.scss new file mode 100644 index 0000000..371676e --- /dev/null +++ b/src/scss/common/11-vendor/_swiper.scss @@ -0,0 +1,56 @@ +@use "../00-variables/theme-json" as *; +@use "swiper/css"; +@use "swiper/css/a11y"; + +.swiper { + --swiper-theme-color: #{$settings-color-gray-100}; + + &__controls { + display: flex; + align-items: center; + justify-content: center; + + &:has(.swiper-button-lock) { + display: none; + } + } + + &__buttons { + display: flex; + gap: $settings-spacing-xs; + + button, + [role="button"] { + &.swiper-button-prev { + transform: scaleX(-1); + } + + &.swiper-button-disabled { + cursor: not-allowed; + opacity: 0.3; + } + } + } + + &__button { + display: flex; + align-items: center; + justify-content: center; + width: 44px; + height: 44px; + cursor: pointer; + background-color: var(--swiper-theme-color); + border-radius: 100%; + } + + &-overflow-visible { + overflow: visible; + } + + .swiper-wrapper { + box-sizing: border-box; + padding: 0; + margin: 0; + list-style: none; + } +} diff --git a/src/scss/common/editor.scss b/src/scss/common/editor.scss index 6eb6916..b509a38 100644 --- a/src/scss/common/editor.scss +++ b/src/scss/common/editor.scss @@ -13,3 +13,5 @@ $entry-file-name: editor; @use "06-core-blocks/heading"; @use "09-template-parts/header"; + +@use "11-vendor/swiper"; diff --git a/src/scss/common/style.scss b/src/scss/common/style.scss index 2083bf7..b487c04 100644 --- a/src/scss/common/style.scss +++ b/src/scss/common/style.scss @@ -17,3 +17,4 @@ $entry-file-name: style; @use "09-template-parts/header"; @use "11-vendor/toggle"; +@use "11-vendor/swiper";