@aglyn/plugins-mui 1.0.0-beta.218 → 1.0.0-beta.220

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/package.json +7 -7
  2. package/src/lib/components/accordion.js +1 -1
  3. package/src/lib/components/accordion.js.map +1 -1
  4. package/src/lib/components/blocks.js +1 -1
  5. package/src/lib/components/blocks.js.map +1 -1
  6. package/src/lib/components/breadcrumbs.js +1 -1
  7. package/src/lib/components/breadcrumbs.js.map +1 -1
  8. package/src/lib/components/button.js +5 -5
  9. package/src/lib/components/button.js.map +1 -1
  10. package/src/lib/components/collection-categories.js +1 -1
  11. package/src/lib/components/collection-categories.js.map +1 -1
  12. package/src/lib/components/collection-entries.js +1 -1
  13. package/src/lib/components/collection-entries.js.map +1 -1
  14. package/src/lib/components/collection-entry-body.js +1 -1
  15. package/src/lib/components/collection-entry-body.js.map +1 -1
  16. package/src/lib/components/collection-search.js +1 -1
  17. package/src/lib/components/collection-search.js.map +1 -1
  18. package/src/lib/components/document-root.js +1 -1
  19. package/src/lib/components/document-root.js.map +1 -1
  20. package/src/lib/components/image.js +4 -4
  21. package/src/lib/components/image.js.map +1 -1
  22. package/src/lib/components/language-switcher.js +3 -3
  23. package/src/lib/components/language-switcher.js.map +1 -1
  24. package/src/lib/components/layout-slot.d.ts +1 -1
  25. package/src/lib/components/layout-slot.js +3 -3
  26. package/src/lib/components/layout-slot.js.map +1 -1
  27. package/src/lib/components/link-box.js +4 -4
  28. package/src/lib/components/link-box.js.map +1 -1
  29. package/src/lib/components/markdown.js +3 -3
  30. package/src/lib/components/markdown.js.map +1 -1
  31. package/src/lib/components/pagination.js +2 -2
  32. package/src/lib/components/pagination.js.map +1 -1
  33. package/src/lib/components/screen-link.js +7 -7
  34. package/src/lib/components/screen-link.js.map +1 -1
  35. package/src/lib/components/tabs.js +2 -2
  36. package/src/lib/components/tabs.js.map +1 -1
  37. package/src/lib/components/video.d.ts +5 -4
  38. package/src/lib/components/video.js +24 -22
  39. package/src/lib/components/video.js.map +1 -1
  40. package/src/lib/video-embed-providers.d.ts +30 -0
  41. package/src/lib/video-embed-providers.js +125 -0
  42. package/src/lib/video-embed-providers.js.map +1 -0
@@ -394,7 +394,7 @@ export const schema = {
394
394
  },
395
395
  {
396
396
  name: 'loading',
397
- description: 'Lazy waits to load the image until a visitor scrolls near it; ' + 'pick Eager for the first image at the top of a screen so it ' + 'shows immediately.',
397
+ description: 'Lazy waits to load the image until a visitor scrolls near it; ' + 'pick Eager for the first image at the top of a page so it ' + 'shows immediately.',
398
398
  component: Aglyn.FieldComponentType.SELECT,
399
399
  label: 'Loading',
400
400
  options: [
@@ -410,13 +410,13 @@ export const schema = {
410
410
  },
411
411
  {
412
412
  name: 'screenId',
413
- description: 'Optional: navigate to this screen when the image is clicked — ' + 'follows the published path like a Screen Link.',
413
+ description: 'Optional: navigate to this page when the image is clicked — ' + 'follows the published path like a Page Link.',
414
414
  component: Aglyn.FieldComponentType.SCREEN_SELECT,
415
- label: 'Link to screen'
415
+ label: 'Link to page'
416
416
  },
417
417
  {
418
418
  name: 'href',
419
- description: 'Makes the image a link to somewhere off this site. Ignored while ' + 'Link to screen names one. Leave both blank and the image is not ' + 'clickable at all.',
419
+ description: 'Makes the image a link to somewhere off this site. Ignored while ' + 'Link to page names one. Leave both blank and the image is not ' + 'clickable at all.',
420
420
  component: Aglyn.FieldComponentType.TEXT_FIELD,
421
421
  label: 'External URL',
422
422
  // A bare `#fragment` saves and goes nowhere (AGL-2867).
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/mui/src/lib/components/image.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 { mdiImage } from '@aglyn/shared-data-mdi'\nimport { AppLink } from '@aglyn/shared-ui-jsx'\nimport Box from '@mui/material/Box'\nimport type { SxProps } from '@mui/material/styles'\nimport { forwardRef, type ReactNode } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { generatePresetId } from '../utils/generate-preset-id'\n\n// Component ids are persisted in screen documents; never rename.\nexport const ID: Aglyn.ComponentId = 'image'\n\n/**\n * The node ids of the images that load eagerly: the first image the LAYOUT\n * chain renders and the first image the SCREEN renders, each in document\n * order (AGL-2486).\n *\n * Every image used to render `loading=\"lazy\"` — the hero included. That is\n * the worst possible default for the one image that is almost always the LCP\n * element: a lazy image is not fetched until layout has run and the browser\n * has decided it is near the viewport, and it is fetched at LOW priority when\n * it finally is. Lighthouse reports that as \"LCP request discovery\", and it is\n * also why an image four sections down could finish before the one the reader\n * is looking at: with everything lazy and everything low, nothing outranked\n * anything, so the order was whatever the network felt like.\n *\n * So the lead images get `loading=\"eager\"` and the rest get\n * `fetchpriority=\"low\"`, which is the browser-level knob that stops\n * below-the-fold images competing with the ones above them.\n *\n * ONE lead image was not enough. On any site whose header carries a logo, the\n * first image in document order is that logo, so the screen's own hero — the\n * element Lighthouse names as the LCP on aglyn.com's solution pages at\n * 375x812 — stayed `lazy` + `low`: undiscovered until layout, then fetched\n * behind every script on the page (measured 2026-09-08: 1.5 s of load delay\n * and 1.5 s of load time for a 13 KB image). Composition namespaces layout\n * nodes by origin ({@link Aglyn.isLayoutComposedNodeId}), so the walk keeps\n * one lead PER ORIGIN: the layout's first image (the logo) and the screen's\n * first image (the hero). A layout image further down — a mega-menu\n * illustration, the footer mark — is still deferred, as is every later\n * screen image. In the besigner canvas nothing is layout-composed, so the\n * screen's first image is the only lead, exactly as before.\n *\n * Neither lead gets `fetchpriority=\"high\"`, and the reasoning is at the\n * `fetchPriority` prop below. Short version: \"first image of its origin\" is\n * still not \"the LCP element\", and a ranking claim needs evidence this\n * function does not have.\n *\n * Resolved from the tree rather than a render-order counter on purpose: the\n * renderer walks the tree in document order on the server AND on hydrate, but\n * a mutable counter would double-count under React's concurrent re-renders\n * and hand the priority to a different image on the client than the one the\n * HTML gave it. A pure function of the tree cannot disagree with itself — and\n * it is deliberately NOT memoized on the root object, because the canvas\n * mutates that tree in place while an author works.\n *\n * The walk STOPS once both leads are known — on a page with a header logo\n * and a hero that is O(nodes above the hero), a dozen or two on a normal\n * page — and at the first image on a screen with no layout.\n *\n * An image with no `src` renders a placeholder box and no `<img>` at all, so\n * it cannot be the LCP element and is skipped.\n */\nexport function leadImageNodeIds(\n root: Aglyn.NodeSchema | undefined,\n): readonly string[] {\n let layoutLead: string | undefined\n let screenLead: string | undefined\n const walk = (node: Aglyn.NodeSchema | undefined): boolean => {\n if (!node) return false\n if (node.componentId === ID) {\n const props = (node.resolvedProps ?? node.props ?? {}) as Record<\n string,\n unknown\n >\n if (String(props['src'] ?? '').trim()) {\n if (Aglyn.isLayoutComposedNodeId(node.$id)) {\n if (!layoutLead) layoutLead = node.$id\n } else if (!screenLead) {\n screenLead = node.$id\n }\n if (layoutLead && screenLead) return true\n }\n }\n for (const child of node.children ?? []) {\n if (walk(child)) return true\n }\n return false\n }\n walk(root)\n return [layoutLead, screenLead].filter((id): id is string => Boolean(id))\n}\n\nexport interface ImageProps {\n /**\n * Where the image comes from (AGL-72). Either a **media reference** —\n * `media:{scope}/{mediaId}`, what \"Browse media\" now stores (AGL-1215) —\n * or any URL, which covers both the legacy values already in published\n * documents and an author-typed hotlink. `resolveMediaSrc` decides.\n */\n src?: string\n alt?: string\n /**\n * Explicit decorative choice (AGL-1305): ON forces `alt=\"\"` (and drops\n * the tooltip) no matter what the alt field says, so screen readers skip\n * the image. Distinct from simply leaving alt unset, which also renders\n * `alt=\"\"` today but records no intent.\n */\n decorative?: boolean\n /** Tooltip shown on hover — the native img `title` attribute. */\n title?: string\n /** Native loading hint; unset stays lazy, exactly as before AGL-1305. */\n loading?: 'lazy' | 'eager'\n objectFit?: 'cover' | 'contain' | 'fill' | 'none' | 'scale-down'\n /** CSS width (e.g. \"100%\", \"320px\"); defaults to 100%. */\n width?: string\n /** CSS height (e.g. \"240px\"); defaults to auto. */\n height?: string\n /**\n * The asset's own pixel dimensions, copied off the media document when the\n * image was picked (AGL-2486), and read from the asset again when the page\n * is composed, so a replace with a different shape reaches every page that\n * places it (AGL-2833).\n *\n * NOT author controls, which is why neither appears in the schema below:\n * they describe the file, and the CSS `width`/`height` above describe the\n * placement. The pair becomes the `<img>`'s intrinsic `width`/`height`\n * attributes, which is the only thing that lets the browser reserve the\n * right box before the bytes arrive — with `width: 100%; height: auto` the\n * element is otherwise zero-height until the image decodes, and every image\n * on the page shifts the layout as it lands.\n *\n * An attribute pair is a RATIO here, not a size: CSS wins for the used\n * dimensions either way, so a stale value costs nothing but a reservation\n * of the wrong shape. Both must be present and positive or neither is\n * emitted — one alone gives the browser no ratio and would be read as a\n * real dimension.\n *\n * Read from the node like any other prop, so this element never fetches a\n * media document: the composition has already laid the asset's current\n * pair over the pick's (`media-asset-facts.ts`), and the pick's copy is\n * what renders wherever that read cannot answer. `srcSet` still selects\n * which variant is downloaded; these only say what shape it will be.\n */\n intrinsicWidth?: number\n /** See `intrinsicWidth` — the two are only ever used as a pair. */\n intrinsicHeight?: number\n /** Border radius in px. */\n radius?: number\n /** Target screen id — resolved rename-safe like Screen Link (AGL-339). */\n screenId?: string\n /** External URL, used only when no `screenId` is set. */\n href?: string\n /**\n * Authored node styles, handed over by the renderer rather than typed into\n * an attribute — recomposed below so the author's Styles-panel values are\n * merged rather than replaced (AGL-1240).\n */\n sx?: SxProps\n /**\n * Accepted and dropped: an `<img>` is a void element and React throws on\n * ANY children value reaching one, which 500'd whole pages (AGL-579).\n * Declared because the implementation deliberately discards it (AGL-1323).\n */\n children?: ReactNode\n}\n\n/**\n * Image element (AGL-74): renders a plain img with fit/size/radius\n * controls; an empty src shows a labeled placeholder so the element stays\n * visible and selectable in the editor.\n */\nconst Image = forwardRef<HTMLElement, ImageProps>((props, ref) => {\n const {\n src: storedSrc,\n alt,\n decorative,\n title,\n loading,\n objectFit,\n width,\n height,\n intrinsicWidth,\n intrinsicHeight,\n radius,\n screenId,\n href: externalHref,\n // Never forward children to the <img> below — React throws on ANY\n // children value reaching a void element, which 500'd whole pages\n // when a renderer passed empty JSX children through (AGL-579).\n children: _children,\n // Pull `sx` out of the spread: the literals below are composed AFTER\n // `{...rest}`, so leaving it there REPLACED every style the author set\n // from the Styles panel. The hero mockups' 16px radius and drop shadow\n // were being discarded on every published page (AGL-1240).\n sx: nodeSxProp,\n ...rest\n } = props\n // Node styles ride the renderer-merged sx; recompose (stack.ts pattern).\n const nodeSx = Array.isArray(nodeSxProp)\n ? nodeSxProp\n : nodeSxProp\n ? [nodeSxProp]\n : []\n // Optional link mode (AGL-339): screen id first (rename-safe), external\n // URL as fallback; suppressed in the besigner canvas like Screen Link.\n // Shared with every other linking element since AGL-1335, so a `Link`\n // component prop bound here behaves as it does on a Button.\n const { href: linkHref, suppressNavigation } = Aglyn.useLinkTarget(\n screenId,\n externalHref,\n )\n /**\n * Eagerness (AGL-2486). An explicit author choice always wins — including\n * an explicit `lazy`, so someone who deliberately deferred the top image\n * keeps that. Only an UNSET `loading` is decided here, and only for the\n * lead images: the layout's first and the screen's first.\n *\n * `leafIdsMatch` rather than `===`: a reusable component instance suffixes\n * its leaf ids, so the id in the tree and the id in the context are the\n * same leaf spelled two ways (the markdown block resolves the same way).\n */\n const nodeId = Aglyn.useNodeId()\n const isLeadImage =\n Boolean(nodeId) &&\n leadImageNodeIds(Aglyn.canvas.rootNode).some((leadId) =>\n Aglyn.leafIdsMatch(leadId, nodeId),\n )\n const eager = loading === 'eager' || (loading == null && isLeadImage)\n /**\n * Resolve the stored value to a URL (AGL-1215). A media reference becomes\n * a CDN URL here rather than in the document, so the route shape stays an\n * app concern; every other value — a legacy firebasestorage URL, a legacy\n * `/api/media/cdn/…` path, an author-typed hotlink — passes through.\n *\n * `useSite().hostId` is the site being rendered: present on the tenant and\n * in Preview, absent in the besigner canvas. When it is there it names the\n * asking site in the org scope, which is what lets ONE reference in a\n * layout or reusable component resolve on each site that uses it.\n */\n const { hostId } = Aglyn.useSite()\n const src = Aglyn.resolveMediaSrc(storedSrc, { hostId })\n const wrapLink = (element: JSX.Element) =>\n linkHref && !suppressNavigation ? (\n <AppLink\n componentVariant=\"naked\"\n href={linkHref}\n style={{ display: 'block' }}\n >\n {element}\n </AppLink>\n ) : (\n element\n )\n if (!src) {\n // The labeled box is for the author, so only editing surfaces draw it; a\n // published page renders the bare element.\n if (!suppressNavigation) return <Box ref={ref} {...rest} sx={nodeSx} />\n return (\n <Box\n ref={ref}\n {...rest}\n sx={[\n {\n width: width || '100%',\n height: height || 120,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n border: '1px dashed',\n borderColor: 'divider',\n borderRadius: radius != null ? `${radius}px` : undefined,\n color: 'text.secondary',\n fontSize: 12,\n fontFamily: 'system-ui, sans-serif',\n },\n ...nodeSx,\n ]}\n >\n {'Image — choose a source'}\n </Box>\n )\n }\n // CDN URLs (AGL-175) carry WebP variants selected by `?w=`; widths\n // without a variant fall back to the original server-side, so a static\n // srcSet is safe for any CDN-form URL. Asked of the RESOLVED url, so a\n // reference and a legacy stored path both keep their WebP variants.\n const isCdnUrl = Aglyn.isMediaCdnUrl(src)\n /** A CSS width that is a plain pixel value, and therefore a real `sizes`. */\n const pinnedWidth = /^\\d+(?:\\.\\d+)?px$/.test(String(width ?? '').trim())\n ? String(width).trim()\n : undefined\n /**\n * The intrinsic attribute pair, or nothing.\n *\n * Both-or-neither: the browser derives an aspect-ratio only from the pair,\n * and a lone `width` is read as a real dimension instead — which would\n * reserve a box of the wrong shape rather than no box at all. Finite and\n * positive because a media document may carry `0` or a partial capture\n * (dimensions are best-effort at upload), and `width=\"0\"` collapses the\n * element.\n */\n const usable = (value: unknown): value is number =>\n typeof value === 'number' && Number.isFinite(value) && value > 0\n const intrinsicAttributes =\n usable(intrinsicWidth) && usable(intrinsicHeight)\n ? { width: intrinsicWidth, height: intrinsicHeight }\n : undefined\n return wrapLink(\n <Box\n ref={ref}\n component=\"img\"\n src={src}\n // EVERY CANDIDATE IS A `?w=` URL, and the bare one is gone (2026-08-26).\n // Why that is worth 335 KB against 4 KB, and why each candidate merges\n // its width into the url's existing query, are documented where the list\n // and the builder live — `MEDIA_CDN_VARIANT_WIDTHS` and `mediaCdnSrcSet`\n // in `media-ref.ts`.\n //\n // Called rather than restated so the Markdown and entry-body renderers\n // can ask for the same list (AGL-3149). Building it here is the reason\n // they had none: a candidate list inside a component is a candidate list\n // no other component can have.\n srcSet={Aglyn.mediaCdnSrcSet(src)}\n // `sizes` is NOT only a delivery hint, and treating it as one broke every\n // fluid image (AGL-2486). With `w` descriptors the browser derives the\n // image's density-corrected INTRINSIC size from `sizes`, so `sizes` is\n // what a CSS `width: 100%` resolves against whenever the containing block\n // is content-sized — shrink-to-fit, inline-block, a flex item sized on its\n // content. Measured in Chrome at a 1200px viewport with the author CSS\n // `width:100%;height:auto;display:block`:\n //\n // parent sizes=100vw sizes=auto\n // inline-block (shrink-to-fit) 1184px 300px\n // block / fixed-width flex 900px 900px\n //\n // 300px is the spec's default object size, used because resolving `auto`\n // against a content-sized parent is circular. So `sizes=\"auto\"` — which\n // genuinely does pick a better candidate, `?w=320` instead of a 357 KB\n // original in a 158px slot — rendered those images tiny and centred in\n // their box, on the canvas, in _preview and on published sites alike.\n //\n // A delivery win may not be paid for in layout, so this is back to\n // `100vw`: it overfetches, but it is the value every published document\n // was authored against. A pinned pixel width is still the better answer\n // where the author gave one, because it is a definite length and cannot\n // be circular. Getting image delivery right for fluid images needs the\n // media pipeline (a WebP variant at source width) or real intrinsic\n // `width`/`height` attributes from media metadata — neither of which\n // perturbs layout the way `sizes` does.\n sizes={isCdnUrl ? (pinnedWidth ?? '100vw') : undefined}\n // Unset alt keeps rendering `alt=\"\"` exactly as it always has —\n // existing documents must not change output (AGL-1305). Decorative\n // ON forces `alt=\"\"` over any alt text and suppresses the tooltip,\n // so the a11y intent is explicit rather than an accident of blank.\n alt={decorative ? '' : (alt ?? '')}\n title={decorative ? undefined : title || undefined}\n // The ordering signal, ONE-DIRECTIONAL on purpose (AGL-2486).\n //\n // `low` on every deferred image, so a footer image cannot be fetched\n // ahead of the section the reader is in. That half is safe in a way the\n // other half is not: deprioritising an image that is provably not being\n // looked at cannot starve whatever the LCP turns out to be.\n //\n // The lead image gets NO `fetchpriority` — the browser's `auto` — where\n // it used to get `high`. `high` is not a statement about this image, it\n // is a claim that this image outranks everything else in flight,\n // including the stylesheet and the webfont that a TEXT LCP is waiting\n // on. We are not in a position to make that claim: the only evidence\n // behind it was \"first `<img>` in document order\", and document order's\n // first image is the HEADER LOGO on any site whose header has one.\n //\n // Measured on aglyn.com at a 375x812 viewport, which is what sent this\n // back: the element carrying `fetchpriority=\"high\"` was the logo at\n // 145x44 = 6,380 px², while the `<h1>` under it was 343x113 =\n // 38,893 px² — six times the area, and the element Lighthouse named as\n // the LCP. So the hint was being spent to make a text LCP arrive later.\n //\n // `auto` is not a retreat to the old behaviour. The old bug was that\n // everything was `lazy`, so the lead image was not discovered until\n // after layout; it still gets `loading=\"eager\"` above, which is the\n // discovery fix and the part that actually earned the win. What goes is\n // only the RANKING claim, back to Chrome's own in-viewport heuristic —\n // which decides after layout, with the viewport and the geometry this\n // function provably does not have.\n //\n // Deliberately not replaced with a size heuristic: nothing here knows\n // the rendered size. `width`/`height` are optional author CSS strings,\n // routinely `100%`, and a logo constrained by its container measures\n // small while declaring nothing. A guess that fails the same way is not\n // an improvement on a guess.\n //\n // An author who genuinely has an image LCP should be able to SAY so —\n // but not through this control. The `loading` field is labelled\n // \"Loading\" and described in terms of lazy versus eager; an author\n // picking Eager for the top image is not asserting a priority ranking,\n // and reading one out of that choice is how the logo got `high` in the\n // first place. A real priority affordance needs its own control and its\n // own words. After September 1.\n // Decoding off the main thread for the deferred ones — they have no\n // paint deadline, and decoding them synchronously is main-thread time\n // spent on pixels nobody is looking at yet. The eager image keeps the\n // browser's default (`auto`) so it is free to decode in time to paint.\n //\n // The deferred set is `DEFERRED_IMAGE_ATTRIBUTES` rather than three\n // literals because every OTHER `<img>` a published page renders has to\n // land in the same rank to be ranked at all — a product grid, an event\n // list, a cart line. Those carried no hint whatsoever and so were\n // fetched EAGERLY, ahead of anything this component deferred. The set\n // is documented at its definition; the reasoning for each member is\n // the two paragraphs above and the two below.\n {...(eager\n ? { loading: 'eager' as const }\n : Aglyn.DEFERRED_IMAGE_ATTRIBUTES)}\n // Ahead of `{...rest}` so an author who has typed a literal width or\n // height attribute onto the node still wins, and ahead of `sx` because\n // these are ATTRIBUTES: the CSS block below sets the used size, and\n // these only supply the ratio it is laid out against. `Box` in this\n // version applies `styleFunctionSx` alone — it has no system-props\n // layer — so both forward to the `<img>` rather than becoming CSS.\n {...intrinsicAttributes}\n {...rest}\n sx={[\n {\n display: 'block',\n width: width || '100%',\n height: height || 'auto',\n objectFit: objectFit || 'cover',\n borderRadius: radius != null ? `${radius}px` : undefined,\n },\n ...nodeSx,\n ]}\n />,\n )\n})\nImage.displayName = 'Image'\n\n/** Alt text and tooltip make no sense on an explicitly decorative image. */\nconst NOT_DECORATIVE = { when: 'decorative', is: true, notMatch: true }\n\nexport const schema: Aglyn.ComponentSchema<ImageProps> = {\n $id: ID,\n pluginId: BUNDLE_ID,\n displayName: 'Image',\n description:\n 'A picture from your media library or any URL, with fit, size and an optional link.',\n category: Aglyn.ComponentCategory.MEDIA,\n icon: {\n path: mdiImage.path,\n sx: { color: '#7b1fa2' },\n },\n flags: {\n selfClosing: Aglyn.FEATURE_FLAG.ENABLED,\n },\n attributes: [\n {\n name: 'src',\n // \"Browse media\" is the path an author should take (AGL-1215) — it\n // stores a reference to the asset, which survives moves, replaces and\n // any future change to how media is delivered. Typing a URL stays\n // supported for hotlinking somebody else's image; nobody should ever\n // be pasting one of OUR paths in here.\n description:\n 'Pick from your media library with \"Browse media\", or paste the ' +\n 'URL of an image hosted somewhere else.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n label: 'Image source',\n },\n {\n name: 'alt',\n // AGL-1896: \"Browse media\" now fills this in from the asset's own alt\n // text when it is empty, so the description says where the value came\n // from — otherwise a field that populates itself reads as a bug.\n description:\n 'Describes the image for screen readers and search engines. ' +\n 'Filled in from the media library when you pick a file that has ' +\n 'alt text; anything you type here wins for this placement only.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n label: 'Alt text',\n // Hidden while Decorative is on — the renderer forces alt=\"\" then,\n // but the text is kept on the node so toggling back restores it.\n condition: NOT_DECORATIVE,\n },\n {\n name: 'decorative',\n description:\n 'Turn on when the image is purely decorative so screen readers ' +\n 'skip it — no alt text is needed then.',\n component: Aglyn.FieldComponentType.SWITCH,\n label: 'Decorative image',\n },\n {\n name: 'title',\n description:\n 'Optional tooltip shown when a visitor hovers over the image.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n label: 'Tooltip',\n condition: NOT_DECORATIVE,\n },\n {\n name: 'objectFit',\n description: 'How the image fills its box.',\n component: Aglyn.FieldComponentType.SELECT,\n label: 'Fit',\n // Both selects on this element take real sentinels (AGL-1451):\n // `cover` and `lazy` are members of their own declared prop unions\n // and the values the render already falls back to. As `''` neither\n // could persist (AGL-1191) — an image switched to Contain or Eager\n // could not be switched back.\n options: [\n { value: 'cover', label: 'Cover (default)' },\n { value: 'contain', label: 'Contain' },\n { value: 'fill', label: 'Fill' },\n { value: 'none', label: 'None' },\n { value: 'scale-down', label: 'Scale down' },\n ],\n },\n {\n name: 'width',\n description:\n 'Width of the image — a number plus a unit, e.g. 100% or 320px.',\n component: Aglyn.FieldComponentType.CSS_DIMENSION,\n label: 'Width',\n },\n {\n name: 'height',\n description: 'Height of the image. Leave empty for auto.',\n component: Aglyn.FieldComponentType.CSS_DIMENSION,\n label: 'Height',\n },\n {\n name: 'loading',\n description:\n 'Lazy waits to load the image until a visitor scrolls near it; ' +\n 'pick Eager for the first image at the top of a screen so it ' +\n 'shows immediately.',\n component: Aglyn.FieldComponentType.SELECT,\n label: 'Loading',\n options: [\n { value: 'lazy', label: 'Lazy (default)' },\n { value: 'eager', label: 'Eager' },\n ],\n },\n {\n name: 'screenId',\n description:\n 'Optional: navigate to this screen when the image is clicked — ' +\n 'follows the published path like a Screen Link.',\n component: Aglyn.FieldComponentType.SCREEN_SELECT,\n label: 'Link to screen',\n },\n {\n name: 'href',\n description:\n 'Makes the image a link to somewhere off this site. Ignored while ' +\n 'Link to screen names one. Leave both blank and the image is not ' +\n 'clickable at all.',\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 ],\n}\n\nexport const presets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Image',\n pluginId: BUNDLE_ID,\n description: 'Image from your media library or any URL',\n category: Aglyn.ComponentCategory.MEDIA,\n icon: {\n path: mdiImage.path,\n sx: { color: '#7b1fa2' },\n },\n data: {\n $id: null,\n componentId: ID,\n pluginId: BUNDLE_ID,\n props: {},\n },\n },\n]\n\nexport default Image\n"],"names":["Aglyn","mdiImage","AppLink","Box","forwardRef","BUNDLE_ID","generatePresetId","ID","leadImageNodeIds","root","layoutLead","screenLead","walk","node","componentId","props","resolvedProps","String","trim","isLayoutComposedNodeId","$id","child","children","filter","id","Boolean","Image","ref","src","storedSrc","alt","decorative","title","loading","objectFit","width","height","intrinsicWidth","intrinsicHeight","radius","screenId","href","externalHref","_children","sx","nodeSxProp","rest","nodeSx","Array","isArray","linkHref","suppressNavigation","useLinkTarget","nodeId","useNodeId","isLeadImage","canvas","rootNode","some","leadId","leafIdsMatch","eager","hostId","useSite","resolveMediaSrc","wrapLink","element","componentVariant","style","display","alignItems","justifyContent","border","borderColor","borderRadius","undefined","color","fontSize","fontFamily","isCdnUrl","isMediaCdnUrl","pinnedWidth","test","usable","value","Number","isFinite","intrinsicAttributes","component","srcSet","mediaCdnSrcSet","sizes","DEFERRED_IMAGE_ATTRIBUTES","displayName","NOT_DECORATIVE","when","is","notMatch","schema","pluginId","description","category","ComponentCategory","MEDIA","icon","path","flags","selfClosing","FEATURE_FLAG","ENABLED","attributes","name","FieldComponentType","TEXT_FIELD","label","condition","SWITCH","SELECT","options","CSS_DIMENSION","SCREEN_SELECT","resolveProps","bareFragmentLinkFieldProps","presets","type","NodeType","PRESET","data"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/mui/src/lib/components/image.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 { mdiImage } from '@aglyn/shared-data-mdi'\nimport { AppLink } from '@aglyn/shared-ui-jsx'\nimport Box from '@mui/material/Box'\nimport type { SxProps } from '@mui/material/styles'\nimport { forwardRef, type ReactNode } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { generatePresetId } from '../utils/generate-preset-id'\n\n// Component ids are persisted in screen documents; never rename.\nexport const ID: Aglyn.ComponentId = 'image'\n\n/**\n * The node ids of the images that load eagerly: the first image the LAYOUT\n * chain renders and the first image the SCREEN renders, each in document\n * order (AGL-2486).\n *\n * Every image used to render `loading=\"lazy\"` — the hero included. That is\n * the worst possible default for the one image that is almost always the LCP\n * element: a lazy image is not fetched until layout has run and the browser\n * has decided it is near the viewport, and it is fetched at LOW priority when\n * it finally is. Lighthouse reports that as \"LCP request discovery\", and it is\n * also why an image four sections down could finish before the one the reader\n * is looking at: with everything lazy and everything low, nothing outranked\n * anything, so the order was whatever the network felt like.\n *\n * So the lead images get `loading=\"eager\"` and the rest get\n * `fetchpriority=\"low\"`, which is the browser-level knob that stops\n * below-the-fold images competing with the ones above them.\n *\n * ONE lead image was not enough. On any site whose header carries a logo, the\n * first image in document order is that logo, so the screen's own hero — the\n * element Lighthouse names as the LCP on aglyn.com's solution pages at\n * 375x812 — stayed `lazy` + `low`: undiscovered until layout, then fetched\n * behind every script on the page (measured 2026-09-08: 1.5 s of load delay\n * and 1.5 s of load time for a 13 KB image). Composition namespaces layout\n * nodes by origin ({@link Aglyn.isLayoutComposedNodeId}), so the walk keeps\n * one lead PER ORIGIN: the layout's first image (the logo) and the screen's\n * first image (the hero). A layout image further down — a mega-menu\n * illustration, the footer mark — is still deferred, as is every later\n * screen image. In the besigner canvas nothing is layout-composed, so the\n * screen's first image is the only lead, exactly as before.\n *\n * Neither lead gets `fetchpriority=\"high\"`, and the reasoning is at the\n * `fetchPriority` prop below. Short version: \"first image of its origin\" is\n * still not \"the LCP element\", and a ranking claim needs evidence this\n * function does not have.\n *\n * Resolved from the tree rather than a render-order counter on purpose: the\n * renderer walks the tree in document order on the server AND on hydrate, but\n * a mutable counter would double-count under React's concurrent re-renders\n * and hand the priority to a different image on the client than the one the\n * HTML gave it. A pure function of the tree cannot disagree with itself — and\n * it is deliberately NOT memoized on the root object, because the canvas\n * mutates that tree in place while an author works.\n *\n * The walk STOPS once both leads are known — on a page with a header logo\n * and a hero that is O(nodes above the hero), a dozen or two on a normal\n * page — and at the first image on a screen with no layout.\n *\n * An image with no `src` renders a placeholder box and no `<img>` at all, so\n * it cannot be the LCP element and is skipped.\n */\nexport function leadImageNodeIds(\n root: Aglyn.NodeSchema | undefined,\n): readonly string[] {\n let layoutLead: string | undefined\n let screenLead: string | undefined\n const walk = (node: Aglyn.NodeSchema | undefined): boolean => {\n if (!node) return false\n if (node.componentId === ID) {\n const props = (node.resolvedProps ?? node.props ?? {}) as Record<\n string,\n unknown\n >\n if (String(props['src'] ?? '').trim()) {\n if (Aglyn.isLayoutComposedNodeId(node.$id)) {\n if (!layoutLead) layoutLead = node.$id\n } else if (!screenLead) {\n screenLead = node.$id\n }\n if (layoutLead && screenLead) return true\n }\n }\n for (const child of node.children ?? []) {\n if (walk(child)) return true\n }\n return false\n }\n walk(root)\n return [layoutLead, screenLead].filter((id): id is string => Boolean(id))\n}\n\nexport interface ImageProps {\n /**\n * Where the image comes from (AGL-72). Either a **media reference** —\n * `media:{scope}/{mediaId}`, what \"Browse media\" now stores (AGL-1215) —\n * or any URL, which covers both the legacy values already in published\n * documents and an author-typed hotlink. `resolveMediaSrc` decides.\n */\n src?: string\n alt?: string\n /**\n * Explicit decorative choice (AGL-1305): ON forces `alt=\"\"` (and drops\n * the tooltip) no matter what the alt field says, so screen readers skip\n * the image. Distinct from simply leaving alt unset, which also renders\n * `alt=\"\"` today but records no intent.\n */\n decorative?: boolean\n /** Tooltip shown on hover — the native img `title` attribute. */\n title?: string\n /** Native loading hint; unset stays lazy, exactly as before AGL-1305. */\n loading?: 'lazy' | 'eager'\n objectFit?: 'cover' | 'contain' | 'fill' | 'none' | 'scale-down'\n /** CSS width (e.g. \"100%\", \"320px\"); defaults to 100%. */\n width?: string\n /** CSS height (e.g. \"240px\"); defaults to auto. */\n height?: string\n /**\n * The asset's own pixel dimensions, copied off the media document when the\n * image was picked (AGL-2486), and read from the asset again when the page\n * is composed, so a replace with a different shape reaches every page that\n * places it (AGL-2833).\n *\n * NOT author controls, which is why neither appears in the schema below:\n * they describe the file, and the CSS `width`/`height` above describe the\n * placement. The pair becomes the `<img>`'s intrinsic `width`/`height`\n * attributes, which is the only thing that lets the browser reserve the\n * right box before the bytes arrive — with `width: 100%; height: auto` the\n * element is otherwise zero-height until the image decodes, and every image\n * on the page shifts the layout as it lands.\n *\n * An attribute pair is a RATIO here, not a size: CSS wins for the used\n * dimensions either way, so a stale value costs nothing but a reservation\n * of the wrong shape. Both must be present and positive or neither is\n * emitted — one alone gives the browser no ratio and would be read as a\n * real dimension.\n *\n * Read from the node like any other prop, so this element never fetches a\n * media document: the composition has already laid the asset's current\n * pair over the pick's (`media-asset-facts.ts`), and the pick's copy is\n * what renders wherever that read cannot answer. `srcSet` still selects\n * which variant is downloaded; these only say what shape it will be.\n */\n intrinsicWidth?: number\n /** See `intrinsicWidth` — the two are only ever used as a pair. */\n intrinsicHeight?: number\n /** Border radius in px. */\n radius?: number\n /** Target screen id — resolved rename-safe like Screen Link (AGL-339). */\n screenId?: string\n /** External URL, used only when no `screenId` is set. */\n href?: string\n /**\n * Authored node styles, handed over by the renderer rather than typed into\n * an attribute — recomposed below so the author's Styles-panel values are\n * merged rather than replaced (AGL-1240).\n */\n sx?: SxProps\n /**\n * Accepted and dropped: an `<img>` is a void element and React throws on\n * ANY children value reaching one, which 500'd whole pages (AGL-579).\n * Declared because the implementation deliberately discards it (AGL-1323).\n */\n children?: ReactNode\n}\n\n/**\n * Image element (AGL-74): renders a plain img with fit/size/radius\n * controls; an empty src shows a labeled placeholder so the element stays\n * visible and selectable in the editor.\n */\nconst Image = forwardRef<HTMLElement, ImageProps>((props, ref) => {\n const {\n src: storedSrc,\n alt,\n decorative,\n title,\n loading,\n objectFit,\n width,\n height,\n intrinsicWidth,\n intrinsicHeight,\n radius,\n screenId,\n href: externalHref,\n // Never forward children to the <img> below — React throws on ANY\n // children value reaching a void element, which 500'd whole pages\n // when a renderer passed empty JSX children through (AGL-579).\n children: _children,\n // Pull `sx` out of the spread: the literals below are composed AFTER\n // `{...rest}`, so leaving it there REPLACED every style the author set\n // from the Styles panel. The hero mockups' 16px radius and drop shadow\n // were being discarded on every published page (AGL-1240).\n sx: nodeSxProp,\n ...rest\n } = props\n // Node styles ride the renderer-merged sx; recompose (stack.ts pattern).\n const nodeSx = Array.isArray(nodeSxProp)\n ? nodeSxProp\n : nodeSxProp\n ? [nodeSxProp]\n : []\n // Optional link mode (AGL-339): screen id first (rename-safe), external\n // URL as fallback; suppressed in the besigner canvas like Screen Link.\n // Shared with every other linking element since AGL-1335, so a `Link`\n // component prop bound here behaves as it does on a Button.\n const { href: linkHref, suppressNavigation } = Aglyn.useLinkTarget(\n screenId,\n externalHref,\n )\n /**\n * Eagerness (AGL-2486). An explicit author choice always wins — including\n * an explicit `lazy`, so someone who deliberately deferred the top image\n * keeps that. Only an UNSET `loading` is decided here, and only for the\n * lead images: the layout's first and the screen's first.\n *\n * `leafIdsMatch` rather than `===`: a reusable component instance suffixes\n * its leaf ids, so the id in the tree and the id in the context are the\n * same leaf spelled two ways (the markdown block resolves the same way).\n */\n const nodeId = Aglyn.useNodeId()\n const isLeadImage =\n Boolean(nodeId) &&\n leadImageNodeIds(Aglyn.canvas.rootNode).some((leadId) =>\n Aglyn.leafIdsMatch(leadId, nodeId),\n )\n const eager = loading === 'eager' || (loading == null && isLeadImage)\n /**\n * Resolve the stored value to a URL (AGL-1215). A media reference becomes\n * a CDN URL here rather than in the document, so the route shape stays an\n * app concern; every other value — a legacy firebasestorage URL, a legacy\n * `/api/media/cdn/…` path, an author-typed hotlink — passes through.\n *\n * `useSite().hostId` is the site being rendered: present on the tenant and\n * in Preview, absent in the besigner canvas. When it is there it names the\n * asking site in the org scope, which is what lets ONE reference in a\n * layout or reusable component resolve on each site that uses it.\n */\n const { hostId } = Aglyn.useSite()\n const src = Aglyn.resolveMediaSrc(storedSrc, { hostId })\n const wrapLink = (element: JSX.Element) =>\n linkHref && !suppressNavigation ? (\n <AppLink\n componentVariant=\"naked\"\n href={linkHref}\n style={{ display: 'block' }}\n >\n {element}\n </AppLink>\n ) : (\n element\n )\n if (!src) {\n // The labeled box is for the author, so only editing surfaces draw it; a\n // published page renders the bare element.\n if (!suppressNavigation) return <Box ref={ref} {...rest} sx={nodeSx} />\n return (\n <Box\n ref={ref}\n {...rest}\n sx={[\n {\n width: width || '100%',\n height: height || 120,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n border: '1px dashed',\n borderColor: 'divider',\n borderRadius: radius != null ? `${radius}px` : undefined,\n color: 'text.secondary',\n fontSize: 12,\n fontFamily: 'system-ui, sans-serif',\n },\n ...nodeSx,\n ]}\n >\n {'Image — choose a source'}\n </Box>\n )\n }\n // CDN URLs (AGL-175) carry WebP variants selected by `?w=`; widths\n // without a variant fall back to the original server-side, so a static\n // srcSet is safe for any CDN-form URL. Asked of the RESOLVED url, so a\n // reference and a legacy stored path both keep their WebP variants.\n const isCdnUrl = Aglyn.isMediaCdnUrl(src)\n /** A CSS width that is a plain pixel value, and therefore a real `sizes`. */\n const pinnedWidth = /^\\d+(?:\\.\\d+)?px$/.test(String(width ?? '').trim())\n ? String(width).trim()\n : undefined\n /**\n * The intrinsic attribute pair, or nothing.\n *\n * Both-or-neither: the browser derives an aspect-ratio only from the pair,\n * and a lone `width` is read as a real dimension instead — which would\n * reserve a box of the wrong shape rather than no box at all. Finite and\n * positive because a media document may carry `0` or a partial capture\n * (dimensions are best-effort at upload), and `width=\"0\"` collapses the\n * element.\n */\n const usable = (value: unknown): value is number =>\n typeof value === 'number' && Number.isFinite(value) && value > 0\n const intrinsicAttributes =\n usable(intrinsicWidth) && usable(intrinsicHeight)\n ? { width: intrinsicWidth, height: intrinsicHeight }\n : undefined\n return wrapLink(\n <Box\n ref={ref}\n component=\"img\"\n src={src}\n // EVERY CANDIDATE IS A `?w=` URL, and the bare one is gone (2026-08-26).\n // Why that is worth 335 KB against 4 KB, and why each candidate merges\n // its width into the url's existing query, are documented where the list\n // and the builder live — `MEDIA_CDN_VARIANT_WIDTHS` and `mediaCdnSrcSet`\n // in `media-ref.ts`.\n //\n // Called rather than restated so the Markdown and entry-body renderers\n // can ask for the same list (AGL-3149). Building it here is the reason\n // they had none: a candidate list inside a component is a candidate list\n // no other component can have.\n srcSet={Aglyn.mediaCdnSrcSet(src)}\n // `sizes` is NOT only a delivery hint, and treating it as one broke every\n // fluid image (AGL-2486). With `w` descriptors the browser derives the\n // image's density-corrected INTRINSIC size from `sizes`, so `sizes` is\n // what a CSS `width: 100%` resolves against whenever the containing block\n // is content-sized — shrink-to-fit, inline-block, a flex item sized on its\n // content. Measured in Chrome at a 1200px viewport with the author CSS\n // `width:100%;height:auto;display:block`:\n //\n // parent sizes=100vw sizes=auto\n // inline-block (shrink-to-fit) 1184px 300px\n // block / fixed-width flex 900px 900px\n //\n // 300px is the spec's default object size, used because resolving `auto`\n // against a content-sized parent is circular. So `sizes=\"auto\"` — which\n // genuinely does pick a better candidate, `?w=320` instead of a 357 KB\n // original in a 158px slot — rendered those images tiny and centred in\n // their box, on the canvas, in _preview and on published sites alike.\n //\n // A delivery win may not be paid for in layout, so this is back to\n // `100vw`: it overfetches, but it is the value every published document\n // was authored against. A pinned pixel width is still the better answer\n // where the author gave one, because it is a definite length and cannot\n // be circular. Getting image delivery right for fluid images needs the\n // media pipeline (a WebP variant at source width) or real intrinsic\n // `width`/`height` attributes from media metadata — neither of which\n // perturbs layout the way `sizes` does.\n sizes={isCdnUrl ? (pinnedWidth ?? '100vw') : undefined}\n // Unset alt keeps rendering `alt=\"\"` exactly as it always has —\n // existing documents must not change output (AGL-1305). Decorative\n // ON forces `alt=\"\"` over any alt text and suppresses the tooltip,\n // so the a11y intent is explicit rather than an accident of blank.\n alt={decorative ? '' : (alt ?? '')}\n title={decorative ? undefined : title || undefined}\n // The ordering signal, ONE-DIRECTIONAL on purpose (AGL-2486).\n //\n // `low` on every deferred image, so a footer image cannot be fetched\n // ahead of the section the reader is in. That half is safe in a way the\n // other half is not: deprioritising an image that is provably not being\n // looked at cannot starve whatever the LCP turns out to be.\n //\n // The lead image gets NO `fetchpriority` — the browser's `auto` — where\n // it used to get `high`. `high` is not a statement about this image, it\n // is a claim that this image outranks everything else in flight,\n // including the stylesheet and the webfont that a TEXT LCP is waiting\n // on. We are not in a position to make that claim: the only evidence\n // behind it was \"first `<img>` in document order\", and document order's\n // first image is the HEADER LOGO on any site whose header has one.\n //\n // Measured on aglyn.com at a 375x812 viewport, which is what sent this\n // back: the element carrying `fetchpriority=\"high\"` was the logo at\n // 145x44 = 6,380 px², while the `<h1>` under it was 343x113 =\n // 38,893 px² — six times the area, and the element Lighthouse named as\n // the LCP. So the hint was being spent to make a text LCP arrive later.\n //\n // `auto` is not a retreat to the old behaviour. The old bug was that\n // everything was `lazy`, so the lead image was not discovered until\n // after layout; it still gets `loading=\"eager\"` above, which is the\n // discovery fix and the part that actually earned the win. What goes is\n // only the RANKING claim, back to Chrome's own in-viewport heuristic —\n // which decides after layout, with the viewport and the geometry this\n // function provably does not have.\n //\n // Deliberately not replaced with a size heuristic: nothing here knows\n // the rendered size. `width`/`height` are optional author CSS strings,\n // routinely `100%`, and a logo constrained by its container measures\n // small while declaring nothing. A guess that fails the same way is not\n // an improvement on a guess.\n //\n // An author who genuinely has an image LCP should be able to SAY so —\n // but not through this control. The `loading` field is labelled\n // \"Loading\" and described in terms of lazy versus eager; an author\n // picking Eager for the top image is not asserting a priority ranking,\n // and reading one out of that choice is how the logo got `high` in the\n // first place. A real priority affordance needs its own control and its\n // own words. After September 1.\n // Decoding off the main thread for the deferred ones — they have no\n // paint deadline, and decoding them synchronously is main-thread time\n // spent on pixels nobody is looking at yet. The eager image keeps the\n // browser's default (`auto`) so it is free to decode in time to paint.\n //\n // The deferred set is `DEFERRED_IMAGE_ATTRIBUTES` rather than three\n // literals because every OTHER `<img>` a published page renders has to\n // land in the same rank to be ranked at all — a product grid, an event\n // list, a cart line. Those carried no hint whatsoever and so were\n // fetched EAGERLY, ahead of anything this component deferred. The set\n // is documented at its definition; the reasoning for each member is\n // the two paragraphs above and the two below.\n {...(eager\n ? { loading: 'eager' as const }\n : Aglyn.DEFERRED_IMAGE_ATTRIBUTES)}\n // Ahead of `{...rest}` so an author who has typed a literal width or\n // height attribute onto the node still wins, and ahead of `sx` because\n // these are ATTRIBUTES: the CSS block below sets the used size, and\n // these only supply the ratio it is laid out against. `Box` in this\n // version applies `styleFunctionSx` alone — it has no system-props\n // layer — so both forward to the `<img>` rather than becoming CSS.\n {...intrinsicAttributes}\n {...rest}\n sx={[\n {\n display: 'block',\n width: width || '100%',\n height: height || 'auto',\n objectFit: objectFit || 'cover',\n borderRadius: radius != null ? `${radius}px` : undefined,\n },\n ...nodeSx,\n ]}\n />,\n )\n})\nImage.displayName = 'Image'\n\n/** Alt text and tooltip make no sense on an explicitly decorative image. */\nconst NOT_DECORATIVE = { when: 'decorative', is: true, notMatch: true }\n\nexport const schema: Aglyn.ComponentSchema<ImageProps> = {\n $id: ID,\n pluginId: BUNDLE_ID,\n displayName: 'Image',\n description:\n 'A picture from your media library or any URL, with fit, size and an optional link.',\n category: Aglyn.ComponentCategory.MEDIA,\n icon: {\n path: mdiImage.path,\n sx: { color: '#7b1fa2' },\n },\n flags: {\n selfClosing: Aglyn.FEATURE_FLAG.ENABLED,\n },\n attributes: [\n {\n name: 'src',\n // \"Browse media\" is the path an author should take (AGL-1215) — it\n // stores a reference to the asset, which survives moves, replaces and\n // any future change to how media is delivered. Typing a URL stays\n // supported for hotlinking somebody else's image; nobody should ever\n // be pasting one of OUR paths in here.\n description:\n 'Pick from your media library with \"Browse media\", or paste the ' +\n 'URL of an image hosted somewhere else.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n label: 'Image source',\n },\n {\n name: 'alt',\n // AGL-1896: \"Browse media\" now fills this in from the asset's own alt\n // text when it is empty, so the description says where the value came\n // from — otherwise a field that populates itself reads as a bug.\n description:\n 'Describes the image for screen readers and search engines. ' +\n 'Filled in from the media library when you pick a file that has ' +\n 'alt text; anything you type here wins for this placement only.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n label: 'Alt text',\n // Hidden while Decorative is on — the renderer forces alt=\"\" then,\n // but the text is kept on the node so toggling back restores it.\n condition: NOT_DECORATIVE,\n },\n {\n name: 'decorative',\n description:\n 'Turn on when the image is purely decorative so screen readers ' +\n 'skip it — no alt text is needed then.',\n component: Aglyn.FieldComponentType.SWITCH,\n label: 'Decorative image',\n },\n {\n name: 'title',\n description:\n 'Optional tooltip shown when a visitor hovers over the image.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n label: 'Tooltip',\n condition: NOT_DECORATIVE,\n },\n {\n name: 'objectFit',\n description: 'How the image fills its box.',\n component: Aglyn.FieldComponentType.SELECT,\n label: 'Fit',\n // Both selects on this element take real sentinels (AGL-1451):\n // `cover` and `lazy` are members of their own declared prop unions\n // and the values the render already falls back to. As `''` neither\n // could persist (AGL-1191) — an image switched to Contain or Eager\n // could not be switched back.\n options: [\n { value: 'cover', label: 'Cover (default)' },\n { value: 'contain', label: 'Contain' },\n { value: 'fill', label: 'Fill' },\n { value: 'none', label: 'None' },\n { value: 'scale-down', label: 'Scale down' },\n ],\n },\n {\n name: 'width',\n description:\n 'Width of the image — a number plus a unit, e.g. 100% or 320px.',\n component: Aglyn.FieldComponentType.CSS_DIMENSION,\n label: 'Width',\n },\n {\n name: 'height',\n description: 'Height of the image. Leave empty for auto.',\n component: Aglyn.FieldComponentType.CSS_DIMENSION,\n label: 'Height',\n },\n {\n name: 'loading',\n description:\n 'Lazy waits to load the image until a visitor scrolls near it; ' +\n 'pick Eager for the first image at the top of a page so it ' +\n 'shows immediately.',\n component: Aglyn.FieldComponentType.SELECT,\n label: 'Loading',\n options: [\n { value: 'lazy', label: 'Lazy (default)' },\n { value: 'eager', label: 'Eager' },\n ],\n },\n {\n name: 'screenId',\n description:\n 'Optional: navigate to this page when the image is clicked — ' +\n 'follows the published path like a Page Link.',\n component: Aglyn.FieldComponentType.SCREEN_SELECT,\n label: 'Link to page',\n },\n {\n name: 'href',\n description:\n 'Makes the image a link to somewhere off this site. Ignored while ' +\n 'Link to page names one. Leave both blank and the image is not ' +\n 'clickable at all.',\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 ],\n}\n\nexport const presets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Image',\n pluginId: BUNDLE_ID,\n description: 'Image from your media library or any URL',\n category: Aglyn.ComponentCategory.MEDIA,\n icon: {\n path: mdiImage.path,\n sx: { color: '#7b1fa2' },\n },\n data: {\n $id: null,\n componentId: ID,\n pluginId: BUNDLE_ID,\n props: {},\n },\n },\n]\n\nexport default Image\n"],"names":["Aglyn","mdiImage","AppLink","Box","forwardRef","BUNDLE_ID","generatePresetId","ID","leadImageNodeIds","root","layoutLead","screenLead","walk","node","componentId","props","resolvedProps","String","trim","isLayoutComposedNodeId","$id","child","children","filter","id","Boolean","Image","ref","src","storedSrc","alt","decorative","title","loading","objectFit","width","height","intrinsicWidth","intrinsicHeight","radius","screenId","href","externalHref","_children","sx","nodeSxProp","rest","nodeSx","Array","isArray","linkHref","suppressNavigation","useLinkTarget","nodeId","useNodeId","isLeadImage","canvas","rootNode","some","leadId","leafIdsMatch","eager","hostId","useSite","resolveMediaSrc","wrapLink","element","componentVariant","style","display","alignItems","justifyContent","border","borderColor","borderRadius","undefined","color","fontSize","fontFamily","isCdnUrl","isMediaCdnUrl","pinnedWidth","test","usable","value","Number","isFinite","intrinsicAttributes","component","srcSet","mediaCdnSrcSet","sizes","DEFERRED_IMAGE_ATTRIBUTES","displayName","NOT_DECORATIVE","when","is","notMatch","schema","pluginId","description","category","ComponentCategory","MEDIA","icon","path","flags","selfClosing","FEATURE_FLAG","ENABLED","attributes","name","FieldComponentType","TEXT_FIELD","label","condition","SWITCH","SELECT","options","CSS_DIMENSION","SCREEN_SELECT","resolveProps","bareFragmentLinkFieldProps","presets","type","NodeType","PRESET","data"],"mappings":"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"}
@@ -66,7 +66,7 @@ export const ID = 'languageSwitcher';
66
66
  },
67
67
  ...nodeSx
68
68
  ],
69
- children: 'Language switcher — set screen translations'
69
+ children: 'Language switcher — set page translations'
70
70
  }));
71
71
  }
72
72
  return /*#__PURE__*/ _jsxs(Stack, _extends({
@@ -113,7 +113,7 @@ export const schema = {
113
113
  $id: ID,
114
114
  pluginId: BUNDLE_ID,
115
115
  displayName: 'Language Switcher',
116
- description: "Links between this screen's translations. It hides itself when there are none.",
116
+ description: "Links between this page's translations. It hides itself when there are none.",
117
117
  category: Aglyn.ComponentCategory.NAVIGATION,
118
118
  icon: {
119
119
  path: mdiTranslate.path,
@@ -158,7 +158,7 @@ export const presets = [
158
158
  type: Aglyn.NodeType.PRESET,
159
159
  displayName: 'Language Switcher',
160
160
  pluginId: BUNDLE_ID,
161
- description: 'Links between this screen’s translations',
161
+ description: 'Links between this page’s translations',
162
162
  category: Aglyn.ComponentCategory.NAVIGATION,
163
163
  icon: {
164
164
  path: mdiTranslate.path,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/mui/src/lib/components/language-switcher.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 { mdiTranslate } from '@aglyn/shared-data-mdi'\nimport { AppLink } from '@aglyn/shared-ui-jsx'\nimport Button from '@mui/material/Button'\nimport Stack from '@mui/material/Stack'\nimport { forwardRef, useContext } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { generatePresetId } from '../utils/generate-preset-id'\n\n// Component ids are persisted in screen documents; never rename.\nexport const ID: Aglyn.ComponentId = 'languageSwitcher'\n\nexport interface LanguageSwitcherProps {\n /** 'text' shows locale codes; 'buttons' renders outlined buttons. */\n variant?: 'text' | 'buttons'\n /** Uppercase the locale codes (EN / ES). */\n uppercase?: boolean\n}\n\n/**\n * Language switcher (AGL-164): renders one link per translation of the\n * CURRENT screen, resolved from ScreenLinkContext (`localeVariants` +\n * routing map), so it works inside shared layouts. Shows a placeholder in\n * the editor when the screen has no variants; renders nothing on the live\n * site in that case.\n */\nconst LanguageSwitcher = forwardRef<HTMLElement, LanguageSwitcherProps>(\n (props, ref) => {\n const { variant, uppercase, ...rest } = props\n // Node styles ride the renderer-merged sx; recompose (stack.ts pattern).\n const nodeSx = Array.isArray(props['sx']) ? props['sx'] : [props['sx']]\n const { screens, localeVariants, currentLocale, suppressNavigation } =\n useContext(Aglyn.ScreenLinkContext)\n const entries = Object.entries(localeVariants ?? {}).filter(\n ([, screenId]) => screens?.[screenId] != null,\n )\n const format = (locale: string) =>\n uppercase === false ? locale : locale.toUpperCase()\n\n if (!entries.length) {\n // Editor-only placeholder; invisible on the live site.\n if (!suppressNavigation) return null\n return (\n <Stack\n ref={ref as any}\n direction=\"row\"\n spacing={1}\n {...rest}\n sx={[\n {\n alignItems: 'center',\n border: '1px dashed',\n borderColor: 'divider',\n borderRadius: 1,\n px: 1.5,\n py: 0.5,\n color: 'text.secondary',\n fontSize: 12,\n fontFamily: 'system-ui, sans-serif',\n },\n ...nodeSx,\n ]}\n >\n {'Language switcher — set screen translations'}\n </Stack>\n )\n }\n\n return (\n <Stack\n ref={ref as any}\n direction=\"row\"\n spacing={0.5}\n {...rest}\n sx={[{ alignItems: 'center' }, ...nodeSx]}\n >\n {currentLocale ? (\n <Button size=\"small\" variant=\"text\" disabled>\n {format(currentLocale)}\n </Button>\n ) : null}\n {entries.map(([locale, screenId]) => {\n const path = screens?.[screenId]\n const href =\n path != null ? Aglyn.screenRoutePathToUrl(path) : undefined\n if (!href || suppressNavigation) {\n return (\n <Button key={locale} size=\"small\" variant=\"text\">\n {format(locale)}\n </Button>\n )\n }\n return (\n <AppLink\n key={locale}\n componentVariant=\"button\"\n href={href}\n size=\"small\"\n variant={variant === 'buttons' ? 'outlined' : 'text'}\n >\n {format(locale)}\n </AppLink>\n )\n })}\n </Stack>\n )\n },\n)\nLanguageSwitcher.displayName = 'LanguageSwitcher'\n\nexport const schema: Aglyn.ComponentSchema<LanguageSwitcherProps> = {\n $id: ID,\n pluginId: BUNDLE_ID,\n displayName: 'Language Switcher',\n description:\n \"Links between this screen's translations. It hides itself when there are none.\",\n category: Aglyn.ComponentCategory.NAVIGATION,\n icon: {\n path: mdiTranslate.path,\n sx: { color: '#2196f3' },\n },\n flags: {\n selfClosing: Aglyn.FEATURE_FLAG.ENABLED,\n },\n attributes: [\n {\n name: 'variant',\n description: 'How the language links render.',\n component: Aglyn.FieldComponentType.SELECT,\n label: 'Style',\n // `text` is half of the declared `'text' | 'buttons'` union and the\n // value the render already falls back to; as `''` the option was\n // unpersistable (AGL-1191) and outside its own prop type (AGL-1451).\n options: [\n { value: 'text', label: 'Text (default)' },\n { value: 'buttons', label: 'Outlined buttons' },\n ],\n },\n {\n name: 'uppercase',\n description: 'Show locale codes uppercased (EN, ES).',\n component: Aglyn.FieldComponentType.CHECKBOX,\n label: 'Uppercase',\n },\n ],\n}\n\nexport const presets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Language Switcher',\n pluginId: BUNDLE_ID,\n description: 'Links between this screen’s translations',\n category: Aglyn.ComponentCategory.NAVIGATION,\n icon: {\n path: mdiTranslate.path,\n sx: { color: '#2196f3' },\n },\n data: {\n $id: null,\n componentId: ID,\n pluginId: BUNDLE_ID,\n props: {},\n },\n },\n]\n\nexport default LanguageSwitcher\n"],"names":["Aglyn","mdiTranslate","AppLink","Button","Stack","forwardRef","useContext","BUNDLE_ID","generatePresetId","ID","LanguageSwitcher","props","ref","variant","uppercase","rest","nodeSx","Array","isArray","screens","localeVariants","currentLocale","suppressNavigation","ScreenLinkContext","entries","Object","filter","screenId","format","locale","toUpperCase","length","direction","spacing","sx","alignItems","border","borderColor","borderRadius","px","py","color","fontSize","fontFamily","size","disabled","map","path","href","screenRoutePathToUrl","undefined","componentVariant","displayName","schema","$id","pluginId","description","category","ComponentCategory","NAVIGATION","icon","flags","selfClosing","FEATURE_FLAG","ENABLED","attributes","name","component","FieldComponentType","SELECT","label","options","value","CHECKBOX","presets","type","NodeType","PRESET","data","componentId"],"mappings":";;;AAAA;;;;;;;;;;;;;;;CAeC,GAED,YAAYA,WAAW,eAAc;AACrC,SAASC,YAAY,QAAQ,yBAAwB;AACrD,SAASC,OAAO,QAAQ,uBAAsB;AAC9C,OAAOC,YAAY,uBAAsB;AACzC,OAAOC,WAAW,sBAAqB;AACvC,SAASC,UAAU,EAAEC,UAAU,QAAQ,QAAO;AAC9C,SAASC,SAAS,QAAQ,gCAA4B;AACtD,SAASC,gBAAgB,QAAQ,iCAA6B;AAE9D,iEAAiE;AACjE,OAAO,MAAMC,KAAwB,mBAAkB;AASvD;;;;;;CAMC,GACD,MAAMC,iCAAmBL,WACvB,CAACM,OAAOC;IACN,MAAM,EAAEC,OAAO,EAAEC,SAAS,EAAW,GAAGH,OAATI,wCAASJ;;;;IACxC,yEAAyE;IACzE,MAAMK,SAASC,MAAMC,OAAO,CAACP,KAAK,CAAC,KAAK,IAAIA,KAAK,CAAC,KAAK,GAAG;QAACA,KAAK,CAAC,KAAK;KAAC;IACvE,MAAM,EAAEQ,OAAO,EAAEC,cAAc,EAAEC,aAAa,EAAEC,kBAAkB,EAAE,GAClEhB,WAAWN,MAAMuB,iBAAiB;IACpC,MAAMC,UAAUC,OAAOD,OAAO,CAACJ,yBAAAA,iBAAkB,CAAC,GAAGM,MAAM,CACzD,CAAC,GAAGC,SAAS,GAAKR,CAAAA,2BAAAA,OAAS,CAACQ,SAAS,KAAI;IAE3C,MAAMC,SAAS,CAACC,SACdf,cAAc,QAAQe,SAASA,OAAOC,WAAW;IAEnD,IAAI,CAACN,QAAQO,MAAM,EAAE;QACnB,uDAAuD;QACvD,IAAI,CAACT,oBAAoB,OAAO;QAChC,qBACE,KAAClB;YACCQ,KAAKA;YACLoB,WAAU;YACVC,SAAS;WACLlB;YACJmB,IAAI;gBACF;oBACEC,YAAY;oBACZC,QAAQ;oBACRC,aAAa;oBACbC,cAAc;oBACdC,IAAI;oBACJC,IAAI;oBACJC,OAAO;oBACPC,UAAU;oBACVC,YAAY;gBACd;mBACG3B;aACJ;sBAEA;;IAGP;IAEA,qBACE,MAACZ;QACCQ,KAAKA;QACLoB,WAAU;QACVC,SAAS;OACLlB;QACJmB,IAAI;YAAC;gBAAEC,YAAY;YAAS;eAAMnB;SAAO;;YAExCK,8BACC,KAAClB;gBAAOyC,MAAK;gBAAQ/B,SAAQ;gBAAOgC,QAAQ;0BACzCjB,OAAOP;iBAER;YACHG,QAAQsB,GAAG,CAAC,CAAC,CAACjB,QAAQF,SAAS;gBAC9B,MAAMoB,OAAO5B,2BAAAA,OAAS,CAACQ,SAAS;gBAChC,MAAMqB,OACJD,QAAQ,OAAO/C,MAAMiD,oBAAoB,CAACF,QAAQG;gBACpD,IAAI,CAACF,QAAQ1B,oBAAoB;oBAC/B,qBACE,KAACnB;wBAAoByC,MAAK;wBAAQ/B,SAAQ;kCACvCe,OAAOC;uBADGA;gBAIjB;gBACA,qBACE,KAAC3B;oBAECiD,kBAAiB;oBACjBH,MAAMA;oBACNJ,MAAK;oBACL/B,SAASA,YAAY,YAAY,aAAa;8BAE7Ce,OAAOC;mBANHA;YASX;;;AAGN;AAEFnB,iBAAiB0C,WAAW,GAAG;AAE/B,OAAO,MAAMC,SAAuD;IAClEC,KAAK7C;IACL8C,UAAUhD;IACV6C,aAAa;IACbI,aACE;IACFC,UAAUzD,MAAM0D,iBAAiB,CAACC,UAAU;IAC5CC,MAAM;QACJb,MAAM9C,aAAa8C,IAAI;QACvBb,IAAI;YAAEO,OAAO;QAAU;IACzB;IACAoB,OAAO;QACLC,aAAa9D,MAAM+D,YAAY,CAACC,OAAO;IACzC;IACAC,YAAY;QACV;YACEC,MAAM;YACNV,aAAa;YACbW,WAAWnE,MAAMoE,kBAAkB,CAACC,MAAM;YAC1CC,OAAO;YACP,oEAAoE;YACpE,iEAAiE;YACjE,qEAAqE;YACrEC,SAAS;gBACP;oBAAEC,OAAO;oBAAQF,OAAO;gBAAiB;gBACzC;oBAAEE,OAAO;oBAAWF,OAAO;gBAAmB;aAC/C;QACH;QACA;YACEJ,MAAM;YACNV,aAAa;YACbW,WAAWnE,MAAMoE,kBAAkB,CAACK,QAAQ;YAC5CH,OAAO;QACT;KACD;AACH,EAAC;AAED,OAAO,MAAMI,UAAgC;IAC3C;QACEpB,KAAK9C,iBAAiBC;QACtBkE,MAAM3E,MAAM4E,QAAQ,CAACC,MAAM;QAC3BzB,aAAa;QACbG,UAAUhD;QACViD,aAAa;QACbC,UAAUzD,MAAM0D,iBAAiB,CAACC,UAAU;QAC5CC,MAAM;YACJb,MAAM9C,aAAa8C,IAAI;YACvBb,IAAI;gBAAEO,OAAO;YAAU;QACzB;QACAqC,MAAM;YACJxB,KAAK;YACLyB,aAAatE;YACb8C,UAAUhD;YACVI,OAAO,CAAC;QACV;IACF;CACD,CAAA;AAED,eAAeD,iBAAgB"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/mui/src/lib/components/language-switcher.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 { mdiTranslate } from '@aglyn/shared-data-mdi'\nimport { AppLink } from '@aglyn/shared-ui-jsx'\nimport Button from '@mui/material/Button'\nimport Stack from '@mui/material/Stack'\nimport { forwardRef, useContext } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { generatePresetId } from '../utils/generate-preset-id'\n\n// Component ids are persisted in screen documents; never rename.\nexport const ID: Aglyn.ComponentId = 'languageSwitcher'\n\nexport interface LanguageSwitcherProps {\n /** 'text' shows locale codes; 'buttons' renders outlined buttons. */\n variant?: 'text' | 'buttons'\n /** Uppercase the locale codes (EN / ES). */\n uppercase?: boolean\n}\n\n/**\n * Language switcher (AGL-164): renders one link per translation of the\n * CURRENT screen, resolved from ScreenLinkContext (`localeVariants` +\n * routing map), so it works inside shared layouts. Shows a placeholder in\n * the editor when the screen has no variants; renders nothing on the live\n * site in that case.\n */\nconst LanguageSwitcher = forwardRef<HTMLElement, LanguageSwitcherProps>(\n (props, ref) => {\n const { variant, uppercase, ...rest } = props\n // Node styles ride the renderer-merged sx; recompose (stack.ts pattern).\n const nodeSx = Array.isArray(props['sx']) ? props['sx'] : [props['sx']]\n const { screens, localeVariants, currentLocale, suppressNavigation } =\n useContext(Aglyn.ScreenLinkContext)\n const entries = Object.entries(localeVariants ?? {}).filter(\n ([, screenId]) => screens?.[screenId] != null,\n )\n const format = (locale: string) =>\n uppercase === false ? locale : locale.toUpperCase()\n\n if (!entries.length) {\n // Editor-only placeholder; invisible on the live site.\n if (!suppressNavigation) return null\n return (\n <Stack\n ref={ref as any}\n direction=\"row\"\n spacing={1}\n {...rest}\n sx={[\n {\n alignItems: 'center',\n border: '1px dashed',\n borderColor: 'divider',\n borderRadius: 1,\n px: 1.5,\n py: 0.5,\n color: 'text.secondary',\n fontSize: 12,\n fontFamily: 'system-ui, sans-serif',\n },\n ...nodeSx,\n ]}\n >\n {'Language switcher — set page translations'}\n </Stack>\n )\n }\n\n return (\n <Stack\n ref={ref as any}\n direction=\"row\"\n spacing={0.5}\n {...rest}\n sx={[{ alignItems: 'center' }, ...nodeSx]}\n >\n {currentLocale ? (\n <Button size=\"small\" variant=\"text\" disabled>\n {format(currentLocale)}\n </Button>\n ) : null}\n {entries.map(([locale, screenId]) => {\n const path = screens?.[screenId]\n const href =\n path != null ? Aglyn.screenRoutePathToUrl(path) : undefined\n if (!href || suppressNavigation) {\n return (\n <Button key={locale} size=\"small\" variant=\"text\">\n {format(locale)}\n </Button>\n )\n }\n return (\n <AppLink\n key={locale}\n componentVariant=\"button\"\n href={href}\n size=\"small\"\n variant={variant === 'buttons' ? 'outlined' : 'text'}\n >\n {format(locale)}\n </AppLink>\n )\n })}\n </Stack>\n )\n },\n)\nLanguageSwitcher.displayName = 'LanguageSwitcher'\n\nexport const schema: Aglyn.ComponentSchema<LanguageSwitcherProps> = {\n $id: ID,\n pluginId: BUNDLE_ID,\n displayName: 'Language Switcher',\n description:\n \"Links between this page's translations. It hides itself when there are none.\",\n category: Aglyn.ComponentCategory.NAVIGATION,\n icon: {\n path: mdiTranslate.path,\n sx: { color: '#2196f3' },\n },\n flags: {\n selfClosing: Aglyn.FEATURE_FLAG.ENABLED,\n },\n attributes: [\n {\n name: 'variant',\n description: 'How the language links render.',\n component: Aglyn.FieldComponentType.SELECT,\n label: 'Style',\n // `text` is half of the declared `'text' | 'buttons'` union and the\n // value the render already falls back to; as `''` the option was\n // unpersistable (AGL-1191) and outside its own prop type (AGL-1451).\n options: [\n { value: 'text', label: 'Text (default)' },\n { value: 'buttons', label: 'Outlined buttons' },\n ],\n },\n {\n name: 'uppercase',\n description: 'Show locale codes uppercased (EN, ES).',\n component: Aglyn.FieldComponentType.CHECKBOX,\n label: 'Uppercase',\n },\n ],\n}\n\nexport const presets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Language Switcher',\n pluginId: BUNDLE_ID,\n description: 'Links between this page’s translations',\n category: Aglyn.ComponentCategory.NAVIGATION,\n icon: {\n path: mdiTranslate.path,\n sx: { color: '#2196f3' },\n },\n data: {\n $id: null,\n componentId: ID,\n pluginId: BUNDLE_ID,\n props: {},\n },\n },\n]\n\nexport default LanguageSwitcher\n"],"names":["Aglyn","mdiTranslate","AppLink","Button","Stack","forwardRef","useContext","BUNDLE_ID","generatePresetId","ID","LanguageSwitcher","props","ref","variant","uppercase","rest","nodeSx","Array","isArray","screens","localeVariants","currentLocale","suppressNavigation","ScreenLinkContext","entries","Object","filter","screenId","format","locale","toUpperCase","length","direction","spacing","sx","alignItems","border","borderColor","borderRadius","px","py","color","fontSize","fontFamily","size","disabled","map","path","href","screenRoutePathToUrl","undefined","componentVariant","displayName","schema","$id","pluginId","description","category","ComponentCategory","NAVIGATION","icon","flags","selfClosing","FEATURE_FLAG","ENABLED","attributes","name","component","FieldComponentType","SELECT","label","options","value","CHECKBOX","presets","type","NodeType","PRESET","data","componentId"],"mappings":"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"}
@@ -51,7 +51,7 @@ export interface LayoutSlotProps extends Omit<BoxProps, 'component'> {
51
51
  caption?: string;
52
52
  }
53
53
  /** Shown when a layout hasn't named its slot. */
54
- export declare const DEFAULT_SLOT_CAPTION = "Screen content renders here";
54
+ export declare const DEFAULT_SLOT_CAPTION = "Page content renders here";
55
55
  /**
56
56
  * Content outlet for shared layouts: marks where a bound screen's nodes are
57
57
  * grafted into the layout tree at composition time. With children (composed
@@ -47,7 +47,7 @@ export const ID = 'layoutSlot';
47
47
  * whenever a subscriber restyles their site — and it already rendered pink
48
48
  * instead of the design's blue.
49
49
  */ const SLOT_ACCENT = '#00B0FF';
50
- /** Shown when a layout hasn't named its slot. */ export const DEFAULT_SLOT_CAPTION = 'Screen content renders here';
50
+ /** Shown when a layout hasn't named its slot. */ export const DEFAULT_SLOT_CAPTION = 'Page content renders here';
51
51
  /**
52
52
  * Content outlet for shared layouts: marks where a bound screen's nodes are
53
53
  * grafted into the layout tree at composition time. With children (composed
@@ -128,7 +128,7 @@ export const schema = {
128
128
  $id: ID,
129
129
  pluginId: BUNDLE_ID,
130
130
  displayName: 'Layout Slot',
131
- description: "Marks where each bound screen's content renders inside this layout. One per layout.",
131
+ description: "Marks where each bound page's content renders inside this layout. One per layout.",
132
132
  category: Aglyn.ComponentCategory.LAYOUT,
133
133
  icon: {
134
134
  path: mdiPageLayoutBody.path,
@@ -158,7 +158,7 @@ export const schema = {
158
158
  {
159
159
  name: 'component',
160
160
  label: 'Component',
161
- description: 'The DOM element the screen content renders inside. Blank is ' + '"main" — the page-content landmark assistive tech skips the ' + 'chrome with. Choose another only if this layout’s slot is not the ' + 'page’s main content.',
161
+ description: 'The DOM element the page content renders inside. Blank is ' + '"main" — the page-content landmark assistive tech skips the ' + 'chrome with. Choose another only if this layout’s slot is not the ' + 'page’s main content.',
162
162
  component: Aglyn.FieldComponentType.SELECT,
163
163
  options: LAYOUT_SLOT_ELEMENTS.map((value)=>({
164
164
  value,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/mui/src/lib/components/layout-slot.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 { mdiPageLayoutBody } from '@aglyn/shared-data-mdi'\nimport Box, { type BoxProps } from '@mui/material/Box'\nimport { alpha } from '@mui/material/styles'\nimport { Children, forwardRef, useContext } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { generatePresetId } from '../utils/generate-preset-id'\n\n// Component ids are persisted in layout documents; never rename.\nexport const ID: Aglyn.ComponentId = 'layoutSlot'\n\n/**\n * Elements the slot may render as (AGL-2486).\n *\n * `main` is here, and is the DEFAULT composition assigns, because the slot is\n * by construction the page content between the layout's chrome — which is the\n * definition of the landmark. It is the one place on the platform that may\n * choose it: `SECTION_ELEMENTS` and `ALLOWED_AUTHOR_HTML_ELEMENTS` both drop\n * `main` so a document can never carry two, and `stampDocumentLandmark`\n * decides which single node gets it.\n */\nexport const LAYOUT_SLOT_ELEMENTS = [\n 'main',\n 'div',\n 'section',\n 'article',\n] as const\nexport type LayoutSlotElement = (typeof LAYOUT_SLOT_ELEMENTS)[number]\n\nexport interface LayoutSlotProps extends Omit<BoxProps, 'component'> {\n /**\n * The DOM element the slot renders as. Left unset, composition fills in\n * `main` — see {@link LAYOUT_SLOT_ELEMENTS}. Unknown values degrade to\n * `div` rather than reaching the DOM as an invented tag.\n *\n * Named `component` like Box's and Typography's, not `element` like\n * Section's: the attribute means the same thing everywhere it appears.\n * Narrower than the `ElementType` Box accepts — a canvas attribute is a\n * persisted STRING, and an allow-list is what keeps `script` out of it —\n * so `BoxProps`' own `component` is omitted rather than widened.\n */\n component?: LayoutSlotElement | string\n /**\n * Editor-only caption under the slot marker. Layouts render different\n * kinds of content — a doc body, a narrow article measure, a full page —\n * and naming it is the difference between four identical dashed boxes and\n * a layout you can read at a glance.\n */\n caption?: string\n}\n\n/**\n * The slot marker is EDITOR CHROME, so it holds the brand accent literally\n * rather than reading `secondary` from the theme: the canvas renders under\n * the host's palette, so a token would repaint the editor's own furniture\n * whenever a subscriber restyles their site — and it already rendered pink\n * instead of the design's blue.\n */\nconst SLOT_ACCENT = '#00B0FF'\n/** Shown when a layout hasn't named its slot. */\nexport const DEFAULT_SLOT_CAPTION = 'Screen content renders here'\n\n/**\n * Content outlet for shared layouts: marks where a bound screen's nodes are\n * grafted into the layout tree at composition time. With children (composed\n * or previewed) it is a plain passthrough region; empty, it renders a\n * dashed placeholder so layout designers can see the slot on the canvas.\n *\n * The placeholder is editor chrome, so it is drawn on editing surfaces only.\n * A published screen with no content of its own still composes an empty\n * slot, and that renders as the plain region.\n */\nconst LayoutSlot = forwardRef<HTMLDivElement, LayoutSlotProps>(\n (props, ref) => {\n const { children, sx, caption, component, ...rest } = props\n const { suppressNavigation } = useContext(Aglyn.ScreenLinkContext)\n const hasChildren = Children.count(children) > 0\n const element = (LAYOUT_SLOT_ELEMENTS as readonly string[]).includes(\n String(component ?? ''),\n )\n ? (component as LayoutSlotElement)\n : 'div'\n\n if (hasChildren || !suppressNavigation) {\n return (\n <Box\n ref={ref}\n component={element}\n data-aglyn-layout-slot=\"\"\n sx={sx}\n {...rest}\n >\n {children}\n </Box>\n )\n }\n\n return (\n <Box\n ref={ref}\n data-aglyn-layout-slot=\"\"\n sx={[\n {\n minHeight: 160,\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n gap: 0.5,\n borderWidth: 2,\n borderStyle: 'dashed',\n // The slot is not an ordinary dashed container — it is the one\n // region a layout does NOT own. Brand blue on a faint tint says\n // that at a glance, and matches the design's slot convention.\n borderColor: SLOT_ACCENT,\n backgroundColor: alpha(SLOT_ACCENT, 0.06),\n borderRadius: 1,\n color: 'text.secondary',\n fontFamily: 'sans-serif',\n fontSize: 13,\n textAlign: 'center',\n p: 2,\n },\n ...(Array.isArray(sx) ? sx : [sx]),\n ]}\n {...rest}\n >\n <Box\n component=\"span\"\n sx={{ color: SLOT_ACCENT, fontWeight: 700 }}\n >\n {'◇ layout-slot'}\n </Box>\n <Box component=\"span\">{caption || DEFAULT_SLOT_CAPTION}</Box>\n </Box>\n )\n },\n)\nLayoutSlot.displayName = 'LayoutSlot'\n\nexport const schema: Aglyn.ComponentSchema<LayoutSlotProps> = {\n $id: ID,\n pluginId: BUNDLE_ID,\n displayName: 'Layout Slot',\n description:\n \"Marks where each bound screen's content renders inside this layout. One per layout.\",\n category: Aglyn.ComponentCategory.LAYOUT,\n icon: {\n path: mdiPageLayoutBody.path,\n sx: { color: '#9c27b0' },\n },\n // The slot's children come from screen composition, never from the canvas.\n restrictChildren: [Aglyn.LinealDirectiveFlag.LIMIT_TO, { components: [] }],\n flags: {\n cloning: Aglyn.FEATURE_FLAG.DISABLED,\n dropping: Aglyn.FEATURE_FLAG.DISABLED,\n },\n attributes: [\n {\n name: 'caption',\n label: 'Slot caption',\n description:\n 'Editor-only label for what renders here — e.g. \"Doc body renders ' +\n 'here\". Never shown on the published site.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n placeholder: DEFAULT_SLOT_CAPTION,\n },\n {\n name: 'component',\n label: 'Component',\n description:\n 'The DOM element the screen content renders inside. Blank is ' +\n '\"main\" — the page-content landmark assistive tech skips the ' +\n 'chrome with. Choose another only if this layout’s slot is not the ' +\n 'page’s main content.',\n component: Aglyn.FieldComponentType.SELECT,\n options: LAYOUT_SLOT_ELEMENTS.map((value) => ({\n value,\n label: value,\n })),\n },\n ],\n}\n\nexport const presets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Layout Slot',\n icon: {\n path: mdiPageLayoutBody.path,\n sx: { color: '#9c27b0' },\n },\n category: Aglyn.ComponentCategory.LAYOUT,\n data: {\n $id: null,\n componentId: ID,\n pluginId: BUNDLE_ID,\n props: {},\n },\n },\n]\n\nexport default LayoutSlot\n"],"names":["Aglyn","mdiPageLayoutBody","Box","alpha","Children","forwardRef","useContext","BUNDLE_ID","generatePresetId","ID","LAYOUT_SLOT_ELEMENTS","SLOT_ACCENT","DEFAULT_SLOT_CAPTION","LayoutSlot","props","ref","children","sx","caption","component","rest","suppressNavigation","ScreenLinkContext","hasChildren","count","element","includes","String","data-aglyn-layout-slot","minHeight","display","flexDirection","alignItems","justifyContent","gap","borderWidth","borderStyle","borderColor","backgroundColor","borderRadius","color","fontFamily","fontSize","textAlign","p","Array","isArray","fontWeight","displayName","schema","$id","pluginId","description","category","ComponentCategory","LAYOUT","icon","path","restrictChildren","LinealDirectiveFlag","LIMIT_TO","components","flags","cloning","FEATURE_FLAG","DISABLED","dropping","attributes","name","label","FieldComponentType","TEXT_FIELD","placeholder","SELECT","options","map","value","presets","type","NodeType","PRESET","data","componentId"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/mui/src/lib/components/layout-slot.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 { mdiPageLayoutBody } from '@aglyn/shared-data-mdi'\nimport Box, { type BoxProps } from '@mui/material/Box'\nimport { alpha } from '@mui/material/styles'\nimport { Children, forwardRef, useContext } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { generatePresetId } from '../utils/generate-preset-id'\n\n// Component ids are persisted in layout documents; never rename.\nexport const ID: Aglyn.ComponentId = 'layoutSlot'\n\n/**\n * Elements the slot may render as (AGL-2486).\n *\n * `main` is here, and is the DEFAULT composition assigns, because the slot is\n * by construction the page content between the layout's chrome — which is the\n * definition of the landmark. It is the one place on the platform that may\n * choose it: `SECTION_ELEMENTS` and `ALLOWED_AUTHOR_HTML_ELEMENTS` both drop\n * `main` so a document can never carry two, and `stampDocumentLandmark`\n * decides which single node gets it.\n */\nexport const LAYOUT_SLOT_ELEMENTS = [\n 'main',\n 'div',\n 'section',\n 'article',\n] as const\nexport type LayoutSlotElement = (typeof LAYOUT_SLOT_ELEMENTS)[number]\n\nexport interface LayoutSlotProps extends Omit<BoxProps, 'component'> {\n /**\n * The DOM element the slot renders as. Left unset, composition fills in\n * `main` — see {@link LAYOUT_SLOT_ELEMENTS}. Unknown values degrade to\n * `div` rather than reaching the DOM as an invented tag.\n *\n * Named `component` like Box's and Typography's, not `element` like\n * Section's: the attribute means the same thing everywhere it appears.\n * Narrower than the `ElementType` Box accepts — a canvas attribute is a\n * persisted STRING, and an allow-list is what keeps `script` out of it —\n * so `BoxProps`' own `component` is omitted rather than widened.\n */\n component?: LayoutSlotElement | string\n /**\n * Editor-only caption under the slot marker. Layouts render different\n * kinds of content — a doc body, a narrow article measure, a full page —\n * and naming it is the difference between four identical dashed boxes and\n * a layout you can read at a glance.\n */\n caption?: string\n}\n\n/**\n * The slot marker is EDITOR CHROME, so it holds the brand accent literally\n * rather than reading `secondary` from the theme: the canvas renders under\n * the host's palette, so a token would repaint the editor's own furniture\n * whenever a subscriber restyles their site — and it already rendered pink\n * instead of the design's blue.\n */\nconst SLOT_ACCENT = '#00B0FF'\n/** Shown when a layout hasn't named its slot. */\nexport const DEFAULT_SLOT_CAPTION = 'Page content renders here'\n\n/**\n * Content outlet for shared layouts: marks where a bound screen's nodes are\n * grafted into the layout tree at composition time. With children (composed\n * or previewed) it is a plain passthrough region; empty, it renders a\n * dashed placeholder so layout designers can see the slot on the canvas.\n *\n * The placeholder is editor chrome, so it is drawn on editing surfaces only.\n * A published screen with no content of its own still composes an empty\n * slot, and that renders as the plain region.\n */\nconst LayoutSlot = forwardRef<HTMLDivElement, LayoutSlotProps>(\n (props, ref) => {\n const { children, sx, caption, component, ...rest } = props\n const { suppressNavigation } = useContext(Aglyn.ScreenLinkContext)\n const hasChildren = Children.count(children) > 0\n const element = (LAYOUT_SLOT_ELEMENTS as readonly string[]).includes(\n String(component ?? ''),\n )\n ? (component as LayoutSlotElement)\n : 'div'\n\n if (hasChildren || !suppressNavigation) {\n return (\n <Box\n ref={ref}\n component={element}\n data-aglyn-layout-slot=\"\"\n sx={sx}\n {...rest}\n >\n {children}\n </Box>\n )\n }\n\n return (\n <Box\n ref={ref}\n data-aglyn-layout-slot=\"\"\n sx={[\n {\n minHeight: 160,\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n gap: 0.5,\n borderWidth: 2,\n borderStyle: 'dashed',\n // The slot is not an ordinary dashed container — it is the one\n // region a layout does NOT own. Brand blue on a faint tint says\n // that at a glance, and matches the design's slot convention.\n borderColor: SLOT_ACCENT,\n backgroundColor: alpha(SLOT_ACCENT, 0.06),\n borderRadius: 1,\n color: 'text.secondary',\n fontFamily: 'sans-serif',\n fontSize: 13,\n textAlign: 'center',\n p: 2,\n },\n ...(Array.isArray(sx) ? sx : [sx]),\n ]}\n {...rest}\n >\n <Box\n component=\"span\"\n sx={{ color: SLOT_ACCENT, fontWeight: 700 }}\n >\n {'◇ layout-slot'}\n </Box>\n <Box component=\"span\">{caption || DEFAULT_SLOT_CAPTION}</Box>\n </Box>\n )\n },\n)\nLayoutSlot.displayName = 'LayoutSlot'\n\nexport const schema: Aglyn.ComponentSchema<LayoutSlotProps> = {\n $id: ID,\n pluginId: BUNDLE_ID,\n displayName: 'Layout Slot',\n description:\n \"Marks where each bound page's content renders inside this layout. One per layout.\",\n category: Aglyn.ComponentCategory.LAYOUT,\n icon: {\n path: mdiPageLayoutBody.path,\n sx: { color: '#9c27b0' },\n },\n // The slot's children come from screen composition, never from the canvas.\n restrictChildren: [Aglyn.LinealDirectiveFlag.LIMIT_TO, { components: [] }],\n flags: {\n cloning: Aglyn.FEATURE_FLAG.DISABLED,\n dropping: Aglyn.FEATURE_FLAG.DISABLED,\n },\n attributes: [\n {\n name: 'caption',\n label: 'Slot caption',\n description:\n 'Editor-only label for what renders here — e.g. \"Doc body renders ' +\n 'here\". Never shown on the published site.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n placeholder: DEFAULT_SLOT_CAPTION,\n },\n {\n name: 'component',\n label: 'Component',\n description:\n 'The DOM element the page content renders inside. Blank is ' +\n '\"main\" — the page-content landmark assistive tech skips the ' +\n 'chrome with. Choose another only if this layout’s slot is not the ' +\n 'page’s main content.',\n component: Aglyn.FieldComponentType.SELECT,\n options: LAYOUT_SLOT_ELEMENTS.map((value) => ({\n value,\n label: value,\n })),\n },\n ],\n}\n\nexport const presets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Layout Slot',\n icon: {\n path: mdiPageLayoutBody.path,\n sx: { color: '#9c27b0' },\n },\n category: Aglyn.ComponentCategory.LAYOUT,\n data: {\n $id: null,\n componentId: ID,\n pluginId: BUNDLE_ID,\n props: {},\n },\n },\n]\n\nexport default LayoutSlot\n"],"names":["Aglyn","mdiPageLayoutBody","Box","alpha","Children","forwardRef","useContext","BUNDLE_ID","generatePresetId","ID","LAYOUT_SLOT_ELEMENTS","SLOT_ACCENT","DEFAULT_SLOT_CAPTION","LayoutSlot","props","ref","children","sx","caption","component","rest","suppressNavigation","ScreenLinkContext","hasChildren","count","element","includes","String","data-aglyn-layout-slot","minHeight","display","flexDirection","alignItems","justifyContent","gap","borderWidth","borderStyle","borderColor","backgroundColor","borderRadius","color","fontFamily","fontSize","textAlign","p","Array","isArray","fontWeight","displayName","schema","$id","pluginId","description","category","ComponentCategory","LAYOUT","icon","path","restrictChildren","LinealDirectiveFlag","LIMIT_TO","components","flags","cloning","FEATURE_FLAG","DISABLED","dropping","attributes","name","label","FieldComponentType","TEXT_FIELD","placeholder","SELECT","options","map","value","presets","type","NodeType","PRESET","data","componentId"],"mappings":"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"}
@@ -139,13 +139,13 @@ export const schema = {
139
139
  attributes: [
140
140
  {
141
141
  name: 'screenId',
142
- description: 'Screen this box navigates to. The address is generated from the ' + 'published path at render time, so it keeps working when the ' + "screen's slug or parent changes.",
142
+ description: 'Page this box 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.",
143
143
  component: Aglyn.FieldComponentType.SCREEN_SELECT,
144
- label: 'Screen'
144
+ label: 'Page'
145
145
  },
146
146
  {
147
147
  name: 'href',
148
- description: 'Where the whole container goes when it points off this site. ' + 'Ignored while Screen names one. Everything inside the box becomes ' + 'part of this one link, so it should hold no other link.',
148
+ description: 'Where the whole container goes when it points off this site. ' + 'Ignored while Page names one. Everything inside the box becomes ' + 'part of this one link, so it should hold no other link.',
149
149
  component: Aglyn.FieldComponentType.TEXT_FIELD,
150
150
  label: 'External URL',
151
151
  // A bare `#fragment` saves and goes nowhere (AGL-2867).
@@ -153,7 +153,7 @@ export const schema = {
153
153
  },
154
154
  {
155
155
  name: 'newTab',
156
- description: 'Open in a new tab. Applies to external URLs only — a link to one ' + 'of your own screens stays in the tab so the visitor keeps their ' + 'history.',
156
+ description: 'Open in a new tab. Applies to external URLs only — a link to one ' + 'of your own pages stays in the tab so the visitor keeps their ' + 'history.',
157
157
  component: Aglyn.FieldComponentType.SWITCH,
158
158
  label: 'Open in a new tab'
159
159
  },
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/mui/src/lib/components/link-box.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport { mdiLinkBoxOutline } from '@aglyn/shared-data-mdi'\nimport { AppLink } from '@aglyn/shared-ui-jsx'\nimport MuiBox from '@mui/material/Box'\nimport type { SxProps } from '@mui/material/styles'\nimport { forwardRef, type ReactNode } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { generatePresetId } from '../utils/generate-preset-id'\nimport {\n linkContainerA11yProps,\n linkContainerLabelFieldProps,\n REDUNDANT_ATTRIBUTE,\n} from './link-box-accessible-name'\n\n// Component ids are persisted in screen documents; never rename.\nexport const ID: Aglyn.ComponentId = 'muiLinkBox'\n\nexport interface LinkBoxProps {\n /** Target screen id, resolved from the host routing map at render time. */\n screenId?: string\n /** External URL escape hatch, used only when no `screenId` is set. */\n href?: string\n /** Opens in a new tab. External destinations only. */\n newTab?: boolean\n /** The link's name for assistive tech, when its content supplies none. */\n ariaLabel?: string\n /**\n * The box repeats a link beside it to the same destination, so it is\n * hidden from assistive tech and left out of the tab order.\n */\n redundant?: boolean\n /**\n * Authored node styles, handed over by the renderer rather than typed into\n * an attribute. Declared because the merge below reads it: undeclared, no\n * typed caller could style a tile (AGL-1323).\n */\n sx?: SxProps\n children?: ReactNode\n}\n\n/**\n * A container that IS the link — icon, heading and description inside one\n * anchor, instead of a tile whose only clickable part is its title.\n *\n * Every other linking element in the plugin is a leaf: Screen Link and\n * Button both render their `children` as inline text, so `nodeAcceptsChildren`\n * reports false and the canvas will not let anything be dropped inside them.\n * That left no way to author a card, a mega-menu tile, or a linked logo —\n * the shapes where the whole box is meant to be one target (AGL-1231).\n *\n * Nothing interactive may be nested inside: an `<a>` inside an `<a>` is\n * invalid HTML and browsers unnest it, which silently moves half the tile\n * back out of the link. Convert an inner Screen Link to plain Text when you\n * wrap it in one of these.\n *\n * The link is named by the text inside it, so a box holding only an icon\n * needs `ariaLabel` — or `redundant`, when a link beside it already goes to\n * the same place (AGL-2886). Both land on the anchor in every branch below,\n * so the canvas carries the same attributes the published page does.\n */\nconst LinkBox = forwardRef<HTMLElement, LinkBoxProps>((props, ref) => {\n const {\n screenId,\n href: externalHref,\n newTab,\n ariaLabel,\n redundant,\n children,\n sx,\n ...rest\n } = props\n const a11y = linkContainerA11yProps(ariaLabel, redundant)\n // Node styles ride the renderer-merged sx; recompose (stack.ts pattern).\n // Spreading `rest` with `sx` still inside it would REPLACE the baseline\n // below rather than merge with it, so every styled tile would quietly lose\n // `textDecoration: none` and get the browser's underlined-blue anchor.\n const nodeSx = Array.isArray(sx) ? sx : sx ? [sx] : []\n // Shared resolution (AGL-1335): screen id first, `javascript:`/`data:`\n // hrefs refused, `screen:`-prefixed prop values understood in either slot.\n // \"External\" means it leaves the site, not merely \"typed by hand\": a\n // site-relative `/pricing` in the href field must stay in the tab, or the\n // visitor loses their history for nothing.\n const {\n href,\n suppressNavigation,\n leavesSite: external,\n } = Aglyn.useLinkTarget(screenId, externalHref)\n\n // The unresolved and suppressed cases keep the same ELEMENT, not just the\n // same box (AGL-1268): this used to render a `<div>` here and an `<a>` when\n // the screen resolved, so the node's element type depended on the screens\n // map — a screen published or unpublished between renders changed the DOM\n // shape. An `<a>` without `href` is HTML's placeholder link: identical\n // element, non-interactive, and the canvas stays inert exactly as before.\n // The style floor matches the active branch so the two render identically.\n if (!href || suppressNavigation) {\n return (\n <MuiBox\n ref={ref}\n component=\"a\"\n {...rest}\n {...a11y}\n sx={[\n { display: 'block', color: 'inherit', textDecoration: 'none' },\n ...nodeSx,\n ]}\n >\n {children}\n </MuiBox>\n )\n }\n return (\n <AppLink\n ref={ref}\n // NOT `componentVariant=\"naked\"`: that branch renders a bare NextLink,\n // which does not process `sx` — it lands on the DOM as a literal `sx=\"\"`\n // attribute and every style is silently dropped. The default branch is\n // MUI's Link, which does. The anchor chrome it brings is neutralised by\n // the baseline below.\n href={href}\n {...(newTab && external\n ? { target: '_blank', rel: 'noopener noreferrer' }\n : {})}\n {...rest}\n {...a11y}\n // `display: block` so the anchor takes the tile's box, and no anchor\n // chrome — authors then style it exactly as they would a Box. This is\n // only the floor: node styles come after it and win.\n sx={[\n { display: 'block', color: 'inherit', textDecoration: 'none' },\n ...nodeSx,\n ]}\n >\n {children}\n </AppLink>\n )\n})\nLinkBox.displayName = 'AglynLinkBox'\n\n/** A label is announced by nothing once the link is hidden as a duplicate. */\nconst NOT_REDUNDANT = { when: REDUNDANT_ATTRIBUTE, is: true, notMatch: true }\n\nexport const schema: Aglyn.ComponentSchema<LinkBoxProps> = {\n $id: ID,\n pluginId: BUNDLE_ID,\n displayName: 'Link Container',\n description:\n 'Container that is itself a link — put an icon, a heading and a ' +\n 'description inside and the whole box becomes one target.',\n category: Aglyn.ComponentCategory.NAVIGATION,\n icon: { path: mdiLinkBoxOutline.path, sx: { color: '#2196f3' } },\n attributes: [\n {\n name: 'screenId',\n description:\n 'Screen this box 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 the whole container goes when it points off this site. ' +\n 'Ignored while Screen names one. Everything inside the box becomes ' +\n 'part of this one link, so it should hold no other link.',\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 {\n name: 'newTab',\n description:\n 'Open in a new tab. Applies to external URLs only — a link to one ' +\n 'of your own screens stays in the tab so the visitor keeps their ' +\n 'history.',\n component: Aglyn.FieldComponentType.SWITCH,\n label: 'Open in a new tab',\n },\n {\n name: 'ariaLabel',\n label: 'Accessible label',\n description:\n 'Names this link for screen readers. Needed when the box holds only ' +\n 'an icon, or an image with no alt text, which is otherwise announced ' +\n 'as just \"link\". Leave it empty when there is text inside: that text ' +\n 'already names the link.',\n // A text field, so the Attributes panel takes a binding here as it\n // does in every other text attribute.\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n condition: NOT_REDUNDANT,\n // Warns while nothing names the link (AGL-2886).\n resolveProps: linkContainerLabelFieldProps,\n },\n {\n name: REDUNDANT_ATTRIBUTE,\n label: 'Duplicate of another link',\n description:\n 'Turn on when another link beside this box goes to the same place, ' +\n 'such as the arrow on a card whose title is already a link. Screen ' +\n 'readers and the Tab key then skip this box, so those visitors meet ' +\n 'the destination once, and a click or tap still follows it. Leave it ' +\n 'off when this is the only link to where it goes.',\n component: Aglyn.FieldComponentType.SWITCH,\n },\n ],\n}\n\nexport const presets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Link Container',\n pluginId: BUNDLE_ID,\n description: 'A whole box that links somewhere',\n category: Aglyn.ComponentCategory.NAVIGATION,\n icon: schema.icon,\n data: {\n $id: null,\n componentId: ID,\n pluginId: BUNDLE_ID,\n // Ships with padding and a hover tint: an empty, zero-height anchor\n // dropped on the canvas looks like nothing happened.\n props: {},\n // Node-level sx (AGL-1346) — the record the Styles panel edits.\n sx: {\n display: 'block',\n paddingTop: 2,\n paddingRight: 2,\n paddingBottom: 2,\n paddingLeft: 2,\n borderRadius: 1,\n '&:hover': { backgroundColor: 'action.hover' },\n },\n },\n },\n]\n\nexport default LinkBox\n"],"names":["Aglyn","mdiLinkBoxOutline","AppLink","MuiBox","forwardRef","BUNDLE_ID","generatePresetId","linkContainerA11yProps","linkContainerLabelFieldProps","REDUNDANT_ATTRIBUTE","ID","LinkBox","props","ref","screenId","href","externalHref","newTab","ariaLabel","redundant","children","sx","rest","a11y","nodeSx","Array","isArray","suppressNavigation","leavesSite","external","useLinkTarget","component","display","color","textDecoration","target","rel","displayName","NOT_REDUNDANT","when","is","notMatch","schema","$id","pluginId","description","category","ComponentCategory","NAVIGATION","icon","path","attributes","name","FieldComponentType","SCREEN_SELECT","label","TEXT_FIELD","resolveProps","bareFragmentLinkFieldProps","SWITCH","condition","presets","type","NodeType","PRESET","data","componentId","paddingTop","paddingRight","paddingBottom","paddingLeft","borderRadius","backgroundColor"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/mui/src/lib/components/link-box.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport { mdiLinkBoxOutline } from '@aglyn/shared-data-mdi'\nimport { AppLink } from '@aglyn/shared-ui-jsx'\nimport MuiBox from '@mui/material/Box'\nimport type { SxProps } from '@mui/material/styles'\nimport { forwardRef, type ReactNode } from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { generatePresetId } from '../utils/generate-preset-id'\nimport {\n linkContainerA11yProps,\n linkContainerLabelFieldProps,\n REDUNDANT_ATTRIBUTE,\n} from './link-box-accessible-name'\n\n// Component ids are persisted in screen documents; never rename.\nexport const ID: Aglyn.ComponentId = 'muiLinkBox'\n\nexport interface LinkBoxProps {\n /** Target screen id, resolved from the host routing map at render time. */\n screenId?: string\n /** External URL escape hatch, used only when no `screenId` is set. */\n href?: string\n /** Opens in a new tab. External destinations only. */\n newTab?: boolean\n /** The link's name for assistive tech, when its content supplies none. */\n ariaLabel?: string\n /**\n * The box repeats a link beside it to the same destination, so it is\n * hidden from assistive tech and left out of the tab order.\n */\n redundant?: boolean\n /**\n * Authored node styles, handed over by the renderer rather than typed into\n * an attribute. Declared because the merge below reads it: undeclared, no\n * typed caller could style a tile (AGL-1323).\n */\n sx?: SxProps\n children?: ReactNode\n}\n\n/**\n * A container that IS the link — icon, heading and description inside one\n * anchor, instead of a tile whose only clickable part is its title.\n *\n * Every other linking element in the plugin is a leaf: Screen Link and\n * Button both render their `children` as inline text, so `nodeAcceptsChildren`\n * reports false and the canvas will not let anything be dropped inside them.\n * That left no way to author a card, a mega-menu tile, or a linked logo —\n * the shapes where the whole box is meant to be one target (AGL-1231).\n *\n * Nothing interactive may be nested inside: an `<a>` inside an `<a>` is\n * invalid HTML and browsers unnest it, which silently moves half the tile\n * back out of the link. Convert an inner Screen Link to plain Text when you\n * wrap it in one of these.\n *\n * The link is named by the text inside it, so a box holding only an icon\n * needs `ariaLabel` — or `redundant`, when a link beside it already goes to\n * the same place (AGL-2886). Both land on the anchor in every branch below,\n * so the canvas carries the same attributes the published page does.\n */\nconst LinkBox = forwardRef<HTMLElement, LinkBoxProps>((props, ref) => {\n const {\n screenId,\n href: externalHref,\n newTab,\n ariaLabel,\n redundant,\n children,\n sx,\n ...rest\n } = props\n const a11y = linkContainerA11yProps(ariaLabel, redundant)\n // Node styles ride the renderer-merged sx; recompose (stack.ts pattern).\n // Spreading `rest` with `sx` still inside it would REPLACE the baseline\n // below rather than merge with it, so every styled tile would quietly lose\n // `textDecoration: none` and get the browser's underlined-blue anchor.\n const nodeSx = Array.isArray(sx) ? sx : sx ? [sx] : []\n // Shared resolution (AGL-1335): screen id first, `javascript:`/`data:`\n // hrefs refused, `screen:`-prefixed prop values understood in either slot.\n // \"External\" means it leaves the site, not merely \"typed by hand\": a\n // site-relative `/pricing` in the href field must stay in the tab, or the\n // visitor loses their history for nothing.\n const {\n href,\n suppressNavigation,\n leavesSite: external,\n } = Aglyn.useLinkTarget(screenId, externalHref)\n\n // The unresolved and suppressed cases keep the same ELEMENT, not just the\n // same box (AGL-1268): this used to render a `<div>` here and an `<a>` when\n // the screen resolved, so the node's element type depended on the screens\n // map — a screen published or unpublished between renders changed the DOM\n // shape. An `<a>` without `href` is HTML's placeholder link: identical\n // element, non-interactive, and the canvas stays inert exactly as before.\n // The style floor matches the active branch so the two render identically.\n if (!href || suppressNavigation) {\n return (\n <MuiBox\n ref={ref}\n component=\"a\"\n {...rest}\n {...a11y}\n sx={[\n { display: 'block', color: 'inherit', textDecoration: 'none' },\n ...nodeSx,\n ]}\n >\n {children}\n </MuiBox>\n )\n }\n return (\n <AppLink\n ref={ref}\n // NOT `componentVariant=\"naked\"`: that branch renders a bare NextLink,\n // which does not process `sx` — it lands on the DOM as a literal `sx=\"\"`\n // attribute and every style is silently dropped. The default branch is\n // MUI's Link, which does. The anchor chrome it brings is neutralised by\n // the baseline below.\n href={href}\n {...(newTab && external\n ? { target: '_blank', rel: 'noopener noreferrer' }\n : {})}\n {...rest}\n {...a11y}\n // `display: block` so the anchor takes the tile's box, and no anchor\n // chrome — authors then style it exactly as they would a Box. This is\n // only the floor: node styles come after it and win.\n sx={[\n { display: 'block', color: 'inherit', textDecoration: 'none' },\n ...nodeSx,\n ]}\n >\n {children}\n </AppLink>\n )\n})\nLinkBox.displayName = 'AglynLinkBox'\n\n/** A label is announced by nothing once the link is hidden as a duplicate. */\nconst NOT_REDUNDANT = { when: REDUNDANT_ATTRIBUTE, is: true, notMatch: true }\n\nexport const schema: Aglyn.ComponentSchema<LinkBoxProps> = {\n $id: ID,\n pluginId: BUNDLE_ID,\n displayName: 'Link Container',\n description:\n 'Container that is itself a link — put an icon, a heading and a ' +\n 'description inside and the whole box becomes one target.',\n category: Aglyn.ComponentCategory.NAVIGATION,\n icon: { path: mdiLinkBoxOutline.path, sx: { color: '#2196f3' } },\n attributes: [\n {\n name: 'screenId',\n description:\n 'Page this box 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 the whole container goes when it points off this site. ' +\n 'Ignored while Page names one. Everything inside the box becomes ' +\n 'part of this one link, so it should hold no other link.',\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 {\n name: 'newTab',\n description:\n 'Open in a new tab. Applies to external URLs only — a link to one ' +\n 'of your own pages stays in the tab so the visitor keeps their ' +\n 'history.',\n component: Aglyn.FieldComponentType.SWITCH,\n label: 'Open in a new tab',\n },\n {\n name: 'ariaLabel',\n label: 'Accessible label',\n description:\n 'Names this link for screen readers. Needed when the box holds only ' +\n 'an icon, or an image with no alt text, which is otherwise announced ' +\n 'as just \"link\". Leave it empty when there is text inside: that text ' +\n 'already names the link.',\n // A text field, so the Attributes panel takes a binding here as it\n // does in every other text attribute.\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n condition: NOT_REDUNDANT,\n // Warns while nothing names the link (AGL-2886).\n resolveProps: linkContainerLabelFieldProps,\n },\n {\n name: REDUNDANT_ATTRIBUTE,\n label: 'Duplicate of another link',\n description:\n 'Turn on when another link beside this box goes to the same place, ' +\n 'such as the arrow on a card whose title is already a link. Screen ' +\n 'readers and the Tab key then skip this box, so those visitors meet ' +\n 'the destination once, and a click or tap still follows it. Leave it ' +\n 'off when this is the only link to where it goes.',\n component: Aglyn.FieldComponentType.SWITCH,\n },\n ],\n}\n\nexport const presets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Link Container',\n pluginId: BUNDLE_ID,\n description: 'A whole box that links somewhere',\n category: Aglyn.ComponentCategory.NAVIGATION,\n icon: schema.icon,\n data: {\n $id: null,\n componentId: ID,\n pluginId: BUNDLE_ID,\n // Ships with padding and a hover tint: an empty, zero-height anchor\n // dropped on the canvas looks like nothing happened.\n props: {},\n // Node-level sx (AGL-1346) — the record the Styles panel edits.\n sx: {\n display: 'block',\n paddingTop: 2,\n paddingRight: 2,\n paddingBottom: 2,\n paddingLeft: 2,\n borderRadius: 1,\n '&:hover': { backgroundColor: 'action.hover' },\n },\n },\n },\n]\n\nexport default LinkBox\n"],"names":["Aglyn","mdiLinkBoxOutline","AppLink","MuiBox","forwardRef","BUNDLE_ID","generatePresetId","linkContainerA11yProps","linkContainerLabelFieldProps","REDUNDANT_ATTRIBUTE","ID","LinkBox","props","ref","screenId","href","externalHref","newTab","ariaLabel","redundant","children","sx","rest","a11y","nodeSx","Array","isArray","suppressNavigation","leavesSite","external","useLinkTarget","component","display","color","textDecoration","target","rel","displayName","NOT_REDUNDANT","when","is","notMatch","schema","$id","pluginId","description","category","ComponentCategory","NAVIGATION","icon","path","attributes","name","FieldComponentType","SCREEN_SELECT","label","TEXT_FIELD","resolveProps","bareFragmentLinkFieldProps","SWITCH","condition","presets","type","NodeType","PRESET","data","componentId","paddingTop","paddingRight","paddingBottom","paddingLeft","borderRadius","backgroundColor"],"mappings":"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"}
@@ -761,7 +761,7 @@ export const markdownSchema = {
761
761
  {
762
762
  name: 'content',
763
763
  label: 'Content',
764
- description: 'The document, as markdown. Headings (## and ###), paragraphs, ' + 'bold/italic, links, bullet and 1. numbered lists, images, fenced ' + 'code, pipe tables and > quotes. Paste the whole source file — a ' + 'Table of contents element on the same screen lists the ## and ### ' + 'headings automatically.',
764
+ description: 'The document, as markdown. Headings (## and ###), paragraphs, ' + 'bold/italic, links, bullet and 1. numbered lists, images, fenced ' + 'code, pipe tables and > quotes. Paste the whole source file — a ' + 'Table of contents element on the same page lists the ## and ### ' + 'headings automatically.',
765
765
  // The WYSIWYG rather than a raw textarea (AGL-1616): this attribute is
766
766
  // a whole document — the published Privacy Policy body is one of them —
767
767
  // and editing it as raw markdown-lite meant a 13 KB paste (AGL-1594).
@@ -789,7 +789,7 @@ export const tableOfContentsSchema = {
789
789
  {
790
790
  name: 'forNodeId',
791
791
  label: 'Markdown element',
792
- description: 'The Markdown element to list the headings of. Leave empty on a ' + 'page with one Markdown element — the first one on the screen is ' + 'used, and a pick that no longer exists falls back to it too.',
792
+ description: 'The Markdown element to list the headings of. Leave empty on a ' + 'page with one Markdown element — the first one on the page is ' + 'used, and a pick that no longer exists falls back to it too.',
793
793
  component: Aglyn.FieldComponentType.NODE_SELECT
794
794
  },
795
795
  {
@@ -843,7 +843,7 @@ export const markdownPresets = [
843
843
  componentId: MARKDOWN_ID,
844
844
  pluginId: BUNDLE_ID,
845
845
  props: {
846
- content: '## First section\n\nReplace this with your document. Paste ' + 'markdown straight from the source file.\n\n' + '### A sub-section\n\nHeadings become anchors, and a Table of ' + 'contents element on the same screen links to them.'
846
+ content: '## First section\n\nReplace this with your document. Paste ' + 'markdown straight from the source file.\n\n' + '### A sub-section\n\nHeadings become anchors, and a Table of ' + 'contents element on the same page links to them.'
847
847
  }
848
848
  }
849
849
  },