From 4ad094dc5c756a42113067bcec5ecb914d5d6c8c Mon Sep 17 00:00:00 2001 From: Antonin Cezard Date: Thu, 20 Feb 2025 10:01:45 +0100 Subject: [PATCH 1/4] Create a deck Fixes #1 Add functionality to create and manage decks with unique names, save to local storage, and switch between decks using tabs. * **src/App.tsx** - Add a prompt to ask the user for a deck name when creating a new deck. - Assign a default name "New Deck" if the user does not provide a name. - Save the new deck to local storage using `localStorage.setItem`. - Load decks from local storage on component mount using `localStorage.getItem`. * **src/components/DeckTab.tsx** - Add tabs for each deck for quick switching. - Display deck stats in a tabbed interface. * **src/App.spec.tsx** - Add tests for creating a new deck with a unique name. - Add tests for saving the deck to local storage. - Add tests for loading decks from local storage on component mount. --- For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/acezard/warpforge-tracker/issues/1?shareId=XXXX-XXXX-XXXX-XXXX). --- src/App.spec.tsx | 34 ++++- src/App.tsx | 17 ++- src/components/DeckTab.tsx | 246 +++++++++++++++++++++---------------- 3 files changed, 186 insertions(+), 111 deletions(-) diff --git a/src/App.spec.tsx b/src/App.spec.tsx index 42fff82..2bef64b 100644 --- a/src/App.spec.tsx +++ b/src/App.spec.tsx @@ -1,6 +1,38 @@ -import { render } from "@testing-library/react"; +import { render, screen, fireEvent } from "@testing-library/react"; import App from "./App"; test("renders", () => { render(); }); + +test("creates a new deck with a unique name", () => { + render(); + const addButton = screen.getByText("Add New Deck"); + fireEvent.click(addButton); + const promptSpy = jest.spyOn(window, "prompt").mockReturnValue("Unique Deck"); + fireEvent.click(addButton); + expect(screen.getByText("Unique Deck")).toBeInTheDocument(); + promptSpy.mockRestore(); +}); + +test("saves the deck to local storage", () => { + render(); + const addButton = screen.getByText("Add New Deck"); + fireEvent.click(addButton); + const promptSpy = jest.spyOn(window, "prompt").mockReturnValue("Saved Deck"); + fireEvent.click(addButton); + expect(localStorage.setItem).toHaveBeenCalledWith( + "decks", + JSON.stringify([{ deckName: "Saved Deck", factions: expect.any(Array) }]) + ); + promptSpy.mockRestore(); +}); + +test("loads decks from local storage on component mount", () => { + const savedDecks = [ + { deckName: "Loaded Deck", factions: [] }, + ]; + jest.spyOn(localStorage, "getItem").mockReturnValue(JSON.stringify(savedDecks)); + render(); + expect(screen.getByText("Loaded Deck")).toBeInTheDocument(); +}); diff --git a/src/App.tsx b/src/App.tsx index f72c273..505a1c0 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,4 @@ -// src/App.tsx -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; import { Deck } from "./models/types"; import { deepCopyMasterTable } from "./models/masterData"; import DeckTab from "./components/DeckTab"; @@ -8,12 +7,22 @@ import { Container, Typography, Button, Box } from "@mui/material"; const App: React.FC = () => { const [decks, setDecks] = useState([]); + useEffect(() => { + const savedDecks = localStorage.getItem("decks"); + if (savedDecks) { + setDecks(JSON.parse(savedDecks)); + } + }, []); + const handleAddDeck = () => { + const deckName = prompt("Enter deck name:", "New Deck") || "New Deck"; const newDeck: Deck = { - deckName: "New Deck", + deckName, factions: deepCopyMasterTable(), }; - setDecks([...decks, newDeck]); + const updatedDecks = [...decks, newDeck]; + setDecks(updatedDecks); + localStorage.setItem("decks", JSON.stringify(updatedDecks)); }; return ( diff --git a/src/components/DeckTab.tsx b/src/components/DeckTab.tsx index 9c71eee..87a7f05 100644 --- a/src/components/DeckTab.tsx +++ b/src/components/DeckTab.tsx @@ -8,6 +8,9 @@ import { TableRow, Paper, Typography, + Tabs, + Tab, + Box, } from "@mui/material"; import { Deck, Faction, Warlord } from "../models/types"; @@ -25,120 +28,151 @@ interface DeckTabProps { } const DeckTab: React.FC = ({ deck }) => { + const [selectedTab, setSelectedTab] = React.useState(0); + + const handleChange = (event: React.SyntheticEvent, newValue: number) => { + setSelectedTab(newValue); + }; + return ( {deck.deckName} - - - - - - Faction - - - Warlord - - - Matches - - - Off. Wins - - - Off. Losses - - - Def. Wins - - - Def. Losses - - - Total Wins - - - Total Losses - - - Win Rate - - - - - {deck.factions.map((faction: Faction) => { - const numberOfWarlords = faction.warlords.length; - - return faction.warlords.map((warlord: Warlord, idx: number) => { - const { matches, totalWins, totalLosses, winRate } = - getWarlordStats(warlord); - - // Color-coded column styles - const offWinStyle = { backgroundColor: "#bfb" }; - const offLossStyle = { backgroundColor: "#fbb" }; - const defWinStyle = { backgroundColor: "#bfb" }; - const defLossStyle = { backgroundColor: "#fbb" }; - const totalWinStyle = { backgroundColor: "#dfd" }; - const totalLossStyle = { backgroundColor: "#fdd" }; - const rateStyle = { backgroundColor: "#eee" }; - - return ( - - {/* - Only render the Faction cell for the first warlord in this faction - so it spans multiple rows. - */} - {idx === 0 && ( + + {deck.factions.map((faction, index) => ( + + ))} + + + {deck.factions.map((faction, index) => ( +
+ + - {faction.factionName} + Warlord - )} - - {/* Warlord Name */} - - {warlord.warlordName} - - - {/* Matches */} - {matches} - - {/* Off. Wins */} - {warlord.offWins} - - {/* Off. Losses */} - {warlord.offLosses} - - {/* Def. Wins */} - {warlord.defWins} - - {/* Def. Losses */} - {warlord.defLosses} - - {/* Total Wins */} - {totalWins} - - {/* Total Losses */} - {totalLosses} - - {/* Win Rate */} - {winRate.toFixed(1)}% - - ); - }); - })} - -
-
+ + Matches + + + Off. Wins + + + Off. Losses + + + Def. Wins + + + Def. Losses + + + Total Wins + + + Total Losses + + + Win Rate + + + + + {faction.warlords.map((warlord: Warlord) => { + const { matches, totalWins, totalLosses, winRate } = + getWarlordStats(warlord); + + // Color-coded column styles + const offWinStyle = { backgroundColor: "#bfb" }; + const offLossStyle = { backgroundColor: "#fbb" }; + const defWinStyle = { backgroundColor: "#bfb" }; + const defLossStyle = { backgroundColor: "#fbb" }; + const totalWinStyle = { backgroundColor: "#dfd" }; + const totalLossStyle = { backgroundColor: "#fdd" }; + const rateStyle = { backgroundColor: "#eee" }; + + return ( + + {/* Warlord Name */} + + {warlord.warlordName} + + + {/* Matches */} + {matches} + + {/* Off. Wins */} + + {warlord.offWins} + + + {/* Off. Losses */} + + {warlord.offLosses} + + + {/* Def. Wins */} + + {warlord.defWins} + + + {/* Def. Losses */} + + {warlord.defLosses} + + + {/* Total Wins */} + + {totalWins} + + + {/* Total Losses */} + + {totalLosses} + + + {/* Win Rate */} + + {winRate.toFixed(1)}% + + + ); + })} + + + + )} + + ))} +
); }; From d8dbe4e875d476c92fe1c84e4a7a755b80b4bc42 Mon Sep 17 00:00:00 2001 From: Antonin Cezard Date: Thu, 20 Feb 2025 10:13:12 +0100 Subject: [PATCH 2/4] Add tabs for each deck and update state to show only one deck per tab * Add `Tabs` and `Tab` components to `src/App.tsx` to allow switching between decks * Update `src/components/DeckTab.tsx` to show all warlords for the current deck on screen * Add tests in `src/App.spec.tsx` for showing only one deck per tab --- src/App.spec.tsx | 15 +++ src/App.tsx | 23 +++- src/components/DeckTab.tsx | 215 ++++++++++++++----------------------- 3 files changed, 119 insertions(+), 134 deletions(-) diff --git a/src/App.spec.tsx b/src/App.spec.tsx index 2bef64b..7d4adfe 100644 --- a/src/App.spec.tsx +++ b/src/App.spec.tsx @@ -36,3 +36,18 @@ test("loads decks from local storage on component mount", () => { render(); expect(screen.getByText("Loaded Deck")).toBeInTheDocument(); }); + +test("shows only one deck per tab", () => { + const savedDecks = [ + { deckName: "Deck 1", factions: [] }, + { deckName: "Deck 2", factions: [] }, + ]; + jest.spyOn(localStorage, "getItem").mockReturnValue(JSON.stringify(savedDecks)); + render(); + expect(screen.getByText("Deck 1")).toBeInTheDocument(); + expect(screen.getByText("Deck 2")).toBeInTheDocument(); + const tab1 = screen.getByRole("tabpanel", { hidden: true, name: "Deck 1" }); + const tab2 = screen.getByRole("tabpanel", { hidden: true, name: "Deck 2" }); + expect(tab1).toBeInTheDocument(); + expect(tab2).toBeInTheDocument(); +}); diff --git a/src/App.tsx b/src/App.tsx index 505a1c0..e05ea5f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,10 +2,11 @@ import React, { useState, useEffect } from "react"; import { Deck } from "./models/types"; import { deepCopyMasterTable } from "./models/masterData"; import DeckTab from "./components/DeckTab"; -import { Container, Typography, Button, Box } from "@mui/material"; +import { Container, Typography, Button, Box, Tabs, Tab } from "@mui/material"; const App: React.FC = () => { const [decks, setDecks] = useState([]); + const [selectedTab, setSelectedTab] = useState(0); useEffect(() => { const savedDecks = localStorage.getItem("decks"); @@ -25,6 +26,10 @@ const App: React.FC = () => { localStorage.setItem("decks", JSON.stringify(updatedDecks)); }; + const handleChange = (event: React.SyntheticEvent, newValue: number) => { + setSelectedTab(newValue); + }; + return ( @@ -34,9 +39,23 @@ const App: React.FC = () => { Add New Deck + + {decks.map((deck, index) => ( + + ))} + + {decks.map((deck, index) => ( - + ))} diff --git a/src/components/DeckTab.tsx b/src/components/DeckTab.tsx index 87a7f05..691ac84 100644 --- a/src/components/DeckTab.tsx +++ b/src/components/DeckTab.tsx @@ -8,11 +8,9 @@ import { TableRow, Paper, Typography, - Tabs, - Tab, Box, } from "@mui/material"; -import { Deck, Faction, Warlord } from "../models/types"; +import { Deck, Warlord } from "../models/types"; // Helper function to compute derived stats function getWarlordStats(w: Warlord) { @@ -28,150 +26,103 @@ interface DeckTabProps { } const DeckTab: React.FC = ({ deck }) => { - const [selectedTab, setSelectedTab] = React.useState(0); - - const handleChange = (event: React.SyntheticEvent, newValue: number) => { - setSelectedTab(newValue); - }; - return ( {deck.deckName} - - {deck.factions.map((faction, index) => ( - - ))} - - {deck.factions.map((faction, index) => ( - - ))} + {/* Win Rate */} + + {winRate.toFixed(1)}% + + + ); + }) + )} + + + ); From 0b28544441cd93607f1b839cd6fa23f1e6c028c6 Mon Sep 17 00:00:00 2001 From: Antonin Cezard Date: Thu, 20 Feb 2025 10:17:49 +0100 Subject: [PATCH 3/4] Group warlords by their faction in the UI * Add a new `Faction` type import * Map over `deck.factions` to create a table row for each faction * Add a table cell with the faction name, spanning all columns * Map over `faction.warlords` to create a table row for each warlord * Apply color-coded styles to columns for offensive wins, offensive losses, defensive wins, defensive losses, total wins, total losses, and win rate --- src/components/DeckTab.tsx | 90 +++++++++++++++++++------------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/src/components/DeckTab.tsx b/src/components/DeckTab.tsx index 691ac84..071d1fc 100644 --- a/src/components/DeckTab.tsx +++ b/src/components/DeckTab.tsx @@ -10,7 +10,7 @@ import { Typography, Box, } from "@mui/material"; -import { Deck, Warlord } from "../models/types"; +import { Deck, Warlord, Faction } from "../models/types"; // Helper function to compute derived stats function getWarlordStats(w: Warlord) { @@ -66,60 +66,60 @@ const DeckTab: React.FC = ({ deck }) => { - {deck.factions.flatMap((faction) => - faction.warlords.map((warlord: Warlord) => { - const { matches, totalWins, totalLosses, winRate } = - getWarlordStats(warlord); + {deck.factions.map((faction: Faction) => ( + + + + {faction.factionName} + + + {faction.warlords.map((warlord: Warlord) => { + const { matches, totalWins, totalLosses, winRate } = getWarlordStats(warlord); - // Color-coded column styles - const offWinStyle = { backgroundColor: "#bfb" }; - const offLossStyle = { backgroundColor: "#fbb" }; - const defWinStyle = { backgroundColor: "#bfb" }; - const defLossStyle = { backgroundColor: "#fbb" }; - const totalWinStyle = { backgroundColor: "#dfd" }; - const totalLossStyle = { backgroundColor: "#fdd" }; - const rateStyle = { backgroundColor: "#eee" }; + // Color-coded column styles + const offWinStyle = { backgroundColor: "#bfb" }; + const offLossStyle = { backgroundColor: "#fbb" }; + const defWinStyle = { backgroundColor: "#bfb" }; + const defLossStyle = { backgroundColor: "#fbb" }; + const totalWinStyle = { backgroundColor: "#dfd" }; + const totalLossStyle = { backgroundColor: "#fdd" }; + const rateStyle = { backgroundColor: "#eee" }; - return ( - - {/* Warlord Name */} - - {warlord.warlordName} - + return ( + + {/* Warlord Name */} + + {warlord.warlordName} + - {/* Matches */} - {matches} + {/* Matches */} + {matches} - {/* Off. Wins */} - {warlord.offWins} + {/* Off. Wins */} + {warlord.offWins} - {/* Off. Losses */} - - {warlord.offLosses} - + {/* Off. Losses */} + {warlord.offLosses} - {/* Def. Wins */} - {warlord.defWins} + {/* Def. Wins */} + {warlord.defWins} - {/* Def. Losses */} - - {warlord.defLosses} - + {/* Def. Losses */} + {warlord.defLosses} - {/* Total Wins */} - {totalWins} + {/* Total Wins */} + {totalWins} - {/* Total Losses */} - {totalLosses} + {/* Total Losses */} + {totalLosses} - {/* Win Rate */} - - {winRate.toFixed(1)}% - - - ); - }) - )} + {/* Win Rate */} + {winRate.toFixed(1)}% + + ); + })} + + ))} From ba2def06c2a6962789793d147fa77055fb1be91f Mon Sep 17 00:00:00 2001 From: Antonin Cezard Date: Thu, 20 Feb 2025 10:33:15 +0100 Subject: [PATCH 4/4] fix: improve test env --- package.json | 1 + src/App.spec.tsx | 19 ++++++---- src/App.tsx | 2 +- src/components/DeckTab.tsx | 28 +++++++++++---- yarn.lock | 71 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 107 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index d9fb09d..0cbf597 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "devDependencies": { "@eslint/js": "^9.19.0", "@testing-library/dom": "^10.4.0", + "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.2.0", "@types/jest": "^29.5.14", "@types/react": "^19.0.8", diff --git a/src/App.spec.tsx b/src/App.spec.tsx index 7d4adfe..1ee9910 100644 --- a/src/App.spec.tsx +++ b/src/App.spec.tsx @@ -1,5 +1,10 @@ import { render, screen, fireEvent } from "@testing-library/react"; import App from "./App"; +import "@testing-library/jest-dom"; + +window.prompt = jest.fn(); +window.localStorage.__proto__.getItem = jest.fn(); +window.localStorage.__proto__.setItem = jest.fn(); test("renders", () => { render(); @@ -23,16 +28,16 @@ test("saves the deck to local storage", () => { fireEvent.click(addButton); expect(localStorage.setItem).toHaveBeenCalledWith( "decks", - JSON.stringify([{ deckName: "Saved Deck", factions: expect.any(Array) }]) + JSON.stringify([{ deckName: "Saved Deck", factions: expect.any(Array) }]), ); promptSpy.mockRestore(); }); test("loads decks from local storage on component mount", () => { - const savedDecks = [ - { deckName: "Loaded Deck", factions: [] }, - ]; - jest.spyOn(localStorage, "getItem").mockReturnValue(JSON.stringify(savedDecks)); + const savedDecks = [{ deckName: "Loaded Deck", factions: [] }]; + jest + .spyOn(localStorage, "getItem") + .mockReturnValue(JSON.stringify(savedDecks)); render(); expect(screen.getByText("Loaded Deck")).toBeInTheDocument(); }); @@ -42,7 +47,9 @@ test("shows only one deck per tab", () => { { deckName: "Deck 1", factions: [] }, { deckName: "Deck 2", factions: [] }, ]; - jest.spyOn(localStorage, "getItem").mockReturnValue(JSON.stringify(savedDecks)); + jest + .spyOn(localStorage, "getItem") + .mockReturnValue(JSON.stringify(savedDecks)); render(); expect(screen.getByText("Deck 1")).toBeInTheDocument(); expect(screen.getByText("Deck 2")).toBeInTheDocument(); diff --git a/src/App.tsx b/src/App.tsx index e05ea5f..23a9ede 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -26,7 +26,7 @@ const App: React.FC = () => { localStorage.setItem("decks", JSON.stringify(updatedDecks)); }; - const handleChange = (event: React.SyntheticEvent, newValue: number) => { + const handleChange = (_event: React.SyntheticEvent, newValue: number) => { setSelectedTab(newValue); }; diff --git a/src/components/DeckTab.tsx b/src/components/DeckTab.tsx index 071d1fc..62f044e 100644 --- a/src/components/DeckTab.tsx +++ b/src/components/DeckTab.tsx @@ -69,12 +69,16 @@ const DeckTab: React.FC = ({ deck }) => { {deck.factions.map((faction: Faction) => ( - + {faction.factionName} {faction.warlords.map((warlord: Warlord) => { - const { matches, totalWins, totalLosses, winRate } = getWarlordStats(warlord); + const { matches, totalWins, totalLosses, winRate } = + getWarlordStats(warlord); // Color-coded column styles const offWinStyle = { backgroundColor: "#bfb" }; @@ -96,16 +100,24 @@ const DeckTab: React.FC = ({ deck }) => { {matches} {/* Off. Wins */} - {warlord.offWins} + + {warlord.offWins} + {/* Off. Losses */} - {warlord.offLosses} + + {warlord.offLosses} + {/* Def. Wins */} - {warlord.defWins} + + {warlord.defWins} + {/* Def. Losses */} - {warlord.defLosses} + + {warlord.defLosses} + {/* Total Wins */} {totalWins} @@ -114,7 +126,9 @@ const DeckTab: React.FC = ({ deck }) => { {totalLosses} {/* Win Rate */} - {winRate.toFixed(1)}% + + {winRate.toFixed(1)}% + ); })} diff --git a/yarn.lock b/yarn.lock index f799688..8d81106 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@adobe/css-tools@^4.4.0": + version "4.4.2" + resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.4.2.tgz#c836b1bd81e6d62cd6cdf3ee4948bcdce8ea79c8" + integrity sha512-baYZExFpsdkBNuvGKTKWCwKH57HRZLVtycZS05WTQNVOiXVSeAki3nU35zlRbToeMW8aHlJfyS+1C4BOv27q0A== + "@ampproject/remapping@^2.2.0": version "2.3.0" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" @@ -1134,6 +1139,19 @@ lz-string "^1.5.0" pretty-format "^27.0.2" +"@testing-library/jest-dom@^6.6.3": + version "6.6.3" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz#26ba906cf928c0f8172e182c6fe214eb4f9f2bd2" + integrity sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA== + dependencies: + "@adobe/css-tools" "^4.4.0" + aria-query "^5.0.0" + chalk "^3.0.0" + css.escape "^1.5.1" + dom-accessibility-api "^0.6.3" + lodash "^4.17.21" + redent "^3.0.0" + "@testing-library/react@^16.2.0": version "16.2.0" resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-16.2.0.tgz#c96126ee01a49cdb47175721911b4a9432afc601" @@ -1521,6 +1539,11 @@ aria-query@5.3.0: dependencies: dequal "^2.0.3" +aria-query@^5.0.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.2.tgz#93f81a43480e33a338f19163a3d10a50c01dcd59" + integrity sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw== + array-buffer-byte-length@^1.0.1, array-buffer-byte-length@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz#384d12a37295aec3769ab022ad323a18a51ccf8b" @@ -1798,6 +1821,14 @@ caniuse-lite@^1.0.30001688: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001700.tgz#26cd429cf09b4fd4e745daf4916039c794d720f6" integrity sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ== +chalk@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" @@ -1931,6 +1962,11 @@ css-to-react-native@3.2.0: css-color-keywords "^1.0.0" postcss-value-parser "^4.0.2" +css.escape@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" + integrity sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg== + cssom@^0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.5.0.tgz#d254fa92cd8b6fbd83811b9fbaed34663cc17c36" @@ -2071,6 +2107,11 @@ dom-accessibility-api@^0.5.9: resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz#5a7429e6066eb3664d911e33fb0e45de8eb08453" integrity sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg== +dom-accessibility-api@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz#993e925cc1d73f2c662e7d75dd5a5445259a8fd8" + integrity sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w== + dom-helpers@^5.0.1: version "5.2.1" resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" @@ -2842,6 +2883,11 @@ imurmurhash@^0.1.4: resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -3674,6 +3720,11 @@ lodash.merge@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" @@ -3752,6 +3803,11 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +min-indent@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" + integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== + minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" @@ -4157,6 +4213,14 @@ react@^19.0.0: resolved "https://registry.yarnpkg.com/react/-/react-19.0.0.tgz#6e1969251b9f108870aa4bff37a0ce9ddfaaabdd" integrity sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ== +redent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" + integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== + dependencies: + indent-string "^4.0.0" + strip-indent "^3.0.0" + reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9: version "1.0.10" resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz#c629219e78a3316d8b604c765ef68996964e7bf9" @@ -4564,6 +4628,13 @@ strip-final-newline@^2.0.0: resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== +strip-indent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" + integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== + dependencies: + min-indent "^1.0.0" + strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"