@aglyn/plugins-mui 1.0.0-beta.221 → 1.0.0-beta.222

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aglyn/plugins-mui",
3
- "version": "1.0.0-beta.221",
3
+ "version": "1.0.0-beta.222",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -25,12 +25,12 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "dependencies": {
28
- "@aglyn/aglyn": "1.0.0-beta.221",
29
- "@aglyn/shared-data-enums": "1.0.0-beta.221",
30
- "@aglyn/shared-data-mdi": "1.0.0-beta.221",
31
- "@aglyn/shared-ui-jsx": "1.0.0-beta.221",
32
- "@aglyn/shared-ui-theme": "1.0.0-beta.221",
33
- "@aglyn/shared-util-vendor": "1.0.0-beta.221",
28
+ "@aglyn/aglyn": "1.0.0-beta.222",
29
+ "@aglyn/shared-data-enums": "1.0.0-beta.222",
30
+ "@aglyn/shared-data-mdi": "1.0.0-beta.222",
31
+ "@aglyn/shared-ui-jsx": "1.0.0-beta.222",
32
+ "@aglyn/shared-ui-theme": "1.0.0-beta.222",
33
+ "@aglyn/shared-util-vendor": "1.0.0-beta.222",
34
34
  "@swc/helpers": "0.5.23",
35
35
  "mobx": "^7.0.5",
36
36
  "mobx-react-lite": "^5.1.0"
@@ -80,6 +80,15 @@ AccordionElement.displayName = 'AglynAccordion';
80
80
  px: 2,
81
81
  py: 1.5
82
82
  };
83
+ /**
84
+ * The summary is a `<button>`, and a button takes the browser's own font
85
+ * rather than the page's — so every accordion question on a site rendered in
86
+ * the system face while the answer under it used the theme's (AGL-3497).
87
+ * Only the family is inherited: size and weight stay MUI's, and the node's own
88
+ * styles, applied after this, still win.
89
+ */ const SUMMARY_FONT_SX = {
90
+ fontFamily: 'inherit'
91
+ };
83
92
  /**
84
93
  * The toggle keeps only its chevron: the label is a sibling now, so the
85
94
  * summary's own content slot would just add empty width.
@@ -143,12 +152,23 @@ AccordionElement.displayName = 'AglynAccordion';
143
152
  // an unpublished or deleted target yields no href.
144
153
  const { href, suppressNavigation } = Aglyn.useScreenLink(screenId);
145
154
  if (!screenId) {
155
+ const { sx: plainSx } = rest, plainRest = _object_without_properties_loose(rest, [
156
+ "sx"
157
+ ]);
158
+ const plainNodeSx = Array.isArray(plainSx) ? plainSx : plainSx ? [
159
+ plainSx
160
+ ] : [];
146
161
  return /*#__PURE__*/ _jsx(MuiAccordionSummary, _extends({
147
162
  ref: ref,
148
163
  expandIcon: /*#__PURE__*/ _jsx(MdiIcon, {
149
164
  path: mdiChevronDown.path
150
165
  })
151
- }, withAuthorHtmlLabel(rest, html)));
166
+ }, withAuthorHtmlLabel(plainRest, html), {
167
+ sx: [
168
+ SUMMARY_FONT_SX,
169
+ ...plainNodeSx
170
+ ]
171
+ }));
152
172
  }
153
173
  const { children, sx } = rest, rowRest = _object_without_properties_loose(rest, [
154
174
  "children",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/mui/src/lib/components/accordion.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 { mdiChevronDown, mdiUnfoldMoreHorizontal } from '@aglyn/shared-data-mdi'\nimport { AppLink, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport MuiAccordion from '@mui/material/Accordion'\nimport MuiAccordionDetails from '@mui/material/AccordionDetails'\nimport MuiAccordionSummary from '@mui/material/AccordionSummary'\nimport Box from '@mui/material/Box'\nimport Link from '@mui/material/Link'\nimport type { SxProps } from '@mui/material/styles'\nimport { forwardRef, isValidElement, type ReactNode } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { FIELD_TEXT_CONTENT } from '../constants/field-presets'\nimport {\n authorHtmlLabel,\n withAuthorHtmlLabel,\n} from '../utils/author-html-label'\nimport { generatePresetId } from '../utils/generate-preset-id'\n\n// Component ids are persisted in screen documents; never rename.\nexport const ACCORDION_ID: Aglyn.ComponentId = 'muiAccordion'\nexport const ACCORDION_SUMMARY_ID: Aglyn.ComponentId = 'muiAccordionSummary'\nexport const ACCORDION_DETAILS_ID: Aglyn.ComponentId = 'muiAccordionDetails'\n\nexport interface AccordionElementProps {\n /** Starts open on the published site. */\n defaultExpanded?: boolean\n disabled?: boolean\n /** Removes the extra margin an expanded accordion normally gains. */\n disableGutters?: boolean\n children?: ReactNode\n}\n\n/**\n * Expand/collapse panel (https://mui.com/material-ui/react-accordion/).\n *\n * On the besigner canvas it renders **expanded regardless of\n * `defaultExpanded`**: collapsed details are unreachable for selection\n * and styling, which is the same reason the Drawer expands its contents\n * while selected (AGL-571). Preview and the published site both honour\n * the author's setting and toggle normally, so what ships is never in\n * doubt — only the editing surface differs.\n */\nconst AccordionElement = forwardRef<HTMLDivElement, AccordionElementProps>(\n (props, ref) => {\n const { defaultExpanded, children, ...rest } = props\n const { editorInert } = Aglyn.useScreenLink(undefined)\n if (editorInert) {\n // Controlled-open with no onChange: the canvas is a static surface,\n // clicks there select rather than toggle.\n // MUI types Accordion's children as NonNullable: an accordion with\n // no summary/details is not a renderable shape, and the renderer\n // always supplies the pair.\n return (\n <MuiAccordion\n ref={ref}\n expanded\n {...rest}\n children={children as NonNullable<ReactNode>}\n />\n )\n }\n return (\n <MuiAccordion\n ref={ref}\n defaultExpanded={!!defaultExpanded}\n {...rest}\n children={children as NonNullable<ReactNode>}\n />\n )\n },\n)\nAccordionElement.displayName = 'AglynAccordion'\n\nexport interface AccordionSummaryElementProps {\n /**\n * Optional screen the header's label links to (AGL-1232). Setting it\n * SPLITS the row — see {@link AccordionSummaryElement}. A screen id, never\n * a path: the href is resolved from the host routing map at render time,\n * so a slug rename or re-parent cannot break it, and a bare `href` would\n * full-reload the site instead of routing.\n */\n screenId?: string\n /**\n * Authored node styles, handed over by the renderer rather than typed\n * into an attribute — recomposed below so the Styles panel's values are\n * merged rather than replaced (AGL-1240/1284).\n */\n sx?: SxProps\n /**\n * Sanitized emphasis markup for the label, written by the inline editor's\n * rich mode (AGL-2557). `children` keeps the plain-text reading of it, and\n * is what every surface that cannot render markup falls back to.\n */\n html?: string\n children?: ReactNode\n}\n\n/** Label and chevron sit on one line; the label takes the slack. */\nconst SUMMARY_ROW_SX = {\n display: 'flex',\n alignItems: 'center',\n width: '100%',\n} as const\n\n/** Matches MuiAccordionSummary's own gutters so the split row lines up. */\nconst SUMMARY_LABEL_SX = {\n flex: 1,\n minWidth: 0,\n px: 2,\n py: 1.5,\n} as const\n\n/**\n * The toggle keeps only its chevron: the label is a sibling now, so the\n * summary's own content slot would just add empty width.\n */\nconst SUMMARY_TOGGLE_SX = {\n flex: '0 0 auto',\n '& .MuiAccordionSummary-content': { display: 'none' },\n} as const\n\n/**\n * The visible text of a React children tree, for naming a control that has no\n * text of its own (AGL-2349).\n *\n * It has to walk, not test: what arrives here is the node renderer's output,\n * which wraps the authored string in an `<AglynText>` element inside an\n * array (`libs/aglyn-node-renderer/src/lib/components/leaf.tsx`), so the string\n * is a grandchild at best and never the value itself. Descends `props.children`\n * rather than reading DOM text, because this runs on the React tree BEFORE\n * anything is in the DOM to read. (It also predates AGL-2011, when\n * `<aglyn-text>` was a shadow host whose `innerText` read back empty — that\n * is no longer true, but walking props is still the right tool here.)\n */\nconst childText = (node: ReactNode): string => {\n if (node == null || typeof node === 'boolean') return ''\n if (typeof node === 'string' || typeof node === 'number') return String(node)\n if (Array.isArray(node)) return node.map(childText).join('')\n if (isValidElement(node)) {\n return childText((node.props as { children?: ReactNode })?.children)\n }\n return ''\n}\n\n/**\n * The always-visible header row. Supplies the expand chevron MUI leaves\n * to the caller — without it the control looks like inert text and gives\n * no hint that there is anything to open.\n *\n * ## Why a linked header is a different shape (AGL-1232)\n *\n * `MuiAccordionSummary` renders a `<button>`, and the mobile drawer needs\n * Product/Solutions to reach their jump pages as well as open. An anchor\n * inside that button is invalid nesting — browsers unnest it, and nested\n * interactive content is unusable with a screen reader or a keyboard — so\n * the row cannot both toggle and navigate while it is one control.\n *\n * With a `screenId` set the row therefore splits in two: the label becomes\n * a real anchor, and a chevron-only `MuiAccordionSummary` beside it becomes\n * the toggle. Two siblings, each independently focusable, no nesting. The\n * summary keeps driving the panel through MUI's accordion context, so\n * `aria-expanded` and the transition are unchanged.\n *\n * With no `screenId` — every accordion authored before this — the output is\n * byte-for-byte what it always was: one `MuiAccordionSummary`, whole row\n * toggles.\n */\nexport const AccordionSummaryElement = forwardRef<\n HTMLDivElement,\n AccordionSummaryElementProps\n>((props, ref) => {\n const { screenId, html, ...rest } = props\n // Resolution is the routing map's job, exactly as Screen Link does it;\n // an unpublished or deleted target yields no href.\n const { href, suppressNavigation } = Aglyn.useScreenLink(screenId)\n\n if (!screenId) {\n return (\n <MuiAccordionSummary\n ref={ref}\n expandIcon={<MdiIcon path={mdiChevronDown.path} />}\n {...withAuthorHtmlLabel(rest, html)}\n />\n )\n }\n\n const { children, sx, ...rowRest } = rest\n const nodeSx = Array.isArray(sx) ? sx : sx ? [sx] : []\n // The chevron button's only content is an icon, so it needs a name of its\n // own — \"Product\" the link and \"Product\" the toggle are two controls.\n //\n // This USED to be `typeof children === 'string'`, which is never true in\n // production (AGL-2349). A summary's label is authored through\n // FIELD_TEXT_CONTENT, and the node renderer does not pass that string down as\n // `children` — `Leaf` lifts it out as `textContent` and renders\n // `<Component>{children}<AglynText>{textContent}</…></Component>`,\n // so `children` is always an ARRAY holding an element. The string branch was\n // reachable only from a spec that handed the component a bare string, so\n // every published split summary named its chevron \"Toggle section\".\n //\n // Read from `children` rather than from the formatted label below, because\n // a formatted one is markup handed to `dangerouslySetInnerHTML` and has no\n // React children to walk. `children` carries the same words either way.\n const label = childText(children).trim()\n const content = authorHtmlLabel(html) ?? children\n return (\n <Box ref={ref} {...rowRest} sx={[SUMMARY_ROW_SX, ...nodeSx]}>\n {href && !suppressNavigation ? (\n <AppLink href={href} underline=\"hover\" sx={SUMMARY_LABEL_SX}>\n {content}\n </AppLink>\n ) : (\n // Unresolved target, or a surface that must not navigate (canvas,\n // preview): same two-part shape, so the editor never shows a row\n // that behaves differently from the one that ships.\n //\n // Same ELEMENT too, not just the same box (AGL-1268). `href` comes\n // from the screens map, which differs between the render that\n // produced an ISR page and the render that hydrates it — a screen\n // published or unpublished in between. Switching `<a>` to `<span>`\n // on that would remount the subtree at hydration; an `<a>` with no\n // href is HTML's placeholder link, so the element is stable and the\n // row is still inert. Link Container settled on this shape first.\n <Link underline=\"none\" color=\"inherit\" sx={SUMMARY_LABEL_SX}>\n {content}\n </Link>\n )}\n <MuiAccordionSummary\n aria-label={label ? `Toggle ${label}` : 'Toggle section'}\n expandIcon={<MdiIcon path={mdiChevronDown.path} />}\n sx={SUMMARY_TOGGLE_SX}\n />\n </Box>\n )\n})\nAccordionSummaryElement.displayName = 'AglynAccordionSummary'\n\nexport const AccordionDetailsElement = forwardRef<\n HTMLDivElement,\n { children?: ReactNode }\n>((props, ref) => <MuiAccordionDetails ref={ref} {...props} />)\nAccordionDetailsElement.displayName = 'AglynAccordionDetails'\n\nexport const accordionSchema: Aglyn.ComponentSchema<AccordionElementProps> = {\n $id: ACCORDION_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Accordion',\n description:\n 'Header that expands to reveal its details — FAQs, long forms, ' +\n 'anything progressive.',\n category: Aglyn.ComponentCategory.SURFACE,\n icon: { path: mdiUnfoldMoreHorizontal.path, sx: { color: '#2196f3' } },\n // Structure is fixed by MUI: summary first, details second.\n restrictChildren: [\n Aglyn.LinealDirectiveFlag.LIMIT_TO,\n { components: [ACCORDION_SUMMARY_ID, ACCORDION_DETAILS_ID] },\n ],\n // …and it reads that structure BY INDEX, so the renderer has to hand it one\n // React child per node child (AGL-1237). Without this the summary swallowed\n // the details and every accordion on every published site expanded to reveal\n // an empty panel, with its content rendering unconditionally inside the\n // summary's heading.\n flags: { positionalChildren: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'defaultExpanded',\n label: 'Start expanded?',\n description:\n 'If true, the panel is already open when a visitor arrives. The ' +\n 'canvas always shows it open so you can edit inside it.',\n component: Aglyn.FieldComponentType.SWITCH,\n },\n {\n name: 'disableGutters',\n label: 'Disable gutters?',\n description:\n 'If true, the panel keeps its height when expanded instead of ' +\n 'gaining extra margin — the tighter look for a stacked FAQ.',\n component: Aglyn.FieldComponentType.SWITCH,\n },\n {\n name: 'disabled',\n label: 'Disabled?',\n description: 'If true, visitors cannot expand the panel.',\n component: Aglyn.FieldComponentType.SWITCH,\n },\n ],\n}\n\nexport const accordionSummarySchema: Aglyn.ComponentSchema = {\n $id: ACCORDION_SUMMARY_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Accordion Summary',\n description: 'The clickable header row of an accordion.',\n category: Aglyn.ComponentCategory.SURFACE,\n icon: { path: mdiUnfoldMoreHorizontal.path, sx: { color: '#2196f3' } },\n flags: {\n textEditable: Aglyn.FEATURE_FLAG.ENABLED,\n richTextEditable: Aglyn.FEATURE_FLAG.ENABLED,\n },\n // Emphasis only (AGL-2557). The row is a `<button>`, and a list or a link\n // inside one is invalid markup the parser unnests and a control a keyboard\n // cannot reach — the same constraint that made a LINKED header two sibling\n // controls rather than an anchor inside the toggle (AGL-1232).\n richTextCommands: [Aglyn.RICH_TEXT_COMMANDS.EMPHASIS],\n attributes: [\n FIELD_TEXT_CONTENT,\n {\n name: 'screenId',\n label: 'Header links to',\n description:\n 'Optional. Give the header a destination and the row splits: the ' +\n 'text becomes a link to that page and the chevron beside it ' +\n 'opens the panel. That is the only way a header can do both — a ' +\n 'link inside the toggle button is invalid and unreachable by ' +\n 'keyboard. Leave it empty and the whole row toggles, as before.',\n component: Aglyn.FieldComponentType.SCREEN_SELECT,\n },\n ],\n}\n\nexport const accordionDetailsSchema: Aglyn.ComponentSchema = {\n $id: ACCORDION_DETAILS_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Accordion Details',\n description: 'The region an accordion reveals when expanded.',\n category: Aglyn.ComponentCategory.SURFACE,\n icon: { path: mdiUnfoldMoreHorizontal.path, sx: { color: '#2196f3' } },\n}\n\nconst panel = (summary: string, body: string) => ({\n $id: null,\n componentId: ACCORDION_ID,\n pluginId: BUNDLE_ID,\n props: {},\n nodes: [\n {\n $id: null,\n componentId: ACCORDION_SUMMARY_ID,\n pluginId: BUNDLE_ID,\n props: { children: summary },\n },\n {\n $id: null,\n componentId: ACCORDION_DETAILS_ID,\n pluginId: BUNDLE_ID,\n nodes: [\n {\n $id: null,\n componentId: 'muiTypography',\n pluginId: BUNDLE_ID,\n props: { variant: 'body2', children: body },\n },\n ],\n },\n ],\n})\n\nexport const accordionPresets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(ACCORDION_ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Accordion',\n pluginId: BUNDLE_ID,\n description: 'One expandable panel with a header and body text',\n category: Aglyn.ComponentCategory.SURFACE,\n icon: accordionSchema.icon,\n data: panel('Accordion header', 'The details revealed when expanded.'),\n },\n {\n $id: generatePresetId(ACCORDION_ID, 'faq'),\n type: Aglyn.NodeType.PRESET,\n displayName: 'FAQ',\n pluginId: BUNDLE_ID,\n description: 'Three stacked question panels, ready to edit',\n category: Aglyn.ComponentCategory.SURFACE,\n icon: { path: mdiUnfoldMoreHorizontal.path, sx: { color: '#1976d2' } },\n data: {\n $id: null,\n componentId: 'muiStack',\n pluginId: BUNDLE_ID,\n props: { direction: 'column' },\n nodes: [\n panel('How do I get started?', 'Answer the first question here.'),\n panel('What does it cost?', 'Answer the second question here.'),\n panel('How do I get support?', 'Answer the third question here.'),\n ],\n },\n },\n]\n\nexport default AccordionElement\n"],"names":["Aglyn","mdiChevronDown","mdiUnfoldMoreHorizontal","AppLink","MdiIcon","MuiAccordion","MuiAccordionDetails","MuiAccordionSummary","Box","Link","forwardRef","isValidElement","BUNDLE_ID","FIELD_TEXT_CONTENT","authorHtmlLabel","withAuthorHtmlLabel","generatePresetId","ACCORDION_ID","ACCORDION_SUMMARY_ID","ACCORDION_DETAILS_ID","AccordionElement","props","ref","defaultExpanded","children","rest","editorInert","useScreenLink","undefined","expanded","displayName","SUMMARY_ROW_SX","display","alignItems","width","SUMMARY_LABEL_SX","flex","minWidth","px","py","SUMMARY_TOGGLE_SX","childText","node","String","Array","isArray","map","join","AccordionSummaryElement","screenId","html","href","suppressNavigation","expandIcon","path","sx","rowRest","nodeSx","label","trim","content","underline","color","aria-label","AccordionDetailsElement","accordionSchema","$id","pluginId","description","category","ComponentCategory","SURFACE","icon","restrictChildren","LinealDirectiveFlag","LIMIT_TO","components","flags","positionalChildren","FEATURE_FLAG","ENABLED","attributes","name","component","FieldComponentType","SWITCH","accordionSummarySchema","textEditable","richTextEditable","richTextCommands","RICH_TEXT_COMMANDS","EMPHASIS","SCREEN_SELECT","accordionDetailsSchema","panel","summary","body","componentId","nodes","variant","accordionPresets","type","NodeType","PRESET","data","direction"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/mui/src/lib/components/accordion.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 { mdiChevronDown, mdiUnfoldMoreHorizontal } from '@aglyn/shared-data-mdi'\nimport { AppLink, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport MuiAccordion from '@mui/material/Accordion'\nimport MuiAccordionDetails from '@mui/material/AccordionDetails'\nimport MuiAccordionSummary from '@mui/material/AccordionSummary'\nimport Box from '@mui/material/Box'\nimport Link from '@mui/material/Link'\nimport type { SxProps } from '@mui/material/styles'\nimport { forwardRef, isValidElement, type ReactNode } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { FIELD_TEXT_CONTENT } from '../constants/field-presets'\nimport {\n authorHtmlLabel,\n withAuthorHtmlLabel,\n} from '../utils/author-html-label'\nimport { generatePresetId } from '../utils/generate-preset-id'\n\n// Component ids are persisted in screen documents; never rename.\nexport const ACCORDION_ID: Aglyn.ComponentId = 'muiAccordion'\nexport const ACCORDION_SUMMARY_ID: Aglyn.ComponentId = 'muiAccordionSummary'\nexport const ACCORDION_DETAILS_ID: Aglyn.ComponentId = 'muiAccordionDetails'\n\nexport interface AccordionElementProps {\n /** Starts open on the published site. */\n defaultExpanded?: boolean\n disabled?: boolean\n /** Removes the extra margin an expanded accordion normally gains. */\n disableGutters?: boolean\n children?: ReactNode\n}\n\n/**\n * Expand/collapse panel (https://mui.com/material-ui/react-accordion/).\n *\n * On the besigner canvas it renders **expanded regardless of\n * `defaultExpanded`**: collapsed details are unreachable for selection\n * and styling, which is the same reason the Drawer expands its contents\n * while selected (AGL-571). Preview and the published site both honour\n * the author's setting and toggle normally, so what ships is never in\n * doubt — only the editing surface differs.\n */\nconst AccordionElement = forwardRef<HTMLDivElement, AccordionElementProps>(\n (props, ref) => {\n const { defaultExpanded, children, ...rest } = props\n const { editorInert } = Aglyn.useScreenLink(undefined)\n if (editorInert) {\n // Controlled-open with no onChange: the canvas is a static surface,\n // clicks there select rather than toggle.\n // MUI types Accordion's children as NonNullable: an accordion with\n // no summary/details is not a renderable shape, and the renderer\n // always supplies the pair.\n return (\n <MuiAccordion\n ref={ref}\n expanded\n {...rest}\n children={children as NonNullable<ReactNode>}\n />\n )\n }\n return (\n <MuiAccordion\n ref={ref}\n defaultExpanded={!!defaultExpanded}\n {...rest}\n children={children as NonNullable<ReactNode>}\n />\n )\n },\n)\nAccordionElement.displayName = 'AglynAccordion'\n\nexport interface AccordionSummaryElementProps {\n /**\n * Optional screen the header's label links to (AGL-1232). Setting it\n * SPLITS the row — see {@link AccordionSummaryElement}. A screen id, never\n * a path: the href is resolved from the host routing map at render time,\n * so a slug rename or re-parent cannot break it, and a bare `href` would\n * full-reload the site instead of routing.\n */\n screenId?: string\n /**\n * Authored node styles, handed over by the renderer rather than typed\n * into an attribute — recomposed below so the Styles panel's values are\n * merged rather than replaced (AGL-1240/1284).\n */\n sx?: SxProps\n /**\n * Sanitized emphasis markup for the label, written by the inline editor's\n * rich mode (AGL-2557). `children` keeps the plain-text reading of it, and\n * is what every surface that cannot render markup falls back to.\n */\n html?: string\n children?: ReactNode\n}\n\n/** Label and chevron sit on one line; the label takes the slack. */\nconst SUMMARY_ROW_SX = {\n display: 'flex',\n alignItems: 'center',\n width: '100%',\n} as const\n\n/** Matches MuiAccordionSummary's own gutters so the split row lines up. */\nconst SUMMARY_LABEL_SX = {\n flex: 1,\n minWidth: 0,\n px: 2,\n py: 1.5,\n} as const\n\n/**\n * The summary is a `<button>`, and a button takes the browser's own font\n * rather than the page's — so every accordion question on a site rendered in\n * the system face while the answer under it used the theme's (AGL-3497).\n * Only the family is inherited: size and weight stay MUI's, and the node's own\n * styles, applied after this, still win.\n */\nconst SUMMARY_FONT_SX = { fontFamily: 'inherit' } as const\n\n/**\n * The toggle keeps only its chevron: the label is a sibling now, so the\n * summary's own content slot would just add empty width.\n */\nconst SUMMARY_TOGGLE_SX = {\n flex: '0 0 auto',\n '& .MuiAccordionSummary-content': { display: 'none' },\n} as const\n\n/**\n * The visible text of a React children tree, for naming a control that has no\n * text of its own (AGL-2349).\n *\n * It has to walk, not test: what arrives here is the node renderer's output,\n * which wraps the authored string in an `<AglynText>` element inside an\n * array (`libs/aglyn-node-renderer/src/lib/components/leaf.tsx`), so the string\n * is a grandchild at best and never the value itself. Descends `props.children`\n * rather than reading DOM text, because this runs on the React tree BEFORE\n * anything is in the DOM to read. (It also predates AGL-2011, when\n * `<aglyn-text>` was a shadow host whose `innerText` read back empty — that\n * is no longer true, but walking props is still the right tool here.)\n */\nconst childText = (node: ReactNode): string => {\n if (node == null || typeof node === 'boolean') return ''\n if (typeof node === 'string' || typeof node === 'number') return String(node)\n if (Array.isArray(node)) return node.map(childText).join('')\n if (isValidElement(node)) {\n return childText((node.props as { children?: ReactNode })?.children)\n }\n return ''\n}\n\n/**\n * The always-visible header row. Supplies the expand chevron MUI leaves\n * to the caller — without it the control looks like inert text and gives\n * no hint that there is anything to open.\n *\n * ## Why a linked header is a different shape (AGL-1232)\n *\n * `MuiAccordionSummary` renders a `<button>`, and the mobile drawer needs\n * Product/Solutions to reach their jump pages as well as open. An anchor\n * inside that button is invalid nesting — browsers unnest it, and nested\n * interactive content is unusable with a screen reader or a keyboard — so\n * the row cannot both toggle and navigate while it is one control.\n *\n * With a `screenId` set the row therefore splits in two: the label becomes\n * a real anchor, and a chevron-only `MuiAccordionSummary` beside it becomes\n * the toggle. Two siblings, each independently focusable, no nesting. The\n * summary keeps driving the panel through MUI's accordion context, so\n * `aria-expanded` and the transition are unchanged.\n *\n * With no `screenId` — every accordion authored before this — the output is\n * byte-for-byte what it always was: one `MuiAccordionSummary`, whole row\n * toggles.\n */\nexport const AccordionSummaryElement = forwardRef<\n HTMLDivElement,\n AccordionSummaryElementProps\n>((props, ref) => {\n const { screenId, html, ...rest } = props\n // Resolution is the routing map's job, exactly as Screen Link does it;\n // an unpublished or deleted target yields no href.\n const { href, suppressNavigation } = Aglyn.useScreenLink(screenId)\n\n if (!screenId) {\n const { sx: plainSx, ...plainRest } = rest\n const plainNodeSx = Array.isArray(plainSx) ? plainSx : plainSx ? [plainSx] : []\n return (\n <MuiAccordionSummary\n ref={ref}\n expandIcon={<MdiIcon path={mdiChevronDown.path} />}\n {...withAuthorHtmlLabel(plainRest, html)}\n sx={[SUMMARY_FONT_SX, ...plainNodeSx]}\n />\n )\n }\n\n const { children, sx, ...rowRest } = rest\n const nodeSx = Array.isArray(sx) ? sx : sx ? [sx] : []\n // The chevron button's only content is an icon, so it needs a name of its\n // own — \"Product\" the link and \"Product\" the toggle are two controls.\n //\n // This USED to be `typeof children === 'string'`, which is never true in\n // production (AGL-2349). A summary's label is authored through\n // FIELD_TEXT_CONTENT, and the node renderer does not pass that string down as\n // `children` — `Leaf` lifts it out as `textContent` and renders\n // `<Component>{children}<AglynText>{textContent}</…></Component>`,\n // so `children` is always an ARRAY holding an element. The string branch was\n // reachable only from a spec that handed the component a bare string, so\n // every published split summary named its chevron \"Toggle section\".\n //\n // Read from `children` rather than from the formatted label below, because\n // a formatted one is markup handed to `dangerouslySetInnerHTML` and has no\n // React children to walk. `children` carries the same words either way.\n const label = childText(children).trim()\n const content = authorHtmlLabel(html) ?? children\n return (\n <Box ref={ref} {...rowRest} sx={[SUMMARY_ROW_SX, ...nodeSx]}>\n {href && !suppressNavigation ? (\n <AppLink href={href} underline=\"hover\" sx={SUMMARY_LABEL_SX}>\n {content}\n </AppLink>\n ) : (\n // Unresolved target, or a surface that must not navigate (canvas,\n // preview): same two-part shape, so the editor never shows a row\n // that behaves differently from the one that ships.\n //\n // Same ELEMENT too, not just the same box (AGL-1268). `href` comes\n // from the screens map, which differs between the render that\n // produced an ISR page and the render that hydrates it — a screen\n // published or unpublished in between. Switching `<a>` to `<span>`\n // on that would remount the subtree at hydration; an `<a>` with no\n // href is HTML's placeholder link, so the element is stable and the\n // row is still inert. Link Container settled on this shape first.\n <Link underline=\"none\" color=\"inherit\" sx={SUMMARY_LABEL_SX}>\n {content}\n </Link>\n )}\n <MuiAccordionSummary\n aria-label={label ? `Toggle ${label}` : 'Toggle section'}\n expandIcon={<MdiIcon path={mdiChevronDown.path} />}\n sx={SUMMARY_TOGGLE_SX}\n />\n </Box>\n )\n})\nAccordionSummaryElement.displayName = 'AglynAccordionSummary'\n\nexport const AccordionDetailsElement = forwardRef<\n HTMLDivElement,\n { children?: ReactNode }\n>((props, ref) => <MuiAccordionDetails ref={ref} {...props} />)\nAccordionDetailsElement.displayName = 'AglynAccordionDetails'\n\nexport const accordionSchema: Aglyn.ComponentSchema<AccordionElementProps> = {\n $id: ACCORDION_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Accordion',\n description:\n 'Header that expands to reveal its details — FAQs, long forms, ' +\n 'anything progressive.',\n category: Aglyn.ComponentCategory.SURFACE,\n icon: { path: mdiUnfoldMoreHorizontal.path, sx: { color: '#2196f3' } },\n // Structure is fixed by MUI: summary first, details second.\n restrictChildren: [\n Aglyn.LinealDirectiveFlag.LIMIT_TO,\n { components: [ACCORDION_SUMMARY_ID, ACCORDION_DETAILS_ID] },\n ],\n // …and it reads that structure BY INDEX, so the renderer has to hand it one\n // React child per node child (AGL-1237). Without this the summary swallowed\n // the details and every accordion on every published site expanded to reveal\n // an empty panel, with its content rendering unconditionally inside the\n // summary's heading.\n flags: { positionalChildren: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'defaultExpanded',\n label: 'Start expanded?',\n description:\n 'If true, the panel is already open when a visitor arrives. The ' +\n 'canvas always shows it open so you can edit inside it.',\n component: Aglyn.FieldComponentType.SWITCH,\n },\n {\n name: 'disableGutters',\n label: 'Disable gutters?',\n description:\n 'If true, the panel keeps its height when expanded instead of ' +\n 'gaining extra margin — the tighter look for a stacked FAQ.',\n component: Aglyn.FieldComponentType.SWITCH,\n },\n {\n name: 'disabled',\n label: 'Disabled?',\n description: 'If true, visitors cannot expand the panel.',\n component: Aglyn.FieldComponentType.SWITCH,\n },\n ],\n}\n\nexport const accordionSummarySchema: Aglyn.ComponentSchema = {\n $id: ACCORDION_SUMMARY_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Accordion Summary',\n description: 'The clickable header row of an accordion.',\n category: Aglyn.ComponentCategory.SURFACE,\n icon: { path: mdiUnfoldMoreHorizontal.path, sx: { color: '#2196f3' } },\n flags: {\n textEditable: Aglyn.FEATURE_FLAG.ENABLED,\n richTextEditable: Aglyn.FEATURE_FLAG.ENABLED,\n },\n // Emphasis only (AGL-2557). The row is a `<button>`, and a list or a link\n // inside one is invalid markup the parser unnests and a control a keyboard\n // cannot reach — the same constraint that made a LINKED header two sibling\n // controls rather than an anchor inside the toggle (AGL-1232).\n richTextCommands: [Aglyn.RICH_TEXT_COMMANDS.EMPHASIS],\n attributes: [\n FIELD_TEXT_CONTENT,\n {\n name: 'screenId',\n label: 'Header links to',\n description:\n 'Optional. Give the header a destination and the row splits: the ' +\n 'text becomes a link to that page and the chevron beside it ' +\n 'opens the panel. That is the only way a header can do both — a ' +\n 'link inside the toggle button is invalid and unreachable by ' +\n 'keyboard. Leave it empty and the whole row toggles, as before.',\n component: Aglyn.FieldComponentType.SCREEN_SELECT,\n },\n ],\n}\n\nexport const accordionDetailsSchema: Aglyn.ComponentSchema = {\n $id: ACCORDION_DETAILS_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Accordion Details',\n description: 'The region an accordion reveals when expanded.',\n category: Aglyn.ComponentCategory.SURFACE,\n icon: { path: mdiUnfoldMoreHorizontal.path, sx: { color: '#2196f3' } },\n}\n\nconst panel = (summary: string, body: string) => ({\n $id: null,\n componentId: ACCORDION_ID,\n pluginId: BUNDLE_ID,\n props: {},\n nodes: [\n {\n $id: null,\n componentId: ACCORDION_SUMMARY_ID,\n pluginId: BUNDLE_ID,\n props: { children: summary },\n },\n {\n $id: null,\n componentId: ACCORDION_DETAILS_ID,\n pluginId: BUNDLE_ID,\n nodes: [\n {\n $id: null,\n componentId: 'muiTypography',\n pluginId: BUNDLE_ID,\n props: { variant: 'body2', children: body },\n },\n ],\n },\n ],\n})\n\nexport const accordionPresets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(ACCORDION_ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Accordion',\n pluginId: BUNDLE_ID,\n description: 'One expandable panel with a header and body text',\n category: Aglyn.ComponentCategory.SURFACE,\n icon: accordionSchema.icon,\n data: panel('Accordion header', 'The details revealed when expanded.'),\n },\n {\n $id: generatePresetId(ACCORDION_ID, 'faq'),\n type: Aglyn.NodeType.PRESET,\n displayName: 'FAQ',\n pluginId: BUNDLE_ID,\n description: 'Three stacked question panels, ready to edit',\n category: Aglyn.ComponentCategory.SURFACE,\n icon: { path: mdiUnfoldMoreHorizontal.path, sx: { color: '#1976d2' } },\n data: {\n $id: null,\n componentId: 'muiStack',\n pluginId: BUNDLE_ID,\n props: { direction: 'column' },\n nodes: [\n panel('How do I get started?', 'Answer the first question here.'),\n panel('What does it cost?', 'Answer the second question here.'),\n panel('How do I get support?', 'Answer the third question here.'),\n ],\n },\n },\n]\n\nexport default AccordionElement\n"],"names":["Aglyn","mdiChevronDown","mdiUnfoldMoreHorizontal","AppLink","MdiIcon","MuiAccordion","MuiAccordionDetails","MuiAccordionSummary","Box","Link","forwardRef","isValidElement","BUNDLE_ID","FIELD_TEXT_CONTENT","authorHtmlLabel","withAuthorHtmlLabel","generatePresetId","ACCORDION_ID","ACCORDION_SUMMARY_ID","ACCORDION_DETAILS_ID","AccordionElement","props","ref","defaultExpanded","children","rest","editorInert","useScreenLink","undefined","expanded","displayName","SUMMARY_ROW_SX","display","alignItems","width","SUMMARY_LABEL_SX","flex","minWidth","px","py","SUMMARY_FONT_SX","fontFamily","SUMMARY_TOGGLE_SX","childText","node","String","Array","isArray","map","join","AccordionSummaryElement","screenId","html","href","suppressNavigation","sx","plainSx","plainRest","plainNodeSx","expandIcon","path","rowRest","nodeSx","label","trim","content","underline","color","aria-label","AccordionDetailsElement","accordionSchema","$id","pluginId","description","category","ComponentCategory","SURFACE","icon","restrictChildren","LinealDirectiveFlag","LIMIT_TO","components","flags","positionalChildren","FEATURE_FLAG","ENABLED","attributes","name","component","FieldComponentType","SWITCH","accordionSummarySchema","textEditable","richTextEditable","richTextCommands","RICH_TEXT_COMMANDS","EMPHASIS","SCREEN_SELECT","accordionDetailsSchema","panel","summary","body","componentId","nodes","variant","accordionPresets","type","NodeType","PRESET","data","direction"],"mappings":";;;AAAA;;;;;;;;;;;;;;;CAeC,GAED,YAAYA,WAAW,eAAc;AACrC,SAASC,cAAc,EAAEC,uBAAuB,QAAQ,yBAAwB;AAChF,SAASC,OAAO,EAAEC,OAAO,QAAQ,uBAAsB;AACvD,OAAOC,kBAAkB,0BAAyB;AAClD,OAAOC,yBAAyB,iCAAgC;AAChE,OAAOC,yBAAyB,iCAAgC;AAChE,OAAOC,SAAS,oBAAmB;AACnC,OAAOC,UAAU,qBAAoB;AAErC,SAASC,UAAU,EAAEC,cAAc,QAAwB,QAAO;AAClE,SAASC,SAAS,QAAQ,gCAA4B;AACtD,SAASC,kBAAkB,QAAQ,gCAA4B;AAC/D,SACEC,eAAe,EACfC,mBAAmB,QACd,gCAA4B;AACnC,SAASC,gBAAgB,QAAQ,iCAA6B;AAE9D,iEAAiE;AACjE,OAAO,MAAMC,eAAkC,eAAc;AAC7D,OAAO,MAAMC,uBAA0C,sBAAqB;AAC5E,OAAO,MAAMC,uBAA0C,sBAAqB;AAW5E;;;;;;;;;CASC,GACD,MAAMC,iCAAmBV,WACvB,CAACW,OAAOC;IACN,MAAM,EAAEC,eAAe,EAAEC,QAAQ,EAAW,GAAGH,OAATI,wCAASJ;;;;IAC/C,MAAM,EAAEK,WAAW,EAAE,GAAG1B,MAAM2B,aAAa,CAACC;IAC5C,IAAIF,aAAa;QACf,oEAAoE;QACpE,0CAA0C;QAC1C,mEAAmE;QACnE,iEAAiE;QACjE,4BAA4B;QAC5B,qBACE,KAACrB;YACCiB,KAAKA;YACLO,QAAQ;WACJJ;YACJD,UAAUA;;IAGhB;IACA,qBACE,KAACnB;QACCiB,KAAKA;QACLC,iBAAiB,CAAC,CAACA;OACfE;QACJD,UAAUA;;AAGhB;AAEFJ,iBAAiBU,WAAW,GAAG;AA0B/B,kEAAkE,GAClE,MAAMC,iBAAiB;IACrBC,SAAS;IACTC,YAAY;IACZC,OAAO;AACT;AAEA,yEAAyE,GACzE,MAAMC,mBAAmB;IACvBC,MAAM;IACNC,UAAU;IACVC,IAAI;IACJC,IAAI;AACN;AAEA;;;;;;CAMC,GACD,MAAMC,kBAAkB;IAAEC,YAAY;AAAU;AAEhD;;;CAGC,GACD,MAAMC,oBAAoB;IACxBN,MAAM;IACN,kCAAkC;QAAEJ,SAAS;IAAO;AACtD;AAEA;;;;;;;;;;;;CAYC,GACD,MAAMW,YAAY,CAACC;IACjB,IAAIA,QAAQ,QAAQ,OAAOA,SAAS,WAAW,OAAO;IACtD,IAAI,OAAOA,SAAS,YAAY,OAAOA,SAAS,UAAU,OAAOC,OAAOD;IACxE,IAAIE,MAAMC,OAAO,CAACH,OAAO,OAAOA,KAAKI,GAAG,CAACL,WAAWM,IAAI,CAAC;IACzD,kBAAItC,eAAeiC,OAAO;YACNA;QAAlB,OAAOD,WAAWC,cAAAA,KAAKvB,KAAK,qBAAX,AAACuB,YAAyCpB,QAAQ;IACrE;IACA,OAAO;AACT;AAEA;;;;;;;;;;;;;;;;;;;;;;CAsBC,GACD,OAAO,MAAM0B,wCAA0BxC,WAGrC,CAACW,OAAOC;QAqCQR;IApChB,MAAM,EAAEqC,QAAQ,EAAEC,IAAI,EAAW,GAAG/B,OAATI,wCAASJ;;;;IACpC,uEAAuE;IACvE,mDAAmD;IACnD,MAAM,EAAEgC,IAAI,EAAEC,kBAAkB,EAAE,GAAGtD,MAAM2B,aAAa,CAACwB;IAEzD,IAAI,CAACA,UAAU;QACb,MAAM,EAAEI,IAAIC,OAAO,EAAgB,GAAG/B,MAAdgC,6CAAchC;;;QACtC,MAAMiC,cAAcZ,MAAMC,OAAO,CAACS,WAAWA,UAAUA,UAAU;YAACA;SAAQ,GAAG,EAAE;QAC/E,qBACE,KAACjD;YACCe,KAAKA;YACLqC,0BAAY,KAACvD;gBAAQwD,MAAM3D,eAAe2D,IAAI;;WAC1C7C,oBAAoB0C,WAAWL;YACnCG,IAAI;gBAACf;mBAAoBkB;aAAY;;IAG3C;IAEA,MAAM,EAAElC,QAAQ,EAAE+B,EAAE,EAAc,GAAG9B,MAAZoC,2CAAYpC;;;;IACrC,MAAMqC,SAAShB,MAAMC,OAAO,CAACQ,MAAMA,KAAKA,KAAK;QAACA;KAAG,GAAG,EAAE;IACtD,0EAA0E;IAC1E,sEAAsE;IACtE,EAAE;IACF,yEAAyE;IACzE,+DAA+D;IAC/D,8EAA8E;IAC9E,gEAAgE;IAChE,mEAAmE;IACnE,6EAA6E;IAC7E,yEAAyE;IACzE,oEAAoE;IACpE,EAAE;IACF,2EAA2E;IAC3E,2EAA2E;IAC3E,wEAAwE;IACxE,MAAMQ,QAAQpB,UAAUnB,UAAUwC,IAAI;IACtC,MAAMC,WAAUnD,mBAAAA,gBAAgBsC,iBAAhBtC,mBAAyBU;IACzC,qBACE,MAAChB;QAAIc,KAAKA;OAASuC;QAASN,IAAI;YAACxB;eAAmB+B;SAAO;;YACxDT,QAAQ,CAACC,mCACR,KAACnD;gBAAQkD,MAAMA;gBAAMa,WAAU;gBAAQX,IAAIpB;0BACxC8B;iBAGH,kEAAkE;YAClE,iEAAiE;YACjE,oDAAoD;YACpD,EAAE;YACF,mEAAmE;YACnE,8DAA8D;YAC9D,kEAAkE;YAClE,mEAAmE;YACnE,mEAAmE;YACnE,oEAAoE;YACpE,kEAAkE;0BAClE,KAACxD;gBAAKyD,WAAU;gBAAOC,OAAM;gBAAUZ,IAAIpB;0BACxC8B;;0BAGL,KAAC1D;gBACC6D,cAAYL,QAAQ,CAAC,OAAO,EAAEA,OAAO,GAAG;gBACxCJ,0BAAY,KAACvD;oBAAQwD,MAAM3D,eAAe2D,IAAI;;gBAC9CL,IAAIb;;;;AAIZ,GAAE;AACFQ,wBAAwBpB,WAAW,GAAG;AAEtC,OAAO,MAAMuC,wCAA0B3D,WAGrC,CAACW,OAAOC,oBAAQ,KAAChB;QAAoBgB,KAAKA;OAASD,SAAU;AAC/DgD,wBAAwBvC,WAAW,GAAG;AAEtC,OAAO,MAAMwC,kBAAgE;IAC3EC,KAAKtD;IACLuD,UAAU5D;IACVkB,aAAa;IACb2C,aACE,mEACA;IACFC,UAAU1E,MAAM2E,iBAAiB,CAACC,OAAO;IACzCC,MAAM;QAAEjB,MAAM1D,wBAAwB0D,IAAI;QAAEL,IAAI;YAAEY,OAAO;QAAU;IAAE;IACrE,4DAA4D;IAC5DW,kBAAkB;QAChB9E,MAAM+E,mBAAmB,CAACC,QAAQ;QAClC;YAAEC,YAAY;gBAAC/D;gBAAsBC;aAAqB;QAAC;KAC5D;IACD,4EAA4E;IAC5E,4EAA4E;IAC5E,6EAA6E;IAC7E,wEAAwE;IACxE,qBAAqB;IACrB+D,OAAO;QAAEC,oBAAoBnF,MAAMoF,YAAY,CAACC,OAAO;IAAC;IACxDC,YAAY;QACV;YACEC,MAAM;YACNxB,OAAO;YACPU,aACE,oEACA;YACFe,WAAWxF,MAAMyF,kBAAkB,CAACC,MAAM;QAC5C;QACA;YACEH,MAAM;YACNxB,OAAO;YACPU,aACE,kEACA;YACFe,WAAWxF,MAAMyF,kBAAkB,CAACC,MAAM;QAC5C;QACA;YACEH,MAAM;YACNxB,OAAO;YACPU,aAAa;YACbe,WAAWxF,MAAMyF,kBAAkB,CAACC,MAAM;QAC5C;KACD;AACH,EAAC;AAED,OAAO,MAAMC,yBAAgD;IAC3DpB,KAAKrD;IACLsD,UAAU5D;IACVkB,aAAa;IACb2C,aAAa;IACbC,UAAU1E,MAAM2E,iBAAiB,CAACC,OAAO;IACzCC,MAAM;QAAEjB,MAAM1D,wBAAwB0D,IAAI;QAAEL,IAAI;YAAEY,OAAO;QAAU;IAAE;IACrEe,OAAO;QACLU,cAAc5F,MAAMoF,YAAY,CAACC,OAAO;QACxCQ,kBAAkB7F,MAAMoF,YAAY,CAACC,OAAO;IAC9C;IACA,0EAA0E;IAC1E,2EAA2E;IAC3E,2EAA2E;IAC3E,+DAA+D;IAC/DS,kBAAkB;QAAC9F,MAAM+F,kBAAkB,CAACC,QAAQ;KAAC;IACrDV,YAAY;QACVzE;QACA;YACE0E,MAAM;YACNxB,OAAO;YACPU,aACE,qEACA,gEACA,oEACA,iEACA;YACFe,WAAWxF,MAAMyF,kBAAkB,CAACQ,aAAa;QACnD;KACD;AACH,EAAC;AAED,OAAO,MAAMC,yBAAgD;IAC3D3B,KAAKpD;IACLqD,UAAU5D;IACVkB,aAAa;IACb2C,aAAa;IACbC,UAAU1E,MAAM2E,iBAAiB,CAACC,OAAO;IACzCC,MAAM;QAAEjB,MAAM1D,wBAAwB0D,IAAI;QAAEL,IAAI;YAAEY,OAAO;QAAU;IAAE;AACvE,EAAC;AAED,MAAMgC,QAAQ,CAACC,SAAiBC,OAAkB,CAAA;QAChD9B,KAAK;QACL+B,aAAarF;QACbuD,UAAU5D;QACVS,OAAO,CAAC;QACRkF,OAAO;YACL;gBACEhC,KAAK;gBACL+B,aAAapF;gBACbsD,UAAU5D;gBACVS,OAAO;oBAAEG,UAAU4E;gBAAQ;YAC7B;YACA;gBACE7B,KAAK;gBACL+B,aAAanF;gBACbqD,UAAU5D;gBACV2F,OAAO;oBACL;wBACEhC,KAAK;wBACL+B,aAAa;wBACb9B,UAAU5D;wBACVS,OAAO;4BAAEmF,SAAS;4BAAShF,UAAU6E;wBAAK;oBAC5C;iBACD;YACH;SACD;IACH,CAAA;AAEA,OAAO,MAAMI,mBAAyC;IACpD;QACElC,KAAKvD,iBAAiBC;QACtByF,MAAM1G,MAAM2G,QAAQ,CAACC,MAAM;QAC3B9E,aAAa;QACb0C,UAAU5D;QACV6D,aAAa;QACbC,UAAU1E,MAAM2E,iBAAiB,CAACC,OAAO;QACzCC,MAAMP,gBAAgBO,IAAI;QAC1BgC,MAAMV,MAAM,oBAAoB;IAClC;IACA;QACE5B,KAAKvD,iBAAiBC,cAAc;QACpCyF,MAAM1G,MAAM2G,QAAQ,CAACC,MAAM;QAC3B9E,aAAa;QACb0C,UAAU5D;QACV6D,aAAa;QACbC,UAAU1E,MAAM2E,iBAAiB,CAACC,OAAO;QACzCC,MAAM;YAAEjB,MAAM1D,wBAAwB0D,IAAI;YAAEL,IAAI;gBAAEY,OAAO;YAAU;QAAE;QACrE0C,MAAM;YACJtC,KAAK;YACL+B,aAAa;YACb9B,UAAU5D;YACVS,OAAO;gBAAEyF,WAAW;YAAS;YAC7BP,OAAO;gBACLJ,MAAM,yBAAyB;gBAC/BA,MAAM,sBAAsB;gBAC5BA,MAAM,yBAAyB;aAChC;QACH;IACF;CACD,CAAA;AAED,eAAe/E,iBAAgB"}
@@ -0,0 +1,29 @@
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 { type CollectionEntriesProps } from './collection-entries';
18
+ /**
19
+ * Search (AGL-1516): a toolbar search box filters the rendered entry clones
20
+ * client-side against the server-stamped `searchIndex` — fuzzy, via the same
21
+ * Fuse the icon picker uses. The clones arrive as `children` in stamp order,
22
+ * one group of template roots per entry, so group N of the children IS entry
23
+ * N of the index. Matches keep the list's own order rather than Fuse's score
24
+ * order: a blog list is chronological, and search narrows it, it does not
25
+ * reshuffle it. In the besigner the field renders as an inert affordance (the
26
+ * template renders once with literal tokens and there is no index to search),
27
+ * matching how Category Pills go inert on editing surfaces.
28
+ */
29
+ export declare const SearchingEntries: import("react").ForwardRefExoticComponent<Omit<CollectionEntriesProps, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,228 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_without_properties_loose";
3
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
4
+ /**
5
+ * @license
6
+ * Copyright 2026 Aglyn LLC
7
+ *
8
+ * Licensed under the Apache License, Version 2.0 (the "License");
9
+ * you may not use this file except in compliance with the License.
10
+ * You may obtain a copy of the License at
11
+ *
12
+ * http://www.apache.org/licenses/LICENSE-2.0
13
+ *
14
+ * Unless required by applicable law or agreed to in writing, software
15
+ * distributed under the License is distributed on an "AS IS" BASIS,
16
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ * See the License for the specific language governing permissions and
18
+ * limitations under the License.
19
+ */ import * as Aglyn from "@aglyn/aglyn";
20
+ import MuiStack from "@mui/material/Stack";
21
+ import Typography from "@mui/material/Typography";
22
+ import { Children, forwardRef, useContext, useState } from "react";
23
+ // One coercion for every authored count in this bundle (AGL-1457) — number
24
+ // fields round-trip as strings, and a second parser here would drift.
25
+ import { toCount } from "../utils/to-count.js";
26
+ import { ENTRIES_SEARCH_ATTRIBUTE } from "./collection-entries.js";
27
+ import { SearchField, SuggestSearchBox, useEntryFuse } from "./collection-search-box.js";
28
+ /**
29
+ * The Collection Entries block with its search box on (AGL-1516, AGL-1525).
30
+ *
31
+ * Its own module, loaded only by a block whose `search` is on (AGL-3438): the
32
+ * box draws MUI's InputBase, TextareaAutosize and Chip, about 22 KB the
33
+ * entries block otherwise has no use for, and most blocks — aglyn.com/press's
34
+ * among them — never turn search on. `collection-entries.tsx` renders the
35
+ * plain block itself and reaches for this one through `loadEntriesSearch`.
36
+ */ /**
37
+ * How much of the collection an entries block's search could actually see
38
+ * (AGL-1516, AGL-2569) — `truncated` says the rendered window is smaller
39
+ * than the set, `paginated` says the missing part lives on other pages of
40
+ * this same list rather than merely outside the block.
41
+ *
42
+ * The test is whether the window is SMALLER THAN THE SET, which only the
43
+ * server knows — `perPage` is the wrong proxy for it in both directions. A
44
+ * block truncated by `entriesLimit` or by the 100-entry cap has no `perPage`
45
+ * at all and would claim a global miss over 6 of 40 posts; a block whose
46
+ * `perPage` exceeds its entry count is a single complete page and would
47
+ * blame pages that do not exist. `searchTotal` is stamped with `searchIndex`,
48
+ * so the two are always in step.
49
+ *
50
+ * Absent — a page cached before that stamp shipped — falls back to the old
51
+ * `perPage` reading rather than guessing "complete": understating the scope
52
+ * of a search is the safe direction to be wrong in.
53
+ *
54
+ * `searchCapped` is the second way a block can fail to hold the set, and the
55
+ * invisible one: `searchTotal` counts the entries the server READ, and that
56
+ * read is bounded. Past the bound `shown === searchTotal` is satisfied by a
57
+ * block holding 100 of 400 posts, and the wording that claims to have looked
58
+ * everywhere is exactly the branch it would take.
59
+ *
60
+ * Both the miss and the hit disclose off this one answer, so the two can
61
+ * never disagree about the same block.
62
+ */ const searchScope = ({ perPage, shown, searchTotal, searchCapped })=>{
63
+ var _toCount;
64
+ const paginated = ((_toCount = toCount(perPage, 0)) != null ? _toCount : 0) > 0;
65
+ return {
66
+ paginated,
67
+ truncated: typeof searchTotal === 'number' ? shown < searchTotal || Boolean(searchCapped) : paginated || Boolean(searchCapped)
68
+ };
69
+ };
70
+ /**
71
+ * Search (AGL-1516): a toolbar search box filters the rendered entry clones
72
+ * client-side against the server-stamped `searchIndex` — fuzzy, via the same
73
+ * Fuse the icon picker uses. The clones arrive as `children` in stamp order,
74
+ * one group of template roots per entry, so group N of the children IS entry
75
+ * N of the index. Matches keep the list's own order rather than Fuse's score
76
+ * order: a blog list is chronological, and search narrows it, it does not
77
+ * reshuffle it. In the besigner the field renders as an inert affordance (the
78
+ * template renders once with literal tokens and there is no index to search),
79
+ * matching how Category Pills go inert on editing surfaces.
80
+ */ export const SearchingEntries = /*#__PURE__*/ forwardRef(// collectionSlug/entriesLimit/filter*/search* are compose-time or
81
+ // search-only attributes: strip so they never hit the DOM.
82
+ (_0, _1)=>{
83
+ let _ref = [
84
+ _0,
85
+ _1
86
+ ], [_ref1, ..._rest] = _ref, { collectionSlug, entriesLimit, filterCategory, filterTag, perPage, page, firstPageOnly, search, searchMode, searchPlaceholder, searchIndex, searchTotal, searchCapped, children } = _ref1, props = _object_without_properties_loose(_ref1, [
87
+ "collectionSlug",
88
+ "entriesLimit",
89
+ "filterCategory",
90
+ "filterTag",
91
+ "perPage",
92
+ "page",
93
+ "firstPageOnly",
94
+ "search",
95
+ "searchMode",
96
+ "searchPlaceholder",
97
+ "searchIndex",
98
+ "searchTotal",
99
+ "searchCapped",
100
+ "children"
101
+ ]), [ref] = _rest;
102
+ const { suppressNavigation } = useContext(Aglyn.ScreenLinkContext);
103
+ const items = searchIndex;
104
+ /** `filter` mode only — `suggest` owns its own query (AGL-1516). */ const [query, setQuery] = useState('');
105
+ const { fuzzy, requestFuzzy } = useEntryFuse(items);
106
+ const childArray = Children.toArray(children);
107
+ // Template roots per entry. Every entry clones the same template, so the
108
+ // children divide evenly; anything else means the children are not the
109
+ // stamped clones, and filtering blind would hide the wrong cards —
110
+ // fail open and render everything.
111
+ const groupSize = (items == null ? void 0 : items.length) && childArray.length % items.length === 0 ? childArray.length / items.length : 0;
112
+ // `suggest` draws its rows from the INDEX and never touches the clones,
113
+ // so the clone-alignment precondition is a `filter` precondition only.
114
+ // A block whose children do not divide evenly still suggests correctly;
115
+ // requiring `groupSize` here would silently kill the panel on exactly
116
+ // the templates that made the fail-open necessary.
117
+ const mode = searchMode === 'suggest' ? 'suggest' : 'filter';
118
+ // Whether there is an index to search, NOT whether the matcher has loaded:
119
+ // the field has to be there for the keystroke that loads it.
120
+ const live = !suppressNavigation && Boolean(items == null ? void 0 : items.length) && (mode === 'suggest' || groupSize > 0);
121
+ const trimmed = query.trim();
122
+ let visible = children;
123
+ /**
124
+ * The one line under the results that says what the search covered —
125
+ * either a miss scoped to the set that came back empty, or a hit scoped
126
+ * to the window it was drawn from. Never both.
127
+ */ let scopeNote = null;
128
+ if (mode === 'suggest') {
129
+ // The grid is deliberately untouched (Figma 496:1218): the panel is an
130
+ // overlay on a page the reader is still reading, not a filter.
131
+ visible = children;
132
+ } else if (live && trimmed && fuzzy) {
133
+ var _ref2;
134
+ const matched = new Set(fuzzy.search(trimmed).map((result)=>result.refIndex));
135
+ visible = childArray.filter((_, index)=>matched.has(Math.floor(index / groupSize)));
136
+ // HONEST scope (AGL-1516, AGL-2569): this block holds whatever window
137
+ // the expansion gave it, and pretending the search was global would
138
+ // turn every miss into a false "this post does not exist" — and every
139
+ // hit into a result set that looks like the whole answer.
140
+ const shown = (_ref2 = items == null ? void 0 : items.length) != null ? _ref2 : 0;
141
+ const { paginated, truncated } = searchScope({
142
+ perPage,
143
+ shown,
144
+ searchTotal,
145
+ searchCapped
146
+ });
147
+ if (!visible.length) {
148
+ scopeNote = /*#__PURE__*/ _jsx(Typography, {
149
+ variant: "body2",
150
+ sx: {
151
+ color: 'text.secondary'
152
+ },
153
+ children: !truncated ? `No matches for “${trimmed}”.` : paginated ? `No matches for “${trimmed}” on this page — other pages ` + 'are not searched.' : `No matches for “${trimmed}” in the ${shown} entries ` + 'shown here — the rest of the collection is not searched.'
154
+ });
155
+ } else if (truncated) {
156
+ // The hit half of the same honesty (AGL-2569). Matches are drawn from
157
+ // the same window the miss apologizes for, they carry no pager of
158
+ // their own, and nothing else on screen says the pages behind this
159
+ // one went unread — so a list that happens to be partial reads as
160
+ // complete. A block that holds the whole collection says nothing
161
+ // here: a disclaimer on a complete answer is noise, and every small
162
+ // site would carry it.
163
+ //
164
+ // No route out to `/search?q=` (AGL-88): the suggestion panel links
165
+ // there because it is the panel's only possible answer, but this
166
+ // mode has a working list under the field and that page does not
167
+ // exist yet. Trading a silent partial answer for a dead link is not
168
+ // the trade.
169
+ scopeNote = /*#__PURE__*/ _jsx(Typography, {
170
+ variant: "body2",
171
+ sx: {
172
+ color: 'text.secondary'
173
+ },
174
+ children: paginated ? 'Showing matches on this page — other pages are not searched.' : `Showing matches in the ${shown} entries here — the rest of ` + 'the collection is not searched.'
175
+ });
176
+ }
177
+ }
178
+ // The field renders when there is something to search, and as an inert
179
+ // affordance on editing surfaces so the author sees what they enabled.
180
+ // A live surface with nothing stamped (unknown collection, zero entries)
181
+ // renders no field at all — a search box over nothing is a lie.
182
+ const showField = suppressNavigation || live;
183
+ const field = !showField ? null : mode === 'suggest' ? // Rows in Fuse's SCORE order, the opposite of what `filter` does and
184
+ // deliberately: filtering narrows a chronological feed, so reshuffling
185
+ // it would be a second, unasked-for change to what the reader is
186
+ // looking at. A suggestion list is not a feed — it is an answer to a
187
+ // question, and the best answer belongs first.
188
+ /*#__PURE__*/ _jsx(SuggestSearchBox, _extends({
189
+ fuzzy: fuzzy,
190
+ requestFuzzy: requestFuzzy
191
+ }, items ? {
192
+ items
193
+ } : {}, searchPlaceholder === undefined ? {} : {
194
+ placeholder: searchPlaceholder
195
+ }, suppressNavigation ? {
196
+ inert: true
197
+ } : {}, {
198
+ // This block searched the entries IT rendered — a page window, an
199
+ // `entriesLimit` slice, or a bounded read. Never "this collection".
200
+ emptyText: (text)=>`No matches for “${text}” on this page.`
201
+ })) : /*#__PURE__*/ _jsx(SearchField, _extends({
202
+ value: query,
203
+ landmark: true
204
+ }, searchPlaceholder === undefined ? {} : {
205
+ placeholder: searchPlaceholder
206
+ }, suppressNavigation ? {
207
+ inert: true
208
+ } : {}, {
209
+ onQuery: (next)=>{
210
+ requestFuzzy();
211
+ setQuery(next);
212
+ }
213
+ }));
214
+ return /*#__PURE__*/ _jsxs(MuiStack, _extends({
215
+ ref: ref,
216
+ spacing: 4
217
+ }, props, {
218
+ [ENTRIES_SEARCH_ATTRIBUTE]: '',
219
+ children: [
220
+ field,
221
+ visible,
222
+ scopeNote
223
+ ]
224
+ }));
225
+ });
226
+ SearchingEntries.displayName = 'AglynCollectionEntriesSearch';
227
+
228
+ //# sourceMappingURL=collection-entries-search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/mui/src/lib/components/collection-entries-search.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 MuiStack from '@mui/material/Stack'\nimport Typography from '@mui/material/Typography'\nimport type { ReactNode } from 'react'\nimport { Children, forwardRef, useContext, useState } from 'react'\n// One coercion for every authored count in this bundle (AGL-1457) — number\n// fields round-trip as strings, and a second parser here would drift.\nimport { toCount } from '../utils/to-count'\nimport {\n ENTRIES_SEARCH_ATTRIBUTE,\n type CollectionEntriesProps,\n type CollectionSearchMode,\n} from './collection-entries'\nimport {\n SearchField,\n SuggestSearchBox,\n useEntryFuse,\n} from './collection-search-box'\n\n/**\n * The Collection Entries block with its search box on (AGL-1516, AGL-1525).\n *\n * Its own module, loaded only by a block whose `search` is on (AGL-3438): the\n * box draws MUI's InputBase, TextareaAutosize and Chip, about 22 KB the\n * entries block otherwise has no use for, and most blocks — aglyn.com/press's\n * among them — never turn search on. `collection-entries.tsx` renders the\n * plain block itself and reaches for this one through `loadEntriesSearch`.\n */\n\n/**\n * How much of the collection an entries block's search could actually see\n * (AGL-1516, AGL-2569) — `truncated` says the rendered window is smaller\n * than the set, `paginated` says the missing part lives on other pages of\n * this same list rather than merely outside the block.\n *\n * The test is whether the window is SMALLER THAN THE SET, which only the\n * server knows — `perPage` is the wrong proxy for it in both directions. A\n * block truncated by `entriesLimit` or by the 100-entry cap has no `perPage`\n * at all and would claim a global miss over 6 of 40 posts; a block whose\n * `perPage` exceeds its entry count is a single complete page and would\n * blame pages that do not exist. `searchTotal` is stamped with `searchIndex`,\n * so the two are always in step.\n *\n * Absent — a page cached before that stamp shipped — falls back to the old\n * `perPage` reading rather than guessing \"complete\": understating the scope\n * of a search is the safe direction to be wrong in.\n *\n * `searchCapped` is the second way a block can fail to hold the set, and the\n * invisible one: `searchTotal` counts the entries the server READ, and that\n * read is bounded. Past the bound `shown === searchTotal` is satisfied by a\n * block holding 100 of 400 posts, and the wording that claims to have looked\n * everywhere is exactly the branch it would take.\n *\n * Both the miss and the hit disclose off this one answer, so the two can\n * never disagree about the same block.\n */\nconst searchScope = ({\n perPage,\n shown,\n searchTotal,\n searchCapped,\n}: {\n perPage?: CollectionEntriesProps['perPage']\n /** Entries this block searched — the length of its stamped index. */\n shown: number\n searchTotal?: number\n searchCapped?: boolean\n}): { paginated: boolean; truncated: boolean } => {\n const paginated = (toCount(perPage, 0) ?? 0) > 0\n return {\n paginated,\n truncated:\n typeof searchTotal === 'number'\n ? shown < searchTotal || Boolean(searchCapped)\n : paginated || Boolean(searchCapped),\n }\n}\n\n/**\n * Search (AGL-1516): a toolbar search box filters the rendered entry clones\n * client-side against the server-stamped `searchIndex` — fuzzy, via the same\n * Fuse the icon picker uses. The clones arrive as `children` in stamp order,\n * one group of template roots per entry, so group N of the children IS entry\n * N of the index. Matches keep the list's own order rather than Fuse's score\n * order: a blog list is chronological, and search narrows it, it does not\n * reshuffle it. In the besigner the field renders as an inert affordance (the\n * template renders once with literal tokens and there is no index to search),\n * matching how Category Pills go inert on editing surfaces.\n */\nexport const SearchingEntries = forwardRef<\n HTMLDivElement,\n CollectionEntriesProps\n>(\n // collectionSlug/entriesLimit/filter*/search* are compose-time or\n // search-only attributes: strip so they never hit the DOM.\n (\n {\n collectionSlug,\n entriesLimit,\n filterCategory,\n filterTag,\n perPage,\n page,\n firstPageOnly,\n search,\n searchMode,\n searchPlaceholder,\n searchIndex,\n searchTotal,\n searchCapped,\n children,\n ...props\n },\n ref,\n ) => {\n const { suppressNavigation } = useContext(Aglyn.ScreenLinkContext)\n const items = searchIndex\n /** `filter` mode only — `suggest` owns its own query (AGL-1516). */\n const [query, setQuery] = useState('')\n const { fuzzy, requestFuzzy } = useEntryFuse(items)\n const childArray = Children.toArray(children)\n // Template roots per entry. Every entry clones the same template, so the\n // children divide evenly; anything else means the children are not the\n // stamped clones, and filtering blind would hide the wrong cards —\n // fail open and render everything.\n const groupSize =\n items?.length && childArray.length % items.length === 0\n ? childArray.length / items.length\n : 0\n // `suggest` draws its rows from the INDEX and never touches the clones,\n // so the clone-alignment precondition is a `filter` precondition only.\n // A block whose children do not divide evenly still suggests correctly;\n // requiring `groupSize` here would silently kill the panel on exactly\n // the templates that made the fail-open necessary.\n const mode: CollectionSearchMode =\n searchMode === 'suggest' ? 'suggest' : 'filter'\n // Whether there is an index to search, NOT whether the matcher has loaded:\n // the field has to be there for the keystroke that loads it.\n const live =\n !suppressNavigation &&\n Boolean(items?.length) &&\n (mode === 'suggest' || groupSize > 0)\n const trimmed = query.trim()\n let visible: ReactNode[] | ReactNode = children\n /**\n * The one line under the results that says what the search covered —\n * either a miss scoped to the set that came back empty, or a hit scoped\n * to the window it was drawn from. Never both.\n */\n let scopeNote: ReactNode = null\n if (mode === 'suggest') {\n // The grid is deliberately untouched (Figma 496:1218): the panel is an\n // overlay on a page the reader is still reading, not a filter.\n visible = children\n } else if (live && trimmed && fuzzy) {\n const matched = new Set(\n fuzzy.search(trimmed).map((result) => result.refIndex),\n )\n visible = childArray.filter((_, index) =>\n matched.has(Math.floor(index / groupSize)),\n )\n // HONEST scope (AGL-1516, AGL-2569): this block holds whatever window\n // the expansion gave it, and pretending the search was global would\n // turn every miss into a false \"this post does not exist\" — and every\n // hit into a result set that looks like the whole answer.\n const shown = items?.length ?? 0\n const { paginated, truncated } = searchScope({\n perPage,\n shown,\n searchTotal,\n searchCapped,\n })\n if (!(visible as ReactNode[]).length) {\n scopeNote = (\n <Typography variant=\"body2\" sx={{ color: 'text.secondary' }}>\n {!truncated\n ? `No matches for “${trimmed}”.`\n : paginated\n ? `No matches for “${trimmed}” on this page — other pages ` +\n 'are not searched.'\n : `No matches for “${trimmed}” in the ${shown} entries ` +\n 'shown here — the rest of the collection is not searched.'}\n </Typography>\n )\n } else if (truncated) {\n // The hit half of the same honesty (AGL-2569). Matches are drawn from\n // the same window the miss apologizes for, they carry no pager of\n // their own, and nothing else on screen says the pages behind this\n // one went unread — so a list that happens to be partial reads as\n // complete. A block that holds the whole collection says nothing\n // here: a disclaimer on a complete answer is noise, and every small\n // site would carry it.\n //\n // No route out to `/search?q=` (AGL-88): the suggestion panel links\n // there because it is the panel's only possible answer, but this\n // mode has a working list under the field and that page does not\n // exist yet. Trading a silent partial answer for a dead link is not\n // the trade.\n scopeNote = (\n <Typography variant=\"body2\" sx={{ color: 'text.secondary' }}>\n {paginated\n ? 'Showing matches on this page — other pages are not searched.'\n : `Showing matches in the ${shown} entries here — the rest of ` +\n 'the collection is not searched.'}\n </Typography>\n )\n }\n }\n // The field renders when there is something to search, and as an inert\n // affordance on editing surfaces so the author sees what they enabled.\n // A live surface with nothing stamped (unknown collection, zero entries)\n // renders no field at all — a search box over nothing is a lie.\n const showField = suppressNavigation || live\n const field = !showField ? null : mode === 'suggest' ? (\n // Rows in Fuse's SCORE order, the opposite of what `filter` does and\n // deliberately: filtering narrows a chronological feed, so reshuffling\n // it would be a second, unasked-for change to what the reader is\n // looking at. A suggestion list is not a feed — it is an answer to a\n // question, and the best answer belongs first.\n <SuggestSearchBox\n fuzzy={fuzzy}\n requestFuzzy={requestFuzzy}\n {...(items ? { items } : {})}\n {...(searchPlaceholder === undefined\n ? {}\n : { placeholder: searchPlaceholder })}\n {...(suppressNavigation ? { inert: true } : {})}\n // This block searched the entries IT rendered — a page window, an\n // `entriesLimit` slice, or a bounded read. Never \"this collection\".\n emptyText={(text) => `No matches for “${text}” on this page.`}\n />\n ) : (\n <SearchField\n value={query}\n landmark\n {...(searchPlaceholder === undefined ? {} : { placeholder: searchPlaceholder })}\n {...(suppressNavigation ? { inert: true } : {})}\n onQuery={(next) => {\n requestFuzzy()\n setQuery(next)\n }}\n />\n )\n return (\n <MuiStack\n ref={ref}\n spacing={4}\n {...props}\n // What the browser looks for before it hydrates the page, so this\n // module is fetched alongside the element rather than after it\n // (`prepareEntriesSearch`).\n {...{ [ENTRIES_SEARCH_ATTRIBUTE]: '' }}\n >\n {field}\n {visible}\n {scopeNote}\n </MuiStack>\n )\n },\n)\nSearchingEntries.displayName = 'AglynCollectionEntriesSearch'\n"],"names":["Aglyn","MuiStack","Typography","Children","forwardRef","useContext","useState","toCount","ENTRIES_SEARCH_ATTRIBUTE","SearchField","SuggestSearchBox","useEntryFuse","searchScope","perPage","shown","searchTotal","searchCapped","paginated","truncated","Boolean","SearchingEntries","collectionSlug","entriesLimit","filterCategory","filterTag","page","firstPageOnly","search","searchMode","searchPlaceholder","searchIndex","children","props","ref","suppressNavigation","ScreenLinkContext","items","query","setQuery","fuzzy","requestFuzzy","childArray","toArray","groupSize","length","mode","live","trimmed","trim","visible","scopeNote","matched","Set","map","result","refIndex","filter","_","index","has","Math","floor","variant","sx","color","showField","field","undefined","placeholder","inert","emptyText","text","value","landmark","onQuery","next","spacing","displayName"],"mappings":"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"}
@@ -114,22 +114,57 @@ export declare const COLLECTION_SEARCH_MODE_OPTIONS: ReadonlyArray<{
114
114
  value: CollectionSearchMode;
115
115
  label: string;
116
116
  }>;
117
+ /** The searching variant's module (AGL-3438). */
118
+ type EntriesSearchModule = typeof import('./collection-entries-search');
119
+ /**
120
+ * What a searching block stamps on its root. The browser looks for it before
121
+ * the page hydrates (`prepareEntriesSearch`); nothing styles or targets it.
122
+ */
123
+ export declare const ENTRIES_SEARCH_ATTRIBUTE = "data-aglyn-entries-search";
124
+ /**
125
+ * The searching variant, fetched once per visit however many blocks ask. A
126
+ * failed fetch (a deploy mid-visit) is forgotten rather than cached, so the
127
+ * next render that asks tries again.
128
+ */
129
+ export declare const loadEntriesSearch: () => Promise<EntriesSearchModule>;
130
+ /**
131
+ * Loads the searching variant BEFORE the element renders, wherever a render
132
+ * that waited for it would cost something (AGL-3438). The plugin calls it as
133
+ * the element registers (`prepare` in `plugin.ts`), and awaits it.
134
+ *
135
+ * - **On the server, always.** A render that suspended there would hold the
136
+ * whole streamed shell for the fetch — and a first, cold one would do it
137
+ * on a page the ISR cache then keeps.
138
+ * - **Where the whole library is loading** (`everything`): the console and
139
+ * the besigner, where an author can turn search on at any moment.
140
+ * - **In a browser about to hydrate a searching block**, which the server
141
+ * HTML says by carrying {@link ENTRIES_SEARCH_ATTRIBUTE}. The fetch then
142
+ * rides along with the element's own instead of starting when hydration
143
+ * reaches the block.
144
+ *
145
+ * Anywhere else nothing is fetched: a page whose blocks have search off —
146
+ * most of them — never downloads the box. A searching block that renders
147
+ * without it (a client-side navigation onto one) suspends until it lands,
148
+ * which holds the navigation, never a half-drawn list.
149
+ */
150
+ export declare const prepareEntriesSearch: (everything: boolean) => Promise<unknown> | undefined;
117
151
  /**
118
152
  * Repeats its children once per published entry of a content collection
119
153
  * (AGL-551) — the collections sibling of the dataset repeatable. The tenant
120
154
  * expands it at compose time with `{{entry.*}}` tokens; in the besigner the
121
155
  * template renders once with literal tokens, matching the repeatable UX.
122
156
  *
123
- * Search (AGL-1516): with `search` on, a toolbar search box filters the
124
- * rendered entry clones client-side against the server-stamped
125
- * `searchIndex` — fuzzy, via the same Fuse the icon picker uses. The clones
126
- * arrive as `children` in stamp order, one group of template roots per
127
- * entry, so group N of the children IS entry N of the index. Matches keep
128
- * the list's own order rather than Fuse's score order: a blog list is
129
- * chronological, and search narrows it, it does not reshuffle it. In the
130
- * besigner the field renders as an inert affordance (the template renders
131
- * once with literal tokens and there is no index to search), matching how
132
- * Category Pills go inert on editing surfaces.
157
+ * With `search` on (AGL-1516) the block is `SearchingEntries`, from its own
158
+ * module (AGL-3438). Off — the default, and most blocks — it is the plain
159
+ * stack below, and the search box's MUI input never reaches the page.
160
+ *
161
+ * There is NO Suspense boundary around the searching variant, on purpose. A
162
+ * boundary would have to be in the server's tree too, and a client render
163
+ * that suspended inside it would show its fallback: a list without its box,
164
+ * then the box pushing it down. Without one, a render that has to wait holds
165
+ * whatever is above it — the hydration of the page (whose server HTML stays
166
+ * exactly as it is), or a navigation (whose old page stays up) — and
167
+ * `prepareEntriesSearch` makes sure the published page almost never has to.
133
168
  */
134
169
  declare const CollectionEntries: import("react").ForwardRefExoticComponent<Omit<CollectionEntriesProps, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
135
170
  export declare const collectionEntriesSchema: Aglyn.ComponentSchema<CollectionEntriesProps>;