@aglyn/plugins-mui 1.0.0-beta.217 → 1.0.0-beta.219
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/README.md +2 -1
- package/package.json +9 -9
- package/src/lib/components/accordion.js +1 -1
- package/src/lib/components/accordion.js.map +1 -1
- package/src/lib/components/blocks.js +1 -1
- package/src/lib/components/blocks.js.map +1 -1
- package/src/lib/components/breadcrumbs.js +1 -1
- package/src/lib/components/breadcrumbs.js.map +1 -1
- package/src/lib/components/button.js +5 -5
- package/src/lib/components/button.js.map +1 -1
- package/src/lib/components/collection-categories.js +1 -1
- package/src/lib/components/collection-categories.js.map +1 -1
- package/src/lib/components/collection-entries.js +1 -1
- package/src/lib/components/collection-entries.js.map +1 -1
- package/src/lib/components/collection-entry-body.js +1 -1
- package/src/lib/components/collection-entry-body.js.map +1 -1
- package/src/lib/components/collection-search.js +1 -1
- package/src/lib/components/collection-search.js.map +1 -1
- package/src/lib/components/document-root.js +1 -1
- package/src/lib/components/document-root.js.map +1 -1
- package/src/lib/components/image.js +4 -4
- package/src/lib/components/image.js.map +1 -1
- package/src/lib/components/language-switcher.js +3 -3
- package/src/lib/components/language-switcher.js.map +1 -1
- package/src/lib/components/layout-slot.d.ts +1 -1
- package/src/lib/components/layout-slot.js +3 -3
- package/src/lib/components/layout-slot.js.map +1 -1
- package/src/lib/components/link-box.js +4 -4
- package/src/lib/components/link-box.js.map +1 -1
- package/src/lib/components/markdown.d.ts +3 -1
- package/src/lib/components/markdown.js +3 -3
- package/src/lib/components/markdown.js.map +1 -1
- package/src/lib/components/pagination.d.ts +1 -1
- package/src/lib/components/pagination.js +6 -2
- package/src/lib/components/pagination.js.map +1 -1
- package/src/lib/components/screen-link.js +7 -7
- package/src/lib/components/screen-link.js.map +1 -1
- package/src/lib/components/tabs.d.ts +1 -1
- package/src/lib/components/tabs.js +6 -2
- package/src/lib/components/tabs.js.map +1 -1
- package/src/lib/components/video.d.ts +5 -4
- package/src/lib/components/video.js +24 -22
- package/src/lib/components/video.js.map +1 -1
- package/src/lib/constants/field-presets.js +4 -0
- package/src/lib/constants/field-presets.js.map +1 -1
- package/src/lib/element-tier.d.ts +0 -2
- package/src/lib/element-tier.js +0 -2
- package/src/lib/element-tier.js.map +1 -1
- package/src/lib/plugin.js +0 -32
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/video-embed-providers.d.ts +30 -0
- package/src/lib/video-embed-providers.js +125 -0
- package/src/lib/video-embed-providers.js.map +1 -0
- package/src/lib/components/function-controls.d.ts +0 -66
- package/src/lib/components/function-controls.js +0 -207
- package/src/lib/components/function-controls.js.map +0 -1
- package/src/lib/components/function-scope.d.ts +0 -110
- package/src/lib/components/function-scope.js +0 -478
- package/src/lib/components/function-scope.js.map +0 -1
- package/src/lib/components/function-widget.d.ts +0 -75
- package/src/lib/components/function-widget.js +0 -307
- package/src/lib/components/function-widget.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/mui/src/lib/components/button.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 { mdiGestureTapButton } from '@aglyn/shared-data-mdi'\nimport { getMdiIconPath } from '@aglyn/shared-data-mdi'\nimport { AppLink, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport Button, { type ButtonProps } from '@mui/material/Button'\nimport { forwardRef } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { withAuthorHtmlLabel } from '../utils/author-html-label'\nimport { dropClearedProps } from '../utils/drop-cleared-props'\nimport {\n FIELD_COLOR,\n FIELD_DISABLED,\n FIELD_FULL_WIDTH,\n FIELD_LINK_TARGET,\n FIELD_LINK_TARGET_NAME,\n FIELD_SIZE,\n FIELD_TEXT_CONTENT,\n} from '../constants/field-presets'\nimport { generatePresetId } from '../utils/generate-preset-id'\nimport {\n type LinkTargetChoice,\n linkTargetProps,\n} from '../utils/link-target-props'\n\n// Component ids are persisted in screen documents; keep the legacy ids.\nexport const ID: Aglyn.ComponentId = 'muiButton'\n\nexport interface LinkableButtonProps extends ButtonProps {\n /** Target screen id; resolved from the routing map (AGL-139). */\n screenId?: string\n /** External URL, used only when no `screenId` is set. */\n href?: string\n /** mdi icon id rendered before the label (AGL-146). */\n startIconId?: string\n /** mdi icon id rendered after the label (AGL-146). */\n endIconId?: string\n /** Resolved SVG path for `startIconId`, stored at author time (AGL-1212). */\n startIconPath?: string\n /** Resolved SVG path for `endIconId`, stored at author time (AGL-1212). */\n endIconPath?: string\n /**\n * What the LINK MODE announces as (AGL-1426). Persisted in screen\n * documents; never rename, and `undefined` must keep meaning `'button'` —\n * every button authored before this existed is one.\n *\n * - `'button'` (or unset) — `<a role=\"button\">` with the button's look.\n * - `'linkButton'` — a plain `<a>` with NO role, keeping the button's\n * `variant`/`size`/`fullWidth`. Appearance without semantics.\n *\n * There is no `'link'` value here: a plain text link is `muiScreenLink`'s\n * job, and this component's whole purpose is the button styling.\n */\n renderAs?: 'button' | 'linkButton'\n /**\n * Where LINK MODE opens. Persisted in screen documents; never rename a\n * value, and `undefined` must keep meaning `'_self'`. Inert on a button\n * with nothing to point at — that is a real `<button>`, which has no\n * browsing context to choose.\n */\n target?: LinkTargetChoice\n /** Window name used only when `target` is `'custom'`. */\n targetName?: string\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}\n\n/**\n * What a button-styled LINK renders as while navigation is suppressed\n * (besigner canvas, preview). `role: undefined` is the load-bearing half:\n * MUI's `ButtonBase` stamps `role=\"button\"` onto any non-`<button>` root,\n * and merges the caller's props AFTER its own, so an explicit `undefined`\n * is what removes it. Mirrors `screen-link.tsx` (AGL-1347).\n */\nconst STYLED_LINK_PLACEHOLDER = { component: 'span', role: undefined } as const\n\n/**\n * mdi id → icon element; unknown/empty ids render nothing.\n *\n * Prefers the persisted path (AGL-1212) so render surfaces never need the\n * ~2.9 MB catalog, and falls back to the catalog for buttons authored before\n * paths were stored. `getMdiIconPath` rather than `getMdiIconFromId`: the\n * latter substitutes `DEFAULT_ICON`, which has a real path, so a miss used to\n * render a \"help\" glyph instead of nothing.\n */\nfunction iconFromId(iconId?: string, iconPath?: string) {\n const path = iconPath || getMdiIconPath(iconId)\n return path ? <MdiIcon path={path} fontSize=\"small\" /> : undefined\n}\n\n/**\n * Button with optional link mode (AGL-139): a screen id or external URL\n * renders through AppLink exactly like the Screen Link component —\n * degrading to a plain button in the besigner/preview and when the id\n * doesn't resolve.\n *\n * Resolution (id-vs-URL precedence, protocol hardening) is `useLinkTarget`\n * so a `Link`-typed component prop bound into EITHER field resolves the\n * same way here as in Screen Link, Link Box and Image (AGL-1335).\n *\n * `renderAs=\"linkButton\"` splits appearance from semantics in that link mode\n * (AGL-1426), the same split `screen-link.tsx` got in AGL-1347. The 12 chips\n * in \"Dig in, or just say hello\" on `aglyn.com/` are Buttons, not Screen\n * Links: docs URLs, `mailto:` and social profiles, every one of them purely\n * navigating, and every one announcing as a button — which tells a\n * screen-reader user to expect an action and costs them the link list and\n * the open-in-new-tab affordance. This mode keeps the `variant`/`size`/\n * `fullWidth` pixels and drops the role, exactly as MUI's own\n * `Button component={Link}` does.\n *\n * It only touches the link branch. A Button with no link target is a genuine\n * `<button>` whose role is correct, so the value is inert there.\n */\nconst LinkableButton = forwardRef<any, LinkableButtonProps>((props, ref) => {\n const {\n screenId,\n href: externalHref,\n startIconId,\n endIconId,\n startIconPath,\n endIconPath,\n renderAs,\n target,\n targetName,\n html,\n ...spread\n } = props\n // A CLEARED attribute persists as null, and null is not \"use the default\"\n // in React — `color={null}` reaches MUI, which capitalizes it and throws\n // error #7 during SSR, 500ing the page (AGL-1226).\n //\n // The formatted label (AGL-2557) is substituted for `children` here rather\n // than at each return below, so the two branches keep one shape and an\n // unformatted button renders the identical tree it always did.\n const rest = withAuthorHtmlLabel(dropClearedProps(spread), html)\n const iconProps = {\n startIcon: iconFromId(startIconId, startIconPath),\n endIcon: iconFromId(endIconId, endIconPath),\n }\n const { href, suppressNavigation, leavesSite } = Aglyn.useLinkTarget(\n screenId,\n externalHref,\n )\n // Resolved rather than spread: `'custom'` is an authoring sentinel, and an\n // anchor carrying `target=\"custom\"` would open a window literally named\n // that. The pair is destructured OUT of `spread` above, so the plain-button\n // and suppressed branches below never stamp it on a `<button>`.\n const targetAttrs = linkTargetProps(target, targetName, leavesSite)\n // Semantics and appearance are independent choices now (AGL-1426): a\n // styled link takes the button's styling props and none of its role.\n const asStyledLink = renderAs === 'linkButton'\n\n if (!href || suppressNavigation) {\n // The canvas must not lie about which element the page will ship, so a\n // suppressed styled link mirrors the live shape. Gated on `href` unlike\n // `screen-link.tsx`: there, no target still means a link, but here it\n // means a genuine button, which must keep being a real `<button>`.\n return (\n <Button\n ref={ref}\n {...(asStyledLink && href ? STYLED_LINK_PLACEHOLDER : {})}\n {...iconProps}\n {...rest}\n />\n )\n }\n return (\n <AppLink\n ref={ref}\n componentVariant=\"button\"\n href={href}\n {...targetAttrs}\n // The whole defect in one attribute. `ButtonBase` adds `role=\"button\"`\n // to every non-`<button>` root, so the button LOOK used to drag the\n // button ROLE onto a navigating anchor. It spreads the caller's props\n // after its own, so passing `undefined` here is what clears it — and\n // only for the mode that asked to be a link. `'button'` is what\n // `ButtonBase` stamps anyway, so nothing already authored moves.\n role={asStyledLink ? undefined : 'button'}\n {...iconProps}\n {...rest}\n />\n )\n})\nLinkableButton.displayName = 'LinkableButton'\n\nexport const schema: Aglyn.ComponentSchema<LinkableButtonProps> = {\n $id: ID,\n pluginId: BUNDLE_ID,\n displayName: 'Button',\n description:\n 'A button — or a link styled as one, if you point it at a screen or URL.',\n category: Aglyn.ComponentCategory.INPUT,\n icon: {\n path: mdiGestureTapButton.path,\n sx: { color: '#2196f3' },\n },\n flags: {\n textEditable: Aglyn.FEATURE_FLAG.ENABLED,\n richTextEditable: Aglyn.FEATURE_FLAG.ENABLED,\n },\n // Emphasis only (AGL-2557). With no link target this is a real `<button>`,\n // whose content model admits no list and no nested control; with one it is\n // an `<a>`, where a second anchor is the same invalid nesting. Both shapes\n // land on the same answer, so the schema does not have to know which one\n // an author picked.\n richTextCommands: [Aglyn.RICH_TEXT_COMMANDS.EMPHASIS],\n attributes: [\n FIELD_TEXT_CONTENT,\n FIELD_COLOR,\n FIELD_DISABLED,\n FIELD_FULL_WIDTH,\n FIELD_SIZE,\n {\n name: 'variant',\n description:\n 'How much weight the button carries: Contained is a filled primary ' +\n 'action, Outlined a secondary one, Text the lightest. One contained ' +\n 'button per view is a good rule.',\n component: Aglyn.FieldComponentType.SELECT,\n label: 'Variant',\n // \"Default\" deleted (AGL-1453): it could not persist, and `text` — the\n // value it resolved to, MUI's own default — is already on the list.\n options: [\n { value: 'text', label: 'Text' },\n { value: 'outlined', label: 'Outlined' },\n { value: 'contained', label: 'Contained' },\n ],\n },\n {\n name: 'screenId',\n description:\n 'Optional: navigate to this screen when clicked — the address ' +\n 'follows the published path like a Screen Link (AGL-139).',\n component: Aglyn.FieldComponentType.SCREEN_SELECT,\n label: 'Link to screen',\n },\n {\n name: 'href',\n description:\n 'Where the button goes when it points off this site. Ignored while ' +\n 'Link to screen names one — that field wins, so clear it to use a ' +\n 'URL. Absolute (https://…) for another site, or a path for one of ' +\n 'your own routes.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n label: 'External URL',\n // A bare `#fragment` saves and goes nowhere (AGL-2867).\n resolveProps: Aglyn.bareFragmentLinkFieldProps,\n },\n FIELD_LINK_TARGET,\n FIELD_LINK_TARGET_NAME,\n {\n name: 'renderAs',\n description:\n 'What this announces as once it links somewhere. Button is for ' +\n 'calls to action; pick \"Link (button styling)\" for a chip that only ' +\n 'navigates, so it keeps this look but announces as the link it is. ' +\n 'Ignored when there is no screen or URL to point at.',\n component: Aglyn.FieldComponentType.SELECT,\n label: 'Render as',\n // `'button'` is a REAL SENTINEL, not a deletion (AGL-1453). Unlike the\n // \"Default\" options on this component, `''` here spelled an author's\n // actual choice, and one they could not make: the attributes form\n // strips `''` before save (AGL-1191), so a button switched to Link\n // (button styling) had no route in this dropdown back to Button — the\n // pick reverted and the stored `linkButton` stayed.\n //\n // Safe because `renderAs` never reaches MUI: it is destructured out\n // above and read only as `renderAs === 'linkButton'`. So `'button'` and\n // an absent value take the identical branch, which is also what keeps\n // every button authored before this existed rendering unchanged.\n options: [\n { value: 'button', label: 'Button' },\n { value: 'linkButton', label: 'Link (button styling)' },\n ],\n },\n {\n name: 'startIconId',\n description: 'Icon shown before the label.',\n component: Aglyn.FieldComponentType.ICON_PICKER,\n label: 'Start icon',\n },\n {\n name: 'endIconId',\n description: 'Icon shown after the label.',\n component: Aglyn.FieldComponentType.ICON_PICKER,\n label: 'End icon',\n },\n ],\n}\n\nexport const presets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Outlined Button',\n icon: {\n path: mdiGestureTapButton.path,\n sx: { color: '#2196f3' },\n },\n category: Aglyn.ComponentCategory.INPUT,\n data: {\n $id: null,\n componentId: ID,\n pluginId: BUNDLE_ID,\n props: {\n variant: 'outlined',\n children: 'Click Me',\n },\n },\n },\n]\n\nexport default LinkableButton\n"],"names":["Aglyn","mdiGestureTapButton","getMdiIconPath","AppLink","MdiIcon","Button","forwardRef","BUNDLE_ID","withAuthorHtmlLabel","dropClearedProps","FIELD_COLOR","FIELD_DISABLED","FIELD_FULL_WIDTH","FIELD_LINK_TARGET","FIELD_LINK_TARGET_NAME","FIELD_SIZE","FIELD_TEXT_CONTENT","generatePresetId","linkTargetProps","ID","STYLED_LINK_PLACEHOLDER","component","role","undefined","iconFromId","iconId","iconPath","path","fontSize","LinkableButton","props","ref","screenId","href","externalHref","startIconId","endIconId","startIconPath","endIconPath","renderAs","target","targetName","html","spread","rest","iconProps","startIcon","endIcon","suppressNavigation","leavesSite","useLinkTarget","targetAttrs","asStyledLink","componentVariant","displayName","schema","$id","pluginId","description","category","ComponentCategory","INPUT","icon","sx","color","flags","textEditable","FEATURE_FLAG","ENABLED","richTextEditable","richTextCommands","RICH_TEXT_COMMANDS","EMPHASIS","attributes","name","FieldComponentType","SELECT","label","options","value","SCREEN_SELECT","TEXT_FIELD","resolveProps","bareFragmentLinkFieldProps","ICON_PICKER","presets","type","NodeType","PRESET","data","componentId","variant","children"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/mui/src/lib/components/button.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 { mdiGestureTapButton } from '@aglyn/shared-data-mdi'\nimport { getMdiIconPath } from '@aglyn/shared-data-mdi'\nimport { AppLink, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport Button, { type ButtonProps } from '@mui/material/Button'\nimport { forwardRef } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { withAuthorHtmlLabel } from '../utils/author-html-label'\nimport { dropClearedProps } from '../utils/drop-cleared-props'\nimport {\n FIELD_COLOR,\n FIELD_DISABLED,\n FIELD_FULL_WIDTH,\n FIELD_LINK_TARGET,\n FIELD_LINK_TARGET_NAME,\n FIELD_SIZE,\n FIELD_TEXT_CONTENT,\n} from '../constants/field-presets'\nimport { generatePresetId } from '../utils/generate-preset-id'\nimport {\n type LinkTargetChoice,\n linkTargetProps,\n} from '../utils/link-target-props'\n\n// Component ids are persisted in screen documents; keep the legacy ids.\nexport const ID: Aglyn.ComponentId = 'muiButton'\n\nexport interface LinkableButtonProps extends ButtonProps {\n /** Target screen id; resolved from the routing map (AGL-139). */\n screenId?: string\n /** External URL, used only when no `screenId` is set. */\n href?: string\n /** mdi icon id rendered before the label (AGL-146). */\n startIconId?: string\n /** mdi icon id rendered after the label (AGL-146). */\n endIconId?: string\n /** Resolved SVG path for `startIconId`, stored at author time (AGL-1212). */\n startIconPath?: string\n /** Resolved SVG path for `endIconId`, stored at author time (AGL-1212). */\n endIconPath?: string\n /**\n * What the LINK MODE announces as (AGL-1426). Persisted in screen\n * documents; never rename, and `undefined` must keep meaning `'button'` —\n * every button authored before this existed is one.\n *\n * - `'button'` (or unset) — `<a role=\"button\">` with the button's look.\n * - `'linkButton'` — a plain `<a>` with NO role, keeping the button's\n * `variant`/`size`/`fullWidth`. Appearance without semantics.\n *\n * There is no `'link'` value here: a plain text link is `muiScreenLink`'s\n * job, and this component's whole purpose is the button styling.\n */\n renderAs?: 'button' | 'linkButton'\n /**\n * Where LINK MODE opens. Persisted in screen documents; never rename a\n * value, and `undefined` must keep meaning `'_self'`. Inert on a button\n * with nothing to point at — that is a real `<button>`, which has no\n * browsing context to choose.\n */\n target?: LinkTargetChoice\n /** Window name used only when `target` is `'custom'`. */\n targetName?: string\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}\n\n/**\n * What a button-styled LINK renders as while navigation is suppressed\n * (besigner canvas, preview). `role: undefined` is the load-bearing half:\n * MUI's `ButtonBase` stamps `role=\"button\"` onto any non-`<button>` root,\n * and merges the caller's props AFTER its own, so an explicit `undefined`\n * is what removes it. Mirrors `screen-link.tsx` (AGL-1347).\n */\nconst STYLED_LINK_PLACEHOLDER = { component: 'span', role: undefined } as const\n\n/**\n * mdi id → icon element; unknown/empty ids render nothing.\n *\n * Prefers the persisted path (AGL-1212) so render surfaces never need the\n * ~2.9 MB catalog, and falls back to the catalog for buttons authored before\n * paths were stored. `getMdiIconPath` rather than `getMdiIconFromId`: the\n * latter substitutes `DEFAULT_ICON`, which has a real path, so a miss used to\n * render a \"help\" glyph instead of nothing.\n */\nfunction iconFromId(iconId?: string, iconPath?: string) {\n const path = iconPath || getMdiIconPath(iconId)\n return path ? <MdiIcon path={path} fontSize=\"small\" /> : undefined\n}\n\n/**\n * Button with optional link mode (AGL-139): a screen id or external URL\n * renders through AppLink exactly like the Screen Link component —\n * degrading to a plain button in the besigner/preview and when the id\n * doesn't resolve.\n *\n * Resolution (id-vs-URL precedence, protocol hardening) is `useLinkTarget`\n * so a `Link`-typed component prop bound into EITHER field resolves the\n * same way here as in Screen Link, Link Box and Image (AGL-1335).\n *\n * `renderAs=\"linkButton\"` splits appearance from semantics in that link mode\n * (AGL-1426), the same split `screen-link.tsx` got in AGL-1347. The 12 chips\n * in \"Dig in, or just say hello\" on `aglyn.com/` are Buttons, not Screen\n * Links: docs URLs, `mailto:` and social profiles, every one of them purely\n * navigating, and every one announcing as a button — which tells a\n * screen-reader user to expect an action and costs them the link list and\n * the open-in-new-tab affordance. This mode keeps the `variant`/`size`/\n * `fullWidth` pixels and drops the role, exactly as MUI's own\n * `Button component={Link}` does.\n *\n * It only touches the link branch. A Button with no link target is a genuine\n * `<button>` whose role is correct, so the value is inert there.\n */\nconst LinkableButton = forwardRef<any, LinkableButtonProps>((props, ref) => {\n const {\n screenId,\n href: externalHref,\n startIconId,\n endIconId,\n startIconPath,\n endIconPath,\n renderAs,\n target,\n targetName,\n html,\n ...spread\n } = props\n // A CLEARED attribute persists as null, and null is not \"use the default\"\n // in React — `color={null}` reaches MUI, which capitalizes it and throws\n // error #7 during SSR, 500ing the page (AGL-1226).\n //\n // The formatted label (AGL-2557) is substituted for `children` here rather\n // than at each return below, so the two branches keep one shape and an\n // unformatted button renders the identical tree it always did.\n const rest = withAuthorHtmlLabel(dropClearedProps(spread), html)\n const iconProps = {\n startIcon: iconFromId(startIconId, startIconPath),\n endIcon: iconFromId(endIconId, endIconPath),\n }\n const { href, suppressNavigation, leavesSite } = Aglyn.useLinkTarget(\n screenId,\n externalHref,\n )\n // Resolved rather than spread: `'custom'` is an authoring sentinel, and an\n // anchor carrying `target=\"custom\"` would open a window literally named\n // that. The pair is destructured OUT of `spread` above, so the plain-button\n // and suppressed branches below never stamp it on a `<button>`.\n const targetAttrs = linkTargetProps(target, targetName, leavesSite)\n // Semantics and appearance are independent choices now (AGL-1426): a\n // styled link takes the button's styling props and none of its role.\n const asStyledLink = renderAs === 'linkButton'\n\n if (!href || suppressNavigation) {\n // The canvas must not lie about which element the page will ship, so a\n // suppressed styled link mirrors the live shape. Gated on `href` unlike\n // `screen-link.tsx`: there, no target still means a link, but here it\n // means a genuine button, which must keep being a real `<button>`.\n return (\n <Button\n ref={ref}\n {...(asStyledLink && href ? STYLED_LINK_PLACEHOLDER : {})}\n {...iconProps}\n {...rest}\n />\n )\n }\n return (\n <AppLink\n ref={ref}\n componentVariant=\"button\"\n href={href}\n {...targetAttrs}\n // The whole defect in one attribute. `ButtonBase` adds `role=\"button\"`\n // to every non-`<button>` root, so the button LOOK used to drag the\n // button ROLE onto a navigating anchor. It spreads the caller's props\n // after its own, so passing `undefined` here is what clears it — and\n // only for the mode that asked to be a link. `'button'` is what\n // `ButtonBase` stamps anyway, so nothing already authored moves.\n role={asStyledLink ? undefined : 'button'}\n {...iconProps}\n {...rest}\n />\n )\n})\nLinkableButton.displayName = 'LinkableButton'\n\nexport const schema: Aglyn.ComponentSchema<LinkableButtonProps> = {\n $id: ID,\n pluginId: BUNDLE_ID,\n displayName: 'Button',\n description:\n 'A button — or a link styled as one, if you point it at a page or URL.',\n category: Aglyn.ComponentCategory.INPUT,\n icon: {\n path: mdiGestureTapButton.path,\n sx: { color: '#2196f3' },\n },\n flags: {\n textEditable: Aglyn.FEATURE_FLAG.ENABLED,\n richTextEditable: Aglyn.FEATURE_FLAG.ENABLED,\n },\n // Emphasis only (AGL-2557). With no link target this is a real `<button>`,\n // whose content model admits no list and no nested control; with one it is\n // an `<a>`, where a second anchor is the same invalid nesting. Both shapes\n // land on the same answer, so the schema does not have to know which one\n // an author picked.\n richTextCommands: [Aglyn.RICH_TEXT_COMMANDS.EMPHASIS],\n attributes: [\n FIELD_TEXT_CONTENT,\n FIELD_COLOR,\n FIELD_DISABLED,\n FIELD_FULL_WIDTH,\n FIELD_SIZE,\n {\n name: 'variant',\n description:\n 'How much weight the button carries: Contained is a filled primary ' +\n 'action, Outlined a secondary one, Text the lightest. One contained ' +\n 'button per view is a good rule.',\n component: Aglyn.FieldComponentType.SELECT,\n label: 'Variant',\n // \"Default\" deleted (AGL-1453): it could not persist, and `text` — the\n // value it resolved to, MUI's own default — is already on the list.\n options: [\n { value: 'text', label: 'Text' },\n { value: 'outlined', label: 'Outlined' },\n { value: 'contained', label: 'Contained' },\n ],\n },\n {\n name: 'screenId',\n description:\n 'Optional: navigate to this page when clicked — the address ' +\n 'follows the published path like a Page Link (AGL-139).',\n component: Aglyn.FieldComponentType.SCREEN_SELECT,\n label: 'Link to page',\n },\n {\n name: 'href',\n description:\n 'Where the button goes when it points off this site. Ignored while ' +\n 'Link to page names one — that field wins, so clear it to use a ' +\n 'URL. Absolute (https://…) for another site, or a path for one of ' +\n 'your own routes.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n label: 'External URL',\n // A bare `#fragment` saves and goes nowhere (AGL-2867).\n resolveProps: Aglyn.bareFragmentLinkFieldProps,\n },\n FIELD_LINK_TARGET,\n FIELD_LINK_TARGET_NAME,\n {\n name: 'renderAs',\n description:\n 'What this announces as once it links somewhere. Button is for ' +\n 'calls to action; pick \"Link (button styling)\" for a chip that only ' +\n 'navigates, so it keeps this look but announces as the link it is. ' +\n 'Ignored when there is no page or URL to point at.',\n component: Aglyn.FieldComponentType.SELECT,\n label: 'Render as',\n // `'button'` is a REAL SENTINEL, not a deletion (AGL-1453). Unlike the\n // \"Default\" options on this component, `''` here spelled an author's\n // actual choice, and one they could not make: the attributes form\n // strips `''` before save (AGL-1191), so a button switched to Link\n // (button styling) had no route in this dropdown back to Button — the\n // pick reverted and the stored `linkButton` stayed.\n //\n // Safe because `renderAs` never reaches MUI: it is destructured out\n // above and read only as `renderAs === 'linkButton'`. So `'button'` and\n // an absent value take the identical branch, which is also what keeps\n // every button authored before this existed rendering unchanged.\n options: [\n { value: 'button', label: 'Button' },\n { value: 'linkButton', label: 'Link (button styling)' },\n ],\n },\n {\n name: 'startIconId',\n description: 'Icon shown before the label.',\n component: Aglyn.FieldComponentType.ICON_PICKER,\n label: 'Start icon',\n },\n {\n name: 'endIconId',\n description: 'Icon shown after the label.',\n component: Aglyn.FieldComponentType.ICON_PICKER,\n label: 'End icon',\n },\n ],\n}\n\nexport const presets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Outlined Button',\n icon: {\n path: mdiGestureTapButton.path,\n sx: { color: '#2196f3' },\n },\n category: Aglyn.ComponentCategory.INPUT,\n data: {\n $id: null,\n componentId: ID,\n pluginId: BUNDLE_ID,\n props: {\n variant: 'outlined',\n children: 'Click Me',\n },\n },\n },\n]\n\nexport default LinkableButton\n"],"names":["Aglyn","mdiGestureTapButton","getMdiIconPath","AppLink","MdiIcon","Button","forwardRef","BUNDLE_ID","withAuthorHtmlLabel","dropClearedProps","FIELD_COLOR","FIELD_DISABLED","FIELD_FULL_WIDTH","FIELD_LINK_TARGET","FIELD_LINK_TARGET_NAME","FIELD_SIZE","FIELD_TEXT_CONTENT","generatePresetId","linkTargetProps","ID","STYLED_LINK_PLACEHOLDER","component","role","undefined","iconFromId","iconId","iconPath","path","fontSize","LinkableButton","props","ref","screenId","href","externalHref","startIconId","endIconId","startIconPath","endIconPath","renderAs","target","targetName","html","spread","rest","iconProps","startIcon","endIcon","suppressNavigation","leavesSite","useLinkTarget","targetAttrs","asStyledLink","componentVariant","displayName","schema","$id","pluginId","description","category","ComponentCategory","INPUT","icon","sx","color","flags","textEditable","FEATURE_FLAG","ENABLED","richTextEditable","richTextCommands","RICH_TEXT_COMMANDS","EMPHASIS","attributes","name","FieldComponentType","SELECT","label","options","value","SCREEN_SELECT","TEXT_FIELD","resolveProps","bareFragmentLinkFieldProps","ICON_PICKER","presets","type","NodeType","PRESET","data","componentId","variant","children"],"mappings":"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"}
|
|
@@ -133,7 +133,7 @@ export const collectionCategoriesSchema = {
|
|
|
133
133
|
{
|
|
134
134
|
name: 'collectionSlug',
|
|
135
135
|
label: 'Collection slug',
|
|
136
|
-
description: 'Content collection whose categories render as pills (e.g. ' + '"blog"). Leave blank on a list-template
|
|
136
|
+
description: 'Content collection whose categories render as pills (e.g. ' + '"blog"). Leave blank on a list-template page to use the ' + 'collection from the URL.',
|
|
137
137
|
component: Aglyn.FieldComponentType.TEXT_FIELD
|
|
138
138
|
},
|
|
139
139
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/mui/src/lib/components/collection-categories.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 { mdiTagMultipleOutline } from '@aglyn/shared-data-mdi'\nimport { AppLink } from '@aglyn/shared-ui-jsx'\nimport Box from '@mui/material/Box'\nimport MuiStack, { type StackProps } from '@mui/material/Stack'\nimport { forwardRef, useContext } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\n\n// Persisted component id (AGL-551/582); the compose pipeline references it\n// through the @aglyn/aglyn constant. Never rename.\nexport const CATEGORIES_ID: Aglyn.ComponentId =\n Aglyn.COLLECTION_CATEGORIES_COMPONENT_ID\n\nexport interface CollectionCategoriesProps extends StackProps {\n /**\n * Collection whose taxonomy renders (compose-time). Blank = the collection\n * routed by the current URL on list-template screens.\n */\n collectionSlug?: string\n /**\n * Label for the unfiltered pill (default \"All\"). Clearing the field\n * persists `Aglyn.COLLECTION_ALL_PILL_NONE`, which omits the pill —\n * `''` cannot be stored at all (AGL-1336).\n */\n allLabel?: string\n /**\n * Server-stamped pill links (`expandCollectionCategories`); never set by\n * hand — the tenant builds them from the collection's categories and the\n * routed category.\n */\n items?: Aglyn.CollectionCategoryLink[]\n}\n\n/** Pill look, in palette tokens so it follows the site theme in both modes. */\nconst pillSx = (active: boolean) => ({\n px: 1.75,\n py: 0.75,\n borderRadius: 999,\n border: '1px solid',\n borderColor: active ? 'primary.main' : 'divider',\n bgcolor: active ? 'primary.main' : 'transparent',\n color: active ? 'primary.contrastText' : 'text.secondary',\n fontSize: 14,\n lineHeight: 1.2,\n whiteSpace: 'nowrap',\n '&:hover': {\n borderColor: active ? 'primary.main' : 'text.primary',\n color: active ? 'primary.contrastText' : 'text.primary',\n },\n})\n\n/**\n * The category filter row for a collection listing (AGL-1321): \"All\" plus one\n * pill per category, each a REAL anchor to `/{collection}/category/{slug}`.\n *\n * Anchors, not click handlers, and that is the whole design. A JS-only toggle\n * would be invisible to crawlers, unopenable in a new tab, unlinkable, and\n * unreachable without hydration — and it could not exist anyway, since the\n * filter is resolved server-side before the page is composed. The pill the\n * current URL selected carries `aria-current=\"page\"`, stamped on the server so\n * it is in the HTML rather than derived after hydration.\n */\nconst CollectionCategories = forwardRef<\n HTMLDivElement,\n CollectionCategoriesProps\n>((props, ref) => {\n // `collectionSlug`/`allLabel` are compose-time: the tenant resolves them\n // while stamping `items`; strip so they never hit the DOM.\n const { collectionSlug, allLabel, items, ...rest } = props\n // Node styles ride the renderer-merged sx; recompose (stack.ts pattern).\n const nodeSx = Array.isArray(props['sx']) ? props['sx'] : [props['sx']]\n const { suppressNavigation } = useContext(Aglyn.ScreenLinkContext)\n if (!items?.length) {\n if (!suppressNavigation) return <Box ref={ref} {...rest} />\n return (\n <Box\n ref={ref}\n {...rest}\n sx={[\n {\n p: 2,\n border: '1px dashed',\n borderColor: 'divider',\n color: 'text.secondary',\n fontSize: 12,\n fontFamily: 'system-ui, sans-serif',\n },\n ...nodeSx,\n ]}\n >\n {'Category pills — All + one pill per collection category render here'}\n </Box>\n )\n }\n return (\n <MuiStack\n ref={ref}\n direction=\"row\"\n spacing={1}\n {...rest}\n // MERGE, never replace (AGL-1450) — see collection-entry-meta.tsx.\n sx={[{ flexWrap: 'wrap', rowGap: 1 }, ...nodeSx]}\n >\n {items.map((item) =>\n // Editing surfaces render the pill look without an href, so a click\n // in the besigner never navigates the canvas away.\n suppressNavigation ? (\n <Box key={item.href} sx={pillSx(item.active)}>\n {item.label}\n </Box>\n ) : (\n <AppLink\n key={item.href}\n href={item.href}\n underline=\"none\"\n aria-current={item.active ? 'page' : undefined}\n sx={pillSx(item.active)}\n >\n {item.label}\n </AppLink>\n ),\n )}\n </MuiStack>\n )\n})\nCollectionCategories.displayName = 'AglynCollectionCategories'\n\nexport const collectionCategoriesSchema: Aglyn.ComponentSchema<CollectionCategoriesProps> =\n {\n $id: CATEGORIES_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Category Pills',\n description: 'A pill per collection category, each filtering the listing.',\n category: Aglyn.ComponentCategory.NAVIGATION,\n icon: { path: mdiTagMultipleOutline.path, sx: { color: 'secondary.main' } },\n flags: { selfClosing: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'collectionSlug',\n label: 'Collection slug',\n description:\n 'Content collection whose categories render as pills (e.g. ' +\n '\"blog\"). Leave blank on a list-template screen to use the ' +\n 'collection from the URL.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'allLabel',\n label: 'All label',\n description:\n 'Label for the unfiltered pill, which links to the collection ' +\n 'root (default \"All\"). Clear the box to omit that pill — it then ' +\n 'reads \"none\", which is what makes the omission stick. Typing ' +\n '\"none\" does the same thing.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n // Without this the emptied field persists NOTHING (AGL-1336): ddf\n // maps an emptied value to `clearedValue`, final-form's parse turns\n // `''` into `undefined`, and the missing key takes the runtime\n // default — so \"clear it to omit that pill\" was undoable by\n // clicking. Same shape as FIELD_COLOR_ALT1's `'default'` (AGL-1191):\n // a value that means something has to BE something.\n //\n // ddf only substitutes `clearedValue` when the field HAD an initial\n // value (`enhancedOnChange`'s `typeof initial !== 'undefined'`),\n // which is why its preset (collection-presets.ts) seeds `allLabel`\n // and why the sentinel is a word an author can also type. A block\n // that never carried the prop has an already-empty box and nothing\n // to clear.\n clearedValue: Aglyn.COLLECTION_ALL_PILL_NONE,\n },\n ],\n }\n\nexport { CollectionCategories }\n"],"names":["Aglyn","mdiTagMultipleOutline","AppLink","Box","MuiStack","forwardRef","useContext","BUNDLE_ID","CATEGORIES_ID","COLLECTION_CATEGORIES_COMPONENT_ID","pillSx","active","px","py","borderRadius","border","borderColor","bgcolor","color","fontSize","lineHeight","whiteSpace","CollectionCategories","props","ref","collectionSlug","allLabel","items","rest","nodeSx","Array","isArray","suppressNavigation","ScreenLinkContext","length","sx","p","fontFamily","direction","spacing","flexWrap","rowGap","map","item","label","href","underline","aria-current","undefined","displayName","collectionCategoriesSchema","$id","pluginId","description","category","ComponentCategory","NAVIGATION","icon","path","flags","selfClosing","FEATURE_FLAG","ENABLED","attributes","name","component","FieldComponentType","TEXT_FIELD","clearedValue","COLLECTION_ALL_PILL_NONE"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/mui/src/lib/components/collection-categories.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 { mdiTagMultipleOutline } from '@aglyn/shared-data-mdi'\nimport { AppLink } from '@aglyn/shared-ui-jsx'\nimport Box from '@mui/material/Box'\nimport MuiStack, { type StackProps } from '@mui/material/Stack'\nimport { forwardRef, useContext } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\n\n// Persisted component id (AGL-551/582); the compose pipeline references it\n// through the @aglyn/aglyn constant. Never rename.\nexport const CATEGORIES_ID: Aglyn.ComponentId =\n Aglyn.COLLECTION_CATEGORIES_COMPONENT_ID\n\nexport interface CollectionCategoriesProps extends StackProps {\n /**\n * Collection whose taxonomy renders (compose-time). Blank = the collection\n * routed by the current URL on list-template screens.\n */\n collectionSlug?: string\n /**\n * Label for the unfiltered pill (default \"All\"). Clearing the field\n * persists `Aglyn.COLLECTION_ALL_PILL_NONE`, which omits the pill —\n * `''` cannot be stored at all (AGL-1336).\n */\n allLabel?: string\n /**\n * Server-stamped pill links (`expandCollectionCategories`); never set by\n * hand — the tenant builds them from the collection's categories and the\n * routed category.\n */\n items?: Aglyn.CollectionCategoryLink[]\n}\n\n/** Pill look, in palette tokens so it follows the site theme in both modes. */\nconst pillSx = (active: boolean) => ({\n px: 1.75,\n py: 0.75,\n borderRadius: 999,\n border: '1px solid',\n borderColor: active ? 'primary.main' : 'divider',\n bgcolor: active ? 'primary.main' : 'transparent',\n color: active ? 'primary.contrastText' : 'text.secondary',\n fontSize: 14,\n lineHeight: 1.2,\n whiteSpace: 'nowrap',\n '&:hover': {\n borderColor: active ? 'primary.main' : 'text.primary',\n color: active ? 'primary.contrastText' : 'text.primary',\n },\n})\n\n/**\n * The category filter row for a collection listing (AGL-1321): \"All\" plus one\n * pill per category, each a REAL anchor to `/{collection}/category/{slug}`.\n *\n * Anchors, not click handlers, and that is the whole design. A JS-only toggle\n * would be invisible to crawlers, unopenable in a new tab, unlinkable, and\n * unreachable without hydration — and it could not exist anyway, since the\n * filter is resolved server-side before the page is composed. The pill the\n * current URL selected carries `aria-current=\"page\"`, stamped on the server so\n * it is in the HTML rather than derived after hydration.\n */\nconst CollectionCategories = forwardRef<\n HTMLDivElement,\n CollectionCategoriesProps\n>((props, ref) => {\n // `collectionSlug`/`allLabel` are compose-time: the tenant resolves them\n // while stamping `items`; strip so they never hit the DOM.\n const { collectionSlug, allLabel, items, ...rest } = props\n // Node styles ride the renderer-merged sx; recompose (stack.ts pattern).\n const nodeSx = Array.isArray(props['sx']) ? props['sx'] : [props['sx']]\n const { suppressNavigation } = useContext(Aglyn.ScreenLinkContext)\n if (!items?.length) {\n if (!suppressNavigation) return <Box ref={ref} {...rest} />\n return (\n <Box\n ref={ref}\n {...rest}\n sx={[\n {\n p: 2,\n border: '1px dashed',\n borderColor: 'divider',\n color: 'text.secondary',\n fontSize: 12,\n fontFamily: 'system-ui, sans-serif',\n },\n ...nodeSx,\n ]}\n >\n {'Category pills — All + one pill per collection category render here'}\n </Box>\n )\n }\n return (\n <MuiStack\n ref={ref}\n direction=\"row\"\n spacing={1}\n {...rest}\n // MERGE, never replace (AGL-1450) — see collection-entry-meta.tsx.\n sx={[{ flexWrap: 'wrap', rowGap: 1 }, ...nodeSx]}\n >\n {items.map((item) =>\n // Editing surfaces render the pill look without an href, so a click\n // in the besigner never navigates the canvas away.\n suppressNavigation ? (\n <Box key={item.href} sx={pillSx(item.active)}>\n {item.label}\n </Box>\n ) : (\n <AppLink\n key={item.href}\n href={item.href}\n underline=\"none\"\n aria-current={item.active ? 'page' : undefined}\n sx={pillSx(item.active)}\n >\n {item.label}\n </AppLink>\n ),\n )}\n </MuiStack>\n )\n})\nCollectionCategories.displayName = 'AglynCollectionCategories'\n\nexport const collectionCategoriesSchema: Aglyn.ComponentSchema<CollectionCategoriesProps> =\n {\n $id: CATEGORIES_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Category Pills',\n description: 'A pill per collection category, each filtering the listing.',\n category: Aglyn.ComponentCategory.NAVIGATION,\n icon: { path: mdiTagMultipleOutline.path, sx: { color: 'secondary.main' } },\n flags: { selfClosing: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'collectionSlug',\n label: 'Collection slug',\n description:\n 'Content collection whose categories render as pills (e.g. ' +\n '\"blog\"). Leave blank on a list-template page to use the ' +\n 'collection from the URL.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'allLabel',\n label: 'All label',\n description:\n 'Label for the unfiltered pill, which links to the collection ' +\n 'root (default \"All\"). Clear the box to omit that pill — it then ' +\n 'reads \"none\", which is what makes the omission stick. Typing ' +\n '\"none\" does the same thing.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n // Without this the emptied field persists NOTHING (AGL-1336): ddf\n // maps an emptied value to `clearedValue`, final-form's parse turns\n // `''` into `undefined`, and the missing key takes the runtime\n // default — so \"clear it to omit that pill\" was undoable by\n // clicking. Same shape as FIELD_COLOR_ALT1's `'default'` (AGL-1191):\n // a value that means something has to BE something.\n //\n // ddf only substitutes `clearedValue` when the field HAD an initial\n // value (`enhancedOnChange`'s `typeof initial !== 'undefined'`),\n // which is why its preset (collection-presets.ts) seeds `allLabel`\n // and why the sentinel is a word an author can also type. A block\n // that never carried the prop has an already-empty box and nothing\n // to clear.\n clearedValue: Aglyn.COLLECTION_ALL_PILL_NONE,\n },\n ],\n }\n\nexport { CollectionCategories }\n"],"names":["Aglyn","mdiTagMultipleOutline","AppLink","Box","MuiStack","forwardRef","useContext","BUNDLE_ID","CATEGORIES_ID","COLLECTION_CATEGORIES_COMPONENT_ID","pillSx","active","px","py","borderRadius","border","borderColor","bgcolor","color","fontSize","lineHeight","whiteSpace","CollectionCategories","props","ref","collectionSlug","allLabel","items","rest","nodeSx","Array","isArray","suppressNavigation","ScreenLinkContext","length","sx","p","fontFamily","direction","spacing","flexWrap","rowGap","map","item","label","href","underline","aria-current","undefined","displayName","collectionCategoriesSchema","$id","pluginId","description","category","ComponentCategory","NAVIGATION","icon","path","flags","selfClosing","FEATURE_FLAG","ENABLED","attributes","name","component","FieldComponentType","TEXT_FIELD","clearedValue","COLLECTION_ALL_PILL_NONE"],"mappings":"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"}
|
|
@@ -266,7 +266,7 @@ export const collectionEntriesSchema = {
|
|
|
266
266
|
{
|
|
267
267
|
name: 'collectionSlug',
|
|
268
268
|
label: 'Collection slug',
|
|
269
|
-
description: 'Content collection whose published entries the children repeat ' + 'over (e.g. "blog"). Leave blank on a list-template
|
|
269
|
+
description: 'Content collection whose published entries the children repeat ' + 'over (e.g. "blog"). Leave blank on a list-template page to use ' + 'the collection from the URL.',
|
|
270
270
|
component: Aglyn.FieldComponentType.TEXT_FIELD
|
|
271
271
|
},
|
|
272
272
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/mui/src/lib/components/collection-entries.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 { mdiPostOutline } from '@aglyn/shared-data-mdi'\nimport MuiStack, { type StackProps } from '@mui/material/Stack'\nimport Typography from '@mui/material/Typography'\nimport type { ReactNode } from 'react'\nimport { Children, forwardRef, useContext, useState } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\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 SearchField,\n SuggestSearchBox,\n useEntryFuse,\n} from './collection-search-box'\n\n// Persisted component id (AGL-551/582); the compose pipeline references it\n// through the @aglyn/aglyn constant. Never rename.\nexport const ENTRIES_ID: Aglyn.ComponentId =\n Aglyn.COLLECTION_ENTRIES_COMPONENT_ID\n\nexport interface CollectionEntriesProps extends StackProps {\n /**\n * Collection to repeat over (compose-time, AGL-551). Blank = the\n * collection routed by the current URL on list-template screens.\n */\n collectionSlug?: string\n /** Maximum entries rendered (compose-time; blank = all, capped at 100). */\n entriesLimit?: number | string\n /**\n * Only entries in this category repeat (compose-time, AGL-582).\n * Matches the collection's category by stable id or display name.\n */\n filterCategory?: string\n /** Only entries carrying this tag repeat (compose-time, AGL-582). */\n filterTag?: string\n /**\n * Entries per page (compose-time, AGL-620). When set, the block renders one\n * page window instead of the top `entriesLimit`; the built-in collection\n * list uses this with the `page` from `/{collection}/page/{n}`.\n */\n perPage?: number | string\n /** 1-based page for `perPage` (compose-time, AGL-620). */\n page?: number | string\n /**\n * Render this block only on page 1 of the routed listing (compose-time,\n * AGL-1871). For a LEAD card — a block with no `perPage`, showing the top\n * of the set — every `/{collection}/page/{n}` past the first otherwise\n * repeats the identical entry above a page of different ones.\n *\n * Opt-in and default off: a block that legitimately belongs on every page\n * of a listing (a \"popular posts\" rail) has exactly the same shape.\n */\n firstPageOnly?: boolean\n /**\n * Show a search box that filters the RENDERED entries by title/excerpt as\n * the reader types (AGL-1516, Figma 494:1220). Opt-in and default off, so\n * every existing instance renders exactly as before.\n *\n * Client-evaluated over the entries this block already holds — the page is\n * ISR-cached, so a keystroke never costs a Firestore read. That set is\n * whatever the expansion left the block holding, which paging, an\n * `entriesLimit` or the 100-entry cap can each cut down. A truncated block\n * says so under its results either way — on a miss and on a hit\n * (AGL-1516, AGL-2569) — rather than pretending global search.\n */\n search?: boolean\n /**\n * What the search box DOES with a query (AGL-1525, Figma 496:1218).\n *\n * - `filter` (default, and the reading of an absent value) — hides the\n * non-matching cards in place. What AGL-1516 shipped.\n * - `suggest` — the card grid stays exactly as it was and a floating panel\n * of matching entries opens under the field, ending in a link to the\n * site-wide results page. The frame's toolbar behaviour: a reader\n * skimming the list is not made to lose it to a typo.\n */\n searchMode?: CollectionSearchMode\n /** Hint text inside the search box (blank = \"Search posts…\"). */\n searchPlaceholder?: string\n /**\n * Server-stamped matchable text per rendered entry\n * (`expandCollectionEntries`, AGL-1516); never set by hand.\n */\n searchIndex?: Aglyn.CollectionEntrySearchItem[]\n /**\n * How many entries `searchIndex` was drawn from — server-stamped by\n * `expandCollectionEntries` alongside it (AGL-1516), never set by hand.\n * `searchIndex.length < searchTotal` is the only honest test of whether\n * this block holds the whole set; `perPage` is not, because\n * `entriesLimit` and the 100-entry cap truncate too.\n */\n searchTotal?: number\n /**\n * Whether the read behind `searchTotal` reached its own bound — server-\n * stamped beside it (AGL-1516), never set by hand.\n *\n * `searchTotal` is a count of what the server SAW, and the collection read\n * is limited. Without this flag a block holding 100 of 400 posts satisfies\n * `searchIndex.length === searchTotal` and renders the one empty state that\n * claims to have looked everywhere.\n */\n searchCapped?: boolean\n}\n\n/** The two things the toolbar search box can do (AGL-1516/AGL-1525). */\nexport type CollectionSearchMode = 'filter' | 'suggest'\n\n/**\n * Author-facing choices for {@link CollectionEntriesProps.searchMode}.\n *\n * Both values are truthy on purpose (AGL-1453): `''` cannot survive a save,\n * so an author who tried the other mode would have no route back. \"Filter\"\n * carries the absent-value reading, which is what every block published\n * before AGL-1525 has.\n */\nexport const COLLECTION_SEARCH_MODE_OPTIONS: ReadonlyArray<{\n value: CollectionSearchMode\n label: string\n}> = [\n { value: 'filter', label: 'Filter the entries in place' },\n { value: 'suggest', label: 'Show a suggestions dropdown' },\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 * Repeats its children once per published entry of a content collection\n * (AGL-551) — the collections sibling of the dataset repeatable. The tenant\n * expands it at compose time with `{{entry.*}}` tokens; in the besigner the\n * template renders once with literal tokens, matching the repeatable UX.\n *\n * Search (AGL-1516): with `search` on, a toolbar search box filters the\n * rendered entry clones client-side against the server-stamped\n * `searchIndex` — fuzzy, via the same Fuse the icon picker uses. The clones\n * arrive as `children` in stamp order, one group of template roots per\n * entry, so group N of the children IS entry N of the index. Matches keep\n * the list's own order rather than Fuse's score order: a blog list is\n * chronological, and search narrows it, it does not reshuffle it. In the\n * besigner the field renders as an inert affordance (the template renders\n * once with literal tokens and there is no index to search), matching how\n * Category Pills go inert on editing surfaces.\n */\nconst CollectionEntries = forwardRef<HTMLDivElement, CollectionEntriesProps>(\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 /** `filter` mode only — `suggest` owns its own query (AGL-1516). */\n const [query, setQuery] = useState('')\n const items = search ? searchIndex : undefined\n const { fuzzy, requestFuzzy } = useEntryFuse(items)\n if (!search) {\n return (\n <MuiStack ref={ref} spacing={4} {...props}>\n {children}\n </MuiStack>\n )\n }\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 ref={ref} spacing={4} {...props}>\n {field}\n {visible}\n {scopeNote}\n </MuiStack>\n )\n },\n)\nCollectionEntries.displayName = 'AglynCollectionEntries'\n\nexport const collectionEntriesSchema: Aglyn.ComponentSchema<CollectionEntriesProps> =\n {\n $id: ENTRIES_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Collection Entries',\n description: 'Repeats its children once per entry in a content collection.',\n category: Aglyn.ComponentCategory.DATA_DISPLAY,\n icon: { path: mdiPostOutline.path, sx: { color: 'secondary.main' } },\n attributes: [\n {\n name: 'collectionSlug',\n label: 'Collection slug',\n description:\n 'Content collection whose published entries the children repeat ' +\n 'over (e.g. \"blog\"). Leave blank on a list-template screen to use ' +\n 'the collection from the URL.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'entriesLimit',\n label: 'Entries limit',\n description: 'Maximum entries rendered (blank = all, capped at 100).',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n {\n name: 'filterCategory',\n label: 'Filter by category',\n description:\n 'Only entries in this category repeat — the category name or its ' +\n 'stable id both match (e.g. \"Guides\"). Blank = no category filter.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'filterTag',\n label: 'Filter by tag',\n description:\n 'Only entries carrying this tag repeat (e.g. \"nextjs\"). Blank = ' +\n 'no tag filter.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'perPage',\n label: 'Entries per page',\n description:\n 'Paginate the list: entries per page (blank = no pagination). ' +\n 'Pairs with the page from /{collection}/page/{n}.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n {\n name: 'page',\n label: 'Page',\n description: '1-based page to render when Entries per page is set.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n {\n name: 'firstPageOnly',\n label: 'Only on page 1',\n description:\n 'Show this block on the first page of the listing only. Use it ' +\n 'for a featured or lead card: without it the same entry repeats ' +\n 'at the top of every /page/{n}, above a page of different ones.',\n component: Aglyn.FieldComponentType.SWITCH,\n },\n {\n name: 'search',\n label: 'Search',\n description:\n 'Show a search box that filters the rendered entries by title ' +\n 'and excerpt as the reader types. It searches the entries this ' +\n 'block rendered — whatever paging, an entry limit or the ' +\n '100-entry cap left it holding, and it says so on a miss.',\n component: Aglyn.FieldComponentType.SWITCH,\n },\n {\n name: 'searchMode',\n label: 'When the reader types',\n description:\n 'Filter the entries in place, or leave the list alone and open a ' +\n 'dropdown of matching entries under the box — each one a link, ' +\n 'ending in \"View all results\" for a search across the whole site.',\n component: Aglyn.FieldComponentType.SELECT,\n options: COLLECTION_SEARCH_MODE_OPTIONS.map((option) => ({\n ...option,\n })),\n // Meaningless while there is no search box to type in.\n condition: { when: 'search', is: true },\n },\n {\n name: 'searchPlaceholder',\n label: 'Search placeholder',\n description:\n 'Hint text inside this block’s own search box — the one that ' +\n 'filters the entries below it as a reader types. Blank shows ' +\n '\"Search posts…\".',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n // Meaningless while there is no search box to hint in.\n condition: { when: 'search', is: true },\n },\n // `searchIndex` and `searchTotal` are deliberately NOT attributes: both\n // are server-stamped by expandCollectionEntries, like Category Pills'\n // `items` and Related Posts' `entries`.\n {\n name: 'spacing',\n label: 'Spacing',\n description: 'Defines the space/gap between entries.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n ],\n }\n\nexport { CollectionEntries }\n// The collection family's palette presets ride this element's registry entry\n// (`presets: 'collectionPresets'` in plugin.ts), as they did when the family\n// was one module.\nexport { collectionPresets } from './collection-presets'\n"],"names":["Aglyn","mdiPostOutline","MuiStack","Typography","Children","forwardRef","useContext","useState","BUNDLE_ID","toCount","SearchField","SuggestSearchBox","useEntryFuse","ENTRIES_ID","COLLECTION_ENTRIES_COMPONENT_ID","COLLECTION_SEARCH_MODE_OPTIONS","value","label","searchScope","perPage","shown","searchTotal","searchCapped","paginated","truncated","Boolean","CollectionEntries","collectionSlug","entriesLimit","filterCategory","filterTag","page","firstPageOnly","search","searchMode","searchPlaceholder","searchIndex","children","props","ref","suppressNavigation","ScreenLinkContext","query","setQuery","items","undefined","fuzzy","requestFuzzy","spacing","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","placeholder","inert","emptyText","text","landmark","onQuery","next","displayName","collectionEntriesSchema","$id","pluginId","description","category","ComponentCategory","DATA_DISPLAY","icon","path","attributes","name","component","FieldComponentType","TEXT_FIELD","type","SWITCH","SELECT","options","option","condition","when","is","collectionPresets"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/mui/src/lib/components/collection-entries.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 { mdiPostOutline } from '@aglyn/shared-data-mdi'\nimport MuiStack, { type StackProps } from '@mui/material/Stack'\nimport Typography from '@mui/material/Typography'\nimport type { ReactNode } from 'react'\nimport { Children, forwardRef, useContext, useState } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\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 SearchField,\n SuggestSearchBox,\n useEntryFuse,\n} from './collection-search-box'\n\n// Persisted component id (AGL-551/582); the compose pipeline references it\n// through the @aglyn/aglyn constant. Never rename.\nexport const ENTRIES_ID: Aglyn.ComponentId =\n Aglyn.COLLECTION_ENTRIES_COMPONENT_ID\n\nexport interface CollectionEntriesProps extends StackProps {\n /**\n * Collection to repeat over (compose-time, AGL-551). Blank = the\n * collection routed by the current URL on list-template screens.\n */\n collectionSlug?: string\n /** Maximum entries rendered (compose-time; blank = all, capped at 100). */\n entriesLimit?: number | string\n /**\n * Only entries in this category repeat (compose-time, AGL-582).\n * Matches the collection's category by stable id or display name.\n */\n filterCategory?: string\n /** Only entries carrying this tag repeat (compose-time, AGL-582). */\n filterTag?: string\n /**\n * Entries per page (compose-time, AGL-620). When set, the block renders one\n * page window instead of the top `entriesLimit`; the built-in collection\n * list uses this with the `page` from `/{collection}/page/{n}`.\n */\n perPage?: number | string\n /** 1-based page for `perPage` (compose-time, AGL-620). */\n page?: number | string\n /**\n * Render this block only on page 1 of the routed listing (compose-time,\n * AGL-1871). For a LEAD card — a block with no `perPage`, showing the top\n * of the set — every `/{collection}/page/{n}` past the first otherwise\n * repeats the identical entry above a page of different ones.\n *\n * Opt-in and default off: a block that legitimately belongs on every page\n * of a listing (a \"popular posts\" rail) has exactly the same shape.\n */\n firstPageOnly?: boolean\n /**\n * Show a search box that filters the RENDERED entries by title/excerpt as\n * the reader types (AGL-1516, Figma 494:1220). Opt-in and default off, so\n * every existing instance renders exactly as before.\n *\n * Client-evaluated over the entries this block already holds — the page is\n * ISR-cached, so a keystroke never costs a Firestore read. That set is\n * whatever the expansion left the block holding, which paging, an\n * `entriesLimit` or the 100-entry cap can each cut down. A truncated block\n * says so under its results either way — on a miss and on a hit\n * (AGL-1516, AGL-2569) — rather than pretending global search.\n */\n search?: boolean\n /**\n * What the search box DOES with a query (AGL-1525, Figma 496:1218).\n *\n * - `filter` (default, and the reading of an absent value) — hides the\n * non-matching cards in place. What AGL-1516 shipped.\n * - `suggest` — the card grid stays exactly as it was and a floating panel\n * of matching entries opens under the field, ending in a link to the\n * site-wide results page. The frame's toolbar behaviour: a reader\n * skimming the list is not made to lose it to a typo.\n */\n searchMode?: CollectionSearchMode\n /** Hint text inside the search box (blank = \"Search posts…\"). */\n searchPlaceholder?: string\n /**\n * Server-stamped matchable text per rendered entry\n * (`expandCollectionEntries`, AGL-1516); never set by hand.\n */\n searchIndex?: Aglyn.CollectionEntrySearchItem[]\n /**\n * How many entries `searchIndex` was drawn from — server-stamped by\n * `expandCollectionEntries` alongside it (AGL-1516), never set by hand.\n * `searchIndex.length < searchTotal` is the only honest test of whether\n * this block holds the whole set; `perPage` is not, because\n * `entriesLimit` and the 100-entry cap truncate too.\n */\n searchTotal?: number\n /**\n * Whether the read behind `searchTotal` reached its own bound — server-\n * stamped beside it (AGL-1516), never set by hand.\n *\n * `searchTotal` is a count of what the server SAW, and the collection read\n * is limited. Without this flag a block holding 100 of 400 posts satisfies\n * `searchIndex.length === searchTotal` and renders the one empty state that\n * claims to have looked everywhere.\n */\n searchCapped?: boolean\n}\n\n/** The two things the toolbar search box can do (AGL-1516/AGL-1525). */\nexport type CollectionSearchMode = 'filter' | 'suggest'\n\n/**\n * Author-facing choices for {@link CollectionEntriesProps.searchMode}.\n *\n * Both values are truthy on purpose (AGL-1453): `''` cannot survive a save,\n * so an author who tried the other mode would have no route back. \"Filter\"\n * carries the absent-value reading, which is what every block published\n * before AGL-1525 has.\n */\nexport const COLLECTION_SEARCH_MODE_OPTIONS: ReadonlyArray<{\n value: CollectionSearchMode\n label: string\n}> = [\n { value: 'filter', label: 'Filter the entries in place' },\n { value: 'suggest', label: 'Show a suggestions dropdown' },\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 * Repeats its children once per published entry of a content collection\n * (AGL-551) — the collections sibling of the dataset repeatable. The tenant\n * expands it at compose time with `{{entry.*}}` tokens; in the besigner the\n * template renders once with literal tokens, matching the repeatable UX.\n *\n * Search (AGL-1516): with `search` on, a toolbar search box filters the\n * rendered entry clones client-side against the server-stamped\n * `searchIndex` — fuzzy, via the same Fuse the icon picker uses. The clones\n * arrive as `children` in stamp order, one group of template roots per\n * entry, so group N of the children IS entry N of the index. Matches keep\n * the list's own order rather than Fuse's score order: a blog list is\n * chronological, and search narrows it, it does not reshuffle it. In the\n * besigner the field renders as an inert affordance (the template renders\n * once with literal tokens and there is no index to search), matching how\n * Category Pills go inert on editing surfaces.\n */\nconst CollectionEntries = forwardRef<HTMLDivElement, CollectionEntriesProps>(\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 /** `filter` mode only — `suggest` owns its own query (AGL-1516). */\n const [query, setQuery] = useState('')\n const items = search ? searchIndex : undefined\n const { fuzzy, requestFuzzy } = useEntryFuse(items)\n if (!search) {\n return (\n <MuiStack ref={ref} spacing={4} {...props}>\n {children}\n </MuiStack>\n )\n }\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 ref={ref} spacing={4} {...props}>\n {field}\n {visible}\n {scopeNote}\n </MuiStack>\n )\n },\n)\nCollectionEntries.displayName = 'AglynCollectionEntries'\n\nexport const collectionEntriesSchema: Aglyn.ComponentSchema<CollectionEntriesProps> =\n {\n $id: ENTRIES_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Collection Entries',\n description: 'Repeats its children once per entry in a content collection.',\n category: Aglyn.ComponentCategory.DATA_DISPLAY,\n icon: { path: mdiPostOutline.path, sx: { color: 'secondary.main' } },\n attributes: [\n {\n name: 'collectionSlug',\n label: 'Collection slug',\n description:\n 'Content collection whose published entries the children repeat ' +\n 'over (e.g. \"blog\"). Leave blank on a list-template page to use ' +\n 'the collection from the URL.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'entriesLimit',\n label: 'Entries limit',\n description: 'Maximum entries rendered (blank = all, capped at 100).',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n {\n name: 'filterCategory',\n label: 'Filter by category',\n description:\n 'Only entries in this category repeat — the category name or its ' +\n 'stable id both match (e.g. \"Guides\"). Blank = no category filter.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'filterTag',\n label: 'Filter by tag',\n description:\n 'Only entries carrying this tag repeat (e.g. \"nextjs\"). Blank = ' +\n 'no tag filter.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'perPage',\n label: 'Entries per page',\n description:\n 'Paginate the list: entries per page (blank = no pagination). ' +\n 'Pairs with the page from /{collection}/page/{n}.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n {\n name: 'page',\n label: 'Page',\n description: '1-based page to render when Entries per page is set.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n {\n name: 'firstPageOnly',\n label: 'Only on page 1',\n description:\n 'Show this block on the first page of the listing only. Use it ' +\n 'for a featured or lead card: without it the same entry repeats ' +\n 'at the top of every /page/{n}, above a page of different ones.',\n component: Aglyn.FieldComponentType.SWITCH,\n },\n {\n name: 'search',\n label: 'Search',\n description:\n 'Show a search box that filters the rendered entries by title ' +\n 'and excerpt as the reader types. It searches the entries this ' +\n 'block rendered — whatever paging, an entry limit or the ' +\n '100-entry cap left it holding, and it says so on a miss.',\n component: Aglyn.FieldComponentType.SWITCH,\n },\n {\n name: 'searchMode',\n label: 'When the reader types',\n description:\n 'Filter the entries in place, or leave the list alone and open a ' +\n 'dropdown of matching entries under the box — each one a link, ' +\n 'ending in \"View all results\" for a search across the whole site.',\n component: Aglyn.FieldComponentType.SELECT,\n options: COLLECTION_SEARCH_MODE_OPTIONS.map((option) => ({\n ...option,\n })),\n // Meaningless while there is no search box to type in.\n condition: { when: 'search', is: true },\n },\n {\n name: 'searchPlaceholder',\n label: 'Search placeholder',\n description:\n 'Hint text inside this block’s own search box — the one that ' +\n 'filters the entries below it as a reader types. Blank shows ' +\n '\"Search posts…\".',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n // Meaningless while there is no search box to hint in.\n condition: { when: 'search', is: true },\n },\n // `searchIndex` and `searchTotal` are deliberately NOT attributes: both\n // are server-stamped by expandCollectionEntries, like Category Pills'\n // `items` and Related Posts' `entries`.\n {\n name: 'spacing',\n label: 'Spacing',\n description: 'Defines the space/gap between entries.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n ],\n }\n\nexport { CollectionEntries }\n// The collection family's palette presets ride this element's registry entry\n// (`presets: 'collectionPresets'` in plugin.ts), as they did when the family\n// was one module.\nexport { collectionPresets } from './collection-presets'\n"],"names":["Aglyn","mdiPostOutline","MuiStack","Typography","Children","forwardRef","useContext","useState","BUNDLE_ID","toCount","SearchField","SuggestSearchBox","useEntryFuse","ENTRIES_ID","COLLECTION_ENTRIES_COMPONENT_ID","COLLECTION_SEARCH_MODE_OPTIONS","value","label","searchScope","perPage","shown","searchTotal","searchCapped","paginated","truncated","Boolean","CollectionEntries","collectionSlug","entriesLimit","filterCategory","filterTag","page","firstPageOnly","search","searchMode","searchPlaceholder","searchIndex","children","props","ref","suppressNavigation","ScreenLinkContext","query","setQuery","items","undefined","fuzzy","requestFuzzy","spacing","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","placeholder","inert","emptyText","text","landmark","onQuery","next","displayName","collectionEntriesSchema","$id","pluginId","description","category","ComponentCategory","DATA_DISPLAY","icon","path","attributes","name","component","FieldComponentType","TEXT_FIELD","type","SWITCH","SELECT","options","option","condition","when","is","collectionPresets"],"mappings":"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"}
|
|
@@ -318,7 +318,7 @@ export const collectionEntryBodySchema = {
|
|
|
318
318
|
{
|
|
319
319
|
name: 'markdown',
|
|
320
320
|
label: 'Markdown',
|
|
321
|
-
description: 'Markdown-lite content. Keep {{entry.body}} on entry-template ' + "
|
|
321
|
+
description: 'Markdown-lite content. Keep {{entry.body}} on entry-template ' + "pages so each entry's body renders here.",
|
|
322
322
|
component: Aglyn.FieldComponentType.TEXT_FIELD
|
|
323
323
|
}
|
|
324
324
|
]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/mui/src/lib/components/collection-entry-body.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 { mdiTextLong } from '@aglyn/shared-data-mdi'\nimport { AppLink } from '@aglyn/shared-ui-jsx'\nimport Box from '@mui/material/Box'\nimport MuiLink from '@mui/material/Link'\nimport Typography from '@mui/material/Typography'\nimport type { ReactNode } from 'react'\nimport { forwardRef, useContext, useMemo } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { UNRESOLVED_TOKEN } from './collection-common'\n\n// Persisted component id (AGL-551/582); the compose pipeline references it\n// through the @aglyn/aglyn constant. Never rename.\nexport const ENTRY_BODY_ID: Aglyn.ComponentId =\n Aglyn.COLLECTION_ENTRY_BODY_COMPONENT_ID\n\nexport interface CollectionEntryBodyProps {\n /**\n * Markdown-lite source. On entry-template screens `{{entry.body}}`\n * resolves to the rendered entry's body at compose time.\n */\n markdown?: string\n /**\n * The pixel pair of each library image this body names, keyed by the target\n * as the body writes it, stamped by the composition from the assets' DAM\n * records (AGL-3149) — never set by hand, and never on the template, where\n * `markdown` is still the `{{entry.body}}` token and names no asset.\n */\n intrinsicSizes?: Record<string, { width: number; height: number }>\n}\n\nconst renderInlines = (\n inlines: Aglyn.MarkdownInline[],\n links: { suppressNavigation?: boolean; screens?: Aglyn.ScreenRouteMap },\n): ReactNode[] =>\n inlines.map((item, index) => {\n if (item.type === 'bold') return <strong key={index}>{item.text}</strong>\n if (item.type === 'italic') return <em key={index}>{item.text}</em>\n if (item.type !== 'link') return <span key={index}>{item.text}</span>\n // Internal paths route through AppLink for client-side navigation\n // (AGL-582); external links stay plain anchors. Editing surfaces render\n // the link look without an href so clicks never navigate. A target named\n // by REFERENCE resolves against the routing map first (AGL-3118), and one\n // whose entry, listing or screen is gone renders as the words alone —\n // never as an anchor holding the stored value.\n const link = Aglyn.resolveMarkdownLink(item.href, links)\n if (link.kind === 'text') return <span key={index}>{item.text}</span>\n if (link.kind === 'inert') {\n return (\n <MuiLink key={index} component=\"span\" sx={{ cursor: 'default' }}>\n {item.text}\n </MuiLink>\n )\n }\n return link.kind === 'internal' ? (\n <AppLink key={index} href={link.href}>\n {item.text}\n </AppLink>\n ) : (\n <MuiLink key={index} href={link.href}>\n {item.text}\n </MuiLink>\n )\n })\n\n/**\n * Renders a content entry's markdown-lite body as themed MUI elements\n * (AGL-551): headings, paragraphs, lists, and images pick up the site\n * theme's typography instead of the old unthemed article HTML. Parsing is\n * pure, so the full body server-renders (SEO keeps the article text).\n */\nconst CollectionEntryBody = forwardRef<\n HTMLDivElement,\n CollectionEntryBodyProps\n>((props, ref) => {\n // `intrinsicSizes` is destructured out rather than left in `rest`: `rest` is\n // spread onto a Box, and a composition-stamped object would reach the DOM as\n // an attribute React has no idea what to do with.\n const { markdown, intrinsicSizes, ...rest } = props\n // Node styles ride the renderer-merged sx; recompose (stack.ts pattern).\n const nodeSx = Array.isArray(props['sx']) ? props['sx'] : [props['sx']]\n // The routing map travels with the suppression flag: both decide what a\n // link in this body renders as (AGL-3118).\n const { suppressNavigation, screens } = useContext(Aglyn.ScreenLinkContext)\n const links = useMemo(\n () => ({ suppressNavigation, screens }),\n [suppressNavigation, screens],\n )\n // The site being rendered, for image blocks (AGL-1686) — read once here\n // because hooks cannot run inside the block map below.\n const { hostId } = Aglyn.useSite()\n const source = (markdown ?? '').trim()\n const unresolved = !source || UNRESOLVED_TOKEN.test(source)\n const blocks = useMemo(\n () => (unresolved ? [] : Aglyn.parseMarkdownLite(source)),\n [source, unresolved],\n )\n // Heading anchors, on the same terms as the Markdown element (AGL-1162).\n // Without them a blog article could not carry an \"On this page\" aside at\n // all: the Table of Contents element emits `#slug` links, and there was\n // nothing on this page for them to land on.\n const slugs = useMemo(() => Aglyn.markdownHeadingSlugs(blocks), [blocks])\n if (unresolved) {\n // Editing surfaces get an affordance; the published site renders\n // nothing rather than a literal token.\n if (!suppressNavigation) return <Box ref={ref} {...rest} />\n return (\n <Box\n ref={ref}\n {...rest}\n sx={[\n {\n p: 2,\n border: '1px dashed',\n borderColor: 'divider',\n color: 'text.secondary',\n fontSize: 12,\n fontFamily: 'system-ui, sans-serif',\n },\n ...nodeSx,\n ]}\n >\n {'Entry body — the {{entry.body}} markdown renders here'}\n </Box>\n )\n }\n return (\n <Box ref={ref} {...rest}>\n {blocks.map((block, index) => {\n if (block.type === 'heading') {\n return (\n <Typography\n key={index}\n id={slugs[index]}\n variant={block.level === 2 ? 'h4' : 'h5'}\n component={block.level === 2 ? 'h2' : 'h3'}\n gutterBottom\n sx={{\n scrollMarginTop: `${Aglyn.HEADING_ANCHOR_SCROLL_MARGIN}px`,\n }}\n >\n {renderInlines(block.inlines, links)}\n </Typography>\n )\n }\n if (block.type === 'image') {\n return (\n <Box\n key={index}\n component=\"img\"\n // Resolved like the related cover image in collection-related.tsx\n // (AGL-1686): a body image is the same kind of asset and\n // has no reason to be stored in a more fragile form.\n //\n // And sized like one too, as of AGL-3149: the same call gives\n // the Markdown element's body images their candidate list and\n // intrinsic pair, so the two bodies cannot disagree about how a\n // picture in prose is delivered.\n {...Aglyn.mediaBodyImageAttributes({\n src: block.src,\n hostId,\n size: intrinsicSizes?.[block.src],\n })}\n alt={block.alt}\n // Paired with the `width`/`height` above — see the Markdown\n // element's copy of this: without it the height hint outlives\n // the `maxWidth` cap and squashes the picture.\n sx={{ maxWidth: '100%', height: 'auto', borderRadius: 1, my: 1 }}\n // An image inside an entry body is below the fold by\n // construction — the title, byline and opening paragraphs are\n // above it. This one carried no loading hint at all, so it was\n // fetched EAGERLY, at default priority, competing with the\n // entry's own cover (AGL-2486).\n {...Aglyn.DEFERRED_IMAGE_ATTRIBUTES}\n />\n )\n }\n if (block.type === 'list') {\n return (\n <Box key={index} component=\"ul\" sx={{ lineHeight: 1.7, pl: 3 }}>\n {block.items.map((item, itemIndex) => (\n <li key={itemIndex}>\n {renderInlines(item, links)}\n </li>\n ))}\n </Box>\n )\n }\n // A numbered list (AGL-1320) — a real `<ol>`, so the markers are the\n // browser's and `start` survives: a notice that resumes at 7 has to\n // read as 7 for the enumeration to mean anything.\n if (block.type === 'orderedList') {\n return (\n <Box\n key={index}\n component=\"ol\"\n start={block.start}\n sx={{ lineHeight: 1.7, pl: 3 }}\n >\n {block.items.map((item, itemIndex) => (\n <li key={itemIndex}>\n {renderInlines(item, links)}\n </li>\n ))}\n </Box>\n )\n }\n // Code blocks and tables (AGL-974). Both scroll rather than wrap:\n // a wrapped code line or a squeezed table column loses the very\n // structure that made the block worth writing.\n if (block.type === 'code') {\n return (\n <Box\n key={index}\n component=\"pre\"\n sx={{\n my: 2,\n p: 2,\n overflowX: 'auto',\n borderRadius: 1,\n bgcolor: 'action.hover',\n fontFamily: 'monospace',\n fontSize: 14,\n }}\n >\n <code>{block.text}</code>\n </Box>\n )\n }\n if (block.type === 'table') {\n return (\n <Box key={index} sx={{ my: 2, overflowX: 'auto' }}>\n <Box\n component=\"table\"\n sx={{\n borderCollapse: 'collapse',\n width: '100%',\n '& th, & td': {\n border: '1px solid',\n borderColor: 'divider',\n px: 1.5,\n py: 1,\n },\n '& th': { bgcolor: 'action.hover' },\n }}\n >\n <thead>\n <tr>\n {block.header.map((cell, cellIndex) => (\n <th\n key={cellIndex}\n style={{ textAlign: block.align[cellIndex] ?? 'left' }}\n >\n {renderInlines(cell, links)}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {block.rows.map((row, rowIndex) => (\n <tr key={rowIndex}>\n {row.map((cell, cellIndex) => (\n <td\n key={cellIndex}\n style={{\n textAlign: block.align[cellIndex] ?? 'left',\n }}\n >\n {renderInlines(cell, links)}\n </td>\n ))}\n </tr>\n ))}\n </tbody>\n </Box>\n </Box>\n )\n }\n // The article template's pull-quote (AGL-1315, Figma 170:225):\n // larger italic prose behind a left accent. Palette tokens only, so\n // it follows the site theme in both modes.\n if (block.type === 'quote') {\n return (\n <Typography\n key={index}\n component=\"blockquote\"\n sx={{\n my: 3,\n mx: 0,\n pl: 2.5,\n borderLeft: '3px solid',\n borderColor: 'primary.main',\n fontStyle: 'italic',\n fontSize: '1.25em',\n lineHeight: 1.6,\n color: 'text.primary',\n }}\n >\n {renderInlines(block.inlines, links)}\n </Typography>\n )\n }\n return (\n <Typography\n key={index}\n variant=\"body1\"\n sx={{ lineHeight: 1.7 }}\n gutterBottom\n >\n {renderInlines(block.inlines, links)}\n </Typography>\n )\n })}\n </Box>\n )\n})\nCollectionEntryBody.displayName = 'AglynCollectionEntryBody'\n\nexport const collectionEntryBodySchema: Aglyn.ComponentSchema<CollectionEntryBodyProps> =\n {\n $id: ENTRY_BODY_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Entry Body',\n description: \"The current entry's markdown body, on an entry template.\",\n category: Aglyn.ComponentCategory.TEXT,\n icon: { path: mdiTextLong.path, sx: { color: 'secondary.main' } },\n flags: { selfClosing: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'markdown',\n label: 'Markdown',\n description:\n 'Markdown-lite content. Keep {{entry.body}} on entry-template ' +\n \"screens so each entry's body renders here.\",\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n ],\n }\n\nexport { CollectionEntryBody }\n"],"names":["Aglyn","mdiTextLong","AppLink","Box","MuiLink","Typography","forwardRef","useContext","useMemo","BUNDLE_ID","UNRESOLVED_TOKEN","ENTRY_BODY_ID","COLLECTION_ENTRY_BODY_COMPONENT_ID","renderInlines","inlines","links","map","item","index","type","strong","text","em","span","link","resolveMarkdownLink","href","kind","component","sx","cursor","CollectionEntryBody","props","ref","markdown","intrinsicSizes","rest","nodeSx","Array","isArray","suppressNavigation","screens","ScreenLinkContext","hostId","useSite","source","trim","unresolved","test","blocks","parseMarkdownLite","slugs","markdownHeadingSlugs","p","border","borderColor","color","fontSize","fontFamily","block","id","variant","level","gutterBottom","scrollMarginTop","HEADING_ANCHOR_SCROLL_MARGIN","mediaBodyImageAttributes","src","size","alt","maxWidth","height","borderRadius","my","DEFERRED_IMAGE_ATTRIBUTES","lineHeight","pl","items","itemIndex","li","start","overflowX","bgcolor","code","borderCollapse","width","px","py","thead","tr","header","cell","cellIndex","th","style","textAlign","align","tbody","rows","row","rowIndex","td","mx","borderLeft","fontStyle","displayName","collectionEntryBodySchema","$id","pluginId","description","category","ComponentCategory","TEXT","icon","path","flags","selfClosing","FEATURE_FLAG","ENABLED","attributes","name","label","FieldComponentType","TEXT_FIELD"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/mui/src/lib/components/collection-entry-body.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 { mdiTextLong } from '@aglyn/shared-data-mdi'\nimport { AppLink } from '@aglyn/shared-ui-jsx'\nimport Box from '@mui/material/Box'\nimport MuiLink from '@mui/material/Link'\nimport Typography from '@mui/material/Typography'\nimport type { ReactNode } from 'react'\nimport { forwardRef, useContext, useMemo } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { UNRESOLVED_TOKEN } from './collection-common'\n\n// Persisted component id (AGL-551/582); the compose pipeline references it\n// through the @aglyn/aglyn constant. Never rename.\nexport const ENTRY_BODY_ID: Aglyn.ComponentId =\n Aglyn.COLLECTION_ENTRY_BODY_COMPONENT_ID\n\nexport interface CollectionEntryBodyProps {\n /**\n * Markdown-lite source. On entry-template screens `{{entry.body}}`\n * resolves to the rendered entry's body at compose time.\n */\n markdown?: string\n /**\n * The pixel pair of each library image this body names, keyed by the target\n * as the body writes it, stamped by the composition from the assets' DAM\n * records (AGL-3149) — never set by hand, and never on the template, where\n * `markdown` is still the `{{entry.body}}` token and names no asset.\n */\n intrinsicSizes?: Record<string, { width: number; height: number }>\n}\n\nconst renderInlines = (\n inlines: Aglyn.MarkdownInline[],\n links: { suppressNavigation?: boolean; screens?: Aglyn.ScreenRouteMap },\n): ReactNode[] =>\n inlines.map((item, index) => {\n if (item.type === 'bold') return <strong key={index}>{item.text}</strong>\n if (item.type === 'italic') return <em key={index}>{item.text}</em>\n if (item.type !== 'link') return <span key={index}>{item.text}</span>\n // Internal paths route through AppLink for client-side navigation\n // (AGL-582); external links stay plain anchors. Editing surfaces render\n // the link look without an href so clicks never navigate. A target named\n // by REFERENCE resolves against the routing map first (AGL-3118), and one\n // whose entry, listing or screen is gone renders as the words alone —\n // never as an anchor holding the stored value.\n const link = Aglyn.resolveMarkdownLink(item.href, links)\n if (link.kind === 'text') return <span key={index}>{item.text}</span>\n if (link.kind === 'inert') {\n return (\n <MuiLink key={index} component=\"span\" sx={{ cursor: 'default' }}>\n {item.text}\n </MuiLink>\n )\n }\n return link.kind === 'internal' ? (\n <AppLink key={index} href={link.href}>\n {item.text}\n </AppLink>\n ) : (\n <MuiLink key={index} href={link.href}>\n {item.text}\n </MuiLink>\n )\n })\n\n/**\n * Renders a content entry's markdown-lite body as themed MUI elements\n * (AGL-551): headings, paragraphs, lists, and images pick up the site\n * theme's typography instead of the old unthemed article HTML. Parsing is\n * pure, so the full body server-renders (SEO keeps the article text).\n */\nconst CollectionEntryBody = forwardRef<\n HTMLDivElement,\n CollectionEntryBodyProps\n>((props, ref) => {\n // `intrinsicSizes` is destructured out rather than left in `rest`: `rest` is\n // spread onto a Box, and a composition-stamped object would reach the DOM as\n // an attribute React has no idea what to do with.\n const { markdown, intrinsicSizes, ...rest } = props\n // Node styles ride the renderer-merged sx; recompose (stack.ts pattern).\n const nodeSx = Array.isArray(props['sx']) ? props['sx'] : [props['sx']]\n // The routing map travels with the suppression flag: both decide what a\n // link in this body renders as (AGL-3118).\n const { suppressNavigation, screens } = useContext(Aglyn.ScreenLinkContext)\n const links = useMemo(\n () => ({ suppressNavigation, screens }),\n [suppressNavigation, screens],\n )\n // The site being rendered, for image blocks (AGL-1686) — read once here\n // because hooks cannot run inside the block map below.\n const { hostId } = Aglyn.useSite()\n const source = (markdown ?? '').trim()\n const unresolved = !source || UNRESOLVED_TOKEN.test(source)\n const blocks = useMemo(\n () => (unresolved ? [] : Aglyn.parseMarkdownLite(source)),\n [source, unresolved],\n )\n // Heading anchors, on the same terms as the Markdown element (AGL-1162).\n // Without them a blog article could not carry an \"On this page\" aside at\n // all: the Table of Contents element emits `#slug` links, and there was\n // nothing on this page for them to land on.\n const slugs = useMemo(() => Aglyn.markdownHeadingSlugs(blocks), [blocks])\n if (unresolved) {\n // Editing surfaces get an affordance; the published site renders\n // nothing rather than a literal token.\n if (!suppressNavigation) return <Box ref={ref} {...rest} />\n return (\n <Box\n ref={ref}\n {...rest}\n sx={[\n {\n p: 2,\n border: '1px dashed',\n borderColor: 'divider',\n color: 'text.secondary',\n fontSize: 12,\n fontFamily: 'system-ui, sans-serif',\n },\n ...nodeSx,\n ]}\n >\n {'Entry body — the {{entry.body}} markdown renders here'}\n </Box>\n )\n }\n return (\n <Box ref={ref} {...rest}>\n {blocks.map((block, index) => {\n if (block.type === 'heading') {\n return (\n <Typography\n key={index}\n id={slugs[index]}\n variant={block.level === 2 ? 'h4' : 'h5'}\n component={block.level === 2 ? 'h2' : 'h3'}\n gutterBottom\n sx={{\n scrollMarginTop: `${Aglyn.HEADING_ANCHOR_SCROLL_MARGIN}px`,\n }}\n >\n {renderInlines(block.inlines, links)}\n </Typography>\n )\n }\n if (block.type === 'image') {\n return (\n <Box\n key={index}\n component=\"img\"\n // Resolved like the related cover image in collection-related.tsx\n // (AGL-1686): a body image is the same kind of asset and\n // has no reason to be stored in a more fragile form.\n //\n // And sized like one too, as of AGL-3149: the same call gives\n // the Markdown element's body images their candidate list and\n // intrinsic pair, so the two bodies cannot disagree about how a\n // picture in prose is delivered.\n {...Aglyn.mediaBodyImageAttributes({\n src: block.src,\n hostId,\n size: intrinsicSizes?.[block.src],\n })}\n alt={block.alt}\n // Paired with the `width`/`height` above — see the Markdown\n // element's copy of this: without it the height hint outlives\n // the `maxWidth` cap and squashes the picture.\n sx={{ maxWidth: '100%', height: 'auto', borderRadius: 1, my: 1 }}\n // An image inside an entry body is below the fold by\n // construction — the title, byline and opening paragraphs are\n // above it. This one carried no loading hint at all, so it was\n // fetched EAGERLY, at default priority, competing with the\n // entry's own cover (AGL-2486).\n {...Aglyn.DEFERRED_IMAGE_ATTRIBUTES}\n />\n )\n }\n if (block.type === 'list') {\n return (\n <Box key={index} component=\"ul\" sx={{ lineHeight: 1.7, pl: 3 }}>\n {block.items.map((item, itemIndex) => (\n <li key={itemIndex}>\n {renderInlines(item, links)}\n </li>\n ))}\n </Box>\n )\n }\n // A numbered list (AGL-1320) — a real `<ol>`, so the markers are the\n // browser's and `start` survives: a notice that resumes at 7 has to\n // read as 7 for the enumeration to mean anything.\n if (block.type === 'orderedList') {\n return (\n <Box\n key={index}\n component=\"ol\"\n start={block.start}\n sx={{ lineHeight: 1.7, pl: 3 }}\n >\n {block.items.map((item, itemIndex) => (\n <li key={itemIndex}>\n {renderInlines(item, links)}\n </li>\n ))}\n </Box>\n )\n }\n // Code blocks and tables (AGL-974). Both scroll rather than wrap:\n // a wrapped code line or a squeezed table column loses the very\n // structure that made the block worth writing.\n if (block.type === 'code') {\n return (\n <Box\n key={index}\n component=\"pre\"\n sx={{\n my: 2,\n p: 2,\n overflowX: 'auto',\n borderRadius: 1,\n bgcolor: 'action.hover',\n fontFamily: 'monospace',\n fontSize: 14,\n }}\n >\n <code>{block.text}</code>\n </Box>\n )\n }\n if (block.type === 'table') {\n return (\n <Box key={index} sx={{ my: 2, overflowX: 'auto' }}>\n <Box\n component=\"table\"\n sx={{\n borderCollapse: 'collapse',\n width: '100%',\n '& th, & td': {\n border: '1px solid',\n borderColor: 'divider',\n px: 1.5,\n py: 1,\n },\n '& th': { bgcolor: 'action.hover' },\n }}\n >\n <thead>\n <tr>\n {block.header.map((cell, cellIndex) => (\n <th\n key={cellIndex}\n style={{ textAlign: block.align[cellIndex] ?? 'left' }}\n >\n {renderInlines(cell, links)}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {block.rows.map((row, rowIndex) => (\n <tr key={rowIndex}>\n {row.map((cell, cellIndex) => (\n <td\n key={cellIndex}\n style={{\n textAlign: block.align[cellIndex] ?? 'left',\n }}\n >\n {renderInlines(cell, links)}\n </td>\n ))}\n </tr>\n ))}\n </tbody>\n </Box>\n </Box>\n )\n }\n // The article template's pull-quote (AGL-1315, Figma 170:225):\n // larger italic prose behind a left accent. Palette tokens only, so\n // it follows the site theme in both modes.\n if (block.type === 'quote') {\n return (\n <Typography\n key={index}\n component=\"blockquote\"\n sx={{\n my: 3,\n mx: 0,\n pl: 2.5,\n borderLeft: '3px solid',\n borderColor: 'primary.main',\n fontStyle: 'italic',\n fontSize: '1.25em',\n lineHeight: 1.6,\n color: 'text.primary',\n }}\n >\n {renderInlines(block.inlines, links)}\n </Typography>\n )\n }\n return (\n <Typography\n key={index}\n variant=\"body1\"\n sx={{ lineHeight: 1.7 }}\n gutterBottom\n >\n {renderInlines(block.inlines, links)}\n </Typography>\n )\n })}\n </Box>\n )\n})\nCollectionEntryBody.displayName = 'AglynCollectionEntryBody'\n\nexport const collectionEntryBodySchema: Aglyn.ComponentSchema<CollectionEntryBodyProps> =\n {\n $id: ENTRY_BODY_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Entry Body',\n description: \"The current entry's markdown body, on an entry template.\",\n category: Aglyn.ComponentCategory.TEXT,\n icon: { path: mdiTextLong.path, sx: { color: 'secondary.main' } },\n flags: { selfClosing: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'markdown',\n label: 'Markdown',\n description:\n 'Markdown-lite content. Keep {{entry.body}} on entry-template ' +\n \"pages so each entry's body renders here.\",\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n ],\n }\n\nexport { CollectionEntryBody }\n"],"names":["Aglyn","mdiTextLong","AppLink","Box","MuiLink","Typography","forwardRef","useContext","useMemo","BUNDLE_ID","UNRESOLVED_TOKEN","ENTRY_BODY_ID","COLLECTION_ENTRY_BODY_COMPONENT_ID","renderInlines","inlines","links","map","item","index","type","strong","text","em","span","link","resolveMarkdownLink","href","kind","component","sx","cursor","CollectionEntryBody","props","ref","markdown","intrinsicSizes","rest","nodeSx","Array","isArray","suppressNavigation","screens","ScreenLinkContext","hostId","useSite","source","trim","unresolved","test","blocks","parseMarkdownLite","slugs","markdownHeadingSlugs","p","border","borderColor","color","fontSize","fontFamily","block","id","variant","level","gutterBottom","scrollMarginTop","HEADING_ANCHOR_SCROLL_MARGIN","mediaBodyImageAttributes","src","size","alt","maxWidth","height","borderRadius","my","DEFERRED_IMAGE_ATTRIBUTES","lineHeight","pl","items","itemIndex","li","start","overflowX","bgcolor","code","borderCollapse","width","px","py","thead","tr","header","cell","cellIndex","th","style","textAlign","align","tbody","rows","row","rowIndex","td","mx","borderLeft","fontStyle","displayName","collectionEntryBodySchema","$id","pluginId","description","category","ComponentCategory","TEXT","icon","path","flags","selfClosing","FEATURE_FLAG","ENABLED","attributes","name","label","FieldComponentType","TEXT_FIELD"],"mappings":"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"}
|
|
@@ -124,7 +124,7 @@ export const collectionSearchSchema = {
|
|
|
124
124
|
{
|
|
125
125
|
name: 'collectionSlug',
|
|
126
126
|
label: 'Collection slug',
|
|
127
|
-
description: 'Content collection this box searches (e.g. "blog"). Leave blank ' + 'on a list-template
|
|
127
|
+
description: 'Content collection this box searches (e.g. "blog"). Leave blank ' + 'on a list-template page to use the collection from the URL.',
|
|
128
128
|
component: Aglyn.FieldComponentType.TEXT_FIELD
|
|
129
129
|
},
|
|
130
130
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/mui/src/lib/components/collection-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 { mdiMagnify } from '@aglyn/shared-data-mdi'\nimport Box from '@mui/material/Box'\nimport MuiStack, { type StackProps } from '@mui/material/Stack'\nimport { forwardRef, useContext } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { SuggestSearchBox, useEntryFuse } from './collection-search-box'\n\n// Persisted component id (AGL-551/582); the compose pipeline references it\n// through the @aglyn/aglyn constant. Never rename.\nexport const SEARCH_ID: Aglyn.ComponentId = Aglyn.COLLECTION_SEARCH_COMPONENT_ID\n\nexport interface CollectionSearchProps extends StackProps {\n /**\n * Collection whose entries this box searches (compose-time). Blank = the\n * collection routed by the current URL on list-template screens.\n */\n collectionSlug?: string\n /** Hint text inside the box (blank = \"Search posts…\"). */\n searchPlaceholder?: string\n /**\n * Server-stamped matchable text per entry (`expandCollectionSearch`);\n * never set by hand.\n */\n searchIndex?: Aglyn.CollectionEntrySearchItem[]\n /** How many entries the index was drawn from; never set by hand. */\n searchTotal?: number\n /** Whether that read reached its bound; never set by hand. */\n searchCapped?: boolean\n}\n\n/**\n * A search box for a collection that lives where the author puts it\n * (AGL-1516, Figma 494:1220).\n *\n * The entries block has had a search box since AGL-1516's first half, and it\n * renders inside that block — first child of its own stack. The frame puts\n * the field in the listing's TOOLBAR: category pills on the left, search and\n * RSS on the right, one row. That was unbuildable, and unbuildable in both\n * directions — a block's own child cannot be lifted out of it, and the pills\n * cannot be moved in, because every child of an entries block is cloned once\n * per entry. Three passes of authoring hit the same wall.\n *\n * So the field became its own block. It searches the collection the listing\n * beside it is drawn from, and answers with the suggestion panel (496:1218)\n * — the only behaviour a standalone box can honestly have, since it owns no\n * cards to hide. Enter submits to the site-wide results page, so the box\n * works with no JavaScript at all.\n *\n * Inert on editing surfaces, like Category Pills: the besigner has no index\n * to search, and a field that answered there would be answering about\n * nothing.\n */\nconst CollectionSearch = forwardRef<HTMLDivElement, CollectionSearchProps>(\n (props, ref) => {\n // Compose-time and search-only attributes: strip so they never hit the\n // DOM.\n const {\n collectionSlug,\n searchPlaceholder,\n searchIndex,\n searchTotal,\n searchCapped,\n ...rest\n } = props\n // Node styles ride the renderer-merged sx; recompose (stack.ts pattern).\n const nodeSx = Array.isArray(props['sx']) ? props['sx'] : [props['sx']]\n const { suppressNavigation } = useContext(Aglyn.ScreenLinkContext)\n const { fuzzy, requestFuzzy } = useEntryFuse(searchIndex)\n if (!suppressNavigation && !searchIndex?.length) {\n // Nothing stamped on a live surface: an unknown collection, an empty\n // one, or a page composed before this block existed. Renders NOTHING\n // rather than a field that can only ever answer \"no matches\" — that\n // answer would read as a fact about the reader's query instead of\n // about the absent index behind it.\n return <Box ref={ref} {...rest} />\n }\n return (\n <MuiStack ref={ref} {...rest} sx={[{ alignItems: 'flex-end' }, ...nodeSx]}>\n <SuggestSearchBox\n fuzzy={fuzzy}\n requestFuzzy={requestFuzzy}\n {...(searchIndex ? { items: searchIndex } : {})}\n {...(searchPlaceholder === undefined\n ? {}\n : { placeholder: searchPlaceholder })}\n {...(suppressNavigation ? { inert: true } : {})}\n // The honest miss for THIS box. It searched one bounded read of the\n // collection — never \"the collection\", and on a category route not\n // even the whole of it, because the source arrives filtered. So it\n // reports the number it actually looked through, and says plainly\n // when that number was a ceiling rather than a total.\n emptyText={(text) =>\n searchCapped\n ? `No matches for “${text}” in the ${searchIndex?.length ?? 0} ` +\n 'entries searched here — the collection holds more, which ' +\n 'this box has not read.'\n : `No matches for “${text}” in the ${searchIndex?.length ?? 0} ` +\n 'entries searched here.'\n }\n />\n </MuiStack>\n )\n },\n)\nCollectionSearch.displayName = 'AglynCollectionSearch'\n\nexport const collectionSearchSchema: Aglyn.ComponentSchema<CollectionSearchProps> =\n {\n $id: SEARCH_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Collection Search',\n description:\n 'A search box for a content collection, with a suggestions dropdown.',\n category: Aglyn.ComponentCategory.NAVIGATION,\n icon: { path: mdiMagnify.path, sx: { color: 'secondary.main' } },\n flags: { selfClosing: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'collectionSlug',\n label: 'Collection slug',\n description:\n 'Content collection this box searches (e.g. \"blog\"). Leave blank ' +\n 'on a list-template
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/mui/src/lib/components/collection-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 { mdiMagnify } from '@aglyn/shared-data-mdi'\nimport Box from '@mui/material/Box'\nimport MuiStack, { type StackProps } from '@mui/material/Stack'\nimport { forwardRef, useContext } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { SuggestSearchBox, useEntryFuse } from './collection-search-box'\n\n// Persisted component id (AGL-551/582); the compose pipeline references it\n// through the @aglyn/aglyn constant. Never rename.\nexport const SEARCH_ID: Aglyn.ComponentId = Aglyn.COLLECTION_SEARCH_COMPONENT_ID\n\nexport interface CollectionSearchProps extends StackProps {\n /**\n * Collection whose entries this box searches (compose-time). Blank = the\n * collection routed by the current URL on list-template screens.\n */\n collectionSlug?: string\n /** Hint text inside the box (blank = \"Search posts…\"). */\n searchPlaceholder?: string\n /**\n * Server-stamped matchable text per entry (`expandCollectionSearch`);\n * never set by hand.\n */\n searchIndex?: Aglyn.CollectionEntrySearchItem[]\n /** How many entries the index was drawn from; never set by hand. */\n searchTotal?: number\n /** Whether that read reached its bound; never set by hand. */\n searchCapped?: boolean\n}\n\n/**\n * A search box for a collection that lives where the author puts it\n * (AGL-1516, Figma 494:1220).\n *\n * The entries block has had a search box since AGL-1516's first half, and it\n * renders inside that block — first child of its own stack. The frame puts\n * the field in the listing's TOOLBAR: category pills on the left, search and\n * RSS on the right, one row. That was unbuildable, and unbuildable in both\n * directions — a block's own child cannot be lifted out of it, and the pills\n * cannot be moved in, because every child of an entries block is cloned once\n * per entry. Three passes of authoring hit the same wall.\n *\n * So the field became its own block. It searches the collection the listing\n * beside it is drawn from, and answers with the suggestion panel (496:1218)\n * — the only behaviour a standalone box can honestly have, since it owns no\n * cards to hide. Enter submits to the site-wide results page, so the box\n * works with no JavaScript at all.\n *\n * Inert on editing surfaces, like Category Pills: the besigner has no index\n * to search, and a field that answered there would be answering about\n * nothing.\n */\nconst CollectionSearch = forwardRef<HTMLDivElement, CollectionSearchProps>(\n (props, ref) => {\n // Compose-time and search-only attributes: strip so they never hit the\n // DOM.\n const {\n collectionSlug,\n searchPlaceholder,\n searchIndex,\n searchTotal,\n searchCapped,\n ...rest\n } = props\n // Node styles ride the renderer-merged sx; recompose (stack.ts pattern).\n const nodeSx = Array.isArray(props['sx']) ? props['sx'] : [props['sx']]\n const { suppressNavigation } = useContext(Aglyn.ScreenLinkContext)\n const { fuzzy, requestFuzzy } = useEntryFuse(searchIndex)\n if (!suppressNavigation && !searchIndex?.length) {\n // Nothing stamped on a live surface: an unknown collection, an empty\n // one, or a page composed before this block existed. Renders NOTHING\n // rather than a field that can only ever answer \"no matches\" — that\n // answer would read as a fact about the reader's query instead of\n // about the absent index behind it.\n return <Box ref={ref} {...rest} />\n }\n return (\n <MuiStack ref={ref} {...rest} sx={[{ alignItems: 'flex-end' }, ...nodeSx]}>\n <SuggestSearchBox\n fuzzy={fuzzy}\n requestFuzzy={requestFuzzy}\n {...(searchIndex ? { items: searchIndex } : {})}\n {...(searchPlaceholder === undefined\n ? {}\n : { placeholder: searchPlaceholder })}\n {...(suppressNavigation ? { inert: true } : {})}\n // The honest miss for THIS box. It searched one bounded read of the\n // collection — never \"the collection\", and on a category route not\n // even the whole of it, because the source arrives filtered. So it\n // reports the number it actually looked through, and says plainly\n // when that number was a ceiling rather than a total.\n emptyText={(text) =>\n searchCapped\n ? `No matches for “${text}” in the ${searchIndex?.length ?? 0} ` +\n 'entries searched here — the collection holds more, which ' +\n 'this box has not read.'\n : `No matches for “${text}” in the ${searchIndex?.length ?? 0} ` +\n 'entries searched here.'\n }\n />\n </MuiStack>\n )\n },\n)\nCollectionSearch.displayName = 'AglynCollectionSearch'\n\nexport const collectionSearchSchema: Aglyn.ComponentSchema<CollectionSearchProps> =\n {\n $id: SEARCH_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Collection Search',\n description:\n 'A search box for a content collection, with a suggestions dropdown.',\n category: Aglyn.ComponentCategory.NAVIGATION,\n icon: { path: mdiMagnify.path, sx: { color: 'secondary.main' } },\n flags: { selfClosing: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'collectionSlug',\n label: 'Collection slug',\n description:\n 'Content collection this box searches (e.g. \"blog\"). Leave blank ' +\n 'on a list-template page to use the collection from the URL.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'searchPlaceholder',\n label: 'Search placeholder',\n description:\n 'Hint text inside this box. Blank shows \"Search posts…\", which is ' +\n 'worth changing when the collection is not posts.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n // `searchIndex`, `searchTotal` and `searchCapped` are deliberately NOT\n // attributes: all three are server-stamped facts about a read, and an\n // author who could edit them could make the box lie about its own\n // reach.\n ],\n }\n\nexport { CollectionSearch }\n"],"names":["Aglyn","mdiMagnify","Box","MuiStack","forwardRef","useContext","BUNDLE_ID","SuggestSearchBox","useEntryFuse","SEARCH_ID","COLLECTION_SEARCH_COMPONENT_ID","CollectionSearch","props","ref","collectionSlug","searchPlaceholder","searchIndex","searchTotal","searchCapped","rest","nodeSx","Array","isArray","suppressNavigation","ScreenLinkContext","fuzzy","requestFuzzy","length","sx","alignItems","items","undefined","placeholder","inert","emptyText","text","displayName","collectionSearchSchema","$id","pluginId","description","category","ComponentCategory","NAVIGATION","icon","path","color","flags","selfClosing","FEATURE_FLAG","ENABLED","attributes","name","label","component","FieldComponentType","TEXT_FIELD"],"mappings":";;;AAAA;;;;;;;;;;;;;;;CAeC,GAED,YAAYA,WAAW,eAAc;AACrC,SAASC,UAAU,QAAQ,yBAAwB;AACnD,OAAOC,SAAS,oBAAmB;AACnC,OAAOC,cAAmC,sBAAqB;AAC/D,SAASC,UAAU,EAAEC,UAAU,QAAQ,QAAO;AAC9C,SAASC,SAAS,QAAQ,gCAA4B;AACtD,SAASC,gBAAgB,EAAEC,YAAY,QAAQ,6BAAyB;AAExE,2EAA2E;AAC3E,mDAAmD;AACnD,OAAO,MAAMC,YAA+BT,MAAMU,8BAA8B,CAAA;AAqBhF;;;;;;;;;;;;;;;;;;;;;CAqBC,GACD,MAAMC,iCAAmBP,WACvB,CAACQ,OAAOC;IACN,uEAAuE;IACvE,OAAO;IACP,MAAM,EACJC,cAAc,EACdC,iBAAiB,EACjBC,WAAW,EACXC,WAAW,EACXC,YAAY,EAEb,GAAGN,OADCO,wCACDP;;;;;;;IACJ,yEAAyE;IACzE,MAAMQ,SAASC,MAAMC,OAAO,CAACV,KAAK,CAAC,KAAK,IAAIA,KAAK,CAAC,KAAK,GAAG;QAACA,KAAK,CAAC,KAAK;KAAC;IACvE,MAAM,EAAEW,kBAAkB,EAAE,GAAGlB,WAAWL,MAAMwB,iBAAiB;IACjE,MAAM,EAAEC,KAAK,EAAEC,YAAY,EAAE,GAAGlB,aAAaQ;IAC7C,IAAI,CAACO,sBAAsB,EAACP,+BAAAA,YAAaW,MAAM,GAAE;QAC/C,qEAAqE;QACrE,qEAAqE;QACrE,oEAAoE;QACpE,kEAAkE;QAClE,oCAAoC;QACpC,qBAAO,KAACzB;YAAIW,KAAKA;WAASM;IAC5B;IACA,qBACE,KAAChB;QAASU,KAAKA;OAASM;QAAMS,IAAI;YAAC;gBAAEC,YAAY;YAAW;eAAMT;SAAO;kBACvE,cAAA,KAACb;YACCkB,OAAOA;YACPC,cAAcA;WACTV,cAAc;YAAEc,OAAOd;QAAY,IAAI,CAAC,GACxCD,sBAAsBgB,YACvB,CAAC,IACD;YAAEC,aAAajB;QAAkB,GAChCQ,qBAAqB;YAAEU,OAAO;QAAK,IAAI,CAAC;YAC7C,oEAAoE;YACpE,mEAAmE;YACnE,mEAAmE;YACnE,kEAAkE;YAClE,sDAAsD;YACtDC,WAAW,CAACC;;uBACVjB,eACI,CAAC,gBAAgB,EAAEiB,KAAK,SAAS,UAAEnB,+BAAAA,YAAaW,MAAM,mBAAI,EAAE,CAAC,CAAC,GAC9D,8DACA,2BACA,CAAC,gBAAgB,EAAEQ,KAAK,SAAS,WAAEnB,+BAAAA,YAAaW,MAAM,oBAAI,EAAE,CAAC,CAAC,GAC9D;;;;AAKd;AAEFhB,iBAAiByB,WAAW,GAAG;AAE/B,OAAO,MAAMC,yBACX;IACEC,KAAK7B;IACL8B,UAAUjC;IACV8B,aAAa;IACbI,aACE;IACFC,UAAUzC,MAAM0C,iBAAiB,CAACC,UAAU;IAC5CC,MAAM;QAAEC,MAAM5C,WAAW4C,IAAI;QAAEjB,IAAI;YAAEkB,OAAO;QAAiB;IAAE;IAC/DC,OAAO;QAAEC,aAAahD,MAAMiD,YAAY,CAACC,OAAO;IAAC;IACjDC,YAAY;QACV;YACEC,MAAM;YACNC,OAAO;YACPb,aACE,qEACA;YACFc,WAAWtD,MAAMuD,kBAAkB,CAACC,UAAU;QAChD;QACA;YACEJ,MAAM;YACNC,OAAO;YACPb,aACE,sEACA;YACFc,WAAWtD,MAAMuD,kBAAkB,CAACC,UAAU;QAChD;KAKD;AACH,EAAC;AAEH,SAAS7C,gBAAgB,GAAE"}
|
|
@@ -100,7 +100,7 @@ export const schema = {
|
|
|
100
100
|
{
|
|
101
101
|
name: 'component',
|
|
102
102
|
label: 'Component',
|
|
103
|
-
description: 'The DOM element the page renders as. On a
|
|
103
|
+
description: 'The DOM element the page renders as. On a page framed by a ' + 'shared layout the layout’s slot carries "main" instead, so this ' + 'stays a plain container unless you say otherwise. Choosing a ' + 'landmark other than "main" leaves the page without one.',
|
|
104
104
|
component: Aglyn.FieldComponentType.SELECT,
|
|
105
105
|
options: DOCUMENT_ROOT_ELEMENTS.map((value)=>({
|
|
106
106
|
value,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/mui/src/lib/components/document-root.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 { mdiFileDocumentOutline } from '@aglyn/shared-data-mdi'\nimport MuiBox from '@mui/material/Box'\nimport { forwardRef, type ReactNode } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\n\n/**\n * The canvas ROOT's component id — the `Document` layer at the top of every\n * hierarchy (`NODE_ROOT_ID`). Persisted in screen and layout documents since\n * the first seed; never rename.\n *\n * Until now nothing was registered under it, so the renderer fell through to\n * its unstyled `styled('div')` fallback and the besigner's attributes panel\n * had no schema to draw: the Document layer could be styled and could not be\n * given an element. Registering the id changes no stored document — the same\n * `div` renders — and gives the root the one attribute it has always needed.\n */\nexport const ID: Aglyn.ComponentId = 'div'\n\n/**\n * Elements the document root may render as (AGL-2486, widened by AGL-2514).\n *\n * `main` is offered here and on the Layout Slot, and nowhere else: those two\n * are the only nodes that can BE the page's content region, and\n * `stampDocumentLandmark` guarantees exactly one of them carries it.\n *\n * The rest of the list is the sectioning set `SECTION_ELEMENTS` offers, for\n * the same reason it offers them: the root is an element like any other, and\n * a document whose whole body is one region — a layout that is nothing but\n * chrome, a fragment composed into a larger page — had no way to say so. It\n * stayed `div` and the region went unnamed. A root that is `header` or\n * `footer` is a document with no `main`, which `stampDocumentLandmark`\n * already treats as the author's choice rather than a state to repair: it\n * hands the landmark to the slot when there is one, and otherwise ships the\n * page without.\n *\n * An allow-list rather than free text: the value is persisted and rendered\n * verbatim, so a typed one would put `script` into every visitor's page.\n */\nexport const DOCUMENT_ROOT_ELEMENTS = [\n 'div',\n 'main',\n 'header',\n 'footer',\n 'nav',\n 'aside',\n 'section',\n 'article',\n] as const\nexport type DocumentRootElement = (typeof DOCUMENT_ROOT_ELEMENTS)[number]\n\nexport interface DocumentRootProps {\n /**\n * The DOM element rendered; defaults to `div`. On a screen with no shared\n * layout, composition fills in `main` — with no slot to carry it, the root\n * IS the page's content region.\n *\n * Named `component` like Box's and Typography's, not `element` like\n * Section's: the attribute means the same thing everywhere it appears, and\n * two names for it is one the author has to learn twice.\n */\n component?: DocumentRootElement | string\n children?: ReactNode\n}\n\n/**\n * The document root: the outermost node of a screen or layout tree.\n *\n * A plain container, as it has always been. It exists as a registered\n * component so the root can answer the two questions every other node can —\n * what element am I, and what does the attributes panel show — rather than\n * being the one node in the tree with no schema.\n */\nconst DocumentRoot = forwardRef<HTMLDivElement, DocumentRootProps>(\n (props, ref) => {\n const { component, children, ...rest } = props\n // Unknown values degrade to `div` rather than reaching the DOM as an\n // invented tag — the resolver pattern `Section` uses.\n const element = (DOCUMENT_ROOT_ELEMENTS as readonly string[]).includes(\n String(component ?? ''),\n )\n ? (component as DocumentRootElement)\n : 'div'\n return (\n <MuiBox ref={ref} component={element} {...rest}>\n {children}\n </MuiBox>\n )\n },\n)\nDocumentRoot.displayName = 'AglynDocumentRoot'\n\nexport const schema: Aglyn.ComponentSchema<DocumentRootProps> = {\n $id: ID,\n pluginId: BUNDLE_ID,\n displayName: Aglyn.NODE_ROOT_LABEL,\n description:\n 'The page itself — the outermost element every other element sits in.',\n category: Aglyn.ComponentCategory.LAYOUT,\n icon: { path: mdiFileDocumentOutline.path, sx: { color: 'text.secondary' } },\n attributes: [\n {\n name: 'component',\n label: 'Component',\n description:\n 'The DOM element the page renders as. On a
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/mui/src/lib/components/document-root.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 { mdiFileDocumentOutline } from '@aglyn/shared-data-mdi'\nimport MuiBox from '@mui/material/Box'\nimport { forwardRef, type ReactNode } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\n\n/**\n * The canvas ROOT's component id — the `Document` layer at the top of every\n * hierarchy (`NODE_ROOT_ID`). Persisted in screen and layout documents since\n * the first seed; never rename.\n *\n * Until now nothing was registered under it, so the renderer fell through to\n * its unstyled `styled('div')` fallback and the besigner's attributes panel\n * had no schema to draw: the Document layer could be styled and could not be\n * given an element. Registering the id changes no stored document — the same\n * `div` renders — and gives the root the one attribute it has always needed.\n */\nexport const ID: Aglyn.ComponentId = 'div'\n\n/**\n * Elements the document root may render as (AGL-2486, widened by AGL-2514).\n *\n * `main` is offered here and on the Layout Slot, and nowhere else: those two\n * are the only nodes that can BE the page's content region, and\n * `stampDocumentLandmark` guarantees exactly one of them carries it.\n *\n * The rest of the list is the sectioning set `SECTION_ELEMENTS` offers, for\n * the same reason it offers them: the root is an element like any other, and\n * a document whose whole body is one region — a layout that is nothing but\n * chrome, a fragment composed into a larger page — had no way to say so. It\n * stayed `div` and the region went unnamed. A root that is `header` or\n * `footer` is a document with no `main`, which `stampDocumentLandmark`\n * already treats as the author's choice rather than a state to repair: it\n * hands the landmark to the slot when there is one, and otherwise ships the\n * page without.\n *\n * An allow-list rather than free text: the value is persisted and rendered\n * verbatim, so a typed one would put `script` into every visitor's page.\n */\nexport const DOCUMENT_ROOT_ELEMENTS = [\n 'div',\n 'main',\n 'header',\n 'footer',\n 'nav',\n 'aside',\n 'section',\n 'article',\n] as const\nexport type DocumentRootElement = (typeof DOCUMENT_ROOT_ELEMENTS)[number]\n\nexport interface DocumentRootProps {\n /**\n * The DOM element rendered; defaults to `div`. On a screen with no shared\n * layout, composition fills in `main` — with no slot to carry it, the root\n * IS the page's content region.\n *\n * Named `component` like Box's and Typography's, not `element` like\n * Section's: the attribute means the same thing everywhere it appears, and\n * two names for it is one the author has to learn twice.\n */\n component?: DocumentRootElement | string\n children?: ReactNode\n}\n\n/**\n * The document root: the outermost node of a screen or layout tree.\n *\n * A plain container, as it has always been. It exists as a registered\n * component so the root can answer the two questions every other node can —\n * what element am I, and what does the attributes panel show — rather than\n * being the one node in the tree with no schema.\n */\nconst DocumentRoot = forwardRef<HTMLDivElement, DocumentRootProps>(\n (props, ref) => {\n const { component, children, ...rest } = props\n // Unknown values degrade to `div` rather than reaching the DOM as an\n // invented tag — the resolver pattern `Section` uses.\n const element = (DOCUMENT_ROOT_ELEMENTS as readonly string[]).includes(\n String(component ?? ''),\n )\n ? (component as DocumentRootElement)\n : 'div'\n return (\n <MuiBox ref={ref} component={element} {...rest}>\n {children}\n </MuiBox>\n )\n },\n)\nDocumentRoot.displayName = 'AglynDocumentRoot'\n\nexport const schema: Aglyn.ComponentSchema<DocumentRootProps> = {\n $id: ID,\n pluginId: BUNDLE_ID,\n displayName: Aglyn.NODE_ROOT_LABEL,\n description:\n 'The page itself — the outermost element every other element sits in.',\n category: Aglyn.ComponentCategory.LAYOUT,\n icon: { path: mdiFileDocumentOutline.path, sx: { color: 'text.secondary' } },\n attributes: [\n {\n name: 'component',\n label: 'Component',\n description:\n 'The DOM element the page renders as. On a page framed by a ' +\n 'shared layout the layout’s slot carries \"main\" instead, so this ' +\n 'stays a plain container unless you say otherwise. Choosing a ' +\n 'landmark other than \"main\" leaves the page without one.',\n component: Aglyn.FieldComponentType.SELECT,\n options: DOCUMENT_ROOT_ELEMENTS.map((value) => ({\n value,\n label: value,\n })),\n },\n ],\n}\n\n// No preset, deliberately: the root is not an element anybody drops. The\n// element drawer lists presets, so registering the schema alone keeps it out\n// of the palette while giving the node its attributes panel.\n\nexport { DocumentRoot }\nexport default DocumentRoot\n"],"names":["Aglyn","mdiFileDocumentOutline","MuiBox","forwardRef","BUNDLE_ID","ID","DOCUMENT_ROOT_ELEMENTS","DocumentRoot","props","ref","component","children","rest","element","includes","String","displayName","schema","$id","pluginId","NODE_ROOT_LABEL","description","category","ComponentCategory","LAYOUT","icon","path","sx","color","attributes","name","label","FieldComponentType","SELECT","options","map","value"],"mappings":";;;AAAA;;;;;;;;;;;;;;;CAeC,GAED,YAAYA,WAAW,eAAc;AACrC,SAASC,sBAAsB,QAAQ,yBAAwB;AAC/D,OAAOC,YAAY,oBAAmB;AACtC,SAASC,UAAU,QAAwB,QAAO;AAClD,SAASC,SAAS,QAAQ,gCAA4B;AAEtD;;;;;;;;;;CAUC,GACD,OAAO,MAAMC,KAAwB,MAAK;AAE1C;;;;;;;;;;;;;;;;;;;CAmBC,GACD,OAAO,MAAMC,yBAAyB;IACpC;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD,CAAS;AAiBV;;;;;;;CAOC,GACD,MAAMC,6BAAeJ,WACnB,CAACK,OAAOC;IACN,MAAM,EAAEC,SAAS,EAAEC,QAAQ,EAAW,GAAGH,OAATI,wCAASJ;;;;IACzC,qEAAqE;IACrE,sDAAsD;IACtD,MAAMK,UAAU,AAACP,uBAA6CQ,QAAQ,CACpEC,OAAOL,oBAAAA,YAAa,OAEjBA,YACD;IACJ,qBACE,KAACR;QAAOO,KAAKA;QAAKC,WAAWG;OAAaD;kBACvCD;;AAGP;AAEFJ,aAAaS,WAAW,GAAG;AAE3B,OAAO,MAAMC,SAAmD;IAC9DC,KAAKb;IACLc,UAAUf;IACVY,aAAahB,MAAMoB,eAAe;IAClCC,aACE;IACFC,UAAUtB,MAAMuB,iBAAiB,CAACC,MAAM;IACxCC,MAAM;QAAEC,MAAMzB,uBAAuByB,IAAI;QAAEC,IAAI;YAAEC,OAAO;QAAiB;IAAE;IAC3EC,YAAY;QACV;YACEC,MAAM;YACNC,OAAO;YACPV,aACE,gEACA,qEACA,kEACA;YACFX,WAAWV,MAAMgC,kBAAkB,CAACC,MAAM;YAC1CC,SAAS5B,uBAAuB6B,GAAG,CAAC,CAACC,QAAW,CAAA;oBAC9CA;oBACAL,OAAOK;gBACT,CAAA;QACF;KACD;AACH,EAAC;AAED,yEAAyE;AACzE,6EAA6E;AAC7E,6DAA6D;AAE7D,SAAS7B,YAAY,GAAE;AACvB,eAAeA,aAAY"}
|