@perxel/spotlight 0.0.0-stage → 0.1.1

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.
Files changed (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +174 -2
  3. package/dist/build/index.d.ts +32 -0
  4. package/dist/build/index.js +68 -0
  5. package/dist/chat/index.d.ts +3 -0
  6. package/dist/chat/index.js +5 -0
  7. package/dist/chat/parts.d.ts +57 -0
  8. package/dist/chat/parts.js +69 -0
  9. package/dist/chat/useAskChat.d.ts +67 -0
  10. package/dist/chat/useAskChat.js +200 -0
  11. package/dist/core/copy.d.ts +4 -0
  12. package/dist/core/copy.js +40 -0
  13. package/dist/core/fold.d.ts +10 -0
  14. package/dist/core/fold.js +23 -0
  15. package/dist/core/messages.d.ts +24 -0
  16. package/dist/core/messages.js +72 -0
  17. package/dist/core/search.d.ts +73 -0
  18. package/dist/core/search.js +210 -0
  19. package/dist/core/storage.d.ts +17 -0
  20. package/dist/core/storage.js +48 -0
  21. package/dist/gate/index.d.ts +2 -0
  22. package/dist/gate/index.js +5 -0
  23. package/dist/gate/parts.d.ts +179 -0
  24. package/dist/gate/parts.js +282 -0
  25. package/dist/gate/useSearchAsk.d.ts +90 -0
  26. package/dist/gate/useSearchAsk.js +122 -0
  27. package/dist/index.d.ts +7 -0
  28. package/dist/index.js +8 -0
  29. package/dist/liquid/context.d.ts +26 -0
  30. package/dist/liquid/context.js +6 -0
  31. package/dist/liquid/gate.d.ts +8 -0
  32. package/dist/liquid/gate.js +52 -0
  33. package/dist/liquid/icons.d.ts +12 -0
  34. package/dist/liquid/icons.js +15 -0
  35. package/dist/liquid/index.d.ts +9 -0
  36. package/dist/liquid/index.js +19 -0
  37. package/dist/liquid/liquid.css +1007 -0
  38. package/dist/liquid/panel.d.ts +8 -0
  39. package/dist/liquid/panel.js +186 -0
  40. package/dist/liquid/util.d.ts +6 -0
  41. package/dist/liquid/util.js +16 -0
  42. package/dist/react/index.d.ts +6 -0
  43. package/dist/react/index.js +10 -0
  44. package/dist/react/provider.d.ts +52 -0
  45. package/dist/react/provider.js +218 -0
  46. package/dist/react/turnstile.d.ts +16 -0
  47. package/dist/react/turnstile.js +100 -0
  48. package/dist/server/handler.d.ts +102 -0
  49. package/dist/server/handler.js +274 -0
  50. package/dist/server/index.d.ts +4 -0
  51. package/dist/server/index.js +5 -0
  52. package/dist/server/sources.d.ts +79 -0
  53. package/dist/server/sources.js +315 -0
  54. package/dist/server/turnstile.d.ts +15 -0
  55. package/dist/server/turnstile.js +29 -0
  56. package/dist/server/types.d.ts +34 -0
  57. package/dist/server/types.js +1 -0
  58. package/dist/types.d.ts +207 -0
  59. package/dist/types.js +7 -0
  60. package/package.json +102 -4
@@ -0,0 +1,122 @@
1
+ "use client";
2
+ // The search hook: search-as-you-type for one text field, with an optional "ask" on Enter.
3
+ // No Next, no styling, no copy (the host renders the field, the rows and every label).
4
+ // Imports only React and the core.
5
+ import { useId, useMemo, useState } from "react";
6
+ import { groupByKind, search } from "../core/search.js";
7
+ const NO_EXTRAS = [];
8
+ export function useSearchAsk({ index, query, enabled = true, onOpen, onAsk, extras = NO_EXTRAS, askRow = true, limit = 8, kindOrder = [], snippetLength }) {
9
+ const base = useId();
10
+ const listboxId = `${base}-results`;
11
+ // `on`: text typed and the index loaded, so there are results (maybe none) and an ask row.
12
+ const on = enabled && !!index && query.trim().length > 0;
13
+ const extraDocs = enabled ? extras : NO_EXTRAS;
14
+ const kindKey = kindOrder.join("\u0000");
15
+ const groups = useMemo(() => (on ? groupByKind(search(index, query, { limit, kindOrder, snippetLength }), kindOrder) : []),
16
+ // `kindOrder` by value: callers often pass a new array literal each render.
17
+ [on, index, query, limit, kindKey, snippetLength]);
18
+ // Flat, in display order: what the arrow keys walk through.
19
+ const results = useMemo(() => groups.flatMap((g) => g.results), [groups]);
20
+ const askable = on && !!onAsk && askRow;
21
+ const askIndex = askable ? results.length : -1;
22
+ const extraStart = results.length + (askable ? 1 : 0);
23
+ const count = extraStart + extraDocs.length;
24
+ // The list shows when it has anything to show.
25
+ const listed = enabled && count > 0;
26
+ // The highlighted option, valid only for the query it was set on: typing clears it.
27
+ const [highlight, setHighlight] = useState({ query: "", i: -1 });
28
+ const active = listed && highlight.query === query && highlight.i < count ? highlight.i : -1;
29
+ const setActive = (i) => setHighlight({ query, i });
30
+ const optionId = (i) => `${base}-opt-${i}`;
31
+ const activate = (i) => {
32
+ if (i === askIndex)
33
+ onAsk(query.trim());
34
+ else if (i >= extraStart) {
35
+ if (extraDocs[i - extraStart])
36
+ onOpen(extraDocs[i - extraStart]);
37
+ }
38
+ else if (results[i])
39
+ onOpen(results[i].doc);
40
+ };
41
+ /** The field's key handler. Returns true when it handled the key (the host then does nothing). */
42
+ const onKeyDown = (e) => {
43
+ if (!enabled || e.nativeEvent.isComposing)
44
+ return false;
45
+ if (e.key === "ArrowDown" || e.key === "ArrowUp") {
46
+ if (!count)
47
+ return false;
48
+ e.preventDefault();
49
+ const step = e.key === "ArrowDown" ? 1 : -1;
50
+ // From nothing, down goes to the first row and up to the last; both wrap through nothing.
51
+ const next = active === -1 ? (step > 0 ? 0 : count - 1) : active + step;
52
+ setActive(next < 0 || next >= count ? -1 : next);
53
+ return true;
54
+ }
55
+ if (e.key === "Escape" && active !== -1) {
56
+ // Clear the highlight only; a second Escape reaches the host (and closes its dialog).
57
+ e.preventDefault();
58
+ e.stopPropagation();
59
+ setActive(-1);
60
+ return true;
61
+ }
62
+ if (e.key === "Enter" && !e.shiftKey) {
63
+ if (active !== -1)
64
+ activate(active);
65
+ else if (!on)
66
+ return false;
67
+ else if (onAsk)
68
+ onAsk(query.trim());
69
+ else if (results[0])
70
+ onOpen(results[0].doc);
71
+ else
72
+ return false;
73
+ e.preventDefault();
74
+ return true;
75
+ }
76
+ return false;
77
+ };
78
+ const option = (i) => ({
79
+ id: optionId(i),
80
+ role: "option",
81
+ "aria-selected": i === active,
82
+ onMouseDown: (e) => e.preventDefault(),
83
+ });
84
+ return {
85
+ /** Results in display order, and the same grouped by kind (each with its flat `index`). */
86
+ results,
87
+ groups: useMemo(() => {
88
+ let i = 0;
89
+ return groups.map((g) => ({ kind: g.kind, results: g.results.map((r) => ({ ...r, index: i++ })) }));
90
+ }, [groups]),
91
+ /** True while text is typed and searched (results, maybe none, and the ask row). */
92
+ open: on,
93
+ /** True while the list has any row (results, the ask row or extras). */
94
+ listed,
95
+ /** The extras, each with its flat `index` (for `optionProps`). */
96
+ extras: useMemo(() => extraDocs.map((doc, i) => ({ doc, index: extraStart + i })), [extraDocs, extraStart]),
97
+ /** The highlighted option, -1 for none. The ask row is `results.length`. */
98
+ active,
99
+ setActive,
100
+ onKeyDown,
101
+ /** Open result `i` or, at `results.length`, ask. For row clicks. */
102
+ activate,
103
+ /** Number of results, for the host's live region ("3 results"). */
104
+ count: results.length,
105
+ /** Spread on the text field (combobox pattern; the focus never leaves it). Empty while disabled. */
106
+ inputProps: enabled && index
107
+ ? {
108
+ role: "combobox",
109
+ "aria-expanded": listed,
110
+ "aria-controls": listed ? listboxId : undefined,
111
+ "aria-autocomplete": "list",
112
+ "aria-activedescendant": active === -1 ? undefined : optionId(active),
113
+ }
114
+ : {},
115
+ /** Spread on the element that holds every option. */
116
+ listboxProps: { id: listboxId, role: "listbox" },
117
+ /** Spread on result row `i` (its flat index). */
118
+ optionProps: option,
119
+ /** Spread on the ask row; null without `onAsk`. */
120
+ askOptionProps: askable ? option(askIndex) : null,
121
+ };
122
+ }
@@ -0,0 +1,7 @@
1
+ export { buildIndex, fold, groupByKind, markTerms, search } from "./core/search.js";
2
+ export { conversationState, localStorageAdapter, noPersist, type ConversationState } from "./core/storage.js";
3
+ export { calledTool, latestFromVisitor, sourcesOf, textOf, toolName, toolOutputOf } from "./core/messages.js";
4
+ export { emailIn } from "./core/fold.js";
5
+ export { defaultCopy, mergeCopy } from "./core/copy.js";
6
+ export { defineAction } from "./types.js";
7
+ export type * from "./types.js";
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ // `@perxel/spotlight`: the core. No React, no AI SDK: the search engine, the conversation store
2
+ // and message helpers, the contracts. Safe anywhere (browser, server, build scripts).
3
+ export { buildIndex, fold, groupByKind, markTerms, search } from "./core/search.js";
4
+ export { conversationState, localStorageAdapter, noPersist } from "./core/storage.js";
5
+ export { calledTool, latestFromVisitor, sourcesOf, textOf, toolName, toolOutputOf } from "./core/messages.js";
6
+ export { emailIn } from "./core/fold.js";
7
+ export { defaultCopy, mergeCopy } from "./core/copy.js";
8
+ export { defineAction } from "./types.js";
@@ -0,0 +1,26 @@
1
+ import { type HTMLAttributes, type ReactNode } from "react";
2
+ import type { AskChat } from "../chat/useAskChat.js";
3
+ /** The liquid skin's own options, read by its gate and panel (lazy chunks) from context. */
4
+ export interface LiquidOptions {
5
+ /** The mark in the panel's header and next to "thinking" (an `<img>`, an SVG). */
6
+ logo?: ReactNode;
7
+ /** Heads every conversation: what happens to the questions. */
8
+ privacyNote?: ReactNode;
9
+ /** Under the conversation, when it returns something (e.g. contact options after a handoff tool call or an error). */
10
+ handoff?: (chat: AskChat) => ReactNode;
11
+ /** Left of the send button in the composer (e.g. a "send to us" link). */
12
+ composerExtra?: (chat: AskChat) => ReactNode;
13
+ /** Tools that only drive the interface: the answer text is not cut at them. */
14
+ uiTools?: string[];
15
+ /** Hosts whose links in answers and sources stay in the app (made relative, then `navigate`). The page's own host always does. */
16
+ localHosts?: string[];
17
+ /** The moving pools of accent colour behind the composer. */
18
+ tide?: boolean;
19
+ /** Extra attributes on the gate's and the panel's roots (`data-*` for a site's own scripts). */
20
+ gateProps?: HTMLAttributes<HTMLDivElement> & Record<`data-${string}`, string | boolean | undefined>;
21
+ panelProps?: HTMLAttributes<HTMLDivElement> & Record<`data-${string}`, string | boolean | undefined>;
22
+ /** Class on both roots (to set the theme variables on). */
23
+ className?: string;
24
+ }
25
+ export declare const LiquidContext: import("react").Context<LiquidOptions>;
26
+ export declare function useLiquid(): LiquidOptions;
@@ -0,0 +1,6 @@
1
+ "use client";
2
+ import { createContext, useContext } from "react";
3
+ export const LiquidContext = createContext({});
4
+ export function useLiquid() {
5
+ return useContext(LiquidContext);
6
+ }
@@ -0,0 +1,8 @@
1
+ import "./liquid.css";
2
+ import type { GateProps } from "../types.js";
3
+ /**
4
+ * The liquid gate: like Spotlight, a glass box near the top over a dimmed, blurred page; under it,
5
+ * only when something matches, one glass group with every result. A hint in the box says what
6
+ * Enter does (keyboards), a send button replaces it on touch screens.
7
+ */
8
+ export default function LiquidGate({ open, onOpenChange }: GateProps): import("react").JSX.Element;
@@ -0,0 +1,52 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useEffect, useLayoutEffect, useState } from "react";
4
+ import "./liquid.css";
5
+ import { Gate, useGateContext } from "../gate/parts.js";
6
+ import { useLiquid } from "./context.js";
7
+ import { ArrowDownIcon, ArrowUpIcon, ArrowUpRightIcon, CornerDownLeftIcon, SearchIcon, XIcon } from "./icons.js";
8
+ import { cx, PHONE_QUERY, REDUCED_MOTION, useMedia } from "./util.js";
9
+ /** Phones: the gate's top in px, under the close button floating above the box; and its results' distance from the keyboard. */
10
+ const PHONE_TOP = 64;
11
+ const PHONE_BOTTOM = 16;
12
+ /** Phones: the visible height (it shrinks when the keyboard opens), so the results end above the keyboard. */
13
+ function useVisibleHeight(active) {
14
+ const [h, setH] = useState(null);
15
+ useEffect(() => {
16
+ const vv = window.visualViewport;
17
+ if (!active || !vv) {
18
+ setH(null);
19
+ return;
20
+ }
21
+ const update = () => setH(vv.height);
22
+ update();
23
+ vv.addEventListener("resize", update);
24
+ return () => vv.removeEventListener("resize", update);
25
+ }, [active]);
26
+ return h;
27
+ }
28
+ /**
29
+ * The liquid gate: like Spotlight, a glass box near the top over a dimmed, blurred page; under it,
30
+ * only when something matches, one glass group with every result. A hint in the box says what
31
+ * Enter does (keyboards), a send button replaces it on touch screens.
32
+ */
33
+ export default function LiquidGate({ open, onOpenChange }) {
34
+ const liquid = useLiquid();
35
+ const phone = useMedia(PHONE_QUERY);
36
+ const visible = useVisibleHeight(open && phone);
37
+ const room = visible ? visible - PHONE_TOP - PHONE_BOTTOM : null;
38
+ return (_jsx(Gate.Root, { open: open, onOpenChange: onOpenChange, overlayProps: { className: cx("sp-overlay", liquid.className) }, ...liquid.gateProps, className: cx("sp-gate", liquid.className, liquid.gateProps?.className), style: room ? { maxHeight: room } : undefined, children: _jsx(Body, { phone: phone, room: room }) }));
39
+ }
40
+ function Body({ phone, room }) {
41
+ const g = useGateContext();
42
+ const { search } = g;
43
+ // The highlight is one glass piece that slides from row to row. Measured from the active row;
44
+ // coming from nothing it appears in place instead of sliding from the last row.
45
+ const [hl, setHl] = useState({ top: 0, height: 0, on: false, jump: true });
46
+ useLayoutEffect(() => {
47
+ const el = search.active === -1 ? null : document.getElementById(search.optionProps(search.active).id);
48
+ setHl((h) => (el ? { top: el.offsetTop, height: el.offsetHeight, on: true, jump: !h.on } : { ...h, on: false }));
49
+ el?.scrollIntoView({ block: "nearest", behavior: matchMedia(REDUCED_MOTION).matches ? "auto" : "smooth" });
50
+ }, [search.active, search.results]);
51
+ return (_jsxs(_Fragment, { children: [_jsx(Gate.Close, { className: "sp-liquid sp-glass sp-gate-close", children: _jsx(XIcon, {}) }), _jsxs("div", { className: "sp-liquid sp-glass sp-gate-box", children: [_jsx(SearchIcon, { className: "sp-gate-icon" }), _jsx(Gate.Input, { className: "sp-gate-input" }), phone ? (_jsx(Gate.Send, { className: "sp-fill sp-gate-send", children: _jsx(ArrowUpIcon, {}) })) : (_jsxs("span", { "aria-hidden": true, className: "sp-gate-hints", children: [_jsx("span", { "data-on": (g.focused && search.count > 0) || undefined, className: "sp-hint", children: _jsxs("kbd", { className: "sp-kbd sp-kbd-arrows", children: [_jsx(ArrowUpIcon, {}), _jsx(ArrowDownIcon, {})] }) }), _jsx("span", { "data-on": (g.focused && !!g.typed && !!g.hint) || undefined, className: "sp-hint", children: _jsxs("span", { className: "sp-hint-enter", children: [_jsx("kbd", { className: "sp-kbd", children: _jsx(CornerDownLeftIcon, {}) }), _jsx("span", { className: "sp-fade-in", children: g.hint }, g.hint ?? "")] }) })] }))] }), search.count > 0 && (_jsx("div", { className: "sp-liquid sp-glass sp-gate-results", children: _jsxs("div", { className: "sp-scroll", "data-lenis-prevent": "", style: room ? { maxHeight: room - 56 - 8 } : undefined, children: [_jsx(Gate.Results, { className: "sp-gate-list", children: ({ index, ...result }) => (_jsxs(Gate.Item, { result: result, index: index, className: "sp-row", style: { animationDelay: `${Math.min(index, 8) * 25}ms` }, children: [_jsxs("span", { className: "sp-row-text", children: [_jsx("span", { className: "sp-row-title", children: _jsx(Gate.Marked, { text: result.doc.title, marks: result.titleMarks }) }), result.snippet ? (_jsx("span", { className: "sp-row-sub sp-truncate", children: _jsx(Gate.Marked, { ...result.snippet }) })) : result.doc.subtitle ? (_jsx("span", { className: "sp-row-sub", children: result.doc.subtitle })) : null] }), _jsxs("span", { className: "sp-row-kind", children: [g.kindLabel(result.doc.kind), _jsx(ArrowUpRightIcon, { className: "sp-row-arrow" })] })] })) }), _jsx("span", { "aria-hidden": true, "data-on": hl.on || undefined, "data-jump": hl.jump || undefined, className: "sp-liquid sp-liquid-accent sp-hl", style: { transform: `translateY(${hl.top}px)`, height: hl.height } })] }) }))] }));
52
+ }
@@ -0,0 +1,12 @@
1
+ import type { SVGProps } from "react";
2
+ export declare const SearchIcon: (p: SVGProps<SVGSVGElement>) => import("react").JSX.Element;
3
+ export declare const XIcon: (p: SVGProps<SVGSVGElement>) => import("react").JSX.Element;
4
+ export declare const ArrowUpIcon: (p: SVGProps<SVGSVGElement>) => import("react").JSX.Element;
5
+ export declare const ArrowDownIcon: (p: SVGProps<SVGSVGElement>) => import("react").JSX.Element;
6
+ export declare const ArrowLeftIcon: (p: SVGProps<SVGSVGElement>) => import("react").JSX.Element;
7
+ export declare const ArrowUpRightIcon: (p: SVGProps<SVGSVGElement>) => import("react").JSX.Element;
8
+ export declare const CornerDownLeftIcon: (p: SVGProps<SVGSVGElement>) => import("react").JSX.Element;
9
+ export declare const SquareIcon: (p: SVGProps<SVGSVGElement>) => import("react").JSX.Element;
10
+ export declare const SquarePenIcon: (p: SVGProps<SVGSVGElement>) => import("react").JSX.Element;
11
+ export declare const ChevronDownIcon: (p: SVGProps<SVGSVGElement>) => import("react").JSX.Element;
12
+ export declare const FileTextIcon: (p: SVGProps<SVGSVGElement>) => import("react").JSX.Element;
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ function Icon({ children, ...props }) {
3
+ return (_jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, ...props, children: children }));
4
+ }
5
+ export const SearchIcon = (p) => _jsxs(Icon, { ...p, children: [_jsx("circle", { cx: "11", cy: "11", r: "8" }), _jsx("path", { d: "m21 21-4.3-4.3" })] });
6
+ export const XIcon = (p) => _jsxs(Icon, { ...p, children: [_jsx("path", { d: "M18 6 6 18" }), _jsx("path", { d: "m6 6 12 12" })] });
7
+ export const ArrowUpIcon = (p) => _jsxs(Icon, { ...p, children: [_jsx("path", { d: "m5 12 7-7 7 7" }), _jsx("path", { d: "M12 19V5" })] });
8
+ export const ArrowDownIcon = (p) => _jsxs(Icon, { ...p, children: [_jsx("path", { d: "M12 5v14" }), _jsx("path", { d: "m19 12-7 7-7-7" })] });
9
+ export const ArrowLeftIcon = (p) => _jsxs(Icon, { ...p, children: [_jsx("path", { d: "m12 19-7-7 7-7" }), _jsx("path", { d: "M19 12H5" })] });
10
+ export const ArrowUpRightIcon = (p) => _jsxs(Icon, { ...p, children: [_jsx("path", { d: "M7 7h10v10" }), _jsx("path", { d: "M7 17 17 7" })] });
11
+ export const CornerDownLeftIcon = (p) => _jsxs(Icon, { ...p, children: [_jsx("path", { d: "M20 4v7a4 4 0 0 1-4 4H4" }), _jsx("path", { d: "m9 10-5 5 5 5" })] });
12
+ export const SquareIcon = (p) => _jsx(Icon, { ...p, children: _jsx("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }) });
13
+ export const SquarePenIcon = (p) => (_jsxs(Icon, { ...p, children: [_jsx("path", { d: "M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" }), _jsx("path", { d: "M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z" })] }));
14
+ export const ChevronDownIcon = (p) => _jsx(Icon, { ...p, children: _jsx("path", { d: "m6 9 6 6 6-6" }) });
15
+ export const FileTextIcon = (p) => (_jsxs(Icon, { ...p, children: [_jsx("path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }), _jsx("path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }), _jsx("path", { d: "M10 9H8" }), _jsx("path", { d: "M16 13H8" }), _jsx("path", { d: "M16 17H8" })] }));
@@ -0,0 +1,9 @@
1
+ import type { SpotlightProps } from "../types.js";
2
+ import { type LiquidOptions } from "./context.js";
3
+ export type SpotlightLiquidProps = Omit<SpotlightProps, "gate" | "panel"> & LiquidOptions;
4
+ /** The provider with the liquid gate and panel. Wrap the app (or the part that offers search/ask). */
5
+ export declare function SpotlightLiquid({ logo, privacyNote, handoff, composerExtra, uiTools, localHosts, tide, gateProps, panelProps, className, ...props }: SpotlightLiquidProps): import("react").JSX.Element;
6
+ export type { LiquidOptions };
7
+ export { Trigger, useSpotlight, isMac, shortcutLabel } from "../react/provider.js";
8
+ export { localStorageAdapter } from "../core/storage.js";
9
+ export { turnstileWidget } from "../react/turnstile.js";
@@ -0,0 +1,19 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ // `@perxel/spotlight/liquid`: the default skin. Glass gate and panel, themed with CSS variables.
4
+ // The gate and the panel are lazy chunks that import the skin's CSS themselves, so a page's first
5
+ // load holds only the provider and the triggers, and no stylesheet. (`@perxel/spotlight/liquid.css`
6
+ // is also exported, for a bundler that does not load CSS imported from a package.)
7
+ import { SpotlightProvider } from "../react/provider.js";
8
+ import { LiquidContext } from "./context.js";
9
+ const gate = () => import("./gate.js");
10
+ const panel = () => import("./panel.js");
11
+ /** The provider with the liquid gate and panel. Wrap the app (or the part that offers search/ask). */
12
+ export function SpotlightLiquid({ logo, privacyNote, handoff, composerExtra, uiTools, localHosts, tide, gateProps, panelProps, className, ...props }) {
13
+ const options = { logo, privacyNote, handoff, composerExtra, uiTools, localHosts, tide, gateProps, panelProps, className };
14
+ return (_jsx(LiquidContext.Provider, { value: options, children: _jsx(SpotlightProvider, { ...props, gate: gate, panel: panel }) }));
15
+ }
16
+ // The light parts of `./react`, so a site on the skin imports from one place.
17
+ export { Trigger, useSpotlight, isMac, shortcutLabel } from "../react/provider.js";
18
+ export { localStorageAdapter } from "../core/storage.js";
19
+ export { turnstileWidget } from "../react/turnstile.js";