From 2398803adfb65bed133217612e4c3ed047773fbb Mon Sep 17 00:00:00 2001 From: mac Date: Fri, 31 Jul 2026 18:37:09 +0800 Subject: [PATCH 1/5] refactor(frontend): add tested shared overlays --- .../app/overlay/OverlayProvider.test.tsx | 61 + .../components/AppDialogProvider.test.tsx | 58 + .../shared/components/BackButton.test.tsx | 18 + .../shared/hooks/useCurrentDate.test.ts | 33 + frontend/__tests__/shared/theme/index.test.ts | 11 + frontend/__tests__/shared/utils/date.test.ts | 80 + frontend/eslint.config.js | 15 + frontend/jest.setup.js | 14 + frontend/package-lock.json | 10191 +++++++++++----- frontend/package.json | 26 +- frontend/src/app/overlay/OverlayProvider.tsx | 109 + .../components/AppDialogProvider.styles.ts | 86 + .../shared/components/AppDialogProvider.tsx | 162 + frontend/src/shared/components/BackButton.tsx | 41 + .../components/BottomSheetFrame.styles.ts | 34 + .../shared/components/BottomSheetFrame.tsx | 106 + .../shared/components/sheetChrome.styles.ts | 27 + frontend/src/shared/hooks/useCurrentDate.ts | 12 + frontend/src/shared/theme/index.ts | 25 + frontend/src/shared/utils/date.ts | 40 + frontend/tsconfig.json | 6 +- 21 files changed, 7838 insertions(+), 3317 deletions(-) create mode 100644 frontend/__tests__/app/overlay/OverlayProvider.test.tsx create mode 100644 frontend/__tests__/shared/components/AppDialogProvider.test.tsx create mode 100644 frontend/__tests__/shared/components/BackButton.test.tsx create mode 100644 frontend/__tests__/shared/hooks/useCurrentDate.test.ts create mode 100644 frontend/__tests__/shared/theme/index.test.ts create mode 100644 frontend/__tests__/shared/utils/date.test.ts create mode 100644 frontend/jest.setup.js create mode 100644 frontend/src/app/overlay/OverlayProvider.tsx create mode 100644 frontend/src/shared/components/AppDialogProvider.styles.ts create mode 100644 frontend/src/shared/components/AppDialogProvider.tsx create mode 100644 frontend/src/shared/components/BackButton.tsx create mode 100644 frontend/src/shared/components/BottomSheetFrame.styles.ts create mode 100644 frontend/src/shared/components/BottomSheetFrame.tsx create mode 100644 frontend/src/shared/components/sheetChrome.styles.ts create mode 100644 frontend/src/shared/hooks/useCurrentDate.ts create mode 100644 frontend/src/shared/theme/index.ts create mode 100644 frontend/src/shared/utils/date.ts diff --git a/frontend/__tests__/app/overlay/OverlayProvider.test.tsx b/frontend/__tests__/app/overlay/OverlayProvider.test.tsx new file mode 100644 index 0000000..b65fedc --- /dev/null +++ b/frontend/__tests__/app/overlay/OverlayProvider.test.tsx @@ -0,0 +1,61 @@ +import { describe, expect, it, jest } from '@jest/globals'; +import { fireEvent, render, screen } from '@testing-library/react-native'; +import { StrictMode } from 'react'; +import { Pressable, Text, View } from 'react-native'; + +import { OverlayProvider, useOverlay, type OverlayKind } from '@/app/overlay/OverlayProvider'; + +function Harness({ onClose }: { onClose: () => void }) { + const { pop, popKind, push, stack } = useOverlay(); + + const add = (kind: OverlayKind) => { + push({ kind, onClose }); + }; + + return ( + <> + {stack.map((entry) => entry.kind).join(',')} + + add('standardCreate')} /> + add('assistant')} /> + + popKind('standardCreate')} /> + + + ); +} + +function renderHarness(onClose: () => void) { + return render( + + + + + , + ); +} + +describe('OverlayProvider', () => { + it('invokes onClose once when the top overlay is popped', () => { + const onClose = jest.fn(); + renderHarness(onClose); + + fireEvent.press(screen.getByLabelText('push-standard')); + fireEvent.press(screen.getByLabelText('pop')); + + expect(onClose).toHaveBeenCalledTimes(1); + }); + + it('removes and closes only the latest matching overlay', () => { + const onClose = jest.fn(); + renderHarness(onClose); + + fireEvent.press(screen.getByLabelText('push-standard')); + fireEvent.press(screen.getByLabelText('push-assistant')); + fireEvent.press(screen.getByLabelText('push-standard')); + fireEvent.press(screen.getByLabelText('pop-standard')); + + expect(onClose).toHaveBeenCalledTimes(1); + expect(screen.getByText('standardCreate,assistant')).toBeTruthy(); + }); +}); diff --git a/frontend/__tests__/shared/components/AppDialogProvider.test.tsx b/frontend/__tests__/shared/components/AppDialogProvider.test.tsx new file mode 100644 index 0000000..e8db627 --- /dev/null +++ b/frontend/__tests__/shared/components/AppDialogProvider.test.tsx @@ -0,0 +1,58 @@ +import { describe, expect, it } from '@jest/globals'; +import { fireEvent, render, screen } from '@testing-library/react-native'; +import { Pressable, Text } from 'react-native'; + +import { AppDialogProvider, useAppDialog } from '@/shared/components/AppDialogProvider'; + +function Harness() { + const { confirm, showNotice } = useAppDialog(); + return ( + <> + void showNotice({ title: '连接不可用', message: '请检查网络' })} + /> + + void confirm({ + title: '删除日程', + message: '删除后无法恢复', + confirmLabel: '删除', + tone: 'danger', + }) + } + /> + content + + ); +} + +describe('AppDialogProvider', () => { + it('renders notices in the app instead of a native Alert', () => { + render( + + + , + ); + + fireEvent.press(screen.getByLabelText('show-notice')); + expect(screen.getByText('连接不可用')).toBeTruthy(); + expect(screen.getByText('请检查网络')).toBeTruthy(); + fireEvent.press(screen.getByLabelText('知道了')); + expect(screen.queryByText('连接不可用')).toBeNull(); + }); + + it('renders custom destructive confirmations', () => { + render( + + + , + ); + + fireEvent.press(screen.getByLabelText('show-confirm')); + expect(screen.getByText('删除后无法恢复')).toBeTruthy(); + expect(screen.getByLabelText('取消')).toBeTruthy(); + expect(screen.getByLabelText('删除')).toBeTruthy(); + }); +}); diff --git a/frontend/__tests__/shared/components/BackButton.test.tsx b/frontend/__tests__/shared/components/BackButton.test.tsx new file mode 100644 index 0000000..3853726 --- /dev/null +++ b/frontend/__tests__/shared/components/BackButton.test.tsx @@ -0,0 +1,18 @@ +import { describe, expect, it, jest } from '@jest/globals'; +import { fireEvent, render, screen } from '@testing-library/react-native'; + +import { BackButton } from '@/shared/components/BackButton'; + +describe('BackButton', () => { + it('uses the default label and fires onPress', () => { + const onPress = jest.fn(); + render(); + fireEvent.press(screen.getByLabelText('返回')); + expect(onPress).toHaveBeenCalled(); + }); + + it('accepts a custom accessibility label', () => { + render(); + expect(screen.getByLabelText('关闭')).toBeTruthy(); + }); +}); diff --git a/frontend/__tests__/shared/hooks/useCurrentDate.test.ts b/frontend/__tests__/shared/hooks/useCurrentDate.test.ts new file mode 100644 index 0000000..98c9a80 --- /dev/null +++ b/frontend/__tests__/shared/hooks/useCurrentDate.test.ts @@ -0,0 +1,33 @@ +import { afterEach, beforeEach, describe, expect, it, jest } from '@jest/globals'; +import { act, renderHook } from '@testing-library/react-native'; + +import { useCurrentDate } from '@/shared/hooks/useCurrentDate'; + +describe('useCurrentDate', () => { + beforeEach(() => { + jest.useFakeTimers(); + jest.setSystemTime(new Date(2026, 6, 31, 12, 0, 0)); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it('starts at the current time and ticks every minute', () => { + const { result } = renderHook(() => useCurrentDate()); + expect(result.current.getTime()).toBe(new Date(2026, 6, 31, 12, 0, 0).getTime()); + + act(() => { + jest.advanceTimersByTime(60_000); + }); + expect(result.current.getTime()).toBe(new Date(2026, 6, 31, 12, 1, 0).getTime()); + }); + + it('clears the interval on unmount', () => { + const clearSpy = jest.spyOn(global, 'clearInterval'); + const { unmount } = renderHook(() => useCurrentDate()); + unmount(); + expect(clearSpy).toHaveBeenCalled(); + clearSpy.mockRestore(); + }); +}); diff --git a/frontend/__tests__/shared/theme/index.test.ts b/frontend/__tests__/shared/theme/index.test.ts new file mode 100644 index 0000000..e8b67e2 --- /dev/null +++ b/frontend/__tests__/shared/theme/index.test.ts @@ -0,0 +1,11 @@ +import { describe, expect, it } from '@jest/globals'; + +import { colors, spacing } from '@/shared/theme/index'; + +describe('theme tokens', () => { + it('exposes the brand palette and spacing scale', () => { + expect(colors.deep).toBe('#15352B'); + expect(colors.lime).toBe('#D7F36A'); + expect(spacing.md).toBe(16); + }); +}); diff --git a/frontend/__tests__/shared/utils/date.test.ts b/frontend/__tests__/shared/utils/date.test.ts new file mode 100644 index 0000000..bb9ebf7 --- /dev/null +++ b/frontend/__tests__/shared/utils/date.test.ts @@ -0,0 +1,80 @@ +import { describe, expect, it } from '@jest/globals'; + +import { + addDays, + dateKey, + formatDate, + formatFullDate, + formatMonthDay, + formatWeekRange, + startOfMonth, + startOfWeek, +} from '@/shared/utils/date'; + +describe('dateKey', () => { + it('pads single-digit months and days', () => { + expect(dateKey(new Date(2026, 0, 5))).toBe('2026-01-05'); + }); + + it('leaves two-digit values alone', () => { + expect(dateKey(new Date(2026, 11, 25))).toBe('2026-12-25'); + }); +}); + +describe('startOfWeek', () => { + it('treats Monday as the first day of the week', () => { + expect(dateKey(startOfWeek(new Date(2026, 6, 29)))).toBe('2026-07-27'); + }); + + it('maps Sunday back to the Monday that started it', () => { + expect(dateKey(startOfWeek(new Date(2026, 7, 2)))).toBe('2026-07-27'); + }); + + it('is a no-op when the date is already Monday', () => { + expect(dateKey(startOfWeek(new Date(2026, 6, 27)))).toBe('2026-07-27'); + }); +}); + +describe('addDays', () => { + it('crosses a month boundary', () => { + expect(dateKey(addDays(new Date(2026, 6, 30), 3))).toBe('2026-08-02'); + }); + + it('accepts a negative amount', () => { + expect(dateKey(addDays(new Date(2026, 7, 1), -1))).toBe('2026-07-31'); + }); +}); + +describe('startOfMonth', () => { + it('returns the first day of the month', () => { + expect(dateKey(startOfMonth(new Date(2026, 6, 29)))).toBe('2026-07-01'); + }); +}); + +describe('formatDate', () => { + it('renders month, day and weekday', () => { + expect(formatDate(new Date(2026, 6, 29))).toBe('7月29日 · 星期三'); + }); + + it('labels Sunday as 星期日 rather than 星期一', () => { + expect(formatDate(new Date(2026, 7, 2))).toBe('8月2日 · 星期日'); + }); +}); + +describe('formatFullDate', () => { + it('includes the year', () => { + expect(formatFullDate(new Date(2026, 6, 29))).toBe('2026年7月29日 · 星期三'); + }); +}); + +describe('formatMonthDay', () => { + it('does not pad single-digit values', () => { + expect(formatMonthDay(new Date(2026, 0, 5))).toBe('1月5日'); + }); +}); + +describe('formatWeekRange', () => { + it('spans seven days from the given start', () => { + expect(formatWeekRange(new Date(2026, 6, 27))).toBe('7月27日—8月2日'); + }); +}); diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js index f041633..5220552 100644 --- a/frontend/eslint.config.js +++ b/frontend/eslint.config.js @@ -15,4 +15,19 @@ module.exports = defineConfig([ 'no-console': ['warn', { allow: ['warn', 'error'] }], }, }, + { + files: ['__tests__/**/*.ts', '__tests__/**/*.tsx'], + rules: { + '@typescript-eslint/no-require-imports': 'off', + 'import/first': 'off', + }, + }, + { + files: ['jest.setup.js'], + languageOptions: { + globals: { + jest: 'readonly', + }, + }, + }, ]); diff --git a/frontend/jest.setup.js b/frontend/jest.setup.js new file mode 100644 index 0000000..69d35d7 --- /dev/null +++ b/frontend/jest.setup.js @@ -0,0 +1,14 @@ +// jest-expo installs fetch lazily; initialize it before the test environment is torn down. +Reflect.get(globalThis, 'fetch'); + +jest.mock('lucide-react-native', () => { + const React = require('react'); + const { View } = require('react-native'); + const Icon = (props) => React.createElement(View, { ...props, testID: props.testID ?? 'icon' }); + return new Proxy( + {}, + { + get: (_target, key) => (typeof key === 'string' && key !== '__esModule' ? Icon : undefined), + }, + ); +}); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index fb6871f..826dd44 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -25,11 +25,17 @@ }, "devDependencies": { "@baidumap/jsapi-v4-types": "^4.0.2", + "@react-native/jest-preset": "0.86.0", + "@testing-library/react-native": "^13.2.0", + "@types/jest": "29.5.14", "@types/react": "~19.2.2", "eslint": "^9.39.5", "eslint-config-expo": "^57.0.0", "eslint-config-prettier": "^10.1.8", + "jest": "~29.7.0", + "jest-expo": "57.0.2", "prettier": "^3.9.5", + "react-test-renderer": "19.2.3", "typescript": "~6.0.3" }, "engines": { @@ -445,6 +451,61 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-decorators": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.29.7.tgz", @@ -502,6 +563,48 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.29.7.tgz", + "integrity": "sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-jsx": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", @@ -517,6 +620,19 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", @@ -529,6 +645,45 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-optional-chaining": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", @@ -541,6 +696,38 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-typescript": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz", @@ -1095,6 +1282,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, "node_modules/@emnapi/core": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", @@ -1840,1322 +2034,3191 @@ "node": ">=12" } }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "license": "MIT", + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "devOptional": true, + "license": "ISC", "dependencies": { - "@sinclair/typebox": "^0.27.8" + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "devOptional": true, "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "sprintf-js": "~1.0.2" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "devOptional": true, "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" + "engines": { + "node": ">=6" } }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "devOptional": true, "license": "MIT", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, "engines": { - "node": ">=6.0.0" + "node": ">=8" } }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.11", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", - "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.0.tgz", + "integrity": "sha512-ttBQIIQPDeLjpPOohtUdXuXUVoA2uIB6fEH9HyJ7234s5mBJ5wTx20njxplLZQgLaOfpmPQA7X2t5AX6tIPbog==", + "devOptional": true, "license": "MIT", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "devOptional": true, "license": "MIT", "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", - "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", - "dev": true, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "devOptional": true, "license": "MIT", - "optional": true, "dependencies": { - "@tybys/wasm-util": "^0.10.3" + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" }, - "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" + "engines": { + "node": ">=8" } }, - "node_modules/@nolyfill/is-core-module": { - "version": "1.0.39", - "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", - "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", - "dev": true, + "node_modules/@istanbuljs/schema": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", + "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", + "devOptional": true, "license": "MIT", "engines": { - "node": ">=12.4.0" + "node": ">=8" } }, - "node_modules/@react-native-community/datetimepicker": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@react-native-community/datetimepicker/-/datetimepicker-9.1.0.tgz", - "integrity": "sha512-eadbnk+I2vxvW30iTAsm/qlCnMMAadkifIMYNEB2lzhxN/SvlKc7S2V4k5DyrwjdCbqdcMk3t9K6fnUMcAV34w==", + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, "license": "MIT", "dependencies": { - "invariant": "^2.2.4" - }, - "peerDependencies": { - "expo": ">=52.0.0", - "react": "*", - "react-native": "*", - "react-native-windows": "*" + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "peerDependenciesMeta": { - "expo": { - "optional": true - }, - "react-native-windows": { + "node-notifier": { "optional": true } } }, - "node_modules/@react-native/assets-registry": { - "version": "0.86.0", - "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.86.0.tgz", - "integrity": "sha512-nIaXbm2jX1OTYp0qbviJ3O6KZivoE8z3BnhUQ2LsqfZSWRoOK/n1qsiAr6oALiNKWnXY3j2KPwtYORnZzp8xew==", + "node_modules/@jest/core/node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], "license": "MIT", "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + "node": ">=8" } }, - "node_modules/@react-native/babel-plugin-codegen": { - "version": "0.86.0", - "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.86.0.tgz", - "integrity": "sha512-qdsABWNW7uTll90l4Vh03gjeyu3WVDi2CyiiyvYGMRDcoYbjbQi6df3BMAm9lQI2yslZ1T14LlDDAsgTwNxplA==", + "node_modules/@jest/create-cache-key-function": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz", + "integrity": "sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==", + "devOptional": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.29.0", - "@react-native/codegen": "0.86.0" + "@jest/types": "^29.6.3" }, "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@react-native/codegen": { - "version": "0.86.0", - "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.86.0.tgz", - "integrity": "sha512-uTs9DBo3+/lUqinsGZK0FKJRBVClrwMXoZToaDxE1Q2SL2e55vs2GwyZfIKzPl5uJnbu4PfFMIp0/mLXLWUMuA==", + "node_modules/@jest/diff-sequences": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.4.0.tgz", + "integrity": "sha512-zOpzlfUs45l6u7jm39qr87JCHUDsaeCtvL+kQe/Vn9jSnRB4/5IPXISm0h9I1vZW/o00Kn4UTJ2MOlhnUGwv3g==", + "dev": true, "license": "MIT", - "dependencies": { - "@babel/core": "^7.25.2", - "@babel/parser": "^7.29.0", - "hermes-parser": "0.36.0", - "invariant": "^2.2.4", - "nullthrows": "^1.1.1", - "tinyglobby": "^0.2.15", - "yargs": "^17.6.2" - }, "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - }, - "peerDependencies": { - "@babel/core": "*" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@react-native/codegen/node_modules/hermes-estree": { - "version": "0.36.0", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.36.0.tgz", - "integrity": "sha512-A1+8zn5oss2CFP7pKsOaxorQG6FNIz1WU1VDqruLPPZl3LVgeE2C5xfFg8Ow6/Ow4mSslLLtYP1J3n38eKyW9w==", - "license": "MIT" - }, - "node_modules/@react-native/codegen/node_modules/hermes-parser": { - "version": "0.36.0", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.36.0.tgz", - "integrity": "sha512-GdpwMmH5x6IpC1cijvcvYnlPB60Mh6kTSF/NFdYV/j56gYdi+0RIakYs+eqOV+bbO0SW7mgVVGSsTJxyPQfo3w==", + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "devOptional": true, "license": "MIT", "dependencies": { - "hermes-estree": "0.36.0" + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@react-native/community-cli-plugin": { - "version": "0.86.0", - "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.86.0.tgz", - "integrity": "sha512-Jv8p1ebEPfTzs8gmrjsdT2XMXFfeAg45Pman+XPLFGaSeGAZkutRFRyX9Cs9aGTSOyIA9YPJ6vDNb1ayTf1FKQ==", + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, "license": "MIT", "dependencies": { - "@react-native/dev-middleware": "0.86.0", - "debug": "^4.4.0", - "invariant": "^2.2.4", - "metro": "^0.84.3", - "metro-config": "^0.84.3", - "metro-core": "^0.84.3", - "semver": "^7.1.3" + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" }, "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - }, - "peerDependencies": { - "@react-native-community/cli": "*", - "@react-native/metro-config": "0.86.0" - }, - "peerDependenciesMeta": { - "@react-native-community/cli": { - "optional": true - }, - "@react-native/metro-config": { - "optional": true - } - } - }, - "node_modules/@react-native/debugger-frontend": { - "version": "0.86.0", - "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.86.0.tgz", - "integrity": "sha512-7Mb3nDfyJeys+ELF75Ageu7VKERlnIMoO+aNPoXqTXvz+b41L6l2CqMyLpDHxkBSlenij6gEepPNgaIyWHbJZw==", - "license": "BSD-3-Clause", - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@react-native/debugger-shell": { - "version": "0.86.0", - "resolved": "https://registry.npmjs.org/@react-native/debugger-shell/-/debugger-shell-0.86.0.tgz", - "integrity": "sha512-Y0zEkZzLz8ou6o/VLml1A31X/rMgc6DRjwxwzPMa94qRTMY070WeBCNTITQo4kKTBAUgbxh07oXPQqp0Tpja8w==", + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.6", - "debug": "^4.4.0", - "fb-dotslash": "0.5.8" + "jest-get-type": "^29.6.3" }, "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@react-native/dev-middleware": { - "version": "0.86.0", - "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.86.0.tgz", - "integrity": "sha512-20pTO6yTybmvXvro520H6C7jydIQnLKOl5qFtVEcHSdFrY63r3OGei+Rx9bILgSRmH6jgnfEcijcMx7pwWuQtw==", + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "devOptional": true, "license": "MIT", "dependencies": { - "@isaacs/ttlcache": "^1.4.1", - "@react-native/debugger-frontend": "0.86.0", - "@react-native/debugger-shell": "0.86.0", - "chrome-launcher": "^0.15.2", - "chromium-edge-launcher": "^0.3.0", - "connect": "^3.6.5", - "debug": "^4.4.0", - "invariant": "^2.2.4", - "nullthrows": "^1.1.1", - "open": "^7.0.3", - "serve-static": "^1.16.2", - "ws": "^7.5.10" + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@react-native/gradle-plugin": { - "version": "0.86.0", - "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.86.0.tgz", - "integrity": "sha512-a1RcfaEDqWExCGfCwadIxt4l8FvKYgFqeMf2uzeKyAOnb+vTGNIeCvifFL2MqvgaeYxlER437HbMIajGcuJ1pQ==", + "node_modules/@jest/get-type": { + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", + "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", + "dev": true, "license": "MIT", "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@react-native/js-polyfills": { - "version": "0.86.0", - "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.86.0.tgz", - "integrity": "sha512-zYy/Cjd1VTnZ2iCNaG9bDF9C3l2ntESiPRscjIlI5FKugu6aeTwsDSv1aI8Bc4Kp3vEdoVg+UQhLAhE4svREaQ==", + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@react-native/normalize-colors": { - "version": "0.86.0", - "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.86.0.tgz", - "integrity": "sha512-kG0wfCGghUKlfxkJyyHCDVutWVYWK7/DG58ojA/4v9EfulgF+osuSQmlbNb3rcKX58qutm7JcldSeVLgGFha9g==", - "license": "MIT" - }, - "node_modules/@react-native/virtualized-lists": { - "version": "0.86.0", - "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.86.0.tgz", - "integrity": "sha512-4/ZLXdf/OSpPDVO0AsQ1SJdRIzt5t9BNQ46QwGgxvX7/cirYR5k8KXctNGGgW8lQo2gZChEfY2zFCZg9nM/jiw==", + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, "license": "MIT", "dependencies": { - "invariant": "^2.2.4", - "nullthrows": "^1.1.1" + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" }, "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { - "@types/react": "^19.2.0", - "react": "*", - "react-native": "0.86.0" + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "peerDependenciesMeta": { - "@types/react": { + "node-notifier": { "optional": true } } }, - "node_modules/@rtsao/scc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", - "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "node_modules/@jest/reporters/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, - "license": "MIT" - }, - "node_modules/@sinclair/typebox": { - "version": "0.27.12", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.12.tgz", - "integrity": "sha512-hhyNJ+nbR6ZR7pToHvllEFun9TL0sbL+tk/ON75lo+Xas054uez98qRbsuNt7MBCyZKK4+8Yli/OAGZhmfBZ/g==", - "license": "MIT" + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", - "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "node_modules/@jest/reporters/node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", "dev": true, - "license": "MIT", - "optional": true, + "license": "BSD-3-Clause", "dependencies": { - "tslib": "^2.4.0" + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" } }, - "node_modules/@types/estree": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", - "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "node_modules/@jest/reporters/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, - "license": "MIT" - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "license": "MIT" + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", - "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "license": "MIT", "dependencies": { - "@types/istanbul-lib-coverage": "*" + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, "license": "MIT", "dependencies": { - "@types/istanbul-lib-report": "*" + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "26.1.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.1.tgz", - "integrity": "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==", "license": "MIT", "dependencies": { - "undici-types": "~8.3.0" + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@types/react": { - "version": "19.2.17", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", - "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", "devOptional": true, "license": "MIT", "dependencies": { - "csstype": "^3.2.2" + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@types/yargs": { - "version": "17.0.35", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", - "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "license": "MIT", "dependencies": { - "@types/yargs-parser": "*" + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "license": "MIT" + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.64.0.tgz", - "integrity": "sha512-CGvQPBxN3wZLu6Rz2kFUpZeoCm78xUic92ck39KPePkO1NPOwjCqdQnm5Q87tpWw9vcBvW8XLrDXjH9PWYtJ3Q==", - "dev": true, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.64.0", - "@typescript-eslint/type-utils": "8.64.0", - "@typescript-eslint/utils": "8.64.0", - "@typescript-eslint/visitor-keys": "8.64.0", - "ignore": "^7.0.5", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.64.0", - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", - "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", - "dev": true, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "license": "MIT", "engines": { - "node": ">= 4" + "node": ">=6.0.0" } }, - "node_modules/@typescript-eslint/parser": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.64.0.tgz", - "integrity": "sha512-KA0OshtlcCCXmbfqyZkM5pV3/WNraJf7DkJRLpyrmwPtud57H5BDX7C3k0LPSPxpprfRL+cJDGabF10mvNCoCw==", - "dev": true, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.64.0", - "@typescript-eslint/types": "8.64.0", - "@typescript-eslint/typescript-estree": "8.64.0", - "@typescript-eslint/visitor-keys": "8.64.0", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" } }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.64.0.tgz", - "integrity": "sha512-tk4WpOJ6IEbGrVHaNmM0YRrwAD3exZlIK3iadQNAxh4YKk6jvUQ4ecq18n+v7+meh+cJ3j+D8nbk8sRKhlwLQg==", - "dev": true, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.64.0", - "@typescript-eslint/types": "^8.64.0", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.64.0.tgz", - "integrity": "sha512-CXEaFdYXjSTgKhisNkwCcJwTP8Pl+fmRrEQrri4nm3vU743bALrxzLmq7fHG/7e6a5xO0lDYeURpZmBuhHk54w==", + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", + "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@typescript-eslint/types": "8.64.0", - "@typescript-eslint/visitor-keys": "8.64.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "@tybys/wasm-util": "^0.10.3" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" } }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.64.0.tgz", - "integrity": "sha512-2yo8rRNKuzbVWQp5kslhANqZ2uDAeROQHBRZNPu8JDsHmeFNj/XJJhX/FhNUWmkHHvoNsKa6+tHJiig87EzsQw==", + "node_modules/@nolyfill/is-core-module": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", + "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", "dev": true, "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" + "node": ">=12.4.0" } }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.64.0.tgz", - "integrity": "sha512-XWG4Fmmv/6SvyS9nH8jWrKs6terwJvE8cyRt1CzYYqzp9OrPhCT4cMc/f7C6RZCwG+qMmiffJS1/qJP8G1URtg==", - "dev": true, + "node_modules/@react-native-community/datetimepicker": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@react-native-community/datetimepicker/-/datetimepicker-9.1.0.tgz", + "integrity": "sha512-eadbnk+I2vxvW30iTAsm/qlCnMMAadkifIMYNEB2lzhxN/SvlKc7S2V4k5DyrwjdCbqdcMk3t9K6fnUMcAV34w==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.64.0", - "@typescript-eslint/typescript-estree": "8.64.0", - "@typescript-eslint/utils": "8.64.0", - "debug": "^4.4.3", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "invariant": "^2.2.4" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" + "expo": ">=52.0.0", + "react": "*", + "react-native": "*", + "react-native-windows": "*" + }, + "peerDependenciesMeta": { + "expo": { + "optional": true + }, + "react-native-windows": { + "optional": true + } } }, - "node_modules/@typescript-eslint/types": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.64.0.tgz", - "integrity": "sha512-qjhfuTfLXjA4IOzXvz0rTjT01BqEiIgPoUeMwiEjnaHKJMTNo8rH5pYW1a2L/0Dnux2fPC85AeyJoWaGa8WxTA==", - "dev": true, + "node_modules/@react-native/assets-registry": { + "version": "0.86.0", + "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.86.0.tgz", + "integrity": "sha512-nIaXbm2jX1OTYp0qbviJ3O6KZivoE8z3BnhUQ2LsqfZSWRoOK/n1qsiAr6oALiNKWnXY3j2KPwtYORnZzp8xew==", "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/babel-plugin-codegen": { + "version": "0.86.0", + "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.86.0.tgz", + "integrity": "sha512-qdsABWNW7uTll90l4Vh03gjeyu3WVDi2CyiiyvYGMRDcoYbjbQi6df3BMAm9lQI2yslZ1T14LlDDAsgTwNxplA==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.0", + "@react-native/codegen": "0.86.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.64.0.tgz", - "integrity": "sha512-Pztpsn1aCE1oWDvDEfUk31nngvvF7vUB5SwHFEaZIFpvw7WJtqUHHL4plBZDA9HfWJJjL13BdG0YrJInTUvoVA==", - "dev": true, + "node_modules/@react-native/codegen": { + "version": "0.86.0", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.86.0.tgz", + "integrity": "sha512-uTs9DBo3+/lUqinsGZK0FKJRBVClrwMXoZToaDxE1Q2SL2e55vs2GwyZfIKzPl5uJnbu4PfFMIp0/mLXLWUMuA==", "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.64.0", - "@typescript-eslint/tsconfig-utils": "8.64.0", - "@typescript-eslint/types": "8.64.0", - "@typescript-eslint/visitor-keys": "8.64.0", - "debug": "^4.4.3", - "minimatch": "^10.2.2", - "semver": "^7.7.3", + "@babel/core": "^7.25.2", + "@babel/parser": "^7.29.0", + "hermes-parser": "0.36.0", + "invariant": "^2.2.4", + "nullthrows": "^1.1.1", "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.5.0" + "yargs": "^17.6.2" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" + "@babel/core": "*" } }, - "node_modules/@typescript-eslint/utils": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.64.0.tgz", - "integrity": "sha512-aJUGVB3+U0htrrCjoA8qukw8cm8fNCGAxK/tVoS70k8aeb7DETKeFozRiVFIwEeN9WJLsjaP3ph8I60tY2XZoQ==", - "dev": true, + "node_modules/@react-native/codegen/node_modules/hermes-estree": { + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.36.0.tgz", + "integrity": "sha512-A1+8zn5oss2CFP7pKsOaxorQG6FNIz1WU1VDqruLPPZl3LVgeE2C5xfFg8Ow6/Ow4mSslLLtYP1J3n38eKyW9w==", + "license": "MIT" + }, + "node_modules/@react-native/codegen/node_modules/hermes-parser": { + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.36.0.tgz", + "integrity": "sha512-GdpwMmH5x6IpC1cijvcvYnlPB60Mh6kTSF/NFdYV/j56gYdi+0RIakYs+eqOV+bbO0SW7mgVVGSsTJxyPQfo3w==", "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.64.0", - "@typescript-eslint/types": "8.64.0", - "@typescript-eslint/typescript-estree": "8.64.0" + "hermes-estree": "0.36.0" + } + }, + "node_modules/@react-native/community-cli-plugin": { + "version": "0.86.0", + "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.86.0.tgz", + "integrity": "sha512-Jv8p1ebEPfTzs8gmrjsdT2XMXFfeAg45Pman+XPLFGaSeGAZkutRFRyX9Cs9aGTSOyIA9YPJ6vDNb1ayTf1FKQ==", + "license": "MIT", + "dependencies": { + "@react-native/dev-middleware": "0.86.0", + "debug": "^4.4.0", + "invariant": "^2.2.4", + "metro": "^0.84.3", + "metro-config": "^0.84.3", + "metro-core": "^0.84.3", + "semver": "^7.1.3" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" + "@react-native-community/cli": "*", + "@react-native/metro-config": "0.86.0" + }, + "peerDependenciesMeta": { + "@react-native-community/cli": { + "optional": true + }, + "@react-native/metro-config": { + "optional": true + } } }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.64.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.64.0.tgz", - "integrity": "sha512-mrtuL8Nsn6gi2H4mo5KMTp823M+3Q19Ew/i+Zlikq20tIMm99C3Ez0dCmkWWnxut20esQvTg8aUSEhMcAOXhEw==", - "dev": true, + "node_modules/@react-native/debugger-frontend": { + "version": "0.86.0", + "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.86.0.tgz", + "integrity": "sha512-7Mb3nDfyJeys+ELF75Ageu7VKERlnIMoO+aNPoXqTXvz+b41L6l2CqMyLpDHxkBSlenij6gEepPNgaIyWHbJZw==", + "license": "BSD-3-Clause", + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/debugger-shell": { + "version": "0.86.0", + "resolved": "https://registry.npmjs.org/@react-native/debugger-shell/-/debugger-shell-0.86.0.tgz", + "integrity": "sha512-Y0zEkZzLz8ou6o/VLml1A31X/rMgc6DRjwxwzPMa94qRTMY070WeBCNTITQo4kKTBAUgbxh07oXPQqp0Tpja8w==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.64.0", - "eslint-visitor-keys": "^5.0.0" + "cross-spawn": "^7.0.6", + "debug": "^4.4.0", + "fb-dotslash": "0.5.8" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.3.tgz", - "integrity": "sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==", - "license": "ISC" + "node_modules/@react-native/dev-middleware": { + "version": "0.86.0", + "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.86.0.tgz", + "integrity": "sha512-20pTO6yTybmvXvro520H6C7jydIQnLKOl5qFtVEcHSdFrY63r3OGei+Rx9bILgSRmH6jgnfEcijcMx7pwWuQtw==", + "license": "MIT", + "dependencies": { + "@isaacs/ttlcache": "^1.4.1", + "@react-native/debugger-frontend": "0.86.0", + "@react-native/debugger-shell": "0.86.0", + "chrome-launcher": "^0.15.2", + "chromium-edge-launcher": "^0.3.0", + "connect": "^3.6.5", + "debug": "^4.4.0", + "invariant": "^2.2.4", + "nullthrows": "^1.1.1", + "open": "^7.0.3", + "serve-static": "^1.16.2", + "ws": "^7.5.10" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } }, - "node_modules/@unrs/resolver-binding-android-arm-eabi": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.12.2.tgz", - "integrity": "sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==", - "cpu": [ - "arm" - ], - "dev": true, + "node_modules/@react-native/gradle-plugin": { + "version": "0.86.0", + "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.86.0.tgz", + "integrity": "sha512-a1RcfaEDqWExCGfCwadIxt4l8FvKYgFqeMf2uzeKyAOnb+vTGNIeCvifFL2MqvgaeYxlER437HbMIajGcuJ1pQ==", "license": "MIT", - "optional": true, - "os": [ - "android" - ] + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } }, - "node_modules/@unrs/resolver-binding-android-arm64": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.12.2.tgz", - "integrity": "sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@react-native/jest-preset": { + "version": "0.86.0", + "resolved": "https://registry.npmjs.org/@react-native/jest-preset/-/jest-preset-0.86.0.tgz", + "integrity": "sha512-KA+xpIP3DvJy7PQJ9c6ZdEKkOPChl+Rk/rV2MhQACEAzfhWU84407KZQv4ccyO3B4caD0gPrFjE96a4P993nsQ==", + "devOptional": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ] + "dependencies": { + "@jest/create-cache-key-function": "^29.7.0", + "@react-native/js-polyfills": "0.86.0", + "babel-jest": "^29.7.0", + "jest-environment-node": "^29.7.0", + "regenerator-runtime": "^0.13.2" + }, + "engines": { + "node": ">= 20.19.4" + }, + "peerDependencies": { + "react": "^19.2.3" + } }, - "node_modules/@unrs/resolver-binding-darwin-arm64": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.12.2.tgz", - "integrity": "sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@react-native/js-polyfills": { + "version": "0.86.0", + "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.86.0.tgz", + "integrity": "sha512-zYy/Cjd1VTnZ2iCNaG9bDF9C3l2ntESiPRscjIlI5FKugu6aeTwsDSv1aI8Bc4Kp3vEdoVg+UQhLAhE4svREaQ==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } }, - "node_modules/@unrs/resolver-binding-darwin-x64": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.12.2.tgz", - "integrity": "sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] + "node_modules/@react-native/normalize-colors": { + "version": "0.86.0", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.86.0.tgz", + "integrity": "sha512-kG0wfCGghUKlfxkJyyHCDVutWVYWK7/DG58ojA/4v9EfulgF+osuSQmlbNb3rcKX58qutm7JcldSeVLgGFha9g==", + "license": "MIT" }, - "node_modules/@unrs/resolver-binding-freebsd-x64": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.12.2.tgz", - "integrity": "sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@react-native/virtualized-lists": { + "version": "0.86.0", + "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.86.0.tgz", + "integrity": "sha512-4/ZLXdf/OSpPDVO0AsQ1SJdRIzt5t9BNQ46QwGgxvX7/cirYR5k8KXctNGGgW8lQo2gZChEfY2zFCZg9nM/jiw==", "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] + "dependencies": { + "invariant": "^2.2.4", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + }, + "peerDependencies": { + "@types/react": "^19.2.0", + "react": "*", + "react-native": "0.86.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } }, - "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.12.2.tgz", - "integrity": "sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==", - "cpu": [ - "arm" - ], + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "license": "MIT" }, - "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.12.2.tgz", - "integrity": "sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==", - "cpu": [ - "arm" - ], + "node_modules/@sinclair/typebox": { + "version": "0.27.12", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.12.tgz", + "integrity": "sha512-hhyNJ+nbR6ZR7pToHvllEFun9TL0sbL+tk/ON75lo+Xas054uez98qRbsuNt7MBCyZKK4+8Yli/OAGZhmfBZ/g==", + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "devOptional": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "devOptional": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@testing-library/react-native": { + "version": "13.3.3", + "resolved": "https://registry.npmjs.org/@testing-library/react-native/-/react-native-13.3.3.tgz", + "integrity": "sha512-k6Mjsd9dbZgvY4Bl7P1NIpePQNi+dfYtlJ5voi9KQlynxSyQkfOgJmYGCYmw/aSgH/rUcFvG8u5gd4npzgRDyg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "jest-matcher-utils": "^30.0.5", + "picocolors": "^1.1.1", + "pretty-format": "^30.0.5", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "jest": ">=29.0.0", + "react": ">=18.2.0", + "react-native": ">=0.71", + "react-test-renderer": ">=18.2.0" + }, + "peerDependenciesMeta": { + "jest": { + "optional": true + } + } }, - "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.12.2.tgz", - "integrity": "sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==", - "cpu": [ - "arm64" - ], + "node_modules/@testing-library/react-native/node_modules/@jest/schemas": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", + "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } }, - "node_modules/@unrs/resolver-binding-linux-arm64-musl": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.12.2.tgz", - "integrity": "sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==", - "cpu": [ - "arm64" - ], + "node_modules/@testing-library/react-native/node_modules/@sinclair/typebox": { + "version": "0.34.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "license": "MIT" }, - "node_modules/@unrs/resolver-binding-linux-loong64-gnu": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-gnu/-/resolver-binding-linux-loong64-gnu-1.12.2.tgz", - "integrity": "sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==", - "cpu": [ - "loong64" - ], + "node_modules/@testing-library/react-native/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } }, - "node_modules/@unrs/resolver-binding-linux-loong64-musl": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-musl/-/resolver-binding-linux-loong64-musl-1.12.2.tgz", - "integrity": "sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==", - "cpu": [ - "loong64" - ], + "node_modules/@testing-library/react-native/node_modules/pretty-format": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@jest/schemas": "30.4.1", + "ansi-styles": "^5.2.0", + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } }, - "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.12.2.tgz", - "integrity": "sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==", - "cpu": [ - "ppc64" - ], + "node_modules/@tootallnate/once": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.1.tgz", + "integrity": "sha512-HqmEUIGRJ5fSXchkVgR5F7qn48bDBzv0kWj/Kfu5e6uci4UlEeng4331LnBkWffb++Ei3FOVLxo8JJWMFBDMeQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": ">= 10" + } }, - "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.12.2.tgz", - "integrity": "sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==", - "cpu": [ - "riscv64" - ], + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "tslib": "^2.4.0" + } }, - "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.12.2.tgz", - "integrity": "sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==", - "cpu": [ - "riscv64" - ], - "dev": true, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "devOptional": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } }, - "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.12.2.tgz", - "integrity": "sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==", - "cpu": [ - "s390x" - ], - "dev": true, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "devOptional": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@babel/types": "^7.0.0" + } }, - "node_modules/@unrs/resolver-binding-linux-x64-gnu": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.12.2.tgz", - "integrity": "sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "devOptional": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } }, - "node_modules/@unrs/resolver-binding-linux-x64-musl": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.12.2.tgz", - "integrity": "sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "devOptional": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-openharmony-arm64": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-openharmony-arm64/-/resolver-binding-openharmony-arm64-1.12.2.tgz", - "integrity": "sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@unrs/resolver-binding-wasm32-wasi": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.12.2.tgz", - "integrity": "sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, "dependencies": { - "@emnapi/core": "1.10.0", - "@emnapi/runtime": "1.10.0", - "@napi-rs/wasm-runtime": "^1.1.4" - }, - "engines": { - "node": ">=14.0.0" + "@babel/types": "^7.28.2" } }, - "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.12.2.tgz", - "integrity": "sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==", - "cpu": [ - "arm64" - ], + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "license": "MIT" }, - "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.12.2.tgz", - "integrity": "sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==", - "cpu": [ - "ia32" - ], - "dev": true, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "devOptional": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "dependencies": { + "@types/node": "*" + } }, - "node_modules/@unrs/resolver-binding-win32-x64-msvc": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.12.2.tgz", - "integrity": "sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "license": "MIT" }, - "node_modules/@xmldom/xmldom": { - "version": "0.8.13", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.13.tgz", - "integrity": "sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==", + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", "license": "MIT", - "engines": { - "node": ">=10.0.0" + "dependencies": { + "@types/istanbul-lib-coverage": "*" } }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", "license": "MIT", "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" + "@types/istanbul-lib-report": "*" } }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "node_modules/@types/jest": { + "version": "29.5.14", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", + "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", + "dev": true, "license": "MIT", "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" + "expect": "^29.0.0", + "pretty-format": "^29.0.0" } }, - "node_modules/acorn": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", - "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "node_modules/@types/jsdom": { + "version": "20.0.1", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz", + "integrity": "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==", + "dev": true, "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" + "dependencies": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" } }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } + "license": "MIT" }, - "node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "26.1.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.1.tgz", + "integrity": "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==", "license": "MIT", - "engines": { - "node": ">= 14" + "dependencies": { + "undici-types": "~8.3.0" } }, - "node_modules/agent-cli-detector": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/agent-cli-detector/-/agent-cli-detector-0.1.4.tgz", - "integrity": "sha512-qPgevFvpaQoBaRJVKzr8R7h1WPvV3DtbgRIQlne4le66KBzXx5hNBwo/+NTw67LgkKBlhCzksrdautpUdlls0Q==", + "node_modules/@types/react": { + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "devOptional": true, "license": "MIT", - "bin": { - "agent-cli-detector": "dist/cli.js" - }, - "engines": { - "node": ">=18.18" + "dependencies": { + "csstype": "^3.2.2" } }, - "node_modules/ajv": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", - "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", "dev": true, + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "@types/yargs-parser": "*" } }, - "node_modules/anser": { - "version": "1.4.10", - "resolved": "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz", - "integrity": "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==", + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", "license": "MIT" }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.64.0.tgz", + "integrity": "sha512-CGvQPBxN3wZLu6Rz2kFUpZeoCm78xUic92ck39KPePkO1NPOwjCqdQnm5Q87tpWw9vcBvW8XLrDXjH9PWYtJ3Q==", + "dev": true, "license": "MIT", "dependencies": { - "type-fest": "^0.21.3" + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.64.0", + "@typescript-eslint/type-utils": "8.64.0", + "@typescript-eslint/utils": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" }, "engines": { - "node": ">=8" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.64.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "license": "(MIT OR CC0-1.0)", + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 4" } }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@typescript-eslint/parser": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.64.0.tgz", + "integrity": "sha512-KA0OshtlcCCXmbfqyZkM5pV3/WNraJf7DkJRLpyrmwPtud57H5BDX7C3k0LPSPxpprfRL+cJDGabF10mvNCoCw==", + "dev": true, "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "@typescript-eslint/scope-manager": "8.64.0", + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/typescript-estree": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.0", + "debug": "^4.4.3" }, "engines": { - "node": ">=8" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/arg": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", - "license": "MIT" - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0" - }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", - "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "node_modules/@typescript-eslint/project-service": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.64.0.tgz", + "integrity": "sha512-tk4WpOJ6IEbGrVHaNmM0YRrwAD3exZlIK3iadQNAxh4YKk6jvUQ4ecq18n+v7+meh+cJ3j+D8nbk8sRKhlwLQg==", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.3", - "is-array-buffer": "^3.0.5" + "@typescript-eslint/tsconfig-utils": "^8.64.0", + "@typescript-eslint/types": "^8.64.0", + "debug": "^4.4.3" }, "engines": { - "node": ">= 0.4" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/array-includes": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", - "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.64.0.tgz", + "integrity": "sha512-CXEaFdYXjSTgKhisNkwCcJwTP8Pl+fmRrEQrri4nm3vU743bALrxzLmq7fHG/7e6a5xO0lDYeURpZmBuhHk54w==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.24.0", - "es-object-atoms": "^1.1.1", - "get-intrinsic": "^1.3.0", - "is-string": "^1.1.1", - "math-intrinsics": "^1.1.0" + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.0" }, "engines": { - "node": ">= 0.4" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/array.prototype.findlast": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", - "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.64.0.tgz", + "integrity": "sha512-2yo8rRNKuzbVWQp5kslhANqZ2uDAeROQHBRZNPu8JDsHmeFNj/XJJhX/FhNUWmkHHvoNsKa6+tHJiig87EzsQw==", "dev": true, "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-shim-unscopables": "^1.0.2" - }, "engines": { - "node": ">= 0.4" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/array.prototype.findlastindex": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", - "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "node_modules/@typescript-eslint/type-utils": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.64.0.tgz", + "integrity": "sha512-XWG4Fmmv/6SvyS9nH8jWrKs6terwJvE8cyRt1CzYYqzp9OrPhCT4cMc/f7C6RZCwG+qMmiffJS1/qJP8G1URtg==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "es-shim-unscopables": "^1.1.0" + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/typescript-estree": "8.64.0", + "@typescript-eslint/utils": "8.64.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" }, "engines": { - "node": ">= 0.4" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/array.prototype.flat": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", - "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "node_modules/@typescript-eslint/types": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.64.0.tgz", + "integrity": "sha512-qjhfuTfLXjA4IOzXvz0rTjT01BqEiIgPoUeMwiEjnaHKJMTNo8rH5pYW1a2L/0Dnux2fPC85AeyJoWaGa8WxTA==", "dev": true, "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-shim-unscopables": "^1.0.2" - }, "engines": { - "node": ">= 0.4" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/array.prototype.flatmap": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", - "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.64.0.tgz", + "integrity": "sha512-Pztpsn1aCE1oWDvDEfUk31nngvvF7vUB5SwHFEaZIFpvw7WJtqUHHL4plBZDA9HfWJJjL13BdG0YrJInTUvoVA==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-shim-unscopables": "^1.0.2" + "@typescript-eslint/project-service": "8.64.0", + "@typescript-eslint/tsconfig-utils": "8.64.0", + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" }, "engines": { - "node": ">= 0.4" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/array.prototype.tosorted": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", - "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "node_modules/@typescript-eslint/utils": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.64.0.tgz", + "integrity": "sha512-aJUGVB3+U0htrrCjoA8qukw8cm8fNCGAxK/tVoS70k8aeb7DETKeFozRiVFIwEeN9WJLsjaP3ph8I60tY2XZoQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.3", - "es-errors": "^1.3.0", - "es-shim-unscopables": "^1.0.2" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.64.0", + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/typescript-estree": "8.64.0" }, "engines": { - "node": ">= 0.4" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", - "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.64.0.tgz", + "integrity": "sha512-mrtuL8Nsn6gi2H4mo5KMTp823M+3Q19Ew/i+Zlikq20tIMm99C3Ez0dCmkWWnxut20esQvTg8aUSEhMcAOXhEw==", "dev": true, "license": "MIT", "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "is-array-buffer": "^3.0.4" + "@typescript-eslint/types": "8.64.0", + "eslint-visitor-keys": "^5.0.0" }, "engines": { - "node": ">= 0.4" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "license": "MIT" + "node_modules/@ungap/structured-clone": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.3.tgz", + "integrity": "sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==", + "license": "ISC" }, - "node_modules/async-function": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", - "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.12.2.tgz", + "integrity": "sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.12.2.tgz", + "integrity": "sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.12.2.tgz", + "integrity": "sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.12.2.tgz", + "integrity": "sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.12.2.tgz", + "integrity": "sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.12.2.tgz", + "integrity": "sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.12.2.tgz", + "integrity": "sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.12.2.tgz", + "integrity": "sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.12.2.tgz", + "integrity": "sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-gnu/-/resolver-binding-linux-loong64-gnu-1.12.2.tgz", + "integrity": "sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-musl/-/resolver-binding-linux-loong64-musl-1.12.2.tgz", + "integrity": "sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.12.2.tgz", + "integrity": "sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.12.2.tgz", + "integrity": "sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.12.2.tgz", + "integrity": "sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.12.2.tgz", + "integrity": "sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.12.2.tgz", + "integrity": "sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.12.2.tgz", + "integrity": "sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-openharmony-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-openharmony-arm64/-/resolver-binding-openharmony-arm64-1.12.2.tgz", + "integrity": "sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.12.2.tgz", + "integrity": "sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.12.2.tgz", + "integrity": "sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.12.2.tgz", + "integrity": "sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.12.2.tgz", + "integrity": "sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.13", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.13.tgz", + "integrity": "sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "deprecated": "Use your platform's native atob() and btoa() methods instead", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-globals": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", + "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.1.0", + "acorn-walk": "^8.0.2" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/agent-cli-detector": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/agent-cli-detector/-/agent-cli-detector-0.1.4.tgz", + "integrity": "sha512-qPgevFvpaQoBaRJVKzr8R7h1WPvV3DtbgRIQlne4le66KBzXx5hNBwo/+NTw67LgkKBlhCzksrdautpUdlls0Q==", + "license": "MIT", + "bin": { + "agent-cli-detector": "dist/cli.js" + }, + "engines": { + "node": ">=18.18" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/anser": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz", + "integrity": "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==", + "license": "MIT" + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "license": "MIT" + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "devOptional": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz", + "integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-define-polyfill-provider": "^0.6.8", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", + "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.5", + "core-js-compat": "^3.43.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz", + "integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==", + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.8" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-react-compiler": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-1.0.0.tgz", + "integrity": "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.26.0" + } + }, + "node_modules/babel-plugin-react-native-web": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/babel-plugin-react-native-web/-/babel-plugin-react-native-web-0.21.2.tgz", + "integrity": "sha512-SPD0J6qjJn8231i0HZhlAGH6NORe+QvRSQM2mwQEzJ2Fb3E4ruWTiiicPlHjmeWShDXLcvoorOCXjeR7k/lyWA==", + "license": "MIT" + }, + "node_modules/babel-plugin-syntax-hermes-parser": { + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.36.0.tgz", + "integrity": "sha512-LhD0xdoedDw7ansQgXbB2DADLZIK/LRXuWNBPuVzMc5S2WK5GyT89tCM+cQzxFGO0mGyLK6D5TrVOJJzAoDy8Q==", + "license": "MIT", + "dependencies": { + "hermes-parser": "0.36.0" + } + }, + "node_modules/babel-plugin-syntax-hermes-parser/node_modules/hermes-estree": { + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.36.0.tgz", + "integrity": "sha512-A1+8zn5oss2CFP7pKsOaxorQG6FNIz1WU1VDqruLPPZl3LVgeE2C5xfFg8Ow6/Ow4mSslLLtYP1J3n38eKyW9w==", + "license": "MIT" + }, + "node_modules/babel-plugin-syntax-hermes-parser/node_modules/hermes-parser": { + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.36.0.tgz", + "integrity": "sha512-GdpwMmH5x6IpC1cijvcvYnlPB60Mh6kTSF/NFdYV/j56gYdi+0RIakYs+eqOV+bbO0SW7mgVVGSsTJxyPQfo3w==", + "license": "MIT", + "dependencies": { + "hermes-estree": "0.36.0" + } + }, + "node_modules/babel-plugin-transform-flow-enums": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-enums/-/babel-plugin-transform-flow-enums-0.0.2.tgz", + "integrity": "sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ==", + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-flow": "^7.12.1" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/babel-preset-expo": { + "version": "57.0.4", + "resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-57.0.4.tgz", + "integrity": "sha512-EkFcNoE23HVzQT6ZNXs/adN8+G7rqEAF6tQn6LpRPYa1YY7wmX5GxhJF0kaYMNtBndNrMTN4+0rYE17VG13KFg==", + "license": "MIT", + "dependencies": { + "@babel/generator": "^7.20.5", + "@babel/helper-module-imports": "^7.25.9", + "@babel/plugin-proposal-decorators": "^7.12.9", + "@babel/plugin-proposal-export-default-from": "^7.24.7", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-default-from": "^7.24.7", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-transform-async-generator-functions": "^7.25.4", + "@babel/plugin-transform-async-to-generator": "^7.24.7", + "@babel/plugin-transform-block-scoping": "^7.25.0", + "@babel/plugin-transform-class-properties": "^7.25.4", + "@babel/plugin-transform-class-static-block": "^7.27.1", + "@babel/plugin-transform-classes": "^7.25.4", + "@babel/plugin-transform-destructuring": "^7.24.8", + "@babel/plugin-transform-export-namespace-from": "^7.25.9", + "@babel/plugin-transform-flow-strip-types": "^7.25.2", + "@babel/plugin-transform-for-of": "^7.24.7", + "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.8", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-object-rest-spread": "^7.24.7", + "@babel/plugin-transform-optional-catch-binding": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.8", + "@babel/plugin-transform-parameters": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "@babel/plugin-transform-react-display-name": "^7.24.7", + "@babel/plugin-transform-react-jsx": "^7.28.6", + "@babel/plugin-transform-react-jsx-development": "^7.27.1", + "@babel/plugin-transform-react-pure-annotations": "^7.27.1", + "@babel/plugin-transform-runtime": "^7.24.7", + "@babel/plugin-transform-typescript": "^7.25.2", + "@babel/plugin-transform-unicode-regex": "^7.24.7", + "@babel/preset-typescript": "^7.23.0", + "@react-native/babel-plugin-codegen": "0.86.0", + "babel-plugin-react-compiler": "^1.0.0", + "babel-plugin-react-native-web": "~0.21.0", + "babel-plugin-syntax-hermes-parser": "^0.36.0", + "babel-plugin-transform-flow-enums": "^0.0.2", + "debug": "^4.3.4" + }, + "peerDependencies": { + "@babel/runtime": "^7.20.0", + "expo": "*", + "expo-widgets": "^57.0.6", + "react-refresh": ">=0.14.0 <1.0.0" + }, + "peerDependenciesMeta": { + "@babel/runtime": { + "optional": true + }, + "expo": { + "optional": true + }, + "expo-widgets": { + "optional": true + } + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.43", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.43.tgz", + "integrity": "sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/big-integer": { + "version": "1.6.52", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", + "license": "Unlicense", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "node_modules/bplist-creator": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.1.0.tgz", + "integrity": "sha512-sXaHZicyEEmY86WyueLTQesbeoH/mquvarJaQNbjuOQO+7gbFcDEWqKmcWA4cOTLzFlfgvkiVxolk1k5bBIpmg==", + "license": "MIT", + "dependencies": { + "stream-buffers": "2.2.x" + } + }, + "node_modules/bplist-parser": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.3.1.tgz", + "integrity": "sha512-PyJxiNtA5T2PlLIeBot4lbp7rj4OadzjnMZD/G5zuBNt8ei/yCU7+wW0h2bag9vr8c+/WuRWmSxbqAl9hL1rBA==", + "license": "MIT", + "dependencies": { + "big-integer": "1.6.x" + }, + "engines": { + "node": ">= 5.10.0" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz", + "integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.6.tgz", + "integrity": "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.42", + "caniuse-lite": "^1.0.30001803", + "electron-to-chromium": "^1.5.389", + "node-releases": "^2.0.51", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001806", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz", + "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/chrome-launcher": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", + "integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==", + "license": "Apache-2.0", + "dependencies": { + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0" + }, + "bin": { + "print-chrome-path": "bin/print-chrome-path.js" + }, + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/chromium-edge-launcher": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/chromium-edge-launcher/-/chromium-edge-launcher-0.3.0.tgz", + "integrity": "sha512-p03azHlGjtyRvFEee3cyvtsRYdniSkwjkzmM/KmVnqT5d7QkkwpJBhis/zCLMYdQMVJ5tt140TBNqqrZPaWeFA==", + "license": "Apache-2.0", + "dependencies": { + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0", + "mkdirp": "^1.0.4" + } + }, + "node_modules/ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "license": "MIT" + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==", + "license": "MIT", + "dependencies": { + "restore-cursor": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/compression/node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/connect/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/connect/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, + "node_modules/core-js-compat": { + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.49.0.tgz", + "integrity": "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "node_modules/cross-fetch": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz", + "integrity": "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==", + "license": "MIT", + "dependencies": { + "node-fetch": "^2.7.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-in-js-utils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-3.1.0.tgz", + "integrity": "sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A==", + "license": "MIT", + "dependencies": { + "hyphenate-style-name": "^1.0.3" + } + }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/css-tree/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/data-urls": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", + "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/data-urls/node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/data-urls/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/data-urls/node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/dedent": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dev": true, "license": "MIT", "dependencies": { - "possible-typed-array-names": "^1.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -3164,369 +5227,658 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.17", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz", - "integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==", + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-define-polyfill-provider": "^0.6.8", - "semver": "^6.3.1" + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" } }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", - "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dnssd-advertise": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/dnssd-advertise/-/dnssd-advertise-1.1.6.tgz", + "integrity": "sha512-Ndrrf6BMPalkQPd/zubL+4YghH2J9NspapQ09uDXwYbvOPkP0oaqf5CkcwJ0b50kS2O3ul6yVu+jz+RY62Cejg==", + "license": "MIT" + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.5", - "core-js-compat": "^3.43.0" + "esutils": "^2.0.2" }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz", - "integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==", + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.8" + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" } }, - "node_modules/babel-plugin-react-compiler": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-1.0.0.tgz", - "integrity": "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==", + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "deprecated": "Use your platform's native DOMException instead", + "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.26.0" + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" } }, - "node_modules/babel-plugin-react-native-web": { - "version": "0.21.2", - "resolved": "https://registry.npmjs.org/babel-plugin-react-native-web/-/babel-plugin-react-native-web-0.21.2.tgz", - "integrity": "sha512-SPD0J6qjJn8231i0HZhlAGH6NORe+QvRSQM2mwQEzJ2Fb3E4ruWTiiicPlHjmeWShDXLcvoorOCXjeR7k/lyWA==", - "license": "MIT" + "node_modules/domexception/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } }, - "node_modules/babel-plugin-syntax-hermes-parser": { - "version": "0.36.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.36.0.tgz", - "integrity": "sha512-LhD0xdoedDw7ansQgXbB2DADLZIK/LRXuWNBPuVzMc5S2WK5GyT89tCM+cQzxFGO0mGyLK6D5TrVOJJzAoDy8Q==", + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, "license": "MIT", "dependencies": { - "hermes-parser": "0.36.0" + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" } }, - "node_modules/babel-plugin-syntax-hermes-parser/node_modules/hermes-estree": { - "version": "0.36.0", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.36.0.tgz", - "integrity": "sha512-A1+8zn5oss2CFP7pKsOaxorQG6FNIz1WU1VDqruLPPZl3LVgeE2C5xfFg8Ow6/Ow4mSslLLtYP1J3n38eKyW9w==", + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, - "node_modules/babel-plugin-syntax-hermes-parser/node_modules/hermes-parser": { - "version": "0.36.0", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.36.0.tgz", - "integrity": "sha512-GdpwMmH5x6IpC1cijvcvYnlPB60Mh6kTSF/NFdYV/j56gYdi+0RIakYs+eqOV+bbO0SW7mgVVGSsTJxyPQfo3w==", + "node_modules/electron-to-chromium": { + "version": "1.5.392", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.392.tgz", + "integrity": "sha512-1yQq3VQCZRwsnYc67Oc+1fge6Lwtn0hzi6zmEVkB61Zx21kTbwJAW4dFLadl5Rc1tKhG/kSpYXnfiAhu0f0a1g==", + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, "license": "MIT", "dependencies": { - "hermes-estree": "0.36.0" + "is-arrayish": "^0.2.1" } }, - "node_modules/babel-plugin-transform-flow-enums": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-enums/-/babel-plugin-transform-flow-enums-0.0.2.tgz", - "integrity": "sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ==", + "node_modules/error-stack-parser": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", "license": "MIT", "dependencies": { - "@babel/plugin-syntax-flow": "^7.12.1" + "stackframe": "^1.3.4" } }, - "node_modules/babel-preset-expo": { - "version": "57.0.4", - "resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-57.0.4.tgz", - "integrity": "sha512-EkFcNoE23HVzQT6ZNXs/adN8+G7rqEAF6tQn6LpRPYa1YY7wmX5GxhJF0kaYMNtBndNrMTN4+0rYE17VG13KFg==", + "node_modules/es-abstract": { + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/generator": "^7.20.5", - "@babel/helper-module-imports": "^7.25.9", - "@babel/plugin-proposal-decorators": "^7.12.9", - "@babel/plugin-proposal-export-default-from": "^7.24.7", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-default-from": "^7.24.7", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-transform-async-generator-functions": "^7.25.4", - "@babel/plugin-transform-async-to-generator": "^7.24.7", - "@babel/plugin-transform-block-scoping": "^7.25.0", - "@babel/plugin-transform-class-properties": "^7.25.4", - "@babel/plugin-transform-class-static-block": "^7.27.1", - "@babel/plugin-transform-classes": "^7.25.4", - "@babel/plugin-transform-destructuring": "^7.24.8", - "@babel/plugin-transform-export-namespace-from": "^7.25.9", - "@babel/plugin-transform-flow-strip-types": "^7.25.2", - "@babel/plugin-transform-for-of": "^7.24.7", - "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", - "@babel/plugin-transform-modules-commonjs": "^7.24.8", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", - "@babel/plugin-transform-object-rest-spread": "^7.24.7", - "@babel/plugin-transform-optional-catch-binding": "^7.24.7", - "@babel/plugin-transform-optional-chaining": "^7.24.8", - "@babel/plugin-transform-parameters": "^7.24.7", - "@babel/plugin-transform-private-methods": "^7.24.7", - "@babel/plugin-transform-private-property-in-object": "^7.24.7", - "@babel/plugin-transform-react-display-name": "^7.24.7", - "@babel/plugin-transform-react-jsx": "^7.28.6", - "@babel/plugin-transform-react-jsx-development": "^7.27.1", - "@babel/plugin-transform-react-pure-annotations": "^7.27.1", - "@babel/plugin-transform-runtime": "^7.24.7", - "@babel/plugin-transform-typescript": "^7.25.2", - "@babel/plugin-transform-unicode-regex": "^7.24.7", - "@babel/preset-typescript": "^7.23.0", - "@react-native/babel-plugin-codegen": "0.86.0", - "babel-plugin-react-compiler": "^1.0.0", - "babel-plugin-react-native-web": "~0.21.0", - "babel-plugin-syntax-hermes-parser": "^0.36.0", - "babel-plugin-transform-flow-enums": "^0.0.2", - "debug": "^4.3.4" + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" }, - "peerDependencies": { - "@babel/runtime": "^7.20.0", - "expo": "*", - "expo-widgets": "^57.0.6", - "react-refresh": ">=0.14.0 <1.0.0" + "engines": { + "node": ">= 0.4" }, - "peerDependenciesMeta": { - "@babel/runtime": { - "optional": true - }, - "expo": { - "optional": true - }, - "expo-widgets": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "node_modules/es-abstract-get": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-abstract-get/-/es-abstract-get-1.0.0.tgz", + "integrity": "sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==", + "dev": true, "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.2", + "is-callable": "^1.2.7", + "object-inspect": "^1.13.4" + }, "engines": { - "node": "18 || 20 || >=22" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/baseline-browser-mapping": { - "version": "2.10.43", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.43.tgz", - "integrity": "sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==", - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=6.0.0" + "node": ">= 0.4" } }, - "node_modules/big-integer": { - "version": "1.6.52", - "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", - "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", - "license": "Unlicense", + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", "engines": { - "node": ">=0.6" + "node": ">= 0.4" } }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "license": "ISC" + "node_modules/es-iterator-helpers": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.4.0.tgz", + "integrity": "sha512-c/A0P0oxkACDc+cKWw8evLXK83oBKgn0qPOqCYT4x9uolpCIJAcYvJC9QYKNDRPsTeGyCrQ326jrvgZWdCdK5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.1.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.3.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.5", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } }, - "node_modules/bplist-creator": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.1.0.tgz", - "integrity": "sha512-sXaHZicyEEmY86WyueLTQesbeoH/mquvarJaQNbjuOQO+7gbFcDEWqKmcWA4cOTLzFlfgvkiVxolk1k5bBIpmg==", + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dev": true, "license": "MIT", "dependencies": { - "stream-buffers": "2.2.x" + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" } }, - "node_modules/bplist-parser": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.3.1.tgz", - "integrity": "sha512-PyJxiNtA5T2PlLIeBot4lbp7rj4OadzjnMZD/G5zuBNt8ei/yCU7+wW0h2bag9vr8c+/WuRWmSxbqAl9hL1rBA==", + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, "license": "MIT", "dependencies": { - "big-integer": "1.6.x" + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { - "node": ">= 5.10.0" + "node": ">= 0.4" } }, - "node_modules/brace-expansion": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz", - "integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==", + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^4.0.2" + "hasown": "^2.0.2" }, "engines": { - "node": "20 || >=22" + "node": ">= 0.4" } }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "node_modules/es-to-primitive": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.4.tgz", + "integrity": "sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==", + "dev": true, "license": "MIT", "dependencies": { - "fill-range": "^7.1.1" + "es-abstract-get": "^1.0.0", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "is-callable": "^1.2.7", + "is-date-object": "^1.1.0", + "is-symbol": "^1.1.1" }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/browserslist": { - "version": "4.28.6", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.6.tgz", - "integrity": "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "baseline-browser-mapping": "^2.10.42", - "caniuse-lite": "^1.0.30001803", - "electron-to-chromium": "^1.5.389", - "node-releases": "^2.0.51", - "update-browserslist-db": "^1.2.3" + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" }, "bin": { - "browserslist": "cli.js" + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" }, "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" } }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "license": "Apache-2.0", - "dependencies": { - "node-int64": "^0.4.0" + "node_modules/escodegen/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" } }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "license": "MIT" - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "node_modules/eslint": { + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.5.tgz", + "integrity": "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==", + "dev": true, "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.6", + "@eslint/js": "9.39.5", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, "engines": { - "node": ">= 0.8" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, - "node_modules/call-bind": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", - "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "node_modules/eslint-config-expo": { + "version": "57.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-expo/-/eslint-config-expo-57.0.0.tgz", + "integrity": "sha512-T7OTN9xrSZYjLw4qTkL1Mn2WfAUVmMGY38+OYAcraI1uiTFVH6jfkSkv84WLTqnneblLcb6AsMDV+SHcUj3hGw==", "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "get-intrinsic": "^1.3.0", - "set-function-length": "^1.2.2" + "@typescript-eslint/eslint-plugin": "^8.59.0", + "@typescript-eslint/parser": "^8.59.0", + "eslint-import-resolver-typescript": "^3.6.3", + "eslint-plugin-expo": "^1.1.0", + "eslint-plugin-import": "^2.30.0", + "eslint-plugin-react": "^7.37.3", + "eslint-plugin-react-hooks": "^7.0.0", + "globals": "^16.0.0" }, - "engines": { - "node": ">= 0.4" + "peerDependencies": { + "eslint": ">=8.10" + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" } }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "node_modules/eslint-import-resolver-node": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.10.tgz", + "integrity": "sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==", "dev": true, "license": "MIT", "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" + "debug": "^3.2.7", + "is-core-module": "^2.16.1", + "resolve": "^2.0.0-next.6" } }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/resolve": { + "version": "2.0.0-next.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.7.tgz", + "integrity": "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" + "es-errors": "^1.3.0", + "is-core-module": "^2.16.2", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" }, "engines": { "node": ">= 0.4" @@ -3535,1281 +5887,1274 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "node_modules/eslint-import-resolver-typescript": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz", + "integrity": "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "license": "MIT", + "license": "ISC", + "dependencies": { + "@nolyfill/is-core-module": "1.0.39", + "debug": "^4.4.0", + "get-tsconfig": "^4.10.0", + "is-bun-module": "^2.0.0", + "stable-hash": "^0.0.5", + "tinyglobby": "^0.2.13", + "unrs-resolver": "^1.6.2" + }, "engines": { - "node": ">=10" + "node": "^14.18.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001806", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz", - "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + "url": "https://opencollective.com/eslint-import-resolver-typescript" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" + "eslint-plugin-import-x": { + "optional": true } - ], - "license": "CC-BY-4.0" + } }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/eslint-module-utils": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.14.0.tgz", + "integrity": "sha512-W2WCRZ9Dqntd+2u8jJcVMV2PKulc6RdLgUUoh/yQr3uB6lo/ZOeGx11sv60/8S4QFFKNslAlWhr9u0Ef7ZW6Ig==", + "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "debug": "^3.2.7" }, "engines": { - "node": ">=10" + "node": ">=4" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "peerDependenciesMeta": { + "eslint": { + "optional": true + } } }, - "node_modules/chrome-launcher": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", - "integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==", - "license": "Apache-2.0", + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", "dependencies": { - "@types/node": "*", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^1.0.0" - }, - "bin": { - "print-chrome-path": "bin/print-chrome-path.js" - }, - "engines": { - "node": ">=12.13.0" + "ms": "^2.1.1" } }, - "node_modules/chromium-edge-launcher": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/chromium-edge-launcher/-/chromium-edge-launcher-0.3.0.tgz", - "integrity": "sha512-p03azHlGjtyRvFEee3cyvtsRYdniSkwjkzmM/KmVnqT5d7QkkwpJBhis/zCLMYdQMVJ5tt140TBNqqrZPaWeFA==", - "license": "Apache-2.0", + "node_modules/eslint-plugin-expo": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-expo/-/eslint-plugin-expo-1.1.0.tgz", + "integrity": "sha512-vPP0EPx7IA7ZfP49dY4rq9RV5jqkFWG+Pih3/oGjzIRjMI+ogcOE8i6isYkLXAdw/yvFV2BRZkTaQaiOGQqn6Q==", + "dev": true, + "license": "MIT", "dependencies": { - "@types/node": "*", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^1.0.0", - "mkdirp": "^1.0.4" + "@typescript-eslint/types": "^8.59.0", + "@typescript-eslint/utils": "^8.59.0", + "eslint": "^9.24.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "eslint": ">=8.10" } }, - "node_modules/ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "license": "MIT" - }, - "node_modules/cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==", + "node_modules/eslint-plugin-import": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "dev": true, "license": "MIT", "dependencies": { - "restore-cursor": "^2.0.0" + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.1", + "hasown": "^2.0.2", + "is-core-module": "^2.16.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.1", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.9", + "tsconfig-paths": "^3.15.0" }, "engines": { "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" } }, - "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "dependencies": { + "ms": "^2.1.1" } }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, "license": "ISC", "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=12" + "node": "*" } }, - "node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "dev": true, "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, "engines": { - "node": ">=0.8" + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" } }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/eslint-plugin-react-hooks": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", + "dev": true, "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" }, "engines": { - "node": ">=7.0.0" + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" } }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "node_modules/eslint-plugin-react-hooks/node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, "license": "MIT" }, - "node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "node_modules/eslint-plugin-react-hooks/node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, "license": "MIT", - "engines": { - "node": ">= 10" + "dependencies": { + "hermes-estree": "0.25.1" } }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "license": "MIT", + "node_modules/eslint-plugin-react/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", "dependencies": { - "mime-db": ">= 1.43.0 < 2" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">= 0.6" + "node": "*" } }, - "node_modules/compression": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", - "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.7.tgz", + "integrity": "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==", + "dev": true, "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "compressible": "~2.0.18", - "debug": "2.6.9", - "negotiator": "~0.6.4", - "on-headers": "~1.1.0", - "safe-buffer": "5.2.1", - "vary": "~1.1.2" + "es-errors": "^1.3.0", + "is-core-module": "^2.16.2", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" }, "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/compression/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/compression/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/compression/node_modules/negotiator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", - "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/connect": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", - "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", - "license": "MIT", + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "debug": "2.6.9", - "finalhandler": "1.1.2", - "parseurl": "~1.3.3", - "utils-merge": "1.0.1" + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" }, "engines": { - "node": ">= 0.10.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/connect/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/connect/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "license": "MIT" - }, - "node_modules/core-js-compat": { - "version": "3.49.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.49.0.tgz", - "integrity": "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.28.1" + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" + "url": "https://opencollective.com/eslint" } }, - "node_modules/cross-fetch": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz", - "integrity": "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==", - "license": "MIT", + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", "dependencies": { - "node-fetch": "^2.7.0" + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" } }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "license": "MIT", + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" }, "engines": { - "node": ">= 8" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/css-in-js-utils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-3.1.0.tgz", - "integrity": "sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A==", - "license": "MIT", - "dependencies": { - "hyphenate-style-name": "^1.0.3" + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/css-select": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", - "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "devOptional": true, "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" + "estraverse": "^5.1.0" }, - "funding": { - "url": "https://github.com/sponsors/fb55" + "engines": { + "node": ">=0.10" } }, - "node_modules/css-tree": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", - "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", - "license": "MIT", + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "mdn-data": "2.0.14", - "source-map": "^0.6.1" + "estraverse": "^5.2.0" }, "engines": { - "node": ">=8.0.0" + "node": ">=4.0" } }, - "node_modules/css-tree/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", "engines": { - "node": ">=0.10.0" + "node": ">=4.0" } }, - "node_modules/css-what": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", - "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, "license": "BSD-2-Clause", "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" + "node": ">=0.10.0" } }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "devOptional": true, - "license": "MIT" + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } }, - "node_modules/data-view-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", - "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", - "dev": true, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=6" } }, - "node_modules/data-view-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", - "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/inspect-js" + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/data-view-byte-offset": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", - "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "node_modules/execa/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true, "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=6" } }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/execa/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "mimic-fn": "^2.1.0" }, "engines": { - "node": ">=6.0" + "node": ">=6" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", "dev": true, - "license": "MIT" - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 0.8.0" } }, - "node_modules/defaults": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", - "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, "license": "MIT", "dependencies": { - "clone": "^1.0.2" + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "node_modules/expect/node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", "dev": true, "license": "MIT", "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "node_modules/expect/node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", "dev": true, "license": "MIT", "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", "engines": { - "node": ">= 0.8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "node_modules/expo": { + "version": "57.0.8", + "resolved": "https://registry.npmjs.org/expo/-/expo-57.0.8.tgz", + "integrity": "sha512-0IxxoPZbT54IH4fHL5NihkvED9HBVQx3uNdPvyv8pFUHWJ81RdFjL0aJys1IB7hbo09KTz4xW4I2aWVOXKAcJQ==", "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/dnssd-advertise": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/dnssd-advertise/-/dnssd-advertise-1.1.6.tgz", - "integrity": "sha512-Ndrrf6BMPalkQPd/zubL+4YghH2J9NspapQ09uDXwYbvOPkP0oaqf5CkcwJ0b50kS2O3ul6yVu+jz+RY62Cejg==", - "license": "MIT" - }, - "node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "license": "Apache-2.0", "dependencies": { - "esutils": "^2.0.2" + "@babel/runtime": "^7.20.0", + "@expo/cli": "^57.0.10", + "@expo/config": "~57.0.6", + "@expo/config-plugins": "~57.0.6", + "@expo/devtools": "~57.0.1", + "@expo/dom-webview": "~57.0.1", + "@expo/fingerprint": "^0.20.6", + "@expo/local-build-cache-provider": "^57.0.4", + "@expo/log-box": "^57.0.1", + "@expo/metro": "~56.0.0", + "@expo/metro-config": "~57.0.7", + "@ungap/structured-clone": "^1.3.0", + "babel-preset-expo": "~57.0.4", + "expo-asset": "~57.0.7", + "expo-constants": "~57.0.7", + "expo-file-system": "~57.0.1", + "expo-font": "~57.0.1", + "expo-keep-awake": "~57.0.1", + "expo-modules-autolinking": "~57.0.9", + "expo-modules-core": "~57.0.7", + "pretty-format": "^29.7.0", + "react-refresh": "^0.14.2", + "whatwg-url-minimum": "^0.1.2" }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" + "bin": { + "expo": "bin/cli", + "expo-modules-autolinking": "bin/autolinking", + "fingerprint": "bin/fingerprint" }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" + "peerDependencies": { + "@expo/dom-webview": "*", + "@expo/metro-runtime": "*", + "react": "*", + "react-dom": "*", + "react-native": "*", + "react-native-web": "*", + "react-native-webview": "*" + }, + "peerDependenciesMeta": { + "@expo/dom-webview": { + "optional": true + }, + "@expo/metro-runtime": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native-web": { + "optional": true + }, + "react-native-webview": { + "optional": true } - ], - "license": "BSD-2-Clause" + } }, - "node_modules/domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "license": "BSD-2-Clause", + "node_modules/expo-asset": { + "version": "57.0.7", + "resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-57.0.7.tgz", + "integrity": "sha512-TA6MRQq1HL0N6KGvNMzL6djdH5tGJ/eHPIhML+6bVu52mnXldmup2swdvP37zDnvoMUUXRVGsvwVHFmvhCbW6Q==", + "license": "MIT", "dependencies": { - "domelementtype": "^2.3.0" - }, - "engines": { - "node": ">= 4" + "@expo/image-utils": "^0.11.4", + "expo-constants": "~57.0.7" }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*" } }, - "node_modules/domutils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", - "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", - "license": "BSD-2-Clause", + "node_modules/expo-constants": { + "version": "57.0.7", + "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-57.0.7.tgz", + "integrity": "sha512-ShDwaKnh3UieCQ/dG0kO8PuicTTatn3WDGmXbq/fukyzPXWhdUxf37DINVDQS6D3DDG7nfqItij+QvnDHSQhTg==", + "license": "MIT", "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" + "@expo/env": "~2.4.2" }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" + "peerDependencies": { + "expo": "*", + "react-native": "*" } }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, + "node_modules/expo-font": { + "version": "57.0.1", + "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-57.0.1.tgz", + "integrity": "sha512-QyS9L1Kh9sKJg4gfU6rdbpxpmH+DyzBX8z6jVvXMUDoqLr1GqmkO/Wu379KCXjL///kWbhpNlbi7AgBuj4VdIQ==", "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" + "fontfaceobserver": "^2.1.0" }, - "engines": { - "node": ">= 0.4" + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*" } }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/electron-to-chromium": { - "version": "1.5.392", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.392.tgz", - "integrity": "sha512-1yQq3VQCZRwsnYc67Oc+1fge6Lwtn0hzi6zmEVkB61Zx21kTbwJAW4dFLadl5Rc1tKhG/kSpYXnfiAhu0f0a1g==", - "license": "ISC" - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "node_modules/expo-image-loader": { + "version": "57.0.1", + "resolved": "https://registry.npmjs.org/expo-image-loader/-/expo-image-loader-57.0.1.tgz", + "integrity": "sha512-uhrZKLT/cTl2mXyR28kPpVkS5O+PK9N1QA/07IFM4f5T4g0lTW1JHT3NEWwEEsGFldPmVX4j7LwUVVZxE+woug==", "license": "MIT", - "engines": { - "node": ">= 0.8" + "peerDependencies": { + "expo": "*" } }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" + "node_modules/expo-image-picker": { + "version": "57.0.6", + "resolved": "https://registry.npmjs.org/expo-image-picker/-/expo-image-picker-57.0.6.tgz", + "integrity": "sha512-6Of7SzyFVC+WFuFxhD4+nRTQ9joqldPhbiVWRsumg4ybttKmY8GxrnXAUSIDfa9DSk9PhgIAy2C1c5y1+CnU3g==", + "license": "MIT", + "dependencies": { + "expo-image-loader": "~57.0.1" }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "peerDependencies": { + "expo": "*" } }, - "node_modules/error-stack-parser": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", - "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", + "node_modules/expo-modules-autolinking": { + "version": "57.0.9", + "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-57.0.9.tgz", + "integrity": "sha512-lj2nsAKMMRLXSFnGgaaQrWJ2fdSpLPc/bca6Rkiw4g8zYPn7qX4MRUJgavvzm/hBrzvMnlhXVgJtidOGuwBh+w==", "license": "MIT", "dependencies": { - "stackframe": "^1.3.4" + "@expo/require-utils": "^57.0.4", + "@expo/spawn-async": "^1.8.0", + "chalk": "^4.1.0", + "commander": "^7.2.0" + }, + "bin": { + "expo-modules-autolinking": "bin/expo-modules-autolinking.js" } }, - "node_modules/es-abstract": { - "version": "1.24.2", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", - "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", - "dev": true, + "node_modules/expo-modules-core": { + "version": "57.0.7", + "resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-57.0.7.tgz", + "integrity": "sha512-5HrbCfgYmLs0a5dzfM4GmRGelVTPIg+eYp0vmSbWnKRTOPj5DyVOfg1rHGEsuNKp07QwDxfLJfQVp8CNbuvxMQ==", "license": "MIT", "dependencies": { - "array-buffer-byte-length": "^1.0.2", - "arraybuffer.prototype.slice": "^1.0.4", - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "data-view-buffer": "^1.0.2", - "data-view-byte-length": "^1.0.2", - "data-view-byte-offset": "^1.0.1", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "es-set-tostringtag": "^2.1.0", - "es-to-primitive": "^1.3.0", - "function.prototype.name": "^1.1.8", - "get-intrinsic": "^1.3.0", - "get-proto": "^1.0.1", - "get-symbol-description": "^1.1.0", - "globalthis": "^1.0.4", - "gopd": "^1.2.0", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "internal-slot": "^1.1.0", - "is-array-buffer": "^3.0.5", - "is-callable": "^1.2.7", - "is-data-view": "^1.0.2", - "is-negative-zero": "^2.0.3", - "is-regex": "^1.2.1", - "is-set": "^2.0.3", - "is-shared-array-buffer": "^1.0.4", - "is-string": "^1.1.1", - "is-typed-array": "^1.1.15", - "is-weakref": "^1.1.1", - "math-intrinsics": "^1.1.0", - "object-inspect": "^1.13.4", - "object-keys": "^1.1.1", - "object.assign": "^4.1.7", - "own-keys": "^1.0.1", - "regexp.prototype.flags": "^1.5.4", - "safe-array-concat": "^1.1.3", - "safe-push-apply": "^1.0.0", - "safe-regex-test": "^1.1.0", - "set-proto": "^1.0.0", - "stop-iteration-iterator": "^1.1.0", - "string.prototype.trim": "^1.2.10", - "string.prototype.trimend": "^1.0.9", - "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.3", - "typed-array-byte-length": "^1.0.3", - "typed-array-byte-offset": "^1.0.4", - "typed-array-length": "^1.0.7", - "unbox-primitive": "^1.1.0", - "which-typed-array": "^1.1.19" - }, - "engines": { - "node": ">= 0.4" + "@expo/expo-modules-macros-plugin": "0.6.1", + "expo-modules-jsi": "~57.0.4", + "invariant": "^2.2.4" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "react": "*", + "react-native": "*", + "react-native-worklets": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0" + }, + "peerDependenciesMeta": { + "react-native-worklets": { + "optional": true + } } }, - "node_modules/es-abstract-get": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-abstract-get/-/es-abstract-get-1.0.0.tgz", - "integrity": "sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==", - "dev": true, + "node_modules/expo-modules-jsi": { + "version": "57.0.4", + "resolved": "https://registry.npmjs.org/expo-modules-jsi/-/expo-modules-jsi-57.0.4.tgz", + "integrity": "sha512-vt7FyqUqqFXiRVnBqYD7y+GSPTgeua5Ocoy0+SYt+RSHkZEA2Fyop7If3g1TYDzQObYybPRo7TG2Rle1XLaWFw==", "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.2", - "is-callable": "^1.2.7", - "object-inspect": "^1.13.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "react-native": "*" } }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, + "node_modules/expo-server": { + "version": "57.0.1", + "resolved": "https://registry.npmjs.org/expo-server/-/expo-server-57.0.1.tgz", + "integrity": "sha512-sBfVDH6dmKVHZxqUxbfkzS00PZELMZt1IpnHKxcOTMZtR/t7CtRAFrbXcisG+EyzeqHSVDacZT+1tbYfZt5D8w==", "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">=20.16.0" } }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "node_modules/expo-status-bar": { + "version": "57.0.1", + "resolved": "https://registry.npmjs.org/expo-status-bar/-/expo-status-bar-57.0.1.tgz", + "integrity": "sha512-Xwaq1gAoVRWx5dPG5VhT5RSbnI9OilhZnO5qoPBnUaBAa5VzRzfdS8q0/bsPt0jR2DKLtGuP0bQ6efMJ4RIMDg==", "license": "MIT", - "engines": { - "node": ">= 0.4" + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*" } }, - "node_modules/es-iterator-helpers": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.4.0.tgz", - "integrity": "sha512-c/A0P0oxkACDc+cKWw8evLXK83oBKgn0qPOqCYT4x9uolpCIJAcYvJC9QYKNDRPsTeGyCrQ326jrvgZWdCdK5Q==", - "dev": true, + "node_modules/expo/node_modules/@expo/cli": { + "version": "57.0.10", + "resolved": "https://registry.npmjs.org/@expo/cli/-/cli-57.0.10.tgz", + "integrity": "sha512-mP+B9ZrTnRE94bxPM/kCVKPyuVuOTRvvsqbXVxdXtrAfOfF94YIZLGUtw/151cGFtdUPbsBsJ9gW02LhU+QMZA==", "license": "MIT", "dependencies": { - "call-bind": "^1.0.9", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.24.2", - "es-errors": "^1.3.0", - "es-set-tostringtag": "^2.1.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.3.0", - "globalthis": "^1.0.4", - "gopd": "^1.2.0", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.2.0", - "has-symbols": "^1.1.0", - "internal-slot": "^1.1.0", - "iterator.prototype": "^1.1.5", - "math-intrinsics": "^1.1.0" + "@expo/code-signing-certificates": "^0.0.6", + "@expo/config": "~57.0.6", + "@expo/config-plugins": "~57.0.6", + "@expo/devcert": "^1.2.1", + "@expo/env": "~2.4.2", + "@expo/image-utils": "^0.11.4", + "@expo/inline-modules": "^0.1.3", + "@expo/json-file": "^11.0.1", + "@expo/log-box": "^57.0.1", + "@expo/metro": "~56.0.0", + "@expo/metro-config": "~57.0.7", + "@expo/metro-file-map": "^57.0.1", + "@expo/osascript": "^2.7.1", + "@expo/package-manager": "^1.13.1", + "@expo/plist": "^0.8.1", + "@expo/prebuild-config": "^57.0.9", + "@expo/require-utils": "^57.0.4", + "@expo/router-server": "^57.0.4", + "@expo/schema-utils": "^57.0.2", + "@expo/spawn-async": "^1.8.0", + "@expo/ws-tunnel": "^2.0.0", + "@expo/xcpretty": "^4.4.4", + "@react-native/dev-middleware": "0.86.0", + "accepts": "^1.3.8", + "agent-cli-detector": "^0.1.2", + "arg": "^5.0.2", + "bplist-creator": "0.1.0", + "bplist-parser": "^0.3.1", + "chalk": "^4.0.0", + "ci-info": "^3.3.0", + "compression": "^1.7.4", + "connect": "^3.7.0", + "debug": "^4.3.4", + "dnssd-advertise": "^1.1.4", + "expo-server": "^57.0.1", + "fetch-nodeshim": "^0.4.10", + "getenv": "^2.0.0", + "glob": "^13.0.0", + "lan-network": "^0.2.1", + "multitars": "^1.0.0", + "node-forge": "^1.3.3", + "npm-package-arg": "^11.0.0", + "ora": "^3.4.0", + "picomatch": "^4.0.4", + "pretty-format": "^29.7.0", + "progress": "^2.0.3", + "prompts": "^2.3.2", + "resolve-from": "^5.0.0", + "semver": "^7.6.0", + "send": "^0.19.0", + "slugify": "^1.3.4", + "stacktrace-parser": "^0.1.10", + "structured-headers": "^0.4.1", + "terminal-link": "^2.1.1", + "toqr": "^0.1.1", + "wrap-ansi": "^7.0.0", + "ws": "^8.12.1", + "zod": "^3.25.76" }, - "engines": { - "node": ">= 0.4" + "bin": { + "expo-internal": "main.js" + }, + "peerDependencies": { + "expo": "*", + "expo-router": "*", + "react-native": "*" + }, + "peerDependenciesMeta": { + "expo-router": { + "optional": true + }, + "react-native": { + "optional": true + } } }, - "node_modules/es-object-atoms": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", - "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", - "dev": true, + "node_modules/expo/node_modules/@expo/cli/node_modules/@expo/router-server": { + "version": "57.0.4", + "resolved": "https://registry.npmjs.org/@expo/router-server/-/router-server-57.0.4.tgz", + "integrity": "sha512-ucqCP0hK8nZb9+S8QJYdQxNCkfRClzkKdg2RpWYDKDYgRIHyoRyxEDRgDgvbhH+q78yfY83abU8OTx8MMOrf1g==", "license": "MIT", "dependencies": { - "es-errors": "^1.3.0" + "debug": "^4.3.4" }, - "engines": { - "node": ">= 0.4" + "peerDependencies": { + "@expo/metro-runtime": "^57.0.7", + "expo": "*", + "expo-constants": "^57.0.7", + "expo-font": "^57.0.1", + "expo-router": "*", + "expo-server": "^57.0.1", + "react": "*", + "react-dom": "*", + "react-server-dom-webpack": "~19.0.1 || ~19.1.2 || ~19.2.1" + }, + "peerDependenciesMeta": { + "@expo/metro-runtime": { + "optional": true + }, + "expo-router": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-server-dom-webpack": { + "optional": true + } } }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, + "node_modules/expo/node_modules/@expo/ws-tunnel": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@expo/ws-tunnel/-/ws-tunnel-2.0.0.tgz", + "integrity": "sha512-j+JfTRdCk820J9dU0sA2SqshQIKFOMo7ED84w9MJFcebfbNQgsLztEY/SABDkGnjatrW4xGqnUhVRxSBVyCkXw==", + "license": "MIT", + "peerDependencies": { + "ws": "^8.0.0" + } + }, + "node_modules/expo/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "license": "MIT", "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" }, "engines": { - "node": ">= 0.4" + "node": ">= 0.6" } }, - "node_modules/es-shim-unscopables": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", - "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", - "dev": true, + "node_modules/expo/node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/expo/node_modules/expo-file-system": { + "version": "57.0.1", + "resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-57.0.1.tgz", + "integrity": "sha512-w7/ERvQFrGP2apTO9lDtZ+O6JQIhfakL7+Xqzh+rfMO9B4LB4qwrz+YvLgir8KFRVX64JHBnRuYBVLY1oQZcqw==", + "license": "MIT", + "peerDependencies": { + "expo": "*", + "react-native": "*" + } + }, + "node_modules/expo/node_modules/expo-keep-awake": { + "version": "57.0.1", + "resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-57.0.1.tgz", + "integrity": "sha512-28lkFImeXTS+bhAjuCFV7w7tW5bXg27BJVrxv+nC/nyYa86qEa0oFeHwqol6ha5k4pdVDQgBF09GM4A1k76Ssg==", + "license": "MIT", + "peerDependencies": { + "expo": "*", + "react": "*" + } + }, + "node_modules/expo/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, "engines": { - "node": ">= 0.4" + "node": ">= 0.6" } }, - "node_modules/es-to-primitive": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.4.tgz", - "integrity": "sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==", - "dev": true, + "node_modules/expo/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", "dependencies": { - "es-abstract-get": "^1.0.0", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "is-callable": "^1.2.7", - "is-date-object": "^1.1.0", - "is-symbol": "^1.1.1" + "mime-db": "1.52.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 0.6" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "node_modules/expo/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "license": "MIT", "engines": { - "node": ">=6" + "node": ">= 0.6" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "node_modules/expo/node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "license": "MIT", "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/eslint": { - "version": "9.39.5", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.5.tgz", - "integrity": "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==", - "dev": true, + "node_modules/expo/node_modules/ws": { + "version": "8.21.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz", + "integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==", "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.2", - "@eslint/config-helpers": "^0.4.2", - "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.6", - "@eslint/js": "9.39.5", - "@eslint/plugin-kit": "^0.4.1", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "ajv": "^6.14.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.4.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.5", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" + "node": ">=10.0.0" }, "peerDependencies": { - "jiti": "*" + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { - "jiti": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { "optional": true } } }, - "node_modules/eslint-config-expo": { - "version": "57.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-expo/-/eslint-config-expo-57.0.0.tgz", - "integrity": "sha512-T7OTN9xrSZYjLw4qTkL1Mn2WfAUVmMGY38+OYAcraI1uiTFVH6jfkSkv84WLTqnneblLcb6AsMDV+SHcUj3hGw==", + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "license": "Apache-2.0" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/eslint-plugin": "^8.59.0", - "@typescript-eslint/parser": "^8.59.0", - "eslint-import-resolver-typescript": "^3.6.3", - "eslint-plugin-expo": "^1.1.0", - "eslint-plugin-import": "^2.30.0", - "eslint-plugin-react": "^7.37.3", - "eslint-plugin-react-hooks": "^7.0.0", - "globals": "^16.0.0" - }, - "peerDependencies": { - "eslint": ">=8.10" - } + "license": "MIT" }, - "node_modules/eslint-config-prettier": { - "version": "10.1.8", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", - "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true, - "license": "MIT", + "license": "MIT" + }, + "node_modules/fb-dotslash": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/fb-dotslash/-/fb-dotslash-0.5.8.tgz", + "integrity": "sha512-XHYLKk9J4BupDxi9bSEhkfss0m+Vr9ChTrjhf9l2iw3jB5C7BnY4GVPoMcqbrTutsKJso6yj2nAB6BI/F2oZaA==", + "license": "(MIT OR Apache-2.0)", "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "funding": { - "url": "https://opencollective.com/eslint-config-prettier" + "dotslash": "bin/dotslash" }, - "peerDependencies": { - "eslint": ">=7.0.0" + "engines": { + "node": ">=20" } }, - "node_modules/eslint-import-resolver-node": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.10.tgz", - "integrity": "sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==", - "dev": true, - "license": "MIT", + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "license": "Apache-2.0", "dependencies": { - "debug": "^3.2.7", - "is-core-module": "^2.16.1", - "resolve": "^2.0.0-next.6" + "bser": "2.1.1" } }, - "node_modules/eslint-import-resolver-node/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, + "node_modules/fbjs": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-3.0.5.tgz", + "integrity": "sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg==", "license": "MIT", "dependencies": { - "ms": "^2.1.1" + "cross-fetch": "^3.1.5", + "fbjs-css-vars": "^1.0.0", + "loose-envify": "^1.0.0", + "object-assign": "^4.1.0", + "promise": "^7.1.1", + "setimmediate": "^1.0.5", + "ua-parser-js": "^1.0.35" } }, - "node_modules/eslint-import-resolver-node/node_modules/resolve": { - "version": "2.0.0-next.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.7.tgz", - "integrity": "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==", - "dev": true, + "node_modules/fbjs-css-vars": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz", + "integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==", + "license": "MIT" + }, + "node_modules/fbjs/node_modules/promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", "license": "MIT", "dependencies": { - "es-errors": "^1.3.0", - "is-core-module": "^2.16.2", - "node-exports-info": "^1.6.0", - "object-keys": "^1.1.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "asap": "~2.0.3" } }, - "node_modules/eslint-import-resolver-typescript": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz", - "integrity": "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==", + "node_modules/fetch-nodeshim": { + "version": "0.4.10", + "resolved": "https://registry.npmjs.org/fetch-nodeshim/-/fetch-nodeshim-0.4.10.tgz", + "integrity": "sha512-m6I8ALe4L4XpdETy7MJZWs6L1IVMbjs99bwbpIKphxX+0CTns4IKDWJY0LWfr4YsFjfg+z1TjzTMU8lKl8rG0w==", + "license": "MIT" + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "@nolyfill/is-core-module": "1.0.39", - "debug": "^4.4.0", - "get-tsconfig": "^4.10.0", - "is-bun-module": "^2.0.0", - "stable-hash": "^0.0.5", - "tinyglobby": "^0.2.13", - "unrs-resolver": "^1.6.2" + "flat-cache": "^4.0.0" }, "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint-import-resolver-typescript" - }, - "peerDependencies": { - "eslint": "*", - "eslint-plugin-import": "*", - "eslint-plugin-import-x": "*" - }, - "peerDependenciesMeta": { - "eslint-plugin-import": { - "optional": true - }, - "eslint-plugin-import-x": { - "optional": true - } + "node": ">=16.0.0" } }, - "node_modules/eslint-module-utils": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.14.0.tgz", - "integrity": "sha512-W2WCRZ9Dqntd+2u8jJcVMV2PKulc6RdLgUUoh/yQr3uB6lo/ZOeGx11sv60/8S4QFFKNslAlWhr9u0Ef7ZW6Ig==", - "dev": true, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "license": "MIT", "dependencies": { - "debug": "^3.2.7" + "to-regex-range": "^5.0.1" }, "engines": { - "node": ">=4" + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } + "engines": { + "node": ">= 0.8" } }, - "node_modules/eslint-module-utils/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", "dependencies": { - "ms": "^2.1.1" + "ms": "2.0.0" } }, - "node_modules/eslint-plugin-expo": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-expo/-/eslint-plugin-expo-1.1.0.tgz", - "integrity": "sha512-vPP0EPx7IA7ZfP49dY4rq9RV5jqkFWG+Pih3/oGjzIRjMI+ogcOE8i6isYkLXAdw/yvFV2BRZkTaQaiOGQqn6Q==", + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "^8.59.0", - "@typescript-eslint/utils": "^8.59.0", - "eslint": "^9.24.0" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=10" }, - "peerDependencies": { - "eslint": ">=8.10" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint-plugin-import": { - "version": "2.32.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", - "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, "license": "MIT", "dependencies": { - "@rtsao/scc": "^1.1.0", - "array-includes": "^3.1.9", - "array.prototype.findlastindex": "^1.2.6", - "array.prototype.flat": "^1.3.3", - "array.prototype.flatmap": "^1.3.3", - "debug": "^3.2.7", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.12.1", - "hasown": "^2.0.2", - "is-core-module": "^2.16.1", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.fromentries": "^2.0.8", - "object.groupby": "^1.0.3", - "object.values": "^1.2.1", - "semver": "^6.3.1", - "string.prototype.trimend": "^1.0.9", - "tsconfig-paths": "^3.15.0" + "flatted": "^3.2.9", + "keyv": "^4.5.4" }, "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + "node": ">=16" } }, - "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/flow-enums-runtime": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz", + "integrity": "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==", + "license": "MIT" + }, + "node_modules/fontfaceobserver": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/fontfaceobserver/-/fontfaceobserver-2.3.0.tgz", + "integrity": "sha512-6FPvD/IVyT4ZlNe7Wcn5Fb/4ChigpucKYSvD6a+0iMoLn2inpo711eyIcKjmDtE5XNcgAkSH9uN/nfAeZzHEfg==", + "license": "BSD-2-Clause" + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", "dev": true, "license": "MIT", "dependencies": { - "ms": "^2.1.1" + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-plugin-import/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "brace-expansion": "^1.1.7" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" }, "engines": { - "node": "*" + "node": ">= 6" } }, - "node_modules/eslint-plugin-import/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/form-data/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "license": "MIT", + "engines": { + "node": ">= 0.6" } }, - "node_modules/eslint-plugin-react": { - "version": "7.37.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", - "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, "license": "MIT", "dependencies": { - "array-includes": "^3.1.8", - "array.prototype.findlast": "^1.2.5", - "array.prototype.flatmap": "^1.3.3", - "array.prototype.tosorted": "^1.1.4", - "doctrine": "^2.1.0", - "es-iterator-helpers": "^1.2.1", - "estraverse": "^5.3.0", - "hasown": "^2.0.2", - "jsx-ast-utils": "^2.4.1 || ^3.0.0", - "minimatch": "^3.1.2", - "object.entries": "^1.1.9", - "object.fromentries": "^2.0.8", - "object.values": "^1.2.1", - "prop-types": "^15.8.1", - "resolve": "^2.0.0-next.5", - "semver": "^6.3.1", - "string.prototype.matchall": "^4.0.12", - "string.prototype.repeat": "^1.0.0" + "mime-db": "1.52.0" }, "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + "node": ">= 0.6" } }, - "node_modules/eslint-plugin-react-hooks": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", - "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", - "dev": true, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "license": "MIT", - "dependencies": { - "@babel/core": "^7.24.4", - "@babel/parser": "^7.24.4", - "hermes-parser": "^0.25.1", - "zod": "^3.25.0 || ^4.0.0", - "zod-validation-error": "^3.5.0 || ^4.0.0" - }, "engines": { - "node": ">=18" - }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" + "node": ">= 0.6" } }, - "node_modules/eslint-plugin-react-hooks/node_modules/hermes-estree": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", - "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", - "dev": true, - "license": "MIT" + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "devOptional": true, + "license": "ISC" }, - "node_modules/eslint-plugin-react-hooks/node_modules/hermes-parser": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", - "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, + "hasInstallScript": true, "license": "MIT", - "dependencies": { - "hermes-estree": "0.25.1" + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/eslint-plugin-react/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.7.tgz", - "integrity": "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==", + "node_modules/function.prototype.name": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.2.0.tgz", + "integrity": "sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==", "dev": true, "license": "MIT", "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", - "is-core-module": "^2.16.2", - "node-exports-info": "^1.6.0", - "object-keys": "^1.1.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2", + "hasown": "^2.0.4", + "is-callable": "^1.2.7", + "is-document.all": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -4818,793 +7163,743 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-plugin-react/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-scope": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, + "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">= 0.4" } }, - "node_modules/eslint-visitor-keys": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", - "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", - "dev": true, - "license": "Apache-2.0", + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=6.9.0" } }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "brace-expansion": "^1.1.7" + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { - "node": "*" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "acorn": "^8.15.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">= 0.4" } }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=10" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/esquery": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", - "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "dependencies": { - "estraverse": "^5.1.0" + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" }, "engines": { - "node": ">=0.10" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "node_modules/get-tsconfig": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "estraverse": "^5.2.0" + "resolve-pkg-maps": "^1.0.0" }, - "engines": { - "node": ">=4.0" + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", + "node_modules/getenv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/getenv/-/getenv-2.0.0.tgz", + "integrity": "sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ==", + "license": "MIT", "engines": { - "node": ">=4.0" + "node": ">=6" } }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", + "node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, "engines": { - "node": ">=0.10.0" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, "engines": { - "node": ">= 0.6" + "node": ">=10.13.0" } }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "node_modules/globals": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", + "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", + "dev": true, "license": "MIT", "engines": { - "node": ">=6" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/expo": { - "version": "57.0.8", - "resolved": "https://registry.npmjs.org/expo/-/expo-57.0.8.tgz", - "integrity": "sha512-0IxxoPZbT54IH4fHL5NihkvED9HBVQx3uNdPvyv8pFUHWJ81RdFjL0aJys1IB7hbo09KTz4xW4I2aWVOXKAcJQ==", + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/runtime": "^7.20.0", - "@expo/cli": "^57.0.10", - "@expo/config": "~57.0.6", - "@expo/config-plugins": "~57.0.6", - "@expo/devtools": "~57.0.1", - "@expo/dom-webview": "~57.0.1", - "@expo/fingerprint": "^0.20.6", - "@expo/local-build-cache-provider": "^57.0.4", - "@expo/log-box": "^57.0.1", - "@expo/metro": "~56.0.0", - "@expo/metro-config": "~57.0.7", - "@ungap/structured-clone": "^1.3.0", - "babel-preset-expo": "~57.0.4", - "expo-asset": "~57.0.7", - "expo-constants": "~57.0.7", - "expo-file-system": "~57.0.1", - "expo-font": "~57.0.1", - "expo-keep-awake": "~57.0.1", - "expo-modules-autolinking": "~57.0.9", - "expo-modules-core": "~57.0.7", - "pretty-format": "^29.7.0", - "react-refresh": "^0.14.2", - "whatwg-url-minimum": "^0.1.2" - }, - "bin": { - "expo": "bin/cli", - "expo-modules-autolinking": "bin/autolinking", - "fingerprint": "bin/fingerprint" + "define-properties": "^1.2.1", + "gopd": "^1.0.1" }, - "peerDependencies": { - "@expo/dom-webview": "*", - "@expo/metro-runtime": "*", - "react": "*", - "react-dom": "*", - "react-native": "*", - "react-native-web": "*", - "react-native-webview": "*" + "engines": { + "node": ">= 0.4" }, - "peerDependenciesMeta": { - "@expo/dom-webview": { - "optional": true - }, - "@expo/metro-runtime": { - "optional": true - }, - "react-dom": { - "optional": true - }, - "react-native-web": { - "optional": true - }, - "react-native-webview": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/expo-asset": { - "version": "57.0.7", - "resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-57.0.7.tgz", - "integrity": "sha512-TA6MRQq1HL0N6KGvNMzL6djdH5tGJ/eHPIhML+6bVu52mnXldmup2swdvP37zDnvoMUUXRVGsvwVHFmvhCbW6Q==", + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, "license": "MIT", - "dependencies": { - "@expo/image-utils": "^0.11.4", - "expo-constants": "~57.0.7" + "engines": { + "node": ">= 0.4" }, - "peerDependencies": { - "expo": "*", - "react": "*", - "react-native": "*" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/expo-constants": { - "version": "57.0.7", - "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-57.0.7.tgz", - "integrity": "sha512-ShDwaKnh3UieCQ/dG0kO8PuicTTatn3WDGmXbq/fukyzPXWhdUxf37DINVDQS6D3DDG7nfqItij+QvnDHSQhTg==", + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, "license": "MIT", - "dependencies": { - "@expo/env": "~2.4.2" + "engines": { + "node": ">= 0.4" }, - "peerDependencies": { - "expo": "*", - "react-native": "*" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/expo-font": { - "version": "57.0.1", - "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-57.0.1.tgz", - "integrity": "sha512-QyS9L1Kh9sKJg4gfU6rdbpxpmH+DyzBX8z6jVvXMUDoqLr1GqmkO/Wu379KCXjL///kWbhpNlbi7AgBuj4VdIQ==", + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, "license": "MIT", "dependencies": { - "fontfaceobserver": "^2.1.0" + "es-define-property": "^1.0.0" }, - "peerDependencies": { - "expo": "*", - "react": "*", - "react-native": "*" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/expo-image-loader": { - "version": "57.0.1", - "resolved": "https://registry.npmjs.org/expo-image-loader/-/expo-image-loader-57.0.1.tgz", - "integrity": "sha512-uhrZKLT/cTl2mXyR28kPpVkS5O+PK9N1QA/07IFM4f5T4g0lTW1JHT3NEWwEEsGFldPmVX4j7LwUVVZxE+woug==", + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, "license": "MIT", - "peerDependencies": { - "expo": "*" + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/expo-image-picker": { - "version": "57.0.6", - "resolved": "https://registry.npmjs.org/expo-image-picker/-/expo-image-picker-57.0.6.tgz", - "integrity": "sha512-6Of7SzyFVC+WFuFxhD4+nRTQ9joqldPhbiVWRsumg4ybttKmY8GxrnXAUSIDfa9DSk9PhgIAy2C1c5y1+CnU3g==", + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, "license": "MIT", - "dependencies": { - "expo-image-loader": "~57.0.1" + "engines": { + "node": ">= 0.4" }, - "peerDependencies": { - "expo": "*" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/expo-modules-autolinking": { - "version": "57.0.9", - "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-57.0.9.tgz", - "integrity": "sha512-lj2nsAKMMRLXSFnGgaaQrWJ2fdSpLPc/bca6Rkiw4g8zYPn7qX4MRUJgavvzm/hBrzvMnlhXVgJtidOGuwBh+w==", + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, "license": "MIT", "dependencies": { - "@expo/require-utils": "^57.0.4", - "@expo/spawn-async": "^1.8.0", - "chalk": "^4.1.0", - "commander": "^7.2.0" + "has-symbols": "^1.0.3" }, - "bin": { - "expo-modules-autolinking": "bin/expo-modules-autolinking.js" + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/expo-modules-core": { - "version": "57.0.7", - "resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-57.0.7.tgz", - "integrity": "sha512-5HrbCfgYmLs0a5dzfM4GmRGelVTPIg+eYp0vmSbWnKRTOPj5DyVOfg1rHGEsuNKp07QwDxfLJfQVp8CNbuvxMQ==", + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "license": "MIT", "dependencies": { - "@expo/expo-modules-macros-plugin": "0.6.1", - "expo-modules-jsi": "~57.0.4", - "invariant": "^2.2.4" - }, - "peerDependencies": { - "react": "*", - "react-native": "*", - "react-native-worklets": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0" + "function-bind": "^1.1.2" }, - "peerDependenciesMeta": { - "react-native-worklets": { - "optional": true - } + "engines": { + "node": ">= 0.4" } }, - "node_modules/expo-modules-jsi": { - "version": "57.0.4", - "resolved": "https://registry.npmjs.org/expo-modules-jsi/-/expo-modules-jsi-57.0.4.tgz", - "integrity": "sha512-vt7FyqUqqFXiRVnBqYD7y+GSPTgeua5Ocoy0+SYt+RSHkZEA2Fyop7If3g1TYDzQObYybPRo7TG2Rle1XLaWFw==", + "node_modules/hermes-compiler": { + "version": "250829098.0.14", + "resolved": "https://registry.npmjs.org/hermes-compiler/-/hermes-compiler-250829098.0.14.tgz", + "integrity": "sha512-5meXwsZxgiqFaJjNzwjzI9IyUkuGGBisu+z9BvQWmGVpjH6nz11hgqkyxe4dl8UAdyIV4lTbz91+Dlnjz0VxqA==", + "license": "MIT" + }, + "node_modules/hermes-estree": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.35.0.tgz", + "integrity": "sha512-xVx5Opwy8Oo1I5yGpVRhCvWL/iV3M+ylksSKVNlxxD90cpDpR/AR1jLYqK8HWihm065a6UI3HeyAmYzwS8NOOg==", + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.35.0.tgz", + "integrity": "sha512-9JLjeHxBx8T4CAsydZR49PNZUaix+WpQJwu9p2010lu+7Kwl6D/7wYFFJxoz+aXkaaClp9Zfg6W6/zVlSJORaA==", "license": "MIT", - "peerDependencies": { - "react-native": "*" + "dependencies": { + "hermes-estree": "0.35.0" } }, - "node_modules/expo-server": { - "version": "57.0.1", - "resolved": "https://registry.npmjs.org/expo-server/-/expo-server-57.0.1.tgz", - "integrity": "sha512-sBfVDH6dmKVHZxqUxbfkzS00PZELMZt1IpnHKxcOTMZtR/t7CtRAFrbXcisG+EyzeqHSVDacZT+1tbYfZt5D8w==", - "license": "MIT", + "node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, "engines": { - "node": ">=20.16.0" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/expo-status-bar": { - "version": "57.0.1", - "resolved": "https://registry.npmjs.org/expo-status-bar/-/expo-status-bar-57.0.1.tgz", - "integrity": "sha512-Xwaq1gAoVRWx5dPG5VhT5RSbnI9OilhZnO5qoPBnUaBAa5VzRzfdS8q0/bsPt0jR2DKLtGuP0bQ6efMJ4RIMDg==", - "license": "MIT", - "peerDependencies": { - "expo": "*", - "react": "*", - "react-native": "*" - } + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" }, - "node_modules/expo/node_modules/@expo/cli": { - "version": "57.0.10", - "resolved": "https://registry.npmjs.org/@expo/cli/-/cli-57.0.10.tgz", - "integrity": "sha512-mP+B9ZrTnRE94bxPM/kCVKPyuVuOTRvvsqbXVxdXtrAfOfF94YIZLGUtw/151cGFtdUPbsBsJ9gW02LhU+QMZA==", + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, "license": "MIT", "dependencies": { - "@expo/code-signing-certificates": "^0.0.6", - "@expo/config": "~57.0.6", - "@expo/config-plugins": "~57.0.6", - "@expo/devcert": "^1.2.1", - "@expo/env": "~2.4.2", - "@expo/image-utils": "^0.11.4", - "@expo/inline-modules": "^0.1.3", - "@expo/json-file": "^11.0.1", - "@expo/log-box": "^57.0.1", - "@expo/metro": "~56.0.0", - "@expo/metro-config": "~57.0.7", - "@expo/metro-file-map": "^57.0.1", - "@expo/osascript": "^2.7.1", - "@expo/package-manager": "^1.13.1", - "@expo/plist": "^0.8.1", - "@expo/prebuild-config": "^57.0.9", - "@expo/require-utils": "^57.0.4", - "@expo/router-server": "^57.0.4", - "@expo/schema-utils": "^57.0.2", - "@expo/spawn-async": "^1.8.0", - "@expo/ws-tunnel": "^2.0.0", - "@expo/xcpretty": "^4.4.4", - "@react-native/dev-middleware": "0.86.0", - "accepts": "^1.3.8", - "agent-cli-detector": "^0.1.2", - "arg": "^5.0.2", - "bplist-creator": "0.1.0", - "bplist-parser": "^0.3.1", - "chalk": "^4.0.0", - "ci-info": "^3.3.0", - "compression": "^1.7.4", - "connect": "^3.7.0", - "debug": "^4.3.4", - "dnssd-advertise": "^1.1.4", - "expo-server": "^57.0.1", - "fetch-nodeshim": "^0.4.10", - "getenv": "^2.0.0", - "glob": "^13.0.0", - "lan-network": "^0.2.1", - "multitars": "^1.0.0", - "node-forge": "^1.3.3", - "npm-package-arg": "^11.0.0", - "ora": "^3.4.0", - "picomatch": "^4.0.4", - "pretty-format": "^29.7.0", - "progress": "^2.0.3", - "prompts": "^2.3.2", - "resolve-from": "^5.0.0", - "semver": "^7.6.0", - "send": "^0.19.0", - "slugify": "^1.3.4", - "stacktrace-parser": "^0.1.10", - "structured-headers": "^0.4.1", - "terminal-link": "^2.1.1", - "toqr": "^0.1.1", - "wrap-ansi": "^7.0.0", - "ws": "^8.12.1", - "zod": "^3.25.76" - }, - "bin": { - "expo-internal": "main.js" + "whatwg-encoding": "^2.0.0" }, - "peerDependencies": { - "expo": "*", - "expo-router": "*", - "react-native": "*" - }, - "peerDependenciesMeta": { - "expo-router": { - "optional": true - }, - "react-native": { - "optional": true - } + "engines": { + "node": ">=12" } }, - "node_modules/expo/node_modules/@expo/cli/node_modules/@expo/router-server": { - "version": "57.0.4", - "resolved": "https://registry.npmjs.org/@expo/router-server/-/router-server-57.0.4.tgz", - "integrity": "sha512-ucqCP0hK8nZb9+S8QJYdQxNCkfRClzkKdg2RpWYDKDYgRIHyoRyxEDRgDgvbhH+q78yfY83abU8OTx8MMOrf1g==", + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "license": "MIT", "dependencies": { - "debug": "^4.3.4" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, - "peerDependencies": { - "@expo/metro-runtime": "^57.0.7", - "expo": "*", - "expo-constants": "^57.0.7", - "expo-font": "^57.0.1", - "expo-router": "*", - "expo-server": "^57.0.1", - "react": "*", - "react-dom": "*", - "react-server-dom-webpack": "~19.0.1 || ~19.1.2 || ~19.2.1" + "engines": { + "node": ">= 0.8" }, - "peerDependenciesMeta": { - "@expo/metro-runtime": { - "optional": true - }, - "expo-router": { - "optional": true - }, - "react-dom": { - "optional": true - }, - "react-server-dom-webpack": { - "optional": true - } + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/expo/node_modules/@expo/ws-tunnel": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@expo/ws-tunnel/-/ws-tunnel-2.0.0.tgz", - "integrity": "sha512-j+JfTRdCk820J9dU0sA2SqshQIKFOMo7ED84w9MJFcebfbNQgsLztEY/SABDkGnjatrW4xGqnUhVRxSBVyCkXw==", + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "license": "MIT", - "peerDependencies": { - "ws": "^8.0.0" + "engines": { + "node": ">= 0.8" } }, - "node_modules/expo/node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, "license": "MIT", "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" }, "engines": { - "node": ">= 0.6" + "node": ">= 6" } }, - "node_modules/expo/node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], + "node_modules/http-proxy-agent/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, "license": "MIT", + "dependencies": { + "debug": "4" + }, "engines": { - "node": ">=8" + "node": ">= 6.0.0" } }, - "node_modules/expo/node_modules/expo-file-system": { - "version": "57.0.1", - "resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-57.0.1.tgz", - "integrity": "sha512-w7/ERvQFrGP2apTO9lDtZ+O6JQIhfakL7+Xqzh+rfMO9B4LB4qwrz+YvLgir8KFRVX64JHBnRuYBVLY1oQZcqw==", + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "license": "MIT", - "peerDependencies": { - "expo": "*", - "react-native": "*" + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" } }, - "node_modules/expo/node_modules/expo-keep-awake": { - "version": "57.0.1", - "resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-57.0.1.tgz", - "integrity": "sha512-28lkFImeXTS+bhAjuCFV7w7tW5bXg27BJVrxv+nC/nyYa86qEa0oFeHwqol6ha5k4pdVDQgBF09GM4A1k76Ssg==", + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/hyphenate-style-name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz", + "integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==", + "license": "BSD-3-Clause" + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, "license": "MIT", - "peerDependencies": { - "expo": "*", - "react": "*" + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "node_modules/expo/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/image-size": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz", + "integrity": "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==", "license": "MIT", + "dependencies": { + "queue": "6.0.2" + }, + "bin": { + "image-size": "bin/image-size.js" + }, "engines": { - "node": ">= 0.6" + "node": ">=16.x" } }, - "node_modules/expo/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, "license": "MIT", "dependencies": { - "mime-db": "1.52.0" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" }, "engines": { - "node": ">= 0.6" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/expo/node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=4" } }, - "node_modules/expo/node_modules/picomatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", - "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, "engines": { - "node": ">=12" + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/expo/node_modules/ws": { - "version": "8.21.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz", - "integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==", + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "devOptional": true, "license": "MIT", "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } + "node": ">=0.8.19" } }, - "node_modules/exponential-backoff": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", - "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", - "license": "Apache-2.0" - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "devOptional": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" }, - "node_modules/fb-dotslash": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/fb-dotslash/-/fb-dotslash-0.5.8.tgz", - "integrity": "sha512-XHYLKk9J4BupDxi9bSEhkfss0m+Vr9ChTrjhf9l2iw3jB5C7BnY4GVPoMcqbrTutsKJso6yj2nAB6BI/F2oZaA==", - "license": "(MIT OR Apache-2.0)", - "bin": { - "dotslash": "bin/dotslash" - }, - "engines": { - "node": ">=20" + "node_modules/inline-style-prefixer": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-7.0.1.tgz", + "integrity": "sha512-lhYo5qNTQp3EvSSp3sRvXMbVQTLrvGV6DycRMJ5dm2BLMiJ30wpXKdDdgX+GmJZ5uQMucwRKHamXSst3Sj/Giw==", + "license": "MIT", + "dependencies": { + "css-in-js-utils": "^3.1.0" } }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "license": "Apache-2.0", + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", "dependencies": { - "bser": "2.1.1" + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" } }, - "node_modules/fbjs": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-3.0.5.tgz", - "integrity": "sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg==", + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", "license": "MIT", "dependencies": { - "cross-fetch": "^3.1.5", - "fbjs-css-vars": "^1.0.0", - "loose-envify": "^1.0.0", - "object-assign": "^4.1.0", - "promise": "^7.1.1", - "setimmediate": "^1.0.5", - "ua-parser-js": "^1.0.35" + "loose-envify": "^1.0.0" } }, - "node_modules/fbjs-css-vars": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz", - "integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==", - "license": "MIT" - }, - "node_modules/fbjs/node_modules/promise": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, "license": "MIT", "dependencies": { - "asap": "~2.0.3" + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fetch-nodeshim": { - "version": "0.4.10", - "resolved": "https://registry.npmjs.org/fetch-nodeshim/-/fetch-nodeshim-0.4.10.tgz", - "integrity": "sha512-m6I8ALe4L4XpdETy7MJZWs6L1IVMbjs99bwbpIKphxX+0CTns4IKDWJY0LWfr4YsFjfg+z1TjzTMU8lKl8rG0w==", + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, "license": "MIT" }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", "dev": true, "license": "MIT", "dependencies": { - "flat-cache": "^4.0.0" + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" }, "engines": { - "node": ">=16.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, "license": "MIT", "dependencies": { - "to-regex-range": "^5.0.1" + "has-bigints": "^1.0.2" }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, "license": "MIT", "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/is-bun-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", + "dev": true, "license": "MIT", "dependencies": { - "ms": "2.0.0" + "semver": "^7.7.1" } }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "dev": true, "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", "license": "MIT", "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" + "hasown": "^2.0.3" }, "engines": { - "node": ">=16" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/flatted": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", - "dev": true, - "license": "ISC" - }, - "node_modules/flow-enums-runtime": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz", - "integrity": "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==", - "license": "MIT" - }, - "node_modules/fontfaceobserver": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/fontfaceobserver/-/fontfaceobserver-2.3.0.tgz", - "integrity": "sha512-6FPvD/IVyT4ZlNe7Wcn5Fb/4ChigpucKYSvD6a+0iMoLn2inpo711eyIcKjmDtE5XNcgAkSH9uN/nfAeZzHEfg==", - "license": "BSD-2-Clause" - }, - "node_modules/for-each": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", - "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", "dev": true, "license": "MIT", "dependencies": { - "is-callable": "^1.2.7" + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -5613,40 +7908,46 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, "engines": { - "node": ">= 0.6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/function.prototype.name": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.2.0.tgz", - "integrity": "sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==", + "node_modules/is-document.all": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-document.all/-/is-document.all-1.0.0.tgz", + "integrity": "sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.9", - "call-bound": "^1.0.4", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2", - "hasown": "^2.0.4", - "is-callable": "^1.2.7", - "is-document.all": "^1.0.0" + "call-bound": "^1.0.4" }, "engines": { "node": ">= 0.4" @@ -5655,61 +7956,63 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/generator-function": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", - "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", "dev": true, "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "license": "MIT", "engines": { - "node": ">=6.9.0" + "node": ">=8" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "license": "ISC", + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", "engines": { - "node": "6.* || 8.* || >= 10.*" + "node": ">=6" } }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -5718,31 +8021,25 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "license": "MIT", "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" + "is-extglob": "^2.1.1" }, "engines": { - "node": ">= 0.4" + "node": ">=0.10.0" } }, - "node_modules/get-symbol-description": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", - "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", "dev": true, "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6" - }, "engines": { "node": ">= 0.4" }, @@ -5750,80 +8047,92 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-tsconfig": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", - "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", "dev": true, "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" + "engines": { + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/getenv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/getenv/-/getenv-2.0.0.tgz", - "integrity": "sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ==", + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "license": "MIT", "engines": { - "node": ">=6" + "node": ">=0.12.0" } }, - "node_modules/glob": { - "version": "13.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", - "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", - "license": "BlueOak-1.0.0", + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", "dependencies": { - "minimatch": "^10.2.2", - "minipass": "^7.1.3", - "path-scurry": "^2.0.2" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { - "node": "18 || 20 || >=22" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", "dev": true, - "license": "ISC", + "license": "MIT" + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", "dependencies": { - "is-glob": "^4.0.3" + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { - "node": ">=10.13.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/globals": { - "version": "16.5.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", - "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", "dev": true, "license": "MIT", "engines": { - "node": ">=18" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/globalthis": { + "node_modules/is-shared-array-buffer": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", - "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", "dev": true, "license": "MIT", "dependencies": { - "define-properties": "^1.2.1", - "gopd": "^1.0.1" + "call-bound": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -5832,31 +8141,29 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, - "node_modules/has-bigints": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", - "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", "dev": true, "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, "engines": { "node": ">= 0.4" }, @@ -5864,36 +8171,32 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", "dev": true, "license": "MIT", "dependencies": { - "es-define-property": "^1.0.0" + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-proto": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", - "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", "dev": true, "license": "MIT", "dependencies": { - "dunder-proto": "^1.0.0" + "which-typed-array": "^1.1.16" }, "engines": { "node": ">= 0.4" @@ -5902,10 +8205,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", "dev": true, "license": "MIT", "engines": { @@ -5915,14 +8218,14 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", "dev": true, "license": "MIT", "dependencies": { - "has-symbols": "^1.0.3" + "call-bound": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -5931,693 +8234,979 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/hasown": { + "node_modules/is-weakset": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", - "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, "license": "MIT", "dependencies": { - "function-bind": "^1.1.2" + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/hermes-compiler": { - "version": "250829098.0.14", - "resolved": "https://registry.npmjs.org/hermes-compiler/-/hermes-compiler-250829098.0.14.tgz", - "integrity": "sha512-5meXwsZxgiqFaJjNzwjzI9IyUkuGGBisu+z9BvQWmGVpjH6nz11hgqkyxe4dl8UAdyIV4lTbz91+Dlnjz0VxqA==", - "license": "MIT" + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } }, - "node_modules/hermes-estree": { - "version": "0.35.0", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.35.0.tgz", - "integrity": "sha512-xVx5Opwy8Oo1I5yGpVRhCvWL/iV3M+ylksSKVNlxxD90cpDpR/AR1jLYqK8HWihm065a6UI3HeyAmYzwS8NOOg==", + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, "license": "MIT" }, - "node_modules/hermes-parser": { - "version": "0.35.0", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.35.0.tgz", - "integrity": "sha512-9JLjeHxBx8T4CAsydZR49PNZUaix+WpQJwu9p2010lu+7Kwl6D/7wYFFJxoz+aXkaaClp9Zfg6W6/zVlSJORaA==", - "license": "MIT", - "dependencies": { - "hermes-estree": "0.35.0" + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "devOptional": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" } }, - "node_modules/hosted-git-info": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", - "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", - "license": "ISC", + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "devOptional": true, + "license": "BSD-3-Clause", "dependencies": { - "lru-cache": "^10.0.1" + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/hosted-git-info/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "devOptional": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } }, - "node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "license": "MIT", + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">= 0.8" + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "engines": { + "node": ">=10" } }, - "node_modules/http-errors/node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", + "node_modules/istanbul-lib-source-maps/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", "engines": { - "node": ">= 0.8" + "node": ">=0.10.0" } }, - "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "license": "MIT", + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" }, "engines": { - "node": ">= 14" + "node": ">=8" } }, - "node_modules/hyphenate-style-name": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz", - "integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==", - "license": "BSD-3-Clause" - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, "engines": { - "node": ">= 4" + "node": ">= 0.4" } }, - "node_modules/image-size": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz", - "integrity": "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==", + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, "license": "MIT", "dependencies": { - "queue": "6.0.2" + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" }, "bin": { - "image-size": "bin/image-size.js" + "jest": "bin/jest.js" }, "engines": { - "node": ">=16.x" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", "dev": true, "license": "MIT", "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" }, "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/import-fresh/node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", "dev": true, "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, "engines": { - "node": ">=4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "node_modules/jest-circus/node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", "dev": true, "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/inline-style-prefixer": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-7.0.1.tgz", - "integrity": "sha512-lhYo5qNTQp3EvSSp3sRvXMbVQTLrvGV6DycRMJ5dm2BLMiJ30wpXKdDdgX+GmJZ5uQMucwRKHamXSst3Sj/Giw==", - "license": "MIT", "dependencies": { - "css-in-js-utils": "^3.1.0" + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/internal-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", - "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "node_modules/jest-circus/node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", "dev": true, "license": "MIT", "dependencies": { - "es-errors": "^1.3.0", - "hasown": "^2.0.2", - "side-channel": "^1.1.0" + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">= 0.4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, "license": "MIT", "dependencies": { - "loose-envify": "^1.0.0" + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/is-array-buffer": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", - "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" }, "engines": { - "node": ">= 0.4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } } }, - "node_modules/is-async-function": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", - "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "node_modules/jest-config/node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-config/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", "dependencies": { - "async-function": "^1.0.0", - "call-bound": "^1.0.3", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "engines": { - "node": ">= 0.4" + "node": "*" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/is-bigint": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", - "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "node_modules/jest-config/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "has-bigints": "^1.0.2" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "*" } }, - "node_modules/is-boolean-object": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", - "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "node_modules/jest-diff": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.4.1.tgz", + "integrity": "sha512-CRpFK0RtLriVDGcPPAnR6HMVI8bSR2jnUIgralhauzYQZIb4RH9AtEInTuQr65LmmGggGcRT6HIASxwqsVsmlA==", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" + "@jest/diff-sequences": "30.4.0", + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "pretty-format": "30.4.1" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/is-bun-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", - "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", + "node_modules/jest-diff/node_modules/@jest/schemas": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", + "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", "dev": true, "license": "MIT", "dependencies": { - "semver": "^7.7.1" + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "node_modules/jest-diff/node_modules/@sinclair/typebox": { + "version": "0.34.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-diff/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/pretty-format": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.4.1", + "ansi-styles": "^5.2.0", + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/is-core-module": { - "version": "2.16.2", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", - "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, "license": "MIT", "dependencies": { - "hasown": "^2.0.3" + "detect-newline": "^3.0.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-data-view": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", - "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "is-typed-array": "^1.1.13" + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-date-object": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", - "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "node_modules/jest-environment-jsdom": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz", + "integrity": "sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/jsdom": "^20.0.0", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0", + "jsdom": "^20.0.0" }, "engines": { - "node": ">= 0.4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } } }, - "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "devOptional": true, "license": "MIT", - "bin": { - "is-docker": "cli.js" + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-document.all": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-document.all/-/is-document.all-1.0.0.tgz", - "integrity": "sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==", + "node_modules/jest-expo": { + "version": "57.0.2", + "resolved": "https://registry.npmjs.org/jest-expo/-/jest-expo-57.0.2.tgz", + "integrity": "sha512-xoKiYyu8c0fdBsFMkeFnxoTZ/0g4rLldA9isVb7VJSGBGesmhkVor7YkftkHqQ5rWiZ99IY+/uIrzTgb1nC/UA==", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.4" + "@jest/create-cache-key-function": "^29.2.1", + "@jest/globals": "^29.2.1", + "babel-jest": "^29.2.1", + "jest-environment-jsdom": "^29.2.1", + "jest-snapshot": "^29.2.1", + "jest-watch-select-projects": "^2.0.0", + "jest-watch-typeahead": "2.2.1", + "json5": "^2.2.3", + "lodash": "^4.17.19", + "react-test-renderer": "19.2.3", + "server-only": "^0.0.1", + "stacktrace-js": "^2.0.2" }, - "engines": { - "node": ">= 0.4" + "bin": { + "jest": "bin/jest.js" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "@react-native/jest-preset": "^0.86.0", + "expo": "*", + "react-native": "*", + "react-server-dom-webpack": "~19.0.4 || ~19.1.5 || ~19.2.4" + }, + "peerDependenciesMeta": { + "expo": { + "optional": true + }, + "react-server-dom-webpack": { + "optional": true + } } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-finalizationregistry": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", - "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", - "dev": true, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "devOptional": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.3" + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" }, "engines": { - "node": ">= 0.4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "optionalDependencies": { + "fsevents": "^2.3.2" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-generator-function": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", - "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "node_modules/jest-matcher-utils": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.4.1.tgz", + "integrity": "sha512-zvYfX5CaeEkFrrLS9suWe9rvJrm9J1Iv3ua8kIBv9GEPzcnsfBf0bob37la7s67fs0nlBC3EuvkOLnXQKxtx4A==", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.4", - "generator-function": "^2.0.0", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "jest-diff": "30.4.1", + "pretty-format": "30.4.1" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "node_modules/jest-matcher-utils/node_modules/@jest/schemas": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", + "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", "dev": true, "license": "MIT", "dependencies": { - "is-extglob": "^2.1.1" + "@sinclair/typebox": "^0.34.0" }, "engines": { - "node": ">=0.10.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/is-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", - "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "node_modules/jest-matcher-utils/node_modules/@sinclair/typebox": { + "version": "0.34.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-matcher-utils/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/is-negative-zero": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", - "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "node_modules/jest-matcher-utils/node_modules/pretty-format": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.4" + "dependencies": { + "@jest/schemas": "30.4.1", + "ansi-styles": "^5.2.0", + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "devOptional": true, "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, "engines": { - "node": ">=0.12.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-number-object": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", - "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", - "dev": true, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "devOptional": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-regex": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", - "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", "dev": true, "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, "engines": { - "node": ">= 0.4" + "node": ">=6" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } } }, - "node_modules/is-set": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", - "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", - "dev": true, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "devOptional": true, "license": "MIT", "engines": { - "node": ">= 0.4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", - "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.3" + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-string": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", - "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-symbol": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", - "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "node_modules/jest-runner/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-symbols": "^1.1.0", - "safe-regex-test": "^1.1.0" - }, + "license": "BSD-3-Clause", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=0.10.0" } }, - "node_modules/is-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", - "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "node_modules/jest-runner/node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", "dev": true, "license": "MIT", "dependencies": { - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" } }, - "node_modules/is-weakmap": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", - "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.4" + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-weakref": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", - "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "node_modules/jest-runtime/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "call-bound": "^1.0.3" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "engines": { - "node": ">= 0.4" + "node": "*" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/is-weakset": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", - "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "node_modules/jest-runtime/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "*" } }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "node_modules/jest-runtime/node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, "license": "MIT", - "dependencies": { - "is-docker": "^2.0.0" - }, "engines": { "node": ">=8" } }, - "node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", "dev": true, - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/iterator.prototype": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", - "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "node_modules/jest-snapshot/node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", "dev": true, "license": "MIT", "dependencies": { - "define-data-property": "^1.1.4", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.6", - "get-proto": "^1.0.0", - "has-symbols": "^1.1.0", - "set-function-name": "^2.0.2" + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">= 0.4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-get-type": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", - "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "node_modules/jest-snapshot/node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -6671,6 +9260,156 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-watch-select-projects": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jest-watch-select-projects/-/jest-watch-select-projects-2.0.0.tgz", + "integrity": "sha512-j00nW4dXc2NiCW6znXgFLF9g8PJ0zP25cpQ1xRro/HU2GBfZQFZD0SoXnAlaoKkIY4MlfTMkKGbNXFpvCdjl1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^4.3.0", + "chalk": "^3.0.0", + "prompts": "^2.2.1" + } + }, + "node_modules/jest-watch-select-projects/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watch-typeahead": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/jest-watch-typeahead/-/jest-watch-typeahead-2.2.1.tgz", + "integrity": "sha512-jYpYmUnTzysmVnwq49TAxlmtOAwp8QIqvZyoofQFn8fiWhEDZj33ZXzg3JA4nGnzWFm1hbWf3ADpteUokvXgFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^6.0.0", + "chalk": "^4.0.0", + "jest-regex-util": "^29.0.0", + "jest-watcher": "^29.0.0", + "slash": "^5.0.0", + "string-length": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "jest": "^27.0.0 || ^28.0.0 || ^29.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/ansi-escapes": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.1.tgz", + "integrity": "sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watch-typeahead/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/jest-watch-typeahead/node_modules/char-regex": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-2.0.2.tgz", + "integrity": "sha512-cbGOjAptfM2LVmWhwRFHEKTPkLwNddVmuqYZQt895yXwAsWsXObCG+YN4DGQ/JBtT4GP1a1lPPdio2z413LmTg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/jest-watch-typeahead/node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watch-typeahead/node_modules/string-length": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-5.0.1.tgz", + "integrity": "sha512-9Ep08KAMUn0OadnVaBuRdE2l615CQ508kr0XMadjClfYpdCyvrbFp6Taebo8yyxokQ4viUd/xPPUA4FGgUa0ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^2.0.0", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watch-typeahead/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/jest-worker": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", @@ -6741,6 +9480,138 @@ "integrity": "sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==", "license": "0BSD" }, + "node_modules/jsdom": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", + "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "abab": "^2.0.6", + "acorn": "^8.8.1", + "acorn-globals": "^7.0.0", + "cssom": "^0.5.0", + "cssstyle": "^2.3.0", + "data-urls": "^3.0.2", + "decimal.js": "^10.4.2", + "domexception": "^4.0.0", + "escodegen": "^2.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.2", + "parse5": "^7.1.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.2", + "w3c-xmlserializer": "^4.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0", + "ws": "^8.11.0", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/jsdom/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/jsdom/node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/jsdom/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/jsdom/node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/jsdom/node_modules/ws": { + "version": "8.21.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz", + "integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -6760,6 +9631,13 @@ "dev": true, "license": "MIT" }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -7127,6 +10005,13 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -7143,6 +10028,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -7277,6 +10169,22 @@ "react-native-svg": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0" } }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -7647,6 +10555,16 @@ "node": ">=4" } }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/minimatch": { "version": "10.2.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", @@ -7828,6 +10746,16 @@ "node": ">=18" } }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/npm-package-arg": { "version": "11.0.3", "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.3.tgz", @@ -7843,6 +10771,19 @@ "node": "^16.14.0 || >=18.0.0" } }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -7861,6 +10802,13 @@ "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", "license": "MIT" }, + "node_modules/nwsapi": { + "version": "2.2.24", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.24.tgz", + "integrity": "sha512-7YRhZ3jS45LwmSCT4b2sVFHt/WuovaktDU07QrtOBY2PXskss5a9jfmR9jptyumwXST+rFjrmppMY1KT/yn35A==", + "dev": true, + "license": "MIT" + }, "node_modules/ob1": { "version": "0.84.4", "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.84.4.tgz", @@ -8016,6 +10964,16 @@ "node": ">= 0.8" } }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, "node_modules/onetime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", @@ -8221,6 +11179,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -8234,6 +11202,25 @@ "node": ">=6" } }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parse-png": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/parse-png/-/parse-png-2.1.0.tgz", @@ -8246,6 +11233,32 @@ "node": ">=10" } }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -8259,12 +11272,22 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" } }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -8323,6 +11346,85 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/plist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.1.tgz", @@ -8510,6 +11612,19 @@ "dev": true, "license": "MIT" }, + "node_modules/psl": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -8520,6 +11635,30 @@ "node": ">=6" } }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true, + "license": "MIT" + }, "node_modules/queue": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", @@ -8575,6 +11714,22 @@ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "license": "MIT" }, + "node_modules/react-is-18": { + "name": "react-is", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/react-is-19": { + "name": "react-is", + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.8.tgz", + "integrity": "sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ==", + "dev": true, + "license": "MIT" + }, "node_modules/react-native": { "version": "0.86.0", "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.86.0.tgz", @@ -8723,6 +11878,41 @@ "node": ">=0.10.0" } }, + "node_modules/react-test-renderer": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-19.2.3.tgz", + "integrity": "sha512-TMR1LnSFiWZMJkCgNf5ATSvAheTT2NvKIwiVwdBPHxjBI7n/JbWd4gaZ16DVd9foAXdvDz+sB5yxZTwMjPRxpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "react-is": "^19.2.3", + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.3" + } + }, + "node_modules/react-test-renderer/node_modules/react-is": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.8.tgz", + "integrity": "sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -8835,6 +12025,13 @@ "node": ">=0.10.0" } }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, "node_modules/resolve": { "version": "1.22.12", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", @@ -8856,6 +12053,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -8881,6 +12091,16 @@ "integrity": "sha512-nR23LHAvaI6aHtMg6RWoaHpdR4D881Nydkzi2CixINyg9T00KgaJdJI6Vwty+Ps8WLxZHuxsS0BseWjxSA4C+w==", "license": "MIT" }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/restore-cursor": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", @@ -8969,6 +12189,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, "node_modules/sax": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz", @@ -8978,6 +12205,19 @@ "node": ">=11.0.0" } }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/scheduler": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", @@ -9098,6 +12338,13 @@ "node": ">= 0.8" } }, + "node_modules/server-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/server-only/-/server-only-0.0.1.tgz", + "integrity": "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==", + "dev": true, + "license": "MIT" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -9291,6 +12538,16 @@ "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", "license": "MIT" }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/slugify": { "version": "1.6.9", "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.9.tgz", @@ -9337,6 +12594,13 @@ "node": ">=0.10.0" } }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "devOptional": true, + "license": "BSD-3-Clause" + }, "node_modules/stable-hash": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", @@ -9344,12 +12608,78 @@ "dev": true, "license": "MIT" }, + "node_modules/stack-generator": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/stack-generator/-/stack-generator-2.0.10.tgz", + "integrity": "sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "stackframe": "^1.3.4" + } + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/stackframe": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", "license": "MIT" }, + "node_modules/stacktrace-gps": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/stacktrace-gps/-/stacktrace-gps-3.1.2.tgz", + "integrity": "sha512-GcUgbO4Jsqqg6RxfyTHFiPxdPqF+3LFmQhm7MgCuYQOYuWyqxo5pwRPz5d/u6/WYJdEnWfK4r+jGbyD8TSggXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "source-map": "0.5.6", + "stackframe": "^1.3.4" + } + }, + "node_modules/stacktrace-gps/node_modules/source-map": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "integrity": "sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stacktrace-js": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stacktrace-js/-/stacktrace-js-2.0.2.tgz", + "integrity": "sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "error-stack-parser": "^2.0.6", + "stack-generator": "^2.0.5", + "stacktrace-gps": "^3.0.4" + } + }, "node_modules/stacktrace-parser": { "version": "0.1.11", "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.11.tgz", @@ -9394,6 +12724,20 @@ "node": ">= 0.10.0" } }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -9529,6 +12873,29 @@ "node": ">=4" } }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -9591,6 +12958,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, "node_modules/terminal-link": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", @@ -9631,6 +13005,56 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "license": "MIT" }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "devOptional": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/throat": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", @@ -9715,6 +13139,22 @@ "integrity": "sha512-FWAPzCIHZHnrE/5/w9MPk0kK25hSQSH2IKhYh9PyjS3SG/+IEMvlwIHbhz+oF7xl54I+ueZlVnMjyzdSwLmAwA==", "license": "MIT" }, + "node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -9781,6 +13221,16 @@ "node": ">= 0.8.0" } }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/type-fest": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", @@ -9973,6 +13423,16 @@ "node": ">=4" } }, + "node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -10060,6 +13520,17 @@ "punycode": "^2.1.0" } }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -10082,6 +13553,21 @@ "uuid": "dist/esm/bin/uuid" } }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, "node_modules/validate-npm-package-name": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", @@ -10106,6 +13592,19 @@ "integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==", "license": "MIT" }, + "node_modules/w3c-xmlserializer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", + "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -10136,12 +13635,36 @@ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", "license": "BSD-2-Clause" }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/whatwg-fetch": { "version": "3.6.20", "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", "license": "MIT" }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", @@ -10289,6 +13812,27 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, "node_modules/ws": { "version": "7.5.12", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.12.tgz", @@ -10323,6 +13867,16 @@ "node": ">=10.0.0" } }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12" + } + }, "node_modules/xml2js": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.0.tgz", @@ -10354,6 +13908,13 @@ "node": ">=8.0" } }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/frontend/package.json b/frontend/package.json index 93c14b2..938a560 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -25,11 +25,17 @@ }, "devDependencies": { "@baidumap/jsapi-v4-types": "^4.0.2", + "@react-native/jest-preset": "0.86.0", + "@testing-library/react-native": "^13.2.0", + "@types/jest": "29.5.14", "@types/react": "~19.2.2", "eslint": "^9.39.5", "eslint-config-expo": "^57.0.0", "eslint-config-prettier": "^10.1.8", + "jest": "~29.7.0", + "jest-expo": "57.0.2", "prettier": "^3.9.5", + "react-test-renderer": "19.2.3", "typescript": "~6.0.3" }, "overrides": { @@ -46,7 +52,25 @@ "format": "prettier --write .", "format:check": "prettier --check .", "typecheck": "tsc --noEmit", - "check": "npm run lint && npm run format:check && npm run typecheck" + "test": "jest", + "check": "npm run lint && npm run format:check && npm run typecheck && npm run test" + }, + "jest": { + "preset": "jest-expo", + "setupFilesAfterEnv": [ + "/jest.setup.js" + ], + "testMatch": [ + "/__tests__/**/*.test.ts", + "/__tests__/**/*.test.tsx" + ], + "moduleNameMapper": { + "^@/(.*)$": "/src/$1", + "^@test/(.*)$": "/__tests__/$1" + }, + "transformIgnorePatterns": [ + "/node_modules/(?!(.pnpm|react-native|@react-native|@react-native-community|expo|@expo|@expo-google-fonts|react-navigation|@react-navigation|@sentry/react-native|native-base|standard-navigation|lucide-react-native|react-native-svg|react-native-swipe-gestures))" + ] }, "private": true } diff --git a/frontend/src/app/overlay/OverlayProvider.tsx b/frontend/src/app/overlay/OverlayProvider.tsx new file mode 100644 index 0000000..4acee11 --- /dev/null +++ b/frontend/src/app/overlay/OverlayProvider.tsx @@ -0,0 +1,109 @@ +import { + createContext, + useCallback, + useContext, + useMemo, + useRef, + useState, + type ReactNode, +} from 'react'; +import { BackHandler } from 'react-native'; +import { useEffect } from 'react'; + +export type OverlayKind = + | 'standardCreate' + | 'assistant' + | 'locationPicker' + | 'addressEditor' + | 'datePicker' + | 'timePicker' + | 'scheduleDetail' + | 'mapPicker'; + +export type OverlayEntry = { + id: string; + kind: OverlayKind; + onClose?: () => void; +}; + +type OverlayContextValue = { + stack: OverlayEntry[]; + push: (entry: Omit & { id?: string }) => string; + pop: () => void; + popKind: (kind: OverlayKind) => void; + isOpen: (kind: OverlayKind) => boolean; + top: OverlayEntry | null; +}; + +const OverlayContext = createContext(null); + +let overlaySeq = 0; + +export function OverlayProvider({ children }: { children: ReactNode }) { + const [stack, setStack] = useState([]); + // Event handlers update this synchronously so multiple pop operations in + // one event use the latest stack without putting callbacks in a state + // updater (which React may invoke more than once in StrictMode). + const stackRef = useRef([]); + + const push = useCallback((entry: Omit & { id?: string }) => { + const id = entry.id ?? `overlay_${++overlaySeq}`; + const nextEntry = { ...entry, id }; + const nextStack = [...stackRef.current, nextEntry]; + stackRef.current = nextStack; + setStack(nextStack); + return id; + }, []); + + const pop = useCallback(() => { + const current = stackRef.current; + const top = current[current.length - 1]; + if (!top) return; + const nextStack = current.slice(0, -1); + stackRef.current = nextStack; + setStack(nextStack); + top.onClose?.(); + }, []); + + const popKind = useCallback((kind: OverlayKind) => { + const current = stackRef.current; + const index = [...current].map((item) => item.kind).lastIndexOf(kind); + if (index < 0) return; + const removed = current[index]; + const nextStack = current.filter((_, i) => i !== index); + stackRef.current = nextStack; + setStack(nextStack); + removed?.onClose?.(); + }, []); + + const isOpen = useCallback( + (kind: OverlayKind) => stack.some((item) => item.kind === kind), + [stack], + ); + + const top = stack.length > 0 ? stack[stack.length - 1]! : null; + + useEffect(() => { + const subscription = BackHandler.addEventListener('hardwareBackPress', () => { + if (stackRef.current.length === 0) return false; + pop(); + return true; + }); + return () => subscription.remove(); + }, [pop]); + + const value = useMemo( + () => ({ stack, push, pop, popKind, isOpen, top }), + [isOpen, pop, popKind, push, stack, top], + ); + + return {children}; +} + +export function useOverlay(): OverlayContextValue { + const value = useContext(OverlayContext); + if (!value) { + throw new Error('useOverlay must be used within OverlayProvider'); + } + return value; +} diff --git a/frontend/src/shared/components/AppDialogProvider.styles.ts b/frontend/src/shared/components/AppDialogProvider.styles.ts new file mode 100644 index 0000000..ff4aa31 --- /dev/null +++ b/frontend/src/shared/components/AppDialogProvider.styles.ts @@ -0,0 +1,86 @@ +import { StyleSheet } from 'react-native'; + +import { colors } from '@/shared/theme'; + +export const appDialogStyles = StyleSheet.create({ + backdrop: { + alignItems: 'center', + backgroundColor: 'rgba(14, 23, 19, 0.46)', + flex: 1, + justifyContent: 'center', + padding: 24, + }, + dismiss: { + bottom: 0, + left: 0, + position: 'absolute', + right: 0, + top: 0, + }, + dialog: { + backgroundColor: colors.surface, + borderColor: colors.line, + borderRadius: 8, + borderWidth: 1, + padding: 18, + width: '100%', + maxWidth: 360, + }, + icon: { + alignItems: 'center', + backgroundColor: colors.limeSoft, + borderRadius: 8, + height: 36, + justifyContent: 'center', + marginBottom: 13, + width: 36, + }, + iconDanger: { + backgroundColor: colors.peach, + }, + title: { + color: colors.ink, + fontSize: 20, + fontWeight: '800', + lineHeight: 26, + }, + message: { + color: colors.sub, + fontSize: 14, + lineHeight: 21, + marginTop: 9, + }, + actions: { + flexDirection: 'row', + gap: 10, + justifyContent: 'flex-end', + marginTop: 22, + }, + action: { + alignItems: 'center', + borderRadius: 8, + minHeight: 42, + minWidth: 84, + justifyContent: 'center', + paddingHorizontal: 15, + }, + cancelAction: { + backgroundColor: colors.surfaceTint, + }, + primaryAction: { + backgroundColor: colors.deep, + }, + dangerAction: { + backgroundColor: colors.coral, + }, + cancelText: { + color: colors.ink, + fontSize: 14, + fontWeight: '800', + }, + primaryText: { + color: colors.surface, + fontSize: 14, + fontWeight: '800', + }, +}); diff --git a/frontend/src/shared/components/AppDialogProvider.tsx b/frontend/src/shared/components/AppDialogProvider.tsx new file mode 100644 index 0000000..692b44a --- /dev/null +++ b/frontend/src/shared/components/AppDialogProvider.tsx @@ -0,0 +1,162 @@ +import { + createContext, + useCallback, + useContext, + useEffect, + useMemo, + useRef, + useState, + type ReactNode, +} from 'react'; +import { CircleAlert } from 'lucide-react-native'; +import { Modal, Pressable, Text, View } from 'react-native'; + +import { colors } from '@/shared/theme'; + +import { appDialogStyles as styles } from './AppDialogProvider.styles'; + +export type AppDialogTone = 'default' | 'danger'; + +export type AppDialogOptions = { + title: string; + message: string; + tone?: AppDialogTone; + confirmLabel?: string; + cancelLabel?: string; +}; + +type DialogRequest = Required> & + Pick & { + id: string; + mode: 'notice' | 'confirm'; + resolve: (confirmed: boolean) => void; + }; + +type AppDialogContextValue = { + showNotice: (options: AppDialogOptions) => Promise; + confirm: (options: AppDialogOptions) => Promise; +}; + +const AppDialogContext = createContext(null); + +let dialogSequence = 0; + +export function AppDialogProvider({ children }: { children: ReactNode }) { + const queueRef = useRef([]); + const [active, setActive] = useState(null); + + const enqueue = useCallback( + (mode: DialogRequest['mode'], options: AppDialogOptions): Promise => { + return new Promise((resolve) => { + const request: DialogRequest = { + id: `dialog_${++dialogSequence}`, + mode, + title: options.title, + message: options.message, + tone: options.tone ?? 'default', + confirmLabel: options.confirmLabel, + cancelLabel: options.cancelLabel, + resolve, + }; + queueRef.current = [...queueRef.current, request]; + if (queueRef.current.length === 1) setActive(request); + }); + }, + [], + ); + + const settle = useCallback((confirmed: boolean) => { + const [current, ...rest] = queueRef.current; + if (!current) return; + queueRef.current = rest; + setActive(rest[0] ?? null); + current.resolve(confirmed); + }, []); + + useEffect(() => { + return () => { + for (const request of queueRef.current) request.resolve(false); + queueRef.current = []; + }; + }, []); + + const value = useMemo( + () => ({ + showNotice: async (options) => { + await enqueue('notice', options); + }, + confirm: (options) => enqueue('confirm', options), + }), + [enqueue], + ); + + const isDanger = active?.tone === 'danger'; + const confirmLabel = active?.confirmLabel ?? (active?.mode === 'confirm' ? '确定' : '知道了'); + const cancelLabel = active?.cancelLabel ?? '取消'; + + return ( + + {children} + settle(false)} + transparent + visible={Boolean(active)} + > + + settle(false)} + style={styles.dismiss} + /> + {active ? ( + + + + + {active.title} + {active.message} + + {active.mode === 'confirm' ? ( + settle(false)} + style={[styles.action, styles.cancelAction]} + > + {cancelLabel} + + ) : null} + settle(true)} + style={[ + styles.action, + styles.primaryAction, + active.mode === 'confirm' && isDanger && styles.dangerAction, + ]} + > + {confirmLabel} + + + + ) : null} + + + + ); +} + +export function useAppDialog(): AppDialogContextValue { + const value = useContext(AppDialogContext); + if (!value) { + throw new Error('useAppDialog must be used within AppDialogProvider'); + } + return value; +} diff --git a/frontend/src/shared/components/BackButton.tsx b/frontend/src/shared/components/BackButton.tsx new file mode 100644 index 0000000..e228824 --- /dev/null +++ b/frontend/src/shared/components/BackButton.tsx @@ -0,0 +1,41 @@ +import { ChevronLeft } from 'lucide-react-native'; +import { Pressable, StyleSheet } from 'react-native'; + +import { colors } from '@/shared/theme'; + +type BackButtonProps = { + accessibilityLabel?: string; + onPress: () => void; +}; + +/** 统一页面层级返回入口的视觉和交互。 */ +export function BackButton({ accessibilityLabel = '返回', onPress }: BackButtonProps) { + return ( + [styles.button, pressed && styles.pressed]} + > + + + ); +} + +const styles = StyleSheet.create({ + button: { + alignItems: 'center', + backgroundColor: colors.surface, + borderColor: colors.line, + borderRadius: 12, + borderWidth: 1, + height: 40, + justifyContent: 'center', + width: 40, + }, + pressed: { + backgroundColor: colors.surfaceTint, + transform: [{ scale: 0.94 }], + }, +}); diff --git a/frontend/src/shared/components/BottomSheetFrame.styles.ts b/frontend/src/shared/components/BottomSheetFrame.styles.ts new file mode 100644 index 0000000..49094af --- /dev/null +++ b/frontend/src/shared/components/BottomSheetFrame.styles.ts @@ -0,0 +1,34 @@ +import { StyleSheet } from 'react-native'; + +import { colors } from '@/shared/theme'; + +import { sheetChromeStyles } from './sheetChrome.styles'; + +const localStyles = StyleSheet.create({ + keyboardAvoider: { flex: 1 }, + sheet: { + backgroundColor: colors.surface, + borderTopLeftRadius: 26, + borderTopRightRadius: 26, + paddingBottom: 28, + paddingHorizontal: 16, + paddingTop: 10, + }, + header: { + alignItems: 'flex-start', + flexDirection: 'row', + justifyContent: 'space-between', + marginBottom: 8, + paddingHorizontal: 4, + }, + eyebrow: { color: colors.muted, fontSize: 10, fontWeight: '700' }, + title: { color: colors.ink, fontSize: 22, fontWeight: '800', marginTop: 5 }, +}); + +export const bottomSheetFrameStyles = { + backdrop: sheetChromeStyles.backdrop, + dismiss: sheetChromeStyles.dismiss, + handle: sheetChromeStyles.handle, + close: sheetChromeStyles.close, + ...localStyles, +}; diff --git a/frontend/src/shared/components/BottomSheetFrame.tsx b/frontend/src/shared/components/BottomSheetFrame.tsx new file mode 100644 index 0000000..2b848dc --- /dev/null +++ b/frontend/src/shared/components/BottomSheetFrame.tsx @@ -0,0 +1,106 @@ +import type { ReactNode } from 'react'; +import { X } from 'lucide-react-native'; +import { + KeyboardAvoidingView, + Modal, + Platform, + Pressable, + Text, + View, + type StyleProp, + type ViewStyle, +} from 'react-native'; + +import { colors } from '@/shared/theme'; + +import { bottomSheetFrameStyles as styles } from './BottomSheetFrame.styles'; + +type BottomSheetFrameProps = { + visible: boolean; + onClose: () => void; + closeAccessibilityLabel: string; + children: ReactNode; + /** 标准 eyebrow + title 头;与 header 二选一。 */ + eyebrow?: string; + title?: string; + /** 自定义头部左侧内容(覆盖 eyebrow/title)。 */ + header?: ReactNode; + showClose?: boolean; + showHandle?: boolean; + sheetStyle?: StyleProp; + headerStyle?: StyleProp; + keyboardAvoiding?: boolean; + animationType?: 'slide' | 'fade' | 'none'; +}; + +/** + * 底部 Sheet 共用外壳:Modal → backdrop → dismiss → sheet → handle → header → content。 + */ +export function BottomSheetFrame({ + visible, + onClose, + closeAccessibilityLabel, + children, + eyebrow, + title, + header, + showClose = true, + showHandle = true, + sheetStyle, + headerStyle, + keyboardAvoiding = false, + animationType = 'slide', +}: BottomSheetFrameProps) { + const hasHeader = Boolean(header || title || eyebrow); + const headerLeft = header ?? ( + + {eyebrow ? {eyebrow} : null} + {title ? {title} : null} + + ); + + const body = ( + + + + {showHandle ? : null} + {hasHeader ? ( + + {headerLeft} + {showClose ? ( + + + + ) : null} + + ) : null} + {children} + + + ); + + return ( + + {keyboardAvoiding ? ( + + {body} + + ) : ( + body + )} + + ); +} diff --git a/frontend/src/shared/components/sheetChrome.styles.ts b/frontend/src/shared/components/sheetChrome.styles.ts new file mode 100644 index 0000000..9c8ac5b --- /dev/null +++ b/frontend/src/shared/components/sheetChrome.styles.ts @@ -0,0 +1,27 @@ +import { StyleSheet } from 'react-native'; + +/** 底部 Sheet 共用外壳:backdrop / dismiss / handle / close。 */ +export const sheetChromeStyles = StyleSheet.create({ + backdrop: { + backgroundColor: 'rgba(14, 23, 19, 0.46)', + flex: 1, + justifyContent: 'flex-end', + }, + dismiss: { bottom: 0, left: 0, position: 'absolute', right: 0, top: 0 }, + handle: { + alignSelf: 'center', + backgroundColor: '#D7D4CD', + borderRadius: 3, + height: 4, + marginBottom: 16, + width: 35, + }, + close: { + alignItems: 'center', + backgroundColor: '#ECE9E2', + borderRadius: 10, + height: 30, + justifyContent: 'center', + width: 30, + }, +}); diff --git a/frontend/src/shared/hooks/useCurrentDate.ts b/frontend/src/shared/hooks/useCurrentDate.ts new file mode 100644 index 0000000..f3e3c43 --- /dev/null +++ b/frontend/src/shared/hooks/useCurrentDate.ts @@ -0,0 +1,12 @@ +import { useEffect, useState } from 'react'; + +export function useCurrentDate() { + const [now, setNow] = useState(() => new Date()); + + useEffect(() => { + const timer = setInterval(() => setNow(new Date()), 60_000); + return () => clearInterval(timer); + }, []); + + return now; +} diff --git a/frontend/src/shared/theme/index.ts b/frontend/src/shared/theme/index.ts new file mode 100644 index 0000000..1c1e998 --- /dev/null +++ b/frontend/src/shared/theme/index.ts @@ -0,0 +1,25 @@ +export const colors = { + background: '#F4F5F1', + surface: '#FFFFFF', + surfaceTint: '#E9ECE7', + deep: '#15352B', + ink: '#1B2923', + sub: '#6C7972', + muted: '#98A29C', + line: '#DDE4DE', + lime: '#D7F36A', + limeSoft: '#EEF5D6', + mint: '#DDEFE5', + coral: '#E98C70', + purple: '#8E86D7', + peach: '#FFE1D6', + violet: '#E8E4FF', +} as const; + +export const spacing = { + xs: 4, + sm: 8, + md: 16, + lg: 20, + xl: 24, +} as const; diff --git a/frontend/src/shared/utils/date.ts b/frontend/src/shared/utils/date.ts new file mode 100644 index 0000000..7fe6195 --- /dev/null +++ b/frontend/src/shared/utils/date.ts @@ -0,0 +1,40 @@ +export const WEEKDAY_LABELS = ['一', '二', '三', '四', '五', '六', '日']; + +export function dateKey(date: Date) { + return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`; +} + +export function startOfWeek(date: Date) { + const day = date.getDay(); + const offset = day === 0 ? -6 : 1 - day; + return new Date(date.getFullYear(), date.getMonth(), date.getDate() + offset); +} + +export function addDays(date: Date, amount: number) { + return new Date(date.getFullYear(), date.getMonth(), date.getDate() + amount); +} + +export function startOfMonth(date: Date) { + return new Date(date.getFullYear(), date.getMonth(), 1); +} + +export function formatDate(date: Date) { + return `${date.getMonth() + 1}月${date.getDate()}日 · 星期${WEEKDAY_LABELS[date.getDay() === 0 ? 6 : date.getDay() - 1]}`; +} + +export function formatFullDate(date: Date) { + return `${date.getFullYear()}年${date.getMonth() + 1}月${date.getDate()}日 · 星期${WEEKDAY_LABELS[date.getDay() === 0 ? 6 : date.getDay() - 1]}`; +} + +export function formatMonthDay(date: Date) { + return `${date.getMonth() + 1}月${date.getDate()}日`; +} + +export function formatTimeValue(value: Date) { + return `${String(value.getHours()).padStart(2, '0')}:${String(value.getMinutes()).padStart(2, '0')}`; +} + +export function formatWeekRange(start: Date) { + const end = addDays(start, 6); + return `${formatMonthDay(start)}—${formatMonthDay(end)}`; +} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index fe061fa..6a7437c 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -1,7 +1,11 @@ { "extends": "./node_modules/expo/tsconfig.base.json", "compilerOptions": { - "strict": true + "strict": true, + "paths": { + "@/*": ["./src/*"], + "@test/*": ["./__tests__/*"] + } }, "exclude": [ "${configDir}/node_modules", From 1f5f1cf82505c20c75fa9f9e247f9e6787b8d0c9 Mon Sep 17 00:00:00 2001 From: mac Date: Fri, 31 Jul 2026 18:47:44 +0800 Subject: [PATCH 2/5] feat(frontend): add session transport and contracts --- frontend/.env.example | 16 +- .../app/session/sessionEndpoint.test.ts | 28 ++ .../fakes/schedule/scheduleConflicts.test.ts | 135 ++++++++ frontend/__tests__/fixtures.ts | 30 ++ .../storage/deviceIdStore.test.ts | 41 +++ .../infrastructure/ws/WsClient.test.ts | 164 ++++++++++ frontend/eslint.config.js | 140 ++++++++- frontend/src/app/session/SessionProvider.tsx | 290 ++++++++++++++++++ frontend/src/app/session/sessionEndpoint.ts | 15 + frontend/src/contracts/envelope.ts | 25 ++ frontend/src/contracts/index.ts | 6 + frontend/src/contracts/reminder.ts | 48 +++ frontend/src/contracts/schedule.ts | 134 ++++++++ frontend/src/contracts/session.ts | 34 ++ frontend/src/contracts/transport.ts | 8 + frontend/src/contracts/voice.ts | 66 ++++ frontend/src/dev/fakes/FakeWsServer.ts | 270 ++++++++++++++++ .../dev/fakes/schedule/scheduleConflicts.ts | 61 ++++ .../src/dev/fakes/schedule/scheduleFactory.ts | 39 +++ .../infrastructure/storage/deviceIdStore.ts | 150 +++++++++ frontend/src/infrastructure/ws/WsClient.ts | 250 +++++++++++++++ frontend/src/shared/types/geo.ts | 7 + frontend/src/shared/utils/requestId.ts | 4 + 23 files changed, 1958 insertions(+), 3 deletions(-) create mode 100644 frontend/__tests__/app/session/sessionEndpoint.test.ts create mode 100644 frontend/__tests__/dev/fakes/schedule/scheduleConflicts.test.ts create mode 100644 frontend/__tests__/fixtures.ts create mode 100644 frontend/__tests__/infrastructure/storage/deviceIdStore.test.ts create mode 100644 frontend/__tests__/infrastructure/ws/WsClient.test.ts create mode 100644 frontend/src/app/session/SessionProvider.tsx create mode 100644 frontend/src/app/session/sessionEndpoint.ts create mode 100644 frontend/src/contracts/envelope.ts create mode 100644 frontend/src/contracts/index.ts create mode 100644 frontend/src/contracts/reminder.ts create mode 100644 frontend/src/contracts/schedule.ts create mode 100644 frontend/src/contracts/session.ts create mode 100644 frontend/src/contracts/transport.ts create mode 100644 frontend/src/contracts/voice.ts create mode 100644 frontend/src/dev/fakes/FakeWsServer.ts create mode 100644 frontend/src/dev/fakes/schedule/scheduleConflicts.ts create mode 100644 frontend/src/dev/fakes/schedule/scheduleFactory.ts create mode 100644 frontend/src/infrastructure/storage/deviceIdStore.ts create mode 100644 frontend/src/infrastructure/ws/WsClient.ts create mode 100644 frontend/src/shared/types/geo.ts create mode 100644 frontend/src/shared/utils/requestId.ts diff --git a/frontend/.env.example b/frontend/.env.example index 877160b..58135df 100644 --- a/frontend/.env.example +++ b/frontend/.env.example @@ -1,2 +1,14 @@ -# Android emulator: 10.0.2.2 reaches the host machine. -EXPO_PUBLIC_API_URL=http://10.0.2.2:8000/api/v1 +# Baidu Maps browser-side AK with JavaScript API enabled. +# Allow localhost for web development and https://timeflow.local/* for the native WebView. +EXPO_PUBLIC_BAIDU_MAP_AK=replace-with-your-baidu-map-ak + +# Real backend WebSocket URL (required for release / production builds). +# The client appends its persisted device_id query parameter automatically. +# Local device example: ws://192.168.1.10:8000/ws +# Production example: wss://api.example.com/ws +EXPO_PUBLIC_WS_URL= + +# Use in-process FakeWsServer when EXPO_PUBLIC_WS_URL is empty. +# In __DEV__, Fake is allowed by default if this is unset. +# Release builds always ignore this flag and fail fast without a URL. +# EXPO_PUBLIC_USE_FAKE_WS=true diff --git a/frontend/__tests__/app/session/sessionEndpoint.test.ts b/frontend/__tests__/app/session/sessionEndpoint.test.ts new file mode 100644 index 0000000..457c936 --- /dev/null +++ b/frontend/__tests__/app/session/sessionEndpoint.test.ts @@ -0,0 +1,28 @@ +import { describe, expect, it } from '@jest/globals'; + +import { buildSessionWebSocketUrl, resolveSessionUserId } from '@/app/session/sessionEndpoint'; + +describe('session endpoint compatibility', () => { + it('adds the persisted device id to the backend WebSocket URL', () => { + expect(buildSessionWebSocketUrl('ws://127.0.0.1:8000/ws', 'device 1')).toBe( + 'ws://127.0.0.1:8000/ws?device_id=device+1', + ); + }); + + it('replaces a stale device id while preserving other query parameters', () => { + expect( + buildSessionWebSocketUrl('wss://api.example.com/ws?token=test&device_id=stale', 'current'), + ).toBe('wss://api.example.com/ws?token=test&device_id=current'); + }); + + it('rejects non-WebSocket URLs', () => { + expect(() => buildSessionWebSocketUrl('http://127.0.0.1:8000/ws', 'device_1')).toThrow( + '必须使用 ws:// 或 wss://', + ); + }); + + it('uses the MVP backend user when session.ready omits user_id', () => { + expect(resolveSessionUserId(undefined)).toBe('default_user'); + expect(resolveSessionUserId(' user_1 ')).toBe('user_1'); + }); +}); diff --git a/frontend/__tests__/dev/fakes/schedule/scheduleConflicts.test.ts b/frontend/__tests__/dev/fakes/schedule/scheduleConflicts.test.ts new file mode 100644 index 0000000..1a66b56 --- /dev/null +++ b/frontend/__tests__/dev/fakes/schedule/scheduleConflicts.test.ts @@ -0,0 +1,135 @@ +import { describe, expect, it } from '@jest/globals'; + +import type { Schedule, ScheduleUpsertCommand } from '@/contracts'; + +import { upsertSchedule } from '@/dev/fakes/schedule/scheduleConflicts'; + +function makeSchedule(overrides: Partial = {}): Schedule { + return { + id: 'schedule_existing', + user_id: 'default_user', + source_mode: 'manual', + schedule_type: 'time', + status: 'scheduled', + title: '已有日程', + notes: null, + start_time: new Date(2026, 6, 29, 9, 0).toISOString(), + end_time: new Date(2026, 6, 29, 10, 0).toISOString(), + timezone: 'Asia/Shanghai', + location_name: null, + location_address: null, + latitude: null, + longitude: null, + geofence_radius_meters: 100, + geofence_armed: false, + time_remind_offset_minutes: 15, + time_triggered_at: null, + geo_triggered_at: null, + system_schedule_ref_id: null, + system_alarm_ref_id: null, + created_at: new Date(2026, 6, 20, 10, 0).toISOString(), + updated_at: new Date(2026, 6, 20, 10, 0).toISOString(), + ...overrides, + }; +} + +function makeCommand(startHour: number, endHour: number | null): ScheduleUpsertCommand { + return { + type: 'schedule.upsert.command', + request_id: 'req_test', + payload: { + source_mode: 'manual', + schedule_type: 'time', + title: '新日程', + start_time: new Date(2026, 6, 29, startHour, 0).toISOString(), + end_time: endHour === null ? null : new Date(2026, 6, 29, endHour, 0).toISOString(), + }, + }; +} + +describe('upsertSchedule conflict detection', () => { + const existing = [makeSchedule()]; + + it('flags a schedule that overlaps an existing one', () => { + const result = upsertSchedule(makeCommand(9, 10), existing, 'schedule_new'); + expect(result.payload.conflicts.map((conflict) => conflict.schedule_id)).toEqual([ + 'schedule_existing', + ]); + }); + + it('flags a partial overlap', () => { + const result = upsertSchedule(makeCommand(9, 11), existing, 'schedule_new'); + expect(result.payload.conflicts).toHaveLength(1); + }); + + it('treats touching boundaries as a conflict', () => { + const result = upsertSchedule(makeCommand(10, 11), existing, 'schedule_new'); + expect(result.payload.conflicts).toHaveLength(1); + }); + + it('reports nothing for a non-overlapping slot', () => { + const result = upsertSchedule(makeCommand(11, 12), existing, 'schedule_new'); + expect(result.payload.conflicts).toHaveLength(0); + }); + + it('does not conflict a schedule with itself while editing', () => { + const result = upsertSchedule(makeCommand(9, 10), existing, 'schedule_existing'); + expect(result.payload.conflicts).toHaveLength(0); + }); + + it('ignores deleted schedules', () => { + const deleted = [makeSchedule({ status: 'deleted' })]; + const result = upsertSchedule(makeCommand(9, 10), deleted, 'schedule_new'); + expect(result.payload.conflicts).toHaveLength(0); + }); + + it('reports nothing when the new schedule has no start time', () => { + const command: ScheduleUpsertCommand = { + type: 'schedule.upsert.command', + request_id: 'req_test', + payload: { + source_mode: 'manual', + schedule_type: 'location', + title: '地点日程', + start_time: null, + }, + }; + const result = upsertSchedule(command, existing, 'schedule_new'); + expect(result.payload.conflicts).toHaveLength(0); + }); + + it('echoes the request id and schedule id back', () => { + const result = upsertSchedule(makeCommand(14, 15), existing, 'schedule_new'); + expect(result.request_id).toBe('req_test'); + expect(result.payload.schedule_id).toBe('schedule_new'); + }); + + it('ignores unparseable start times on the new command', () => { + const command: ScheduleUpsertCommand = { + type: 'schedule.upsert.command', + request_id: 'req_test', + payload: { + source_mode: 'manual', + schedule_type: 'time', + title: '坏时间', + start_time: 'not-a-date', + end_time: null, + }, + }; + expect(upsertSchedule(command, existing, 'schedule_new').payload.conflicts).toHaveLength(0); + }); + + it('ignores existing items whose times do not parse', () => { + const broken = [makeSchedule({ id: 'broken', start_time: 'bad', end_time: 'also-bad' })]; + expect( + upsertSchedule(makeCommand(9, 10), broken, 'schedule_new').payload.conflicts, + ).toHaveLength(0); + }); + + it('inherits geofence_armed from the existing schedule when omitted', () => { + const armed = [makeSchedule({ id: 'schedule_existing', geofence_armed: true })]; + const command = makeCommand(14, 15); + delete (command.payload as { geofence_armed?: boolean }).geofence_armed; + expect(upsertSchedule(command, armed, 'schedule_existing').payload.geofence_armed).toBe(true); + }); +}); diff --git a/frontend/__tests__/fixtures.ts b/frontend/__tests__/fixtures.ts new file mode 100644 index 0000000..4195d90 --- /dev/null +++ b/frontend/__tests__/fixtures.ts @@ -0,0 +1,30 @@ +import type { Schedule } from '@/contracts'; + +export function makeSchedule(overrides: Partial = {}): Schedule { + return { + id: 'schedule_test', + user_id: 'default_user', + source_mode: 'manual', + schedule_type: 'time', + status: 'scheduled', + title: '测试日程', + notes: null, + start_time: new Date(2026, 6, 29, 9, 5).toISOString(), + end_time: null, + timezone: 'Asia/Shanghai', + location_name: null, + location_address: null, + latitude: null, + longitude: null, + geofence_radius_meters: 100, + geofence_armed: false, + time_remind_offset_minutes: 15, + time_triggered_at: null, + geo_triggered_at: null, + system_schedule_ref_id: null, + system_alarm_ref_id: null, + created_at: new Date(2026, 6, 20, 10, 0).toISOString(), + updated_at: new Date(2026, 6, 20, 10, 0).toISOString(), + ...overrides, + }; +} diff --git a/frontend/__tests__/infrastructure/storage/deviceIdStore.test.ts b/frontend/__tests__/infrastructure/storage/deviceIdStore.test.ts new file mode 100644 index 0000000..6da89a4 --- /dev/null +++ b/frontend/__tests__/infrastructure/storage/deviceIdStore.test.ts @@ -0,0 +1,41 @@ +import { beforeEach, describe, expect, it, jest } from '@jest/globals'; + +const mockGetInfoAsync = jest.fn(async () => ({ exists: true })); +const mockReadAsStringAsync = jest.fn(async () => ' device_existing '); +const mockWriteAsStringAsync = jest.fn(async () => undefined); +const mockFileSystem = { + documentDirectory: 'file:///documents/', + getInfoAsync: mockGetInfoAsync, + readAsStringAsync: mockReadAsStringAsync, + writeAsStringAsync: mockWriteAsStringAsync, +}; + +jest.mock('react-native', () => ({ + Platform: { OS: 'android' }, +})); + +jest.mock('expo', () => ({ + requireOptionalNativeModule: jest.fn(() => mockFileSystem), +})); + +import { createDeviceIdStore } from '@/infrastructure/storage/deviceIdStore'; + +describe('native deviceIdStore', () => { + beforeEach(() => { + jest.clearAllMocks(); + mockGetInfoAsync.mockResolvedValue({ exists: true }); + mockReadAsStringAsync.mockResolvedValue(' device_existing '); + }); + + it('passes the options arguments required by Expo SDK 57 native methods', async () => { + const store = await createDeviceIdStore(); + + await expect(store.get()).resolves.toBe('device_existing'); + await store.set('device_updated'); + + const path = 'file:///documents/.timeflow-device-id'; + expect(mockGetInfoAsync).toHaveBeenCalledWith(path, {}); + expect(mockReadAsStringAsync).toHaveBeenCalledWith(path, {}); + expect(mockWriteAsStringAsync).toHaveBeenCalledWith(path, 'device_updated', {}); + }); +}); diff --git a/frontend/__tests__/infrastructure/ws/WsClient.test.ts b/frontend/__tests__/infrastructure/ws/WsClient.test.ts new file mode 100644 index 0000000..bebed06 --- /dev/null +++ b/frontend/__tests__/infrastructure/ws/WsClient.test.ts @@ -0,0 +1,164 @@ +import { describe, expect, it } from '@jest/globals'; + +import { FakeWsServer } from '@/dev/fakes/FakeWsServer'; +import { WsClient } from '@/infrastructure/ws/WsClient'; +import { makeSchedule } from '@test/fixtures'; + +describe('WsClient + FakeWsServer', () => { + it('completes session hello and lists schedules', async () => { + const server = new FakeWsServer({ userId: 'user_test' }); + const client = new WsClient({ fakeHandler: server.handleMessage }); + server.attach(client); + await client.connect(); + + const ready = new Promise((resolve) => { + client.onMessage((message) => { + if (!(message instanceof ArrayBuffer) && message.type === 'session.ready') { + resolve(); + } + }); + }); + client.sendJson({ + type: 'session.hello', + device_id: 'device_1', + app_version: '1.0.0', + }); + await ready; + + const list = await client.request({ + type: 'schedule.list.query', + request_id: 'req_list_1', + payload: { status: null, include_deleted: false }, + }); + expect(list.type).toBe('schedule.list.result'); + expect(list.ok).toBe(true); + client.close(); + }); + + it('upserts a schedule through fake WS', async () => { + const server = new FakeWsServer({ userId: 'user_test' }); + const client = new WsClient({ fakeHandler: server.handleMessage }); + server.attach(client); + await client.connect(); + + const response = await client.request({ + type: 'schedule.upsert.command', + request_id: 'req_up_1', + payload: { + source_mode: 'manual', + schedule_type: 'time', + title: '测试', + start_time: new Date(Date.now() + 3_600_000).toISOString(), + }, + }); + expect(response.ok).toBe(true); + expect(server.getSchedules()).toHaveLength(1); + client.close(); + }); + + it('acks delete without losing synchronous fake replies', async () => { + const server = new FakeWsServer({ + userId: 'user_test', + seedSchedules: [makeSchedule({ id: 'del_sync', user_id: 'user_test' })], + }); + const client = new WsClient({ fakeHandler: server.handleMessage }); + server.attach(client); + await client.connect(); + + const ack = await client.request({ + type: 'schedule.deleted', + request_id: 'req_del_sync', + schedule_id: 'del_sync', + deleted: true, + timestamp: new Date().toISOString(), + }); + expect(ack.type).toBe('schedule.deleted.ack'); + expect(ack.ok).toBe(true); + expect(server.getSchedules()[0]?.status).toBe('deleted'); + client.close(); + }); + + it('updates status to done without marking deleted', async () => { + const server = new FakeWsServer({ + userId: 'user_test', + seedSchedules: [makeSchedule({ id: 'status_1', user_id: 'user_test' })], + }); + const client = new WsClient({ fakeHandler: server.handleMessage }); + server.attach(client); + await client.connect(); + + const response = await client.request({ + type: 'schedule.status.command', + request_id: 'req_status_1', + payload: { schedule_id: 'status_1', status: 'done' }, + }); + expect(response.ok).toBe(true); + expect(server.getSchedules()[0]?.status).toBe('done'); + client.close(); + }); + + it('rejects pending requests immediately when the remote socket closes unexpectedly', async () => { + class TestWebSocket { + static readonly OPEN = 1; + static instance: TestWebSocket | null = null; + + binaryType = ''; + readyState = 0; + onopen: (() => void) | null = null; + onerror: (() => void) | null = null; + onclose: (() => void) | null = null; + onmessage: ((event: { data: unknown }) => void) | null = null; + + constructor(_url: string) { + TestWebSocket.instance = this; + } + + send(_data: string | ArrayBuffer) {} + + close() { + this.readyState = 3; + this.onclose?.(); + } + + open() { + this.readyState = TestWebSocket.OPEN; + this.onopen?.(); + } + + closeUnexpectedly() { + this.readyState = 3; + this.onclose?.(); + } + } + + const originalWebSocket = globalThis.WebSocket; + Object.defineProperty(globalThis, 'WebSocket', { + configurable: true, + value: TestWebSocket, + writable: true, + }); + + try { + const client = new WsClient({ url: 'ws://test.invalid', requestTimeoutMs: 60_000 }); + const connecting = client.connect(); + TestWebSocket.instance?.open(); + await connecting; + + const pending = client.request({ + type: 'schedule.list.query', + request_id: 'req_disconnect', + payload: { status: null, include_deleted: false }, + }); + TestWebSocket.instance?.closeUnexpectedly(); + + await expect(pending).rejects.toThrow('WebSocket closed unexpectedly'); + expect(client.getConnectionStatus()).toBe('closed'); + } finally { + Object.defineProperty(globalThis, 'WebSocket', { + configurable: true, + value: originalWebSocket, + writable: true, + }); + } + }); +}); diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js index 5220552..d9e20b2 100644 --- a/frontend/eslint.config.js +++ b/frontend/eslint.config.js @@ -8,18 +8,148 @@ module.exports = defineConfig([ expoConfig, prettierConfig, { - ignores: ['dist/**', '.expo/**', 'web-build/**', 'node_modules/**'], + ignores: [ + 'dist/**', + '.expo/**', + 'web-build/**', + 'node_modules/**', + '_backup_*/**', + '_shots/**', + '**/*.apk', + 'android/**', + 'ios/**', + 'modules/**', + ], }, { rules: { 'no-console': ['warn', { allow: ['warn', 'error'] }], }, }, + { + files: ['src/features/**/*.ts', 'src/features/**/*.tsx'], + rules: { + 'no-restricted-imports': [ + 'error', + { + patterns: [ + { + group: [ + '@/app', + '@/app/*', + '@/features/*', + '@/features/*/*', + '@/infrastructure', + '@/infrastructure/*', + ], + message: + 'Features may depend only on contracts/shared and their own relative modules. Compose adapters in app.', + }, + ], + }, + ], + }, + }, + { + files: [ + 'src/contracts/**/*.ts', + 'src/contracts/**/*.tsx', + 'src/shared/**/*.ts', + 'src/shared/**/*.tsx', + ], + rules: { + 'no-restricted-imports': [ + 'error', + { + patterns: [ + { + group: [ + '@/app', + '@/app/*', + '@/dev', + '@/dev/*', + '@/features/*', + '@/features/*/*', + '@/infrastructure', + '@/infrastructure/*', + ], + message: 'Contracts/shared must not depend on app, dev, features, or infrastructure.', + }, + ], + }, + ], + }, + }, + { + files: ['src/infrastructure/**/*.ts', 'src/infrastructure/**/*.tsx'], + rules: { + 'no-restricted-imports': [ + 'error', + { + patterns: [ + { + group: ['@/app', '@/app/*', '@/dev', '@/dev/*', '@/features/*', '@/features/*/*'], + message: 'Infrastructure must not depend on app, dev, or feature implementations.', + }, + ], + }, + ], + }, + }, + { + files: ['src/dev/**/*.ts', 'src/dev/**/*.tsx'], + rules: { + 'no-restricted-imports': [ + 'error', + { + patterns: [ + { + group: ['@/features/*', '@/features/*/*'], + message: 'Development fakes must not depend on feature-private implementations.', + }, + ], + }, + ], + }, + }, + { + files: ['src/app/**/*.ts', 'src/app/**/*.tsx', 'App.tsx'], + rules: { + 'no-restricted-imports': [ + 'error', + { + patterns: [ + { + group: [ + '@/features/*/hooks/*', + '@/features/*/components/*', + '@/features/*/data/*', + '@/features/*/domain/*', + '@/features/*/application/*', + '@/features/*/calendar/*', + '@/features/*/editor/*', + '@/features/*/detail/*', + '@/features/*/screens/*', + '@/features/*/model/*', + '@/features/*/location/*', + '@/features/*/native/*', + '@/features/*/presentation/*', + '@/features/*/services/*', + '@/features/*/utils/*', + ], + message: 'Import from @/features/ public entry instead of deep paths.', + }, + ], + }, + ], + }, + }, { files: ['__tests__/**/*.ts', '__tests__/**/*.tsx'], rules: { '@typescript-eslint/no-require-imports': 'off', 'import/first': 'off', + 'no-restricted-imports': 'off', }, }, { @@ -30,4 +160,12 @@ module.exports = defineConfig([ }, }, }, + { + files: ['react-native.config.js'], + languageOptions: { + globals: { + __dirname: 'readonly', + }, + }, + }, ]); diff --git a/frontend/src/app/session/SessionProvider.tsx b/frontend/src/app/session/SessionProvider.tsx new file mode 100644 index 0000000..e87828f --- /dev/null +++ b/frontend/src/app/session/SessionProvider.tsx @@ -0,0 +1,290 @@ +import { + createContext, + useContext, + useEffect, + useMemo, + useRef, + useState, + type ReactNode, +} from 'react'; + +import type { ConnectionStatus, SessionHello, SessionReady, WsJsonMessage } from '@/contracts'; + +import { FakeWsServer } from '@/dev/fakes/FakeWsServer'; +import { getOrCreateDeviceId, type DeviceIdStore } from '@/infrastructure/storage/deviceIdStore'; +import { WsClient } from '@/infrastructure/ws/WsClient'; + +import { buildSessionWebSocketUrl, resolveSessionUserId } from './sessionEndpoint'; + +export type SessionTransportMode = 'remote' | 'fake' | 'unavailable'; + +export type SessionContextValue = { + deviceId: string | null; + userId: string | null; + connectionStatus: ConnectionStatus; + transportMode: SessionTransportMode; + /** 每次成功 session.ready 递增,供 schedule 重连后 resync。 */ + sessionEpoch: number; + client: WsClient | null; + fakeServer: FakeWsServer | null; + connectionError: string | null; +}; + +const SessionContext = createContext(null); + +function resolveWsUrl(): string | null { + const fromEnv = + typeof process !== 'undefined' ? process.env.EXPO_PUBLIC_WS_URL?.trim() : undefined; + return fromEnv || null; +} + +function resolveAllowFake(): boolean { + const flag = + typeof process !== 'undefined' ? process.env.EXPO_PUBLIC_USE_FAKE_WS?.trim() : undefined; + const isDevBuild = typeof __DEV__ !== 'undefined' && __DEV__; + // Fake 只能进入开发/调试构建;release 即使误带变量也必须拒绝。 + if (!isDevBuild) return false; + if (flag === '0' || flag === 'false') return false; + return flag === '1' || flag === 'true' || flag == null; +} + +const RECONNECT_BASE_MS = 1000; +const RECONNECT_MAX_MS = 30_000; +const SESSION_READY_TIMEOUT_MS = 10_000; +const UNAVAILABLE_CONNECTION_ERROR = + '缺少 EXPO_PUBLIC_WS_URL。开发环境可设置 EXPO_PUBLIC_USE_FAKE_WS=true 使用进程内 Fake。'; + +function isSessionReady(message: WsJsonMessage, deviceId: string): message is SessionReady { + return ( + message.type === 'session.ready' && + message.device_id === deviceId && + (message.user_id == null || + (typeof message.user_id === 'string' && message.user_id.trim().length > 0)) && + typeof message.server_time === 'string' + ); +} + +export function SessionProvider({ + children, + deviceIdStore, +}: { + children: ReactNode; + deviceIdStore?: DeviceIdStore; +}) { + const [deviceId, setDeviceId] = useState(null); + const [userId, setUserId] = useState(null); + const [connectionStatus, setConnectionStatus] = useState('idle'); + const [sessionEpoch, setSessionEpoch] = useState(0); + const [connectionError, setConnectionError] = useState(null); + + const url = useMemo(() => resolveWsUrl(), []); + const allowFake = useMemo(() => resolveAllowFake(), []); + const transportMode: SessionTransportMode = url ? 'remote' : allowFake ? 'fake' : 'unavailable'; + + const remoteEndpoint = useMemo(() => { + if (transportMode !== 'remote' || !url || !deviceId) { + return { url: null, error: null }; + } + try { + return { url: buildSessionWebSocketUrl(url, deviceId), error: null }; + } catch (error) { + return { + url: null, + error: error instanceof Error ? error.message : 'WebSocket 地址不合法', + }; + } + }, [deviceId, transportMode, url]); + + const { client, fakeServer } = useMemo(() => { + if (transportMode === 'unavailable') { + return { client: null as WsClient | null, fakeServer: null as FakeWsServer | null }; + } + if (transportMode === 'remote' && !remoteEndpoint.url) { + return { client: null as WsClient | null, fakeServer: null as FakeWsServer | null }; + } + const server = transportMode === 'fake' ? new FakeWsServer() : null; + const ws = new WsClient({ + url: transportMode === 'remote' ? remoteEndpoint.url : null, + fakeHandler: server ? server.handleMessage : undefined, + }); + server?.attach(ws); + return { client: ws, fakeServer: server }; + }, [remoteEndpoint.url, transportMode]); + + const reconnectAttempt = useRef(0); + const reconnectTimer = useRef | null>(null); + + useEffect(() => { + let cancelled = false; + void getOrCreateDeviceId(deviceIdStore) + .then((id) => { + if (!cancelled) setDeviceId(id); + }) + .catch((error: unknown) => { + if (cancelled) return; + setConnectionStatus('error'); + setConnectionError( + error instanceof Error ? error.message : '无法初始化设备身份,请检查原生存储配置', + ); + }); + return () => { + cancelled = true; + }; + }, [deviceIdStore]); + + useEffect(() => { + if (!client) return; + if (!deviceId) return; + + let cancelled = false; + let sessionReadyTimer: ReturnType | null = null; + + const clearReconnect = () => { + if (reconnectTimer.current) { + clearTimeout(reconnectTimer.current); + reconnectTimer.current = null; + } + }; + + const clearSessionReadyTimer = () => { + if (!sessionReadyTimer) return; + clearTimeout(sessionReadyTimer); + sessionReadyTimer = null; + }; + + const sendHello = () => { + const hello: SessionHello = { + type: 'session.hello', + device_id: deviceId, + app_version: '1.0.0', + }; + clearSessionReadyTimer(); + sessionReadyTimer = setTimeout(() => { + if (cancelled) return; + setConnectionStatus('error'); + setConnectionError('会话握手超时,请检查服务连接'); + client.close(); + scheduleReconnect(); + }, SESSION_READY_TIMEOUT_MS); + client.sendJson(hello); + }; + + const connectOnce = async () => { + try { + setConnectionError(null); + await client.connect(); + if (cancelled) return; + reconnectAttempt.current = 0; + sendHello(); + } catch (error) { + if (cancelled) return; + clearSessionReadyTimer(); + client.close(); + setConnectionStatus('error'); + setConnectionError(error instanceof Error ? error.message : 'WebSocket 连接失败'); + scheduleReconnect(); + } + }; + + const scheduleReconnect = () => { + if (cancelled || transportMode === 'fake') return; + clearReconnect(); + const attempt = reconnectAttempt.current; + const delay = Math.min(RECONNECT_BASE_MS * 2 ** attempt, RECONNECT_MAX_MS); + reconnectAttempt.current = attempt + 1; + setConnectionStatus('reconnecting'); + reconnectTimer.current = setTimeout(() => { + void connectOnce(); + }, delay); + }; + + const unsubscribeStatus = client.onStatus((status) => { + if (cancelled) return; + // WebSocket open 只代表 socket 可用;session.ready 才代表身份握手完成。 + setConnectionStatus(status === 'ready' ? 'connecting' : status); + if (status === 'closed') { + clearSessionReadyTimer(); + scheduleReconnect(); + } + }); + + const unsubscribeMessage = client.onMessage((message) => { + if (message instanceof ArrayBuffer) return; + if (isSessionReady(message, deviceId)) { + clearSessionReadyTimer(); + setUserId(resolveSessionUserId(message.user_id)); + setSessionEpoch((value) => value + 1); + setConnectionStatus('ready'); + setConnectionError(null); + return; + } + if (message.type === 'session.error') { + clearSessionReadyTimer(); + setConnectionStatus('error'); + setConnectionError( + typeof message.error === 'object' && + message.error !== null && + 'message' in message.error && + typeof message.error.message === 'string' + ? message.error.message + : '会话握手失败', + ); + } + }); + + void connectOnce(); + + return () => { + cancelled = true; + clearReconnect(); + clearSessionReadyTimer(); + unsubscribeStatus(); + unsubscribeMessage(); + client.close(); + }; + }, [client, deviceId, transportMode]); + + const effectiveConnectionStatus: ConnectionStatus = + transportMode === 'unavailable' || remoteEndpoint.error || (connectionError && !client) + ? 'error' + : client + ? connectionStatus + : 'connecting'; + const effectiveConnectionError = + transportMode === 'unavailable' + ? UNAVAILABLE_CONNECTION_ERROR + : (remoteEndpoint.error ?? connectionError); + + const value = useMemo( + () => ({ + deviceId, + userId, + connectionStatus: effectiveConnectionStatus, + transportMode, + sessionEpoch, + client, + fakeServer, + connectionError: effectiveConnectionError, + }), + [ + client, + deviceId, + effectiveConnectionError, + effectiveConnectionStatus, + fakeServer, + sessionEpoch, + transportMode, + userId, + ], + ); + + return {children}; +} + +export function useSession(): SessionContextValue { + const value = useContext(SessionContext); + if (!value) { + throw new Error('useSession must be used within SessionProvider'); + } + return value; +} diff --git a/frontend/src/app/session/sessionEndpoint.ts b/frontend/src/app/session/sessionEndpoint.ts new file mode 100644 index 0000000..6b49fd8 --- /dev/null +++ b/frontend/src/app/session/sessionEndpoint.ts @@ -0,0 +1,15 @@ +const LEGACY_BACKEND_USER_ID = 'default_user'; + +export function buildSessionWebSocketUrl(baseUrl: string, deviceId: string): string { + const url = new URL(baseUrl); + if (url.protocol !== 'ws:' && url.protocol !== 'wss:') { + throw new Error('EXPO_PUBLIC_WS_URL 必须使用 ws:// 或 wss://'); + } + url.searchParams.set('device_id', deviceId); + return url.toString(); +} + +/** Current MVP backend owns a single default user but omits it from session.ready. */ +export function resolveSessionUserId(userId: unknown): string { + return typeof userId === 'string' && userId.trim() ? userId.trim() : LEGACY_BACKEND_USER_ID; +} diff --git a/frontend/src/contracts/envelope.ts b/frontend/src/contracts/envelope.ts new file mode 100644 index 0000000..f7ff041 --- /dev/null +++ b/frontend/src/contracts/envelope.ts @@ -0,0 +1,25 @@ +export type ApiError = { + code: string; + message: string; + details: Record | null; +}; + +export type WsRequest = { + type: TType; + request_id: string; + payload: TPayload; +}; + +export type WsSuccess = { + type: TType; + request_id: string; + ok: true; + payload: TPayload; +}; + +export type WsFailure = { + type: TType; + request_id: string; + ok: false; + error: ApiError; +}; diff --git a/frontend/src/contracts/index.ts b/frontend/src/contracts/index.ts new file mode 100644 index 0000000..5b1d678 --- /dev/null +++ b/frontend/src/contracts/index.ts @@ -0,0 +1,6 @@ +export type * from './envelope'; +export type * from './reminder'; +export type * from './schedule'; +export type * from './session'; +export type * from './transport'; +export type * from './voice'; diff --git a/frontend/src/contracts/reminder.ts b/frontend/src/contracts/reminder.ts new file mode 100644 index 0000000..7936d25 --- /dev/null +++ b/frontend/src/contracts/reminder.ts @@ -0,0 +1,48 @@ +import type { ApiError } from './envelope'; + +/** + * 提醒通道协议(预留)。 + * 当前前端尚未接入 WS 提醒控制 / TTS 音频流;类型仅作与后端对齐的权威契约文档。 + * 接入客户端前请勿在业务层依赖这些消息。 + */ + +/** 服务端下发提醒控制;具体展示通道由客户端根据前后台自行决定。 */ +export type ReminderControl = { + type: 'reminder.control'; + schedule_id: string; + reason: string; + action: 'show'; +}; + +export type ReminderControlAck = + | { type: 'reminder.control.ack'; schedule_id: string; ok: true } + | { type: 'reminder.control.ack'; schedule_id: string; ok: false; error: ApiError }; + +/** 提醒 TTS 音频流开始;随后通过同一 WebSocket 连接发送 Binary Frame。 */ +export type ReminderAudioStart = { + type: 'reminder.audio.start'; + schedule_id: string; + stream_id: string; + audio_format: 'mp3'; +}; + +export type ReminderAudioEnd = { + type: 'reminder.audio.end'; + schedule_id: string; + stream_id: string; +}; + +export type ReminderAudioAck = + | { + type: 'reminder.audio.ack'; + schedule_id: string; + stream_id: string; + ok: true; + } + | { + type: 'reminder.audio.ack'; + schedule_id: string; + stream_id: string; + ok: false; + error: ApiError; + }; diff --git a/frontend/src/contracts/schedule.ts b/frontend/src/contracts/schedule.ts new file mode 100644 index 0000000..9978407 --- /dev/null +++ b/frontend/src/contracts/schedule.ts @@ -0,0 +1,134 @@ +import type { ApiError, WsFailure, WsRequest, WsSuccess } from './envelope'; + +export type ScheduleSourceMode = 'manual' | 'voice'; +export type ScheduleType = 'time' | 'location'; +export type ScheduleStatus = 'scheduled' | 'done' | 'deleted'; + +export type Schedule = { + id: string; + user_id: string; + source_mode: ScheduleSourceMode; + schedule_type: ScheduleType; + status: ScheduleStatus; + title: string; + notes: string | null; + start_time: string | null; + end_time: string | null; + timezone: string | null; + location_name: string | null; + location_address: string | null; + latitude: number | null; + longitude: number | null; + geofence_radius_meters: number; + geofence_armed: boolean; + time_remind_offset_minutes: number; + time_triggered_at: string | null; + geo_triggered_at: string | null; + system_schedule_ref_id: string | null; + system_alarm_ref_id: string | null; + created_at: string; + updated_at: string; +}; + +export type ScheduleListQueryPayload = { + status: ScheduleStatus | null; + include_deleted: boolean; +}; + +export type ScheduleListQuery = WsRequest<'schedule.list.query', ScheduleListQueryPayload>; + +export type ScheduleListResultPayload = { + schedules: Schedule[]; +}; + +export type ScheduleListResult = WsSuccess<'schedule.list.result', ScheduleListResultPayload>; + +export type ScheduleListError = WsFailure<'schedule.list.error'>; +export type ScheduleListResponse = ScheduleListResult | ScheduleListError; + +export type ScheduleConflict = { + schedule_id: string; + title: string; + start_time: string; + end_time: string | null; +}; + +/** 草稿业务字段(创建/语音解析共用,不含 schedule_id / source_mode)。 */ +export type ScheduleDraftFields = { + schedule_type: ScheduleType; + title: string; + notes?: string | null; + start_time?: string | null; + end_time?: string | null; + timezone?: string | null; + location_name?: string | null; + location_address?: string | null; + latitude?: number | null; + longitude?: number | null; + geofence_radius_meters?: number | null; + geofence_armed?: boolean | null; + time_remind_offset_minutes?: number | null; +}; + +export type ScheduleUpsertPayload = ScheduleDraftFields & { + schedule_id?: string | null; + source_mode: ScheduleSourceMode; +}; + +export type ScheduleUpsertCommand = WsRequest<'schedule.upsert.command', ScheduleUpsertPayload>; + +export type ScheduleUpsertResultPayload = { + schedule_id: string; + schedule_type: ScheduleType; + status: ScheduleStatus; + conflicts: ScheduleConflict[]; + geofence_armed: boolean; +}; + +export type ScheduleUpsertResult = WsSuccess<'schedule.upsert.result', ScheduleUpsertResultPayload>; + +export type ScheduleUpsertError = WsFailure<'schedule.upsert.error'>; +export type ScheduleUpsertResponse = ScheduleUpsertResult | ScheduleUpsertError; + +/** 完成 / 恢复为已安排;与删除语义分离。 */ +export type ScheduleStatusUpdatePayload = { + schedule_id: string; + status: Extract; +}; + +export type ScheduleStatusUpdateCommand = WsRequest< + 'schedule.status.command', + ScheduleStatusUpdatePayload +>; + +export type ScheduleStatusUpdateResultPayload = { + schedule_id: string; + status: ScheduleStatus; +}; + +export type ScheduleStatusUpdateResult = WsSuccess< + 'schedule.status.result', + ScheduleStatusUpdateResultPayload +>; + +export type ScheduleStatusUpdateError = WsFailure<'schedule.status.error'>; +export type ScheduleStatusUpdateResponse = ScheduleStatusUpdateResult | ScheduleStatusUpdateError; + +/** 客户端确认删除后,通知服务端取消监听与提醒(仅删除,不含完成)。 */ +export type ScheduleDeleted = { + type: 'schedule.deleted'; + request_id: string; + schedule_id: string; + deleted: true; + timestamp: string; +}; + +export type ScheduleDeletedAck = + | { type: 'schedule.deleted.ack'; request_id?: string; schedule_id: string; ok: true } + | { + type: 'schedule.deleted.ack'; + request_id?: string; + schedule_id: string; + ok: false; + error: ApiError; + }; diff --git a/frontend/src/contracts/session.ts b/frontend/src/contracts/session.ts new file mode 100644 index 0000000..1029e6d --- /dev/null +++ b/frontend/src/contracts/session.ts @@ -0,0 +1,34 @@ +import type { ApiError, WsFailure, WsRequest, WsSuccess } from './envelope'; + +export type SessionHello = { + type: 'session.hello'; + device_id: string; + app_version: string; +}; + +export type SessionReady = { + type: 'session.ready'; + device_id: string; + /** Newer servers return this; the current single-user MVP server omits it. */ + user_id?: string; + server_time: string; +}; + +export type SessionError = { + type: 'session.error'; + ok: false; + error: ApiError; +}; + +export type LocationReportPayload = { + schedule_scope: 'current'; + latitude: number; + longitude: number; + accuracy: number; + timestamp: string; +}; + +export type LocationReport = WsRequest<'location.report', LocationReportPayload>; + +export type LocationReportAck = + WsSuccess<'location.report.ack', null> | WsFailure<'location.report.ack'>; diff --git a/frontend/src/contracts/transport.ts b/frontend/src/contracts/transport.ts new file mode 100644 index 0000000..9530433 --- /dev/null +++ b/frontend/src/contracts/transport.ts @@ -0,0 +1,8 @@ +export type ConnectionStatus = + 'idle' | 'connecting' | 'ready' | 'reconnecting' | 'closed' | 'error'; + +export type WsJsonMessage = { + type: string; + request_id?: string; + [key: string]: unknown; +}; diff --git a/frontend/src/contracts/voice.ts b/frontend/src/contracts/voice.ts new file mode 100644 index 0000000..730d98e --- /dev/null +++ b/frontend/src/contracts/voice.ts @@ -0,0 +1,66 @@ +import type { ApiError, WsFailure, WsRequest, WsSuccess } from './envelope'; +import type { ScheduleDraftFields } from './schedule'; + +export type VoiceStreamStartPayload = { + audio_format: 'pcm_s16le'; + sample_rate_hz: number; + channels: number; +}; + +export type VoiceStreamStartCommand = WsRequest<'voice.stream.start', VoiceStreamStartPayload>; + +export type VoiceStreamEndPayload = { + stream_id: string; +}; + +export type VoiceStreamEndCommand = WsRequest<'voice.stream.end', VoiceStreamEndPayload>; + +export type VoiceStreamError = WsFailure<'voice.stream.error'>; + +export type VoiceStreamCancelPayload = { + stream_id: string; + job_id: string | null; +}; + +export type VoiceStreamCancelCommand = WsRequest<'voice.stream.cancel', VoiceStreamCancelPayload>; + +export type VoiceStreamCancelAck = + | WsSuccess<'voice.stream.cancelled', { stream_id: string }> + | VoiceStreamError + | WsFailure<'voice.stream.cancel'>; + +export type VoiceStreamStarted = WsSuccess< + 'voice.stream.started', + { stream_id: string; job_id: string } +>; + +export type VoiceStreamEnded = WsSuccess< + 'voice.stream.ended', + { stream_id: string; job_id: string; status: 'processing' } +>; + +export type VoiceStreamStartResponse = VoiceStreamStarted | VoiceStreamError; +export type VoiceStreamEndResponse = VoiceStreamEnded | VoiceStreamError; + +export type VoiceParseDraft = Omit; + +export type VoiceParseReadyResult = { + type: 'voice.parse.result'; + request_id: string; + job_id: string; + status: 'ready_for_confirmation'; + draft: VoiceParseDraft; + missing_fields: string[]; + ambiguous_fields: string[]; + needs_confirmation: true; +}; + +export type VoiceParseFailedResult = { + type: 'voice.parse.result'; + request_id: string; + job_id: string; + status: 'failed'; + error: ApiError; +}; + +export type VoiceParseResultMessage = VoiceParseReadyResult | VoiceParseFailedResult; diff --git a/frontend/src/dev/fakes/FakeWsServer.ts b/frontend/src/dev/fakes/FakeWsServer.ts new file mode 100644 index 0000000..136323e --- /dev/null +++ b/frontend/src/dev/fakes/FakeWsServer.ts @@ -0,0 +1,270 @@ +import type { + LocationReport, + LocationReportAck, + Schedule, + ScheduleDeleted, + ScheduleDeletedAck, + ScheduleListQuery, + ScheduleListResponse, + ScheduleStatusUpdateCommand, + ScheduleStatusUpdateResponse, + ScheduleUpsertCommand, + ScheduleUpsertResponse, + SessionHello, + SessionReady, + VoiceParseResultMessage, + VoiceStreamCancelCommand, + VoiceStreamEndCommand, + VoiceStreamStartCommand, + VoiceStreamStartResponse, + VoiceStreamEndResponse, + WsJsonMessage, +} from '@/contracts'; +import type { WsClient } from '@/infrastructure/ws/WsClient'; + +import { upsertSchedule } from './schedule/scheduleConflicts'; +import { createFakeSchedule } from './schedule/scheduleFactory'; + +type FakeWsServerOptions = { + userId?: string; + seedSchedules?: Schedule[]; +}; + +/** + * 进程内 Fake WS:只依赖 contracts + WsClient,供本地与测试使用。 + */ +export class FakeWsServer { + private readonly schedules = new Map(); + private readonly userId: string; + private client: WsClient | null = null; + private voiceJobCounter = 0; + + constructor(options: FakeWsServerOptions = {}) { + this.userId = options.userId ?? 'user_fake_1'; + for (const schedule of options.seedSchedules ?? []) { + this.schedules.set(schedule.id, schedule); + } + } + + attach(client: WsClient): void { + this.client = client; + } + + getUserId(): string { + return this.userId; + } + + getSchedules(): Schedule[] { + return [...this.schedules.values()]; + } + + handleMessage = async (message: WsJsonMessage | ArrayBuffer): Promise => { + if (message instanceof ArrayBuffer) { + return; + } + + switch (message.type) { + case 'session.hello': + this.handleSessionHello(message as SessionHello); + return; + case 'schedule.list.query': + this.handleList(message as ScheduleListQuery); + return; + case 'schedule.upsert.command': + this.handleUpsert(message as ScheduleUpsertCommand); + return; + case 'schedule.status.command': + this.handleStatusUpdate(message as ScheduleStatusUpdateCommand); + return; + case 'schedule.deleted': + this.handleDeleted(message as ScheduleDeleted); + return; + case 'location.report': + this.handleLocationReport(message as LocationReport); + return; + case 'voice.stream.start': + this.handleVoiceStart(message as VoiceStreamStartCommand); + return; + case 'voice.stream.end': + this.handleVoiceEnd(message as VoiceStreamEndCommand); + return; + case 'voice.stream.cancel': + this.handleVoiceCancel(message as VoiceStreamCancelCommand); + return; + default: + return; + } + }; + + private reply(message: WsJsonMessage): void { + this.client?.emitFromServer(message); + } + + private handleSessionHello(message: SessionHello): void { + const ready: SessionReady = { + type: 'session.ready', + device_id: message.device_id, + user_id: this.userId, + server_time: new Date().toISOString(), + }; + this.reply(ready); + } + + private handleList(message: ScheduleListQuery): void { + const includeDeleted = message.payload.include_deleted; + const statusFilter = message.payload.status; + const schedules = [...this.schedules.values()].filter((item) => { + if (!includeDeleted && item.status === 'deleted') return false; + if (statusFilter && item.status !== statusFilter) return false; + return true; + }); + const response: ScheduleListResponse = { + type: 'schedule.list.result', + request_id: message.request_id, + ok: true, + payload: { schedules }, + }; + this.reply(response); + } + + private handleUpsert(message: ScheduleUpsertCommand): void { + const scheduleId = message.payload.schedule_id ?? `schedule_${Date.now()}`; + const current = [...this.schedules.values()]; + const existing = this.schedules.get(scheduleId) ?? null; + const result = upsertSchedule(message, current, scheduleId); + const entity = createFakeSchedule({ + draft: { ...message.payload, schedule_id: scheduleId }, + scheduleId, + userId: this.userId, + status: result.payload.status, + geofenceArmed: result.payload.geofence_armed, + existing, + }); + this.schedules.set(scheduleId, entity); + const response: ScheduleUpsertResponse = result; + this.reply(response); + this.reply({ + type: 'schedule.updated', + schedule: entity, + }); + } + + private handleStatusUpdate(message: ScheduleStatusUpdateCommand): void { + const existing = this.schedules.get(message.payload.schedule_id); + if (!existing || existing.status === 'deleted') { + const response: ScheduleStatusUpdateResponse = { + type: 'schedule.status.error', + request_id: message.request_id, + ok: false, + error: { + code: 'schedule_not_found', + message: '日程不存在或已删除', + details: null, + }, + }; + this.reply(response); + return; + } + + const next: Schedule = { + ...existing, + status: message.payload.status, + updated_at: new Date().toISOString(), + }; + this.schedules.set(next.id, next); + const response: ScheduleStatusUpdateResponse = { + type: 'schedule.status.result', + request_id: message.request_id, + ok: true, + payload: { schedule_id: next.id, status: next.status }, + }; + this.reply(response); + this.reply({ type: 'schedule.updated', schedule: next }); + } + + private handleDeleted(message: ScheduleDeleted): void { + const existing = this.schedules.get(message.schedule_id); + if (existing) { + const next: Schedule = { + ...existing, + status: 'deleted', + updated_at: new Date().toISOString(), + }; + this.schedules.set(message.schedule_id, next); + this.reply({ type: 'schedule.updated', schedule: next }); + } + const ack: ScheduleDeletedAck = { + type: 'schedule.deleted.ack', + request_id: message.request_id, + schedule_id: message.schedule_id, + ok: true, + }; + this.reply(ack); + } + + private handleLocationReport(_message: LocationReport): void { + const ack: LocationReportAck = { + type: 'location.report.ack', + request_id: _message.request_id, + ok: true, + payload: null, + }; + this.reply(ack); + } + + private handleVoiceStart(message: VoiceStreamStartCommand): void { + this.voiceJobCounter += 1; + const streamId = `stream_${this.voiceJobCounter}`; + const jobId = `job_${this.voiceJobCounter}`; + const response: VoiceStreamStartResponse = { + type: 'voice.stream.started', + request_id: message.request_id, + ok: true, + payload: { stream_id: streamId, job_id: jobId }, + }; + this.reply(response); + } + + private handleVoiceEnd(message: VoiceStreamEndCommand): void { + const jobId = `job_${this.voiceJobCounter || 1}`; + const response: VoiceStreamEndResponse = { + type: 'voice.stream.ended', + request_id: message.request_id, + ok: true, + payload: { + stream_id: message.payload.stream_id, + job_id: jobId, + status: 'processing', + }, + }; + this.reply(response); + + const parseResult: VoiceParseResultMessage = { + type: 'voice.parse.result', + request_id: message.request_id, + job_id: jobId, + status: 'ready_for_confirmation', + draft: { + schedule_type: 'time', + title: '语音创建的日程', + start_time: new Date(Date.now() + 3_600_000).toISOString(), + end_time: null, + timezone: 'Asia/Shanghai', + time_remind_offset_minutes: 0, + }, + missing_fields: [], + ambiguous_fields: [], + needs_confirmation: true, + }; + setTimeout(() => this.reply(parseResult), 0); + } + + private handleVoiceCancel(message: VoiceStreamCancelCommand): void { + this.reply({ + type: 'voice.stream.cancelled', + request_id: message.request_id, + ok: true, + payload: { stream_id: message.payload.stream_id }, + }); + } +} diff --git a/frontend/src/dev/fakes/schedule/scheduleConflicts.ts b/frontend/src/dev/fakes/schedule/scheduleConflicts.ts new file mode 100644 index 0000000..d2612b5 --- /dev/null +++ b/frontend/src/dev/fakes/schedule/scheduleConflicts.ts @@ -0,0 +1,61 @@ +import type { + Schedule, + ScheduleConflict, + ScheduleUpsertCommand, + ScheduleUpsertResult, +} from '@/contracts'; + +/** 检测与现有日程的时间重叠冲突(排除自身与已删除项)。 */ +function findScheduleConflicts( + command: ScheduleUpsertCommand, + schedules: Schedule[], + currentScheduleId: string, +): ScheduleConflict[] { + const { end_time: endTime, start_time: startTime } = command.payload; + if (!startTime) return []; + + const start = new Date(startTime).getTime(); + const end = endTime ? new Date(endTime).getTime() : start; + if (!Number.isFinite(start) || !Number.isFinite(end)) return []; + + return schedules + .filter((item) => item.id !== currentScheduleId && item.status !== 'deleted' && item.start_time) + .filter((item) => { + const itemStart = new Date(item.start_time!).getTime(); + const itemEnd = item.end_time ? new Date(item.end_time).getTime() : itemStart; + return ( + Number.isFinite(itemStart) && + Number.isFinite(itemEnd) && + start <= itemEnd && + itemStart <= end + ); + }) + .map((item) => ({ + schedule_id: item.id, + title: item.title, + start_time: item.start_time!, + end_time: item.end_time, + })); +} + +/** 拼装本地 upsert 结果(含冲突列表与 geofence 默认值)。 */ +export function upsertSchedule( + command: ScheduleUpsertCommand, + current: Schedule[], + scheduleId: string, +): ScheduleUpsertResult { + const existingSchedule = current.find((item) => item.id === scheduleId); + + return { + type: 'schedule.upsert.result', + request_id: command.request_id, + ok: true, + payload: { + schedule_id: scheduleId, + schedule_type: command.payload.schedule_type, + status: 'scheduled', + conflicts: findScheduleConflicts(command, current, scheduleId), + geofence_armed: command.payload.geofence_armed ?? existingSchedule?.geofence_armed ?? true, + }, + }; +} diff --git a/frontend/src/dev/fakes/schedule/scheduleFactory.ts b/frontend/src/dev/fakes/schedule/scheduleFactory.ts new file mode 100644 index 0000000..89863e8 --- /dev/null +++ b/frontend/src/dev/fakes/schedule/scheduleFactory.ts @@ -0,0 +1,39 @@ +import type { Schedule, ScheduleStatus, ScheduleUpsertPayload } from '@/contracts'; + +export function createFakeSchedule(input: { + draft: ScheduleUpsertPayload; + scheduleId: string; + userId: string; + status: ScheduleStatus; + geofenceArmed: boolean; + existing?: Schedule | null; +}): Schedule { + const { draft, existing } = input; + const now = new Date().toISOString(); + + return { + id: input.scheduleId, + user_id: existing?.user_id ?? input.userId, + source_mode: draft.source_mode, + schedule_type: draft.schedule_type, + status: input.status, + title: draft.title, + notes: draft.notes ?? null, + start_time: draft.start_time ?? null, + end_time: draft.end_time ?? null, + timezone: draft.timezone ?? null, + location_name: draft.location_name ?? null, + location_address: draft.location_address ?? null, + latitude: draft.latitude ?? null, + longitude: draft.longitude ?? null, + geofence_radius_meters: draft.geofence_radius_meters ?? existing?.geofence_radius_meters ?? 100, + geofence_armed: input.geofenceArmed, + time_remind_offset_minutes: draft.time_remind_offset_minutes ?? 0, + time_triggered_at: existing?.time_triggered_at ?? null, + geo_triggered_at: existing?.geo_triggered_at ?? null, + system_schedule_ref_id: existing?.system_schedule_ref_id ?? null, + system_alarm_ref_id: existing?.system_alarm_ref_id ?? null, + created_at: existing?.created_at ?? now, + updated_at: now, + }; +} diff --git a/frontend/src/infrastructure/storage/deviceIdStore.ts b/frontend/src/infrastructure/storage/deviceIdStore.ts new file mode 100644 index 0000000..21d998e --- /dev/null +++ b/frontend/src/infrastructure/storage/deviceIdStore.ts @@ -0,0 +1,150 @@ +import { Platform } from 'react-native'; +import { requireOptionalNativeModule } from 'expo'; + +const DEVICE_ID_KEY = 'timeflow.device_id'; + +export type DeviceIdStore = { + get(): Promise; + set(value: string): Promise; +}; + +type ExpoFileSystemLike = { + documentDirectory: string | null; + getInfoAsync: (path: string, options: Record) => Promise<{ exists: boolean }>; + readAsStringAsync: (path: string, options: Record) => Promise; + writeAsStringAsync: ( + path: string, + contents: string, + options: Record, + ) => Promise; +}; + +/** + * Native storage is deliberately a hard dependency at runtime. Falling back + * to an in-process map makes the device identity change on every cold start, + * which is worse than refusing to establish a session. + */ +export class DeviceIdPersistenceUnavailableError extends Error { + constructor(message = '原生设备存储不可用,无法持久化 device_id') { + super(message); + this.name = 'DeviceIdPersistenceUnavailableError'; + } +} + +function webStore(): DeviceIdStore { + return { + async get() { + if (typeof localStorage === 'undefined') { + throw new DeviceIdPersistenceUnavailableError('浏览器 localStorage 不可用'); + } + return localStorage.getItem(DEVICE_ID_KEY); + }, + async set(value) { + if (typeof localStorage === 'undefined') { + throw new DeviceIdPersistenceUnavailableError('浏览器 localStorage 不可用'); + } + localStorage.setItem(DEVICE_ID_KEY, value); + }, + }; +} + +/** Test/host adapter. Production code must inject a persistent implementation. */ +export function memoryStore(seed: Map = new Map()): DeviceIdStore { + return { + async get() { + return seed.get(DEVICE_ID_KEY) ?? null; + }, + async set(value) { + seed.set(DEVICE_ID_KEY, value); + }, + }; +} + +let nativeFileStore: DeviceIdStore | null = null; +let nativeFileStorePromise: Promise | null = null; + +function loadExpoFileSystem(): ExpoFileSystemLike | null { + // Expo Go and a custom Expo runtime expose the legacy module under this + // name. The lookup is static and Metro-visible; there is no hidden import + // or optional JS package that can silently disappear from a release bundle. + return requireOptionalNativeModule('ExponentFileSystem'); +} + +function createNativeFileStore(FileSystem = loadExpoFileSystem()): DeviceIdStore { + const base = FileSystem?.documentDirectory; + if ( + !FileSystem || + !base || + typeof FileSystem.getInfoAsync !== 'function' || + typeof FileSystem.readAsStringAsync !== 'function' || + typeof FileSystem.writeAsStringAsync !== 'function' + ) { + throw new DeviceIdPersistenceUnavailableError( + 'ExpoFileSystem 原生模块未链接;请在构建中声明 expo-file-system 或注入 DeviceIdStore', + ); + } + const path = `${base}.timeflow-device-id`; + return { + async get() { + let info: { exists: boolean }; + try { + info = await FileSystem.getInfoAsync(path, {}); + } catch (error) { + throw new DeviceIdPersistenceUnavailableError( + `读取 device_id 失败: ${error instanceof Error ? error.message : String(error)}`, + ); + } + if (!info.exists) return null; + try { + const value = await FileSystem.readAsStringAsync(path, {}); + return value.trim() || null; + } catch (error) { + throw new DeviceIdPersistenceUnavailableError( + `读取 device_id 失败: ${error instanceof Error ? error.message : String(error)}`, + ); + } + }, + async set(value) { + try { + await FileSystem.writeAsStringAsync(path, value, {}); + } catch (error) { + throw new DeviceIdPersistenceUnavailableError( + `写入 device_id 失败: ${error instanceof Error ? error.message : String(error)}`, + ); + } + }, + }; +} + +export async function createDeviceIdStore(): Promise { + if (Platform.OS === 'web') { + return webStore(); + } + if (!nativeFileStore) { + nativeFileStorePromise ??= Promise.resolve() + .then(() => createNativeFileStore()) + .catch((error) => { + // A transient host/module setup failure should not poison all later + // attempts during the same app lifetime. + nativeFileStorePromise = null; + throw error; + }); + nativeFileStore = await nativeFileStorePromise; + } + return nativeFileStore; +} + +export async function getOrCreateDeviceId(store?: DeviceIdStore): Promise { + const backend = store ?? (await createDeviceIdStore()); + const existing = await backend.get(); + if (existing) return existing; + const next = `device_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`; + await backend.set(next); + // Read back once so a best-effort native implementation cannot report a + // successful write while losing the value (for example, a denied file URI). + const persisted = await backend.get(); + if (persisted !== next) { + throw new DeviceIdPersistenceUnavailableError('device_id 写入后校验失败'); + } + return next; +} diff --git a/frontend/src/infrastructure/ws/WsClient.ts b/frontend/src/infrastructure/ws/WsClient.ts new file mode 100644 index 0000000..45c9b88 --- /dev/null +++ b/frontend/src/infrastructure/ws/WsClient.ts @@ -0,0 +1,250 @@ +import type { ConnectionStatus, WsJsonMessage } from '@/contracts'; + +export type { ConnectionStatus, WsJsonMessage } from '@/contracts'; + +export type WsClientOptions = { + /** 真实后端地址;为空则走 fakeHandler 进程内通道。 */ + url?: string | null; + /** 无 URL 时的进程内消息处理器(由 SessionProvider 在显式 Fake 模式下注入)。 */ + fakeHandler?: (message: WsJsonMessage | ArrayBuffer) => void | Promise; + requestTimeoutMs?: number; +}; + +type PendingRequest = { + resolve: (value: WsJsonMessage) => void; + reject: (error: Error) => void; + timer: ReturnType; + isMatch: (response: WsJsonMessage) => boolean; +}; + +/** + * 契约对齐的 WS 客户端:按 request_id 等待响应,支持订阅推送与二进制帧。 + * 无 URL 时走进程内 Fake 通道,便于本地与单测。 + */ +export class WsClient { + private socket: WebSocket | null = null; + private readonly pending = new Map(); + private readonly listeners = new Set<(message: WsJsonMessage | ArrayBuffer) => void>(); + private readonly statusListeners = new Set<(status: ConnectionStatus) => void>(); + private status: ConnectionStatus = 'idle'; + private readonly requestTimeoutMs: number; + private readonly url: string | null; + private readonly fakeHandler?: (message: WsJsonMessage | ArrayBuffer) => void | Promise; + private fakeReply: ((message: WsJsonMessage | ArrayBuffer) => void) | null = null; + private intentionallyClosed = false; + /** Invalidates callbacks belonging to a socket that has been replaced. */ + private socketGeneration = 0; + private connecting: { generation: number; reject: (error: Error) => void } | null = null; + + constructor(options: WsClientOptions = {}) { + this.url = options.url?.trim() || null; + this.fakeHandler = options.fakeHandler; + this.requestTimeoutMs = options.requestTimeoutMs ?? 15_000; + } + + getConnectionStatus(): ConnectionStatus { + return this.status; + } + + onStatus(listener: (status: ConnectionStatus) => void): () => void { + this.statusListeners.add(listener); + listener(this.status); + return () => this.statusListeners.delete(listener); + } + + onMessage(listener: (message: WsJsonMessage | ArrayBuffer) => void): () => void { + this.listeners.add(listener); + return () => this.listeners.delete(listener); + } + + async connect(): Promise { + this.intentionallyClosed = false; + if (!this.url) { + this.setStatus('connecting'); + this.fakeReply = (message) => this.dispatch(message); + this.setStatus('ready'); + return; + } + + this.setStatus(this.status === 'ready' ? 'reconnecting' : 'connecting'); + await new Promise((resolve, reject) => { + const generation = ++this.socketGeneration; + const socket = new WebSocket(this.url!); + this.socket = socket; + this.connecting = { generation, reject }; + socket.binaryType = 'arraybuffer'; + socket.onopen = () => { + if (this.socket !== socket || this.socketGeneration !== generation) return; + this.connecting = null; + this.setStatus('ready'); + resolve(); + }; + socket.onerror = () => { + if (this.socket !== socket || this.socketGeneration !== generation) return; + const error = new Error('WebSocket connection failed'); + this.setStatus('error'); + this.rejectPending(error); + this.rejectConnecting(generation, error); + }; + socket.onclose = () => { + const isCurrent = this.socket === socket && this.socketGeneration === generation; + if (!isCurrent) return; + this.socket = null; + if (this.intentionallyClosed) return; + const error = new Error('WebSocket closed unexpectedly'); + this.rejectPending(error); + this.setStatus('closed'); + this.rejectConnecting(generation, new Error('WebSocket closed before becoming ready')); + }; + socket.onmessage = (event) => { + if (this.socket !== socket || this.socketGeneration !== generation) return; + if (typeof event.data === 'string') { + try { + this.dispatch(JSON.parse(event.data) as WsJsonMessage); + } catch { + // ignore malformed JSON + } + return; + } + if (event.data instanceof ArrayBuffer) { + this.dispatch(event.data); + } + }; + }); + } + + close(): void { + this.intentionallyClosed = true; + this.rejectConnecting(this.socketGeneration, new Error('WebSocket closed')); + this.socketGeneration += 1; + const socket = this.socket; + this.socket = null; + socket?.close(); + this.fakeReply = null; + this.rejectPending(new Error('WebSocket closed')); + this.setStatus('closed'); + } + + sendJson(message: WsJsonMessage): void { + if (!this.url) { + void Promise.resolve(this.fakeHandler?.(message)).catch((error) => { + if (message.request_id) { + this.rejectRequest( + message.request_id, + error instanceof Error ? error : new Error(String(error)), + ); + } + }); + return; + } + if (!this.socket || this.socket.readyState !== WebSocket.OPEN) { + throw new Error('WebSocket is not open'); + } + this.socket.send(JSON.stringify(message)); + } + + sendBinary(data: ArrayBuffer): void { + if (!this.url) { + void Promise.resolve(this.fakeHandler?.(data)).catch(() => undefined); + return; + } + if (!this.socket || this.socket.readyState !== WebSocket.OPEN) { + throw new Error('WebSocket is not open'); + } + this.socket.send(data); + } + + /** Fake 服务端向客户端推送。 */ + emitFromServer(message: WsJsonMessage | ArrayBuffer): void { + this.fakeReply?.(message); + } + + request( + message: WsJsonMessage & { request_id: string }, + isMatch: (response: WsJsonMessage) => boolean = (response) => + response.request_id === message.request_id, + ): Promise { + return new Promise((resolve, reject) => { + if (!message.request_id) { + reject(new Error(`Request id is required: ${message.type}`)); + return; + } + if (this.pending.has(message.request_id)) { + reject(new Error(`Duplicate request id: ${message.request_id}`)); + return; + } + const timer = setTimeout(() => { + this.pending.delete(message.request_id); + reject(new Error(`Request timed out: ${message.type}`)); + }, this.requestTimeoutMs); + + this.pending.set(message.request_id, { + isMatch, + resolve: (value) => { + clearTimeout(timer); + this.pending.delete(message.request_id); + resolve(value as T); + }, + reject: (error) => { + clearTimeout(timer); + this.pending.delete(message.request_id); + reject(error); + }, + timer, + }); + + try { + this.sendJson(message); + } catch (error) { + clearTimeout(timer); + this.pending.delete(message.request_id); + reject(error instanceof Error ? error : new Error(String(error))); + } + }); + } + + private dispatch(message: WsJsonMessage | ArrayBuffer): void { + if (!(message instanceof ArrayBuffer)) { + for (const pending of this.pending.values()) { + if (pending.isMatch(message)) { + pending.resolve(message); + break; + } + } + } + for (const listener of this.listeners) { + listener(message); + } + } + + private rejectPending(error: Error): void { + const pending = [...this.pending.values()]; + this.pending.clear(); + for (const request of pending) { + clearTimeout(request.timer); + request.reject(error); + } + } + + private rejectRequest(requestId: string, error: Error): void { + const request = this.pending.get(requestId); + if (!request) return; + clearTimeout(request.timer); + this.pending.delete(requestId); + request.reject(error); + } + + private rejectConnecting(generation: number, error: Error): void { + const connecting = this.connecting; + if (!connecting || connecting.generation !== generation) return; + this.connecting = null; + connecting.reject(error); + } + + private setStatus(status: ConnectionStatus): void { + this.status = status; + for (const listener of this.statusListeners) { + listener(status); + } + } +} diff --git a/frontend/src/shared/types/geo.ts b/frontend/src/shared/types/geo.ts new file mode 100644 index 0000000..9bb1e6e --- /dev/null +++ b/frontend/src/shared/types/geo.ts @@ -0,0 +1,7 @@ +/** 中立地理点类型:feature 与地图 adapter 共用,不归属具体供应商。 */ +export type MapLocation = { + address: string; + latitude: number; + longitude: number; + name?: string; +}; diff --git a/frontend/src/shared/utils/requestId.ts b/frontend/src/shared/utils/requestId.ts new file mode 100644 index 0000000..f5aca22 --- /dev/null +++ b/frontend/src/shared/utils/requestId.ts @@ -0,0 +1,4 @@ +/** 生成 WS 请求 ID:`prefix_timestamp_random`。 */ +export function nextRequestId(prefix: string): string { + return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`; +} From e725131f8663009b194c9c7171cdbe0a4eee8792 Mon Sep 17 00:00:00 2001 From: mac Date: Fri, 31 Jul 2026 18:57:39 +0800 Subject: [PATCH 3/5] feat(frontend): add schedule workflows --- .../application/ScheduleService.test.ts | 211 +++++++++++ .../schedule/calendar/MonthView.test.tsx | 62 +++ .../schedule/calendar/ScheduleRow.test.tsx | 40 ++ .../data/WsScheduleRepository.test.ts | 33 ++ .../features/schedule/data/adapters.test.ts | 38 ++ .../detail/ScheduleDetailSheet.test.tsx | 84 ++++ .../schedule/domain/scheduleOrdering.test.ts | 41 ++ .../schedule/editor/DateTimeField.test.tsx | 91 +++++ .../editor/StandardCreateSheet.test.tsx | 190 ++++++++++ .../features/schedule/editor/datetime.test.ts | 126 ++++++ .../location/AddressEditorSheet.test.tsx | 39 ++ .../location/LocationPickerSheet.test.tsx | 100 +++++ .../MapPicker/MapPicker.native.test.tsx | 149 ++++++++ .../location/MapPicker/Overlay.test.tsx | 92 +++++ .../MapPicker/baidu/baiduMapWebView.test.ts | 25 ++ .../baidu/reverseGeocodeGate.test.ts | 91 +++++ .../location/MapPicker/baidu/services.test.ts | 35 ++ .../schedule/location/locationUtils.test.ts | 90 +++++ .../presentation/scheduleFormat.test.ts | 139 +++++++ .../schedule/screens/ScheduleScreen.test.tsx | 74 ++++ .../components/DatePickerSheet.test.tsx | 62 +++ .../components/TimePickerSheet.test.tsx | 38 ++ frontend/jest.setup.js | 20 + frontend/package-lock.json | 83 +++- frontend/package.json | 3 +- .../schedule/application/AlarmPort.ts | 24 ++ .../application/ScheduleNotificationPort.ts | 4 + .../schedule/application/ScheduleService.ts | 149 ++++++++ .../schedule/calendar/MonthView.styles.ts | 102 +++++ .../features/schedule/calendar/MonthView.tsx | 158 ++++++++ .../schedule/calendar/ScheduleRow.tsx | 81 ++++ .../schedule/calendar/scheduleIndex.ts | 48 +++ .../schedule/calendar/scheduleRow.styles.ts | 67 ++++ .../features/schedule/data/ScheduleCache.ts | 52 +++ .../schedule/data/ScheduleRepositoryPort.ts | 24 ++ .../schedule/data/ScheduleTransport.ts | 14 + .../schedule/data/WsScheduleRepository.ts | 112 ++++++ .../src/features/schedule/data/adapters.ts | 116 ++++++ .../schedule/detail/ScheduleDetailSheet.tsx | 200 ++++++++++ .../features/schedule/detail/detail.styles.ts | 158 ++++++++ .../schedule/domain/scheduleOrdering.ts | 10 + .../schedule/domain/scheduleStatus.ts | 28 ++ .../schedule/editor/ClearFieldButton.tsx | 23 ++ .../schedule/editor/DateTimeField.tsx | 56 +++ .../schedule/editor/StandardCreateModal.tsx | 53 +++ .../schedule/editor/StandardCreateSheet.tsx | 358 ++++++++++++++++++ .../schedule/editor/createSheet.styles.ts | 164 ++++++++ .../src/features/schedule/editor/datetime.ts | 79 ++++ .../schedule/hooks/useScheduleCommands.tsx | 213 +++++++++++ frontend/src/features/schedule/index.ts | 9 + .../location/AddressEditorSheet.styles.ts | 57 +++ .../schedule/location/AddressEditorSheet.tsx | 129 +++++++ .../location/LocationPickerSheet.styles.ts | 60 +++ .../schedule/location/LocationPickerSheet.tsx | 112 ++++++ .../location/MapPicker/MapPicker.native.tsx | 184 +++++++++ .../schedule/location/MapPicker/MapPicker.tsx | 284 ++++++++++++++ .../schedule/location/MapPicker/Overlay.tsx | 216 +++++++++++ .../MapPicker/baidu/baiduMapWebView.ts | 245 ++++++++++++ .../location/MapPicker/baidu/index.ts | 8 + .../MapPicker/baidu/reverseGeocodeGate.ts | 82 ++++ .../location/MapPicker/baidu/services.ts | 26 ++ .../location/MapPicker/baidu/types.ts | 3 + .../schedule/location/MapPicker/index.ts | 2 + .../schedule/location/MapPicker/styles.ts | 150 ++++++++ .../schedule/location/MapPicker/types.ts | 9 + .../src/features/schedule/location/index.ts | 4 + .../src/features/schedule/location/types.ts | 7 + .../location/useSessionSavedLocations.ts | 21 + .../src/features/schedule/location/utils.ts | 60 +++ .../schedule/presentation/scheduleFormat.ts | 53 +++ .../schedule/screens/ScheduleScreen.tsx | 122 ++++++ .../schedule/screens/scheduleScreen.styles.ts | 41 ++ .../components/DatePickerSheet.styles.ts | 22 ++ .../src/shared/components/DatePickerSheet.tsx | 145 +++++++ .../components/TimePickerSheet.styles.ts | 65 ++++ .../src/shared/components/TimePickerSheet.tsx | 127 +++++++ 76 files changed, 6488 insertions(+), 4 deletions(-) create mode 100644 frontend/__tests__/features/schedule/application/ScheduleService.test.ts create mode 100644 frontend/__tests__/features/schedule/calendar/MonthView.test.tsx create mode 100644 frontend/__tests__/features/schedule/calendar/ScheduleRow.test.tsx create mode 100644 frontend/__tests__/features/schedule/data/WsScheduleRepository.test.ts create mode 100644 frontend/__tests__/features/schedule/data/adapters.test.ts create mode 100644 frontend/__tests__/features/schedule/detail/ScheduleDetailSheet.test.tsx create mode 100644 frontend/__tests__/features/schedule/domain/scheduleOrdering.test.ts create mode 100644 frontend/__tests__/features/schedule/editor/DateTimeField.test.tsx create mode 100644 frontend/__tests__/features/schedule/editor/StandardCreateSheet.test.tsx create mode 100644 frontend/__tests__/features/schedule/editor/datetime.test.ts create mode 100644 frontend/__tests__/features/schedule/location/AddressEditorSheet.test.tsx create mode 100644 frontend/__tests__/features/schedule/location/LocationPickerSheet.test.tsx create mode 100644 frontend/__tests__/features/schedule/location/MapPicker/MapPicker.native.test.tsx create mode 100644 frontend/__tests__/features/schedule/location/MapPicker/Overlay.test.tsx create mode 100644 frontend/__tests__/features/schedule/location/MapPicker/baidu/baiduMapWebView.test.ts create mode 100644 frontend/__tests__/features/schedule/location/MapPicker/baidu/reverseGeocodeGate.test.ts create mode 100644 frontend/__tests__/features/schedule/location/MapPicker/baidu/services.test.ts create mode 100644 frontend/__tests__/features/schedule/location/locationUtils.test.ts create mode 100644 frontend/__tests__/features/schedule/presentation/scheduleFormat.test.ts create mode 100644 frontend/__tests__/features/schedule/screens/ScheduleScreen.test.tsx create mode 100644 frontend/__tests__/shared/components/DatePickerSheet.test.tsx create mode 100644 frontend/__tests__/shared/components/TimePickerSheet.test.tsx create mode 100644 frontend/src/features/schedule/application/AlarmPort.ts create mode 100644 frontend/src/features/schedule/application/ScheduleNotificationPort.ts create mode 100644 frontend/src/features/schedule/application/ScheduleService.ts create mode 100644 frontend/src/features/schedule/calendar/MonthView.styles.ts create mode 100644 frontend/src/features/schedule/calendar/MonthView.tsx create mode 100644 frontend/src/features/schedule/calendar/ScheduleRow.tsx create mode 100644 frontend/src/features/schedule/calendar/scheduleIndex.ts create mode 100644 frontend/src/features/schedule/calendar/scheduleRow.styles.ts create mode 100644 frontend/src/features/schedule/data/ScheduleCache.ts create mode 100644 frontend/src/features/schedule/data/ScheduleRepositoryPort.ts create mode 100644 frontend/src/features/schedule/data/ScheduleTransport.ts create mode 100644 frontend/src/features/schedule/data/WsScheduleRepository.ts create mode 100644 frontend/src/features/schedule/data/adapters.ts create mode 100644 frontend/src/features/schedule/detail/ScheduleDetailSheet.tsx create mode 100644 frontend/src/features/schedule/detail/detail.styles.ts create mode 100644 frontend/src/features/schedule/domain/scheduleOrdering.ts create mode 100644 frontend/src/features/schedule/domain/scheduleStatus.ts create mode 100644 frontend/src/features/schedule/editor/ClearFieldButton.tsx create mode 100644 frontend/src/features/schedule/editor/DateTimeField.tsx create mode 100644 frontend/src/features/schedule/editor/StandardCreateModal.tsx create mode 100644 frontend/src/features/schedule/editor/StandardCreateSheet.tsx create mode 100644 frontend/src/features/schedule/editor/createSheet.styles.ts create mode 100644 frontend/src/features/schedule/editor/datetime.ts create mode 100644 frontend/src/features/schedule/hooks/useScheduleCommands.tsx create mode 100644 frontend/src/features/schedule/index.ts create mode 100644 frontend/src/features/schedule/location/AddressEditorSheet.styles.ts create mode 100644 frontend/src/features/schedule/location/AddressEditorSheet.tsx create mode 100644 frontend/src/features/schedule/location/LocationPickerSheet.styles.ts create mode 100644 frontend/src/features/schedule/location/LocationPickerSheet.tsx create mode 100644 frontend/src/features/schedule/location/MapPicker/MapPicker.native.tsx create mode 100644 frontend/src/features/schedule/location/MapPicker/MapPicker.tsx create mode 100644 frontend/src/features/schedule/location/MapPicker/Overlay.tsx create mode 100644 frontend/src/features/schedule/location/MapPicker/baidu/baiduMapWebView.ts create mode 100644 frontend/src/features/schedule/location/MapPicker/baidu/index.ts create mode 100644 frontend/src/features/schedule/location/MapPicker/baidu/reverseGeocodeGate.ts create mode 100644 frontend/src/features/schedule/location/MapPicker/baidu/services.ts create mode 100644 frontend/src/features/schedule/location/MapPicker/baidu/types.ts create mode 100644 frontend/src/features/schedule/location/MapPicker/index.ts create mode 100644 frontend/src/features/schedule/location/MapPicker/styles.ts create mode 100644 frontend/src/features/schedule/location/MapPicker/types.ts create mode 100644 frontend/src/features/schedule/location/index.ts create mode 100644 frontend/src/features/schedule/location/types.ts create mode 100644 frontend/src/features/schedule/location/useSessionSavedLocations.ts create mode 100644 frontend/src/features/schedule/location/utils.ts create mode 100644 frontend/src/features/schedule/presentation/scheduleFormat.ts create mode 100644 frontend/src/features/schedule/screens/ScheduleScreen.tsx create mode 100644 frontend/src/features/schedule/screens/scheduleScreen.styles.ts create mode 100644 frontend/src/shared/components/DatePickerSheet.styles.ts create mode 100644 frontend/src/shared/components/DatePickerSheet.tsx create mode 100644 frontend/src/shared/components/TimePickerSheet.styles.ts create mode 100644 frontend/src/shared/components/TimePickerSheet.tsx diff --git a/frontend/__tests__/features/schedule/application/ScheduleService.test.ts b/frontend/__tests__/features/schedule/application/ScheduleService.test.ts new file mode 100644 index 0000000..aea4b9c --- /dev/null +++ b/frontend/__tests__/features/schedule/application/ScheduleService.test.ts @@ -0,0 +1,211 @@ +import { afterEach, beforeEach, describe, expect, it, jest } from '@jest/globals'; + +import type { + Schedule, + ScheduleUpsertCommand, + ScheduleUpsertPayload as ScheduleDraft, + ScheduleUpsertResponse, +} from '@/contracts'; +import { ScheduleService } from '@/features/schedule/application/ScheduleService'; +import { ScheduleCache } from '@/features/schedule/data/ScheduleCache'; +import type { ScheduleRepositoryPort } from '@/features/schedule/data/ScheduleRepositoryPort'; +import { makeSchedule } from '@test/fixtures'; + +function makeDraft(overrides: Partial = {}): ScheduleDraft { + return { + source_mode: 'manual', + schedule_type: 'time', + title: '新会议', + start_time: new Date(Date.now() + 3_600_000).toISOString(), + end_time: null, + time_remind_offset_minutes: 5, + ...overrides, + }; +} + +function upsertOk(command: ScheduleUpsertCommand, id: string): ScheduleUpsertResponse { + return { + type: 'schedule.upsert.result', + request_id: command.request_id, + ok: true, + payload: { + schedule_id: id, + schedule_type: command.payload.schedule_type, + status: 'scheduled', + conflicts: [], + geofence_armed: true, + }, + }; +} + +describe('ScheduleService', () => { + let cache: ScheduleCache; + let repository: jest.Mocked; + let syncForSchedule: jest.MockedFunction< + NonNullable[0]['alarmAdapter']>['syncForSchedule'] + >; + let cancel: jest.MockedFunction< + NonNullable[0]['alarmAdapter']>['cancel'] + >; + let notifyConflicts: jest.MockedFunction< + NonNullable[0]['notifyConflicts']> + >; + let service: ScheduleService; + + beforeEach(() => { + jest.clearAllMocks(); + cache = new ScheduleCache(); + repository = { + list: jest.fn(async () => [] as Schedule[]), + upsert: jest.fn(async (command: ScheduleUpsertCommand) => + upsertOk(command, command.payload.schedule_id ?? 'schedule_auto'), + ), + updateStatus: jest.fn(async (id: string, status: 'scheduled' | 'done') => ({ + type: 'schedule.status.result' as const, + request_id: `req_status_${id}`, + ok: true as const, + payload: { schedule_id: id, status }, + })), + notifyDeleted: jest.fn(async (id: string) => ({ + type: 'schedule.deleted.ack' as const, + request_id: `req_deleted_${id}`, + schedule_id: id, + ok: true as const, + })), + subscribe: jest.fn(() => () => undefined), + }; + syncForSchedule = jest.fn(async () => null); + cancel = jest.fn(async () => null); + notifyConflicts = jest.fn(); + service = new ScheduleService({ + repository, + cache, + getUserId: () => 'default_user', + alarmAdapter: { syncForSchedule, cancel }, + notifyConflicts, + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('bootstraps from repository.list into cache', async () => { + const seed = [makeSchedule({ id: 'seed' })]; + repository.list.mockResolvedValueOnce(seed); + await service.bootstrap(); + expect(service.getItems()).toEqual(seed); + }); + + it('creates a schedule via upsert and caches it', async () => { + const saved = await service.saveDraft(makeDraft({ title: 'A' })); + expect(saved.title).toBe('A'); + expect(saved.id).toBe('schedule_auto'); + expect(service.getItems()).toHaveLength(1); + expect(repository.upsert).toHaveBeenCalled(); + expect(repository.upsert.mock.calls[0]![0].payload.schedule_id).toBeUndefined(); + }); + + it('alerts when upsert reports conflicts', async () => { + repository.upsert.mockImplementation(async (command) => ({ + type: 'schedule.upsert.result', + request_id: command.request_id, + ok: true, + payload: { + schedule_id: command.payload.schedule_id ?? 'x', + schedule_type: command.payload.schedule_type, + status: 'scheduled', + conflicts: [ + { + schedule_id: 'other', + title: '已有会议', + start_time: new Date().toISOString(), + end_time: null, + }, + ], + geofence_armed: true, + }, + })); + + await service.saveDraft(makeDraft()); + expect(notifyConflicts).toHaveBeenCalledWith([expect.objectContaining({ title: '已有会议' })]); + }); + + it('updates an existing schedule when schedule_id is set', async () => { + await service.saveDraft(makeDraft({ schedule_id: 'schedule_edit', title: '旧标题' })); + await service.saveDraft(makeDraft({ schedule_id: 'schedule_edit', title: '新标题' })); + expect(service.getItems()).toHaveLength(1); + expect(service.getItems()[0]?.title).toBe('新标题'); + }); + + it('syncs android alarms when adapter returns an id', async () => { + syncForSchedule.mockResolvedValue('alarm_99'); + const saved = await service.saveDraft(makeDraft({ title: '安卓会议' })); + expect(syncForSchedule).toHaveBeenCalled(); + expect(saved.system_schedule_ref_id).toBe('alarm_99'); + }); + + it('toggles done through updateStatus and re-arms on undo', async () => { + syncForSchedule.mockResolvedValue('alarm_old'); + await service.saveDraft( + makeDraft({ + schedule_id: 'toggle_1', + start_time: new Date(Date.now() + 3_600_000).toISOString(), + }), + ); + const item = service.getItems()[0]!; + await service.toggleDone(item); + expect(repository.updateStatus).toHaveBeenCalledWith('toggle_1', 'done'); + expect(repository.notifyDeleted).not.toHaveBeenCalled(); + expect(service.getItems()[0]?.status).toBe('done'); + + syncForSchedule.mockResolvedValue('alarm_rearm'); + await service.toggleDone(service.getItems()[0]!); + expect(repository.updateStatus).toHaveBeenCalledWith('toggle_1', 'scheduled'); + expect(service.getItems()[0]?.status).toBe('scheduled'); + expect(syncForSchedule).toHaveBeenCalled(); + }); + + it('marks a schedule deleted and cancels its alarm', async () => { + syncForSchedule.mockResolvedValue('alarm_del'); + await service.saveDraft(makeDraft({ schedule_id: 'del_1' })); + await service.deleteSchedule(service.getItems()[0]!); + expect(repository.notifyDeleted).toHaveBeenCalledWith('del_1'); + expect(cancel).toHaveBeenCalledWith('alarm_del'); + expect(service.getItems()[0]?.status).toBe('deleted'); + expect(service.getItems()[0]?.system_schedule_ref_id).toBeNull(); + }); + + it('keeps the alarm reference returned by the platform adapter', async () => { + cancel.mockResolvedValue('remote_alarm'); + cache.replaceAll([makeSchedule({ id: 'remote', system_schedule_ref_id: 'local_alarm' })]); + + await service.deleteSchedule(service.getItems()[0]!); + + expect(service.getItems()[0]?.system_schedule_ref_id).toBe('remote_alarm'); + }); + + it('does not mutate cache or alarms when delete is rejected', async () => { + repository.notifyDeleted.mockResolvedValueOnce({ + type: 'schedule.deleted.ack', + request_id: 'req_delete_failed', + schedule_id: 'reject', + ok: false, + error: { code: 'denied', message: '删除被拒绝', details: null }, + }); + const schedule = makeSchedule({ id: 'reject', system_schedule_ref_id: 'alarm_reject' }); + cache.replaceAll([schedule]); + + await expect(service.deleteSchedule(schedule)).rejects.toThrow('删除被拒绝'); + expect(cancel).not.toHaveBeenCalled(); + expect(service.getItems()[0]).toEqual(schedule); + }); + + it('ignores toggle and delete for already deleted items', async () => { + cache.replaceAll([makeSchedule({ id: 'gone', status: 'deleted' })]); + await service.toggleDone(service.getItems()[0]!); + await service.deleteSchedule(service.getItems()[0]!); + expect(repository.updateStatus).not.toHaveBeenCalled(); + expect(repository.notifyDeleted).not.toHaveBeenCalled(); + }); +}); diff --git a/frontend/__tests__/features/schedule/calendar/MonthView.test.tsx b/frontend/__tests__/features/schedule/calendar/MonthView.test.tsx new file mode 100644 index 0000000..8f8f2e2 --- /dev/null +++ b/frontend/__tests__/features/schedule/calendar/MonthView.test.tsx @@ -0,0 +1,62 @@ +import { describe, expect, it, jest } from '@jest/globals'; +import { fireEvent, render, screen } from '@testing-library/react-native'; + +import { makeSchedule } from '@test/fixtures'; + +import { MonthView } from '@/features/schedule/calendar/MonthView'; +import { buildScheduleIndex } from '@/features/schedule/calendar/scheduleIndex'; + +describe('MonthView', () => { + const now = new Date(2026, 6, 31); + const month = new Date(2026, 6, 1); + + it('navigates months and selects a day with events', () => { + const onMonthChange = jest.fn(); + const onSelectDate = jest.fn(); + const onOpenSchedule = jest.fn(); + + render( + , + ); + + expect(screen.getByText('7月')).toBeTruthy(); + expect(screen.getByText('月底会议')).toBeTruthy(); + + fireEvent.press(screen.getByLabelText('上个月')); + expect(onMonthChange).toHaveBeenCalled(); + fireEvent.press(screen.getByLabelText('下个月')); + expect(onMonthChange).toHaveBeenCalledTimes(2); + + fireEvent.press(screen.getByText('月底会议')); + expect(onOpenSchedule).toHaveBeenCalledWith('m1'); + }); + + it('shows empty agenda copy when the selected day has no events', () => { + render( + , + ); + expect(screen.getByText('这一天暂无详细安排')).toBeTruthy(); + }); +}); diff --git a/frontend/__tests__/features/schedule/calendar/ScheduleRow.test.tsx b/frontend/__tests__/features/schedule/calendar/ScheduleRow.test.tsx new file mode 100644 index 0000000..5569c86 --- /dev/null +++ b/frontend/__tests__/features/schedule/calendar/ScheduleRow.test.tsx @@ -0,0 +1,40 @@ +import { describe, expect, it, jest } from '@jest/globals'; +import { fireEvent, render, screen } from '@testing-library/react-native'; + +import { makeSchedule } from '@test/fixtures'; + +import { ScheduleRow } from '@/features/schedule/calendar/ScheduleRow'; + +describe('ScheduleRow', () => { + it('renders title, time and optional meta', () => { + const onPress = jest.fn(); + const onToggle = jest.fn(); + render( + , + ); + expect(screen.getByText('晨会')).toBeTruthy(); + expect(screen.getByText('会议室')).toBeTruthy(); + fireEvent.press(screen.getByLabelText('09:05 晨会')); + expect(onPress).toHaveBeenCalled(); + fireEvent.press(screen.getByLabelText('完成 晨会')); + expect(onToggle).toHaveBeenCalled(); + }); + + it('shows restore affordance for done items in compact mode', () => { + const onToggle = jest.fn(); + render( + , + ); + fireEvent.press(screen.getByLabelText('恢复 已做完')); + expect(onToggle).toHaveBeenCalled(); + }); +}); diff --git a/frontend/__tests__/features/schedule/data/WsScheduleRepository.test.ts b/frontend/__tests__/features/schedule/data/WsScheduleRepository.test.ts new file mode 100644 index 0000000..6813609 --- /dev/null +++ b/frontend/__tests__/features/schedule/data/WsScheduleRepository.test.ts @@ -0,0 +1,33 @@ +import { describe, expect, it, jest } from '@jest/globals'; + +import type { WsJsonMessage } from '@/contracts'; +import type { ScheduleTransport } from '@/features/schedule/data/ScheduleTransport'; +import { WsScheduleRepository } from '@/features/schedule/data/WsScheduleRepository'; + +describe('WsScheduleRepository backend compatibility', () => { + it('accepts the MVP delete acknowledgement without request_id', async () => { + const request = jest.fn( + async ( + _message: WsJsonMessage & { request_id: string }, + isMatch?: (response: WsJsonMessage) => boolean, + ) => { + const response = { + type: 'schedule.deleted.ack', + schedule_id: 'schedule_1', + ok: true, + }; + expect(isMatch?.(response)).toBe(true); + return response; + }, + ); + const transport = { + onMessage: () => () => undefined, + request, + sendJson: () => undefined, + } as unknown as ScheduleTransport; + const repository = new WsScheduleRepository(transport); + + await expect(repository.notifyDeleted('schedule_1')).resolves.toMatchObject({ ok: true }); + repository.dispose(); + }); +}); diff --git a/frontend/__tests__/features/schedule/data/adapters.test.ts b/frontend/__tests__/features/schedule/data/adapters.test.ts new file mode 100644 index 0000000..e7fbcfe --- /dev/null +++ b/frontend/__tests__/features/schedule/data/adapters.test.ts @@ -0,0 +1,38 @@ +import { describe, expect, it } from '@jest/globals'; + +import { upsertDraftForSchedule } from '@/features/schedule/data/adapters'; +import { makeSchedule } from '@test/fixtures'; + +describe('upsertDraftForSchedule', () => { + it('maps schedule fields into a domain draft', () => { + const schedule = makeSchedule({ + id: 'schedule_42', + notes: '备注', + location_name: '办公室', + location_address: '南京东路1号', + latitude: 31.2, + longitude: 121.4, + geofence_radius_meters: 200, + geofence_armed: true, + time_remind_offset_minutes: 10, + }); + + expect(upsertDraftForSchedule(schedule)).toEqual({ + schedule_id: 'schedule_42', + source_mode: 'manual', + schedule_type: 'time', + title: '测试日程', + notes: '备注', + start_time: schedule.start_time, + end_time: null, + timezone: 'Asia/Shanghai', + location_name: '办公室', + location_address: '南京东路1号', + latitude: 31.2, + longitude: 121.4, + geofence_radius_meters: 200, + geofence_armed: true, + time_remind_offset_minutes: 10, + }); + }); +}); diff --git a/frontend/__tests__/features/schedule/detail/ScheduleDetailSheet.test.tsx b/frontend/__tests__/features/schedule/detail/ScheduleDetailSheet.test.tsx new file mode 100644 index 0000000..3fd7f92 --- /dev/null +++ b/frontend/__tests__/features/schedule/detail/ScheduleDetailSheet.test.tsx @@ -0,0 +1,84 @@ +import { describe, expect, it, jest } from '@jest/globals'; +import { fireEvent, render, screen, waitFor } from '@testing-library/react-native'; +import type { ReactElement } from 'react'; + +import { makeSchedule } from '@test/fixtures'; +import { ScheduleDetailSheet } from '@/features/schedule/detail/ScheduleDetailSheet'; +import { AppDialogProvider } from '@/shared/components/AppDialogProvider'; + +function renderWithDialog(element: ReactElement) { + return render({element}); +} + +describe('ScheduleDetailSheet', () => { + it('is hidden when schedule is null', () => { + renderWithDialog( + , + ); + expect(screen.queryByText('安排详情')).toBeNull(); + }); + + it('shows schedule content and opens the day view', () => { + const onClose = jest.fn(); + const onOpenDay = jest.fn(); + renderWithDialog( + , + ); + expect(screen.getByText('评审会')).toBeTruthy(); + fireEvent.press(screen.getByLabelText('查看当天日程')); + expect(onClose).toHaveBeenCalled(); + expect(onOpenDay).toHaveBeenCalled(); + }); + + it('offers edit when editable and confirms delete', async () => { + const onEdit = jest.fn(); + const onDelete = jest.fn(); + const onClose = jest.fn(); + renderWithDialog( + , + ); + fireEvent.press(screen.getByLabelText('编辑日程')); + expect(onEdit).toHaveBeenCalled(); + + fireEvent.press(screen.getByLabelText('删除日程')); + expect(screen.getByText('确定删除这个日程吗?相关提醒也会一并取消。')).toBeTruthy(); + fireEvent.press(screen.getByLabelText('删除')); + await waitFor(() => expect(onDelete).toHaveBeenCalled()); + expect(onClose).toHaveBeenCalled(); + }); + + it('renders completed status styling', () => { + renderWithDialog( + , + ); + expect(screen.getAllByText('已完成').length).toBeGreaterThan(0); + expect(screen.getByText('已完成 · 可回顾这次安排')).toBeTruthy(); + }); +}); diff --git a/frontend/__tests__/features/schedule/domain/scheduleOrdering.test.ts b/frontend/__tests__/features/schedule/domain/scheduleOrdering.test.ts new file mode 100644 index 0000000..a7c14d6 --- /dev/null +++ b/frontend/__tests__/features/schedule/domain/scheduleOrdering.test.ts @@ -0,0 +1,41 @@ +import { describe, expect, it } from '@jest/globals'; + +import { makeSchedule } from '@test/fixtures'; + +import { compareSchedules } from '@/features/schedule/domain/scheduleOrdering'; + +describe('compareSchedules', () => { + it('orders by start_time when both have one', () => { + const earlier = makeSchedule({ + id: 'a', + start_time: new Date(2026, 6, 29, 8, 0).toISOString(), + }); + const later = makeSchedule({ + id: 'b', + start_time: new Date(2026, 6, 29, 10, 0).toISOString(), + }); + expect(compareSchedules(earlier, later)).toBeLessThan(0); + expect(compareSchedules(later, earlier)).toBeGreaterThan(0); + }); + + it('puts timed schedules before location-only ones', () => { + const timed = makeSchedule({ id: 't', start_time: new Date(2026, 6, 29, 9, 0).toISOString() }); + const locationOnly = makeSchedule({ id: 'l', start_time: null }); + expect(compareSchedules(timed, locationOnly)).toBe(-1); + expect(compareSchedules(locationOnly, timed)).toBe(1); + }); + + it('falls back to created_at when neither has start_time', () => { + const older = makeSchedule({ + id: 'old', + start_time: null, + created_at: new Date(2026, 6, 1).toISOString(), + }); + const newer = makeSchedule({ + id: 'new', + start_time: null, + created_at: new Date(2026, 6, 20).toISOString(), + }); + expect(compareSchedules(older, newer)).toBeGreaterThan(0); + }); +}); diff --git a/frontend/__tests__/features/schedule/editor/DateTimeField.test.tsx b/frontend/__tests__/features/schedule/editor/DateTimeField.test.tsx new file mode 100644 index 0000000..b730369 --- /dev/null +++ b/frontend/__tests__/features/schedule/editor/DateTimeField.test.tsx @@ -0,0 +1,91 @@ +import { describe, expect, it, jest } from '@jest/globals'; +import { fireEvent, render, screen } from '@testing-library/react-native'; + +jest.mock('@/shared/components/DatePickerSheet', () => ({ + DatePickerSheet: ({ + visible, + onSelect, + onClose, + }: { + visible: boolean; + onSelect: (date: Date) => void; + onClose: () => void; + }) => { + const { Pressable, Text } = require('react-native'); + if (!visible) return null; + return ( + { + onSelect(new Date(2026, 7, 1)); + onClose(); + }} + > + mock-date + + ); + }, +})); + +jest.mock('@/shared/components/TimePickerSheet', () => ({ + TimePickerSheet: ({ + visible, + onSelect, + onClose, + }: { + visible: boolean; + onSelect: (value: string) => void; + onClose: () => void; + }) => { + const { Pressable, Text } = require('react-native'); + if (!visible) return null; + return ( + { + onSelect('15:30'); + onClose(); + }} + > + mock-time + + ); + }, +})); + +import { DateTimeField } from '@/features/schedule/editor/DateTimeField'; + +describe('DateTimeField', () => { + it('opens the date sheet and formats the selection', () => { + const onChange = jest.fn(); + render( + , + ); + fireEvent.press(screen.getByLabelText('日期')); + fireEvent.press(screen.getByLabelText('mock-date-select')); + expect(onChange).toHaveBeenCalledWith('2026 / 08 / 01'); + }); + + it('opens the time sheet and returns HH:mm', () => { + const onChange = jest.fn(); + render( + , + ); + expect(screen.getByText('09:00')).toBeTruthy(); + fireEvent.press(screen.getByLabelText('时间')); + fireEvent.press(screen.getByLabelText('mock-time-select')); + expect(onChange).toHaveBeenCalledWith('15:30'); + }); +}); diff --git a/frontend/__tests__/features/schedule/editor/StandardCreateSheet.test.tsx b/frontend/__tests__/features/schedule/editor/StandardCreateSheet.test.tsx new file mode 100644 index 0000000..d369d72 --- /dev/null +++ b/frontend/__tests__/features/schedule/editor/StandardCreateSheet.test.tsx @@ -0,0 +1,190 @@ +import { describe, expect, it, jest } from '@jest/globals'; +import { fireEvent, render, screen, waitFor } from '@testing-library/react-native'; + +import { StandardCreateSheet } from '@/features/schedule/editor/StandardCreateSheet'; +import type { ScheduleUpsertPayload as ScheduleDraft } from '@/contracts'; + +function pressPrimaryAction(label: string) { + const matches = screen.getAllByText(label); + fireEvent.press(matches[matches.length - 1]!); +} + +describe('StandardCreateSheet', () => { + const baseProps = { + onClose: jest.fn(), + onSave: jest.fn(async (_draft: ScheduleDraft) => undefined), + onUpsertLocation: jest.fn(), + savedLocations: [] as [], + }; + + it('rejects an empty title', async () => { + render(); + pressPrimaryAction('添加日程'); + expect(await screen.findByText('请填写日程标题。')).toBeTruthy(); + expect(baseProps.onSave).not.toHaveBeenCalled(); + }); + + it('creates a time schedule with a future start', async () => { + const onSave = jest.fn(async (_draft: ScheduleDraft) => undefined); + + render(); + fireEvent.changeText(screen.getByPlaceholderText('请输入日程标题'), '项目评审'); + pressPrimaryAction('添加日程'); + + await waitFor(() => expect(onSave).toHaveBeenCalled()); + const draft = onSave.mock.calls[0]![0]; + expect(draft.title).toBe('项目评审'); + expect(draft.schedule_type).toBe('time'); + expect(draft.start_time).toBeTruthy(); + }); + + it('rejects a start time that is not in the future', async () => { + const past = new Date(Date.now() - 120_000); + render( + , + ); + pressPrimaryAction('保存修改'); + expect(await screen.findByText('开始时间需晚于当前分钟,请选择下一分钟及以后。')).toBeTruthy(); + }); + + it('surfaces save errors from onSave', async () => { + const onSave = jest.fn(async () => { + throw new Error('网络异常'); + }); + render(); + fireEvent.changeText(screen.getByPlaceholderText('请输入日程标题'), '会失败'); + pressPrimaryAction('添加日程'); + expect(await screen.findByText('网络异常')).toBeTruthy(); + }); + + it('surfaces non-Error save failures', async () => { + const onSave = jest.fn(async () => { + throw 'boom'; + }); + render(); + fireEvent.changeText(screen.getByPlaceholderText('请输入日程标题'), '会失败'); + pressPrimaryAction('添加日程'); + expect(await screen.findByText('保存失败,请稍后重试。')).toBeTruthy(); + }); + + it('saves a location-only schedule', async () => { + const onSave = jest.fn(async (_draft: ScheduleDraft) => undefined); + const location = { + id: 'loc_1', + address: '南京东路1号', + latitude: 31.2, + longitude: 121.5, + name: '办公室', + }; + render( + , + ); + // Clearing the date also clears start/end, so the schedule becomes location-only. + fireEvent.press(screen.getByLabelText('清除日期')); + pressPrimaryAction('添加日程'); + await waitFor(() => expect(onSave).toHaveBeenCalled()); + expect(onSave.mock.calls[0]![0].schedule_type).toBe('location'); + }); + + it('rejects end time earlier than start', async () => { + const future = new Date(Date.now() + 3_600_000); + const later = new Date(Date.now() + 7_200_000); + render( + , + ); + pressPrimaryAction('保存修改'); + expect(await screen.findByText('结束时间不能早于开始时间。')).toBeTruthy(); + }); + + it('rejects a negative remind offset', async () => { + const future = new Date(Date.now() + 3_600_000); + render( + , + ); + fireEvent.changeText(screen.getByLabelText('提前提醒分钟数'), '-1'); + pressPrimaryAction('保存修改'); + expect(await screen.findByText('提前提醒分钟数必须是非负整数。')).toBeTruthy(); + }); + + it('rejects invalid geofence radius for location schedules', async () => { + render( + , + ); + fireEvent.press(screen.getByLabelText('清除日期')); + fireEvent.changeText(screen.getByLabelText('地理围栏半径'), '0'); + pressPrimaryAction('添加日程'); + expect(await screen.findByText('地理围栏半径必须是大于 0 的整数。')).toBeTruthy(); + }); +}); diff --git a/frontend/__tests__/features/schedule/editor/datetime.test.ts b/frontend/__tests__/features/schedule/editor/datetime.test.ts new file mode 100644 index 0000000..10bfce8 --- /dev/null +++ b/frontend/__tests__/features/schedule/editor/datetime.test.ts @@ -0,0 +1,126 @@ +import { describe, expect, it } from '@jest/globals'; + +import { + currentTimezone, + dateAndTimeFromIso, + defaultCreateDateAndTime, + formatDateValue, + isoFromDateAndTime, + optionalNumber, + parseDateValue, + parsePickerValue, + parseTimeValue, +} from '@/features/schedule/editor/datetime'; +import { formatTimeValue } from '@/shared/utils/date'; + +describe('parseDateValue', () => { + it('round-trips the format the field renders', () => { + expect(formatDateValue(parseDateValue('2026 / 07 / 29'))).toBe('2026 / 07 / 29'); + }); + + it('accepts any non-digit separator', () => { + expect(formatDateValue(parseDateValue('2026-7-29'))).toBe('2026 / 07 / 29'); + }); + + it('falls back to today when the input is incomplete', () => { + const today = new Date(); + today.setHours(0, 0, 0, 0); + expect(parseDateValue('2026 / 07').getTime()).toBe(today.getTime()); + }); +}); + +describe('parseTimeValue', () => { + it('reads hours and minutes', () => { + expect(formatTimeValue(parseTimeValue('09:05'))).toBe('09:05'); + }); + + it('accepts a single-digit hour', () => { + expect(formatTimeValue(parseTimeValue('9:05'))).toBe('09:05'); + }); +}); + +describe('isoFromDateAndTime', () => { + it('combines the two fields into one instant', () => { + expect(isoFromDateAndTime('2026 / 07 / 29', '09:05')).toBe( + new Date(2026, 6, 29, 9, 5).toISOString(), + ); + }); + + it('returns null when the time does not parse', () => { + expect(isoFromDateAndTime('2026 / 07 / 29', '9am')).toBeNull(); + }); + + it('returns null when the date is incomplete', () => { + expect(isoFromDateAndTime('2026 / 07', '09:05')).toBeNull(); + }); +}); + +describe('defaultCreateDateAndTime', () => { + it('defaults to the next whole minute', () => { + expect(defaultCreateDateAndTime(new Date(2026, 6, 30, 13, 58, 40, 123))).toEqual({ + date: '2026 / 07 / 30', + time: '13:59', + }); + }); + + it('rolls to the next day near midnight', () => { + expect(defaultCreateDateAndTime(new Date(2026, 6, 30, 23, 59, 10))).toEqual({ + date: '2026 / 07 / 31', + time: '00:00', + }); + }); +}); + +describe('dateAndTimeFromIso', () => { + it('returns empty strings for a missing value', () => { + expect(dateAndTimeFromIso(null)).toEqual({ date: '', time: '' }); + }); + + it('returns empty strings for an unparseable value', () => { + expect(dateAndTimeFromIso('not-a-date')).toEqual({ date: '', time: '' }); + }); + + it('splits an ISO string back into the two fields', () => { + expect(dateAndTimeFromIso(new Date(2026, 6, 29, 9, 5).toISOString())).toEqual({ + date: '2026 / 07 / 29', + time: '09:05', + }); + }); +}); + +describe('optionalNumber', () => { + it('treats blank input as absent rather than zero', () => { + expect(optionalNumber('')).toBeNull(); + expect(optionalNumber(' ')).toBeNull(); + }); + + it('rejects non-numeric input', () => { + expect(optionalNumber('abc')).toBeNull(); + }); + + it('keeps negative and decimal values', () => { + expect(optionalNumber('-31.2451')).toBe(-31.2451); + expect(optionalNumber('0')).toBe(0); + }); +}); + +describe('parsePickerValue', () => { + it('delegates to date or time parsers by mode', () => { + expect(formatDateValue(parsePickerValue('2026 / 07 / 29', 'date'))).toBe('2026 / 07 / 29'); + expect(formatTimeValue(parsePickerValue('09:05', 'time'))).toBe('09:05'); + }); +}); + +describe('parseTimeValue fallback', () => { + it('keeps the current clock when the string does not match', () => { + const parsed = parseTimeValue('bad'); + expect(parsed.getSeconds()).toBe(0); + expect(parsed.getMilliseconds()).toBe(0); + }); +}); + +describe('currentTimezone', () => { + it('returns the runtime timezone when Intl is available', () => { + expect(typeof currentTimezone()).toBe('string'); + }); +}); diff --git a/frontend/__tests__/features/schedule/location/AddressEditorSheet.test.tsx b/frontend/__tests__/features/schedule/location/AddressEditorSheet.test.tsx new file mode 100644 index 0000000..5a19bb7 --- /dev/null +++ b/frontend/__tests__/features/schedule/location/AddressEditorSheet.test.tsx @@ -0,0 +1,39 @@ +import { describe, expect, it, jest } from '@jest/globals'; +import { fireEvent, render, screen } from '@testing-library/react-native'; + +jest.mock('@/features/schedule/location/MapPicker', () => ({ + MapPicker: () => null, +})); + +import { AddressEditorSheet } from '@/features/schedule/location/AddressEditorSheet'; + +describe('AddressEditorSheet', () => { + it('requires a map location before save', () => { + const onSave = jest.fn(); + render(); + fireEvent.press(screen.getByLabelText('保存地点')); + expect(screen.getByText('请选择一个地图位置')).toBeTruthy(); + expect(onSave).not.toHaveBeenCalled(); + }); + + it('saves with an optional name', () => { + const onSave = jest.fn(); + render( + , + ); + fireEvent.changeText(screen.getByLabelText('地点名称'), '办公室'); + fireEvent.press(screen.getByLabelText('保存地点')); + expect(onSave).toHaveBeenCalledWith( + expect.objectContaining({ + address: '南京东路1号', + name: '办公室', + }), + ); + }); +}); diff --git a/frontend/__tests__/features/schedule/location/LocationPickerSheet.test.tsx b/frontend/__tests__/features/schedule/location/LocationPickerSheet.test.tsx new file mode 100644 index 0000000..71a22cf --- /dev/null +++ b/frontend/__tests__/features/schedule/location/LocationPickerSheet.test.tsx @@ -0,0 +1,100 @@ +import { describe, expect, it, jest } from '@jest/globals'; +import { fireEvent, render, screen } from '@testing-library/react-native'; + +jest.mock('@/features/schedule/location/AddressEditorSheet', () => ({ + AddressEditorSheet: ({ + visible, + onSave, + onClose, + }: { + visible: boolean; + onSave: (location: { + address: string; + latitude: number; + longitude: number; + name?: string; + }) => void; + onClose: () => void; + }) => { + const { Pressable, Text } = require('react-native'); + if (!visible) return null; + return ( + <> + + onSave({ address: '新地址', latitude: 31.1, longitude: 121.1, name: '新地点' }) + } + > + mock-save + + + mock-close + + + ); + }, +})); + +import { LocationPickerSheet } from '@/features/schedule/location/LocationPickerSheet'; + +const office = { + id: 'loc_1', + address: '南京东路1号', + latitude: 31.2, + longitude: 121.5, + name: '办公室', +}; + +describe('LocationPickerSheet', () => { + it('shows empty state', () => { + render( + , + ); + expect(screen.getByText('还没有常用地点')).toBeTruthy(); + }); + + it('selects a location and closes', () => { + const onSelect = jest.fn(); + const onClose = jest.fn(); + render( + , + ); + fireEvent.press(screen.getByLabelText('选择地点 办公室')); + expect(onSelect).toHaveBeenCalledWith(office); + expect(onClose).toHaveBeenCalled(); + }); + + it('opens the editor and upserts a new location', () => { + const onUpsert = jest.fn(); + const onSelect = jest.fn(); + const onClose = jest.fn(); + render( + , + ); + fireEvent.press(screen.getByLabelText('添加地点')); + fireEvent.press(screen.getByLabelText('mock-save-location')); + expect(onUpsert).toHaveBeenCalled(); + expect(onSelect).toHaveBeenCalled(); + expect(onClose).toHaveBeenCalled(); + }); +}); diff --git a/frontend/__tests__/features/schedule/location/MapPicker/MapPicker.native.test.tsx b/frontend/__tests__/features/schedule/location/MapPicker/MapPicker.native.test.tsx new file mode 100644 index 0000000..113de65 --- /dev/null +++ b/frontend/__tests__/features/schedule/location/MapPicker/MapPicker.native.test.tsx @@ -0,0 +1,149 @@ +import { describe, expect, it, jest } from '@jest/globals'; +import { act, fireEvent, render, screen } from '@testing-library/react-native'; + +jest.mock('@/features/schedule/location/MapPicker/baidu', () => ({ + BAIDU_MAP_AK: 'test-ak', + createCoordinateLocation: (latitude: number, longitude: number) => ({ + address: `坐标 ${latitude},${longitude}`, + latitude, + longitude, + }), + buildBaiduMapDocument: () => '', +})); + +jest.mock('@/features/schedule/location/MapPicker/Overlay', () => ({ + MapPickerOverlay: ({ + onCancel, + onConfirm, + onLocate, + onSearch, + mapReady, + selection, + }: { + onCancel: () => void; + onConfirm: () => void; + onLocate: () => void; + onSearch: (query: string) => Promise; + mapReady: boolean; + selection: { address: string } | null; + }) => { + const { Pressable, Text } = require('react-native'); + return ( + <> + {mapReady ? 'ready' : 'loading'} + {selection?.address ?? 'no-selection'} + + cancel + + + confirm + + + locate + + { + void onSearch('外滩'); + }} + > + search + + + ); + }, +})); + +import { MapPicker } from '@/features/schedule/location/MapPicker/MapPicker.native'; + +describe('MapPicker.native', () => { + it('handles bridge messages and confirms the selection', async () => { + const onConfirm = jest.fn(); + const onCancel = jest.fn(); + const { UNSAFE_getByType } = render( + , + ); + + const WebView = require('react-native-webview').WebView; + const webview = UNSAFE_getByType(WebView); + + await act(async () => { + webview.props.onMessage({ + nativeEvent: { data: JSON.stringify({ type: 'map-ready' }) }, + }); + }); + expect(screen.getByText('ready')).toBeTruthy(); + + await act(async () => { + webview.props.onMessage({ + nativeEvent: { + data: JSON.stringify({ type: 'selecting', latitude: 31.2, longitude: 121.5 }), + }, + }); + }); + expect(screen.getByText('坐标 31.2,121.5')).toBeTruthy(); + + await act(async () => { + webview.props.onMessage({ + nativeEvent: { + data: JSON.stringify({ + type: 'selected', + location: { address: '外滩', latitude: 31.2, longitude: 121.5 }, + }), + }, + }); + }); + expect(screen.getByText('外滩')).toBeTruthy(); + + fireEvent.press(screen.getByLabelText('mock-confirm')); + expect(onConfirm).toHaveBeenCalledWith(expect.objectContaining({ address: '外滩' })); + fireEvent.press(screen.getByLabelText('mock-cancel')); + expect(onCancel).toHaveBeenCalled(); + }); + + it('surfaces map errors from the bridge', async () => { + const { UNSAFE_getByType } = render( + , + ); + const WebView = require('react-native-webview').WebView; + const webview = UNSAFE_getByType(WebView); + await act(async () => { + webview.props.onMessage({ + nativeEvent: { data: JSON.stringify({ type: 'map-error', message: '坏了' }) }, + }); + }); + expect(screen.getByText('loading')).toBeTruthy(); + }); + + it('handles location errors from the bridge', async () => { + const { UNSAFE_getByType } = render( + , + ); + const WebView = require('react-native-webview').WebView; + const webview = UNSAFE_getByType(WebView); + + await act(async () => { + webview.props.onMessage({ + nativeEvent: { data: JSON.stringify({ type: 'map-ready' }) }, + }); + }); + + await act(async () => { + webview.props.onMessage({ + nativeEvent: { + data: JSON.stringify({ type: 'location-error', message: '无定位' }), + }, + }); + }); + + fireEvent.press(screen.getByLabelText('mock-locate')); + + await act(async () => { + webview.props.onMessage({ nativeEvent: { data: 'not-json' } }); + }); + }); +}); diff --git a/frontend/__tests__/features/schedule/location/MapPicker/Overlay.test.tsx b/frontend/__tests__/features/schedule/location/MapPicker/Overlay.test.tsx new file mode 100644 index 0000000..6ab1054 --- /dev/null +++ b/frontend/__tests__/features/schedule/location/MapPicker/Overlay.test.tsx @@ -0,0 +1,92 @@ +import { afterEach, beforeEach, describe, expect, it, jest } from '@jest/globals'; +import { act, fireEvent, render, screen } from '@testing-library/react-native'; + +import { MapPickerOverlay } from '@/features/schedule/location/MapPicker/Overlay'; + +async function flushDebouncedSearch() { + await act(async () => { + jest.advanceTimersByTime(320); + await Promise.resolve(); + await Promise.resolve(); + }); +} + +describe('MapPickerOverlay', () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + const base = { + mapError: null as string | null, + mapReady: true, + locating: false, + locationError: null as string | null, + onCancel: jest.fn(), + onLocate: jest.fn(), + onConfirm: jest.fn(), + onSearch: jest.fn(async () => [ + { address: '外滩 · 中山东一路', latitude: 31.24, longitude: 121.49 }, + ]), + onSelectSearchResult: jest.fn(), + selection: { + address: '南京东路', + latitude: 31.23, + longitude: 121.48, + }, + }; + + it('debounces search and lists results', async () => { + render(); + fireEvent.changeText(screen.getByLabelText('搜索地点'), '外滩'); + await flushDebouncedSearch(); + expect(base.onSearch).toHaveBeenCalledWith('外滩'); + expect(screen.getByText('外滩 · 中山东一路')).toBeTruthy(); + fireEvent.press(screen.getByLabelText('选择 外滩 · 中山东一路')); + expect(base.onSelectSearchResult).toHaveBeenCalled(); + }); + + it('confirms and cancels', () => { + render(); + fireEvent.press(screen.getByLabelText('确认选中的地址')); + expect(base.onConfirm).toHaveBeenCalled(); + fireEvent.press(screen.getByLabelText('退出地图选点')); + expect(base.onCancel).toHaveBeenCalled(); + }); + + it('shows map errors and locating state', () => { + render( + , + ); + expect(screen.getByText('地图加载失败')).toBeTruthy(); + expect(screen.getByText('定位失败')).toBeTruthy(); + expect(screen.getByText('正在获取当前位置...')).toBeTruthy(); + }); + + it('surfaces search failures', async () => { + const onSearch = jest.fn(async () => { + throw new Error('qps'); + }); + render(); + fireEvent.changeText(screen.getByLabelText('搜索地点'), '失败'); + await flushDebouncedSearch(); + expect(screen.getByText('搜索暂时不可用,请直接在地图上选点')).toBeTruthy(); + }); + + it('shows empty search results', async () => { + const onSearch = jest.fn(async () => []); + render(); + fireEvent.changeText(screen.getByLabelText('搜索地点'), '空'); + await flushDebouncedSearch(); + expect(screen.getByText('没有找到相关地点')).toBeTruthy(); + }); +}); diff --git a/frontend/__tests__/features/schedule/location/MapPicker/baidu/baiduMapWebView.test.ts b/frontend/__tests__/features/schedule/location/MapPicker/baidu/baiduMapWebView.test.ts new file mode 100644 index 0000000..e52aeda --- /dev/null +++ b/frontend/__tests__/features/schedule/location/MapPicker/baidu/baiduMapWebView.test.ts @@ -0,0 +1,25 @@ +import { describe, expect, it } from '@jest/globals'; + +import { buildBaiduMapDocument } from '@/features/schedule/location/MapPicker/baidu/baiduMapWebView'; + +describe('buildBaiduMapDocument', () => { + it('embeds the AK and default Shanghai center when no initial location', () => { + const html = buildBaiduMapDocument('test-ak', null); + expect(html).toContain(encodeURIComponent('test-ak')); + expect(html).toContain('31.236305'); + expect(html).toContain('121.480237'); + expect(html).toContain('null'); + }); + + it('embeds the provided initial location', () => { + const html = buildBaiduMapDocument('ak', { + address: '办公室', + latitude: 31.1, + longitude: 121.2, + name: '办公室', + }); + expect(html).toContain('31.1'); + expect(html).toContain('121.2'); + expect(html).toContain('办公室'); + }); +}); diff --git a/frontend/__tests__/features/schedule/location/MapPicker/baidu/reverseGeocodeGate.test.ts b/frontend/__tests__/features/schedule/location/MapPicker/baidu/reverseGeocodeGate.test.ts new file mode 100644 index 0000000..7dc492d --- /dev/null +++ b/frontend/__tests__/features/schedule/location/MapPicker/baidu/reverseGeocodeGate.test.ts @@ -0,0 +1,91 @@ +import { afterEach, beforeEach, describe, expect, it, jest } from '@jest/globals'; + +import { + createReverseGeocodeGate, + type ReverseGeocodeJob, + type ReverseGeocodeRunner, +} from '@/features/schedule/location/MapPicker/baidu/reverseGeocodeGate'; + +describe('createReverseGeocodeGate', () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it('debounces rapid schedules and only runs the latest job', async () => { + const gate = createReverseGeocodeGate({ debounceMs: 450, minIntervalMs: 0 }); + const run = jest.fn(); + + gate.schedule({ latitude: 1, longitude: 1, requestId: 1 }, run); + gate.schedule({ latitude: 2, longitude: 2, requestId: 2 }, run); + gate.schedule({ latitude: 3, longitude: 3, requestId: 3 }, run); + + expect(run).not.toHaveBeenCalled(); + await jest.advanceTimersByTimeAsync(450); + + expect(run).toHaveBeenCalledTimes(1); + const firstCall = run.mock.calls[0]?.[0] as ReverseGeocodeJob | undefined; + expect(firstCall).toEqual({ + latitude: 3, + longitude: 3, + requestId: 3, + }); + }); + + it('waits for the minimum interval before starting the next run', async () => { + const gate = createReverseGeocodeGate({ debounceMs: 0, minIntervalMs: 350 }); + const run = jest.fn(async () => undefined); + + gate.schedule({ latitude: 1, longitude: 1, requestId: 1 }, run); + await jest.advanceTimersByTimeAsync(0); + expect(run).toHaveBeenCalledTimes(1); + + gate.schedule({ latitude: 2, longitude: 2, requestId: 2 }, run); + await jest.advanceTimersByTimeAsync(0); + expect(run).toHaveBeenCalledTimes(1); + + await jest.advanceTimersByTimeAsync(350); + expect(run).toHaveBeenCalledTimes(2); + }); + + it('clear cancels pending timers and drops the queued job', async () => { + const gate = createReverseGeocodeGate({ debounceMs: 450, minIntervalMs: 0 }); + const run = jest.fn(); + + gate.schedule({ latitude: 1, longitude: 1, requestId: 1 }, run); + gate.clear(); + await jest.advanceTimersByTimeAsync(450); + expect(run).not.toHaveBeenCalled(); + }); + + it('queues the next job until the in-flight request finishes', async () => { + const gate = createReverseGeocodeGate({ debounceMs: 0, minIntervalMs: 0 }); + let resolveFirst: (() => void) | undefined; + const first = jest.fn( + () => + new Promise((resolve) => { + resolveFirst = resolve; + }), + ); + const second = jest.fn(async () => undefined); + + gate.schedule({ latitude: 1, longitude: 1, requestId: 1 }, first); + await jest.advanceTimersByTimeAsync(0); + expect(first).toHaveBeenCalledTimes(1); + + gate.schedule({ latitude: 2, longitude: 2, requestId: 2 }, second); + await jest.advanceTimersByTimeAsync(0); + expect(second).not.toHaveBeenCalled(); + + resolveFirst?.(); + await Promise.resolve(); + await jest.advanceTimersByTimeAsync(0); + + expect(second).toHaveBeenCalledTimes(1); + const secondCall = second.mock.calls[0]?.[0] as ReverseGeocodeJob | undefined; + expect(secondCall?.requestId).toBe(2); + }); +}); diff --git a/frontend/__tests__/features/schedule/location/MapPicker/baidu/services.test.ts b/frontend/__tests__/features/schedule/location/MapPicker/baidu/services.test.ts new file mode 100644 index 0000000..697f643 --- /dev/null +++ b/frontend/__tests__/features/schedule/location/MapPicker/baidu/services.test.ts @@ -0,0 +1,35 @@ +import { describe, expect, it } from '@jest/globals'; + +import { + BAIDU_COORDINATE_SYSTEM, + SHANGHAI_CENTER, + coordinateAddress, + createCoordinateLocation, + readablePoiAddress, +} from '@/features/schedule/location/MapPicker/baidu/services'; + +describe('map picker services', () => { + it('exposes the Baidu coordinate system and Shanghai default', () => { + expect(BAIDU_COORDINATE_SYSTEM).toBe('bd09ll'); + expect(SHANGHAI_CENTER.latitude).toBeCloseTo(31.236305); + expect(SHANGHAI_CENTER.longitude).toBeCloseTo(121.480237); + }); + + it('formats a coordinate fallback address', () => { + expect(coordinateAddress(31.2, 121.5)).toBe('百度地图选点 · 31.20000, 121.50000'); + }); + + it('builds a MapLocation from coordinates', () => { + expect(createCoordinateLocation(31.2, 121.5)).toEqual({ + address: '百度地图选点 · 31.20000, 121.50000', + latitude: 31.2, + longitude: 121.5, + }); + }); + + it('joins POI title with address when present', () => { + expect(readablePoiAddress('外滩', '中山东一路')).toBe('外滩 · 中山东一路'); + expect(readablePoiAddress('外滩', ' ')).toBe('外滩'); + expect(readablePoiAddress('外滩')).toBe('外滩'); + }); +}); diff --git a/frontend/__tests__/features/schedule/location/locationUtils.test.ts b/frontend/__tests__/features/schedule/location/locationUtils.test.ts new file mode 100644 index 0000000..1f642fd --- /dev/null +++ b/frontend/__tests__/features/schedule/location/locationUtils.test.ts @@ -0,0 +1,90 @@ +import { afterEach, describe, expect, it, jest } from '@jest/globals'; + +import { + createSavedLocation, + matchSavedLocation, + upsertSavedLocation, +} from '@/features/schedule/location/utils'; +import type { SavedLocation } from '@/features/schedule/location/types'; + +const office: SavedLocation = { + id: 'loc_office', + address: '南京东路1号', + latitude: 31.23, + longitude: 121.48, + name: '办公室', +}; + +describe('createSavedLocation', () => { + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('keeps an explicit id', () => { + expect( + createSavedLocation({ address: 'A', latitude: 1, longitude: 2, name: 'N' }, 'loc_fixed'), + ).toEqual({ + address: 'A', + latitude: 1, + longitude: 2, + name: 'N', + id: 'loc_fixed', + }); + }); + + it('generates an id from Date.now when omitted', () => { + jest.spyOn(Date, 'now').mockReturnValue(42); + expect(createSavedLocation({ address: 'A', latitude: 1, longitude: 2 }).id).toBe('loc_42'); + }); +}); + +describe('upsertSavedLocation', () => { + it('appends a new location', () => { + expect(upsertSavedLocation([], office)).toEqual([office]); + }); + + it('replaces an existing location with the same id', () => { + const updated = { ...office, name: '总部' }; + expect(upsertSavedLocation([office], updated)).toEqual([updated]); + }); +}); + +describe('matchSavedLocation', () => { + const locations = [office]; + + it('matches by coordinates first', () => { + expect( + matchSavedLocation(locations, { + latitude: 31.23, + longitude: 121.48, + location_name: '别的名字', + }), + ).toBe(office); + }); + + it('matches by name and address together', () => { + expect( + matchSavedLocation(locations, { + location_name: '办公室', + location_address: '南京东路1号', + }), + ).toBe(office); + }); + + it('matches by name alone', () => { + expect(matchSavedLocation(locations, { location_name: '办公室' })).toBe(office); + }); + + it('matches by address alone', () => { + expect(matchSavedLocation(locations, { location_address: '南京东路1号' })).toBe(office); + }); + + it('returns null when there is nothing to match on', () => { + expect(matchSavedLocation(locations, {})).toBeNull(); + expect(matchSavedLocation(locations, { location_name: ' ', location_address: '' })).toBeNull(); + }); + + it('returns null when nothing matches', () => { + expect(matchSavedLocation(locations, { location_name: '咖啡馆' })).toBeNull(); + }); +}); diff --git a/frontend/__tests__/features/schedule/presentation/scheduleFormat.test.ts b/frontend/__tests__/features/schedule/presentation/scheduleFormat.test.ts new file mode 100644 index 0000000..b6ce272 --- /dev/null +++ b/frontend/__tests__/features/schedule/presentation/scheduleFormat.test.ts @@ -0,0 +1,139 @@ +import { describe, expect, it } from '@jest/globals'; + +import type { Schedule } from '@/contracts'; + +import { + scheduleColor, + scheduleDate, + scheduleDuration, + scheduleRange, + scheduleSourceLabel, + scheduleStatusLabel, + scheduleTime, + timeToMinutes, +} from '@/features/schedule/presentation/scheduleFormat'; + +// Built from local-time components on purpose: the formatters read getHours() +// and friends, so a fixed offset string would make these tests timezone-bound. +function makeSchedule(overrides: Partial = {}): Schedule { + return { + id: 'schedule_test', + user_id: 'default_user', + source_mode: 'manual', + schedule_type: 'time', + status: 'scheduled', + title: '测试日程', + notes: null, + start_time: new Date(2026, 6, 29, 9, 5).toISOString(), + end_time: null, + timezone: 'Asia/Shanghai', + location_name: null, + location_address: null, + latitude: null, + longitude: null, + geofence_radius_meters: 100, + geofence_armed: false, + time_remind_offset_minutes: 15, + time_triggered_at: null, + geo_triggered_at: null, + system_schedule_ref_id: null, + system_alarm_ref_id: null, + created_at: new Date(2026, 6, 20, 10, 0).toISOString(), + updated_at: new Date(2026, 6, 20, 10, 0).toISOString(), + ...overrides, + }; +} + +describe('scheduleDate', () => { + it('returns null when there is no start time', () => { + expect(scheduleDate(makeSchedule({ start_time: null }))).toBeNull(); + }); + + it('returns null for an unparseable start time', () => { + expect(scheduleDate(makeSchedule({ start_time: 'not-a-date' }))).toBeNull(); + }); +}); + +describe('scheduleTime', () => { + it('pads hours and minutes to two digits', () => { + expect(scheduleTime(makeSchedule())).toBe('09:05'); + }); + + it('falls back to 地点 for schedules without a start time', () => { + expect(scheduleTime(makeSchedule({ start_time: null }))).toBe('地点'); + }); +}); + +describe('scheduleRange', () => { + it('joins start and end times', () => { + const item = makeSchedule({ end_time: new Date(2026, 6, 29, 10, 40).toISOString() }); + expect(scheduleRange(item)).toBe('09:05–10:40'); + }); + + it('returns only the start when there is no end time', () => { + expect(scheduleRange(makeSchedule())).toBe('09:05'); + }); + + it('ignores an unparseable end time', () => { + expect(scheduleRange(makeSchedule({ end_time: 'not-a-date' }))).toBe('09:05'); + }); + + it('prefers the location name for location schedules', () => { + const item = makeSchedule({ start_time: null, location_name: '办公室' }); + expect(scheduleRange(item)).toBe('办公室'); + }); + + it('falls back to a generic label with neither time nor place', () => { + expect(scheduleRange(makeSchedule({ start_time: null }))).toBe('地点提醒'); + }); +}); + +describe('scheduleDuration', () => { + it('reports the gap in minutes', () => { + const item = makeSchedule({ end_time: new Date(2026, 6, 29, 9, 40).toISOString() }); + expect(scheduleDuration(item)).toBe('35 分钟'); + }); + + it('reports 未设置时长 when the end time is missing', () => { + expect(scheduleDuration(makeSchedule())).toBe('未设置时长'); + }); + + it('reports 未设置时长 when the end is not after the start', () => { + const item = makeSchedule({ end_time: new Date(2026, 6, 29, 9, 5).toISOString() }); + expect(scheduleDuration(item)).toBe('未设置时长'); + }); +}); + +describe('scheduleColor', () => { + it('uses the done colour whatever the type is', () => { + expect(scheduleColor(makeSchedule({ status: 'done', schedule_type: 'location' }))).toBe( + '#A8C7B5', + ); + }); + + it('distinguishes location, voice and manual schedules', () => { + expect(scheduleColor(makeSchedule({ schedule_type: 'location' }))).toBe('#E79472'); + expect(scheduleColor(makeSchedule({ source_mode: 'voice' }))).toBe('#AEC46B'); + expect(scheduleColor(makeSchedule())).toBe('#7DA6B8'); + }); +}); + +describe('label helpers', () => { + it('names the creation source', () => { + expect(scheduleSourceLabel(makeSchedule({ source_mode: 'voice' }))).toBe('语音创建'); + expect(scheduleSourceLabel(makeSchedule())).toBe('手动创建'); + }); + + it('names all three statuses', () => { + expect(scheduleStatusLabel(makeSchedule({ status: 'done' }))).toBe('已完成'); + expect(scheduleStatusLabel(makeSchedule({ status: 'deleted' }))).toBe('已删除'); + expect(scheduleStatusLabel(makeSchedule())).toBe('待完成'); + }); +}); + +describe('timeToMinutes', () => { + it('counts minutes since midnight', () => { + expect(timeToMinutes('08:30')).toBe(510); + expect(timeToMinutes('00:00')).toBe(0); + }); +}); diff --git a/frontend/__tests__/features/schedule/screens/ScheduleScreen.test.tsx b/frontend/__tests__/features/schedule/screens/ScheduleScreen.test.tsx new file mode 100644 index 0000000..eafeeb7 --- /dev/null +++ b/frontend/__tests__/features/schedule/screens/ScheduleScreen.test.tsx @@ -0,0 +1,74 @@ +import { describe, expect, it, jest } from '@jest/globals'; +import { fireEvent, render, screen } from '@testing-library/react-native'; +import type { ReactElement } from 'react'; + +import { makeSchedule } from '@test/fixtures'; + +jest.mock('@/shared/hooks/useCurrentDate', () => ({ + useCurrentDate: () => new Date(2026, 6, 31, 12, 0, 0), +})); + +jest.mock('@/shared/components/DatePickerSheet', () => ({ + DatePickerSheet: () => null, +})); + +import { ScheduleScreen } from '@/features/schedule/screens/ScheduleScreen'; +import { AppDialogProvider } from '@/shared/components/AppDialogProvider'; + +function renderWithDialog(element: ReactElement) { + return render({element}); +} + +describe('ScheduleScreen', () => { + const props = { + onCreate: jest.fn(), + onDeleteSchedule: jest.fn(), + onEditSchedule: jest.fn(), + scheduleItems: [ + makeSchedule({ + id: 't1', + title: '今日评审', + start_time: new Date(2026, 6, 31, 10, 0).toISOString(), + }), + ], + }; + + it('shows the month view by default', () => { + renderWithDialog(); + expect(screen.getAllByText('7月').length).toBeGreaterThan(0); + expect(screen.getByText('今日评审')).toBeTruthy(); + }); + + it('opens create from the add button', () => { + renderWithDialog(); + fireEvent.press(screen.getByLabelText('添加日程')); + expect(props.onCreate).toHaveBeenCalled(); + }); + + it('routes completion from both the agenda row and detail sheet', () => { + const onToggleSchedule = jest.fn(); + renderWithDialog(); + + fireEvent.press(screen.getByLabelText('完成 今日评审')); + expect(onToggleSchedule).toHaveBeenCalledWith(expect.objectContaining({ id: 't1' })); + + fireEvent.press(screen.getByLabelText('10:00 今日评审')); + fireEvent.press(screen.getByLabelText('完成日程')); + expect(onToggleSchedule).toHaveBeenCalledTimes(2); + }); + + it('disables mutation affordances until the schedule service is ready', () => { + const onToggleSchedule = jest.fn(); + renderWithDialog( + , + ); + + expect(screen.getByLabelText('添加日程').props.accessibilityState).toEqual({ disabled: true }); + expect(screen.getByLabelText('完成 今日评审').props.accessibilityState).toEqual({ + checked: false, + disabled: true, + }); + fireEvent.press(screen.getByLabelText('完成 今日评审')); + expect(onToggleSchedule).not.toHaveBeenCalled(); + }); +}); diff --git a/frontend/__tests__/shared/components/DatePickerSheet.test.tsx b/frontend/__tests__/shared/components/DatePickerSheet.test.tsx new file mode 100644 index 0000000..76985bb --- /dev/null +++ b/frontend/__tests__/shared/components/DatePickerSheet.test.tsx @@ -0,0 +1,62 @@ +import { describe, expect, it, jest } from '@jest/globals'; +import { fireEvent, render, screen } from '@testing-library/react-native'; + +jest.mock('react-native-calendars', () => { + const React = require('react'); + const { Pressable, Text } = require('react-native'); + return { + LocaleConfig: { locales: {}, defaultLocale: 'zh' }, + Calendar: ({ + onDayPress, + markedDates, + }: { + onDayPress: (day: { dateString: string }) => void; + markedDates?: Record; + }) => + React.createElement( + Pressable, + { + accessibilityLabel: 'mock-calendar-day', + onPress: () => onDayPress({ dateString: '2026-08-02' }), + }, + React.createElement(Text, null, `marks:${Object.keys(markedDates ?? {}).join(',')}`), + ), + }; +}); + +import { DatePickerSheet } from '@/shared/components/DatePickerSheet'; + +describe('DatePickerSheet', () => { + it('is hidden when not visible', () => { + render( + , + ); + expect(screen.queryByLabelText('mock-calendar-day')).toBeNull(); + }); + + it('selects a day and can jump to today', () => { + const onSelect = jest.fn(); + const onClose = jest.fn(); + render( + , + ); + expect(screen.getByText(/marks:2026-07-31/)).toBeTruthy(); + fireEvent.press(screen.getByLabelText('mock-calendar-day')); + expect(onSelect).toHaveBeenCalled(); + expect(onClose).toHaveBeenCalled(); + + fireEvent.press(screen.getByLabelText('回到今天')); + expect(onSelect).toHaveBeenCalledTimes(2); + }); +}); diff --git a/frontend/__tests__/shared/components/TimePickerSheet.test.tsx b/frontend/__tests__/shared/components/TimePickerSheet.test.tsx new file mode 100644 index 0000000..20e45ce --- /dev/null +++ b/frontend/__tests__/shared/components/TimePickerSheet.test.tsx @@ -0,0 +1,38 @@ +import { describe, expect, it, jest } from '@jest/globals'; +import { fireEvent, render, screen } from '@testing-library/react-native'; + +import { TimePickerSheet } from '@/shared/components/TimePickerSheet'; + +describe('TimePickerSheet', () => { + it('is hidden when not visible', () => { + render( + , + ); + expect(screen.queryByText('选择时间')).toBeNull(); + }); + + it('lets the user change hour/minute and confirm', () => { + const onSelect = jest.fn(); + const onClose = jest.fn(); + render( + , + ); + expect(screen.getByText('09:05')).toBeTruthy(); + fireEvent.press(screen.getByLabelText('15 时')); + fireEvent.press(screen.getByLabelText('30 分')); + expect(screen.getByText('15:30')).toBeTruthy(); + fireEvent.press(screen.getByLabelText('确认时间')); + expect(onSelect).toHaveBeenCalledWith('15:30'); + expect(onClose).toHaveBeenCalled(); + }); +}); diff --git a/frontend/jest.setup.js b/frontend/jest.setup.js index 69d35d7..496542b 100644 --- a/frontend/jest.setup.js +++ b/frontend/jest.setup.js @@ -12,3 +12,23 @@ jest.mock('lucide-react-native', () => { }, ); }); + +jest.mock('react-native-safe-area-context', () => { + return { + SafeAreaProvider: ({ children }) => children, + SafeAreaView: ({ children }) => children, + useSafeAreaInsets: () => ({ top: 0, right: 0, bottom: 0, left: 0 }), + }; +}); + +jest.mock('react-native-webview', () => { + const React = require('react'); + const { View } = require('react-native'); + const WebView = React.forwardRef((props, _ref) => + React.createElement(View, { testID: 'webview', ...props }), + ); + WebView.displayName = 'MockWebView'; + return { + WebView, + }; +}); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 826dd44..0431d27 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -18,6 +18,7 @@ "react": "19.2.3", "react-dom": "19.2.3", "react-native": "0.86.0", + "react-native-calendars": "^1.1314.0", "react-native-safe-area-context": "~5.7.0", "react-native-svg": "15.15.4", "react-native-web": "^0.21.2", @@ -7495,6 +7496,21 @@ "hermes-estree": "0.35.0" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, "node_modules/hosted-git-info": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", @@ -10032,7 +10048,6 @@ "version": "4.18.1", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", - "dev": true, "license": "MIT" }, "node_modules/lodash.debounce": { @@ -10611,6 +10626,16 @@ "node": ">=10" } }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "license": "MIT", + "optional": true, + "engines": { + "node": "*" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -11597,7 +11622,6 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", @@ -11609,7 +11633,6 @@ "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true, "license": "MIT" }, "node_modules/psl": { @@ -11789,6 +11812,27 @@ } } }, + "node_modules/react-native-calendars": { + "version": "1.1314.0", + "resolved": "https://registry.npmjs.org/react-native-calendars/-/react-native-calendars-1.1314.0.tgz", + "integrity": "sha512-4DLAVto8Qo9L3ggL2vsY9Gk8FFpJWtne8F/3wN8yUb7Xha9/SKS4B+vs7xlhWjKeqZUHws/Vi/q/6IZ8s60kcQ==", + "license": "MIT", + "dependencies": { + "hoist-non-react-statics": "^3.3.1", + "lodash": "^4.17.15", + "memoize-one": "^5.2.1", + "prop-types": "^15.5.10", + "react-native-swipe-gestures": "^1.0.5", + "recyclerlistview": "^4.0.0", + "xdate": "^0.8.0" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "moment": "^2.29.4" + } + }, "node_modules/react-native-safe-area-context": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.7.0.tgz", @@ -11814,6 +11858,12 @@ "react-native": "*" } }, + "node_modules/react-native-swipe-gestures": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/react-native-swipe-gestures/-/react-native-swipe-gestures-1.0.5.tgz", + "integrity": "sha512-Ns7Bn9H/Tyw278+5SQx9oAblDZ7JixyzeOczcBK8dipQk2pD7Djkcfnf1nB/8RErAmMLL9iXgW0QHqiII8AhKw==", + "license": "MIT" + }, "node_modules/react-native-web": { "version": "0.21.2", "resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.21.2.tgz", @@ -11899,6 +11949,21 @@ "dev": true, "license": "MIT" }, + "node_modules/recyclerlistview": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/recyclerlistview/-/recyclerlistview-4.2.3.tgz", + "integrity": "sha512-STR/wj/FyT8EMsBzzhZ1l2goYirMkIgfV3gYEPxI3Kf3lOnu6f7Dryhyw7/IkQrgX5xtTcDrZMqytvteH9rL3g==", + "license": "Apache-2.0", + "dependencies": { + "lodash.debounce": "4.0.8", + "prop-types": "15.8.1", + "ts-object-utils": "0.0.5" + }, + "peerDependencies": { + "react": ">= 15.2.1", + "react-native": ">= 0.30.0" + } + }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -13174,6 +13239,12 @@ "typescript": ">=4.8.4" } }, + "node_modules/ts-object-utils": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/ts-object-utils/-/ts-object-utils-0.0.5.tgz", + "integrity": "sha512-iV0GvHqOmilbIKJsfyfJY9/dNHCs969z3so90dQWsO1eMMozvTpnB1MEaUbb3FYtZTGjv5sIy/xmslEz0Rg2TA==", + "license": "ISC" + }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", @@ -13867,6 +13938,12 @@ "node": ">=10.0.0" } }, + "node_modules/xdate": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/xdate/-/xdate-0.8.3.tgz", + "integrity": "sha512-1NhJWPJwN+VjbkACT9XHbQK4o6exeSVtS2CxhMPwUE7xQakoEFTlwra9YcqV/uHQVyeEUYoYC46VGDJ+etnIiw==", + "license": "(MIT OR GPL-2.0)" + }, "node_modules/xml-name-validator": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 938a560..fa739af 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -18,6 +18,7 @@ "react": "19.2.3", "react-dom": "19.2.3", "react-native": "0.86.0", + "react-native-calendars": "^1.1314.0", "react-native-safe-area-context": "~5.7.0", "react-native-svg": "15.15.4", "react-native-web": "^0.21.2", @@ -69,7 +70,7 @@ "^@test/(.*)$": "/__tests__/$1" }, "transformIgnorePatterns": [ - "/node_modules/(?!(.pnpm|react-native|@react-native|@react-native-community|expo|@expo|@expo-google-fonts|react-navigation|@react-navigation|@sentry/react-native|native-base|standard-navigation|lucide-react-native|react-native-svg|react-native-swipe-gestures))" + "/node_modules/(?!(.pnpm|react-native|@react-native|@react-native-community|expo|@expo|@expo-google-fonts|react-navigation|@react-navigation|@sentry/react-native|native-base|standard-navigation|lucide-react-native|react-native-svg|react-native-calendars|react-native-swipe-gestures))" ] }, "private": true diff --git a/frontend/src/features/schedule/application/AlarmPort.ts b/frontend/src/features/schedule/application/AlarmPort.ts new file mode 100644 index 0000000..6c3c3c0 --- /dev/null +++ b/frontend/src/features/schedule/application/AlarmPort.ts @@ -0,0 +1,24 @@ +import type { Schedule } from '@/contracts'; + +export type AlarmPort = { + /** + * Returns the system reference that should be persisted after syncing. + * Adapters for platforms without a local alarm should return the previous + * reference, while adapters that own the alarm lifecycle may return null + * when no alarm is armed. + */ + syncForSchedule(input: { + scheduleType: Schedule['schedule_type']; + title: string; + startTime: string | null; + offsetMinutes: number; + previousAlarmId: string | null; + shouldArm: boolean; + }): Promise; + /** + * Cancels an alarm and returns the reference that should remain on the + * schedule entity. This keeps platform-specific reference semantics out of + * the application service. + */ + cancel(alarmId: string | null | undefined): Promise; +}; diff --git a/frontend/src/features/schedule/application/ScheduleNotificationPort.ts b/frontend/src/features/schedule/application/ScheduleNotificationPort.ts new file mode 100644 index 0000000..0458d08 --- /dev/null +++ b/frontend/src/features/schedule/application/ScheduleNotificationPort.ts @@ -0,0 +1,4 @@ +import type { ScheduleConflict } from '@/contracts'; + +/** UI-facing feedback supplied by the app composition root. */ +export type ScheduleConflictNotifier = (conflicts: readonly ScheduleConflict[]) => void; diff --git a/frontend/src/features/schedule/application/ScheduleService.ts b/frontend/src/features/schedule/application/ScheduleService.ts new file mode 100644 index 0000000..67eb74f --- /dev/null +++ b/frontend/src/features/schedule/application/ScheduleService.ts @@ -0,0 +1,149 @@ +import type { Schedule, ScheduleUpsertPayload as ScheduleDraft } from '@/contracts'; +import { nextRequestId } from '@/shared/utils/requestId'; + +import type { AlarmPort } from './AlarmPort'; +import { scheduleFromUpsertPayload, toUpsertCommand } from '../data/adapters'; +import type { ScheduleCache } from '../data/ScheduleCache'; +import type { ScheduleRepositoryPort } from '../data/ScheduleRepositoryPort'; +import { markDeleted, nextStatusAfterToggle, withStatus } from '../domain/scheduleStatus'; +import type { ScheduleConflictNotifier } from './ScheduleNotificationPort'; + +export type ScheduleServiceDeps = { + repository: ScheduleRepositoryPort & { dispose?: () => void }; + cache: ScheduleCache; + getUserId: () => string; + alarmAdapter: AlarmPort; + /** UI feedback is supplied by the app composition root, never by this use case. */ + notifyConflicts?: ScheduleConflictNotifier; +}; + +export class ScheduleService { + private readonly alarm: AlarmPort; + private pushUnsubscribe: (() => void) | null = null; + private loadGeneration = 0; + + constructor(private readonly deps: ScheduleServiceDeps) { + this.alarm = deps.alarmAdapter; + } + + async bootstrap(): Promise { + const generation = ++this.loadGeneration; + const schedules = await this.deps.repository.list({ + status: null, + include_deleted: false, + }); + if (generation !== this.loadGeneration) return; + this.deps.cache.replaceAll(schedules); + if (!this.pushUnsubscribe) { + this.pushUnsubscribe = this.deps.repository.subscribe((event) => { + this.deps.cache.applyPush(event); + }); + } + } + + /** 重连后强制重新拉取列表。 */ + async resync(): Promise { + await this.bootstrap(); + } + + dispose(): void { + this.loadGeneration += 1; + this.pushUnsubscribe?.(); + this.pushUnsubscribe = null; + this.deps.repository.dispose?.(); + } + + getItems(): Schedule[] { + return this.deps.cache.getSnapshot(); + } + + subscribe(listener: (items: Schedule[]) => void): () => void { + return this.deps.cache.subscribe(listener); + } + + async saveDraft(draft: ScheduleDraft): Promise { + const userId = this.deps.getUserId(); + const requestId = nextRequestId('req_schedule'); + // A missing ID means create. The backend owns ID generation; sending a + // client-generated ID makes the MVP backend treat the command as an edit. + const command = toUpsertCommand(draft, requestId); + const existing = draft.schedule_id + ? (this.deps.cache.getSnapshot().find((item) => item.id === draft.schedule_id) ?? null) + : null; + + const response = await this.deps.repository.upsert(command); + if (!response.ok) { + throw new Error(response.error.message); + } + + if (response.payload.conflicts.length > 0) { + try { + this.deps.notifyConflicts?.(response.payload.conflicts); + } catch { + // User feedback must not turn a successful server write into a failed + // mutation when a host notifier is unavailable. + } + } + + const scheduleId = response.payload.schedule_id; + + const offsetMinutes = draft.time_remind_offset_minutes ?? 0; + const syncedSystemScheduleRefId = await this.alarm.syncForSchedule({ + scheduleType: draft.schedule_type, + title: draft.title, + startTime: draft.start_time ?? null, + offsetMinutes, + previousAlarmId: existing?.system_schedule_ref_id ?? null, + shouldArm: response.payload.status === 'scheduled', + }); + + const entity = scheduleFromUpsertPayload({ + draft: { ...draft, schedule_id: scheduleId }, + scheduleId, + userId, + status: response.payload.status, + geofenceArmed: response.payload.geofence_armed, + existing, + systemScheduleRefId: syncedSystemScheduleRefId, + }); + + this.deps.cache.upsert(entity); + return entity; + } + + async toggleDone(schedule: Schedule): Promise { + const nextStatus = nextStatusAfterToggle(schedule.status); + if (!nextStatus || nextStatus === 'deleted') return; + + const response = await this.deps.repository.updateStatus(schedule.id, nextStatus); + if (!response.ok) { + throw new Error(response.error.message); + } + + let systemScheduleRefId = schedule.system_schedule_ref_id; + if (nextStatus === 'done') { + systemScheduleRefId = await this.alarm.cancel(systemScheduleRefId); + } else { + systemScheduleRefId = await this.alarm.syncForSchedule({ + scheduleType: schedule.schedule_type, + title: schedule.title, + startTime: schedule.start_time, + offsetMinutes: schedule.time_remind_offset_minutes, + previousAlarmId: schedule.system_schedule_ref_id, + shouldArm: true, + }); + } + + this.deps.cache.upsert(withStatus(schedule, response.payload.status, systemScheduleRefId)); + } + + async deleteSchedule(schedule: Schedule): Promise { + if (schedule.status === 'deleted') return; + const response = await this.deps.repository.notifyDeleted(schedule.id); + if (!response.ok) { + throw new Error(response.error.message); + } + const systemScheduleRefId = await this.alarm.cancel(schedule.system_schedule_ref_id); + this.deps.cache.upsert(markDeleted(schedule, systemScheduleRefId)); + } +} diff --git a/frontend/src/features/schedule/calendar/MonthView.styles.ts b/frontend/src/features/schedule/calendar/MonthView.styles.ts new file mode 100644 index 0000000..c0a4a55 --- /dev/null +++ b/frontend/src/features/schedule/calendar/MonthView.styles.ts @@ -0,0 +1,102 @@ +import { StyleSheet } from 'react-native'; + +import { colors } from '@/shared/theme'; + +export const monthStyles = StyleSheet.create({ + monthContent: { paddingBottom: 28 }, + monthCard: { + backgroundColor: colors.surface, + borderColor: colors.line, + borderRadius: 20, + borderWidth: 1, + marginBottom: 14, + paddingHorizontal: 12, + paddingVertical: 14, + }, + monthHeader: { + alignItems: 'center', + flexDirection: 'row', + justifyContent: 'space-between', + marginBottom: 11, + minHeight: 46, + paddingHorizontal: 2, + }, + monthNavButton: { + alignItems: 'center', + backgroundColor: '#EFEDE7', + borderRadius: 9, + height: 29, + justifyContent: 'center', + minWidth: 42, + paddingHorizontal: 9, + }, + monthNavText: { color: colors.sub, fontSize: 10, fontWeight: '700' }, + monthYear: { color: '#8C938F', fontSize: 6, textAlign: 'center' }, + monthTitle: { + color: colors.ink, + fontSize: 16, + fontWeight: '700', + marginTop: 2, + textAlign: 'center', + }, + weekdayRow: { flexDirection: 'row', gap: 3, height: 20, marginBottom: 0 }, + weekday: { color: colors.sub, flex: 1, fontSize: 10, paddingBottom: 6, textAlign: 'center' }, + monthGrid: { gap: 3 }, + monthWeekRow: { flexDirection: 'row', gap: 3 }, + monthDay: { + alignItems: 'center', + backgroundColor: '#EFEFEF', + borderRadius: 10, + flex: 1, + height: 40, + justifyContent: 'center', + }, + monthDayActive: { backgroundColor: colors.deep }, + monthDaySelected: { backgroundColor: colors.limeSoft }, + monthDayMuted: { backgroundColor: 'rgba(239, 239, 239, 0.3)' }, + monthDayText: { color: colors.ink, fontSize: 12 }, + monthDayTextActive: { color: colors.surface, fontWeight: '800' }, + monthDayTextSelected: { color: colors.ink, fontWeight: '700' }, + monthDayTextMuted: { color: '#C2C6C3' }, + monthDot: { + alignItems: 'center', + backgroundColor: '#87A16C', + borderRadius: 2, + height: 4, + justifyContent: 'center', + marginTop: 3, + width: 4, + }, + monthDayActiveDot: { backgroundColor: colors.lime }, + monthCompletedMarker: { + backgroundColor: '#7CA38A', + borderRadius: 6, + height: 12, + marginTop: 2, + width: 12, + }, + monthSelectedHeading: { + alignItems: 'center', + borderBottomColor: colors.line, + borderBottomWidth: 1, + flexDirection: 'row', + justifyContent: 'space-between', + marginBottom: 10, + marginTop: 0, + minHeight: 35, + paddingBottom: 6, + }, + monthSelectedTitle: { color: colors.ink, fontSize: 16, fontWeight: '600' }, + scheduleEmpty: { + backgroundColor: colors.surface, + borderColor: colors.line, + borderRadius: 14, + borderStyle: 'dashed', + borderWidth: 1, + color: colors.sub, + fontSize: 12, + paddingHorizontal: 12, + paddingVertical: 18, + textAlign: 'center', + }, +}); diff --git a/frontend/src/features/schedule/calendar/MonthView.tsx b/frontend/src/features/schedule/calendar/MonthView.tsx new file mode 100644 index 0000000..b4c0db0 --- /dev/null +++ b/frontend/src/features/schedule/calendar/MonthView.tsx @@ -0,0 +1,158 @@ +import { Check } from 'lucide-react-native'; +import { Pressable, ScrollView, Text, View } from 'react-native'; + +import { colors } from '@/shared/theme'; +import type { Schedule } from '@/contracts'; +import { dateKey, formatMonthDay, WEEKDAY_LABELS } from '@/shared/utils/date'; + +import { ScheduleRow } from './ScheduleRow'; +import type { ScheduleIndex } from './scheduleIndex'; +import { schedulesOnDate } from './scheduleIndex'; +import { monthStyles as styles } from './MonthView.styles'; + +export function MonthView({ + now, + selectedDate, + scheduleIndex, + visibleMonth, + onMonthChange, + onOpenSchedule, + onSelectDate, + onToggleSchedule, +}: { + now: Date; + selectedDate: Date; + scheduleIndex: ScheduleIndex; + onMonthChange: (month: Date) => void; + onOpenSchedule: (scheduleId: string) => void; + onSelectDate: (date: Date) => void; + onToggleSchedule?: (schedule: Schedule) => void; + visibleMonth: Date; +}) { + const year = visibleMonth.getFullYear(); + const month = visibleMonth.getMonth(); + const firstDayOffset = (new Date(year, month, 1).getDay() + 6) % 7; + const days = Array.from( + { length: 42 }, + (_, index) => new Date(year, month, index - firstDayOffset + 1), + ); + const selectedKey = dateKey(selectedDate); + const todayKey = dateKey(now); + const selectedItems = schedulesOnDate(scheduleIndex, selectedDate); + const dateItems = scheduleIndex.byDateKey; + + return ( + + + + onMonthChange(new Date(year, month - 1, 1))} + style={styles.monthNavButton} + > + 上月 + + + {year} + {month + 1}月 + + onMonthChange(new Date(year, month + 1, 1))} + style={styles.monthNavButton} + > + 下月 + + + + {WEEKDAY_LABELS.map((day) => ( + + {day} + + ))} + + + {Array.from({ length: 6 }, (_, rowIndex) => ( + + {days.slice(rowIndex * 7, rowIndex * 7 + 7).map((day) => { + const inMonth = day.getMonth() === month; + const key = dateKey(day); + const active = key === todayKey; + const selected = key === selectedKey; + const dayItems = dateItems.get(key) ?? []; + const hasCompletedMarker = + inMonth && + dayItems.length > 0 && + dayItems.every((item) => item.status === 'done'); + const hasRegularMarker = + inMonth && dayItems.some((item) => item.status === 'scheduled'); + return ( + onSelectDate(day)} + style={[ + styles.monthDay, + active && styles.monthDayActive, + selected && !active && styles.monthDaySelected, + !inMonth && styles.monthDayMuted, + ]} + > + + {day.getDate()} + + {(hasCompletedMarker || hasRegularMarker) && ( + + {hasCompletedMarker && ( + + )} + + )} + + ); + })} + + ))} + + + + {formatMonthDay(selectedDate)} + + {selectedItems.length > 0 ? ( + selectedItems.map((item, index) => ( + onOpenSchedule(item.id)} + onToggle={onToggleSchedule ? () => onToggleSchedule(item) : undefined} + showConnector={index < selectedItems.length - 1} + /> + )) + ) : ( + 这一天暂无详细安排 + )} + + ); +} diff --git a/frontend/src/features/schedule/calendar/ScheduleRow.tsx b/frontend/src/features/schedule/calendar/ScheduleRow.tsx new file mode 100644 index 0000000..bb9f6df --- /dev/null +++ b/frontend/src/features/schedule/calendar/ScheduleRow.tsx @@ -0,0 +1,81 @@ +import { Check } from 'lucide-react-native'; +import { Pressable, Text, View } from 'react-native'; + +import { colors } from '@/shared/theme'; +import type { Schedule } from '@/contracts'; + +import { scheduleColor, scheduleRange, scheduleTime } from '../presentation/scheduleFormat'; +import { scheduleRowStyles as styles } from './scheduleRow.styles'; + +export function ScheduleRow({ + compact = false, + item, + onPress, + onToggle, + showConnector = true, +}: { + compact?: boolean; + item: Schedule; + onPress?: () => void; + onToggle?: () => void; + showConnector?: boolean; +}) { + const done = item.status === 'done'; + + return ( + + + {scheduleTime(item)} + + + { + event?.stopPropagation?.(); + onToggle?.(); + }} + style={[ + styles.scheduleDot, + { backgroundColor: scheduleColor(item) }, + done && styles.scheduleDotCompleted, + ]} + > + {done ? : null} + + {showConnector && } + + + + + {item.title} + + + {(item.location_name || item.notes) && ( + {item.location_name ?? item.notes} + )} + + {scheduleRange(item)} + + + + ); +} diff --git a/frontend/src/features/schedule/calendar/scheduleIndex.ts b/frontend/src/features/schedule/calendar/scheduleIndex.ts new file mode 100644 index 0000000..176dc02 --- /dev/null +++ b/frontend/src/features/schedule/calendar/scheduleIndex.ts @@ -0,0 +1,48 @@ +import type { Schedule } from '@/contracts'; +import { dateKey } from '@/shared/utils/date'; +import { scheduleDate } from '../presentation/scheduleFormat'; + +export type ScheduleIndex = { + byDateKey: Map; + locationSchedules: Schedule[]; + timeSchedules: Schedule[]; + markedDateKeys: string[]; +}; + +/** 一次遍历活跃日程,按日期/类型分桶,供月视图使用。 */ +export function buildScheduleIndex(items: Schedule[]): ScheduleIndex { + const byDateKey = new Map(); + const locationSchedules: Schedule[] = []; + const timeSchedules: Schedule[] = []; + const markedKeys = new Set(); + + for (const item of items) { + if (item.status === 'deleted') continue; + + if (item.schedule_type === 'location') { + locationSchedules.push(item); + } + if (item.schedule_type === 'time') { + timeSchedules.push(item); + } + + const date = scheduleDate(item); + if (!date) continue; + const key = dateKey(date); + const bucket = byDateKey.get(key); + if (bucket) bucket.push(item); + else byDateKey.set(key, [item]); + if (item.schedule_type === 'time') markedKeys.add(key); + } + + return { + byDateKey, + locationSchedules, + timeSchedules, + markedDateKeys: [...markedKeys], + }; +} + +export function schedulesOnDate(index: ScheduleIndex, date: Date): Schedule[] { + return index.byDateKey.get(dateKey(date)) ?? []; +} diff --git a/frontend/src/features/schedule/calendar/scheduleRow.styles.ts b/frontend/src/features/schedule/calendar/scheduleRow.styles.ts new file mode 100644 index 0000000..870565f --- /dev/null +++ b/frontend/src/features/schedule/calendar/scheduleRow.styles.ts @@ -0,0 +1,67 @@ +import { StyleSheet } from 'react-native'; + +import { colors } from '@/shared/theme'; + +export const scheduleRowStyles = StyleSheet.create({ + scheduleRow: { alignItems: 'flex-start', flexDirection: 'row', minHeight: 84 }, + scheduleRowCompact: { minHeight: 72 }, + scheduleRowCompleted: { opacity: 0.82 }, + scheduleTime: { + color: '#7D8983', + fontSize: 11, + lineHeight: 14, + paddingTop: 7, + width: 58, + }, + scheduleTimeCompact: { paddingTop: 6, width: 52 }, + scheduleRail: { alignItems: 'center', alignSelf: 'stretch', paddingTop: 6, width: 17 }, + scheduleRailCompact: { paddingTop: 8, width: 12 }, + scheduleDot: { borderRadius: 6, height: 11, width: 11, zIndex: 1 }, + scheduleDotCompleted: { + alignItems: 'center', + backgroundColor: '#7CA38A', + justifyContent: 'center', + }, + scheduleLine: { + backgroundColor: '#D7DCD7', + bottom: 0, + position: 'absolute', + top: 19, + width: 1, + }, + scheduleCopy: { + borderBottomColor: colors.line, + borderBottomWidth: 1, + flex: 1, + minWidth: 0, + paddingBottom: 15, + paddingTop: 3, + }, + scheduleCopyCompact: { marginBottom: 4, paddingBottom: 14, paddingTop: 4 }, + scheduleHeading: { + alignItems: 'flex-start', + flexDirection: 'row', + justifyContent: 'space-between', + }, + scheduleTitleCompact: { fontSize: 13, lineHeight: 22 }, + scheduleTitle: { + color: colors.ink, + flex: 1, + fontSize: 14, + fontWeight: '700', + lineHeight: 19, + }, + scheduleTitleCompleted: { color: '#7F8882', textDecorationLine: 'line-through' }, + scheduleMeta: { + alignSelf: 'flex-start', + backgroundColor: '#ECF2D7', + borderRadius: 8, + color: '#70814F', + fontSize: 8, + marginTop: 6, + paddingHorizontal: 8, + paddingVertical: 4, + }, + scheduleRange: { color: '#818B85', fontSize: 10, lineHeight: 14, marginTop: 7 }, + scheduleRangeCompact: { color: '#747C77', fontSize: 11, lineHeight: 16, marginTop: 4 }, +}); diff --git a/frontend/src/features/schedule/data/ScheduleCache.ts b/frontend/src/features/schedule/data/ScheduleCache.ts new file mode 100644 index 0000000..42550a8 --- /dev/null +++ b/frontend/src/features/schedule/data/ScheduleCache.ts @@ -0,0 +1,52 @@ +import type { Schedule } from '@/contracts'; + +import { compareSchedules } from '../domain/scheduleOrdering'; + +import type { SchedulePushEvent } from './ScheduleRepositoryPort'; + +/** 本地列表真相:由 list/upsert/push 更新,供 UI 订阅。 */ +export class ScheduleCache { + private items: Schedule[] = []; + private readonly listeners = new Set<(items: Schedule[]) => void>(); + + getSnapshot(): Schedule[] { + return this.items; + } + + subscribe(listener: (items: Schedule[]) => void): () => void { + this.listeners.add(listener); + listener(this.items); + return () => this.listeners.delete(listener); + } + + replaceAll(schedules: Schedule[]): void { + this.items = [...schedules].sort(compareSchedules); + this.emit(); + } + + upsert(schedule: Schedule): void { + const index = this.items.findIndex((item) => item.id === schedule.id); + if (index < 0) { + this.items = [...this.items, schedule].sort(compareSchedules); + } else { + const next = [...this.items]; + next[index] = schedule; + this.items = next.sort(compareSchedules); + } + this.emit(); + } + + applyPush(event: SchedulePushEvent): void { + if (event.type === 'schedule.snapshot') { + this.replaceAll(event.schedules); + return; + } + this.upsert(event.schedule); + } + + private emit(): void { + for (const listener of this.listeners) { + listener(this.items); + } + } +} diff --git a/frontend/src/features/schedule/data/ScheduleRepositoryPort.ts b/frontend/src/features/schedule/data/ScheduleRepositoryPort.ts new file mode 100644 index 0000000..15bc196 --- /dev/null +++ b/frontend/src/features/schedule/data/ScheduleRepositoryPort.ts @@ -0,0 +1,24 @@ +import type { + Schedule, + ScheduleDeletedAck, + ScheduleListQueryPayload, + ScheduleStatus, + ScheduleStatusUpdateResponse, + ScheduleUpsertCommand, + ScheduleUpsertResponse, +} from '@/contracts'; + +export type SchedulePushEvent = + | { type: 'schedule.updated'; schedule: Schedule } + | { type: 'schedule.snapshot'; schedules: Schedule[] }; + +export interface ScheduleRepositoryPort { + list(query: ScheduleListQueryPayload): Promise; + upsert(command: ScheduleUpsertCommand): Promise; + updateStatus( + scheduleId: string, + status: Extract, + ): Promise; + notifyDeleted(scheduleId: string): Promise; + subscribe(listener: (event: SchedulePushEvent) => void): () => void; +} diff --git a/frontend/src/features/schedule/data/ScheduleTransport.ts b/frontend/src/features/schedule/data/ScheduleTransport.ts new file mode 100644 index 0000000..c779f03 --- /dev/null +++ b/frontend/src/features/schedule/data/ScheduleTransport.ts @@ -0,0 +1,14 @@ +import type { WsJsonMessage } from '@/contracts'; + +/** + * schedule data 层所需的最小传输面。 + * app 注入 WsClient;feature 不依赖 SessionProvider。 + */ +export type ScheduleTransport = { + onMessage(listener: (message: WsJsonMessage | ArrayBuffer) => void): () => void; + request( + message: WsJsonMessage & { request_id: string }, + isMatch?: (response: WsJsonMessage) => boolean, + ): Promise; + sendJson(message: WsJsonMessage): void; +}; diff --git a/frontend/src/features/schedule/data/WsScheduleRepository.ts b/frontend/src/features/schedule/data/WsScheduleRepository.ts new file mode 100644 index 0000000..928d3c9 --- /dev/null +++ b/frontend/src/features/schedule/data/WsScheduleRepository.ts @@ -0,0 +1,112 @@ +import type { + Schedule, + ScheduleDeleted, + ScheduleDeletedAck, + ScheduleListQuery, + ScheduleListResponse, + ScheduleListQueryPayload, + ScheduleStatus, + ScheduleStatusUpdateCommand, + ScheduleStatusUpdateResponse, + ScheduleUpsertCommand, + ScheduleUpsertResponse, + WsJsonMessage, +} from '@/contracts'; +import { nextRequestId } from '@/shared/utils/requestId'; + +import type { SchedulePushEvent, ScheduleRepositoryPort } from './ScheduleRepositoryPort'; +import type { ScheduleTransport } from './ScheduleTransport'; + +export class WsScheduleRepository implements ScheduleRepositoryPort { + private readonly listeners = new Set<(event: SchedulePushEvent) => void>(); + private readonly unsubscribeClient: () => void; + + constructor(private readonly client: ScheduleTransport) { + this.unsubscribeClient = this.client.onMessage((message) => { + if (message instanceof ArrayBuffer) return; + this.routePush(message); + }); + } + + dispose(): void { + this.unsubscribeClient(); + this.listeners.clear(); + } + + async list(query: ScheduleListQueryPayload): Promise { + const request: ScheduleListQuery = { + type: 'schedule.list.query', + request_id: nextRequestId('req_list'), + payload: query, + }; + const response = await this.client.request(request, (message) => { + return ( + message.request_id === request.request_id && + (message.type === 'schedule.list.result' || message.type === 'schedule.list.error') + ); + }); + if (!response.ok) { + throw new Error(response.error.message); + } + return response.payload.schedules; + } + + async upsert(command: ScheduleUpsertCommand): Promise { + return this.client.request(command, (message) => { + return ( + message.request_id === command.request_id && + (message.type === 'schedule.upsert.result' || message.type === 'schedule.upsert.error') + ); + }); + } + + async updateStatus( + scheduleId: string, + status: Extract, + ): Promise { + const command: ScheduleStatusUpdateCommand = { + type: 'schedule.status.command', + request_id: nextRequestId('req_status'), + payload: { schedule_id: scheduleId, status }, + }; + return this.client.request(command, (message) => { + return ( + message.request_id === command.request_id && + (message.type === 'schedule.status.result' || message.type === 'schedule.status.error') + ); + }); + } + + async notifyDeleted(scheduleId: string): Promise { + const command: ScheduleDeleted = { + type: 'schedule.deleted', + request_id: nextRequestId('req_deleted'), + schedule_id: scheduleId, + deleted: true, + timestamp: new Date().toISOString(), + }; + // 先登记 pending 再发送,避免 Fake 同步 ACK 竞态。 + return this.client.request(command, (message) => { + return ( + message.type === 'schedule.deleted.ack' && + (message.request_id == null || message.request_id === command.request_id) && + message.schedule_id === scheduleId + ); + }); + } + + subscribe(listener: (event: SchedulePushEvent) => void): () => void { + this.listeners.add(listener); + return () => this.listeners.delete(listener); + } + + private routePush(message: WsJsonMessage): void { + if (message.type === 'schedule.updated' && message.schedule) { + const event: SchedulePushEvent = { + type: 'schedule.updated', + schedule: message.schedule as Schedule, + }; + for (const listener of this.listeners) listener(event); + } + } +} diff --git a/frontend/src/features/schedule/data/adapters.ts b/frontend/src/features/schedule/data/adapters.ts new file mode 100644 index 0000000..34c9313 --- /dev/null +++ b/frontend/src/features/schedule/data/adapters.ts @@ -0,0 +1,116 @@ +import type { + Schedule, + ScheduleDraftFields, + ScheduleUpsertCommand, + ScheduleUpsertPayload, + VoiceParseDraft, +} from '@/contracts'; + +type ScheduleDraft = ScheduleUpsertPayload; + +/** 草稿业务字段归一化(`?? null`);调用方再补 source_mode / 特有默认值。 */ +function normalizeScheduleDraftFields(fields: ScheduleDraftFields) { + return { + schedule_type: fields.schedule_type, + title: fields.title, + notes: fields.notes ?? null, + start_time: fields.start_time ?? null, + end_time: fields.end_time ?? null, + timezone: fields.timezone ?? null, + location_name: fields.location_name ?? null, + location_address: fields.location_address ?? null, + latitude: fields.latitude ?? null, + longitude: fields.longitude ?? null, + geofence_radius_meters: fields.geofence_radius_meters ?? null, + geofence_armed: fields.geofence_armed ?? null, + time_remind_offset_minutes: fields.time_remind_offset_minutes ?? null, + }; +} + +/** Schedule → wire upsert payload / 编辑回填草稿(同一份字段投影)。 */ +function toUpsertPayload(schedule: Schedule): ScheduleUpsertPayload { + return { + schedule_id: schedule.id, + source_mode: schedule.source_mode, + schedule_type: schedule.schedule_type, + title: schedule.title, + notes: schedule.notes, + start_time: schedule.start_time, + end_time: schedule.end_time, + timezone: schedule.timezone, + location_name: schedule.location_name, + location_address: schedule.location_address, + latitude: schedule.latitude, + longitude: schedule.longitude, + geofence_radius_meters: schedule.geofence_radius_meters, + geofence_armed: schedule.geofence_armed, + time_remind_offset_minutes: schedule.time_remind_offset_minutes, + }; +} + +export function upsertDraftForSchedule(schedule: Schedule): ScheduleDraft { + return toUpsertPayload(schedule); +} + +/** AppShell:将语音解析草稿映射为日程草稿。 */ +export function scheduleDraftFromVoiceParse(draft: VoiceParseDraft): ScheduleDraft { + return { + source_mode: 'voice', + ...normalizeScheduleDraftFields({ + ...draft, + time_remind_offset_minutes: draft.time_remind_offset_minutes ?? 0, + }), + }; +} + +export function toUpsertCommand(draft: ScheduleDraft, requestId: string): ScheduleUpsertCommand { + return { + type: 'schedule.upsert.command', + request_id: requestId, + payload: draft, + }; +} + +/** draft → Schedule 实体(保存时组装)。 */ +export function scheduleFromUpsertPayload(input: { + draft: ScheduleDraft; + scheduleId: string; + userId: string; + status: Schedule['status']; + geofenceArmed: boolean; + existing?: Schedule | null; + systemScheduleRefId?: string | null; +}): Schedule { + const { draft, existing } = input; + const fields = normalizeScheduleDraftFields(draft); + const now = new Date().toISOString(); + return { + id: input.scheduleId, + user_id: existing?.user_id ?? input.userId, + source_mode: draft.source_mode, + schedule_type: fields.schedule_type, + status: input.status, + title: fields.title, + notes: fields.notes, + start_time: fields.start_time, + end_time: fields.end_time, + timezone: fields.timezone, + location_name: fields.location_name, + location_address: fields.location_address, + latitude: fields.latitude, + longitude: fields.longitude, + geofence_radius_meters: + fields.geofence_radius_meters ?? existing?.geofence_radius_meters ?? 100, + geofence_armed: input.geofenceArmed, + time_remind_offset_minutes: fields.time_remind_offset_minutes ?? 0, + time_triggered_at: existing?.time_triggered_at ?? null, + geo_triggered_at: existing?.geo_triggered_at ?? null, + system_schedule_ref_id: + input.systemScheduleRefId !== undefined + ? input.systemScheduleRefId + : (existing?.system_schedule_ref_id ?? null), + system_alarm_ref_id: existing?.system_alarm_ref_id ?? null, + created_at: existing?.created_at ?? now, + updated_at: now, + }; +} diff --git a/frontend/src/features/schedule/detail/ScheduleDetailSheet.tsx b/frontend/src/features/schedule/detail/ScheduleDetailSheet.tsx new file mode 100644 index 0000000..56e5714 --- /dev/null +++ b/frontend/src/features/schedule/detail/ScheduleDetailSheet.tsx @@ -0,0 +1,200 @@ +import { + CalendarClock, + CheckCircle2, + Clock3, + Pencil, + RotateCcw, + Trash2, +} from 'lucide-react-native'; +import { Modal, Pressable, ScrollView, Text, View } from 'react-native'; + +import { BackButton } from '@/shared/components/BackButton'; +import { useAppDialog } from '@/shared/components/AppDialogProvider'; +import type { Schedule } from '@/contracts'; +import { colors } from '@/shared/theme'; +import { formatFullDate } from '@/shared/utils/date'; + +import { + scheduleColor, + scheduleDate, + scheduleDuration, + scheduleRange, + scheduleSourceLabel, + scheduleStatusLabel, +} from '../presentation/scheduleFormat'; +import { detailStyles as styles } from './detail.styles'; + +export function ScheduleDetailSheet({ + onClose, + onDelete, + onEdit, + onOpenDay, + onToggle, + schedule, +}: { + onClose: () => void; + onDelete?: () => void; + onEdit?: () => void; + onOpenDay: (date: Date) => void; + onToggle?: () => void; + schedule: Schedule | null; +}) { + const { confirm } = useAppDialog(); + const editable = schedule != null && schedule.status !== 'deleted'; + const actionIsEdit = Boolean(onEdit && editable); + const canDelete = Boolean(onDelete && editable); + const canToggle = Boolean(onToggle && editable); + const statusLabel = schedule ? scheduleStatusLabel(schedule) : ''; + const isDone = schedule?.status === 'done'; + const itemDate = schedule ? scheduleDate(schedule) : null; + const displayDate = itemDate ?? new Date(); + const dateLabel = schedule?.start_time ? formatFullDate(displayDate) : '按地点触发'; + const rangeLabel = schedule ? scheduleRange(schedule) : ''; + + const handleDelete = async () => { + if (!canDelete) return; + const confirmed = await confirm({ + title: '删除日程', + message: '确定删除这个日程吗?相关提醒也会一并取消。', + confirmLabel: '删除', + cancelLabel: '取消', + tone: 'danger', + }); + if (!confirmed) return; + onDelete?.(); + onClose(); + }; + + return ( + + + + {schedule && ( + + + + + 安排详情 + + + {isDone && } + + {statusLabel} + + + + + + {scheduleSourceLabel(schedule)} + {schedule.title} + + {isDone ? '已完成 · 可回顾这次安排' : '安排已加入你的时间轴'} + + + + + + + + 日期与时间 + {dateLabel} + {rangeLabel} + + {scheduleDuration(schedule)} + + + + + + 状态 + + + {statusLabel} + + + + + + + {isDone ? ( + + ) : ( + + )} + {isDone ? '恢复' : '完成'} + + void handleDelete()} + style={[ + styles.scheduleModalSecondaryAction, + canDelete && styles.scheduleModalDeleteAction, + !canDelete && styles.scheduleModalDeleteDisabled, + ]} + > + {canDelete ? : null} + + 删除 + + + { + onClose(); + if (actionIsEdit) onEdit?.(); + else onOpenDay(displayDate); + }} + style={styles.scheduleModalPrimaryAction} + > + {actionIsEdit && } + + {actionIsEdit ? '编辑日程' : '查看当天'} + + + + + )} + + + ); +} diff --git a/frontend/src/features/schedule/detail/detail.styles.ts b/frontend/src/features/schedule/detail/detail.styles.ts new file mode 100644 index 0000000..8e7ea06 --- /dev/null +++ b/frontend/src/features/schedule/detail/detail.styles.ts @@ -0,0 +1,158 @@ +import { StyleSheet } from 'react-native'; + +import { colors } from '@/shared/theme'; + +export const detailStyles = StyleSheet.create({ + scheduleModalBackdrop: { + alignItems: 'center', + backgroundColor: 'rgba(20, 40, 33, 0.34)', + flex: 1, + justifyContent: 'center', + paddingHorizontal: 14, + }, + scheduleModalDismiss: { + bottom: 0, + left: 0, + position: 'absolute', + right: 0, + top: 0, + }, + scheduleModalSheet: { + backgroundColor: '#F7F8F6', + borderRadius: 24, + maxHeight: '88%', + maxWidth: 520, + overflow: 'hidden', + width: '100%', + }, + scheduleModalReferenceHeader: { + alignItems: 'center', + backgroundColor: 'rgba(255, 255, 255, 0.94)', + borderBottomColor: colors.line, + borderBottomWidth: 1, + flexDirection: 'row', + gap: 10, + minHeight: 56, + paddingHorizontal: 16, + paddingVertical: 8, + }, + scheduleModalHeaderCopy: { flex: 1, minWidth: 0 }, + scheduleModalHeaderTitle: { + color: colors.ink, + fontSize: 13, + fontWeight: '700', + lineHeight: 18, + marginTop: 2, + }, + scheduleModalStatus: { + alignItems: 'center', + backgroundColor: '#EDF2DA', + borderRadius: 8, + flexDirection: 'row', + gap: 4, + paddingHorizontal: 8, + paddingVertical: 5, + }, + scheduleModalStatusText: { color: '#5C7045', fontSize: 8, fontWeight: '700' }, + scheduleModalStatusCompleted: { backgroundColor: '#E4EEE6' }, + scheduleModalStatusTextCompleted: { color: '#63866E' }, + scheduleModalScroll: { flexGrow: 0, flexShrink: 1 }, + scheduleModalScrollContent: { paddingBottom: 16, paddingHorizontal: 20, paddingTop: 18 }, + scheduleModalTitleBlock: { + paddingBottom: 16, + paddingRight: 2, + }, + scheduleModalSource: { color: '#73806F', fontSize: 9, fontWeight: '600' }, + scheduleModalTitle: { + color: colors.ink, + fontSize: 22, + fontWeight: '700', + lineHeight: 29, + marginTop: 7, + }, + scheduleModalSubtitle: { color: '#7B877F', fontSize: 10, lineHeight: 15, marginTop: 7 }, + scheduleModalTimeCard: { + alignItems: 'center', + backgroundColor: colors.surface, + borderColor: '#DFE3DD', + borderRadius: 8, + borderWidth: 1, + flexDirection: 'row', + gap: 12, + justifyContent: 'space-between', + padding: 15, + }, + scheduleModalTimeIcon: { + alignItems: 'center', + borderRadius: 10, + flexShrink: 0, + height: 35, + justifyContent: 'center', + width: 35, + }, + scheduleModalTimeCopy: { flex: 1, minWidth: 0 }, + scheduleModalTimeEyebrow: { color: colors.sub, fontSize: 8 }, + scheduleModalDate: { color: colors.ink, fontSize: 13, fontWeight: '700', marginTop: 5 }, + scheduleModalTime: { color: '#64716A', fontSize: 11, marginTop: 4 }, + scheduleModalDuration: { + backgroundColor: colors.deep, + borderRadius: 8, + color: colors.surface, + flexShrink: 0, + fontSize: 9, + fontWeight: '700', + paddingHorizontal: 8, + paddingVertical: 6, + }, + scheduleModalMeta: { borderTopColor: colors.line, borderTopWidth: 1, marginTop: 14 }, + scheduleModalMetaRow: { + alignItems: 'center', + borderBottomColor: colors.line, + borderBottomWidth: 1, + flexDirection: 'row', + justifyContent: 'space-between', + minHeight: 43, + }, + scheduleModalMetaLabelGroup: { alignItems: 'center', flexDirection: 'row', gap: 7 }, + scheduleModalMetaLabel: { color: colors.sub, fontSize: 10 }, + scheduleModalMetaValue: { color: colors.ink, fontSize: 11, fontWeight: '600' }, + scheduleModalCompleted: { color: '#63866E' }, + scheduleModalActions: { + backgroundColor: colors.surface, + borderTopColor: colors.line, + borderTopWidth: 1, + flexDirection: 'row', + gap: 9, + minHeight: 66, + paddingHorizontal: 20, + paddingBottom: 12, + paddingTop: 10, + }, + scheduleModalSecondaryAction: { + alignItems: 'center', + backgroundColor: '#E9ECE8', + borderRadius: 10, + flex: 1, + flexDirection: 'row', + gap: 6, + height: 44, + justifyContent: 'center', + }, + scheduleModalSecondaryText: { color: colors.ink, fontSize: 10, fontWeight: '700' }, + scheduleModalDeleteAction: { + backgroundColor: colors.peach, + }, + scheduleModalDeleteText: { color: colors.coral }, + scheduleModalDeleteDisabled: { opacity: 0.45 }, + scheduleModalPrimaryAction: { + alignItems: 'center', + backgroundColor: colors.deep, + borderRadius: 10, + flex: 1.35, + flexDirection: 'row', + gap: 6, + height: 44, + justifyContent: 'center', + }, + scheduleModalPrimaryText: { color: colors.surface, fontSize: 10, fontWeight: '700' }, +}); diff --git a/frontend/src/features/schedule/domain/scheduleOrdering.ts b/frontend/src/features/schedule/domain/scheduleOrdering.ts new file mode 100644 index 0000000..051592c --- /dev/null +++ b/frontend/src/features/schedule/domain/scheduleOrdering.ts @@ -0,0 +1,10 @@ +import type { Schedule } from '@/contracts'; + +export function compareSchedules(first: Schedule, second: Schedule) { + if (first.start_time && second.start_time) { + return new Date(first.start_time).getTime() - new Date(second.start_time).getTime(); + } + if (first.start_time) return -1; + if (second.start_time) return 1; + return new Date(second.created_at).getTime() - new Date(first.created_at).getTime(); +} diff --git a/frontend/src/features/schedule/domain/scheduleStatus.ts b/frontend/src/features/schedule/domain/scheduleStatus.ts new file mode 100644 index 0000000..88259ec --- /dev/null +++ b/frontend/src/features/schedule/domain/scheduleStatus.ts @@ -0,0 +1,28 @@ +import type { Schedule } from '@/contracts'; + +export function nextStatusAfterToggle(status: Schedule['status']): Schedule['status'] | null { + if (status === 'deleted') return null; + return status === 'done' ? 'scheduled' : 'done'; +} + +export function markDeleted(schedule: Schedule, systemScheduleRefId: string | null): Schedule { + return { + ...schedule, + status: 'deleted', + system_schedule_ref_id: systemScheduleRefId, + updated_at: new Date().toISOString(), + }; +} + +export function withStatus( + schedule: Schedule, + status: Schedule['status'], + systemScheduleRefId: string | null, +): Schedule { + return { + ...schedule, + status, + system_schedule_ref_id: systemScheduleRefId, + updated_at: new Date().toISOString(), + }; +} diff --git a/frontend/src/features/schedule/editor/ClearFieldButton.tsx b/frontend/src/features/schedule/editor/ClearFieldButton.tsx new file mode 100644 index 0000000..bbb3b24 --- /dev/null +++ b/frontend/src/features/schedule/editor/ClearFieldButton.tsx @@ -0,0 +1,23 @@ +import { Pressable, Text } from 'react-native'; + +import { createSheetStyles as styles } from './createSheet.styles'; + +export function ClearFieldButton({ + accessibilityLabel, + onPress, +}: { + accessibilityLabel: string; + onPress: () => void; +}) { + return ( + + 清除 + + ); +} diff --git a/frontend/src/features/schedule/editor/DateTimeField.tsx b/frontend/src/features/schedule/editor/DateTimeField.tsx new file mode 100644 index 0000000..eda3ba5 --- /dev/null +++ b/frontend/src/features/schedule/editor/DateTimeField.tsx @@ -0,0 +1,56 @@ +import { useState } from 'react'; +import { Pressable, Text, View } from 'react-native'; + +import { DatePickerSheet } from '@/shared/components/DatePickerSheet'; +import { TimePickerSheet } from '@/shared/components/TimePickerSheet'; + +import { createSheetStyles as styles } from './createSheet.styles'; +import { formatDateValue, parsePickerValue, type PickerMode } from './datetime'; + +/** 统一日期/时间字段:日期走 DatePickerSheet,时间走 TimePickerSheet。 */ +export function DateTimeField({ + accessibilityLabel, + mode, + onChange, + placeholder, + value, +}: { + accessibilityLabel: string; + mode: PickerMode; + onChange: (value: string) => void; + placeholder: string; + value: string; +}) { + const [open, setOpen] = useState(false); + const selected = parsePickerValue(value, mode); + + return ( + + setOpen(true)} + style={styles.pickerField} + > + + {value || placeholder} + + + {mode === 'date' ? ( + setOpen(false)} + onSelect={(date) => onChange(formatDateValue(date))} + selectedDate={selected} + visible={open} + /> + ) : ( + setOpen(false)} + onSelect={onChange} + selectedTime={selected} + visible={open} + /> + )} + + ); +} diff --git a/frontend/src/features/schedule/editor/StandardCreateModal.tsx b/frontend/src/features/schedule/editor/StandardCreateModal.tsx new file mode 100644 index 0000000..2545ece --- /dev/null +++ b/frontend/src/features/schedule/editor/StandardCreateModal.tsx @@ -0,0 +1,53 @@ +import { KeyboardAvoidingView, Modal, Platform, Pressable, View } from 'react-native'; + +import type { ScheduleUpsertPayload as ScheduleDraft } from '@/contracts'; + +import type { SavedLocation } from '../location'; +import { createSheetStyles as styles } from './createSheet.styles'; +import { StandardCreateSheet } from './StandardCreateSheet'; + +export function StandardCreateModal({ + initialDraft, + onClose, + onSave, + onUpsertLocation, + savedLocations, + visible, +}: { + initialDraft: ScheduleDraft | null; + onClose: () => void; + onSave: (draft: ScheduleDraft) => void | Promise; + onUpsertLocation: (location: SavedLocation) => void; + savedLocations: SavedLocation[]; + visible: boolean; +}) { + return ( + + + + + + + + + ); +} diff --git a/frontend/src/features/schedule/editor/StandardCreateSheet.tsx b/frontend/src/features/schedule/editor/StandardCreateSheet.tsx new file mode 100644 index 0000000..ec04947 --- /dev/null +++ b/frontend/src/features/schedule/editor/StandardCreateSheet.tsx @@ -0,0 +1,358 @@ +import { useState } from 'react'; +import { ChevronDown, ChevronUp, MapPin } from 'lucide-react-native'; +import { Pressable, ScrollView, Text, TextInput, View } from 'react-native'; + +import { colors } from '@/shared/theme'; +import type { ScheduleType, ScheduleUpsertPayload as ScheduleDraft } from '@/contracts'; +import type { SavedLocation } from '../location'; +import { createSavedLocation, matchSavedLocation, LocationPickerSheet } from '../location'; + +import { + currentTimezone, + dateAndTimeFromIso, + defaultCreateDateAndTime, + isoFromDateAndTime, + optionalNumber, +} from './datetime'; +import { createSheetStyles as styles } from './createSheet.styles'; +import { ClearFieldButton } from './ClearFieldButton'; +import { DateTimeField } from './DateTimeField'; + +export function StandardCreateSheet({ + initialDraft, + onClose, + onSave, + onUpsertLocation, + savedLocations, +}: { + initialDraft?: ScheduleDraft | null; + onClose: () => void; + onSave: (draft: ScheduleDraft) => void | Promise; + onUpsertLocation: (location: SavedLocation) => void; + savedLocations: SavedLocation[]; +}) { + const initialStart = initialDraft?.start_time + ? dateAndTimeFromIso(initialDraft.start_time) + : defaultCreateDateAndTime(); + const initialEnd = dateAndTimeFromIso(initialDraft?.end_time); + const initialLocation = + matchSavedLocation(savedLocations, { + latitude: initialDraft?.latitude, + longitude: initialDraft?.longitude, + location_name: initialDraft?.location_name, + location_address: initialDraft?.location_address, + }) ?? + (initialDraft?.latitude != null && initialDraft?.longitude != null + ? createSavedLocation({ + address: initialDraft.location_address ?? '', + latitude: initialDraft.latitude, + longitude: initialDraft.longitude, + name: initialDraft.location_name ?? undefined, + }) + : null); + const [title, setTitle] = useState(initialDraft?.title ?? ''); + const [notes, setNotes] = useState(initialDraft?.notes ?? ''); + const [date, setDate] = useState(initialStart.date); + const [start, setStart] = useState(initialStart.time); + const [end, setEnd] = useState(initialEnd.time); + const [selectedLocation, setSelectedLocation] = useState(initialLocation); + const [geofenceRadius, setGeofenceRadius] = useState( + String(initialDraft?.geofence_radius_meters ?? 100), + ); + const [remindOffset, setRemindOffset] = useState( + String(initialDraft?.time_remind_offset_minutes ?? 0), + ); + const [moreOpen, setMoreOpen] = useState(() => + Boolean( + initialDraft?.notes || + initialEnd.time || + (initialDraft?.geofence_radius_meters != null && + initialDraft.geofence_radius_meters !== 100) || + (initialDraft?.time_remind_offset_minutes != null && + initialDraft.time_remind_offset_minutes !== 0), + ), + ); + const [locationPickerOpen, setLocationPickerOpen] = useState(false); + const [error, setError] = useState(''); + const [saving, setSaving] = useState(false); + const editing = Boolean(initialDraft?.schedule_id); + const MoreIcon = moreOpen ? ChevronUp : ChevronDown; + + const applyLocation = (location: SavedLocation | null) => { + setSelectedLocation(location); + }; + + const handleSave = async () => { + const normalizedTitle = title.trim(); + const startTime = date && start ? isoFromDateAndTime(date, start) : null; + const endTime = startTime && end ? isoFromDateAndTime(date, end) : null; + const latitudeValue = selectedLocation?.latitude ?? null; + const longitudeValue = selectedLocation?.longitude ?? null; + const hasLocation = selectedLocation != null && latitudeValue != null && longitudeValue != null; + const radiusValue = optionalNumber(geofenceRadius); + const remindOffsetValue = optionalNumber(remindOffset); + // 有时间 → time;仅地点 → location;时间和地点都有仍按 time。 + const resolvedType: ScheduleType = startTime ? 'time' : 'location'; + + if (!normalizedTitle) { + setError('请填写日程标题。'); + return; + } + if (!startTime && !hasLocation) { + setError('请至少填写时间或地点。'); + return; + } + if (date && !start) { + setError('已选日期时请一并选择开始时间。'); + return; + } + if (start && !date) { + setError('已选时间时请一并选择日期。'); + return; + } + if (startTime && new Date(startTime).getTime() <= Date.now()) { + setError('开始时间需晚于当前分钟,请选择下一分钟及以后。'); + return; + } + if (endTime && startTime && new Date(endTime) < new Date(startTime)) { + setError('结束时间不能早于开始时间。'); + return; + } + if ( + hasLocation && + (radiusValue === null || !Number.isInteger(radiusValue) || radiusValue <= 0) + ) { + setError('地理围栏半径必须是大于 0 的整数。'); + return; + } + if ( + remindOffsetValue === null || + !Number.isInteger(remindOffsetValue) || + remindOffsetValue < 0 + ) { + setError('提前提醒分钟数必须是非负整数。'); + return; + } + + const nextDraft: ScheduleDraft = { + end_time: endTime, + geofence_armed: initialDraft?.geofence_armed ?? null, + geofence_radius_meters: hasLocation + ? radiusValue + : (initialDraft?.geofence_radius_meters ?? 100), + latitude: latitudeValue, + location_address: selectedLocation?.address ?? null, + location_name: selectedLocation?.name?.trim() || selectedLocation?.address || null, + longitude: longitudeValue, + notes: notes.trim() || null, + schedule_id: initialDraft?.schedule_id ?? null, + schedule_type: resolvedType, + source_mode: initialDraft?.source_mode ?? 'manual', + start_time: startTime, + time_remind_offset_minutes: remindOffsetValue, + timezone: startTime ? currentTimezone() : null, + title: normalizedTitle, + }; + setError(''); + setSaving(true); + try { + await onSave(nextDraft); + } catch (saveError) { + setError(saveError instanceof Error ? saveError.message : '保存失败,请稍后重试。'); + } finally { + setSaving(false); + } + }; + + return ( + + + + + + + {editing ? 'EDIT SCHEDULE' : 'STANDARD SCHEDULE'} + + {editing ? '编辑日程' : '添加日程'} + + + × + + + + 标题(必填) + + + 日期 + + + + + {date ? ( + { + setDate(''); + setStart(''); + setEnd(''); + }} + /> + ) : null} + + + 开始时间 + + + + + {start ? ( + { + setStart(''); + setEnd(''); + }} + /> + ) : null} + + + 地点 + + setLocationPickerOpen(true)} + style={styles.locationFieldMain} + > + + + + + + {selectedLocation + ? (selectedLocation.name ?? selectedLocation.address) + : '从常用地点中选择'} + + {selectedLocation ? ( + + {selectedLocation.address} + + ) : ( + 时间或地点至少填一项 + )} + + + {selectedLocation ? ( + applyLocation(null)} /> + ) : null} + + + setMoreOpen((open) => !open)} + style={[styles.moreToggle, moreOpen && styles.moreToggleActive]} + > + + 更多信息 + + + + + {moreOpen ? ( + + 备注(可选) + + 结束时间(可选) + + 提前提醒(分钟) + + {selectedLocation ? ( + <> + 围栏半径(米) + + + ) : null} + + ) : null} + + {error ? {error} : null} + + + {saving ? '正在保存…' : editing ? '保存修改' : '添加日程'} + + + + + setLocationPickerOpen(false)} + onSelect={applyLocation} + onUpsertLocation={onUpsertLocation} + selectedId={selectedLocation?.id ?? null} + visible={locationPickerOpen} + /> + + ); +} diff --git a/frontend/src/features/schedule/editor/createSheet.styles.ts b/frontend/src/features/schedule/editor/createSheet.styles.ts new file mode 100644 index 0000000..1e5c632 --- /dev/null +++ b/frontend/src/features/schedule/editor/createSheet.styles.ts @@ -0,0 +1,164 @@ +import { StyleSheet } from 'react-native'; + +import { colors } from '@/shared/theme'; + +export const createSheetStyles = StyleSheet.create({ + modalBackdrop: { + alignItems: 'center', + backgroundColor: 'rgba(22,32,28,0.52)', + flex: 1, + justifyContent: 'flex-end', + }, + modalDismiss: { alignSelf: 'stretch', flex: 1 }, + modalKeyboardAvoider: { flex: 1 }, + editModalBackdrop: { + alignItems: 'center', + backgroundColor: 'rgba(22,32,28,0.52)', + flex: 1, + justifyContent: 'center', + paddingHorizontal: 18, + }, + editModalDismiss: { bottom: 0, left: 0, position: 'absolute', right: 0, top: 0 }, + standardSheet: { + backgroundColor: colors.surface, + borderTopLeftRadius: 26, + borderTopRightRadius: 26, + padding: 18, + paddingBottom: 28, + maxWidth: 430, + maxHeight: '92%', + width: '100%', + }, + standardDialog: { + borderRadius: 20, + maxHeight: '88%', + paddingBottom: 20, + paddingTop: 20, + }, + sheetCloseText: { color: colors.ink, fontSize: 17 }, + fieldLabel: { color: colors.ink, fontSize: 11, fontWeight: '700', marginBottom: 7, marginTop: 9 }, + formInputControl: { + borderColor: colors.line, + borderRadius: 10, + borderWidth: 1, + color: colors.ink, + fontSize: 13, + height: 48, + paddingHorizontal: 13, + }, + formInputMultiline: { + height: 72, + paddingTop: 12, + textAlignVertical: 'top', + }, + pickerField: { + alignItems: 'center', + borderColor: colors.line, + borderRadius: 10, + borderWidth: 1, + flexDirection: 'row', + height: 48, + paddingHorizontal: 13, + }, + pickerFieldText: { color: colors.ink, flex: 1, fontSize: 13, fontWeight: '600' }, + pickerFieldPlaceholder: { color: colors.muted, fontWeight: '500' }, + formError: { color: '#B66752', fontSize: 11, marginTop: 10 }, + moreToggle: { + alignItems: 'center', + backgroundColor: colors.surfaceTint, + borderRadius: 12, + flexDirection: 'row', + justifyContent: 'space-between', + marginTop: 14, + minHeight: 44, + paddingHorizontal: 12, + }, + moreToggleActive: { backgroundColor: colors.limeSoft }, + moreToggleText: { color: '#7D8982', fontSize: 13, fontWeight: '800' }, + moreToggleTextActive: { color: '#52745D' }, + moreSection: { marginTop: 2 }, + fieldWithClear: { + alignItems: 'center', + flexDirection: 'row', + gap: 8, + }, + fieldWithClearMain: { + flex: 1, + minWidth: 0, + }, + locationField: { + alignItems: 'center', + borderColor: colors.line, + borderRadius: 10, + borderWidth: 1, + flexDirection: 'row', + minHeight: 52, + paddingHorizontal: 10, + paddingVertical: 8, + }, + locationFieldMain: { + alignItems: 'center', + flex: 1, + flexDirection: 'row', + }, + locationFieldIcon: { + alignItems: 'center', + backgroundColor: colors.limeSoft, + borderRadius: 9, + height: 32, + justifyContent: 'center', + width: 32, + }, + locationFieldCopy: { flex: 1, marginLeft: 10 }, + locationFieldTitle: { color: colors.ink, fontSize: 13, fontWeight: '700' }, + locationFieldPlaceholder: { color: colors.muted, fontWeight: '500' }, + locationFieldHint: { color: colors.sub, fontSize: 10, marginTop: 2 }, + locationClear: { + backgroundColor: colors.surfaceTint, + borderRadius: 8, + paddingHorizontal: 8, + paddingVertical: 6, + }, + locationClearText: { color: colors.sub, fontSize: 11, fontWeight: '700' }, + standardPrimary: { + alignItems: 'center', + backgroundColor: colors.deep, + borderRadius: 12, + justifyContent: 'center', + marginTop: 14, + minHeight: 50, + }, + standardPrimaryText: { color: colors.surface, fontSize: 14, fontWeight: '800' }, + standardFormScroll: { flexGrow: 0, flexShrink: 1 }, + standardFormContent: { paddingBottom: 2 }, + sheetHandle: { + alignSelf: 'center', + backgroundColor: '#D8D6CF', + borderRadius: 3, + height: 4, + marginBottom: 18, + width: 34, + }, + sheetHeader: { + alignItems: 'flex-end', + flexDirection: 'row', + justifyContent: 'space-between', + marginBottom: 16, + }, + sheetEyebrow: { + color: colors.muted, + fontSize: 10, + fontWeight: '700', + letterSpacing: 0, + marginBottom: 6, + }, + sheetTitle: { color: colors.ink, fontSize: 23, fontWeight: '800' }, + sheetClose: { + alignItems: 'center', + backgroundColor: colors.surfaceTint, + borderRadius: 11, + height: 36, + justifyContent: 'center', + width: 36, + }, +}); diff --git a/frontend/src/features/schedule/editor/datetime.ts b/frontend/src/features/schedule/editor/datetime.ts new file mode 100644 index 0000000..0aa76f7 --- /dev/null +++ b/frontend/src/features/schedule/editor/datetime.ts @@ -0,0 +1,79 @@ +import { formatTimeValue } from '@/shared/utils/date'; + +export type PickerMode = 'date' | 'time'; + +export function parseDateValue(value: string) { + const parts = value + .split(/[^0-9]+/) + .filter(Boolean) + .map(Number); + const next = new Date(); + next.setHours(0, 0, 0, 0); + if (parts.length === 3 && parts[0] >= 1 && parts[1] >= 1 && parts[1] <= 12 && parts[2] >= 1) { + next.setFullYear(parts[0], parts[1] - 1, parts[2]); + } + return next; +} + +export function parseTimeValue(value: string) { + const parsed = value.match(/^(\d{1,2}):(\d{2})$/); + const next = new Date(); + if (parsed) next.setHours(Number(parsed[1]), Number(parsed[2]), 0, 0); + else next.setSeconds(0, 0); + return next; +} + +export function parsePickerValue(value: string, mode: PickerMode) { + return mode === 'date' ? parseDateValue(value) : parseTimeValue(value); +} + +export function formatDateValue(value: Date) { + return `${value.getFullYear()} / ${String(value.getMonth() + 1).padStart(2, '0')} / ${String(value.getDate()).padStart(2, '0')}`; +} + +export function dateAndTimeFromIso(value?: string | null) { + if (!value) return { date: '', time: '' }; + const parsed = new Date(value); + if (Number.isNaN(parsed.getTime())) return { date: '', time: '' }; + return { date: formatDateValue(parsed), time: formatTimeValue(parsed) }; +} + +/** 新建日程默认选下一分钟(当前分钟已过去/不允许创建)。 */ +export function defaultCreateDateAndTime(now = new Date()) { + const next = new Date(now); + next.setSeconds(0, 0); + next.setMinutes(next.getMinutes() + 1); + return { date: formatDateValue(next), time: formatTimeValue(next) }; +} + +export function isoFromDateAndTime(dateValue: string, timeValue: string) { + const timeParts = timeValue.match(/^(\d{1,2}):(\d{2})$/); + if (!timeParts) return null; + + const date = parseDateValue(dateValue); + // parseDateValue 在非法输入时回退到「今天」;需确认输入本身合法。 + const parts = dateValue + .split(/[^0-9]+/) + .filter(Boolean) + .map(Number); + if (parts.length !== 3 || parts[0] < 1 || parts[1] < 1 || parts[1] > 12 || parts[2] < 1) { + return null; + } + + date.setHours(Number(timeParts[1]), Number(timeParts[2]), 0, 0); + return Number.isNaN(date.getTime()) ? null : date.toISOString(); +} + +export function optionalNumber(value: string) { + if (!value.trim()) return null; + const parsed = Number(value); + return Number.isFinite(parsed) ? parsed : null; +} + +export function currentTimezone() { + try { + return Intl.DateTimeFormat().resolvedOptions().timeZone || null; + } catch { + return null; + } +} diff --git a/frontend/src/features/schedule/hooks/useScheduleCommands.tsx b/frontend/src/features/schedule/hooks/useScheduleCommands.tsx new file mode 100644 index 0000000..016f196 --- /dev/null +++ b/frontend/src/features/schedule/hooks/useScheduleCommands.tsx @@ -0,0 +1,213 @@ +import { + createContext, + useCallback, + useContext, + useEffect, + useMemo, + useState, + useSyncExternalStore, + type ReactNode, +} from 'react'; + +import type { + ConnectionStatus, + Schedule, + ScheduleUpsertPayload as ScheduleDraft, +} from '@/contracts'; + +import type { AlarmPort } from '../application/AlarmPort'; +import type { ScheduleConflictNotifier } from '../application/ScheduleNotificationPort'; +import { ScheduleService } from '../application/ScheduleService'; +import { ScheduleCache } from '../data/ScheduleCache'; +import type { ScheduleTransport } from '../data/ScheduleTransport'; +import { WsScheduleRepository } from '../data/WsScheduleRepository'; + +type ScheduleMutationState = { + status: 'idle' | 'pending' | 'error'; + error: string | null; + pendingId: string | null; +}; + +const IDLE_MUTATION: ScheduleMutationState = { + status: 'idle', + error: null, + pendingId: null, +}; + +const EMPTY_SCHEDULES: Schedule[] = []; + +type ReadySnapshot = { + service: ScheduleService; + sessionEpoch: number; + userId: string; +}; + +type ScheduleCommandsValue = { + items: Schedule[]; + ready: boolean; + mutation: ScheduleMutationState; + saveDraft: (draft: ScheduleDraft) => Promise; + toggleScheduleDone: (schedule: Schedule) => Promise; + deleteSchedule: (schedule: Schedule) => Promise; + service: ScheduleService | null; +}; + +const ScheduleCommandsContext = createContext(null); + +export type ScheduleProviderProps = { + alarmAdapter: AlarmPort; + children: ReactNode; + /** 由 app 从 SessionProvider 注入,feature 不反向依赖 app。 */ + client: ScheduleTransport | null; + /** 当前 session 的连接状态;断线期间禁止写操作。 */ + connectionStatus: ConnectionStatus; + /** App-owned feedback for server-reported schedule conflicts. */ + notifyConflicts?: ScheduleConflictNotifier; + userId: string | null; + /** 每次 session.ready 递增;用于重连后 resync。 */ + sessionEpoch: number; +}; + +export function ScheduleProvider({ + alarmAdapter, + children, + client, + connectionStatus, + notifyConflicts, + userId, + sessionEpoch, +}: ScheduleProviderProps) { + const [readySnapshot, setReadySnapshot] = useState(null); + const [mutation, setMutation] = useState(IDLE_MUTATION); + + const service = useMemo(() => { + if (!client) return null; + const cache = new ScheduleCache(); + const repository = new WsScheduleRepository(client); + return new ScheduleService({ + alarmAdapter, + repository, + cache, + getUserId: () => { + if (!userId) throw new Error('会话身份尚未就绪'); + return userId; + }, + notifyConflicts, + }); + }, [alarmAdapter, client, notifyConflicts, userId]); + + const subscribeToItems = useCallback( + (onStoreChange: () => void) => { + if (!service) return () => undefined; + return service.subscribe(() => onStoreChange()); + }, + [service], + ); + + const getItemsSnapshot = useCallback(() => service?.getItems() ?? EMPTY_SCHEDULES, [service]); + + const items = useSyncExternalStore(subscribeToItems, getItemsSnapshot, getItemsSnapshot); + + useEffect(() => { + return () => service?.dispose(); + }, [service]); + + useEffect(() => { + if (!service || !userId || sessionEpoch === 0) return; + let cancelled = false; + void (async () => { + try { + await service.resync(); + if (!cancelled) setReadySnapshot({ service, sessionEpoch, userId }); + } catch (error) { + if (!cancelled) { + setMutation({ + status: 'error', + error: error instanceof Error ? error.message : '加载日程失败', + pendingId: null, + }); + } + } + })(); + return () => { + cancelled = true; + }; + }, [service, userId, sessionEpoch]); + + const ready = Boolean( + service && + connectionStatus === 'ready' && + userId && + readySnapshot?.service === service && + readySnapshot.userId === userId && + readySnapshot.sessionEpoch === sessionEpoch, + ); + + const runMutation = useCallback( + async ( + pendingId: string, + fallbackError: string, + action: (activeService: ScheduleService) => Promise, + ): Promise => { + if (!service || !ready) { + const error = new Error('日程服务尚未连接,请稍后重试'); + setMutation({ status: 'error', error: error.message, pendingId }); + throw error; + } + setMutation({ status: 'pending', error: null, pendingId }); + try { + const result = await action(service); + setMutation(IDLE_MUTATION); + return result; + } catch (error) { + const message = error instanceof Error ? error.message : fallbackError; + setMutation({ status: 'error', error: message, pendingId }); + throw error instanceof Error ? error : new Error(message); + } + }, + [ready, service], + ); + + const saveDraft = useCallback( + (draft: ScheduleDraft) => + runMutation(draft.schedule_id ?? 'new', '保存失败', (active) => active.saveDraft(draft)), + [runMutation], + ); + + const toggleScheduleDone = useCallback( + (schedule: Schedule) => + runMutation(schedule.id, '更新失败', (active) => active.toggleDone(schedule)), + [runMutation], + ); + + const deleteSchedule = useCallback( + (schedule: Schedule) => + runMutation(schedule.id, '删除失败', (active) => active.deleteSchedule(schedule)), + [runMutation], + ); + + const value = useMemo( + () => ({ + items, + ready, + mutation, + saveDraft, + toggleScheduleDone, + deleteSchedule, + service, + }), + [deleteSchedule, items, mutation, ready, saveDraft, service, toggleScheduleDone], + ); + + return ( + {children} + ); +} + +export function useScheduleCommands(): ScheduleCommandsValue { + const value = useContext(ScheduleCommandsContext); + if (!value) { + throw new Error('useScheduleCommands must be used within ScheduleProvider'); + } + return value; +} diff --git a/frontend/src/features/schedule/index.ts b/frontend/src/features/schedule/index.ts new file mode 100644 index 0000000..b4763c9 --- /dev/null +++ b/frontend/src/features/schedule/index.ts @@ -0,0 +1,9 @@ +export { ScheduleScreen } from './screens/ScheduleScreen'; +export { StandardCreateModal } from './editor/StandardCreateModal'; +export { ScheduleProvider, useScheduleCommands } from './hooks/useScheduleCommands'; +export type { AlarmPort } from './application/AlarmPort'; +export type { ScheduleConflictNotifier } from './application/ScheduleNotificationPort'; +export { scheduleDraftFromVoiceParse, upsertDraftForSchedule } from './data/adapters'; +export type { Schedule, ScheduleUpsertPayload as ScheduleDraft } from '@/contracts'; +export type { SavedLocation } from './location'; +export { useSessionSavedLocations } from './location'; diff --git a/frontend/src/features/schedule/location/AddressEditorSheet.styles.ts b/frontend/src/features/schedule/location/AddressEditorSheet.styles.ts new file mode 100644 index 0000000..7410647 --- /dev/null +++ b/frontend/src/features/schedule/location/AddressEditorSheet.styles.ts @@ -0,0 +1,57 @@ +import { StyleSheet } from 'react-native'; + +import { colors } from '@/shared/theme'; + +export const addressEditorStyles = StyleSheet.create({ + sheet: { + paddingHorizontal: 20, + }, + fieldLabel: { color: colors.ink, fontSize: 12, fontWeight: '800', marginTop: 20 }, + input: { + backgroundColor: '#FFFFFF', + borderColor: '#C7D0C9', + borderRadius: 14, + borderWidth: 1, + color: colors.ink, + fontSize: 14, + height: 52, + marginTop: 8, + outlineColor: 'transparent', + outlineStyle: 'solid', + outlineWidth: 0, + paddingHorizontal: 13, + }, + error: { color: '#A85F4E', fontSize: 11, marginTop: 6 }, + mapField: { + alignItems: 'center', + backgroundColor: '#F8FAF7', + borderColor: colors.line, + borderRadius: 14, + borderWidth: 1, + flexDirection: 'row', + marginTop: 8, + minHeight: 66, + paddingHorizontal: 12, + paddingVertical: 10, + }, + mapIcon: { + alignItems: 'center', + backgroundColor: colors.limeSoft, + borderRadius: 10, + height: 36, + justifyContent: 'center', + width: 36, + }, + mapCopy: { flex: 1, marginLeft: 10 }, + mapTitle: { color: colors.ink, fontSize: 13, lineHeight: 18 }, + mapHint: { color: colors.sub, fontSize: 10, marginTop: 3 }, + primary: { + alignItems: 'center', + backgroundColor: colors.deep, + borderRadius: 13, + height: 50, + justifyContent: 'center', + marginTop: 17, + }, + primaryText: { color: colors.surface, fontSize: 13, fontWeight: '800' }, +}); diff --git a/frontend/src/features/schedule/location/AddressEditorSheet.tsx b/frontend/src/features/schedule/location/AddressEditorSheet.tsx new file mode 100644 index 0000000..bdde1fd --- /dev/null +++ b/frontend/src/features/schedule/location/AddressEditorSheet.tsx @@ -0,0 +1,129 @@ +import { useState } from 'react'; +import { MapPin } from 'lucide-react-native'; +import { Modal, Pressable, Text, TextInput, View } from 'react-native'; + +import { BottomSheetFrame } from '@/shared/components/BottomSheetFrame'; +import { colors } from '@/shared/theme'; + +import { addressEditorStyles as styles } from './AddressEditorSheet.styles'; +import { MapPicker, type MapLocation } from './MapPicker'; + +type AddressEditorSheetProps = { + initialLocation?: MapLocation | null; + onClose: () => void; + onSave: (location: MapLocation) => void; + title: string; + visible: boolean; +}; + +export function AddressEditorSheet({ + initialLocation = null, + onClose, + onSave, + title, + visible, +}: AddressEditorSheetProps) { + const [mapOpen, setMapOpen] = useState(false); + const [pendingLocation, setPendingLocation] = useState(initialLocation); + const [locationName, setLocationName] = useState(initialLocation?.name ?? ''); + const [formError, setFormError] = useState(''); + const [syncedVisible, setSyncedVisible] = useState(visible); + const [syncedInitial, setSyncedInitial] = useState(initialLocation); + + if (visible !== syncedVisible || initialLocation !== syncedInitial) { + setSyncedVisible(visible); + setSyncedInitial(initialLocation); + if (visible) { + setPendingLocation(initialLocation); + setLocationName(initialLocation?.name ?? ''); + setFormError(''); + setMapOpen(false); + } + } + + const handleClose = () => { + setMapOpen(false); + setFormError(''); + onClose(); + }; + + const handleSave = () => { + if (!pendingLocation) { + setFormError('请选择一个地图位置'); + return; + } + const nextName = locationName.trim(); + onSave({ ...pendingLocation, name: nextName || undefined }); + }; + + return ( + <> + + 地点名称 + + 地图位置 + { + setFormError(''); + setMapOpen(true); + }} + style={styles.mapField} + > + + + + + + {pendingLocation?.address ?? '请选择地点'} + + {pendingLocation ? '点击重新选择' : '点击打开地图'} + + + {formError ? {formError} : null} + + 保存地址 + + + + setMapOpen(false)} + visible={visible && mapOpen} + > + setMapOpen(false)} + onConfirm={(location) => { + setMapOpen(false); + setPendingLocation(location); + setFormError(''); + }} + /> + + + ); +} diff --git a/frontend/src/features/schedule/location/LocationPickerSheet.styles.ts b/frontend/src/features/schedule/location/LocationPickerSheet.styles.ts new file mode 100644 index 0000000..4b2cde5 --- /dev/null +++ b/frontend/src/features/schedule/location/LocationPickerSheet.styles.ts @@ -0,0 +1,60 @@ +import { StyleSheet } from 'react-native'; + +import { colors } from '@/shared/theme'; + +export const locationPickerStyles = StyleSheet.create({ + sheet: { + maxHeight: '78%', + paddingHorizontal: 20, + }, + list: { flexGrow: 0, flexShrink: 1 }, + listContent: { gap: 8, paddingBottom: 8 }, + empty: { + alignItems: 'center', + backgroundColor: colors.limeSoft, + borderColor: '#D7E6B2', + borderRadius: 14, + borderWidth: 1, + paddingHorizontal: 16, + paddingVertical: 22, + }, + emptyTitle: { color: colors.deep, fontSize: 14, fontWeight: '800' }, + emptyHint: { color: colors.sub, fontSize: 11, marginTop: 6 }, + item: { + alignItems: 'center', + backgroundColor: '#F8FAF7', + borderColor: colors.line, + borderRadius: 14, + borderWidth: 1, + flexDirection: 'row', + minHeight: 68, + paddingHorizontal: 12, + paddingVertical: 10, + }, + itemSelected: { + backgroundColor: colors.limeSoft, + borderColor: '#C7D69A', + }, + itemIcon: { + alignItems: 'center', + backgroundColor: colors.surface, + borderRadius: 10, + height: 36, + justifyContent: 'center', + width: 36, + }, + itemCopy: { flex: 1, marginLeft: 10 }, + itemName: { color: colors.ink, fontSize: 14, fontWeight: '800' }, + itemAddress: { color: colors.sub, fontSize: 12, lineHeight: 17, marginTop: 3 }, + addButton: { + alignItems: 'center', + backgroundColor: colors.deep, + borderRadius: 13, + flexDirection: 'row', + gap: 8, + height: 50, + justifyContent: 'center', + marginTop: 12, + }, + addButtonText: { color: colors.surface, fontSize: 13, fontWeight: '800' }, +}); diff --git a/frontend/src/features/schedule/location/LocationPickerSheet.tsx b/frontend/src/features/schedule/location/LocationPickerSheet.tsx new file mode 100644 index 0000000..c472124 --- /dev/null +++ b/frontend/src/features/schedule/location/LocationPickerSheet.tsx @@ -0,0 +1,112 @@ +import { useState } from 'react'; +import { MapPin, Plus } from 'lucide-react-native'; +import { Pressable, ScrollView, Text, View } from 'react-native'; + +import { BottomSheetFrame } from '@/shared/components/BottomSheetFrame'; +import { colors } from '@/shared/theme'; + +import { AddressEditorSheet } from './AddressEditorSheet'; +import { locationPickerStyles as styles } from './LocationPickerSheet.styles'; +import type { SavedLocation } from './types'; +import { createSavedLocation } from './utils'; + +type LocationPickerSheetProps = { + locations: SavedLocation[]; + onClose: () => void; + onSelect: (location: SavedLocation) => void; + onUpsertLocation: (location: SavedLocation) => void; + selectedId?: string | null; + visible: boolean; +}; + +export function LocationPickerSheet({ + locations, + onClose, + onSelect, + onUpsertLocation, + selectedId = null, + visible, +}: LocationPickerSheetProps) { + const [editorOpen, setEditorOpen] = useState(false); + + const handleClose = () => { + setEditorOpen(false); + onClose(); + }; + + return ( + <> + + + {locations.length === 0 ? ( + + 还没有常用地点 + 先添加一个地点,再用于日程提醒 + + ) : ( + locations.map((location) => { + const selected = location.id === selectedId; + return ( + { + onSelect(location); + handleClose(); + }} + style={[styles.item, selected && styles.itemSelected]} + > + + + + + {location.name ?? '未命名地点'} + + {location.address} + + + + ); + }) + )} + + + setEditorOpen(true)} + style={styles.addButton} + > + + 添加地点 + + + + setEditorOpen(false)} + onSave={(location) => { + const saved = createSavedLocation(location); + onUpsertLocation(saved); + onSelect(saved); + setEditorOpen(false); + onClose(); + }} + title="添加地点" + visible={visible && editorOpen} + /> + + ); +} diff --git a/frontend/src/features/schedule/location/MapPicker/MapPicker.native.tsx b/frontend/src/features/schedule/location/MapPicker/MapPicker.native.tsx new file mode 100644 index 0000000..e333f33 --- /dev/null +++ b/frontend/src/features/schedule/location/MapPicker/MapPicker.native.tsx @@ -0,0 +1,184 @@ +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { View } from 'react-native'; +import { WebView, WebViewMessageEvent } from 'react-native-webview'; + +import { + BaiduMapBridgeMessage, + buildBaiduMapDocument, + BAIDU_MAP_AK, + createCoordinateLocation, +} from './baidu'; +import { MapPickerOverlay } from './Overlay'; +import { mapPickerStyles as styles } from './styles'; +import type { MapLocation, MapPickerProps } from './types'; + +const SEARCH_TIMEOUT_MS = 8000; + +type PendingSearch = { + reject: (error: Error) => void; + resolve: (locations: MapLocation[]) => void; + timeout: ReturnType; +}; + +export function MapPicker({ initialLocation, onCancel, onConfirm }: MapPickerProps) { + const webViewRef = useRef(null); + const pendingSearchRef = useRef(null); + const [selection, setSelection] = useState(initialLocation); + const [locating, setLocating] = useState(false); + const [locationError, setLocationError] = useState(null); + const [mapReady, setMapReady] = useState(false); + const [mapError, setMapError] = useState( + BAIDU_MAP_AK ? null : '缺少百度地图浏览器端密钥,请完成地图服务配置。', + ); + const document = useMemo( + () => buildBaiduMapDocument(BAIDU_MAP_AK, initialLocation), + [initialLocation], + ); + + useEffect(() => { + return () => { + if (pendingSearchRef.current) { + clearTimeout(pendingSearchRef.current.timeout); + pendingSearchRef.current = null; + } + }; + }, []); + + const failPendingSearch = useCallback((message: string) => { + const pending = pendingSearchRef.current; + if (!pending) return; + clearTimeout(pending.timeout); + pending.reject(new Error(message)); + pendingSearchRef.current = null; + }, []); + + const handleMessage = useCallback( + ({ nativeEvent }: WebViewMessageEvent) => { + let message: BaiduMapBridgeMessage; + try { + message = JSON.parse(nativeEvent.data) as BaiduMapBridgeMessage; + } catch { + return; + } + + if (message.type === 'map-ready') { + setMapReady(true); + setMapError(null); + if (!initialLocation) { + webViewRef.current?.injectJavaScript('window.__timeflowLocate(); true;'); + } + return; + } + if (message.type === 'map-error') { + setMapReady(false); + setMapError(message.message); + failPendingSearch(message.message); + return; + } + if (message.type === 'selecting') { + setLocationError(null); + setSelection(createCoordinateLocation(message.latitude, message.longitude)); + setLocating(true); + return; + } + if (message.type === 'selected') { + setSelection(message.location); + setLocating(false); + setLocationError(null); + return; + } + if (message.type === 'location-error') { + setLocating(false); + setLocationError(message.message); + return; + } + if (message.type === 'search-results') { + const pending = pendingSearchRef.current; + if (!pending) return; + clearTimeout(pending.timeout); + pending.resolve(message.results); + pendingSearchRef.current = null; + return; + } + if (message.type === 'search-error') { + failPendingSearch('Baidu place search failed'); + } + }, + [failPendingSearch, initialLocation], + ); + + const searchLocations = useCallback( + (query: string) => { + return new Promise((resolve, reject) => { + if (!mapReady || !webViewRef.current) { + reject(new Error('Baidu map is not ready')); + return; + } + + failPendingSearch('A newer search replaced this request'); + const timeout = setTimeout(() => { + failPendingSearch('Baidu place search timed out'); + }, SEARCH_TIMEOUT_MS); + pendingSearchRef.current = { reject, resolve, timeout }; + webViewRef.current.injectJavaScript( + `window.__timeflowSearch(${JSON.stringify(query)}); true;`, + ); + }); + }, + [failPendingSearch, mapReady], + ); + + const selectSearchResult = (location: MapLocation) => { + setLocationError(null); + setLocating(false); + setSelection(location); + webViewRef.current?.injectJavaScript( + `window.__timeflowSelect(${location.longitude}, ${location.latitude}); true;`, + ); + }; + + const locateCurrentPosition = () => { + setLocationError(null); + setLocating(true); + webViewRef.current?.injectJavaScript('window.__timeflowLocate(); true;'); + }; + + return ( + + {BAIDU_MAP_AK ? ( + { + setMapReady(false); + setMapError('地图加载失败,请检查网络或百度地图密钥配置。'); + }} + onMessage={handleMessage} + originWhitelist={['https://*']} + ref={webViewRef} + scrollEnabled={false} + setSupportMultipleWindows={false} + source={{ baseUrl: 'https://timeflow.local/', html: document }} + style={styles.mapCanvas} + /> + ) : ( + + )} + selection && onConfirm(selection)} + onSearch={searchLocations} + onSelectSearchResult={selectSearchResult} + selection={selection} + /> + + ); +} diff --git a/frontend/src/features/schedule/location/MapPicker/MapPicker.tsx b/frontend/src/features/schedule/location/MapPicker/MapPicker.tsx new file mode 100644 index 0000000..4e817d2 --- /dev/null +++ b/frontend/src/features/schedule/location/MapPicker/MapPicker.tsx @@ -0,0 +1,284 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; +import { load as loadBaiduMap } from '@baidumap/jsapi-loader'; +import { View } from 'react-native'; + +import { + BAIDU_MAP_AK, + createCoordinateLocation, + createReverseGeocodeGate, + readablePoiAddress, + SHANGHAI_CENTER, +} from './baidu'; +import { MapPickerOverlay } from './Overlay'; +import { mapPickerStyles as styles } from './styles'; +import type { MapLocation, MapPickerProps } from './types'; + +const MAP_REQUEST_TIMEOUT_MS = 6000; +const MAP_LOAD_TIMEOUT_MS = 5000; +const MARKER_SVG = encodeURIComponent( + '', +); + +export function MapPicker({ initialLocation, onCancel, onConfirm }: MapPickerProps) { + const mapHostRef = useRef(null); + const bmapRef = useRef(null); + const mapRef = useRef(null); + const markerRef = useRef(null); + const requestRef = useRef(0); + const reverseGeocodeGateRef = useRef(createReverseGeocodeGate()); + const [selection, setSelection] = useState(initialLocation); + const [locating, setLocating] = useState(false); + const [locationError, setLocationError] = useState(null); + const [mapReady, setMapReady] = useState(false); + const [mapError, setMapError] = useState( + BAIDU_MAP_AK ? null : '缺少百度地图浏览器端密钥,请完成地图服务配置。', + ); + + const moveMarker = useCallback((location: MapLocation) => { + const BMapApi = bmapRef.current; + const map = mapRef.current; + if (!BMapApi || !map) return; + + const point = new BMapApi.Point(location.longitude, location.latitude); + if (markerRef.current) { + markerRef.current.setPosition(point); + return; + } + + const icon = new BMapApi.Icon( + `data:image/svg+xml;charset=utf-8,${MARKER_SVG}`, + new BMapApi.Size(28, 28), + { anchor: new BMapApi.Size(14, 14) }, + ); + markerRef.current = new BMapApi.Marker(point, { icon }); + map.addOverlay(markerRef.current); + }, []); + + const selectCoordinates = useCallback( + (latitude: number, longitude: number) => { + const BMapApi = bmapRef.current; + if (!BMapApi) return; + + const requestId = requestRef.current + 1; + requestRef.current = requestId; + const pendingLocation = createCoordinateLocation(latitude, longitude); + + setLocationError(null); + setSelection(pendingLocation); + moveMarker(pendingLocation); + setLocating(true); + + reverseGeocodeGateRef.current.schedule({ latitude, longitude, requestId }, (job) => { + if (requestRef.current !== job.requestId) return; + + return new Promise((resolve) => { + let completed = false; + const finish = (address?: string) => { + if (completed) { + resolve(); + return; + } + completed = true; + window.clearTimeout(timeout); + if (requestRef.current === job.requestId) { + if (address?.trim()) { + setSelection({ + ...createCoordinateLocation(job.latitude, job.longitude), + address: address.trim(), + }); + } + setLocating(false); + } + resolve(); + }; + + const timeout = window.setTimeout(() => finish(), MAP_REQUEST_TIMEOUT_MS); + const geocoder = new BMapApi.Geocoder({ language: 'zh-CN' }); + geocoder.getLocation( + new BMapApi.Point(job.longitude, job.latitude), + (result: BMap.GeocoderResult | null) => finish(result?.address), + ); + }); + }); + }, + [moveMarker], + ); + + const locateCurrentPosition = useCallback(() => { + const BMapApi = bmapRef.current; + const map = mapRef.current; + if (!BMapApi || !map) return; + + setLocationError(null); + setLocating(true); + const geolocation = new BMapApi.Geolocation(); + geolocation.getCurrentPosition( + (result) => { + if (geolocation.getStatus() !== 0 || !result?.point) { + setLocating(false); + setLocationError('无法获取当前位置,请允许定位权限后重试。'); + return; + } + + map.setCenter(result.point, { noAnimation: false }); + map.setZoom(17, { noAnimation: false }); + selectCoordinates(result.point.lat, result.point.lng); + }, + { enableHighAccuracy: true }, + ); + }, [selectCoordinates]); + + useEffect(() => { + const host = mapHostRef.current as unknown as HTMLElement | null; + if (!host) return; + + if (!BAIDU_MAP_AK) return; + + const reverseGeocodeGate = reverseGeocodeGateRef.current; + let disposed = false; + const loadTimeout = window.setTimeout(() => { + if (!disposed) { + setMapError('请在百度地图控制台为此 Key 开通 JavaScript API 服务后重试。'); + } + }, MAP_LOAD_TIMEOUT_MS); + + void loadBaiduMap({ + ak: BAIDU_MAP_AK, + globalConfig: { coordType: 'bd09ll' }, + timeout: 10000, + version: '4.0', + }) + .then((namespace: typeof BMap) => { + if (disposed) return; + window.clearTimeout(loadTimeout); + + const center = initialLocation ?? SHANGHAI_CENTER; + const point = new namespace.Point(center.longitude, center.latitude); + const map = new namespace.Map(host, { + center: point, + enablePinchZoom: true, + enableWheelZoom: true, + fixCenterWhenResize: true, + zoom: initialLocation ? 17 : 14, + }); + + bmapRef.current = namespace; + mapRef.current = map; + if (initialLocation) moveMarker(initialLocation); + map.addEventListener('click', (event) => { + selectCoordinates(event.point.lat, event.point.lng); + }); + setMapError(null); + setMapReady(true); + if (!initialLocation) locateCurrentPosition(); + }) + .catch(() => { + if (!disposed) { + window.clearTimeout(loadTimeout); + setMapError('地图加载失败,请检查网络或百度地图密钥配置。'); + } + }); + + return () => { + disposed = true; + window.clearTimeout(loadTimeout); + reverseGeocodeGate.clear(); + requestRef.current += 1; + markerRef.current = null; + const map = mapRef.current; + if (map) { + try { + const destroy = (map as unknown as { destroy?: () => void }).destroy; + if (typeof destroy === 'function') { + destroy.call(map); + } else { + (map as unknown as { clearOverlays?: () => void }).clearOverlays?.(); + } + } catch { + // Baidu may have already torn down the map while the overlay closes. + } + } + mapRef.current = null; + bmapRef.current = null; + }; + }, [initialLocation, locateCurrentPosition, moveMarker, selectCoordinates]); + + const searchLocations = useCallback((query: string) => { + return new Promise((resolve, reject) => { + const BMapApi = bmapRef.current; + const map = mapRef.current; + if (!BMapApi || !map) { + reject(new Error('Baidu map is not ready')); + return; + } + + let completed = false; + const finish = (locations?: MapLocation[]) => { + if (completed) return; + completed = true; + window.clearTimeout(timeout); + if (locations) resolve(locations); + else reject(new Error('Baidu place search failed')); + }; + const timeout = window.setTimeout(() => finish(), MAP_REQUEST_TIMEOUT_MS); + const localSearch = new BMapApi.LocalSearch(map, { + onSearchComplete: (rawResults) => { + const result = Array.isArray(rawResults) ? rawResults[0] : rawResults; + if (!result) { + finish([]); + return; + } + + const locations: MapLocation[] = []; + const count = Math.min(result.getCurrentNumPois(), 5); + for (let index = 0; index < count; index += 1) { + const poi = result.getPoi(index); + if (!poi?.point) continue; + locations.push({ + address: readablePoiAddress(poi.title, poi.address), + latitude: poi.point.lat, + longitude: poi.point.lng, + }); + } + finish(locations); + }, + pageCapacity: 5, + renderOptions: { autoViewport: false }, + }); + localSearch.search(query); + }); + }, []); + + const selectSearchResult = (location: MapLocation) => { + const BMapApi = bmapRef.current; + const map = mapRef.current; + if (!BMapApi || !map) return; + + setLocationError(null); + reverseGeocodeGateRef.current.clear(); + requestRef.current += 1; + setLocating(false); + setSelection(location); + moveMarker(location); + map.setCenter(new BMapApi.Point(location.longitude, location.latitude), { noAnimation: false }); + map.setZoom(17, { noAnimation: false }); + }; + + return ( + + + selection && onConfirm(selection)} + onSearch={searchLocations} + onSelectSearchResult={selectSearchResult} + selection={selection} + /> + + ); +} diff --git a/frontend/src/features/schedule/location/MapPicker/Overlay.tsx b/frontend/src/features/schedule/location/MapPicker/Overlay.tsx new file mode 100644 index 0000000..92f805b --- /dev/null +++ b/frontend/src/features/schedule/location/MapPicker/Overlay.tsx @@ -0,0 +1,216 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; +import { LocateFixed, MapPin, Search } from 'lucide-react-native'; +import { Pressable, Text, TextInput, View } from 'react-native'; + +import { colors } from '@/shared/theme'; +import { BackButton } from '@/shared/components/BackButton'; +import { mapPickerStyles as styles } from './styles'; +import type { MapLocation } from './types'; + +type MapPickerOverlayProps = { + mapError: string | null; + mapReady: boolean; + locating: boolean; + locationError: string | null; + onCancel: () => void; + onLocate: () => void; + onConfirm: () => void; + onSearch: (query: string) => Promise; + onSelectSearchResult: (location: MapLocation) => void; + selection: MapLocation | null; +}; + +export function MapPickerOverlay({ + mapError, + mapReady, + locating, + locationError, + onCancel, + onLocate, + onConfirm, + onSearch, + onSelectSearchResult, + selection, +}: MapPickerOverlayProps) { + const [query, setQuery] = useState(''); + const [results, setResults] = useState([]); + const [searching, setSearching] = useState(false); + const [searched, setSearched] = useState(false); + const [searchFailed, setSearchFailed] = useState(false); + const searchRequestRef = useRef(0); + + const search = useCallback( + async (value: string) => { + const nextQuery = value.trim(); + if (!nextQuery || !mapReady) return; + + const requestId = searchRequestRef.current + 1; + searchRequestRef.current = requestId; + setSearching(true); + setSearched(false); + setSearchFailed(false); + try { + const nextResults = await onSearch(nextQuery); + if (requestId !== searchRequestRef.current) return; + setResults(nextResults); + setSearched(true); + } catch { + if (requestId !== searchRequestRef.current) return; + setResults([]); + setSearchFailed(true); + } finally { + if (requestId === searchRequestRef.current) setSearching(false); + } + }, + [mapReady, onSearch], + ); + + useEffect(() => { + const nextQuery = query.trim(); + if (!nextQuery || !mapReady) return; + + const timer = setTimeout(() => { + void search(nextQuery); + }, 320); + return () => clearTimeout(timer); + }, [mapReady, query, search]); + + const chooseResult = (location: MapLocation) => { + searchRequestRef.current += 1; + setSearching(false); + setQuery(''); + setResults([]); + setSearched(false); + onSelectSearchResult(location); + }; + + return ( + <> + + + + + + { + setQuery(value); + searchRequestRef.current += 1; + setSearching(false); + setSearchFailed(false); + setSearched(false); + if (!value.trim()) { + setResults([]); + } + }} + onSubmitEditing={() => void search(query)} + placeholder="搜索地点或地址" + placeholderTextColor="#909892" + returnKeyType="search" + style={styles.searchInput} + value={query} + /> + void search(query)} + style={styles.searchButton} + > + + + + + + + + + {(searching || searchFailed || searched) && ( + + {searching ? ( + 正在搜索... + ) : searchFailed ? ( + 搜索暂时不可用,请直接在地图上选点 + ) : results.length === 0 ? ( + 没有找到相关地点 + ) : ( + results.map((result, index) => ( + chooseResult(result)} + style={[ + styles.searchResult, + index === results.length - 1 && styles.searchResultLast, + ]} + > + + + {result.address} + + + )) + )} + + )} + {locationError ? ( + + {locationError} + + ) : null} + + + {mapError && ( + + + 百度地图暂时不可用 + {mapError} + + )} + + + + + + + + 选中的地点 + {selection ? ( + + {locating ? '正在获取详细地址...' : selection.address} + + ) : locating ? ( + 正在获取当前位置... + ) : ( + 点击地图,或搜索后选择一个地点 + )} + + + {selection && ( + + {selection.latitude.toFixed(5)}, {selection.longitude.toFixed(5)} · 百度地图 · BD-09 + + )} + + 确认这个地点 + + + + ); +} diff --git a/frontend/src/features/schedule/location/MapPicker/baidu/baiduMapWebView.ts b/frontend/src/features/schedule/location/MapPicker/baidu/baiduMapWebView.ts new file mode 100644 index 0000000..0fe20a2 --- /dev/null +++ b/frontend/src/features/schedule/location/MapPicker/baidu/baiduMapWebView.ts @@ -0,0 +1,245 @@ +import type { MapLocation } from './types'; + +export type BaiduMapBridgeMessage = + | { type: 'map-ready' } + | { message: string; type: 'map-error' } + | { latitude: number; longitude: number; type: 'selecting' } + | { location: MapLocation; type: 'selected' } + | { message: string; type: 'location-error' } + | { results: MapLocation[]; type: 'search-results' } + | { type: 'search-error' }; + +export function buildBaiduMapDocument(ak: string, initialLocation: MapLocation | null) { + const center = initialLocation ?? { + address: '上海市 · 默认地图中心', + latitude: 31.236305, + longitude: 121.480237, + }; + const initialJson = JSON.stringify(initialLocation); + const centerJson = JSON.stringify(center); + + return ` + + + + + + + + +
+ + +`; +} diff --git a/frontend/src/features/schedule/location/MapPicker/baidu/index.ts b/frontend/src/features/schedule/location/MapPicker/baidu/index.ts new file mode 100644 index 0000000..091cfbb --- /dev/null +++ b/frontend/src/features/schedule/location/MapPicker/baidu/index.ts @@ -0,0 +1,8 @@ +export { + BAIDU_MAP_AK, + SHANGHAI_CENTER, + createCoordinateLocation, + readablePoiAddress, +} from './services'; +export { createReverseGeocodeGate } from './reverseGeocodeGate'; +export { buildBaiduMapDocument, type BaiduMapBridgeMessage } from './baiduMapWebView'; diff --git a/frontend/src/features/schedule/location/MapPicker/baidu/reverseGeocodeGate.ts b/frontend/src/features/schedule/location/MapPicker/baidu/reverseGeocodeGate.ts new file mode 100644 index 0000000..7c0ef2c --- /dev/null +++ b/frontend/src/features/schedule/location/MapPicker/baidu/reverseGeocodeGate.ts @@ -0,0 +1,82 @@ +/** + * 逆地理编码调度:防抖 + 串行,避免连点/拖图触发超免费 QPS。 + * 同一时间只保留最新待解析坐标;上一请求结束后再发下一请求。 + */ +export type ReverseGeocodeJob = { + latitude: number; + longitude: number; + requestId: number; +}; + +export type ReverseGeocodeRunner = (job: ReverseGeocodeJob) => Promise | void; + +export type ReverseGeocodeGate = { + schedule: (job: ReverseGeocodeJob, run: ReverseGeocodeRunner) => void; + clear: () => void; +}; + +export function createReverseGeocodeGate(options?: { + debounceMs?: number; + minIntervalMs?: number; +}): ReverseGeocodeGate { + const debounceMs = options?.debounceMs ?? 450; + const minIntervalMs = options?.minIntervalMs ?? 350; + + let debounceTimer: ReturnType | null = null; + let intervalTimer: ReturnType | null = null; + let pending: { job: ReverseGeocodeJob; run: ReverseGeocodeRunner } | null = null; + let inFlight = false; + let lastStartedAt = 0; + + const clearTimers = () => { + if (debounceTimer != null) { + clearTimeout(debounceTimer); + debounceTimer = null; + } + if (intervalTimer != null) { + clearTimeout(intervalTimer); + intervalTimer = null; + } + }; + + const flush = async () => { + if (inFlight || !pending) return; + + const elapsed = Date.now() - lastStartedAt; + const wait = lastStartedAt === 0 ? 0 : Math.max(0, minIntervalMs - elapsed); + if (wait > 0) { + intervalTimer = setTimeout(() => { + intervalTimer = null; + void flush(); + }, wait); + return; + } + + const current = pending; + pending = null; + inFlight = true; + lastStartedAt = Date.now(); + + try { + await current.run(current.job); + } finally { + inFlight = false; + if (pending) void flush(); + } + }; + + return { + schedule(job, run) { + pending = { job, run }; + if (debounceTimer != null) clearTimeout(debounceTimer); + debounceTimer = setTimeout(() => { + debounceTimer = null; + void flush(); + }, debounceMs); + }, + clear() { + clearTimers(); + pending = null; + }, + }; +} diff --git a/frontend/src/features/schedule/location/MapPicker/baidu/services.ts b/frontend/src/features/schedule/location/MapPicker/baidu/services.ts new file mode 100644 index 0000000..060eadb --- /dev/null +++ b/frontend/src/features/schedule/location/MapPicker/baidu/services.ts @@ -0,0 +1,26 @@ +import type { MapLocation } from '@/shared/types/geo'; + +export const BAIDU_MAP_AK = process.env.EXPO_PUBLIC_BAIDU_MAP_AK?.trim() ?? ''; +export const BAIDU_COORDINATE_SYSTEM = 'bd09ll' as const; + +export const SHANGHAI_CENTER: MapLocation = { + address: '上海市 · 默认地图中心', + latitude: 31.236305, + longitude: 121.480237, +}; + +export function coordinateAddress(latitude: number, longitude: number) { + return `百度地图选点 · ${latitude.toFixed(5)}, ${longitude.toFixed(5)}`; +} + +export function createCoordinateLocation(latitude: number, longitude: number): MapLocation { + return { + address: coordinateAddress(latitude, longitude), + latitude, + longitude, + }; +} + +export function readablePoiAddress(title: string, address?: string) { + return address?.trim() ? `${title} · ${address.trim()}` : title; +} diff --git a/frontend/src/features/schedule/location/MapPicker/baidu/types.ts b/frontend/src/features/schedule/location/MapPicker/baidu/types.ts new file mode 100644 index 0000000..2b533c6 --- /dev/null +++ b/frontend/src/features/schedule/location/MapPicker/baidu/types.ts @@ -0,0 +1,3 @@ +import type { MapLocation } from '@/shared/types/geo'; + +export type { MapLocation }; diff --git a/frontend/src/features/schedule/location/MapPicker/index.ts b/frontend/src/features/schedule/location/MapPicker/index.ts new file mode 100644 index 0000000..bfcef56 --- /dev/null +++ b/frontend/src/features/schedule/location/MapPicker/index.ts @@ -0,0 +1,2 @@ +export { MapPicker } from './MapPicker'; +export type { MapLocation, MapPickerProps } from './types'; diff --git a/frontend/src/features/schedule/location/MapPicker/styles.ts b/frontend/src/features/schedule/location/MapPicker/styles.ts new file mode 100644 index 0000000..63a020f --- /dev/null +++ b/frontend/src/features/schedule/location/MapPicker/styles.ts @@ -0,0 +1,150 @@ +import { StyleSheet } from 'react-native'; + +import { colors } from '@/shared/theme'; + +export const mapPickerStyles = StyleSheet.create({ + screen: { backgroundColor: '#D8E4DE', flex: 1 }, + mapCanvas: { flex: 1 }, + mapError: { + alignItems: 'center', + backgroundColor: 'rgba(247, 248, 245, 0.96)', + borderColor: colors.line, + borderRadius: 16, + borderWidth: 1, + left: 44, + paddingHorizontal: 20, + paddingVertical: 18, + position: 'absolute', + right: 44, + top: '38%', + zIndex: 900, + }, + mapErrorTitle: { color: colors.ink, fontSize: 14, fontWeight: '800', marginTop: 10 }, + mapErrorText: { + color: colors.sub, + fontSize: 11, + lineHeight: 17, + marginTop: 5, + textAlign: 'center', + }, + topArea: { + left: 14, + position: 'absolute', + right: 14, + top: 14, + zIndex: 1000, + }, + toolbar: { alignItems: 'center', flexDirection: 'row', gap: 8 }, + searchBox: { + alignItems: 'center', + backgroundColor: colors.surface, + borderColor: colors.line, + borderRadius: 14, + borderWidth: 1, + elevation: 4, + flex: 1, + flexDirection: 'row', + height: 48, + paddingHorizontal: 12, + }, + searchInput: { + borderWidth: 0, + color: colors.ink, + flex: 1, + fontSize: 13, + height: 46, + marginHorizontal: 8, + outlineColor: 'transparent', + outlineStyle: 'solid', + outlineWidth: 0, + paddingVertical: 0, + }, + searchButton: { + alignItems: 'center', + height: 34, + justifyContent: 'center', + width: 30, + }, + locateButton: { + alignItems: 'center', + backgroundColor: colors.surface, + borderColor: colors.line, + borderRadius: 14, + borderWidth: 1, + elevation: 4, + height: 48, + justifyContent: 'center', + width: 42, + }, + locateButtonActive: { backgroundColor: colors.limeSoft }, + searchResults: { + backgroundColor: colors.surface, + borderColor: colors.line, + borderRadius: 14, + borderWidth: 1, + marginLeft: 50, + marginTop: 7, + overflow: 'hidden', + }, + searchResult: { + alignItems: 'center', + borderBottomColor: colors.line, + borderBottomWidth: 1, + flexDirection: 'row', + minHeight: 48, + paddingHorizontal: 12, + paddingVertical: 9, + }, + searchResultLast: { borderBottomWidth: 0 }, + searchResultText: { color: colors.ink, flex: 1, fontSize: 12, lineHeight: 17, marginLeft: 9 }, + searchMessage: { color: colors.sub, fontSize: 11, padding: 14, textAlign: 'center' }, + locationError: { + alignSelf: 'center', + backgroundColor: 'rgba(247, 248, 245, 0.96)', + borderColor: colors.line, + borderRadius: 11, + borderWidth: 1, + marginTop: 7, + paddingHorizontal: 10, + paddingVertical: 7, + }, + locationErrorText: { color: colors.sub, fontSize: 11 }, + selectionCard: { + backgroundColor: colors.surface, + borderColor: colors.line, + borderRadius: 18, + borderWidth: 1, + bottom: 16, + elevation: 6, + left: 14, + padding: 14, + position: 'absolute', + right: 14, + zIndex: 1000, + }, + selectionHeading: { alignItems: 'center', flexDirection: 'row' }, + selectionIcon: { + alignItems: 'center', + backgroundColor: colors.limeSoft, + borderRadius: 12, + height: 38, + justifyContent: 'center', + marginRight: 10, + width: 38, + }, + selectionCopy: { flex: 1 }, + selectionKicker: { color: '#728456', fontSize: 10, fontWeight: '800' }, + selectionAddress: { color: colors.ink, fontSize: 13, lineHeight: 19, marginTop: 4 }, + selectionHint: { color: colors.sub, fontSize: 11, lineHeight: 17, marginTop: 4 }, + selectionMeta: { color: colors.muted, fontSize: 10, marginTop: 8 }, + confirmButton: { + alignItems: 'center', + backgroundColor: colors.deep, + borderRadius: 12, + height: 48, + justifyContent: 'center', + marginTop: 12, + }, + confirmButtonDisabled: { opacity: 0.38 }, + confirmButtonText: { color: colors.surface, fontSize: 13, fontWeight: '800' }, +}); diff --git a/frontend/src/features/schedule/location/MapPicker/types.ts b/frontend/src/features/schedule/location/MapPicker/types.ts new file mode 100644 index 0000000..c6e4128 --- /dev/null +++ b/frontend/src/features/schedule/location/MapPicker/types.ts @@ -0,0 +1,9 @@ +import type { MapLocation } from '../types'; + +export type { MapLocation }; + +export type MapPickerProps = { + initialLocation: MapLocation | null; + onCancel: () => void; + onConfirm: (location: MapLocation) => void; +}; diff --git a/frontend/src/features/schedule/location/index.ts b/frontend/src/features/schedule/location/index.ts new file mode 100644 index 0000000..421840d --- /dev/null +++ b/frontend/src/features/schedule/location/index.ts @@ -0,0 +1,4 @@ +export { LocationPickerSheet } from './LocationPickerSheet'; +export type { SavedLocation } from './types'; +export { createSavedLocation, matchSavedLocation, upsertSavedLocation } from './utils'; +export { useSessionSavedLocations } from './useSessionSavedLocations'; diff --git a/frontend/src/features/schedule/location/types.ts b/frontend/src/features/schedule/location/types.ts new file mode 100644 index 0000000..661083e --- /dev/null +++ b/frontend/src/features/schedule/location/types.ts @@ -0,0 +1,7 @@ +import type { MapLocation } from '@/shared/types/geo'; + +export type { MapLocation }; + +export type SavedLocation = MapLocation & { + id: string; +}; diff --git a/frontend/src/features/schedule/location/useSessionSavedLocations.ts b/frontend/src/features/schedule/location/useSessionSavedLocations.ts new file mode 100644 index 0000000..0ca552b --- /dev/null +++ b/frontend/src/features/schedule/location/useSessionSavedLocations.ts @@ -0,0 +1,21 @@ +import { useCallback, useMemo, useState } from 'react'; + +import type { SavedLocation } from './types'; +import { upsertSavedLocation } from './utils'; + +/** + * App-session scoped saved locations. + * + * Persistence is intentionally not implied: a host can replace this hook with + * a storage-backed provider once a cross-platform storage adapter is part of + * the composition root. + */ +export function useSessionSavedLocations() { + const [locations, setLocations] = useState([]); + + const upsert = useCallback((location: SavedLocation) => { + setLocations((current) => upsertSavedLocation(current, location)); + }, []); + + return useMemo(() => ({ locations, upsert }), [locations, upsert]); +} diff --git a/frontend/src/features/schedule/location/utils.ts b/frontend/src/features/schedule/location/utils.ts new file mode 100644 index 0000000..519c313 --- /dev/null +++ b/frontend/src/features/schedule/location/utils.ts @@ -0,0 +1,60 @@ +import type { MapLocation, SavedLocation } from './types'; + +export function createSavedLocation(location: MapLocation, id?: string): SavedLocation { + return { + ...location, + id: id ?? `loc_${Date.now()}`, + }; +} + +export function upsertSavedLocation( + locations: SavedLocation[], + location: SavedLocation, +): SavedLocation[] { + const index = locations.findIndex((item) => item.id === location.id); + if (index < 0) { + return [...locations, location]; + } + const next = [...locations]; + next[index] = location; + return next; +} + +export function matchSavedLocation( + locations: SavedLocation[], + candidate: { + latitude?: number | null; + longitude?: number | null; + location_name?: string | null; + location_address?: string | null; + }, +): SavedLocation | null { + if (candidate.latitude != null && candidate.longitude != null) { + const byCoords = locations.find( + (item) => item.latitude === candidate.latitude && item.longitude === candidate.longitude, + ); + if (byCoords) { + return byCoords; + } + } + + const name = candidate.location_name?.trim(); + const address = candidate.location_address?.trim(); + if (!name && !address) { + return null; + } + + return ( + locations.find((item) => { + const itemName = item.name?.trim() ?? ''; + const itemAddress = item.address.trim(); + if (name && address) { + return itemName === name && itemAddress === address; + } + if (name) { + return itemName === name; + } + return itemAddress === address; + }) ?? null + ); +} diff --git a/frontend/src/features/schedule/presentation/scheduleFormat.ts b/frontend/src/features/schedule/presentation/scheduleFormat.ts new file mode 100644 index 0000000..f8ed665 --- /dev/null +++ b/frontend/src/features/schedule/presentation/scheduleFormat.ts @@ -0,0 +1,53 @@ +import type { Schedule } from '@/contracts'; +import { formatTimeValue } from '@/shared/utils/date'; + +export function timeToMinutes(value: string) { + const [hours, minutes] = value.split(':').map(Number); + return hours * 60 + minutes; +} + +export function scheduleDate(item: Schedule) { + if (!item.start_time) return null; + const value = new Date(item.start_time); + return Number.isNaN(value.getTime()) ? null : value; +} + +export function scheduleTime(item: Schedule) { + const date = scheduleDate(item); + return date ? formatTimeValue(date) : '地点'; +} + +export function scheduleRange(item: Schedule) { + const start = scheduleDate(item); + if (!start) return item.location_name ?? item.location_address ?? '地点提醒'; + const startLabel = scheduleTime(item); + if (!item.end_time) return startLabel; + const end = new Date(item.end_time); + if (Number.isNaN(end.getTime())) return startLabel; + return `${startLabel}–${formatTimeValue(end)}`; +} + +export function scheduleDuration(item: Schedule) { + const start = scheduleDate(item); + const end = item.end_time ? new Date(item.end_time) : null; + if (!start || !end || Number.isNaN(end.getTime())) return '未设置时长'; + const minutes = Math.round((end.getTime() - start.getTime()) / 60_000); + return minutes > 0 ? `${minutes} 分钟` : '未设置时长'; +} + +export function scheduleColor(item: Schedule) { + if (item.status === 'done') return '#A8C7B5'; + if (item.schedule_type === 'location') return '#E79472'; + return item.source_mode === 'voice' ? '#AEC46B' : '#7DA6B8'; +} + +export function scheduleSourceLabel(item: Schedule) { + return item.source_mode === 'voice' ? '语音创建' : '手动创建'; +} + +/** 只映射契约里的三种 status,不做「已过期」等过程态。 */ +export function scheduleStatusLabel(item: Schedule) { + if (item.status === 'done') return '已完成'; + if (item.status === 'deleted') return '已删除'; + return '待完成'; +} diff --git a/frontend/src/features/schedule/screens/ScheduleScreen.tsx b/frontend/src/features/schedule/screens/ScheduleScreen.tsx new file mode 100644 index 0000000..37fb44a --- /dev/null +++ b/frontend/src/features/schedule/screens/ScheduleScreen.tsx @@ -0,0 +1,122 @@ +import { useMemo, useState } from 'react'; +import { ChevronDown, Plus } from 'lucide-react-native'; +import { Pressable, Text, View } from 'react-native'; + +import { DatePickerSheet } from '@/shared/components/DatePickerSheet'; +import type { Schedule } from '@/contracts'; +import { useCurrentDate } from '@/shared/hooks/useCurrentDate'; +import { colors } from '@/shared/theme'; +import { startOfMonth } from '@/shared/utils/date'; + +import { MonthView } from '../calendar/MonthView'; +import { buildScheduleIndex } from '../calendar/scheduleIndex'; +import { ScheduleDetailSheet } from '../detail/ScheduleDetailSheet'; +import { scheduleScreenStyles as styles } from './scheduleScreen.styles'; + +export function ScheduleScreen({ + canMutate = true, + onCreate, + onDeleteSchedule, + onEditSchedule, + onToggleSchedule, + scheduleItems, +}: { + canMutate?: boolean; + onCreate: () => void; + onDeleteSchedule: (item: Schedule) => void; + onEditSchedule: (item: Schedule) => void; + onToggleSchedule?: (item: Schedule) => void; + scheduleItems: Schedule[]; +}) { + const now = useCurrentDate(); + const [visibleMonth, setVisibleMonth] = useState(() => startOfMonth(new Date())); + const [selectedDate, setSelectedDate] = useState(() => new Date()); + const [selectedScheduleId, setSelectedScheduleId] = useState(null); + const [datePickerOpen, setDatePickerOpen] = useState(false); + + // Derive the selected entity from the cache. When a push removes it, the + // detail sheet naturally closes without mutating state during render. + const selectedSchedule = selectedScheduleId + ? scheduleItems.find((item) => item.id === selectedScheduleId) + : undefined; + const scheduleIndex = useMemo(() => buildScheduleIndex(scheduleItems), [scheduleItems]); + + const selectDate = (date: Date) => { + setSelectedDate(date); + setVisibleMonth(startOfMonth(date)); + }; + + return ( + + + setDatePickerOpen(true)} + style={styles.headerButton} + > + + {visibleMonth.getFullYear()}年{visibleMonth.getMonth() + 1}月 + + + {visibleMonth.getMonth() + 1}月 + + + + + + + + + + + setDatePickerOpen(false)} + onSelect={selectDate} + selectedDate={selectedDate} + visible={datePickerOpen} + /> + onDeleteSchedule(selectedSchedule) + : undefined + } + onEdit={ + canMutate && selectedSchedule && selectedSchedule.status !== 'deleted' + ? () => onEditSchedule(selectedSchedule) + : undefined + } + onToggle={ + canMutate && selectedSchedule && selectedSchedule.status !== 'deleted' && onToggleSchedule + ? () => onToggleSchedule(selectedSchedule) + : undefined + } + onClose={() => setSelectedScheduleId(null)} + onOpenDay={(date) => { + selectDate(date); + }} + /> + + ); +} diff --git a/frontend/src/features/schedule/screens/scheduleScreen.styles.ts b/frontend/src/features/schedule/screens/scheduleScreen.styles.ts new file mode 100644 index 0000000..696e772 --- /dev/null +++ b/frontend/src/features/schedule/screens/scheduleScreen.styles.ts @@ -0,0 +1,41 @@ +import { StyleSheet } from 'react-native'; + +import { colors } from '@/shared/theme'; + +export const scheduleScreenStyles = StyleSheet.create({ + screen: { flex: 1, paddingBottom: 76, paddingHorizontal: 20, paddingTop: 20 }, + header: { + alignItems: 'center', + flexDirection: 'row', + justifyContent: 'space-between', + marginBottom: 14, + minHeight: 50, + }, + headerButton: { flex: 1, marginRight: 12 }, + headerEyebrow: { + color: colors.muted, + fontSize: 10, + fontWeight: '700', + letterSpacing: 0, + marginBottom: 4, + }, + headerTitleRow: { alignItems: 'center', flexDirection: 'row', gap: 4 }, + headerTitle: { + color: colors.ink, + fontSize: 28, + fontWeight: '800', + letterSpacing: 0, + lineHeight: 29, + }, + addButton: { + alignItems: 'center', + backgroundColor: colors.limeSoft, + borderColor: '#D4E3B1', + borderRadius: 13, + borderWidth: 1, + height: 40, + justifyContent: 'center', + width: 40, + }, + addButtonDisabled: { opacity: 0.45 }, +}); diff --git a/frontend/src/shared/components/DatePickerSheet.styles.ts b/frontend/src/shared/components/DatePickerSheet.styles.ts new file mode 100644 index 0000000..08bf810 --- /dev/null +++ b/frontend/src/shared/components/DatePickerSheet.styles.ts @@ -0,0 +1,22 @@ +import { StyleSheet } from 'react-native'; + +import { colors } from '@/shared/theme'; + +export const datePickerSheetStyles = StyleSheet.create({ + calendar: { + borderRadius: 14, + overflow: 'hidden', + }, + todayButton: { + alignItems: 'center', + backgroundColor: colors.limeSoft, + borderColor: '#D7E6B2', + borderRadius: 13, + borderWidth: 1, + height: 48, + justifyContent: 'center', + marginHorizontal: 4, + marginTop: 12, + }, + todayButtonText: { color: colors.deep, fontSize: 13, fontWeight: '800' }, +}); diff --git a/frontend/src/shared/components/DatePickerSheet.tsx b/frontend/src/shared/components/DatePickerSheet.tsx new file mode 100644 index 0000000..8435237 --- /dev/null +++ b/frontend/src/shared/components/DatePickerSheet.tsx @@ -0,0 +1,145 @@ +import { useMemo } from 'react'; +import { Pressable, Text } from 'react-native'; +import { Calendar, LocaleConfig, type DateData } from 'react-native-calendars'; + +import { BottomSheetFrame } from '@/shared/components/BottomSheetFrame'; +import { colors } from '@/shared/theme'; +import { dateKey } from '@/shared/utils/date'; + +import { datePickerSheetStyles as styles } from './DatePickerSheet.styles'; + +type DayMarking = { + dotColor?: string; + marked?: boolean; + selected?: boolean; + selectedColor?: string; +}; + +type MarkedDates = Record; + +LocaleConfig.locales.zh = { + monthNames: [ + '一月', + '二月', + '三月', + '四月', + '五月', + '六月', + '七月', + '八月', + '九月', + '十月', + '十一月', + '十二月', + ], + monthNamesShort: [ + '1月', + '2月', + '3月', + '4月', + '5月', + '6月', + '7月', + '8月', + '9月', + '10月', + '11月', + '12月', + ], + dayNames: ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'], + dayNamesShort: ['日', '一', '二', '三', '四', '五', '六'], + today: '今天', +}; +LocaleConfig.defaultLocale = 'zh'; + +const calendarTheme = { + arrowColor: colors.deep, + backgroundColor: colors.surface, + calendarBackground: colors.surface, + dayTextColor: colors.ink, + dotColor: colors.coral, + monthTextColor: colors.ink, + selectedDayBackgroundColor: colors.deep, + selectedDayTextColor: colors.surface, + selectedDotColor: colors.surface, + textDayFontSize: 14, + textDayFontWeight: '600' as const, + textDayHeaderFontSize: 12, + textDayHeaderFontWeight: '700' as const, + textMonthFontSize: 16, + textMonthFontWeight: '800' as const, + textSectionTitleColor: colors.sub, + todayTextColor: colors.deep, +}; + +function parseDateKey(value: string) { + const [year, month, day] = value.split('-').map(Number); + return new Date(year, month - 1, day); +} + +type DatePickerSheetProps = { + markedDateKeys?: string[]; + onClose: () => void; + onSelect: (date: Date) => void; + selectedDate: Date; + visible: boolean; +}; + +export function DatePickerSheet({ + markedDateKeys = [], + onClose, + onSelect, + selectedDate, + visible, +}: DatePickerSheetProps) { + const selectedKey = dateKey(selectedDate); + const markedDates = useMemo(() => { + const next: MarkedDates = {}; + for (const key of markedDateKeys) { + next[key] = { marked: true, dotColor: colors.coral }; + } + next[selectedKey] = { + ...(next[selectedKey] ?? {}), + selected: true, + selectedColor: colors.deep, + }; + return next; + }, [markedDateKeys, selectedKey]); + + const handleDayPress = (day: DateData) => { + onSelect(parseDateKey(day.dateString)); + onClose(); + }; + + return ( + + + { + onSelect(new Date()); + onClose(); + }} + style={styles.todayButton} + > + 回到今天 + + + ); +} diff --git a/frontend/src/shared/components/TimePickerSheet.styles.ts b/frontend/src/shared/components/TimePickerSheet.styles.ts new file mode 100644 index 0000000..b38c1cd --- /dev/null +++ b/frontend/src/shared/components/TimePickerSheet.styles.ts @@ -0,0 +1,65 @@ +import { StyleSheet } from 'react-native'; + +import { colors } from '@/shared/theme'; + +export const timePickerSheetStyles = StyleSheet.create({ + preview: { + color: colors.ink, + fontSize: 28, + fontWeight: '800', + marginBottom: 12, + marginTop: 4, + textAlign: 'center', + }, + columns: { + flexDirection: 'row', + gap: 10, + paddingHorizontal: 4, + }, + column: { flex: 1 }, + columnLabel: { + color: colors.sub, + fontSize: 12, + fontWeight: '800', + marginBottom: 8, + textAlign: 'center', + }, + list: { + backgroundColor: '#F8FAF7', + borderColor: colors.line, + borderRadius: 14, + borderWidth: 1, + maxHeight: 220, + }, + item: { + alignItems: 'center', + height: 44, + justifyContent: 'center', + }, + itemSelected: { + backgroundColor: colors.limeSoft, + }, + itemText: { + color: colors.ink, + fontSize: 16, + fontWeight: '600', + }, + itemTextSelected: { + color: colors.deep, + fontWeight: '800', + }, + confirm: { + alignItems: 'center', + backgroundColor: colors.deep, + borderRadius: 13, + height: 48, + justifyContent: 'center', + marginHorizontal: 4, + marginTop: 14, + }, + confirmText: { + color: colors.surface, + fontSize: 13, + fontWeight: '800', + }, +}); diff --git a/frontend/src/shared/components/TimePickerSheet.tsx b/frontend/src/shared/components/TimePickerSheet.tsx new file mode 100644 index 0000000..4e12239 --- /dev/null +++ b/frontend/src/shared/components/TimePickerSheet.tsx @@ -0,0 +1,127 @@ +import { useEffect, useMemo, useRef, useState, type RefObject } from 'react'; +import { Pressable, ScrollView, Text, View } from 'react-native'; + +import { BottomSheetFrame } from '@/shared/components/BottomSheetFrame'; + +import { timePickerSheetStyles as styles } from './TimePickerSheet.styles'; + +const HOURS = Array.from({ length: 24 }, (_, index) => String(index).padStart(2, '0')); +const MINUTES = Array.from({ length: 60 }, (_, index) => String(index).padStart(2, '0')); +const ITEM_HEIGHT = 44; + +type TimePickerSheetProps = { + onClose: () => void; + onSelect: (time: string) => void; + selectedTime: Date; + visible: boolean; +}; + +function TimeWheelColumn({ + accessibilityUnit, + label, + listRef, + onSelect, + selected, + values, +}: { + accessibilityUnit: string; + label: string; + listRef: RefObject; + onSelect: (value: string) => void; + selected: string; + values: string[]; +}) { + return ( + + {label} + + {values.map((value) => { + const isSelected = value === selected; + return ( + onSelect(value)} + style={[styles.item, isSelected && styles.itemSelected]} + > + {value} + + ); + })} + + + ); +} + +export function TimePickerSheet({ + onClose, + onSelect, + selectedTime, + visible, +}: TimePickerSheetProps) { + const [hour, setHour] = useState(() => String(selectedTime.getHours()).padStart(2, '0')); + const [minute, setMinute] = useState(() => String(selectedTime.getMinutes()).padStart(2, '0')); + const hourListRef = useRef(null); + const minuteListRef = useRef(null); + const selectedTimeMs = selectedTime.getTime(); + + useEffect(() => { + if (!visible) return; + const nextHour = String(new Date(selectedTimeMs).getHours()).padStart(2, '0'); + const nextMinute = String(new Date(selectedTimeMs).getMinutes()).padStart(2, '0'); + // 打开或外部时间变化时同步滚轮;属受控 sheet 的合法同步。 + // eslint-disable-next-line react-hooks/set-state-in-effect -- sync picker when sheet opens + setHour(nextHour); + setMinute(nextMinute); + const frame = requestAnimationFrame(() => { + hourListRef.current?.scrollTo({ y: Number(nextHour) * ITEM_HEIGHT, animated: false }); + minuteListRef.current?.scrollTo({ y: Number(nextMinute) * ITEM_HEIGHT, animated: false }); + }); + return () => cancelAnimationFrame(frame); + }, [selectedTimeMs, visible]); + + const preview = useMemo(() => `${hour}:${minute}`, [hour, minute]); + + const confirm = () => { + onSelect(`${hour}:${minute}`); + onClose(); + }; + + return ( + + {preview} + + + + + + 确认 + + + ); +} From 05d231695c248634477f2d161cb7c59f06e3ca2d Mon Sep 17 00:00:00 2001 From: mac Date: Fri, 31 Jul 2026 18:59:00 +0800 Subject: [PATCH 4/5] feat(frontend): add voice assistant flows --- .../components/AssistantChatSheet.test.tsx | 77 ++++++ .../components/AssistantDock.test.tsx | 31 +++ .../components/AssistantDraftCard.test.tsx | 49 ++++ .../components/TempoAssistantIcon.test.tsx | 13 + .../components/VoiceHoldButton.test.tsx | 71 +++++ .../hooks/useAssistantSession.test.tsx | 250 +++++++++++++++++ .../components/AssistantChatSheet.styles.ts | 95 +++++++ .../components/AssistantChatSheet.tsx | 133 +++++++++ .../components/AssistantDock.styles.ts | 20 ++ .../assistant/components/AssistantDock.tsx | 32 +++ .../components/AssistantDraftCard.styles.ts | 134 +++++++++ .../components/AssistantDraftCard.tsx | 93 +++++++ .../components/TempoAssistantIcon.tsx | 24 ++ .../components/VoiceHoldButton.styles.ts | 54 ++++ .../assistant/components/VoiceHoldButton.tsx | 177 ++++++++++++ .../assistant/data/VoiceStreamPort.ts | 154 +++++++++++ .../assistant/hooks/useAssistantSession.ts | 256 ++++++++++++++++++ frontend/src/features/assistant/index.ts | 5 + frontend/src/features/assistant/types.ts | 31 +++ 19 files changed, 1699 insertions(+) create mode 100644 frontend/__tests__/features/assistant/components/AssistantChatSheet.test.tsx create mode 100644 frontend/__tests__/features/assistant/components/AssistantDock.test.tsx create mode 100644 frontend/__tests__/features/assistant/components/AssistantDraftCard.test.tsx create mode 100644 frontend/__tests__/features/assistant/components/TempoAssistantIcon.test.tsx create mode 100644 frontend/__tests__/features/assistant/components/VoiceHoldButton.test.tsx create mode 100644 frontend/__tests__/features/assistant/hooks/useAssistantSession.test.tsx create mode 100644 frontend/src/features/assistant/components/AssistantChatSheet.styles.ts create mode 100644 frontend/src/features/assistant/components/AssistantChatSheet.tsx create mode 100644 frontend/src/features/assistant/components/AssistantDock.styles.ts create mode 100644 frontend/src/features/assistant/components/AssistantDock.tsx create mode 100644 frontend/src/features/assistant/components/AssistantDraftCard.styles.ts create mode 100644 frontend/src/features/assistant/components/AssistantDraftCard.tsx create mode 100644 frontend/src/features/assistant/components/TempoAssistantIcon.tsx create mode 100644 frontend/src/features/assistant/components/VoiceHoldButton.styles.ts create mode 100644 frontend/src/features/assistant/components/VoiceHoldButton.tsx create mode 100644 frontend/src/features/assistant/data/VoiceStreamPort.ts create mode 100644 frontend/src/features/assistant/hooks/useAssistantSession.ts create mode 100644 frontend/src/features/assistant/index.ts create mode 100644 frontend/src/features/assistant/types.ts diff --git a/frontend/__tests__/features/assistant/components/AssistantChatSheet.test.tsx b/frontend/__tests__/features/assistant/components/AssistantChatSheet.test.tsx new file mode 100644 index 0000000..50ae6d7 --- /dev/null +++ b/frontend/__tests__/features/assistant/components/AssistantChatSheet.test.tsx @@ -0,0 +1,77 @@ +import { describe, expect, it, jest } from '@jest/globals'; +import { fireEvent, render, screen } from '@testing-library/react-native'; + +import { AssistantChatSheet } from '@/features/assistant/components/AssistantChatSheet'; + +describe('AssistantChatSheet', () => { + it('shows empty state and closes', () => { + const onClose = jest.fn(); + render( + , + ); + expect(screen.getByText('等你说第一句话')).toBeTruthy(); + fireEvent.press(screen.getAllByLabelText('关闭语音助手')[0]!); + expect(onClose).toHaveBeenCalled(); + }); + + it('renders user, draft and assistant messages', () => { + const onAction = jest.fn(); + render( + , + ); + expect(screen.getByText('明天下午开会')).toBeTruthy(); + expect(screen.getByText('开会')).toBeTruthy(); + expect(screen.getByText('已记下')).toBeTruthy(); + fireEvent.press(screen.getByLabelText('加入')); + expect(onAction).toHaveBeenCalledWith('d1', { id: 'ok', kind: 'confirm', label: '加入' }); + }); + + it('hides when not visible', () => { + render( + , + ); + expect(screen.queryByText('语音助手')).toBeNull(); + }); + + it('shows a processing state after recording is released', () => { + render( + , + ); + expect(screen.getByText('正在整理录音…')).toBeTruthy(); + }); +}); diff --git a/frontend/__tests__/features/assistant/components/AssistantDock.test.tsx b/frontend/__tests__/features/assistant/components/AssistantDock.test.tsx new file mode 100644 index 0000000..553fe6c --- /dev/null +++ b/frontend/__tests__/features/assistant/components/AssistantDock.test.tsx @@ -0,0 +1,31 @@ +import { describe, expect, it, jest } from '@jest/globals'; +import { fireEvent, render, screen } from '@testing-library/react-native'; + +import { AssistantDock } from '@/features/assistant/components/AssistantDock'; + +jest.mock('@/features/assistant/components/VoiceHoldButton', () => ({ + VoiceHoldButton: ({ onPress }: { onPress?: () => void }) => { + const { Pressable, Text } = require('react-native'); + return ( + + voice-hold + + ); + }, +})); + +describe('AssistantDock', () => { + it('hides when requested', () => { + const { queryByText } = render( +