From 73e84ee7039db8bee944700197ec48600c8cbd7f Mon Sep 17 00:00:00 2001 From: Shinuwa Date: Mon, 24 Aug 2026 21:54:43 +0200 Subject: [PATCH] Split combos list rendering from module state --- tests/static-toolboxes.test.mjs | 6 +- .../features/toolboxes/modules/CombosList.jsx | 139 +++++++++++++++++ .../toolboxes/modules/CombosModule.jsx | 145 +++--------------- 3 files changed, 168 insertions(+), 122 deletions(-) create mode 100644 website/src/features/toolboxes/modules/CombosList.jsx diff --git a/tests/static-toolboxes.test.mjs b/tests/static-toolboxes.test.mjs index 7813019..a3eba93 100644 --- a/tests/static-toolboxes.test.mjs +++ b/tests/static-toolboxes.test.mjs @@ -141,6 +141,8 @@ test("toolbox module registry and modules expose expected behavior", async () => const notepadModule = await readFile("website/src/features/toolboxes/modules/NotepadModule.jsx", "utf8"); const combosModule = await readFile("website/src/features/toolboxes/modules/CombosModule.jsx", "utf8"); const combosParts = await readFile("website/src/features/toolboxes/modules/CombosParts.jsx", "utf8"); + const combosList = await readFile("website/src/features/toolboxes/modules/CombosList.jsx", "utf8"); + const combosBundle = `${combosModule}\n${combosParts}\n${combosList}`; const combosUtils = await readFile("website/src/features/toolboxes/modules/combosUtils.js", "utf8"); const calculatorModule = await readFile("website/src/features/toolboxes/modules/CalculatorModule.jsx", "utf8"); const tableModule = await readFile("website/src/features/toolboxes/modules/TableModule.jsx", "utf8"); @@ -249,8 +251,8 @@ test("toolbox module registry and modules expose expected behavior", async () => assert.match(combosModule, /useGroupedReorder/); assert.match(combosModule, /reorderFeatures/); assert.match(combosModule, /namespace: `combos-\$\{moduleId\}`/); - assert.match(combosModule, /getGroupBoundaryProps/); - assert.match(combosModule, /combos-category-boundary-drop-zone/); + assert.match(combosBundle, /getGroupBoundaryProps/); + assert.match(combosBundle, /combos-category-boundary-drop-zone/); assert.match(combosUtils, /function setComboItemCategory/); assert.match(combosModule, /setItemGroup: setComboItemCategory/); assert.doesNotMatch(combosModule, /canMoveItem:/); diff --git a/website/src/features/toolboxes/modules/CombosList.jsx b/website/src/features/toolboxes/modules/CombosList.jsx new file mode 100644 index 0000000..bf51a89 --- /dev/null +++ b/website/src/features/toolboxes/modules/CombosList.jsx @@ -0,0 +1,139 @@ +// Rôle : rend les combos enregistrés, leurs catégories et les contrôles de réorganisation. +import { Icon } from "../../../components/Icon.jsx"; +import { ComboSequence, InlineTextInput } from "./CombosParts.jsx"; +import { getComboCategory } from "./combosUtils.js"; + +export function CombosList({ + entries, + data, + textContent, + reorder, + onComboUpdate, + onComboEdit, + onComboDelete, + onCategoryToggle +}) { + const categoryCount = entries.filter((entry) => entry.type === "group").length; + + function renderCombo(combo) { + const category = getComboCategory(combo); + const comboClassName = [ + "combo-card", + "is-editing", + reorder.isItemDragging(combo.id) ? "is-dragging" : "", + reorder.isItemDropTarget(combo.id) ? "is-drop-target" : "", + reorder.getDropPlacement("item", combo.id) === "after" ? "drop-after" : "" + ].filter(Boolean).join(" "); + return ( +
+ +
+ onComboUpdate(combo.id, (current) => ({ ...current, name: name || current.name }))} + ariaLabel={textContent.comboNameLabel || "Nom"} + /> + +
+
+ + +
+
+ ); + } + + function renderCategory(group, index, groupCount) { + const category = group.category; + const isCollapsed = data.collapsedCategories.includes(category); + const isLastCategory = index === groupCount - 1; + const categoryClassName = [ + "combos-category checklist-section is-grouped", + isCollapsed ? "is-collapsed" : "", + reorder.isGroupDragging(category) ? "is-dragging" : "", + reorder.isGroupDropTarget(category) ? "is-drop-target" : "", + reorder.getDropPlacement("group", category) === "after" ? "drop-after" : "" + ].filter(Boolean).join(" "); + const showBoundaryDropZones = reorder.shouldShowGroupBoundaries(); + const beforeDropZone = showBoundaryDropZones ? ( +
+ ) : null; + const afterDropZone = showBoundaryDropZones ? ( +
+ ) : null; + return [ + beforeDropZone, +
+
+
+ +

{category}

+
+
+ {group.combos.length} + +
+
+ {!isCollapsed && ( +
+ {group.combos.map((combo) => renderCombo(combo))} +
+ )} +
, + showBoundaryDropZones && isLastCategory ? afterDropZone : null + ]; + } + + function getCategoryIndex(entryIndex) { + return entries.slice(0, entryIndex + 1).filter((entry) => entry.type === "group").length - 1; + } + + function renderEntry(entry, entryIndex) { + if (entry.type !== "group") return renderCombo(entry.item); + return renderCategory(entry.group, getCategoryIndex(entryIndex), categoryCount); + } + + return ( +
+ {entries.map(renderEntry)} +
+ ); +} diff --git a/website/src/features/toolboxes/modules/CombosModule.jsx b/website/src/features/toolboxes/modules/CombosModule.jsx index aa3bb53..de394cd 100644 --- a/website/src/features/toolboxes/modules/CombosModule.jsx +++ b/website/src/features/toolboxes/modules/CombosModule.jsx @@ -7,7 +7,8 @@ import { useGroupedReorder } from "../../../hooks/useGroupedReorder.js"; import { TextExchangeActions } from "./TextExchangeActions.jsx"; -import { ComboInputToken, ComboSequence, InlineTextInput } from "./CombosParts.jsx"; +import { ComboInputToken, ComboSequence } from "./CombosParts.jsx"; +import { CombosList } from "./CombosList.jsx"; import { DEVICE_OPTIONS, createCombo, @@ -322,122 +323,6 @@ export function CombosModule({ toolboxId, moduleId, context, editing, textExchan save({ ...data, collapsedCategories }); } - function renderCombo(combo) { - const category = getComboCategory(combo); - const comboClassName = [ - "combo-card", - "is-editing", - isItemDragging(combo.id) ? "is-dragging" : "", - isItemDropTarget(combo.id) ? "is-drop-target" : "", - getDropPlacement("item", combo.id) === "after" ? "drop-after" : "" - ].filter(Boolean).join(" "); - return ( -
- -
- updateCombo(combo.id, (current) => ({ ...current, name: name || current.name }))} - ariaLabel={textContent.comboNameLabel || "Nom"} - /> - -
-
- - -
-
- ); - } - - function renderCategory(group, index, groupCount) { - const category = group.category; - const isCollapsed = data.collapsedCategories.includes(category); - const isLastCategory = index === groupCount - 1; - const categoryClassName = [ - "combos-category checklist-section is-grouped", - isCollapsed ? "is-collapsed" : "", - isGroupDragging(category) ? "is-dragging" : "", - isGroupDropTarget(category) ? "is-drop-target" : "", - getDropPlacement("group", category) === "after" ? "drop-after" : "" - ].filter(Boolean).join(" "); - const showBoundaryDropZones = shouldShowGroupBoundaries(); - const beforeDropZone = showBoundaryDropZones ? ( -
- ) : null; - const afterDropZone = showBoundaryDropZones ? ( -
- ) : null; - return [ - beforeDropZone, -
-
-
- -

{category}

-
-
- {group.combos.length} - -
-
- {!isCollapsed && ( -
- {group.combos.map((combo) => renderCombo(combo))} -
- )} -
, - showBoundaryDropZones && isLastCategory ? afterDropZone : null - ]; - } - - const categoryCount = entries.filter((entry) => entry.type === "group").length; - let renderedCategoryIndex = -1; - - function renderEntry(entry) { - if (entry.type !== "group") return renderCombo(entry.item); - renderedCategoryIndex += 1; - return renderCategory(entry.group, renderedCategoryIndex, categoryCount); - } - return (
{(editing || editingComboId) && ( @@ -601,9 +486,29 @@ export function CombosModule({ toolboxId, moduleId, context, editing, textExchan

{textContent.emptyCombos || "Aucun combo enregistré."}

)} -
- {entries.map(renderEntry)} -
+
); }