/** * `` — the neutral suggestion list for trigger sessions, * mirroring the toolbar's override pattern: items are data * ({@link TriggerItem}), row rendering is replaceable via `renderItem` * (a plugin's `trigger.renderItem`). * * Anchored by the caret rect from `bindselection`, placed above the caret by * default and flipped below when there's no room, and clamped so it never * extends under the keyboard — see `position.ts` for the math. The keyboard * inset comes from `@sigx/lynx-keyboard`'s `useKeyboard()`, which requires a * `` ancestor; without one it reads 0 and the keyboard * clamp is effectively disabled. The **viewport** frame of the relative * container it's positioned in arrives via `containerFrame` (the editor * measures it with `useViewportRect`). * * Because that frame is only valid until the container moves, the popup owns * the re-measure trigger for the one thing it can see and the editor can't: * the keyboard. It calls `onMeasureRequest` on mount, on every keyboard-height * change, and once more after the lift animation settles (#755 — a composer * inside a sheet riding `liftSV` is still translating when the height lands). * * Ships with `ignore-focus` on the root: tapping a suggestion must never * blur the editor (same iOS `endEditing:` rule the toolbar handles). */ import { type Define, type ElementLayout, type JSXElement } from '@sigx/lynx'; import type { TriggerItem } from '../plugin.js'; import { type CaretRect } from './position.js'; export type SuggestionRenderItem = (item: TriggerItem, active: boolean) => JSXElement; /** * Consumer-facing styling for the trigger suggestion popup — the bundle * `MarkdownEditor` forwards via its `suggestionPopup` prop. Every field is * optional; omitted ones keep the neutral, theme-agnostic defaults. A themed * host (e.g. daisyUI's `useMarkdownEditorTheme().suggestionPopup`) fills these * with concrete theme colors. Generic to *any* trigger — carries no * plugin-specific knowledge. */ export interface SuggestionPopupStyle { /** Extra root class on the popup container (layout tweaks). */ class?: string; /** Popup width in dp (clamped to the editor width). Default 240. */ width?: number; /** Max popup height in dp before it scrolls. Default 220. */ maxHeight?: number; /** * Pin which side of the caret the popup opens on. Default `'auto'` picks * the side with room, which depends on a live measurement of the editor's * position; a host that already knows its layout (a composer pinned to the * bottom of the screen is always `'above'`) can skip that dependency. */ placement?: 'auto' | 'above' | 'below'; /** Surface (background) color. Default neutral light gray. */ surfaceColor?: string; /** Border color. Default neutral translucent gray. */ borderColor?: string; /** Active-row background (built-in `renderItem`). Default neutral gray. */ activeColor?: string; /** Text color for the built-in `renderItem`. Default inherits. */ textColor?: string; } export type SuggestionPopupProps = Define.Prop<'items', TriggerItem[], false> & Define.Prop<'caretRect', CaretRect | null, false> /** Viewport frame of the relative container (from `useViewportRect`). */ & Define.Prop<'containerFrame', ElementLayout | null, false> /** * Ask the owner to re-measure `containerFrame`. Called on mount and * whenever the keyboard changes — the popup is the only side that sees * the keyboard, and a keyboard change is exactly when a composer moves. */ & Define.Prop<'onMeasureRequest', () => void, false> & Define.Prop<'renderItem', SuggestionRenderItem, false> & Define.Prop<'onSelect', (item: TriggerItem) => void, false> /** Highlighted row index (e.g. hardware-keyboard navigation); `-1`/omitted = none. */ & Define.Prop<'activeIndex', number, false> & Define.Prop<'maxHeight', number, false> & Define.Prop<'width', number, false> /** Pin the side of the caret; `'auto'` (default) measures for room. */ & Define.Prop<'placement', 'auto' | 'above' | 'below', false> & Define.Prop<'class', string, false> /** * Popup surface (background) color. Defaults to a neutral light gray so the * renderer stands alone on any platform; a themed host (e.g. daisyUI) passes * a concrete theme color so the popup matches a dark/light app. */ & Define.Prop<'surfaceColor', string, false> /** Popup border color. Defaults to a neutral translucent gray. */ & Define.Prop<'borderColor', string, false> /** Active-row background, used by the built-in `renderItem`. Neutral default. */ & Define.Prop<'activeColor', string, false> /** * Text color for the built-in `renderItem` (a custom `renderItem` owns its * own colors). Unset → inherit; a themed host passes the theme's text color * so the default row stays readable on a dark surface. */ & Define.Prop<'textColor', string, false>; /** * Synthesize a popup style from the editor's resolved `textColor`, so a host * that themes the editor body (`textColor`) but leaves the popup colors unset * still gets a popup that doesn't clash — the white-on-dark trap. Used by * `MarkdownEditor` as a *per-field fallback*: it spreads this before any * explicit `suggestionPopup`, so each color the host sets (e.g. daisyUI's * `useMarkdownEditorTheme().suggestionPopup`) wins, while colors it omits — * including when it passes only layout fields (`width`/`maxHeight`/`class`) — * fall back to this tint rather than the neutral light default. * * • `surfaceColor` flips to a dark neutral when the text is light (every daisy * theme's `base-100` is the near-inverse luminance of `base-content`), else * keeps the light neutral. * • `borderColor`/`activeColor` are the text color at low alpha, so they read * on either surface. * * Returns `{}` for an absent or non-hex color (named/`rgb()`), leaving the * neutral standalone defaults untouched — no regression for unthemed use. */ export declare function derivePopupStyleFromText(textColor?: string): SuggestionPopupStyle; export declare const SuggestionPopup: import("@sigx/runtime-core").ComponentFactory;