@meetreeve/capacitor-bridge 0.1.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,483 @@
1
+ // src/react/CapacitorProvider.tsx
2
+ import * as React from "react";
3
+
4
+ // src/core/platform.ts
5
+ function getCapacitor() {
6
+ if (typeof window === "undefined") return null;
7
+ const cap = window.Capacitor;
8
+ if (!cap || typeof cap.isNativePlatform !== "function") return null;
9
+ return cap;
10
+ }
11
+ function getPlatform() {
12
+ const cap = getCapacitor();
13
+ if (!cap) return "web";
14
+ const p = cap.getPlatform();
15
+ return p === "ios" ? "ios" : p === "android" ? "android" : "web";
16
+ }
17
+ function isNativePlatform() {
18
+ return getPlatform() !== "web";
19
+ }
20
+
21
+ // src/core/safe-area.ts
22
+ var ZERO = { top: 0, right: 0, bottom: 0, left: 0 };
23
+ function getSafeAreaInsets() {
24
+ if (typeof window === "undefined" || typeof document === "undefined") {
25
+ return ZERO;
26
+ }
27
+ const root = document.documentElement;
28
+ const styles = window.getComputedStyle(root);
29
+ const read = (side) => {
30
+ const v = styles.getPropertyValue(`--safe-area-inset-${side}`).trim();
31
+ const n = parseFloat(v);
32
+ return Number.isFinite(n) ? n : 0;
33
+ };
34
+ return {
35
+ top: read("top"),
36
+ right: read("right"),
37
+ bottom: read("bottom"),
38
+ left: read("left")
39
+ };
40
+ }
41
+ function installSafeAreaVars() {
42
+ if (typeof document === "undefined") return;
43
+ const TAG = "reeve-capacitor-bridge-safe-area";
44
+ if (document.querySelector(`style[data-${TAG}]`)) return;
45
+ const style = document.createElement("style");
46
+ style.setAttribute(`data-${TAG}`, "1");
47
+ style.textContent = `
48
+ :root {
49
+ --safe-area-inset-top: env(safe-area-inset-top, 0px);
50
+ --safe-area-inset-right: env(safe-area-inset-right, 0px);
51
+ --safe-area-inset-bottom: env(safe-area-inset-bottom, 0px);
52
+ --safe-area-inset-left: env(safe-area-inset-left, 0px);
53
+ }
54
+ `;
55
+ document.head.appendChild(style);
56
+ }
57
+
58
+ // src/core/splash.ts
59
+ function getSplashPlugin() {
60
+ if (typeof window === "undefined") return null;
61
+ const cap = window.Capacitor;
62
+ return cap?.Plugins?.SplashScreen ?? null;
63
+ }
64
+ var GLOBAL_KEY = /* @__PURE__ */ Symbol.for("@meetreeve/capacitor-bridge/splash@1");
65
+ function getGlobalState() {
66
+ const g = globalThis;
67
+ if (!g[GLOBAL_KEY]) g[GLOBAL_KEY] = { done: null, pending: null };
68
+ return g[GLOBAL_KEY];
69
+ }
70
+ async function dismissSplash(fadeOutDuration = 250) {
71
+ if (!isNativePlatform()) return;
72
+ const state = getGlobalState();
73
+ if (state.done) return state.done;
74
+ if (state.pending) return state.pending;
75
+ const plugin = getSplashPlugin();
76
+ if (!plugin) return;
77
+ const attempt = (async () => {
78
+ try {
79
+ await plugin.hide({ fadeOutDuration });
80
+ state.done = Promise.resolve();
81
+ } catch (err) {
82
+ state.pending = null;
83
+ throw err;
84
+ } finally {
85
+ if (state.done) state.pending = null;
86
+ }
87
+ })();
88
+ state.pending = attempt;
89
+ return attempt.catch(() => {
90
+ });
91
+ }
92
+
93
+ // src/react/CapacitorProvider.tsx
94
+ import { jsx } from "react/jsx-runtime";
95
+ var FALLBACK_VALUE = {
96
+ platform: "web",
97
+ isNative: false,
98
+ isIOS: false,
99
+ isAndroid: false,
100
+ dismissSplash: async () => {
101
+ }
102
+ };
103
+ var CapacitorContext = React.createContext(FALLBACK_VALUE);
104
+ function CapacitorProvider({
105
+ children,
106
+ autoDismissSplash = true
107
+ }) {
108
+ const value = React.useMemo(() => {
109
+ const platform = getPlatform();
110
+ return {
111
+ platform,
112
+ isNative: platform !== "web",
113
+ isIOS: platform === "ios",
114
+ isAndroid: platform === "android",
115
+ dismissSplash
116
+ };
117
+ }, []);
118
+ React.useEffect(() => {
119
+ installSafeAreaVars();
120
+ if (typeof document !== "undefined") {
121
+ document.documentElement.setAttribute("data-platform", value.platform);
122
+ }
123
+ if (autoDismissSplash) {
124
+ void dismissSplash();
125
+ }
126
+ }, [autoDismissSplash, value.platform]);
127
+ return /* @__PURE__ */ jsx(CapacitorContext.Provider, { value, children });
128
+ }
129
+ function useCapacitor() {
130
+ return React.useContext(CapacitorContext);
131
+ }
132
+
133
+ // src/react/useSafeArea.ts
134
+ import * as React2 from "react";
135
+ function useSafeArea() {
136
+ const [insets, setInsets] = React2.useState(() => getSafeAreaInsets());
137
+ React2.useEffect(() => {
138
+ if (typeof window === "undefined") return;
139
+ const update = () => setInsets(getSafeAreaInsets());
140
+ window.addEventListener("orientationchange", update);
141
+ window.addEventListener("resize", update);
142
+ return () => {
143
+ window.removeEventListener("orientationchange", update);
144
+ window.removeEventListener("resize", update);
145
+ };
146
+ }, []);
147
+ return insets;
148
+ }
149
+
150
+ // src/react/BugReportButton.tsx
151
+ import * as React3 from "react";
152
+
153
+ // src/bugreport/index.ts
154
+ var GLOBAL_KEY2 = /* @__PURE__ */ Symbol.for("@meetreeve/capacitor-bridge/bugreport@1");
155
+ function getGlobalState2() {
156
+ const g = globalThis;
157
+ if (!g[GLOBAL_KEY2]) g[GLOBAL_KEY2] = { config: null };
158
+ return g[GLOBAL_KEY2];
159
+ }
160
+ function getBugReportEnvironment() {
161
+ const config = getGlobalState2().config;
162
+ if (typeof window === "undefined") {
163
+ return {
164
+ platform: getPlatform(),
165
+ userAgent: "",
166
+ currentUrl: "",
167
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
168
+ };
169
+ }
170
+ return {
171
+ platform: getPlatform(),
172
+ userAgent: window.navigator?.userAgent ?? "",
173
+ appVersion: config?.appVersion,
174
+ buildNumber: config?.buildNumber,
175
+ currentUrl: window.location?.href ?? "",
176
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
177
+ };
178
+ }
179
+ async function submitBugReport(payload) {
180
+ const config = getGlobalState2().config;
181
+ if (!config) {
182
+ throw new Error(
183
+ "[@meetreeve/capacitor-bridge/bugreport] submitBugReport called without configureBugReport({endpoint})."
184
+ );
185
+ }
186
+ const submission = {
187
+ ...payload,
188
+ environment: getBugReportEnvironment()
189
+ };
190
+ const headers = {
191
+ "Content-Type": "application/json"
192
+ };
193
+ if (config.headers) {
194
+ const extraHeaders = await config.headers();
195
+ Object.assign(headers, extraHeaders);
196
+ }
197
+ const response = await fetch(config.endpoint, {
198
+ method: "POST",
199
+ headers,
200
+ body: JSON.stringify(submission),
201
+ credentials: config.credentials ?? "omit"
202
+ });
203
+ if (!response.ok) {
204
+ throw new Error(
205
+ `[@meetreeve/capacitor-bridge/bugreport] bug-report submission failed: ${response.status} ${response.statusText}`
206
+ );
207
+ }
208
+ }
209
+ async function captureScreenshot() {
210
+ if (typeof window === "undefined") return null;
211
+ const cap = window.Capacitor;
212
+ const plugin = cap?.Plugins?.Screenshot;
213
+ if (plugin) {
214
+ try {
215
+ const { base64 } = await plugin.take();
216
+ return `data:image/png;base64,${base64}`;
217
+ } catch {
218
+ }
219
+ }
220
+ return null;
221
+ }
222
+
223
+ // src/react/BugReportButton.tsx
224
+ import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
225
+ var initialState = {
226
+ open: false,
227
+ message: "",
228
+ screenshot: null,
229
+ capturing: false,
230
+ submitting: false,
231
+ error: null
232
+ };
233
+ function BugReportButton({
234
+ userIdentifier,
235
+ renderTrigger,
236
+ extra,
237
+ onSubmitted
238
+ }) {
239
+ const [state, setState] = React3.useState(initialState);
240
+ const triggerRef = React3.useRef(null);
241
+ const textareaRef = React3.useRef(null);
242
+ const previouslyFocusedRef = React3.useRef(null);
243
+ const open = React3.useCallback(() => {
244
+ previouslyFocusedRef.current = document.activeElement;
245
+ setState({ ...initialState, open: true });
246
+ }, []);
247
+ const close = React3.useCallback(() => {
248
+ setState(initialState);
249
+ const prev = previouslyFocusedRef.current;
250
+ if (prev && typeof prev.focus === "function") {
251
+ prev.focus();
252
+ }
253
+ }, []);
254
+ const captureScreen = React3.useCallback(async () => {
255
+ setState((s) => ({ ...s, capturing: true, error: null }));
256
+ try {
257
+ const img = await captureScreenshot();
258
+ setState((s) => ({ ...s, screenshot: img, capturing: false }));
259
+ } catch (err) {
260
+ setState((s) => ({
261
+ ...s,
262
+ capturing: false,
263
+ error: err instanceof Error ? err.message : String(err)
264
+ }));
265
+ }
266
+ }, []);
267
+ React3.useEffect(() => {
268
+ if (!state.open) return;
269
+ textareaRef.current?.focus();
270
+ const onKey = (e) => {
271
+ if (e.key === "Escape" && !state.submitting) {
272
+ e.stopPropagation();
273
+ close();
274
+ }
275
+ };
276
+ document.addEventListener("keydown", onKey);
277
+ return () => document.removeEventListener("keydown", onKey);
278
+ }, [state.open, state.submitting, close]);
279
+ const submit = React3.useCallback(async () => {
280
+ if (!state.message.trim()) return;
281
+ setState((s) => ({ ...s, submitting: true, error: null }));
282
+ const payload = {
283
+ message: state.message,
284
+ userIdentifier,
285
+ extra
286
+ };
287
+ if (state.screenshot) payload.screenshot = state.screenshot;
288
+ try {
289
+ await submitBugReport(payload);
290
+ setState(initialState);
291
+ onSubmitted?.();
292
+ } catch (err) {
293
+ setState((s) => ({
294
+ ...s,
295
+ submitting: false,
296
+ error: err instanceof Error ? err.message : String(err)
297
+ }));
298
+ }
299
+ }, [state.message, state.screenshot, userIdentifier, extra, onSubmitted]);
300
+ const trigger = renderTrigger ? renderTrigger(open) : /* @__PURE__ */ jsx2(
301
+ "button",
302
+ {
303
+ ref: triggerRef,
304
+ type: "button",
305
+ onClick: open,
306
+ style: {
307
+ position: "fixed",
308
+ bottom: "calc(env(safe-area-inset-bottom, 0px) + 16px)",
309
+ right: "calc(env(safe-area-inset-right, 0px) + 16px)",
310
+ zIndex: 9999,
311
+ padding: "10px 14px",
312
+ background: "rgba(0,0,0,0.78)",
313
+ color: "white",
314
+ border: "none",
315
+ borderRadius: "999px",
316
+ fontSize: 14,
317
+ fontWeight: 500,
318
+ boxShadow: "0 4px 12px rgba(0,0,0,0.25)",
319
+ cursor: "pointer"
320
+ },
321
+ "aria-label": "Report a bug",
322
+ children: "Report bug"
323
+ }
324
+ );
325
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
326
+ trigger,
327
+ state.open && /* @__PURE__ */ jsx2(
328
+ "div",
329
+ {
330
+ role: "dialog",
331
+ "aria-modal": "true",
332
+ "aria-label": "Report a bug",
333
+ style: {
334
+ position: "fixed",
335
+ inset: 0,
336
+ zIndex: 1e4,
337
+ background: "rgba(0,0,0,0.55)",
338
+ display: "flex",
339
+ alignItems: "flex-end",
340
+ justifyContent: "center"
341
+ },
342
+ onClick: (e) => {
343
+ if (e.target === e.currentTarget && !state.submitting) close();
344
+ },
345
+ children: /* @__PURE__ */ jsxs(
346
+ "div",
347
+ {
348
+ style: {
349
+ width: "100%",
350
+ maxWidth: 460,
351
+ background: "white",
352
+ color: "#111",
353
+ borderTopLeftRadius: 16,
354
+ borderTopRightRadius: 16,
355
+ padding: "20px 20px calc(env(safe-area-inset-bottom, 0px) + 20px)",
356
+ boxSizing: "border-box"
357
+ },
358
+ children: [
359
+ /* @__PURE__ */ jsx2("h2", { style: { margin: "0 0 12px", fontSize: 18 }, children: "Report a bug" }),
360
+ /* @__PURE__ */ jsx2(
361
+ "textarea",
362
+ {
363
+ ref: textareaRef,
364
+ value: state.message,
365
+ onChange: (e) => setState((s) => ({ ...s, message: e.target.value })),
366
+ placeholder: "What broke? What were you doing when it broke?",
367
+ rows: 4,
368
+ style: {
369
+ width: "100%",
370
+ boxSizing: "border-box",
371
+ padding: 10,
372
+ fontSize: 15,
373
+ border: "1px solid #ddd",
374
+ borderRadius: 8,
375
+ resize: "vertical"
376
+ },
377
+ disabled: state.submitting
378
+ }
379
+ ),
380
+ /* @__PURE__ */ jsxs(
381
+ "div",
382
+ {
383
+ style: {
384
+ marginTop: 10,
385
+ display: "flex",
386
+ gap: 8,
387
+ alignItems: "center"
388
+ },
389
+ children: [
390
+ state.screenshot ? /* @__PURE__ */ jsx2(
391
+ "img",
392
+ {
393
+ src: state.screenshot,
394
+ alt: "screenshot",
395
+ style: {
396
+ width: 56,
397
+ height: 56,
398
+ borderRadius: 6,
399
+ objectFit: "cover",
400
+ border: "1px solid #eee"
401
+ }
402
+ }
403
+ ) : /* @__PURE__ */ jsx2(
404
+ "button",
405
+ {
406
+ type: "button",
407
+ onClick: captureScreen,
408
+ disabled: state.capturing || state.submitting,
409
+ style: {
410
+ padding: "6px 10px",
411
+ fontSize: 13,
412
+ border: "1px solid #ccc",
413
+ borderRadius: 6,
414
+ background: "white",
415
+ cursor: "pointer"
416
+ },
417
+ children: state.capturing ? "Capturing\u2026" : "Attach screenshot"
418
+ }
419
+ ),
420
+ /* @__PURE__ */ jsx2("div", { style: { flex: 1 } }),
421
+ /* @__PURE__ */ jsx2(
422
+ "button",
423
+ {
424
+ type: "button",
425
+ onClick: close,
426
+ disabled: state.submitting,
427
+ style: {
428
+ padding: "8px 14px",
429
+ fontSize: 14,
430
+ border: "1px solid #ccc",
431
+ borderRadius: 6,
432
+ background: "white",
433
+ cursor: "pointer"
434
+ },
435
+ children: "Cancel"
436
+ }
437
+ ),
438
+ /* @__PURE__ */ jsx2(
439
+ "button",
440
+ {
441
+ type: "button",
442
+ onClick: submit,
443
+ disabled: !state.message.trim() || state.submitting,
444
+ style: {
445
+ padding: "8px 14px",
446
+ fontSize: 14,
447
+ border: "none",
448
+ borderRadius: 6,
449
+ background: state.message.trim() ? "#111" : "#bbb",
450
+ color: "white",
451
+ cursor: state.message.trim() ? "pointer" : "default"
452
+ },
453
+ children: state.submitting ? "Sending\u2026" : "Send"
454
+ }
455
+ )
456
+ ]
457
+ }
458
+ ),
459
+ state.error && /* @__PURE__ */ jsx2(
460
+ "div",
461
+ {
462
+ role: "alert",
463
+ style: {
464
+ marginTop: 12,
465
+ color: "#b00020",
466
+ fontSize: 13
467
+ },
468
+ children: state.error
469
+ }
470
+ )
471
+ ]
472
+ }
473
+ )
474
+ }
475
+ )
476
+ ] });
477
+ }
478
+ export {
479
+ BugReportButton,
480
+ CapacitorProvider,
481
+ useCapacitor,
482
+ useSafeArea
483
+ };
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Platform detection — the single source of truth for "are we in Capacitor?"
3
+ * used by every other module in the bridge.
4
+ *
5
+ * Capacitor exposes `(window as any).Capacitor` once the runtime initializes.
6
+ * We probe it carefully so the same code can run server-side (SSR) or in a
7
+ * plain browser tab without throwing.
8
+ */
9
+ type NativePlatform = "ios" | "android";
10
+ type Platform = NativePlatform | "web";
11
+ declare function getPlatform(): Platform;
12
+ declare function isNativePlatform(): boolean;
13
+ declare function isIOS(): boolean;
14
+ declare function isAndroid(): boolean;
15
+
16
+ /**
17
+ * Safe-area helpers. iOS notch / Dynamic Island / home indicator and Android
18
+ * cutout insets are exposed by Capacitor via env(safe-area-inset-*) CSS vars
19
+ * the WebView automatically populates. Consumer apps that already use those
20
+ * CSS env() values get this for free — but a lot of stock wp-frontend code
21
+ * uses tailwind padding constants that don't honor the insets. These helpers
22
+ * give Capacitor-aware components a JS-readable number.
23
+ */
24
+ interface SafeAreaInsets {
25
+ top: number;
26
+ right: number;
27
+ bottom: number;
28
+ left: number;
29
+ }
30
+ /**
31
+ * Read the current safe-area insets, in CSS pixels. Returns zeros on web
32
+ * (where the env() vars are 0) and in SSR.
33
+ */
34
+ declare function getSafeAreaInsets(): SafeAreaInsets;
35
+ /**
36
+ * Install a one-time stylesheet that copies env(safe-area-inset-*) onto
37
+ * `--safe-area-inset-*` CSS custom properties on :root, so getSafeAreaInsets()
38
+ * can read them via getComputedStyle. Call once at app boot — typically from
39
+ * a CapacitorProvider in React.
40
+ *
41
+ * Idempotent: calling twice is a no-op (we tag the inserted style node).
42
+ */
43
+ declare function installSafeAreaVars(): void;
44
+ /**
45
+ * True when we're on a platform that reports nonzero safe-area insets
46
+ * (every modern iPhone, most Android handsets). Useful for conditionally
47
+ * showing a "tap to dismiss" affordance above the home indicator.
48
+ */
49
+ declare function hasSafeAreaInsets(): boolean;
50
+
51
+ export { type NativePlatform as N, type Platform as P, type SafeAreaInsets as S, getSafeAreaInsets as a, isAndroid as b, isIOS as c, isNativePlatform as d, getPlatform as g, hasSafeAreaInsets as h, installSafeAreaVars as i };
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Platform detection — the single source of truth for "are we in Capacitor?"
3
+ * used by every other module in the bridge.
4
+ *
5
+ * Capacitor exposes `(window as any).Capacitor` once the runtime initializes.
6
+ * We probe it carefully so the same code can run server-side (SSR) or in a
7
+ * plain browser tab without throwing.
8
+ */
9
+ type NativePlatform = "ios" | "android";
10
+ type Platform = NativePlatform | "web";
11
+ declare function getPlatform(): Platform;
12
+ declare function isNativePlatform(): boolean;
13
+ declare function isIOS(): boolean;
14
+ declare function isAndroid(): boolean;
15
+
16
+ /**
17
+ * Safe-area helpers. iOS notch / Dynamic Island / home indicator and Android
18
+ * cutout insets are exposed by Capacitor via env(safe-area-inset-*) CSS vars
19
+ * the WebView automatically populates. Consumer apps that already use those
20
+ * CSS env() values get this for free — but a lot of stock wp-frontend code
21
+ * uses tailwind padding constants that don't honor the insets. These helpers
22
+ * give Capacitor-aware components a JS-readable number.
23
+ */
24
+ interface SafeAreaInsets {
25
+ top: number;
26
+ right: number;
27
+ bottom: number;
28
+ left: number;
29
+ }
30
+ /**
31
+ * Read the current safe-area insets, in CSS pixels. Returns zeros on web
32
+ * (where the env() vars are 0) and in SSR.
33
+ */
34
+ declare function getSafeAreaInsets(): SafeAreaInsets;
35
+ /**
36
+ * Install a one-time stylesheet that copies env(safe-area-inset-*) onto
37
+ * `--safe-area-inset-*` CSS custom properties on :root, so getSafeAreaInsets()
38
+ * can read them via getComputedStyle. Call once at app boot — typically from
39
+ * a CapacitorProvider in React.
40
+ *
41
+ * Idempotent: calling twice is a no-op (we tag the inserted style node).
42
+ */
43
+ declare function installSafeAreaVars(): void;
44
+ /**
45
+ * True when we're on a platform that reports nonzero safe-area insets
46
+ * (every modern iPhone, most Android handsets). Useful for conditionally
47
+ * showing a "tap to dismiss" affordance above the home indicator.
48
+ */
49
+ declare function hasSafeAreaInsets(): boolean;
50
+
51
+ export { type NativePlatform as N, type Platform as P, type SafeAreaInsets as S, getSafeAreaInsets as a, isAndroid as b, isIOS as c, isNativePlatform as d, getPlatform as g, hasSafeAreaInsets as h, installSafeAreaVars as i };
package/package.json ADDED
@@ -0,0 +1,78 @@
1
+ {
2
+ "name": "@meetreeve/capacitor-bridge",
3
+ "version": "0.1.0",
4
+ "description": "Reeve.Mobile substrate — Capacitor wrapper bridge for cross-product mobile apps. Provides useCapacitor() hook, safe-area helpers, native-aware paywall gate, splash bridge, deep-link router, bug-report widget, and Apple compliance pack.",
5
+ "license": "UNLICENSED",
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "type": "module",
10
+ "main": "./dist/core/index.js",
11
+ "module": "./dist/core/index.mjs",
12
+ "types": "./dist/core/index.d.ts",
13
+ "exports": {
14
+ "./core": {
15
+ "types": "./dist/core/index.d.ts",
16
+ "import": "./dist/core/index.mjs",
17
+ "require": "./dist/core/index.js"
18
+ },
19
+ "./react": {
20
+ "types": "./dist/react/index.d.ts",
21
+ "import": "./dist/react/index.mjs",
22
+ "require": "./dist/react/index.js"
23
+ },
24
+ "./paywall": {
25
+ "types": "./dist/paywall/index.d.ts",
26
+ "import": "./dist/paywall/index.mjs",
27
+ "require": "./dist/paywall/index.js"
28
+ },
29
+ "./deeplink": {
30
+ "types": "./dist/deeplink/index.d.ts",
31
+ "import": "./dist/deeplink/index.mjs",
32
+ "require": "./dist/deeplink/index.js"
33
+ },
34
+ "./bugreport": {
35
+ "types": "./dist/bugreport/index.d.ts",
36
+ "import": "./dist/bugreport/index.mjs",
37
+ "require": "./dist/bugreport/index.js"
38
+ },
39
+ "./compliance": "./compliance/index.cjs"
40
+ },
41
+ "files": [
42
+ "dist",
43
+ "compliance",
44
+ "README.md"
45
+ ],
46
+ "scripts": {
47
+ "build": "tsup",
48
+ "test": "vitest run",
49
+ "dev": "tsup --watch",
50
+ "typecheck": "tsc --noEmit",
51
+ "prepublishOnly": "tsup"
52
+ },
53
+ "devDependencies": {
54
+ "@capacitor/core": "^6.0.0",
55
+ "@testing-library/jest-dom": "^6.9.1",
56
+ "@testing-library/react": "^14.0.0",
57
+ "@types/node": "^20.0.0",
58
+ "@types/react": "^18.0.0",
59
+ "jsdom": "^28.1.0",
60
+ "react": "^18.0.0",
61
+ "react-dom": "^18.0.0",
62
+ "tsup": "^8.0.0",
63
+ "typescript": "^5.0.0",
64
+ "vitest": "^1.0.0"
65
+ },
66
+ "peerDependencies": {
67
+ "@capacitor/core": "^6.0.0",
68
+ "react": ">=18"
69
+ },
70
+ "peerDependenciesMeta": {
71
+ "@capacitor/core": {
72
+ "optional": true
73
+ },
74
+ "react": {
75
+ "optional": true
76
+ }
77
+ }
78
+ }