diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9a98f86a..9e8801b0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,27 +1,27 @@
# 5.5.0
-- Ajout du réglage `is_lcp` à `get_the_image()` (fetchpriority high, decoding sync, pas de lazy loading)
+- Ajout du réglage `is_lcp` à `get_the_image()` (`fetchpriority` élevé, pas de lazy loading ; `decoding` en opt-in via les attributs)
- Ne jamais produire `fetchpriority="high"` avec `loading="lazy"` dans les helpers d’images
-- Ajout des helpers `get_theme_static_image_markup()` : images statiques du thème avec width/height (CLS) et conservation de `alt=""` pour les images décoratives
-- Abaissement du `fetchpriority` du logo personnalisé pour ne pas concurrencer l’image LCP
-- Ajout du filtre `bea_theme_framework_preload_fonts` (vide par défaut) pour précharger les polices above-the-fold
-- Réécriture de `Animation` avec des IntersectionObservers partagés au lieu d’une boucle au scroll (mêmes options et callbacks)
+- Ajout des helpers `get_theme_static_image_markup()` / `the_theme_static_image()` : images statiques du thème avec `width` / `height` (CLS) et conservation de `alt=""` pour les images décoratives
+- Stockage des dimensions des images statiques dans `dist/assets.json` au build (`imageDimensions`) pour éviter les lectures disque répétées à la requête
+- Ajout du filtre `bea_theme_framework_preload_fonts` (vide par défaut) pour précharger les polices affichées au premier rendu
+- Réécriture de `Animation` avec des `IntersectionObserver` partagés au lieu d’une boucle au scroll (mêmes options et callbacks)
- Ajout de l’utilitaire JS `deferInitialization()` pour exécuter du code non critique à idle (INP)
-- Documentation de la base Core Web Vitals dans le README
+- Documentation de la base Core Web Vitals dans le README (dont compatibilité ARI / `is_lcp`)
# 5.3.2
- Mise à jour de CHANGELOG.md et package.json avec le numéro de version BFF le plus récent
- Indentation par tabulations imposée dans la base de code
- Remplacement de toutes les règles dépréciées des dernières évolutions Dart Sass
-- Ajout d’une option d’attribut role dans le service link
+- Ajout d’une option d’attribut `role` dans le service link
- Suppression du contenu personnalisé du fichier index.php
-- Suppression du package browsersync
-- Améliorations a11y (skip link, bloc image, bloc tableau)
+- Suppression du package BrowserSync
+- Améliorations a11y (lien d’évitement, bloc image, bloc tableau)
- Ajout du lien de partage Bluesky
-- Suppression du custom JS each function
-- Ajout de la logique de paramètre URL pour désactiver les animations JS/CSS
-- Mise à jour de la version du package oneloop js
+- Suppression du custom JS « each »
+- Ajout de la logique de paramètre d’URL pour désactiver les animations JS/CSS
+- Mise à jour de la version du package oneloop.js
- Passage de Node 20.11.0 à 22.13.0
- Ajout du comportement logique RTL
- Mise à jour des règles CSS ACF vers Gutenberg
@@ -43,7 +43,7 @@
# 5.3.0 et 5.3.1
- Correction de l’exemple de namespace
-- Injection de données dans get_terms_list
+- Injection de données dans `get_terms_list`
# 5.2.1
@@ -52,14 +52,14 @@
# 5.2.0
- Mise à jour des packages npm @wordpress
-- Refactor des scripts éditeur pour utiliser les packages @wordpress
+- Refactorisation des scripts éditeur pour utiliser les packages @wordpress
- Mise à jour de l’enregistrement des assets avec le nouveau mécanisme asset data
- Ajout d’une nouvelle méthode pour charger les données
- Ajout et configuration du package dependency-extraction-webpack-plugin
# 5.1.6
-- Mise à jour des packages npm postcss
+- Mise à jour des packages npm PostCSS
# 5.1.5
@@ -76,7 +76,7 @@
# 5.1.2
- Mise à jour du package npm canvas
-- Correction du script webpack des tailles d’images
+- Correction du script Webpack des tailles d’images
# 5.1.1
@@ -102,12 +102,12 @@
# 5.0.6
-- Ajout de helpers de templating (the_image, the_text, the_link, etc.)
+- Ajout de helpers de templating (`the_image`, `the_text`, `the_link`, etc.)
# 5.0.0
- Ajout de Webpack 5
-- Déplacement des fichiers de configuration build dans le dossier config
+- Déplacement des fichiers de configuration build dans le dossier `config`
- Suppression des scripts bash
- Optimisation des images côté front
- Suppression des templates statiques
@@ -122,5 +122,5 @@
- Gestion du JS backend éditeur
- Gestion du CSS frontend/backend éditeur
- Version PHP minimale 7.2
-- Suppression de loadFont.php et utilisation de l’enqueue de scripts classique
-- Ajout de esc_attr sur defer/async script
+- Suppression de `loadFont.php` et utilisation de l’enqueue de scripts classique
+- Ajout de `esc_attr` sur les scripts defer/async
diff --git a/README.md b/README.md
index 72aa7755..1ae9a2db 100644
--- a/README.md
+++ b/README.md
@@ -86,7 +86,7 @@ BFF inclut une configuration [wp-env](https://developer.wordpress.org/block-edit
Vous pouvez lancer wp-env sans installation globale via `npx @wordpress/env`, ou installer la CLI une fois et utiliser la commande `wp-env` :
```bash
-# Global install (npm or yarn)
+# Installation globale (npm ou yarn)
npm install -g @wordpress/env
# ou
yarn global add @wordpress/env
@@ -198,21 +198,31 @@ yarn bundle-report
### Ce que le thème fournit
-- **Image LCP** : passez `'is_lcp' => true` dans les réglages de `get_the_image()` pour l’image principale above-the-fold (hero, couverture). Cela retire `loading="lazy"` et ajoute `fetchpriority="high"` et `decoding="sync"`. À utiliser sur une seule image par page.
+- **Image LCP** : passez `'is_lcp' => true` dans les réglages de `get_the_image()` pour l’image principale au-dessus de la ligne de flottaison (hero, couverture). Cela retire `loading="lazy"` et ajoute `fetchpriority="high"`. Définissez `decoding` dans `$attributes` uniquement si une mesure le justifie (ex. WebPageTest). Une seule image par page. Par défaut `is_lcp => false` (opt-in).
```php
the_image( $image_id, [ 'alt' => '' ], [ 'size' => 'hero', 'is_lcp' => true ] );
```
-- **Images décoratives** : passez `'alt' => ''` pour produire `alt=""`, afin que les technologies d’assistance ignorent l’image. Omettre `alt` n’est pas équivalent.
-- **Images statiques du thème** (logos, placeholders, illustrations depuis `dist/`) : utilisez `get_theme_static_image_markup()` / `the_theme_static_image()`. `width` et `height` sont lus depuis le fichier (SVG ou raster) pour limiter les décalages de mise en page (CLS).
+- **ARI (Auto Responsive Images)** : sur les projets avec ARI, ne pas activer `is_lcp` sur les images déjà gérées par ARI (taille / `data-location` au bon format). ARI pilote la priorité et le lazy-load ; `is_lcp` sur la même image est redondant ou incompatible. Réserver `is_lcp` aux projets sans ARI, ou valider manuellement (page hero avec ARI, sans `is_lcp`).
+
+- **Images décoratives (`alt=""`)** — même objectif, deux helpers selon la source :
+ - **Médiathèque** (`the_image` / `get_the_image`) : passez `'alt' => ''` explicitement si l’image est décorative. Sinon WordPress reprend l’alt saisi dans le BO. Omettre la clé `alt` n’est pas équivalent à `alt=""`.
+ - **Fichiers buildés** dans `dist/` (`the_theme_static_image` / `get_theme_static_image_markup`) : décoratif par défaut (`alt=""` si vous ne passez pas `alt`). Indiquez un texte d’alt si l’image porte du sens (ex. logo + nom du site). Ne pas utiliser `the_image` pour ces fichiers (pas d’ID attachment).
+- **Images statiques du thème** (placeholders, illustrations dans `dist/`) : le helper statique ajoute aussi `width` et `height` via `dist/assets.json` (`imageDimensions`, généré au build) pour limiter le CLS, sans lecture disque répétée lorsque le manifeste est présent. Hors manifeste : une lecture par chemin et par requête.
```php
+// Média (décoratif)
+the_image( $image_id, [ 'alt' => '', 'class' => 'card__thumb' ], [ 'size' => 'card' ] );
+
+// Asset dist/ (décoratif par défaut)
the_theme_static_image( 'dist/images/placeholder.svg', [ 'class' => 'card__image', 'loading' => 'lazy' ] );
+
+// Asset dist/ (alt porteur de sens)
+the_theme_static_image( 'dist/images/logo.svg', [ 'alt' => get_bloginfo( 'name' ), 'class' => 'header__logo' ] );
```
-- **Logo personnalisé** : reçoit `fetchpriority="low"` pour ne pas concurrencer la vraie image LCP.
-- **Preload des polices** : opt-in, uniquement pour les polices rendues au premier affichage.
+- **Preload des polices** : opt-in, uniquement pour les polices rendues au premier affichage. Indiquez le chemin **après build** (ex. polices Fontsource ou custom copiées dans `dist/fonts/`, en `.woff2`).
```php
add_filter( 'bea_theme_framework_preload_fonts', fn() => [ 'dist/fonts/my-font-regular.woff2' ] );
@@ -224,12 +234,12 @@ add_filter( 'bea_theme_framework_preload_fonts', fn() => [ 'dist/fonts/my-font-r
Cela dépend de l’hébergement, des extensions et du contenu ; ce n’est donc pas inclus dans le thème :
-- Cache de page et réglages WP Rocket (Remove Unused CSS, defer / delay JS et leurs exclusions, exclusions lazyload pour l’image LCP, preconnect limités aux tiers chargés tôt).
+- Cache de page et réglages WP Rocket (suppression du CSS inutilisé, différer / retarder le JS et leurs exclusions, exclusions du lazyload pour l’image LCP, preconnect limités aux tiers chargés tôt).
- `robots.txt` (endpoints techniques et AJAX) et en-têtes `Cache-Control` pour les assets statiques.
- Réglages du plugin SEO : URLs canoniques sur pagination personnalisée, doublons de données structurées.
- Polices : sous-ensemble des fichiers aux graisses et glyphes réellement utilisés, suppression des fichiers inutiles (italiques, graisses supplémentaires).
- Intégrations tierces (vidéos, formulaires, webinars) : chargement à l’interaction ou à l’entrée dans le viewport.
-- Assets des plugins : dequeue des scripts et styles front sur les pages qui ne les utilisent pas.
+- Assets des extensions : retirer les scripts et styles front (dequeue) sur les pages qui ne les utilisent pas.
## Éditeur WordPress (Gutenberg)
@@ -240,13 +250,13 @@ Le filtre `bff_editor_custom_settings` permet de personnaliser les styles et var
```php
add_filter( 'bff_editor_custom_settings', 'customize_editor_settings', 10, 1 );
function customize_editor_settings( $settings ) {
- // Disable all block styles for Separator block
+ // Désactiver tous les styles du bloc Séparateur
$settings[ 'disableAllBlocksStyles' ] = [ 'core/separator' ];
- // Disable specific block style for Button block
+ // Désactiver le style outline du bloc Bouton
$settings[ 'disabledBlocksStyles' ] = [ 'core/button' => [ 'outline' ] ];
- // Allow only YouTube variation for Embed block
+ // Autoriser uniquement la variation YouTube du bloc Intégration
$settings[ 'allowedBlocksVariations' ] = [ 'core/embed' => [ 'youtube' ] ];
return $settings;
diff --git a/config/plugins.js b/config/plugins.js
index 8b41c5d0..bbe1a74b 100644
--- a/config/plugins.js
+++ b/config/plugins.js
@@ -10,6 +10,7 @@ const DependencyExtractionWebpackPlugin = require('@wordpress/dependency-extract
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
const WebpackImageSizesPlugin = require('./webpack-image-sizes-plugin')
+const WebpackStaticImageDimensionsPlugin = require('./webpack-static-image-dimensions-plugin')
const WebpackThemeJsonPlugin = require('./webpack-theme-json-plugin')
const SpriteHashPlugin = require('./webpack-sprite-hash-plugin')
const WebpackIconFilesPlugin = require('./webpack-icon-files-plugin')
@@ -64,6 +65,7 @@ module.exports = {
new WebpackManifestPlugin({
fileName: 'assets.json',
}),
+ new WebpackStaticImageDimensionsPlugin(),
]
if (mode === 'production') {
diff --git a/config/webpack-static-image-dimensions-plugin.js b/config/webpack-static-image-dimensions-plugin.js
new file mode 100644
index 00000000..ed83ee88
--- /dev/null
+++ b/config/webpack-static-image-dimensions-plugin.js
@@ -0,0 +1,144 @@
+const fs = require('fs')
+const path = require('path')
+
+let sharp
+
+try {
+ sharp = require('sharp')
+} catch (error) {
+ sharp = null
+}
+
+const IMAGE_EXT_PATTERN = /\.(png|jpe?g|gif|svg|webp|avif)$/i
+
+/**
+ * Parse intrinsic SVG dimensions (width/height attributes, then viewBox).
+ *
+ * @param {string} contents SVG file contents.
+ * @return {{ width: number, height: number }|null}
+ */
+function getSvgDimensionsFromContents(contents) {
+ if (!contents) {
+ return null
+ }
+
+ const widthMatch = contents.match(/\bwidth="(\d+(?:\.\d+)?)"/)
+ const heightMatch = contents.match(/\bheight="(\d+(?:\.\d+)?)"/)
+
+ if (widthMatch && heightMatch) {
+ return {
+ width: Math.round(Number(widthMatch[1])),
+ height: Math.round(Number(heightMatch[1])),
+ }
+ }
+
+ const viewBoxMatch = contents.match(/\bviewBox="[\d.\s-]+?\s+([\d.]+)\s+([\d.]+)"/)
+
+ if (viewBoxMatch) {
+ return {
+ width: Math.max(1, Math.round(Number(viewBoxMatch[1]))),
+ height: Math.max(1, Math.round(Number(viewBoxMatch[2]))),
+ }
+ }
+
+ return null
+}
+
+/**
+ * Read width/height from a built image file under dist/.
+ *
+ * @param {string} filePath Absolute path to the image file.
+ * @return {Promise<{ width: number, height: number }|null>}
+ */
+async function getImageDimensions(filePath) {
+ if (!fs.existsSync(filePath)) {
+ return null
+ }
+
+ if (/\.svg$/i.test(filePath)) {
+ const contents = fs.readFileSync(filePath, 'utf8')
+ return getSvgDimensionsFromContents(contents)
+ }
+
+ if (!sharp) {
+ return null
+ }
+
+ try {
+ const metadata = await sharp(filePath).metadata()
+
+ if (!metadata.width || !metadata.height) {
+ return null
+ }
+
+ return {
+ width: metadata.width,
+ height: metadata.height,
+ }
+ } catch (error) {
+ return null
+ }
+}
+
+/**
+ * Webpack plugin: merge static image dimensions into dist/assets.json.
+ */
+class WebpackStaticImageDimensionsPlugin {
+ constructor(options = {}) {
+ this.options = {
+ outputPath: 'dist',
+ manifestFile: 'assets.json',
+ imagesSubdir: 'images',
+ ...options,
+ }
+ }
+
+ apply(compiler) {
+ compiler.hooks.afterEmit.tapAsync('WebpackStaticImageDimensionsPlugin', async (compilation, callback) => {
+ try {
+ const context = compiler.options.context
+ const outputPath = path.resolve(context, this.options.outputPath)
+ const manifestPath = path.join(outputPath, this.options.manifestFile)
+ const imageDimensions = {}
+
+ const assets = compilation.getAssets()
+
+ for (const asset of assets) {
+ if (!asset.name.startsWith(`${this.options.imagesSubdir}/`) || !IMAGE_EXT_PATTERN.test(asset.name)) {
+ continue
+ }
+
+ const filePath = path.join(outputPath, asset.name)
+ const dimensions = await getImageDimensions(filePath)
+
+ if (!dimensions) {
+ continue
+ }
+
+ const themeRelativePath = `dist/${asset.name.replace(/\\/g, '/')}`
+ imageDimensions[themeRelativePath] = dimensions
+ }
+
+ let manifest = {}
+
+ if (fs.existsSync(manifestPath)) {
+ const raw = fs.readFileSync(manifestPath, 'utf8')
+ const parsed = JSON.parse(raw)
+
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
+ manifest = parsed
+ }
+ }
+
+ manifest.imageDimensions = imageDimensions
+
+ fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`)
+ callback()
+ } catch (error) {
+ callback(error)
+ }
+ })
+ }
+}
+
+module.exports = WebpackStaticImageDimensionsPlugin
diff --git a/inc/Helpers/Formatting/Image.php b/inc/Helpers/Formatting/Image.php
index 55b49ac2..7c5903cf 100644
--- a/inc/Helpers/Formatting/Image.php
+++ b/inc/Helpers/Formatting/Image.php
@@ -1,6 +1,8 @@
'image-size' ] );
*
@@ -17,6 +19,8 @@
* @type string $alt Image description for the alt attribute.
* @type string $srcset The 'srcset' attribute value.
* @type string $sizes The 'sizes' attribute value.
+ * @type string $fetchpriority The 'fetchpriority' attribute value (e.g. 'high', 'low').
+ * @type string $decoding The 'decoding' attribute value (e.g. 'sync', 'async').
* @type string|false $loading The 'loading' attribute value. Passing a value of false
* will result in the attribute being omitted for the image.
* Defaults to 'lazy', depending on wp_lazy_loading_enabled().
@@ -29,6 +33,9 @@
* @type string $before Optional. Markup to prepend to the image. Default empty.
* @type string $after Optional. Markup to append to the image. Default empty.
* @type boolean $default Optional. If WP image does not exists, display a default image.
+ * @type boolean $is_lcp Optional. When true, applies LCP-friendly attributes
+ * (loading omitted, fetchpriority=high).
+ * decoding may be set explicitly in $attributes.
*
* }
*
@@ -56,11 +63,23 @@ function get_the_image( int $image_id, array $attributes, array $settings = [] )
'before' => '',
'after' => '',
'default' => false,
+ 'is_lcp' => false,
]
);
$settings = apply_filters( 'bea_theme_framework_the_image_settings', $settings, $image_id, $attributes );
+ if ( ! empty( $settings['is_lcp'] ) ) {
+ // Always win over caller/WP defaults: the LCP image must not be lazy-loaded.
+ $attributes['loading'] = false;
+ $attributes['fetchpriority'] = 'high';
+ }
+
+ // Never combine fetchpriority=high with loading=lazy (undefined browser behavior).
+ if ( isset( $attributes['fetchpriority'] ) && 'high' === $attributes['fetchpriority'] ) {
+ $attributes['loading'] = false;
+ }
+
if ( 0 === $image_id && ! $settings['default'] ) {
return '';
}
@@ -102,6 +121,8 @@ function get_the_image( int $image_id, array $attributes, array $settings = [] )
* @type string $alt Image description for the alt attribute.
* @type string $srcset The 'srcset' attribute value.
* @type string $sizes The 'sizes' attribute value.
+ * @type string $fetchpriority The 'fetchpriority' attribute value (e.g. 'high', 'low').
+ * @type string $decoding The 'decoding' attribute value (e.g. 'sync', 'async').
* @type string|false $loading The 'loading' attribute value. Passing a value of false
* will result in the attribute being omitted for the image.
* Defaults to 'lazy', depending on wp_lazy_loading_enabled().
@@ -113,6 +134,7 @@ function get_the_image( int $image_id, array $attributes, array $settings = [] )
* @type string $size Optional. The 'sizes' attribute value.
* @type string $before Optional. Markup to prepend to the image. Default empty.
* @type string $after Optional. Markup to append to the image. Default empty.
+ * @type boolean $is_lcp Optional. When true, applies LCP-friendly attributes.
*
* }
*
@@ -121,3 +143,190 @@ function get_the_image( int $image_id, array $attributes, array $settings = [] )
function the_image( int $image_id, array $attributes, array $settings = [] ): void {
echo get_the_image( $image_id, $attributes, $settings );
}
+
+/**
+ * Resolve the public URI for a static theme asset without redundant filesystem checks.
+ *
+ * @param string $relative_theme_path Path relative to the theme root.
+ *
+ * @return string
+ */
+function get_theme_static_image_uri( string $relative_theme_path ): string {
+ static $uri_cache = [];
+
+ $relative_theme_path = ltrim( $relative_theme_path, '/' );
+
+ if ( isset( $uri_cache[ $relative_theme_path ] ) ) {
+ return $uri_cache[ $relative_theme_path ];
+ }
+
+ if ( get_stylesheet() === get_template() ) {
+ $uri = get_stylesheet_directory_uri() . '/' . $relative_theme_path;
+ $uri = apply_filters( 'theme_file_uri', $uri, $relative_theme_path );
+ } else {
+ $uri = get_theme_file_uri( $relative_theme_path );
+ }
+
+ $uri_cache[ $relative_theme_path ] = $uri;
+
+ return $uri;
+}
+
+/**
+ * Read intrinsic dimensions from a static file under the active theme directory.
+ *
+ * Uses build-time dimensions from `dist/assets.json` when available; otherwise reads the file once per request.
+ *
+ * @usage BEA\Theme\Framework\Helpers\Formatting\Image\get_theme_static_image_dimensions( 'dist/images/logo.svg' );
+ *
+ * @param string $relative_theme_path Path relative to the theme root (e.g. dist/images/logo.svg).
+ *
+ * @return array{width:int,height:int}|null Null when the file is missing or dimensions cannot be read.
+ */
+function get_theme_static_image_dimensions( string $relative_theme_path ): ?array {
+ static $cache = [];
+
+ $relative_theme_path = ltrim( $relative_theme_path, '/' );
+
+ if ( array_key_exists( $relative_theme_path, $cache ) ) {
+ return $cache[ $relative_theme_path ];
+ }
+
+ $assets = Framework::get_container()->get_service( 'assets' );
+ $from_manifest = $assets->get_static_image_dimensions( $relative_theme_path );
+
+ if ( null !== $from_manifest ) {
+ $cache[ $relative_theme_path ] = $from_manifest;
+
+ return $from_manifest;
+ }
+
+ $from_filesystem = read_theme_static_image_dimensions_from_filesystem( $relative_theme_path );
+ $cache[ $relative_theme_path ] = $from_filesystem;
+
+ return $from_filesystem;
+}
+
+/**
+ * Fallback: read intrinsic dimensions from the theme filesystem.
+ *
+ * @param string $relative_theme_path Path relative to the theme root.
+ *
+ * @return array{width:int,height:int}|null
+ */
+function read_theme_static_image_dimensions_from_filesystem( string $relative_theme_path ): ?array {
+ $file_path = get_theme_file_path( $relative_theme_path );
+
+ if ( ! is_string( $file_path ) || '' === $file_path || ! is_readable( $file_path ) ) {
+ return null;
+ }
+
+ if ( str_ends_with( strtolower( $file_path ), '.svg' ) ) {
+ $contents = file_get_contents( $file_path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Local theme SVG asset.
+
+ if ( ! is_string( $contents ) || '' === $contents ) {
+ return null;
+ }
+
+ if ( preg_match( '/\bwidth="(\d+(?:\.\d+)?)"/', $contents, $width_match )
+ && preg_match( '/\bheight="(\d+(?:\.\d+)?)"/', $contents, $height_match ) ) {
+ return [
+ 'width' => (int) round( (float) $width_match[1] ),
+ 'height' => (int) round( (float) $height_match[1] ),
+ ];
+ }
+
+ if ( preg_match( '/\bviewBox="[\d.\s-]+?\s+([\d.]+)\s+([\d.]+)"/', $contents, $view_box_match ) ) {
+ return [
+ 'width' => max( 1, (int) round( (float) $view_box_match[1] ) ),
+ 'height' => max( 1, (int) round( (float) $view_box_match[2] ) ),
+ ];
+ }
+
+ return null;
+ }
+
+ $size = wp_getimagesize( $file_path );
+ if ( ! is_array( $size ) || empty( $size[0] ) || empty( $size[1] ) ) {
+ return null;
+ }
+
+ return [
+ 'width' => (int) $size[0],
+ 'height' => (int) $size[1],
+ ];
+}
+
+/**
+ * Build an img tag for a static theme asset, with width and height when known (prevents CLS).
+ *
+ * @usage BEA\Theme\Framework\Helpers\Formatting\Image\get_theme_static_image_markup( 'dist/images/logo.svg', [ 'alt' => 'Logo' ] );
+ *
+ * @param string $relative_theme_path Path relative to the theme root.
+ * @param array $attributes Img attributes. src and alt are set when omitted.
+ * An empty alt is output as alt="" (decorative image);
+ * false or null omits the attribute.
+ *
+ * @return string Return the markup of the image
+ */
+function get_theme_static_image_markup( string $relative_theme_path, array $attributes = [] ): string {
+ $relative_theme_path = ltrim( $relative_theme_path, '/' );
+
+ $attributes = wp_parse_args(
+ $attributes,
+ [
+ 'src' => get_theme_static_image_uri( $relative_theme_path ),
+ 'alt' => '',
+ ]
+ );
+
+ $dimensions = get_theme_static_image_dimensions( $relative_theme_path );
+ if ( null !== $dimensions ) {
+ if ( ! isset( $attributes['width'] ) ) {
+ $attributes['width'] = (string) $dimensions['width'];
+ }
+ if ( ! isset( $attributes['height'] ) ) {
+ $attributes['height'] = (string) $dimensions['height'];
+ }
+ }
+
+ if ( isset( $attributes['fetchpriority'] ) && 'high' === $attributes['fetchpriority'] ) {
+ $attributes['loading'] = false;
+ }
+
+ $processor = new \WP_HTML_Tag_Processor( '
' );
+ if ( ! $processor->next_tag( 'img' ) ) {
+ return '';
+ }
+
+ foreach ( $attributes as $name => $value ) {
+ $name = (string) $name;
+
+ if ( false === $value || null === $value ) {
+ $processor->remove_attribute( $name );
+ continue;
+ }
+
+ // Empty alt marks a decorative image; omitting the attribute is not equivalent for AT.
+ if ( '' === $value && 'alt' !== $name ) {
+ $processor->remove_attribute( $name );
+ continue;
+ }
+
+ $processor->set_attribute( $name, (string) $value );
+ }
+
+ return $processor->get_updated_html();
+}
+
+/**
+ * @usage BEA\Theme\Framework\Helpers\Formatting\Image\the_theme_static_image( 'dist/images/logo.svg', [ 'alt' => 'Logo' ] );
+ *
+ * @param string $relative_theme_path Path relative to the theme root.
+ * @param array $attributes Img attributes, see get_theme_static_image_markup().
+ *
+ * @return void Echo of the image markup
+ */
+function the_theme_static_image( string $relative_theme_path, array $attributes = [] ): void {
+ echo get_theme_static_image_markup( $relative_theme_path, $attributes ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Attributes are escaped by WP_HTML_Tag_Processor.
+}
diff --git a/inc/Services/Assets.php b/inc/Services/Assets.php
index 474af5bb..24c54417 100644
--- a/inc/Services/Assets.php
+++ b/inc/Services/Assets.php
@@ -140,19 +140,19 @@ public function get_min_file( string $type ): string {
switch ( $type ) {
case 'css':
- $file = $assets['app.css'];
+ $file = $assets['app.css'] ?? null;
break;
case 'editor.css':
- $file = $assets['editor.css'];
+ $file = $assets['editor.css'] ?? null;
break;
case 'login':
- $file = $assets['login.css'];
+ $file = $assets['login.css'] ?? null;
break;
case 'editor.js':
- $file = $assets['editor.js'];
+ $file = $assets['editor.js'] ?? null;
break;
case 'js':
- $file = $assets['app.js'];
+ $file = $assets['app.js'] ?? null;
break;
default:
$file = null;
@@ -160,17 +160,51 @@ public function get_min_file( string $type ): string {
}
// Custom type
- if ( ! empty( $assets[ $type ] ) ) {
+ if ( null === $file && ! empty( $assets[ $type ] ) && is_string( $assets[ $type ] ) ) {
$file = $assets[ $type ];
}
- if ( empty( $file ) ) {
+ if ( empty( $file ) || ! is_string( $file ) ) {
return '';
}
return $file;
}
+ /**
+ * Return intrinsic dimensions for a static theme image from the build manifest.
+ *
+ * @param string $relative_theme_path Path relative to the theme root (e.g. dist/images/logo.svg).
+ *
+ * @return array{width:int,height:int}|null Null when dimensions are not in the manifest.
+ */
+ public function get_static_image_dimensions( string $relative_theme_path ): ?array {
+ $relative_theme_path = ltrim( $relative_theme_path, '/' );
+ $manifest = $this->get_assets_manifest();
+
+ if ( empty( $manifest['imageDimensions'] ) || ! is_array( $manifest['imageDimensions'] ) ) {
+ return null;
+ }
+
+ $dimensions = $manifest['imageDimensions'][ $relative_theme_path ] ?? null;
+
+ if ( ! is_array( $dimensions ) ) {
+ return null;
+ }
+
+ $width = isset( $dimensions['width'] ) ? (int) $dimensions['width'] : 0;
+ $height = isset( $dimensions['height'] ) ? (int) $dimensions['height'] : 0;
+
+ if ( $width <= 0 || $height <= 0 ) {
+ return null;
+ }
+
+ return [
+ 'width' => $width,
+ 'height' => $height,
+ ];
+ }
+
/**
* Read and decode the `dist/assets.json` manifest once per request.
*
@@ -178,7 +212,7 @@ public function get_min_file( string $type ): string {
* `stylesheet_uri` filter, editor assets…): memoize the decoded content to
* avoid repeated filesystem reads and JSON decoding.
*
- * @return array Manifest entries, or an empty array if unavailable/invalid.
+ * @return array Manifest entries, or an empty array if unavailable/invalid.
*/
private function get_assets_manifest(): array {
static $manifest = null;
diff --git a/inc/Services/Performance.php b/inc/Services/Performance.php
index c0d24ea1..8e6ec43d 100644
--- a/inc/Services/Performance.php
+++ b/inc/Services/Performance.php
@@ -25,6 +25,7 @@ public function boot( Service_Container $container ): void {
* Add hooks for the scripts and styles to hook on
*/
add_action( 'wp', [ $this, 'register_assets' ] );
+ add_action( 'wp_head', [ $this, 'preload_fonts' ], 1 );
}
/**
@@ -45,6 +46,49 @@ public function register_assets(): void {
wp_add_inline_script( 'jquery', $this->passive_touchstart() );
}
+ /**
+ * Preload the woff2 fonts used by the above-the-fold content.
+ *
+ * Opt-in: the list is empty by default. Only preload fonts that are actually
+ * rendered on first paint, otherwise they compete with the LCP resource.
+ *
+ * @usage add_filter( 'bea_theme_framework_preload_fonts', fn() => [ 'dist/fonts/my-font.woff2' ] );
+ *
+ * @return void
+ */
+ public function preload_fonts(): void {
+ if ( is_admin() ) {
+ return;
+ }
+
+ /**
+ * Filter the font files to preload, relative to the theme root.
+ *
+ * @param string[] $fonts Font paths relative to the theme root. Default empty.
+ */
+ $fonts = apply_filters( 'bea_theme_framework_preload_fonts', [] );
+ if ( ! is_array( $fonts ) ) {
+ return;
+ }
+
+ foreach ( $fonts as $relative_path ) {
+ if ( ! is_string( $relative_path ) || '' === $relative_path ) {
+ continue;
+ }
+
+ $relative_path = ltrim( $relative_path, '/' );
+ $path = get_theme_file_path( $relative_path );
+ if ( ! is_readable( $path ) ) {
+ continue;
+ }
+
+ printf(
+ '' . "\n",
+ esc_url( get_theme_file_uri( $relative_path ) )
+ );
+ }
+ }
+
/**
* Add passsive touchstart for performance
* @return string
diff --git a/package-lock.json b/package-lock.json
index dcecc85b..38bf9428 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -72,14 +72,42 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
+ "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@babel/helper-validator-identifier": {
- "version": "7.25.9",
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
+ "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
+ "node_modules/@babel/parser": {
+ "version": "7.29.9",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz",
+ "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.8"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
"node_modules/@babel/runtime": {
"version": "7.27.0",
"dev": true,
@@ -91,6 +119,20 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@babel/types": {
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz",
+ "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@cacheable/memory": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.2.0.tgz",
@@ -403,9 +445,9 @@
}
},
"node_modules/@csstools/postcss-cascade-layers/node_modules/postcss-selector-parser": {
- "version": "7.1.5",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz",
- "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==",
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz",
+ "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -840,9 +882,9 @@
}
},
"node_modules/@csstools/postcss-is-pseudo-class/node_modules/postcss-selector-parser": {
- "version": "7.1.5",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz",
- "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==",
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz",
+ "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1305,9 +1347,9 @@
}
},
"node_modules/@csstools/postcss-scope-pseudo-class/node_modules/postcss-selector-parser": {
- "version": "7.1.5",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz",
- "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==",
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz",
+ "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2050,7 +2092,9 @@
}
},
"node_modules/@eslint/eslintrc/node_modules/ajv": {
- "version": "6.12.6",
+ "version": "6.15.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz",
+ "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2699,6 +2743,17 @@
"node": ">=6.0.0"
}
},
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"dev": true,
@@ -2725,12 +2780,16 @@
}
},
"node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.5.0",
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz",
+ "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==",
"dev": true,
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.25",
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2778,16 +2837,18 @@
}
},
"node_modules/@parcel/watcher": {
- "version": "2.5.1",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.6.0.tgz",
+ "integrity": "sha512-7FNeNl8NCE7aINx7WXiKQrPYZWC/hvrTsmk6zmxbI7LTXE7hVek/n8AfVgpe2y82zl3w0HvCHN0bVKMBoJcC0w==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"dependencies": {
- "detect-libc": "^1.0.3",
+ "detect-libc": "^2.0.3",
"is-glob": "^4.0.3",
- "micromatch": "^4.0.5",
- "node-addon-api": "^7.0.0"
+ "node-addon-api": "^7.0.0",
+ "picomatch": "^4.0.4"
},
"engines": {
"node": ">= 10.0.0"
@@ -2797,25 +2858,24 @@
"url": "https://opencollective.com/parcel"
},
"optionalDependencies": {
- "@parcel/watcher-android-arm64": "2.5.1",
- "@parcel/watcher-darwin-arm64": "2.5.1",
- "@parcel/watcher-darwin-x64": "2.5.1",
- "@parcel/watcher-freebsd-x64": "2.5.1",
- "@parcel/watcher-linux-arm-glibc": "2.5.1",
- "@parcel/watcher-linux-arm-musl": "2.5.1",
- "@parcel/watcher-linux-arm64-glibc": "2.5.1",
- "@parcel/watcher-linux-arm64-musl": "2.5.1",
- "@parcel/watcher-linux-x64-glibc": "2.5.1",
- "@parcel/watcher-linux-x64-musl": "2.5.1",
- "@parcel/watcher-win32-arm64": "2.5.1",
- "@parcel/watcher-win32-ia32": "2.5.1",
- "@parcel/watcher-win32-x64": "2.5.1"
+ "@parcel/watcher-android-arm64": "2.6.0",
+ "@parcel/watcher-darwin-arm64": "2.6.0",
+ "@parcel/watcher-darwin-x64": "2.6.0",
+ "@parcel/watcher-freebsd-x64": "2.6.0",
+ "@parcel/watcher-linux-arm-glibc": "2.6.0",
+ "@parcel/watcher-linux-arm-musl": "2.6.0",
+ "@parcel/watcher-linux-arm64-glibc": "2.6.0",
+ "@parcel/watcher-linux-arm64-musl": "2.6.0",
+ "@parcel/watcher-linux-x64-glibc": "2.6.0",
+ "@parcel/watcher-linux-x64-musl": "2.6.0",
+ "@parcel/watcher-win32-arm64": "2.6.0",
+ "@parcel/watcher-win32-x64": "2.6.0"
}
},
"node_modules/@parcel/watcher-android-arm64": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz",
- "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.6.0.tgz",
+ "integrity": "sha512-trgpLSCKRC/huFjXX/Smh+0sWe4+YtKfktIToiMl59ghz7z+qkH6kMvNnUbLyRs9N11t8l4svSCs1+5B3rOAhA==",
"cpu": [
"arm64"
],
@@ -2834,7 +2894,9 @@
}
},
"node_modules/@parcel/watcher-darwin-arm64": {
- "version": "2.5.1",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.6.0.tgz",
+ "integrity": "sha512-Y3QV0gl7Q1zbfueunkWIERICbEojQFCgpyG7YqOGNFLsckXyI1xu9mAIUpKY9QBYzBtSkN8dBPwd3yiAO9ovMw==",
"cpu": [
"arm64"
],
@@ -2853,9 +2915,9 @@
}
},
"node_modules/@parcel/watcher-darwin-x64": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz",
- "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.6.0.tgz",
+ "integrity": "sha512-Ohv6OpzhUfKYD7Beb8kDvG0jbIxORCYY1JRdZnaBtnjjkJxgD7ZVL0nw2sCYd0yTMKTvz3nnTnOF3cDifK+kvw==",
"cpu": [
"x64"
],
@@ -2874,9 +2936,9 @@
}
},
"node_modules/@parcel/watcher-freebsd-x64": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz",
- "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.6.0.tgz",
+ "integrity": "sha512-5HmXvDgs8VK+74jF9y9/2FE3/OnlcKmc56tjmSrEuZjpSZOGL+fvAu+HKJBdPs9uwoP2hE6TlSUpXZ/C5jUFmQ==",
"cpu": [
"x64"
],
@@ -2895,13 +2957,16 @@
}
},
"node_modules/@parcel/watcher-linux-arm-glibc": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz",
- "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.6.0.tgz",
+ "integrity": "sha512-Ps/hui3A+vMbjdqlqAowK2ZL8+BO8dBjxeWXj6npTBs3jx4wWmbPpaLuqwrQrSqIVMCnpWo238bJ1U37GhQOYg==",
"cpu": [
"arm"
],
"dev": true,
+ "libc": [
+ "glibc"
+ ],
"license": "MIT",
"optional": true,
"os": [
@@ -2916,13 +2981,16 @@
}
},
"node_modules/@parcel/watcher-linux-arm-musl": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz",
- "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.6.0.tgz",
+ "integrity": "sha512-9c6AUHgHoG+IY88MRIHupztQiQnrbqHYQjkM2btA+Bf/wQnQMuiD0Wfk1EVv3TlNT3x41uU71rn6E4xh/+zvkw==",
"cpu": [
"arm"
],
"dev": true,
+ "libc": [
+ "musl"
+ ],
"license": "MIT",
"optional": true,
"os": [
@@ -2937,13 +3005,16 @@
}
},
"node_modules/@parcel/watcher-linux-arm64-glibc": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz",
- "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.6.0.tgz",
+ "integrity": "sha512-yHRqS2owEXe6Hic9z6Mh1ECsCd+ODVOGvZDyciqRd21+v+o+DnXMOrw50DSpIG2sb8GPEaPPmfeCAWKPJdq46g==",
"cpu": [
"arm64"
],
"dev": true,
+ "libc": [
+ "glibc"
+ ],
"license": "MIT",
"optional": true,
"os": [
@@ -2958,13 +3029,16 @@
}
},
"node_modules/@parcel/watcher-linux-arm64-musl": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz",
- "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.6.0.tgz",
+ "integrity": "sha512-WhB2e/V7rqdHHWZusBSPuy5Ei8S6lSz6FE5TKKQz5h3a0O+C+mhY7vxU9b/stqvMb8beLnPY82ZrFTLKs+SrKA==",
"cpu": [
"arm64"
],
"dev": true,
+ "libc": [
+ "musl"
+ ],
"license": "MIT",
"optional": true,
"os": [
@@ -2979,13 +3053,16 @@
}
},
"node_modules/@parcel/watcher-linux-x64-glibc": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz",
- "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.6.0.tgz",
+ "integrity": "sha512-ulGE6x6Oz6iAwg75T8YQSoguBWasniIbX+QWpaYPcCnDOpdWX3k+4xbEYPZVLxOuoJI+svJJPD3sEj8G7lrQ3A==",
"cpu": [
"x64"
],
"dev": true,
+ "libc": [
+ "glibc"
+ ],
"license": "MIT",
"optional": true,
"os": [
@@ -3000,13 +3077,16 @@
}
},
"node_modules/@parcel/watcher-linux-x64-musl": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz",
- "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.6.0.tgz",
+ "integrity": "sha512-tkBYKt7YQrjIJWYDnto2YgO8MRkjlMTSNoRHzsXinBqbLdeOM3L32wPZJvIZxqaLMfSlS/4sUjH/6STVP/XDLw==",
"cpu": [
"x64"
],
"dev": true,
+ "libc": [
+ "musl"
+ ],
"license": "MIT",
"optional": true,
"os": [
@@ -3021,9 +3101,9 @@
}
},
"node_modules/@parcel/watcher-win32-arm64": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz",
- "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.6.0.tgz",
+ "integrity": "sha512-gIZAP23jaHjGWasY/TY6yL7NHFClf0Ga7FN+iINvk+KN94rhm94lYZhFsbYFNcA04/onvGD9kKmiJLJB2HbNwQ==",
"cpu": [
"arm64"
],
@@ -3041,12 +3121,12 @@
"url": "https://opencollective.com/parcel"
}
},
- "node_modules/@parcel/watcher-win32-ia32": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz",
- "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==",
+ "node_modules/@parcel/watcher-win32-x64": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.6.0.tgz",
+ "integrity": "sha512-cA+/pXV2YkfxlIcXOQ5fSWqAzzPyD78/x5qbK/I0vUkrlYHA8TIz+MXjAbGouguKVSI4bOmkTSJ1/poVSsgt+A==",
"cpu": [
- "ia32"
+ "x64"
],
"dev": true,
"license": "MIT",
@@ -3062,25 +3142,18 @@
"url": "https://opencollective.com/parcel"
}
},
- "node_modules/@parcel/watcher-win32-x64": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz",
- "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==",
- "cpu": [
- "x64"
- ],
+ "node_modules/@parcel/watcher/node_modules/picomatch": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
+ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
"dev": true,
"license": "MIT",
"optional": true,
- "os": [
- "win32"
- ],
"engines": {
- "node": ">= 10.0.0"
+ "node": ">=12"
},
"funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
+ "url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/@polka/url": {
@@ -3172,20 +3245,12 @@
"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"
},
- "node_modules/@trysound/sax": {
- "version": "0.2.0",
- "dev": true,
- "license": "ISC",
- "engines": {
- "node": ">=10.13.0"
- }
- },
"node_modules/@types/eslint": {
"version": "8.56.12",
"dev": true,
@@ -3518,14 +3583,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/@wordpress/blocks/node_modules/uuid": {
- "version": "8.3.2",
- "dev": true,
- "license": "MIT",
- "bin": {
- "uuid": "dist/bin/uuid"
- }
- },
"node_modules/@wordpress/browserslist-config": {
"version": "6.53.0",
"resolved": "https://registry.npmjs.org/@wordpress/browserslist-config/-/browserslist-config-6.53.0.tgz",
@@ -3747,14 +3804,13 @@
}
},
"node_modules/@wordpress/private-apis": {
- "version": "1.53.0",
- "resolved": "https://registry.npmjs.org/@wordpress/private-apis/-/private-apis-1.53.0.tgz",
- "integrity": "sha512-xXW2yeBC0n/doFLsv6chEl3Qs5phqmRH7wcUyil+Xvh3msVGzUJxvyo0szmKpQcmoYafASpkQux82HCtNp6jxQ==",
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@wordpress/private-apis/-/private-apis-2.0.0.tgz",
+ "integrity": "sha512-VmGwebQZoebF5t1xFMXbj6z/dTtAca4d8YaO3IPN2aP8IT+eOrUBvoAthC5BmLPU4JLN5m3r8vbrid3mcli6vw==",
"dev": true,
"license": "GPL-2.0-or-later",
"engines": {
- "node": ">=18.12.0",
- "npm": ">=8.19.2"
+ "node": ">=18.12.0"
}
},
"node_modules/@wordpress/redux-routine": {
@@ -3787,25 +3843,24 @@
}
},
"node_modules/@wordpress/style-runtime": {
- "version": "0.9.0",
- "resolved": "https://registry.npmjs.org/@wordpress/style-runtime/-/style-runtime-0.9.0.tgz",
- "integrity": "sha512-K2T4OfVbqgsp07zi7C6UemlJK7fF78EPmxFYuxSjnbbB14fJcOxX+Tqi/4y24hn9ECahoGjCKHZCY2U2kts8dQ==",
+ "version": "0.13.0",
+ "resolved": "https://registry.npmjs.org/@wordpress/style-runtime/-/style-runtime-0.13.0.tgz",
+ "integrity": "sha512-P8/7C0NLt+0r6Ks7csJPRUvTgdPccI51AMs05h8/LEJdVtW2k1nSdAgSpgC/lFAsZCPlCeicxjnelJChNRhXZQ==",
"dev": true,
"license": "GPL-2.0-or-later",
"engines": {
- "node": ">=20.10.0",
- "npm": ">=10.2.3"
+ "node": ">=20.10.0"
}
},
"node_modules/@wordpress/stylelint-config": {
- "version": "24.2.0",
- "resolved": "https://registry.npmjs.org/@wordpress/stylelint-config/-/stylelint-config-24.2.0.tgz",
- "integrity": "sha512-hRAcYVoGAAeh8abfkF5T7d6O2T7jQgf5jRP6HEWmQOqpOFkhenZaOFC6jdoX+t/PxXFUYFV3mzgjWqNa+b1jXw==",
+ "version": "24.3.0",
+ "resolved": "https://registry.npmjs.org/@wordpress/stylelint-config/-/stylelint-config-24.3.0.tgz",
+ "integrity": "sha512-DfuhPR402icUZd/6MyWRm6jwr7tY5Fbtt+sswFVql8Z+95sNe0kdS4vJyAwhRGWNnZkJ1SuQk6ye6nHFu+cm/g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@stylistic/stylelint-plugin": "^3.1.3",
- "@wordpress/theme": "^1.2.0",
+ "@wordpress/theme": "^2.0.0",
"stylelint-config-recommended": "^14.0.1",
"stylelint-config-recommended-scss": "^14.1.0"
},
@@ -3819,82 +3874,81 @@
}
},
"node_modules/@wordpress/stylelint-config/node_modules/@wordpress/compose": {
- "version": "8.6.0",
- "resolved": "https://registry.npmjs.org/@wordpress/compose/-/compose-8.6.0.tgz",
- "integrity": "sha512-aurjKO5vgnKl3p4iRM/maMEB3z36e0FdAuJOvPTHKMUjd0OUk8n2M/n2mfRS68QxVHv8mmDm1IEg8zKobwIDmA==",
+ "version": "8.10.0",
+ "resolved": "https://registry.npmjs.org/@wordpress/compose/-/compose-8.10.0.tgz",
+ "integrity": "sha512-ilezxcwhnlCLBZHYjgRgVf4+QxIKb373WLrab47EVFck9GwdLlq1rBWcWCnXME6r1MJ1r8q30RTGSbA5shub6Q==",
"dev": true,
"license": "GPL-2.0-or-later",
"dependencies": {
"@types/mousetrap": "^1.6.8",
- "@wordpress/deprecated": "^4.53.0",
- "@wordpress/dom": "^4.53.0",
- "@wordpress/element": "^8.5.0",
- "@wordpress/is-shallow-equal": "^5.53.0",
- "@wordpress/keycodes": "^4.53.0",
- "@wordpress/priority-queue": "^3.53.0",
- "@wordpress/private-apis": "^1.53.0",
- "@wordpress/undo-manager": "^1.53.0",
+ "@wordpress/deprecated": "^4.57.0",
+ "@wordpress/dom": "^4.57.0",
+ "@wordpress/element": "^9.0.0",
+ "@wordpress/is-shallow-equal": "^5.57.0",
+ "@wordpress/keycodes": "^4.57.0",
+ "@wordpress/priority-queue": "^3.57.0",
+ "@wordpress/private-apis": "^2.0.0",
+ "@wordpress/undo-manager": "^1.57.0",
"change-case": "^4.1.2",
- "mousetrap": "^1.6.5",
- "use-memo-one": "^1.1.1"
+ "mousetrap": "^1.6.5"
},
"engines": {
- "node": ">=18.12.0",
- "npm": ">=8.19.2"
+ "node": ">=18.12.0"
},
"peerDependencies": {
"@types/react": "^18 || ^19",
- "react": "^18 || ^19"
+ "@types/react-dom": "^18 || ^19",
+ "react": "^18 || ^19",
+ "react-dom": "^18 || ^19"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
}
}
},
"node_modules/@wordpress/stylelint-config/node_modules/@wordpress/deprecated": {
- "version": "4.53.0",
- "resolved": "https://registry.npmjs.org/@wordpress/deprecated/-/deprecated-4.53.0.tgz",
- "integrity": "sha512-JaIWiGVoKEmsiWP4XwAZvrET44T34EtPG+xCJe9vPIs9O3DHPyB83VHV4WxVRxddaf1osTmR6iPLx0sBvVTjZw==",
+ "version": "4.57.0",
+ "resolved": "https://registry.npmjs.org/@wordpress/deprecated/-/deprecated-4.57.0.tgz",
+ "integrity": "sha512-eRjYiN5wE8cmV+sehqGTHpb2VxwSqv/FMkcQ+0po4G3JK+Y1V0StqZRDhsgcwUocPRKuAtTO7E28fcdo615DFw==",
"dev": true,
"license": "GPL-2.0-or-later",
"dependencies": {
- "@wordpress/hooks": "^4.53.0"
+ "@wordpress/hooks": "^4.57.0"
},
"engines": {
- "node": ">=18.12.0",
- "npm": ">=8.19.2"
+ "node": ">=18.12.0"
}
},
"node_modules/@wordpress/stylelint-config/node_modules/@wordpress/dom": {
- "version": "4.53.0",
- "resolved": "https://registry.npmjs.org/@wordpress/dom/-/dom-4.53.0.tgz",
- "integrity": "sha512-cf+WkRxuXtPLmAWXYShsbitUIkPOjIgiScUJQztA4OSqy9vfSPvrOgb6pUqijPiIo9+fhBSoyryOFCOg7xABOQ==",
+ "version": "4.57.0",
+ "resolved": "https://registry.npmjs.org/@wordpress/dom/-/dom-4.57.0.tgz",
+ "integrity": "sha512-KjnGUcxtdh8+Gvi0zUhh4iDRE8fsvVJkd798aS65HL6ysZYLuOVsdHwy5Qh6LT//tB3juSn1EMJ/TzdgtIOg6A==",
"dev": true,
"license": "GPL-2.0-or-later",
"dependencies": {
- "@wordpress/deprecated": "^4.53.0"
+ "@wordpress/deprecated": "^4.57.0"
},
"engines": {
- "node": ">=18.12.0",
- "npm": ">=8.19.2"
+ "node": ">=18.12.0"
}
},
"node_modules/@wordpress/stylelint-config/node_modules/@wordpress/element": {
- "version": "8.5.0",
- "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-8.5.0.tgz",
- "integrity": "sha512-d6Ct+JETXRoGjfAxu5kSPjportHloZ3auAPLguQOLEoNBiZJp60fslYYd6LeVw7M7dUR3lArwcYVK0x0G06c9Q==",
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-9.0.0.tgz",
+ "integrity": "sha512-o4m80wTlAkqbdJ7F7vkmFzWx6B46fJ3eyG9dWu5BJ1adjL1tA1wv0xStTkFzKuU1iPHCRN0dhauS9G9rfdF3jw==",
"dev": true,
"license": "GPL-2.0-or-later",
"dependencies": {
- "@wordpress/deprecated": "^4.53.0",
- "@wordpress/escape-html": "^3.53.0",
+ "@wordpress/escape-html": "^3.57.0",
"change-case": "^4.1.2",
- "is-plain-object": "^5.0.0"
+ "is-plain-object": "^5.1.0"
},
"engines": {
- "node": ">=18.12.0",
- "npm": ">=8.19.2"
+ "node": ">=18.12.0"
},
"peerDependencies": {
"@types/react": "^18 || ^19",
@@ -3912,36 +3966,34 @@
}
},
"node_modules/@wordpress/stylelint-config/node_modules/@wordpress/escape-html": {
- "version": "3.53.0",
- "resolved": "https://registry.npmjs.org/@wordpress/escape-html/-/escape-html-3.53.0.tgz",
- "integrity": "sha512-6EE4hOsd0kUp4bO9voTsIeK0oa80FYMbxvJkjN9Vitnf0ZTRVB7Iz2IviaYorhGUDemlRy3kOPEZdP9DA/CoCA==",
+ "version": "3.57.0",
+ "resolved": "https://registry.npmjs.org/@wordpress/escape-html/-/escape-html-3.57.0.tgz",
+ "integrity": "sha512-QT0x2X39blQjLHru11Dp00DAR0LLRMKMC6ATSWidliD3xQfKz+BjiPzTJd/LyhPLEE8C9li7ylKUlseIO2EYjQ==",
"dev": true,
"license": "GPL-2.0-or-later",
"engines": {
- "node": ">=18.12.0",
- "npm": ">=8.19.2"
+ "node": ">=18.12.0"
}
},
"node_modules/@wordpress/stylelint-config/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/stylelint-config/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": "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.53.0",
+ "@wordpress/hooks": "^4.57.0",
"gettext-parser": "^1.3.1",
"tannin": "^1.2.0"
},
@@ -3949,33 +4001,30 @@
"pot-to-php": "tools/pot-to-php.js"
},
"engines": {
- "node": ">=18.12.0",
- "npm": ">=8.19.2"
+ "node": ">=18.12.0"
}
},
"node_modules/@wordpress/stylelint-config/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",
- "integrity": "sha512-NHhvEnqi82Vzon458Jz6nzmbAPWM1zy9k6rBXQTc+9PsRiWHWpqXqldKAeTFVrl0vspZf4PwmLlsxqP3ckCLWw==",
+ "version": "5.57.0",
+ "resolved": "https://registry.npmjs.org/@wordpress/is-shallow-equal/-/is-shallow-equal-5.57.0.tgz",
+ "integrity": "sha512-QoIVWgnkKHAgrFB9hfpnmGvrAbutsa53DNl08i98guhtVVTSLzttlxgJxA3wpIepmR8FYiLNGB2Kfa49NdyIwg==",
"dev": true,
"license": "GPL-2.0-or-later",
"engines": {
- "node": ">=18.12.0",
- "npm": ">=8.19.2"
+ "node": ">=18.12.0"
}
},
"node_modules/@wordpress/stylelint-config/node_modules/@wordpress/keycodes": {
- "version": "4.53.0",
- "resolved": "https://registry.npmjs.org/@wordpress/keycodes/-/keycodes-4.53.0.tgz",
- "integrity": "sha512-e2o0NjkyGpPUGz8sfRd1OeEHdeOk3UVlL1J8P4Y9/fgQQJumTjulbiwps6yheE5kRNqbrh2o424QLdMaODu9Kw==",
+ "version": "4.57.0",
+ "resolved": "https://registry.npmjs.org/@wordpress/keycodes/-/keycodes-4.57.0.tgz",
+ "integrity": "sha512-IhPt1cAy00wxGECraOlWJMT+PwgLdh1CqS57RDsdhXMCs//AbIKFBMNxvEjPyq9aeU3looVJzcBR1zVhWkDErQ==",
"dev": true,
"license": "GPL-2.0-or-later",
"dependencies": {
- "@wordpress/i18n": "^6.26.0"
+ "@wordpress/i18n": "^6.30.0"
},
"engines": {
- "node": ">=18.12.0",
- "npm": ">=8.19.2"
+ "node": ">=18.12.0"
},
"peerDependencies": {
"@types/react": "^18 || ^19"
@@ -3987,41 +4036,45 @@
}
},
"node_modules/@wordpress/stylelint-config/node_modules/@wordpress/priority-queue": {
- "version": "3.53.0",
- "resolved": "https://registry.npmjs.org/@wordpress/priority-queue/-/priority-queue-3.53.0.tgz",
- "integrity": "sha512-HQ5AatN1QWimFHQnhhhzrfgNoiVWTqwhUEv5326hhTnyUxvh5SOYxg46IBGow+zj/VJjIPyaxv1ftuqzVxgRMQ==",
+ "version": "3.57.0",
+ "resolved": "https://registry.npmjs.org/@wordpress/priority-queue/-/priority-queue-3.57.0.tgz",
+ "integrity": "sha512-ZIfR49tJ8MKEARh/Ucy18QsVQXzeriFXcR0q+urvnCwaPwgpAY1bQo1a5oA0Mw5wO+uDvjGt7m5uZZeSxfHzxg==",
"dev": true,
"license": "GPL-2.0-or-later",
"dependencies": {
"requestidlecallback": "^0.3.0"
},
"engines": {
- "node": ">=18.12.0",
- "npm": ">=8.19.2"
+ "node": ">=18.12.0"
}
},
"node_modules/@wordpress/stylelint-config/node_modules/@wordpress/theme": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/@wordpress/theme/-/theme-1.2.0.tgz",
- "integrity": "sha512-pILS4ytGGiASrpJ8sLp+pRbcBspFYeMTr5LdM4fWBv/EN2CIKhBLpX3qGlXwKzS+HzjrbXZwj60Bfbg/PxvQew==",
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@wordpress/theme/-/theme-2.3.0.tgz",
+ "integrity": "sha512-rPIBgKLq3WE0OiwNMBUGQcIpweLGYUjjt6IBn30J1i/Jvxpz8bE/SVMMZxN5R40orq3oswWXvjsuB1L61Bqvkg==",
"dev": true,
"license": "GPL-2.0-or-later",
"dependencies": {
- "@wordpress/compose": "^8.6.0",
- "@wordpress/deprecated": "^4.53.0",
- "@wordpress/element": "^8.5.0",
- "@wordpress/private-apis": "^1.53.0",
- "@wordpress/style-runtime": "^0.9.0",
+ "@babel/parser": "^7.28.6",
+ "@babel/types": "^7.29.7",
+ "@jridgewell/remapping": "^2.3.5",
+ "@jridgewell/trace-mapping": "^0.3.31",
+ "@wordpress/compose": "^8.10.0",
+ "@wordpress/deprecated": "^4.57.0",
+ "@wordpress/element": "^9.0.0",
+ "@wordpress/private-apis": "^2.0.0",
+ "@wordpress/style-runtime": "^0.13.0",
"colorjs.io": "^0.7.1",
+ "magic-string": "^0.30.21",
"memize": "^2.1.1"
},
"engines": {
- "node": "^20.19.0 || >=22.13.0",
- "npm": ">=10.2.3"
+ "node": "^20.19.0 || >=22.13.0"
},
"peerDependencies": {
"@types/react": "^18 || ^19",
"esbuild": ">=0.27.2 <1.0.0",
+ "lightningcss": ">=1.33.0 <2.0.0",
"postcss": "^8.0.0",
"react": "^18 || ^19",
"react-dom": "^18 || ^19",
@@ -4035,9 +4088,18 @@
"esbuild": {
"optional": true
},
+ "lightningcss": {
+ "optional": true
+ },
"postcss": {
"optional": true
},
+ "react": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ },
"stylelint": {
"optional": true
},
@@ -4047,9 +4109,9 @@
}
},
"node_modules/@wordpress/stylelint-config/node_modules/react": {
- "version": "19.2.8",
- "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz",
- "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==",
+ "version": "19.3.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.3.1.tgz",
+ "integrity": "sha512-QNhWdt7nrgVWWQG58QuGsAR3jv3Hbe4mb4vz7w2csnHavciZJgda1CPNaldF6eVxv90ud3jUXpKlHL69gO38yw==",
"dev": true,
"license": "MIT",
"peer": true,
@@ -4058,50 +4120,48 @@
}
},
"node_modules/@wordpress/stylelint-config/node_modules/react-dom": {
- "version": "19.2.8",
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz",
- "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==",
+ "version": "19.3.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.3.1.tgz",
+ "integrity": "sha512-Y0bARz53lhEC1NMCASs0/j+faB/zqt3R8hpvg1e8+61ZVJ4UL0UdC4cBbXfZMcIuhrfgeILTLe+ZeBCavQPtKQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
- "scheduler": "^0.27.0"
+ "scheduler": "^0.28.0"
},
"peerDependencies": {
- "react": "^19.2.8"
+ "react": "^19.3.1"
}
},
"node_modules/@wordpress/stylelint-config/node_modules/scheduler": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
- "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.28.0.tgz",
+ "integrity": "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==",
"dev": true,
"license": "MIT",
"peer": true
},
"node_modules/@wordpress/undo-manager": {
- "version": "1.53.0",
- "resolved": "https://registry.npmjs.org/@wordpress/undo-manager/-/undo-manager-1.53.0.tgz",
- "integrity": "sha512-0MtlfeCa0uFWAGubP2rAJ1NoWQOWOuzW0kTwZgqfFFg81HEgNIBb8LU3eXMbMxERjdZYpCrqUL3MbVPl7ExiGw==",
+ "version": "1.57.0",
+ "resolved": "https://registry.npmjs.org/@wordpress/undo-manager/-/undo-manager-1.57.0.tgz",
+ "integrity": "sha512-LGTnpgeuRKDiEMrfzfAxkivlhN6apCsIVx3v7lyRb3vcr1rM9j8JL34W/+sLDLaUafb4I1f/LEBFRB18F55/Gg==",
"dev": true,
"license": "GPL-2.0-or-later",
"dependencies": {
- "@wordpress/is-shallow-equal": "^5.53.0"
+ "@wordpress/is-shallow-equal": "^5.57.0"
},
"engines": {
- "node": ">=18.12.0",
- "npm": ">=8.19.2"
+ "node": ">=18.12.0"
}
},
"node_modules/@wordpress/undo-manager/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",
- "integrity": "sha512-NHhvEnqi82Vzon458Jz6nzmbAPWM1zy9k6rBXQTc+9PsRiWHWpqXqldKAeTFVrl0vspZf4PwmLlsxqP3ckCLWw==",
+ "version": "5.57.0",
+ "resolved": "https://registry.npmjs.org/@wordpress/is-shallow-equal/-/is-shallow-equal-5.57.0.tgz",
+ "integrity": "sha512-QoIVWgnkKHAgrFB9hfpnmGvrAbutsa53DNl08i98guhtVVTSLzttlxgJxA3wpIepmR8FYiLNGB2Kfa49NdyIwg==",
"dev": true,
"license": "GPL-2.0-or-later",
"engines": {
- "node": ">=18.12.0",
- "npm": ">=8.19.2"
+ "node": ">=18.12.0"
}
},
"node_modules/@xtuc/ieee754": {
@@ -4147,7 +4207,9 @@
}
},
"node_modules/ajv": {
- "version": "8.17.1",
+ "version": "8.20.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
+ "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4456,7 +4518,9 @@
"license": "ISC"
},
"node_modules/brace-expansion": {
- "version": "1.1.11",
+ "version": "1.1.21",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz",
+ "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4996,7 +5060,9 @@
"license": "MIT"
},
"node_modules/colord": {
- "version": "2.9.3",
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/colord/-/colord-2.10.0.tgz",
+ "integrity": "sha512-AidJptpBJmjTclAp9BkLwJi0T93fo5epJnbaZslpg6QVzpHjAiveF55mE9AcUJiGMqRHgMDY8soMsQtuNYMHfw==",
"dev": true,
"license": "MIT"
},
@@ -5152,9 +5218,9 @@
}
},
"node_modules/css-blank-pseudo/node_modules/postcss-selector-parser": {
- "version": "7.1.5",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz",
- "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==",
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz",
+ "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -5240,9 +5306,9 @@
}
},
"node_modules/css-has-pseudo/node_modules/postcss-selector-parser": {
- "version": "7.1.5",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz",
- "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==",
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz",
+ "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -5549,14 +5615,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/decode-uri-component": {
- "version": "0.2.2",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10"
- }
- },
"node_modules/deep-is": {
"version": "0.1.4",
"dev": true,
@@ -5698,15 +5756,13 @@
"license": "MIT"
},
"node_modules/detect-libc": {
- "version": "1.0.3",
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"dev": true,
"license": "Apache-2.0",
- "optional": true,
- "bin": {
- "detect-libc": "bin/detect-libc.js"
- },
"engines": {
- "node": ">=0.10"
+ "node": ">=8"
}
},
"node_modules/dir-glob": {
@@ -5808,18 +5864,6 @@
"node": ">= 0.4"
}
},
- "node_modules/dunder-proto/node_modules/call-bind-apply-helpers": {
- "version": "1.0.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "function-bind": "^1.1.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
"node_modules/electron-to-chromium": {
"version": "1.5.411",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.411.tgz",
@@ -6273,7 +6317,9 @@
}
},
"node_modules/eslint/node_modules/ajv": {
- "version": "6.12.6",
+ "version": "6.15.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz",
+ "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -6477,7 +6523,9 @@
"license": "MIT"
},
"node_modules/fast-uri": {
- "version": "3.0.6",
+ "version": "3.1.9",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.9.tgz",
+ "integrity": "sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ==",
"dev": true,
"funding": [
{
@@ -6706,18 +6754,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/get-intrinsic/node_modules/call-bind-apply-helpers": {
- "version": "1.0.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "function-bind": "^1.1.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
"node_modules/get-proto": {
"version": "1.0.1",
"dev": true,
@@ -7680,7 +7716,9 @@
}
},
"node_modules/is-plain-object": {
- "version": "5.0.0",
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.1.0.tgz",
+ "integrity": "sha512-bUi/yjmtKYcRVUtWRGr0UA6xEFh2I6zWUwMrUXB3s7bmYCaZ8a+0ZsTRkrawh/mzlSD1Y0Ph8bp/U+TvBpWDNw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -7900,8 +7938,20 @@
"license": "MIT"
},
"node_modules/js-yaml": {
- "version": "4.1.0",
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz",
+ "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==",
"dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/puzrin"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/nodeca"
+ }
+ ],
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
@@ -8033,7 +8083,9 @@
}
},
"node_modules/lodash": {
- "version": "4.17.21",
+ "version": "4.18.1",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
+ "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
"dev": true,
"license": "MIT"
},
@@ -8080,6 +8132,16 @@
"tslib": "^2.0.3"
}
},
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
"node_modules/map-cache": {
"version": "0.2.2",
"dev": true,
@@ -8196,7 +8258,9 @@
}
},
"node_modules/minimatch": {
- "version": "3.1.2",
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
+ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"dev": true,
"license": "ISC",
"dependencies": {
@@ -8352,9 +8416,9 @@
"license": "MIT"
},
"node_modules/nanoid": {
- "version": "3.3.18",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz",
- "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==",
+ "version": "3.3.20",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz",
+ "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==",
"dev": true,
"funding": [
{
@@ -8899,9 +8963,9 @@
}
},
"node_modules/postcss": {
- "version": "8.5.26",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz",
- "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==",
+ "version": "8.5.31",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.31.tgz",
+ "integrity": "sha512-YUbWYCgErwnMAVkjUnYa+KlMBzGpHwiIt/DE7f0G33AuX2LH9UcTk6KgWxd3zRU+pN3u/C+Xl3EnMLM4Hea7Sg==",
"dev": true,
"funding": [
{
@@ -8919,9 +8983,9 @@
],
"license": "MIT",
"dependencies": {
- "nanoid": "^3.3.17",
+ "nanoid": "^3.3.20",
"picocolors": "^1.1.1",
- "source-map-js": "^1.2.1"
+ "source-map-js": "^1.2.2"
},
"engines": {
"node": "^10 || ^12 || >=14"
@@ -8954,9 +9018,9 @@
}
},
"node_modules/postcss-attribute-case-insensitive/node_modules/postcss-selector-parser": {
- "version": "7.1.5",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz",
- "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==",
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz",
+ "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -8985,9 +9049,9 @@
}
},
"node_modules/postcss-calc/node_modules/postcss-selector-parser": {
- "version": "7.1.5",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz",
- "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==",
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz",
+ "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -9221,9 +9285,9 @@
}
},
"node_modules/postcss-custom-selectors/node_modules/postcss-selector-parser": {
- "version": "7.1.5",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz",
- "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==",
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz",
+ "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -9261,9 +9325,9 @@
}
},
"node_modules/postcss-dir-pseudo-class/node_modules/postcss-selector-parser": {
- "version": "7.1.5",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz",
- "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==",
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz",
+ "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -9291,9 +9355,9 @@
}
},
"node_modules/postcss-discard-comments/node_modules/postcss-selector-parser": {
- "version": "7.1.5",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz",
- "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==",
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz",
+ "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -9398,9 +9462,9 @@
}
},
"node_modules/postcss-focus-visible/node_modules/postcss-selector-parser": {
- "version": "7.1.5",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz",
- "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==",
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz",
+ "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -9438,9 +9502,9 @@
}
},
"node_modules/postcss-focus-within/node_modules/postcss-selector-parser": {
- "version": "7.1.5",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz",
- "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==",
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz",
+ "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -9659,9 +9723,9 @@
}
},
"node_modules/postcss-merge-rules/node_modules/postcss-selector-parser": {
- "version": "7.1.5",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz",
- "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==",
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz",
+ "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -9744,9 +9808,9 @@
}
},
"node_modules/postcss-minify-selectors/node_modules/postcss-selector-parser": {
- "version": "7.1.5",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz",
- "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==",
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz",
+ "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -9785,7 +9849,9 @@
}
},
"node_modules/postcss-modules-local-by-default/node_modules/postcss-selector-parser": {
- "version": "7.1.0",
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz",
+ "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -9811,7 +9877,9 @@
}
},
"node_modules/postcss-modules-scope/node_modules/postcss-selector-parser": {
- "version": "7.1.0",
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz",
+ "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -9911,9 +9979,9 @@
}
},
"node_modules/postcss-nesting/node_modules/postcss-selector-parser": {
- "version": "7.1.5",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz",
- "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==",
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz",
+ "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -10289,9 +10357,9 @@
}
},
"node_modules/postcss-pseudo-class-any-link/node_modules/postcss-selector-parser": {
- "version": "7.1.5",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz",
- "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==",
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz",
+ "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -10437,9 +10505,9 @@
}
},
"node_modules/postcss-selector-not/node_modules/postcss-selector-parser": {
- "version": "7.1.5",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz",
- "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==",
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz",
+ "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -10451,7 +10519,9 @@
}
},
"node_modules/postcss-selector-parser": {
- "version": "6.1.2",
+ "version": "6.1.5",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.5.tgz",
+ "integrity": "sha512-TEHFzb5KRDKaOgJ4TYE+0jyJtFbjLvkdVOu0fZ2b6MTWzoqZd4Le+61aEppWkvTlYePgOX4ATL1wsAIPMLTw6Q==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -10520,9 +10590,9 @@
}
},
"node_modules/postcss-unique-selectors/node_modules/postcss-selector-parser": {
- "version": "7.1.5",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz",
- "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==",
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz",
+ "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -11420,16 +11490,6 @@
"@img/sharp-win32-x64": "0.34.5"
}
},
- "node_modules/sharp/node_modules/detect-libc": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
- "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/shebang-regex": {
"version": "3.0.0",
"dev": true,
@@ -11786,7 +11846,9 @@
}
},
"node_modules/source-map-js": {
- "version": "1.2.1",
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz",
+ "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
@@ -11794,12 +11856,14 @@
}
},
"node_modules/source-map-resolve": {
- "version": "0.5.3",
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.0.tgz",
+ "integrity": "sha512-3HnSRlPHZMKs84V7TnCyhhTVJAWoghTh4lbLZbv1OMOt4X0hLx5c2zQF3qsWD11v2QK0OB4RKMYZnbaHsBJPeg==",
+ "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated",
"dev": true,
"license": "MIT",
"dependencies": {
- "atob": "^2.1.2",
- "decode-uri-component": "^0.2.0",
+ "atob": "^2.0.0",
"resolve-url": "^0.2.1",
"source-map-url": "^0.4.0",
"urix": "^0.1.0"
@@ -12028,9 +12092,9 @@
}
},
"node_modules/stylehacks/node_modules/postcss-selector-parser": {
- "version": "7.1.5",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz",
- "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==",
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz",
+ "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -12106,9 +12170,9 @@
}
},
"node_modules/stylelint-config-recess-order": {
- "version": "7.8.0",
- "resolved": "https://registry.npmjs.org/stylelint-config-recess-order/-/stylelint-config-recess-order-7.8.0.tgz",
- "integrity": "sha512-ObYusdkXavm8ulgb5xvmU1l8wgCdveNYG12hOTUEdjS8bNhSRggl4or0mE0JaOnQz+Ve2xZemVpwqERCgJbntg==",
+ "version": "7.8.2",
+ "resolved": "https://registry.npmjs.org/stylelint-config-recess-order/-/stylelint-config-recess-order-7.8.2.tgz",
+ "integrity": "sha512-IIRCTkrGiynshEapnjdc53Nq/EaoqdWr/5U0Kk42b9O026+CmgBu4BHQPraWdV98E/U/XsotAhWZXs5gS7/7Zw==",
"dev": true,
"license": "ISC",
"peerDependencies": {
@@ -12232,9 +12296,9 @@
"license": "CC0-1.0"
},
"node_modules/stylelint-scss/node_modules/postcss-selector-parser": {
- "version": "7.1.5",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz",
- "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==",
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz",
+ "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -12392,9 +12456,9 @@
}
},
"node_modules/stylelint/node_modules/postcss-selector-parser": {
- "version": "7.1.5",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz",
- "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==",
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz",
+ "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -12797,16 +12861,18 @@
}
},
"node_modules/svgo-loader/node_modules/svgo": {
- "version": "2.8.0",
+ "version": "2.8.4",
+ "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.4.tgz",
+ "integrity": "sha512-2GJ4h3rl13qYTdwllaK6QlL8tG+UrM8626V2Ylcd/yBUv2Y/EwLsYisVV6UDCRmlk+C74G8nMpxJCk0RWPdDCw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@trysound/sax": "0.2.0",
"commander": "^7.2.0",
"css-select": "^4.1.3",
"css-tree": "^1.1.3",
"csso": "^4.2.0",
"picocolors": "^1.0.0",
+ "sax": "^1.5.0",
"stable": "^0.1.8"
},
"bin": {
@@ -13021,14 +13087,15 @@
}
},
"node_modules/terser-webpack-plugin": {
- "version": "5.3.14",
+ "version": "5.6.1",
+ "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.6.1.tgz",
+ "integrity": "sha512-201R5j+sJpK8nFWwKVyNfZot8FaJbLZDq5evriVzbV1wDtSXDjRUDRfJzHpAaxFDMEhsZL1QkeqM61wgsS3KaQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.25",
"jest-worker": "^27.4.5",
"schema-utils": "^4.3.0",
- "serialize-javascript": "^6.0.2",
"terser": "^5.31.1"
},
"engines": {
@@ -13042,12 +13109,39 @@
"webpack": "^5.1.0"
},
"peerDependenciesMeta": {
+ "@minify-html/node": {
+ "optional": true
+ },
"@swc/core": {
"optional": true
},
+ "@swc/css": {
+ "optional": true
+ },
+ "@swc/html": {
+ "optional": true
+ },
+ "clean-css": {
+ "optional": true
+ },
+ "cssnano": {
+ "optional": true
+ },
+ "csso": {
+ "optional": true
+ },
"esbuild": {
"optional": true
},
+ "html-minifier-terser": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "postcss": {
+ "optional": true
+ },
"uglify-js": {
"optional": true
}
@@ -13512,6 +13606,17 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/uuid": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
+ "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
"node_modules/watchpack": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.2.tgz",
diff --git a/src/js/classes/Animation.js b/src/js/classes/Animation.js
index 1bde520c..5cbc1d64 100644
--- a/src/js/classes/Animation.js
+++ b/src/js/classes/Animation.js
@@ -1,5 +1,5 @@
import AbstractDomElement from './AbstractDomElement'
-import { ScrollObserver, SplittedText, ThrottledEvent } from 'oneloop.js'
+import { SplittedText } from 'oneloop.js'
import noop from '../utils/noop'
// ----
@@ -7,8 +7,10 @@ import noop from '../utils/noop'
// ----
const instances = []
const animationClass = 'js-animation'
-let scrollObserver
-let resize
+// one IntersectionObserver per rootMargin, shared by all instances using it
+const observers = new Map()
+const lastScroll = { x: window.scrollX, y: window.scrollY }
+let scrollUpdateScheduled = false
// ----
// class Animation
@@ -27,16 +29,10 @@ class Animation extends AbstractDomElement {
return instance
}
- const that = this
const el = this._element
const s = this._settings
- const start = getValue(el, s.start)
- const end = getValue(el, s.end)
const callbacksSharedData = {}
- this._elementHeight = this._element.offsetHeight
- this._windowHeight = window.innerHeight
- this._onResize = onResize.bind(this)
this._isVisible = false
this._callbacksSharedData = callbacksSharedData
@@ -48,44 +44,20 @@ class Animation extends AbstractDomElement {
window.addEventListener('afterprint', onAfterPrint)
}
- // init scrollObserver and throttledEvent
- if (!scrollObserver) {
- scrollObserver = new ScrollObserver()
- resize = new ThrottledEvent(window, 'resize')
- }
-
- resize.add('resize', this._onResize)
-
// add animation class
el.classList.add(s.animationClass)
// intialize callback
- s.onInit(el, scrollObserver.getScrollInfos(), callbacksSharedData)
-
- // add element to scrollObserver
- scrollObserver.observe(el, {
- onVisible: function (scrollInfos, percentRTW) {
- const pStart = (this.distanceRTW.y * percentRTW.y) / that._windowHeight
- const pEnd = (this.distanceRTW.y * percentRTW.y) / (that._windowHeight + that._elementHeight)
-
- if (!that._isVisible && pStart >= start && pEnd <= end) {
- // show element
- that._isVisible = true
- s.onShow(el, scrollInfos, callbacksSharedData)
- el.classList.add(s.visibleClass)
-
- // destroy if playOnce = true
- if (s.playOnce) {
- that.destroy(el, scrollInfos, callbacksSharedData)
- }
- } else if (that._isVisible && (pStart < start || (pEnd > end && s.hideOnReachEnd))) {
- // hide element
- that._isVisible = false
- s.onHide(el, scrollInfos, callbacksSharedData)
- el.classList.remove(s.visibleClass)
- }
- },
- })
+ s.onInit(el, getScrollInfos(), callbacksSharedData)
+
+ // without IntersectionObserver support, never leave content hidden
+ if (typeof IntersectionObserver === 'undefined') {
+ this._show(getScrollInfos())
+ return
+ }
+
+ this._rootMargin = getRootMargin(getValue(el, s.start), getValue(el, s.end), s.hideOnReachEnd)
+ getObserver(this._rootMargin).observe(el)
}
/**
@@ -96,6 +68,63 @@ class Animation extends AbstractDomElement {
return this._isVisible
}
+ /**
+ * Show the element
+ * @param {object} scrollInfos
+ * @returns {undefined}
+ */
+ _show(scrollInfos) {
+ const el = this._element
+ const s = this._settings
+
+ this._isVisible = true
+ s.onShow(el, scrollInfos, this._callbacksSharedData)
+ el.classList.add(s.visibleClass)
+
+ // destroy if playOnce = true
+ if (s.playOnce) {
+ this.destroy()
+ }
+ }
+
+ /**
+ * Hide the element
+ * @param {object} scrollInfos
+ * @returns {undefined}
+ */
+ _hide(scrollInfos) {
+ const el = this._element
+ const s = this._settings
+
+ this._isVisible = false
+ s.onHide(el, scrollInfos, this._callbacksSharedData)
+ el.classList.remove(s.visibleClass)
+ }
+
+ /**
+ * Handle an intersection change of the element
+ * @param {IntersectionObserverEntry} entry
+ * @param {object} scrollInfos
+ * @returns {undefined}
+ */
+ _onIntersection(entry, scrollInfos) {
+ if (entry.isIntersecting) {
+ if (!this._isVisible) {
+ this._show(scrollInfos)
+ }
+ return
+ }
+
+ if (!this._isVisible) {
+ return
+ }
+
+ // element went back below the start line (scroll up), or above the end line when hideOnReachEnd is set
+ if (entry.boundingClientRect.top > 0 || this._settings.hideOnReachEnd) {
+ this._hide(scrollInfos)
+ }
+ }
+
/**
* Destroy
* @returns {undefined}
@@ -105,7 +134,7 @@ class Animation extends AbstractDomElement {
const s = this._settings
const index = instances.indexOf(this)
const callbacksSharedData = this._callbacksSharedData
- let scrollInfos
+ const scrollInfos = getScrollInfos()
if (index === -1) {
return
@@ -113,7 +142,6 @@ class Animation extends AbstractDomElement {
super.destroy()
- scrollInfos = scrollObserver.getScrollInfos()
instances.splice(index, 1)
if (s.showOnDestroy) {
@@ -121,15 +149,13 @@ class Animation extends AbstractDomElement {
el.classList.add(s.visibleClass)
}
- scrollObserver.unobserve(el)
- resize.remove('resize', this._onResize)
-
- if (!scrollObserver.hasEntry()) {
- scrollObserver = scrollObserver.destroy()
- resize = resize.destroy()
+ if (this._rootMargin && observers.has(this._rootMargin)) {
+ observers.get(this._rootMargin).unobserve(el)
}
if (instances.length === 0) {
+ observers.forEach((observer) => observer.disconnect())
+ observers.clear()
window.removeEventListener('beforeprint', onBeforePrint)
window.removeEventListener('afterprint', onAfterPrint)
}
@@ -167,6 +193,7 @@ Animation.defaults = {
// start (relative to bottom of the screen), can be a float, a function (element) {} or an array of two values (range) []
start: 0.25,
// end (relative to bottom of the screen), can be a float, a function (element) {} or an array of two values (range) []
+ // only used when hideOnReachEnd is true
end: 0.75,
// if true, the instance will be destroyed after the element is visible
playOnce: false,
@@ -185,13 +212,20 @@ Animation.defaults = {
// events
// ----
/**
- * onResize
- * @param {Object} event
+ * Dispatch intersection entries to their instances
+ * @param {IntersectionObserverEntry[]} entries
* @returns {undefined}
*/
-function onResize() {
- this._elementHeight = this._element.offsetHeight
- this._windowHeight = window.innerHeight
+function onIntersection(entries) {
+ const scrollInfos = getScrollInfos()
+
+ for (const entry of entries) {
+ const instance = entry.target.beapi && entry.target.beapi[Animation.nameSpace]
+
+ if (instance) {
+ instance._onIntersection(entry, scrollInfos)
+ }
+ }
}
/**
@@ -230,6 +264,70 @@ function getValue(element, value) {
return rt
}
+/**
+ * Convert start / end settings (fractions of the viewport) to an IntersectionObserver rootMargin.
+ * Values are rounded to whole percents so instances share observers.
+ * @param {number} start
+ * @param {number} end
+ * @param {boolean} hideOnReachEnd
+ * @returns {string}
+ */
+function getRootMargin(start, end, hideOnReachEnd) {
+ const bottom = Math.round(clamp(start) * 100)
+ const top = hideOnReachEnd ? Math.round((1 - clamp(end)) * 100) : 0
+
+ return `-${top}% 0px -${bottom}% 0px`
+}
+
+/**
+ * Clamp a value between 0 and 1
+ * @param {number} value
+ * @returns {number}
+ */
+function clamp(value) {
+ return Math.min(1, Math.max(0, Number(value) || 0))
+}
+
+/**
+ * Get (or create) the shared observer for a rootMargin
+ * @param {string} rootMargin
+ * @returns {IntersectionObserver}
+ */
+function getObserver(rootMargin) {
+ if (!observers.has(rootMargin)) {
+ observers.set(rootMargin, new IntersectionObserver(onIntersection, { rootMargin }))
+ }
+
+ return observers.get(rootMargin)
+}
+
+/**
+ * Scroll infos passed to callbacks, same shape as oneloop.js ScrollObserver (direction.y = 1 when scrolling down).
+ * Computed on demand: no scroll listener is attached.
+ * lastScroll is committed on the next animation frame so every caller in the same turn
+ * (multiple IntersectionObservers, destroy) shares the same delta / direction.
+ * @returns {object}
+ */
+function getScrollInfos() {
+ const scroll = { x: window.scrollX, y: window.scrollY }
+ const delta = { x: scroll.x - lastScroll.x, y: scroll.y - lastScroll.y }
+
+ if (!scrollUpdateScheduled) {
+ scrollUpdateScheduled = true
+ requestAnimationFrame(() => {
+ lastScroll.x = window.scrollX
+ lastScroll.y = window.scrollY
+ scrollUpdateScheduled = false
+ })
+ }
+
+ return {
+ scroll,
+ delta,
+ direction: { x: Math.sign(delta.x), y: Math.sign(delta.y) },
+ }
+}
+
// ----
// presets
// ----
@@ -275,7 +373,9 @@ Animation.preset = {
})
},
onDestroy: function (el, scrollInfos, data) {
- data.splittedText.destroy()
+ if (data.splittedText) {
+ data.splittedText.destroy()
+ }
},
},
}
diff --git a/src/js/utils/deferInitialization.js b/src/js/utils/deferInitialization.js
new file mode 100644
index 00000000..da33ff72
--- /dev/null
+++ b/src/js/utils/deferInitialization.js
@@ -0,0 +1,20 @@
+/**
+ * Run non-critical work when the main thread is idle, to keep it free for the first interactions (INP).
+ * Falls back to a short timeout when requestIdleCallback is not supported (Safari).
+ *
+ * @example
+ * import deferInitialization from '../utils/deferInitialization'
+ * deferInitialization(() => import('./classes/Slider'))
+ *
+ * @param {Function} callback
+ * @param {number} timeout Maximum delay (ms) before the callback is forced to run.
+ * @returns {undefined}
+ */
+export default function deferInitialization(callback, timeout = 2000) {
+ if (typeof window.requestIdleCallback === 'function') {
+ window.requestIdleCallback(callback, { timeout })
+ return
+ }
+
+ setTimeout(callback, 100)
+}
diff --git a/src/scss/02-tools/_f-assign-inputs.scss b/src/scss/02-tools/_f-assign-inputs.scss
index 5deee2e2..2a8cdc7d 100644
--- a/src/scss/02-tools/_f-assign-inputs.scss
+++ b/src/scss/02-tools/_f-assign-inputs.scss
@@ -31,7 +31,7 @@
@each $input in $inputs {
$input: string.unquote($input);
- $input: if($pseudo, $input + ":" + $pseudo, $input);
+ $input: if(sass($pseudo): $input + ":" + $pseudo; else: $input);
$list: list.append($list, $input, comma);
}
diff --git a/src/scss/03-base/_variables-css.scss b/src/scss/03-base/_variables-css.scss
index babf0cc1..7661591d 100644
--- a/src/scss/03-base/_variables-css.scss
+++ b/src/scss/03-base/_variables-css.scss
@@ -1,3 +1,4 @@
+@use "sass:map";
@use "../01-abstract/variables" as *;
@use "../02-tools/f-fluid-size" as *;
@use "../02-tools/m-breakpoint" as *;
@@ -7,7 +8,7 @@
/*
* Breakpoints
*/
- --breakpoint-mobile-to-desktop-nav: #{map-get($breakpoints, mobile-to-desktop-nav)}; // Used by JavaScript
+ --breakpoint-mobile-to-desktop-nav: #{map.get($breakpoints, mobile-to-desktop-nav)}; // Used by JavaScript
/*
* Heading