@aglyn/plugins-mui 1.0.0-beta.214 → 1.0.0-beta.216

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 (59) hide show
  1. package/package.json +7 -7
  2. package/src/lib/components/collection-author-links.d.ts +41 -0
  3. package/src/lib/components/collection-author-links.js +135 -0
  4. package/src/lib/components/collection-author-links.js.map +1 -0
  5. package/src/lib/components/collection-author-profile.d.ts +67 -0
  6. package/src/lib/components/collection-author-profile.js +269 -0
  7. package/src/lib/components/collection-author-profile.js.map +1 -0
  8. package/src/lib/components/collection-categories.d.ts +52 -0
  9. package/src/lib/components/collection-categories.js +163 -0
  10. package/src/lib/components/collection-categories.js.map +1 -0
  11. package/src/lib/components/collection-common.d.ts +104 -0
  12. package/src/lib/components/collection-common.js +95 -0
  13. package/src/lib/components/collection-common.js.map +1 -0
  14. package/src/lib/components/collection-entries.d.ts +137 -0
  15. package/src/lib/components/collection-entries.js +358 -0
  16. package/src/lib/components/collection-entries.js.map +1 -0
  17. package/src/lib/components/collection-entry-author.d.ts +81 -0
  18. package/src/lib/components/collection-entry-author.js +259 -0
  19. package/src/lib/components/collection-entry-author.js.map +1 -0
  20. package/src/lib/components/collection-entry-body.d.ts +44 -0
  21. package/src/lib/components/collection-entry-body.js +328 -0
  22. package/src/lib/components/collection-entry-body.js.map +1 -0
  23. package/src/lib/components/collection-entry-meta.d.ts +88 -0
  24. package/src/lib/components/collection-entry-meta.js +331 -0
  25. package/src/lib/components/collection-entry-meta.js.map +1 -0
  26. package/src/lib/components/collection-presets.d.ts +18 -0
  27. package/src/lib/components/collection-presets.js +328 -0
  28. package/src/lib/components/collection-presets.js.map +1 -0
  29. package/src/lib/components/collection-related.d.ts +101 -0
  30. package/src/lib/components/collection-related.js +536 -0
  31. package/src/lib/components/collection-related.js.map +1 -0
  32. package/src/lib/components/collection-search-box.d.ts +92 -0
  33. package/src/lib/components/collection-search-box.js +334 -0
  34. package/src/lib/components/collection-search-box.js.map +1 -0
  35. package/src/lib/components/collection-search-fuse.d.ts +28 -0
  36. package/src/lib/components/collection-search-fuse.js +28 -0
  37. package/src/lib/components/collection-search-fuse.js.map +1 -0
  38. package/src/lib/components/collection-search.d.ts +62 -0
  39. package/src/lib/components/collection-search.js +140 -0
  40. package/src/lib/components/collection-search.js.map +1 -0
  41. package/src/lib/components/collection-share.d.ts +31 -0
  42. package/src/lib/components/collection-share.js +154 -0
  43. package/src/lib/components/collection-share.js.map +1 -0
  44. package/src/lib/components/collection.d.ts +19 -497
  45. package/src/lib/components/collection.js +19 -2982
  46. package/src/lib/components/collection.js.map +1 -1
  47. package/src/lib/components/image-list.d.ts +2 -2
  48. package/src/lib/components/image-list.js +4 -6
  49. package/src/lib/components/image-list.js.map +1 -1
  50. package/src/lib/components/theme-mode-switcher.js +19 -19
  51. package/src/lib/components/theme-mode-switcher.js.map +1 -1
  52. package/src/lib/element-tier.d.ts +9 -1
  53. package/src/lib/element-tier.js +12 -1
  54. package/src/lib/element-tier.js.map +1 -1
  55. package/src/lib/plugin.js +20 -12
  56. package/src/lib/plugin.js.map +1 -1
  57. package/src/lib/utils/to-count.d.ts +25 -0
  58. package/src/lib/utils/to-count.js +30 -0
  59. package/src/lib/utils/to-count.js.map +1 -0
@@ -0,0 +1,92 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import * as Aglyn from '@aglyn/aglyn';
18
+ import type { Fuse } from '@aglyn/shared-util-vendor/fuse';
19
+ /** What a built index can be searched by — Fuse over the stamped rows. */
20
+ type EntryFuse = InstanceType<typeof Fuse<Aglyn.CollectionEntrySearchItem>>;
21
+ /**
22
+ * The fuzzy matcher, fetched the first time a reader types into a collection
23
+ * search box (AGL-3401) — one fetch per visit, however many boxes ask.
24
+ *
25
+ * It was a static import, and `fuse.js` is 26 KB of the 68 KB this module
26
+ * weighed: every page that placed ANY collection element downloaded and
27
+ * parsed it, including the many whose entries block has no search at all and
28
+ * the rest, where most visitors never type. The box itself still renders on
29
+ * the server; only the matcher waits for a query.
30
+ *
31
+ * A failed fetch (offline, a deploy mid-visit) is forgotten rather than
32
+ * cached, so the next keystroke tries again instead of the box going dead.
33
+ */
34
+ export declare const loadCollectionFuse: () => Promise<typeof Fuse>;
35
+ /**
36
+ * The Fuse index over `items`, and the call that asks for the matcher.
37
+ *
38
+ * `fuzzy` is null until the first `requestFuzzy()` has resolved — a box
39
+ * nobody typed into never builds an index. A box mounted after another one
40
+ * loaded the matcher starts with it, so a second search on the page answers
41
+ * its first keystroke synchronously.
42
+ */
43
+ export declare const useEntryFuse: (items: readonly Aglyn.CollectionEntrySearchItem[] | undefined) => {
44
+ fuzzy: EntryFuse | null;
45
+ requestFuzzy: () => void;
46
+ };
47
+ /**
48
+ * The frame's search input (Figma 494:1220) — the magnify glyph and a bare
49
+ * `InputBase` on the quiet surface token.
50
+ *
51
+ * Presentational and stateless, so the entries block's in-place filter and
52
+ * the standalone toolbar box (AGL-1516) are the SAME field rather than two
53
+ * that merely look alike. `inert` is the editing-surface rendering: read-only
54
+ * and handler-free, so a click or stray keystroke in the besigner never edits
55
+ * state the canvas cannot use — how Category Pills go inert there too.
56
+ */
57
+ export declare const SearchField: ({ value, placeholder, inert, landmark, onQuery, onEscape, }: {
58
+ value: string;
59
+ placeholder?: string;
60
+ inert?: boolean;
61
+ /**
62
+ * Put `role="search"` on the FIELD. False when a wrapping form carries it
63
+ * instead — one search landmark per box, or a screen reader announces the
64
+ * same field twice.
65
+ */
66
+ landmark?: boolean;
67
+ onQuery?: (next: string) => void;
68
+ onEscape?: () => void;
69
+ }) => import("react").JSX.Element;
70
+ /**
71
+ * Field plus dropdown, in a REAL form (AGL-1525) — so Enter reaches the
72
+ * results page and the box still works with the panel's JS doing nothing at
73
+ * all. The panel is an enhancement over a working search box, not the search
74
+ * box.
75
+ *
76
+ * Owns the query, because nothing outside it needs one: `suggest` never
77
+ * touches the cards underneath. That is what lets the standalone toolbar
78
+ * block exist at all (AGL-1516) — a box with no clones to hide is a complete
79
+ * feature, not a crippled one.
80
+ */
81
+ export declare const SuggestSearchBox: ({ fuzzy, requestFuzzy, items, placeholder, inert, emptyText, }: {
82
+ /** Null until the matcher has loaded; see {@link useEntryFuse}. */
83
+ fuzzy: EntryFuse | null;
84
+ /** Asks for the matcher; called on every query, a no-op once it is here. */
85
+ requestFuzzy: () => void;
86
+ items?: readonly Aglyn.CollectionEntrySearchItem[];
87
+ placeholder?: string;
88
+ inert?: boolean;
89
+ /** The honest miss for the set THIS box searched. */
90
+ emptyText: (query: string) => string;
91
+ }) => import("react").JSX.Element;
92
+ export {};
@@ -0,0 +1,334 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ /**
4
+ * @license
5
+ * Copyright 2026 Aglyn LLC
6
+ *
7
+ * Licensed under the Apache License, Version 2.0 (the "License");
8
+ * you may not use this file except in compliance with the License.
9
+ * You may obtain a copy of the License at
10
+ *
11
+ * http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing, software
14
+ * distributed under the License is distributed on an "AS IS" BASIS,
15
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ * See the License for the specific language governing permissions and
17
+ * limitations under the License.
18
+ */ import * as Aglyn from "@aglyn/aglyn";
19
+ import { mdiMagnify } from "@aglyn/shared-data-mdi";
20
+ import { AppLink, MdiIcon } from "@aglyn/shared-ui-jsx";
21
+ import Box from "@mui/material/Box";
22
+ import Chip from "@mui/material/Chip";
23
+ import InputBase from "@mui/material/InputBase";
24
+ import MuiStack from "@mui/material/Stack";
25
+ import Typography from "@mui/material/Typography";
26
+ import { useMemo, useState } from "react";
27
+ /**
28
+ * The search box the Collection Entries block and the standalone Collection
29
+ * Search block both draw (AGL-1516/AGL-1525), and the fuzzy matcher behind
30
+ * it. Its own module so the other collection elements carry neither the MUI
31
+ * input nor the panel (AGL-3401); the matcher itself is fetched only when a
32
+ * reader types.
33
+ */ /** The toolbar search field, from the frame (Figma 494:1220). */ const SEARCH_FIELD_WIDTH = 240;
34
+ /** Suggestion panel (Figma 496:1218). */ const SUGGESTION_PANEL_WIDTH = 420;
35
+ /**
36
+ * Rows in the panel before it stops. The frame shows five; the sixth row is
37
+ * always the "View all results" link, which is the honest overflow answer —
38
+ * a scrolling suggestion panel is a worse results page.
39
+ */ const SUGGESTION_LIMIT = 5;
40
+ /** Where "View all results" goes — the reserved site-search route (AGL-88). */ const searchResultsHref = (query)=>`/search?q=${encodeURIComponent(query)}`;
41
+ /**
42
+ * The frame's compact filled field: magnify glyph + hint on the quiet
43
+ * surface token, right-aligned so it sits where the toolbar row puts it.
44
+ * Palette tokens only, so it follows the site theme in both modes.
45
+ */ const searchFieldSx = {
46
+ alignSelf: 'flex-end',
47
+ display: 'flex',
48
+ alignItems: 'center',
49
+ gap: 1,
50
+ px: 1.5,
51
+ py: 0.75,
52
+ borderRadius: 2,
53
+ bgcolor: 'action.hover',
54
+ color: 'text.secondary',
55
+ width: SEARCH_FIELD_WIDTH,
56
+ maxWidth: '100%'
57
+ };
58
+ /**
59
+ * The positioned parent of the suggestion panel (AGL-1525). It takes over
60
+ * `alignSelf` from the field so the toolbar row is unchanged — the panel
61
+ * hangs off the field, it does not move it.
62
+ */ const suggestionAnchorSx = {
63
+ alignSelf: 'flex-end',
64
+ position: 'relative',
65
+ width: SEARCH_FIELD_WIDTH,
66
+ maxWidth: '100%'
67
+ };
68
+ /**
69
+ * The floating panel itself. Right-aligned to the field it drops from, and
70
+ * wider than it, as the frame draws it — a suggestion row carries a title, a
71
+ * chip, a date and a line of excerpt, none of which fit in 240px.
72
+ *
73
+ * Palette and shadow tokens only, so it follows the site theme in both
74
+ * modes rather than pinning a light-mode card onto a dark page.
75
+ */ const suggestionPanelSx = {
76
+ position: 'absolute',
77
+ top: 'calc(100% + 8px)',
78
+ right: 0,
79
+ zIndex: 10,
80
+ width: SUGGESTION_PANEL_WIDTH,
81
+ // Never wider than the viewport on a phone, where the anchor is narrow
82
+ // and `right: 0` would otherwise hang the panel off the left edge.
83
+ maxWidth: 'calc(100vw - 32px)',
84
+ bgcolor: 'background.paper',
85
+ color: 'text.primary',
86
+ border: 1,
87
+ borderColor: 'divider',
88
+ borderRadius: 2,
89
+ boxShadow: 6,
90
+ overflow: 'hidden',
91
+ textAlign: 'left'
92
+ };
93
+ const suggestionRowLinkSx = {
94
+ display: 'block',
95
+ color: 'inherit',
96
+ '&:hover': {
97
+ bgcolor: 'action.hover'
98
+ }
99
+ };
100
+ const suggestionFooterSx = {
101
+ display: 'block',
102
+ px: 2,
103
+ py: 1.25,
104
+ borderTop: 1,
105
+ borderColor: 'divider',
106
+ bgcolor: 'action.hover',
107
+ fontSize: 14,
108
+ fontWeight: 500
109
+ };
110
+ /** The matcher's constructor, once something has asked for it. */ let loadedFuse;
111
+ let fuseLoad;
112
+ /**
113
+ * The fuzzy matcher, fetched the first time a reader types into a collection
114
+ * search box (AGL-3401) — one fetch per visit, however many boxes ask.
115
+ *
116
+ * It was a static import, and `fuse.js` is 26 KB of the 68 KB this module
117
+ * weighed: every page that placed ANY collection element downloaded and
118
+ * parsed it, including the many whose entries block has no search at all and
119
+ * the rest, where most visitors never type. The box itself still renders on
120
+ * the server; only the matcher waits for a query.
121
+ *
122
+ * A failed fetch (offline, a deploy mid-visit) is forgotten rather than
123
+ * cached, so the next keystroke tries again instead of the box going dead.
124
+ */ export const loadCollectionFuse = ()=>fuseLoad != null ? fuseLoad : fuseLoad = import("./collection-search-fuse.js").then((module)=>loadedFuse = module.Fuse, (error)=>{
125
+ fuseLoad = undefined;
126
+ throw error;
127
+ });
128
+ /**
129
+ * The Fuse index over `items`, and the call that asks for the matcher.
130
+ *
131
+ * `fuzzy` is null until the first `requestFuzzy()` has resolved — a box
132
+ * nobody typed into never builds an index. A box mounted after another one
133
+ * loaded the matcher starts with it, so a second search on the page answers
134
+ * its first keystroke synchronously.
135
+ */ export const useEntryFuse = (items)=>{
136
+ const [FuseConstructor, setFuseConstructor] = useState(()=>loadedFuse);
137
+ const fuzzy = useMemo(()=>FuseConstructor && (items == null ? void 0 : items.length) ? // results page the suggestion panel links to — a query the panel
138
+ // forgave must not come back empty from "View all results".
139
+ new FuseConstructor(items, _extends({}, Aglyn.COLLECTION_SEARCH_FUSE_OPTIONS)) : null, [
140
+ FuseConstructor,
141
+ items
142
+ ]);
143
+ const requestFuzzy = ()=>{
144
+ if (FuseConstructor) return;
145
+ loadCollectionFuse().then((loaded)=>setFuseConstructor(()=>loaded), // The box keeps working as a form that submits to `/search`; the next
146
+ // keystroke asks again.
147
+ ()=>undefined);
148
+ };
149
+ return {
150
+ fuzzy,
151
+ requestFuzzy
152
+ };
153
+ };
154
+ /**
155
+ * The frame's search input (Figma 494:1220) — the magnify glyph and a bare
156
+ * `InputBase` on the quiet surface token.
157
+ *
158
+ * Presentational and stateless, so the entries block's in-place filter and
159
+ * the standalone toolbar box (AGL-1516) are the SAME field rather than two
160
+ * that merely look alike. `inert` is the editing-surface rendering: read-only
161
+ * and handler-free, so a click or stray keystroke in the besigner never edits
162
+ * state the canvas cannot use — how Category Pills go inert there too.
163
+ */ export const SearchField = ({ value, placeholder, inert, landmark, onQuery, onEscape })=>/*#__PURE__*/ _jsxs(Box, _extends({}, landmark ? {
164
+ role: 'search'
165
+ } : {}, {
166
+ sx: searchFieldSx,
167
+ children: [
168
+ /*#__PURE__*/ _jsx(MdiIcon, {
169
+ path: mdiMagnify.path
170
+ }),
171
+ /*#__PURE__*/ _jsx(InputBase, _extends({
172
+ value: value,
173
+ name: "q",
174
+ placeholder: (placeholder != null ? placeholder : '').trim() || 'Search posts…',
175
+ inputProps: {
176
+ 'aria-label': 'Search entries'
177
+ },
178
+ sx: {
179
+ flex: 1,
180
+ fontSize: 14,
181
+ color: 'text.primary'
182
+ }
183
+ }, inert ? {
184
+ readOnly: true
185
+ } : {
186
+ onChange: (event)=>onQuery == null ? void 0 : onQuery(event.target.value),
187
+ onKeyDown: (event)=>{
188
+ if (event.key === 'Escape') onEscape == null ? void 0 : onEscape();
189
+ }
190
+ }))
191
+ ]
192
+ }));
193
+ /**
194
+ * The floating suggestion panel (AGL-1525, Figma 496:1218) — rows straight
195
+ * off the server-stamped index, and a "View all results" link that is there
196
+ * hits or no hits.
197
+ *
198
+ * `emptyText` is the caller's, not this component's, and that is the whole
199
+ * point of it being a parameter: a page-windowed entries block and a
200
+ * whole-collection toolbar box searched different sets, and a miss has to say
201
+ * WHICH set came back empty. A shared "No matches." would let one of them
202
+ * claim a reach it never had.
203
+ */ const SuggestionPanel = ({ suggestions, trimmed, emptyText })=>/*#__PURE__*/ _jsxs(Box, {
204
+ sx: suggestionPanelSx,
205
+ children: [
206
+ suggestions.length ? suggestions.map((item, index)=>{
207
+ const row = /*#__PURE__*/ _jsxs(MuiStack, {
208
+ spacing: 0.5,
209
+ sx: {
210
+ px: 2,
211
+ py: 1.25
212
+ },
213
+ children: [
214
+ /*#__PURE__*/ _jsxs(MuiStack, {
215
+ direction: "row",
216
+ spacing: 1,
217
+ sx: {
218
+ alignItems: 'center'
219
+ },
220
+ children: [
221
+ /*#__PURE__*/ _jsx(Typography, {
222
+ variant: "subtitle2",
223
+ sx: {
224
+ color: 'text.primary'
225
+ },
226
+ children: item.title
227
+ }),
228
+ item.category ? /*#__PURE__*/ _jsx(Chip, {
229
+ label: item.category,
230
+ size: "small"
231
+ }) : null,
232
+ item.date ? /*#__PURE__*/ _jsx(Typography, {
233
+ variant: "caption",
234
+ sx: {
235
+ color: 'text.secondary'
236
+ },
237
+ children: item.date
238
+ }) : null
239
+ ]
240
+ }),
241
+ item.excerpt ? /*#__PURE__*/ _jsx(Typography, {
242
+ variant: "body2",
243
+ sx: {
244
+ color: 'text.secondary',
245
+ // One line of excerpt, as the frame draws it. A row that
246
+ // grows with the prose turns the panel into a page.
247
+ overflow: 'hidden',
248
+ textOverflow: 'ellipsis',
249
+ whiteSpace: 'nowrap'
250
+ },
251
+ children: item.excerpt
252
+ }) : null
253
+ ]
254
+ });
255
+ // A row without a `url` is a page cached before AGL-1525 stamped one,
256
+ // or a slugless entry. It renders as TEXT rather than as a link to
257
+ // nowhere: a suggestion that navigates to the wrong page is worse
258
+ // than one the reader has to read.
259
+ return item.url ? /*#__PURE__*/ _jsx(AppLink, {
260
+ href: item.url,
261
+ sx: suggestionRowLinkSx,
262
+ underline: "none",
263
+ children: row
264
+ }, index) : /*#__PURE__*/ _jsx(Box, {
265
+ children: row
266
+ }, index);
267
+ }) : /*#__PURE__*/ _jsx(Typography, {
268
+ variant: "body2",
269
+ sx: {
270
+ color: 'text.secondary',
271
+ px: 2,
272
+ py: 1.25
273
+ },
274
+ children: emptyText
275
+ }),
276
+ /*#__PURE__*/ _jsx(AppLink, {
277
+ href: searchResultsHref(trimmed),
278
+ sx: suggestionFooterSx,
279
+ underline: "none",
280
+ children: `View all results for “${trimmed}” →`
281
+ })
282
+ ]
283
+ });
284
+ /**
285
+ * Field plus dropdown, in a REAL form (AGL-1525) — so Enter reaches the
286
+ * results page and the box still works with the panel's JS doing nothing at
287
+ * all. The panel is an enhancement over a working search box, not the search
288
+ * box.
289
+ *
290
+ * Owns the query, because nothing outside it needs one: `suggest` never
291
+ * touches the cards underneath. That is what lets the standalone toolbar
292
+ * block exist at all (AGL-1516) — a box with no clones to hide is a complete
293
+ * feature, not a crippled one.
294
+ */ export const SuggestSearchBox = ({ fuzzy, requestFuzzy, items, placeholder, inert, emptyText })=>{
295
+ const [query, setQuery] = useState('');
296
+ /** Escape dismissed THIS answer (AGL-1525); typing brings it back. */ const [closed, setClosed] = useState(false);
297
+ const trimmed = query.trim();
298
+ const suggestions = !inert && trimmed && fuzzy && items ? fuzzy.search(trimmed).slice(0, SUGGESTION_LIMIT).map((result)=>items[result.refIndex]).filter(Boolean) : [];
299
+ const field = /*#__PURE__*/ _jsx(SearchField, _extends({
300
+ value: query
301
+ }, placeholder === undefined ? {} : {
302
+ placeholder
303
+ }, inert ? {
304
+ inert: true,
305
+ landmark: true
306
+ } : {}, {
307
+ onQuery: (next)=>{
308
+ requestFuzzy();
309
+ setQuery(next);
310
+ // A new query reopens a panel the reader dismissed — Escape closes
311
+ // THIS answer, it does not turn the feature off for the visit.
312
+ setClosed(false);
313
+ },
314
+ onEscape: ()=>setClosed(true)
315
+ }));
316
+ if (inert) return field;
317
+ return /*#__PURE__*/ _jsxs(Box, {
318
+ component: "form",
319
+ role: "search",
320
+ action: "/search",
321
+ method: "get",
322
+ sx: suggestionAnchorSx,
323
+ children: [
324
+ field,
325
+ trimmed && !closed && fuzzy ? /*#__PURE__*/ _jsx(SuggestionPanel, {
326
+ suggestions: suggestions,
327
+ trimmed: trimmed,
328
+ emptyText: emptyText(trimmed)
329
+ }) : null
330
+ ]
331
+ });
332
+ };
333
+
334
+ //# sourceMappingURL=collection-search-box.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/mui/src/lib/components/collection-search-box.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport { mdiMagnify } from '@aglyn/shared-data-mdi'\nimport { AppLink, MdiIcon } from '@aglyn/shared-ui-jsx'\n// The icon picker's fuzzy matcher (use-mdi-icons-fuzzy), not a re-implementation\n// (AGL-1516): search here has to feel like search does everywhere else in the\n// product, and two matchers is how they drift. A TYPE here: the matcher itself\n// is fetched on a reader's first keystroke — see `loadCollectionFuse`.\nimport type { Fuse } from '@aglyn/shared-util-vendor/fuse'\nimport Box from '@mui/material/Box'\nimport Chip from '@mui/material/Chip'\nimport InputBase from '@mui/material/InputBase'\nimport MuiStack from '@mui/material/Stack'\nimport Typography from '@mui/material/Typography'\nimport { useMemo, useState } from 'react'\n\n/**\n * The search box the Collection Entries block and the standalone Collection\n * Search block both draw (AGL-1516/AGL-1525), and the fuzzy matcher behind\n * it. Its own module so the other collection elements carry neither the MUI\n * input nor the panel (AGL-3401); the matcher itself is fetched only when a\n * reader types.\n */\n\n/** The toolbar search field, from the frame (Figma 494:1220). */\nconst SEARCH_FIELD_WIDTH = 240\n\n/** Suggestion panel (Figma 496:1218). */\nconst SUGGESTION_PANEL_WIDTH = 420\n\n/**\n * Rows in the panel before it stops. The frame shows five; the sixth row is\n * always the \"View all results\" link, which is the honest overflow answer —\n * a scrolling suggestion panel is a worse results page.\n */\nconst SUGGESTION_LIMIT = 5\n\n/** Where \"View all results\" goes — the reserved site-search route (AGL-88). */\nconst searchResultsHref = (query: string) =>\n `/search?q=${encodeURIComponent(query)}`\n\n/**\n * The frame's compact filled field: magnify glyph + hint on the quiet\n * surface token, right-aligned so it sits where the toolbar row puts it.\n * Palette tokens only, so it follows the site theme in both modes.\n */\nconst searchFieldSx = {\n alignSelf: 'flex-end',\n display: 'flex',\n alignItems: 'center',\n gap: 1,\n px: 1.5,\n py: 0.75,\n borderRadius: 2,\n bgcolor: 'action.hover',\n color: 'text.secondary',\n width: SEARCH_FIELD_WIDTH,\n maxWidth: '100%',\n}\n\n/**\n * The positioned parent of the suggestion panel (AGL-1525). It takes over\n * `alignSelf` from the field so the toolbar row is unchanged — the panel\n * hangs off the field, it does not move it.\n */\nconst suggestionAnchorSx = {\n alignSelf: 'flex-end',\n position: 'relative',\n width: SEARCH_FIELD_WIDTH,\n maxWidth: '100%',\n}\n\n/**\n * The floating panel itself. Right-aligned to the field it drops from, and\n * wider than it, as the frame draws it — a suggestion row carries a title, a\n * chip, a date and a line of excerpt, none of which fit in 240px.\n *\n * Palette and shadow tokens only, so it follows the site theme in both\n * modes rather than pinning a light-mode card onto a dark page.\n */\nconst suggestionPanelSx = {\n position: 'absolute',\n top: 'calc(100% + 8px)',\n right: 0,\n zIndex: 10,\n width: SUGGESTION_PANEL_WIDTH,\n // Never wider than the viewport on a phone, where the anchor is narrow\n // and `right: 0` would otherwise hang the panel off the left edge.\n maxWidth: 'calc(100vw - 32px)',\n bgcolor: 'background.paper',\n color: 'text.primary',\n border: 1,\n borderColor: 'divider',\n borderRadius: 2,\n boxShadow: 6,\n overflow: 'hidden',\n textAlign: 'left',\n}\n\nconst suggestionRowLinkSx = {\n display: 'block',\n color: 'inherit',\n '&:hover': { bgcolor: 'action.hover' },\n}\n\nconst suggestionFooterSx = {\n display: 'block',\n px: 2,\n py: 1.25,\n borderTop: 1,\n borderColor: 'divider',\n bgcolor: 'action.hover',\n fontSize: 14,\n fontWeight: 500,\n}\n\n/** What a built index can be searched by — Fuse over the stamped rows. */\ntype EntryFuse = InstanceType<typeof Fuse<Aglyn.CollectionEntrySearchItem>>\n\n/** The matcher's constructor, once something has asked for it. */\nlet loadedFuse: typeof Fuse | undefined\nlet fuseLoad: Promise<typeof Fuse> | undefined\n\n/**\n * The fuzzy matcher, fetched the first time a reader types into a collection\n * search box (AGL-3401) — one fetch per visit, however many boxes ask.\n *\n * It was a static import, and `fuse.js` is 26 KB of the 68 KB this module\n * weighed: every page that placed ANY collection element downloaded and\n * parsed it, including the many whose entries block has no search at all and\n * the rest, where most visitors never type. The box itself still renders on\n * the server; only the matcher waits for a query.\n *\n * A failed fetch (offline, a deploy mid-visit) is forgotten rather than\n * cached, so the next keystroke tries again instead of the box going dead.\n */\nexport const loadCollectionFuse = (): Promise<typeof Fuse> =>\n (fuseLoad ??= import('./collection-search-fuse').then(\n (module) => (loadedFuse = module.Fuse),\n (error: unknown) => {\n fuseLoad = undefined\n throw error\n },\n ))\n\n/**\n * The Fuse index over `items`, and the call that asks for the matcher.\n *\n * `fuzzy` is null until the first `requestFuzzy()` has resolved — a box\n * nobody typed into never builds an index. A box mounted after another one\n * loaded the matcher starts with it, so a second search on the page answers\n * its first keystroke synchronously.\n */\nexport const useEntryFuse = (\n items: readonly Aglyn.CollectionEntrySearchItem[] | undefined,\n): { fuzzy: EntryFuse | null; requestFuzzy: () => void } => {\n const [FuseConstructor, setFuseConstructor] = useState<\n typeof Fuse | undefined\n >(() => loadedFuse)\n const fuzzy = useMemo(\n () =>\n FuseConstructor && items?.length\n ? // The ONE matcher config (AGL-1525), shared with the site-wide\n // results page the suggestion panel links to — a query the panel\n // forgave must not come back empty from \"View all results\".\n new FuseConstructor(items, { ...Aglyn.COLLECTION_SEARCH_FUSE_OPTIONS })\n : null,\n [FuseConstructor, items],\n )\n const requestFuzzy = () => {\n if (FuseConstructor) return\n loadCollectionFuse().then(\n (loaded) => setFuseConstructor(() => loaded),\n // The box keeps working as a form that submits to `/search`; the next\n // keystroke asks again.\n () => undefined,\n )\n }\n return { fuzzy, requestFuzzy }\n}\n\n/**\n * The frame's search input (Figma 494:1220) — the magnify glyph and a bare\n * `InputBase` on the quiet surface token.\n *\n * Presentational and stateless, so the entries block's in-place filter and\n * the standalone toolbar box (AGL-1516) are the SAME field rather than two\n * that merely look alike. `inert` is the editing-surface rendering: read-only\n * and handler-free, so a click or stray keystroke in the besigner never edits\n * state the canvas cannot use — how Category Pills go inert there too.\n */\nexport const SearchField = ({\n value,\n placeholder,\n inert,\n landmark,\n onQuery,\n onEscape,\n}: {\n value: string\n placeholder?: string\n inert?: boolean\n /**\n * Put `role=\"search\"` on the FIELD. False when a wrapping form carries it\n * instead — one search landmark per box, or a screen reader announces the\n * same field twice.\n */\n landmark?: boolean\n onQuery?: (next: string) => void\n onEscape?: () => void\n}) => (\n <Box {...(landmark ? { role: 'search' } : {})} sx={searchFieldSx}>\n <MdiIcon path={mdiMagnify.path} />\n <InputBase\n value={value}\n name=\"q\"\n placeholder={(placeholder ?? '').trim() || 'Search posts…'}\n inputProps={{ 'aria-label': 'Search entries' }}\n sx={{ flex: 1, fontSize: 14, color: 'text.primary' }}\n {...(inert\n ? { readOnly: true }\n : {\n onChange: (event) => onQuery?.(event.target.value),\n onKeyDown: (event) => {\n if (event.key === 'Escape') onEscape?.()\n },\n })}\n />\n </Box>\n)\n\n/**\n * The floating suggestion panel (AGL-1525, Figma 496:1218) — rows straight\n * off the server-stamped index, and a \"View all results\" link that is there\n * hits or no hits.\n *\n * `emptyText` is the caller's, not this component's, and that is the whole\n * point of it being a parameter: a page-windowed entries block and a\n * whole-collection toolbar box searched different sets, and a miss has to say\n * WHICH set came back empty. A shared \"No matches.\" would let one of them\n * claim a reach it never had.\n */\nconst SuggestionPanel = ({\n suggestions,\n trimmed,\n emptyText,\n}: {\n suggestions: readonly Aglyn.CollectionEntrySearchItem[]\n trimmed: string\n emptyText: string\n}) => (\n <Box sx={suggestionPanelSx}>\n {suggestions.length ? (\n suggestions.map((item, index) => {\n const row = (\n <MuiStack spacing={0.5} sx={{ px: 2, py: 1.25 }}>\n <MuiStack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"subtitle2\" sx={{ color: 'text.primary' }}>\n {item.title}\n </Typography>\n {item.category ? (\n <Chip label={item.category} size=\"small\" />\n ) : null}\n {item.date ? (\n <Typography variant=\"caption\" sx={{ color: 'text.secondary' }}>\n {item.date}\n </Typography>\n ) : null}\n </MuiStack>\n {item.excerpt ? (\n <Typography\n variant=\"body2\"\n sx={{\n color: 'text.secondary',\n // One line of excerpt, as the frame draws it. A row that\n // grows with the prose turns the panel into a page.\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n }}\n >\n {item.excerpt}\n </Typography>\n ) : null}\n </MuiStack>\n )\n // A row without a `url` is a page cached before AGL-1525 stamped one,\n // or a slugless entry. It renders as TEXT rather than as a link to\n // nowhere: a suggestion that navigates to the wrong page is worse\n // than one the reader has to read.\n return item.url ? (\n <AppLink\n key={index}\n href={item.url}\n sx={suggestionRowLinkSx}\n underline=\"none\"\n >\n {row}\n </AppLink>\n ) : (\n <Box key={index}>{row}</Box>\n )\n })\n ) : (\n <Typography\n variant=\"body2\"\n sx={{ color: 'text.secondary', px: 2, py: 1.25 }}\n >\n {emptyText}\n </Typography>\n )}\n {/*\n Always present, hits or no hits (Figma 496:1218). A box that searches\n one page, or one bounded read of a collection, cannot answer \"then\n where is it?\" on its own — the site-wide results page is the only\n honest answer, and it matters most in exactly the case where the panel\n came back empty.\n */}\n <AppLink\n href={searchResultsHref(trimmed)}\n sx={suggestionFooterSx}\n underline=\"none\"\n >\n {`View all results for “${trimmed}” →`}\n </AppLink>\n </Box>\n)\n\n/**\n * Field plus dropdown, in a REAL form (AGL-1525) — so Enter reaches the\n * results page and the box still works with the panel's JS doing nothing at\n * all. The panel is an enhancement over a working search box, not the search\n * box.\n *\n * Owns the query, because nothing outside it needs one: `suggest` never\n * touches the cards underneath. That is what lets the standalone toolbar\n * block exist at all (AGL-1516) — a box with no clones to hide is a complete\n * feature, not a crippled one.\n */\nexport const SuggestSearchBox = ({\n fuzzy,\n requestFuzzy,\n items,\n placeholder,\n inert,\n emptyText,\n}: {\n /** Null until the matcher has loaded; see {@link useEntryFuse}. */\n fuzzy: EntryFuse | null\n /** Asks for the matcher; called on every query, a no-op once it is here. */\n requestFuzzy: () => void\n items?: readonly Aglyn.CollectionEntrySearchItem[]\n placeholder?: string\n inert?: boolean\n /** The honest miss for the set THIS box searched. */\n emptyText: (query: string) => string\n}) => {\n const [query, setQuery] = useState('')\n /** Escape dismissed THIS answer (AGL-1525); typing brings it back. */\n const [closed, setClosed] = useState(false)\n const trimmed = query.trim()\n const suggestions =\n !inert && trimmed && fuzzy && items\n ? fuzzy\n .search(trimmed)\n .slice(0, SUGGESTION_LIMIT)\n .map((result) => items[result.refIndex])\n .filter(Boolean)\n : []\n const field = (\n <SearchField\n value={query}\n {...(placeholder === undefined ? {} : { placeholder })}\n // No form wraps the inert field, so the landmark belongs on it.\n {...(inert ? { inert: true, landmark: true } : {})}\n onQuery={(next) => {\n requestFuzzy()\n setQuery(next)\n // A new query reopens a panel the reader dismissed — Escape closes\n // THIS answer, it does not turn the feature off for the visit.\n setClosed(false)\n }}\n onEscape={() => setClosed(true)}\n />\n )\n if (inert) return field\n return (\n <Box\n component=\"form\"\n role=\"search\"\n action=\"/search\"\n method=\"get\"\n sx={suggestionAnchorSx}\n >\n {field}\n {/*\n Not until the matcher is here: a panel drawn in the moment it takes\n to arrive would say \"No matches\" about a query nothing has searched.\n */}\n {trimmed && !closed && fuzzy ? (\n <SuggestionPanel\n suggestions={suggestions}\n trimmed={trimmed}\n emptyText={emptyText(trimmed)}\n />\n ) : null}\n </Box>\n )\n}\n"],"names":["Aglyn","mdiMagnify","AppLink","MdiIcon","Box","Chip","InputBase","MuiStack","Typography","useMemo","useState","SEARCH_FIELD_WIDTH","SUGGESTION_PANEL_WIDTH","SUGGESTION_LIMIT","searchResultsHref","query","encodeURIComponent","searchFieldSx","alignSelf","display","alignItems","gap","px","py","borderRadius","bgcolor","color","width","maxWidth","suggestionAnchorSx","position","suggestionPanelSx","top","right","zIndex","border","borderColor","boxShadow","overflow","textAlign","suggestionRowLinkSx","suggestionFooterSx","borderTop","fontSize","fontWeight","loadedFuse","fuseLoad","loadCollectionFuse","then","module","Fuse","error","undefined","useEntryFuse","items","FuseConstructor","setFuseConstructor","fuzzy","length","COLLECTION_SEARCH_FUSE_OPTIONS","requestFuzzy","loaded","SearchField","value","placeholder","inert","landmark","onQuery","onEscape","role","sx","path","name","trim","inputProps","flex","readOnly","onChange","event","target","onKeyDown","key","SuggestionPanel","suggestions","trimmed","emptyText","map","item","index","row","spacing","direction","variant","title","category","label","size","date","excerpt","textOverflow","whiteSpace","url","href","underline","SuggestSearchBox","setQuery","closed","setClosed","search","slice","result","refIndex","filter","Boolean","field","next","component","action","method"],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /**
18
+ * The fuzzy matcher a collection search box fetches on its first keystroke
19
+ * (AGL-3401), behind a module of this package's own.
20
+ *
21
+ * `collection-search-box` `import()`s THIS file rather than the vendor
22
+ * package itself. The chunk is the same either way, but a dynamic import of
23
+ * another project is a lazy edge in the Nx graph, and because the apps load
24
+ * this plugin lazily too, every static import of `@aglyn/shared-util-vendor`
25
+ * in the console and the tenant became "a static import of a lazy-loaded
26
+ * library" — a lint error in files that never touch Fuse.
27
+ */
28
+ export { Fuse } from '@aglyn/shared-util-vendor/fuse';
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ /**
17
+ * The fuzzy matcher a collection search box fetches on its first keystroke
18
+ * (AGL-3401), behind a module of this package's own.
19
+ *
20
+ * `collection-search-box` `import()`s THIS file rather than the vendor
21
+ * package itself. The chunk is the same either way, but a dynamic import of
22
+ * another project is a lazy edge in the Nx graph, and because the apps load
23
+ * this plugin lazily too, every static import of `@aglyn/shared-util-vendor`
24
+ * in the console and the tenant became "a static import of a lazy-loaded
25
+ * library" — a lint error in files that never touch Fuse.
26
+ */ export { Fuse } from "@aglyn/shared-util-vendor/fuse";
27
+
28
+ //# sourceMappingURL=collection-search-fuse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/mui/src/lib/components/collection-search-fuse.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * The fuzzy matcher a collection search box fetches on its first keystroke\n * (AGL-3401), behind a module of this package's own.\n *\n * `collection-search-box` `import()`s THIS file rather than the vendor\n * package itself. The chunk is the same either way, but a dynamic import of\n * another project is a lazy edge in the Nx graph, and because the apps load\n * this plugin lazily too, every static import of `@aglyn/shared-util-vendor`\n * in the console and the tenant became \"a static import of a lazy-loaded\n * library\" — a lint error in files that never touch Fuse.\n */\nexport { Fuse } from '@aglyn/shared-util-vendor/fuse'\n"],"names":["Fuse"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;CAUC,GACD,SAASA,IAAI,QAAQ,iCAAgC"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import * as Aglyn from '@aglyn/aglyn';
18
+ import { type StackProps } from '@mui/material/Stack';
19
+ export declare const SEARCH_ID: Aglyn.ComponentId;
20
+ export interface CollectionSearchProps extends StackProps {
21
+ /**
22
+ * Collection whose entries this box searches (compose-time). Blank = the
23
+ * collection routed by the current URL on list-template screens.
24
+ */
25
+ collectionSlug?: string;
26
+ /** Hint text inside the box (blank = "Search posts…"). */
27
+ searchPlaceholder?: string;
28
+ /**
29
+ * Server-stamped matchable text per entry (`expandCollectionSearch`);
30
+ * never set by hand.
31
+ */
32
+ searchIndex?: Aglyn.CollectionEntrySearchItem[];
33
+ /** How many entries the index was drawn from; never set by hand. */
34
+ searchTotal?: number;
35
+ /** Whether that read reached its bound; never set by hand. */
36
+ searchCapped?: boolean;
37
+ }
38
+ /**
39
+ * A search box for a collection that lives where the author puts it
40
+ * (AGL-1516, Figma 494:1220).
41
+ *
42
+ * The entries block has had a search box since AGL-1516's first half, and it
43
+ * renders inside that block — first child of its own stack. The frame puts
44
+ * the field in the listing's TOOLBAR: category pills on the left, search and
45
+ * RSS on the right, one row. That was unbuildable, and unbuildable in both
46
+ * directions — a block's own child cannot be lifted out of it, and the pills
47
+ * cannot be moved in, because every child of an entries block is cloned once
48
+ * per entry. Three passes of authoring hit the same wall.
49
+ *
50
+ * So the field became its own block. It searches the collection the listing
51
+ * beside it is drawn from, and answers with the suggestion panel (496:1218)
52
+ * — the only behaviour a standalone box can honestly have, since it owns no
53
+ * cards to hide. Enter submits to the site-wide results page, so the box
54
+ * works with no JavaScript at all.
55
+ *
56
+ * Inert on editing surfaces, like Category Pills: the besigner has no index
57
+ * to search, and a field that answered there would be answering about
58
+ * nothing.
59
+ */
60
+ declare const CollectionSearch: import("react").ForwardRefExoticComponent<Omit<CollectionSearchProps, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
61
+ export declare const collectionSearchSchema: Aglyn.ComponentSchema<CollectionSearchProps>;
62
+ export { CollectionSearch };