@aglyn/plugins-mui 1.0.0-beta.215 → 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.
- package/package.json +7 -7
- package/src/lib/components/collection-author-links.d.ts +41 -0
- package/src/lib/components/collection-author-links.js +135 -0
- package/src/lib/components/collection-author-links.js.map +1 -0
- package/src/lib/components/collection-author-profile.d.ts +67 -0
- package/src/lib/components/collection-author-profile.js +269 -0
- package/src/lib/components/collection-author-profile.js.map +1 -0
- package/src/lib/components/collection-categories.d.ts +52 -0
- package/src/lib/components/collection-categories.js +163 -0
- package/src/lib/components/collection-categories.js.map +1 -0
- package/src/lib/components/collection-common.d.ts +104 -0
- package/src/lib/components/collection-common.js +95 -0
- package/src/lib/components/collection-common.js.map +1 -0
- package/src/lib/components/collection-entries.d.ts +137 -0
- package/src/lib/components/collection-entries.js +358 -0
- package/src/lib/components/collection-entries.js.map +1 -0
- package/src/lib/components/collection-entry-author.d.ts +81 -0
- package/src/lib/components/collection-entry-author.js +259 -0
- package/src/lib/components/collection-entry-author.js.map +1 -0
- package/src/lib/components/collection-entry-body.d.ts +44 -0
- package/src/lib/components/collection-entry-body.js +328 -0
- package/src/lib/components/collection-entry-body.js.map +1 -0
- package/src/lib/components/collection-entry-meta.d.ts +88 -0
- package/src/lib/components/collection-entry-meta.js +331 -0
- package/src/lib/components/collection-entry-meta.js.map +1 -0
- package/src/lib/components/collection-presets.d.ts +18 -0
- package/src/lib/components/collection-presets.js +328 -0
- package/src/lib/components/collection-presets.js.map +1 -0
- package/src/lib/components/collection-related.d.ts +101 -0
- package/src/lib/components/collection-related.js +536 -0
- package/src/lib/components/collection-related.js.map +1 -0
- package/src/lib/components/collection-search-box.d.ts +92 -0
- package/src/lib/components/collection-search-box.js +334 -0
- package/src/lib/components/collection-search-box.js.map +1 -0
- package/src/lib/components/collection-search-fuse.d.ts +28 -0
- package/src/lib/components/collection-search-fuse.js +28 -0
- package/src/lib/components/collection-search-fuse.js.map +1 -0
- package/src/lib/components/collection-search.d.ts +62 -0
- package/src/lib/components/collection-search.js +140 -0
- package/src/lib/components/collection-search.js.map +1 -0
- package/src/lib/components/collection-share.d.ts +31 -0
- package/src/lib/components/collection-share.js +154 -0
- package/src/lib/components/collection-share.js.map +1 -0
- package/src/lib/components/collection.d.ts +19 -497
- package/src/lib/components/collection.js +19 -2982
- package/src/lib/components/collection.js.map +1 -1
- package/src/lib/components/image-list.d.ts +2 -2
- package/src/lib/components/image-list.js +4 -6
- package/src/lib/components/image-list.js.map +1 -1
- package/src/lib/element-tier.d.ts +9 -1
- package/src/lib/element-tier.js +12 -1
- package/src/lib/element-tier.js.map +1 -1
- package/src/lib/plugin.js +20 -12
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/utils/to-count.d.ts +25 -0
- package/src/lib/utils/to-count.js +30 -0
- 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 };
|