@dreamlake/dockit 0.2.12 → 0.2.14

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.
@@ -4,6 +4,17 @@ import { defaultResults, searchPages } from "../lib/pagefind-search.js";
4
4
  import { useMediaQuery } from "../lib/use-media-query.js";
5
5
  import { useSearchResize, SearchResizeHandles } from "./SearchResize.js";
6
6
  import { useHiddenToggle } from "../lib/use-hidden-toggle.js";
7
+ import { Chip } from "./Chip.js";
8
+ function SourceBadge({ label }) {
9
+ return /* @__PURE__ */ jsx(
10
+ Chip,
11
+ {
12
+ variant: "accent",
13
+ style: { fontSize: 8.5, padding: "0px 5px", letterSpacing: "0.06em", flexShrink: 0 },
14
+ children: label
15
+ }
16
+ );
17
+ }
7
18
  const PAL_MAX_W = 860;
8
19
  const PAL_GUTTER = 24;
9
20
  const PAL_INSET = `max(${PAL_GUTTER}px, calc((100vw - ${PAL_MAX_W}px) / 2))`;
@@ -66,7 +77,7 @@ function SearchPalette({ open, onClose, query }) {
66
77
  const pageCacheRef = useRef(/* @__PURE__ */ new Map());
67
78
  const activePath = hits[active]?.path;
68
79
  useEffect(() => {
69
- if (!activePath || isSingleCol) {
80
+ if (!activePath || isSingleCol || /^https?:\/\//.test(activePath)) {
70
81
  setPageHtml("");
71
82
  return;
72
83
  }
@@ -341,6 +352,7 @@ function SearchPalette({ open, onClose, query }) {
341
352
  textOverflow: "ellipsis"
342
353
  },
343
354
  children: [
355
+ hit.sourceLabel && /* @__PURE__ */ jsx("span", { style: { marginRight: 5 }, children: /* @__PURE__ */ jsx(SourceBadge, { label: hit.sourceLabel }) }),
344
356
  hit.section && /* @__PURE__ */ jsxs(
345
357
  "span",
346
358
  {
@@ -379,19 +391,23 @@ function SearchPalette({ open, onClose, query }) {
379
391
  }
380
392
  )
381
393
  ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
382
- /* @__PURE__ */ jsx(
383
- "span",
384
- {
385
- className: "text-doc-template-ink",
386
- style: {
387
- fontWeight: 500,
388
- whiteSpace: "nowrap",
389
- overflow: "hidden",
390
- textOverflow: "ellipsis"
391
- },
392
- children: highlight(hit.title, query)
393
- }
394
- ),
394
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center", style: { gap: 6, minWidth: 0 }, children: [
395
+ /* @__PURE__ */ jsx(
396
+ "span",
397
+ {
398
+ className: "text-doc-template-ink",
399
+ style: {
400
+ fontWeight: 500,
401
+ whiteSpace: "nowrap",
402
+ overflow: "hidden",
403
+ textOverflow: "ellipsis",
404
+ minWidth: 0
405
+ },
406
+ children: highlight(hit.title, query)
407
+ }
408
+ ),
409
+ hit.sourceLabel && /* @__PURE__ */ jsx(SourceBadge, { label: hit.sourceLabel })
410
+ ] }),
395
411
  hit.section && /* @__PURE__ */ jsx(
396
412
  "span",
397
413
  {
@@ -497,7 +513,7 @@ function SearchPalette({ open, onClose, query }) {
497
513
  letterSpacing: "0.05em",
498
514
  marginBottom: 6
499
515
  },
500
- children: previewHit.section || "—"
516
+ children: previewHit.section || previewHit.sourceLabel || "—"
501
517
  }
502
518
  ),
503
519
  /* @__PURE__ */ jsx(
@@ -1,11 +1,14 @@
1
1
  import { jsxs, jsx } from "react/jsx-runtime";
2
2
  import { useMemo, useRef, useState, useLayoutEffect } from "react";
3
3
  import { usePageContext } from "vike-react/usePageContext";
4
- import { tabForUrl, TABS, urlInTab } from "../lib/tabs.js";
4
+ import { tabForUrl, TABS, isExternalTab, urlInTab } from "../lib/tabs.js";
5
5
  import { pages } from "../lib/navigation.js";
6
6
  import { useHiddenToggle } from "../lib/use-hidden-toggle.js";
7
7
  const SPRING = "cubic-bezier(0.4, 0.7, 0.3, 1)";
8
8
  const SSR_BAR_BOTTOM = -13;
9
+ function tabKey(tab) {
10
+ return isExternalTab(tab) ? `ext:${tab.href}` : tab.id;
11
+ }
9
12
  function TabStrip() {
10
13
  const { urlPathname } = usePageContext();
11
14
  const activeTab = tabForUrl(urlPathname);
@@ -13,7 +16,7 @@ function TabStrip() {
13
16
  const visibleTabs = useMemo(() => {
14
17
  if (showHidden) return TABS;
15
18
  return TABS.filter(
16
- (tab) => tab.id === activeTab || pages.some((p) => !p.hidden && urlInTab(p.path, tab.id))
19
+ (tab) => isExternalTab(tab) || tab.id === activeTab || pages.some((p) => !p.hidden && urlInTab(p.path, tab.id))
17
20
  );
18
21
  }, [showHidden, activeTab]);
19
22
  const containerRef = useRef(null);
@@ -75,19 +78,22 @@ function TabStrip() {
75
78
  onMouseLeave: () => setHovered(null),
76
79
  children: [
77
80
  visibleTabs.map((tab) => {
78
- const isActive = tab.id === activeTab;
81
+ const key = tabKey(tab);
82
+ const external = isExternalTab(tab);
83
+ const isActive = !external && tab.id === activeTab;
79
84
  return /* @__PURE__ */ jsx(
80
85
  "li",
81
86
  {
82
87
  className: "flex items-stretch shrink-0",
83
- onMouseEnter: () => setHovered(tab.id),
84
- children: /* @__PURE__ */ jsx(
88
+ onMouseEnter: () => setHovered(key),
89
+ children: /* @__PURE__ */ jsxs(
85
90
  "a",
86
91
  {
87
92
  ref: (el) => {
88
- linkRefs.current[tab.id] = el;
93
+ linkRefs.current[key] = el;
89
94
  },
90
- href: tab.landing,
95
+ href: external ? tab.href : tab.landing,
96
+ ...external ? { target: "_blank", rel: "noreferrer" } : {},
91
97
  "aria-current": isActive ? "page" : void 0,
92
98
  className: "flex items-center no-underline px-1.5 md:px-2.5",
93
99
  style: {
@@ -97,15 +103,36 @@ function TabStrip() {
97
103
  letterSpacing: "0.12em",
98
104
  textTransform: "uppercase",
99
105
  whiteSpace: "nowrap",
100
- color: isActive || hovered === tab.id ? "var(--color-doc-template-ink)" : "var(--color-doc-template-muted)",
106
+ color: isActive || hovered === key ? "var(--color-doc-template-ink)" : "var(--color-doc-template-muted)",
101
107
  cursor: "pointer",
102
108
  transition: "color 0.18s ease"
103
109
  },
104
- children: tab.label
110
+ children: [
111
+ tab.label,
112
+ external && // ↗ affordance — drawn, not the glyph, so its weight and
113
+ // optical size stay consistent across the mono font stack.
114
+ /* @__PURE__ */ jsxs(
115
+ "svg",
116
+ {
117
+ viewBox: "0 0 24 24",
118
+ fill: "none",
119
+ stroke: "currentColor",
120
+ strokeWidth: "2.5",
121
+ strokeLinecap: "round",
122
+ strokeLinejoin: "round",
123
+ "aria-hidden": true,
124
+ style: { width: 9, height: 9, marginLeft: 4, opacity: 0.8, flexShrink: 0 },
125
+ children: [
126
+ /* @__PURE__ */ jsx("path", { d: "M7 17L17 7" }),
127
+ /* @__PURE__ */ jsx("path", { d: "M8 7h9v9" })
128
+ ]
129
+ }
130
+ )
131
+ ]
105
132
  }
106
133
  )
107
134
  },
108
- tab.id
135
+ key
109
136
  );
110
137
  }),
111
138
  /* @__PURE__ */ jsx(
@@ -10,7 +10,7 @@ import { ThemeToggle } from "./ThemeToggle.js";
10
10
  import { VersionBadge } from "./VersionBadge.js";
11
11
  import { useMerge } from "../renderer/Layout.js";
12
12
  import { TabStrip } from "./TabStrip.js";
13
- import { TABS, tabForUrl } from "../lib/tabs.js";
13
+ import { internalTabs, tabForUrl } from "../lib/tabs.js";
14
14
  function HiddenRevealChip() {
15
15
  const [show, setShow] = useHiddenToggle();
16
16
  if (!show) return null;
@@ -38,7 +38,7 @@ const SEARCH_INSET = `max(${SEARCH_GUTTER}px, calc((100vw - ${SEARCH_MAX_W}px) /
38
38
  function Topbar({ searchOpen, onOpenSearch, onCloseSearch, query, setQuery }) {
39
39
  const { urlPathname } = usePageContext();
40
40
  const current = findPage(urlPathname);
41
- const activeTab = TABS.find((t) => t.id === tabForUrl(urlPathname));
41
+ const activeTab = internalTabs().find((t) => t.id === tabForUrl(urlPathname));
42
42
  const versionChips = activeTab?.versionChips ?? siteConfig.versionChips ?? [];
43
43
  const inputRef = useRef(null);
44
44
  const labelRef = useRef(null);
@@ -160,7 +160,7 @@ function Topbar({ searchOpen, onOpenSearch, onCloseSearch, query, setQuery }) {
160
160
  const [tabsFit, setTabsFit] = useState(false);
161
161
  const [tabsW, setTabsW] = useState(0);
162
162
  const [pinLeft, setPinLeft] = useState(0);
163
- const [searchPinRight, setSearchPinRight] = useState(null);
163
+ const [searchPinLeft, setSearchPinLeft] = useState(null);
164
164
  useLayoutEffect(() => {
165
165
  const tabsEl = tabsRef.current;
166
166
  if (!tabsEl) return;
@@ -178,13 +178,13 @@ function Topbar({ searchOpen, onOpenSearch, onCloseSearch, query, setQuery }) {
178
178
  const brandClear = brandMaxRight.current + 16 <= pin;
179
179
  const actions = actionsRef.current?.getBoundingClientRect();
180
180
  const actionsLeft = actions ? actions.left : window.innerWidth;
181
- const pinnedSearchLeft = textRight - closedWidth;
181
+ const pinnedSearchLeft = textRight;
182
182
  const packedSearchLeft = actionsLeft - 8 - closedWidth;
183
183
  const fits = (searchLeft) => w > 0 && brandClear && pin + w + 16 <= searchLeft;
184
- const fullPin = textRight + 16 <= actionsLeft && fits(pinnedSearchLeft);
184
+ const fullPin = textRight + closedWidth + 16 <= actionsLeft && fits(pinnedSearchLeft);
185
185
  setTabsW(w);
186
186
  setPinLeft(pin);
187
- setSearchPinRight(fullPin ? window.innerWidth - textRight : null);
187
+ setSearchPinLeft(fullPin ? pinnedSearchLeft : null);
188
188
  setTabsFit(fullPin || fits(packedSearchLeft));
189
189
  };
190
190
  measure();
@@ -244,14 +244,17 @@ function Topbar({ searchOpen, onOpenSearch, onCloseSearch, query, setQuery }) {
244
244
  placeSelf: "stretch",
245
245
  boxShadow: "0 4px 14px rgb(0 0 0 / 0.06)",
246
246
  transition: `left ${EXPAND_MS}ms ${EXPAND_EASE}, right ${EXPAND_MS}ms ${EXPAND_EASE}, background 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease`
247
- } : searchPinRight != null ? {
248
- // Closed, right-aligned to the content column's right
249
- // edge (mirrors the pinned tab strip on the left edge).
247
+ } : searchPinLeft != null ? {
248
+ // Closed, sitting just outside the content column with
249
+ // its left edge on the column's right border (mirrors
250
+ // the tab strip pinned to the left edge). Left-anchored,
251
+ // so breakpoint width changes grow it away from the
252
+ // column and the shared border never moves.
250
253
  // (gridArea/placeSelf: see the note above about
251
254
  // fixed-positioned grid items.)
252
255
  position: "fixed",
253
256
  top: 6,
254
- right: searchPinRight,
257
+ left: searchPinLeft,
255
258
  width: closedWidth,
256
259
  maxWidth: closedWidth,
257
260
  height: 28,
@@ -180,6 +180,7 @@ const H1 = ({ id, children, style, ...rest }) => /* @__PURE__ */ jsxs(
180
180
  fontWeight: 700,
181
181
  letterSpacing: "-0.025em",
182
182
  margin: "18px 0 10px",
183
+ paddingBottom: 24,
183
184
  lineHeight: 1.1,
184
185
  gap: 8,
185
186
  ...style
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { SiteConfig as _SiteConfig } from './site-config';
1
+ import { SiteConfig as _SiteConfig, SearchConfig as _SearchConfig } from './site-config';
2
2
  import { PageFrontmatter as _PageFrontmatter } from './lib/navigation';
3
- import { TabDef as _TabDef } from './lib/tabs';
3
+ import { TabEntry as _TabEntry } from './lib/tabs';
4
4
  /**
5
5
  * @dreamlake/dockit — the DreamLake documentation shell as a library.
6
6
  *
@@ -17,11 +17,11 @@ import { TabDef as _TabDef } from './lib/tabs';
17
17
  * tabs: [{ id: 'overview', label: 'Get Started', numeral: 'I', landing: '/', urlPrefix: '/get-started' }],
18
18
  * })
19
19
  */
20
- export { siteConfig, defineSiteConfig, type SiteConfig, type VersionChip, } from './site-config';
20
+ export { siteConfig, defineSiteConfig, type SiteConfig, type VersionChip, type SearchConfig, type MergeIndexDef, } from './site-config';
21
21
  export { pages, groupedPages, groupedVisiblePages, initNavigation, getAdjacentPages, normalizePath, findPage, type PageMeta, type PageFrontmatter, type PageModule, type NavGroup, } from './lib/navigation';
22
22
  export { searchIndex, rawMarkdown, initSearchIndex, search, type SearchEntry, type SearchHit, } from './lib/search-index';
23
23
  export { searchPages, defaultResults, type SearchResult, type SubResult, } from './lib/pagefind-search';
24
- export { TABS, initTabs, tabForUrl, urlInTab, type TabDef } from './lib/tabs';
24
+ export { TABS, initTabs, tabForUrl, urlInTab, isExternalTab, internalTabs, type TabDef, type ExternalTabDef, type TabEntry, } from './lib/tabs';
25
25
  export { useHiddenToggle } from './lib/use-hidden-toggle';
26
26
  export { useLineNumbers } from './lib/use-line-numbers';
27
27
  export { useLocalStorage, DOC_PREFIX, docKey } from './lib/use-local-storage';
@@ -56,8 +56,14 @@ export interface InitDocsOptions {
56
56
  rawPages?: Record<string, string>;
57
57
  /** Authored sidebar section order; unknown sections fall to the end. */
58
58
  sectionOrder?: string[];
59
- /** Topbar tab lenses. Omit (or pass []) for a tabless site. */
60
- tabs?: _TabDef[];
59
+ /** Topbar tab lenses, plus optional external-link entries
60
+ * (`{ label, href, external: true }`). Omit (or pass []) for a
61
+ * tabless site. */
62
+ tabs?: _TabEntry[];
63
+ /** Search-layer options — e.g. `mergeIndexes` to merge remote Pagefind
64
+ * indexes into the palette. Equivalent to `site.search`; this
65
+ * top-level key wins when both are given. */
66
+ search?: _SearchConfig;
61
67
  }
62
68
  /** One call wires the whole shell: site config, navigation, search, tabs. */
63
69
  export declare function initDocs(opts: InitDocsOptions): void;
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import { initSearchIndex } from "./lib/search-index.js";
6
6
  import { rawMarkdown, search, searchIndex } from "./lib/search-index.js";
7
7
  import { defaultResults, searchPages } from "./lib/pagefind-search.js";
8
8
  import { initTabs } from "./lib/tabs.js";
9
- import { TABS, tabForUrl, urlInTab } from "./lib/tabs.js";
9
+ import { TABS, internalTabs, isExternalTab, tabForUrl, urlInTab } from "./lib/tabs.js";
10
10
  import { useHiddenToggle } from "./lib/use-hidden-toggle.js";
11
11
  import { useLineNumbers } from "./lib/use-line-numbers.js";
12
12
  import { DOC_PREFIX, docKey, useLocalStorage } from "./lib/use-local-storage.js";
@@ -30,7 +30,7 @@ import { Topbar } from "./components/Topbar.js";
30
30
  import { VersionBadge } from "./components/VersionBadge.js";
31
31
  import { Layout, useMerge } from "./renderer/Layout.js";
32
32
  function initDocs(opts) {
33
- defineSiteConfig(opts.site);
33
+ defineSiteConfig(opts.search ? { ...opts.site, search: opts.search } : opts.site);
34
34
  initNavigation(opts.pages, opts.sectionOrder ?? []);
35
35
  initSearchIndex(opts.rawPages ?? {});
36
36
  initTabs(opts.tabs ?? []);
@@ -67,6 +67,8 @@ export {
67
67
  initNavigation,
68
68
  initSearchIndex,
69
69
  initTabs,
70
+ internalTabs,
71
+ isExternalTab,
70
72
  mdxComponents,
71
73
  normalizePath,
72
74
  pages,
@@ -13,6 +13,10 @@ export interface SearchResult {
13
13
  excerptHtml: string;
14
14
  subResults: SubResult[];
15
15
  score: number;
16
+ /** Label of the merged remote index this result came from (see
17
+ * `search.mergeIndexes` in the site config). Unset for local results.
18
+ * Remote results carry absolute `path`/sub-result URLs on that site. */
19
+ sourceLabel?: string;
16
20
  }
17
21
  /** Default (empty-query) results — computed lazily because the
18
22
  * `searchIndex` singleton is only populated once the consuming site's
@@ -1,7 +1,9 @@
1
1
  import { searchIndex, search } from "./search-index.js";
2
2
  import { pages } from "./navigation.js";
3
+ import { siteConfig } from "../site-config.js";
3
4
  let pagefind = null;
4
5
  let initPromise = null;
6
+ const mergedOriginLabels = /* @__PURE__ */ new Map();
5
7
  async function getPagefind() {
6
8
  if (pagefind) return pagefind;
7
9
  if (initPromise) return initPromise;
@@ -13,6 +15,14 @@ async function getPagefind() {
13
15
  path
14
16
  );
15
17
  await pf.options({ excerptLength: 200 });
18
+ for (const idx of siteConfig.search?.mergeIndexes ?? []) {
19
+ try {
20
+ const origin = new URL(idx.url).origin;
21
+ await pf.mergeIndex(idx.url, { baseUrl: `${origin}/` });
22
+ mergedOriginLabels.set(origin, idx.label);
23
+ } catch {
24
+ }
25
+ }
16
26
  pagefind = pf;
17
27
  return pf;
18
28
  } catch {
@@ -21,6 +31,14 @@ async function getPagefind() {
21
31
  })();
22
32
  return initPromise;
23
33
  }
34
+ function sourceLabelFor(url) {
35
+ if (!/^https?:\/\//.test(url)) return void 0;
36
+ try {
37
+ return mergedOriginLabels.get(new URL(url).origin);
38
+ } catch {
39
+ return void 0;
40
+ }
41
+ }
24
42
  const STOP_WORDS = /* @__PURE__ */ new Set([
25
43
  "a",
26
44
  "an",
@@ -189,7 +207,8 @@ async function searchPages(query, limit = 30, showHidden = false) {
189
207
  snippet: r.excerpt?.replace(/<[^>]+>/g, "").trim() ?? "",
190
208
  excerptHtml: r.excerpt ?? "",
191
209
  subResults,
192
- score
210
+ score,
211
+ sourceLabel: sourceLabelFor(path)
193
212
  };
194
213
  });
195
214
  mapped.sort((a, b) => b.score - a.score);
@@ -13,11 +13,26 @@ export interface TabDef {
13
13
  * chips; set to `[]` to show none on this tab. */
14
14
  versionChips?: VersionChip[];
15
15
  }
16
+ /** An external-link entry in the tab strip — renders alongside the tab
17
+ * lenses with an ↗ affordance, but is a plain link out to another site
18
+ * (e.g. a sibling docs property). It scopes no pages, is never "active",
19
+ * and does not participate in `tabForUrl`. */
20
+ export interface ExternalTabDef {
21
+ label: string;
22
+ /** Absolute URL the tab links out to (e.g. "https://cli.dreamlake.ai"). */
23
+ href: string;
24
+ external: true;
25
+ }
26
+ export type TabEntry = TabDef | ExternalTabDef;
27
+ export declare function isExternalTab(tab: TabEntry): tab is ExternalTabDef;
16
28
  /** Module-level singleton, populated by `initTabs()`. Mutated in place —
17
29
  * never reassigned — so importing components keep a live reference. */
18
- export declare const TABS: TabDef[];
30
+ export declare const TABS: TabEntry[];
19
31
  /** Populate the tab singleton from the consuming site. */
20
- export declare function initTabs(tabs: TabDef[]): void;
32
+ export declare function initTabs(tabs: TabEntry[]): void;
33
+ /** The page-scoping (non-external) tabs, in order. All URL→tab logic
34
+ * runs over this list — external entries are chrome, not lenses. */
35
+ export declare function internalTabs(): TabDef[];
21
36
  /** Derive active tab from a page URL. The site root `/` resolves to the
22
37
  * first tab. Unknown prefixes also fall back to the first tab so orphan
23
38
  * pages never leave the sidebar empty. With no tabs registered, always
package/dist/lib/tabs.js CHANGED
@@ -1,8 +1,14 @@
1
+ function isExternalTab(tab) {
2
+ return tab.external === true;
3
+ }
1
4
  const TABS = [];
2
5
  function initTabs(tabs) {
3
6
  TABS.length = 0;
4
7
  TABS.push(...tabs);
5
8
  }
9
+ function internalTabs() {
10
+ return TABS.filter((t) => !isExternalTab(t));
11
+ }
6
12
  function firstSegment(url) {
7
13
  const path = url.split(/[?#]/)[0];
8
14
  const trimmed = path.replace(/^\/+/, "").replace(/\/+$/, "");
@@ -10,14 +16,15 @@ function firstSegment(url) {
10
16
  return trimmed.split("/")[0];
11
17
  }
12
18
  function tabForUrl(url) {
13
- if (TABS.length === 0) return "";
14
- if (!url) return TABS[0].id;
19
+ const tabs = internalTabs();
20
+ if (tabs.length === 0) return "";
21
+ if (!url) return tabs[0].id;
15
22
  const seg = firstSegment(url);
16
- if (!seg) return TABS[0].id;
17
- for (const tab of TABS) {
23
+ if (!seg) return tabs[0].id;
24
+ for (const tab of tabs) {
18
25
  if (tab.urlPrefix === `/${seg}`) return tab.id;
19
26
  }
20
- return TABS[0].id;
27
+ return tabs[0].id;
21
28
  }
22
29
  function urlInTab(url, tabId) {
23
30
  return tabForUrl(url) === tabId;
@@ -25,6 +32,8 @@ function urlInTab(url, tabId) {
25
32
  export {
26
33
  TABS,
27
34
  initTabs,
35
+ internalTabs,
36
+ isExternalTab,
28
37
  tabForUrl,
29
38
  urlInTab
30
39
  };
@@ -17,6 +17,24 @@ export interface VersionChip {
17
17
  version: string;
18
18
  dropdown?: boolean;
19
19
  }
20
+ /** One remote Pagefind index merged into the site's own search via
21
+ * `pagefind.mergeIndex()` — Pagefind's first-class multisite API. The
22
+ * remote site must serve its `/pagefind/` bundle with CORS
23
+ * (`Access-Control-Allow-Origin`) and a compatible index format (same
24
+ * Pagefind 1.x major). An index that fails to load degrades silently to
25
+ * local-only search. */
26
+ export interface MergeIndexDef {
27
+ /** Absolute URL of the remote bundle directory, e.g.
28
+ * "https://cli.dreamlake.ai/pagefind/". */
29
+ url: string;
30
+ /** Badge label shown on results from this index (e.g. "CLI"). */
31
+ label: string;
32
+ }
33
+ /** Search-layer options (runtime Pagefind behavior). */
34
+ export interface SearchConfig {
35
+ /** Remote Pagefind indexes to merge into the palette's results. */
36
+ mergeIndexes?: MergeIndexDef[];
37
+ }
20
38
  export interface SiteConfig {
21
39
  /** Brand name in the top-left of the shell (e.g. "DreamLake"). */
22
40
  brand: string;
@@ -72,6 +90,10 @@ export interface SiteConfig {
72
90
  * — http(s), protocol-relative, or absolute-path; anything else is
73
91
  * silently dropped. */
74
92
  headScripts?: string[];
93
+ /** Runtime search options — e.g. remote Pagefind indexes to merge into
94
+ * the palette (`search.mergeIndexes`). Also settable via the top-level
95
+ * `search` key of `initDocs()`. */
96
+ search?: SearchConfig;
75
97
  /** Search-engine visibility. DEFAULT FALSE per org policy: every page
76
98
  * gets `<meta name="robots" content="noindex, nofollow">` unless a site
77
99
  * explicitly opts in with `indexable: true`. Page-level `noindex`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dreamlake/dockit",
3
- "version": "0.2.12",
3
+ "version": "0.2.14",
4
4
  "description": "The DreamLake documentation shell — Vike + React + Tailwind v4 + MDX docs framework with sidebar, TOC, search, and theming",
5
5
  "license": "MIT",
6
6
  "type": "module",