import React, { ReactNode, ReactElement, ComponentProps } from 'react'; import * as react_jsx_runtime from 'react/jsx-runtime'; import { StyleProp, ViewStyle, AlertButton } from 'react-native'; /** Pure URL helpers shared by the Expo auth client and its tests. */ declare function buildExpoOAuthStartUrl(baseUrl: string, provider: string, redirectUri: string, state?: string, startPath?: string, codeChallenge?: string): string; declare function codeFromExpoRedirect(url: string): string | null; declare function stateFromExpoRedirect(url: string): string | null; declare function normalizeExpoBaseUrl(baseUrl: string): string; /** The Expo client's errors, without its native dependencies, so the web build of deepspace/native can share them. */ declare class DeepSpaceExpoError extends Error { readonly status: number; readonly body: unknown; constructor(message: string, status: number, body: unknown); } /** * Thrown by `signIn` when the person closes the sign-in sheet. It isn't a * failure, so apps usually return to where they were without showing an error. * Check `code`: `instanceof` fails if the bundler includes two copies of * this package. */ declare class DeepSpaceSignInCancelledError extends Error { readonly code: "sign_in_cancelled"; constructor(); } type ExpoAuthProvider = 'google' | (string & {}); interface DeepSpaceExpoSession { sessionToken: string; accessToken: string; } interface DeepSpaceExpoUser { userId: string; claims: Record; } interface ExpoAuthStorage { getItem(key: string): Promise; setItem(key: string, value: string): Promise; deleteItem(key: string): Promise; } interface DeepSpaceExpoClientOptions { /** Deployed app origin, for example `https://example.app.space`. */ baseUrl: string; /** Defaults to Expo's `createURL('auth/callback')`. */ redirectUri?: string; /** SecureStore-backed by default; inject a test store when unit testing. */ storage?: ExpoAuthStorage; /** Namespaced storage key so multiple apps can share one device. */ storageKey?: string; /** Worker endpoint overrides for apps with a custom auth route. */ paths?: Partial; fetch?: typeof globalThis.fetch; /** Abort stalled auth/API requests after this many milliseconds. */ timeoutMs?: number; } interface ExpoAuthPaths { start: string; exchange: string; token: string; me: string; signOut: string; } /** Called with the new session after sign-in or refresh, and with `null` after sign-out or expiry. */ type DeepSpaceExpoSessionListener = (session: DeepSpaceExpoSession | null) => void; declare class DeepSpaceExpoClient { private readonly baseUrl; private readonly redirectUri; private readonly storage; private readonly storageKey; private readonly paths; private readonly fetcher; private readonly timeoutMs; private session; private refreshing; private sessionGeneration; private authOperationGeneration; private storageWrites; private readonly listeners; constructor(options: DeepSpaceExpoClientOptions); /** The deployed app origin this client talks to, without a trailing slash. */ get origin(): string; /** Observe session changes. Loading a stored session does not notify. Returns an unsubscribe function. */ subscribe(listener: DeepSpaceExpoSessionListener): () => void; getSession(): Promise; signIn(provider?: ExpoAuthProvider, state?: string): Promise; signInWithGoogle(state?: string): Promise; /** Mint a fresh bearer from the stored session. Concurrent callers share one request. */ refresh(): Promise; private refreshOnce; getAuthToken(): Promise; me(): Promise; request(path: string, init?: RequestInit, retried?: boolean): Promise; signOut(): Promise; private json; private saveSession; private clearSession; private writeStorage; private notify; private fetchWithTimeout; } declare function createDeepSpaceExpoClient(options: DeepSpaceExpoClientOptions): DeepSpaceExpoClient; /** Same shape as the browser `useAuth`, so the shared storage layer reads it unchanged. */ interface NativeAuthState { /** True once the stored session has been read. */ isLoaded: boolean; isSignedIn: boolean; userId: string | null; /** Native sessions are opaque to the app, so this is always null. */ sessionId: string | null; } /** Auth state for the signed-in DeepSpace user on this device. */ declare function useAuth(): NativeAuthState; /** * DeepSpaceNativeProvider and the hooks that read the bound native client. */ interface DeepSpaceNativeProviderProps { /** The app's single client from `createDeepSpaceExpoClient`. Create it once, at module scope. */ client: DeepSpaceExpoClient; children?: ReactNode; } /** * Binds the app's DeepSpace client for every hook below it. Render exactly one, * at the root, above `RecordProvider`. It also roots `useKeyboardShortcuts`, * so it renders a full-size view (`flex: 1`). */ declare function DeepSpaceNativeProvider({ client, children, }: DeepSpaceNativeProviderProps): ReactElement; interface DeepSpaceNative extends NativeAuthState { client: DeepSpaceExpoClient; /** Opens the system sign-in sheet. Throws `sign_in_cancelled` when the person closes it. */ signIn: (provider?: ExpoAuthProvider) => Promise; signOut: () => Promise; } /** Auth state plus sign-in and sign-out for the bound client. */ declare function useDeepSpace(): DeepSpaceNative; /** * Shared type definitions for the DeepSpace SDK * * Types used by both client-side SDK and server-side workers. */ type ColumnInterpretation = { kind: 'plain'; } | { kind: 'currency'; symbol: string; decimals: number; } | { kind: 'date'; format?: string; } | { kind: 'datetime'; format?: string; } | { kind: 'boolean'; trueLabel?: string; falseLabel?: string; } | { kind: 'percent'; decimals?: number; } | { kind: 'select'; options: string[]; } | { kind: 'multiselect'; options: string[]; } | { kind: 'url'; } | { kind: 'email'; } | { kind: 'json'; } | { kind: 'reference'; targetTable: string; displayColumn: string; }; interface ColumnDefinition { /** Stable ID override (survives renames). Falls back to `col_{name}`. */ id?: string; name: string; storage: 'number' | 'text'; interpretation: ColumnInterpretation | string; expression?: string; userBound?: boolean; immutable?: boolean; required?: boolean; default?: unknown; timestampTrigger?: { field: string; value?: unknown; }; } type PermissionLevel = boolean | 'own' | 'unclaimed-or-own' | 'collaborator' | 'team' | 'access' | 'published' | 'shared'; interface RolePermissions { read: PermissionLevel; /** Create has no existing record to evaluate, so it is an explicit grant. */ create: boolean; update: PermissionLevel; delete: PermissionLevel; /** If set, only these caller-supplied columns can be created or updated by this role. */ writableFields?: string[]; } interface CollectionSchema { name: string; /** Every collection is stored in typed SQL columns. */ columns: ColumnDefinition[]; /** Composite uniqueness constraint (for example `['userId', 'taskId']`). */ uniqueOn?: string[]; /** Ownership column; defaults to the row creator. */ ownerField?: string; /** Column containing a JSON array of collaborator user ids. */ collaboratorsField?: string; /** Column containing the team id used by team permission rules. */ teamField?: string; /** A string is public when equal to `public`; an object supplies an exact value. */ visibilityField?: string | { field: string; value: unknown; }; /** Permissions per role; `*` is the catch-all role. */ permissions: Record; /** Default role assigned on the `users` collection. */ defaultRole?: string; /** * `users` collection only: what the `user.list` roster shows a non-admin * whose `read` policy is row-scoped (`'own'`, `'team'`, …). Default * `'public-identity'`: every registered user's id/name/imageUrl/role/lastSeenAt * (`lastSeenAt` is what `usePresence` reads) — the * row policy keeps guarding full-row reads on the records/query path. * `'read-policy'`: the roster contains only the rows the caller's read * policy grants (still projected to public identity) — for apps that scope * users to a tenant/team and must not show names across it. */ roster?: 'public-identity' | 'read-policy'; } interface Query { collection: string; where?: Record; orderBy?: string; orderDir?: 'asc' | 'desc'; limit?: number; } /** * Storage Types * * Type definitions for the RecordRoom storage system. */ interface RecordData { recordId: string; data: T; createdBy: string; createdAt: string; updatedAt: string; } interface UserKarma { total: number; breakdown: { publishing: number; content: number; comment: number; curation: number; }; rank: number; monthlyKarma: number; monthlyRank: number; } interface UserCredits { total: number; subscription: number; bonus: number; purchased: number; } /** * User profile. Core fields (id, name, email, imageUrl) come from the JWT. * Optional fields (billing, karma) can be loaded by the app separately. */ interface UserProfile { id: string; name: string; email: string; imageUrl?: string; isAdmin?: boolean; publicUsername?: string | null; subscriptionTier?: string | null; subscriptionStatus?: string | null; karma?: UserKarma | null; credits?: UserCredits | null; } /** * Complete user data combining API profile + room-specific role. * * Used by useUser() hook. Merges: * - UserProfile from API (karma, credits, isAdmin, etc.) * - Room role from WebSocket (derived from user-roles collection) */ interface User extends UserProfile { /** User's role in this room (e.g., 'applicant', 'intern', 'admin') */ role: string; } /** * User info from RecordRoom WebSocket. * Used by useUsers() hook for listing other users in the room. */ interface RoomUser { id: string; /** Present only in the admin user-management view. */ email?: string; name: string; imageUrl?: string; role: string; /** Present only in the admin user-management view. */ createdAt?: string; /** In every roster projection: `usePresence` derives online state from it. */ lastSeenAt?: string; } /** A rejected fire-and-forget write. */ interface WriteError { /** * `permission` — RBAC denial; `validation` — data validation/other server * rejection; `not_ready` — rejected client-side, never sent: the room could * not accept writes yet (gate the control on `ready` from `useMutations()`), * or a queued confirmed write was dropped because the account changed or * its scope unmounted (`WriteCancelledError`; the title says which). */ kind: 'permission' | 'validation' | 'not_ready'; /** Short human-readable summary, safe to show end users. */ title: string; /** Longer human-readable explanation; may be empty. */ detail: string; } interface RecordProviderProps { children: ReactNode; /** * If true, allow anonymous users to connect as read-only viewers. * Non-signed-in users skip the sign-in screen and connect * to the WebSocket without a userId. The server assigns them * an ephemeral anon-* ID with 'viewer' role. */ allowAnonymous?: boolean; /** Auth token provider for WS connections. */ getAuthToken?: () => Promise; /** * Receives every rejected write — denied or invalid by the server, and * attempted before the room was ready. Wire this to app UI so users see * changes that didn't land. Defaults to a deduplicated console error. */ onWriteError?: (error: WriteError) => void; } /** * RecordProvider Context * * Provides shared authentication state and scope registration for RecordRoom. * RecordScope components own individual WebSocket connections. */ /** * RecordProvider supplies shared authentication and scope registration. * Use `` for every RecordRoom connection. * * @example * ```tsx * * * * * * * * ``` */ declare function RecordProvider({ children, allowAnonymous, getAuthToken: getAuthTokenProp, onWriteError, }: RecordProviderProps): React.ReactElement; interface SharedScopeConfig { roomId: string; schemas: CollectionSchema[]; } interface RecordScopeProps$1 { roomId: string; schemas: CollectionSchema[]; children?: ReactNode; /** Additional scopes to connect (headless — no children, just register collections). */ sharedScopes?: SharedScopeConfig[]; /** WebSocket base URL override. Derived from window.location if omitted. */ wsUrl?: string; /** Path prefix for WebSocket route. Default: '/ws'. */ wsPathPrefix?: string; /** Don't register collections in ScopeRegistry (prevents name collisions). */ isolated?: boolean; } declare function RecordScope$1({ roomId, schemas, children, sharedScopes, wsUrl, wsPathPrefix, isolated, }: RecordScopeProps$1): react_jsx_runtime.JSX.Element; /** * RecordScope for React Native. Identical to the browser component except that * the WebSocket base defaults to the bound client's origin, since a native app * has no `window.location` to derive it from. */ type RecordScopeProps = ComponentProps; declare function RecordScope(props: RecordScopeProps): ReactElement; /** * useQuery Hook * * Subscribe to a query with real-time updates. * Resolves the correct scope via ScopeRegistry (multi-scope) * or uses the nearest RecordScope context during registry transitions. */ /** * Subscribe to a query with real-time updates. * * @example * ```tsx * const { records, status } = useQuery('tasks', { * where: { userId: me }, * orderBy: 'createdAt', * orderDir: 'desc', * limit: 50 * }) * ``` */ declare function useQuery(collection: string, options?: { where?: Record; orderBy?: string; orderDir?: 'asc' | 'desc'; limit?: number; }): { records: RecordData[]; status: 'loading' | 'ready' | 'error'; error?: string; }; /** * useMutations Hook * * Get mutation functions for a collection. * Resolves the correct scope via ScopeRegistry (multi-scope) * or uses the nearest RecordScope context during registry transitions. */ /** * Get mutation functions for a collection. * * Type contract: * - `create(data)` requires the **full** record shape `T`. New records * have no existing data to merge with, so all fields the schema * considers required must be present. * - `put(recordId, patch)` accepts a **`Partial`**. The server * merges the patch into the existing row (`{...existing, ...patch}`), * so callers should send only the fields they want to change. * * @example * ```tsx * const { create, put, remove } = useMutations('tasks') * * // create needs the full Task shape * const id = await create({ title: 'New task', completed: false, priority: 1 }) * * // put is a partial update — only the fields you want to change * await put(id, { completed: true }) * await remove(id) * ``` */ declare function useMutations(collection: string): { /** True once the collection's RecordRoom can accept writes. Disable the * control on it (`disabled={!ready}`) rather than early-returning from * the handler: an early return drops the user's write silently, while a * write attempted before `ready` at least raises a `not_ready` error. */ ready: boolean; create: (data: T) => Promise; put: (recordId: string, data: Partial) => Promise; remove: (recordId: string) => Promise; /** Resolve once the server has applied the write. They survive a dropped * connection: a write made while reconnecting waits and is sent, in * order, when the room is back, and the room applies a resend at most * once. They reject when the server refuses the write; with * `WriteUnconfirmedError` if the room never answers it across several * fresh connections, its safe resend window expires, or its scope closes * after sending; with `not_ready` before the room's first connection; and * with `WriteCancelledError` when it was never sent before the account * changed or scope unmounted, or was made after the scope unmounted. Unsent * writes can wait offline indefinitely; a sent write is not retried after * the server can forget its outcome (just under a day). */ createConfirmed: (data: T) => Promise; putConfirmed: (recordId: string, data: Partial) => Promise; removeConfirmed: (recordId: string) => Promise; }; /** * useUser Hook * * Access current user with profile data and room role. */ /** * Hook to access current user. * * Returns merged user data: * - Profile from API (karma, credits, subscription, isAdmin) * - Role from room (derived from user-roles collection) * * The role is the user's role in THIS miniapp/room, which can be * different from their global isAdmin status. * * @example * ```tsx * const { user, refetch, isLoading } = useUser() * * // Check role * if (user?.role === 'admin') { ... } * if (user?.role === 'intern') { ... } * * // Access karma/credits * const karma = user?.karma?.total ?? 0 * * // Refetch after expensive operations * await refetch() // Updates user.credits * ``` */ declare function useUser(): { user: User | null; isLoading: boolean; refetch: () => Promise; }; /** * useUsers Hook * * Access all users in the current room. * User data (name, email, imageUrl, role) comes from the RecordRoom's users collection. */ declare function useUsers(): { users: RoomUser[]; usersLoaded: boolean; setRole: (userId: string, role: string) => void; refresh: () => void; }; /** * useUserLookup Hook * * Provides helper functions to look up user information by userId. * This is a convenience wrapper around useUsers() that provides O(1) lookups. * * The authoritative user data comes from the users collection. */ interface UserInfo { id: string; /** Available to admins; ordinary collaborators receive public identity only. */ email?: string; name: string; imageUrl?: string; role: string; } /** * Hook for looking up user information by ID. * * @example * ```tsx * const { getUser, getEmail, getName } = useUserLookup() * * // Get full user info * const user = getUser(userId) * * // Get just email * const email = getEmail(userId) * ``` */ declare function useUserLookup(): { /** All users from the users collection */ users: RoomUser[]; /** True once the user list has been received from the server. */ usersLoaded: boolean; /** Map of userId -> UserInfo for direct access */ userMap: Map; /** Get full user info by userId */ getUser: (userId: string) => UserInfo | null; /** Get user email by userId */ getEmail: (userId: string) => string | null; /** Get user name by userId */ getName: (userId: string) => string | null; }; /** * usePresence — Derives online/offline from lastSeenAt in users collection. * * Two mechanisms keep presence working: * 1. Heartbeat: Sends MSG.USER_UPDATE every 60s so the server refreshes * our lastSeenAt in the c_users table and broadcasts the change. * 2. Tick: A re-render trigger every 30s so isOnline() re-evaluates * Date.now() and users who stopped heartbeating transition to offline. */ interface UsePresenceOptions { timeoutMs?: number; } declare function usePresence(opts?: UsePresenceOptions): { isOnline: (userId: string) => boolean; getLastSeen: (userId: string) => string | null; users: RoomUser[]; }; /** A write was attempted before its RecordRoom connection became ready. */ declare class RecordRoomNotReadyError extends Error { readonly code = "not_ready"; constructor(collection?: string); } /** * The room left a confirmed write unanswered on several fresh connections * (RecordSocket's MAX_UNANSWERED_SENDS), its safe resend window expired, or * its scope closed after sending it but before learning the outcome. */ declare class WriteUnconfirmedError extends Error { readonly code = "unconfirmed"; constructor(); } /** * A confirmed write was still waiting to be sent when its connection was * retired: the signed-in account changed, or the scope that owned it * unmounted. It was never sent, so it was not saved; waiting longer would not * help, and sending it as another account would be wrong. */ declare class WriteCancelledError extends Error { readonly code = "cancelled"; constructor(); } /** * The room id a client asks for to open its own account store * (`accountStores` in deepspace/worker). The server decides which store * actually opens from the verified token, never from this name; the user id * in it only keeps each account's local cache apart on a shared device. */ declare function accountRoomId(userId: string): string; /** * ConnectionStatus type and utility for domain hooks. * * Maps useQuery() status to a simpler connection state * used by app-level platform providers. */ type ConnectionStatus = 'connecting' | 'connected' | 'reconnecting' | 'error'; /** * Map a useQuery() status string to a ConnectionStatus. * * useQuery returns: 'pending' | 'ready' | 'error' * This maps to the simplified connection states used by platform providers. */ declare function toConnectionStatus(queryStatus: string): ConnectionStatus; /** * Which files a request addresses. `self`: the signed-in user's private * folder, readable by that user alone. `app`: the app's public allocation, * readable by anyone. */ type AppFileScope = 'self' | 'app'; /** `{ uri, headers }`, the source shape `` and expo-video take. */ interface FileSource { uri: string; headers?: Record; } interface FileSourceOptions { /** Same meaning as `useR2Files`: `self` (default) is the user's private folder, `app` is public. */ scope?: AppFileScope; } /** * The source for a stored file key (the `key` an upload returned), or null * while there is no key or, for a private file, no signed-in bearer yet. The * source is rebuilt when the session changes, so an image loaded after a * token refresh carries the new bearer. */ declare function useFileSource(key: string | null | undefined, options?: FileSourceOptions): FileSource | null; /** * Messaging Types * * TypeScript interfaces for the messaging collection schemas. */ interface Channel { name: string; description?: string; type: 'public'; createdBy: string; archived: boolean; } interface Message { channelId: string; content: string; authorId: string; parentMessageId?: string; edited: boolean; editedAt?: string; /** Soft-delete flag. The row stays — UIs render a tombstone. */ deleted?: boolean; } interface Reaction { messageId: string; channelId: string; emoji: string; userId: string; } interface ChannelMember { channelId: string; userId: string; joinedAt: string; } interface ReadReceipt { channelId: string; userId: string; lastReadAt: string; } interface GroupedReaction { emoji: string; count: number; currentUserReacted: boolean; userIds: string[]; } /** * useMessages — subscribe to messages in a channel with send/edit/remove. */ declare function useMessages(channelId: string | undefined, options?: { parentMessageId?: string; }): { messages: RecordData[]; status: "error" | "ready" | "loading"; error: string | undefined; send: (content: string, parentMessageId?: string) => Promise | undefined; edit: (messageId: string, newContent: string) => void; remove: (messageId: string) => void; softDelete: (messageId: string) => void; }; /** * useChannels — subscribe to channels with create/archive/update. */ declare function useChannels(): { channels: RecordData[]; status: "error" | "ready" | "loading"; error: string | undefined; create: (data: { name: string; description?: string; }) => Promise; archive: (channelId: string) => void; update: (channelId: string, data: Partial>) => void; remove: (recordId: string) => Promise; }; /** * useReactions — subscribe to reactions in a channel with toggle. */ declare function useReactions(channelId: string | undefined): { reactions: RecordData[]; status: "error" | "ready" | "loading"; error: string | undefined; getReactionsForMessage: (messageId: string) => GroupedReaction[]; toggle: (messageId: string, emoji: string) => void; }; /** * useChannelMembers — subscribe to channel membership with join/leave. */ declare function useChannelMembers(channelId: string | undefined): { members: RecordData[]; status: "error" | "ready" | "loading"; error: string | undefined; join: () => Promise; leave: () => Promise; isMember: boolean; }; /** * useReadReceipts — track read state per channel for the current user. */ declare function useReadReceipts(): { receipts: RecordData[]; status: "error" | "ready" | "loading"; error: string | undefined; markAsRead: (channelId: string) => void; getUnreadCount: (channelId: string, messages: RecordData[]) => number; }; /** * Platform API error normalization — the one chokepoint that turns the * api-worker's error envelope into a render-safe + branch-safe shape. * * The platform emits errors as `{ error: , message?: , * ...details }` (e.g. 402 `{ error: 'insufficient_credits', message: * 'Insufficient credits.', availableCredits, requiredCredits }`; some routes * send the bare slug with no `message` at all, e.g. 409 * `{ error: 'owner_connect_not_ready' }`). Consumers must never show the slug * to a person or make an agent parse the sentence — this module separates the * two once, for the integration client, server actions, billing hooks, and the * CLI. */ /** One structured validation issue (Zod shape) from a `validation_failed` response. */ interface ApiErrorIssue { path?: string[]; message: string; code?: string; } /** * Integration API client. * * Plain functions for calling third-party integrations (OpenAI, weather, etc.) * through the DeepSpace API worker. Works anywhere — React components, * event handlers, server actions, outside React entirely. * * Usage: * import { integration } from 'deepspace' * const result = await integration.post('openai/chat-completion', { messages }) */ interface IntegrationResponse { success: boolean; data?: T; /** Human-readable error, safe to render directly. */ error?: string; /** Machine slug (e.g. 'insufficient_credits') for branching — never render it. */ code?: string; /** HTTP status of the failed response; 0 when no response arrived (timeout, abort, network). */ status?: number; /** Structured fields the server sent alongside the error (e.g. `availableCredits`). */ details?: Record; issues?: ApiErrorIssue[]; } interface RequestOptions { headers?: Record; timeoutMs?: number; signal?: AbortSignal; } declare const integration: { post(endpoint: string, data?: unknown, options?: RequestOptions): Promise>; get(endpoint: string, params?: Record, options?: RequestOptions): Promise>; put(endpoint: string, data?: unknown, options?: RequestOptions): Promise>; delete(endpoint: string, data?: unknown, options?: RequestOptions): Promise>; }; /** The consent URL in an integration result, shared by the native and web builds. */ /** * The consent URL inside an integration result, or null when none is needed. * Reads `data`, falling back to the result itself for a flattened envelope. */ declare function consentUrlOf(result: { success: boolean; data?: unknown; }): string | null; /** * Opens a consent URL in the system browser sheet and resolves when the person * closes it. Whether they granted access is learned by retrying the call. */ declare function openIntegrationConsent(authUrl: string): Promise; /** * Server Action Types * * Types for app-defined server actions that run in the site worker. * Actions bypass user RBAC via the X-App-Action header — the app's * server-side code IS the trust boundary. */ /** * Discriminated result wrapper. Narrowing on `.success` lets TS know * `.data` is present in the success branch and `.error` in the failure * branch, so callers can't read the wrong field by accident. * * `TData` is the per-operation data shape — `tools.query` returns * `{ records, count }`, `tools.get` returns `{ record }`, etc. Apps * that compose their own server actions can specialize further. */ type ActionResult = { success: true; data: TData; error?: never; } | { success: false; data?: never; /** Human-readable error, safe to render directly. */ error: string; /** Machine slug for branching, when the failing tool supplied one. */ code?: string; /** HTTP status from an upstream platform request, when applicable. */ status?: number; /** Structured fields supplied with the error response. */ details?: Record; /** Validation issues supplied with a `validation_failed` response. */ issues?: ApiErrorIssue[]; }; /** * Server actions from React Native. Actions answer HTTP 200 with an * `ActionResult` envelope, so callers narrow on `success`; transport failures * and HTTP errors throw `DeepSpaceExpoError`. */ declare function callAction(name: string, params?: Record): Promise>; /** * Keyboard shortcuts for React Native apps, shared by the native and browser * implementations (`keyboard.native.tsx`, `keyboard.tsx`): the shortcut shape, * the registry every mounted `useKeyboardShortcuts` adds to, and which * registered shortcut a key press runs. * * The most recently registered matching shortcut wins, so a sheet or a screen * mounted over another can take a key without the one below it firing too. */ /** A named key or one printable character ("1", "f", "]", "-"). */ type ShortcutKey = 'space' | 'return' | 'escape' | 'tab' | 'delete' | 'up' | 'down' | 'left' | 'right' | (string & {}); /** `command` is the Command key on Apple keyboards and Control elsewhere. */ type ShortcutModifier = 'command' | 'shift' | 'option' | 'control'; interface KeyboardShortcut { key: ShortcutKey; modifiers?: readonly ShortcutModifier[]; /** * Also run while a text field is being typed in, as Escape or Command-Return * usually should. Off by default, so a plain letter or Space types normally. */ whileTyping?: boolean; /** Listed in the iPad keyboard-shortcut overlay (hold Command). */ title?: string; run: () => void; } interface KeyboardShortcutOptions { /** Default true. While false the shortcuts are off, as if unmounted. */ enabled?: boolean; /** * Shortcuts registered before this one (the screen below a dialog or * sheet) neither run nor show while this one is mounted and enabled, * even for keys this one does not use. */ modal?: boolean; } /** * Keyboard shortcuts while this component is mounted (and `enabled`). The * latest handlers always run. When two mounted components register the same * key, the one registered last wins, so a sheet over a screen can take it; * `modal` takes every key. Needs `DeepSpaceNativeProvider` above it. */ declare function useKeyboardShortcuts(shortcuts: readonly KeyboardShortcut[], { enabled, modal }?: KeyboardShortcutOptions): void; interface ContextMenuItem { id: string; title: string; /** An SF Symbol name, shown natively; ignored in the browser. */ icon?: string; destructive?: boolean; disabled?: boolean; } interface ContextMenuProps { items: readonly ContextMenuItem[]; /** * Default true. On touch screens, touch and hold usually drags or swipes, so * apps often pass `isPointerPrimary` to offer the menu only to a mouse or trackpad. */ enabled?: boolean; onSelect: (id: string) => void; style?: StyleProp; children?: ReactNode; } /** * The context menu in the browser: a menu at the pointer on secondary click, * closed by a choice, Escape, a click elsewhere, scrolling, or leaving the * window. Rendered into `document.body`, so no container clips it. While open * it takes the keyboard (a modal shortcut layer), so Escape closes it and the * screen's shortcuts wait, as they do under a native menu. */ declare function ContextMenu({ items, enabled, onSelect, style, children }: ContextMenuProps): ReactElement; /** * How the person is pointing, in the browser: a mouse or trackpad unless the * main pointer is coarse (a phone or tablet browser). */ /** True when the app is the iPad build running on a Mac. Never in a browser. */ declare const isIOSAppOnMac = false; /** * The main input is a mouse or trackpad: click and drag, and a secondary-click * menu in place of swipes and touch-and-hold. */ declare const isPointerPrimary: boolean; /** @deprecated Renamed `isPointerPrimary`; this alias goes in a later minor. */ declare const pointerInput: boolean; /** * `Alert.alert`, the same call on every platform: React Native's alert here, * an in-page dialog in the web build (`native/web-alert.tsx`). */ declare const Alert: { alert(title: string, message?: string, buttons?: AlertButton[]): void; }; export { type ActionResult, Alert, type Channel, type ChannelMember, type CollectionSchema, type ConnectionStatus, ContextMenu, type ContextMenuItem, type ContextMenuProps, DeepSpaceExpoClient, type DeepSpaceExpoClientOptions, DeepSpaceExpoError, type DeepSpaceExpoSession, type DeepSpaceExpoSessionListener, type DeepSpaceExpoUser, type DeepSpaceNative, DeepSpaceNativeProvider, type DeepSpaceNativeProviderProps, DeepSpaceSignInCancelledError, type ExpoAuthPaths, type ExpoAuthProvider, type ExpoAuthStorage, type FileSource, type FileSourceOptions, type GroupedReaction, type KeyboardShortcut, type KeyboardShortcutOptions, type Message, type NativeAuthState, type Query, type Reaction, type ReadReceipt, type RecordData, RecordProvider, type RecordProviderProps, RecordRoomNotReadyError, RecordScope, type RecordScopeProps, type RoomUser, type ShortcutKey, type ShortcutModifier, type User, type UserInfo, type UserProfile, WriteCancelledError, type WriteError, WriteUnconfirmedError, accountRoomId, buildExpoOAuthStartUrl, callAction, codeFromExpoRedirect, consentUrlOf, createDeepSpaceExpoClient, integration, isIOSAppOnMac, isPointerPrimary, normalizeExpoBaseUrl, openIntegrationConsent, pointerInput, stateFromExpoRedirect, toConnectionStatus, useAuth, useChannelMembers, useChannels, useDeepSpace, useFileSource, useKeyboardShortcuts, useMessages, useMutations, usePresence, useQuery, useReactions, useReadReceipts, useUser, useUserLookup, useUsers };