Phase 5: App-Shell mit responsivem Layout, Navigation und Theme
Zwei Layouts aus derselben Zusammensetzung: unter 1000px Tab-Leiste unten, darueber Seitenleiste links mit Brotkrume statt Zurueck-Pfeil. Navigation ist wie im alten Panel ein flacher Zustand mit fester ZURUECK-Tabelle statt eines Router-Pakets - die Verschachtelung ist hoechstens dreistufig. Theme und Tab-Beschriftung ueberleben einen Neustart (localStorage, abgesichert fuer den Privatmodus), Startwert folgt der Systemeinstellung. Symbole aus dem alten Panel uebernommen, damit App und Panel gleich aussehen. Neun Rendertests belegen die Abnahmekriterien: Layoutwechsel in beide Richtungen, Zurueck-Ziel ueber die Tabelle, Zahnrad statt Tab-Leiste fuer die Einstellungen, Theme-Persistenz.
This commit is contained in:
@@ -20,11 +20,13 @@
|
||||
"react-dom": "^18.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/react": "^16.3.2",
|
||||
"@types/leaflet": "^1.9.12",
|
||||
"@types/node": "^24.13.3",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"jsdom": "^29.1.1",
|
||||
"typescript": "^5.7.2",
|
||||
"vite": "^6.0.7",
|
||||
"vitest": "^2.1.8"
|
||||
|
||||
+27
-11
@@ -1,19 +1,35 @@
|
||||
import { Tile } from "@audi-dash/ui"
|
||||
import { useCallback, useState } from "react"
|
||||
|
||||
import { Shell } from "./Shell"
|
||||
import type { Route, SeitenName } from "./navigation"
|
||||
import { useTabBeschriftung, useTheme } from "./theme"
|
||||
import { Platzhalter } from "./screens/Platzhalter"
|
||||
|
||||
import "./stile/rahmen.css"
|
||||
|
||||
/**
|
||||
* Vorläufiger Einstieg — wird in Phase 5 durch die App-Shell ersetzt.
|
||||
* Das äußerste Element trägt immer `ads-root` und `data-theme`, sonst greifen
|
||||
* die Design-Tokens nicht (siehe design-system/.design-sync/conventions.md).
|
||||
* Wurzel der App. Hält den Navigationszustand und das Theme; alles Weitere
|
||||
* hängt darunter. Das äußerste Element trägt `ads-root` und `data-theme` —
|
||||
* ohne diese Wurzelklasse greifen die Design-Tokens nicht
|
||||
* (design-system/.design-sync/conventions.md).
|
||||
*/
|
||||
export function App() {
|
||||
const [route, setzeRoute] = useState<Route>({ name: "home" })
|
||||
const [theme] = useTheme()
|
||||
const [tabBeschriftung] = useTabBeschriftung()
|
||||
|
||||
const geheZu = useCallback((name: SeitenName, id?: string) => {
|
||||
setzeRoute(id === undefined ? { name } : { name, id })
|
||||
// Beim Seitenwechsel immer oben anfangen — sonst erbt die neue Seite die
|
||||
// Scrollposition der alten.
|
||||
document.querySelector(".dm-inhalt")?.scrollTo({ top: 0 })
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="ads-root dm-wurzel" data-theme="nacht">
|
||||
<div className="dm-scroll" style={{ flex: 1, padding: 22 }}>
|
||||
<span className="ads-eyebrow">DataMetric360</span>
|
||||
<Tile>
|
||||
<div style={{ fontWeight: 300, fontSize: 20 }}>Gerüst steht</div>
|
||||
</Tile>
|
||||
</div>
|
||||
<div className="ads-root dm-wurzel" data-theme={theme}>
|
||||
<Shell route={route} geheZu={geheZu} tabBeschriftung={tabBeschriftung}>
|
||||
<Platzhalter route={route} geheZu={geheZu} />
|
||||
</Shell>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Abnahmekriterien aus Phase 5 des Umsetzungsplans: Die App muss beide
|
||||
* Layouts beherrschen und die Zurück-Navigation über die ZURUECK-Tabelle
|
||||
* korrekt auflösen.
|
||||
*/
|
||||
|
||||
import { fireEvent, render, screen } from "@testing-library/react"
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest"
|
||||
|
||||
import { Shell } from "./Shell"
|
||||
import { bereichVon } from "./navigation"
|
||||
import { bildschirmbreiteSetzen } from "./tests/aufbau"
|
||||
|
||||
function rahmen(routeName: Parameters<typeof bereichVon>[0], geheZu = vi.fn()) {
|
||||
render(
|
||||
<Shell route={{ name: routeName }} geheZu={geheZu} tabBeschriftung>
|
||||
<p>Inhalt</p>
|
||||
</Shell>,
|
||||
)
|
||||
return geheZu
|
||||
}
|
||||
|
||||
describe("Shell", () => {
|
||||
beforeEach(() => {
|
||||
bildschirmbreiteSetzen(390)
|
||||
})
|
||||
|
||||
it("zeigt auf schmalen Bildschirmen die Tab-Leiste unten", () => {
|
||||
const { container } = render(
|
||||
<Shell route={{ name: "home" }} geheZu={vi.fn()} tabBeschriftung>
|
||||
<p>Inhalt</p>
|
||||
</Shell>,
|
||||
)
|
||||
expect(container.querySelector(".ads-tabbar")).not.toBeNull()
|
||||
expect(container.querySelector(".dm-seitenleiste")).toBeNull()
|
||||
})
|
||||
|
||||
it("zeigt auf breiten Bildschirmen die Seitenleiste statt der Tab-Leiste", () => {
|
||||
bildschirmbreiteSetzen(1280)
|
||||
const { container } = render(
|
||||
<Shell route={{ name: "home" }} geheZu={vi.fn()} tabBeschriftung>
|
||||
<p>Inhalt</p>
|
||||
</Shell>,
|
||||
)
|
||||
expect(container.querySelector(".dm-seitenleiste")).not.toBeNull()
|
||||
expect(container.querySelector(".ads-tabbar")).toBeNull()
|
||||
})
|
||||
|
||||
it("führt der Zurück-Pfeil zur übergeordneten Seite", () => {
|
||||
const geheZu = rahmen("beitrag")
|
||||
fireEvent.click(screen.getByLabelText("Zurück"))
|
||||
// beitrag -> vers laut ZURUECK-Tabelle
|
||||
expect(geheZu).toHaveBeenCalledWith("vers")
|
||||
})
|
||||
|
||||
it("blendet den Zurück-Pfeil auf Hauptbereichen aus", () => {
|
||||
rahmen("trips")
|
||||
expect(screen.queryByLabelText("Zurück")).toBeNull()
|
||||
})
|
||||
|
||||
it("hebt den Hauptbereich hervor, zu dem eine Unterseite gehört", () => {
|
||||
// schutz -> vertragsdetails -> vers -> audi
|
||||
expect(bereichVon("schutz")).toBe("audi")
|
||||
expect(bereichVon("fill")).toBe("fuel")
|
||||
expect(bereichVon("trip")).toBe("trips")
|
||||
})
|
||||
|
||||
it("erreicht die Einstellungen über das Zahnrad, nicht über die Tab-Leiste", () => {
|
||||
const geheZu = rahmen("home")
|
||||
fireEvent.click(screen.getByLabelText("Einstellungen"))
|
||||
expect(geheZu).toHaveBeenCalledWith("einst")
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
* Rahmen der App: Kopfzeile, Navigation, Inhaltsfläche.
|
||||
*
|
||||
* Zwei Layouts aus derselben Zusammensetzung — die entscheidende Anforderung
|
||||
* aus dem Design-Auftrag: schmal (Handy) mit Tab-Leiste unten, breit (Tablet,
|
||||
* eingebettet in Home Assistant) mit Seitenleiste links. Der Umschaltpunkt
|
||||
* liegt bei 1000px.
|
||||
*/
|
||||
|
||||
import type { ReactNode } from "react"
|
||||
|
||||
import { TabBar } from "@audi-dash/ui"
|
||||
|
||||
import { useBreiterBildschirm } from "./hooks/useBreiterBildschirm"
|
||||
import type { Hauptbereich, Route, SeitenName } from "./navigation"
|
||||
import { TITEL, ZURUECK, bereichVon, istHauptbereich } from "./navigation"
|
||||
import {
|
||||
SymbolAudi,
|
||||
SymbolFahrten,
|
||||
SymbolStatistik,
|
||||
SymbolTanken,
|
||||
SymbolUebersicht,
|
||||
SymbolZahnrad,
|
||||
SymbolZurueck,
|
||||
} from "./symbole"
|
||||
|
||||
export interface ShellProps {
|
||||
route: Route
|
||||
geheZu: (name: SeitenName, id?: string) => void
|
||||
tabBeschriftung: boolean
|
||||
/** Wird über der Kopfzeile eingeblendet (Offline-Hinweis, Warteschlange). */
|
||||
hinweis?: ReactNode | undefined
|
||||
/** Heißt `children`, weil JSX-Verschachtelung genau diesen Namen erwartet. */
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
const BEREICHE: { key: Hauptbereich; label: string; icon: ReactNode }[] = [
|
||||
{ key: "home", label: "Übersicht", icon: <SymbolUebersicht /> },
|
||||
{ key: "audi", label: "Mein Audi", icon: <SymbolAudi /> },
|
||||
{ key: "trips", label: "Fahrten", icon: <SymbolFahrten /> },
|
||||
{ key: "stat", label: "Statistik", icon: <SymbolStatistik /> },
|
||||
{ key: "fuel", label: "Tanken", icon: <SymbolTanken /> },
|
||||
]
|
||||
|
||||
export function Shell({ route, geheZu, tabBeschriftung, hinweis, children }: ShellProps) {
|
||||
const breit = useBreiterBildschirm()
|
||||
const aktiverBereich = bereichVon(route.name)
|
||||
const zurueckZiel = ZURUECK[route.name]
|
||||
|
||||
const kopf = (
|
||||
<header className="dm-kopf">
|
||||
{zurueckZiel && !breit ? (
|
||||
<button
|
||||
type="button"
|
||||
className="dm-kopf__zurueck"
|
||||
aria-label="Zurück"
|
||||
onClick={() => geheZu(zurueckZiel)}
|
||||
>
|
||||
<SymbolZurueck groesse={22} />
|
||||
</button>
|
||||
) : null}
|
||||
<div className="dm-kopf__titel">
|
||||
<span className="ads-eyebrow">DataMetric360</span>
|
||||
<div className="ads-title">{TITEL[route.name]}</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="dm-kopf__aktion"
|
||||
aria-label="Einstellungen"
|
||||
aria-current={route.name === "einst" ? "page" : undefined}
|
||||
onClick={() => geheZu("einst")}
|
||||
>
|
||||
<SymbolZahnrad groesse={22} />
|
||||
</button>
|
||||
</header>
|
||||
)
|
||||
|
||||
if (breit) {
|
||||
return (
|
||||
<>
|
||||
<nav className="dm-seitenleiste" aria-label="Hauptbereiche">
|
||||
<span className="ads-eyebrow dm-seitenleiste__marke">DataMetric360</span>
|
||||
{BEREICHE.map((bereich) => (
|
||||
<button
|
||||
key={bereich.key}
|
||||
type="button"
|
||||
className={
|
||||
bereich.key === aktiverBereich
|
||||
? "dm-seitenleiste__punkt dm-seitenleiste__punkt--an"
|
||||
: "dm-seitenleiste__punkt"
|
||||
}
|
||||
aria-current={bereich.key === aktiverBereich ? "page" : undefined}
|
||||
onClick={() => geheZu(bereich.key)}
|
||||
>
|
||||
{bereich.icon}
|
||||
<span>{bereich.label}</span>
|
||||
</button>
|
||||
))}
|
||||
<div className="dm-seitenleiste__fuss">
|
||||
<button
|
||||
type="button"
|
||||
className={
|
||||
route.name === "einst"
|
||||
? "dm-seitenleiste__punkt dm-seitenleiste__punkt--an"
|
||||
: "dm-seitenleiste__punkt"
|
||||
}
|
||||
onClick={() => geheZu("einst")}
|
||||
>
|
||||
<SymbolZahnrad />
|
||||
<span>Einstellungen</span>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
<div className="dm-spalte">
|
||||
{hinweis}
|
||||
{!istHauptbereich(route.name) && zurueckZiel ? (
|
||||
<div className="dm-brotkrume">
|
||||
<button type="button" onClick={() => geheZu(zurueckZiel)}>
|
||||
<SymbolZurueck groesse={16} />
|
||||
<span>{TITEL[zurueckZiel]}</span>
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
<main className="dm-scroll dm-inhalt dm-inhalt--breit">{children}</main>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="dm-spalte">
|
||||
{hinweis}
|
||||
{kopf}
|
||||
<main className="dm-scroll dm-inhalt">{children}</main>
|
||||
<TabBar
|
||||
items={BEREICHE}
|
||||
activeKey={aktiverBereich}
|
||||
onChange={(schluessel) => geheZu(schluessel as Hauptbereich)}
|
||||
iconOnly={!tabBeschriftung}
|
||||
className="dm-tableiste"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { useEffect, useState } from "react"
|
||||
|
||||
/**
|
||||
* Ab dieser Breite wechselt die App vom Handy- aufs Flächenlayout: Navigation
|
||||
* wandert von unten nach links, Inhalte dürfen mehrspaltig werden. Der Wert
|
||||
* stammt aus dem Design-Auftrag ("ab ca. 1000px").
|
||||
*/
|
||||
export const BREITER_BILDSCHIRM = "(min-width: 1000px)"
|
||||
|
||||
export function useBreiterBildschirm(): boolean {
|
||||
const [breit, setzeBreit] = useState<boolean>(() => {
|
||||
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false
|
||||
return window.matchMedia(BREITER_BILDSCHIRM).matches
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return
|
||||
const abfrage = window.matchMedia(BREITER_BILDSCHIRM)
|
||||
const beiAenderung = (ereignis: MediaQueryListEvent) => setzeBreit(ereignis.matches)
|
||||
abfrage.addEventListener("change", beiAenderung)
|
||||
setzeBreit(abfrage.matches)
|
||||
return () => abfrage.removeEventListener("change", beiAenderung)
|
||||
}, [])
|
||||
|
||||
return breit
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* Flacher Navigationszustand — kein Router-Paket.
|
||||
*
|
||||
* Übernommen aus dem alten HA-Panel (`route` + `ZURUECK`): Die App hat keinen
|
||||
* Verlaufsstapel, sondern genau eine aktuelle Seite und eine feste Tabelle,
|
||||
* welche Seite der Zurück-Pfeil ansteuert. Das ist bewusst so geblieben —
|
||||
* die Verschachtelung ist flach genug (höchstens drei Ebenen), und ein Router
|
||||
* würde nur Gewicht ohne Nutzen bringen.
|
||||
*/
|
||||
|
||||
export const HAUPTBEREICHE = ["home", "audi", "trips", "stat", "fuel"] as const
|
||||
|
||||
export type Hauptbereich = (typeof HAUPTBEREICHE)[number]
|
||||
|
||||
export type SeitenName =
|
||||
| Hauptbereich
|
||||
// Unterseiten von "Mein Audi"
|
||||
| "ident"
|
||||
| "battverlauf"
|
||||
| "service"
|
||||
| "werkstatt"
|
||||
| "sbuch"
|
||||
| "reifen"
|
||||
| "vers"
|
||||
| "beitrag"
|
||||
| "vertragsdetails"
|
||||
| "schutz"
|
||||
| "notruf"
|
||||
| "steuer"
|
||||
// Detailseiten der Listen
|
||||
| "trip"
|
||||
| "fill"
|
||||
// Sonstige
|
||||
| "sicherheit"
|
||||
| "live"
|
||||
| "einst"
|
||||
|
||||
export interface Route {
|
||||
name: SeitenName
|
||||
/** Datensatz-Kennung bei Detailseiten (Fahrt, Tankvorgang, Servicebuch). */
|
||||
id?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Ziel des Zurück-Pfeils je Unterseite. Seiten, die hier fehlen, sind
|
||||
* Hauptbereiche und zeigen keinen Zurück-Pfeil.
|
||||
*/
|
||||
export const ZURUECK: Partial<Record<SeitenName, SeitenName>> = {
|
||||
trip: "trips",
|
||||
fill: "fuel",
|
||||
service: "audi",
|
||||
reifen: "audi",
|
||||
ident: "audi",
|
||||
battverlauf: "audi",
|
||||
vers: "audi",
|
||||
beitrag: "vers",
|
||||
vertragsdetails: "vers",
|
||||
schutz: "vertragsdetails",
|
||||
steuer: "vers",
|
||||
notruf: "vers",
|
||||
werkstatt: "service",
|
||||
sbuch: "service",
|
||||
einst: "home",
|
||||
sicherheit: "home",
|
||||
live: "home",
|
||||
}
|
||||
|
||||
/** Überschrift je Seite. Leer bedeutet: die Seite bringt ihre eigene mit. */
|
||||
export const TITEL: Record<SeitenName, string> = {
|
||||
home: "Übersicht",
|
||||
audi: "Mein Audi",
|
||||
trips: "Fahrten",
|
||||
stat: "Statistik",
|
||||
fuel: "Tanken",
|
||||
ident: "Fahrzeugdaten",
|
||||
battverlauf: "Batterie",
|
||||
service: "Service",
|
||||
werkstatt: "Werkstatt",
|
||||
sbuch: "Servicebuch",
|
||||
reifen: "Reifen",
|
||||
vers: "Versicherung & Steuer",
|
||||
beitrag: "Beitrag",
|
||||
vertragsdetails: "Vertragsdetails",
|
||||
schutz: "Schutzbrief",
|
||||
notruf: "Notruf",
|
||||
steuer: "Kfz-Steuer",
|
||||
trip: "Fahrt",
|
||||
fill: "Tankvorgang",
|
||||
sicherheit: "Fahrzeugstatus",
|
||||
live: "Fahrt läuft",
|
||||
einst: "Einstellungen",
|
||||
}
|
||||
|
||||
export function istHauptbereich(name: SeitenName): name is Hauptbereich {
|
||||
return (HAUPTBEREICHE as readonly string[]).includes(name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Der Hauptbereich, zu dem eine Seite gehört — bestimmt, welcher Eintrag in
|
||||
* Tab-Leiste bzw. Seitenleiste hervorgehoben wird. Folgt der ZURUECK-Kette
|
||||
* nach oben.
|
||||
*/
|
||||
export function bereichVon(name: SeitenName): Hauptbereich {
|
||||
let aktuell: SeitenName = name
|
||||
for (let tiefe = 0; tiefe < 5; tiefe++) {
|
||||
if (istHauptbereich(aktuell)) return aktuell
|
||||
const eltern = ZURUECK[aktuell]
|
||||
if (!eltern) return "home"
|
||||
aktuell = eltern
|
||||
}
|
||||
return "home"
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Vorläufiger Inhalt aller Seiten — wird in Phase 7 des Umsetzungsplans Seite
|
||||
* für Seite durch die echten Ansichten ersetzt. Bis dahin belegt er die
|
||||
* Navigation, damit sich der Rahmen bedienen lässt.
|
||||
*/
|
||||
|
||||
import { Tile } from "@audi-dash/ui"
|
||||
|
||||
import type { Route, SeitenName } from "../navigation"
|
||||
import { TITEL } from "../navigation"
|
||||
|
||||
const UNTERSEITEN: Partial<Record<SeitenName, SeitenName[]>> = {
|
||||
home: ["sicherheit", "einst"],
|
||||
audi: ["ident", "battverlauf", "service", "reifen", "vers"],
|
||||
service: ["werkstatt", "sbuch"],
|
||||
vers: ["beitrag", "vertragsdetails", "steuer", "notruf"],
|
||||
vertragsdetails: ["schutz"],
|
||||
trips: ["trip"],
|
||||
fuel: ["fill"],
|
||||
}
|
||||
|
||||
export function Platzhalter({
|
||||
route,
|
||||
geheZu,
|
||||
}: {
|
||||
route: Route
|
||||
geheZu: (name: SeitenName, id?: string) => void
|
||||
}) {
|
||||
const weiter = UNTERSEITEN[route.name] ?? []
|
||||
return (
|
||||
<>
|
||||
<Tile>
|
||||
<div style={{ fontWeight: 300, fontSize: 18 }}>{TITEL[route.name]}</div>
|
||||
<p style={{ color: "var(--fg2)", fontSize: 14, fontWeight: 300, margin: "8px 0 0" }}>
|
||||
Diese Seite wird noch gebaut.
|
||||
</p>
|
||||
</Tile>
|
||||
{weiter.length > 0 && (
|
||||
<div className="dm-raster">
|
||||
{weiter.map((ziel) => (
|
||||
<Tile key={ziel} variant="button" chevron onClick={() => geheZu(ziel)}>
|
||||
<span className="ads-eyebrow">weiter</span>
|
||||
<div style={{ fontWeight: 300, fontSize: 16, marginTop: 4 }}>{TITEL[ziel]}</div>
|
||||
</Tile>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* Rahmen der App (Shell.tsx): Kopfzeile, Seitenleiste, Inhaltsfläche.
|
||||
* Farben und Radien ausschließlich aus den Tokens von @audi-dash/ui.
|
||||
*/
|
||||
|
||||
.dm-spalte {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------- Kopfzeile */
|
||||
|
||||
.dm-kopf {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--sp-3);
|
||||
padding: calc(env(safe-area-inset-top, 0px) + var(--sp-4)) var(--sp-5) var(--sp-3);
|
||||
border-bottom: 1px solid var(--line);
|
||||
background: var(--canvas);
|
||||
}
|
||||
|
||||
.dm-kopf__titel {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.dm-kopf__zurueck,
|
||||
.dm-kopf__aktion {
|
||||
flex: none;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border: 0;
|
||||
border-radius: var(--r-pill);
|
||||
background: transparent;
|
||||
color: var(--fg2);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dm-kopf__zurueck:hover,
|
||||
.dm-kopf__aktion:hover {
|
||||
background: var(--shade);
|
||||
color: var(--fg);
|
||||
}
|
||||
|
||||
.dm-kopf__aktion[aria-current="page"] {
|
||||
color: var(--red);
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------- Inhaltsfläche */
|
||||
|
||||
.dm-inhalt {
|
||||
flex: 1;
|
||||
padding: var(--sp-4) var(--sp-5) calc(env(safe-area-inset-bottom, 0px) + var(--sp-5));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-3);
|
||||
}
|
||||
|
||||
.dm-inhalt--breit {
|
||||
padding: var(--sp-5) 32px 32px;
|
||||
}
|
||||
|
||||
/* Mehrspaltige Anordnung auf großen Flächen: Kacheln nebeneinander statt
|
||||
untereinander. Die Screens setzen die Klasse dort, wo es sinnvoll ist. */
|
||||
.dm-raster {
|
||||
display: grid;
|
||||
gap: var(--sp-3);
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
@media (min-width: 1000px) {
|
||||
.dm-raster {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.dm-raster--drei {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.dm-raster__breit {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Listen- und Detailansicht nebeneinander (Fahrten, Tanken auf großer Fläche) */
|
||||
.dm-zweispalter {
|
||||
display: grid;
|
||||
gap: var(--sp-4);
|
||||
grid-template-columns: 1fr;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
@media (min-width: 1000px) {
|
||||
.dm-zweispalter {
|
||||
grid-template-columns: minmax(0, 4fr) minmax(0, 5fr);
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------- Seitenleiste */
|
||||
|
||||
.dm-seitenleiste {
|
||||
flex: 0 0 240px;
|
||||
width: 240px;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
border-right: 1px solid var(--line);
|
||||
padding: 24px 14px 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
background: var(--canvas);
|
||||
}
|
||||
|
||||
.dm-seitenleiste__marke {
|
||||
padding: 0 10px var(--sp-4);
|
||||
}
|
||||
|
||||
.dm-seitenleiste__punkt {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--sp-3);
|
||||
width: 100%;
|
||||
padding: 11px 10px;
|
||||
border: 0;
|
||||
border-radius: var(--r-func);
|
||||
background: transparent;
|
||||
color: var(--fg2);
|
||||
font-size: 14px;
|
||||
font-weight: 300;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dm-seitenleiste__punkt:hover {
|
||||
background: var(--shade);
|
||||
color: var(--fg);
|
||||
}
|
||||
|
||||
.dm-seitenleiste__punkt--an {
|
||||
background: var(--tile);
|
||||
color: var(--fg);
|
||||
}
|
||||
|
||||
.dm-seitenleiste__fuss {
|
||||
margin-top: auto;
|
||||
padding-top: var(--sp-3);
|
||||
border-top: 1px solid var(--line);
|
||||
}
|
||||
|
||||
/* Zurück-Weg auf großer Fläche: als Brotkrume über dem Inhalt statt als
|
||||
Pfeil in der Kopfzeile — auf dem Handy nimmt der Pfeil den Platz ein. */
|
||||
.dm-brotkrume {
|
||||
padding: var(--sp-4) 32px 0;
|
||||
}
|
||||
|
||||
.dm-brotkrume button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: var(--fg2);
|
||||
font-size: 13px;
|
||||
font-weight: 300;
|
||||
cursor: pointer;
|
||||
padding: 6px 0;
|
||||
}
|
||||
|
||||
.dm-brotkrume button:hover {
|
||||
color: var(--fg);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ Tab-Leiste */
|
||||
|
||||
.dm-tableiste {
|
||||
flex: none;
|
||||
padding-bottom: env(safe-area-inset-bottom, 0px);
|
||||
border-top: 1px solid var(--line);
|
||||
background: var(--canvas);
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
/**
|
||||
* Symbole als React-Bausteine. Aus dem alten Panel übernommen (dort als
|
||||
* SVG-Fragmente in Zeichenketten), damit App und Panel identisch aussehen.
|
||||
* Bewusst keine Symbolbibliothek: es sind eine Handvoll, und jede Bibliothek
|
||||
* wäre größer als der gesamte Rest.
|
||||
*/
|
||||
|
||||
import type { ReactElement } from "react"
|
||||
|
||||
// Ausdrücklich `| undefined`, weil exactOptionalPropertyTypes eingeschaltet ist:
|
||||
// ein durchgereichtes `groesse={undefined}` muss zulässig bleiben.
|
||||
interface SymbolProps {
|
||||
groesse?: number | undefined
|
||||
}
|
||||
|
||||
function Rahmen({ groesse = 24, kinder }: SymbolProps & { kinder: ReactElement }) {
|
||||
return (
|
||||
<svg
|
||||
width={groesse}
|
||||
height={groesse}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
{kinder}
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function SymbolUebersicht({ groesse }: SymbolProps) {
|
||||
return (
|
||||
<Rahmen
|
||||
groesse={groesse}
|
||||
kinder={
|
||||
<g>
|
||||
<path d="M4 17a9 9 0 0 1 18 0" />
|
||||
<path d="M13 17l5.5-5" />
|
||||
<circle cx="13" cy="17" r="1.3" />
|
||||
</g>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export function SymbolAudi({ groesse }: SymbolProps) {
|
||||
return (
|
||||
<Rahmen
|
||||
groesse={groesse}
|
||||
kinder={<polygon points="21.6,5 16.5,19 2.4,19 7.5,5" />}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export function SymbolFahrten({ groesse }: SymbolProps) {
|
||||
return (
|
||||
<Rahmen
|
||||
groesse={groesse}
|
||||
kinder={
|
||||
<g>
|
||||
<circle cx="6" cy="6" r="2.2" />
|
||||
<circle cx="18" cy="18" r="2.2" />
|
||||
<path d="M8 7.5c4 1.5 4.5 3 3 5s-1 3.5 4.5 5" />
|
||||
</g>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export function SymbolStatistik({ groesse }: SymbolProps) {
|
||||
return <Rahmen groesse={groesse} kinder={<path d="M4 20V9.5M10 20V4.5M16 20v-8M22 20H2" />} />
|
||||
}
|
||||
|
||||
export function SymbolTanken({ groesse }: SymbolProps) {
|
||||
return (
|
||||
<Rahmen
|
||||
groesse={groesse}
|
||||
kinder={
|
||||
<g>
|
||||
<path d="M4.5 20V6.2c0-.9.8-1.7 1.7-1.7h5.6c1 0 1.7.8 1.7 1.7V20" />
|
||||
<path d="M3 20h12.5" />
|
||||
<path d="M4.5 11h10.5" />
|
||||
<path d="M17 20v-8.5a2 2 0 0 0-2-2" />
|
||||
<path d="M17 11.5V7.8l-2-2.3" />
|
||||
</g>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export function SymbolZurueck({ groesse }: SymbolProps) {
|
||||
return <Rahmen groesse={groesse} kinder={<path d="M15 5l-7 7 7 7" />} />
|
||||
}
|
||||
|
||||
export function SymbolZahnrad({ groesse }: SymbolProps) {
|
||||
return (
|
||||
<Rahmen
|
||||
groesse={groesse}
|
||||
kinder={
|
||||
<g>
|
||||
<circle cx="12" cy="12" r="3" />
|
||||
<path d="M12 2v3M12 19v3M2 12h3M19 12h3M4.9 4.9l2.1 2.1M17 17l2.1 2.1M19.1 4.9L17 7M7 17l-2.1 2.1" />
|
||||
</g>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export function SymbolSonne({ groesse }: SymbolProps) {
|
||||
return (
|
||||
<Rahmen
|
||||
groesse={groesse}
|
||||
kinder={
|
||||
<g>
|
||||
<circle cx="12" cy="12" r="4.5" />
|
||||
<path d="M12 2v2.4M12 19.6V22M2 12h2.4M19.6 12H22M4.7 4.7l1.7 1.7M17.6 17.6l1.7 1.7M19.3 4.7l-1.7 1.7M6.4 17.6l-1.7 1.7" />
|
||||
</g>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export function SymbolMond({ groesse }: SymbolProps) {
|
||||
return (
|
||||
<Rahmen
|
||||
groesse={groesse}
|
||||
kinder={<path d="M19.5 14.4A8.4 8.4 0 1 1 9.6 4.5a6.7 6.7 0 0 0 9.9 9.9z" />}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export function SymbolMehr({ groesse }: SymbolProps) {
|
||||
return (
|
||||
<Rahmen
|
||||
groesse={groesse}
|
||||
kinder={
|
||||
<g>
|
||||
<circle cx="5" cy="12" r="1.4" />
|
||||
<circle cx="12" cy="12" r="1.4" />
|
||||
<circle cx="19" cy="12" r="1.4" />
|
||||
</g>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export function SymbolWarnung({ groesse }: SymbolProps) {
|
||||
return (
|
||||
<Rahmen
|
||||
groesse={groesse}
|
||||
kinder={
|
||||
<g>
|
||||
<path d="M12 4.5 2.5 20h19z" />
|
||||
<path d="M12 10v4.5" />
|
||||
<circle cx="12" cy="17.3" r="0.9" fill="currentColor" stroke="none" />
|
||||
</g>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Testaufbau. jsdom kennt matchMedia nicht — die App fragt es aber ab, um
|
||||
* zwischen Handy- und Flächenlayout zu wählen. `bildschirmbreiteSetzen()`
|
||||
* stellt eine steuerbare Fassung bereit.
|
||||
*/
|
||||
|
||||
let aktuelleBreite = 390
|
||||
|
||||
export function bildschirmbreiteSetzen(breite: number): void {
|
||||
aktuelleBreite = breite
|
||||
}
|
||||
|
||||
function passt(abfrage: string): boolean {
|
||||
const min = /\(min-width:\s*(\d+)px\)/.exec(abfrage)
|
||||
if (min?.[1]) return aktuelleBreite >= Number(min[1])
|
||||
const max = /\(max-width:\s*(\d+)px\)/.exec(abfrage)
|
||||
if (max?.[1]) return aktuelleBreite <= Number(max[1])
|
||||
if (abfrage.includes("prefers-color-scheme: light")) return false
|
||||
return false
|
||||
}
|
||||
|
||||
window.matchMedia = ((abfrage: string) => ({
|
||||
matches: passt(abfrage),
|
||||
media: abfrage,
|
||||
onchange: null,
|
||||
addEventListener: () => {},
|
||||
removeEventListener: () => {},
|
||||
addListener: () => {},
|
||||
removeListener: () => {},
|
||||
dispatchEvent: () => false,
|
||||
})) as unknown as typeof window.matchMedia
|
||||
@@ -0,0 +1,23 @@
|
||||
import { beforeEach, describe, expect, it } from "vitest"
|
||||
|
||||
import { themeStartwert } from "./theme"
|
||||
|
||||
describe("Theme", () => {
|
||||
beforeEach(() => {
|
||||
localStorage.clear()
|
||||
})
|
||||
|
||||
it("startet ohne gespeicherte Wahl im Nachtmodus", () => {
|
||||
expect(themeStartwert()).toBe("nacht")
|
||||
})
|
||||
|
||||
it("übernimmt eine gespeicherte Wahl", () => {
|
||||
localStorage.setItem("dm360.theme", "tag")
|
||||
expect(themeStartwert()).toBe("tag")
|
||||
})
|
||||
|
||||
it("ignoriert unsinnige gespeicherte Werte", () => {
|
||||
localStorage.setItem("dm360.theme", "irgendwas")
|
||||
expect(themeStartwert()).toBe("nacht")
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Tag/Nacht-Umschaltung, übernommen aus dem alten Panel (A6 des Design-Audits):
|
||||
* gespeicherte Wahl schlägt Systemeinstellung, Standard ist "nacht".
|
||||
* Alle localStorage-Zugriffe sind abgesichert, weil sie im Privatmodus mancher
|
||||
* Browser werfen.
|
||||
*/
|
||||
|
||||
import { useCallback, useEffect, useState } from "react"
|
||||
|
||||
export type Theme = "nacht" | "tag"
|
||||
|
||||
const SCHLUESSEL = "dm360.theme"
|
||||
|
||||
function gespeichertLesen(): Theme | null {
|
||||
try {
|
||||
const wert = localStorage.getItem(SCHLUESSEL)
|
||||
return wert === "nacht" || wert === "tag" ? wert : null
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function gespeichertSchreiben(theme: Theme): void {
|
||||
try {
|
||||
localStorage.setItem(SCHLUESSEL, theme)
|
||||
} catch {
|
||||
// Privatmodus — die Wahl gilt dann nur für diese Sitzung
|
||||
}
|
||||
}
|
||||
|
||||
export function themeStartwert(): Theme {
|
||||
const gespeichert = gespeichertLesen()
|
||||
if (gespeichert) return gespeichert
|
||||
const hell =
|
||||
typeof window !== "undefined" &&
|
||||
typeof window.matchMedia === "function" &&
|
||||
window.matchMedia("(prefers-color-scheme: light)").matches
|
||||
return hell ? "tag" : "nacht"
|
||||
}
|
||||
|
||||
export function useTheme(): [Theme, (theme: Theme) => void] {
|
||||
const [theme, setzeTheme] = useState<Theme>(themeStartwert)
|
||||
|
||||
const setzen = useCallback((neu: Theme) => {
|
||||
setzeTheme(neu)
|
||||
gespeichertSchreiben(neu)
|
||||
}, [])
|
||||
|
||||
// Farbe der Systemleisten mitziehen, damit auf dem Handy kein heller
|
||||
// Balken über einer dunklen App steht.
|
||||
useEffect(() => {
|
||||
const marke = document.querySelector('meta[name="theme-color"]')
|
||||
if (marke) marke.setAttribute("content", theme === "nacht" ? "#161b23" : "#ffffff")
|
||||
}, [theme])
|
||||
|
||||
return [theme, setzen]
|
||||
}
|
||||
|
||||
/** Sichtbarkeit der Tab-Beschriftungen, ebenfalls dauerhaft gespeichert. */
|
||||
const BESCHRIFTUNG_SCHLUESSEL = "dm360.tabbeschriftung"
|
||||
|
||||
export function useTabBeschriftung(): [boolean, (an: boolean) => void] {
|
||||
const [an, setzeAn] = useState<boolean>(() => {
|
||||
try {
|
||||
return localStorage.getItem(BESCHRIFTUNG_SCHLUESSEL) !== "aus"
|
||||
} catch {
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
const setzen = useCallback((neu: boolean) => {
|
||||
setzeAn(neu)
|
||||
try {
|
||||
localStorage.setItem(BESCHRIFTUNG_SCHLUESSEL, neu ? "an" : "aus")
|
||||
} catch {
|
||||
// Privatmodus
|
||||
}
|
||||
}, [])
|
||||
|
||||
return [an, setzen]
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { defineConfig } from "vitest/config"
|
||||
import react from "@vitejs/plugin-react"
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
test: {
|
||||
environment: "jsdom",
|
||||
globals: true,
|
||||
include: ["src/**/*.test.ts", "src/**/*.test.tsx"],
|
||||
setupFiles: ["./src/tests/aufbau.ts"],
|
||||
},
|
||||
})
|
||||
Generated
+686
-1
@@ -22,11 +22,13 @@
|
||||
"react-dom": "^18.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/react": "^16.3.2",
|
||||
"@types/leaflet": "^1.9.12",
|
||||
"@types/node": "^24.13.3",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"jsdom": "^29.1.1",
|
||||
"typescript": "^5.7.2",
|
||||
"vite": "^6.0.7",
|
||||
"vitest": "^2.1.8"
|
||||
@@ -48,6 +50,57 @@
|
||||
"react-dom": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@asamuzakjp/css-color": {
|
||||
"version": "5.1.11",
|
||||
"resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz",
|
||||
"integrity": "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@asamuzakjp/generational-cache": "^1.0.1",
|
||||
"@csstools/css-calc": "^3.2.0",
|
||||
"@csstools/css-color-parser": "^4.1.0",
|
||||
"@csstools/css-parser-algorithms": "^4.0.0",
|
||||
"@csstools/css-tokenizer": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@asamuzakjp/dom-selector": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.1.1.tgz",
|
||||
"integrity": "sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@asamuzakjp/generational-cache": "^1.0.1",
|
||||
"@asamuzakjp/nwsapi": "^2.3.9",
|
||||
"bidi-js": "^1.0.3",
|
||||
"css-tree": "^3.2.1",
|
||||
"is-potential-custom-element-name": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@asamuzakjp/generational-cache": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz",
|
||||
"integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@asamuzakjp/nwsapi": {
|
||||
"version": "2.3.9",
|
||||
"resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz",
|
||||
"integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@audi-dash/ui": {
|
||||
"resolved": "design-system",
|
||||
"link": true
|
||||
@@ -287,6 +340,16 @@
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz",
|
||||
"integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/template": {
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
|
||||
@@ -335,6 +398,161 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@bramus/specificity": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz",
|
||||
"integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"css-tree": "^3.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"specificity": "bin/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/color-helpers": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.0.tgz",
|
||||
"integrity": "sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/csstools"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
],
|
||||
"license": "MIT-0",
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/css-calc": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.3.0.tgz",
|
||||
"integrity": "sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/csstools"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@csstools/css-parser-algorithms": "^4.0.0",
|
||||
"@csstools/css-tokenizer": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/css-color-parser": {
|
||||
"version": "4.1.10",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.10.tgz",
|
||||
"integrity": "sha512-UZhQLIUyJaaMepqehrCODwCg2KW25vFvLWBmqYFaPclYvvxzj/sG8LBOhBFCp11i9uE7t1EyS+RAoV9tztPFyw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/csstools"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@csstools/color-helpers": "^6.1.0",
|
||||
"@csstools/css-calc": "^3.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@csstools/css-parser-algorithms": "^4.0.0",
|
||||
"@csstools/css-tokenizer": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/css-parser-algorithms": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz",
|
||||
"integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/csstools"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@csstools/css-tokenizer": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/css-syntax-patches-for-csstree": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.7.tgz",
|
||||
"integrity": "sha512-fQ+05118eQS1cofO3aJpB5efgpBZMvIzwr/sbC8kDLVA5XLG8q1kJV5yzrUAI1f7lvhPnm8fgIjzFB8/O/5Dig==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/csstools"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
],
|
||||
"license": "MIT-0",
|
||||
"peerDependencies": {
|
||||
"css-tree": "^3.2.1"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"css-tree": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/css-tokenizer": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz",
|
||||
"integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/csstools"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz",
|
||||
@@ -777,6 +995,24 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@exodus/bytes": {
|
||||
"version": "1.15.1",
|
||||
"resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz",
|
||||
"integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@noble/hashes": "^1.8.0 || ^2.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@noble/hashes": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/gen-mapping": {
|
||||
"version": "0.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||
@@ -1201,6 +1437,62 @@
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@testing-library/dom": {
|
||||
"version": "10.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz",
|
||||
"integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.10.4",
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@types/aria-query": "^5.0.1",
|
||||
"aria-query": "5.3.0",
|
||||
"dom-accessibility-api": "^0.5.9",
|
||||
"lz-string": "^1.5.0",
|
||||
"picocolors": "1.1.1",
|
||||
"pretty-format": "^27.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@testing-library/react": {
|
||||
"version": "16.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz",
|
||||
"integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@testing-library/dom": "^10.0.0",
|
||||
"@types/react": "^18.0.0 || ^19.0.0",
|
||||
"@types/react-dom": "^18.0.0 || ^19.0.0",
|
||||
"react": "^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@types/aria-query": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
|
||||
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/babel__core": {
|
||||
"version": "7.20.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
||||
@@ -1294,7 +1586,6 @@
|
||||
"integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"csstype": "^3.2.2"
|
||||
@@ -1444,6 +1735,29 @@
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-styles": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
|
||||
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/any-promise": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
|
||||
@@ -1451,6 +1765,16 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/aria-query": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
|
||||
"integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"dequal": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/assertion-error": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
|
||||
@@ -1474,6 +1798,16 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bidi-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
|
||||
"integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"require-from-string": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/browserslist": {
|
||||
"version": "4.28.8",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz",
|
||||
@@ -1633,6 +1967,20 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/css-tree": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz",
|
||||
"integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mdn-data": "2.27.1",
|
||||
"source-map-js": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||
@@ -1640,6 +1988,20 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/data-urls": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz",
|
||||
"integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"whatwg-mimetype": "^5.0.0",
|
||||
"whatwg-url": "^16.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/datametric360": {
|
||||
"resolved": "companion-app",
|
||||
"link": true
|
||||
@@ -1662,6 +2024,13 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/decimal.js": {
|
||||
"version": "10.6.0",
|
||||
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
|
||||
"integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/deep-eql": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
|
||||
@@ -1672,6 +2041,23 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/dequal": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
|
||||
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/dom-accessibility-api": {
|
||||
"version": "0.5.16",
|
||||
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
|
||||
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.404",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.404.tgz",
|
||||
@@ -1679,6 +2065,19 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz",
|
||||
"integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/es-module-lexer": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
|
||||
@@ -1814,6 +2213,26 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/html-encoding-sniffer": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz",
|
||||
"integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@exodus/bytes": "^1.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-potential-custom-element-name": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
|
||||
"integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/joycon": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz",
|
||||
@@ -1830,6 +2249,58 @@
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jsdom": {
|
||||
"version": "29.1.1",
|
||||
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.1.tgz",
|
||||
"integrity": "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@asamuzakjp/css-color": "^5.1.11",
|
||||
"@asamuzakjp/dom-selector": "^7.1.1",
|
||||
"@bramus/specificity": "^2.4.2",
|
||||
"@csstools/css-syntax-patches-for-csstree": "^1.1.3",
|
||||
"@exodus/bytes": "^1.15.0",
|
||||
"css-tree": "^3.2.1",
|
||||
"data-urls": "^7.0.0",
|
||||
"decimal.js": "^10.6.0",
|
||||
"html-encoding-sniffer": "^6.0.0",
|
||||
"is-potential-custom-element-name": "^1.0.1",
|
||||
"lru-cache": "^11.3.5",
|
||||
"parse5": "^8.0.1",
|
||||
"saxes": "^6.0.0",
|
||||
"symbol-tree": "^3.2.4",
|
||||
"tough-cookie": "^6.0.1",
|
||||
"undici": "^7.25.0",
|
||||
"w3c-xmlserializer": "^5.0.0",
|
||||
"webidl-conversions": "^8.0.1",
|
||||
"whatwg-mimetype": "^5.0.0",
|
||||
"whatwg-url": "^16.0.1",
|
||||
"xml-name-validator": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.13.0 || >=24.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"canvas": "^3.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"canvas": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/jsdom/node_modules/lru-cache": {
|
||||
"version": "11.5.2",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz",
|
||||
"integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/jsesc": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
|
||||
@@ -1921,6 +2392,16 @@
|
||||
"yallist": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/lz-string": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
|
||||
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"lz-string": "bin/bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.30.21",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
||||
@@ -1931,6 +2412,13 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/mdn-data": {
|
||||
"version": "2.27.1",
|
||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz",
|
||||
"integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==",
|
||||
"dev": true,
|
||||
"license": "CC0-1.0"
|
||||
},
|
||||
"node_modules/mlly": {
|
||||
"version": "1.8.2",
|
||||
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz",
|
||||
@@ -2002,6 +2490,19 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/parse5": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz",
|
||||
"integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"entities": "^8.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/pathe": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
|
||||
@@ -2135,6 +2636,31 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/pretty-format": {
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
|
||||
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1",
|
||||
"ansi-styles": "^5.0.0",
|
||||
"react-is": "^17.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
||||
@@ -2153,6 +2679,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"scheduler": "^0.23.2"
|
||||
@@ -2161,6 +2688,13 @@
|
||||
"react": "^18.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "17.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-refresh": {
|
||||
"version": "0.17.0",
|
||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
|
||||
@@ -2185,6 +2719,16 @@
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/require-from-string": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
||||
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve-from": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
|
||||
@@ -2241,6 +2785,19 @@
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/saxes": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
|
||||
"integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"xmlchars": "^2.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=v12.22.7"
|
||||
}
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.23.2",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
|
||||
@@ -2324,6 +2881,13 @@
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/symbol-tree": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
|
||||
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/thenify": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
|
||||
@@ -2408,6 +2972,52 @@
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tldts": {
|
||||
"version": "7.4.10",
|
||||
"resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.10.tgz",
|
||||
"integrity": "sha512-GgouD1B+sWwvkaEq8vXC15DjQitxbvs12oIXELpconwm+Tg3zfcEv4jgzq3vtKverDXsg3VI8aRgNL2Nra0Iog==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tldts-core": "^7.4.10"
|
||||
},
|
||||
"bin": {
|
||||
"tldts": "bin/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/tldts-core": {
|
||||
"version": "7.4.10",
|
||||
"resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.10.tgz",
|
||||
"integrity": "sha512-KnQjp53ZekKgm/r3l+u8kJGGzYgrWdP8+Mql7a4vijh2WE0IrZWspQj/TpTxDho/YxO+AnOZnIjQcCD+q6iJsw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tough-cookie": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.2.tgz",
|
||||
"integrity": "sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"tldts": "^7.0.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz",
|
||||
"integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"punycode": "^2.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/tree-kill": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
|
||||
@@ -2500,6 +3110,16 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz",
|
||||
"integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20.18.1"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.18.2",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
|
||||
@@ -4209,6 +4829,54 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/w3c-xmlserializer": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
|
||||
"integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"xml-name-validator": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz",
|
||||
"integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/whatwg-mimetype": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz",
|
||||
"integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/whatwg-url": {
|
||||
"version": "16.0.1",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz",
|
||||
"integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@exodus/bytes": "^1.11.0",
|
||||
"tr46": "^6.0.0",
|
||||
"webidl-conversions": "^8.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/why-is-node-running": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
|
||||
@@ -4226,6 +4894,23 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/xml-name-validator": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
|
||||
"integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/xmlchars": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
|
||||
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
||||
|
||||
Reference in New Issue
Block a user