/** * @param {Object} options * @param {() => SurfaceManager | null | undefined} options.getSurfaceManager * @param {() => Editor | null | undefined} options.getActiveEditor * @param {() => SuperDocUI | null | undefined} options.getUi * @param {() => HyperlinkActivationHandler | LinkPopoverResolver | null | undefined} options.getActivationHandler * @param {() => 'hyperlinks.onActivate' | 'compatibility' | null | undefined} [options.getActivationHandlerSource] * @param {() => boolean} [options.getBuiltInEditorDisabled] * @param {() => boolean} [options.shouldInterceptNavigationOnlyHyperlinks] * @param {() => HTMLElement | null | undefined} options.getLayerElement * @param {(payload: { error: Error, source: string }) => void} options.emitException */ export function useLinkPopover({ getSurfaceManager, getActiveEditor, getUi, getActivationHandler, getActivationHandlerSource, getBuiltInEditorDisabled, shouldInterceptNavigationOnlyHyperlinks, getLayerElement, emitException, }: { getSurfaceManager: () => SurfaceManager | null | undefined; getActiveEditor: () => Editor | null | undefined; getUi: () => SuperDocUI | null | undefined; getActivationHandler: () => HyperlinkActivationHandler | LinkPopoverResolver | null | undefined; getActivationHandlerSource?: (() => "hyperlinks.onActivate" | "compatibility" | null | undefined) | undefined; getBuiltInEditorDisabled?: (() => boolean) | undefined; shouldInterceptNavigationOnlyHyperlinks?: (() => boolean) | undefined; getLayerElement: () => HTMLElement | null | undefined; emitException: (payload: { error: Error; source: string; }) => void; }): { handleLinkClick: (payload: LinkClickPayload | null | undefined) => void; closeCurrentPopover: (reason?: string) => void; destroy: () => void; }; export type PaintedSourceRef = { partUri?: string | undefined; xpathLikePath?: string | undefined; }; export type HyperlinkActivationContext = import('../core/types/index.js').HyperlinkActivationContext; export type HyperlinkActivationResult = import('../core/types/index.js').HyperlinkActivationResult; export type HyperlinkActivationHandler = import('../core/types/index.js').HyperlinkActivationHandler; export type LinkPopoverResolution = import('../core/types/index.js').LinkPopoverResolution; export type LinkPopoverResolver = import('../core/types/index.js').LinkPopoverResolver; export type HyperlinkActivationResolution = HyperlinkActivationResult | LinkPopoverResolution; export type SurfaceManager = import('../core/surface-manager.js').SurfaceManager; export type SurfaceHandle = import('../core/types/index.js').SurfaceHandle; export type SurfaceOutcome = import('../core/types/index.js').SurfaceOutcome; export type SurfaceRequest = import('../core/types/index.js').SurfaceRequest; export type DirectSurfaceRequest = import('../core/types/index.js').DirectSurfaceRequest; export type ExternalSurfaceRenderContext = import('../core/types/index.js').ExternalSurfaceRenderContext; export type DocumentMode = import('../core/types/index.js').DocumentMode; export type Editor = import('../core/types/index.js').Editor; export type SuperDocUI = import('../public/ui/types.js').SuperDocUI; export type HyperlinkTarget = import('../../../document-api/src/index.js').HyperlinkTarget; /** * A single-block text-selection target in the Document API `query.match` * result shape. */ export type TextSelectionTarget = { kind: "selection"; start: { kind: "text"; blockId: string; offset: number; }; end: { kind: "text"; blockId: string; offset: number; }; }; /** * Block-anchored address carried by a hyperlink record. */ export type HyperlinkAddressShape = { anchor?: { start?: { blockId?: string; offset?: number; }; end?: { blockId?: string; offset?: number; }; } | undefined; }; /** * The subset of a Document API hyperlink record this composable reads. The * records come from the async `doc.hyperlinks` facade, so every field is * optional here. */ export type HyperlinkRecord = { rId?: string | undefined; text?: string | undefined; externalTarget?: string | undefined; anchor?: string | undefined; targetKind?: string | undefined; hyperlinkNodeId?: string | undefined; address?: import('../../../document-api/src/index.js').HyperlinkTarget | HyperlinkAddressShape | undefined; }; /** * A block record as read from `doc.blocks.list()`. */ export type BlockRecord = { ordinal?: number | undefined; nodeId?: string | undefined; }; /** * Result of `doc.blocks.list()` as read here. */ export type BlocksListResult = { blocks?: BlockRecord[] | undefined; }; /** * One item of a `doc.query.match` result as read here. */ export type QueryMatchItem = { target?: TextSelectionTarget | undefined; }; /** * Result of `doc.query.match` as read here. */ export type QueryMatchResult = { items?: QueryMatchItem[] | undefined; }; /** * One story entry of a `doc.hyperlinks.list()` result. */ export type HyperlinkStory = { storyId?: string | undefined; partUri?: string | undefined; hyperlinks?: HyperlinkRecord[] | undefined; }; /** * Result of `doc.hyperlinks.list()` as read here. */ export type HyperlinksListResult = { stories?: HyperlinkStory[] | undefined; }; /** * Result of `doc.hyperlinks.get()` as read here. */ export type HyperlinkGetResult = { success?: boolean | undefined; hyperlink?: HyperlinkRecord | undefined; }; /** * Result of `doc.bookmarks.get()` as read here (Document API `BookmarkInfo`). * `range.from.blockId` identifies the model paragraph sent to the viewport. * `address.story` is populated for non-body bookmarks (omitted for body), * which keeps anchor navigation scoped to body targets. */ export type BookmarkGetResult = { address?: { story?: { kind?: string; storyType?: string; }; } | undefined; range?: { from?: { blockId?: string; }; } | undefined; }; /** * The narrow Document API subset this composable reads. The host facade * (`Editor['doc']`) carries no typed surface, so the facades and their results * are declared structurally here with every field optional; the call sites * keep their runtime narrowing (`Array.isArray`, `typeof`, `?.`) before use. * The async-capable facades may return promises, so their results are awaited * through `Promise.resolve(...)`. */ export type LinkPopoverDocumentApi = { blocks?: { list?: () => BlocksListResult | null | undefined; } | null | undefined; query?: { match?: (query: { select: { type: "text"; pattern: string; caseSensitive: boolean; }; require: "any"; }) => QueryMatchResult | null | undefined; } | null | undefined; hyperlinks?: { list?: () => HyperlinksListResult | Promise | null | undefined; get?: (input: { storyId: string; hyperlinkNodeId: string; }) => HyperlinkGetResult | Promise | null | undefined; } | null | undefined; bookmarks?: { get?: (input: { target: { kind: "entity"; entityType: "bookmark"; name: string; }; }) => BookmarkGetResult | Promise | null | undefined; } | null | undefined; }; /** * The `v2-link-click` payload the shell relays from the v2 host's * `onLinkClick` callback. */ export type LinkClickPayload = { href: string; target: string | null; rel: string | null; tooltip: string | null; element: HTMLAnchorElement; clientX: number; clientY: number; documentMode: DocumentMode; editableText?: boolean | undefined; }; /** * Hyperlink identity resolved from the Document API for a clicked anchor. */ export type ResolvedHyperlinkTarget = { storyId: string; hyperlinkNodeId: string | undefined; href: string; text: string | undefined; targetKind: string | undefined; address: HyperlinkTarget | undefined; textTarget: TextSelectionTarget | null; };