diff --git a/custom_components/cookcli/www/cookcli-card.js b/custom_components/cookcli/www/cookcli-card.js
index a954aa4..19ffea3 100644
--- a/custom_components/cookcli/www/cookcli-card.js
+++ b/custom_components/cookcli/www/cookcli-card.js
@@ -636,8 +636,151 @@ class CookCliCardEditor extends HTMLElement {
}
}
+/**
+ * Checklist d'ingrédients d'une étape (cases à cocher, état local).
+ *
+ * L'état coché est stocké dans localStorage du navigateur, par recette et par
+ * étape (`storage_key`) : il n'est pas partagé entre appareils, ni lié à la
+ * todo du Résumé. Config :
+ * type: custom:cookcli-checklist-card
+ * storage_key: chocolat-chaud.cook:2
+ * items: [{ quantity: "200 ml", name: "lait" }, ...]
+ */
+class CookCliChecklistCard extends HTMLElement {
+ setConfig(config) {
+ if (!config || !Array.isArray(config.items)) {
+ throw new Error("La carte a besoin d'une liste `items`.");
+ }
+ this._config = config;
+ this._checked = this._load();
+
+ if (!this.shadowRoot) {
+ this.attachShadow({ mode: "open" });
+ this.shadowRoot.addEventListener("change", (ev) => this._onChange(ev));
+ this.shadowRoot.addEventListener("click", (ev) => this._onClick(ev));
+ }
+ this._render();
+ }
+
+ getCardSize() {
+ return (this._config?.items?.length || 0) + 1;
+ }
+
+ _storageKey() {
+ return `cookcli-checklist:${this._config.storage_key || "default"}`;
+ }
+
+ // Identifiant d'une ligne : index + libellé, pour rester correct si deux
+ // lignes ont le même nom ou si la recette est modifiée.
+ _itemId(item, index) {
+ return `${index}:${item.quantity || ""} ${item.name || ""}`.trim();
+ }
+
+ _load() {
+ try {
+ const raw = localStorage.getItem(this._storageKey());
+ const parsed = raw ? JSON.parse(raw) : [];
+ return new Set(Array.isArray(parsed) ? parsed : []);
+ } catch (err) {
+ return new Set();
+ }
+ }
+
+ _save() {
+ try {
+ localStorage.setItem(this._storageKey(), JSON.stringify([...this._checked]));
+ } catch (err) {
+ // localStorage indisponible : l'état reste valable jusqu'au prochain rendu.
+ }
+ }
+
+ _onChange(ev) {
+ const input = ev.target.closest("input[data-id]");
+ if (!input) return;
+ if (input.checked) this._checked.add(input.dataset.id);
+ else this._checked.delete(input.dataset.id);
+ this._save();
+ this._render();
+ }
+
+ _onClick(ev) {
+ if (ev.target.closest("[data-reset]")) {
+ this._checked.clear();
+ this._save();
+ this._render();
+ }
+ }
+
+ _escape(str) {
+ const div = document.createElement("div");
+ div.textContent = str ?? "";
+ return div.innerHTML;
+ }
+
+ _render() {
+ if (!this.shadowRoot) return;
+ const items = this._config.items;
+ const anyChecked = items.some((item, i) => this._checked.has(this._itemId(item, i)));
+
+ const rows = items
+ .map((item, i) => {
+ const id = this._itemId(item, i);
+ const checked = this._checked.has(id);
+ return `
+ `;
+ })
+ .join("");
+
+ this.shadowRoot.innerHTML = `
+
+
+ ${rows}
+
+
+ `;
+ }
+}
+
customElements.define("cookcli-card", CookCliCard);
customElements.define("cookcli-card-editor", CookCliCardEditor);
+customElements.define("cookcli-checklist-card", CookCliChecklistCard);
window.customCards = window.customCards || [];
window.customCards.push({
diff --git a/custom_components/cookcli/www/cookcli-recipe-strategy.js b/custom_components/cookcli/www/cookcli-recipe-strategy.js
index 8c8feb0..cff2d40 100644
--- a/custom_components/cookcli/www/cookcli-recipe-strategy.js
+++ b/custom_components/cookcli/www/cookcli-recipe-strategy.js
@@ -388,13 +388,17 @@ class CookCliRecipeViewStrategy extends HTMLElement {
const leftCards = [];
const stepIngredients = (step.items || []).filter((item) => item.type === "ingredient");
if (stepIngredients.length) {
- const lines = stepIngredients.map((item) => {
- const qty = item.quantity ?? ""
- ? `${item.quantity.value ?? ""} ${item.quantity.unit ?? ""}`.trim()
- : "";
- return `- ${qty ? `**${qty}** ` : ""}${item.name ?? ""}`;
+ leftCards.push({
+ type: "custom:cookcli-checklist-card",
+ // recette + étape : l'état coché est propre à chaque étape de chaque recette
+ storage_key: `${config.path}:${tabIndex}`,
+ items: stepIngredients.map((item) => ({
+ quantity: item.quantity
+ ? `${item.quantity.value ?? ""} ${item.quantity.unit ?? ""}`.trim()
+ : "",
+ name: item.name ?? "",
+ })),
});
- leftCards.push({ type: "markdown", content: lines.join("\n") });
}
if (config.timer_entity) {