@arkyn/components 3.0.9 → 3.0.11
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/components/calendar/_calendarProvider.d.ts.map +1 -1
- package/dist/components/datePicker/datePickerCalendarContainer/index.d.ts.map +1 -1
- package/dist/components/googleAnalytics/snippets/generateGAElements.d.ts.map +1 -1
- package/dist/components/googleTagManager/snippets/appendToDataLayer.d.ts.map +1 -1
- package/dist/components/googleTagManager/snippets/generateGTMElements.d.ts.map +1 -1
- package/dist/components/multiSelect/multiSelectOptionsContainer/index.d.ts.map +1 -1
- package/dist/components/richText/index.d.ts.map +1 -1
- package/dist/components/select/selectOptionsContainer/index.d.ts.map +1 -1
- package/dist/hooks/useAutomation.d.ts.map +1 -1
- package/dist/hooks/useFlipPosition.d.ts +38 -0
- package/dist/hooks/useFlipPosition.d.ts.map +1 -0
- package/dist/index.js +560 -534
- package/dist/index.js.map +1 -1
- package/dist/modules/components/calendar/_calendarProvider.js +47 -34
- package/dist/modules/components/calendar/_calendarProvider.js.map +1 -1
- package/dist/modules/components/datePicker/datePickerCalendarContainer/index.js +12 -19
- package/dist/modules/components/datePicker/datePickerCalendarContainer/index.js.map +1 -1
- package/dist/modules/components/googleAnalytics/snippets/generateGAElements.js +7 -6
- package/dist/modules/components/googleAnalytics/snippets/generateGAElements.js.map +1 -1
- package/dist/modules/components/googleTagManager/snippets/appendToDataLayer.js +7 -6
- package/dist/modules/components/googleTagManager/snippets/appendToDataLayer.js.map +1 -1
- package/dist/modules/components/googleTagManager/snippets/generateGTMElements.js +20 -14
- package/dist/modules/components/googleTagManager/snippets/generateGTMElements.js.map +1 -1
- package/dist/modules/components/multiSelect/multiSelectOptionsContainer/index.js +21 -28
- package/dist/modules/components/multiSelect/multiSelectOptionsContainer/index.js.map +1 -1
- package/dist/modules/components/richText/index.js +1 -1
- package/dist/modules/components/richText/index.js.map +1 -1
- package/dist/modules/components/select/selectOptionsContainer/index.js +21 -28
- package/dist/modules/components/select/selectOptionsContainer/index.js.map +1 -1
- package/dist/modules/hooks/useAutomation.js +29 -30
- package/dist/modules/hooks/useAutomation.js.map +1 -1
- package/dist/modules/hooks/useFlipPosition.js +26 -0
- package/dist/modules/hooks/useFlipPosition.js.map +1 -0
- package/dist/modules/utils/escapeForInlineScript.js +14 -0
- package/dist/modules/utils/escapeForInlineScript.js.map +1 -0
- package/dist/utils/escapeForInlineScript.d.ts +23 -0
- package/dist/utils/escapeForInlineScript.d.ts.map +1 -0
- package/package.json +9 -6
|
@@ -2,46 +2,45 @@ import { badResponses as e } from "../templates/badResponses.js";
|
|
|
2
2
|
import { successResponses as t } from "../templates/successResponses.js";
|
|
3
3
|
import { useModal as n } from "./useModal.js";
|
|
4
4
|
import { useToast as r } from "./useToast.js";
|
|
5
|
-
import {
|
|
6
|
-
import { scroller as
|
|
5
|
+
import { useEffect as i } from "react";
|
|
6
|
+
import { scroller as a } from "react-scroll";
|
|
7
7
|
//#region src/hooks/useAutomation.ts
|
|
8
|
-
function
|
|
9
|
-
let { closeAll:
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
8
|
+
function o(o) {
|
|
9
|
+
let { closeAll: s } = n(), { showToast: c } = r();
|
|
10
|
+
i(() => {
|
|
11
|
+
let n = o?.closeModal, r = o?.message, i = o?.name, l = o?.cause?.data?.scrollTo, u = o?.cause?.fieldErrors ? Object.values(o?.cause?.fieldErrors)[0] : null;
|
|
12
|
+
if (n && s(), l && a.scrollTo(l, {
|
|
13
|
+
smooth: !0,
|
|
14
|
+
offset: 20
|
|
15
|
+
}), !(!r && !u)) {
|
|
16
|
+
if (t.includes(i)) {
|
|
17
|
+
c({
|
|
18
|
+
message: r,
|
|
19
|
+
type: "success"
|
|
19
20
|
});
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (e.includes(i)) {
|
|
24
|
+
if (u) {
|
|
25
|
+
c({
|
|
26
|
+
message: u,
|
|
27
|
+
type: "danger"
|
|
28
|
+
});
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
r !== "Unprocessable entity" && c({
|
|
32
|
+
message: r,
|
|
22
33
|
type: "danger"
|
|
23
34
|
});
|
|
24
35
|
}
|
|
25
36
|
}
|
|
26
37
|
}, [
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
f,
|
|
30
|
-
l
|
|
31
|
-
]);
|
|
32
|
-
a(() => {
|
|
33
|
-
u && c(), p && o.scrollTo(p, {
|
|
34
|
-
smooth: !0,
|
|
35
|
-
offset: 20
|
|
36
|
-
}), h();
|
|
37
|
-
}, [
|
|
38
|
-
u,
|
|
39
|
-
p,
|
|
40
|
-
h,
|
|
38
|
+
o,
|
|
39
|
+
s,
|
|
41
40
|
c
|
|
42
41
|
]);
|
|
43
42
|
}
|
|
44
43
|
//#endregion
|
|
45
|
-
export {
|
|
44
|
+
export { o as useAutomation };
|
|
46
45
|
|
|
47
46
|
//# sourceMappingURL=useAutomation.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAutomation.js","names":[],"sources":["../../../src/hooks/useAutomation.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"useAutomation.js","names":[],"sources":["../../../src/hooks/useAutomation.ts"],"sourcesContent":["import { useEffect } from \"react\";\nimport { scroller } from \"react-scroll\";\nimport { badResponses } from \"../templates/badResponses\";\nimport { successResponses } from \"../templates/successResponses\";\nimport { useModal } from \"./useModal\";\nimport { useToast } from \"./useToast\";\n\n/**\n * useAutomation, runs UI side-effects (close modals, scroll, toast) in response to a server action payload.\n *\n * Pass the raw response from a form submission. On every change the hook:\n * 1. Closes all open modals if `closeModal` is `true`.\n * 2. Smooth-scrolls to a named element if `cause.data.scrollTo` is set.\n * 3. Shows a toast based on `name` (matched against `successResponses`/`badResponses`) and `message`.\n * When `cause.fieldErrors` is present, the first field error takes priority in the toast.\n * The message `\"Unprocessable entity\"` is intentionally suppressed.\n *\n * @param formResponseData - Raw server response payload.\n * @param formResponseData.closeModal - Closes all open modals when `true`.\n * @param formResponseData.name - Response identifier matched against success/bad response lists.\n * @param formResponseData.message - Text shown in the toast notification.\n * @param formResponseData.cause.data.scrollTo - Element name to scroll to via `react-scroll`.\n * @param formResponseData.cause.fieldErrors - Field-level errors; the first value is shown in the toast.\n *\n * @example\n * ```tsx\n * // After a successful create action\n * useAutomation({ closeModal: true, name: \"created\", message: \"Saved successfully\" });\n * ```\n *\n * @example\n * ```tsx\n * // Validation error with field-level feedback and scroll\n * useAutomation({\n * name: \"validation_error\",\n * message: \"Invalid payload\",\n * cause: {\n * data: { scrollTo: \"email\" },\n * fieldErrors: { email: \"E-mail is required\" },\n * },\n * });\n * ```\n */\n\n// biome-ignore lint/suspicious/noExplicitAny: intentional\nfunction useAutomation(formResponseData: any) {\n\tconst { closeAll } = useModal();\n\tconst { showToast } = useToast();\n\n\tuseEffect(() => {\n\t\tconst closeModal = formResponseData?.closeModal;\n\t\tconst message = formResponseData?.message;\n\t\tconst name = formResponseData?.name;\n\t\tconst scrollTo = formResponseData?.cause?.data?.scrollTo;\n\t\tconst firstErrorField = formResponseData?.cause?.fieldErrors\n\t\t\t? (Object.values(formResponseData?.cause?.fieldErrors)[0] as string)\n\t\t\t: null;\n\n\t\tif (closeModal) closeAll();\n\t\tif (scrollTo) scroller.scrollTo(scrollTo, { smooth: true, offset: 20 });\n\n\t\tif (!message && !firstErrorField) return;\n\n\t\tif (successResponses.includes(name)) {\n\t\t\tshowToast({ message, type: \"success\" });\n\t\t\treturn;\n\t\t}\n\n\t\tif (!badResponses.includes(name)) return;\n\t\tif (firstErrorField) {\n\t\t\tshowToast({ message: firstErrorField, type: \"danger\" });\n\t\t\treturn;\n\t\t}\n\n\t\tif (message === \"Unprocessable entity\") return;\n\t\tshowToast({ message, type: \"danger\" });\n\t}, [formResponseData, closeAll, showToast]);\n}\n\nexport { useAutomation };\n"],"mappings":";;;;;;;AA6CA,SAAS,EAAc,GAAuB;CAC7C,IAAM,EAAE,gBAAa,EAAS,GACxB,EAAE,iBAAc,EAAS;CAE/B,QAAgB;EACf,IAAM,IAAa,GAAkB,YAC/B,IAAU,GAAkB,SAC5B,IAAO,GAAkB,MACzB,IAAW,GAAkB,OAAO,MAAM,UAC1C,IAAkB,GAAkB,OAAO,cAC7C,OAAO,OAAO,GAAkB,OAAO,WAAW,CAAC,CAAC,KACrD;EAEH,IAAI,KAAY,EAAS,GACrB,KAAU,EAAS,SAAS,GAAU;GAAE,QAAQ;GAAM,QAAQ;EAAG,CAAC,GAElE,GAAC,KAAW,CAAC,IAEjB;OAAI,EAAiB,SAAS,CAAI,GAAG;IACpC,EAAU;KAAE;KAAS,MAAM;IAAU,CAAC;IACtC;GACD;GAEK,MAAa,SAAS,CAAI,GAC/B;QAAI,GAAiB;KACpB,EAAU;MAAE,SAAS;MAAiB,MAAM;KAAS,CAAC;KACtD;IACD;IAEI,MAAY,0BAChB,EAAU;KAAE;KAAS,MAAM;IAAS,CAAC;GAHrC;EANA;CAUD,GAAG;EAAC;EAAkB;EAAU;CAAS,CAAC;AAC3C"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { useEffect as e, useState as t } from "react";
|
|
2
|
+
//#region src/hooks/useFlipPosition.ts
|
|
3
|
+
function n(n, r, i) {
|
|
4
|
+
let [a, o] = t("bottom");
|
|
5
|
+
return e(() => {
|
|
6
|
+
if (!r) return;
|
|
7
|
+
function e() {
|
|
8
|
+
if (!n.current) return;
|
|
9
|
+
let e = n.current.parentElement;
|
|
10
|
+
if (!e) return;
|
|
11
|
+
let t = e.getBoundingClientRect();
|
|
12
|
+
window.innerHeight - t.bottom < i && t.top > i ? o("top") : o("bottom");
|
|
13
|
+
}
|
|
14
|
+
return e(), window.addEventListener("resize", e), () => {
|
|
15
|
+
window.removeEventListener("resize", e);
|
|
16
|
+
};
|
|
17
|
+
}, [
|
|
18
|
+
n,
|
|
19
|
+
r,
|
|
20
|
+
i
|
|
21
|
+
]), a;
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
export { n as useFlipPosition };
|
|
25
|
+
|
|
26
|
+
//# sourceMappingURL=useFlipPosition.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useFlipPosition.js","names":[],"sources":["../../../src/hooks/useFlipPosition.ts"],"sourcesContent":["import { type RefObject, useEffect, useState } from \"react\";\n\ntype FlipPosition = \"bottom\" | \"top\";\n\n/**\n * useFlipPosition, computes whether a floating panel should render `\"bottom\"`\n * or `\"top\"` relative to its anchor, flipping to `\"top\"` when there isn't\n * enough viewport space below it.\n *\n * The anchor used for the calculation is `containerRef.current.parentElement`,\n * matching how `SelectOptionsContainer`, `MultiSelectOptionsContainer`, and\n * `DatePickerCalendarContainer` render their floating panel as a sibling\n * positioned relative to that parent.\n *\n * Recomputes when `isActive` becomes `true`, and again on every `resize`\n * event while `isActive` stays `true`, so the position stays correct if the\n * viewport is resized while the panel is open.\n *\n * Only positioning is handled here — no selection, focus, or keyboard logic.\n *\n * @param containerRef - Ref to the floating panel element (its `parentElement` is used as the anchor).\n * @param isActive - When `true`, the position is computed and kept up to date.\n * @param estimatedContainerHeight - Estimated height (px) of the floating panel, used to decide if it fits below the anchor.\n * @returns `\"bottom\"` or `\"top\"`.\n *\n * @example\n * ```tsx\n * function CustomFloatingPanel({ isOpen }) {\n * const containerRef = useRef<HTMLDivElement>(null);\n * const position = useFlipPosition(containerRef, isOpen, 300);\n * return isOpen ? (\n * <div ref={containerRef} className={`panel ${position}`} />\n * ) : null;\n * }\n * ```\n */\n\nfunction useFlipPosition(\n\tcontainerRef: RefObject<HTMLElement | null>,\n\tisActive: boolean,\n\testimatedContainerHeight: number,\n): FlipPosition {\n\tconst [position, setPosition] = useState<FlipPosition>(\"bottom\");\n\n\tuseEffect(() => {\n\t\tif (!isActive) return;\n\n\t\tfunction checkContainerPosition() {\n\t\t\tif (!containerRef.current) return;\n\n\t\t\tconst parentElement = containerRef.current.parentElement;\n\t\t\tif (!parentElement) return;\n\n\t\t\tconst parentRect = parentElement.getBoundingClientRect();\n\t\t\tconst viewportHeight = window.innerHeight;\n\n\t\t\tconst spaceBelow = viewportHeight - parentRect.bottom;\n\n\t\t\tif (\n\t\t\t\tspaceBelow < estimatedContainerHeight &&\n\t\t\t\tparentRect.top > estimatedContainerHeight\n\t\t\t) {\n\t\t\t\tsetPosition(\"top\");\n\t\t\t} else {\n\t\t\t\tsetPosition(\"bottom\");\n\t\t\t}\n\t\t}\n\n\t\tcheckContainerPosition();\n\n\t\twindow.addEventListener(\"resize\", checkContainerPosition);\n\n\t\treturn () => {\n\t\t\twindow.removeEventListener(\"resize\", checkContainerPosition);\n\t\t};\n\t}, [containerRef, isActive, estimatedContainerHeight]);\n\n\treturn position;\n}\n\nexport type { FlipPosition };\nexport { useFlipPosition };\n"],"mappings":";;AAqCA,SAAS,EACR,GACA,GACA,GACe;CACf,IAAM,CAAC,GAAU,KAAe,EAAuB,QAAQ;CAmC/D,OAjCA,QAAgB;EACf,IAAI,CAAC,GAAU;EAEf,SAAS,IAAyB;GACjC,IAAI,CAAC,EAAa,SAAS;GAE3B,IAAM,IAAgB,EAAa,QAAQ;GAC3C,IAAI,CAAC,GAAe;GAEpB,IAAM,IAAa,EAAc,sBAAsB;GAKvD,AAJuB,OAAO,cAEM,EAAW,SAGjC,KACb,EAAW,MAAM,IAEjB,EAAY,KAAK,IAEjB,EAAY,QAAQ;EAEtB;EAMA,OAJA,EAAuB,GAEvB,OAAO,iBAAiB,UAAU,CAAsB,SAE3C;GACZ,OAAO,oBAAoB,UAAU,CAAsB;EAC5D;CACD,GAAG;EAAC;EAAc;EAAU;CAAwB,CAAC,GAE9C;AACR"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//#region src/utils/escapeForInlineScript.ts
|
|
2
|
+
function e(e) {
|
|
3
|
+
return JSON.stringify(e ?? "").replace(/</g, "\\u003C");
|
|
4
|
+
}
|
|
5
|
+
function t(e) {
|
|
6
|
+
return JSON.stringify(e).replace(/</g, "\\u003C");
|
|
7
|
+
}
|
|
8
|
+
function n(e) {
|
|
9
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(e);
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
12
|
+
export { n as isValidJsIdentifier, t as toSafeScriptJson, e as toSafeScriptString };
|
|
13
|
+
|
|
14
|
+
//# sourceMappingURL=escapeForInlineScript.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"escapeForInlineScript.js","names":[],"sources":["../../../src/utils/escapeForInlineScript.ts"],"sourcesContent":["/**\n * Serializes a value so it can be embedded as a JS string literal inside an inline\n * `<script>` snippet. Wraps `JSON.stringify` (which already escapes quotes/backslashes/\n * control characters) and additionally escapes `<` so a value containing `</script>`\n * can't prematurely close the surrounding script tag.\n *\n * @example\n * ```typescript\n * `gtag('config', ${toSafeScriptString(measurementId)});`\n * // measurementId = \"G-XXXX\" -> gtag('config', \"G-XXXX\");\n * // measurementId = \"\\\"); alert(1); //\" -> gtag('config', \"\\\"); alert(1); //\");\n * ```\n */\nfunction toSafeScriptString(value: unknown): string {\n\treturn JSON.stringify(value ?? \"\").replace(/</g, \"\\\\u003C\");\n}\n\n/**\n * Serializes an object/array to JSON for embedding inside an inline `<script>` snippet,\n * escaping `<` so a value containing `</script>` can't prematurely close the script tag.\n */\nfunction toSafeScriptJson(value: unknown): string {\n\treturn JSON.stringify(value).replace(/</g, \"\\\\u003C\");\n}\n\n/** Whether `name` is a valid bare JS identifier (safe to use as `window.<name>`). */\nfunction isValidJsIdentifier(name: string): boolean {\n\treturn /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name);\n}\n\nexport { isValidJsIdentifier, toSafeScriptJson, toSafeScriptString };\n"],"mappings":";AAaA,SAAS,EAAmB,GAAwB;CACnD,OAAO,KAAK,UAAU,KAAS,EAAE,CAAC,CAAC,QAAQ,MAAM,SAAS;AAC3D;AAMA,SAAS,EAAiB,GAAwB;CACjD,OAAO,KAAK,UAAU,CAAK,CAAC,CAAC,QAAQ,MAAM,SAAS;AACrD;AAGA,SAAS,EAAoB,GAAuB;CACnD,OAAO,6BAA6B,KAAK,CAAI;AAC9C"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serializes a value so it can be embedded as a JS string literal inside an inline
|
|
3
|
+
* `<script>` snippet. Wraps `JSON.stringify` (which already escapes quotes/backslashes/
|
|
4
|
+
* control characters) and additionally escapes `<` so a value containing `</script>`
|
|
5
|
+
* can't prematurely close the surrounding script tag.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* `gtag('config', ${toSafeScriptString(measurementId)});`
|
|
10
|
+
* // measurementId = "G-XXXX" -> gtag('config', "G-XXXX");
|
|
11
|
+
* // measurementId = "\"); alert(1); //" -> gtag('config', "\"); alert(1); //");
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
declare function toSafeScriptString(value: unknown): string;
|
|
15
|
+
/**
|
|
16
|
+
* Serializes an object/array to JSON for embedding inside an inline `<script>` snippet,
|
|
17
|
+
* escaping `<` so a value containing `</script>` can't prematurely close the script tag.
|
|
18
|
+
*/
|
|
19
|
+
declare function toSafeScriptJson(value: unknown): string;
|
|
20
|
+
/** Whether `name` is a valid bare JS identifier (safe to use as `window.<name>`). */
|
|
21
|
+
declare function isValidJsIdentifier(name: string): boolean;
|
|
22
|
+
export { isValidJsIdentifier, toSafeScriptJson, toSafeScriptString };
|
|
23
|
+
//# sourceMappingURL=escapeForInlineScript.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"escapeForInlineScript.d.ts","sourceRoot":"","sources":["../../src/utils/escapeForInlineScript.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,iBAAS,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAElD;AAED;;;GAGG;AACH,iBAAS,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEhD;AAED,qFAAqF;AACrF,iBAAS,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAElD;AAED,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arkyn/components",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.11",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"module": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -48,13 +48,13 @@
|
|
|
48
48
|
"prebuild": "rm -rf dist",
|
|
49
49
|
"publish:beta": "bash ../../scripts/publish-idempotent.sh beta",
|
|
50
50
|
"publish:latest": "bash ../../scripts/publish-idempotent.sh latest",
|
|
51
|
-
"release:beta": "bun
|
|
52
|
-
"release:patch": "bun
|
|
53
|
-
"release:minor": "bun
|
|
54
|
-
"release:major": "bun
|
|
51
|
+
"release:beta": "bun ../../scripts/generate-version.ts beta",
|
|
52
|
+
"release:patch": "bun ../../scripts/generate-version.ts patch",
|
|
53
|
+
"release:minor": "bun ../../scripts/generate-version.ts minor",
|
|
54
|
+
"release:major": "bun ../../scripts/generate-version.ts major",
|
|
55
55
|
"test": "bunx vitest --config vitest.config.ts --run",
|
|
56
56
|
"test:watch": "bunx vitest --config vitest.config.ts --watch",
|
|
57
|
-
"typecheck": "bunx tsc --project tsconfig.json --noEmit"
|
|
57
|
+
"typecheck": "bunx tsc --project tsconfig.json --noEmit && bunx tsc --project tsconfig.scripts.json --noEmit"
|
|
58
58
|
},
|
|
59
59
|
"sideEffects": [
|
|
60
60
|
"*.css"
|
|
@@ -123,11 +123,14 @@
|
|
|
123
123
|
"@types/react-dom": "^19.2.3",
|
|
124
124
|
"@types/react-scroll": "^1.8.10",
|
|
125
125
|
"@vitejs/plugin-react": "^6.0.3",
|
|
126
|
+
"@vitest/browser": "4.1.11",
|
|
127
|
+
"@vitest/browser-playwright": "4.1.11",
|
|
126
128
|
"html-react-parser": "6.1.7",
|
|
127
129
|
"is-hotkey": "^0.2.0",
|
|
128
130
|
"jsdom": "^30.0.1",
|
|
129
131
|
"lucide-react": "catalog:",
|
|
130
132
|
"mapbox-gl": "^3.25.0",
|
|
133
|
+
"playwright": "^1.62.1",
|
|
131
134
|
"postcss": "^8.5.16",
|
|
132
135
|
"react": "^19.2.7",
|
|
133
136
|
"react-dom": "^19.2.7",
|