wiki-formant 0.4.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,216 @@
1
+ import { type CSSProperties, type KeyboardEvent, type ReactNode } from 'react';
2
+ export interface SidebarOptions {
3
+ /**
4
+ * Where the reader's choice is remembered, in `localStorage`. Give each wiki
5
+ * its own key; they are different sites and may deserve different answers.
6
+ */
7
+ storageKey?: string;
8
+ /** Below this width (px) the rail defaults to closed. */
9
+ breakpoint?: number;
10
+ }
11
+ export interface SidebarState {
12
+ open: boolean;
13
+ setOpen: (open: boolean) => void;
14
+ toggle: () => void;
15
+ /** True below `breakpoint`. Drives "close the rail after following a link". */
16
+ isMobile: boolean;
17
+ /**
18
+ * False until the effect has read storage and matchMedia. Render the rail
19
+ * without its open/closed transition while this is false, so a remembered
20
+ * closed rail does not animate shut on every load.
21
+ */
22
+ ready: boolean;
23
+ }
24
+ /**
25
+ * Collapse state for a wiki rail: remembered across loads, defaulted from the
26
+ * viewport only when the reader has never chosen.
27
+ *
28
+ * The reader's choice outranks the breakpoint. Someone who collapses the rail
29
+ * on a laptop and then narrows the window has still collapsed the rail.
30
+ */
31
+ export declare function useCollapsibleSidebar(options?: SidebarOptions): SidebarState;
32
+ export interface SidebarProviderProps extends SidebarOptions {
33
+ children: ReactNode;
34
+ }
35
+ /** Wrap the layout that contains both the rail and its toggle. */
36
+ export declare function SidebarProvider({ children, ...options }: SidebarProviderProps): import("react").FunctionComponentElement<import("react").ProviderProps<SidebarState | null>>;
37
+ /**
38
+ * The rail's collapse state. Throws outside a provider rather than silently
39
+ * handing back a second, unconnected copy — two sources of truth for one rail
40
+ * is the failure this context exists to prevent.
41
+ */
42
+ export declare function useSidebar(): SidebarState;
43
+ export interface TocHeading {
44
+ id: string;
45
+ text: string;
46
+ /** 1 for `<h1>`, 2 for `<h2>`, and so on. */
47
+ level: number;
48
+ }
49
+ export interface TocClassNames {
50
+ root?: string;
51
+ button?: string;
52
+ label?: string;
53
+ list?: string;
54
+ item?: string;
55
+ /** Applied alongside `item` on the section currently under the viewport top. */
56
+ itemActive?: string;
57
+ }
58
+ export interface TableOfContentsProps {
59
+ /**
60
+ * The element holding the article. A wiki whose content streams in as blocks
61
+ * has to read the DOM, which is why a selector is the default route — by the
62
+ * time the rail mounts, the article may not be there yet.
63
+ *
64
+ * Optional when `headings` is given: a wiki that renders its article on the
65
+ * server already knows them and should not pay for a MutationObserver to
66
+ * rediscover what it just wrote.
67
+ */
68
+ containerSelector?: string;
69
+ /**
70
+ * Headings known ahead of time. Supplying these skips the DOM scan entirely;
71
+ * scroll-spy still runs, since that needs the rendered elements either way.
72
+ */
73
+ headings?: TocHeading[];
74
+ /** Re-query and re-observe when this changes. Pass the pathname. */
75
+ resetKey?: string;
76
+ classNames?: TocClassNames;
77
+ /** Rendered before the label. Receives the current expanded state. */
78
+ icon?: (expanded: boolean) => ReactNode;
79
+ label?: string;
80
+ /**
81
+ * Mint an id for a heading that lacks one. Wikis that inject ids server-side
82
+ * (see `wiki-formant/headings`) should leave this off — an id is a URL, and
83
+ * one minted in the browser is not the one the server published.
84
+ */
85
+ slug?: (text: string) => string;
86
+ /**
87
+ * CSS custom property holding the sticky header's height, used to place the
88
+ * scroll-spy line. e.g. `'--header-height'`.
89
+ */
90
+ offsetVar?: string;
91
+ /** Fallback offset in px when `offsetVar` is unset or unparseable. */
92
+ offsetFallback?: number;
93
+ /** Indent per heading level, in rem. Set 0 for a flat list. */
94
+ indentRem?: number;
95
+ /** Rendered instead of nothing when the article has no headings. */
96
+ empty?: ReactNode;
97
+ }
98
+ /**
99
+ * An "on this page" rail with scroll-spy, read from the rendered article.
100
+ *
101
+ * Both wikis this came from had written the same thing: a debounced
102
+ * MutationObserver to survive content arriving late, an IntersectionObserver to
103
+ * track the section under the viewport top, and the same reference-stability
104
+ * trick so an unrelated DOM mutation (a price ticker, an RSS feed) does not tear
105
+ * the observer down.
106
+ */
107
+ export declare function TableOfContents({ containerSelector, headings: providedHeadings, resetKey, classNames, icon, label, slug, offsetVar, offsetFallback, indentRem, empty, }: TableOfContentsProps): import("react").JSX.Element;
108
+ /**
109
+ * Calls `onClose` on any mousedown outside the returned ref's element.
110
+ *
111
+ * The `offsetParent` check is the whole reason this is shared. All three wikis
112
+ * had written this hook; only one had that line, and without it a container
113
+ * that is `display: none` at the current breakpoint still claims outside-clicks
114
+ * — so a tap anywhere on a phone dismisses a popover the reader is looking at,
115
+ * because the hidden desktop copy of it answered first.
116
+ *
117
+ * `onClose` is an effect dependency: wrap it in `useCallback` or hoist it, or
118
+ * the listener is torn down and rebuilt on every render.
119
+ */
120
+ export declare function useClickOutside<T extends HTMLElement>(onClose: () => void): import("react").RefObject<T | null>;
121
+ export interface TypeaheadOptions<T> {
122
+ /**
123
+ * Runs the search. MUST be referentially stable — a module function, a server
124
+ * action, or something wrapped in `useCallback` — because it is an effect
125
+ * dependency and an inline arrow re-runs the search on every render.
126
+ *
127
+ * The `signal` is optional to implement: a REST fetcher should forward it, a
128
+ * server action simply declares one parameter and ignores it.
129
+ */
130
+ fetch: (query: string, signal: AbortSignal) => Promise<T[]>;
131
+ onPick: (item: T) => void;
132
+ /** Runs after Escape has cleared the list, for surface-level dismissal. */
133
+ onEscape?: () => void;
134
+ /** Debounce, in ms. */
135
+ delay?: number;
136
+ /** Queries shorter than this never reach `fetch`. */
137
+ minLength?: number;
138
+ /** Remember results per query for this mount. */
139
+ cache?: boolean;
140
+ }
141
+ export interface TypeaheadState<T> {
142
+ query: string;
143
+ setQuery: (query: string) => void;
144
+ debouncedQuery: string;
145
+ items: T[];
146
+ highlight: number;
147
+ setHighlight: (index: number) => void;
148
+ isSearching: boolean;
149
+ onKeyDown: (e: KeyboardEvent<HTMLInputElement>) => void;
150
+ /** Drop the results, keep the query. For an outside click. */
151
+ clearItems: () => void;
152
+ /** Drop everything. For a pick, or a closing popover. */
153
+ reset: () => void;
154
+ }
155
+ /**
156
+ * The debounced-search state machine behind every typeahead on these sites.
157
+ *
158
+ * Five surfaces across the three wikis had three implementations, and no two
159
+ * agreed on what a search field does. This is their union, because each had a
160
+ * piece the others were missing:
161
+ *
162
+ * - a **request-id guard**, so a slow response cannot paint over a newer one.
163
+ * Two of the five surfaces had no guard at all: type fast enough and the
164
+ * results you are reading are for a query you have already replaced.
165
+ * - **keyboard navigation**. Two surfaces had none — the header dropdown of a
166
+ * wiki whose search is its primary navigation was mouse-only.
167
+ * - **abort and a per-query cache**, which only the third had. The cache is
168
+ * per mount, not module-level, so a stale result cannot outlive the page.
169
+ *
170
+ * The debounce is inlined rather than pulled from a package: this one is nine
171
+ * lines, and the point of the package is to have no runtime dependencies.
172
+ */
173
+ export declare function useTypeahead<T>({ fetch, onPick, onEscape, delay, minLength, cache, }: TypeaheadOptions<T>): TypeaheadState<T>;
174
+ export interface LinkPreviewOptions<T> {
175
+ /**
176
+ * Which anchors get a card. Both wikis scope this to the article container
177
+ * (`a.closest('.prose-content')`) as well as testing the href, and the scope
178
+ * is exactly the part that differs — so the whole predicate is yours.
179
+ */
180
+ eligible: (anchor: HTMLAnchorElement) => boolean;
181
+ /** Resolve a preview for a href, or null for "no card". REST or server action. */
182
+ fetch: (href: string) => Promise<T | null>;
183
+ /** Must match the class on the element you spread `cardProps` onto. */
184
+ cardClassName?: string;
185
+ cardWidth?: number;
186
+ /** Used to decide whether the card flips above the link. */
187
+ cardHeight?: number;
188
+ /** Hover this long before fetching, in ms. */
189
+ showDelay?: number;
190
+ /** Grace period for the cursor to travel from link to card, in ms. */
191
+ hideDelay?: number;
192
+ /** Minimum gap from the viewport edge, in px. */
193
+ margin?: number;
194
+ }
195
+ export interface LinkPreviewState<T> {
196
+ /** The resolved preview, or null when no card should render. */
197
+ preview: T | null;
198
+ /** Spread onto your card element: position, and the keep-open handlers. */
199
+ cardProps: {
200
+ style: CSSProperties;
201
+ onMouseEnter: () => void;
202
+ onMouseLeave: () => void;
203
+ };
204
+ }
205
+ /**
206
+ * Wikipedia-style hover cards over a rendered article.
207
+ *
208
+ * One delegated listener on `document` rather than a component per link, which
209
+ * is what makes this viable over an article holding hundreds of anchors. Two
210
+ * wikis had written the same ~90 lines: the same 350ms intent delay, the same
211
+ * 200ms grace so the cursor can cross the gap into the card, the same
212
+ * viewport-clamp arithmetic, the same per-href cache. They differed in exactly
213
+ * three things, and those three are the options above.
214
+ */
215
+ export declare function useLinkPreview<T>({ eligible, fetch, cardClassName, cardWidth, cardHeight, showDelay, hideDelay, margin, }: LinkPreviewOptions<T>): LinkPreviewState<T>;
216
+ //# sourceMappingURL=react.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"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"}