2025-07-03

- étoffe le fichier `JOURNAL` avec les nouveaux changements majeurs.
- propose une tâche _Justfile_ pour un rechargement à chaud primitif lors de changements
  CSS.
- ne précompresse pas et ne propose plus de versions « legacy » des scripts JS en methodes
  développement.
- appose correctement `aria-current` sur le lien de la page courante dans les deux menus
  de navigation.
- remplace une image statique « Scroll down » avec une animation SVG reposant sur du texte
  et des chemins.
- renomme moultes choses.
This commit is contained in:
gcch 2025-07-03 11:50:08 +02:00
commit d30b83d093
49 changed files with 830 additions and 359 deletions

View file

@ -1 +1,4 @@
{ "dictionaries": ["fr-fr", "en-gb"], "words": ["oxlint", "Vali", "mobily", "valibot", "GLITCHTIP"] }
{
"dictionaries": ["fr-fr", "en-gb"],
"words": ["GLITCHTIP", "Vali", "fdir", "mobily", "oxlint", "valibot", "zstandard", "Eles", "logtape"]
}

36
docs/JOURNAL.md Normal file
View file

@ -0,0 +1,36 @@
# Journal de développement
## 2025-06-13
### Informations produit sous forme de grille
- L'idée est de réimplémenter les informations essentiels du produit (nom, prix), le sélecteur de variation, les textes de détail, de conditions et d'entretien, et le bouton d'ajout au Panier.
- Plutôt qu'un encart flottant, contenant tout, il n'y aurait qu'une barre pleine longueur comprenant nom, prix et sélecteur de variation (`.essentiel-produit`).
- Il flotterait en bas de l'écran jusqu'à ce se poser à la fin des photos.
- Une nouvelle section, statique elle et faisant suite à la fois aux photos et à la barre, s'afficherait en pleine longueur sous forme d'accordéon, avec les anciens onglets comme sections.
- Par défaut, la section « Détails » serait développée.
- `<details>` et `<summary>` sont aujourd'hui pris en charge par les navigateurs (niveau _Baseline_), mais l'attribut `name` permettant l'ouverture exclusive d'une section par accordéon (p. ex. le développement d'une ferme toutes les autres) n'a que récemment été implémentée (2024 pour _Firefox_).
- Une implémentation en _JavaScript_ est donc pour l'instant nécessaire.
### Remaniement du défilement des photos de la page Produit
- Les flèches de défilement sont supprimées.
- À la place, les photos ne s'affichent plus en pleine longueur.
- Elles le sont à ~93%, pour que l'on perçoive sur les bords la photo précédente/suivante et signale à l'Utilisateur qu'il est possible de défiler.
## 2025-06-30
- Test de _LogTape_ comme librairie de journalisation.
## 2025-07-03
- Création d'une animation SVG avec des `<text>` défilants sur un `<path>`.
- Le redimensionnement dynamique du conteneur de l'animation se fait via JavaScript.
- Le script récupère la longueur d'une image au redimensionnement de la vue.
- Il injecte un attribut CSS `inline-size` dans le HTML de la page correspondant à cette nouvelle longueur.
- Il n'est pas encore possible de mettre en pause l'animation au survol de la souris, qui est une bonne pratique pour ce style d'éléments visuels.
- Le `<svg>` doit être d'une longueur supérieure au conteneur (ici `120%`) pour que le texte disparaisse de façon progressive, qu'il ne soit pas « coupé » brutalement aux extrémités.
- Début de l'implémentation d'une classe `no-js` pour un affichage adapté en cas d'absence de JavaScript.
- Développement d'un script `fish` qui lance un onglet Cromite en mode sans-tête avec profile Invité sur le site Haiku Atelier.
- Il est possible, en utilisant le débogueur à distance Chromium, de rafraîchir un onglet via le terminal en passant par `fx` et `websocat` pour l'interface _WebSocket_.
- Cela offre une forme primitive de « hot reload » (rechargement à chaud) pour éviter d'avoir manuellement taper F5 à chaque changement.

View file

@ -12,16 +12,23 @@
- Champs
- Utiliser un polyfill pour BroadcastChannel
- PAGE SHOP
- [ ] Faire apparaître le menu des catégories de Produits quand on scroll vers le haut
- PAGE PANIER
- Erreur lorsque l'on ajoute deux variations d'un même produit et que l'on essaie d'en supprimer une.
- Il semblerait que supprimer une variation supprime toutes les entrées du même produit.
- MÉTHODES DE LIVRAISON
- [ ] Proposer la livraison à domicile en Belgique et en France pour le coût unique de 8 euros, quel que soit le montant de la commande
- [ ] Proposer la livraison aux États-Unis.
- PAGE PRODUIT
- PIED DE PAGE
- TOUTES LES PAGES
- Trouver la source des bordures superflues.
- Sur Chromium : box-shadow sur <article> ?
- MODE VACANCES
- Pour l'été, superposer à la page SHOP un calque gris sur lequel défile une animation d'un texte indiquant que la boutique est en vacances.
- La page doit rester défilable.
- MODE NO JS
- Laisser la page normalement défilable avec les images les une après les autres quand _JavaScript_ n'est pas présent.
---

View file

@ -1,9 +0,0 @@
# Informations produit sous forme de grille
- L'idée est de réimplémenter les informations essentiels du produit (nom, prix), le sélecteur de variation, les textes de détail, de conditions et d'entretien, et le bouton d'ajout au Panier.
- Plutôt qu'un encart flottant, contenant tout, il n'y aurait qu'une barre pleine longueur comprenant nom, prix et sélecteur de variation (`.essentiel-produit`).
- Il flotterait en bas de l'écran jusqu'à ce se poser à la fin des photos.
- Une nouvelle section, statique elle et faisant suite à la fois aux photos et à la barre, s'afficherait en pleine longueur sous forme d'accordéon, avec les anciens onglets comme sections.
- Par défaut, la section « Détails » serait développée.
- `<details>` et `<summary>` sont aujourd'hui pris en charge par les navigateurs (niveau _Baseline_), mais l'attribut `name` permettant l'ouverture exclusive d'une section par accordéon (p. ex. le développement d'une ferme toutes les autres) n'a que récemment été implémentée (2024 pour _Firefox_).
- Une implémentation en _JavaScript_ est donc pour l'instant nécessaire.

View file

@ -1,5 +0,0 @@
# Remaniement du défilement des photos de la page Produit
- Les flèches de défilement sont supprimées.
- À la place, les photos ne s'affichent plus en pleine longueur.
- Elles le sont à ~93%, pour que l'on perçoive sur les bords la photo précédente/suivante et signale à l'utilisteur qu'il est possible de défiler.

View file

@ -46,12 +46,8 @@ export default tseslint.config(
"@typescript-eslint/no-misused-promises": "off",
/* Cette règle empêche l'usage de génériques précisant les types de retour de fonctions. */
"@typescript-eslint/no-unnecessary-type-parameters": "off",
"@typescript-eslint/no-unused-expressions": [
"error",
{
allowTernary: true,
},
],
// Pour utiliser LogTape.
"@typescript-eslint/no-unused-expressions": "off",
/* Cette règle est doublon avec les règles noUnused* de TypeScript. */
"@typescript-eslint/no-unused-vars": "off",
/* Cette règle empêche de lever des erreurs génériques (p.ex. `E extends Error`). */

View file

@ -9,18 +9,10 @@ stylelintCacheFile := "stylelintcache"
list:
@just --list --list-heading 'Recettes disponibles :'\n'' --unsorted
# Démarre le conteneur ddev
start:
ddev start
# Arrête le conteneur ddev
stop:
ddev stop
# Met à jour les dépendances composer et npm
update:
composer update
bun update
bun update
# Formatte avec Prettier et dprint
format:
@ -76,10 +68,8 @@ build-css:
# Compile le CSS à chaque changement de fichier
watch-css:
bunx sass \
--update \
--watch \
"web/app/themes/haiku-atelier-2024/src/sass":"web/app/themes/haiku-atelier-2024/assets/css"
@just dev
@watchexec -w "web/app/themes/haiku-atelier-2024/src/sass" -- just build-css reload-tab
# Compile TypeScript en JavaScript
build-js:
@ -112,8 +102,17 @@ lint-code-mort:
squash-and-push:
-jj squash --ignore-immutable && jj bookmark set principale -r @- --allow-backwards && jj git push
# Compile, analyse statiquement (avec corrections automatiques) et formate le CSS
build-lint-format-css:
-just build-css
# Analyse statiquement, compile et formate le CSS
lint-build-format-css:
-just lint-css
-just build-css
-just format
dev:
@/opt/cromite/chrome --remote-debugging-address=127.0.0.1 --remote-debugging-port=9222 --profile-directory=Guest "https://haikuatelier.gcch.local" &
reload-tab:
#!/usr/bin/fish
set -f WSURL (curl -s http://127.1:9222/json | fx '.[0].webSocketDebuggerUrl')
set -f REQUEST '{ "id": 2, "method": "Page.reload", "params": { "ignoreCache": true, "scriptToEvaluateOnLoad": "" } }'
echo $REQUEST | websocat $WSURL

View file

@ -17,13 +17,40 @@ const SRC_TYPESCRIPT_PATHS = new fdir()
.crawl(`web/app/themes/${SLUG_THEME}/src/scripts`)
.withPromise();
/* Voir le fichier vite.env.d.ts */
// Voir le fichier vite.env.d.ts.
const SCHEMA_ENVIRONNEMENT = v.object({
VITE_GLITCHTIP_NSD: v.pipe(v.string(), v.url(), v.readonly()),
VITE_MODE: v.pipe(v.string(), v.readonly()),
VITE_URL: v.pipe(v.string(), v.nonEmpty(), v.url(), v.readonly()),
});
const basePlugins = [
// Permet de valider les variables d'environnements définies à partir d'un schéma Valibot
valibot(SCHEMA_ENVIRONNEMENT),
manifestSRI({ algorithms: ["sha512"] }),
nodePolyfills({
include: [],
protocolImports: true,
}),
];
// Les extensions activées en production.
const prodPlugins = [
legacy({
modernPolyfills: true,
modernTargets:
"chrome >0 and last 3 years, edge >0 and last 3 years, safari >0 and last 3 years, firefox >0 and last 3 years, and_chr >0 and last 3 years, and_ff >0 and last 3 years, ios >0 and last 3 years",
renderLegacyChunks: true,
}),
compression({
algorithms: [
"brotliCompress",
"gzip",
"zstandard",
],
threshold: 1000,
}),
];
export default defineConfig(async ({ mode }) => {
const env = loadEnv(mode, process.cwd(), "VITE");
@ -32,9 +59,9 @@ export default defineConfig(async ({ mode }) => {
build: {
assetsDir: ".",
emptyOutDir: true,
/* Génère un fichier manifeste dans outDir */
// Génère un fichier manifeste dans outDir.
manifest: true,
minify: env.VITE_MODE === "production",
minify: env["VITE_MODE"] === "production",
outDir: resolve("./web/app/themes/haiku-atelier-2024/assets/js"),
reportCompressedSize: true,
rollupOptions: {
@ -42,39 +69,17 @@ export default defineConfig(async ({ mode }) => {
output: {
assetFileNames: "[name].[hash].[extname]",
chunkFileNames: "[name].[hash].js",
compact: env.VITE_MODE === "production",
compact: env["VITE_MODE"] === "production",
entryFileNames: "[name].js",
validate: true,
},
treeshake: true,
},
sourcemap: env.VITE_MODE === "production",
sourcemap: env["VITE_MODE"] === "production",
target: "es2020",
write: true,
},
mode: env.VITE_MODE ?? "production",
plugins: [
// Permet de valider les variables d'environnements définies à partir d'un schéma Valibot
valibot(SCHEMA_ENVIRONNEMENT),
manifestSRI({ algorithms: ["sha512"] }),
nodePolyfills({
include: [],
protocolImports: true,
}),
legacy({
modernPolyfills: true,
modernTargets:
"chrome >0 and last 3 years, edge >0 and last 3 years, safari >0 and last 3 years, firefox >0 and last 3 years, and_chr >0 and last 3 years, and_ff >0 and last 3 years, ios >0 and last 3 years",
renderLegacyChunks: true,
}),
compression({
algorithms: [
"brotliCompress",
"gzip",
"zstandard",
],
threshold: 1000,
}),
],
mode: env["VITE_MODE"] ?? "production",
plugins: env["VITE_MODE"] === "production" ? [...basePlugins, ...prodPlugins] : [...basePlugins],
};
});

View file

@ -52,6 +52,7 @@
--hauteur-ligne-compacte: 1.1;
--hauteur-ligne-rapprochee: 1;
/* Espacements entre les lettres */
--espacement-inter-lettres-rapproche-m: -1px;
--espacement-inter-lettres-rapproche-s: -0.5px;
--espacement-inter-lettres-etendu-s: 0.5px;
--espacement-inter-lettres-etendu-m: 1px;
@ -66,7 +67,7 @@
);
--contenu-page-hauteur-minimale-avec-categories: calc(
100svh - var(--en-tete-hauteur) - var(--pied-de-page-hauteur)
- var(--menu-categories-produits-hauteur)
- var(--menu-categories-produits-hauteur)
);
/* Espacements */
--espace-xs: 0.25rem;
@ -109,6 +110,7 @@ html {
* 2. Utilise la couleur primaire du site.
*/
body {
overscroll-behavior: none;
accent-color: var(--couleur-jaune); /* 2 */
background: var(--couleur-gris); /* 1 */
}
@ -688,14 +690,17 @@ body:has(#menu-mobile:not([aria-hidden=true])) {
/* Dispositions */
--liste-puce-cercle-puce-position-horizontale: 3.5ch; /* 3 */
}
#en-tete .menu-navigation__entree:has(a[aria-current=page]) {
background: url("/app/themes/haiku-atelier-2024/assets/img/icons/cloud-penche.svg") center/auto 90% no-repeat;
}
#en-tete .menu-navigation__entree--courante {
background: url("/app/themes/haiku-atelier-2024/assets/img/icons/cloud-penche.svg") center/auto 90% no-repeat;
}
#en-tete .menu-navigation__entree a {
display: inline-block; /* 1 */
padding: var(--nav-entree-marges-internes-bloc) var(--nav-entree-marges-internes-ligne); /* 2 */
text-align: center; /* 4 */
}
#en-tete .menu-navigation__entree--courante {
background: url("/app/themes/haiku-atelier-2024/assets/img/icons/cloud-penche.svg") center/auto 90% no-repeat;
}
@media (hover: hover) {
#en-tete .menu-navigation__entree:hover {
background: url("/app/themes/haiku-atelier-2024/assets/img/icons/cloud-penche.svg") center/auto 90% no-repeat;
@ -1230,9 +1235,6 @@ body:has(#menu-mobile:not([aria-hidden=true])) {
.details-produit__textes .section-textuelle:not(:last-of-type) {
border-block-end: 1px solid var(--couleur-noir);
}
.details-produit__textes .section-textuelle:has(button[aria-expanded=true]) button {
/* padding: initial; */
}
.details-produit__textes .section-textuelle:has(button[aria-expanded=false]) .section-textuelle__contenu {
display: none;
}
@ -1263,7 +1265,7 @@ body:has(#menu-mobile:not([aria-hidden=true])) {
.details-produit__actions {
--section-marges-internes: var(--espace-l);
overflow: hidden;
border: 1px solid var(--couleur-noir);
border-block: 1px solid var(--couleur-noir);
background: var(--couleur-jaune);
transition: 0.2s background;
}
@ -1286,6 +1288,7 @@ body:has(#menu-mobile:not([aria-hidden=true])) {
.produits-similaires {
--carte-produit-longueur-minimale: 448px;
--carte-produit-longueur-maximale: 1000px;
--en-tete-flottante-hauteur: calc(1rem + var(--espace-l) * 2 + 1px);
position: relative;
display: grid;
grid-template-areas: "en-tete en-tete en-tete" "produits produits produits";
@ -1297,10 +1300,10 @@ body:has(#menu-mobile:not([aria-hidden=true])) {
.produits-similaires header {
position: sticky;
z-index: 10;
top: calc(1lh + var(--espace-l) + var(--espace-m));
top: var(--en-tete-flottante-hauteur);
grid-area: en-tete;
width: 100%;
padding: var(--espace-l) 0 var(--espace-m);
padding: var(--espace-l) 0;
color: var(--couleur-blanc);
text-align: center;
background: var(--couleur-noir);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,45 +1,115 @@
@charset "UTF-8";
#page-accueil {
--hauteur-conteneur: var(--contenu-page-hauteur-minimale-sans-categories);
--page-marges-bloc-debut: var(--en-tete-hauteur);
--conteneur-marges-internes-ligne: var(--espace-xl);
overflow: hidden;
display: flex;
flex-flow: column nowrap;
min-height: var(--hauteur-conteneur);
max-height: var(--hauteur-conteneur);
min-block-size: var(--hauteur-conteneur);
max-block-size: var(--hauteur-conteneur);
margin-top: var(--page-marges-bloc-debut);
}
#page-accueil .storytelling {
overflow-y: scroll;
place-items: center;
min-height: inherit;
max-height: inherit;
overscroll-behavior: none;
min-block-size: inherit;
max-block-size: inherit;
}
#page-accueil .storytelling__conteneur {
overscroll-behavior: inherit;
display: flex;
flex-flow: column nowrap;
min-height: calc(var(--hauteur-conteneur) * 13);
padding: 0 var(--espace-xl);
place-items: center;
min-block-size: calc(var(--hauteur-conteneur) * 13);
padding: 0 var(--conteneur-marges-internes-ligne);
}
#page-accueil .storytelling__animation {
--hauteur-animation: 90px;
--taille-police: calc(var(--espace-xl) * 2.5);
pointer-events: none;
position: absolute;
z-index: 3;
top: 0;
right: 0;
left: 0;
overflow: hidden;
display: grid;
place-content: center;
place-items: center;
block-size: 100%;
margin: auto;
visibility: visible;
opacity: 1;
transition: 1s opacity ease-in-out, 1s visibility ease-in-out;
mask-image: linear-gradient(var(--mask-direction, to right), hsla(0, 0%, 0%, 0), hsl(0, 0%, 0%) 20%, hsl(0, 0%, 0%) 80%, hsla(0, 0%, 0%, 0));
}
#page-accueil .storytelling__animation[hidden] {
display: grid !important;
/* visibility: hidden;
opacity: 0; */
transition: 1s opacity ease-in-out, 1s visibility ease-in-out;
}
#page-accueil .storytelling__animation.no-js {
/* visibility: hidden;
opacity: 0; */
transition: 1s opacity ease-in-out, 1s visibility ease-in-out;
}
#page-accueil .storytelling__animation .animation-conteneur {
overflow: visible;
inline-size: 120%;
block-size: var(--hauteur-animation);
}
#page-accueil .storytelling__animation .animation-texte {
overflow: visible;
font-size: var(--taille-police);
font-weight: 600;
text-shadow: 4px 4px 0 var(--couleur-blanc);
text-transform: uppercase;
letter-spacing: var(--espacement-inter-lettres-rapproche-s);
}
#page-accueil .storytelling__image {
position: sticky;
top: 0;
align-content: center;
width: 100%;
min-height: var(--hauteur-conteneur);
max-height: var(--hauteur-conteneur);
inline-size: max-content;
max-inline-size: 100%;
min-block-size: var(--hauteur-conteneur);
max-block-size: var(--hauteur-conteneur);
}
#page-accueil .storytelling__image[data-cache] {
display: none;
#page-accueil .storytelling__image[data-caché] {
display: none !important;
}
#page-accueil .storytelling__image picture {
max-height: inherit;
max-block-size: inherit;
}
#page-accueil .storytelling__image img {
scale: 0.9;
max-height: inherit;
scale: 0.95;
max-block-size: inherit;
margin: auto;
object-fit: contain;
background: transparent;
}
@media (scripting: none) {
#page-accueil .storytelling__animation {
visibility: hidden;
}
}
@media (width <= 700px) {
#page-accueil {
--conteneur-marges-internes-ligne: var(--espace-l);
}
}
@media (width <= 500px) {
#page-accueil {
--conteneur-marges-internes-ligne: var(--espace-m);
}
}
@supports (-moz-appearance: none) {
#page-accueil .storytelling__animation {
--taille-police: calc(var(--espace-xl) * 2.2);
}
}
/*# sourceMappingURL=page-accueil.css.map */

View file

@ -1 +1 @@
{"version":3,"sourceRoot":"","sources":["../../../src/sass/pages/page-accueil.scss"],"names":[],"mappings":"AAEA;EAEE;EAGA;EAEA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA","file":"page-accueil.css"}
{"version":3,"sourceRoot":"","sources":["../../../src/sass/pages/page-accueil.scss"],"names":[],"mappings":";AAEA;EAEE;EAGA;EACA;EAEA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAIF;EACE;EACA;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAQA;EACE;AAEA;AAAA;EAEA;;AAIF;AACE;AAAA;EAEA;;AAGF;EACE;EAGA;EACA;;AAGF;EACE;EAGA;EACA;EAGA;EACA;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAMN;EACE;IACE;;;AAIJ;EAjIF;IAkII;;;AAGF;EArIF;IAsII;;;;AAKJ;EACE;IACE","file":"page-accueil.css"}

View file

@ -1 +1 @@
#page-accueil{--hauteur-conteneur:var(--contenu-page-hauteur-minimale-sans-categories);--page-marges-bloc-debut:var(--en-tete-hauteur);min-height:var(--hauteur-conteneur);max-height:var(--hauteur-conteneur);margin-top:var(--page-marges-bloc-debut);flex-flow:column;display:flex;overflow:hidden}#page-accueil .storytelling{min-height:inherit;max-height:inherit;place-items:center;overflow-y:scroll}#page-accueil .storytelling__conteneur{min-height:calc(var(--hauteur-conteneur)*13);padding:0 var(--espace-xl);flex-flow:column;display:flex}#page-accueil .storytelling__image{width:100%;min-height:var(--hauteur-conteneur);max-height:var(--hauteur-conteneur);align-content:center;position:sticky;top:0}#page-accueil .storytelling__image[data-cache]{display:none}#page-accueil .storytelling__image picture{max-height:inherit}#page-accueil .storytelling__image img{max-height:inherit;object-fit:contain;background:0 0;margin:auto;scale:.9}
#page-accueil{--hauteur-conteneur:var(--contenu-page-hauteur-minimale-sans-categories);--page-marges-bloc-debut:var(--en-tete-hauteur);--conteneur-marges-internes-ligne:var(--espace-xl);min-block-size:var(--hauteur-conteneur);max-block-size:var(--hauteur-conteneur);margin-top:var(--page-marges-bloc-debut);flex-flow:column;display:flex;overflow:hidden}#page-accueil .storytelling{overscroll-behavior:none;min-block-size:inherit;max-block-size:inherit;overflow-y:scroll}#page-accueil .storytelling__conteneur{overscroll-behavior:inherit;min-block-size:calc(var(--hauteur-conteneur)*13);padding:0 var(--conteneur-marges-internes-ligne);flex-flow:column;place-items:center;display:flex}#page-accueil .storytelling__animation{--hauteur-animation:90px;--taille-police:calc(var(--espace-xl)*2.5);pointer-events:none;z-index:3;visibility:visible;opacity:1;block-size:100%;mask-image:linear-gradient(var(--mask-direction,to right),#0000,#000 20%,#000 80%,#0000);place-content:center;place-items:center;margin:auto;transition:opacity 1s ease-in-out,visibility 1s ease-in-out;display:grid;position:absolute;top:0;left:0;right:0;overflow:hidden}#page-accueil .storytelling__animation[hidden]{transition:opacity 1s ease-in-out,visibility 1s ease-in-out;display:grid!important}#page-accueil .storytelling__animation.no-js{transition:opacity 1s ease-in-out,visibility 1s ease-in-out}#page-accueil .storytelling__animation .animation-conteneur{inline-size:120%;block-size:var(--hauteur-animation);overflow:visible}#page-accueil .storytelling__animation .animation-texte{font-size:var(--taille-police);text-shadow:4px 4px 0 var(--couleur-blanc);text-transform:uppercase;letter-spacing:var(--espacement-inter-lettres-rapproche-s);font-weight:600;overflow:visible}#page-accueil .storytelling__image{inline-size:max-content;max-inline-size:100%;min-block-size:var(--hauteur-conteneur);max-block-size:var(--hauteur-conteneur);align-content:center;position:sticky;top:0}#page-accueil .storytelling__image[data-caché]{display:none!important}#page-accueil .storytelling__image picture{max-block-size:inherit}#page-accueil .storytelling__image img{max-block-size:inherit;object-fit:contain;background:0 0;margin:auto;scale:.95}@media (scripting:none){#page-accueil .storytelling__animation{visibility:hidden}}@media (width<=700px){#page-accueil{--conteneur-marges-internes-ligne:var(--espace-l)}}@media (width<=500px){#page-accueil{--conteneur-marges-internes-ligne:var(--espace-m)}}@supports ((-moz-appearance:none)){#page-accueil .storytelling__animation{--taille-police:calc(var(--espace-xl)*2.2)}}

View file

@ -0,0 +1,25 @@
# haiku_gleam
[![Package Version](https://img.shields.io/hexpm/v/haiku_gleam)](https://hex.pm/packages/haiku_gleam)
[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/haiku_gleam/)
```sh
gleam add haiku_gleam@1
```
```gleam
import haiku_gleam
pub fn main() -> Nil {
// TODO: An example of the project in use
}
```
Further documentation can be found at <https://hexdocs.pm/haiku_gleam>.
## Development
```sh
gleam run # Run the project
gleam test # Run the tests
```

View file

@ -0,0 +1,11 @@
name = "haiku_gleam"
target = "javascript"
version = "0.0.1"
[dependencies]
gleam_javascript = ">= 1.0.0 and < 2.0.0"
gleam_stdlib = ">= 0.44.0 and < 2.0.0"
plinth = ">= 0.6.1 and < 1.0.0"
[dev-dependencies]
gleeunit = ">= 1.0.0 and < 2.0.0"

View file

@ -0,0 +1,16 @@
# This file was generated by Gleam
# You typically do not need to edit this file
packages = [
{ name = "gleam_javascript", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_javascript", source = "hex", outer_checksum = "EF6C77A506F026C6FB37941889477CD5E4234FCD4337FF0E9384E297CB8F97EB" },
{ name = "gleam_json", version = "3.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "5BA154440B22D9800955B1AB854282FA37B97F30F409D76B0824D0A60C934188" },
{ name = "gleam_stdlib", version = "0.60.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "621D600BB134BC239CB2537630899817B1A42E60A1D46C5E9F3FAE39F88C800B" },
{ name = "gleeunit", version = "1.5.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "D33B7736CF0766ED3065F64A1EBB351E72B2E8DE39BAFC8ADA0E35E92A6A934F" },
{ name = "plinth", version = "0.6.1", build_tools = ["gleam"], requirements = ["gleam_javascript", "gleam_json", "gleam_stdlib"], otp_app = "plinth", source = "hex", outer_checksum = "02D6421A27795CDC5C3A452240E21D5D3CB8217219020BB247917132E36CC8F1" },
]
[requirements]
gleam_javascript = { version = ">= 1.0.0 and < 2.0.0" }
gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" }
gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
plinth = { version = ">= 0.6.1 and < 1.0.0" }

View file

@ -0,0 +1,5 @@
import gleam/io
pub fn main() -> Nil {
io.println("Hello from haiku_gleam!")
}

View file

@ -0,0 +1,16 @@
/**
* @param {IntersectionObserverInit} options
* @param {IntersectionObserverCallback} callback
* @returns {IntersectionObserver}
*/
export const new_intersection_observer = (options, callback) => {
return new IntersectionObserver(callback, options);
};
/**
* @param {IntersectionObserver} observer
* @param {Element} element
*/
export const observe_element = (observer, element) => {
observer.observe(element);
};

View file

@ -0,0 +1,22 @@
import gleam/option.{type Option}
import plinth/browser/document
import plinth/browser/element
pub type IntersectionObserver
pub type ObservableElement {
Element(element.Element)
Document(document.Document)
}
pub type Threshold {
Threshold(values: List(Int))
}
pub type IntersectionObserverOptions {
IntersectionObserverOptions(
root: ObservableElement,
root_margin: Option(String),
threshold: Threshold,
)
}

View file

@ -0,0 +1,13 @@
import gleeunit
pub fn main() -> Nil {
gleeunit.main()
}
// gleeunit test functions end in `_test`
pub fn hello_world_test() {
let name = "Joe"
let greeting = "Hello, " <> name <> "!"
assert greeting == "Hello, Joe!"
}

View file

@ -24,6 +24,7 @@
--hauteur-ligne-rapprochee: 1;
/* Espacements entre les lettres */
--espacement-inter-lettres-rapproche-m: -1px;
--espacement-inter-lettres-rapproche-s: -0.5px;
--espacement-inter-lettres-etendu-s: 0.5px;
--espacement-inter-lettres-etendu-m: 1px;

View file

@ -36,6 +36,7 @@ html {
* 2. Utilise la couleur primaire du site.
*/
body {
overscroll-behavior: none;
accent-color: var(--couleur-jaune); /* 2 */
background: var(--couleur-gris); /* 1 */
}

View file

@ -85,6 +85,18 @@
/* Dispositions */
--liste-puce-cercle-puce-position-horizontale: 3.5ch; /* 3 */
// BASELINE001: Marchera seulement pour les navigateurs > 2023.
&:has(a[aria-current="page"]) {
background: url("/app/themes/haiku-atelier-2024/assets/img/icons/cloud-penche.svg")
center/auto 90% no-repeat;
}
// COMPAT001: Pour les navigateurs < 2023.
&--courante {
background: url("/app/themes/haiku-atelier-2024/assets/img/icons/cloud-penche.svg")
center/auto 90% no-repeat;
}
a {
display: inline-block; /* 1 */
padding: var(--nav-entree-marges-internes-bloc)
@ -93,11 +105,6 @@
text-align: center; /* 4 */
}
&--courante {
background: url("/app/themes/haiku-atelier-2024/assets/img/icons/cloud-penche.svg")
center/auto 90% no-repeat;
}
@media (hover: hover) {
&:hover {
background: url("/app/themes/haiku-atelier-2024/assets/img/icons/cloud-penche.svg")

View file

@ -182,13 +182,6 @@
border-block-end: 1px solid var(--couleur-noir);
}
// La section est dépliée.
&:has(button[aria-expanded="true"]) {
button {
/* padding: initial; */
}
}
// La section est fermée.
&:has(button[aria-expanded="false"]) .section-textuelle__contenu {
display: none;
@ -233,7 +226,7 @@
--section-marges-internes: var(--espace-l);
overflow: hidden;
border: 1px solid var(--couleur-noir);
border-block: 1px solid var(--couleur-noir);
background: var(--couleur-jaune);
transition: 0.2s background;

View file

@ -4,6 +4,7 @@
// Dimensions
--carte-produit-longueur-minimale: 448px;
--carte-produit-longueur-maximale: 1000px;
--en-tete-flottante-hauteur: calc(1rem + var(--espace-l) * 2 + 1px);
position: relative;
display: grid;
@ -18,10 +19,10 @@
header {
position: sticky;
z-index: 10;
top: calc(1lh + var(--espace-l) + var(--espace-m));
top: var(--en-tete-flottante-hauteur);
grid-area: en-tete;
width: 100%;
padding: var(--espace-l) 0 var(--espace-m);
padding: var(--espace-l) 0;
color: var(--couleur-blanc);
text-align: center;
background: var(--couleur-noir);

View file

@ -6,50 +6,141 @@
// Marges
--page-marges-bloc-debut: var(--en-tete-hauteur);
--conteneur-marges-internes-ligne: var(--espace-xl);
overflow: hidden;
display: flex;
flex-flow: column nowrap;
min-height: var(--hauteur-conteneur);
max-height: var(--hauteur-conteneur);
min-block-size: var(--hauteur-conteneur);
max-block-size: var(--hauteur-conteneur);
margin-top: var(--page-marges-bloc-debut);
.storytelling {
overflow-y: scroll;
place-items: center;
min-height: inherit;
max-height: inherit;
overscroll-behavior: none;
min-block-size: inherit;
max-block-size: inherit;
&__conteneur {
overscroll-behavior: inherit;
display: flex;
flex-flow: column nowrap;
min-height: calc(var(--hauteur-conteneur) * 13);
padding: 0 var(--espace-xl);
place-items: center;
min-block-size: calc(var(--hauteur-conteneur) * 13);
padding: 0 var(--conteneur-marges-internes-ligne);
}
// Texte animé indiquant à l'Utilisateur de défiler vers le bas.
&__animation {
--hauteur-animation: 90px;
--taille-police: calc(var(--espace-xl) * 2.5);
pointer-events: none;
position: absolute;
z-index: 3;
top: 0;
right: 0;
left: 0;
overflow: hidden;
display: grid;
place-content: center;
place-items: center;
block-size: 100%;
margin: auto;
visibility: visible;
opacity: 1;
transition: 1s opacity ease-in-out, 1s visibility ease-in-out;
mask-image: linear-gradient(
var(--mask-direction, to right),
hsl(0deg 0% 0% / 0%),
hsl(0deg 0% 0% / 100%) 20%,
hsl(0deg 0% 0% / 100%) 80%,
hsl(0deg 0% 0% / 0%)
);
&[hidden] {
display: grid !important;
/* visibility: hidden;
opacity: 0; */
transition: 1s opacity ease-in-out, 1s visibility ease-in-out;
}
// N'affiche rien si JavaScript n'est pas activé.
&.no-js {
/* visibility: hidden;
opacity: 0; */
transition: 1s opacity ease-in-out, 1s visibility ease-in-out;
}
.animation-conteneur {
overflow: visible;
// Nécessaire pour que les lettres apparaissent « en douceur » dans la vue.
inline-size: 120%;
block-size: var(--hauteur-animation);
}
.animation-texte {
overflow: visible;
// TODO: Pourquoi y-a-t'il une telle difference de rendu entre Chromium et FF ?
font-size: var(--taille-police);
font-weight: 600;
// TODO: Pourquoi y-a-t'il une telle difference de rendu entre Chromium et FF ?
text-shadow: 4px 4px 0 var(--couleur-blanc);
text-transform: uppercase;
letter-spacing: var(--espacement-inter-lettres-rapproche-s);
}
}
&__image {
position: sticky;
top: 0;
align-content: center;
width: 100%;
min-height: var(--hauteur-conteneur);
max-height: var(--hauteur-conteneur);
inline-size: max-content;
max-inline-size: 100%;
min-block-size: var(--hauteur-conteneur);
max-block-size: var(--hauteur-conteneur);
&[data-cache] {
display: none;
&[data-caché] {
display: none !important;
}
picture {
max-height: inherit;
max-block-size: inherit;
}
img {
scale: 0.9;
max-height: inherit;
scale: 0.95;
max-block-size: inherit;
margin: auto;
object-fit: contain;
background: transparent;
}
}
}
// Désactive l'animation si JavaScript n'est pas disponible.
@media (scripting: none) {
.storytelling__animation {
visibility: hidden;
}
}
@media (width <= 700px) {
--conteneur-marges-internes-ligne: var(--espace-l);
}
@media (width <= 500px) {
--conteneur-marges-internes-ligne: var(--espace-m);
}
}
// TODO: Réduit la taille de la police de l'animation sur FF car elle apparaît plus grande...
@supports (-moz-appearance: none) {
#page-accueil .storytelling__animation {
--taille-police: calc(var(--espace-xl) * 2.2);
}
}

View file

@ -1,11 +1,11 @@
/** Constantes de valeurs pour la manipulation du DOM : sélecteurs et attributs. */
export const ATTRIBUT_ACTIF = "data-actif";
export const ATTRIBUT_ARIA_CONTROLS = "aria-controls";
export const ATTRIBUT_ARIA_EXPANDED = "aria-expanded";
export const ATTRIBUT_ARIA_HIDDEN = "aria-hidden";
export const ATTRIBUT_ARIA_SELECTED = "aria-selected";
export const ATTRIBUT_CACHE = "data-cache";
export const ATTRIBUT_ACTIF = "data-actif";
export const ATTRIBUT_CACHÉ = "data-caché";
export const ATTRIBUT_CHARGEMENT = "data-chargement";
export const ATTRIBUT_CLE_PANIER = "data-cle-panier";
export const ATTRIBUT_CODE_PROMO_PRESENT = "data-code-promo-present";
@ -24,61 +24,61 @@ export const ATTRIBUT_PRIX = "data-prix";
export const ATTRIBUT_TABINDEX = "tabindex";
// En-tête
export const SELECTEUR_BOUTON_MENU_MOBILE = "#bouton-menu-mobile";
export const SELECTEUR_BOUTON_PANIER = ".compte-panier a[rel='cart']";
export const SELECTEUR_ENTREE_MENU_CATEGORIES_PRODUITS = "#menu-categories-produits ul li a";
export const SELECTEUR_FLECHE_DROITE_CATEGORIES_PRODUITS = "#fleche-defilement-categories-produits-droite";
export const SELECTEUR_FLECHE_GAUCHE_CATEGORIES_PRODUITS = "#fleche-defilement-categories-produits-gauche";
export const SELECTEUR_MENU_CATEGORIES_PRODUITS = "#menu-categories-produits";
export const SELECTEUR_MENU_MOBILE = "#menu-mobile";
export const DOM_BOUTON_MENU_MOBILE = "#bouton-menu-mobile";
export const DOM_BOUTON_PANIER = ".compte-panier a[rel='cart']";
export const DOM_ENTREE_MENU_CATEGORIES_PRODUITS = "#menu-categories-produits ul li a";
export const DOM_MENU_CATEGORIES_PRODUITS = "#menu-categories-produits";
export const DOM_MENU_MOBILE = "#menu-mobile";
// Panier
export const SELECTEUR_BOUTON_ACTIONS_FORMULAIRE = "#panneau-informations-client .panneau__pied-de-page button";
export const SELECTEUR_BOUTON_ADDITION_QUANTITE = "button.detail-produit__actions__addition";
export const SELECTEUR_BOUTON_CODE_PROMO = "#panneau-panier #bouton-code-promo";
export const SELECTEUR_BOUTON_SEPARATION_ADRESSES = "#separation-adresses";
export const SELECTEUR_BOUTON_SOUSTRACTION_QUANTITE = "button.detail-produit__actions__soustraction";
export const SELECTEUR_BOUTON_SUPPRESSION_PANIER = "button.detail-produit__actions__suppression";
export const SELECTEUR_CHAMP_CODE_PROMO = "#panneau-panier #champ-code-promo";
export const SELECTEUR_CHAMP_QUANTITE_LIGNE_PANIER = "input";
export const SELECTEUR_CONTENEUR_METHODES_LIVRAISON = "#panneau-panier #choix-methode-livraison";
export const SELECTEUR_ENSEMBLE_CODE_PROMO = "#panneau-panier #ensemble-code-promo";
export const SELECTEUR_ENTREES_PANIER = "article";
export const SELECTEUR_FORMULAIRE_FACTURATION = "#panneau-informations-client .panneau__formulaires__facturation";
export const SELECTEUR_FORMULAIRE_LIVRAISON = "#panneau-informations-client .panneau__formulaires__livraison";
export const SELECTEUR_FORMULAIRE_PANIER = "#panneau-informations-client form";
export const SELECTEUR_INSTRUCTIONS_CLIENT = "#panneau-panier #instructions-client";
export const SELECTEUR_MESSAGE_CODE_PROMO = "#panneau-panier .panneau__instructions-code-promo__code-promo__message";
export const SELECTEUR_MESSAGE_FORMULAIRE_ADRESSES = "#panneau-informations-client #message-formulaire-adresses";
export const SELECTEUR_PRIX_LIGNE_PANIER = ".detail-produit__nom-prix span";
export const SELECTEUR_SOUS_TOTAL_LIVRAISON_COUT = "#panneau-panier #sous-total-livraison strong";
export const SELECTEUR_SOUS_TOTAL_LIVRAISON_PRESTATAIRE = "#panneau-panier #sous-total-livraison span";
export const SELECTEUR_SOUS_TOTAL_PRODUITS = "#panneau-panier #sous-total-produits strong";
export const SELECTEUR_TOTAL_PANIER = "#panneau-panier .panneau__pied-de-page p span";
export const SELECTEUR_TOTAL_REDUCTION = "#panneau-panier #sous-total-reduction";
export const SELECTEUR_TOTAL_REDUCTION_VALEUR = "#panneau-panier #sous-total-reduction strong";
export const DOM_BOUTON_ACTIONS_FORMULAIRE = "#panneau-informations-client .panneau__pied-de-page button";
export const DOM_BOUTON_ADDITION_QUANTITE = "button.detail-produit__actions__addition";
export const DOM_BOUTON_CODE_PROMO = "#panneau-panier #bouton-code-promo";
export const DOM_BOUTON_SEPARATION_ADRESSES = "#separation-adresses";
export const DOM_BOUTON_SOUSTRACTION_QUANTITE = "button.detail-produit__actions__soustraction";
export const DOM_BOUTON_SUPPRESSION_PANIER = "button.detail-produit__actions__suppression";
export const DOM_CHAMP_CODE_PROMO = "#panneau-panier #champ-code-promo";
export const DOM_CHAMP_QUANTITE_LIGNE_PANIER = "input";
export const DOM_CONTENEUR_METHODES_LIVRAISON = "#panneau-panier #choix-methode-livraison";
export const DOM_ENSEMBLE_CODE_PROMO = "#panneau-panier #ensemble-code-promo";
export const DOM_ENTREES_PANIER = "article";
export const DOM_FORMULAIRE_FACTURATION = "#panneau-informations-client .panneau__formulaires__facturation";
export const DOM_FORMULAIRE_LIVRAISON = "#panneau-informations-client .panneau__formulaires__livraison";
export const DOM_FORMULAIRE_PANIER = "#panneau-informations-client form";
export const DOM_INSTRUCTIONS_CLIENT = "#panneau-panier #instructions-client";
export const DOM_MESSAGE_CODE_PROMO = "#panneau-panier .panneau__instructions-code-promo__code-promo__message";
export const DOM_MESSAGE_FORMULAIRE_ADRESSES = "#panneau-informations-client #message-formulaire-adresses";
export const DOM_PRIX_LIGNE_PANIER = ".detail-produit__nom-prix span";
export const DOM_SOUS_TOTAL_LIVRAISON_COUT = "#panneau-panier #sous-total-livraison strong";
export const DOM_SOUS_TOTAL_LIVRAISON_PRESTATAIRE = "#panneau-panier #sous-total-livraison span";
export const DOM_SOUS_TOTAL_PRODUITS = "#panneau-panier #sous-total-produits strong";
export const DOM_TOTAL_PANIER = "#panneau-panier .panneau__pied-de-page p span";
export const DOM_TOTAL_REDUCTION = "#panneau-panier #sous-total-reduction";
export const DOM_TOTAL_REDUCTION_VALEUR = "#panneau-panier #sous-total-reduction strong";
// Accueil
export const SELECTEUR_CONTENEUR_STORYTELLING = ".storytelling";
export const SELECTEUR_IMAGES_STORYTELLING = ".storytelling__image";
export const DOM_CONTENEUR_ANIMATION = ".storytelling__animation";
export const DOM_CONTENEUR_STORYTELLING = ".storytelling";
export const DOM_GARDE_FOU_JS = "no-js";
export const DOM_IMAGES_STORYTELLING = ".storytelling__image";
// Boutique
export const SELECTEUR_BOUTON_PLUS_PRODUITS = "#page-boutique #bouton-plus-de-produits";
export const SELECTEUR_GRILLE_PRODUITS = "#page-boutique .grille-produits";
export const DOM_BOUTON_PLUS_PRODUITS = "#page-boutique #bouton-plus-de-produits";
export const DOM_GRILLE_PRODUITS = "#page-boutique .grille-produits";
// À propos
export const CLASS_BOITE_TEXTE = "boite-texte";
export const CLASS_BOUTON_FERMETURE_BOITE_TEXTE = "boite-texte__bouton-fermeture";
export const CLASS_EPINGLE = "epingle";
export const SELECTEUR_BOITE_TEXTE = `.${CLASS_BOITE_TEXTE}`;
export const SELECTEUR_BOUTON_FERMETURE_BOITE_TEXTE = `.${CLASS_BOUTON_FERMETURE_BOITE_TEXTE}`;
export const SELECTEUR_CONTENEUR_STORYTELLING_A_PROPOS = ".storytelling__conteneur";
export const SELECTEUR_EPINGLE = `.${CLASS_EPINGLE}`;
export const DOM_BOITE_TEXTE = `.${CLASS_BOITE_TEXTE}`;
export const DOM_BOUTON_FERMETURE_BOITE_TEXTE = `.${CLASS_BOUTON_FERMETURE_BOITE_TEXTE}`;
export const DOM_CONTENEUR_STORYTELLING_A_PROPOS = ".storytelling__conteneur";
export const DOM_EPINGLE = `.${CLASS_EPINGLE}`;
// Produit
export const SELECTEUR_BOUTON_AJOUT_PANIER = "#bouton-ajout-panier";
export const SELECTEUR_CONTENEUR_PANIER = "#page-panier";
export const SELECTEUR_PRIX_PRODUIT = ".selecteur-produit__prix";
export const SELECTEUR_SELECTEUR_QUANTITE = "#selecteur-variation";
export const SELECTEUR_BOUTONS_ACCORDEON = ".section-textuelle button";
export const SELECTEUR_CONTENUS_ACCORDEON = ".section-textuelle__contenu";
export const DOM_BOUTON_AJOUT_PANIER = "#bouton-ajout-panier";
export const DOM_CONTENEUR_PANIER = "#page-panier";
export const DOM_PRIX_PRODUIT = ".selecteur-produit__prix";
export const DOM_DOM_QUANTITE = "#selecteur-variation";
export const DOM_BOUTONS_ACCORDEON = ".section-textuelle button";
export const DOM_CONTENUS_ACCORDEON = ".section-textuelle__contenu";

View file

@ -1,3 +1,4 @@
import { configureSync, getConsoleSink, getLogger } from "@logtape/logtape";
import chalk from "chalk";
import log, { type Logger } from "loglevel";
import prefix from "loglevel-plugin-prefix";
@ -19,3 +20,17 @@ prefix.apply(logger, {
return `${chalk.gray(`[${timestamp}]`)} ${colors[level.toUpperCase()](level)}`;
},
});
const HAIKU_ATELIER_LOGGER = "haiku-atelier";
configureSync({
loggers: [
{
category: HAIKU_ATELIER_LOGGER,
lowestLevel: "debug",
sinks: ["console"],
},
],
sinks: { console: getConsoleSink() },
});
export const nuLogger = getLogger(HAIKU_ATELIER_LOGGER);

View file

@ -4,12 +4,12 @@ import { Either, identity, Left, Right } from "purify-ts";
import type { ParentElement } from "./types/dom.d.ts";
import { ATTRIBUT_CHARGEMENT, ATTRIBUT_DESACTIVE } from "../constantes/dom.ts";
import { logger } from "../logging.ts";
import { logger } from "../journalisation.ts";
import { lanceAnimationCycleLoading } from "./animations.ts";
import {
BadRequestError,
creeSyntaxError,
ERREUR_SELECTEUR_INEXISTANT,
ERREUR_DOM_INEXISTANT,
ERREUR_SYNTAXE_INVALIDE,
ForbiddenError,
NotFoundError,
@ -26,9 +26,7 @@ export const recupereElementAvecSelecteur =
// Transforme le Left en une erreur plus sympathique
.mapLeft(_ => creeSyntaxError(ERREUR_SYNTAXE_INVALIDE(selecteur)))
// Retourne une SyntaxError si l'Élément est null
.chain((e: E | null) =>
G.isNotNullable(e) ? Right(e) : Left(creeSyntaxError(ERREUR_SELECTEUR_INEXISTANT(selecteur)))
);
.chain((e: E | null) => G.isNotNullable(e) ? Right(e) : Left(creeSyntaxError(ERREUR_DOM_INEXISTANT(selecteur))));
export const getDOMElementsWithSelector =
(parent: ParentElement) => <E extends Element = Element>(selecteur: string): Either<SyntaxError, Array<E>> =>
@ -38,7 +36,7 @@ export const getDOMElementsWithSelector =
// Transforme le Left en une erreur plus sympathique
.mapLeft(_ => creeSyntaxError(ERREUR_SYNTAXE_INVALIDE(selecteur)))
// Retourne une SyntaxError si le tableau est vide
.chain((e: Array<E>) => A.isEmpty(e) ? Left(creeSyntaxError(ERREUR_SELECTEUR_INEXISTANT(selecteur))) : Right(e));
.chain((e: Array<E>) => A.isEmpty(e) ? Left(creeSyntaxError(ERREUR_DOM_INEXISTANT(selecteur))) : Right(e));
export const recupereElementOuLeve = <E extends Element = Element>(elementOuErreur: Either<SyntaxError, E>): E =>
elementOuErreur.caseOf({

View file

@ -11,7 +11,7 @@ import { ErreurAdresseInvalide } from "./erreurs/adresses";
/* Messages d'erreur */
export const ERREUR_SYNTAXE_INVALIDE = (selecteur: string): string => `Le selecteur "${selecteur}" est invalide`;
export const ERREUR_SELECTEUR_INEXISTANT = (selecteur: string): string =>
export const ERREUR_DOM_INEXISTANT = (selecteur: string): string =>
`La requête "${selecteur}" n'a retourné aucun Élément.`;
/* Création d'erreurs */

View file

@ -40,7 +40,7 @@ import { WCStoreCartUpdateCustomerArgsSchema } from "../lib/schemas/api/cart-upd
import { estWCAddressError } from "../lib/schemas/api/erreurs";
import { WCV3OrdersArgsSchema, WCV3OrderSchema } from "../lib/schemas/api/v3/orders";
import { safeSchemaParse } from "../lib/validation";
import { logger } from "../logging";
import { logger } from "../journalisation.ts";
import { E } from "./scripts-page-panier-elements";
import { getShippingRatesLS } from "./scripts-page-panier-local-storage";

View file

@ -20,7 +20,7 @@ import {
ATTRIBUT_CODE_PROMO_PRESENT,
ATTRIBUT_DESACTIVE,
ATTRIBUT_HIDDEN,
SELECTEUR_BOUTON_CODE_PROMO,
DOM_BOUTON_CODE_PROMO,
} from "../constantes/dom";
import { NOM_CANAL_REVALIDATION_LIVRAISON } from "../constantes/messages";
import { lanceAnimationCycleLoading } from "../lib/animations";
@ -59,7 +59,7 @@ export const initialiseElementsCodePromo = (): void => {
.with(
{
cible: P.when((cible: EventTarget | null) =>
targetMatchesSelector<HTMLButtonElement>(cible, SELECTEUR_BOUTON_CODE_PROMO)
targetMatchesSelector<HTMLButtonElement>(cible, DOM_BOUTON_CODE_PROMO)
),
codePromoPresent: false,
valeurCodePromo: P.string,
@ -170,7 +170,7 @@ export const initialiseElementsCodePromo = (): void => {
// Un code promo est présent sous forme de chaîne
.with(
{
cible: P.when(cible => targetMatchesSelector<HTMLButtonElement>(cible, SELECTEUR_BOUTON_CODE_PROMO)),
cible: P.when(cible => targetMatchesSelector<HTMLButtonElement>(cible, DOM_BOUTON_CODE_PROMO)),
codePromoPresent: true,
valeurCodePromo: P.string,
},

View file

@ -1,48 +1,48 @@
import {
SELECTEUR_BOUTON_ACTIONS_FORMULAIRE,
SELECTEUR_BOUTON_CODE_PROMO,
SELECTEUR_BOUTON_SEPARATION_ADRESSES,
SELECTEUR_CHAMP_CODE_PROMO,
SELECTEUR_CONTENEUR_METHODES_LIVRAISON,
SELECTEUR_CONTENEUR_PANIER,
SELECTEUR_ENSEMBLE_CODE_PROMO,
SELECTEUR_ENTREES_PANIER,
SELECTEUR_FORMULAIRE_FACTURATION,
SELECTEUR_FORMULAIRE_PANIER,
SELECTEUR_INSTRUCTIONS_CLIENT,
SELECTEUR_MESSAGE_CODE_PROMO,
SELECTEUR_MESSAGE_FORMULAIRE_ADRESSES,
SELECTEUR_SOUS_TOTAL_LIVRAISON_COUT,
SELECTEUR_SOUS_TOTAL_PRODUITS,
SELECTEUR_TOTAL_PANIER,
SELECTEUR_TOTAL_REDUCTION,
SELECTEUR_TOTAL_REDUCTION_VALEUR,
DOM_BOUTON_ACTIONS_FORMULAIRE,
DOM_BOUTON_CODE_PROMO,
DOM_BOUTON_SEPARATION_ADRESSES,
DOM_CHAMP_CODE_PROMO,
DOM_CONTENEUR_METHODES_LIVRAISON,
DOM_CONTENEUR_PANIER,
DOM_ENSEMBLE_CODE_PROMO,
DOM_ENTREES_PANIER,
DOM_FORMULAIRE_FACTURATION,
DOM_FORMULAIRE_PANIER,
DOM_INSTRUCTIONS_CLIENT,
DOM_MESSAGE_CODE_PROMO,
DOM_MESSAGE_FORMULAIRE_ADRESSES,
DOM_SOUS_TOTAL_LIVRAISON_COUT,
DOM_SOUS_TOTAL_PRODUITS,
DOM_TOTAL_PANIER,
DOM_TOTAL_REDUCTION,
DOM_TOTAL_REDUCTION_VALEUR,
} from "../constantes/dom";
import { mustGetEleInDocument, recupereElementsDocumentEither } from "../lib/dom";
export const E = {
BOUTON_ACTIONS_FORMULAIRE: mustGetEleInDocument<HTMLButtonElement>(SELECTEUR_BOUTON_ACTIONS_FORMULAIRE),
BOUTON_CODE_PROMO: mustGetEleInDocument<HTMLButtonElement>(SELECTEUR_BOUTON_CODE_PROMO),
BOUTON_SEPARATION_ADRESSES: mustGetEleInDocument<HTMLInputElement>(SELECTEUR_BOUTON_SEPARATION_ADRESSES),
CHAMP_CODE_PROMO: mustGetEleInDocument<HTMLInputElement>(SELECTEUR_CHAMP_CODE_PROMO),
CONTENEUR_METHODES_LIVRAISON: mustGetEleInDocument<HTMLFieldSetElement>(SELECTEUR_CONTENEUR_METHODES_LIVRAISON),
CONTENEUR_PANIER: mustGetEleInDocument<HTMLElement>(SELECTEUR_CONTENEUR_PANIER),
ENSEMBLE_CODE_PROMO: mustGetEleInDocument<HTMLFormElement>(SELECTEUR_ENSEMBLE_CODE_PROMO),
BOUTON_ACTIONS_FORMULAIRE: mustGetEleInDocument<HTMLButtonElement>(DOM_BOUTON_ACTIONS_FORMULAIRE),
BOUTON_CODE_PROMO: mustGetEleInDocument<HTMLButtonElement>(DOM_BOUTON_CODE_PROMO),
BOUTON_SEPARATION_ADRESSES: mustGetEleInDocument<HTMLInputElement>(DOM_BOUTON_SEPARATION_ADRESSES),
CHAMP_CODE_PROMO: mustGetEleInDocument<HTMLInputElement>(DOM_CHAMP_CODE_PROMO),
CONTENEUR_METHODES_LIVRAISON: mustGetEleInDocument<HTMLFieldSetElement>(DOM_CONTENEUR_METHODES_LIVRAISON),
CONTENEUR_PANIER: mustGetEleInDocument<HTMLElement>(DOM_CONTENEUR_PANIER),
ENSEMBLE_CODE_PROMO: mustGetEleInDocument<HTMLFormElement>(DOM_ENSEMBLE_CODE_PROMO),
ENTREES_PANIER: recupereElementsDocumentEither<HTMLElement>(
SELECTEUR_ENTREES_PANIER,
DOM_ENTREES_PANIER,
),
FORMULAIRE_FACTURATION: mustGetEleInDocument<HTMLDivElement>(SELECTEUR_FORMULAIRE_FACTURATION),
FORMULAIRE_PANIER: mustGetEleInDocument<HTMLFormElement>(SELECTEUR_FORMULAIRE_PANIER),
INSTRUCTIONS_CLIENT: mustGetEleInDocument<HTMLTextAreaElement>(SELECTEUR_INSTRUCTIONS_CLIENT),
MESSAGE_ADRESSES: mustGetEleInDocument<HTMLParagraphElement>(SELECTEUR_MESSAGE_FORMULAIRE_ADRESSES),
MESSAGE_CODE_PROMO: mustGetEleInDocument<HTMLParagraphElement>(SELECTEUR_MESSAGE_CODE_PROMO),
SOUS_TOTAL_LIVRAISON_VALEUR: mustGetEleInDocument<HTMLElement>(SELECTEUR_SOUS_TOTAL_LIVRAISON_COUT),
SOUS_TOTAL_PRODUITS: mustGetEleInDocument<HTMLElement>(SELECTEUR_SOUS_TOTAL_PRODUITS),
SOUS_TOTAL_PRODUITS_VALEUR: mustGetEleInDocument<HTMLElement>(SELECTEUR_SOUS_TOTAL_PRODUITS),
SOUS_TOTAL_REDUCTION: mustGetEleInDocument<HTMLSpanElement>(SELECTEUR_TOTAL_REDUCTION_VALEUR),
SOUS_TOTAL_REDUCTION_VALEUR: mustGetEleInDocument<HTMLSpanElement>(SELECTEUR_TOTAL_REDUCTION_VALEUR),
TOTAL_PANIER: mustGetEleInDocument<HTMLParagraphElement>(SELECTEUR_TOTAL_PANIER),
TOTAL_PANIER_VALEUR: mustGetEleInDocument<HTMLSpanElement>(SELECTEUR_TOTAL_PANIER),
TOTAL_REDUCTION_LIGNE: mustGetEleInDocument<HTMLDivElement>(SELECTEUR_TOTAL_REDUCTION),
TOTAL_REDUCTION_VALEUR: mustGetEleInDocument<HTMLSpanElement>(SELECTEUR_TOTAL_REDUCTION_VALEUR),
FORMULAIRE_FACTURATION: mustGetEleInDocument<HTMLDivElement>(DOM_FORMULAIRE_FACTURATION),
FORMULAIRE_PANIER: mustGetEleInDocument<HTMLFormElement>(DOM_FORMULAIRE_PANIER),
INSTRUCTIONS_CLIENT: mustGetEleInDocument<HTMLTextAreaElement>(DOM_INSTRUCTIONS_CLIENT),
MESSAGE_ADRESSES: mustGetEleInDocument<HTMLParagraphElement>(DOM_MESSAGE_FORMULAIRE_ADRESSES),
MESSAGE_CODE_PROMO: mustGetEleInDocument<HTMLParagraphElement>(DOM_MESSAGE_CODE_PROMO),
SOUS_TOTAL_LIVRAISON_VALEUR: mustGetEleInDocument<HTMLElement>(DOM_SOUS_TOTAL_LIVRAISON_COUT),
SOUS_TOTAL_PRODUITS: mustGetEleInDocument<HTMLElement>(DOM_SOUS_TOTAL_PRODUITS),
SOUS_TOTAL_PRODUITS_VALEUR: mustGetEleInDocument<HTMLElement>(DOM_SOUS_TOTAL_PRODUITS),
SOUS_TOTAL_REDUCTION: mustGetEleInDocument<HTMLSpanElement>(DOM_TOTAL_REDUCTION_VALEUR),
SOUS_TOTAL_REDUCTION_VALEUR: mustGetEleInDocument<HTMLSpanElement>(DOM_TOTAL_REDUCTION_VALEUR),
TOTAL_PANIER: mustGetEleInDocument<HTMLParagraphElement>(DOM_TOTAL_PANIER),
TOTAL_PANIER_VALEUR: mustGetEleInDocument<HTMLSpanElement>(DOM_TOTAL_PANIER),
TOTAL_REDUCTION_LIGNE: mustGetEleInDocument<HTMLDivElement>(DOM_TOTAL_REDUCTION),
TOTAL_REDUCTION_VALEUR: mustGetEleInDocument<HTMLSpanElement>(DOM_TOTAL_REDUCTION_VALEUR),
};

View file

@ -7,7 +7,7 @@ import { ADRESSES_MAJ, CODE_PROMO_MAJ, SHIPPING_RATES_UPDATED, TOTALS_UPDATED }
import { reporteEtJournaliseErreur } from "../lib/erreurs";
import { formateEnEuros } from "../lib/nombres";
import { eitherSetSessionStorage } from "../lib/session-storage";
import { logger } from "../logging";
import { logger } from "../journalisation.ts";
import { E } from "./scripts-page-panier-elements";
import { generateShippingRatesHTML } from "./scripts-page-panier-methodes-livraison";

View file

@ -13,7 +13,7 @@ import { formateEnEuros } from "../lib/nombres";
import { find } from "../lib/safe-arrays";
import { WCStoreCartTotalsSchema } from "../lib/schemas/api/cart";
import { getSessionStorageByKey } from "../lib/session-storage";
import { logger } from "../logging";
import { logger } from "../journalisation.ts";
import { E } from "./scripts-page-panier-elements";
import { getShippingRatesLS } from "./scripts-page-panier-local-storage";

View file

@ -16,10 +16,10 @@ import { ROUTE_API_MAJ_ARTICLE_PANIER, ROUTE_API_RETIRE_ARTICLE_PANIER } from ".
import {
ATTRIBUT_CLE_PANIER,
ATTRIBUT_DESACTIVE,
SELECTEUR_BOUTON_ADDITION_QUANTITE,
SELECTEUR_BOUTON_SOUSTRACTION_QUANTITE,
SELECTEUR_BOUTON_SUPPRESSION_PANIER,
SELECTEUR_CHAMP_QUANTITE_LIGNE_PANIER,
DOM_BOUTON_ADDITION_QUANTITE,
DOM_BOUTON_SOUSTRACTION_QUANTITE,
DOM_BOUTON_SUPPRESSION_PANIER,
DOM_CHAMP_QUANTITE_LIGNE_PANIER,
} from "../constantes/dom";
import { NOM_CANAL_REVALIDATION_LIVRAISON } from "../constantes/messages";
import { mustGetEleInParent } from "../lib/dom";
@ -51,10 +51,10 @@ type CartEntryInteractiveElements = {
const getCartEntryInteractiveEles = (entree: HTMLElement): CartEntryInteractiveElements => {
const mustGetEle = mustGetEleInParent(entree);
return {
additionButton: mustGetEle<HTMLButtonElement>(SELECTEUR_BOUTON_ADDITION_QUANTITE),
deletionButton: mustGetEle<HTMLButtonElement>(SELECTEUR_BOUTON_SUPPRESSION_PANIER),
quantityInput: mustGetEle<HTMLInputElement>(SELECTEUR_CHAMP_QUANTITE_LIGNE_PANIER),
substractionButton: mustGetEle<HTMLButtonElement>(SELECTEUR_BOUTON_SOUSTRACTION_QUANTITE),
additionButton: mustGetEle<HTMLButtonElement>(DOM_BOUTON_ADDITION_QUANTITE),
deletionButton: mustGetEle<HTMLButtonElement>(DOM_BOUTON_SUPPRESSION_PANIER),
quantityInput: mustGetEle<HTMLInputElement>(DOM_CHAMP_QUANTITE_LIGNE_PANIER),
substractionButton: mustGetEle<HTMLButtonElement>(DOM_BOUTON_SOUSTRACTION_QUANTITE),
};
};
@ -102,7 +102,7 @@ export const initialiseActionsEntreesPanier = (): void => {
.with(P.nullish, () => console.error(event.target))
// Clic sur le Bouton d'addition
.when(
(target: EventTarget) => (target as HTMLElement).matches(SELECTEUR_BOUTON_ADDITION_QUANTITE),
(target: EventTarget) => (target as HTMLElement).matches(DOM_BOUTON_ADDITION_QUANTITE),
(): void => {
void EitherAsync
.liftEither(
@ -174,7 +174,7 @@ export const initialiseActionsEntreesPanier = (): void => {
)
// Bouton de soustraction
.when(
(cible: EventTarget) => (cible as HTMLElement).matches(SELECTEUR_BOUTON_SOUSTRACTION_QUANTITE),
(cible: EventTarget) => (cible as HTMLElement).matches(DOM_BOUTON_SOUSTRACTION_QUANTITE),
(): void => {
Maybe
// Nécessaire pour que l'on ait une valeur à incrémenter
@ -258,7 +258,7 @@ export const initialiseActionsEntreesPanier = (): void => {
)
// Bouton de suppression
.when(
(cible: EventTarget) => (cible as HTMLElement).matches(SELECTEUR_BOUTON_SUPPRESSION_PANIER),
(cible: EventTarget) => (cible as HTMLElement).matches(DOM_BOUTON_SUPPRESSION_PANIER),
(): void => {
Maybe
// TODO: Pourquoi ?

View file

@ -4,7 +4,7 @@
import type { MessageMajBoutonPanier } from "./lib/types/messages";
import { ATTRIBUT_CONTIENT_ARTICLES, SELECTEUR_BOUTON_PANIER } from "./constantes/dom.ts";
import { ATTRIBUT_CONTIENT_ARTICLES, DOM_BOUTON_PANIER } from "./constantes/dom.ts";
import { NOM_CANAL_BOUTON_PANIER } from "./constantes/messages.ts";
import { mustGetEleInDocument } from "./lib/dom.ts";
import { valideMessageMajBoutonPanier } from "./lib/messages.ts";
@ -16,7 +16,7 @@ import { valideMessageMajBoutonPanier } from "./lib/messages.ts";
*/
const initialiseBoutonPanier = (): void => {
/** Le « Bouton » vers le Panier avec un indicateur de la quantité de Produits ajoutés. */
const BOUTON_PANIER: HTMLAnchorElement = mustGetEleInDocument<HTMLAnchorElement>(SELECTEUR_BOUTON_PANIER);
const BOUTON_PANIER: HTMLAnchorElement = mustGetEleInDocument<HTMLAnchorElement>(DOM_BOUTON_PANIER);
const CANAL_BOUTON_PANIER: BroadcastChannel = new BroadcastChannel(NOM_CANAL_BOUTON_PANIER);
CANAL_BOUTON_PANIER.onmessage = (evenementMessage: MessageEvent<unknown>): void => {

View file

@ -4,16 +4,11 @@ import { pipe } from "@mobily/ts-belt";
import { head as arrayHead } from "@mobily/ts-belt/Array";
import { tap as optionTap } from "@mobily/ts-belt/Option";
import {
ATTRIBUT_ACTIF,
ATTRIBUT_ARIA_HIDDEN,
ATTRIBUT_TABINDEX,
SELECTEUR_BOUTON_MENU_MOBILE,
} from "./constantes/dom";
import { ATTRIBUT_ACTIF, ATTRIBUT_ARIA_HIDDEN, ATTRIBUT_TABINDEX, DOM_BOUTON_MENU_MOBILE } from "./constantes/dom";
import { mustGetEleInDocument } from "./lib/dom";
const E = {
BOUTON_MENU_MOBILE: mustGetEleInDocument<HTMLButtonElement>(SELECTEUR_BOUTON_MENU_MOBILE),
BOUTON_MENU_MOBILE: mustGetEleInDocument<HTMLButtonElement>(DOM_BOUTON_MENU_MOBILE),
BOUTON_RETOUR_SOMMET: mustGetEleInDocument<HTMLButtonElement>("#bouton-retour-haut"),
CORPS_HTML: mustGetEleInDocument<HTMLBodyElement>("body"),
IMAGE_BOUTON: mustGetEleInDocument<HTMLImageElement>("#bouton-retour-haut img"),

View file

@ -3,13 +3,13 @@
import { A } from "@mobily/ts-belt";
import { match } from "ts-pattern";
import { SELECTEUR_ENTREE_MENU_CATEGORIES_PRODUITS, SELECTEUR_MENU_CATEGORIES_PRODUITS } from "./constantes/dom.ts";
import { DOM_ENTREE_MENU_CATEGORIES_PRODUITS, DOM_MENU_CATEGORIES_PRODUITS } from "./constantes/dom.ts";
import { mustGetEleInDocument, mustGetElesInDocument } from "./lib/dom.ts";
document.addEventListener("DOMContentLoaded", (): void => {
const MENU_CATEGORIES_PRODUITS: HTMLElement = mustGetEleInDocument(SELECTEUR_MENU_CATEGORIES_PRODUITS);
const MENU_CATEGORIES_PRODUITS: HTMLElement = mustGetEleInDocument(DOM_MENU_CATEGORIES_PRODUITS);
const ENTREES_MENU_CATEGORIES_PRODUITS: Array<HTMLAnchorElement> = mustGetElesInDocument(
SELECTEUR_ENTREE_MENU_CATEGORIES_PRODUITS,
DOM_ENTREE_MENU_CATEGORIES_PRODUITS,
);
A.forEachWithIndex(

View file

@ -5,14 +5,14 @@
import { A, O, pipe } from "@mobily/ts-belt";
import A11yDialog from "a11y-dialog";
import { ATTRIBUT_MENU_MOBILE_ACTIVE, SELECTEUR_BOUTON_MENU_MOBILE, SELECTEUR_MENU_MOBILE } from "./constantes/dom.ts";
import { ATTRIBUT_MENU_MOBILE_ACTIVE, DOM_BOUTON_MENU_MOBILE, DOM_MENU_MOBILE } from "./constantes/dom.ts";
import { mustGetEleInDocument } from "./lib/dom.ts";
// Éléments d'intérêt
const E = {
BOUTON_MENU_MOBILE: mustGetEleInDocument<HTMLButtonElement>(SELECTEUR_BOUTON_MENU_MOBILE),
BOUTON_MENU_MOBILE: mustGetEleInDocument<HTMLButtonElement>(DOM_BOUTON_MENU_MOBILE),
CORPS_HTML: mustGetEleInDocument<HTMLBodyElement>("body"),
MENU_MOBILE: mustGetEleInDocument<HTMLDivElement>(SELECTEUR_MENU_MOBILE),
MENU_MOBILE: mustGetEleInDocument<HTMLDivElement>(DOM_MENU_MOBILE),
};
const initialiseBoutonMenuMobile = (): void => {

View file

@ -9,20 +9,20 @@ import {
ATTRIBUT_ID_ENSEMBLE_EPINGLE_BOITE,
CLASS_BOUTON_FERMETURE_BOITE_TEXTE,
CLASS_EPINGLE,
SELECTEUR_BOITE_TEXTE,
SELECTEUR_CONTENEUR_STORYTELLING_A_PROPOS,
SELECTEUR_EPINGLE,
DOM_BOITE_TEXTE,
DOM_CONTENEUR_STORYTELLING_A_PROPOS,
DOM_EPINGLE,
} from "./constantes/dom.ts";
import { mustGetEleInDocument, mustGetElesInDocument } from "./lib/dom.ts";
/** Le Conteneur des images du storytelling. */
const CONTENEUR_STORYTELLING = mustGetEleInDocument<HTMLElement>(
SELECTEUR_CONTENEUR_STORYTELLING_A_PROPOS,
DOM_CONTENEUR_STORYTELLING_A_PROPOS,
);
/** */
const EPINGLES = mustGetElesInDocument<HTMLButtonElement>(SELECTEUR_EPINGLE);
const EPINGLES = mustGetElesInDocument<HTMLButtonElement>(DOM_EPINGLE);
/** */
const BOITES_TEXTE = mustGetElesInDocument<HTMLDivElement>(SELECTEUR_BOITE_TEXTE);
const BOITES_TEXTE = mustGetElesInDocument<HTMLDivElement>(DOM_BOITE_TEXTE);
/** */
const ENSEMBLES_EPINGLES_BOITES_TEXTE = new Map<string, [HTMLButtonElement, HTMLDivElement]>();
A.forEachWithIndex(EPINGLES, (index, epingle) => {

View file

@ -3,78 +3,131 @@
import { A, O, pipe } from "@mobily/ts-belt";
import {
ATTRIBUT_ARIA_HIDDEN,
ATTRIBUT_CACHE,
SELECTEUR_CONTENEUR_STORYTELLING,
SELECTEUR_IMAGES_STORYTELLING,
ATTRIBUT_CACHÉ,
ATTRIBUT_HIDDEN,
DOM_CONTENEUR_ANIMATION,
DOM_CONTENEUR_STORYTELLING,
DOM_GARDE_FOU_JS,
DOM_IMAGES_STORYTELLING,
} from "./constantes/dom.ts";
import { nuLogger } from "./journalisation.ts";
import { mustGetEleInDocument, mustGetElesInDocument } from "./lib/dom.ts";
import { estEntreDeuxNombres } from "./lib/nombres.ts";
const initialiseScrollStorytelling = (): void => {
const E = {
/** Le conteneur des images du storytelling. */
CONTENEUR_STORYTELLING: mustGetEleInDocument<HTMLElement>(".storytelling__conteneur"),
/** Les images du storytelling. */
IMAGES_STORYTELLING: mustGetElesInDocument<HTMLDivElement>(SELECTEUR_IMAGES_STORYTELLING),
/** Le bloc contenant le storytelling. */
STORYTELLING: mustGetEleInDocument<HTMLElement>(SELECTEUR_CONTENEUR_STORYTELLING),
};
const E = {
/** Le bloc contenant l'animation. */
CONTENEUR_ANIMATION: mustGetEleInDocument<HTMLDivElement>(DOM_CONTENEUR_ANIMATION),
/** Le conteneur des images du storytelling. */
CONTENEUR_STORYTELLING: mustGetEleInDocument<HTMLElement>(".storytelling__conteneur"),
/** Les images du storytelling. */
IMAGES_STORYTELLING: mustGetElesInDocument<HTMLDivElement>(DOM_IMAGES_STORYTELLING),
/** Le bloc contenant le storytelling. */
STORYTELLING: mustGetEleInDocument<HTMLElement>(DOM_CONTENEUR_STORYTELLING),
};
/**
* Retire la classe garde-fou `.js` cachant les éléments nécessitant JavaScript pour s'afficher/fonctionner correctement.
*/
const retireClasseGardeFouJs = (): void => {
E.CONTENEUR_ANIMATION.classList.remove(DOM_GARDE_FOU_JS);
};
const initDefilementStorytelling = (): void => {
/** La hauteur d'une image du storytelling. */
let hauteurImage = E.IMAGES_STORYTELLING.at(0)?.getBoundingClientRect().height ?? 0;
let dimensionsImage = {
height: E.IMAGES_STORYTELLING.at(0)?.getBoundingClientRect().height ?? 0,
width: E.IMAGES_STORYTELLING.at(0)?.getBoundingClientRect().width ?? 0,
};
/** La position du défilement (en pixels) du Conteneur des images du storytelling. */
let positionDefilementConteneur = 0;
nuLogger.debug`initStorytellingScroll | dimensionsImages ${dimensionsImage.height}`;
/**
* TODO
* Bascule la visibilité d'une image en
* @param image
* @param visible
* @param estVisible
*/
const changeVisibiliteImage = (image: HTMLDivElement, visible: boolean) => {
image.toggleAttribute(ATTRIBUT_CACHE, visible);
image.toggleAttribute(ATTRIBUT_ARIA_HIDDEN, visible);
const basculeVisibilitéImage = (image: HTMLDivElement, estVisible: boolean) => {
image.toggleAttribute(ATTRIBUT_CACHÉ, estVisible);
};
/**
* TODO
*/
const majDimensions = (): void => {
hauteurImage = pipe(
A.getBy(E.IMAGES_STORYTELLING, (i: HTMLDivElement) => !i.hasAttribute(ATTRIBUT_CACHE)),
O.map((i: HTMLDivElement) => i.getBoundingClientRect().height),
O.getWithDefault(0),
const majDimensionsStorytelling = (): void => {
dimensionsImage = pipe(
A.getBy(E.IMAGES_STORYTELLING, (i: HTMLDivElement) => !i.hasAttribute(ATTRIBUT_CACHÉ)),
O.map((i: HTMLDivElement) => ({
height: i.getBoundingClientRect().height,
width: i.getBoundingClientRect().width,
})),
O.getWithDefault({ height: 0, width: 0 }),
);
E.CONTENEUR_STORYTELLING.style.minHeight = `${String(hauteurImage * E.IMAGES_STORYTELLING.length + 61)}px`;
E.CONTENEUR_STORYTELLING.style.maxHeight = `${String(hauteurImage * E.IMAGES_STORYTELLING.length + 61)}px`;
nuLogger.debug`majDimensions | dimensionsImage ${dimensionsImage}`;
// Adapte la longueur du conteneur d'animation à la nouvelle longueur d'une image.
E.CONTENEUR_ANIMATION.style.inlineSize = `${String(dimensionsImage.width)}px`;
// Adapte la hauteur du conteneur des images pour un défilement « seamless ».
const nouvelleHauteurMax = `${String(dimensionsImage.height * E.IMAGES_STORYTELLING.length + 61)}px`;
E.CONTENEUR_STORYTELLING.style.minHeight = nouvelleHauteurMax;
E.CONTENEUR_STORYTELLING.style.maxHeight = nouvelleHauteurMax;
nuLogger.debug`majDimensions | nouvelleHauteurMax ${nouvelleHauteurMax}`;
};
/**
* TODO
*/
const majImages = (): void => {
// Met à jour la position du défilement dans le Conteneur
const majVisibilitéImagesStorytelling = (): void => {
// Met à jour la position du défilement dans le Conteneur.
positionDefilementConteneur = E.STORYTELLING.scrollTop;
// Met à jour l'attribut de visibilité des images en fonction du défilement
// Met à jour l'attribut de visibilité des images en fonction du défilement.
E.IMAGES_STORYTELLING.forEach((image: HTMLDivElement, index: number): void => {
const debutYImage = hauteurImage * index;
const finYImage = hauteurImage * (index + 1);
const debutYImage = dimensionsImage.height * index;
const finYImage = dimensionsImage.height * (index + 1);
changeVisibiliteImage(image, !estEntreDeuxNombres(positionDefilementConteneur, debutYImage, finYImage));
basculeVisibilitéImage(image, !estEntreDeuxNombres(positionDefilementConteneur, debutYImage, finYImage));
});
};
// Initialise l'Observateur de Redimensionnement (ResizeObserver)
// Initialise l'Observateur de Redimensionnement (ResizeObserver).
new ResizeObserver((): void => {
majDimensions();
majImages();
majDimensionsStorytelling();
majVisibilitéImagesStorytelling();
}).observe(E.STORYTELLING);
// Initialise la mise à jour des images au défilement sur le Conteneur
E.STORYTELLING.addEventListener("scroll", (): void => majImages());
// Initialise la mise à jour des images au défilement sur le Conteneur.
E.STORYTELLING.addEventListener("scroll", (): void => majVisibilitéImagesStorytelling());
};
const initGestionAnimation = (): void => {
pipe(
A.at(E.IMAGES_STORYTELLING, 0),
O.tap(img => {
const options: IntersectionObserverInit = {
root: null,
rootMargin: "0px",
threshold: 0,
};
const callback = (entries: Array<IntersectionObserverEntry>) => {
A.forEach(entries, e => {
e.intersectionRatio === 1
? E.CONTENEUR_ANIMATION.removeAttribute(ATTRIBUT_HIDDEN)
: E.CONTENEUR_ANIMATION.setAttribute(ATTRIBUT_HIDDEN, "");
nuLogger.debug`initGestionAnimation | estCache ${e.intersectionRatio === 1} | ${e}`;
});
};
new IntersectionObserver(callback, options).observe(img);
}),
);
};
document.addEventListener("DOMContentLoaded", (): void => {
initialiseScrollStorytelling();
retireClasseGardeFouJs();
initDefilementStorytelling();
initGestionAnimation();
});

View file

@ -19,8 +19,8 @@ import {
ATTRIBUT_HIDDEN,
ATTRIBUT_ID_CATEGORIE_PRODUITS,
ATTRIBUT_PAGE,
SELECTEUR_BOUTON_PLUS_PRODUITS,
SELECTEUR_GRILLE_PRODUITS,
DOM_BOUTON_PLUS_PRODUITS,
DOM_GRILLE_PRODUITS,
} from "./constantes/dom.ts";
import { lanceAnimationCycleLoading } from "./lib/animations.ts";
import { html, mustGetEleInDocument } from "./lib/dom.ts";
@ -43,8 +43,8 @@ const PRODUCTS_PER_PAGE = 12;
// Éléments d'intérêt
const E = {
BOUTON_PLUS_DE_PRODUITS: mustGetEleInDocument<HTMLButtonElement>(SELECTEUR_BOUTON_PLUS_PRODUITS),
GRILLE_PRODUITS: mustGetEleInDocument<HTMLDivElement>(SELECTEUR_GRILLE_PRODUITS),
BOUTON_PLUS_DE_PRODUITS: mustGetEleInDocument<HTMLButtonElement>(DOM_BOUTON_PLUS_PRODUITS),
GRILLE_PRODUITS: mustGetEleInDocument<HTMLDivElement>(DOM_GRILLE_PRODUITS),
};
/**

View file

@ -13,11 +13,11 @@ import {
ATTRIBUT_CONTIENT_ARTICLES,
ATTRIBUT_DESACTIVE,
ATTRIBUT_HIDDEN,
SELECTEUR_BOUTON_ADDITION_QUANTITE,
SELECTEUR_BOUTON_SOUSTRACTION_QUANTITE,
SELECTEUR_BOUTON_SUPPRESSION_PANIER,
SELECTEUR_CHAMP_QUANTITE_LIGNE_PANIER,
SELECTEUR_PRIX_LIGNE_PANIER,
DOM_BOUTON_ADDITION_QUANTITE,
DOM_BOUTON_SOUSTRACTION_QUANTITE,
DOM_BOUTON_SUPPRESSION_PANIER,
DOM_CHAMP_QUANTITE_LIGNE_PANIER,
DOM_PRIX_LIGNE_PANIER,
} from "./constantes/dom.ts";
import { NOM_CANAL_BOUTON_PANIER, NOM_CANAL_CONTENU_PANIER } from "./constantes/messages.ts";
import { getDOMElementsWithSelector, recupereElementAvecSelecteur, recupereElementOuLeve } from "./lib/dom.ts";
@ -73,10 +73,10 @@ const majEtatsActivationBoutons = (entrees: Array<HTMLElement>): void =>
const recupereElementDansEntree = recupereElementDansEntreePanierOuLeve(entree);
const elements: ElementsEntreePanier = {
boutonAddition: recupereElementDansEntree<HTMLButtonElement>(SELECTEUR_BOUTON_ADDITION_QUANTITE),
boutonSoustraction: recupereElementDansEntree<HTMLButtonElement>(SELECTEUR_BOUTON_SOUSTRACTION_QUANTITE),
boutonSuppression: recupereElementDansEntree<HTMLButtonElement>(SELECTEUR_BOUTON_SUPPRESSION_PANIER),
champQuantite: recupereElementDansEntree<HTMLInputElement>(SELECTEUR_CHAMP_QUANTITE_LIGNE_PANIER),
boutonAddition: recupereElementDansEntree<HTMLButtonElement>(DOM_BOUTON_ADDITION_QUANTITE),
boutonSoustraction: recupereElementDansEntree<HTMLButtonElement>(DOM_BOUTON_SOUSTRACTION_QUANTITE),
boutonSuppression: recupereElementDansEntree<HTMLButtonElement>(DOM_BOUTON_SUPPRESSION_PANIER),
champQuantite: recupereElementDansEntree<HTMLInputElement>(DOM_CHAMP_QUANTITE_LIGNE_PANIER),
};
Number(elements.champQuantite?.value) === 1
@ -115,9 +115,9 @@ const initialiseMajContenuPanier = (): void => {
const recupereElementDansEntree = recupereElementDansEntreePanierOuLeve(entree);
// Récupère les Éléments à mettre à jour
const prixLigne = recupereElementDansEntree<HTMLSpanElement>(SELECTEUR_PRIX_LIGNE_PANIER);
const prixLigne = recupereElementDansEntree<HTMLSpanElement>(DOM_PRIX_LIGNE_PANIER);
const champQuantite = recupereElementDansEntree<HTMLInputElement>(
SELECTEUR_CHAMP_QUANTITE_LIGNE_PANIER,
DOM_CHAMP_QUANTITE_LIGNE_PANIER,
);
// Met à jour les valeurs

View file

@ -20,11 +20,11 @@ import {
ATTRIBUT_DESACTIVE,
ATTRIBUT_HIDDEN,
ATTRIBUT_PRIX,
SELECTEUR_BOUTON_AJOUT_PANIER,
SELECTEUR_BOUTONS_ACCORDEON,
SELECTEUR_CONTENUS_ACCORDEON,
SELECTEUR_PRIX_PRODUIT,
SELECTEUR_SELECTEUR_QUANTITE,
DOM_BOUTON_AJOUT_PANIER,
DOM_BOUTONS_ACCORDEON,
DOM_CONTENUS_ACCORDEON,
DOM_PRIX_PRODUIT,
DOM_DOM_QUANTITE,
} from "./constantes/dom.ts";
import { lanceAnimationCycleLoading } from "./lib/animations.ts";
import { mustGetEleInDocument, mustGetElesInDocument, recupereElementDocumentEither } from "./lib/dom.ts";
@ -61,11 +61,11 @@ const deplieToutesSections = (ensembleLiensContenus: Array<EnsembleLienContenu>)
// Éléments d'intérêt
const E = {
BOUTON_AJOUT_PANIER: mustGetEleInDocument<HTMLButtonElement>(SELECTEUR_BOUTON_AJOUT_PANIER),
BOUTONS_ACCORDEON: mustGetElesInDocument<HTMLAnchorElement>(SELECTEUR_BOUTONS_ACCORDEON),
CONTENUS_ACCORDEON: mustGetElesInDocument<HTMLDivElement>(SELECTEUR_CONTENUS_ACCORDEON),
PRIX_PRODUIT: mustGetEleInDocument<HTMLParagraphElement>(SELECTEUR_PRIX_PRODUIT),
SELECTEUR_VARIATION: recupereElementDocumentEither<HTMLSelectElement>(SELECTEUR_SELECTEUR_QUANTITE),
BOUTON_AJOUT_PANIER: mustGetEleInDocument<HTMLButtonElement>(DOM_BOUTON_AJOUT_PANIER),
BOUTONS_ACCORDEON: mustGetElesInDocument<HTMLAnchorElement>(DOM_BOUTONS_ACCORDEON),
CONTENUS_ACCORDEON: mustGetElesInDocument<HTMLDivElement>(DOM_CONTENUS_ACCORDEON),
PRIX_PRODUIT: mustGetEleInDocument<HTMLParagraphElement>(DOM_PRIX_PRODUIT),
DOM_VARIATION: recupereElementDocumentEither<HTMLSelectElement>(DOM_DOM_QUANTITE),
};
const gereAccordeonDetailsProduit = (): void => {
@ -99,7 +99,7 @@ const gereAccordeonDetailsProduit = (): void => {
});
// Ajoute des Écouteurs d'Événements
E.SELECTEUR_VARIATION.ifRight((selecteur): void =>
E.DOM_VARIATION.ifRight((selecteur): void =>
selecteur.addEventListener("change", (evenement: Event): void => {
const cibleSelecteur: Maybe<HTMLSelectElement> = Maybe
.fromNullable(evenement.target)
@ -125,7 +125,7 @@ const gereAccordeonDetailsProduit = (): void => {
const ajouteProduitAuPanier = (): void => {
// Construis les arguments de la requête au backend
const argsRequete: WCStoreCartAddItemArgs = {
id: E.SELECTEUR_VARIATION
id: E.DOM_VARIATION
.map((selecteur: HTMLSelectElement): number => Number(selecteur.value))
// Récupère l'ID du Produit de la Page pour les Produits simples
.orDefault(ETATS_PAGE.idProduit),

View file

@ -2,21 +2,117 @@
{% import "macros/images.twig" as images %}
{% block contenu %}
<main id="page-accueil">
<main
id="page-accueil"
aria-label="Scroll down to navigate through the pictures"
>
<div class="storytelling">
<div class="storytelling__conteneur">
<div
class="storytelling__image"
data-index="0"
>
{{
images.genere_source_img_multi_formats("#{ site.theme.link }/assets/img/storytelling/scroll0", "", 903, 1080, "image-scroll0")
}}
<div
aria-hidden="true"
class="storytelling__conteneur"
inert
>
{# Animation #}
<div class="storytelling__animation no-js">
<svg
class="animation-conteneur"
height="90px"
preserveAspectRatio="xMidYMin"
viewBox="0 0 1200 90"
width="100%"
xmlns="http://www.w3.org/2000/svg"
>
<svg
y="50%"
class="animation-texte"
>
<path
d="m0 0c600-90 600 90 1200 0"
fill="transparent"
id="curve-1"
/>
{# TODO: Créer une fonction pour générer les images #}
<text dominant-baseline="middle">
<textPath
id="text-path-1"
xlink:href="#curve-1"
>
Scroll down
</textPath>
<animate
attributeName="startOffset"
dur="5s"
fill="remove"
from="-50%"
to="0%"
repeatCount="indefinite"
xlink:href="#text-path-1"
/>
</text>
<text dominant-baseline="middle">
<textPath
id="text-path-2"
xlink:href="#curve-1"
>
Scroll down
</textPath>
<animate
attributeName="startOffset"
dur="5s"
fill="remove"
from="0%"
to="50%"
repeatCount="indefinite"
xlink:href="#text-path-2"
/>
</text>
<text dominant-baseline="middle">
<textPath
id="text-path-3"
xlink:href="#curve-1"
>
Scroll down
</textPath>
<animate
attributeName="startOffset"
dur="5s"
fill="remove"
from="50%"
to="100%"
repeatCount="indefinite"
xlink:href="#text-path-3"
/>
</text>
<text dominant-baseline="middle">
<textPath
id="text-path-4"
xlink:href="#curve-1"
>
Scroll down
</textPath>
<animate
attributeName="startOffset"
dur="5s"
fill="remove"
from="100%"
to="150%"
repeatCount="indefinite"
xlink:href="#text-path-4"
/>
</text>
</svg>
</svg>
</div>
{# Images #}
<div
class="storytelling__image"
data-index="1"
tabindex="-1"
>
{{
images.genere_source_img_multi_formats("#{ site.theme.link }/assets/img/storytelling/scroll1", "", 903, 1080, "image-scroll1")
@ -24,10 +120,10 @@
</div>
<div
aria-hidden
class="storytelling__image"
data-cache
data-caché
data-index="2"
tabindex="-1"
>
{{
images.genere_source_img_multi_formats("#{ site.theme.link }/assets/img/storytelling/scroll2", "", 903, 1080, "image-scroll2")
@ -35,10 +131,10 @@
</div>
<div
aria-hidden
class="storytelling__image"
data-cache
data-caché
data-index="3"
tabindex="-1"
>
{{
images.genere_source_img_multi_formats("#{ site.theme.link }/assets/img/storytelling/scroll3", "", 903, 1080, "image-scroll3")
@ -46,10 +142,10 @@
</div>
<div
aria-hidden
class="storytelling__image"
data-cache
data-caché
data-index="4"
tabindex="-1"
>
{{
images.genere_source_img_multi_formats("#{ site.theme.link }/assets/img/storytelling/scroll4", "", 903, 1080, "image-scroll4")
@ -57,10 +153,10 @@
</div>
<div
aria-hidden
class="storytelling__image"
data-cache
data-caché
data-index="5"
tabindex="-1"
>
{{
images.genere_source_img_multi_formats("#{ site.theme.link }/assets/img/storytelling/scroll5", "", 903, 1080, "image-scroll5")
@ -68,10 +164,10 @@
</div>
<div
aria-hidden
class="storytelling__image"
data-cache
data-caché
data-index="6"
tabindex="-1"
>
{{
images.genere_source_img_multi_formats("#{ site.theme.link }/assets/img/storytelling/scroll6", "", 903, 1080, "image-scroll6")
@ -79,10 +175,10 @@
</div>
<div
aria-hidden
class="storytelling__image"
data-cache
data-caché
data-index="7"
tabindex="-1"
>
{{
images.genere_source_img_multi_formats("#{ site.theme.link }/assets/img/storytelling/scroll7", "", 903, 1080, "image-scroll7")
@ -90,10 +186,10 @@
</div>
<div
aria-hidden
class="storytelling__image"
data-cache
data-caché
data-index="8"
tabindex="-1"
>
{{
images.genere_source_img_multi_formats("#{ site.theme.link }/assets/img/storytelling/scroll8", "", 903, 1080, "image-scroll8")
@ -101,10 +197,10 @@
</div>
<div
aria-hidden
class="storytelling__image"
data-cache
data-caché
data-index="9"
tabindex="-1"
>
{{
images.genere_source_img_multi_formats("#{ site.theme.link }/assets/img/storytelling/scroll9", "", 903, 1080, "image-scroll9")
@ -112,10 +208,10 @@
</div>
<div
aria-hidden
class="storytelling__image"
data-cache
data-caché
data-index="10"
tabindex="-1"
>
{{
images.genere_source_img_multi_formats("#{ site.theme.link }/assets/img/storytelling/scroll10", "", 903, 1080, "image-scroll10")
@ -123,10 +219,10 @@
</div>
<div
aria-hidden
class="storytelling__image"
data-cache
data-caché
data-index="11"
tabindex="-1"
>
{{
images.genere_source_img_multi_formats("#{ site.theme.link }/assets/img/storytelling/scroll11", "", 903, 1080, "image-scroll11")
@ -134,10 +230,10 @@
</div>
<div
aria-hidden
class="storytelling__image"
data-cache
data-caché
data-index="12"
tabindex="-1"
>
{{
images.genere_source_img_multi_formats("#{ site.theme.link }/assets/img/storytelling/scroll12", "", 903, 1080, "image-scroll12")
@ -145,10 +241,10 @@
</div>
<div
aria-hidden
class="storytelling__image"
data-cache
data-caché
data-index="13"
tabindex="-1"
>
{{
images.genere_source_img_multi_formats("#{ site.theme.link }/assets/img/storytelling/scroll13", "", 903, 1080, "image-scroll13")

View file

@ -26,7 +26,7 @@
id="bouton-plus-de-produits"
type="button"
>
Show more products
Show more
</button>
</div>
</main>

View file

@ -16,8 +16,7 @@
</button>
</section>
{# TODO: Utiliser un Menu WordPress #}
{# TODO: Utiliser des <span> À L'INTÉRIEUR de <li> #}
{# TODO: Utiliser un Menu WordPress ? #}
<nav
class="menu-navigation"
id="menu-navigation-en-tete"
@ -28,6 +27,7 @@
>
<span>
<a
{{ page_courante == pages.home.lien ? "aria-current=page" : ""}}
class="lien-menu"
href="{{ pages.home.lien }}"
>
@ -40,6 +40,7 @@
>
<span>
<a
{{ est_page_boutique ? "aria-current=page" : ""}}
class="lien-menu"
href="{{ pages.shop.lien }}"
>
@ -52,6 +53,7 @@
>
<span>
<a
{{ page_courante == pages.about.lien ? "aria-current=page" : ""}}
class="lien-menu"
href="{{ pages.about.lien }}"
>
@ -65,6 +67,7 @@
>
<span>
<a
{{ page_courante == pages.contact.lien ? "aria-current=page" : ""}}
class="lien-menu"
href="{{ pages.contact.lien }}"
>
@ -110,6 +113,7 @@
>
<span>
<a
{{ page_courante == pages.home.lien ? "aria-current=page" : ""}}
class="lien-menu"
href="{{ pages.home.lien }}"
>
@ -123,6 +127,7 @@
>
<span>
<a
{{ est_page_boutique ? "aria-current=page" : ""}}
class="lien-menu"
href="{{ pages.shop.lien }}"
>
@ -136,6 +141,7 @@
>
<span>
<a
{{ page_courante == pages.about.lien ? "aria-current=page" : ""}}
class="lien-menu"
href="{{ pages.about.lien }}"
>
@ -149,6 +155,7 @@
>
<span>
<a
{{ page_courante == pages.contact.lien ? "aria-current=page" : ""}}
class="lien-menu"
href="{{ pages.contact.lien }}"
>