@aglyn/plugins-mui 1.0.0-beta.218 → 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/package.json +7 -7
- 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.js +3 -3
- package/src/lib/components/markdown.js.map +1 -1
- package/src/lib/components/pagination.js +2 -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.js +2 -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/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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/mui/src/lib/components/markdown.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 { mdiLanguageMarkdown, mdiTableOfContents } from '@aglyn/shared-data-mdi'\nimport { AppLink } from '@aglyn/shared-ui-jsx'\nimport {\n scrollOverflowFadeKeyframes,\n scrollOverflowFadeTimeline,\n scrollRegionProps,\n scrollableTableSx,\n scrollableTableWrapperSx,\n} from '@aglyn/shared-ui-jsx/utils/scroll-overflow'\nimport Box, { type BoxProps } from '@mui/material/Box'\nimport MuiLink from '@mui/material/Link'\nimport type { Theme } from '@mui/material/styles'\nimport Typography from '@mui/material/Typography'\nimport { observer } from 'mobx-react-lite'\nimport type { MouseEvent, ReactNode } from 'react'\nimport { forwardRef, useContext, useMemo } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { dropClearedProps } from '../utils/drop-cleared-props'\nimport { generatePresetId } from '../utils/generate-preset-id'\n\n// Component ids are persisted in screen documents; never rename.\nexport const MARKDOWN_ID: Aglyn.ComponentId = 'markdown'\n/** A compose-time token the tenant has not substituted — never a document. */\nconst UNRESOLVED_TOKEN = /^\\{\\{[^}]+\\}\\}$/\n/**\n * What a screen reader announces on reaching a table's scroll box.\n *\n * The plain word, not the table's own heading: the name has to come from\n * something every document has, and a markdown table carries no caption.\n */\nconst TABLE_REGION_LABEL = 'Table'\nexport const TABLE_OF_CONTENTS_ID: Aglyn.ComponentId = 'tableOfContents'\n\n/**\n * A long document's body type, which is NOT MUI's `body1` (AGL-1162).\n *\n * A privacy policy is read, not skimmed: the house measure for running prose\n * is 17px on a 1.75 leading, in `text.primary` rather than the softened\n * secondary a caption uses. MUI's default 16/1.5 sets the same words tighter\n * and lighter, and a page built from Typography defaults reads visibly\n * different from one built by hand next to it — which is how the legal pages\n * ended up hand-assembled from hundreds of Typography nodes in the first\n * place.\n *\n * ⚑ `inherit`, not `text.primary` (AGL-3210). The colour is still\n * `text.primary` — {@link BODY_COLOUR_SX} puts it on the element's own root,\n * where an authored `color` can replace it. Repeating the token on every\n * block instead made the author's colour unreachable: it landed on the root\n * and every child overrode it, so a card with a fixed light background kept\n * dark mode's near-white body text and read as blank.\n */\nconst BODY_SX = {\n fontSize: 17,\n lineHeight: 1.75,\n color: 'inherit',\n} as const\n\n/**\n * The element's own colour, applied to its root BEFORE the authored `sx`\n * (AGL-3210), so it is a default the Styles panel can replace rather than a\n * floor every block re-asserts. Inside the element everything inherits it.\n */\nconst BODY_COLOUR_SX = { color: 'text.primary' } as const\n\n/**\n * Heading ramp (AGL-1162). Sizes step across `{xs, sm, md}` and the tracking\n * steps WITH them: tracking is a function of size, so a single letterSpacing\n * that looks right at the desktop size is visibly loose on a phone. Negative\n * throughout — display sizes need the letters pulled together, which is the\n * opposite of the positive tracking small uppercase labels take.\n */\nconst HEADING_SX = {\n 2: {\n fontSize: { xs: 26, sm: 30, md: 34 },\n letterSpacing: { xs: '-0.3px', sm: '-0.4px', md: '-0.5px' },\n lineHeight: 1.25,\n fontWeight: 700,\n },\n 3: {\n fontSize: { xs: 19, sm: 20, md: 22 },\n letterSpacing: { xs: '-0.1px', sm: '-0.15px', md: '-0.2px' },\n lineHeight: 1.35,\n fontWeight: 700,\n },\n} as const\n\n/**\n * Breathing room a heading keeps above it when an anchor lands on it. Sites\n * built here usually have a sticky app bar, and without this the browser\n * scrolls the heading to y=0 — underneath the bar — so a working link looks\n * like a broken one.\n */\nconst ANCHOR_SCROLL_MARGIN = Aglyn.HEADING_ANCHOR_SCROLL_MARGIN\n\n/**\n * Inline runs as elements (AGL-1162), following the entry-body renderer:\n * internal links route through AppLink for client-side navigation, external\n * ones stay plain anchors, and editing surfaces render the link look without\n * an href so clicking never leaves the canvas.\n *\n * Which of those a link is — and whether it is a link at all — is\n * `resolveMarkdownLink`'s to say (AGL-3118), so a target named by reference\n * resolves through the same routing map an element link does, and one whose\n * target is gone renders as its words rather than as `href=\"entry:…\"`.\n */\nconst renderInlines = (\n inlines: Aglyn.MarkdownInline[],\n links: { suppressNavigation?: boolean; screens?: Aglyn.ScreenRouteMap },\n): ReactNode[] =>\n inlines.map((inline, index) => {\n if (inline.type === 'bold') return <strong key={index}>{inline.text}</strong>\n if (inline.type === 'italic') return <em key={index}>{inline.text}</em>\n if (inline.type === 'link') {\n const link = Aglyn.resolveMarkdownLink(inline.href, links)\n if (link.kind === 'text') return <span key={index}>{inline.text}</span>\n if (link.kind === 'inert') {\n return (\n <MuiLink key={index} component=\"span\" sx={{ cursor: 'default' }}>\n {inline.text}\n </MuiLink>\n )\n }\n return link.kind === 'internal' ? (\n <AppLink key={index} href={link.href}>\n {inline.text}\n </AppLink>\n ) : (\n <MuiLink key={index} href={link.href}>\n {inline.text}\n </MuiLink>\n )\n }\n return <span key={index}>{inline.text}</span>\n })\n\nexport interface MarkdownProps extends BoxProps {\n /** The document, as markdown-lite source. */\n content?: string\n /**\n * The pixel pair of each library image this document names, keyed by the\n * target as the document writes it, stamped by the composition from the\n * assets' DAM records (AGL-3149) — never set by hand.\n *\n * An image with no entry here renders exactly as it did before this prop\n * existed: see `mediaBodyImageAttributes` for why a partial answer is worse\n * than none.\n */\n intrinsicSizes?: Record<string, { width: number; height: number }>\n}\n\n/**\n * A whole document authored as markdown (AGL-1162).\n *\n * The elements that made a legal or policy page before this were hundreds of\n * Typography nodes, one per paragraph — which meant the page could not be\n * pasted from, or back into, the markdown file in Drive that is its actual\n * source. Here the source IS the element's content, so updating the page is\n * one paste.\n *\n * Rendered through the shared markdown-lite parser, which emits plain data\n * blocks and never an HTML string — so there is no `dangerouslySetInnerHTML`\n * anywhere on this path and nothing to sanitize. Parsing is pure, so the full\n * document server-renders and the text is in the crawled HTML.\n *\n * Child NODES are never rendered, in either branch (AGL-1388). The document\n * is the `content` prop; there is no position in a parsed block list that a\n * dropped element could occupy, so the schema turns dropping off and\n * `nodeAcceptsChildren` now honors that — a drop lands as a sibling instead.\n * `children` is destructured away rather than left in `rest` because the\n * empty-content branch spreads `rest` onto a childless Box: without this, a\n * Markdown element rendered exactly the child nodes it had swallowed a\n * moment earlier as soon as its content was cleared.\n */\nconst Markdown = forwardRef<HTMLDivElement, MarkdownProps>((props, ref) => {\n const { content, intrinsicSizes, sx, children: _children, ...rest } = props\n // The routing map travels with the suppression flag: both decide what a\n // link in this document renders as (AGL-3118).\n const { suppressNavigation, screens } = useContext(Aglyn.ScreenLinkContext)\n const links = useMemo(\n () => ({ suppressNavigation, screens }),\n [suppressNavigation, screens],\n )\n /**\n * The site being rendered, for image blocks (AGL-1686). Read once here\n * rather than per block: hooks cannot run inside the `blocks.map` below,\n * and a document may hold any number of images.\n */\n const { hostId } = Aglyn.useSite()\n const source = (content ?? '').trim()\n const blocks = useMemo(\n () => (source ? Aglyn.parseMarkdownLite(source) : []),\n [source],\n )\n // Slugs are derived rather than stored (see collectMarkdownHeadings), and\n // keyed by block index so the ids land on the right headings even when two\n // of them carry the same words. The map is built in `markdown-lite` now,\n // because every renderer needs exactly this and five of the six were not\n // getting it (AGL-1162).\n const slugs = useMemo(() => Aglyn.markdownHeadingSlugs(blocks), [blocks])\n\n if (!source) {\n // Editing surfaces get somewhere to click; the published page renders\n // nothing rather than a placeholder nobody meant to publish.\n if (!suppressNavigation) return <Box ref={ref} sx={sx} {...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 },\n ...(Array.isArray(sx) ? sx : sx ? [sx] : []),\n ]}\n >\n {'Markdown — paste the document into the Content attribute'}\n </Box>\n )\n }\n\n return (\n <Box\n ref={ref}\n {...rest}\n sx={[BODY_COLOUR_SX, ...(Array.isArray(sx) ? sx : sx ? [sx] : [])]}\n >\n {blocks.map((block, index) => {\n if (block.type === 'heading') {\n return (\n <Typography\n key={index}\n // The anchor a table of contents links to. `component` is set\n // explicitly because a Typography with only an `sx` fontSize\n // still renders a <p>: the page would screenshot correctly and\n // have no headings in it at all.\n id={slugs[index]}\n component={block.level === 2 ? 'h2' : 'h3'}\n sx={{\n ...HEADING_SX[block.level],\n // See BODY_SX: the token lives on the root, not on every\n // block, so an authored colour reaches the words (AGL-3210).\n color: 'inherit',\n scrollMarginTop: `${ANCHOR_SCROLL_MARGIN}px`,\n mt: block.level === 2 ? 5 : 3.5,\n mb: 1.5,\n '&:first-of-type': { mt: 0 },\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, exactly as `image.tsx` resolves its `src` prop\n // (AGL-1686). A document records WHICH ASSET; turning that into\n // a URL is the renderer's business, so the `/api/media/cdn/…`\n // route shape never has to be baked into a published document.\n // Every other value — a legacy storage URL, a legacy CDN path,\n // an author-typed hotlink — passes through untouched.\n //\n // The resolve now comes back with the WebP candidate list and\n // the intrinsic pair beside it, when the composition could read\n // the asset's record (AGL-3149). Shared with the entry body and\n // with `image.tsx`'s own srcSet so a body image cannot drift\n // away from the element again.\n {...Aglyn.mediaBodyImageAttributes({\n src: block.src,\n hostId,\n size: intrinsicSizes?.[block.src],\n })}\n alt={block.alt}\n // `height: 'auto'` is load-bearing beside the pair above, not\n // tidiness: the pair's `height` arrives as a presentational hint\n // too, and a hint that survives the `maxWidth` cap squashes the\n // picture instead of scaling it. It is what `auto` already\n // resolved to before the pair existed, so this changes nothing\n // for an image the composition could not measure.\n sx={{ maxWidth: '100%', height: 'auto', borderRadius: 1, my: 2 }}\n // `lazy` alone until AGL-2486. A markdown image is by\n // definition inside prose the reader scrolls through, so it\n // belongs in the same deferred rank as everything else that is\n // not the lead image.\n {...Aglyn.DEFERRED_IMAGE_ATTRIBUTES}\n />\n )\n }\n if (block.type === 'list') {\n return (\n <Box\n key={index}\n component=\"ul\"\n sx={{ ...BODY_SX, my: 2, pl: 3, '& li': { mb: 1 } }}\n >\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): the same measure as the bullet list,\n // as a real `<ol>` carrying `start` — a legal document's enumerated\n // elements are the reason this block exists.\n if (block.type === 'orderedList') {\n return (\n <Box\n key={index}\n component=\"ol\"\n start={block.start}\n sx={{ ...BODY_SX, my: 2, pl: 3, '& li': { mb: 1 } }}\n >\n {block.items.map((item, itemIndex) => (\n <li key={itemIndex}>\n {renderInlines(item, links)}\n </li>\n ))}\n </Box>\n )\n }\n // Code and tables scroll rather than wrap, like every other\n // markdown-lite renderer here (AGL-974): a wrapped command line\n // reads as two commands, and a squeezed table column loses the\n // structure that made it a table.\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\n key={index}\n {...scrollRegionProps(TABLE_REGION_LABEL)}\n sx={{ my: 2, ...scrollableTableWrapperSx }}\n >\n <Box\n component=\"table\"\n sx={{\n ...BODY_SX,\n fontSize: 16,\n borderCollapse: 'collapse',\n ...scrollableTableSx,\n '& th, & td': {\n border: '1px solid',\n borderColor: 'divider',\n px: 1.5,\n py: 1,\n },\n '& th': { bgcolor: 'action.hover', fontWeight: 600 },\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 // A pull-quote (AGL-1315): the document's body measure, one size up\n // and italic, behind a left accent — palette tokens keep it right in\n // both modes.\n if (block.type === 'quote') {\n return (\n <Typography\n key={index}\n component=\"blockquote\"\n sx={{\n ...BODY_SX,\n my: 3,\n mx: 0,\n pl: 2.5,\n borderLeft: '3px solid',\n borderColor: 'primary.main',\n fontStyle: 'italic',\n fontSize: 21,\n lineHeight: 1.6,\n }}\n >\n {renderInlines(block.inlines, links)}\n </Typography>\n )\n }\n return (\n <Typography key={index} component=\"p\" sx={{ ...BODY_SX, my: 2 }}>\n {renderInlines(block.inlines, links)}\n </Typography>\n )\n })}\n </Box>\n )\n})\nMarkdown.displayName = 'AglynMarkdown'\n\n/* ── Table of contents ──────────────────────────────────────────────────── */\n\nexport interface TableOfContentsProps extends BoxProps {\n /**\n * The Markdown element this lists the headings of. Empty means \"the first\n * Markdown element on the screen\", which is the answer on every page that\n * has exactly one — see {@link resolveMarkdownSource}.\n */\n forNodeId?: string\n /** Aside heading; an empty string renders no heading at all. */\n heading?: string\n /** Deepest heading level listed: `'2'` for top level only. */\n depth?: '2' | '3'\n}\n\n/**\n * Which Markdown element a table of contents speaks for (AGL-1162).\n *\n * Resolved through the NODE TREE, never the DOM: the besigner canvas renders\n * inside a closed shadow root, so a `document.querySelector` from a component\n * finds nothing there and the aside would be empty in the one place it is\n * being authored.\n *\n * The tree is walked from the root in document order, so \"the first Markdown\n * element\" means the first one a reader meets — not the first key of a node\n * map, whose order is storage detail.\n *\n * An explicit `forNodeId` wins, compared with `leafIdsMatch` because the\n * stored id is the raw canvas id while a composed tree namespaces layout and\n * reusable-component grafts (AGL-573/1229). A pick that no longer resolves —\n * the element was deleted and re-added, which is exactly what re-pasting a\n * document looks like — falls back to the first Markdown element rather than\n * leaving a published page with an empty aside.\n */\nexport function resolveMarkdownSource(\n root: Aglyn.NodeSchema | undefined,\n forNodeId?: string,\n): string {\n const found: Array<{ $id: string; content: string }> = []\n const walk = (node: Aglyn.NodeSchema | undefined): void => {\n if (!node) return\n // Two element types hold a markdown-lite document, and an aside beside a\n // blog article is the case this issue was opened for (AGL-1162): the\n // Collection Entry Body carries the post, under `markdown` rather than\n // `content`, and used to be invisible here — so \"On this page\" on an\n // article template rendered its authoring affordance forever.\n const source =\n node.componentId === MARKDOWN_ID\n ? 'content'\n : node.componentId === Aglyn.COLLECTION_ENTRY_BODY_COMPONENT_ID\n ? 'markdown'\n : undefined\n if (source) {\n const props = (node.resolvedProps ?? node.props ?? {}) as Record<\n string,\n unknown\n >\n const content = String(props[source] ?? '')\n // An entry body on an unresolved surface is the literal\n // `{{entry.body}}` token. It is not a document, and treating it as one\n // would let it win over a real Markdown element further down the page\n // and empty the aside on the canvas.\n if (!UNRESOLVED_TOKEN.test(content.trim())) {\n found.push({ $id: node.$id, content })\n }\n }\n for (const child of node.children ?? []) walk(child)\n }\n walk(root)\n if (forNodeId) {\n const picked = found.find((entry) => Aglyn.leafIdsMatch(entry.$id, forNodeId))\n if (picked) return picked.content\n }\n return found[0]?.content ?? ''\n}\n\n/**\n * Scrolls to a heading in the cases the browser cannot reach, and ONLY those\n * (AGL-1162). Returns whether it handled the click.\n *\n * In `document` it deliberately does nothing and lets the plain `#slug`\n * anchor work: native fragment navigation updates the address bar, adds a\n * history entry the back button understands, and honours the site's CSS\n * `scroll-behavior` — none of which a hand-rolled `scrollIntoView` gives\n * back. It is also the only version that works with JavaScript off.\n *\n * (`behavior: 'smooth'` was the first attempt at the whole job, and it is\n * exactly the wrong shape: a browser that declines to animate — a background\n * tab, reduced-motion — leaves the click prevented and the page unmoved, so\n * a working link reads as a broken one.)\n *\n * The besigner canvas renders inside a CLOSED shadow root, where the heading\n * is not in `document` at all and a fragment navigation finds nothing. There\n * the link's own `getRootNode()` is the shadow root holding both ends, and\n * `ShadowRoot.getElementById` finds the heading.\n */\nexport function scrollToHeading(anchor: Element, slug: string): boolean {\n const root = anchor.getRootNode()\n if (root === anchor.ownerDocument || !(root as ShadowRoot).getElementById) {\n return false\n }\n const target = (root as ShadowRoot).getElementById(slug)\n if (!target) return false\n // No explicit `behavior`: 'auto' follows the scrolling box's CSS\n // `scroll-behavior`, so a site that asked for smooth still gets it.\n target.scrollIntoView({ block: 'start' })\n return true\n}\n\n/**\n * What the sidebar's chrome costs, above and below the list (AGL-2486).\n *\n * The list's bound is `100dvh` MINUS this, so it is a function of the window\n * rather than a height somebody picked. Measured on the live\n * `/legal/marketplace-publisher-agreement` sidebar, which is the shape every\n * legal page uses: the card is `position: sticky; top: 96px` under the app\n * bar, then 24px of card padding and 31px of \"ON THIS PAGE\" label sit above\n * the list and 31px of padding below it — 182px of chrome. 200 leaves the\n * card's bottom BORDER inside the window rather than flush against it, so\n * the card reads as a card and not as a page that ran out.\n *\n * It is an allowance, not a contract: this component renders inside whatever\n * the author built around it, and it cannot read that card's `top` offset.\n * Being a MAX height, an allowance a little too generous only means a few\n * pixels of the card sit below the fold; one a little too tight only means\n * the scroll box starts a line sooner. Both are recoverable, which is the\n * whole reason this is a `max-height` and not a `height`.\n */\nconst TOC_VIEWPORT_ALLOWANCE = '200px'\n\n/**\n * The list is its own scroll box once it outgrows the window (AGL-2486).\n *\n * The bug: nothing here was bounded, and the card the author wraps this in is\n * `position: sticky`. A sticky box taller than the viewport does not scroll\n * into view — it pins at its `top` offset and everything past the window's\n * bottom edge is simply unreachable. Measured on the Marketplace Publisher\n * Agreement (14 `h2`s) in a 1100x480 window: the card stood 502px tall from\n * `top: 96px` and overhung the fold by 118px, entry 11 was cut through the\n * middle of its own text at the window edge, and 12 to 14 could not be\n * reached by any scroll — `ol.scrollTop` stayed 0, because the list was not a\n * scroll box at all. Which row gets cut moves with the window height and the\n * browser zoom; the reported sighting was entry 8. Either way it did not read\n * as \"there is more\", it read as a document with fewer sections than it has.\n *\n * Three things this deliberately is NOT:\n *\n * - NOT a `height`. A `max-height` is inert until the content exceeds it, so\n * the four-item TOCs (`/legal/dmca`, `/legal/subprocessors`) keep their\n * exact present box: no scroll region, no reserved gutter, no dead space.\n * That is also why `overflow-y` is `auto` and never `scroll`.\n *\n * - NOT applied below `md`. The bound only makes sense for a sidebar. In the\n * stacked narrow layout the TOC is a full-width block in the page flow, and\n * bounding it there would plant a scroll trap in the middle of an article\n * to solve a clipping problem that layout does not have.\n *\n * - NOT silent, and the FADE is the part that carries that. A scroll box a\n * reader cannot see is the same bug wearing a scrollbar: measured in Chrome\n * on macOS, `offsetWidth - clientWidth` on this list is 0 even with\n * `scrollbar-color` set — the platform draws an OVERLAY scrollbar, which is\n * invisible until you have already guessed to scroll. So the thumb styling\n * is the secondary cue, for the platforms that draw one at rest, and the\n * primary cue is a bottom fade: the cut stops being a slice through the\n * middle of a word and becomes an edge that visibly continues.\n *\n * The fade rides a scroll-driven animation on purpose: a `scroll()` timeline\n * on a box with nothing to scroll is INACTIVE, so its animation applies no\n * styles at all. That is the only way to say \"fade only when overflowing\" in\n * CSS with no measuring and no JavaScript, and it degrades to plain\n * unfaded-but-scrollable in a browser that lacks it. Verified both ways at\n * 1100x480: the 14-entry list fades and scrolls, the 4-entry\n * `/legal/subprocessors` list computes `mask-image: none` and does not.\n *\n * NO new attribute goes with this, deliberately. The three the element has —\n * `forNodeId`, `heading`, `depth` — all answer \"which headings does this\n * list\", and a \"list height\" field would hand the author back the very\n * decision this bug is made of. The right answer is \"as much of the window as\n * there is\", which is a thing CSS can work out and an author cannot type.\n */\nconst TABLE_OF_CONTENTS_LIST_SX = (theme: Theme) => ({\n listStyle: 'none',\n m: 0,\n p: 0,\n [theme.breakpoints.up('md')]: {\n maxHeight: `calc(100vh - ${TOC_VIEWPORT_ALLOWANCE})`,\n // `dvh` over `vh` where it exists: `vh` is the LARGEST viewport, so a\n // window with a visible toolbar gets a bound taller than the space it\n // has — which is the bug again, in miniature.\n '@supports (height: 100dvh)': {\n maxHeight: `calc(100dvh - ${TOC_VIEWPORT_ALLOWANCE})`,\n },\n overflowY: 'auto',\n // Reaching the end of the list must not then throw the page down a\n // screen — the reader was navigating the sidebar, not the document.\n overscrollBehavior: 'contain',\n scrollbarWidth: 'thin',\n // `text.disabled`, not `divider`: a 12%-black thumb is one you have to\n // already know is there. `3px` is QUOTED because a bare 3 is a theme\n // radius STEP and lands as 12px on a 6px-wide thumb.\n scrollbarColor: `${theme.palette.text.disabled} transparent`,\n '&::-webkit-scrollbar': { width: 6 },\n '&::-webkit-scrollbar-thumb': {\n backgroundColor: theme.palette.text.disabled,\n borderRadius: '3px',\n },\n ...scrollOverflowFadeTimeline('block'),\n },\n ...scrollOverflowFadeKeyframes('block'),\n})\n\n/**\n * \"On this page\" (AGL-1162) — the headings of a Markdown element on the same\n * screen, as working anchor links.\n *\n * A mobx `observer` because it reads ANOTHER node's props: the renderer's\n * Leaf re-renders on changes to the node it is rendering, and nothing else,\n * so without this the aside in the besigner would keep listing the headings\n * of the document as it was when the aside mounted.\n */\nconst TableOfContents = observer(\n forwardRef<HTMLElement, TableOfContentsProps>((props, ref) => {\n const { forNodeId, heading, depth, sx, ...rawRest } = props\n /**\n * The cleared-prop guard, applied to the props that LEAVE this\n * component rather than to all of them (AGL-1451).\n *\n * A props-wide `dropClearedProps` would be wrong here, and this is the\n * component that shows why the wrapper is not a reflex: `heading` reads\n * an EMPTY value as a real author choice — \"clear it to render no\n * label\", which is what its help text promises and what the `heading\n * === undefined ? 'On this page' : heading` line below implements. A\n * guard over the whole props object would strip the cleared heading and\n * put the default label back, i.e. eat the choice the author made. So\n * `heading` and `depth` are read as authored, and the guard covers only\n * what is spread onto the Box.\n */\n const rest = dropClearedProps(rawRest)\n const { suppressNavigation } = useContext(Aglyn.ScreenLinkContext)\n const source = resolveMarkdownSource(Aglyn.canvas.rootNode, forNodeId)\n const entries = useMemo(() => {\n const headings = Aglyn.collectMarkdownHeadings(\n Aglyn.parseMarkdownLite(source),\n )\n return depth === '2'\n ? headings.filter((entry) => entry.level === 2)\n : headings\n }, [source, depth])\n const title = heading === undefined ? 'On this page' : heading\n\n if (!entries.length) {\n // Same split as the Markdown element: an affordance while authoring,\n // nothing at all on the published page — and no empty `nav` landmark.\n if (!suppressNavigation) return <Box ref={ref} sx={sx} {...rest} />\n return (\n <Box\n ref={ref}\n component=\"nav\"\n sx={[\n {\n p: 2,\n border: '1px dashed',\n borderColor: 'divider',\n color: 'text.secondary',\n fontSize: 12,\n },\n ...(Array.isArray(sx) ? sx : sx ? [sx] : []),\n ]}\n {...rest}\n >\n {'Table of contents — add ## headings to the Markdown element'}\n </Box>\n )\n }\n\n return (\n <Box\n ref={ref}\n component=\"nav\"\n aria-label={title || 'Table of contents'}\n sx={sx}\n {...rest}\n >\n {title ? (\n <Typography\n component=\"p\"\n sx={{\n fontSize: 12,\n fontWeight: 700,\n // Positive tracking here, unlike the document headings: this is\n // a small uppercase label, where the letters need pushing apart\n // rather than pulling together.\n letterSpacing: '0.8px',\n textTransform: 'uppercase',\n color: 'text.secondary',\n mb: 1.5,\n }}\n >\n {title}\n </Typography>\n ) : null}\n <Box component=\"ol\" sx={TABLE_OF_CONTENTS_LIST_SX}>\n {entries.map((entry) => (\n <Box component=\"li\" key={entry.slug} sx={{ mb: 0.75 }}>\n <MuiLink\n // A real href, so the entry is a link to a crawler, works\n // with no JavaScript, and can be copied out of the page.\n href={`#${entry.slug}`}\n underline=\"hover\"\n color=\"text.secondary\"\n onClick={(event: MouseEvent<HTMLAnchorElement>) => {\n // Prevented ONLY when this actually moved the page — on a\n // published page it does nothing and the anchor works the\n // way anchors do.\n if (scrollToHeading(event.currentTarget, entry.slug)) {\n event.preventDefault()\n }\n }}\n sx={{\n display: 'block',\n fontSize: 15,\n lineHeight: 1.5,\n // Sub-headings indent; nothing else distinguishes them, so\n // a flat list of both levels reads as one long list.\n pl: entry.level === 3 ? 2 : 0,\n '&:hover': { color: 'text.primary' },\n }}\n >\n {entry.text}\n </MuiLink>\n </Box>\n ))}\n </Box>\n </Box>\n )\n }),\n)\nTableOfContents.displayName = 'AglynTableOfContents'\n\nexport const markdownSchema: Aglyn.ComponentSchema<MarkdownProps> = {\n $id: MARKDOWN_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Markdown',\n description:\n 'A whole document written as markdown. It takes no child elements.',\n category: Aglyn.ComponentCategory.TEXT,\n icon: { path: mdiLanguageMarkdown.path, sx: { color: '#455a64' } },\n flags: {\n // The document is the `content` attribute; dropping elements inside a\n // rendered document would put them somewhere the markdown cannot say.\n dropping: Aglyn.FEATURE_FLAG.DISABLED,\n },\n attributes: [\n {\n name: 'content',\n label: 'Content',\n description:\n 'The document, as markdown. Headings (## and ###), paragraphs, ' +\n 'bold/italic, links, bullet and 1. numbered lists, images, fenced ' +\n 'code, pipe tables and > quotes. Paste the whole source file — a ' +\n 'Table of contents element on the same screen lists the ## and ### ' +\n 'headings automatically.',\n // The WYSIWYG rather than a raw textarea (AGL-1616): this attribute is\n // a whole document — the published Privacy Policy body is one of them —\n // and editing it as raw markdown-lite meant a 13 KB paste (AGL-1594).\n // The stored value is unchanged, so every renderer is untouched.\n component: Aglyn.FieldComponentType.MARKDOWN,\n },\n ],\n}\n\nexport const tableOfContentsSchema: Aglyn.ComponentSchema<TableOfContentsProps> =\n {\n $id: TABLE_OF_CONTENTS_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Table of Contents',\n description:\n \"An on-this-page list linking to a Markdown element's headings.\",\n category: Aglyn.ComponentCategory.NAVIGATION,\n icon: { path: mdiTableOfContents.path, sx: { color: '#455a64' } },\n flags: { selfClosing: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'forNodeId',\n label: 'Markdown element',\n description:\n 'The Markdown element to list the headings of. Leave empty on a ' +\n 'page with one Markdown element — the first one on the screen is ' +\n 'used, and a pick that no longer exists falls back to it too.',\n component: Aglyn.FieldComponentType.NODE_SELECT,\n },\n {\n name: 'heading',\n label: 'Heading',\n description: 'Label above the list. Clear it to render no label.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'depth',\n label: 'Levels',\n description: 'How deep the list goes.',\n component: Aglyn.FieldComponentType.SELECT,\n // `depth` never reaches MUI — it is read here, as `depth === '2'`.\n // So the `''` was not a rendering defect; it was an option that\n // could not survive a save (AGL-1191), which meant an author who\n // set \"## headings only\" and then changed their mind could not put\n // it back. `'3'` states the same thing as a real, persistable\n // value — and is what `TableOfContentsProps.depth` has declared\n // all along, so the option list was the half that had drifted.\n // Both levels remain what an unset field means (AGL-1451).\n options: [\n { value: '3', label: '## and ### headings' },\n { value: '2', label: '## headings only' },\n ],\n },\n ],\n }\n\nexport const markdownPresets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(MARKDOWN_ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Markdown',\n pluginId: BUNDLE_ID,\n description: 'A whole document, authored as markdown',\n category: Aglyn.ComponentCategory.TEXT,\n icon: { path: mdiLanguageMarkdown.path, sx: { color: '#455a64' } },\n data: {\n $id: null,\n componentId: MARKDOWN_ID,\n pluginId: BUNDLE_ID,\n props: {\n content:\n '## First section\\n\\nReplace this with your document. Paste ' +\n 'markdown straight from the source file.\\n\\n' +\n '### A sub-section\\n\\nHeadings become anchors, and a Table of ' +\n 'contents element on the same screen links to them.',\n },\n },\n },\n {\n $id: generatePresetId(TABLE_OF_CONTENTS_ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Table of Contents',\n pluginId: BUNDLE_ID,\n description: 'On this page — links to a Markdown element’s headings',\n category: Aglyn.ComponentCategory.NAVIGATION,\n icon: { path: mdiTableOfContents.path, sx: { color: '#455a64' } },\n data: {\n $id: null,\n componentId: TABLE_OF_CONTENTS_ID,\n pluginId: BUNDLE_ID,\n props: {},\n },\n },\n]\n\nexport { Markdown, TableOfContents }\nexport default Markdown\n"],"names":["Aglyn","mdiLanguageMarkdown","mdiTableOfContents","AppLink","scrollOverflowFadeKeyframes","scrollOverflowFadeTimeline","scrollRegionProps","scrollableTableSx","scrollableTableWrapperSx","Box","MuiLink","Typography","observer","forwardRef","useContext","useMemo","BUNDLE_ID","dropClearedProps","generatePresetId","MARKDOWN_ID","UNRESOLVED_TOKEN","TABLE_REGION_LABEL","TABLE_OF_CONTENTS_ID","BODY_SX","fontSize","lineHeight","color","BODY_COLOUR_SX","HEADING_SX","xs","sm","md","letterSpacing","fontWeight","ANCHOR_SCROLL_MARGIN","HEADING_ANCHOR_SCROLL_MARGIN","renderInlines","inlines","links","map","inline","index","type","strong","text","em","link","resolveMarkdownLink","href","kind","span","component","sx","cursor","Markdown","props","ref","content","intrinsicSizes","children","_children","rest","suppressNavigation","screens","ScreenLinkContext","hostId","useSite","source","trim","blocks","parseMarkdownLite","slugs","markdownHeadingSlugs","p","border","borderColor","Array","isArray","block","id","level","scrollMarginTop","mt","mb","mediaBodyImageAttributes","src","size","alt","maxWidth","height","borderRadius","my","DEFERRED_IMAGE_ATTRIBUTES","pl","items","item","itemIndex","li","start","overflowX","bgcolor","fontFamily","code","borderCollapse","px","py","thead","tr","header","cell","cellIndex","th","style","textAlign","align","tbody","rows","row","rowIndex","td","mx","borderLeft","fontStyle","displayName","resolveMarkdownSource","root","forNodeId","found","walk","node","componentId","COLLECTION_ENTRY_BODY_COMPONENT_ID","undefined","resolvedProps","String","test","push","$id","child","picked","find","entry","leafIdsMatch","scrollToHeading","anchor","slug","getRootNode","ownerDocument","getElementById","target","scrollIntoView","TOC_VIEWPORT_ALLOWANCE","TABLE_OF_CONTENTS_LIST_SX","theme","listStyle","m","breakpoints","up","maxHeight","overflowY","overscrollBehavior","scrollbarWidth","scrollbarColor","palette","disabled","width","backgroundColor","TableOfContents","heading","depth","rawRest","canvas","rootNode","entries","headings","collectMarkdownHeadings","filter","title","length","aria-label","textTransform","underline","onClick","event","currentTarget","preventDefault","display","markdownSchema","pluginId","description","category","ComponentCategory","TEXT","icon","path","flags","dropping","FEATURE_FLAG","DISABLED","attributes","name","label","FieldComponentType","MARKDOWN","tableOfContentsSchema","NAVIGATION","selfClosing","ENABLED","NODE_SELECT","TEXT_FIELD","SELECT","options","value","markdownPresets","NodeType","PRESET","data"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/mui/src/lib/components/markdown.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 { mdiLanguageMarkdown, mdiTableOfContents } from '@aglyn/shared-data-mdi'\nimport { AppLink } from '@aglyn/shared-ui-jsx'\nimport {\n scrollOverflowFadeKeyframes,\n scrollOverflowFadeTimeline,\n scrollRegionProps,\n scrollableTableSx,\n scrollableTableWrapperSx,\n} from '@aglyn/shared-ui-jsx/utils/scroll-overflow'\nimport Box, { type BoxProps } from '@mui/material/Box'\nimport MuiLink from '@mui/material/Link'\nimport type { Theme } from '@mui/material/styles'\nimport Typography from '@mui/material/Typography'\nimport { observer } from 'mobx-react-lite'\nimport type { MouseEvent, ReactNode } from 'react'\nimport { forwardRef, useContext, useMemo } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { dropClearedProps } from '../utils/drop-cleared-props'\nimport { generatePresetId } from '../utils/generate-preset-id'\n\n// Component ids are persisted in screen documents; never rename.\nexport const MARKDOWN_ID: Aglyn.ComponentId = 'markdown'\n/** A compose-time token the tenant has not substituted — never a document. */\nconst UNRESOLVED_TOKEN = /^\\{\\{[^}]+\\}\\}$/\n/**\n * What a screen reader announces on reaching a table's scroll box.\n *\n * The plain word, not the table's own heading: the name has to come from\n * something every document has, and a markdown table carries no caption.\n */\nconst TABLE_REGION_LABEL = 'Table'\nexport const TABLE_OF_CONTENTS_ID: Aglyn.ComponentId = 'tableOfContents'\n\n/**\n * A long document's body type, which is NOT MUI's `body1` (AGL-1162).\n *\n * A privacy policy is read, not skimmed: the house measure for running prose\n * is 17px on a 1.75 leading, in `text.primary` rather than the softened\n * secondary a caption uses. MUI's default 16/1.5 sets the same words tighter\n * and lighter, and a page built from Typography defaults reads visibly\n * different from one built by hand next to it — which is how the legal pages\n * ended up hand-assembled from hundreds of Typography nodes in the first\n * place.\n *\n * ⚑ `inherit`, not `text.primary` (AGL-3210). The colour is still\n * `text.primary` — {@link BODY_COLOUR_SX} puts it on the element's own root,\n * where an authored `color` can replace it. Repeating the token on every\n * block instead made the author's colour unreachable: it landed on the root\n * and every child overrode it, so a card with a fixed light background kept\n * dark mode's near-white body text and read as blank.\n */\nconst BODY_SX = {\n fontSize: 17,\n lineHeight: 1.75,\n color: 'inherit',\n} as const\n\n/**\n * The element's own colour, applied to its root BEFORE the authored `sx`\n * (AGL-3210), so it is a default the Styles panel can replace rather than a\n * floor every block re-asserts. Inside the element everything inherits it.\n */\nconst BODY_COLOUR_SX = { color: 'text.primary' } as const\n\n/**\n * Heading ramp (AGL-1162). Sizes step across `{xs, sm, md}` and the tracking\n * steps WITH them: tracking is a function of size, so a single letterSpacing\n * that looks right at the desktop size is visibly loose on a phone. Negative\n * throughout — display sizes need the letters pulled together, which is the\n * opposite of the positive tracking small uppercase labels take.\n */\nconst HEADING_SX = {\n 2: {\n fontSize: { xs: 26, sm: 30, md: 34 },\n letterSpacing: { xs: '-0.3px', sm: '-0.4px', md: '-0.5px' },\n lineHeight: 1.25,\n fontWeight: 700,\n },\n 3: {\n fontSize: { xs: 19, sm: 20, md: 22 },\n letterSpacing: { xs: '-0.1px', sm: '-0.15px', md: '-0.2px' },\n lineHeight: 1.35,\n fontWeight: 700,\n },\n} as const\n\n/**\n * Breathing room a heading keeps above it when an anchor lands on it. Sites\n * built here usually have a sticky app bar, and without this the browser\n * scrolls the heading to y=0 — underneath the bar — so a working link looks\n * like a broken one.\n */\nconst ANCHOR_SCROLL_MARGIN = Aglyn.HEADING_ANCHOR_SCROLL_MARGIN\n\n/**\n * Inline runs as elements (AGL-1162), following the entry-body renderer:\n * internal links route through AppLink for client-side navigation, external\n * ones stay plain anchors, and editing surfaces render the link look without\n * an href so clicking never leaves the canvas.\n *\n * Which of those a link is — and whether it is a link at all — is\n * `resolveMarkdownLink`'s to say (AGL-3118), so a target named by reference\n * resolves through the same routing map an element link does, and one whose\n * target is gone renders as its words rather than as `href=\"entry:…\"`.\n */\nconst renderInlines = (\n inlines: Aglyn.MarkdownInline[],\n links: { suppressNavigation?: boolean; screens?: Aglyn.ScreenRouteMap },\n): ReactNode[] =>\n inlines.map((inline, index) => {\n if (inline.type === 'bold') return <strong key={index}>{inline.text}</strong>\n if (inline.type === 'italic') return <em key={index}>{inline.text}</em>\n if (inline.type === 'link') {\n const link = Aglyn.resolveMarkdownLink(inline.href, links)\n if (link.kind === 'text') return <span key={index}>{inline.text}</span>\n if (link.kind === 'inert') {\n return (\n <MuiLink key={index} component=\"span\" sx={{ cursor: 'default' }}>\n {inline.text}\n </MuiLink>\n )\n }\n return link.kind === 'internal' ? (\n <AppLink key={index} href={link.href}>\n {inline.text}\n </AppLink>\n ) : (\n <MuiLink key={index} href={link.href}>\n {inline.text}\n </MuiLink>\n )\n }\n return <span key={index}>{inline.text}</span>\n })\n\nexport interface MarkdownProps extends BoxProps {\n /** The document, as markdown-lite source. */\n content?: string\n /**\n * The pixel pair of each library image this document names, keyed by the\n * target as the document writes it, stamped by the composition from the\n * assets' DAM records (AGL-3149) — never set by hand.\n *\n * An image with no entry here renders exactly as it did before this prop\n * existed: see `mediaBodyImageAttributes` for why a partial answer is worse\n * than none.\n */\n intrinsicSizes?: Record<string, { width: number; height: number }>\n}\n\n/**\n * A whole document authored as markdown (AGL-1162).\n *\n * The elements that made a legal or policy page before this were hundreds of\n * Typography nodes, one per paragraph — which meant the page could not be\n * pasted from, or back into, the markdown file in Drive that is its actual\n * source. Here the source IS the element's content, so updating the page is\n * one paste.\n *\n * Rendered through the shared markdown-lite parser, which emits plain data\n * blocks and never an HTML string — so there is no `dangerouslySetInnerHTML`\n * anywhere on this path and nothing to sanitize. Parsing is pure, so the full\n * document server-renders and the text is in the crawled HTML.\n *\n * Child NODES are never rendered, in either branch (AGL-1388). The document\n * is the `content` prop; there is no position in a parsed block list that a\n * dropped element could occupy, so the schema turns dropping off and\n * `nodeAcceptsChildren` now honors that — a drop lands as a sibling instead.\n * `children` is destructured away rather than left in `rest` because the\n * empty-content branch spreads `rest` onto a childless Box: without this, a\n * Markdown element rendered exactly the child nodes it had swallowed a\n * moment earlier as soon as its content was cleared.\n */\nconst Markdown = forwardRef<HTMLDivElement, MarkdownProps>((props, ref) => {\n const { content, intrinsicSizes, sx, children: _children, ...rest } = props\n // The routing map travels with the suppression flag: both decide what a\n // link in this document renders as (AGL-3118).\n const { suppressNavigation, screens } = useContext(Aglyn.ScreenLinkContext)\n const links = useMemo(\n () => ({ suppressNavigation, screens }),\n [suppressNavigation, screens],\n )\n /**\n * The site being rendered, for image blocks (AGL-1686). Read once here\n * rather than per block: hooks cannot run inside the `blocks.map` below,\n * and a document may hold any number of images.\n */\n const { hostId } = Aglyn.useSite()\n const source = (content ?? '').trim()\n const blocks = useMemo(\n () => (source ? Aglyn.parseMarkdownLite(source) : []),\n [source],\n )\n // Slugs are derived rather than stored (see collectMarkdownHeadings), and\n // keyed by block index so the ids land on the right headings even when two\n // of them carry the same words. The map is built in `markdown-lite` now,\n // because every renderer needs exactly this and five of the six were not\n // getting it (AGL-1162).\n const slugs = useMemo(() => Aglyn.markdownHeadingSlugs(blocks), [blocks])\n\n if (!source) {\n // Editing surfaces get somewhere to click; the published page renders\n // nothing rather than a placeholder nobody meant to publish.\n if (!suppressNavigation) return <Box ref={ref} sx={sx} {...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 },\n ...(Array.isArray(sx) ? sx : sx ? [sx] : []),\n ]}\n >\n {'Markdown — paste the document into the Content attribute'}\n </Box>\n )\n }\n\n return (\n <Box\n ref={ref}\n {...rest}\n sx={[BODY_COLOUR_SX, ...(Array.isArray(sx) ? sx : sx ? [sx] : [])]}\n >\n {blocks.map((block, index) => {\n if (block.type === 'heading') {\n return (\n <Typography\n key={index}\n // The anchor a table of contents links to. `component` is set\n // explicitly because a Typography with only an `sx` fontSize\n // still renders a <p>: the page would screenshot correctly and\n // have no headings in it at all.\n id={slugs[index]}\n component={block.level === 2 ? 'h2' : 'h3'}\n sx={{\n ...HEADING_SX[block.level],\n // See BODY_SX: the token lives on the root, not on every\n // block, so an authored colour reaches the words (AGL-3210).\n color: 'inherit',\n scrollMarginTop: `${ANCHOR_SCROLL_MARGIN}px`,\n mt: block.level === 2 ? 5 : 3.5,\n mb: 1.5,\n '&:first-of-type': { mt: 0 },\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, exactly as `image.tsx` resolves its `src` prop\n // (AGL-1686). A document records WHICH ASSET; turning that into\n // a URL is the renderer's business, so the `/api/media/cdn/…`\n // route shape never has to be baked into a published document.\n // Every other value — a legacy storage URL, a legacy CDN path,\n // an author-typed hotlink — passes through untouched.\n //\n // The resolve now comes back with the WebP candidate list and\n // the intrinsic pair beside it, when the composition could read\n // the asset's record (AGL-3149). Shared with the entry body and\n // with `image.tsx`'s own srcSet so a body image cannot drift\n // away from the element again.\n {...Aglyn.mediaBodyImageAttributes({\n src: block.src,\n hostId,\n size: intrinsicSizes?.[block.src],\n })}\n alt={block.alt}\n // `height: 'auto'` is load-bearing beside the pair above, not\n // tidiness: the pair's `height` arrives as a presentational hint\n // too, and a hint that survives the `maxWidth` cap squashes the\n // picture instead of scaling it. It is what `auto` already\n // resolved to before the pair existed, so this changes nothing\n // for an image the composition could not measure.\n sx={{ maxWidth: '100%', height: 'auto', borderRadius: 1, my: 2 }}\n // `lazy` alone until AGL-2486. A markdown image is by\n // definition inside prose the reader scrolls through, so it\n // belongs in the same deferred rank as everything else that is\n // not the lead image.\n {...Aglyn.DEFERRED_IMAGE_ATTRIBUTES}\n />\n )\n }\n if (block.type === 'list') {\n return (\n <Box\n key={index}\n component=\"ul\"\n sx={{ ...BODY_SX, my: 2, pl: 3, '& li': { mb: 1 } }}\n >\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): the same measure as the bullet list,\n // as a real `<ol>` carrying `start` — a legal document's enumerated\n // elements are the reason this block exists.\n if (block.type === 'orderedList') {\n return (\n <Box\n key={index}\n component=\"ol\"\n start={block.start}\n sx={{ ...BODY_SX, my: 2, pl: 3, '& li': { mb: 1 } }}\n >\n {block.items.map((item, itemIndex) => (\n <li key={itemIndex}>\n {renderInlines(item, links)}\n </li>\n ))}\n </Box>\n )\n }\n // Code and tables scroll rather than wrap, like every other\n // markdown-lite renderer here (AGL-974): a wrapped command line\n // reads as two commands, and a squeezed table column loses the\n // structure that made it a table.\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\n key={index}\n {...scrollRegionProps(TABLE_REGION_LABEL)}\n sx={{ my: 2, ...scrollableTableWrapperSx }}\n >\n <Box\n component=\"table\"\n sx={{\n ...BODY_SX,\n fontSize: 16,\n borderCollapse: 'collapse',\n ...scrollableTableSx,\n '& th, & td': {\n border: '1px solid',\n borderColor: 'divider',\n px: 1.5,\n py: 1,\n },\n '& th': { bgcolor: 'action.hover', fontWeight: 600 },\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 // A pull-quote (AGL-1315): the document's body measure, one size up\n // and italic, behind a left accent — palette tokens keep it right in\n // both modes.\n if (block.type === 'quote') {\n return (\n <Typography\n key={index}\n component=\"blockquote\"\n sx={{\n ...BODY_SX,\n my: 3,\n mx: 0,\n pl: 2.5,\n borderLeft: '3px solid',\n borderColor: 'primary.main',\n fontStyle: 'italic',\n fontSize: 21,\n lineHeight: 1.6,\n }}\n >\n {renderInlines(block.inlines, links)}\n </Typography>\n )\n }\n return (\n <Typography key={index} component=\"p\" sx={{ ...BODY_SX, my: 2 }}>\n {renderInlines(block.inlines, links)}\n </Typography>\n )\n })}\n </Box>\n )\n})\nMarkdown.displayName = 'AglynMarkdown'\n\n/* ── Table of contents ──────────────────────────────────────────────────── */\n\nexport interface TableOfContentsProps extends BoxProps {\n /**\n * The Markdown element this lists the headings of. Empty means \"the first\n * Markdown element on the screen\", which is the answer on every page that\n * has exactly one — see {@link resolveMarkdownSource}.\n */\n forNodeId?: string\n /** Aside heading; an empty string renders no heading at all. */\n heading?: string\n /** Deepest heading level listed: `'2'` for top level only. */\n depth?: '2' | '3'\n}\n\n/**\n * Which Markdown element a table of contents speaks for (AGL-1162).\n *\n * Resolved through the NODE TREE, never the DOM: the besigner canvas renders\n * inside a closed shadow root, so a `document.querySelector` from a component\n * finds nothing there and the aside would be empty in the one place it is\n * being authored.\n *\n * The tree is walked from the root in document order, so \"the first Markdown\n * element\" means the first one a reader meets — not the first key of a node\n * map, whose order is storage detail.\n *\n * An explicit `forNodeId` wins, compared with `leafIdsMatch` because the\n * stored id is the raw canvas id while a composed tree namespaces layout and\n * reusable-component grafts (AGL-573/1229). A pick that no longer resolves —\n * the element was deleted and re-added, which is exactly what re-pasting a\n * document looks like — falls back to the first Markdown element rather than\n * leaving a published page with an empty aside.\n */\nexport function resolveMarkdownSource(\n root: Aglyn.NodeSchema | undefined,\n forNodeId?: string,\n): string {\n const found: Array<{ $id: string; content: string }> = []\n const walk = (node: Aglyn.NodeSchema | undefined): void => {\n if (!node) return\n // Two element types hold a markdown-lite document, and an aside beside a\n // blog article is the case this issue was opened for (AGL-1162): the\n // Collection Entry Body carries the post, under `markdown` rather than\n // `content`, and used to be invisible here — so \"On this page\" on an\n // article template rendered its authoring affordance forever.\n const source =\n node.componentId === MARKDOWN_ID\n ? 'content'\n : node.componentId === Aglyn.COLLECTION_ENTRY_BODY_COMPONENT_ID\n ? 'markdown'\n : undefined\n if (source) {\n const props = (node.resolvedProps ?? node.props ?? {}) as Record<\n string,\n unknown\n >\n const content = String(props[source] ?? '')\n // An entry body on an unresolved surface is the literal\n // `{{entry.body}}` token. It is not a document, and treating it as one\n // would let it win over a real Markdown element further down the page\n // and empty the aside on the canvas.\n if (!UNRESOLVED_TOKEN.test(content.trim())) {\n found.push({ $id: node.$id, content })\n }\n }\n for (const child of node.children ?? []) walk(child)\n }\n walk(root)\n if (forNodeId) {\n const picked = found.find((entry) => Aglyn.leafIdsMatch(entry.$id, forNodeId))\n if (picked) return picked.content\n }\n return found[0]?.content ?? ''\n}\n\n/**\n * Scrolls to a heading in the cases the browser cannot reach, and ONLY those\n * (AGL-1162). Returns whether it handled the click.\n *\n * In `document` it deliberately does nothing and lets the plain `#slug`\n * anchor work: native fragment navigation updates the address bar, adds a\n * history entry the back button understands, and honours the site's CSS\n * `scroll-behavior` — none of which a hand-rolled `scrollIntoView` gives\n * back. It is also the only version that works with JavaScript off.\n *\n * (`behavior: 'smooth'` was the first attempt at the whole job, and it is\n * exactly the wrong shape: a browser that declines to animate — a background\n * tab, reduced-motion — leaves the click prevented and the page unmoved, so\n * a working link reads as a broken one.)\n *\n * The besigner canvas renders inside a CLOSED shadow root, where the heading\n * is not in `document` at all and a fragment navigation finds nothing. There\n * the link's own `getRootNode()` is the shadow root holding both ends, and\n * `ShadowRoot.getElementById` finds the heading.\n */\nexport function scrollToHeading(anchor: Element, slug: string): boolean {\n const root = anchor.getRootNode()\n if (root === anchor.ownerDocument || !(root as ShadowRoot).getElementById) {\n return false\n }\n const target = (root as ShadowRoot).getElementById(slug)\n if (!target) return false\n // No explicit `behavior`: 'auto' follows the scrolling box's CSS\n // `scroll-behavior`, so a site that asked for smooth still gets it.\n target.scrollIntoView({ block: 'start' })\n return true\n}\n\n/**\n * What the sidebar's chrome costs, above and below the list (AGL-2486).\n *\n * The list's bound is `100dvh` MINUS this, so it is a function of the window\n * rather than a height somebody picked. Measured on the live\n * `/legal/marketplace-publisher-agreement` sidebar, which is the shape every\n * legal page uses: the card is `position: sticky; top: 96px` under the app\n * bar, then 24px of card padding and 31px of \"ON THIS PAGE\" label sit above\n * the list and 31px of padding below it — 182px of chrome. 200 leaves the\n * card's bottom BORDER inside the window rather than flush against it, so\n * the card reads as a card and not as a page that ran out.\n *\n * It is an allowance, not a contract: this component renders inside whatever\n * the author built around it, and it cannot read that card's `top` offset.\n * Being a MAX height, an allowance a little too generous only means a few\n * pixels of the card sit below the fold; one a little too tight only means\n * the scroll box starts a line sooner. Both are recoverable, which is the\n * whole reason this is a `max-height` and not a `height`.\n */\nconst TOC_VIEWPORT_ALLOWANCE = '200px'\n\n/**\n * The list is its own scroll box once it outgrows the window (AGL-2486).\n *\n * The bug: nothing here was bounded, and the card the author wraps this in is\n * `position: sticky`. A sticky box taller than the viewport does not scroll\n * into view — it pins at its `top` offset and everything past the window's\n * bottom edge is simply unreachable. Measured on the Marketplace Publisher\n * Agreement (14 `h2`s) in a 1100x480 window: the card stood 502px tall from\n * `top: 96px` and overhung the fold by 118px, entry 11 was cut through the\n * middle of its own text at the window edge, and 12 to 14 could not be\n * reached by any scroll — `ol.scrollTop` stayed 0, because the list was not a\n * scroll box at all. Which row gets cut moves with the window height and the\n * browser zoom; the reported sighting was entry 8. Either way it did not read\n * as \"there is more\", it read as a document with fewer sections than it has.\n *\n * Three things this deliberately is NOT:\n *\n * - NOT a `height`. A `max-height` is inert until the content exceeds it, so\n * the four-item TOCs (`/legal/dmca`, `/legal/subprocessors`) keep their\n * exact present box: no scroll region, no reserved gutter, no dead space.\n * That is also why `overflow-y` is `auto` and never `scroll`.\n *\n * - NOT applied below `md`. The bound only makes sense for a sidebar. In the\n * stacked narrow layout the TOC is a full-width block in the page flow, and\n * bounding it there would plant a scroll trap in the middle of an article\n * to solve a clipping problem that layout does not have.\n *\n * - NOT silent, and the FADE is the part that carries that. A scroll box a\n * reader cannot see is the same bug wearing a scrollbar: measured in Chrome\n * on macOS, `offsetWidth - clientWidth` on this list is 0 even with\n * `scrollbar-color` set — the platform draws an OVERLAY scrollbar, which is\n * invisible until you have already guessed to scroll. So the thumb styling\n * is the secondary cue, for the platforms that draw one at rest, and the\n * primary cue is a bottom fade: the cut stops being a slice through the\n * middle of a word and becomes an edge that visibly continues.\n *\n * The fade rides a scroll-driven animation on purpose: a `scroll()` timeline\n * on a box with nothing to scroll is INACTIVE, so its animation applies no\n * styles at all. That is the only way to say \"fade only when overflowing\" in\n * CSS with no measuring and no JavaScript, and it degrades to plain\n * unfaded-but-scrollable in a browser that lacks it. Verified both ways at\n * 1100x480: the 14-entry list fades and scrolls, the 4-entry\n * `/legal/subprocessors` list computes `mask-image: none` and does not.\n *\n * NO new attribute goes with this, deliberately. The three the element has —\n * `forNodeId`, `heading`, `depth` — all answer \"which headings does this\n * list\", and a \"list height\" field would hand the author back the very\n * decision this bug is made of. The right answer is \"as much of the window as\n * there is\", which is a thing CSS can work out and an author cannot type.\n */\nconst TABLE_OF_CONTENTS_LIST_SX = (theme: Theme) => ({\n listStyle: 'none',\n m: 0,\n p: 0,\n [theme.breakpoints.up('md')]: {\n maxHeight: `calc(100vh - ${TOC_VIEWPORT_ALLOWANCE})`,\n // `dvh` over `vh` where it exists: `vh` is the LARGEST viewport, so a\n // window with a visible toolbar gets a bound taller than the space it\n // has — which is the bug again, in miniature.\n '@supports (height: 100dvh)': {\n maxHeight: `calc(100dvh - ${TOC_VIEWPORT_ALLOWANCE})`,\n },\n overflowY: 'auto',\n // Reaching the end of the list must not then throw the page down a\n // screen — the reader was navigating the sidebar, not the document.\n overscrollBehavior: 'contain',\n scrollbarWidth: 'thin',\n // `text.disabled`, not `divider`: a 12%-black thumb is one you have to\n // already know is there. `3px` is QUOTED because a bare 3 is a theme\n // radius STEP and lands as 12px on a 6px-wide thumb.\n scrollbarColor: `${theme.palette.text.disabled} transparent`,\n '&::-webkit-scrollbar': { width: 6 },\n '&::-webkit-scrollbar-thumb': {\n backgroundColor: theme.palette.text.disabled,\n borderRadius: '3px',\n },\n ...scrollOverflowFadeTimeline('block'),\n },\n ...scrollOverflowFadeKeyframes('block'),\n})\n\n/**\n * \"On this page\" (AGL-1162) — the headings of a Markdown element on the same\n * screen, as working anchor links.\n *\n * A mobx `observer` because it reads ANOTHER node's props: the renderer's\n * Leaf re-renders on changes to the node it is rendering, and nothing else,\n * so without this the aside in the besigner would keep listing the headings\n * of the document as it was when the aside mounted.\n */\nconst TableOfContents = observer(\n forwardRef<HTMLElement, TableOfContentsProps>((props, ref) => {\n const { forNodeId, heading, depth, sx, ...rawRest } = props\n /**\n * The cleared-prop guard, applied to the props that LEAVE this\n * component rather than to all of them (AGL-1451).\n *\n * A props-wide `dropClearedProps` would be wrong here, and this is the\n * component that shows why the wrapper is not a reflex: `heading` reads\n * an EMPTY value as a real author choice — \"clear it to render no\n * label\", which is what its help text promises and what the `heading\n * === undefined ? 'On this page' : heading` line below implements. A\n * guard over the whole props object would strip the cleared heading and\n * put the default label back, i.e. eat the choice the author made. So\n * `heading` and `depth` are read as authored, and the guard covers only\n * what is spread onto the Box.\n */\n const rest = dropClearedProps(rawRest)\n const { suppressNavigation } = useContext(Aglyn.ScreenLinkContext)\n const source = resolveMarkdownSource(Aglyn.canvas.rootNode, forNodeId)\n const entries = useMemo(() => {\n const headings = Aglyn.collectMarkdownHeadings(\n Aglyn.parseMarkdownLite(source),\n )\n return depth === '2'\n ? headings.filter((entry) => entry.level === 2)\n : headings\n }, [source, depth])\n const title = heading === undefined ? 'On this page' : heading\n\n if (!entries.length) {\n // Same split as the Markdown element: an affordance while authoring,\n // nothing at all on the published page — and no empty `nav` landmark.\n if (!suppressNavigation) return <Box ref={ref} sx={sx} {...rest} />\n return (\n <Box\n ref={ref}\n component=\"nav\"\n sx={[\n {\n p: 2,\n border: '1px dashed',\n borderColor: 'divider',\n color: 'text.secondary',\n fontSize: 12,\n },\n ...(Array.isArray(sx) ? sx : sx ? [sx] : []),\n ]}\n {...rest}\n >\n {'Table of contents — add ## headings to the Markdown element'}\n </Box>\n )\n }\n\n return (\n <Box\n ref={ref}\n component=\"nav\"\n aria-label={title || 'Table of contents'}\n sx={sx}\n {...rest}\n >\n {title ? (\n <Typography\n component=\"p\"\n sx={{\n fontSize: 12,\n fontWeight: 700,\n // Positive tracking here, unlike the document headings: this is\n // a small uppercase label, where the letters need pushing apart\n // rather than pulling together.\n letterSpacing: '0.8px',\n textTransform: 'uppercase',\n color: 'text.secondary',\n mb: 1.5,\n }}\n >\n {title}\n </Typography>\n ) : null}\n <Box component=\"ol\" sx={TABLE_OF_CONTENTS_LIST_SX}>\n {entries.map((entry) => (\n <Box component=\"li\" key={entry.slug} sx={{ mb: 0.75 }}>\n <MuiLink\n // A real href, so the entry is a link to a crawler, works\n // with no JavaScript, and can be copied out of the page.\n href={`#${entry.slug}`}\n underline=\"hover\"\n color=\"text.secondary\"\n onClick={(event: MouseEvent<HTMLAnchorElement>) => {\n // Prevented ONLY when this actually moved the page — on a\n // published page it does nothing and the anchor works the\n // way anchors do.\n if (scrollToHeading(event.currentTarget, entry.slug)) {\n event.preventDefault()\n }\n }}\n sx={{\n display: 'block',\n fontSize: 15,\n lineHeight: 1.5,\n // Sub-headings indent; nothing else distinguishes them, so\n // a flat list of both levels reads as one long list.\n pl: entry.level === 3 ? 2 : 0,\n '&:hover': { color: 'text.primary' },\n }}\n >\n {entry.text}\n </MuiLink>\n </Box>\n ))}\n </Box>\n </Box>\n )\n }),\n)\nTableOfContents.displayName = 'AglynTableOfContents'\n\nexport const markdownSchema: Aglyn.ComponentSchema<MarkdownProps> = {\n $id: MARKDOWN_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Markdown',\n description:\n 'A whole document written as markdown. It takes no child elements.',\n category: Aglyn.ComponentCategory.TEXT,\n icon: { path: mdiLanguageMarkdown.path, sx: { color: '#455a64' } },\n flags: {\n // The document is the `content` attribute; dropping elements inside a\n // rendered document would put them somewhere the markdown cannot say.\n dropping: Aglyn.FEATURE_FLAG.DISABLED,\n },\n attributes: [\n {\n name: 'content',\n label: 'Content',\n description:\n 'The document, as markdown. Headings (## and ###), paragraphs, ' +\n 'bold/italic, links, bullet and 1. numbered lists, images, fenced ' +\n 'code, pipe tables and > quotes. Paste the whole source file — a ' +\n 'Table of contents element on the same page lists the ## and ### ' +\n 'headings automatically.',\n // The WYSIWYG rather than a raw textarea (AGL-1616): this attribute is\n // a whole document — the published Privacy Policy body is one of them —\n // and editing it as raw markdown-lite meant a 13 KB paste (AGL-1594).\n // The stored value is unchanged, so every renderer is untouched.\n component: Aglyn.FieldComponentType.MARKDOWN,\n },\n ],\n}\n\nexport const tableOfContentsSchema: Aglyn.ComponentSchema<TableOfContentsProps> =\n {\n $id: TABLE_OF_CONTENTS_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Table of Contents',\n description:\n \"An on-this-page list linking to a Markdown element's headings.\",\n category: Aglyn.ComponentCategory.NAVIGATION,\n icon: { path: mdiTableOfContents.path, sx: { color: '#455a64' } },\n flags: { selfClosing: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'forNodeId',\n label: 'Markdown element',\n description:\n 'The Markdown element to list the headings of. Leave empty on a ' +\n 'page with one Markdown element — the first one on the page is ' +\n 'used, and a pick that no longer exists falls back to it too.',\n component: Aglyn.FieldComponentType.NODE_SELECT,\n },\n {\n name: 'heading',\n label: 'Heading',\n description: 'Label above the list. Clear it to render no label.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n },\n {\n name: 'depth',\n label: 'Levels',\n description: 'How deep the list goes.',\n component: Aglyn.FieldComponentType.SELECT,\n // `depth` never reaches MUI — it is read here, as `depth === '2'`.\n // So the `''` was not a rendering defect; it was an option that\n // could not survive a save (AGL-1191), which meant an author who\n // set \"## headings only\" and then changed their mind could not put\n // it back. `'3'` states the same thing as a real, persistable\n // value — and is what `TableOfContentsProps.depth` has declared\n // all along, so the option list was the half that had drifted.\n // Both levels remain what an unset field means (AGL-1451).\n options: [\n { value: '3', label: '## and ### headings' },\n { value: '2', label: '## headings only' },\n ],\n },\n ],\n }\n\nexport const markdownPresets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(MARKDOWN_ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Markdown',\n pluginId: BUNDLE_ID,\n description: 'A whole document, authored as markdown',\n category: Aglyn.ComponentCategory.TEXT,\n icon: { path: mdiLanguageMarkdown.path, sx: { color: '#455a64' } },\n data: {\n $id: null,\n componentId: MARKDOWN_ID,\n pluginId: BUNDLE_ID,\n props: {\n content:\n '## First section\\n\\nReplace this with your document. Paste ' +\n 'markdown straight from the source file.\\n\\n' +\n '### A sub-section\\n\\nHeadings become anchors, and a Table of ' +\n 'contents element on the same page links to them.',\n },\n },\n },\n {\n $id: generatePresetId(TABLE_OF_CONTENTS_ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Table of Contents',\n pluginId: BUNDLE_ID,\n description: 'On this page — links to a Markdown element’s headings',\n category: Aglyn.ComponentCategory.NAVIGATION,\n icon: { path: mdiTableOfContents.path, sx: { color: '#455a64' } },\n data: {\n $id: null,\n componentId: TABLE_OF_CONTENTS_ID,\n pluginId: BUNDLE_ID,\n props: {},\n },\n },\n]\n\nexport { Markdown, TableOfContents }\nexport default Markdown\n"],"names":["Aglyn","mdiLanguageMarkdown","mdiTableOfContents","AppLink","scrollOverflowFadeKeyframes","scrollOverflowFadeTimeline","scrollRegionProps","scrollableTableSx","scrollableTableWrapperSx","Box","MuiLink","Typography","observer","forwardRef","useContext","useMemo","BUNDLE_ID","dropClearedProps","generatePresetId","MARKDOWN_ID","UNRESOLVED_TOKEN","TABLE_REGION_LABEL","TABLE_OF_CONTENTS_ID","BODY_SX","fontSize","lineHeight","color","BODY_COLOUR_SX","HEADING_SX","xs","sm","md","letterSpacing","fontWeight","ANCHOR_SCROLL_MARGIN","HEADING_ANCHOR_SCROLL_MARGIN","renderInlines","inlines","links","map","inline","index","type","strong","text","em","link","resolveMarkdownLink","href","kind","span","component","sx","cursor","Markdown","props","ref","content","intrinsicSizes","children","_children","rest","suppressNavigation","screens","ScreenLinkContext","hostId","useSite","source","trim","blocks","parseMarkdownLite","slugs","markdownHeadingSlugs","p","border","borderColor","Array","isArray","block","id","level","scrollMarginTop","mt","mb","mediaBodyImageAttributes","src","size","alt","maxWidth","height","borderRadius","my","DEFERRED_IMAGE_ATTRIBUTES","pl","items","item","itemIndex","li","start","overflowX","bgcolor","fontFamily","code","borderCollapse","px","py","thead","tr","header","cell","cellIndex","th","style","textAlign","align","tbody","rows","row","rowIndex","td","mx","borderLeft","fontStyle","displayName","resolveMarkdownSource","root","forNodeId","found","walk","node","componentId","COLLECTION_ENTRY_BODY_COMPONENT_ID","undefined","resolvedProps","String","test","push","$id","child","picked","find","entry","leafIdsMatch","scrollToHeading","anchor","slug","getRootNode","ownerDocument","getElementById","target","scrollIntoView","TOC_VIEWPORT_ALLOWANCE","TABLE_OF_CONTENTS_LIST_SX","theme","listStyle","m","breakpoints","up","maxHeight","overflowY","overscrollBehavior","scrollbarWidth","scrollbarColor","palette","disabled","width","backgroundColor","TableOfContents","heading","depth","rawRest","canvas","rootNode","entries","headings","collectMarkdownHeadings","filter","title","length","aria-label","textTransform","underline","onClick","event","currentTarget","preventDefault","display","markdownSchema","pluginId","description","category","ComponentCategory","TEXT","icon","path","flags","dropping","FEATURE_FLAG","DISABLED","attributes","name","label","FieldComponentType","MARKDOWN","tableOfContentsSchema","NAVIGATION","selfClosing","ENABLED","NODE_SELECT","TEXT_FIELD","SELECT","options","value","markdownPresets","NodeType","PRESET","data"],"mappings":"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"}
|
|
@@ -70,7 +70,7 @@ export const schema = {
|
|
|
70
70
|
$id: ID,
|
|
71
71
|
pluginId: BUNDLE_ID,
|
|
72
72
|
displayName: 'Pagination',
|
|
73
|
-
description: 'Page picker. It highlights the chosen
|
|
73
|
+
description: 'Page-number picker. It highlights the chosen number; wire an ' + 'interaction to make it change what the page shows.',
|
|
74
74
|
category: Aglyn.ComponentCategory.NAVIGATION,
|
|
75
75
|
icon: {
|
|
76
76
|
path: mdiFormatListNumbered.path,
|
|
@@ -228,7 +228,7 @@ export const presets = [
|
|
|
228
228
|
type: Aglyn.NodeType.PRESET,
|
|
229
229
|
displayName: 'Pagination',
|
|
230
230
|
pluginId: BUNDLE_ID,
|
|
231
|
-
description: 'Page picker with previous/next arrows',
|
|
231
|
+
description: 'Page-number picker with previous/next arrows',
|
|
232
232
|
category: Aglyn.ComponentCategory.NAVIGATION,
|
|
233
233
|
icon: schema.icon,
|
|
234
234
|
data: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/mui/src/lib/components/pagination.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 { mdiFormatListNumbered } from '@aglyn/shared-data-mdi'\nimport MuiPagination from '@mui/material/Pagination'\nimport { forwardRef, type ReactNode } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { FIELD_DISABLED, FIELD_SIZE } from '../constants/field-presets'\nimport { dropClearedProps } from '../utils/drop-cleared-props'\nimport { generatePresetId } from '../utils/generate-preset-id'\n\n// Component ids are persisted in screen documents; never rename.\nexport const ID: Aglyn.ComponentId = 'muiPagination'\n\nexport interface PaginationElementProps {\n count?: number | string\n defaultPage?: number | string\n color?: 'primary' | 'secondary' | 'tertiary' | 'standard'\n shape?: 'circular' | 'rounded'\n size?: 'small' | 'medium' | 'large'\n variant?: 'text' | 'outlined'\n siblingCount?: number | string\n boundaryCount?: number | string\n showFirstButton?: boolean\n showLastButton?: boolean\n hidePrevButton?: boolean\n hideNextButton?: boolean\n disabled?: boolean\n /**\n * Accepted and dropped. The element is self-closing (see `flags`), so the\n * editor never authors children — but the renderer builds elements from\n * persisted props, and a stray child from an older document would reach\n * MUI's Pagination and render beside the page list. Declared because the\n * implementation deliberately discards it (AGL-1323).\n */\n children?: ReactNode\n}\n\n/** Number-typed attribute fields round-trip as strings. */\nfunction toCount(value: unknown, fallback: number): number {\n if (value == null || value === '') return fallback\n const parsed = Number(value)\n if (!Number.isFinite(parsed) || parsed < 0) return fallback\n return Math.round(parsed)\n}\n\n/**\n * Pagination control (https://mui.com/material-ui/react-pagination/).\n *\n * Uncontrolled: it tracks and highlights the page a visitor picks, but\n * it does not by itself fetch or filter anything — pair it with an\n * interaction, or with a repeat that reads the page. That is stated in\n * the element's own description too, because a page-picker that looks\n * wired and isn't is worse than one that says what it is.\n *\n * The `size` here is MUI's own three-value scale; the shared FIELD_SIZE\n * preset also offers `inherit`, which Pagination does not accept, so the\n * shared field is narrowed rather than re-declared.\n */\nconst PaginationElement = forwardRef<HTMLElement, PaginationElementProps>(\n (props, ref) => {\n const {\n count,\n defaultPage,\n siblingCount,\n boundaryCount,\n children: _children,\n ...spread\n } = props\n // A cleared `color`/`size` persists as null; MUI capitalizes both and\n // throws during SSR, 500ing the page (AGL-1226).\n const rest = dropClearedProps(spread)\n const total = toCount(count, 10)\n return (\n <MuiPagination\n ref={ref}\n count={total}\n // Out-of-range defaults make MUI drop the selection entirely.\n defaultPage={Math.min(Math.max(toCount(defaultPage, 1), 1), total)}\n siblingCount={toCount(siblingCount, 1)}\n boundaryCount={toCount(boundaryCount, 1)}\n {...rest}\n />\n )\n },\n)\nPaginationElement.displayName = 'AglynPagination'\n\nexport const schema: Aglyn.ComponentSchema<PaginationElementProps> = {\n $id: ID,\n pluginId: BUNDLE_ID,\n displayName: 'Pagination',\n description:\n 'Page picker. It highlights the chosen page; wire an interaction to ' +\n 'make it change what the page shows.',\n category: Aglyn.ComponentCategory.NAVIGATION,\n icon: { path: mdiFormatListNumbered.path, sx: { color: '#2196f3' } },\n flags: { selfClosing: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'count',\n label: 'Pages',\n description: 'Total number of pages. Default 10.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n {\n name: 'defaultPage',\n label: 'Starting page',\n description: 'The page selected when a visitor arrives. Default 1.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n {\n name: 'variant',\n label: 'Variant',\n description: 'Outlined draws a border around each page item.',\n component: Aglyn.FieldComponentType.SELECT,\n // REAL SENTINELS here, not deletions (AGL-1453) — the opposite call\n // from Button's and Typography's \"Default\" options, for a reason that\n // is visible in the lists themselves: those name MUI's default among\n // their choices, so `''` was a duplicate. These four offered ONLY the\n // non-default alternative, so `''` was the sole route back — and it\n // could not persist (AGL-1191). Picking Outlined, Rounded, Primary or\n // Small was a one-way door: the \"(default)\" option reverted on save and\n // the field snapped back to what it was.\n //\n // Each sentinel is MUI Pagination's own literal default for that prop\n // (`Pagination.js`: `variant = 'text'`, `shape = 'circular'`,\n // `color = 'standard'`, `size = 'medium'`), so the labels stop lying\n // and a stored value renders exactly what an unset one did.\n options: [\n { value: 'text', label: 'Text (default)' },\n { value: 'outlined', label: 'Outlined' },\n ],\n },\n {\n name: 'shape',\n label: 'Shape',\n description: 'Rounded squares instead of circles.',\n component: Aglyn.FieldComponentType.SELECT,\n options: [\n { value: 'circular', label: 'Circular (default)' },\n { value: 'rounded', label: 'Rounded' },\n ],\n },\n {\n name: 'color',\n label: 'Theme color',\n description: 'Color of the selected page item.',\n component: Aglyn.FieldComponentType.SELECT,\n options: [\n { value: 'standard', label: 'Standard (default)' },\n { value: 'primary', label: 'Primary' },\n { value: 'secondary', label: 'Secondary' },\n { value: 'tertiary', label: 'Tertiary' },\n ],\n },\n {\n // Narrowed from the shared preset: Pagination has no `inherit`\n // size, and offering it would render nothing different.\n ...FIELD_SIZE,\n options: [\n { value: 'medium', label: 'Medium (default)' },\n { value: 'small', label: 'Small' },\n { value: 'large', label: 'Large' },\n ],\n },\n {\n name: 'siblingCount',\n label: 'Sibling pages',\n description:\n 'How many page numbers to show either side of the current one.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n {\n name: 'boundaryCount',\n label: 'Boundary pages',\n description:\n 'How many page numbers to always show at the start and end.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n {\n name: 'showFirstButton',\n label: 'Show first-page button?',\n description: 'Adds a jump-to-first control.',\n component: Aglyn.FieldComponentType.SWITCH,\n },\n {\n name: 'showLastButton',\n label: 'Show last-page button?',\n description: 'Adds a jump-to-last control.',\n component: Aglyn.FieldComponentType.SWITCH,\n },\n {\n name: 'hidePrevButton',\n label: 'Hide previous button?',\n description: 'Removes the previous-page arrow.',\n component: Aglyn.FieldComponentType.SWITCH,\n },\n {\n name: 'hideNextButton',\n label: 'Hide next button?',\n description: 'Removes the next-page arrow.',\n component: Aglyn.FieldComponentType.SWITCH,\n },\n FIELD_DISABLED,\n ],\n}\n\nexport const presets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Pagination',\n pluginId: BUNDLE_ID,\n description: 'Page picker with previous/next arrows',\n category: Aglyn.ComponentCategory.NAVIGATION,\n icon: schema.icon,\n data: {\n $id: null,\n componentId: ID,\n pluginId: BUNDLE_ID,\n props: { count: 10, color: 'primary' },\n },\n },\n]\n\nexport default PaginationElement\n"],"names":["Aglyn","mdiFormatListNumbered","MuiPagination","forwardRef","BUNDLE_ID","FIELD_DISABLED","FIELD_SIZE","dropClearedProps","generatePresetId","ID","toCount","value","fallback","parsed","Number","isFinite","Math","round","PaginationElement","props","ref","count","defaultPage","siblingCount","boundaryCount","children","_children","spread","rest","total","min","max","displayName","schema","$id","pluginId","description","category","ComponentCategory","NAVIGATION","icon","path","sx","color","flags","selfClosing","FEATURE_FLAG","ENABLED","attributes","name","label","component","FieldComponentType","TEXT_FIELD","type","SELECT","options","SWITCH","presets","NodeType","PRESET","data","componentId"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/mui/src/lib/components/pagination.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 { mdiFormatListNumbered } from '@aglyn/shared-data-mdi'\nimport MuiPagination from '@mui/material/Pagination'\nimport { forwardRef, type ReactNode } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { FIELD_DISABLED, FIELD_SIZE } from '../constants/field-presets'\nimport { dropClearedProps } from '../utils/drop-cleared-props'\nimport { generatePresetId } from '../utils/generate-preset-id'\n\n// Component ids are persisted in screen documents; never rename.\nexport const ID: Aglyn.ComponentId = 'muiPagination'\n\nexport interface PaginationElementProps {\n count?: number | string\n defaultPage?: number | string\n color?: 'primary' | 'secondary' | 'tertiary' | 'standard'\n shape?: 'circular' | 'rounded'\n size?: 'small' | 'medium' | 'large'\n variant?: 'text' | 'outlined'\n siblingCount?: number | string\n boundaryCount?: number | string\n showFirstButton?: boolean\n showLastButton?: boolean\n hidePrevButton?: boolean\n hideNextButton?: boolean\n disabled?: boolean\n /**\n * Accepted and dropped. The element is self-closing (see `flags`), so the\n * editor never authors children — but the renderer builds elements from\n * persisted props, and a stray child from an older document would reach\n * MUI's Pagination and render beside the page list. Declared because the\n * implementation deliberately discards it (AGL-1323).\n */\n children?: ReactNode\n}\n\n/** Number-typed attribute fields round-trip as strings. */\nfunction toCount(value: unknown, fallback: number): number {\n if (value == null || value === '') return fallback\n const parsed = Number(value)\n if (!Number.isFinite(parsed) || parsed < 0) return fallback\n return Math.round(parsed)\n}\n\n/**\n * Pagination control (https://mui.com/material-ui/react-pagination/).\n *\n * Uncontrolled: it tracks and highlights the page a visitor picks, but\n * it does not by itself fetch or filter anything — pair it with an\n * interaction, or with a repeat that reads the page. That is stated in\n * the element's own description too, because a page-picker that looks\n * wired and isn't is worse than one that says what it is.\n *\n * The `size` here is MUI's own three-value scale; the shared FIELD_SIZE\n * preset also offers `inherit`, which Pagination does not accept, so the\n * shared field is narrowed rather than re-declared.\n */\nconst PaginationElement = forwardRef<HTMLElement, PaginationElementProps>(\n (props, ref) => {\n const {\n count,\n defaultPage,\n siblingCount,\n boundaryCount,\n children: _children,\n ...spread\n } = props\n // A cleared `color`/`size` persists as null; MUI capitalizes both and\n // throws during SSR, 500ing the page (AGL-1226).\n const rest = dropClearedProps(spread)\n const total = toCount(count, 10)\n return (\n <MuiPagination\n ref={ref}\n count={total}\n // Out-of-range defaults make MUI drop the selection entirely.\n defaultPage={Math.min(Math.max(toCount(defaultPage, 1), 1), total)}\n siblingCount={toCount(siblingCount, 1)}\n boundaryCount={toCount(boundaryCount, 1)}\n {...rest}\n />\n )\n },\n)\nPaginationElement.displayName = 'AglynPagination'\n\nexport const schema: Aglyn.ComponentSchema<PaginationElementProps> = {\n $id: ID,\n pluginId: BUNDLE_ID,\n displayName: 'Pagination',\n description:\n 'Page-number picker. It highlights the chosen number; wire an ' +\n 'interaction to make it change what the page shows.',\n category: Aglyn.ComponentCategory.NAVIGATION,\n icon: { path: mdiFormatListNumbered.path, sx: { color: '#2196f3' } },\n flags: { selfClosing: Aglyn.FEATURE_FLAG.ENABLED },\n attributes: [\n {\n name: 'count',\n label: 'Pages',\n description: 'Total number of pages. Default 10.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n {\n name: 'defaultPage',\n label: 'Starting page',\n description: 'The page selected when a visitor arrives. Default 1.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n {\n name: 'variant',\n label: 'Variant',\n description: 'Outlined draws a border around each page item.',\n component: Aglyn.FieldComponentType.SELECT,\n // REAL SENTINELS here, not deletions (AGL-1453) — the opposite call\n // from Button's and Typography's \"Default\" options, for a reason that\n // is visible in the lists themselves: those name MUI's default among\n // their choices, so `''` was a duplicate. These four offered ONLY the\n // non-default alternative, so `''` was the sole route back — and it\n // could not persist (AGL-1191). Picking Outlined, Rounded, Primary or\n // Small was a one-way door: the \"(default)\" option reverted on save and\n // the field snapped back to what it was.\n //\n // Each sentinel is MUI Pagination's own literal default for that prop\n // (`Pagination.js`: `variant = 'text'`, `shape = 'circular'`,\n // `color = 'standard'`, `size = 'medium'`), so the labels stop lying\n // and a stored value renders exactly what an unset one did.\n options: [\n { value: 'text', label: 'Text (default)' },\n { value: 'outlined', label: 'Outlined' },\n ],\n },\n {\n name: 'shape',\n label: 'Shape',\n description: 'Rounded squares instead of circles.',\n component: Aglyn.FieldComponentType.SELECT,\n options: [\n { value: 'circular', label: 'Circular (default)' },\n { value: 'rounded', label: 'Rounded' },\n ],\n },\n {\n name: 'color',\n label: 'Theme color',\n description: 'Color of the selected page item.',\n component: Aglyn.FieldComponentType.SELECT,\n options: [\n { value: 'standard', label: 'Standard (default)' },\n { value: 'primary', label: 'Primary' },\n { value: 'secondary', label: 'Secondary' },\n { value: 'tertiary', label: 'Tertiary' },\n ],\n },\n {\n // Narrowed from the shared preset: Pagination has no `inherit`\n // size, and offering it would render nothing different.\n ...FIELD_SIZE,\n options: [\n { value: 'medium', label: 'Medium (default)' },\n { value: 'small', label: 'Small' },\n { value: 'large', label: 'Large' },\n ],\n },\n {\n name: 'siblingCount',\n label: 'Sibling pages',\n description:\n 'How many page numbers to show either side of the current one.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n {\n name: 'boundaryCount',\n label: 'Boundary pages',\n description:\n 'How many page numbers to always show at the start and end.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n type: 'number',\n },\n {\n name: 'showFirstButton',\n label: 'Show first-page button?',\n description: 'Adds a jump-to-first control.',\n component: Aglyn.FieldComponentType.SWITCH,\n },\n {\n name: 'showLastButton',\n label: 'Show last-page button?',\n description: 'Adds a jump-to-last control.',\n component: Aglyn.FieldComponentType.SWITCH,\n },\n {\n name: 'hidePrevButton',\n label: 'Hide previous button?',\n description: 'Removes the previous-page arrow.',\n component: Aglyn.FieldComponentType.SWITCH,\n },\n {\n name: 'hideNextButton',\n label: 'Hide next button?',\n description: 'Removes the next-page arrow.',\n component: Aglyn.FieldComponentType.SWITCH,\n },\n FIELD_DISABLED,\n ],\n}\n\nexport const presets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Pagination',\n pluginId: BUNDLE_ID,\n description: 'Page-number picker with previous/next arrows',\n category: Aglyn.ComponentCategory.NAVIGATION,\n icon: schema.icon,\n data: {\n $id: null,\n componentId: ID,\n pluginId: BUNDLE_ID,\n props: { count: 10, color: 'primary' },\n },\n },\n]\n\nexport default PaginationElement\n"],"names":["Aglyn","mdiFormatListNumbered","MuiPagination","forwardRef","BUNDLE_ID","FIELD_DISABLED","FIELD_SIZE","dropClearedProps","generatePresetId","ID","toCount","value","fallback","parsed","Number","isFinite","Math","round","PaginationElement","props","ref","count","defaultPage","siblingCount","boundaryCount","children","_children","spread","rest","total","min","max","displayName","schema","$id","pluginId","description","category","ComponentCategory","NAVIGATION","icon","path","sx","color","flags","selfClosing","FEATURE_FLAG","ENABLED","attributes","name","label","component","FieldComponentType","TEXT_FIELD","type","SELECT","options","SWITCH","presets","NodeType","PRESET","data","componentId"],"mappings":"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"}
|
|
@@ -188,8 +188,8 @@ ScreenLink.displayName = 'ScreenLink';
|
|
|
188
188
|
export const schema = {
|
|
189
189
|
$id: ID,
|
|
190
190
|
pluginId: BUNDLE_ID,
|
|
191
|
-
displayName: '
|
|
192
|
-
description: 'Links to another
|
|
191
|
+
displayName: 'Page Link',
|
|
192
|
+
description: 'Links to another page by id, so it survives a slug change.',
|
|
193
193
|
category: Aglyn.ComponentCategory.NAVIGATION,
|
|
194
194
|
icon: {
|
|
195
195
|
path: mdiLinkVariant.path,
|
|
@@ -210,13 +210,13 @@ export const schema = {
|
|
|
210
210
|
FIELD_TEXT_CONTENT,
|
|
211
211
|
{
|
|
212
212
|
name: 'screenId',
|
|
213
|
-
description: '
|
|
213
|
+
description: 'Page this link navigates to. The address is generated from the ' + 'published path at render time, so it keeps working when the ' + "page's slug or parent changes.",
|
|
214
214
|
component: Aglyn.FieldComponentType.SCREEN_SELECT,
|
|
215
|
-
label: '
|
|
215
|
+
label: 'Page'
|
|
216
216
|
},
|
|
217
217
|
{
|
|
218
218
|
name: 'href',
|
|
219
|
-
description: 'Where this link goes when it points off this site. Ignored while ' + '
|
|
219
|
+
description: 'Where this link goes when it points off this site. Ignored while ' + 'Page names one — that field wins, so clear it to use a URL.',
|
|
220
220
|
component: Aglyn.FieldComponentType.TEXT_FIELD,
|
|
221
221
|
label: 'External URL',
|
|
222
222
|
// A bare `#fragment` saves and goes nowhere (AGL-2867).
|
|
@@ -297,7 +297,7 @@ export const presets = [
|
|
|
297
297
|
{
|
|
298
298
|
$id: generatePresetId(ID),
|
|
299
299
|
type: Aglyn.NodeType.PRESET,
|
|
300
|
-
displayName: '
|
|
300
|
+
displayName: 'Page Link',
|
|
301
301
|
icon: {
|
|
302
302
|
path: mdiLinkVariant.path,
|
|
303
303
|
sx: {
|
|
@@ -310,7 +310,7 @@ export const presets = [
|
|
|
310
310
|
componentId: ID,
|
|
311
311
|
pluginId: BUNDLE_ID,
|
|
312
312
|
props: {
|
|
313
|
-
children: '
|
|
313
|
+
children: 'Page Link',
|
|
314
314
|
// Same visible-on-any-surface defaults as the button preset; the
|
|
315
315
|
// default text color disappears against a same-hue appbar.
|
|
316
316
|
variant: 'outlined',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/mui/src/lib/components/screen-link.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 { mdiLinkVariant } from '@aglyn/shared-data-mdi'\nimport { AppLink } from '@aglyn/shared-ui-jsx'\nimport Button, { type ButtonProps } from '@mui/material/Button'\nimport Link from '@mui/material/Link'\nimport { forwardRef } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport {\n FIELD_COLOR,\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 { withAuthorHtmlLabel } from '../utils/author-html-label'\nimport { dropClearedProps } from '../utils/drop-cleared-props'\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; never rename.\nexport const ID: Aglyn.ComponentId = 'muiScreenLink'\n\nexport interface ScreenLinkProps extends ButtonProps {\n /**\n * Target screen id. The href is resolved from the host routing map at\n * render time (see `ScreenLinkContext`), so slug renames and re-parenting\n * never break the link — which is what makes navigation inside a shared\n * layout viable.\n */\n screenId?: string\n /** External URL escape hatch, used only when no `screenId` is set. */\n href?: string\n /**\n * Which element this ships as (AGL-1195, AGL-1347). Persisted in screen\n * documents; never rename, and `undefined` must keep meaning `'button'` —\n * every link authored before this existed is one.\n *\n * - `'button'` (or unset) — `<a role=\"button\">` with the button's look.\n * - `'link'` — a plain text link; the button-only props are dropped.\n * - `'linkButton'` — a plain `<a>` with NO role, wearing the button's\n * `variant`/`size`/`fullWidth`. Appearance without semantics: a pill\n * that navigates announces as the link it is.\n */\n renderAs?: 'button' | 'link' | 'linkButton'\n /**\n * Where the link opens. Persisted in screen documents; never rename a\n * value, and `undefined` must keep meaning `'_self'` — every link authored\n * before this existed stays in the tab.\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 with nothing to point at (canvas,\n * preview, unresolved screen). `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.\n */\nconst STYLED_LINK_PLACEHOLDER = { component: 'span', role: undefined } as const\n\n/**\n * Link that targets a screen by id, never a hardcoded path. Degrades to a\n * plain button when the id doesn't resolve (unpublished/deleted screen) and\n * when navigation is suppressed (besigner canvas, preview).\n *\n * `renderAs=\"link\"` renders MUI's `Link` instead of `Button` (AGL-1195).\n * Footer and inline navigation are *text*: as buttons they inherit button\n * typography, uppercase transforms and the wrong role for assistive tech.\n * The button-only props are dropped in that mode rather than passed through\n * — `variant` means the typography variant on a `Link`, so forwarding\n * `\"contained\"` would silently produce an unstyled element.\n *\n * `renderAs=\"linkButton\"` splits the two halves that used to travel together\n * (AGL-1347). A pill row whose chips all navigate — docs URLs, `mailto:`,\n * social profiles — is a legitimate design, but every chip announced as a\n * button, which tells a screen-reader user to expect an action and costs\n * them the link-list and open-in-new-tab affordances. This mode keeps the\n * button's `variant`/`size`/`fullWidth` and drops the role, exactly as MUI's\n * own `Button component={Link}` does. It is a THIRD value rather than\n * `variant` becoming live in `\"link\"` mode, because a node switched to Text\n * link keeps whatever `variant` it was authored with (the shipped preset is\n * `variant: 'outlined'`) — honouring those would repaint the site's nav and\n * footer as outlined pills.\n */\nconst ScreenLink = forwardRef<any, ScreenLinkProps>((props, ref) => {\n const {\n screenId,\n href: externalHref,\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). This is the site's\n // most-used element (70–77 nodes per page), so the guard runs BEFORE\n // `variant`/`size`/`fullWidth` are split out: they are forwarded as named\n // attributes, and guarding only the spread would leave those three exposed.\n //\n // The formatted label (AGL-2557) is substituted for `children` in the same\n // breath, so all five shapes below carry it without five more spreads —\n // and an unformatted link, which is nearly all of them, gets the very\n // object `dropClearedProps` returned.\n const { variant, size, fullWidth, ...rest } = withAuthorHtmlLabel(\n dropClearedProps(spread),\n html,\n )\n // Id-vs-URL precedence and the `javascript:`/`data:` guard live in\n // `useLinkTarget` (AGL-1335) — one copy for every linking element, and\n // the only place that knows a `screen:`-prefixed value is a reference.\n const { href, suppressNavigation, editorInert, broken, leavesSite } =\n Aglyn.useLinkTarget(screenId, externalHref)\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 inert canvas\n // branches below never see either half.\n const targetAttrs = linkTargetProps(target, targetName, leavesSite)\n /**\n * The same dead-target problem Tabs had, on the element there are 326 of\n * (AGL-1893). Here it is less violent — an unresolved Screen Link already\n * degrades to inert content rather than to a fake control — but it is just\n * as silent: unpublish a screen and every link to it quietly stops\n * navigating, with nothing in the editor to say which ones.\n *\n * So the LIVE rendering is deliberately unchanged, and only the authoring\n * surface gains anything: on the besigner canvas the link wears a warning\n * ring and explains itself on hover. A live restyle would be the riskier\n * half of this by far — it would repaint any published CTA whose screen is\n * temporarily unpublished, on sites whose owners never asked.\n */\n const flagged = Aglyn.brokenScreenLinkProps(broken, editorInert)\n const asLink = renderAs === 'link'\n // Semantics and appearance are independent choices now (AGL-1347): 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\n // the unresolved/suppressed case mirrors the same three shapes.\n //\n // `sx` is lifted out of the spread so the editor ring can be merged\n // rather than replace the author's styles — the AGL-1284 mistake, in a\n // place where the symptom would be \"my link lost all its styling the\n // day its screen was unpublished\". The ring goes LAST, and is the one\n // thing here allowed to outrank an authored value: it is a diagnostic\n // on a surface only the author sees, not a style.\n const { sx: authoredSx, ...restNoSx } = rest as Record<string, any>\n const inertSx = [\n ...(Array.isArray(authoredSx) ? authoredSx : authoredSx ? [authoredSx] : []),\n ...(broken && editorInert ? [Aglyn.BROKEN_SCREEN_LINK_SX] : []),\n ]\n if (asLink) {\n return (\n <Link\n ref={ref}\n component=\"span\"\n underline=\"hover\"\n {...flagged}\n {...restNoSx}\n sx={inertSx}\n />\n )\n }\n return (\n <Button\n ref={ref}\n {...(asStyledLink ? STYLED_LINK_PLACEHOLDER : {})}\n variant={variant}\n size={size}\n fullWidth={fullWidth}\n {...flagged}\n {...restNoSx}\n sx={inertSx}\n />\n )\n }\n return asLink ? (\n <AppLink\n ref={ref}\n underline=\"hover\"\n href={href}\n {...targetAttrs}\n {...rest}\n />\n ) : (\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.\n role={asStyledLink ? undefined : 'button'}\n variant={variant}\n size={size}\n fullWidth={fullWidth}\n {...rest}\n />\n )\n})\nScreenLink.displayName = 'ScreenLink'\n\n/**\n * Shows an attribute in the two modes that wear button styling — Button and\n * Link (button styling). `notMatch` inverts `is`, so an unset `renderAs` —\n * every link authored before AGL-1195 — still counts as a button and keeps\n * its controls, and only Text link hides them.\n */\nconst BUTTON_STYLED = { when: 'renderAs', is: 'link', notMatch: true }\n\nexport const schema: Aglyn.ComponentSchema<ScreenLinkProps> = {\n $id: ID,\n pluginId: BUNDLE_ID,\n displayName: 'Screen Link',\n description: 'Links to another screen by id, so it survives a slug change.',\n category: Aglyn.ComponentCategory.NAVIGATION,\n icon: {\n path: mdiLinkVariant.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). Every mode of this element ships an anchor or\n // a button, and neither may contain a list or a second control.\n richTextCommands: [Aglyn.RICH_TEXT_COMMANDS.EMPHASIS],\n attributes: [\n FIELD_TEXT_CONTENT,\n {\n name: 'screenId',\n description:\n 'Screen this link navigates to. The address is generated from the ' +\n 'published path at render time, so it keeps working when the ' +\n \"screen's slug or parent changes.\",\n component: Aglyn.FieldComponentType.SCREEN_SELECT,\n label: 'Screen',\n },\n {\n name: 'href',\n description:\n 'Where this link goes when it points off this site. Ignored while ' +\n 'Screen names one — that field wins, so clear it to use a URL.',\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. Button is for calls to action; both link ' +\n 'options announce as a link, which is what navigation should be — ' +\n 'pick \"Link (button styling)\" to keep the button look on something ' +\n 'that navigates.',\n component: Aglyn.FieldComponentType.SELECT,\n label: 'Render as',\n // `'button'` is a REAL SENTINEL, not a deletion (AGL-1453): `''` here\n // spelled an author's actual choice, and the one they could not make.\n // The attributes form strips `''` before save (AGL-1191), so a link\n // switched to Text link had NO route in this dropdown back to Button —\n // and 213 of the 326 Screen Links in the corpus are in that mode.\n //\n // Safe because `renderAs` never reaches MUI: it is destructured out\n // above and read only as `=== 'link'` / `=== 'linkButton'`, so\n // `'button'` and an absent value take the identical branch. That is\n // also what keeps every link authored before AGL-1195 rendering\n // unchanged.\n options: [\n { value: 'button', label: 'Button' },\n { value: 'link', label: 'Text link' },\n { value: 'linkButton', label: 'Link (button styling)' },\n ],\n },\n // `color` survives both shapes — MUI's Link takes it too.\n FIELD_COLOR,\n // The rest are appearance: the renderer drops them in Text link mode, so\n // leaving them on screen there would offer three controls that silently\n // do nothing — but they are live in Link (button styling), which is the\n // point of that mode. Spread rather than mutate — these presets are\n // shared.\n { ...FIELD_SIZE, condition: BUTTON_STYLED },\n { ...FIELD_FULL_WIDTH, condition: BUTTON_STYLED },\n {\n name: 'variant',\n description:\n 'How the link is drawn once its style is Button: Contained is a ' +\n 'filled call to action, Outlined a secondary one, Text the lightest. ' +\n 'Ignored while the link is styled as plain text.',\n component: Aglyn.FieldComponentType.SELECT,\n label: 'Variant',\n // \"Default\" deleted (AGL-1453): unpersistable, and a second name for\n // `text`, MUI Button's own default, already on the list.\n options: [\n { value: 'text', label: 'Text' },\n { value: 'outlined', label: 'Outlined' },\n { value: 'contained', label: 'Contained' },\n ],\n condition: BUTTON_STYLED,\n },\n ],\n}\n\nexport const presets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Screen Link',\n icon: {\n path: mdiLinkVariant.path,\n sx: { color: '#2196f3' },\n },\n category: Aglyn.ComponentCategory.NAVIGATION,\n data: {\n $id: null,\n componentId: ID,\n pluginId: BUNDLE_ID,\n props: {\n children: 'Screen Link',\n // Same visible-on-any-surface defaults as the button preset; the\n // default text color disappears against a same-hue appbar.\n variant: 'outlined',\n color: 'inherit',\n },\n },\n },\n]\n\nexport default ScreenLink\n"],"names":["Aglyn","mdiLinkVariant","AppLink","Button","Link","forwardRef","BUNDLE_ID","FIELD_COLOR","FIELD_FULL_WIDTH","FIELD_LINK_TARGET","FIELD_LINK_TARGET_NAME","FIELD_SIZE","FIELD_TEXT_CONTENT","withAuthorHtmlLabel","dropClearedProps","generatePresetId","linkTargetProps","ID","STYLED_LINK_PLACEHOLDER","component","role","undefined","ScreenLink","props","ref","screenId","href","externalHref","renderAs","target","targetName","html","spread","variant","size","fullWidth","rest","suppressNavigation","editorInert","broken","leavesSite","useLinkTarget","targetAttrs","flagged","brokenScreenLinkProps","asLink","asStyledLink","sx","authoredSx","restNoSx","inertSx","Array","isArray","BROKEN_SCREEN_LINK_SX","underline","componentVariant","displayName","BUTTON_STYLED","when","is","notMatch","schema","$id","pluginId","description","category","ComponentCategory","NAVIGATION","icon","path","color","flags","textEditable","FEATURE_FLAG","ENABLED","richTextEditable","richTextCommands","RICH_TEXT_COMMANDS","EMPHASIS","attributes","name","FieldComponentType","SCREEN_SELECT","label","TEXT_FIELD","resolveProps","bareFragmentLinkFieldProps","SELECT","options","value","condition","presets","type","NodeType","PRESET","data","componentId","children"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/mui/src/lib/components/screen-link.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 { mdiLinkVariant } from '@aglyn/shared-data-mdi'\nimport { AppLink } from '@aglyn/shared-ui-jsx'\nimport Button, { type ButtonProps } from '@mui/material/Button'\nimport Link from '@mui/material/Link'\nimport { forwardRef } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport {\n FIELD_COLOR,\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 { withAuthorHtmlLabel } from '../utils/author-html-label'\nimport { dropClearedProps } from '../utils/drop-cleared-props'\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; never rename.\nexport const ID: Aglyn.ComponentId = 'muiScreenLink'\n\nexport interface ScreenLinkProps extends ButtonProps {\n /**\n * Target screen id. The href is resolved from the host routing map at\n * render time (see `ScreenLinkContext`), so slug renames and re-parenting\n * never break the link — which is what makes navigation inside a shared\n * layout viable.\n */\n screenId?: string\n /** External URL escape hatch, used only when no `screenId` is set. */\n href?: string\n /**\n * Which element this ships as (AGL-1195, AGL-1347). Persisted in screen\n * documents; never rename, and `undefined` must keep meaning `'button'` —\n * every link authored before this existed is one.\n *\n * - `'button'` (or unset) — `<a role=\"button\">` with the button's look.\n * - `'link'` — a plain text link; the button-only props are dropped.\n * - `'linkButton'` — a plain `<a>` with NO role, wearing the button's\n * `variant`/`size`/`fullWidth`. Appearance without semantics: a pill\n * that navigates announces as the link it is.\n */\n renderAs?: 'button' | 'link' | 'linkButton'\n /**\n * Where the link opens. Persisted in screen documents; never rename a\n * value, and `undefined` must keep meaning `'_self'` — every link authored\n * before this existed stays in the tab.\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 with nothing to point at (canvas,\n * preview, unresolved screen). `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.\n */\nconst STYLED_LINK_PLACEHOLDER = { component: 'span', role: undefined } as const\n\n/**\n * Link that targets a screen by id, never a hardcoded path. Degrades to a\n * plain button when the id doesn't resolve (unpublished/deleted screen) and\n * when navigation is suppressed (besigner canvas, preview).\n *\n * `renderAs=\"link\"` renders MUI's `Link` instead of `Button` (AGL-1195).\n * Footer and inline navigation are *text*: as buttons they inherit button\n * typography, uppercase transforms and the wrong role for assistive tech.\n * The button-only props are dropped in that mode rather than passed through\n * — `variant` means the typography variant on a `Link`, so forwarding\n * `\"contained\"` would silently produce an unstyled element.\n *\n * `renderAs=\"linkButton\"` splits the two halves that used to travel together\n * (AGL-1347). A pill row whose chips all navigate — docs URLs, `mailto:`,\n * social profiles — is a legitimate design, but every chip announced as a\n * button, which tells a screen-reader user to expect an action and costs\n * them the link-list and open-in-new-tab affordances. This mode keeps the\n * button's `variant`/`size`/`fullWidth` and drops the role, exactly as MUI's\n * own `Button component={Link}` does. It is a THIRD value rather than\n * `variant` becoming live in `\"link\"` mode, because a node switched to Text\n * link keeps whatever `variant` it was authored with (the shipped preset is\n * `variant: 'outlined'`) — honouring those would repaint the site's nav and\n * footer as outlined pills.\n */\nconst ScreenLink = forwardRef<any, ScreenLinkProps>((props, ref) => {\n const {\n screenId,\n href: externalHref,\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). This is the site's\n // most-used element (70–77 nodes per page), so the guard runs BEFORE\n // `variant`/`size`/`fullWidth` are split out: they are forwarded as named\n // attributes, and guarding only the spread would leave those three exposed.\n //\n // The formatted label (AGL-2557) is substituted for `children` in the same\n // breath, so all five shapes below carry it without five more spreads —\n // and an unformatted link, which is nearly all of them, gets the very\n // object `dropClearedProps` returned.\n const { variant, size, fullWidth, ...rest } = withAuthorHtmlLabel(\n dropClearedProps(spread),\n html,\n )\n // Id-vs-URL precedence and the `javascript:`/`data:` guard live in\n // `useLinkTarget` (AGL-1335) — one copy for every linking element, and\n // the only place that knows a `screen:`-prefixed value is a reference.\n const { href, suppressNavigation, editorInert, broken, leavesSite } =\n Aglyn.useLinkTarget(screenId, externalHref)\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 inert canvas\n // branches below never see either half.\n const targetAttrs = linkTargetProps(target, targetName, leavesSite)\n /**\n * The same dead-target problem Tabs had, on the element there are 326 of\n * (AGL-1893). Here it is less violent — an unresolved Screen Link already\n * degrades to inert content rather than to a fake control — but it is just\n * as silent: unpublish a screen and every link to it quietly stops\n * navigating, with nothing in the editor to say which ones.\n *\n * So the LIVE rendering is deliberately unchanged, and only the authoring\n * surface gains anything: on the besigner canvas the link wears a warning\n * ring and explains itself on hover. A live restyle would be the riskier\n * half of this by far — it would repaint any published CTA whose screen is\n * temporarily unpublished, on sites whose owners never asked.\n */\n const flagged = Aglyn.brokenScreenLinkProps(broken, editorInert)\n const asLink = renderAs === 'link'\n // Semantics and appearance are independent choices now (AGL-1347): 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\n // the unresolved/suppressed case mirrors the same three shapes.\n //\n // `sx` is lifted out of the spread so the editor ring can be merged\n // rather than replace the author's styles — the AGL-1284 mistake, in a\n // place where the symptom would be \"my link lost all its styling the\n // day its screen was unpublished\". The ring goes LAST, and is the one\n // thing here allowed to outrank an authored value: it is a diagnostic\n // on a surface only the author sees, not a style.\n const { sx: authoredSx, ...restNoSx } = rest as Record<string, any>\n const inertSx = [\n ...(Array.isArray(authoredSx) ? authoredSx : authoredSx ? [authoredSx] : []),\n ...(broken && editorInert ? [Aglyn.BROKEN_SCREEN_LINK_SX] : []),\n ]\n if (asLink) {\n return (\n <Link\n ref={ref}\n component=\"span\"\n underline=\"hover\"\n {...flagged}\n {...restNoSx}\n sx={inertSx}\n />\n )\n }\n return (\n <Button\n ref={ref}\n {...(asStyledLink ? STYLED_LINK_PLACEHOLDER : {})}\n variant={variant}\n size={size}\n fullWidth={fullWidth}\n {...flagged}\n {...restNoSx}\n sx={inertSx}\n />\n )\n }\n return asLink ? (\n <AppLink\n ref={ref}\n underline=\"hover\"\n href={href}\n {...targetAttrs}\n {...rest}\n />\n ) : (\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.\n role={asStyledLink ? undefined : 'button'}\n variant={variant}\n size={size}\n fullWidth={fullWidth}\n {...rest}\n />\n )\n})\nScreenLink.displayName = 'ScreenLink'\n\n/**\n * Shows an attribute in the two modes that wear button styling — Button and\n * Link (button styling). `notMatch` inverts `is`, so an unset `renderAs` —\n * every link authored before AGL-1195 — still counts as a button and keeps\n * its controls, and only Text link hides them.\n */\nconst BUTTON_STYLED = { when: 'renderAs', is: 'link', notMatch: true }\n\nexport const schema: Aglyn.ComponentSchema<ScreenLinkProps> = {\n $id: ID,\n pluginId: BUNDLE_ID,\n displayName: 'Page Link',\n description: 'Links to another page by id, so it survives a slug change.',\n category: Aglyn.ComponentCategory.NAVIGATION,\n icon: {\n path: mdiLinkVariant.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). Every mode of this element ships an anchor or\n // a button, and neither may contain a list or a second control.\n richTextCommands: [Aglyn.RICH_TEXT_COMMANDS.EMPHASIS],\n attributes: [\n FIELD_TEXT_CONTENT,\n {\n name: 'screenId',\n description:\n 'Page this link navigates to. The address is generated from the ' +\n 'published path at render time, so it keeps working when the ' +\n \"page's slug or parent changes.\",\n component: Aglyn.FieldComponentType.SCREEN_SELECT,\n label: 'Page',\n },\n {\n name: 'href',\n description:\n 'Where this link goes when it points off this site. Ignored while ' +\n 'Page names one — that field wins, so clear it to use a URL.',\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. Button is for calls to action; both link ' +\n 'options announce as a link, which is what navigation should be — ' +\n 'pick \"Link (button styling)\" to keep the button look on something ' +\n 'that navigates.',\n component: Aglyn.FieldComponentType.SELECT,\n label: 'Render as',\n // `'button'` is a REAL SENTINEL, not a deletion (AGL-1453): `''` here\n // spelled an author's actual choice, and the one they could not make.\n // The attributes form strips `''` before save (AGL-1191), so a link\n // switched to Text link had NO route in this dropdown back to Button —\n // and 213 of the 326 Screen Links in the corpus are in that mode.\n //\n // Safe because `renderAs` never reaches MUI: it is destructured out\n // above and read only as `=== 'link'` / `=== 'linkButton'`, so\n // `'button'` and an absent value take the identical branch. That is\n // also what keeps every link authored before AGL-1195 rendering\n // unchanged.\n options: [\n { value: 'button', label: 'Button' },\n { value: 'link', label: 'Text link' },\n { value: 'linkButton', label: 'Link (button styling)' },\n ],\n },\n // `color` survives both shapes — MUI's Link takes it too.\n FIELD_COLOR,\n // The rest are appearance: the renderer drops them in Text link mode, so\n // leaving them on screen there would offer three controls that silently\n // do nothing — but they are live in Link (button styling), which is the\n // point of that mode. Spread rather than mutate — these presets are\n // shared.\n { ...FIELD_SIZE, condition: BUTTON_STYLED },\n { ...FIELD_FULL_WIDTH, condition: BUTTON_STYLED },\n {\n name: 'variant',\n description:\n 'How the link is drawn once its style is Button: Contained is a ' +\n 'filled call to action, Outlined a secondary one, Text the lightest. ' +\n 'Ignored while the link is styled as plain text.',\n component: Aglyn.FieldComponentType.SELECT,\n label: 'Variant',\n // \"Default\" deleted (AGL-1453): unpersistable, and a second name for\n // `text`, MUI Button's own default, already on the list.\n options: [\n { value: 'text', label: 'Text' },\n { value: 'outlined', label: 'Outlined' },\n { value: 'contained', label: 'Contained' },\n ],\n condition: BUTTON_STYLED,\n },\n ],\n}\n\nexport const presets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Page Link',\n icon: {\n path: mdiLinkVariant.path,\n sx: { color: '#2196f3' },\n },\n category: Aglyn.ComponentCategory.NAVIGATION,\n data: {\n $id: null,\n componentId: ID,\n pluginId: BUNDLE_ID,\n props: {\n children: 'Page Link',\n // Same visible-on-any-surface defaults as the button preset; the\n // default text color disappears against a same-hue appbar.\n variant: 'outlined',\n color: 'inherit',\n },\n },\n },\n]\n\nexport default ScreenLink\n"],"names":["Aglyn","mdiLinkVariant","AppLink","Button","Link","forwardRef","BUNDLE_ID","FIELD_COLOR","FIELD_FULL_WIDTH","FIELD_LINK_TARGET","FIELD_LINK_TARGET_NAME","FIELD_SIZE","FIELD_TEXT_CONTENT","withAuthorHtmlLabel","dropClearedProps","generatePresetId","linkTargetProps","ID","STYLED_LINK_PLACEHOLDER","component","role","undefined","ScreenLink","props","ref","screenId","href","externalHref","renderAs","target","targetName","html","spread","variant","size","fullWidth","rest","suppressNavigation","editorInert","broken","leavesSite","useLinkTarget","targetAttrs","flagged","brokenScreenLinkProps","asLink","asStyledLink","sx","authoredSx","restNoSx","inertSx","Array","isArray","BROKEN_SCREEN_LINK_SX","underline","componentVariant","displayName","BUTTON_STYLED","when","is","notMatch","schema","$id","pluginId","description","category","ComponentCategory","NAVIGATION","icon","path","color","flags","textEditable","FEATURE_FLAG","ENABLED","richTextEditable","richTextCommands","RICH_TEXT_COMMANDS","EMPHASIS","attributes","name","FieldComponentType","SCREEN_SELECT","label","TEXT_FIELD","resolveProps","bareFragmentLinkFieldProps","SELECT","options","value","condition","presets","type","NodeType","PRESET","data","componentId","children"],"mappings":"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"}
|
|
@@ -560,7 +560,7 @@ TabPanelElement.displayName = 'AglynTabPanel';
|
|
|
560
560
|
return {
|
|
561
561
|
name: tabLinkProp(position),
|
|
562
562
|
label: `Tab ${position} link`,
|
|
563
|
-
description: `
|
|
563
|
+
description: `Page the ${ordinal(position)} tab navigates to. A tab with a ` + 'link is a real link — it can be opened in a new tab and search ' + 'engines follow it — and it no longer switches panels. Leave it ' + 'unset for a tab that just reveals its own panel, including the ' + 'tab for the page this row is already on.',
|
|
564
564
|
component: Aglyn.FieldComponentType.SCREEN_SELECT,
|
|
565
565
|
condition: atLeastLabels(position)
|
|
566
566
|
};
|
|
@@ -706,7 +706,7 @@ export const tabsSchema = {
|
|
|
706
706
|
{
|
|
707
707
|
name: 'ariaLabel',
|
|
708
708
|
label: 'Accessible label',
|
|
709
|
-
description: 'Names the row for screen readers. Default "Tabs". Give a row ' + 'whose tabs link to other
|
|
709
|
+
description: 'Names the row for screen readers. Default "Tabs". Give a row ' + 'whose tabs link to other pages its own name — it becomes a ' + 'navigation landmark, and two landmarks called the same thing are ' + 'no help to anyone.',
|
|
710
710
|
component: Aglyn.FieldComponentType.TEXT_FIELD
|
|
711
711
|
}
|
|
712
712
|
]
|