UNPKG

sanity

Version:

Sanity is a real-time content infrastructure with a scalable, hosted backend featuring a Graph Oriented Query Language (GROQ), asset pipelines and fast edge caches

1,375 lines 667 kB
import { CSSProperties, Component, ComponentProps, ComponentType, Dispatch, ElementType, ErrorInfo, FC, FocusEvent, FocusEventHandler, FormEvent, FormEventHandler, HTMLAttributes, HTMLProps, KeyboardEvent, MutableRefObject, PropsWithChildren, PureComponent, ReactElement, ReactNode, Ref, RefAttributes, RefObject, SVGProps, SetStateAction } from "react"; import { ArraySchemaType, AssetMetadataType, AssetSource, AssetSourceSpec, BlockDecoratorDefinition, BlockListDefinition, BlockStyleDefinition, BooleanSchemaType, ConditionalProperty, CrossDatasetReferenceSchemaType, CrossDatasetReferenceValue, CrossDatasetType, CurrentUser, DeprecatedProperty, DocumentSystem, FieldGroup, File as File$1, FileAsset, FileSchemaType, FileValue, FormNodeValidation, GeopointValue, GlobalDocumentReferenceType, I18nTextRecord, Image, ImageAsset, ImageSchemaType, ImageUrlFitMode, ImageValue, IndexTuple, InitialValueProperty, InitialValueResolverContext, IntrinsicTypeName, KeyedObject, KeyedSegment, MendozaEffectPair, NumberSchemaType, ObjectField, ObjectFieldType, ObjectSchemaType, PatchOperations, Path, PathSegment, PortableTextBlock, PortableTextObject, PortableTextTextBlock, PrepareViewOptions, PreviewValue, Reference, ReferenceSchemaType, ReferenceValue, Role, RuleClass, SanityDocument, SanityDocumentLike, Schema, SchemaType, SchemaTypeDefinition, SearchStrategy, SlugSchemaType, SlugValue, SortOrdering, StrictVersionLayeringOptions, StringSchemaType, TextSchemaType, TransactionLogEventWithEffects, TransactionLogEventWithMutations, UploadState, User, ValidationMarker } from "@sanity/types"; import { BifurClient } from "@sanity/bifur-client"; import { ClientConfig, ClientError, ClientPerspective, QueryParams, ReconnectEvent, ReleaseDocument, ReleaseDocument as ReleaseDocument$1, ReleaseType, RequestHandler, ResetEvent, SanityClient, SanityDocument as SanityDocument$1, SingleActionResult, SingleMutationResult, StackablePerspective, WelcomeBackEvent, WelcomeEvent } from "@sanity/client"; import { AvatarPosition, AvatarProps, AvatarSize, AvatarStatus, BadgeProps, BadgeTone, BoxProps, ButtonProps, ButtonTone, DialogProps, HotkeysProps, MenuButtonProps, MenuItem, Placement, PopoverProps, ResponsivePaddingProps, ResponsiveWidthProps, TextProps, ThemeColorSchemeKey, ToastParams, TooltipProps } from "@sanity/ui"; import { FlatNamespace, KeyPrefix, Namespace, TFunction, TFunction as TFunction$1, i18n as i18n$1 } from "i18next"; import { MonoTypeOperatorFunction, Observable, OperatorFunction, Subject } from "rxjs"; import { IntentLinkProps, IntentParameters, Router, RouterState, SearchParam } from "sanity/router"; import { ScrollToOptions } from "@tanstack/react-virtual"; import { DRAFTS_FOLDER, DraftId, PublishedId, VERSION_FOLDER, getDraftId, getPublishedId, getVersionFromId, getVersionId, isDraftId, isPublishedId, isVersionId } from "@sanity/client/csm"; import { Mutation } from "@sanity/mutator"; import { CanvasResource, FrameMessages, MediaResource, StudioResource, WindowMessages } from "@sanity/message-protocol"; import { DocumentHandle } from "@sanity/sdk"; import { DocumentDivergencesContextValue, DocumentLimitUpsellContextValue, FeedbackContext, FeedbackContextValue, FieldActionsContextValue, HoveredFieldContextValue, LocaleContextValue, SchedulesContext, SingleDocReleaseContextValue } from "sanity/_singletons"; import { FallbackNs } from "react-i18next"; import { BrowserHistory, HashHistory, History, MemoryHistory } from "history"; import { RootTheme, ThemeColorSchemeKey as ThemeColorSchemeKey$1 } from "@sanity/ui/theme"; import { EditorSelection, HotkeyOptions, InvalidValueResolution, OnCopyFn, OnPasteResultOrPromise, PasteData, Patch, PortableTextEditor, RangeDecoration, RangeDecorationOnMovedDetails, RenderBlockFunction } from "@portabletext/editor"; import { ColorHueKey, ColorTintKey, ColorTints } from "@sanity/color"; import { Subscriber } from "nano-pubsub"; import { ArrayDiff, BooleanDiff, Diff, ItemDiff, NullDiff, NumberDiff, ObjectDiff, StringDiff, StringSegmentChanged, StringSegmentUnchanged, TypeChangeDiff } from "@sanity/diff"; import { CSSProperties as CSSProperties$1, ExecutionProps } from "styled-components"; import { ThrottleSettings } from "lodash-es/throttle.js"; import { DEFAULT_ANNOTATIONS, DEFAULT_DECORATORS } from "@sanity/schema"; import { SanityImageSource } from "@sanity/asset-utils"; import { PortableTextMemberSchemaTypes } from "@portabletext/sanity-bridge"; import { ImageUrlBuilder, ImageUrlBuilder as ImageUrlBuilder$1 } from "@sanity/image-url"; import { MarkdownShortcutsPluginProps } from "@portabletext/plugin-markdown-shortcuts"; import { PasteLinkPluginProps } from "@portabletext/plugin-paste-link"; import { TypographyPluginProps } from "@portabletext/plugin-typography"; import { Node } from "@sanity/comlink"; interface CreateSanityMediaLibrarySourceProps { i18nKey?: string; icon?: React.ComponentType; libraryId: string | null; name?: string; } /** * Create a new image asset source for the Media Library * * @beta */ declare function createSanityMediaLibraryImageSource(props: CreateSanityMediaLibrarySourceProps): AssetSource; /** * Create a new file asset source for the Media Library * * @beta */ declare function createSanityMediaLibraryFileSource(props: CreateSanityMediaLibrarySourceProps): AssetSource; interface SharedProps$1 { children?: ReactNode; header: string; width: ResponsiveWidthProps['width']; } interface DialogProps$2 extends SharedProps$1 { type: 'dialog'; id?: string; autofocus?: boolean; onClose?: () => void; } interface PopoverProps$3 extends SharedProps$1 { type: 'popover'; legacy_referenceElement: HTMLElement | null; onClose: () => void; } /** * @beta * Creates a dialog or a popover for editing content. * Handles presence and virtual scrolling. */ declare function EditPortal(props: PopoverProps$3 | DialogProps$2): React.JSX.Element; interface SharedProps { children?: ReactNode; header: string; width: ResponsiveWidthProps['width']; } interface DialogProps$1 extends SharedProps { type: 'dialog'; id?: string; autofocus?: boolean; onClose?: () => void; } interface PopoverProps$2 extends SharedProps { type: 'popover'; id?: string; legacy_referenceElement: HTMLElement | null; onClose: () => void; } /** * @beta * Creates a dialog or a popover for editing content. * Handles presence and virtual scrolling. * * When multiple dialogs are open, only the top-most dialog is visible. * Non-top dialogs are hidden via CSS while preserving their state. */ declare function EnhancedObjectDialog(props: PopoverProps$2 | DialogProps$1): React.JSX.Element; /** * Returns whether the given version name (e.g. from `getVersionFromId`) is an * agent bundle name (starts with `agent-`). * * @internal */ declare function isAgentBundleName(versionName: unknown): boolean; /** * Display overrides for an agent bundle version chip. * * @internal */ type AgentVersionDisplay = { displayName: string; tone: BadgeTone; }; /** * Filters a list of version document IDs and provides display metadata for * agent bundles. * * - Other users' `agent-*` versions are removed from the returned list. * - The current user's `agent-*` versions are kept and can be resolved to * display overrides via `getVersionDisplay`. * - Non-agent versions pass through unchanged. * - While the SSE connection is loading, only the currently active agent * bundle (if any) is kept; all others are hidden until ownership is confirmed. * * Call this hook once and thread the results down — don't call it per-item. * * @internal */ declare function useAgentVersionDisplay(versionIds: string[], activeBundleId?: string): { /** Version IDs with other users' agent bundles removed. */filteredVersionIds: string[]; /** * Returns display overrides for a version document ID if it's the current * user's agent bundle, or `null` for all other versions. */ getVersionDisplay: (versionDocumentId: string) => AgentVersionDisplay | null; }; /** * Login methods that may be used for Studio authentication. * * @public */ type LoginMethod = 'dual' | 'cookie' | 'token'; /** * Login methods that acknowledge cookieless authentication tokens. * * @internal * @hidden */ type CookielessCompatibleLoginMethod = Extract<LoginMethod, 'dual' | 'token'>; /** * Authentication options * * @public */ interface AuthConfig { /** * Login method to use for the studio. Can be one of: * - `dual` (default) - attempt to use cookies where possible, falling back to * storing authentication token in `localStorage` otherwise * - `cookie` - explicitly disable `localStorage` method, relying only on cookies. May fail due * to cookies being treated as third-party cookies in some browsers, thus the default is `dual`. * - `token` - explicitly disable cookies, relying only on `localStorage` method */ loginMethod?: LoginMethod; /** * If true, the "Choose login provider" (eg "Google, "GitHub", "E-mail/password") screen * will be skipped if only a single provider is configured in the `providers` array - * instead it will redirect unauthenticated users straight to the authentication URL. */ redirectOnSingle?: boolean; /** * Array of authentication providers to use, or a function that takes an array of default * authentication providers (fetched from the Sanity API) and should return a new list of * providers. This can be used to selectively replace, add or remove providers from the * list of choices. * * @remarks If a static array of providers is provided it will replace the default * providers with the ones specified. * * If not set, the default providers will be used. */ providers?: AuthProvider[] | ((prev: AuthProvider[]) => AuthProvider[] | Promise<AuthProvider[]>); /** * The API hostname for requests. Should usually be left undefined, * but can be set if using custom cname for API domain. */ apiHost?: string; } /** * A provider of authentication. * * By default, a list of providers for a project will be fetched from the * {@link https://api.sanity.io/v1/auth/providers | Sanity API}, but you may choose to limit this * list by explicitly defining the providers you want to allow, or add additional custom providers * that conforms to the authentication provider specification outlined in * {@link https://www.sanity.io/docs/third-party-login | the documentation}. * * @public */ interface AuthProvider { /** * URL-friendly identifier/name for the provider, eg `github` */ name: string; /** * Human friendly title for the provider, eg `GitHub` */ title: string; /** * URL for the authentication endpoint that will trigger the authentication flow */ url: string; /** * URL for a logo to display next to the provider in the login screen */ logo?: string; } /** @internal */ interface WorkspacesProviderProps { config: Config; children: ReactNode; basePath?: string; LoadingComponent: ComponentType; /** * Project ID of the first workspace in the Studio config. Forwarded to * `CorsOriginErrorScreen` so it can surface the "Register Studio" option * when the failing project matches the studio's primary project. */ primaryProjectId?: string; } /** * Verdict from `/check/cors` for the current origin against the project's * CORS allowlist. Both fields come straight from the endpoint: * * - `allowed`: whether the origin is in the allowlist at all. * - `withCredentials`: whether the allowlist entry permits credentialed * requests (the studio always sends them). * * The studio needs both `true` to function. A `null` probe result means * the probe couldn't reach a conclusion (network down, endpoint down, no * projectId) — callers should treat that as "not CORS" and let the * original error flow to its normal branch. * * @internal */ type CorsCheckResult = { allowed: boolean; withCredentials: boolean; }; /** * Outcome of the `/check/cors` probe: * - a {@link CorsCheckResult} — the allow/credentials verdict * - `'project-not-found'` — the probe itself 404s because the project * doesn't exist (`errorCode: "SIO-404-PNF"`). This is the one place we * can detect a missing project: the CORS endpoint answers with CORS * headers (so the `fetch` resolves) but a 404 body, whereas the data * API requests fail as opaque network/CORS errors that can't tell us * why. * - `null` — inconclusive (network down, endpoint down, no projectId) * * @internal */ type CorsProbeOutcome = CorsCheckResult | 'project-not-found'; /** @internal */ declare function WorkspacesProvider({ config, children, basePath, LoadingComponent, primaryProjectId }: WorkspacesProviderProps): import("react").JSX.Element; /** * A diagnosed request failure — the reason a request the studio can't recover * from locally failed. Produced by {@link RequestFailureProbe} from an * otherwise-opaque error. * * - `project-not-found` / `dataset-not-found` — a configuration error: the * project or dataset the studio points at doesn't exist. Unrecoverable by * retry; the studio takes over the screen with guidance. * - `cors` — the origin isn't allowed (or can't send credentials) by the * project's CORS allowlist. The studio shows the CORS-origin screen. This * can begin at any time (e.g. an admin editing the allowlist in Manage), * not only at boot. * - `unknown` — couldn't be diagnosed (a transient network error, a timeout, * a CORS-allowed origin, …). The caller falls back to its normal handling * (e.g. the generic network-error dialog). * * @internal */ type RequestFailureResult = { type: 'project-not-found'; } | { type: 'dataset-not-found'; } | { type: 'cors'; allowed: boolean; withCredentials: boolean; } | { type: 'unknown'; }; /** * Diagnoses why a request failed. Pure classifier: it inspects the error (and, * for opaque network failures, probes `/check/cors`) and returns a * {@link RequestFailureResult}. It performs no side effects — the caller * decides what to do with the result (take over the screen, re-throw, …). * * Used in two places that must agree: the studio's request handler (for * requests that flow through it) and the auth store's `/users/me` probe (which * runs on a client with the request handler stripped, so it needs to diagnose * its own failures). * * @internal */ type RequestFailureProbe = (err: unknown) => Promise<RequestFailureResult>; /** * Options for delegating a request error to the studio's error UI. * * @beta */ interface RequestErrorReportOptions { /** * Whether the failed request is safe to re-run. Only set this to `true` * when re-running cannot cause harm: the request is idempotent (reads), * or it is guaranteed to have been rejected before processing (429s). * * When `true`, the studio's error dialog offers a "Try again" button * that re-runs the request. When `false` (the default), the dialog only * offers "Reload Studio" and uses more conservative copy, warning that * the last change may or may not have been applied. * * Note: retry requires a re-runnable request, so it only takes effect * with `attempt()` (which has the thunk to re-invoke). The `handle` * rejection-handler form cannot re-run the request. */ retryable?: boolean; } /** * An error claimed by the studio's request-error UI, as rendered by the * studio dialog. Mirrors the classification plus the caller's retry * assertion. * * @internal */ type RequestErrorClaim = { type: 'networkError'; error: Error; retryable: boolean; } | { type: 'serverError'; error: Error; retryable: boolean; } | { type: 'rateLimited'; error: Error; retryAfterSeconds?: number; retryable: boolean; } | { type: 'unauthorized'; error: Error; projectId?: string; }; /** * Call-site API for delegating unrecoverable request errors to the * studio's built-in error UI (dialogs, verified forced logout). * * The studio never intercepts your requests — errors only reach this * channel when you hand them over. Handle what you can locally (inline * errors, toasts, fallbacks); delegate what you can't. * * Two shapes, both feeding the same dialog: * * ```ts * const {attempt, handle} = useStudioErrorHandler() * * // 1. Thunk wrapper — the dialog's "Try again" re-invokes the thunk. * // The thunk may return a promise or a (single-shot) observable: * const user = await attempt(() => client.request({uri: '/users/me'}), { * retryable: true, * }) * * // 2. Promise rejection handler — fire-and-surface, no retry: * client.create(doc).catch(handle) * ``` * * Unclaimable errors (4xx other than 429, parse errors, ...) are * re-thrown unchanged, so downstream `.catch` / `catchError` handlers * still see them. 401s are claimed only when the API explicitly tags them * as session expiry (`SIO-401-AEX`), in which case a forced logout * follows; untagged resource-level 401s are re-thrown to the caller. * * @beta */ interface StudioErrorHandler { /** * Promise rejection handler — use directly in `.catch(handle)`. * Claimable errors surface the studio dialog and leave the promise * pending (the request is in limbo until the user reloads); * unclaimable errors are re-thrown to the next `.catch`: * * ```ts * client * .create(doc) * .catch(handle) * .catch((err) => { * // caller-domain errors (validation, permissions, 404, ...) * }) * ``` */ handle(err: unknown): Promise<never>; /** * Runs `thunk` and delegates claimable failures to the studio dialog. * With `retryable: true`, the dialog's "Try again" re-invokes the thunk; * the returned promise resolves with the first successful attempt. * Unclaimable failures reject the returned promise. * * The thunk receives the attempt number (starting at 1), useful for * logging or cache-busting. It may return a promise or an observable — * an observable is drained to its last emitted value (single-shot * request observables emit once and complete, so this is their value). * * The observable MUST be finite. `attempt` waits for completion, so a * long-lived or multicast source (`client.listen()`, a `Subject`) would * never resolve and would leak its subscription for the life of the * returned promise. An observable that completes without emitting * rejects with rxjs's `EmptyError`. Pass request-style observables only. * * Promises are eager, so the thunk must CREATE the request when called * — retry works by invoking it again for a fresh request: * * ```ts * // ✓ each invocation issues a new request * attempt(() => client.fetch(query), {retryable: true}) * * // ✗ the request already happened; retry re-awaits the same * // settled rejection and the dialog just reappears * const promise = client.fetch(query) * attempt(() => promise, {retryable: true}) * ``` */ attempt<T>(thunk: (attemptNumber: number) => PromiseLike<T> | Observable<T>, options?: RequestErrorReportOptions): Promise<T>; } /** * The full channel, as owned by `WorkspacesProvider`: the reporter * surface plus the state stream and retry trigger the dialog needs. * * @internal */ interface RequestErrorChannel extends StudioErrorHandler { /** Currently active claim (undefined → no dialog). */ claim$: Observable<RequestErrorClaim | undefined>; /** Re-runs all parked retryable requests and clears the claim. */ retry(): void; } type AuthProbeResult = { authenticated: false; } | { authenticated: true; id: string; }; /** * The interface used by the Studio that produces a `SanityClient` and * `CurrentUser` that gets passed to the resulting `Workspace`s and `Source`s. * * NOTE: This interface is primarily for internal use. Refer to * `createAuthStore` instead. * * @beta * @hidden */ interface AuthStore { /** * Emits `AuthState`s. This should update when the user's auth state changes. * E.g. After a login, a new `AuthState` could be emitted with a non-null * `currentUser` and `authenticated: true` * * NOTE: all auth store implementations should emit on subscribe using * something like shareReplay(1) to ensure all new subscribers get an * `AuthState` value on subscribe */ state: Observable<AuthState>; /** * Emits auth tokens, or `null` if not configured to use them or they do not exist */ token?: Observable<string | null>; /** * Custom auth stores are expected to implement a UI that initiates the user's * authentication. For the typical case in `createAuthStore`, this means * loading the providers and showing them as options to the user. */ LoginComponent?: ComponentType<LoginComponentProps>; /** * Custom auth stores can implement a function that runs when the user logs * out. The implementation is expected to remove all credentials both locally * and on the server. */ logout?: () => Promise<void>; /** * Custom auth stores can implement a function that is designated to run when * the Studio loads (e.g. to trade a session ID for a token in cookie-less * mode). Within the Studio, this is called within the `AuthBoundary`. */ handleCallbackUrl?: () => Promise<HandleCallbackResult>; } /** * The unit an `AuthStore` emits to determine the user's authentication state. * * @beta * @hidden */ interface AuthState { /** * Similar to a logged-in flag. This state is used in places like the * `AuthBoundary` to determine whether or not it should render the * `NotAuthenticatedComponent`. Implementers may choose to set this to `true` * while also also emitting a `currentUser` of `null` if a `null` user is * accepted (e.g. a project that doesn't require a login) */ authenticated: boolean; /** * The value of the user logged in or `null` if none is provided */ currentUser: CurrentUser | null; /** * A client that is expected to be pre-configured to allow for any downstream * requests in the Studio */ client: SanityClient; } /** * @beta * @hidden */ type LoginComponentProps = { projectId: string; /** @deprecated use redirectPath instead */ basePath: string; redirectPath?: string; /** * Invoked when the user chooses to switch workspaces from the login screen * (e.g. when the active workspace has no usable login providers). Only * provided when more than one workspace is available to switch to. */ onChooseAnotherWorkspace?: () => void; } | { projectId: string; redirectPath: string; /** @deprecated use redirectPath instead */ basePath?: string; /** * Invoked when the user chooses to switch workspaces from the login screen * (e.g. when the active workspace has no usable login providers). Only * provided when more than one workspace is available to switch to. */ onChooseAnotherWorkspace?: () => void; }; /** * Result returned from `handleCallbackUrl` describing what happened during * the auth callback flow. Used for telemetry, diagnostics, and error handling * in the AuthBoundary. * * @internal */ interface HandleCallbackResult { /** The login method configured for this auth store (e.g. `'cookie'` or `'token'`). */ loginMethod: LoginMethod; /** * Which auth flow was taken: * - `'already-authenticated'`: No sid in hash - user was already authenticated or not. * - `'exchange'`: sid was present, went through /auth/exchange + probe flow. */ flow: 'already-authenticated' | 'exchange'; /** Whether the auth flow completed successfully. */ success: boolean; /** Total wall-clock time for the callback handling, in milliseconds. */ durationMs: number; /** Time spent on the /auth/exchange call specifically. Only set for `'exchange'` flow. */ exchangeDurationMs?: number; /** Time spent on the /users/me probe calls. Only set for `'exchange'` flow. */ probeDurationMs?: number; /** Which auth method was selected by the probes. Only set when `success` is `true` and flow is `'exchange'`. */ authMethod?: 'cookie' | 'token'; /** Human-readable reason for failure. Only set when `success` is `false`. */ failureReason?: string; /** * Structured error for the AuthBoundary to render appropriate UI. * Only set when `success` is `false` and flow is `'exchange'`. * * - `'cookie-blocked'`: cookie-only mode and the cookie probe failed. * The browser likely has strict cookie policies. * - `'auth-failed'`: all probe methods failed. The user should retry. */ error?: { type: 'cookie-blocked' | 'auth-failed'; message: string; }; } /** @internal */ interface AuthStoreOptions extends AuthConfig { clientFactory?: (options: ClientConfig) => SanityClient; projectId: string; dataset: string; /** * Lazily resolves the channel for delegating unrecoverable boot-time * request errors (network / 5xx on the initial `/users/me` probe) to the * studio's error dialog instead of crashing the boot sequence. * * @internal */ getRequestErrorHandler?: () => StudioErrorHandler | undefined; /** * Lazily resolves diagnostics for the `/users/me` probe, so it can detect * and report the CORS / missing-project-or-dataset failures the studio * request handler would otherwise catch (the probe bypasses that handler). * Resolved lazily for the same reason as `getRequestErrorHandler`: it's * unhashable runtime wiring that must stay out of the auth-store memo key. * * @internal */ getRequestFailureDiagnostics?: () => RequestFailureDiagnostics | undefined; /** * Retrieves the session ID from the URL hash for the auth callback flow. * Called by `handleCallbackUrl` to obtain the session ID that is exchanged * for a token or cookie. * @internal */ getSessionId: () => string | undefined; /** * Extracts and consumes a `#token=…` fragment from the URL hash. * Called at init to pick up hash tokens and on `hashchange` events. * @internal */ consumeHashToken: () => string | undefined; } /** * Lets the auth store's `/users/me` probe diagnose and report the failures the * studio request handler would normally catch — but can't here, because the * probe runs on a client with that handler stripped (see {@link getCurrentUser}). * * - `diagnose` — the shared classifier. The client is passed per call rather * than bound up front, because the auth store builds its own clients * internally (so there's no single client to bind the probe to). * - `onRequestFailure` — reports a non-`unknown` result to the studio so it * can take over the screen (CORS / missing project or dataset). Must be * idempotent: the probe runs inside a retryable thunk, so a recurring * failure can report the same result more than once. * * @internal */ interface RequestFailureDiagnostics { diagnose: (err: unknown, client: SanityClient) => ReturnType<RequestFailureProbe>; onRequestFailure: (result: Exclude<RequestFailureResult, { type: 'unknown'; }>, client: SanityClient) => void; } /** * @internal */ declare function _createAuthStore({ clientFactory: clientFactoryOption, projectId, dataset, apiHost, loginMethod, getSessionId, consumeHashToken, getRequestErrorHandler, getRequestFailureDiagnostics, ...providerOptions }: AuthStoreOptions): AuthStore; /** * Public options for `createAuthStore`. The `getSessionId` and `consumeHashToken` * dependencies are wired automatically using the default implementations. * @internal */ type CreateAuthStoreOptions = Omit<AuthStoreOptions, 'getSessionId' | 'consumeHashToken'>; /** * @internal */ declare const createAuthStore: (options: CreateAuthStoreOptions) => AuthStore; /** @internal */ interface MockAuthStoreOptions { currentUser: CurrentUser | null; client: SanityClient; } /** * Creates a mock `AuthStore` (for testing) that emits an `AuthState` derived * from the `client` and `currentUser` given. * * @internal */ declare function createMockAuthStore({ client, currentUser }: MockAuthStoreOptions): AuthStore; /** @internal */ declare function getProviderTitle(provider?: string): string | undefined; /** * Duck-type check for whether or not this looks like an auth store * * @param maybeStore - Item to check if matches the AuthStore interface * @returns True if auth store, false otherwise * @internal */ declare function isAuthStore(maybeStore: unknown): maybeStore is AuthStore; /** * Check whether the provided login method is compatible with cookieless auth, e.g. whether any * authentication token found in localStorage should be acknowledged. * * @internal */ declare function isCookielessCompatibleLoginMethod(loginMethod: LoginMethod): loginMethod is CookielessCompatibleLoginMethod; /** @internal */ interface ConnectionStatusStore { connectionStatus$: Observable<ConnectionStatus>; } /** @internal */ type ConnectingStatus = { type: 'connecting'; }; /** @internal */ type ErrorStatus = { type: 'error'; error: Error; attemptNo: number; isOffline: boolean; retryAt: Date; }; /** @internal */ type RetryingStatus = { type: 'retrying'; }; /** @internal */ type ConnectedStatus = { type: 'connected'; lastHeartbeat: Date; }; /** @internal */ declare const CONNECTING: ConnectingStatus; /** @internal */ type ConnectionStatus = ConnectingStatus | ErrorStatus | ConnectedStatus | RetryingStatus; /** @internal */ declare const onRetry: () => void; /** @internal */ interface ConnectionStatusStoreOptions { bifur: BifurClient; } /** * This is the beginning of what should be the data store tracking connection status in the Sanity studio. * * @internal */ declare function createConnectionStatusStore({ bifur }: ConnectionStatusStoreOptions): ConnectionStatusStore; /** @internal */ type BetaBadgeProps = Omit<BadgeProps, 'mode' | 'tone'>; /** @internal */ declare function BetaBadge(props: BetaBadgeProps & Omit<HTMLProps<HTMLDivElement>, 'ref'>): import("react").JSX.Element; /** @internal */ type CommandListElementType = 'input' | 'list'; /** @internal */ type CommandListGetItemDisabledCallback = (virtualIndex: number) => boolean; /** @internal */ type CommandListGetItemKeyCallback = (virtualIndex: number) => number | string; /** @internal */ type CommandListGetItemSelectedCallback = (virtualIndex: number) => boolean; /** @internal */ type CommandListItemContext = { activeIndex: number | null; disabled?: boolean; selected?: boolean; virtualIndex: number; }; /** @internal */ type CommandListRenderItemCallback<T> = (item: T, context: CommandListItemContext) => ReactNode; /** @internal */ interface CommandListHandle { focusInputElement: () => void; focusListElement: () => void; getTopIndex: () => number; scrollToIndex: (index: number) => void; } /** @internal */ interface CommandListProps<T = any> extends ResponsivePaddingProps { /** The data attribute to apply to any active virtual list items */ activeItemDataAttr?: string; /** `aria-label` to apply to the virtual list container element */ ariaLabel: string; /** Whether `aria-multiselectable` is enabled on the virtual list container element */ ariaMultiselectable?: boolean; /** Automatically focus the input or virtual list */ autoFocus?: CommandListElementType; /** Whether the virtual list can receive focus */ canReceiveFocus?: boolean; /** Pixel offset of the virtual list focus ring. Negative values will cause the focus ring to appear inset */ focusRingOffset?: number; /** Force a fixed height for all virtual list children and skip measurement (faster). */ fixedHeight?: boolean; /** Custom function to map disabled items */ getItemDisabled?: CommandListGetItemDisabledCallback; /** Custom function to map virtual list items to custom keys */ getItemKey?: CommandListGetItemKeyCallback; /** Custom function to map selected items */ getItemSelected?: CommandListGetItemSelectedCallback; /** Scroll alignment of the initial active index */ initialScrollAlign?: ScrollToOptions['align']; /** Initial active index on mount */ initialIndex?: number; /** Input element to associate with this virtual list. Associated inputs will receive focus and handle key events */ inputElement?: HTMLInputElement | null; /** Estimated height for each list item */ itemHeight: number; /** Virtual list item values, accessible to all rendered item components */ items: T[]; /** Callback fired when the virtual list is within `onEndReachedIndexThreshold` of rendered content */ onEndReached?: () => void; /** Number of items from the end of the virtual list before which `onEndReached` is triggered */ onEndReachedIndexOffset?: number; /** Only show selection state when the virtual list is active (is hovered or has focus) */ onlyShowSelectionWhenActive?: boolean; /** Number of items to render above and below the visible area*/ overscan?: number; /** Rendered component in virtual lists */ renderItem: CommandListRenderItemCallback<T>; /** `data-testid` to apply to outermost container */ testId?: string; /** Allow wraparound keyboard navigation between first and last items */ wrapAround?: boolean; } /** * Renders a Command List with support for the following: * * - Keyboard navigation (↑ / ↓ / ENTER) to children with a specified container (`childContainerRef`) * - Focus redirection when clicking child elements * - Pointer blocking when navigating with arrow keys (to ensure that only one active state is visible at any given time) * - ARIA attributes to define a `combobox` input that controls a separate `listbox` * * @internal */ declare const CommandList: import("react").NamedExoticComponent<CommandListProps<any> & import("react").RefAttributes<CommandListHandle>>; type BaseButtonProps = Pick<ButtonProps<ElementType>, 'as' | 'icon' | 'iconRight' | 'justify' | 'loading' | 'mode' | 'paddingY' | 'paddingLeft' | 'selected' | 'tone' | 'type' | 'width'> & { size?: 'default' | 'large'; radius?: 'full'; }; type ButtonWithText = { text: string; tooltipProps?: TooltipProps$1 | null; icon?: ButtonProps['icon']; }; type IconButton = { text?: undefined; icon?: ButtonProps['icon']; /** * When using a button with an icon, tooltipProps are required to enforce consistency in UI. */ tooltipProps: TooltipProps$1 | null; }; /** @internal */ type ButtonProps$1 = BaseButtonProps & (ButtonWithText | IconButton); /** * Customized Sanity UI <Button> with pre-defined layout options. * * @internal */ declare const Button: import("react").ForwardRefExoticComponent<(Omit<Pick<ButtonProps<ElementType>, "as" | "icon" | "iconRight" | "justify" | "loading" | "mode" | "paddingLeft" | "paddingY" | "selected" | "tone" | "type" | "width"> & { size?: 'default' | 'large'; radius?: 'full'; } & ButtonWithText & Omit<HTMLProps<HTMLButtonElement>, "as" | "size" | "title">, "ref"> | Omit<Pick<ButtonProps<ElementType>, "as" | "icon" | "iconRight" | "justify" | "loading" | "mode" | "paddingLeft" | "paddingY" | "selected" | "tone" | "type" | "width"> & { size?: 'default' | 'large'; radius?: 'full'; } & IconButton & Omit<HTMLProps<HTMLButtonElement>, "as" | "size" | "title">, "ref">) & import("react").RefAttributes<HTMLButtonElement>>; /** @internal */ type MenuButtonProps$1 = Omit<MenuButtonProps, 'popover'> & { popover?: Omit<PopoverProps, 'animate' | 'content' | 'open'>; }; /** @internal */ type TooltipProps$1 = Omit<TooltipProps, 'arrow' | 'padding' | 'shadow'> & { hotkeys?: HotkeysProps['keys']; }; /** @internal */ type PopoverProps$1 = PopoverProps; type ContextMenuButtonProps = Pick<ButtonProps$1, 'mode' | 'selected' | 'size' | 'tone' | 'tooltipProps' | 'loading'>; /** * Simple context menu button (with horizontal ellipsis icon) with shared localization. * * @internal */ declare const ContextMenuButton: import("react").ForwardRefExoticComponent<ContextMenuButtonProps & Pick<HTMLProps<HTMLButtonElement>, "disabled" | "hidden" | "onClick"> & import("react").RefAttributes<HTMLButtonElement>>; /** * Indicates the type of document variant, either `draft`, `version` or `published`. * Draft documents are prefixed with `drafts.`. * Version documents are prefixed with `versions.<versionName>` * The rest are considered published documents. * @public */ type DocumentVariantType = 'draft' | 'version' | 'published'; /** * Takes a document id and returns the variant type for that document * If it's a document that starts with `version.` it's a `version` document. * If it's a document that starts with `drafts.` it's a `draft` document. * Otherwise, it's a `published` document. * @public * */ declare function getDocumentVariantType(documentId: string): DocumentVariantType; /** * @internal */ declare const Chip: import("react").ForwardRefExoticComponent<Omit<Omit<any, "as" | keyof import("@sanity/ui").ButtonOwnProps> & import("@sanity/ui").ButtonOwnProps & { as?: import("@sanity/ui").ElementType<any> | undefined; }, "ref"> & import("react").RefAttributes<unknown>>; /** * * Checks if the document ID `documentId` has the same ID as `equalsDocumentId`, * ignoring the draft prefix. * * @public * * @param documentId - The document ID to check * @param equalsDocumentId - The document ID to check against * * @example * Draft vs published document ID, but representing the same document: * ``` * // Prints "true": * console.log(documentIdEquals('drafts.agot', 'agot')); * ``` * @example * Different documents: * ``` * // Prints "false": * console.log(documentIdEquals('hp-tcos', 'hp-hbp')); * ``` * * @returns `true` if the document IDs are equal, `false` otherwise */ declare function documentIdEquals(documentId: string, equalsDocumentId: string): boolean; /** @internal */ declare function isDraft(document: SanityDocumentLike): boolean; /** * TODO: Improve return type based on presence of `version` option. * * @internal */ declare function getIdPair(id: string, { version }?: { version?: string; }): { draftId: DraftId; publishedId: PublishedId; versionId?: string; }; /** * System bundles are sets of documents owned by the system. * * - Draft documents contain data that has not yet been published. These documents all exist in the "drafts" path. * - Published documents contain data that has been published. These documents all exist in the root path. * * These differ to user bundles, which are created when a user establishes a custom set of documents * (e.g. by creating a release). * * @public */ declare const systemBundles: readonly ['drafts', 'published']; /** * System bundles are sets of documents owned by the system. * * - Draft documents contain data that has not yet been published. These documents all exist in the "drafts" path. * - Published documents contain data that has been published. These documents all exist in the root path. * * These differ to user bundles, which are created when a user establishes a custom set of documents * (e.g. by creating a release). * * @public */ type SystemBundle = 'drafts' | 'published'; /** @internal */ declare function isSystemBundle(maybeSystemBundle: unknown): maybeSystemBundle is SystemBundle; /** @internal */ type SystemBundleName = 'draft' | 'published'; /** * `isSystemBundle` should be preferred, but some parts of the codebase currently use the singular * "draft" name instead of the plural "drafts". * * @internal */ declare function isSystemBundleName(maybeSystemBundleName: unknown): maybeSystemBundleName is SystemBundleName; /** * @internal * Given a perspective stack and a document id, returns true if the document id matches any of the provided perspectives * e.g. `idMatchesPerspective('['summer'], 'versions.summer.foo') === true` * e.g. `idMatchesPerspective('['drafts', 'summer'], 'versions.summer.foo') === true` * e.g. `idMatchesPerspective('['drafts'], 'versions.summer.foo') === false` * e.g. `idMatchesPerspective('['drafts', 'summer'], 'versions.winter.foo') === false` * * Note: a published id will match any perspective * e.g. `idMatchesPerspective('['drafts', 'summer'], 'foo') === true` */ declare function idMatchesPerspective(perspectiveStack: StackablePerspective[], documentId: string): boolean; /** @internal */ declare function createDraftFrom(document: SanityDocument): SanityDocument; /** @internal */ declare function newDraftFrom(document: SanityDocument): SanityDocument; /** @internal */ declare function createPublishedFrom(document: SanityDocument): SanityDocument; /** * Takes a list of documents and collates draft/published pairs into single entries * `{id: <published id>, draft?: <draft document>, published?: <published document>}` * * Note: because Map is ordered by insertion key the resulting array will be ordered by whichever * version appeared first * * @internal */ interface CollatedHit<T extends { _id: string; } = { _id: string; }> { id: string; type: string; draft?: T; published?: T; versions: T[]; } /** @internal */ declare function collate<T extends { _id: string; _type: string; }>(documents: T[]): CollatedHit<T>[]; /** @internal */ declare function removeDupes(documents: SanityDocumentLike[]): SanityDocumentLike[]; declare const VARIANT_DOCUMENT_TYPE: 'system.variant'; declare const VARIANT_DOCUMENTS_PATH: '_.variants'; interface SystemVariant extends SanityDocument { _type: typeof VARIANT_DOCUMENT_TYPE; _id: `${typeof VARIANT_DOCUMENTS_PATH}.${string}`; name?: string; conditions: Record<string, string>; priority: number; metadata?: { title?: string; description?: PortableTextBlock[]; [key: string]: unknown; }; } /** * @beta */ type ReleaseId = string; /** * A value representing a perspective, including the data describing it. This is either the name of a * system bundle, or a document describing a release. * * @public */ type TargetPerspective = ReleaseDocument | SystemBundle | string; /** * @beta * @deprecated Use `TargetPerspective` instead. */ type SelectedPerspective = TargetPerspective; /** * @beta */ type PerspectiveStack = ExtractArray<ClientPerspective>; type PerspectiveBundle = 'published' | 'drafts' | (string & {}); /** * @beta */ interface PerspectiveContextValue { selectedPerspectiveName: 'published' | ReleaseId | undefined; /** * The releaseId as `r<string>`; it will be undefined if the selected perspective is `published` or `drafts` */ selectedReleaseId: ReleaseId | undefined; selectedPerspective: TargetPerspective; /** * The stacked array of perspectives ids ordered chronologically to represent the state of documents at the given point in time. * It can be used as the perspective param in the client to get the correct view of the documents. * @returns ["published"] | ["drafts"] | ["releaseId2", "releaseId1", "drafts"] */ perspectiveStack: PerspectiveStack; excludedPerspectives: string[]; /** * Resolved variant definition; undefined = default (all users) or still loading * @beta * @internal */ selectedVariant: SystemVariant | undefined; /** * The selected bundle, either `published`, `drafts` or a release id or the bundle id for anonymous bundles like agent documents. */ bundle: PerspectiveBundle; } /** * @internal */ type ExtractArray<Union> = Union extends unknown[] ? Union : never; /** * @internal */ type ReleasesNavMenuItemPropsGetter = (content: { perspective: TargetPerspective; }) => Partial<ComponentProps<typeof MenuItem>>; /** * @internal */ declare const VersionChip: import("react").MemoExoticComponent<(props: { disabled?: boolean; selected: boolean; tooltipContent?: ReactNode; onClick: () => void; text: string; contextMenuPortal?: boolean; tone: BadgeTone; locked?: boolean; onCopyToDraftsNavigate: () => void; contextValues: { documentId: string; documentType: string; releases: ReleaseDocument[]; releasesLoading: boolean; bundleId: string; isVersion: boolean; disabled?: boolean; isGoingToUnpublish?: boolean; release?: ReleaseDocument; }; }) => import("react").JSX.Element>; /** @internal */ type ReleaseAvatarIconProps = { release: TargetPerspective; tone?: never; releaseType?: never; } | { releaseType: ReleaseType; tone?: never; release?: never; } | { /** * @deprecated - Prefer `release` or `releaseType`. */ tone: BadgeTone; release?: never; releaseType?: never; }; declare const ReleaseAvatarIcon: ({ tone, release, releaseType }: ReleaseAvatarIconProps) => import("react").JSX.Element; declare function ReleaseAvatar({ fontSize, padding, ...iconProps }: ReleaseAvatarIconProps & { fontSize?: number; padding?: number; }): React.JSX.Element; /** @internal */ interface ReleaseTitleDetails { displayTitle: string; fullTitle: string; isTruncated: boolean; } /** @internal */ interface ReleaseTitleProps { title: string | undefined; fallback: string; enableTooltip?: boolean; tooltipMaxWidth?: string; children?: (details: ReleaseTitleDetails) => ReactElement; textProps?: Omit<TextProps, 'children'> & { style?: CSSProperties; }; } /** @internal */ declare function ReleaseTitle(props: ReleaseTitleProps): ReactNode; /** * @internal */ declare const VersionInlineBadge: ({ children, $tone }: PropsWithChildren<{ $tone?: BadgeTone; }>) => import("react").JSX.Element; /** * @internal */ declare const getVersionInlineBadge: (release?: TargetPerspective) => FC<{ children?: import("react").ReactNode | undefined; }>; interface ObserveDocumentAPIConfig { dataset?: string; projectId?: string; apiVersion?: string; } type DocumentIdSetObserverState = { status: 'reconnecting' | 'connected'; documentIds: string[]; }; /** @internal */ type Id = string; /** * @hidden * @beta */ type Previewable = ({ _id: string; } | { _type: string; } | { _system?: { delete: boolean; }; } | { _ref: string; _dataset?: string; _projectId?: string; }) & { /** * optional object used to attach meta data to the prepared result. * currently used to add a flag for the invalid preview error fallback and * insufficient permissions fallback * @internal */ _internalMeta?: { type?: string; }; }; /** * TODO: unify with content path from `@sanity/types` * * * @hidden * @beta */ type PreviewPath = FieldName[]; /** @internal */ type Selection = [id: Id, fields: FieldName[]]; /** * @hidden * @beta */ type FieldName = string; /** @internal */ interface AvailabilityResponse { omitted: { id: string; reason: 'existence' | 'permission'; }[]; } /** @internal */ type AvailabilityReason = 'READABLE' | 'PERMISSION_DENIED' | 'NOT_FOUND'; /** * @hidden * @beta */ type PreviewableType = SchemaType | CrossDatasetType | GlobalDocumentReferenceType; /** * @hidden * @beta */ interface ApiConfig { projectId: string; dataset: string; } /** * @hidden * @beta */ type DocumentAvailability = { available: true; reason: 'READABLE'; } | { available: false; reason: 'PERMISSION_DENIED' | 'NOT_FOUND' | 'VERSION_DELETED'; }; /** * @hidden * @beta */ interface DraftsModelDocumentAvailability { /** * document readability for the published document */ published: DocumentAvailability; /** * document readability for the draft document */ draft: DocumentAvailability; /** * document readability for the version document */ version?: DocumentAvailability; } /** * @hidden * @beta */ interface DocumentStackAvailability { /** * Document id */ id: string; /** * Availability for the document in this stack */ availability: DocumentAvailability; } /** * @hidden * @beta */ interface DraftsModelDocument<T extends SanityDocumentLike = SanityDocumentLike> { id: string; type: string | null; draft: { availability: DocumentAvailability; snapshot: T | undefined; }; published: { availability: DocumentAvailability; snapshot: T | undefined; }; version?: { availability: DocumentAvailability; snapshot: T | undefined; }; } /** * Event emitted to notify preview subscribers when they need to refetch a document being previewed * - 'connected' will happen when the store is connected to the invalidation channel, both initially and after a reconnect after a connection loss * - 'mutation' will happen when a document has been mutated and the store needs to refetch a document * @hidden * @beta */ type InvalidationChannelEvent = { type: 'connected'; } | { type: 'mutation'; documentId: string; visibility: string; }; /** * @hidden * @beta */ interface PreparedSnapshot { type?: PreviewableType; snapshot: PreviewValue | null | undefined; } /** @internal */ type ObserveDocumentTypeFromIdFn = (id: string, apiConfig?: ApiConfig, perspective?: StackablePerspective[]) => Observable<string | undefined>; /** * @hidden * @beta */ interface ObservePathsFn { (value: Previewable, paths: (string | PreviewPath)[], apiConfig?: ApiConfig, perspective?: StackablePerspective[]): Observable<PreviewValue | SanityDocumentLike | Reference | string | null>; } /** * @hidden * @beta */ interface ObserveDocumentAvailabilityFn { (id: string, options?: { version?: string; }): Observable<{ draft: DocumentAvailability; published: DocumentAvailability; version?: DocumentAvailability; }>; } /** * @hidden * @beta */ type ObserveForPreviewFn = (value: Previewable, type: PreviewableType, options?: { viewOptions?: PrepareViewOptions; perspective?: StackablePerspective[]; apiConfig?: ApiConfig; }) => Observable<PreparedSnapshot>; /** * The document preview store supports subscribing to content for previewing purposes. * Documents observed by this store will be kept in sync and receive real-time updates from all collaborators, * but has no support for optimistic updates, so a