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)}
-
+
);
}