@ilokesto/overlay 1.0.0

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/README.ko.md ADDED
@@ -0,0 +1,143 @@
1
+ # @ilokesto/overlay
2
+
3
+ [English](./README.md) | **한국어**
4
+
5
+ `@ilokesto/store` 위에 얹는 작은 React overlay runtime입니다.
6
+
7
+ 이 패키지는 provider-scoped overlay core, built-in host, item lifecycle 관리, adapter 주입 구조를 제공합니다. modal이나 toast 의미론은 의도적으로 코어에 넣지 않아서, 상위 패키지가 같은 runtime 위에서 자신만의 동작을 구현할 수 있게 되어 있습니다.
8
+
9
+ ## Features
10
+
11
+ - 전역 싱글턴이 아닌 provider-scoped overlay runtime
12
+ - adapter registry를 통해 overlay item을 렌더링하는 built-in host
13
+ - 같은 store lifecycle 위에서 동기/비동기 overlay 열기 지원
14
+ - runtime core와 공용 contract의 명확한 분리
15
+ - overlay를 열고 닫고 제거하고 관찰하는 작은 공개 API
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ pnpm add @ilokesto/overlay react
21
+ ```
22
+
23
+ 또는
24
+
25
+ ```bash
26
+ npm install @ilokesto/overlay react
27
+ ```
28
+
29
+ ## Basic Usage
30
+
31
+ ```tsx
32
+ import { OverlayProvider, useOverlay, type OverlayAdapterMap } from '@ilokesto/overlay';
33
+
34
+ const adapters: OverlayAdapterMap = {
35
+ modal: ({ isOpen, close, title }) => {
36
+ if (!isOpen) {
37
+ return null;
38
+ }
39
+
40
+ return (
41
+ <div role="dialog" aria-modal="true">
42
+ <h1>{String(title)}</h1>
43
+ <button onClick={() => close(true)}>Confirm</button>
44
+ </div>
45
+ );
46
+ },
47
+ };
48
+
49
+ function ExampleButton() {
50
+ const { display } = useOverlay();
51
+
52
+ const handleClick = async () => {
53
+ const result = await display<boolean>({
54
+ type: 'modal',
55
+ props: { title: 'Delete this item?' },
56
+ });
57
+
58
+ console.log(result);
59
+ };
60
+
61
+ return <button onClick={handleClick}>Open</button>;
62
+ }
63
+
64
+ export function App() {
65
+ return (
66
+ <OverlayProvider adapters={adapters}>
67
+ <ExampleButton />
68
+ </OverlayProvider>
69
+ );
70
+ }
71
+ ```
72
+
73
+ ## Source Layout
74
+
75
+ ```text
76
+ src/
77
+ core/
78
+ createOverlayStore.ts
79
+ OverlayProvider.tsx
80
+ OverlayHost.tsx
81
+ useOverlay.ts
82
+ useOverlayItems.ts
83
+ contracts/
84
+ adapter.ts
85
+ overlay.ts
86
+ index.ts
87
+ ```
88
+
89
+ ## Responsibilities
90
+
91
+ ### `src/core`
92
+
93
+ - `createOverlayStore.ts` → provider 단위 overlay store를 만들고 `open`, `close`, `remove`, `clear` 수명주기를 관리합니다
94
+ - `OverlayProvider.tsx` → store를 만들거나 주입받아 context로 노출하고 built-in `OverlayHost`를 마운트합니다
95
+ - `OverlayHost.tsx` → 현재 overlay item 목록을 읽고 각 item을 `adapters[item.type]`에 위임해 렌더링합니다
96
+ - `useOverlay.ts` → overlay를 열고 닫고 제거하는 명령형 API를 제공합니다
97
+ - `useOverlayItems.ts` → `useSyncExternalStore`로 현재 overlay item 목록을 구독합니다
98
+
99
+ ### `src/contracts`
100
+
101
+ - `adapter.ts` → `OverlayRenderProps`, `OverlayAdapterComponent`, `OverlayAdapterMap` 같은 adapter 렌더링 계약을 정의합니다
102
+ - `overlay.ts` → `OverlayItem`, `OverlayStoreApi`, `DisplayOptions`, `OverlayProviderProps` 같은 overlay runtime 계약을 정의합니다
103
+
104
+ ### `src/index.ts`
105
+
106
+ - `core/*`의 runtime API를 다시 export합니다
107
+ - `contracts/*`의 공용 타입을 다시 export합니다
108
+
109
+ ## Dependency Direction
110
+
111
+ - `core/*` 는 `contracts/*` 에 의존합니다
112
+ - `contracts/overlay.ts` 는 `contracts/adapter.ts` 에 의존합니다
113
+ - `contracts/adapter.ts` 는 runtime 코드에 의존하지 않습니다
114
+ - modal이나 toast 같은 adapter 패키지는 `@ilokesto/overlay` 에 의존해야 합니다
115
+ - `@ilokesto/overlay` 는 modal/toast 구현을 직접 import하면 안 됩니다
116
+
117
+ 한 줄로 말하면, 코어는 lifecycle과 hosting을 담당하고 adapter 패키지는 의미론과 표현을 담당합니다.
118
+
119
+ ## Adapter Packages
120
+
121
+ 이 패키지는 의도적으로 generic하게 설계되어 있습니다.
122
+
123
+ - modal 패키지는 overlay runtime 위에 modal adapter를 주입해서 사용할 수 있습니다
124
+ - toast 패키지도 같은 runtime 위에 toast adapter를 주입해서 사용할 수 있습니다
125
+ - focus trap, scroll lock, backdrop 동작, deduplication, timer, animation 같은 정책은 overlay core가 아니라 adapter 레이어에 있어야 합니다
126
+
127
+ ## Exports
128
+
129
+ - `@ilokesto/overlay` → `createOverlayStore`, `OverlayProvider`, `OverlayHost`, `useOverlay`, `useOverlayItems`
130
+ - `@ilokesto/overlay` 타입 → `src/contracts/adapter.ts`, `src/contracts/overlay.ts`, `UseOverlayReturn`에서 다시 export된 타입
131
+
132
+ ## Development
133
+
134
+ ```bash
135
+ pnpm install
136
+ pnpm run build
137
+ ```
138
+
139
+ 빌드 결과물은 `dist` 디렉터리에 생성됩니다.
140
+
141
+ ## License
142
+
143
+ MIT
package/README.md ADDED
@@ -0,0 +1,143 @@
1
+ # @ilokesto/overlay
2
+
3
+ **English** | [한국어](./README.ko.md)
4
+
5
+ A small React overlay runtime built on top of `@ilokesto/store`.
6
+
7
+ This package provides a provider-scoped overlay core with a built-in host, item lifecycle management, and adapter injection. It is intentionally headless about modal or toast semantics so higher-level packages can build on top of the same runtime without leaking behavior into the core.
8
+
9
+ ## Features
10
+
11
+ - Provider-scoped overlay runtime instead of a global singleton
12
+ - Built-in host that renders overlay items through an adapter registry
13
+ - Sync and async overlay opening through the same store lifecycle
14
+ - Clean separation between runtime core and shared contracts
15
+ - A small public API for opening, closing, removing, and observing overlays
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ pnpm add @ilokesto/overlay react
21
+ ```
22
+
23
+ or
24
+
25
+ ```bash
26
+ npm install @ilokesto/overlay react
27
+ ```
28
+
29
+ ## Basic Usage
30
+
31
+ ```tsx
32
+ import { OverlayProvider, useOverlay, type OverlayAdapterMap } from '@ilokesto/overlay';
33
+
34
+ const adapters: OverlayAdapterMap = {
35
+ modal: ({ isOpen, close, title }) => {
36
+ if (!isOpen) {
37
+ return null;
38
+ }
39
+
40
+ return (
41
+ <div role="dialog" aria-modal="true">
42
+ <h1>{String(title)}</h1>
43
+ <button onClick={() => close(true)}>Confirm</button>
44
+ </div>
45
+ );
46
+ },
47
+ };
48
+
49
+ function ExampleButton() {
50
+ const { display } = useOverlay();
51
+
52
+ const handleClick = async () => {
53
+ const result = await display<boolean>({
54
+ type: 'modal',
55
+ props: { title: 'Delete this item?' },
56
+ });
57
+
58
+ console.log(result);
59
+ };
60
+
61
+ return <button onClick={handleClick}>Open</button>;
62
+ }
63
+
64
+ export function App() {
65
+ return (
66
+ <OverlayProvider adapters={adapters}>
67
+ <ExampleButton />
68
+ </OverlayProvider>
69
+ );
70
+ }
71
+ ```
72
+
73
+ ## Source Layout
74
+
75
+ ```text
76
+ src/
77
+ core/
78
+ createOverlayStore.ts
79
+ OverlayProvider.tsx
80
+ OverlayHost.tsx
81
+ useOverlay.ts
82
+ useOverlayItems.ts
83
+ contracts/
84
+ adapter.ts
85
+ overlay.ts
86
+ index.ts
87
+ ```
88
+
89
+ ## Responsibilities
90
+
91
+ ### `src/core`
92
+
93
+ - `createOverlayStore.ts` → creates the provider-scoped overlay store and manages `open`, `close`, `remove`, and `clear`
94
+ - `OverlayProvider.tsx` → creates or receives a store, exposes it through context, and mounts the built-in `OverlayHost`
95
+ - `OverlayHost.tsx` → reads the current overlay items and dispatches each item to `adapters[item.type]`
96
+ - `useOverlay.ts` → exposes the command API for opening and dismissing overlays
97
+ - `useOverlayItems.ts` → subscribes to the current overlay item list with `useSyncExternalStore`
98
+
99
+ ### `src/contracts`
100
+
101
+ - `adapter.ts` → adapter-facing rendering contracts such as `OverlayRenderProps`, `OverlayAdapterComponent`, and `OverlayAdapterMap`
102
+ - `overlay.ts` → overlay runtime contracts such as `OverlayItem`, `OverlayStoreApi`, `DisplayOptions`, and `OverlayProviderProps`
103
+
104
+ ### `src/index.ts`
105
+
106
+ - Re-exports the runtime APIs from `core/*`
107
+ - Re-exports shared contract types from `contracts/*`
108
+
109
+ ## Dependency Direction
110
+
111
+ - `core/*` depends on `contracts/*`
112
+ - `contracts/overlay.ts` depends on `contracts/adapter.ts`
113
+ - `contracts/adapter.ts` does not depend on runtime code
114
+ - Adapter packages such as modal or toast should depend on `@ilokesto/overlay`
115
+ - `@ilokesto/overlay` should not import modal or toast implementations directly
116
+
117
+ In short, the core owns lifecycle and hosting, while adapter packages own semantics and presentation.
118
+
119
+ ## Adapter Packages
120
+
121
+ This package is intentionally generic.
122
+
123
+ - A modal package can use the overlay runtime and inject modal adapters
124
+ - A toast package can use the same runtime and inject toast adapters
125
+ - Policies such as focus trapping, scroll lock, backdrop behavior, deduplication, timers, and animations belong in the adapter layer, not in the overlay core
126
+
127
+ ## Exports
128
+
129
+ - `@ilokesto/overlay` → `createOverlayStore`, `OverlayProvider`, `OverlayHost`, `useOverlay`, `useOverlayItems`
130
+ - `@ilokesto/overlay` types → contracts from `src/contracts/adapter.ts`, `src/contracts/overlay.ts`, and `UseOverlayReturn`
131
+
132
+ ## Development
133
+
134
+ ```bash
135
+ pnpm install
136
+ pnpm run build
137
+ ```
138
+
139
+ Build outputs are generated in the `dist` directory.
140
+
141
+ ## License
142
+
143
+ MIT
@@ -0,0 +1,10 @@
1
+ import type { ComponentType } from "react";
2
+ export interface OverlayRenderProps<TResult = unknown> {
3
+ readonly id: string;
4
+ readonly isOpen: boolean;
5
+ readonly status: "open" | "closing";
6
+ readonly close: (result?: TResult) => void;
7
+ readonly remove: () => void;
8
+ }
9
+ export type OverlayAdapterComponent<TResult = unknown> = ComponentType<OverlayRenderProps<TResult> & Record<string, unknown>>;
10
+ export type OverlayAdapterMap = Readonly<Record<string, OverlayAdapterComponent>>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,37 @@
1
+ import type { ReactNode } from "react";
2
+ import type { OverlayAdapterMap } from "./adapter";
3
+ export type OverlayId = string;
4
+ export type OverlayStatus = "open" | "closing";
5
+ export interface OverlayItem {
6
+ readonly id: OverlayId;
7
+ readonly type: string;
8
+ readonly props: Readonly<Record<string, unknown>>;
9
+ readonly status: OverlayStatus;
10
+ readonly createdAt: number;
11
+ }
12
+ export interface OverlayState {
13
+ readonly items: ReadonlyArray<OverlayItem>;
14
+ }
15
+ export interface DisplayOptions {
16
+ readonly id?: OverlayId;
17
+ readonly type: string;
18
+ readonly props?: Record<string, unknown>;
19
+ }
20
+ export interface OverlayRequest<TResult = unknown> {
21
+ readonly id: OverlayId;
22
+ readonly promise: Promise<TResult | undefined>;
23
+ }
24
+ export interface OverlayStoreApi {
25
+ open: <TResult = unknown>(options: DisplayOptions) => OverlayRequest<TResult>;
26
+ close: (id: OverlayId, result?: unknown) => void;
27
+ remove: (id?: OverlayId) => void;
28
+ clear: () => void;
29
+ subscribe: (listener: () => void) => () => void;
30
+ getSnapshot: () => ReadonlyArray<OverlayItem>;
31
+ getInitialSnapshot: () => ReadonlyArray<OverlayItem>;
32
+ }
33
+ export interface OverlayProviderProps {
34
+ readonly adapters: OverlayAdapterMap;
35
+ readonly children: ReactNode;
36
+ readonly store?: OverlayStoreApi;
37
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export declare function OverlayHost(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,29 @@
1
+ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useCallback } from "react";
3
+ import { useOverlayContext } from "./OverlayProvider";
4
+ import { useOverlayItems } from "./useOverlayItems";
5
+ function OverlayItemRenderer({ item }) {
6
+ const { store, adapters } = useOverlayContext();
7
+ const Adapter = adapters[item.type];
8
+ const close = useCallback((result) => {
9
+ store.close(item.id, result);
10
+ }, [item.id, store]);
11
+ const remove = useCallback(() => {
12
+ store.remove(item.id);
13
+ }, [item.id, store]);
14
+ if (!Adapter) {
15
+ return null;
16
+ }
17
+ const renderProps = {
18
+ id: item.id,
19
+ isOpen: item.status === "open",
20
+ status: item.status,
21
+ close,
22
+ remove,
23
+ };
24
+ return _jsx(Adapter, { ...renderProps, ...item.props });
25
+ }
26
+ export function OverlayHost() {
27
+ const items = useOverlayItems();
28
+ return (_jsx(_Fragment, { children: items.map((item) => (_jsx(OverlayItemRenderer, { item: item }, item.id))) }));
29
+ }
@@ -0,0 +1,9 @@
1
+ import type { OverlayAdapterMap } from "../contracts/adapter";
2
+ import type { OverlayProviderProps, OverlayStoreApi } from "../contracts/overlay";
3
+ interface OverlayContextValue {
4
+ readonly store: OverlayStoreApi;
5
+ readonly adapters: OverlayAdapterMap;
6
+ }
7
+ export declare function useOverlayContext(): OverlayContextValue;
8
+ export declare function OverlayProvider({ adapters, children, store: storeProp, }: OverlayProviderProps): import("react/jsx-runtime").JSX.Element;
9
+ export {};
@@ -0,0 +1,17 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { createContext, useContext, useMemo } from "react";
3
+ import { createOverlayStore } from "./createOverlayStore";
4
+ import { OverlayHost } from "./OverlayHost";
5
+ const OverlayContext = createContext(null);
6
+ export function useOverlayContext() {
7
+ const context = useContext(OverlayContext);
8
+ if (context === null) {
9
+ throw new Error("useOverlayContext must be used within an <OverlayProvider>.");
10
+ }
11
+ return context;
12
+ }
13
+ export function OverlayProvider({ adapters, children, store: storeProp, }) {
14
+ const store = useMemo(() => storeProp ?? createOverlayStore(), [storeProp]);
15
+ const value = useMemo(() => ({ store, adapters }), [store, adapters]);
16
+ return (_jsxs(OverlayContext.Provider, { value: value, children: [children, _jsx(OverlayHost, {})] }));
17
+ }
@@ -0,0 +1,2 @@
1
+ import type { OverlayStoreApi } from "../contracts/overlay";
2
+ export declare function createOverlayStore(): OverlayStoreApi;
@@ -0,0 +1,102 @@
1
+ import { Store } from "@ilokesto/store";
2
+ function createOverlayItem(options, id) {
3
+ return {
4
+ id,
5
+ type: options.type,
6
+ props: options.props ?? {},
7
+ status: "open",
8
+ createdAt: Date.now(),
9
+ };
10
+ }
11
+ export function createOverlayStore() {
12
+ const store = new Store({ items: [] });
13
+ const pendingResolvers = new Map();
14
+ let counter = 0;
15
+ function nextId() {
16
+ counter += 1;
17
+ return `overlay-${counter}-${Date.now()}`;
18
+ }
19
+ function settle(id, value) {
20
+ const resolver = pendingResolvers.get(id);
21
+ if (!resolver) {
22
+ return;
23
+ }
24
+ pendingResolvers.delete(id);
25
+ resolver(value);
26
+ }
27
+ function open(options) {
28
+ const id = options.id ?? nextId();
29
+ const item = createOverlayItem(options, id);
30
+ const promise = new Promise((resolve) => {
31
+ pendingResolvers.set(id, resolve);
32
+ });
33
+ store.setState((prev) => ({
34
+ ...prev,
35
+ items: [...prev.items, item],
36
+ }));
37
+ return { id, promise };
38
+ }
39
+ function close(id, result) {
40
+ let shouldResolve = false;
41
+ store.setState((prev) => ({
42
+ ...prev,
43
+ items: prev.items.map((item) => {
44
+ if (item.id !== id || item.status === "closing") {
45
+ return item;
46
+ }
47
+ shouldResolve = true;
48
+ return {
49
+ ...item,
50
+ status: "closing",
51
+ };
52
+ }),
53
+ }));
54
+ if (shouldResolve) {
55
+ settle(id, result);
56
+ }
57
+ }
58
+ function remove(id) {
59
+ const targetId = id ?? store.getState().items.at(-1)?.id;
60
+ if (!targetId) {
61
+ return;
62
+ }
63
+ const targetItem = store.getState().items.find((item) => item.id === targetId);
64
+ if (!targetItem) {
65
+ return;
66
+ }
67
+ if (targetItem.status !== "closing") {
68
+ settle(targetId, undefined);
69
+ }
70
+ store.setState((prev) => ({
71
+ ...prev,
72
+ items: prev.items.filter((item) => item.id !== targetId),
73
+ }));
74
+ }
75
+ function clear() {
76
+ for (const item of store.getState().items) {
77
+ settle(item.id, undefined);
78
+ }
79
+ store.setState((prev) => ({
80
+ ...prev,
81
+ items: [],
82
+ }));
83
+ }
84
+ function subscribe(listener) {
85
+ return store.subscribe(listener);
86
+ }
87
+ function getSnapshot() {
88
+ return store.getState().items;
89
+ }
90
+ function getInitialSnapshot() {
91
+ return store.getInitialState().items;
92
+ }
93
+ return {
94
+ open,
95
+ close,
96
+ remove,
97
+ clear,
98
+ subscribe,
99
+ getSnapshot,
100
+ getInitialSnapshot,
101
+ };
102
+ }
@@ -0,0 +1,9 @@
1
+ import type { DisplayOptions, OverlayId } from "../contracts/overlay";
2
+ export interface UseOverlayReturn {
3
+ readonly display: <TResult = unknown>(options: DisplayOptions) => Promise<TResult | undefined>;
4
+ readonly open: (options: DisplayOptions) => OverlayId;
5
+ readonly close: (id: OverlayId, result?: unknown) => void;
6
+ readonly remove: (id?: OverlayId) => void;
7
+ readonly clear: () => void;
8
+ }
9
+ export declare function useOverlay(): UseOverlayReturn;
@@ -0,0 +1,23 @@
1
+ import { useCallback } from "react";
2
+ import { useOverlayContext } from "./OverlayProvider";
3
+ export function useOverlay() {
4
+ const { store } = useOverlayContext();
5
+ const display = useCallback((options) => {
6
+ const { promise } = store.open(options);
7
+ return promise;
8
+ }, [store]);
9
+ const open = useCallback((options) => {
10
+ const { id } = store.open(options);
11
+ return id;
12
+ }, [store]);
13
+ const close = useCallback((id, result) => {
14
+ store.close(id, result);
15
+ }, [store]);
16
+ const remove = useCallback((id) => {
17
+ store.remove(id);
18
+ }, [store]);
19
+ const clear = useCallback(() => {
20
+ store.clear();
21
+ }, [store]);
22
+ return { display, open, close, remove, clear };
23
+ }
@@ -0,0 +1,2 @@
1
+ import type { OverlayItem } from "../contracts/overlay";
2
+ export declare function useOverlayItems(): ReadonlyArray<OverlayItem>;
@@ -0,0 +1,6 @@
1
+ import { useSyncExternalStore } from "react";
2
+ import { useOverlayContext } from "./OverlayProvider";
3
+ export function useOverlayItems() {
4
+ const { store } = useOverlayContext();
5
+ return useSyncExternalStore(store.subscribe, store.getSnapshot, store.getInitialSnapshot);
6
+ }
@@ -0,0 +1,8 @@
1
+ export { createOverlayStore } from "./core/createOverlayStore";
2
+ export { OverlayProvider } from "./core/OverlayProvider";
3
+ export { OverlayHost } from "./core/OverlayHost";
4
+ export { useOverlay } from "./core/useOverlay";
5
+ export { useOverlayItems } from "./core/useOverlayItems";
6
+ export type { OverlayAdapterComponent, OverlayAdapterMap, OverlayRenderProps, } from "./contracts/adapter";
7
+ export type { DisplayOptions, OverlayId, OverlayItem, OverlayProviderProps, OverlayRequest, OverlayState, OverlayStatus, OverlayStoreApi, } from "./contracts/overlay";
8
+ export type { UseOverlayReturn } from "./core/useOverlay";
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { createOverlayStore } from "./core/createOverlayStore";
2
+ export { OverlayProvider } from "./core/OverlayProvider";
3
+ export { OverlayHost } from "./core/OverlayHost";
4
+ export { useOverlay } from "./core/useOverlay";
5
+ export { useOverlayItems } from "./core/useOverlayItems";
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@ilokesto/overlay",
3
+ "version": "1.0.0",
4
+ "scripts": {
5
+ "build": "rm -rf dist && tsc"
6
+ },
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/ilokesto/overlay"
10
+ },
11
+ "homepage": "https://ilokesto.ayden94.com/en/overlay",
12
+ "sideEffects": false,
13
+ "types": "dist/index.d.ts",
14
+ "module": "dist/index.js",
15
+ "exports": {
16
+ ".": {
17
+ "import": "./dist/index.js",
18
+ "types": "./dist/index.d.ts"
19
+ }
20
+ },
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "keywords": [
25
+ "overlay",
26
+ "react",
27
+ "modal",
28
+ "provider",
29
+ "hooks"
30
+ ],
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
34
+ "author": "Jeong Jinho <wpfekdml@me.com>",
35
+ "license": "MIT",
36
+ "dependencies": {
37
+ "@ilokesto/store": "1.1.1"
38
+ },
39
+ "peerDependencies": {
40
+ "react": "18.2.0"
41
+ },
42
+ "devDependencies": {
43
+ "@types/node": "^22.19.1",
44
+ "@types/react": "^19.1.6",
45
+ "typescript": "^6.0.2"
46
+ }
47
+ }