@mhosaic/feedback 0.3.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.
@@ -0,0 +1,10 @@
1
+ import { a as FeedbackConfig, F as FeedbackApi, R as ReportTransformer } from './types-HSdlLTzw.js';
2
+
3
+ interface InternalConfig extends FeedbackConfig {
4
+ fetchImpl?: typeof fetch;
5
+ }
6
+ declare function createFeedback(config: InternalConfig): FeedbackApi & {
7
+ _registerTransformer(fn: ReportTransformer): void;
8
+ };
9
+
10
+ export { type InternalConfig as I, createFeedback as c };
@@ -0,0 +1,2 @@
1
+ export { c as createFeedback } from './index-BYZZDht1.js';
2
+ export { C as CapturedContext, F as FeedbackApi, a as FeedbackConfig, b as FeedbackEnv, c as FeedbackSeverity, d as FeedbackType, e as ReportPayload, S as SubmittedReport, U as UserIdentity } from './types-HSdlLTzw.js';
package/dist/index.mjs ADDED
@@ -0,0 +1,7 @@
1
+ import {
2
+ createFeedback
3
+ } from "./chunk-RSBFN26C.mjs";
4
+ export {
5
+ createFeedback
6
+ };
7
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,13 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+ import { I as InternalConfig } from './index-BYZZDht1.js';
4
+ import { F as FeedbackApi } from './types-HSdlLTzw.js';
5
+
6
+ interface FeedbackProviderProps extends InternalConfig {
7
+ children?: ReactNode;
8
+ }
9
+ declare function FeedbackProvider({ children, ...config }: FeedbackProviderProps): react_jsx_runtime.JSX.Element | null;
10
+
11
+ declare function useFeedback(): FeedbackApi;
12
+
13
+ export { FeedbackProvider, useFeedback };
package/dist/react.mjs ADDED
@@ -0,0 +1,34 @@
1
+ import {
2
+ createFeedback
3
+ } from "./chunk-RSBFN26C.mjs";
4
+
5
+ // src/react/FeedbackProvider.tsx
6
+ import { createContext, useContext, useEffect, useRef, useState } from "react";
7
+ import { jsx } from "react/jsx-runtime";
8
+ var FeedbackContext = createContext(null);
9
+ function FeedbackProvider({ children, ...config }) {
10
+ const apiRef = useRef(null);
11
+ const [api, setApi] = useState(null);
12
+ useEffect(() => {
13
+ const instance = createFeedback(config);
14
+ apiRef.current = instance;
15
+ setApi(instance);
16
+ return () => {
17
+ instance.shutdown();
18
+ apiRef.current = null;
19
+ setApi(null);
20
+ };
21
+ }, [config.apiKey]);
22
+ if (!api) return null;
23
+ return /* @__PURE__ */ jsx(FeedbackContext.Provider, { value: api, children });
24
+ }
25
+ function useFeedback() {
26
+ const api = useContext(FeedbackContext);
27
+ if (!api) throw new Error("useFeedback must be used inside <FeedbackProvider>");
28
+ return api;
29
+ }
30
+ export {
31
+ FeedbackProvider,
32
+ useFeedback
33
+ };
34
+ //# sourceMappingURL=react.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/react/FeedbackProvider.tsx"],"sourcesContent":["/** @jsxImportSource react */\nimport { createContext, useContext, useEffect, useRef, useState } from 'react'\nimport type { ReactNode } from 'react'\n\nimport { createFeedback, type InternalConfig } from '../core'\nimport type { FeedbackApi } from '../types'\n\nconst FeedbackContext = createContext<FeedbackApi | null>(null)\n\ninterface FeedbackProviderProps extends InternalConfig {\n children?: ReactNode\n}\n\nexport function FeedbackProvider({ children, ...config }: FeedbackProviderProps) {\n const apiRef = useRef<FeedbackApi | null>(null)\n const [api, setApi] = useState<FeedbackApi | null>(null)\n\n useEffect(() => {\n const instance = createFeedback(config)\n apiRef.current = instance\n setApi(instance)\n return () => {\n instance.shutdown()\n apiRef.current = null\n setApi(null)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [config.apiKey])\n\n // Only render children once the api instance is ready so useFeedback()\n // never sees a null context value.\n if (!api) return null\n\n return <FeedbackContext.Provider value={api}>{children}</FeedbackContext.Provider>\n}\n\nexport { FeedbackContext }\n\nexport function useFeedback(): FeedbackApi {\n const api = useContext(FeedbackContext)\n if (!api) throw new Error('useFeedback must be used inside <FeedbackProvider>')\n return api\n}\n"],"mappings":";;;;;AACA,SAAS,eAAe,YAAY,WAAW,QAAQ,gBAAgB;AAgC9D;AA1BT,IAAM,kBAAkB,cAAkC,IAAI;AAMvD,SAAS,iBAAiB,EAAE,UAAU,GAAG,OAAO,GAA0B;AAC/E,QAAM,SAAS,OAA2B,IAAI;AAC9C,QAAM,CAAC,KAAK,MAAM,IAAI,SAA6B,IAAI;AAEvD,YAAU,MAAM;AACd,UAAM,WAAW,eAAe,MAAM;AACtC,WAAO,UAAU;AACjB,WAAO,QAAQ;AACf,WAAO,MAAM;AACX,eAAS,SAAS;AAClB,aAAO,UAAU;AACjB,aAAO,IAAI;AAAA,IACb;AAAA,EAEF,GAAG,CAAC,OAAO,MAAM,CAAC;AAIlB,MAAI,CAAC,IAAK,QAAO;AAEjB,SAAO,oBAAC,gBAAgB,UAAhB,EAAyB,OAAO,KAAM,UAAS;AACzD;AAIO,SAAS,cAA2B;AACzC,QAAM,MAAM,WAAW,eAAe;AACtC,MAAI,CAAC,IAAK,OAAM,IAAI,MAAM,oDAAoD;AAC9E,SAAO;AACT;","names":[]}
@@ -0,0 +1,12 @@
1
+ import { F as FeedbackApi } from './types-HSdlLTzw.js';
2
+
3
+ interface ReplayOptions {
4
+ durationMs?: number;
5
+ maskAllText?: boolean;
6
+ maskAllInputs?: boolean;
7
+ maskTextSelector?: string;
8
+ blockSelector?: string;
9
+ }
10
+ declare function withReplay(fb: FeedbackApi, options?: ReplayOptions): FeedbackApi;
11
+
12
+ export { type ReplayOptions, withReplay };
@@ -0,0 +1,53 @@
1
+ // src/modules/replay.ts
2
+ function withReplay(fb, options = {}) {
3
+ const internal = fb;
4
+ if (typeof internal._registerTransformer !== "function") return fb;
5
+ const duration = options.durationMs ?? 6e4;
6
+ const events = [];
7
+ let stopFn = null;
8
+ function evictOld() {
9
+ const cutoff = Date.now() - duration;
10
+ while (events.length > 0 && (events[0].timestamp ?? 0) < cutoff) events.shift();
11
+ }
12
+ void (async () => {
13
+ try {
14
+ const { record } = await import("rrweb");
15
+ stopFn = record({
16
+ emit(event) {
17
+ events.push(event);
18
+ evictOld();
19
+ },
20
+ maskAllInputs: options.maskAllInputs ?? true,
21
+ ...options.maskAllText === true && { maskTextClass: new RegExp(".*") },
22
+ ...options.maskTextSelector !== void 0 && { maskTextSelector: options.maskTextSelector },
23
+ blockSelector: options.blockSelector ?? "[data-mfb-block]"
24
+ }) ?? null;
25
+ } catch {
26
+ }
27
+ })();
28
+ internal._registerTransformer((payload) => {
29
+ if (events.length === 0) return payload;
30
+ evictOld();
31
+ const blob = {
32
+ events: events.slice(),
33
+ durationMs: Math.min(duration, Date.now() - (events[0]?.timestamp ?? Date.now()))
34
+ };
35
+ return {
36
+ ...payload,
37
+ technical_context: { ...payload.technical_context, replay: blob }
38
+ };
39
+ });
40
+ const originalShutdown = fb.shutdown.bind(fb);
41
+ fb.shutdown = () => {
42
+ try {
43
+ stopFn?.();
44
+ } catch {
45
+ }
46
+ originalShutdown();
47
+ };
48
+ return fb;
49
+ }
50
+ export {
51
+ withReplay
52
+ };
53
+ //# sourceMappingURL=replay.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/modules/replay.ts"],"sourcesContent":["import type { FeedbackApi, FeedbackApiInternal, ReplayBlob } from '../types'\n\nexport interface ReplayOptions {\n durationMs?: number\n maskAllText?: boolean\n maskAllInputs?: boolean\n maskTextSelector?: string\n blockSelector?: string\n}\n\ntype RrwebEvent = { timestamp: number } & Record<string, unknown>\n\nexport function withReplay(fb: FeedbackApi, options: ReplayOptions = {}): FeedbackApi {\n const internal = fb as FeedbackApiInternal\n if (typeof internal._registerTransformer !== 'function') return fb\n\n const duration = options.durationMs ?? 60_000\n const events: RrwebEvent[] = []\n let stopFn: (() => void) | null = null\n\n function evictOld() {\n const cutoff = Date.now() - duration\n while (events.length > 0 && (events[0]!.timestamp ?? 0) < cutoff) events.shift()\n }\n\n void (async () => {\n try {\n const { record } = await import('rrweb')\n stopFn = record({\n emit(event: RrwebEvent) {\n events.push(event)\n evictOld()\n },\n maskAllInputs: options.maskAllInputs ?? true,\n ...(options.maskAllText === true && { maskTextClass: new RegExp('.*') }),\n ...(options.maskTextSelector !== undefined && { maskTextSelector: options.maskTextSelector }),\n blockSelector: options.blockSelector ?? '[data-mfb-block]',\n }) ?? null\n } catch { /* rrweb unavailable */ }\n })()\n\n internal._registerTransformer((payload) => {\n if (events.length === 0) return payload\n evictOld()\n const blob: ReplayBlob = {\n events: events.slice(),\n durationMs: Math.min(duration, Date.now() - (events[0]?.timestamp ?? Date.now())),\n }\n return {\n ...payload,\n technical_context: { ...payload.technical_context, replay: blob },\n }\n })\n\n const originalShutdown = fb.shutdown.bind(fb)\n fb.shutdown = () => {\n try { stopFn?.() } catch { /* noop */ }\n originalShutdown()\n }\n\n return fb\n}\n"],"mappings":";AAYO,SAAS,WAAW,IAAiB,UAAyB,CAAC,GAAgB;AACpF,QAAM,WAAW;AACjB,MAAI,OAAO,SAAS,yBAAyB,WAAY,QAAO;AAEhE,QAAM,WAAW,QAAQ,cAAc;AACvC,QAAM,SAAuB,CAAC;AAC9B,MAAI,SAA8B;AAElC,WAAS,WAAW;AAClB,UAAM,SAAS,KAAK,IAAI,IAAI;AAC5B,WAAO,OAAO,SAAS,MAAM,OAAO,CAAC,EAAG,aAAa,KAAK,OAAQ,QAAO,MAAM;AAAA,EACjF;AAEA,QAAM,YAAY;AAChB,QAAI;AACF,YAAM,EAAE,OAAO,IAAI,MAAM,OAAO,OAAO;AACvC,eAAS,OAAO;AAAA,QACd,KAAK,OAAmB;AACtB,iBAAO,KAAK,KAAK;AACjB,mBAAS;AAAA,QACX;AAAA,QACA,eAAe,QAAQ,iBAAiB;AAAA,QACxC,GAAI,QAAQ,gBAAgB,QAAQ,EAAE,eAAe,IAAI,OAAO,IAAI,EAAE;AAAA,QACtE,GAAI,QAAQ,qBAAqB,UAAa,EAAE,kBAAkB,QAAQ,iBAAiB;AAAA,QAC3F,eAAe,QAAQ,iBAAiB;AAAA,MAC1C,CAAC,KAAK;AAAA,IACR,QAAQ;AAAA,IAA0B;AAAA,EACpC,GAAG;AAEH,WAAS,qBAAqB,CAAC,YAAY;AACzC,QAAI,OAAO,WAAW,EAAG,QAAO;AAChC,aAAS;AACT,UAAM,OAAmB;AAAA,MACvB,QAAQ,OAAO,MAAM;AAAA,MACrB,YAAY,KAAK,IAAI,UAAU,KAAK,IAAI,KAAK,OAAO,CAAC,GAAG,aAAa,KAAK,IAAI,EAAE;AAAA,IAClF;AACA,WAAO;AAAA,MACL,GAAG;AAAA,MACH,mBAAmB,EAAE,GAAG,QAAQ,mBAAmB,QAAQ,KAAK;AAAA,IAClE;AAAA,EACF,CAAC;AAED,QAAM,mBAAmB,GAAG,SAAS,KAAK,EAAE;AAC5C,KAAG,WAAW,MAAM;AAClB,QAAI;AAAE,eAAS;AAAA,IAAE,QAAQ;AAAA,IAAa;AACtC,qBAAiB;AAAA,EACnB;AAEA,SAAO;AACT;","names":[]}
@@ -0,0 +1,120 @@
1
+ type FeedbackEnv = 'dev' | 'staging' | 'prod' | (string & {});
2
+ type FeedbackType = 'bug' | 'feature' | 'question' | 'praise' | 'typo';
3
+ type FeedbackSeverity = 'blocker' | 'high' | 'medium' | 'low';
4
+ type CaptureMethod = 'html2canvas' | 'display_media' | 'manual' | 'none';
5
+ interface UserIdentity {
6
+ id?: string | number;
7
+ email?: string;
8
+ name?: string;
9
+ }
10
+ interface FeedbackConfig {
11
+ apiKey: string;
12
+ env?: FeedbackEnv;
13
+ endpoint?: string;
14
+ user?: UserIdentity;
15
+ metadata?: Record<string, unknown>;
16
+ beforeSend?: (payload: ReportPayload) => ReportPayload | false | Promise<ReportPayload | false>;
17
+ onSubmitSuccess?: (report: SubmittedReport) => void;
18
+ onError?: (err: Error) => void;
19
+ maskAllInputs?: boolean;
20
+ sanitizeUrl?: (url: string) => string;
21
+ showFAB?: boolean;
22
+ attachTo?: string | Element;
23
+ locale?: string;
24
+ translations?: Record<string, string>;
25
+ theme?: 'light' | 'dark' | 'auto' | 'none';
26
+ }
27
+ interface ConsoleEntry {
28
+ level: string;
29
+ message: string;
30
+ ts: number;
31
+ stack?: string;
32
+ }
33
+ interface NetworkEntry {
34
+ url: string;
35
+ method: string;
36
+ status: number;
37
+ durationMs: number;
38
+ ts: number;
39
+ error?: string;
40
+ }
41
+ interface ErrorEntry {
42
+ message: string;
43
+ stack?: string;
44
+ ts: number;
45
+ source: 'window.error' | 'unhandledrejection';
46
+ }
47
+ interface DeviceContext {
48
+ viewport: {
49
+ w: number;
50
+ h: number;
51
+ dpr: number;
52
+ };
53
+ screen?: {
54
+ w: number;
55
+ h: number;
56
+ };
57
+ platform?: string;
58
+ language?: string;
59
+ timezone?: string;
60
+ timezoneOffset?: number;
61
+ connection?: string;
62
+ online?: boolean;
63
+ deviceMemory?: number;
64
+ hardwareConcurrency?: number;
65
+ referrer?: string;
66
+ title?: string;
67
+ pathname?: string;
68
+ }
69
+ interface WebVitalEntry {
70
+ name: 'CLS' | 'INP' | 'LCP' | 'FCP' | 'TTFB';
71
+ value: number;
72
+ rating: 'good' | 'needs-improvement' | 'poor';
73
+ attribution?: unknown;
74
+ }
75
+ interface ReplayBlob {
76
+ events: unknown[];
77
+ durationMs: number;
78
+ }
79
+ interface CapturedContext {
80
+ consoleLogs: ConsoleEntry[];
81
+ networkRequests: NetworkEntry[];
82
+ errors: ErrorEntry[];
83
+ device: DeviceContext;
84
+ capturedAt: number;
85
+ webVitals?: WebVitalEntry[];
86
+ replay?: ReplayBlob;
87
+ }
88
+ interface ReportPayload {
89
+ description: string;
90
+ feedback_type: FeedbackType;
91
+ severity: FeedbackSeverity;
92
+ env: FeedbackEnv;
93
+ page_url: string;
94
+ user_agent: string;
95
+ capture_method: CaptureMethod;
96
+ technical_context: CapturedContext;
97
+ screenshot?: Blob;
98
+ }
99
+ interface SubmittedReport {
100
+ id: string;
101
+ status: string;
102
+ created_at: string;
103
+ }
104
+ interface FeedbackApi {
105
+ show(): void;
106
+ hide(): void;
107
+ open(options?: {
108
+ type?: FeedbackType;
109
+ description?: string;
110
+ }): void;
111
+ submit(partial: Partial<ReportPayload> & {
112
+ description: string;
113
+ }): Promise<SubmittedReport>;
114
+ identify(user: UserIdentity): void;
115
+ setMetadata(kv: Record<string, unknown>): void;
116
+ shutdown(): void;
117
+ }
118
+ type ReportTransformer = (payload: ReportPayload) => ReportPayload | Promise<ReportPayload>;
119
+
120
+ export type { CapturedContext as C, FeedbackApi as F, ReportTransformer as R, SubmittedReport as S, UserIdentity as U, FeedbackConfig as a, FeedbackEnv as b, FeedbackSeverity as c, FeedbackType as d, ReportPayload as e };
@@ -0,0 +1,8 @@
1
+ import { F as FeedbackApi } from './types-HSdlLTzw.js';
2
+
3
+ interface WebVitalsOptions {
4
+ reportAllChanges?: boolean;
5
+ }
6
+ declare function withWebVitals(fb: FeedbackApi, options?: WebVitalsOptions): FeedbackApi;
7
+
8
+ export { type WebVitalsOptions, withWebVitals };
@@ -0,0 +1,36 @@
1
+ // src/modules/webvitals.ts
2
+ import { onCLS, onFCP, onINP, onLCP, onTTFB } from "web-vitals/attribution";
3
+ function withWebVitals(fb, options = {}) {
4
+ const internal = fb;
5
+ if (typeof internal._registerTransformer !== "function") return fb;
6
+ const entries = /* @__PURE__ */ new Map();
7
+ const record = (metric) => {
8
+ entries.set(metric.name, {
9
+ name: metric.name,
10
+ value: metric.value,
11
+ rating: metric.rating,
12
+ ...metric.attribution !== void 0 && { attribution: metric.attribution }
13
+ });
14
+ };
15
+ const opts = { reportAllChanges: options.reportAllChanges ?? false };
16
+ try {
17
+ onCLS(record, opts);
18
+ onINP(record, opts);
19
+ onLCP(record, opts);
20
+ onFCP(record, opts);
21
+ onTTFB(record, opts);
22
+ } catch {
23
+ }
24
+ internal._registerTransformer((payload) => ({
25
+ ...payload,
26
+ technical_context: {
27
+ ...payload.technical_context,
28
+ webVitals: Array.from(entries.values())
29
+ }
30
+ }));
31
+ return fb;
32
+ }
33
+ export {
34
+ withWebVitals
35
+ };
36
+ //# sourceMappingURL=webvitals.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/modules/webvitals.ts"],"sourcesContent":["import { onCLS, onFCP, onINP, onLCP, onTTFB } from 'web-vitals/attribution'\n\nimport type { FeedbackApi, FeedbackApiInternal, WebVitalEntry } from '../types'\n\nexport interface WebVitalsOptions {\n reportAllChanges?: boolean\n}\n\nexport function withWebVitals(fb: FeedbackApi, options: WebVitalsOptions = {}): FeedbackApi {\n const internal = fb as FeedbackApiInternal\n if (typeof internal._registerTransformer !== 'function') return fb\n\n const entries = new Map<string, WebVitalEntry>()\n const record = (metric: { name: string; value: number; rating: string; attribution?: unknown }) => {\n entries.set(metric.name, {\n name: metric.name as WebVitalEntry['name'],\n value: metric.value,\n rating: metric.rating as WebVitalEntry['rating'],\n ...(metric.attribution !== undefined && { attribution: metric.attribution }),\n })\n }\n\n const opts = { reportAllChanges: options.reportAllChanges ?? false }\n try {\n onCLS(record, opts)\n onINP(record, opts)\n onLCP(record, opts)\n onFCP(record, opts)\n onTTFB(record, opts)\n } catch { /* unsupported env */ }\n\n internal._registerTransformer((payload) => ({\n ...payload,\n technical_context: {\n ...payload.technical_context,\n webVitals: Array.from(entries.values()),\n },\n }))\n\n return fb\n}\n"],"mappings":";AAAA,SAAS,OAAO,OAAO,OAAO,OAAO,cAAc;AAQ5C,SAAS,cAAc,IAAiB,UAA4B,CAAC,GAAgB;AAC1F,QAAM,WAAW;AACjB,MAAI,OAAO,SAAS,yBAAyB,WAAY,QAAO;AAEhE,QAAM,UAAU,oBAAI,IAA2B;AAC/C,QAAM,SAAS,CAAC,WAAmF;AACjG,YAAQ,IAAI,OAAO,MAAM;AAAA,MACvB,MAAM,OAAO;AAAA,MACb,OAAO,OAAO;AAAA,MACd,QAAQ,OAAO;AAAA,MACf,GAAI,OAAO,gBAAgB,UAAa,EAAE,aAAa,OAAO,YAAY;AAAA,IAC5E,CAAC;AAAA,EACH;AAEA,QAAM,OAAO,EAAE,kBAAkB,QAAQ,oBAAoB,MAAM;AACnE,MAAI;AACF,UAAM,QAAQ,IAAI;AAClB,UAAM,QAAQ,IAAI;AAClB,UAAM,QAAQ,IAAI;AAClB,UAAM,QAAQ,IAAI;AAClB,WAAO,QAAQ,IAAI;AAAA,EACrB,QAAQ;AAAA,EAAwB;AAEhC,WAAS,qBAAqB,CAAC,aAAa;AAAA,IAC1C,GAAG;AAAA,IACH,mBAAmB;AAAA,MACjB,GAAG,QAAQ;AAAA,MACX,WAAW,MAAM,KAAK,QAAQ,OAAO,CAAC;AAAA,IACxC;AAAA,EACF,EAAE;AAEF,SAAO;AACT;","names":[]}
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@mhosaic/feedback",
3
+ "version": "0.3.0",
4
+ "type": "module",
5
+ "sideEffects": false,
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/index.d.ts",
9
+ "import": "./dist/index.mjs"
10
+ },
11
+ "./react": {
12
+ "types": "./dist/react.d.ts",
13
+ "import": "./dist/react.mjs"
14
+ },
15
+ "./webvitals": {
16
+ "types": "./dist/webvitals.d.ts",
17
+ "import": "./dist/webvitals.mjs"
18
+ },
19
+ "./replay": {
20
+ "types": "./dist/replay.d.ts",
21
+ "import": "./dist/replay.mjs"
22
+ }
23
+ },
24
+ "files": [
25
+ "dist"
26
+ ],
27
+ "peerDependencies": {
28
+ "react": ">=18"
29
+ },
30
+ "peerDependenciesMeta": {
31
+ "react": {
32
+ "optional": true
33
+ }
34
+ },
35
+ "dependencies": {
36
+ "preact": "^10.24.0",
37
+ "html2canvas-pro": "^1.5.8",
38
+ "web-vitals": "^4.2.0",
39
+ "rrweb": "^2.0.0-alpha.18"
40
+ },
41
+ "devDependencies": {
42
+ "@testing-library/preact": "^3.2.4",
43
+ "@testing-library/react": "^14.0.0",
44
+ "@types/node": "^22.5.0",
45
+ "@types/react": "^18.3.3",
46
+ "@types/react-dom": "^18.3.0",
47
+ "jsdom": "^25.0.0",
48
+ "react": "^18.3.1",
49
+ "react-dom": "^18.3.1",
50
+ "size-limit": "^11.1.5",
51
+ "@size-limit/preset-small-lib": "^11.1.5",
52
+ "tsup": "^8.3.0",
53
+ "typescript": "5.6.3",
54
+ "vitest": "^2.1.0"
55
+ },
56
+ "scripts": {
57
+ "build": "tsup",
58
+ "dev": "tsup --watch",
59
+ "test": "vitest",
60
+ "typecheck": "tsc --noEmit",
61
+ "size": "size-limit"
62
+ }
63
+ }