@sentropic/design-system-svelte 0.34.20 → 0.34.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AppChrome.svelte +660 -0
- package/dist/AppChrome.svelte.d.ts +74 -0
- package/dist/AppChrome.svelte.d.ts.map +1 -0
- package/dist/AppChrome.test.d.ts +2 -0
- package/dist/AppChrome.test.d.ts.map +1 -0
- package/dist/AppChrome.test.js +122 -0
- package/dist/ConfigItemCard.svelte +303 -0
- package/dist/ConfigItemCard.svelte.d.ts +37 -0
- package/dist/ConfigItemCard.svelte.d.ts.map +1 -0
- package/dist/DataTable.svelte.d.ts +1 -1
- package/dist/ErrorSummary.svelte +75 -0
- package/dist/ErrorSummary.svelte.d.ts +17 -0
- package/dist/ErrorSummary.svelte.d.ts.map +1 -0
- package/dist/FieldCard.svelte +220 -0
- package/dist/FieldCard.svelte.d.ts +28 -0
- package/dist/FieldCard.svelte.d.ts.map +1 -0
- package/dist/ScoreCard.svelte +172 -0
- package/dist/ScoreCard.svelte.d.ts +27 -0
- package/dist/ScoreCard.svelte.d.ts.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/package.json +1 -1
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
/** Un lien de navigation principal du chrome. */
|
|
3
|
+
export interface AppChromeNavItem {
|
|
4
|
+
label: string;
|
|
5
|
+
href: string;
|
|
6
|
+
/** Marqué actif (souligné, aria-current=page). */
|
|
7
|
+
active?: boolean;
|
|
8
|
+
}
|
|
9
|
+
/** Une option du sélecteur de thème. */
|
|
10
|
+
export interface AppChromeThemeOption {
|
|
11
|
+
id: string;
|
|
12
|
+
label: string;
|
|
13
|
+
}
|
|
14
|
+
export type AppChromeColorMode = "light" | "dark" | "auto";
|
|
15
|
+
export type AppChromeLocale = "fr" | "en";
|
|
16
|
+
export interface AppChromeProps {
|
|
17
|
+
/** Nom de marque (défaut « Sentropic »). */
|
|
18
|
+
brandName?: string;
|
|
19
|
+
/** Sous-titre produit sous le nom (ex. « Design System », « dataviz »). */
|
|
20
|
+
productName?: string;
|
|
21
|
+
/** Source du logo carré (ex. `/SENT-logo-squared.svg`). */
|
|
22
|
+
logoSrc?: string;
|
|
23
|
+
/** Texte alternatif du logo (décoratif par défaut). */
|
|
24
|
+
logoAlt?: string;
|
|
25
|
+
/** Cible du lien de marque. Défaut `/`. */
|
|
26
|
+
brandHref?: string;
|
|
27
|
+
/** aria-label du lien de marque (sinon dérivé de brandName + productName). */
|
|
28
|
+
brandLabel?: string;
|
|
29
|
+
/** Liens de nav principaux (pills soulignées + état actif). */
|
|
30
|
+
nav?: AppChromeNavItem[];
|
|
31
|
+
/** aria-label de la nav principale. */
|
|
32
|
+
navLabel?: string;
|
|
33
|
+
/** Options de thème. Vide => le sélecteur est masqué. */
|
|
34
|
+
themes?: AppChromeThemeOption[];
|
|
35
|
+
/** Id du thème actif. */
|
|
36
|
+
theme?: string;
|
|
37
|
+
/** Callback de changement de thème. */
|
|
38
|
+
onThemeChange?: (id: string) => void;
|
|
39
|
+
/** aria-label du sélecteur de thème. */
|
|
40
|
+
themeLabel?: string;
|
|
41
|
+
/** Mode couleur actif. Undefined => le toggle est masqué. */
|
|
42
|
+
colorMode?: AppChromeColorMode;
|
|
43
|
+
/** Callback de changement (cycle light -> dark -> auto). */
|
|
44
|
+
onColorModeChange?: (mode: AppChromeColorMode) => void;
|
|
45
|
+
/** Libellés accessibles des 3 modes (light/dark/auto). */
|
|
46
|
+
colorModeLabels?: {
|
|
47
|
+
light: string;
|
|
48
|
+
dark: string;
|
|
49
|
+
auto: string;
|
|
50
|
+
};
|
|
51
|
+
/** Langue active. Undefined => le sélecteur est masqué. */
|
|
52
|
+
locale?: AppChromeLocale;
|
|
53
|
+
/** Callback de changement de langue. */
|
|
54
|
+
onLocaleChange?: (locale: AppChromeLocale) => void;
|
|
55
|
+
/** aria-label du sélecteur de langue. */
|
|
56
|
+
localeLabel?: string;
|
|
57
|
+
/** URL du dépôt. Undefined => le lien est masqué. */
|
|
58
|
+
githubHref?: string;
|
|
59
|
+
/** aria-label du lien GitHub. */
|
|
60
|
+
githubLabel?: string;
|
|
61
|
+
/** Zone identité à droite (IdentityMenu, bouton connexion, …). */
|
|
62
|
+
identity?: Snippet;
|
|
63
|
+
/** État ouvert du tiroir mobile (contrôlé). */
|
|
64
|
+
mobileMenuOpen?: boolean;
|
|
65
|
+
/** Callback de bascule du tiroir mobile. */
|
|
66
|
+
onMobileMenuToggle?: () => void;
|
|
67
|
+
/** aria-label du bouton burger. */
|
|
68
|
+
menuLabel?: string;
|
|
69
|
+
class?: string;
|
|
70
|
+
}
|
|
71
|
+
declare const AppChrome: import("svelte").Component<AppChromeProps, {}, "">;
|
|
72
|
+
type AppChrome = ReturnType<typeof AppChrome>;
|
|
73
|
+
export default AppChrome;
|
|
74
|
+
//# sourceMappingURL=AppChrome.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AppChrome.svelte.d.ts","sourceRoot":"","sources":["../src/lib/AppChrome.svelte.ts"],"names":[],"mappings":"AAGE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEtC,iDAAiD;AACjD,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,kDAAkD;IAClD,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,wCAAwC;AACxC,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;AAC3D,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG,IAAI,CAAC;AAE1C,MAAM,WAAW,cAAc;IAE7B,4CAA4C;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2EAA2E;IAC3E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2DAA2D;IAC3D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uDAAuD;IACvD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,MAAM,CAAC;IAGpB,+DAA+D;IAC/D,GAAG,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACzB,uCAAuC;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAGlB,yDAAyD;IACzD,MAAM,CAAC,EAAE,oBAAoB,EAAE,CAAC;IAChC,yBAAyB;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uCAAuC;IACvC,aAAa,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,wCAAwC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;IAGpB,6DAA6D;IAC7D,SAAS,CAAC,EAAE,kBAAkB,CAAC;IAC/B,4DAA4D;IAC5D,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACvD,0DAA0D;IAC1D,eAAe,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAGhE,2DAA2D;IAC3D,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB,wCAAwC;IACxC,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,IAAI,CAAC;IACnD,yCAAyC;IACzC,WAAW,CAAC,EAAE,MAAM,CAAC;IAGrB,qDAAqD;IACrD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iCAAiC;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC;IAGrB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,OAAO,CAAC;IAGnB,+CAA+C;IAC/C,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,4CAA4C;IAC5C,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;IAChC,mCAAmC;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAqRH,QAAA,MAAM,SAAS,oDAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AppChrome.test.d.ts","sourceRoot":"","sources":["../src/lib/AppChrome.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { fireEvent, render } from "@testing-library/svelte";
|
|
2
|
+
import { createRawSnippet } from "svelte";
|
|
3
|
+
import { describe, expect, it, vi } from "vitest";
|
|
4
|
+
import AppChrome from "./AppChrome.svelte";
|
|
5
|
+
const snippet = (html) => createRawSnippet(() => ({ render: () => `<span>${html}</span>` }));
|
|
6
|
+
const themes = [
|
|
7
|
+
{ id: "sent-tech", label: "Sentropic" },
|
|
8
|
+
{ id: "forge", label: "Forge" },
|
|
9
|
+
{ id: "entropic", label: "Entropic" },
|
|
10
|
+
{ id: "carbon", label: "Carbon" },
|
|
11
|
+
{ id: "dsfr", label: "DSFR" },
|
|
12
|
+
{ id: "airbus", label: "Airbus" },
|
|
13
|
+
];
|
|
14
|
+
const nav = [
|
|
15
|
+
{ label: "Vues", href: "/views", active: true },
|
|
16
|
+
{ label: "Données", href: "/data" },
|
|
17
|
+
{ label: "Réglages", href: "/settings" },
|
|
18
|
+
];
|
|
19
|
+
describe("AppChrome — marque", () => {
|
|
20
|
+
it("renders the canonical brand block (logo + name + product)", () => {
|
|
21
|
+
const { container } = render(AppChrome, {
|
|
22
|
+
props: { brandName: "Sentropic", productName: "dataviz", logoSrc: "/SENT-logo-squared.svg", brandHref: "/home" },
|
|
23
|
+
});
|
|
24
|
+
const brand = container.querySelector("a.st-appChrome__brand");
|
|
25
|
+
expect(brand).toBeTruthy();
|
|
26
|
+
expect(brand.getAttribute("href")).toBe("/home");
|
|
27
|
+
expect(brand.getAttribute("aria-label")).toBe("Sentropic dataviz");
|
|
28
|
+
expect(container.querySelector("img.st-appChrome__brandMark").getAttribute("src")).toBe("/SENT-logo-squared.svg");
|
|
29
|
+
expect(container.querySelector(".st-appChrome__brandName")?.textContent).toBe("Sentropic");
|
|
30
|
+
expect(container.querySelector(".st-appChrome__brandProduct")?.textContent).toBe("dataviz");
|
|
31
|
+
});
|
|
32
|
+
it("defaults brandName to Sentropic", () => {
|
|
33
|
+
const { container } = render(AppChrome, { props: { productName: "dataviz" } });
|
|
34
|
+
expect(container.querySelector(".st-appChrome__brandName")?.textContent).toBe("Sentropic");
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
describe("AppChrome — navigation", () => {
|
|
38
|
+
it("renders nav links and marks the active one with aria-current", () => {
|
|
39
|
+
const { container } = render(AppChrome, { props: { nav } });
|
|
40
|
+
const links = Array.from(container.querySelectorAll(".st-appChrome__navLink"));
|
|
41
|
+
expect(links.map((a) => a.textContent?.trim())).toEqual(["Vues", "Données", "Réglages"]);
|
|
42
|
+
expect(links[0].getAttribute("aria-current")).toBe("page");
|
|
43
|
+
expect(links[1].getAttribute("aria-current")).toBeNull();
|
|
44
|
+
expect(links[0].classList.contains("st-appHeader__navLink")).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
describe("AppChrome — contrôle thème", () => {
|
|
48
|
+
it("shows the active theme label and fires onThemeChange on selection", async () => {
|
|
49
|
+
const onThemeChange = vi.fn();
|
|
50
|
+
const { container } = render(AppChrome, { props: { themes, theme: "sent-tech", onThemeChange } });
|
|
51
|
+
const trigger = container.querySelector(".st-appChrome__themeWrap button");
|
|
52
|
+
expect(trigger.textContent).toContain("Sentropic");
|
|
53
|
+
await fireEvent.click(trigger);
|
|
54
|
+
expect(container.querySelector('[role="menu"]')).toBeTruthy();
|
|
55
|
+
const carbon = Array.from(container.querySelectorAll(".st-appChrome__menuItem")).find((b) => b.textContent?.includes("Carbon"));
|
|
56
|
+
await fireEvent.click(carbon);
|
|
57
|
+
expect(onThemeChange).toHaveBeenCalledWith("carbon");
|
|
58
|
+
});
|
|
59
|
+
it("hides the theme selector when themes is empty", () => {
|
|
60
|
+
const { container } = render(AppChrome);
|
|
61
|
+
expect(container.querySelector(".st-appChrome__themeWrap")).toBeNull();
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
describe("AppChrome — mode couleur", () => {
|
|
65
|
+
it("cycles light -> dark -> auto via onColorModeChange", async () => {
|
|
66
|
+
const onColorModeChange = vi.fn();
|
|
67
|
+
const { container, rerender } = render(AppChrome, { props: { colorMode: "light", onColorModeChange } });
|
|
68
|
+
await fireEvent.click(container.querySelector(".st-appChrome__iconControl"));
|
|
69
|
+
expect(onColorModeChange).toHaveBeenCalledWith("dark");
|
|
70
|
+
await rerender({ colorMode: "dark", onColorModeChange });
|
|
71
|
+
await fireEvent.click(container.querySelector(".st-appChrome__iconControl"));
|
|
72
|
+
expect(onColorModeChange).toHaveBeenLastCalledWith("auto");
|
|
73
|
+
await rerender({ colorMode: "auto", onColorModeChange });
|
|
74
|
+
await fireEvent.click(container.querySelector(".st-appChrome__iconControl"));
|
|
75
|
+
expect(onColorModeChange).toHaveBeenLastCalledWith("light");
|
|
76
|
+
});
|
|
77
|
+
it("hides the color-mode toggle when colorMode is undefined", () => {
|
|
78
|
+
const { container } = render(AppChrome, { props: { nav } });
|
|
79
|
+
expect(container.querySelector(".st-appChrome__iconControl")).toBeNull();
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
describe("AppChrome — langue", () => {
|
|
83
|
+
it("shows the active locale and fires onLocaleChange on selection", async () => {
|
|
84
|
+
const onLocaleChange = vi.fn();
|
|
85
|
+
const { container } = render(AppChrome, { props: { locale: "fr", onLocaleChange } });
|
|
86
|
+
const trigger = container.querySelector(".st-appChrome__localeWrap button");
|
|
87
|
+
expect(trigger.textContent).toContain("FR");
|
|
88
|
+
await fireEvent.click(trigger);
|
|
89
|
+
const en = Array.from(container.querySelectorAll(".st-appChrome__menuItem")).find((b) => b.textContent?.includes("English"));
|
|
90
|
+
await fireEvent.click(en);
|
|
91
|
+
expect(onLocaleChange).toHaveBeenCalledWith("en");
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
describe("AppChrome — github + identité", () => {
|
|
95
|
+
it("renders the github link with the provided href", () => {
|
|
96
|
+
const { container } = render(AppChrome, { props: { githubHref: "https://github.com/x/y" } });
|
|
97
|
+
const link = container.querySelector('.st-appChrome__utilityNav a[target="_blank"]');
|
|
98
|
+
expect(link.getAttribute("href")).toBe("https://github.com/x/y");
|
|
99
|
+
});
|
|
100
|
+
it("renders the identity snippet content", () => {
|
|
101
|
+
const { container } = render(AppChrome, { props: { identity: snippet("account-zone") } });
|
|
102
|
+
expect(container.querySelector(".st-appChrome__identity")?.textContent).toContain("account-zone");
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
describe("AppChrome — mobile burger + tiroir", () => {
|
|
106
|
+
it("wires the burger aria-controls to the drawer id and renders the drawer when open", () => {
|
|
107
|
+
const { container } = render(AppChrome, { props: { nav, mobileMenuOpen: true } });
|
|
108
|
+
const burger = container.querySelector(".st-appChrome__burgerTrigger");
|
|
109
|
+
const drawer = container.querySelector(".st-appChrome__drawer");
|
|
110
|
+
expect(drawer).toBeTruthy();
|
|
111
|
+
expect(burger.getAttribute("aria-expanded")).toBe("true");
|
|
112
|
+
expect(burger.getAttribute("aria-controls")).toBe(drawer.id);
|
|
113
|
+
expect(drawer.querySelectorAll(".st-appChrome__drawerLink").length).toBeGreaterThanOrEqual(3);
|
|
114
|
+
});
|
|
115
|
+
it("does not render the drawer when closed and toggles via callback", async () => {
|
|
116
|
+
const onMobileMenuToggle = vi.fn();
|
|
117
|
+
const { container } = render(AppChrome, { props: { nav, onMobileMenuToggle } });
|
|
118
|
+
expect(container.querySelector(".st-appChrome__drawer")).toBeNull();
|
|
119
|
+
await fireEvent.click(container.querySelector(".st-appChrome__burgerTrigger"));
|
|
120
|
+
expect(onMobileMenuToggle).toHaveBeenCalledTimes(1);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
3
|
+
import type { Snippet } from "svelte";
|
|
4
|
+
|
|
5
|
+
export type ConfigItemSourceLevel = "code" | "admin" | "user";
|
|
6
|
+
|
|
7
|
+
export type ConfigItem = {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
key?: string;
|
|
11
|
+
description?: string | null;
|
|
12
|
+
/** Provenance : `code`/`admin` = système (verrouillé), `user` = personnalisé. */
|
|
13
|
+
sourceLevel: ConfigItemSourceLevel;
|
|
14
|
+
/** Identifiant du parent si l'item est une copie d'un défaut système. */
|
|
15
|
+
parentId?: string | null;
|
|
16
|
+
version?: number;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type ConfigItemCardProps = Omit<HTMLAttributes<HTMLElement>, "class"> & {
|
|
20
|
+
/** L'item de configuration décrit par la carte. */
|
|
21
|
+
item: ConfigItem;
|
|
22
|
+
/** Vrai si une copie de cet item système existe déjà : masque l'action Copier. */
|
|
23
|
+
hasCopy?: boolean;
|
|
24
|
+
/** Action Copier (items système uniquement). */
|
|
25
|
+
onCopy?: (id: string) => void;
|
|
26
|
+
/** Action Éditer (items copiés ou créés par l'utilisateur). */
|
|
27
|
+
onEdit?: (id: string) => void;
|
|
28
|
+
/** Action Réinitialiser (items copiés uniquement). */
|
|
29
|
+
onReset?: (id: string) => void;
|
|
30
|
+
/** Action Supprimer (items créés par l'utilisateur uniquement). */
|
|
31
|
+
onDelete?: (id: string) => void;
|
|
32
|
+
/** Désactive toutes les actions. */
|
|
33
|
+
disabled?: boolean;
|
|
34
|
+
class?: string;
|
|
35
|
+
/** Contenu supplémentaire sous la carte (formulaire d'édition, détails…). */
|
|
36
|
+
children?: Snippet;
|
|
37
|
+
};
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
<script lang="ts">
|
|
41
|
+
let {
|
|
42
|
+
item,
|
|
43
|
+
hasCopy = false,
|
|
44
|
+
onCopy,
|
|
45
|
+
onEdit,
|
|
46
|
+
onReset,
|
|
47
|
+
onDelete,
|
|
48
|
+
disabled = false,
|
|
49
|
+
class: className,
|
|
50
|
+
children,
|
|
51
|
+
...rest
|
|
52
|
+
}: ConfigItemCardProps = $props();
|
|
53
|
+
|
|
54
|
+
const isSystem = $derived(item.sourceLevel === "code" || item.sourceLevel === "admin");
|
|
55
|
+
const isCopied = $derived(item.sourceLevel === "user" && !!item.parentId);
|
|
56
|
+
const isUserCreated = $derived(item.sourceLevel === "user" && !item.parentId);
|
|
57
|
+
|
|
58
|
+
const classes = $derived(["st-configItemCard", className].filter(Boolean).join(" "));
|
|
59
|
+
</script>
|
|
60
|
+
|
|
61
|
+
<article
|
|
62
|
+
{...rest}
|
|
63
|
+
class={classes}
|
|
64
|
+
data-testid={`config-item-card-${item.key ?? item.id}`}
|
|
65
|
+
>
|
|
66
|
+
<div class="st-configItemCard__header">
|
|
67
|
+
<span class="st-configItemCard__name">{item.name}</span>
|
|
68
|
+
{#if isSystem}
|
|
69
|
+
<span class="st-configItemCard__badge st-configItemCard__badge--system">
|
|
70
|
+
<svg
|
|
71
|
+
class="st-configItemCard__badgeIcon"
|
|
72
|
+
width="12"
|
|
73
|
+
height="12"
|
|
74
|
+
viewBox="0 0 14 14"
|
|
75
|
+
aria-hidden="true"
|
|
76
|
+
focusable="false"
|
|
77
|
+
>
|
|
78
|
+
<path
|
|
79
|
+
d="M4 6V4.5a3 3 0 0 1 6 0V6M3.5 6h7A1.5 1.5 0 0 1 12 7.5v3A1.5 1.5 0 0 1 10.5 12h-7A1.5 1.5 0 0 1 2 10.5v-3A1.5 1.5 0 0 1 3.5 6Z"
|
|
80
|
+
fill="none"
|
|
81
|
+
stroke="currentColor"
|
|
82
|
+
stroke-width="1.25"
|
|
83
|
+
stroke-linecap="round"
|
|
84
|
+
stroke-linejoin="round"
|
|
85
|
+
/>
|
|
86
|
+
</svg>
|
|
87
|
+
Système
|
|
88
|
+
</span>
|
|
89
|
+
{:else if isCopied}
|
|
90
|
+
<span class="st-configItemCard__badge st-configItemCard__badge--custom">
|
|
91
|
+
Personnalisé
|
|
92
|
+
</span>
|
|
93
|
+
{/if}
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
{#if item.key}
|
|
97
|
+
<div class="st-configItemCard__key">{item.key}</div>
|
|
98
|
+
{/if}
|
|
99
|
+
|
|
100
|
+
{#if item.description}
|
|
101
|
+
<p class="st-configItemCard__description">{item.description}</p>
|
|
102
|
+
{/if}
|
|
103
|
+
|
|
104
|
+
{#if (isSystem && !hasCopy && onCopy) || ((isCopied || isUserCreated) && onEdit) || (isCopied && onReset) || (isUserCreated && onDelete)}
|
|
105
|
+
<div class="st-configItemCard__actions">
|
|
106
|
+
{#if isSystem && !hasCopy && onCopy}
|
|
107
|
+
<button
|
|
108
|
+
type="button"
|
|
109
|
+
class="st-configItemCard__action"
|
|
110
|
+
title="Copier"
|
|
111
|
+
aria-label="Copier"
|
|
112
|
+
{disabled}
|
|
113
|
+
onclick={() => onCopy?.(item.id)}
|
|
114
|
+
>
|
|
115
|
+
<svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true" focusable="false">
|
|
116
|
+
<path
|
|
117
|
+
d="M5.5 5.5V3.75A1.25 1.25 0 0 1 6.75 2.5h5.5A1.25 1.25 0 0 1 13.5 3.75v5.5a1.25 1.25 0 0 1-1.25 1.25H10.5M3.75 5.5h5.5A1.25 1.25 0 0 1 10.5 6.75v5.5a1.25 1.25 0 0 1-1.25 1.25h-5.5A1.25 1.25 0 0 1 2.5 12.25v-5.5A1.25 1.25 0 0 1 3.75 5.5Z"
|
|
118
|
+
fill="none"
|
|
119
|
+
stroke="currentColor"
|
|
120
|
+
stroke-width="1.25"
|
|
121
|
+
stroke-linecap="round"
|
|
122
|
+
stroke-linejoin="round"
|
|
123
|
+
/>
|
|
124
|
+
</svg>
|
|
125
|
+
</button>
|
|
126
|
+
{/if}
|
|
127
|
+
{#if (isCopied || isUserCreated) && onEdit}
|
|
128
|
+
<button
|
|
129
|
+
type="button"
|
|
130
|
+
class="st-configItemCard__action"
|
|
131
|
+
title="Éditer"
|
|
132
|
+
aria-label="Éditer"
|
|
133
|
+
{disabled}
|
|
134
|
+
onclick={() => onEdit?.(item.id)}
|
|
135
|
+
>
|
|
136
|
+
<svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true" focusable="false">
|
|
137
|
+
<path
|
|
138
|
+
d="m10.5 2.5 3 3L6 13l-3.5.5L3 10l7.5-7.5Z"
|
|
139
|
+
fill="none"
|
|
140
|
+
stroke="currentColor"
|
|
141
|
+
stroke-width="1.25"
|
|
142
|
+
stroke-linecap="round"
|
|
143
|
+
stroke-linejoin="round"
|
|
144
|
+
/>
|
|
145
|
+
</svg>
|
|
146
|
+
</button>
|
|
147
|
+
{/if}
|
|
148
|
+
{#if isCopied && onReset}
|
|
149
|
+
<button
|
|
150
|
+
type="button"
|
|
151
|
+
class="st-configItemCard__action st-configItemCard__action--warning"
|
|
152
|
+
title="Réinitialiser"
|
|
153
|
+
aria-label="Réinitialiser"
|
|
154
|
+
{disabled}
|
|
155
|
+
onclick={() => onReset?.(item.id)}
|
|
156
|
+
>
|
|
157
|
+
<svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true" focusable="false">
|
|
158
|
+
<path
|
|
159
|
+
d="M3 8a5 5 0 1 1 1.5 3.5M3 8V5M3 8h3"
|
|
160
|
+
fill="none"
|
|
161
|
+
stroke="currentColor"
|
|
162
|
+
stroke-width="1.25"
|
|
163
|
+
stroke-linecap="round"
|
|
164
|
+
stroke-linejoin="round"
|
|
165
|
+
/>
|
|
166
|
+
</svg>
|
|
167
|
+
</button>
|
|
168
|
+
{/if}
|
|
169
|
+
{#if isUserCreated && onDelete}
|
|
170
|
+
<button
|
|
171
|
+
type="button"
|
|
172
|
+
class="st-configItemCard__action st-configItemCard__action--danger"
|
|
173
|
+
title="Supprimer"
|
|
174
|
+
aria-label="Supprimer"
|
|
175
|
+
{disabled}
|
|
176
|
+
onclick={() => onDelete?.(item.id)}
|
|
177
|
+
>
|
|
178
|
+
<svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true" focusable="false">
|
|
179
|
+
<path
|
|
180
|
+
d="M3 4.5h10M6 4.5V3.25A.75.75 0 0 1 6.75 2.5h2.5a.75.75 0 0 1 .75.75V4.5M4.25 4.5l.5 8A1 1 0 0 0 5.75 13.5h4.5a1 1 0 0 0 1-.99l.5-8"
|
|
181
|
+
fill="none"
|
|
182
|
+
stroke="currentColor"
|
|
183
|
+
stroke-width="1.25"
|
|
184
|
+
stroke-linecap="round"
|
|
185
|
+
stroke-linejoin="round"
|
|
186
|
+
/>
|
|
187
|
+
</svg>
|
|
188
|
+
</button>
|
|
189
|
+
{/if}
|
|
190
|
+
</div>
|
|
191
|
+
{/if}
|
|
192
|
+
|
|
193
|
+
{@render children?.()}
|
|
194
|
+
</article>
|
|
195
|
+
|
|
196
|
+
<style>
|
|
197
|
+
.st-configItemCard {
|
|
198
|
+
background: var(--st-component-card-background, var(--st-semantic-surface-raised));
|
|
199
|
+
border-width: var(--st-component-card-anatomy-shape-borderWidth, 1px);
|
|
200
|
+
border-style: var(--st-component-card-anatomy-shape-borderStyle, solid);
|
|
201
|
+
border-color: var(--st-component-card-border, var(--st-semantic-border-subtle));
|
|
202
|
+
border-radius: var(--st-component-card-anatomy-shape-radius, 0.5rem);
|
|
203
|
+
color: var(--st-semantic-text-primary);
|
|
204
|
+
display: flex;
|
|
205
|
+
flex-direction: column;
|
|
206
|
+
gap: var(--st-spacing-1, 0.25rem);
|
|
207
|
+
padding: var(--st-spacing-4, 1rem);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.st-configItemCard__header {
|
|
211
|
+
align-items: center;
|
|
212
|
+
display: flex;
|
|
213
|
+
gap: var(--st-spacing-2, 0.5rem);
|
|
214
|
+
justify-content: space-between;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.st-configItemCard__name {
|
|
218
|
+
color: var(--st-semantic-text-primary);
|
|
219
|
+
font-weight: 600;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.st-configItemCard__badge {
|
|
223
|
+
align-items: center;
|
|
224
|
+
border-radius: 9999px;
|
|
225
|
+
display: inline-flex;
|
|
226
|
+
flex: 0 0 auto;
|
|
227
|
+
font-size: 0.6875rem;
|
|
228
|
+
font-weight: 500;
|
|
229
|
+
gap: var(--st-spacing-1, 0.25rem);
|
|
230
|
+
line-height: 1;
|
|
231
|
+
padding: 0.1875rem var(--st-spacing-2, 0.5rem);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.st-configItemCard__badge--system {
|
|
235
|
+
background: var(--st-semantic-surface-subtle);
|
|
236
|
+
color: var(--st-semantic-text-secondary);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.st-configItemCard__badge--custom {
|
|
240
|
+
background: var(--st-semantic-feedback-info);
|
|
241
|
+
color: var(--st-semantic-text-inverse);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.st-configItemCard__badgeIcon {
|
|
245
|
+
display: block;
|
|
246
|
+
flex: 0 0 auto;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.st-configItemCard__key {
|
|
250
|
+
color: var(--st-semantic-text-muted);
|
|
251
|
+
font-size: 0.75rem;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.st-configItemCard__description {
|
|
255
|
+
color: var(--st-semantic-text-secondary);
|
|
256
|
+
font-size: 0.875rem;
|
|
257
|
+
line-height: 1.4;
|
|
258
|
+
margin: var(--st-spacing-1, 0.25rem) 0 0;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.st-configItemCard__actions {
|
|
262
|
+
display: flex;
|
|
263
|
+
gap: var(--st-spacing-1, 0.25rem);
|
|
264
|
+
margin-top: var(--st-spacing-3, 0.75rem);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.st-configItemCard__action {
|
|
268
|
+
align-items: center;
|
|
269
|
+
background: transparent;
|
|
270
|
+
border: none;
|
|
271
|
+
border-radius: var(--st-radius-sm, 0.25rem);
|
|
272
|
+
color: var(--st-semantic-text-muted);
|
|
273
|
+
cursor: var(--st-cursor-interactive, pointer);
|
|
274
|
+
display: inline-flex;
|
|
275
|
+
justify-content: center;
|
|
276
|
+
padding: var(--st-spacing-1, 0.25rem);
|
|
277
|
+
transition: color var(--st-motion-fast, 120ms) var(--st-motion-easing, ease),
|
|
278
|
+
background-color var(--st-motion-fast, 120ms) var(--st-motion-easing, ease);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.st-configItemCard__action:hover:not(:disabled) {
|
|
282
|
+
background: var(--st-semantic-surface-hover);
|
|
283
|
+
color: var(--st-semantic-text-primary);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.st-configItemCard__action--warning:hover:not(:disabled) {
|
|
287
|
+
color: var(--st-semantic-feedback-warning);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.st-configItemCard__action--danger:hover:not(:disabled) {
|
|
291
|
+
color: var(--st-semantic-feedback-error);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.st-configItemCard__action:focus-visible {
|
|
295
|
+
outline: 2px solid var(--st-semantic-border-interactive);
|
|
296
|
+
outline-offset: 2px;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.st-configItemCard__action:disabled {
|
|
300
|
+
cursor: not-allowed;
|
|
301
|
+
opacity: 0.5;
|
|
302
|
+
}
|
|
303
|
+
</style>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
export type ConfigItemSourceLevel = "code" | "admin" | "user";
|
|
4
|
+
export type ConfigItem = {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
key?: string;
|
|
8
|
+
description?: string | null;
|
|
9
|
+
/** Provenance : `code`/`admin` = système (verrouillé), `user` = personnalisé. */
|
|
10
|
+
sourceLevel: ConfigItemSourceLevel;
|
|
11
|
+
/** Identifiant du parent si l'item est une copie d'un défaut système. */
|
|
12
|
+
parentId?: string | null;
|
|
13
|
+
version?: number;
|
|
14
|
+
};
|
|
15
|
+
export type ConfigItemCardProps = Omit<HTMLAttributes<HTMLElement>, "class"> & {
|
|
16
|
+
/** L'item de configuration décrit par la carte. */
|
|
17
|
+
item: ConfigItem;
|
|
18
|
+
/** Vrai si une copie de cet item système existe déjà : masque l'action Copier. */
|
|
19
|
+
hasCopy?: boolean;
|
|
20
|
+
/** Action Copier (items système uniquement). */
|
|
21
|
+
onCopy?: (id: string) => void;
|
|
22
|
+
/** Action Éditer (items copiés ou créés par l'utilisateur). */
|
|
23
|
+
onEdit?: (id: string) => void;
|
|
24
|
+
/** Action Réinitialiser (items copiés uniquement). */
|
|
25
|
+
onReset?: (id: string) => void;
|
|
26
|
+
/** Action Supprimer (items créés par l'utilisateur uniquement). */
|
|
27
|
+
onDelete?: (id: string) => void;
|
|
28
|
+
/** Désactive toutes les actions. */
|
|
29
|
+
disabled?: boolean;
|
|
30
|
+
class?: string;
|
|
31
|
+
/** Contenu supplémentaire sous la carte (formulaire d'édition, détails…). */
|
|
32
|
+
children?: Snippet;
|
|
33
|
+
};
|
|
34
|
+
declare const ConfigItemCard: import("svelte").Component<ConfigItemCardProps, {}, "">;
|
|
35
|
+
type ConfigItemCard = ReturnType<typeof ConfigItemCard>;
|
|
36
|
+
export default ConfigItemCard;
|
|
37
|
+
//# sourceMappingURL=ConfigItemCard.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConfigItemCard.svelte.d.ts","sourceRoot":"","sources":["../src/lib/ConfigItemCard.svelte.ts"],"names":[],"mappings":"AAGE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEtC,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;AAE9D,MAAM,MAAM,UAAU,GAAG;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,iFAAiF;IACjF,WAAW,EAAE,qBAAqB,CAAC;IACnC,yEAAyE;IACzE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,IAAI,CAAC,cAAc,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC,GAAG;IAC7E,mDAAmD;IACnD,IAAI,EAAE,UAAU,CAAC;IACjB,kFAAkF;IAClF,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gDAAgD;IAChD,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,+DAA+D;IAC/D,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,sDAAsD;IACtD,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,mEAAmE;IACnE,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,oCAAoC;IACpC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AA0FJ,QAAA,MAAM,cAAc,yDAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
|
|
@@ -44,7 +44,7 @@ type DataTableProps = Omit<HTMLTableAttributes, "class"> & {
|
|
|
44
44
|
onRowClick?: (row: DataTableRow) => void;
|
|
45
45
|
class?: string;
|
|
46
46
|
};
|
|
47
|
-
declare const DataTable: import("svelte").Component<DataTableProps, {}, "
|
|
47
|
+
declare const DataTable: import("svelte").Component<DataTableProps, {}, "page" | "selectedIds" | "sortBy">;
|
|
48
48
|
type DataTable = ReturnType<typeof DataTable>;
|
|
49
49
|
export default DataTable;
|
|
50
50
|
//# sourceMappingURL=DataTable.svelte.d.ts.map
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
3
|
+
|
|
4
|
+
/** A single field error: `href` points to the offending control, `text` is the message. */
|
|
5
|
+
export type ErrorSummaryItem = { href: string; text: string };
|
|
6
|
+
|
|
7
|
+
type ErrorSummaryProps = Omit<HTMLAttributes<HTMLElement>, "class"> & {
|
|
8
|
+
/** Summary heading. */
|
|
9
|
+
heading?: string;
|
|
10
|
+
/** The list of errors, each linking to the field that needs attention. */
|
|
11
|
+
errors?: ErrorSummaryItem[];
|
|
12
|
+
class?: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
let {
|
|
16
|
+
heading = "There was a problem",
|
|
17
|
+
errors = [],
|
|
18
|
+
class: className,
|
|
19
|
+
...rest
|
|
20
|
+
}: ErrorSummaryProps = $props();
|
|
21
|
+
|
|
22
|
+
const classes = () => ["st-error-summary", className].filter(Boolean).join(" ");
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<!--
|
|
26
|
+
ErrorSummary — GCDS « Error summary »: an aggregated list of a form's errors,
|
|
27
|
+
each entry linking to the field that needs attention. role="alert" + tabindex
|
|
28
|
+
so it can take focus when shown after a failed submission.
|
|
29
|
+
-->
|
|
30
|
+
<section {...rest} class={classes()} role="alert" tabindex="-1">
|
|
31
|
+
<h2 class="st-error-summary__heading">{heading}</h2>
|
|
32
|
+
{#if errors.length > 0}
|
|
33
|
+
<ul class="st-error-summary__list">
|
|
34
|
+
{#each errors as error (error.href)}
|
|
35
|
+
<li class="st-error-summary__item">
|
|
36
|
+
<a class="st-error-summary__link" href={error.href}>{error.text}</a>
|
|
37
|
+
</li>
|
|
38
|
+
{/each}
|
|
39
|
+
</ul>
|
|
40
|
+
{/if}
|
|
41
|
+
</section>
|
|
42
|
+
|
|
43
|
+
<style>
|
|
44
|
+
.st-error-summary {
|
|
45
|
+
background: var(--st-semantic-surface-default);
|
|
46
|
+
border: 2px solid var(--st-semantic-feedback-error);
|
|
47
|
+
border-radius: var(--st-radius-md, 0.25rem);
|
|
48
|
+
color: var(--st-semantic-text-primary);
|
|
49
|
+
padding: var(--st-spacing-4, 1rem);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.st-error-summary__heading {
|
|
53
|
+
font-size: 1.125rem;
|
|
54
|
+
font-weight: 700;
|
|
55
|
+
margin: 0 0 var(--st-spacing-2, 0.5rem);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.st-error-summary__list {
|
|
59
|
+
display: grid;
|
|
60
|
+
gap: var(--st-spacing-1, 0.25rem);
|
|
61
|
+
margin: 0;
|
|
62
|
+
padding-left: var(--st-spacing-4, 1rem);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.st-error-summary__link {
|
|
66
|
+
color: var(--st-semantic-feedback-error);
|
|
67
|
+
font-weight: 600;
|
|
68
|
+
text-decoration: underline;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.st-error-summary__link:hover,
|
|
72
|
+
.st-error-summary__link:focus-visible {
|
|
73
|
+
text-decoration: none;
|
|
74
|
+
}
|
|
75
|
+
</style>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
2
|
+
/** A single field error: `href` points to the offending control, `text` is the message. */
|
|
3
|
+
export type ErrorSummaryItem = {
|
|
4
|
+
href: string;
|
|
5
|
+
text: string;
|
|
6
|
+
};
|
|
7
|
+
type ErrorSummaryProps = Omit<HTMLAttributes<HTMLElement>, "class"> & {
|
|
8
|
+
/** Summary heading. */
|
|
9
|
+
heading?: string;
|
|
10
|
+
/** The list of errors, each linking to the field that needs attention. */
|
|
11
|
+
errors?: ErrorSummaryItem[];
|
|
12
|
+
class?: string;
|
|
13
|
+
};
|
|
14
|
+
declare const ErrorSummary: import("svelte").Component<ErrorSummaryProps, {}, "">;
|
|
15
|
+
type ErrorSummary = ReturnType<typeof ErrorSummary>;
|
|
16
|
+
export default ErrorSummary;
|
|
17
|
+
//# sourceMappingURL=ErrorSummary.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ErrorSummary.svelte.d.ts","sourceRoot":"","sources":["../src/lib/ErrorSummary.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGpD,2FAA2F;AAC3F,MAAM,MAAM,gBAAgB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9D,KAAK,iBAAiB,GAAG,IAAI,CAAC,cAAc,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC,GAAG;IACpE,uBAAuB;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,0EAA0E;IAC1E,MAAM,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAkCJ,QAAA,MAAM,YAAY,uDAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
|