@canopy-iiif/app 0.6.28

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,692 @@
1
+ // ui/src/Fallback.jsx
2
+ import React from "react";
3
+ function Fallback({ name, ...props }) {
4
+ const style = {
5
+ padding: "0.75rem 1rem",
6
+ border: "1px dashed #d1d5db",
7
+ color: "#6b7280",
8
+ borderRadius: 6,
9
+ background: "#f9fafb",
10
+ fontSize: 14
11
+ };
12
+ return /* @__PURE__ */ React.createElement("div", { style, "data-fallback-component": name || "Unknown" }, /* @__PURE__ */ React.createElement("strong", null, name || "Unknown component"), " not available in UI.");
13
+ }
14
+
15
+ // ui/src/HelloWorld.jsx
16
+ import React2 from "react";
17
+ var HelloWorld = () => {
18
+ return /* @__PURE__ */ React2.createElement("div", null, "Hello, World!");
19
+ };
20
+
21
+ // ui/src/layout/Card.jsx
22
+ import React3, { useEffect, useRef, useState } from "react";
23
+ function Card({
24
+ href,
25
+ src,
26
+ alt,
27
+ title,
28
+ subtitle,
29
+ // Optional intrinsic dimensions or aspect ratio to compute a responsive height
30
+ imgWidth,
31
+ imgHeight,
32
+ aspectRatio,
33
+ className,
34
+ style,
35
+ children,
36
+ ...rest
37
+ }) {
38
+ const containerRef = useRef(null);
39
+ const [inView, setInView] = useState(false);
40
+ const [imageLoaded, setImageLoaded] = useState(false);
41
+ useEffect(() => {
42
+ if (!containerRef.current) return;
43
+ if (typeof IntersectionObserver !== "function") {
44
+ setInView(true);
45
+ return;
46
+ }
47
+ const el = containerRef.current;
48
+ const obs = new IntersectionObserver(
49
+ (entries) => {
50
+ for (const entry of entries) {
51
+ if (entry.isIntersecting) {
52
+ setInView(true);
53
+ try {
54
+ obs.unobserve(el);
55
+ } catch (_) {
56
+ }
57
+ break;
58
+ }
59
+ }
60
+ },
61
+ { root: null, rootMargin: "100px", threshold: 0.1 }
62
+ );
63
+ try {
64
+ obs.observe(el);
65
+ } catch (_) {
66
+ }
67
+ return () => {
68
+ try {
69
+ obs.disconnect();
70
+ } catch (_) {
71
+ }
72
+ };
73
+ }, []);
74
+ const w = Number(imgWidth);
75
+ const h = Number(imgHeight);
76
+ const ratio = Number.isFinite(Number(aspectRatio)) && Number(aspectRatio) > 0 ? Number(aspectRatio) : Number.isFinite(w) && w > 0 && Number.isFinite(h) && h > 0 ? w / h : void 0;
77
+ const paddingPercent = ratio ? 100 / ratio : 100;
78
+ const caption = /* @__PURE__ */ React3.createElement("figcaption", null, title && /* @__PURE__ */ React3.createElement("span", null, title), subtitle && /* @__PURE__ */ React3.createElement("span", null, subtitle), children);
79
+ return /* @__PURE__ */ React3.createElement(
80
+ "a",
81
+ {
82
+ href,
83
+ className: ["canopy-card", className].filter(Boolean).join(" "),
84
+ style,
85
+ ref: containerRef,
86
+ "data-aspect-ratio": ratio,
87
+ "data-in-view": inView ? "true" : "false",
88
+ "data-image-loaded": imageLoaded ? "true" : "false",
89
+ ...rest
90
+ },
91
+ /* @__PURE__ */ React3.createElement("figure", null, src ? ratio ? /* @__PURE__ */ React3.createElement(
92
+ "div",
93
+ {
94
+ className: "canopy-card-media",
95
+ style: { "--canopy-card-padding": `${paddingPercent}%` }
96
+ },
97
+ inView ? /* @__PURE__ */ React3.createElement(
98
+ "img",
99
+ {
100
+ src,
101
+ alt: alt || title || "",
102
+ loading: "lazy",
103
+ onLoad: () => setImageLoaded(true),
104
+ onError: () => setImageLoaded(true)
105
+ }
106
+ ) : null
107
+ ) : /* @__PURE__ */ React3.createElement(
108
+ "img",
109
+ {
110
+ src,
111
+ alt: alt || title || "",
112
+ loading: "lazy",
113
+ onLoad: () => setImageLoaded(true),
114
+ onError: () => setImageLoaded(true),
115
+ className: "canopy-card-image"
116
+ }
117
+ ) : null, caption)
118
+ );
119
+ }
120
+
121
+ // ui/src/layout/Grid.jsx
122
+ import Masonry from "react-masonry-css";
123
+ import React4 from "react";
124
+ function GridItem({ children, className = "", style = {}, ...rest }) {
125
+ return /* @__PURE__ */ React4.createElement(
126
+ "div",
127
+ {
128
+ className: `canopy-grid-item ${className}`.trim(),
129
+ style,
130
+ ...rest
131
+ },
132
+ children
133
+ );
134
+ }
135
+ function Grid({
136
+ breakpointCols,
137
+ gap = "2rem",
138
+ paddingY = "0",
139
+ className = "",
140
+ style = {},
141
+ columnClassName = "canopy-grid-column",
142
+ children,
143
+ ...rest
144
+ }) {
145
+ const cols = breakpointCols || {
146
+ default: 6,
147
+ 1280: 5,
148
+ 1024: 4,
149
+ 768: 3,
150
+ 640: 2
151
+ };
152
+ const vars = { "--grid-gap": gap, "--grid-padding-y": paddingY };
153
+ return /* @__PURE__ */ React4.createElement("div", { className: "canopy-grid-wrap" }, /* @__PURE__ */ React4.createElement(
154
+ "style",
155
+ {
156
+ dangerouslySetInnerHTML: {
157
+ __html: `
158
+ .canopy-grid { display: flex; width: auto; position: relative; padding: var(--grid-padding-y, 0) 0; z-index: 1; }
159
+ .canopy-grid .${columnClassName} { margin-left: var(--grid-gap, 1rem); }
160
+ .canopy-grid .${columnClassName}:first-child { margin-left: 0; }
161
+ .canopy-grid-item { margin-bottom: var(--grid-gap, 1rem); }
162
+ `
163
+ }
164
+ }
165
+ ), /* @__PURE__ */ React4.createElement(
166
+ Masonry,
167
+ {
168
+ breakpointCols: cols,
169
+ className: `canopy-grid ${className}`.trim(),
170
+ columnClassName,
171
+ style: { ...vars, ...style },
172
+ ...rest
173
+ },
174
+ children
175
+ ));
176
+ }
177
+
178
+ // ui/src/iiif/Viewer.jsx
179
+ import React5, { useEffect as useEffect2, useState as useState2 } from "react";
180
+ var DEFAULT_VIEWER_OPTIONS = {
181
+ showDownload: false,
182
+ showIIIFBadge: false,
183
+ showTitle: false,
184
+ informationPanel: {
185
+ open: false,
186
+ renderAbout: false,
187
+ renderToggle: false
188
+ }
189
+ };
190
+ function isPlainObject(val) {
191
+ return val && typeof val === "object" && !Array.isArray(val);
192
+ }
193
+ function deepMerge(base, override) {
194
+ if (!isPlainObject(base)) return override;
195
+ const out = { ...base };
196
+ if (!isPlainObject(override)) return out;
197
+ for (const key of Object.keys(override)) {
198
+ const a = base[key];
199
+ const b = override[key];
200
+ if (isPlainObject(a) && isPlainObject(b)) out[key] = deepMerge(a, b);
201
+ else out[key] = b;
202
+ }
203
+ return out;
204
+ }
205
+ var Viewer = (props) => {
206
+ const [CloverViewer, setCloverViewer] = useState2(null);
207
+ const mergedOptions = deepMerge(
208
+ DEFAULT_VIEWER_OPTIONS,
209
+ props && props.options
210
+ );
211
+ useEffect2(() => {
212
+ let mounted = true;
213
+ const canUseDom = typeof window !== "undefined" && typeof document !== "undefined";
214
+ if (canUseDom) {
215
+ import("@samvera/clover-iiif/viewer").then((mod) => {
216
+ if (!mounted) return;
217
+ const Comp = mod && (mod.default || mod.Viewer || mod);
218
+ setCloverViewer(() => Comp);
219
+ }).catch(() => {
220
+ });
221
+ }
222
+ return () => {
223
+ mounted = false;
224
+ };
225
+ }, []);
226
+ if (!CloverViewer) {
227
+ let json = "{}";
228
+ try {
229
+ const p = { ...props || {} };
230
+ if (mergedOptions) p.options = mergedOptions;
231
+ json = JSON.stringify(p);
232
+ } catch (_) {
233
+ json = "{}";
234
+ }
235
+ return /* @__PURE__ */ React5.createElement("div", { "data-canopy-viewer": "1", className: "not-prose" }, /* @__PURE__ */ React5.createElement(
236
+ "script",
237
+ {
238
+ type: "application/json",
239
+ dangerouslySetInnerHTML: { __html: json }
240
+ }
241
+ ));
242
+ }
243
+ return /* @__PURE__ */ React5.createElement(CloverViewer, { ...props, options: mergedOptions });
244
+ };
245
+
246
+ // ui/src/iiif/Slider.jsx
247
+ import React6, { useEffect as useEffect3, useState as useState3 } from "react";
248
+ var Slider = (props) => {
249
+ const [CloverSlider, setCloverSlider] = useState3(null);
250
+ useEffect3(() => {
251
+ let mounted = true;
252
+ const canUseDom = typeof window !== "undefined" && typeof document !== "undefined";
253
+ if (canUseDom) {
254
+ import("@samvera/clover-iiif/slider").then((mod) => {
255
+ if (!mounted) return;
256
+ console.log(mod);
257
+ const Comp = mod && (mod.default || mod.Slider || mod);
258
+ setCloverSlider(() => Comp);
259
+ }).catch(() => {
260
+ });
261
+ }
262
+ return () => {
263
+ mounted = false;
264
+ };
265
+ }, []);
266
+ if (!CloverSlider) {
267
+ let json = "{}";
268
+ try {
269
+ json = JSON.stringify(props || {});
270
+ } catch (_) {
271
+ json = "{}";
272
+ }
273
+ return /* @__PURE__ */ React6.createElement("div", { "data-canopy-slider": "1", className: "not-prose" }, /* @__PURE__ */ React6.createElement(
274
+ "script",
275
+ {
276
+ type: "application/json",
277
+ dangerouslySetInnerHTML: { __html: json }
278
+ }
279
+ ));
280
+ }
281
+ return /* @__PURE__ */ React6.createElement(CloverSlider, { ...props });
282
+ };
283
+
284
+ // ui/src/iiif/MdxRelatedItems.jsx
285
+ import React7 from "react";
286
+ function MdxRelatedItems(props) {
287
+ let json = "{}";
288
+ try {
289
+ json = JSON.stringify(props || {});
290
+ } catch (_) {
291
+ json = "{}";
292
+ }
293
+ return /* @__PURE__ */ React7.createElement("div", { "data-canopy-related-items": "1", className: "not-prose" }, /* @__PURE__ */ React7.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: json } }));
294
+ }
295
+
296
+ // ui/src/search/MdxSearchForm.jsx
297
+ import React8 from "react";
298
+ function MdxSearchForm(props) {
299
+ let json = "{}";
300
+ try {
301
+ json = JSON.stringify(props || {});
302
+ } catch (_) {
303
+ json = "{}";
304
+ }
305
+ return /* @__PURE__ */ React8.createElement("div", { "data-canopy-search-form": "1" }, /* @__PURE__ */ React8.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: json } }));
306
+ }
307
+
308
+ // ui/src/search/MdxSearchResults.jsx
309
+ import React9 from "react";
310
+ function MdxSearchResults(props) {
311
+ let json = "{}";
312
+ try {
313
+ json = JSON.stringify(props || {});
314
+ } catch (_) {
315
+ json = "{}";
316
+ }
317
+ return /* @__PURE__ */ React9.createElement("div", { "data-canopy-search-results": "1" }, /* @__PURE__ */ React9.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: json } }));
318
+ }
319
+
320
+ // ui/src/search/SearchSummary.jsx
321
+ import React10 from "react";
322
+ function SearchSummary(props) {
323
+ let json = "{}";
324
+ try {
325
+ json = JSON.stringify(props || {});
326
+ } catch (_) {
327
+ json = "{}";
328
+ }
329
+ return /* @__PURE__ */ React10.createElement("div", { "data-canopy-search-summary": "1" }, /* @__PURE__ */ React10.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: json } }));
330
+ }
331
+
332
+ // ui/src/search/SearchTotal.jsx
333
+ import React11 from "react";
334
+ function SearchTotal(props) {
335
+ let json = "{}";
336
+ try {
337
+ json = JSON.stringify(props || {});
338
+ } catch (_) {
339
+ json = "{}";
340
+ }
341
+ return /* @__PURE__ */ React11.createElement("div", { "data-canopy-search-total": "1" }, /* @__PURE__ */ React11.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: json } }));
342
+ }
343
+
344
+ // ui/src/search/SearchForm.jsx
345
+ import React12 from "react";
346
+ function SearchForm({ query, onQueryChange, type = "all", onTypeChange, types = [], counts = {} }) {
347
+ const orderedTypes = Array.isArray(types) ? types : [];
348
+ const toLabel = (t) => t && t.length ? t.charAt(0).toUpperCase() + t.slice(1) : "";
349
+ return /* @__PURE__ */ React12.createElement("form", { onSubmit: (e) => e.preventDefault(), className: "space-y-3" }, /* @__PURE__ */ React12.createElement(
350
+ "input",
351
+ {
352
+ id: "search-input",
353
+ type: "search",
354
+ value: query,
355
+ placeholder: "Type to search\u2026",
356
+ onChange: (e) => onQueryChange && onQueryChange(e.target.value),
357
+ className: "w-full px-3 py-2 border border-slate-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-500 focus:border-brand-500"
358
+ }
359
+ ), /* @__PURE__ */ React12.createElement("div", { role: "tablist", "aria-label": "Search types", className: "flex items-center gap-2 border-b border-slate-200" }, orderedTypes.map((t) => {
360
+ const active = String(type).toLowerCase() === String(t).toLowerCase();
361
+ const cRaw = counts && Object.prototype.hasOwnProperty.call(counts, t) ? counts[t] : void 0;
362
+ const c = Number.isFinite(Number(cRaw)) ? Number(cRaw) : 0;
363
+ return /* @__PURE__ */ React12.createElement(
364
+ "button",
365
+ {
366
+ key: t,
367
+ role: "tab",
368
+ "aria-selected": active,
369
+ type: "button",
370
+ onClick: () => onTypeChange && onTypeChange(t),
371
+ className: "px-3 py-1.5 text-sm rounded-t-md border-b-2 -mb-px transition-colors " + (active ? "border-brand-600 text-brand-700" : "border-transparent text-slate-600 hover:text-slate-900 hover:border-slate-300")
372
+ },
373
+ toLabel(t),
374
+ " (",
375
+ c,
376
+ ")"
377
+ );
378
+ })));
379
+ }
380
+
381
+ // ui/src/search/SearchResults.jsx
382
+ import React13 from "react";
383
+ function SearchResults({
384
+ results = [],
385
+ type = "all",
386
+ layout = "grid"
387
+ }) {
388
+ if (!results.length) {
389
+ return /* @__PURE__ */ React13.createElement("div", { className: "text-slate-600" }, /* @__PURE__ */ React13.createElement("em", null, "No results"));
390
+ }
391
+ if (layout === "list") {
392
+ return /* @__PURE__ */ React13.createElement("ul", { id: "search-results", className: "space-y-3" }, results.map((r, i) => {
393
+ const hasDims = Number.isFinite(Number(r.thumbnailWidth)) && Number(r.thumbnailWidth) > 0 && Number.isFinite(Number(r.thumbnailHeight)) && Number(r.thumbnailHeight) > 0;
394
+ const aspect = hasDims ? Number(r.thumbnailWidth) / Number(r.thumbnailHeight) : void 0;
395
+ return /* @__PURE__ */ React13.createElement(
396
+ "li",
397
+ {
398
+ key: i,
399
+ className: `search-result ${r.type}`,
400
+ "data-thumbnail-aspect-ratio": aspect
401
+ },
402
+ /* @__PURE__ */ React13.createElement(
403
+ Card,
404
+ {
405
+ href: r.href,
406
+ title: r.title || r.href,
407
+ src: r.type === "work" ? r.thumbnail : void 0,
408
+ imgWidth: r.thumbnailWidth,
409
+ imgHeight: r.thumbnailHeight,
410
+ aspectRatio: aspect
411
+ }
412
+ )
413
+ );
414
+ }));
415
+ }
416
+ return /* @__PURE__ */ React13.createElement("div", { id: "search-results" }, /* @__PURE__ */ React13.createElement(Grid, null, results.map((r, i) => {
417
+ const hasDims = Number.isFinite(Number(r.thumbnailWidth)) && Number(r.thumbnailWidth) > 0 && Number.isFinite(Number(r.thumbnailHeight)) && Number(r.thumbnailHeight) > 0;
418
+ const aspect = hasDims ? Number(r.thumbnailWidth) / Number(r.thumbnailHeight) : void 0;
419
+ return /* @__PURE__ */ React13.createElement(
420
+ GridItem,
421
+ {
422
+ key: i,
423
+ className: `search-result ${r.type}`,
424
+ "data-thumbnail-aspect-ratio": aspect
425
+ },
426
+ /* @__PURE__ */ React13.createElement(
427
+ Card,
428
+ {
429
+ href: r.href,
430
+ title: r.title || r.href,
431
+ src: r.type === "work" ? r.thumbnail : void 0,
432
+ imgWidth: r.thumbnailWidth,
433
+ imgHeight: r.thumbnailHeight,
434
+ aspectRatio: aspect
435
+ }
436
+ )
437
+ );
438
+ })));
439
+ }
440
+
441
+ // ui/src/search/useSearch.js
442
+ import { useEffect as useEffect4, useMemo, useRef as useRef2, useState as useState4 } from "react";
443
+ function useSearch(query, type) {
444
+ const [records, setRecords] = useState4([]);
445
+ const [loading, setLoading] = useState4(true);
446
+ const indexRef = useRef2(null);
447
+ const idToRecRef = useRef2([]);
448
+ const [types, setTypes] = useState4([]);
449
+ useEffect4(() => {
450
+ let cancelled = false;
451
+ setLoading(true);
452
+ import("flexsearch").then((mod) => {
453
+ const FlexSearch = mod.default || mod;
454
+ return fetch("./search-index.json").then((r) => r.ok ? r.json() : []).catch(() => []).then((data) => {
455
+ if (cancelled) return;
456
+ const idx = new FlexSearch.Index({ tokenize: "forward" });
457
+ const idToRec = [];
458
+ data.forEach((rec, i) => {
459
+ try {
460
+ idx.add(i, rec && rec.title ? String(rec.title) : "");
461
+ } catch (_) {
462
+ }
463
+ idToRec[i] = rec || {};
464
+ });
465
+ const ts = Array.from(
466
+ new Set(data.map((r) => String(r && r.type || "page")))
467
+ );
468
+ const order = ["work", "docs", "page"];
469
+ ts.sort((a, b) => {
470
+ const ia = order.indexOf(a);
471
+ const ib = order.indexOf(b);
472
+ return (ia < 0 ? 99 : ia) - (ib < 0 ? 99 : ib) || a.localeCompare(b);
473
+ });
474
+ indexRef.current = idx;
475
+ idToRecRef.current = idToRec;
476
+ setRecords(data);
477
+ setTypes(ts);
478
+ setLoading(false);
479
+ });
480
+ });
481
+ return () => {
482
+ cancelled = true;
483
+ };
484
+ }, []);
485
+ const results = useMemo(() => {
486
+ const all = idToRecRef.current;
487
+ if (!all || !all.length) return [];
488
+ const t = String(type || "all").toLowerCase();
489
+ if (!query) {
490
+ return all.filter((r) => t === "all" ? true : String(r.type).toLowerCase() === t);
491
+ }
492
+ let ids = [];
493
+ try {
494
+ ids = indexRef.current && indexRef.current.search(query, { limit: 200 }) || [];
495
+ } catch (_) {
496
+ ids = [];
497
+ }
498
+ const out = [];
499
+ for (const id of Array.isArray(ids) ? ids : []) {
500
+ const rec = all[id];
501
+ if (!rec) continue;
502
+ if (t !== "all" && String(rec.type).toLowerCase() !== t) continue;
503
+ out.push(rec);
504
+ }
505
+ return out;
506
+ }, [query, type, records]);
507
+ return { results, total: records.length || 0, loading, types };
508
+ }
509
+
510
+ // ui/src/search/Search.jsx
511
+ import React14 from "react";
512
+ function Search(props) {
513
+ let json = "{}";
514
+ try {
515
+ json = JSON.stringify(props || {});
516
+ } catch (_) {
517
+ json = "{}";
518
+ }
519
+ return /* @__PURE__ */ React14.createElement("div", { "data-canopy-search": "1", className: "not-prose" }, /* @__PURE__ */ React14.createElement(
520
+ "script",
521
+ {
522
+ type: "application/json",
523
+ dangerouslySetInnerHTML: { __html: json }
524
+ }
525
+ ));
526
+ }
527
+
528
+ // ui/src/command/MdxCommandPalette.jsx
529
+ import React15 from "react";
530
+ function MdxCommandPalette(props = {}) {
531
+ const {
532
+ placeholder = "Search\u2026",
533
+ hotkey = "mod+k",
534
+ maxResults = 8,
535
+ groupOrder = ["work", "page"],
536
+ button = true,
537
+ buttonLabel = "Search"
538
+ } = props || {};
539
+ const data = { placeholder, hotkey, maxResults, groupOrder };
540
+ return /* @__PURE__ */ React15.createElement("div", { "data-canopy-command": true }, button && /* @__PURE__ */ React15.createElement(
541
+ "button",
542
+ {
543
+ type: "button",
544
+ "data-canopy-command-trigger": true,
545
+ className: "inline-flex items-center gap-1 px-2 py-1 rounded border border-slate-300 text-slate-700 hover:bg-slate-50",
546
+ "aria-label": "Open search"
547
+ },
548
+ /* @__PURE__ */ React15.createElement("span", { "aria-hidden": true }, "\u2318K"),
549
+ /* @__PURE__ */ React15.createElement("span", { className: "sr-only" }, buttonLabel)
550
+ ), /* @__PURE__ */ React15.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: JSON.stringify(data) } }));
551
+ }
552
+
553
+ // ui/src/command/CommandApp.jsx
554
+ import React16, { useEffect as useEffect5, useMemo as useMemo2, useState as useState5 } from "react";
555
+ import { Command } from "cmdk";
556
+ function normalize(s) {
557
+ try {
558
+ return String(s || "").toLowerCase();
559
+ } catch (e) {
560
+ return "";
561
+ }
562
+ }
563
+ function groupLabel(t) {
564
+ const type = String(t || "").toLowerCase();
565
+ if (type === "work") return "Works";
566
+ if (type === "page") return "Pages";
567
+ return type.charAt(0).toUpperCase() + type.slice(1);
568
+ }
569
+ function CommandPaletteApp(props) {
570
+ const {
571
+ records = [],
572
+ loading = false,
573
+ open: controlledOpen,
574
+ onOpenChange,
575
+ onSelect = () => {
576
+ },
577
+ config = {}
578
+ } = props || {};
579
+ const {
580
+ placeholder = "Search\u2026",
581
+ hotkey = "mod+k",
582
+ maxResults = 8,
583
+ groupOrder = ["work", "page"],
584
+ button = true,
585
+ buttonLabel = "Search"
586
+ } = config || {};
587
+ const [open, setOpen] = useState5(!!controlledOpen);
588
+ useEffect5(() => {
589
+ if (typeof controlledOpen === "boolean") setOpen(controlledOpen);
590
+ }, [controlledOpen]);
591
+ const setOpenBoth = (v) => {
592
+ setOpen(!!v);
593
+ if (onOpenChange) onOpenChange(!!v);
594
+ };
595
+ const [q, setQ] = useState5("");
596
+ useEffect5(() => {
597
+ function handler(e) {
598
+ try {
599
+ const hk = String(hotkey || "mod+k").toLowerCase();
600
+ const isMod = hk.includes("mod+");
601
+ const key = hk.split("+").pop();
602
+ if ((isMod ? e.metaKey || e.ctrlKey : true) && e.key.toLowerCase() === String(key || "k")) {
603
+ e.preventDefault();
604
+ setOpenBoth(true);
605
+ }
606
+ } catch (e2) {
607
+ }
608
+ }
609
+ document.addEventListener("keydown", handler);
610
+ return () => document.removeEventListener("keydown", handler);
611
+ }, [hotkey]);
612
+ useEffect5(() => {
613
+ function onKey(e) {
614
+ if (e.key === "Escape") setOpenBoth(false);
615
+ }
616
+ document.addEventListener("keydown", onKey);
617
+ return () => document.removeEventListener("keydown", onKey);
618
+ }, []);
619
+ const results = useMemo2(() => {
620
+ if (!q) return [];
621
+ const qq = normalize(q);
622
+ const out = [];
623
+ for (const r of records || []) {
624
+ const title = String(r && r.title || "");
625
+ if (!title) continue;
626
+ if (normalize(title).includes(qq)) out.push(r);
627
+ if (out.length >= Math.max(1, Number(maxResults) || 8)) break;
628
+ }
629
+ return out;
630
+ }, [q, records, maxResults]);
631
+ const grouped = useMemo2(() => {
632
+ const map = /* @__PURE__ */ new Map();
633
+ for (const r of results) {
634
+ const t = String(r && r.type || "page");
635
+ if (!map.has(t)) map.set(t, []);
636
+ map.get(t).push(r);
637
+ }
638
+ return map;
639
+ }, [results]);
640
+ const onOverlayMouseDown = (e) => {
641
+ if (e.target === e.currentTarget) setOpenBoth(false);
642
+ };
643
+ const onItemSelect = (href) => {
644
+ try {
645
+ onSelect(String(href || ""));
646
+ setOpenBoth(false);
647
+ } catch (e) {
648
+ }
649
+ };
650
+ return /* @__PURE__ */ React16.createElement("div", { className: "canopy-cmdk" }, button && /* @__PURE__ */ React16.createElement(
651
+ "button",
652
+ {
653
+ type: "button",
654
+ className: "canopy-cmdk__trigger",
655
+ onClick: () => setOpenBoth(true),
656
+ "aria-label": "Open search",
657
+ "data-canopy-command-trigger": true
658
+ },
659
+ /* @__PURE__ */ React16.createElement("span", { "aria-hidden": true }, "\u2318K"),
660
+ /* @__PURE__ */ React16.createElement("span", { className: "sr-only" }, buttonLabel)
661
+ ), /* @__PURE__ */ React16.createElement(
662
+ "div",
663
+ {
664
+ className: "canopy-cmdk__overlay",
665
+ "data-open": open ? "1" : "0",
666
+ onMouseDown: onOverlayMouseDown,
667
+ style: { display: open ? "flex" : "none" }
668
+ },
669
+ /* @__PURE__ */ React16.createElement("div", { className: "canopy-cmdk__panel" }, /* @__PURE__ */ React16.createElement("button", { className: "canopy-cmdk__close", "aria-label": "Close", onClick: () => setOpenBoth(false) }, "\xD7"), /* @__PURE__ */ React16.createElement("div", { className: "canopy-cmdk__inputWrap" }, /* @__PURE__ */ React16.createElement(Command, null, /* @__PURE__ */ React16.createElement(Command.Input, { autoFocus: true, value: q, onValueChange: setQ, placeholder, className: "canopy-cmdk__input" }), /* @__PURE__ */ React16.createElement(Command.List, { className: "canopy-cmdk__list" }, loading && /* @__PURE__ */ React16.createElement(Command.Loading, null, "Hang on\u2026"), /* @__PURE__ */ React16.createElement(Command.Empty, null, "No results found."), (Array.isArray(groupOrder) ? groupOrder : []).map((t) => grouped.has(t) ? /* @__PURE__ */ React16.createElement(Command.Group, { key: t, heading: groupLabel(t) }, grouped.get(t).map((r, i) => /* @__PURE__ */ React16.createElement(Command.Item, { key: t + "-" + i, onSelect: () => onItemSelect(r.href) }, /* @__PURE__ */ React16.createElement("div", { className: "canopy-cmdk__item" }, String(r.type || "") === "work" && r.thumbnail ? /* @__PURE__ */ React16.createElement("img", { className: "canopy-cmdk__thumb", src: r.thumbnail, alt: "" }) : null, /* @__PURE__ */ React16.createElement("span", { className: "canopy-cmdk__title" }, r.title))))) : null), Array.from(grouped.keys()).filter((t) => !(groupOrder || []).includes(t)).map((t) => /* @__PURE__ */ React16.createElement(Command.Group, { key: t, heading: groupLabel(t) }, grouped.get(t).map((r, i) => /* @__PURE__ */ React16.createElement(Command.Item, { key: t + "-x-" + i, onSelect: () => onItemSelect(r.href) }, /* @__PURE__ */ React16.createElement("div", { className: "canopy-cmdk__item" }, String(r.type || "") === "work" && r.thumbnail ? /* @__PURE__ */ React16.createElement("img", { className: "canopy-cmdk__thumb", src: r.thumbnail, alt: "" }) : null, /* @__PURE__ */ React16.createElement("span", { className: "canopy-cmdk__title" }, r.title))))))))))
670
+ ));
671
+ }
672
+ export {
673
+ Card,
674
+ MdxCommandPalette as CommandPalette,
675
+ CommandPaletteApp,
676
+ Fallback,
677
+ Grid,
678
+ GridItem,
679
+ HelloWorld,
680
+ MdxRelatedItems as RelatedItems,
681
+ Search,
682
+ MdxSearchForm as SearchForm,
683
+ SearchForm as SearchFormUI,
684
+ MdxSearchResults as SearchResults,
685
+ SearchResults as SearchResultsUI,
686
+ SearchSummary,
687
+ SearchTotal,
688
+ Slider,
689
+ Viewer,
690
+ useSearch
691
+ };
692
+ //# sourceMappingURL=index.mjs.map