@aglyn/besigner-ui 1.0.0-beta.147 → 1.0.0-beta.150
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +17 -17
- package/src/lib/components/custom-css-form.component.js +11 -1
- package/src/lib/components/custom-css-form.component.js.map +1 -1
- package/src/lib/components/node-leaf.js +48 -11
- package/src/lib/components/node-leaf.js.map +1 -1
- package/src/lib/utils/style-target.d.ts +13 -0
- package/src/lib/utils/style-target.js +6 -0
- package/src/lib/utils/style-target.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aglyn/besigner-ui",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.150",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://aglyn.com",
|
|
6
6
|
"repository": {
|
|
@@ -25,22 +25,22 @@
|
|
|
25
25
|
"./package.json": "./package.json"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@aglyn/aglyn": "1.0.0-beta.
|
|
29
|
-
"@aglyn/aglyn-markdown-editor": "1.0.0-beta.
|
|
30
|
-
"@aglyn/aglyn-node-renderer": "1.0.0-beta.
|
|
31
|
-
"@aglyn/besigner": "1.0.0-beta.
|
|
32
|
-
"@aglyn/shared-data-enums": "1.0.0-beta.
|
|
33
|
-
"@aglyn/shared-data-mdi": "1.0.0-beta.
|
|
34
|
-
"@aglyn/shared-data-types": "1.0.0-beta.
|
|
35
|
-
"@aglyn/shared-svg-icons": "1.0.0-beta.
|
|
36
|
-
"@aglyn/shared-ui-json-editor": "1.0.0-beta.
|
|
37
|
-
"@aglyn/shared-ui-jsx": "1.0.0-beta.
|
|
38
|
-
"@aglyn/shared-ui-jsx-forms": "1.0.0-beta.
|
|
39
|
-
"@aglyn/shared-ui-snackstack": "1.0.0-beta.
|
|
40
|
-
"@aglyn/shared-ui-theme": "1.0.0-beta.
|
|
41
|
-
"@aglyn/shared-util-dom": "1.0.0-beta.
|
|
42
|
-
"@aglyn/shared-util-tools": "1.0.0-beta.
|
|
43
|
-
"@aglyn/shared-util-vendor": "1.0.0-beta.
|
|
28
|
+
"@aglyn/aglyn": "1.0.0-beta.150",
|
|
29
|
+
"@aglyn/aglyn-markdown-editor": "1.0.0-beta.150",
|
|
30
|
+
"@aglyn/aglyn-node-renderer": "1.0.0-beta.150",
|
|
31
|
+
"@aglyn/besigner": "1.0.0-beta.150",
|
|
32
|
+
"@aglyn/shared-data-enums": "1.0.0-beta.150",
|
|
33
|
+
"@aglyn/shared-data-mdi": "1.0.0-beta.150",
|
|
34
|
+
"@aglyn/shared-data-types": "1.0.0-beta.150",
|
|
35
|
+
"@aglyn/shared-svg-icons": "1.0.0-beta.150",
|
|
36
|
+
"@aglyn/shared-ui-json-editor": "1.0.0-beta.150",
|
|
37
|
+
"@aglyn/shared-ui-jsx": "1.0.0-beta.150",
|
|
38
|
+
"@aglyn/shared-ui-jsx-forms": "1.0.0-beta.150",
|
|
39
|
+
"@aglyn/shared-ui-snackstack": "1.0.0-beta.150",
|
|
40
|
+
"@aglyn/shared-ui-theme": "1.0.0-beta.150",
|
|
41
|
+
"@aglyn/shared-util-dom": "1.0.0-beta.150",
|
|
42
|
+
"@aglyn/shared-util-tools": "1.0.0-beta.150",
|
|
43
|
+
"@aglyn/shared-util-vendor": "1.0.0-beta.150",
|
|
44
44
|
"@dnd-kit/core": "^6.3.1",
|
|
45
45
|
"@dnd-kit/utilities": "^3.2.2",
|
|
46
46
|
"@react-aria/utils": "^3.34.1",
|
|
@@ -648,8 +648,18 @@ const draftKey = (nodeId, overrideKey, mode)=>[
|
|
|
648
648
|
/*#__PURE__*/ _jsx(Typography, {
|
|
649
649
|
variant: "caption",
|
|
650
650
|
color: "text.secondary",
|
|
651
|
-
children: 'Full sx document — supports responsive objects ' + '({ xs, sm, … }) and nested selectors ("&:hover").'
|
|
651
|
+
children: (target.isComposed ? 'The effective sx — supports responsive objects ' : 'Full sx document — supports responsive objects ') + '({ xs, sm, … }) and nested selectors ("&:hover").'
|
|
652
652
|
}),
|
|
653
|
+
target.isComposed ? // The panel has to say this out loud (AGL-3218). Everything
|
|
654
|
+
// below is correct and it is also not what the editor looks
|
|
655
|
+
// like it is doing, and the author only finds out by reading
|
|
656
|
+
// the stored node somewhere else — where the edit reads as
|
|
657
|
+
// missing.
|
|
658
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
659
|
+
variant: "caption",
|
|
660
|
+
color: "text.secondary",
|
|
661
|
+
children: 'This element stores styles in two places: a props.sx its ' + 'component or preset authored, and its own sx. The box ' + 'shows both merged, the way the page renders them. Apply ' + 'writes only what you changed into the element’s own sx ' + 'and leaves the rest where it is, so a property you edit ' + 'here will not appear in props.sx.'
|
|
662
|
+
}) : null,
|
|
653
663
|
/*#__PURE__*/ _jsx(TextField, {
|
|
654
664
|
multiline: true,
|
|
655
665
|
minRows: 6,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../libs/besigner/feature/designer/src/lib/components/custom-css-form.component.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 type * as Aglyn from '@aglyn/aglyn'\nimport {\n canvas,\n collectThirdPartyAuthorCssUrlHosts,\n collectThirdPartyAuthorSxUrlHosts,\n} from '@aglyn/aglyn'\nimport { mdiClose, mdiPlus } from '@aglyn/shared-data-mdi'\nimport {\n canonicalSxProperties,\n expandSxAliases,\n isAtomicSxValue,\n} from '@aglyn/shared-data-enums'\nimport { MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { FIELD_MUTED_STYLES, FieldMuteButton } from '@aglyn/shared-ui-jsx-forms'\nimport {\n Autocomplete,\n Button,\n IconButton,\n Stack,\n TextField,\n ToggleButton,\n ToggleButtonGroup,\n Typography,\n} from '@mui/material'\nimport { observer } from 'mobx-react-lite'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport useAglynBesignerFlag from '../hooks/use-aglyn-besigner-flag'\nimport { buildStyleMute, toggleMutedStyle } from '../utils/muted-styles'\nimport type { SxBreakpoint } from '../utils/responsive-sx'\nimport { readSxValue } from '../utils/responsive-sx'\nimport { applyStylePartialToSx } from '../utils/style-field-groups'\nimport { getNodeStyleTarget } from '../utils/style-target'\n\n/**\n * Curated, grouped suggestions for the builder's property picker\n * (freeSolo, so any CSS/sx property still works).\n */\nconst CSS_PROPERTY_SUGGESTIONS: Array<{ group: string; name: string }> = [\n ...[\n 'width',\n 'height',\n 'minWidth',\n 'maxWidth',\n 'minHeight',\n 'maxHeight',\n 'overflow',\n 'position',\n 'top',\n 'right',\n 'bottom',\n 'left',\n 'zIndex',\n ].map((name) => ({ group: 'Layout', name })),\n ...['margin', 'padding', 'gap', 'rowGap', 'columnGap'].map((name) => ({\n group: 'Spacing',\n name,\n })),\n ...[\n 'fontSize',\n 'fontWeight',\n 'fontFamily',\n 'lineHeight',\n 'letterSpacing',\n 'textTransform',\n 'textDecoration',\n 'whiteSpace',\n ].map((name) => ({ group: 'Typography', name })),\n ...[\n 'color',\n 'backgroundColor',\n 'background',\n 'backgroundImage',\n 'backgroundSize',\n 'backgroundPosition',\n 'opacity',\n ].map((name) => ({ group: 'Background & color', name })),\n ...[\n 'border',\n 'borderRadius',\n 'borderColor',\n 'borderWidth',\n 'borderStyle',\n 'outline',\n ].map((name) => ({ group: 'Border', name })),\n ...[\n 'boxShadow',\n 'transform',\n 'transition',\n 'filter',\n 'cursor',\n 'objectFit',\n 'aspectRatio',\n ].map((name) => ({ group: 'Effects', name })),\n]\n\nconst kebabToCamel = (name: string) =>\n name.replace(/-([a-z])/g, (match, letter: string) => letter.toUpperCase())\nconst camelToKebab = (name: string) =>\n name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)\n\n/** Parses `prop: value;` CSS declarations into a JSS record (AGL-332). */\nexport function parseCssDeclarations(css: string): Record<string, string> {\n const result: Record<string, string> = {}\n for (const raw of css.split(';')) {\n const declaration = raw.trim()\n if (!declaration) continue\n const colon = declaration.indexOf(':')\n if (colon <= 0) continue\n const property = kebabToCamel(declaration.slice(0, colon).trim())\n const value = declaration.slice(colon + 1).trim()\n if (property && value) result[property] = value\n }\n return result\n}\n\n/**\n * The scalar declarations the Builder rows and the CSS tab show for `sx`,\n * named in the Styles panel's own spelling (AGL-2390).\n *\n * MUI honours two names for the same declaration, and every other control\n * in this panel is named for the CSS longhand (AGL-2207). Listing the raw\n * keys made this form the one place that said `bgcolor` where the Colors\n * group said `backgroundColor` and BoxStyler said `paddingTop` — the same\n * node, two vocabularies, and the author with no way to tell that the two\n * rows were one declaration. The expansion is a pure renaming of what\n * already renders (order preserved, non-atomic values left alone), and it\n * is what makes the row's × and the CSS tab's clear reach the key that is\n * actually painting.\n *\n * The JSS tab deliberately does NOT go through here: it edits the stored\n * document verbatim, which is the escape hatch for everything the named\n * controls cannot express.\n */\nexport function customCssDeclarations(\n sx: Record<string, any> | undefined,\n breakpoint: SxBreakpoint | null,\n): Array<{ property: string; value: string }> {\n const source = expandSxAliases((sx ?? {}) as Record<string, any>, {\n deep: true,\n })\n const out: Array<{ property: string; value: string }> = []\n for (const property of Object.keys(source)) {\n const value = readSxValue(source, property, breakpoint)\n if (value === undefined || value === null) continue\n if (typeof value === 'object') continue // selectors/nested — JSON tab\n out.push({ property, value: String(value) })\n }\n return out\n}\n\n/** Serializes scalar sx entries into CSS declarations for the CSS tab. */\nexport function serializeCssDeclarations(\n sx: Record<string, any> | undefined,\n breakpoint: SxBreakpoint | null,\n): string {\n return customCssDeclarations(sx, breakpoint)\n .map(({ property, value }) => `${camelToKebab(property)}: ${value};`)\n .join('\\n')\n}\n\n/**\n * The declaration(s) one named property writes, in the panel's spelling.\n *\n * The property field is free-solo and the CSS tab takes any declaration, so\n * an author can type `bgcolor` or `py` at any time — this form is the one\n * seam in the product where a fresh alias can still enter a document. It\n * resolves the name the same way {@link expandSxAliases} resolves a stored\n * one, so what the author typed and what the panel reads back agree.\n *\n * A value the expansion cannot carry per-side — `p: '10px 20px'`, the CSS\n * shorthand's own syntax — keeps the name the author gave it rather than\n * being copied onto four longhands CSS would drop. Clearing always expands,\n * because a cleared alias has to take every longhand it was painting with\n * it.\n */\nexport function canonicalCssPartial(\n property: string,\n value: string | undefined,\n): Record<string, unknown> {\n const canonical = canonicalSxProperties(property)\n const expandable =\n canonical.length === 1 || value === undefined || isAtomicSxValue(value)\n if (!canonical.length || !expandable) return { [property]: value }\n return Object.fromEntries(canonical.map((name) => [name, value]))\n}\n\n/**\n * Applies custom-CSS edits to `sx` — the write both the Builder rows and\n * the CSS tab's Apply go through (AGL-2390).\n *\n * Routed through the Styles panel's own write seam so this form cannot\n * drift from it again: {@link applyStylePartialToSx} expands the alias keys\n * the edit COLLIDES with before writing, which is what makes clearing a\n * declaration actually clear (deleting `backgroundColor` while a `bgcolor`\n * underneath keeps painting is AGL-2207's sharper failure) and what stops a\n * write leaving two spellings of one declaration in the record.\n *\n * Scheme-agnostic (`null`): this form has always written the base slice,\n * and the dark-preview scoping belongs to the named color fields.\n */\nexport function applyCustomCssEdits(\n sx: Record<string, any> | undefined,\n edits: Record<string, string | undefined>,\n breakpoint: SxBreakpoint | null,\n): Record<string, any> {\n const partial: Record<string, unknown> = {}\n for (const [property, value] of Object.entries(edits)) {\n if (!property) continue\n Object.assign(\n partial,\n canonicalCssPartial(property, value === '' ? undefined : value),\n )\n }\n return applyStylePartialToSx(sx, partial, breakpoint, null)\n}\n\n/**\n * Un-applied CSS / JSS text, held ACROSS an unmount of this form (AGL-2486).\n *\n * The JSS tab is the one control in the Styles panel whose work lives only\n * in React state until Apply — and this form is mounted under\n * `withLastSelectedNode`, which renders \"Select an element\" the instant the\n * canvas selection empties. Anything that clears the selection therefore\n * UNMOUNTS the editor, and a `useState('')` buffer takes the author's\n * half-typed sx document with it. Measured on a live besigner: a selection\n * held with no keyboard or pointer input at all lost the editor 46s in, so\n * this is not something the author can avoid by typing carefully — it is\n * why the wipe reads as random, and why it gets blamed on whichever key was\n * pressed last.\n *\n * Keyed by node AND style target, so a component instance's root and its\n * leaves each keep their own pending text, and re-selecting the element\n * hands the author back exactly what they had typed. An entry lives only\n * while the text DIFFERS from the stored document: applying it, or typing\n * it back to what sx already says, drops the entry, so this never resurrects\n * a draft the author has already resolved.\n *\n * Deliberately not persisted beyond the page: it is un-applied text, not a\n * document, and the crash-recovery draft store is the thing that owns\n * surviving a reload.\n */\nconst pendingDrafts = new Map<string, string>()\n\nconst draftKey = (\n nodeId: string,\n overrideKey: string,\n mode: 'css' | 'json',\n): string => [nodeId, overrideKey, mode].join('\\x00')\n\n/** Drops every pending draft — test seam, and a hard reset for the canvas. */\nexport function resetPendingCustomCssDrafts(): void {\n pendingDrafts.clear()\n}\n\n/**\n * Why a JSS buffer cannot be applied, or `null` when it is ready\n * (AGL-2486). Rendered live as the author types, so \"nothing happened when\n * I pressed Apply\" is never the first sign that the text does not parse.\n *\n * Mid-edit JSON is TRANSIENTLY invalid by nature — `{\"a\": 1,` is what every\n * new property looks like a keystroke before it is finished — so this is a\n * hint, never a block on typing and never a reason to touch the document.\n *\n * An empty buffer is called out rather than treated as `{}`: `JSON.parse`\n * would need a fallback to accept it at all, and silently reading \"\" as\n * \"delete every style on this element\" is the most destructive thing this\n * panel could do with an accidental Select-All. Clearing all styles stays\n * available, spelled `{}`, which nobody types by accident.\n */\nexport function jsonDraftProblem(text: string): string | null {\n const trimmed = (text ?? '').trim()\n if (!trimmed) {\n return 'Empty — type {} to clear every style on this element.'\n }\n let parsed: unknown\n try {\n parsed = JSON.parse(trimmed)\n } catch {\n return 'Not valid JSON yet — Apply will not change anything until it parses.'\n }\n // Explicit `=== null` and an array check: `strictNullChecks` is off\n // repo-wide, so `!parsed` would fold `null`, `0` and `\"\"` together, and\n // `typeof null` is `'object'`.\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n return 'The sx value must be a JSON object.'\n }\n return null\n}\n\nexport interface CustomCssFormProps {\n node?: Aglyn.NodeSchema\n /** Active artboard breakpoint; builder/CSS edits scope to it (AGL-333). */\n breakpoint: SxBreakpoint | null\n /**\n * Which `styleOverrides` slice to edit on a component instance\n * (AGL-1332) — the Styles panel's current style target. Ignored for a\n * plain node; defaults to the component root.\n */\n overrideKey?: string\n}\n\ntype BuilderRow = { property: string; value: string }\n\n/**\n * Custom CSS for the selected element's `sx` (AGL-332): a friendly\n * builder (property + value rows), a raw CSS mode, and a raw JSS/JSON\n * mode. Builder and CSS edits respect the active breakpoint scope; the\n * JSON mode edits the full sx document (including responsive objects and\n * nested selectors) verbatim.\n */\nexport const CustomCssForm = observer((props: CustomCssFormProps) => {\n const { node, breakpoint, overrideKey } = props\n // Same target the rest of the Styles panel writes (AGL-1306/1332): a\n // plain node's own sx, or the instance override slice the panel's style\n // target names — root, or one leaf inside the component.\n const target = useMemo(\n () => getNodeStyleTarget(node, overrideKey),\n [node, overrideKey],\n )\n const nodeSx = (target.sx ?? {}) as Record<string, any>\n const [mode, setMode] = useState<'builder' | 'css' | 'json'>('builder')\n const [cssDraft, setCssDraft] = useState('')\n const [jsonDraft, setJsonDraft] = useState('')\n const [error, setError] = useState<string | null>(null)\n\n const rows = useMemo<BuilderRow[]>(\n () => customCssDeclarations(nodeSx, breakpoint),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [JSON.stringify(nodeSx), breakpoint],\n )\n const [draftRow, setDraftRow] = useState<BuilderRow>({\n property: '',\n value: '',\n })\n\n // Declarations switched off for comparison (AGL-2486). Canvas only, and\n // the same flag every other row in the panel writes, so a property muted\n // here and the same property muted from its typed field are one state.\n // The builder lists BASE scalar declarations only — a state slice is a\n // nested object it skips — so these mutes carry no state scope.\n const [mutedStyles, setMutedStyles] = useAglynBesignerFlag('mutedStyles')\n const rowMute = useCallback(\n (property: string) =>\n buildStyleMute(property, property, {\n nodeId: node?.$id,\n state: null,\n breakpoint,\n // Every row the builder lists is, by construction, declared.\n scopeValues: { [property]: true },\n mutedStyles,\n onToggle: (target) =>\n setMutedStyles((current) => toggleMutedStyle(current, target)),\n }),\n [node, breakpoint, mutedStyles, setMutedStyles],\n )\n\n const nodeId = node?.$id ?? ''\n /** What the STORED document says, in each tab's spelling. */\n const storedCss = useMemo(\n () => serializeCssDeclarations(nodeSx, breakpoint),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [JSON.stringify(nodeSx), breakpoint],\n )\n const storedJson = useMemo(\n () => JSON.stringify(nodeSx, null, 2),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [JSON.stringify(nodeSx)],\n )\n\n /**\n * Records (or drops) the author's un-applied text for one tab, so an\n * unmount cannot take it (AGL-2486). Text equal to the stored document is\n * not a draft at all, so it clears the entry rather than pinning a value\n * that would then shadow a later change from elsewhere in the panel.\n */\n const rememberDraft = useCallback(\n (draftMode: 'css' | 'json', value: string, stored: string) => {\n const key = draftKey(nodeId, target.overrideKey, draftMode)\n if (value === stored) pendingDrafts.delete(key)\n else pendingDrafts.set(key, value)\n },\n [nodeId, target.overrideKey],\n )\n\n useEffect(() => {\n // A pending draft wins over the stored document — that is the whole\n // point of keeping it. `!== undefined` rather than a truthiness test:\n // `''` is a legitimate buffer the author emptied on purpose, and with\n // `strictNullChecks` off `if (!pending)` would silently discard it.\n const pendingCss = pendingDrafts.get(\n draftKey(nodeId, target.overrideKey, 'css'),\n )\n const pendingJson = pendingDrafts.get(\n draftKey(nodeId, target.overrideKey, 'json'),\n )\n setCssDraft(pendingCss !== undefined ? pendingCss : storedCss)\n setJsonDraft(pendingJson !== undefined ? pendingJson : storedJson)\n setError(null)\n // Re-seeded on the override target too (AGL-1332): switching from the\n // component root to a leaf must reload the drafts, or a stale CSS\n // buffer would be written back onto the newly picked target.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [mode, nodeId, target.overrideKey])\n\n /**\n * Live \"is this appliable yet\" verdict for the JSS tab (AGL-2486) — a\n * caption only. Nothing here writes, so a buffer that stops parsing\n * mid-property changes the hint and leaves the document alone.\n */\n const jsonProblem = useMemo(\n () => (mode === 'json' ? jsonDraftProblem(jsonDraft) : null),\n [mode, jsonDraft],\n )\n\n // Passive off-site url() hint (AGL-1737) — the \"warn\" half of AGL-1725's\n // warn-and-disclose, for the panel with no per-field description to carry\n // it. Names the hosts a url() in the CURRENT value would make every\n // visitor's browser contact: the live draft in the CSS and JSON modes\n // (ahead of Apply, so the author reads it while typing), the committed sx\n // in the builder (whose rows write live). A caption and nothing more — it\n // never blocks a value, and it deliberately does not touch how or when\n // this form commits (an attribute panel edit commits on blur, and that\n // behavior is load-bearing).\n const thirdPartyHosts = useMemo<string[]>(() => {\n if (mode === 'css') return collectThirdPartyAuthorCssUrlHosts(cssDraft)\n if (mode === 'json') {\n try {\n return collectThirdPartyAuthorSxUrlHosts(JSON.parse(jsonDraft || '{}'))\n } catch {\n // Mid-edit JSON — scan the raw text so the hint tracks typing.\n return collectThirdPartyAuthorCssUrlHosts(jsonDraft)\n }\n }\n return collectThirdPartyAuthorSxUrlHosts(nodeSx)\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [mode, cssDraft, jsonDraft, JSON.stringify(nodeSx)])\n\n const writeProperty = useCallback(\n (property: string, value: string | undefined) => {\n if (!node || !property) return\n // Coalesced per property (AGL-1204): the builder rows write on every\n // keystroke, so typing one value is one undo step, and editing a\n // different property starts another. The write goes through the style\n // target (AGL-1306), so this is undoable on an instance's override\n // layer exactly as on a plain node's own sx.\n canvas.transact(\n () => {\n target.setSx(\n applyCustomCssEdits(\n (target.sx ?? {}) as Record<string, any>,\n { [property]: value },\n breakpoint,\n ),\n )\n },\n ['sx', node.$id, breakpoint ?? 'base', property].join(':'),\n )\n },\n [node, target, breakpoint],\n )\n\n const applyCss = useCallback(() => {\n if (!node) return\n const parsed = parseCssDeclarations(cssDraft)\n // Uncoalesced: pressing Apply is one deliberate commit, however many\n // declarations it carries (AGL-1204).\n canvas.transact(() => {\n // One edit map, applied once: declarations no longer in the draft\n // clear, the rest write. Both halves resolve their spelling through\n // `applyCustomCssEdits`, so a row the author deleted takes the alias\n // that was painting it with it, and a declaration they typed under\n // MUI's spelling lands where every other control can read it.\n const edits: Record<string, string | undefined> = {}\n for (const row of rows) {\n if (!(row.property in parsed)) edits[row.property] = undefined\n }\n Object.assign(edits, parsed)\n target.setSx(\n applyCustomCssEdits(\n (target.sx ?? {}) as Record<string, any>,\n edits,\n breakpoint,\n ),\n )\n })\n setError(null)\n // Applied text is no longer un-applied.\n pendingDrafts.delete(draftKey(nodeId, target.overrideKey, 'css'))\n }, [node, target, cssDraft, rows, breakpoint, nodeId])\n\n /**\n * Commits the JSS buffer — the ONLY path from this tab to the document\n * (AGL-2486).\n *\n * A buffer that does not parse to a JSON object is a NON-COMMIT: it sets\n * the inline error and returns, leaving both the document and the text\n * exactly as they are, so the author can keep editing from where they\n * were. There is no blur, change or unmount handler anywhere in this form\n * that reaches the document — Apply is the whole seam, which is what\n * makes a transiently invalid buffer harmless.\n */\n const applyJson = useCallback(() => {\n if (!node) return\n const problem = jsonDraftProblem(jsonDraft)\n if (problem !== null) {\n setError(problem)\n return\n }\n // Re-parsed rather than carried out of the guard: `jsonDraftProblem`\n // owns the verdict, and one function deciding while another commits is\n // how \"valid enough to pass, wrong shape to store\" gets in.\n const parsed = JSON.parse(jsonDraft.trim())\n // Replacing sx wholesale is the single most destructive edit this panel\n // offers, so it is the one that most needs to be undoable (AGL-1204).\n canvas.transact(() => {\n target.setSx(parsed)\n })\n setError(null)\n pendingDrafts.delete(draftKey(nodeId, target.overrideKey, 'json'))\n }, [node, target, jsonDraft, nodeId])\n\n return (\n <Stack spacing={1.5}>\n <ToggleButtonGroup\n size=\"small\"\n exclusive\n fullWidth\n value={mode}\n onChange={(event, value) => value && setMode(value)}\n >\n <ToggleButton value=\"builder\">{'Builder'}</ToggleButton>\n <ToggleButton value=\"css\">{'CSS'}</ToggleButton>\n <ToggleButton value=\"json\">{'JSS (sx)'}</ToggleButton>\n </ToggleButtonGroup>\n\n {mode === 'builder' ? (\n <Stack spacing={1}>\n {rows.map((row) => {\n const mute = rowMute(row.property)\n return (\n <Stack\n key={row.property}\n direction=\"row\"\n spacing={1}\n sx={[\n { alignItems: 'center' },\n mute?.muted ? FIELD_MUTED_STYLES : null,\n ]}\n >\n <TextField\n size=\"small\"\n value={row.property}\n disabled\n sx={{ flex: 1 }}\n />\n <TextField\n size=\"small\"\n value={row.value}\n onChange={(event) =>\n writeProperty(row.property, event.target.value)\n }\n sx={{ flex: 1 }}\n />\n {/* The eye stops the declaration applying and keeps its\n value; the ✕ beside it is still the only thing that\n takes the declaration off the element. */}\n <FieldMuteButton mute={mute} />\n <IconButton\n size=\"small\"\n aria-label={`Remove ${row.property}`}\n onClick={() => writeProperty(row.property, undefined)}\n >\n <MdiIcon path={mdiClose.path} size={0.7} />\n </IconButton>\n </Stack>\n )\n })}\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Autocomplete\n freeSolo\n size=\"small\"\n options={CSS_PROPERTY_SUGGESTIONS}\n groupBy={(option) =>\n typeof option === 'string' ? '' : option.group\n }\n getOptionLabel={(option) =>\n typeof option === 'string' ? option : option.name\n }\n inputValue={draftRow.property}\n onInputChange={(event, value) =>\n setDraftRow((prev) => ({ ...prev, property: value }))\n }\n renderInput={(params) => (\n <TextField {...params} placeholder=\"property\" />\n )}\n sx={{ flex: 1 }}\n />\n <TextField\n size=\"small\"\n placeholder=\"value\"\n value={draftRow.value}\n onChange={(event) =>\n setDraftRow((prev) => ({ ...prev, value: event.target.value }))\n }\n sx={{ flex: 1 }}\n />\n <IconButton\n size=\"small\"\n aria-label=\"Add property\"\n disabled={!draftRow.property.trim() || !draftRow.value.trim()}\n onClick={() => {\n writeProperty(\n kebabToCamel(draftRow.property.trim()),\n draftRow.value.trim(),\n )\n setDraftRow({ property: '', value: '' })\n }}\n >\n <MdiIcon path={mdiPlus.path} size={0.8} />\n </IconButton>\n </Stack>\n </Stack>\n ) : null}\n\n {mode === 'css' ? (\n <Stack spacing={1}>\n <TextField\n multiline\n minRows={5}\n value={cssDraft}\n onChange={(event) => {\n setCssDraft(event.target.value)\n rememberDraft('css', event.target.value, storedCss)\n }}\n placeholder={\n 'border-radius: 8px;\\nbox-shadow: 0 2px 8px rgba(0,0,0,0.2);'\n }\n slotProps={{\n htmlInput: {\n style: { fontFamily: 'monospace', fontSize: 12 },\n },\n }}\n />\n <Button size=\"small\" variant=\"outlined\" onClick={applyCss}>\n {'Apply CSS'}\n </Button>\n </Stack>\n ) : null}\n\n {mode === 'json' ? (\n <Stack spacing={1}>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Full sx document — supports responsive objects ' +\n '({ xs, sm, … }) and nested selectors (\"&:hover\").'}\n </Typography>\n <TextField\n multiline\n minRows={6}\n value={jsonDraft}\n onChange={(event) => {\n setJsonDraft(event.target.value)\n rememberDraft('json', event.target.value, storedJson)\n // A keystroke that fixes the JSON clears a stale Apply error;\n // it never raises one, so typing is not nagged mid-property.\n if (error && jsonDraftProblem(event.target.value) === null) {\n setError(null)\n }\n }}\n error={Boolean(error)}\n helperText={error ?? jsonProblem ?? undefined}\n slotProps={{\n htmlInput: {\n style: { fontFamily: 'monospace', fontSize: 12 },\n },\n }}\n />\n <Button size=\"small\" variant=\"outlined\" onClick={applyJson}>\n {'Apply JSS'}\n </Button>\n </Stack>\n ) : null}\n\n {thirdPartyHosts.length ? (\n // Same voice as the Custom HTML `css` description (AGL-1737), with\n // the host named back so the consequence is attached to the value\n // the author typed rather than left as general prose.\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'A url() here points off your site — every visitor’s browser ' +\n `will contact ${thirdPartyHosts.join(', ')}, disclosing their ` +\n 'IP address and which page they are on. Insecure http:// URLs ' +\n 'are not loaded.'}\n </Typography>\n ) : null}\n </Stack>\n )\n})\nCustomCssForm.displayName = 'CustomCssForm'\n\nexport default CustomCssForm\n"],"names":["canvas","collectThirdPartyAuthorCssUrlHosts","collectThirdPartyAuthorSxUrlHosts","mdiClose","mdiPlus","canonicalSxProperties","expandSxAliases","isAtomicSxValue","MdiIcon","FIELD_MUTED_STYLES","FieldMuteButton","Autocomplete","Button","IconButton","Stack","TextField","ToggleButton","ToggleButtonGroup","Typography","observer","useCallback","useEffect","useMemo","useState","useAglynBesignerFlag","buildStyleMute","toggleMutedStyle","readSxValue","applyStylePartialToSx","getNodeStyleTarget","CSS_PROPERTY_SUGGESTIONS","map","name","group","kebabToCamel","replace","match","letter","toUpperCase","camelToKebab","toLowerCase","parseCssDeclarations","css","result","raw","split","declaration","trim","colon","indexOf","property","slice","value","customCssDeclarations","sx","breakpoint","source","deep","out","Object","keys","undefined","push","String","serializeCssDeclarations","join","canonicalCssPartial","canonical","expandable","length","fromEntries","applyCustomCssEdits","edits","partial","entries","assign","pendingDrafts","Map","draftKey","nodeId","overrideKey","mode","resetPendingCustomCssDrafts","clear","jsonDraftProblem","text","trimmed","parsed","JSON","parse","Array","isArray","CustomCssForm","props","target","error","node","nodeSx","setMode","cssDraft","setCssDraft","jsonDraft","setJsonDraft","setError","rows","stringify","draftRow","setDraftRow","mutedStyles","setMutedStyles","rowMute","$id","state","scopeValues","onToggle","current","storedCss","storedJson","rememberDraft","draftMode","stored","key","delete","set","pendingCss","get","pendingJson","jsonProblem","thirdPartyHosts","writeProperty","transact","setSx","applyCss","row","applyJson","problem","spacing","size","exclusive","fullWidth","onChange","event","mute","direction","alignItems","muted","disabled","flex","aria-label","onClick","path","freeSolo","options","groupBy","option","getOptionLabel","inputValue","onInputChange","prev","renderInput","params","placeholder","multiline","minRows","slotProps","htmlInput","style","fontFamily","fontSize","variant","color","Boolean","helperText","displayName"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC;;AAGD,SACEA,MAAM,EACNC,kCAAkC,EAClCC,iCAAiC,QAC5B,eAAc;AACrB,SAASC,QAAQ,EAAEC,OAAO,QAAQ,yBAAwB;AAC1D,SACEC,qBAAqB,EACrBC,eAAe,EACfC,eAAe,QACV,2BAA0B;AACjC,SAASC,OAAO,QAAQ,uBAAsB;AAC9C,SAASC,kBAAkB,EAAEC,eAAe,QAAQ,6BAA4B;AAChF,SACEC,YAAY,EACZC,MAAM,EACNC,UAAU,EACVC,KAAK,EACLC,SAAS,EACTC,YAAY,EACZC,iBAAiB,EACjBC,UAAU,QACL,gBAAe;AACtB,SAASC,QAAQ,QAAQ,kBAAiB;AAC1C,SAASC,WAAW,EAAEC,SAAS,EAAEC,OAAO,EAAEC,QAAQ,QAAQ,QAAO;AACjE,OAAOC,0BAA0B,sCAAkC;AACnE,SAASC,cAAc,EAAEC,gBAAgB,QAAQ,2BAAuB;AAExE,SAASC,WAAW,QAAQ,4BAAwB;AACpD,SAASC,qBAAqB,QAAQ,iCAA6B;AACnE,SAASC,kBAAkB,QAAQ,2BAAuB;AAE1D;;;CAGC,GACD,MAAMC,2BAAmE;OACpE;QACD;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;KACD,CAACC,GAAG,CAAC,CAACC,OAAU,CAAA;YAAEC,OAAO;YAAUD;QAAK,CAAA;OACtC;QAAC;QAAU;QAAW;QAAO;QAAU;KAAY,CAACD,GAAG,CAAC,CAACC,OAAU,CAAA;YACpEC,OAAO;YACPD;QACF,CAAA;OACG;QACD;QACA;QACA;QACA;QACA;QACA;QACA;QACA;KACD,CAACD,GAAG,CAAC,CAACC,OAAU,CAAA;YAAEC,OAAO;YAAcD;QAAK,CAAA;OAC1C;QACD;QACA;QACA;QACA;QACA;QACA;QACA;KACD,CAACD,GAAG,CAAC,CAACC,OAAU,CAAA;YAAEC,OAAO;YAAsBD;QAAK,CAAA;OAClD;QACD;QACA;QACA;QACA;QACA;QACA;KACD,CAACD,GAAG,CAAC,CAACC,OAAU,CAAA;YAAEC,OAAO;YAAUD;QAAK,CAAA;OACtC;QACD;QACA;QACA;QACA;QACA;QACA;QACA;KACD,CAACD,GAAG,CAAC,CAACC,OAAU,CAAA;YAAEC,OAAO;YAAWD;QAAK,CAAA;CAC3C;AAED,MAAME,eAAe,CAACF,OACpBA,KAAKG,OAAO,CAAC,aAAa,CAACC,OAAOC,SAAmBA,OAAOC,WAAW;AACzE,MAAMC,eAAe,CAACP,OACpBA,KAAKG,OAAO,CAAC,UAAU,CAACE,SAAW,CAAC,CAAC,EAAEA,OAAOG,WAAW,IAAI;AAE/D,wEAAwE,GACxE,OAAO,SAASC,qBAAqBC,GAAW;IAC9C,MAAMC,SAAiC,CAAC;IACxC,KAAK,MAAMC,OAAOF,IAAIG,KAAK,CAAC,KAAM;QAChC,MAAMC,cAAcF,IAAIG,IAAI;QAC5B,IAAI,CAACD,aAAa;QAClB,MAAME,QAAQF,YAAYG,OAAO,CAAC;QAClC,IAAID,SAAS,GAAG;QAChB,MAAME,WAAWhB,aAAaY,YAAYK,KAAK,CAAC,GAAGH,OAAOD,IAAI;QAC9D,MAAMK,QAAQN,YAAYK,KAAK,CAACH,QAAQ,GAAGD,IAAI;QAC/C,IAAIG,YAAYE,OAAOT,MAAM,CAACO,SAAS,GAAGE;IAC5C;IACA,OAAOT;AACT;AAEA;;;;;;;;;;;;;;;;;CAiBC,GACD,OAAO,SAASU,sBACdC,EAAmC,EACnCC,UAA+B;IAE/B,MAAMC,SAASlD,gBAAiBgD,aAAAA,KAAM,CAAC,GAA2B;QAChEG,MAAM;IACR;IACA,MAAMC,MAAkD,EAAE;IAC1D,KAAK,MAAMR,YAAYS,OAAOC,IAAI,CAACJ,QAAS;QAC1C,MAAMJ,QAAQzB,YAAY6B,QAAQN,UAAUK;QAC5C,IAAIH,UAAUS,aAAaT,UAAU,MAAM;QAC3C,IAAI,OAAOA,UAAU,UAAU,UAAS,8BAA8B;QACtEM,IAAII,IAAI,CAAC;YAAEZ;YAAUE,OAAOW,OAAOX;QAAO;IAC5C;IACA,OAAOM;AACT;AAEA,wEAAwE,GACxE,OAAO,SAASM,yBACdV,EAAmC,EACnCC,UAA+B;IAE/B,OAAOF,sBAAsBC,IAAIC,YAC9BxB,GAAG,CAAC,CAAC,EAAEmB,QAAQ,EAAEE,KAAK,EAAE,GAAK,GAAGb,aAAaW,UAAU,EAAE,EAAEE,MAAM,CAAC,CAAC,EACnEa,IAAI,CAAC;AACV;AAEA;;;;;;;;;;;;;;CAcC,GACD,OAAO,SAASC,oBACdhB,QAAgB,EAChBE,KAAyB;IAEzB,MAAMe,YAAY9D,sBAAsB6C;IACxC,MAAMkB,aACJD,UAAUE,MAAM,KAAK,KAAKjB,UAAUS,aAAatD,gBAAgB6C;IACnE,IAAI,CAACe,UAAUE,MAAM,IAAI,CAACD,YAAY,OAAO;QAAE,CAAClB,SAAS,EAAEE;IAAM;IACjE,OAAOO,OAAOW,WAAW,CAACH,UAAUpC,GAAG,CAAC,CAACC,OAAS;YAACA;YAAMoB;SAAM;AACjE;AAEA;;;;;;;;;;;;;CAaC,GACD,OAAO,SAASmB,oBACdjB,EAAmC,EACnCkB,KAAyC,EACzCjB,UAA+B;IAE/B,MAAMkB,UAAmC,CAAC;IAC1C,KAAK,MAAM,CAACvB,UAAUE,MAAM,IAAIO,OAAOe,OAAO,CAACF,OAAQ;QACrD,IAAI,CAACtB,UAAU;QACfS,OAAOgB,MAAM,CACXF,SACAP,oBAAoBhB,UAAUE,UAAU,KAAKS,YAAYT;IAE7D;IACA,OAAOxB,sBAAsB0B,IAAImB,SAASlB,YAAY;AACxD;AAEA;;;;;;;;;;;;;;;;;;;;;;;;CAwBC,GACD,MAAMqB,gBAAgB,IAAIC;AAE1B,MAAMC,WAAW,CACfC,QACAC,aACAC,OACW;QAACF;QAAQC;QAAaC;KAAK,CAAChB,IAAI,CAAC;AAE9C,4EAA4E,GAC5E,OAAO,SAASiB;IACdN,cAAcO,KAAK;AACrB;AAEA;;;;;;;;;;;;;;CAcC,GACD,OAAO,SAASC,iBAAiBC,IAAY;IAC3C,MAAMC,UAAU,CAACD,eAAAA,OAAQ,IAAItC,IAAI;IACjC,IAAI,CAACuC,SAAS;QACZ,OAAO;IACT;IACA,IAAIC;IACJ,IAAI;QACFA,SAASC,KAAKC,KAAK,CAACH;IACtB,EAAE,eAAM;QACN,OAAO;IACT;IACA,oEAAoE;IACpE,wEAAwE;IACxE,+BAA+B;IAC/B,IAAI,OAAOC,WAAW,YAAYA,WAAW,QAAQG,MAAMC,OAAO,CAACJ,SAAS;QAC1E,OAAO;IACT;IACA,OAAO;AACT;AAgBA;;;;;;CAMC,GACD,OAAO,MAAMK,gBAAgBzE,SAAS,CAAC0E;QASrBC,kBA8VMC;IAtWtB,MAAM,EAAEC,IAAI,EAAEzC,UAAU,EAAEyB,WAAW,EAAE,GAAGa;IAC1C,qEAAqE;IACrE,wEAAwE;IACxE,yDAAyD;IACzD,MAAMC,SAASxE,QACb,IAAMO,mBAAmBmE,MAAMhB,cAC/B;QAACgB;QAAMhB;KAAY;IAErB,MAAMiB,UAAUH,aAAAA,OAAOxC,EAAE,YAATwC,aAAa,CAAC;IAC9B,MAAM,CAACb,MAAMiB,QAAQ,GAAG3E,SAAqC;IAC7D,MAAM,CAAC4E,UAAUC,YAAY,GAAG7E,SAAS;IACzC,MAAM,CAAC8E,WAAWC,aAAa,GAAG/E,SAAS;IAC3C,MAAM,CAACwE,OAAOQ,SAAS,GAAGhF,SAAwB;IAElD,MAAMiF,OAAOlF,QACX,IAAM+B,sBAAsB4C,QAAQ1C,aACpC,uDAAuD;IACvD;QAACiC,KAAKiB,SAAS,CAACR;QAAS1C;KAAW;IAEtC,MAAM,CAACmD,UAAUC,YAAY,GAAGpF,SAAqB;QACnD2B,UAAU;QACVE,OAAO;IACT;IAEA,wEAAwE;IACxE,yEAAyE;IACzE,uEAAuE;IACvE,uEAAuE;IACvE,gEAAgE;IAChE,MAAM,CAACwD,aAAaC,eAAe,GAAGrF,qBAAqB;IAC3D,MAAMsF,UAAU1F,YACd,CAAC8B,WACCzB,eAAeyB,UAAUA,UAAU;YACjC6B,MAAM,EAAEiB,wBAAAA,KAAMe,GAAG;YACjBC,OAAO;YACPzD;YACA,6DAA6D;YAC7D0D,aAAa;gBAAE,CAAC/D,SAAS,EAAE;YAAK;YAChC0D;YACAM,UAAU,CAACpB,SACTe,eAAe,CAACM,UAAYzF,iBAAiByF,SAASrB;QAC1D,IACF;QAACE;QAAMzC;QAAYqD;QAAaC;KAAe;IAGjD,MAAM9B,iBAASiB,wBAAAA,KAAMe,GAAG,mBAAI;IAC5B,2DAA2D,GAC3D,MAAMK,YAAY9F,QAChB,IAAM0C,yBAAyBiC,QAAQ1C,aACvC,uDAAuD;IACvD;QAACiC,KAAKiB,SAAS,CAACR;QAAS1C;KAAW;IAEtC,MAAM8D,aAAa/F,QACjB,IAAMkE,KAAKiB,SAAS,CAACR,QAAQ,MAAM,IACnC,uDAAuD;IACvD;QAACT,KAAKiB,SAAS,CAACR;KAAQ;IAG1B;;;;;GAKC,GACD,MAAMqB,gBAAgBlG,YACpB,CAACmG,WAA2BnE,OAAeoE;QACzC,MAAMC,MAAM3C,SAASC,QAAQe,OAAOd,WAAW,EAAEuC;QACjD,IAAInE,UAAUoE,QAAQ5C,cAAc8C,MAAM,CAACD;aACtC7C,cAAc+C,GAAG,CAACF,KAAKrE;IAC9B,GACA;QAAC2B;QAAQe,OAAOd,WAAW;KAAC;IAG9B3D,UAAU;QACR,oEAAoE;QACpE,sEAAsE;QACtE,sEAAsE;QACtE,oEAAoE;QACpE,MAAMuG,aAAahD,cAAciD,GAAG,CAClC/C,SAASC,QAAQe,OAAOd,WAAW,EAAE;QAEvC,MAAM8C,cAAclD,cAAciD,GAAG,CACnC/C,SAASC,QAAQe,OAAOd,WAAW,EAAE;QAEvCoB,YAAYwB,eAAe/D,YAAY+D,aAAaR;QACpDd,aAAawB,gBAAgBjE,YAAYiE,cAAcT;QACvDd,SAAS;IACT,sEAAsE;IACtE,kEAAkE;IAClE,6DAA6D;IAC7D,uDAAuD;IACzD,GAAG;QAACtB;QAAMF;QAAQe,OAAOd,WAAW;KAAC;IAErC;;;;GAIC,GACD,MAAM+C,cAAczG,QAClB,IAAO2D,SAAS,SAASG,iBAAiBiB,aAAa,MACvD;QAACpB;QAAMoB;KAAU;IAGnB,yEAAyE;IACzE,0EAA0E;IAC1E,oEAAoE;IACpE,sEAAsE;IACtE,0EAA0E;IAC1E,0EAA0E;IAC1E,uEAAuE;IACvE,uEAAuE;IACvE,6BAA6B;IAC7B,MAAM2B,kBAAkB1G,QAAkB;QACxC,IAAI2D,SAAS,OAAO,OAAOhF,mCAAmCkG;QAC9D,IAAIlB,SAAS,QAAQ;YACnB,IAAI;gBACF,OAAO/E,kCAAkCsF,KAAKC,KAAK,CAACY,aAAa;YACnE,EAAE,eAAM;gBACN,+DAA+D;gBAC/D,OAAOpG,mCAAmCoG;YAC5C;QACF;QACA,OAAOnG,kCAAkC+F;IACzC,uDAAuD;IACzD,GAAG;QAAChB;QAAMkB;QAAUE;QAAWb,KAAKiB,SAAS,CAACR;KAAQ;IAEtD,MAAMgC,gBAAgB7G,YACpB,CAAC8B,UAAkBE;QACjB,IAAI,CAAC4C,QAAQ,CAAC9C,UAAU;QACxB,qEAAqE;QACrE,iEAAiE;QACjE,sEAAsE;QACtE,mEAAmE;QACnE,6CAA6C;QAC7ClD,OAAOkI,QAAQ,CACb;gBAGOpC;YAFLA,OAAOqC,KAAK,CACV5D,qBACGuB,aAAAA,OAAOxC,EAAE,YAATwC,aAAa,CAAC,GACf;gBAAE,CAAC5C,SAAS,EAAEE;YAAM,GACpBG;QAGN,GACA;YAAC;YAAMyC,KAAKe,GAAG;YAAExD,qBAAAA,aAAc;YAAQL;SAAS,CAACe,IAAI,CAAC;IAE1D,GACA;QAAC+B;QAAMF;QAAQvC;KAAW;IAG5B,MAAM6E,WAAWhH,YAAY;QAC3B,IAAI,CAAC4E,MAAM;QACX,MAAMT,SAAS9C,qBAAqB0D;QACpC,qEAAqE;QACrE,sCAAsC;QACtCnG,OAAOkI,QAAQ,CAAC;gBAaTpC;YAZL,kEAAkE;YAClE,oEAAoE;YACpE,qEAAqE;YACrE,mEAAmE;YACnE,8DAA8D;YAC9D,MAAMtB,QAA4C,CAAC;YACnD,KAAK,MAAM6D,OAAO7B,KAAM;gBACtB,IAAI,CAAE6B,CAAAA,IAAInF,QAAQ,IAAIqC,MAAK,GAAIf,KAAK,CAAC6D,IAAInF,QAAQ,CAAC,GAAGW;YACvD;YACAF,OAAOgB,MAAM,CAACH,OAAOe;YACrBO,OAAOqC,KAAK,CACV5D,qBACGuB,aAAAA,OAAOxC,EAAE,YAATwC,aAAa,CAAC,GACftB,OACAjB;QAGN;QACAgD,SAAS;QACT,wCAAwC;QACxC3B,cAAc8C,MAAM,CAAC5C,SAASC,QAAQe,OAAOd,WAAW,EAAE;IAC5D,GAAG;QAACgB;QAAMF;QAAQK;QAAUK;QAAMjD;QAAYwB;KAAO;IAErD;;;;;;;;;;GAUC,GACD,MAAMuD,YAAYlH,YAAY;QAC5B,IAAI,CAAC4E,MAAM;QACX,MAAMuC,UAAUnD,iBAAiBiB;QACjC,IAAIkC,YAAY,MAAM;YACpBhC,SAASgC;YACT;QACF;QACA,qEAAqE;QACrE,uEAAuE;QACvE,4DAA4D;QAC5D,MAAMhD,SAASC,KAAKC,KAAK,CAACY,UAAUtD,IAAI;QACxC,wEAAwE;QACxE,sEAAsE;QACtE/C,OAAOkI,QAAQ,CAAC;YACdpC,OAAOqC,KAAK,CAAC5C;QACf;QACAgB,SAAS;QACT3B,cAAc8C,MAAM,CAAC5C,SAASC,QAAQe,OAAOd,WAAW,EAAE;IAC5D,GAAG;QAACgB;QAAMF;QAAQO;QAAWtB;KAAO;IAEpC,qBACE,MAACjE;QAAM0H,SAAS;;0BACd,MAACvH;gBACCwH,MAAK;gBACLC,SAAS;gBACTC,SAAS;gBACTvF,OAAO6B;gBACP2D,UAAU,CAACC,OAAOzF,QAAUA,SAAS8C,QAAQ9C;;kCAE7C,KAACpC;wBAAaoC,OAAM;kCAAW;;kCAC/B,KAACpC;wBAAaoC,OAAM;kCAAO;;kCAC3B,KAACpC;wBAAaoC,OAAM;kCAAQ;;;;YAG7B6B,SAAS,0BACR,MAACnE;gBAAM0H,SAAS;;oBACbhC,KAAKzE,GAAG,CAAC,CAACsG;wBACT,MAAMS,OAAOhC,QAAQuB,IAAInF,QAAQ;wBACjC,qBACE,MAACpC;4BAECiI,WAAU;4BACVP,SAAS;4BACTlF,IAAI;gCACF;oCAAE0F,YAAY;gCAAS;gCACvBF,CAAAA,wBAAAA,KAAMG,KAAK,IAAGxI,qBAAqB;6BACpC;;8CAED,KAACM;oCACC0H,MAAK;oCACLrF,OAAOiF,IAAInF,QAAQ;oCACnBgG,QAAQ;oCACR5F,IAAI;wCAAE6F,MAAM;oCAAE;;8CAEhB,KAACpI;oCACC0H,MAAK;oCACLrF,OAAOiF,IAAIjF,KAAK;oCAChBwF,UAAU,CAACC,QACTZ,cAAcI,IAAInF,QAAQ,EAAE2F,MAAM/C,MAAM,CAAC1C,KAAK;oCAEhDE,IAAI;wCAAE6F,MAAM;oCAAE;;8CAKhB,KAACzI;oCAAgBoI,MAAMA;;8CACvB,KAACjI;oCACC4H,MAAK;oCACLW,cAAY,CAAC,OAAO,EAAEf,IAAInF,QAAQ,EAAE;oCACpCmG,SAAS,IAAMpB,cAAcI,IAAInF,QAAQ,EAAEW;8CAE3C,cAAA,KAACrD;wCAAQ8I,MAAMnJ,SAASmJ,IAAI;wCAAEb,MAAM;;;;2BA/BjCJ,IAAInF,QAAQ;oBAmCvB;kCACA,MAACpC;wBAAMiI,WAAU;wBAAMP,SAAS;wBAAGlF,IAAI;4BAAE0F,YAAY;wBAAS;;0CAC5D,KAACrI;gCACC4I,QAAQ;gCACRd,MAAK;gCACLe,SAAS1H;gCACT2H,SAAS,CAACC,SACR,OAAOA,WAAW,WAAW,KAAKA,OAAOzH,KAAK;gCAEhD0H,gBAAgB,CAACD,SACf,OAAOA,WAAW,WAAWA,SAASA,OAAO1H,IAAI;gCAEnD4H,YAAYlD,SAASxD,QAAQ;gCAC7B2G,eAAe,CAAChB,OAAOzF,QACrBuD,YAAY,CAACmD,OAAU,aAAKA;4CAAM5G,UAAUE;;gCAE9C2G,aAAa,CAACC,uBACZ,KAACjJ,wBAAciJ;wCAAQC,aAAY;;gCAErC3G,IAAI;oCAAE6F,MAAM;gCAAE;;0CAEhB,KAACpI;gCACC0H,MAAK;gCACLwB,aAAY;gCACZ7G,OAAOsD,SAAStD,KAAK;gCACrBwF,UAAU,CAACC,QACTlC,YAAY,CAACmD,OAAU,aAAKA;4CAAM1G,OAAOyF,MAAM/C,MAAM,CAAC1C,KAAK;;gCAE7DE,IAAI;oCAAE6F,MAAM;gCAAE;;0CAEhB,KAACtI;gCACC4H,MAAK;gCACLW,cAAW;gCACXF,UAAU,CAACxC,SAASxD,QAAQ,CAACH,IAAI,MAAM,CAAC2D,SAAStD,KAAK,CAACL,IAAI;gCAC3DsG,SAAS;oCACPpB,cACE/F,aAAawE,SAASxD,QAAQ,CAACH,IAAI,KACnC2D,SAAStD,KAAK,CAACL,IAAI;oCAErB4D,YAAY;wCAAEzD,UAAU;wCAAIE,OAAO;oCAAG;gCACxC;0CAEA,cAAA,KAAC5C;oCAAQ8I,MAAMlJ,QAAQkJ,IAAI;oCAAEb,MAAM;;;;;;iBAIvC;YAEHxD,SAAS,sBACR,MAACnE;gBAAM0H,SAAS;;kCACd,KAACzH;wBACCmJ,SAAS;wBACTC,SAAS;wBACT/G,OAAO+C;wBACPyC,UAAU,CAACC;4BACTzC,YAAYyC,MAAM/C,MAAM,CAAC1C,KAAK;4BAC9BkE,cAAc,OAAOuB,MAAM/C,MAAM,CAAC1C,KAAK,EAAEgE;wBAC3C;wBACA6C,aACE;wBAEFG,WAAW;4BACTC,WAAW;gCACTC,OAAO;oCAAEC,YAAY;oCAAaC,UAAU;gCAAG;4BACjD;wBACF;;kCAEF,KAAC5J;wBAAO6H,MAAK;wBAAQgC,SAAQ;wBAAWpB,SAASjB;kCAC9C;;;iBAGH;YAEHnD,SAAS,uBACR,MAACnE;gBAAM0H,SAAS;;kCACd,KAACtH;wBAAWuJ,SAAQ;wBAAUC,OAAM;kCACjC,oDACC;;kCAEJ,KAAC3J;wBACCmJ,SAAS;wBACTC,SAAS;wBACT/G,OAAOiD;wBACPuC,UAAU,CAACC;4BACTvC,aAAauC,MAAM/C,MAAM,CAAC1C,KAAK;4BAC/BkE,cAAc,QAAQuB,MAAM/C,MAAM,CAAC1C,KAAK,EAAEiE;4BAC1C,8DAA8D;4BAC9D,6DAA6D;4BAC7D,IAAItB,SAASX,iBAAiByD,MAAM/C,MAAM,CAAC1C,KAAK,MAAM,MAAM;gCAC1DmD,SAAS;4BACX;wBACF;wBACAR,OAAO4E,QAAQ5E;wBACf6E,UAAU,GAAE7E,QAAAA,gBAAAA,QAASgC,uBAAThC,QAAwBlC;wBACpCuG,WAAW;4BACTC,WAAW;gCACTC,OAAO;oCAAEC,YAAY;oCAAaC,UAAU;gCAAG;4BACjD;wBACF;;kCAEF,KAAC5J;wBAAO6H,MAAK;wBAAQgC,SAAQ;wBAAWpB,SAASf;kCAC9C;;;iBAGH;YAEHN,gBAAgB3D,MAAM,GACrB,mEAAmE;YACnE,kEAAkE;YAClE,sDAAsD;0BACtD,KAACnD;gBAAWuJ,SAAQ;gBAAUC,OAAM;0BACjC,iEACC,CAAC,aAAa,EAAE1C,gBAAgB/D,IAAI,CAAC,MAAM,mBAAmB,CAAC,GAC/D,kEACA;iBAEF;;;AAGV,GAAE;AACF2B,cAAciF,WAAW,GAAG;AAE5B,eAAejF,cAAa"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/besigner/feature/designer/src/lib/components/custom-css-form.component.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 type * as Aglyn from '@aglyn/aglyn'\nimport {\n canvas,\n collectThirdPartyAuthorCssUrlHosts,\n collectThirdPartyAuthorSxUrlHosts,\n} from '@aglyn/aglyn'\nimport { mdiClose, mdiPlus } from '@aglyn/shared-data-mdi'\nimport {\n canonicalSxProperties,\n expandSxAliases,\n isAtomicSxValue,\n} from '@aglyn/shared-data-enums'\nimport { MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { FIELD_MUTED_STYLES, FieldMuteButton } from '@aglyn/shared-ui-jsx-forms'\nimport {\n Autocomplete,\n Button,\n IconButton,\n Stack,\n TextField,\n ToggleButton,\n ToggleButtonGroup,\n Typography,\n} from '@mui/material'\nimport { observer } from 'mobx-react-lite'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport useAglynBesignerFlag from '../hooks/use-aglyn-besigner-flag'\nimport { buildStyleMute, toggleMutedStyle } from '../utils/muted-styles'\nimport type { SxBreakpoint } from '../utils/responsive-sx'\nimport { readSxValue } from '../utils/responsive-sx'\nimport { applyStylePartialToSx } from '../utils/style-field-groups'\nimport { getNodeStyleTarget } from '../utils/style-target'\n\n/**\n * Curated, grouped suggestions for the builder's property picker\n * (freeSolo, so any CSS/sx property still works).\n */\nconst CSS_PROPERTY_SUGGESTIONS: Array<{ group: string; name: string }> = [\n ...[\n 'width',\n 'height',\n 'minWidth',\n 'maxWidth',\n 'minHeight',\n 'maxHeight',\n 'overflow',\n 'position',\n 'top',\n 'right',\n 'bottom',\n 'left',\n 'zIndex',\n ].map((name) => ({ group: 'Layout', name })),\n ...['margin', 'padding', 'gap', 'rowGap', 'columnGap'].map((name) => ({\n group: 'Spacing',\n name,\n })),\n ...[\n 'fontSize',\n 'fontWeight',\n 'fontFamily',\n 'lineHeight',\n 'letterSpacing',\n 'textTransform',\n 'textDecoration',\n 'whiteSpace',\n ].map((name) => ({ group: 'Typography', name })),\n ...[\n 'color',\n 'backgroundColor',\n 'background',\n 'backgroundImage',\n 'backgroundSize',\n 'backgroundPosition',\n 'opacity',\n ].map((name) => ({ group: 'Background & color', name })),\n ...[\n 'border',\n 'borderRadius',\n 'borderColor',\n 'borderWidth',\n 'borderStyle',\n 'outline',\n ].map((name) => ({ group: 'Border', name })),\n ...[\n 'boxShadow',\n 'transform',\n 'transition',\n 'filter',\n 'cursor',\n 'objectFit',\n 'aspectRatio',\n ].map((name) => ({ group: 'Effects', name })),\n]\n\nconst kebabToCamel = (name: string) =>\n name.replace(/-([a-z])/g, (match, letter: string) => letter.toUpperCase())\nconst camelToKebab = (name: string) =>\n name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)\n\n/** Parses `prop: value;` CSS declarations into a JSS record (AGL-332). */\nexport function parseCssDeclarations(css: string): Record<string, string> {\n const result: Record<string, string> = {}\n for (const raw of css.split(';')) {\n const declaration = raw.trim()\n if (!declaration) continue\n const colon = declaration.indexOf(':')\n if (colon <= 0) continue\n const property = kebabToCamel(declaration.slice(0, colon).trim())\n const value = declaration.slice(colon + 1).trim()\n if (property && value) result[property] = value\n }\n return result\n}\n\n/**\n * The scalar declarations the Builder rows and the CSS tab show for `sx`,\n * named in the Styles panel's own spelling (AGL-2390).\n *\n * MUI honours two names for the same declaration, and every other control\n * in this panel is named for the CSS longhand (AGL-2207). Listing the raw\n * keys made this form the one place that said `bgcolor` where the Colors\n * group said `backgroundColor` and BoxStyler said `paddingTop` — the same\n * node, two vocabularies, and the author with no way to tell that the two\n * rows were one declaration. The expansion is a pure renaming of what\n * already renders (order preserved, non-atomic values left alone), and it\n * is what makes the row's × and the CSS tab's clear reach the key that is\n * actually painting.\n *\n * The JSS tab deliberately does NOT go through here: it edits the stored\n * document verbatim, which is the escape hatch for everything the named\n * controls cannot express.\n */\nexport function customCssDeclarations(\n sx: Record<string, any> | undefined,\n breakpoint: SxBreakpoint | null,\n): Array<{ property: string; value: string }> {\n const source = expandSxAliases((sx ?? {}) as Record<string, any>, {\n deep: true,\n })\n const out: Array<{ property: string; value: string }> = []\n for (const property of Object.keys(source)) {\n const value = readSxValue(source, property, breakpoint)\n if (value === undefined || value === null) continue\n if (typeof value === 'object') continue // selectors/nested — JSON tab\n out.push({ property, value: String(value) })\n }\n return out\n}\n\n/** Serializes scalar sx entries into CSS declarations for the CSS tab. */\nexport function serializeCssDeclarations(\n sx: Record<string, any> | undefined,\n breakpoint: SxBreakpoint | null,\n): string {\n return customCssDeclarations(sx, breakpoint)\n .map(({ property, value }) => `${camelToKebab(property)}: ${value};`)\n .join('\\n')\n}\n\n/**\n * The declaration(s) one named property writes, in the panel's spelling.\n *\n * The property field is free-solo and the CSS tab takes any declaration, so\n * an author can type `bgcolor` or `py` at any time — this form is the one\n * seam in the product where a fresh alias can still enter a document. It\n * resolves the name the same way {@link expandSxAliases} resolves a stored\n * one, so what the author typed and what the panel reads back agree.\n *\n * A value the expansion cannot carry per-side — `p: '10px 20px'`, the CSS\n * shorthand's own syntax — keeps the name the author gave it rather than\n * being copied onto four longhands CSS would drop. Clearing always expands,\n * because a cleared alias has to take every longhand it was painting with\n * it.\n */\nexport function canonicalCssPartial(\n property: string,\n value: string | undefined,\n): Record<string, unknown> {\n const canonical = canonicalSxProperties(property)\n const expandable =\n canonical.length === 1 || value === undefined || isAtomicSxValue(value)\n if (!canonical.length || !expandable) return { [property]: value }\n return Object.fromEntries(canonical.map((name) => [name, value]))\n}\n\n/**\n * Applies custom-CSS edits to `sx` — the write both the Builder rows and\n * the CSS tab's Apply go through (AGL-2390).\n *\n * Routed through the Styles panel's own write seam so this form cannot\n * drift from it again: {@link applyStylePartialToSx} expands the alias keys\n * the edit COLLIDES with before writing, which is what makes clearing a\n * declaration actually clear (deleting `backgroundColor` while a `bgcolor`\n * underneath keeps painting is AGL-2207's sharper failure) and what stops a\n * write leaving two spellings of one declaration in the record.\n *\n * Scheme-agnostic (`null`): this form has always written the base slice,\n * and the dark-preview scoping belongs to the named color fields.\n */\nexport function applyCustomCssEdits(\n sx: Record<string, any> | undefined,\n edits: Record<string, string | undefined>,\n breakpoint: SxBreakpoint | null,\n): Record<string, any> {\n const partial: Record<string, unknown> = {}\n for (const [property, value] of Object.entries(edits)) {\n if (!property) continue\n Object.assign(\n partial,\n canonicalCssPartial(property, value === '' ? undefined : value),\n )\n }\n return applyStylePartialToSx(sx, partial, breakpoint, null)\n}\n\n/**\n * Un-applied CSS / JSS text, held ACROSS an unmount of this form (AGL-2486).\n *\n * The JSS tab is the one control in the Styles panel whose work lives only\n * in React state until Apply — and this form is mounted under\n * `withLastSelectedNode`, which renders \"Select an element\" the instant the\n * canvas selection empties. Anything that clears the selection therefore\n * UNMOUNTS the editor, and a `useState('')` buffer takes the author's\n * half-typed sx document with it. Measured on a live besigner: a selection\n * held with no keyboard or pointer input at all lost the editor 46s in, so\n * this is not something the author can avoid by typing carefully — it is\n * why the wipe reads as random, and why it gets blamed on whichever key was\n * pressed last.\n *\n * Keyed by node AND style target, so a component instance's root and its\n * leaves each keep their own pending text, and re-selecting the element\n * hands the author back exactly what they had typed. An entry lives only\n * while the text DIFFERS from the stored document: applying it, or typing\n * it back to what sx already says, drops the entry, so this never resurrects\n * a draft the author has already resolved.\n *\n * Deliberately not persisted beyond the page: it is un-applied text, not a\n * document, and the crash-recovery draft store is the thing that owns\n * surviving a reload.\n */\nconst pendingDrafts = new Map<string, string>()\n\nconst draftKey = (\n nodeId: string,\n overrideKey: string,\n mode: 'css' | 'json',\n): string => [nodeId, overrideKey, mode].join('\\x00')\n\n/** Drops every pending draft — test seam, and a hard reset for the canvas. */\nexport function resetPendingCustomCssDrafts(): void {\n pendingDrafts.clear()\n}\n\n/**\n * Why a JSS buffer cannot be applied, or `null` when it is ready\n * (AGL-2486). Rendered live as the author types, so \"nothing happened when\n * I pressed Apply\" is never the first sign that the text does not parse.\n *\n * Mid-edit JSON is TRANSIENTLY invalid by nature — `{\"a\": 1,` is what every\n * new property looks like a keystroke before it is finished — so this is a\n * hint, never a block on typing and never a reason to touch the document.\n *\n * An empty buffer is called out rather than treated as `{}`: `JSON.parse`\n * would need a fallback to accept it at all, and silently reading \"\" as\n * \"delete every style on this element\" is the most destructive thing this\n * panel could do with an accidental Select-All. Clearing all styles stays\n * available, spelled `{}`, which nobody types by accident.\n */\nexport function jsonDraftProblem(text: string): string | null {\n const trimmed = (text ?? '').trim()\n if (!trimmed) {\n return 'Empty — type {} to clear every style on this element.'\n }\n let parsed: unknown\n try {\n parsed = JSON.parse(trimmed)\n } catch {\n return 'Not valid JSON yet — Apply will not change anything until it parses.'\n }\n // Explicit `=== null` and an array check: `strictNullChecks` is off\n // repo-wide, so `!parsed` would fold `null`, `0` and `\"\"` together, and\n // `typeof null` is `'object'`.\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n return 'The sx value must be a JSON object.'\n }\n return null\n}\n\nexport interface CustomCssFormProps {\n node?: Aglyn.NodeSchema\n /** Active artboard breakpoint; builder/CSS edits scope to it (AGL-333). */\n breakpoint: SxBreakpoint | null\n /**\n * Which `styleOverrides` slice to edit on a component instance\n * (AGL-1332) — the Styles panel's current style target. Ignored for a\n * plain node; defaults to the component root.\n */\n overrideKey?: string\n}\n\ntype BuilderRow = { property: string; value: string }\n\n/**\n * Custom CSS for the selected element's `sx` (AGL-332): a friendly\n * builder (property + value rows), a raw CSS mode, and a raw JSS/JSON\n * mode. Builder and CSS edits respect the active breakpoint scope; the\n * JSON mode edits the full sx document (including responsive objects and\n * nested selectors) verbatim.\n */\nexport const CustomCssForm = observer((props: CustomCssFormProps) => {\n const { node, breakpoint, overrideKey } = props\n // Same target the rest of the Styles panel writes (AGL-1306/1332): a\n // plain node's own sx, or the instance override slice the panel's style\n // target names — root, or one leaf inside the component.\n const target = useMemo(\n () => getNodeStyleTarget(node, overrideKey),\n [node, overrideKey],\n )\n const nodeSx = (target.sx ?? {}) as Record<string, any>\n const [mode, setMode] = useState<'builder' | 'css' | 'json'>('builder')\n const [cssDraft, setCssDraft] = useState('')\n const [jsonDraft, setJsonDraft] = useState('')\n const [error, setError] = useState<string | null>(null)\n\n const rows = useMemo<BuilderRow[]>(\n () => customCssDeclarations(nodeSx, breakpoint),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [JSON.stringify(nodeSx), breakpoint],\n )\n const [draftRow, setDraftRow] = useState<BuilderRow>({\n property: '',\n value: '',\n })\n\n // Declarations switched off for comparison (AGL-2486). Canvas only, and\n // the same flag every other row in the panel writes, so a property muted\n // here and the same property muted from its typed field are one state.\n // The builder lists BASE scalar declarations only — a state slice is a\n // nested object it skips — so these mutes carry no state scope.\n const [mutedStyles, setMutedStyles] = useAglynBesignerFlag('mutedStyles')\n const rowMute = useCallback(\n (property: string) =>\n buildStyleMute(property, property, {\n nodeId: node?.$id,\n state: null,\n breakpoint,\n // Every row the builder lists is, by construction, declared.\n scopeValues: { [property]: true },\n mutedStyles,\n onToggle: (target) =>\n setMutedStyles((current) => toggleMutedStyle(current, target)),\n }),\n [node, breakpoint, mutedStyles, setMutedStyles],\n )\n\n const nodeId = node?.$id ?? ''\n /** What the STORED document says, in each tab's spelling. */\n const storedCss = useMemo(\n () => serializeCssDeclarations(nodeSx, breakpoint),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [JSON.stringify(nodeSx), breakpoint],\n )\n const storedJson = useMemo(\n () => JSON.stringify(nodeSx, null, 2),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [JSON.stringify(nodeSx)],\n )\n\n /**\n * Records (or drops) the author's un-applied text for one tab, so an\n * unmount cannot take it (AGL-2486). Text equal to the stored document is\n * not a draft at all, so it clears the entry rather than pinning a value\n * that would then shadow a later change from elsewhere in the panel.\n */\n const rememberDraft = useCallback(\n (draftMode: 'css' | 'json', value: string, stored: string) => {\n const key = draftKey(nodeId, target.overrideKey, draftMode)\n if (value === stored) pendingDrafts.delete(key)\n else pendingDrafts.set(key, value)\n },\n [nodeId, target.overrideKey],\n )\n\n useEffect(() => {\n // A pending draft wins over the stored document — that is the whole\n // point of keeping it. `!== undefined` rather than a truthiness test:\n // `''` is a legitimate buffer the author emptied on purpose, and with\n // `strictNullChecks` off `if (!pending)` would silently discard it.\n const pendingCss = pendingDrafts.get(\n draftKey(nodeId, target.overrideKey, 'css'),\n )\n const pendingJson = pendingDrafts.get(\n draftKey(nodeId, target.overrideKey, 'json'),\n )\n setCssDraft(pendingCss !== undefined ? pendingCss : storedCss)\n setJsonDraft(pendingJson !== undefined ? pendingJson : storedJson)\n setError(null)\n // Re-seeded on the override target too (AGL-1332): switching from the\n // component root to a leaf must reload the drafts, or a stale CSS\n // buffer would be written back onto the newly picked target.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [mode, nodeId, target.overrideKey])\n\n /**\n * Live \"is this appliable yet\" verdict for the JSS tab (AGL-2486) — a\n * caption only. Nothing here writes, so a buffer that stops parsing\n * mid-property changes the hint and leaves the document alone.\n */\n const jsonProblem = useMemo(\n () => (mode === 'json' ? jsonDraftProblem(jsonDraft) : null),\n [mode, jsonDraft],\n )\n\n // Passive off-site url() hint (AGL-1737) — the \"warn\" half of AGL-1725's\n // warn-and-disclose, for the panel with no per-field description to carry\n // it. Names the hosts a url() in the CURRENT value would make every\n // visitor's browser contact: the live draft in the CSS and JSON modes\n // (ahead of Apply, so the author reads it while typing), the committed sx\n // in the builder (whose rows write live). A caption and nothing more — it\n // never blocks a value, and it deliberately does not touch how or when\n // this form commits (an attribute panel edit commits on blur, and that\n // behavior is load-bearing).\n const thirdPartyHosts = useMemo<string[]>(() => {\n if (mode === 'css') return collectThirdPartyAuthorCssUrlHosts(cssDraft)\n if (mode === 'json') {\n try {\n return collectThirdPartyAuthorSxUrlHosts(JSON.parse(jsonDraft || '{}'))\n } catch {\n // Mid-edit JSON — scan the raw text so the hint tracks typing.\n return collectThirdPartyAuthorCssUrlHosts(jsonDraft)\n }\n }\n return collectThirdPartyAuthorSxUrlHosts(nodeSx)\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [mode, cssDraft, jsonDraft, JSON.stringify(nodeSx)])\n\n const writeProperty = useCallback(\n (property: string, value: string | undefined) => {\n if (!node || !property) return\n // Coalesced per property (AGL-1204): the builder rows write on every\n // keystroke, so typing one value is one undo step, and editing a\n // different property starts another. The write goes through the style\n // target (AGL-1306), so this is undoable on an instance's override\n // layer exactly as on a plain node's own sx.\n canvas.transact(\n () => {\n target.setSx(\n applyCustomCssEdits(\n (target.sx ?? {}) as Record<string, any>,\n { [property]: value },\n breakpoint,\n ),\n )\n },\n ['sx', node.$id, breakpoint ?? 'base', property].join(':'),\n )\n },\n [node, target, breakpoint],\n )\n\n const applyCss = useCallback(() => {\n if (!node) return\n const parsed = parseCssDeclarations(cssDraft)\n // Uncoalesced: pressing Apply is one deliberate commit, however many\n // declarations it carries (AGL-1204).\n canvas.transact(() => {\n // One edit map, applied once: declarations no longer in the draft\n // clear, the rest write. Both halves resolve their spelling through\n // `applyCustomCssEdits`, so a row the author deleted takes the alias\n // that was painting it with it, and a declaration they typed under\n // MUI's spelling lands where every other control can read it.\n const edits: Record<string, string | undefined> = {}\n for (const row of rows) {\n if (!(row.property in parsed)) edits[row.property] = undefined\n }\n Object.assign(edits, parsed)\n target.setSx(\n applyCustomCssEdits(\n (target.sx ?? {}) as Record<string, any>,\n edits,\n breakpoint,\n ),\n )\n })\n setError(null)\n // Applied text is no longer un-applied.\n pendingDrafts.delete(draftKey(nodeId, target.overrideKey, 'css'))\n }, [node, target, cssDraft, rows, breakpoint, nodeId])\n\n /**\n * Commits the JSS buffer — the ONLY path from this tab to the document\n * (AGL-2486).\n *\n * A buffer that does not parse to a JSON object is a NON-COMMIT: it sets\n * the inline error and returns, leaving both the document and the text\n * exactly as they are, so the author can keep editing from where they\n * were. There is no blur, change or unmount handler anywhere in this form\n * that reaches the document — Apply is the whole seam, which is what\n * makes a transiently invalid buffer harmless.\n */\n const applyJson = useCallback(() => {\n if (!node) return\n const problem = jsonDraftProblem(jsonDraft)\n if (problem !== null) {\n setError(problem)\n return\n }\n // Re-parsed rather than carried out of the guard: `jsonDraftProblem`\n // owns the verdict, and one function deciding while another commits is\n // how \"valid enough to pass, wrong shape to store\" gets in.\n const parsed = JSON.parse(jsonDraft.trim())\n // Replacing sx wholesale is the single most destructive edit this panel\n // offers, so it is the one that most needs to be undoable (AGL-1204).\n canvas.transact(() => {\n target.setSx(parsed)\n })\n setError(null)\n pendingDrafts.delete(draftKey(nodeId, target.overrideKey, 'json'))\n }, [node, target, jsonDraft, nodeId])\n\n return (\n <Stack spacing={1.5}>\n <ToggleButtonGroup\n size=\"small\"\n exclusive\n fullWidth\n value={mode}\n onChange={(event, value) => value && setMode(value)}\n >\n <ToggleButton value=\"builder\">{'Builder'}</ToggleButton>\n <ToggleButton value=\"css\">{'CSS'}</ToggleButton>\n <ToggleButton value=\"json\">{'JSS (sx)'}</ToggleButton>\n </ToggleButtonGroup>\n\n {mode === 'builder' ? (\n <Stack spacing={1}>\n {rows.map((row) => {\n const mute = rowMute(row.property)\n return (\n <Stack\n key={row.property}\n direction=\"row\"\n spacing={1}\n sx={[\n { alignItems: 'center' },\n mute?.muted ? FIELD_MUTED_STYLES : null,\n ]}\n >\n <TextField\n size=\"small\"\n value={row.property}\n disabled\n sx={{ flex: 1 }}\n />\n <TextField\n size=\"small\"\n value={row.value}\n onChange={(event) =>\n writeProperty(row.property, event.target.value)\n }\n sx={{ flex: 1 }}\n />\n {/* The eye stops the declaration applying and keeps its\n value; the ✕ beside it is still the only thing that\n takes the declaration off the element. */}\n <FieldMuteButton mute={mute} />\n <IconButton\n size=\"small\"\n aria-label={`Remove ${row.property}`}\n onClick={() => writeProperty(row.property, undefined)}\n >\n <MdiIcon path={mdiClose.path} size={0.7} />\n </IconButton>\n </Stack>\n )\n })}\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Autocomplete\n freeSolo\n size=\"small\"\n options={CSS_PROPERTY_SUGGESTIONS}\n groupBy={(option) =>\n typeof option === 'string' ? '' : option.group\n }\n getOptionLabel={(option) =>\n typeof option === 'string' ? option : option.name\n }\n inputValue={draftRow.property}\n onInputChange={(event, value) =>\n setDraftRow((prev) => ({ ...prev, property: value }))\n }\n renderInput={(params) => (\n <TextField {...params} placeholder=\"property\" />\n )}\n sx={{ flex: 1 }}\n />\n <TextField\n size=\"small\"\n placeholder=\"value\"\n value={draftRow.value}\n onChange={(event) =>\n setDraftRow((prev) => ({ ...prev, value: event.target.value }))\n }\n sx={{ flex: 1 }}\n />\n <IconButton\n size=\"small\"\n aria-label=\"Add property\"\n disabled={!draftRow.property.trim() || !draftRow.value.trim()}\n onClick={() => {\n writeProperty(\n kebabToCamel(draftRow.property.trim()),\n draftRow.value.trim(),\n )\n setDraftRow({ property: '', value: '' })\n }}\n >\n <MdiIcon path={mdiPlus.path} size={0.8} />\n </IconButton>\n </Stack>\n </Stack>\n ) : null}\n\n {mode === 'css' ? (\n <Stack spacing={1}>\n <TextField\n multiline\n minRows={5}\n value={cssDraft}\n onChange={(event) => {\n setCssDraft(event.target.value)\n rememberDraft('css', event.target.value, storedCss)\n }}\n placeholder={\n 'border-radius: 8px;\\nbox-shadow: 0 2px 8px rgba(0,0,0,0.2);'\n }\n slotProps={{\n htmlInput: {\n style: { fontFamily: 'monospace', fontSize: 12 },\n },\n }}\n />\n <Button size=\"small\" variant=\"outlined\" onClick={applyCss}>\n {'Apply CSS'}\n </Button>\n </Stack>\n ) : null}\n\n {mode === 'json' ? (\n <Stack spacing={1}>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {(target.isComposed\n ? 'The effective sx — supports responsive objects '\n : 'Full sx document — supports responsive objects ') +\n '({ xs, sm, … }) and nested selectors (\"&:hover\").'}\n </Typography>\n {target.isComposed ? (\n // The panel has to say this out loud (AGL-3218). Everything\n // below is correct and it is also not what the editor looks\n // like it is doing, and the author only finds out by reading\n // the stored node somewhere else — where the edit reads as\n // missing.\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'This element stores styles in two places: a props.sx its ' +\n 'component or preset authored, and its own sx. The box ' +\n 'shows both merged, the way the page renders them. Apply ' +\n 'writes only what you changed into the element’s own sx ' +\n 'and leaves the rest where it is, so a property you edit ' +\n 'here will not appear in props.sx.'}\n </Typography>\n ) : null}\n <TextField\n multiline\n minRows={6}\n value={jsonDraft}\n onChange={(event) => {\n setJsonDraft(event.target.value)\n rememberDraft('json', event.target.value, storedJson)\n // A keystroke that fixes the JSON clears a stale Apply error;\n // it never raises one, so typing is not nagged mid-property.\n if (error && jsonDraftProblem(event.target.value) === null) {\n setError(null)\n }\n }}\n error={Boolean(error)}\n helperText={error ?? jsonProblem ?? undefined}\n slotProps={{\n htmlInput: {\n style: { fontFamily: 'monospace', fontSize: 12 },\n },\n }}\n />\n <Button size=\"small\" variant=\"outlined\" onClick={applyJson}>\n {'Apply JSS'}\n </Button>\n </Stack>\n ) : null}\n\n {thirdPartyHosts.length ? (\n // Same voice as the Custom HTML `css` description (AGL-1737), with\n // the host named back so the consequence is attached to the value\n // the author typed rather than left as general prose.\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'A url() here points off your site — every visitor’s browser ' +\n `will contact ${thirdPartyHosts.join(', ')}, disclosing their ` +\n 'IP address and which page they are on. Insecure http:// URLs ' +\n 'are not loaded.'}\n </Typography>\n ) : null}\n </Stack>\n )\n})\nCustomCssForm.displayName = 'CustomCssForm'\n\nexport default CustomCssForm\n"],"names":["canvas","collectThirdPartyAuthorCssUrlHosts","collectThirdPartyAuthorSxUrlHosts","mdiClose","mdiPlus","canonicalSxProperties","expandSxAliases","isAtomicSxValue","MdiIcon","FIELD_MUTED_STYLES","FieldMuteButton","Autocomplete","Button","IconButton","Stack","TextField","ToggleButton","ToggleButtonGroup","Typography","observer","useCallback","useEffect","useMemo","useState","useAglynBesignerFlag","buildStyleMute","toggleMutedStyle","readSxValue","applyStylePartialToSx","getNodeStyleTarget","CSS_PROPERTY_SUGGESTIONS","map","name","group","kebabToCamel","replace","match","letter","toUpperCase","camelToKebab","toLowerCase","parseCssDeclarations","css","result","raw","split","declaration","trim","colon","indexOf","property","slice","value","customCssDeclarations","sx","breakpoint","source","deep","out","Object","keys","undefined","push","String","serializeCssDeclarations","join","canonicalCssPartial","canonical","expandable","length","fromEntries","applyCustomCssEdits","edits","partial","entries","assign","pendingDrafts","Map","draftKey","nodeId","overrideKey","mode","resetPendingCustomCssDrafts","clear","jsonDraftProblem","text","trimmed","parsed","JSON","parse","Array","isArray","CustomCssForm","props","target","error","node","nodeSx","setMode","cssDraft","setCssDraft","jsonDraft","setJsonDraft","setError","rows","stringify","draftRow","setDraftRow","mutedStyles","setMutedStyles","rowMute","$id","state","scopeValues","onToggle","current","storedCss","storedJson","rememberDraft","draftMode","stored","key","delete","set","pendingCss","get","pendingJson","jsonProblem","thirdPartyHosts","writeProperty","transact","setSx","applyCss","row","applyJson","problem","spacing","size","exclusive","fullWidth","onChange","event","mute","direction","alignItems","muted","disabled","flex","aria-label","onClick","path","freeSolo","options","groupBy","option","getOptionLabel","inputValue","onInputChange","prev","renderInput","params","placeholder","multiline","minRows","slotProps","htmlInput","style","fontFamily","fontSize","variant","color","isComposed","Boolean","helperText","displayName"],"mappings":"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"}
|
|
@@ -499,9 +499,9 @@ export const NodeLeaf = observer(/*#__PURE__*/ forwardRef((props, ref)=>{
|
|
|
499
499
|
* design the memo is `undefined`, the children render, and the form the
|
|
500
500
|
* author drew is the form they see — which is every form built before the
|
|
501
501
|
* entity existed.
|
|
502
|
-
*/ const
|
|
503
|
-
var _node_props;
|
|
504
|
-
var _node_props1
|
|
502
|
+
*/ const placedForm = useMemo(()=>{
|
|
503
|
+
var _node_props, _ref;
|
|
504
|
+
var _node_props1;
|
|
505
505
|
if ((node == null ? void 0 : node.componentId) !== FORM_COMPONENT_ID) return undefined;
|
|
506
506
|
const formId = node == null ? void 0 : (_node_props1 = node.props) == null ? void 0 : _node_props1.formId;
|
|
507
507
|
if (typeof formId !== 'string' || !formId) return undefined;
|
|
@@ -519,8 +519,25 @@ export const NodeLeaf = observer(/*#__PURE__*/ forwardRef((props, ref)=>{
|
|
|
519
519
|
definitions, [
|
|
520
520
|
placedFormPlacement(formDesigns)
|
|
521
521
|
]);
|
|
522
|
-
|
|
523
|
-
|
|
522
|
+
/*
|
|
523
|
+
* The composed REPLACEMENT is the node the published page draws in this
|
|
524
|
+
* placement's place — the design's root, carrying the placement's id
|
|
525
|
+
* (AGL-2521) — and its children are the design's own.
|
|
526
|
+
*
|
|
527
|
+
* Reading the replacement rather than the placement is what keeps the
|
|
528
|
+
* canvas honest about the element itself. A real form design is a
|
|
529
|
+
* container holding the `form` node, so the placement's `form` used to
|
|
530
|
+
* be drawn AROUND the design's: two `<form>` elements nested, each
|
|
531
|
+
* drawing its own send button, where a visitor sees one. Taking only
|
|
532
|
+
* the first child also dropped everything a design put beside its form.
|
|
533
|
+
*/ const replacement = composed[node.$id];
|
|
534
|
+
const childIds = (_ref = replacement == null ? void 0 : replacement.nodes) != null ? _ref : [];
|
|
535
|
+
if (!replacement || !childIds.length) return undefined;
|
|
536
|
+
const trees = childIds.map((childId)=>denormalizeTree(composed, childId)).filter(Boolean);
|
|
537
|
+
return trees.length ? {
|
|
538
|
+
replacement,
|
|
539
|
+
trees
|
|
540
|
+
} : undefined;
|
|
524
541
|
// Observable props: the JSON string keys the memo, as above.
|
|
525
542
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
526
543
|
}, [
|
|
@@ -529,6 +546,26 @@ export const NodeLeaf = observer(/*#__PURE__*/ forwardRef((props, ref)=>{
|
|
|
529
546
|
definitions,
|
|
530
547
|
formDesigns
|
|
531
548
|
]);
|
|
549
|
+
/**
|
|
550
|
+
* The element this leaf DRAWS, once a placed form resolves.
|
|
551
|
+
*
|
|
552
|
+
* The published page renders the entity's root here, not the placement:
|
|
553
|
+
* the two denote the same element and the graft merges them. The canvas
|
|
554
|
+
* draws the same one, so an author sees the form their visitors will. As
|
|
555
|
+
* with the DAM facts and the host tokens above, this is a render copy —
|
|
556
|
+
* selection, the panels and every save keep reading `node`.
|
|
557
|
+
*/ const shownLeafNode = useMemo(()=>{
|
|
558
|
+
var _placedForm_replacement_props;
|
|
559
|
+
return placedForm ? _extends({}, shownNode, {
|
|
560
|
+
componentId: placedForm.replacement.componentId,
|
|
561
|
+
props: (_placedForm_replacement_props = placedForm.replacement.props) != null ? _placedForm_replacement_props : {}
|
|
562
|
+
}, placedForm.replacement.sx === undefined ? {} : {
|
|
563
|
+
sx: placedForm.replacement.sx
|
|
564
|
+
}) : shownNode;
|
|
565
|
+
}, [
|
|
566
|
+
placedForm,
|
|
567
|
+
shownNode
|
|
568
|
+
]);
|
|
532
569
|
return /*#__PURE__*/ _jsx(DraggableDroppable, {
|
|
533
570
|
node: node,
|
|
534
571
|
type: Besigner.DragType.CANVAS,
|
|
@@ -536,7 +573,7 @@ export const NodeLeaf = observer(/*#__PURE__*/ forwardRef((props, ref)=>{
|
|
|
536
573
|
disableDragging: !Besigner.dnd.canDragNode(node),
|
|
537
574
|
children: /*#__PURE__*/ _jsxs(Leaf, _extends({
|
|
538
575
|
ref: registerElement,
|
|
539
|
-
node:
|
|
576
|
+
node: shownLeafNode,
|
|
540
577
|
"data-aglyn-selected": Besigner.focus.isNodeSelected(node),
|
|
541
578
|
// Present while the selection lives in this node's subtree (the
|
|
542
579
|
// node itself or any descendant). Canvas-aware components (nav
|
|
@@ -553,7 +590,7 @@ export const NodeLeaf = observer(/*#__PURE__*/ forwardRef((props, ref)=>{
|
|
|
553
590
|
"data-aglyn-bound": boundProps.length ? '' : undefined
|
|
554
591
|
}, rest, {
|
|
555
592
|
children: [
|
|
556
|
-
|
|
593
|
+
placedForm ? null : firstRecord ? // The template IS the first copy: the real children, drawing the
|
|
557
594
|
// first record. Provided around them rather than applied to them,
|
|
558
595
|
// so every descendant leaf resolves its own tokens the way the
|
|
559
596
|
// page's substitution walks the whole cloned subtree.
|
|
@@ -582,7 +619,7 @@ export const NodeLeaf = observer(/*#__PURE__*/ forwardRef((props, ref)=>{
|
|
|
582
619
|
label: repeatPreview.label,
|
|
583
620
|
count: repeatPreview.records.length
|
|
584
621
|
}) : null,
|
|
585
|
-
|
|
622
|
+
placedForm ? // Inert and click-through like the instance preview below: the
|
|
586
623
|
// fields belong to the form, and they are edited in the form's own
|
|
587
624
|
// besigner, so the only selectable thing here is the placement.
|
|
588
625
|
/*#__PURE__*/ _jsx(Box, {
|
|
@@ -592,9 +629,9 @@ export const NodeLeaf = observer(/*#__PURE__*/ forwardRef((props, ref)=>{
|
|
|
592
629
|
"data-aglyn-form-preview": "",
|
|
593
630
|
children: /*#__PURE__*/ _jsx(RendererComponents.Provider, {
|
|
594
631
|
value: INERT_RENDERER,
|
|
595
|
-
children:
|
|
596
|
-
|
|
597
|
-
|
|
632
|
+
children: placedForm.trees.map((tree)=>/*#__PURE__*/ _jsx(Stem, {
|
|
633
|
+
node: tree
|
|
634
|
+
}, tree.$id))
|
|
598
635
|
})
|
|
599
636
|
}) : null,
|
|
600
637
|
instanceTree ? // `pointerEvents: none` so a click anywhere on the rendered
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../libs/besigner/feature/designer/src/lib/components/node-leaf.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2023 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 {\n buildComponentDefaultIconPaths,\n buildComponentDefaultTokens,\n buildComponentDefaultValues,\n composeReusableComponentNodes,\n displayBindingTokens,\n FORM_COMPONENT_ID,\n hasBindings,\n HostViewType,\n LAYOUT_SLOT_COMPONENT_ID,\n NODE_ROOT_ID,\n placedFormPlacement,\n resolveBindings,\n resolveComponentPropTokens,\n REUSABLE_INSTANCE_COMPONENT_ID,\n} from '@aglyn/aglyn'\nimport {\n Branch,\n Leaf,\n type LeafProps,\n RendererComponents,\n Stem,\n Trunk,\n} from '@aglyn/aglyn-node-renderer'\nimport * as Besigner from '@aglyn/besigner'\nimport { alpha, Box } from '@mui/material'\nimport { observer } from 'mobx-react-lite'\nimport { forwardRef, useCallback, useContext, useMemo, useRef } from 'react'\nimport BindingPickerContext from '../contexts/binding-picker-context'\nimport CanvasRevealContext, {\n CanvasMutedClassesContext,\n} from '../contexts/canvas-reveal-context'\nimport ComponentPromotionContext from '../contexts/component-promotion-context'\nimport { useRenderedCanvasElements } from '../contexts/rendered-canvas-elements'\nimport useAglynBesignerFlag from '../hooks/use-aglyn-besigner-flag'\nimport { useNodeWithMediaAssetFacts } from '../hooks/use-media-asset-facts-overlay'\nimport useNodeWithHostTokens from '../hooks/use-node-with-host-tokens'\nimport {\n useNodeWithRepeatRecord,\n useRepeatCopies,\n useRepeatPreview,\n} from '../hooks/use-repeat-preview'\nimport { RepeatRecordContext } from '../contexts/repeat-record-context'\nimport {\n isNodeHiddenOnSite,\n isNodeRevealedOnCanvas,\n} from '../utils/canvas-reveal'\nimport { stripMutedClasses } from '../utils/muted-classes'\nimport DraggableDroppable from './dnd/draggable-droppable'\nimport EmptyDocumentSlot from './empty-document-slot'\n\n/**\n * The plain `Leaf`, drawing a node the way the published page composes it: its\n * host variables filled in from the site being edited (AGL-2881), and a placed\n * image or film with its DAM asset's current facts (AGL-2838, AGL-2856).\n *\n * For the nodes the canvas renders that are not canvas nodes — a component\n * instance's definition, a placed form's design, the layout chrome — and so\n * never reach `NodeLeaf`. A footer's `{{host.businessName}}` still has to read\n * as the site's name, an asset inside one of them still has to show the shape\n * the published page gives it, and a film its poster.\n */\nexport const MediaFactsLeaf = forwardRef<any, LeafProps>((props, ref) => {\n const { node, ...rest } = props\n const withHostTokens = useNodeWithHostTokens(node)\n const withRecord = useNodeWithRepeatRecord(withHostTokens)\n const shown = useNodeWithMediaAssetFacts(withRecord)\n return <Leaf ref={ref} node={shown} {...rest} />\n})\nMediaFactsLeaf.displayName = 'MediaFactsLeaf'\n\n/**\n * Renderer overrides for the inside of a component instance (AGL-1251).\n *\n * The plain `Leaf` (through `MediaFactsLeaf`), deliberately, where the canvas\n * otherwise uses `NodeLeaf`: the definition's nodes are NOT in the canvas, so\n * anything that made them draggable, selectable or droppable would be reaching\n * for canvas state that has no entry for them. Rendering them inert is not a\n * restriction bolted on afterwards — it is the only thing these nodes can be.\n */\nconst INERT_RENDERER = {\n TrunkComponent: Trunk,\n StemComponent: Stem,\n BranchComponent: Branch,\n LeafComponent: MediaFactsLeaf,\n}\n\n/**\n * Nested-object form of a normalized node map, which is what `Branch` walks\n * (`node.children`). Cycle-safe via `seen`: a definition that somehow\n * referenced an ancestor would otherwise recurse until the stack gave out.\n */\nexport function denormalizeTree(\n nodes: Record<string, any>,\n rootId: string,\n seen: Set<string> = new Set(),\n): any {\n const node = nodes?.[rootId]\n if (!node || seen.has(rootId)) return undefined\n seen.add(rootId)\n const childIds = Array.isArray(node.nodes) ? node.nodes : []\n return {\n ...node,\n children: childIds\n .map((childId: string) => denormalizeTree(nodes, childId, seen))\n .filter(Boolean),\n }\n}\n\nexport interface NodeLeafProps extends LeafProps {}\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\n/**\n * Visible placement marker for the LayoutSlot while editing a layout. The\n * slot is a passthrough at runtime, so without this it disappears once it\n * has children and designers lose track of where screen content lands.\n */\nconst SlotMarker = ({ caption }: { caption?: string }) => (\n <Box\n aria-hidden\n data-aglyn-slot-marker=\"\"\n sx={{\n m: 1,\n p: 2,\n minHeight: 64,\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n gap: 0.5,\n borderWidth: 2,\n borderStyle: 'dashed',\n // Brand blue on a faint tint, not a grey dashed box: the slot is the\n // one region the layout does not own, and it should not read as just\n // another empty container.\n borderColor: SLOT_ACCENT,\n backgroundColor: alpha(SLOT_ACCENT, 0.06),\n borderRadius: 1,\n color: 'text.secondary',\n fontSize: 13,\n textAlign: 'center',\n }}\n >\n <Box component=\"span\" sx={{ color: SLOT_ACCENT, fontWeight: 700 }}>\n {'◇ layout-slot'}\n </Box>\n <Box component=\"span\">{caption || 'Screen content renders here'}</Box>\n </Box>\n)\n\n/**\n * The repeat badge (AGL-3111).\n *\n * Editor chrome, so it holds its accent literally for the reason\n * {@link SLOT_ACCENT} does: the canvas renders under the SITE's palette, and a\n * theme token here would repaint the editor's own furniture whenever a\n * subscriber restyled their site.\n *\n * It cannot reach a published page: only `NodeLeaf` draws it, and a published\n * page is composed from stored nodes and rendered through the plain `Leaf`.\n * The stored node is untouched either way — this is a render copy, like every\n * other thing the canvas lays over a node.\n */\nconst REPEAT_ACCENT = '#7C4DFF'\n\n/** The chip's own ink, for the same reason and read against that accent. */\nconst REPEAT_INK = '#FFFFFF'\n\nconst RepeatBadge = ({ label, count }: { label: string; count: number }) => (\n <Box\n aria-hidden\n data-aglyn-repeat-badge=\"\"\n sx={{\n // Out of the element's own layout: a badge that took part in a flex row\n // would move the design it is describing.\n position: 'absolute',\n top: 0,\n right: 0,\n zIndex: 2,\n // Never swallows a click — the element under it stays selectable.\n pointerEvents: 'none',\n px: 0.75,\n py: 0.25,\n maxWidth: '100%',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n borderBottomLeftRadius: 3,\n backgroundColor: REPEAT_ACCENT,\n color: REPEAT_INK,\n fontSize: 11,\n fontWeight: 700,\n lineHeight: 1.6,\n letterSpacing: 0.2,\n }}\n >\n {`⟳ ${label} · ${count} ${count === 1 ? 'record' : 'records'}`}\n </Box>\n)\n\nexport const NodeLeaf = observer(\n forwardRef<any, NodeLeafProps>((props, ref) => {\n const { node, children, ...rest } = props\n const [viewType] = useAglynBesignerFlag('viewType')\n // The canvas element registry, finally given a WRITER (AGL-2486).\n //\n // `RenderedCanvasElementsProvider` has existed since the registry was\n // introduced and `setElementRef`/`deleteElementRef` were called from\n // NOWHERE in the repo — `elements.current` was `{}` for the life of every\n // session. Everything that reads it therefore read an empty map and\n // failed the only way an empty map can: silently.\n //\n // Two features were void because of it, and neither logged anything:\n //\n // `usePresence`'s cursor broadcast resolves the canvas root through\n // this registry and bails with `if (!root) return`, so no editor has\n // EVER written a cursor position. Confirmed against production RTDB on\n // 2026-08-22: three live presence entries across three rooms, not one\n // carrying `cursorX`/`cursorY`.\n //\n // `CollaboratorOverlays` resolves both the canvas root and each peer's\n // selected node here, so `placements` was always empty and the overlay\n // returned null. Confirmed live in the running console: a collaborator\n // entry carrying both a cursor and a selection rendered its avatar and\n // drew no cursor, no selection box and no name label.\n //\n // Registering here rather than in the renderer keeps it to the canvas:\n // `NodeLeaf` is the besigner's leaf component, so component-definition\n // previews (which render the plain `Leaf` through `INERT_RENDERER`) stay\n // out of the registry — they are not in the canvas and must not be\n // addressable as though they were.\n //\n // `NODE_ROOT_ID` and `CANVAS_ROOT_ELEMENT_ID` are the same string\n // (`'_@_'`), so keying on `$id` registers the canvas root under the id\n // the cursor code already looks for, with no special case.\n const { elements, setElementRef, deleteElementRef } =\n useRenderedCanvasElements()\n const $id = node?.$id\n // What THIS leaf last put in the registry. A remount can hand the new\n // element its ref before the old one is cleared, so an unconditional\n // delete on `null` would evict the live registration and put the\n // registry back to the state this whole change exists to fix.\n const registered = useRef<Element | null>(null)\n const registerElement = useCallback(\n (element: unknown) => {\n // Forward FIRST and unconditionally: dnd and the renderer already\n // depend on this ref, and registration must never change what they\n // observe.\n if (typeof ref === 'function') ref(element)\n else if (ref) (ref as { current: unknown }).current = element\n if (!$id) return\n if (element instanceof Element) {\n registered.current = element\n setElementRef($id, {\n $id,\n node: element,\n dragHandle: undefined,\n } as never)\n return\n }\n // Cleared (or never a DOM element — a class instance, say). Only\n // withdraw the registration if it is still OURS.\n const current = elements?.current?.[$id]?.node\n if (!registered.current || current === registered.current) {\n deleteElementRef($id)\n }\n registered.current = null\n },\n [ref, $id, elements, setElementRef, deleteElementRef],\n )\n const showSlotMarker =\n node?.componentId === LAYOUT_SLOT_COMPONENT_ID &&\n viewType === HostViewType.LAYOUT\n // A document with no nodes has nothing to aim at. Wrapped in a shared\n // layout it is worse than empty — the slot passes its children straight\n // through, so the root collapses to a zero-height strip between locked\n // chrome and the nav renders directly into the footer. Hung off the ROOT\n // leaf, which is already inside `DraggableDroppable`, so giving it height\n // is all a drop needs to resolve against the document root.\n const showEmptyDocumentSlot =\n node?.$id === NODE_ROOT_ID &&\n !node?.nodes?.length &&\n viewType !== HostViewType.LAYOUT\n\n // Nodes the author has opened up for designing (AGL-592). Through a\n // context rather than the flag itself: the canvas subscribes once and\n // every leaf reads the result.\n const revealedNodeIds = useContext(CanvasRevealContext)\n const mutedClasses = useContext(CanvasMutedClassesContext)\n\n // WYSIWYG bindings (AGL-97): resolve variable/function tokens\n // live on the rendered copy (selection/dnd keep the original node).\n // Bound nodes are flagged either way so editors can spot them.\n const [resolveFlag] = useAglynBesignerFlag('resolveBindings')\n const { variables, functions, componentProps } = useContext(\n BindingPickerContext,\n )\n const boundProps = useMemo(\n () =>\n Object.entries(node?.props ?? {}).filter(\n ([, value]) => typeof value === 'string' && hasBindings(value),\n ),\n // MobX props are observable; the JSON string keys the memo cheaply.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [node, JSON.stringify(node?.props ?? {})],\n )\n /**\n * A component editor draws with the definition's OWN defaults\n * (AGL-2870), so `{{prop.headline}}` shows the headline and a film with a\n * default source actually plays.\n *\n * Only props that HAVE a default are substituted. One without stays a\n * visible token, which is the signal an author needs: the slot is real\n * and nothing fills it yet. Substituting `''` there would draw an empty\n * component and hide the very thing they still have to decide.\n *\n * On a screen this path does nothing — the props belong to the component\n * route, and an instance is composed through `composeReusableComponentNodes`\n * below, which is the single substitution path a published page uses.\n */\n const defaultPropTokens = useMemo(\n () => buildComponentDefaultTokens(componentProps),\n [componentProps],\n )\n // The paths of the default icons, which a field bound to an icon property\n // draws with — the editor has no reason to have loaded the icon catalog.\n const defaultIconPaths = useMemo(\n () => buildComponentDefaultIconPaths(componentProps),\n [componentProps],\n )\n // The values the defaults hand their bound fields (AGL-2893), so a list\n // stays a list and a theme multiple a number on the editor's canvas too.\n const defaultValues = useMemo(\n () => buildComponentDefaultValues(componentProps),\n [componentProps],\n )\n const renderNode = useMemo(() => {\n const hasDeclaredProps = Boolean(componentProps?.length)\n if (\n !boundProps.length ||\n (!Object.keys(variables ?? {}).length &&\n !Object.keys(functions ?? {}).length &&\n !hasDeclaredProps)\n ) {\n return node\n }\n const resolved: Record<string, unknown> = { ...(node?.props ?? {}) }\n for (const [key, value] of boundProps) {\n // Resolve toggle off → show friendly token text: id tokens map to\n // the referent's CURRENT name (AGL-186), never raw doc ids.\n resolved[key] =\n resolveFlag === false\n ? displayBindingTokens(\n value as string,\n (variables ?? {}) as any,\n (functions ?? {}) as any,\n )\n : resolveBindings(\n value as string,\n (variables ?? {}) as any,\n (functions ?? {}) as any,\n )\n }\n const next = { ...node, props: resolved }\n if (!hasDeclaredProps) return next\n // Through the same substitution a placed instance uses, keyed by this\n // node alone — one code path deciding what a token becomes, never two\n // that could disagree.\n //\n // The raw-token view substitutes no defaults: an author who turned\n // resolution OFF asked to see the tokens, and a default is a resolved\n // value like any other. A field bound to a Yes/no property still\n // receives a yes or a no there, because a switch has no way to show a\n // token and reads the token's text as a yes.\n return (\n resolveComponentPropTokens(\n { [String(node?.$id)]: next as any },\n componentProps,\n resolveFlag === false ? undefined : defaultPropTokens,\n resolveFlag === false ? undefined : defaultIconPaths,\n resolveFlag === false ? undefined : defaultValues,\n )[String(node?.$id)] ?? next\n )\n }, [\n node,\n boundProps,\n resolveFlag,\n variables,\n functions,\n componentProps,\n defaultPropTokens,\n defaultIconPaths,\n defaultValues,\n ])\n\n // Host variables (AGL-2881), filled in on the same render copy and after\n // the bindings and the component's own defaults, which is the order the\n // published page composes in: a default that names the host resolves\n // too. The raw-token view reaches this leaf as no site at all\n // (`CanvasHostTokensProvider`), so its tokens stay as written. Keyed by\n // `boundProps`, which follows the observable props this copy reads.\n const hostResolvedNode = useNodeWithHostTokens(renderNode, boundProps)\n\n // Classes switched off for comparison (AGL-2486). Composed onto the SAME\n // render copy the binding resolution builds, never onto the canvas node:\n // selection, the hierarchy and every save keep reading the element's real\n // class list, and the canvas paints without the switched-off names.\n const renderNodeUnclassed = useMemo(\n () =>\n stripMutedClasses(hostResolvedNode as never, node?.$id, mutedClasses),\n [hostResolvedNode, node, mutedClasses],\n )\n\n /**\n * The record a repeat above this one is drawing (AGL-3111).\n *\n * Applied in the same place, and for the same reason, as the host tokens\n * and the DAM facts above: the published page substitutes `{{item.*}}`\n * when it composes, so the canvas substitutes it on the render copy while\n * the node keeps the tokens every save writes.\n */\n const withRepeatRecord = useNodeWithRepeatRecord(renderNodeUnclassed)\n\n // A placed asset's shape, and a film's length and poster, as its DAM\n // document records them NOW (AGL-2838, AGL-2856). The published page lays\n // the asset over the node when it is composed, so the canvas lays it over\n // the same render copy: a replace shows here as it shows to a visitor,\n // while selection, the panels and every save keep reading the node's\n // stored props.\n const shownNode = useNodeWithMediaAssetFacts(withRepeatRecord)\n\n /**\n * What this element repeats over, and the copies it draws (AGL-3111).\n *\n * The first record's copy is the element's own children — real canvas\n * nodes an author selects, drags and edits — with that record laid over\n * their render copies through {@link RepeatRecordContext}. Records 2..n\n * are inert pictures of the same template, built by the page's own\n * expansion. Editing the template therefore edits every copy, because\n * there is only ever one.\n */\n const repeatPreview = useRepeatPreview(node)\n const repeatCopies = useRepeatCopies(node, repeatPreview)\n const firstRecord = useMemo(\n () =>\n repeatPreview\n ? {\n record: repeatPreview.records[0],\n model: repeatPreview.dataset?.model,\n datasetsByKey: repeatPreview.datasetsByKey,\n }\n : undefined,\n [repeatPreview],\n )\n\n // A component instance renders its definition (AGL-1251) instead of the\n // named dashed box. Authors placed a hero and saw a grey rectangle, so\n // the props they filled in were invisible until Preview.\n //\n // Rendered, never grafted: the definition's nodes stay out of the canvas\n // entirely. That is what makes this safe — there is nothing to lock,\n // nothing to strip before a save, and no way for a definition's node to\n // be persisted into the document that placed it. Same shape as the\n // binding resolution above, which renders a resolved copy while\n // selection and dnd keep the original node.\n const { definitions, formDesigns } = useContext(ComponentPromotionContext)\n const instanceTree = useMemo(() => {\n if (node?.componentId !== REUSABLE_INSTANCE_COMPONENT_ID) {\n return undefined\n }\n // Already expanded into this canvas — the layout-chrome store grafts\n // before it loads (AGL-1218), so its instances arrive with children.\n // Rendering here too would draw the nav twice.\n if (node?.nodes?.length) return undefined\n const props = (node?.props ?? {}) as { refId?: string }\n const definition = props.refId ? definitions?.[props.refId] : undefined\n if (!definition?.nodes || !definition?.rootId) return undefined\n // Plain snapshot: props are MobX observables and the graft reads nested\n // `propValues` off them. The placement's classes stay out of it, as its\n // `sx` does: this leaf already renders both on the element around the\n // preview, and the graft joins a placement's classes onto the root it\n // becomes. Handed them here, the preview's root would apply every class\n // a second time, and keep `aglyn-hidden` collapsed inside a placement\n // the canvas has revealed for designing.\n const { className: _placementClasses, ...snapshotProps } = JSON.parse(\n JSON.stringify(node.props ?? {}),\n ) as Record<string, unknown>\n // Reuse the real graft so the canvas resolves `{{prop.*}}` exactly as\n // the published page will — one substitution path, not a second one\n // that could disagree about which value wins.\n const composed = composeReusableComponentNodes(\n {\n [node.$id]: {\n $id: node.$id,\n componentId: REUSABLE_INSTANCE_COMPONENT_ID,\n props: snapshotProps,\n // Root style overrides (AGL-1306) ride the same snapshot so\n // the canvas renders the SAME merged root sx the published\n // page will — the graft is the one merge point.\n ...(node.styleOverrides && {\n styleOverrides: JSON.parse(JSON.stringify(node.styleOverrides)),\n }),\n // Attribute overrides ride the same snapshot for the same\n // reason (AGL-1899). Leaving them out is invisible rather than\n // broken: Preview and tenant SSR compose from the stored node\n // and WOULD apply them, so the canvas alone would draw the\n // component's own attributes and disagree with both.\n ...(node.attrOverrides && {\n attrOverrides: JSON.parse(JSON.stringify(node.attrOverrides)),\n }),\n nodes: [],\n } as any,\n },\n // The host's WHOLE definitions map, not just this instance's own\n // (AGL-1898). A definition may contain instances of OTHER\n // definitions, and `composeReusableComponentNodes` expands those —\n // but only the ones it is handed. Preview, tenant SSR and the\n // layout-chrome graft all pass the full map, so a one-entry map\n // here made the canvas the only surface that left a nested\n // component as an unexpanded placeholder.\n //\n // It also silently voided propagation for the inner component:\n // publishing a shared button re-rendered every canvas showing it\n // directly and none of the ones showing it through the nav that\n // contains it — which is the case a shared component is usually in.\n //\n // Safe to widen because the `nodes` argument above is this ONE\n // instance: the extra definitions can only be reached from inside\n // the subtree being grafted, never applied to some other node.\n definitions as any,\n )\n // The graft puts the definition's root IN the instance's place rather\n // than under it (AGL-2521), so the component to draw is at the\n // instance's own id. Reading `nodes[0]` here would render the root's\n // FIRST CHILD and drop the component's outer element with its siblings.\n const grafted = composed[node.$id]\n return grafted && grafted.componentId !== REUSABLE_INSTANCE_COMPONENT_ID\n ? denormalizeTree(composed, node.$id)\n : undefined\n // Observable props/overrides: the JSON strings key the memo, as above.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n node,\n JSON.stringify(node?.props ?? {}),\n JSON.stringify(node?.styleOverrides ?? {}),\n JSON.stringify(node?.attrOverrides ?? {}),\n definitions,\n ])\n\n /**\n * A placed form draws its ENTITY'S published fields, not the page's copy\n * of them (`docs/specs/reusable-forms.md`).\n *\n * The same render-copy treatment the instance above gets, and for the same\n * reason: the entity's nodes stay out of the canvas, so there is nothing to\n * lock, nothing to strip before a save, and no way for a form's fields to\n * be persisted into the screen that placed it.\n *\n * It runs the REAL graft rather than reading `design.nodes` directly, so a\n * component nested inside the form design expands here exactly as it will\n * on the published page — one resolution path, not a second that could\n * disagree.\n *\n * When it resolves, the node's own children are not rendered (below): the\n * published page discards them, and a canvas that drew both would show the\n * author a page that does not exist. Where the entity has no published\n * design the memo is `undefined`, the children render, and the form the\n * author drew is the form they see — which is every form built before the\n * entity existed.\n */\n const placedFormTree = useMemo(() => {\n if (node?.componentId !== FORM_COMPONENT_ID) return undefined\n const formId = (node?.props as { formId?: unknown } | undefined)?.formId\n if (typeof formId !== 'string' || !formId) return undefined\n const composed = composeReusableComponentNodes(\n {\n [node.$id]: {\n $id: node.$id,\n componentId: FORM_COMPONENT_ID,\n // Plain snapshot: props are MobX observables, as above.\n props: JSON.parse(JSON.stringify(node.props ?? {})),\n nodes: [],\n } as any,\n },\n // Component definitions ride along so an instance INSIDE the form\n // design expands; the map is safe to pass whole for the reason the\n // instance graft above states — the one node handed in is this form.\n definitions as any,\n [placedFormPlacement(formDesigns as any)],\n )\n const graftedRootId = (composed[node.$id]?.nodes as string[])?.[0]\n return graftedRootId\n ? denormalizeTree(composed, graftedRootId)\n : undefined\n // Observable props: the JSON string keys the memo, as above.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [node, JSON.stringify(node?.props ?? {}), definitions, formDesigns])\n\n return (\n <DraggableDroppable\n node={node}\n type={Besigner.DragType.CANVAS}\n accept={Object.values(Besigner.DragType)}\n disableDragging={!Besigner.dnd.canDragNode(node)}\n >\n <Leaf\n ref={registerElement}\n node={shownNode as typeof node}\n data-aglyn-selected={Besigner.focus.isNodeSelected(node)}\n // Present while the selection lives in this node's subtree (the\n // node itself or any descendant). Canvas-aware components (nav\n // menus, drawers) read this neutral leaf attribute to expand\n // only while they are being authored (AGL-571). Presence-based\n // (''/undefined) so unaffected leaves carry no attribute.\n data-aglyn-selected-within={\n Besigner.focus.isNodeOrDescendantSelected(node) ? '' : undefined\n }\n // Present on an element that carries the hidden class while the\n // canvas is showing it anyway (AGL-592) — the flag the canvas\n // stylesheet checks before collapsing it. Stamped on the hidden\n // element itself, so no wrapper a component renders around its\n // children can come between the two.\n data-aglyn-revealed={\n isNodeHiddenOnSite(node) &&\n isNodeRevealedOnCanvas(node, revealedNodeIds)\n ? ''\n : undefined\n }\n data-aglyn-bound={boundProps.length ? '' : undefined}\n {...rest}\n >\n {/* A resolved form entity REPLACES the page's own fields, exactly\n as the published page composes it — see `placedFormTree`. */}\n {placedFormTree ? null : firstRecord ? (\n // The template IS the first copy: the real children, drawing the\n // first record. Provided around them rather than applied to them,\n // so every descendant leaf resolves its own tokens the way the\n // page's substitution walks the whole cloned subtree.\n <RepeatRecordContext.Provider value={firstRecord}>\n {children}\n </RepeatRecordContext.Provider>\n ) : (\n children\n )}\n {repeatCopies.length ? (\n // Inert and click-through like the component and form previews\n // below, and for the same reason: a copy is a picture of the\n // template, not a document. Selecting one would offer an author\n // an element no save can reach.\n <Box\n sx={{ pointerEvents: 'none' }}\n data-aglyn-repeat-preview=\"\"\n aria-hidden\n >\n <RendererComponents.Provider value={INERT_RENDERER as any}>\n {repeatCopies.map((copy) => (\n <Stem key={copy.$id} node={copy} />\n ))}\n </RendererComponents.Provider>\n </Box>\n ) : null}\n {repeatPreview ? (\n <RepeatBadge\n label={repeatPreview.label}\n count={repeatPreview.records.length}\n />\n ) : null}\n {placedFormTree ? (\n // Inert and click-through like the instance preview below: the\n // fields belong to the form, and they are edited in the form's own\n // besigner, so the only selectable thing here is the placement.\n <Box sx={{ pointerEvents: 'none' }} data-aglyn-form-preview=\"\">\n <RendererComponents.Provider value={INERT_RENDERER as any}>\n <Stem node={placedFormTree} />\n </RendererComponents.Provider>\n </Box>\n ) : null}\n {instanceTree ? (\n // `pointerEvents: none` so a click anywhere on the rendered\n // component still lands on the instance beneath it — the\n // instance is the only thing here that can be selected, and the\n // only thing whose Attributes are editable.\n <Box sx={{ pointerEvents: 'none' }} data-aglyn-component-preview=\"\">\n <RendererComponents.Provider value={INERT_RENDERER as any}>\n <Stem node={instanceTree} />\n </RendererComponents.Provider>\n </Box>\n ) : null}\n {showSlotMarker ? (\n <SlotMarker\n caption={node?.props?.['caption'] as string | undefined}\n />\n ) : null}\n {showEmptyDocumentSlot ? <EmptyDocumentSlot /> : null}\n </Leaf>\n </DraggableDroppable>\n )\n }),\n)\nNodeLeaf.displayName = 'BesignerLeafComponent'\nNodeLeaf.aglyn = true\n\nexport default NodeLeaf\n"],"names":["buildComponentDefaultIconPaths","buildComponentDefaultTokens","buildComponentDefaultValues","composeReusableComponentNodes","displayBindingTokens","FORM_COMPONENT_ID","hasBindings","HostViewType","LAYOUT_SLOT_COMPONENT_ID","NODE_ROOT_ID","placedFormPlacement","resolveBindings","resolveComponentPropTokens","REUSABLE_INSTANCE_COMPONENT_ID","Branch","Leaf","RendererComponents","Stem","Trunk","Besigner","alpha","Box","observer","forwardRef","useCallback","useContext","useMemo","useRef","BindingPickerContext","CanvasRevealContext","CanvasMutedClassesContext","ComponentPromotionContext","useRenderedCanvasElements","useAglynBesignerFlag","useNodeWithMediaAssetFacts","useNodeWithHostTokens","useNodeWithRepeatRecord","useRepeatCopies","useRepeatPreview","RepeatRecordContext","isNodeHiddenOnSite","isNodeRevealedOnCanvas","stripMutedClasses","DraggableDroppable","EmptyDocumentSlot","MediaFactsLeaf","props","ref","node","rest","withHostTokens","withRecord","shown","displayName","INERT_RENDERER","TrunkComponent","StemComponent","BranchComponent","LeafComponent","denormalizeTree","nodes","rootId","seen","Set","has","undefined","add","childIds","Array","isArray","children","map","childId","filter","Boolean","SLOT_ACCENT","SlotMarker","caption","aria-hidden","data-aglyn-slot-marker","sx","m","p","minHeight","display","flexDirection","alignItems","justifyContent","gap","borderWidth","borderStyle","borderColor","backgroundColor","borderRadius","color","fontSize","textAlign","component","fontWeight","REPEAT_ACCENT","REPEAT_INK","RepeatBadge","label","count","data-aglyn-repeat-badge","position","top","right","zIndex","pointerEvents","px","py","maxWidth","overflow","textOverflow","whiteSpace","borderBottomLeftRadius","lineHeight","letterSpacing","NodeLeaf","viewType","elements","setElementRef","deleteElementRef","$id","registered","registerElement","element","current","Element","dragHandle","showSlotMarker","componentId","LAYOUT","showEmptyDocumentSlot","length","revealedNodeIds","mutedClasses","resolveFlag","variables","functions","componentProps","boundProps","Object","entries","value","JSON","stringify","defaultPropTokens","defaultIconPaths","defaultValues","renderNode","hasDeclaredProps","keys","resolved","key","next","String","hostResolvedNode","renderNodeUnclassed","withRepeatRecord","shownNode","repeatPreview","repeatCopies","firstRecord","record","records","model","dataset","datasetsByKey","definitions","formDesigns","instanceTree","definition","refId","parse","className","_placementClasses","snapshotProps","composed","styleOverrides","attrOverrides","grafted","placedFormTree","formId","graftedRootId","type","DragType","CANVAS","accept","values","disableDragging","dnd","canDragNode","data-aglyn-selected","focus","isNodeSelected","data-aglyn-selected-within","isNodeOrDescendantSelected","data-aglyn-revealed","data-aglyn-bound","Provider","data-aglyn-repeat-preview","copy","data-aglyn-form-preview","data-aglyn-component-preview","aglyn"],"mappings":";;;AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,8BAA8B,EAC9BC,2BAA2B,EAC3BC,2BAA2B,EAC3BC,6BAA6B,EAC7BC,oBAAoB,EACpBC,iBAAiB,EACjBC,WAAW,EACXC,YAAY,EACZC,wBAAwB,EACxBC,YAAY,EACZC,mBAAmB,EACnBC,eAAe,EACfC,0BAA0B,EAC1BC,8BAA8B,QACzB,eAAc;AACrB,SACEC,MAAM,EACNC,IAAI,EAEJC,kBAAkB,EAClBC,IAAI,EACJC,KAAK,QACA,6BAA4B;AACnC,YAAYC,cAAc,kBAAiB;AAC3C,SAASC,KAAK,EAAEC,GAAG,QAAQ,gBAAe;AAC1C,SAASC,QAAQ,QAAQ,kBAAiB;AAC1C,SAASC,UAAU,EAAEC,WAAW,EAAEC,UAAU,EAAEC,OAAO,EAAEC,MAAM,QAAQ,QAAO;AAC5E,OAAOC,0BAA0B,wCAAoC;AACrE,OAAOC,uBACLC,yBAAyB,QACpB,uCAAmC;AAC1C,OAAOC,+BAA+B,6CAAyC;AAC/E,SAASC,yBAAyB,QAAQ,0CAAsC;AAChF,OAAOC,0BAA0B,sCAAkC;AACnE,SAASC,0BAA0B,QAAQ,4CAAwC;AACnF,OAAOC,2BAA2B,wCAAoC;AACtE,SACEC,uBAAuB,EACvBC,eAAe,EACfC,gBAAgB,QACX,iCAA6B;AACpC,SAASC,mBAAmB,QAAQ,uCAAmC;AACvE,SACEC,kBAAkB,EAClBC,sBAAsB,QACjB,4BAAwB;AAC/B,SAASC,iBAAiB,QAAQ,4BAAwB;AAC1D,OAAOC,wBAAwB,+BAA2B;AAC1D,OAAOC,uBAAuB,2BAAuB;AAErD;;;;;;;;;;CAUC,GACD,OAAO,MAAMC,+BAAiBtB,WAA2B,CAACuB,OAAOC;IAC/D,MAAM,EAAEC,IAAI,EAAW,GAAGF,OAATG,wCAASH;;;IAC1B,MAAMI,iBAAiBf,sBAAsBa;IAC7C,MAAMG,aAAaf,wBAAwBc;IAC3C,MAAME,QAAQlB,2BAA2BiB;IACzC,qBAAO,KAACpC;QAAKgC,KAAKA;QAAKC,MAAMI;OAAWH;AAC1C,GAAE;AACFJ,eAAeQ,WAAW,GAAG;AAE7B;;;;;;;;CAQC,GACD,MAAMC,iBAAiB;IACrBC,gBAAgBrC;IAChBsC,eAAevC;IACfwC,iBAAiB3C;IACjB4C,eAAeb;AACjB;AAEA;;;;CAIC,GACD,OAAO,SAASc,gBACdC,KAA0B,EAC1BC,MAAc,EACdC,OAAoB,IAAIC,KAAK;IAE7B,MAAMf,OAAOY,yBAAAA,KAAO,CAACC,OAAO;IAC5B,IAAI,CAACb,QAAQc,KAAKE,GAAG,CAACH,SAAS,OAAOI;IACtCH,KAAKI,GAAG,CAACL;IACT,MAAMM,WAAWC,MAAMC,OAAO,CAACrB,KAAKY,KAAK,IAAIZ,KAAKY,KAAK,GAAG,EAAE;IAC5D,OAAO,aACFZ;QACHsB,UAAUH,SACPI,GAAG,CAAC,CAACC,UAAoBb,gBAAgBC,OAAOY,SAASV,OACzDW,MAAM,CAACC;;AAEd;AAIA;;;;;;CAMC,GACD,MAAMC,cAAc;AAEpB;;;;CAIC,GACD,MAAMC,aAAa,CAAC,EAAEC,OAAO,EAAwB,iBACnD,MAACxD;QACCyD,aAAW;QACXC,0BAAuB;QACvBC,IAAI;YACFC,GAAG;YACHC,GAAG;YACHC,WAAW;YACXC,SAAS;YACTC,eAAe;YACfC,YAAY;YACZC,gBAAgB;YAChBC,KAAK;YACLC,aAAa;YACbC,aAAa;YACb,qEAAqE;YACrE,qEAAqE;YACrE,2BAA2B;YAC3BC,aAAahB;YACbiB,iBAAiBxE,MAAMuD,aAAa;YACpCkB,cAAc;YACdC,OAAO;YACPC,UAAU;YACVC,WAAW;QACb;;0BAEA,KAAC3E;gBAAI4E,WAAU;gBAAOjB,IAAI;oBAAEc,OAAOnB;oBAAauB,YAAY;gBAAI;0BAC7D;;0BAEH,KAAC7E;gBAAI4E,WAAU;0BAAQpB,WAAW;;;;AAItC;;;;;;;;;;;;CAYC,GACD,MAAMsB,gBAAgB;AAEtB,0EAA0E,GAC1E,MAAMC,aAAa;AAEnB,MAAMC,cAAc,CAAC,EAAEC,KAAK,EAAEC,KAAK,EAAoC,iBACrE,KAAClF;QACCyD,aAAW;QACX0B,2BAAwB;QACxBxB,IAAI;YACF,wEAAwE;YACxE,0CAA0C;YAC1CyB,UAAU;YACVC,KAAK;YACLC,OAAO;YACPC,QAAQ;YACR,kEAAkE;YAClEC,eAAe;YACfC,IAAI;YACJC,IAAI;YACJC,UAAU;YACVC,UAAU;YACVC,cAAc;YACdC,YAAY;YACZC,wBAAwB;YACxBxB,iBAAiBO;YACjBL,OAAOM;YACPL,UAAU;YACVG,YAAY;YACZmB,YAAY;YACZC,eAAe;QACjB;kBAEC,CAAC,EAAE,EAAEhB,MAAM,GAAG,EAAEC,MAAM,CAAC,EAAEA,UAAU,IAAI,WAAW,WAAW;;AAIlE,OAAO,MAAMgB,WAAWjG,uBACtBC,WAA+B,CAACuB,OAAOC;;QAgFlCC,aAyZgBA;IAxenB,MAAM,EAAEA,IAAI,EAAEsB,QAAQ,EAAW,GAAGxB,OAATG,wCAASH;;;;IACpC,MAAM,CAAC0E,SAAS,GAAGvF,qBAAqB;IACxC,kEAAkE;IAClE,EAAE;IACF,sEAAsE;IACtE,qEAAqE;IACrE,0EAA0E;IAC1E,oEAAoE;IACpE,kDAAkD;IAClD,EAAE;IACF,qEAAqE;IACrE,EAAE;IACF,sEAAsE;IACtE,uEAAuE;IACvE,yEAAyE;IACzE,wEAAwE;IACxE,kCAAkC;IAClC,EAAE;IACF,yEAAyE;IACzE,yEAAyE;IACzE,yEAAyE;IACzE,yEAAyE;IACzE,wDAAwD;IACxD,EAAE;IACF,uEAAuE;IACvE,uEAAuE;IACvE,yEAAyE;IACzE,mEAAmE;IACnE,mCAAmC;IACnC,EAAE;IACF,kEAAkE;IAClE,uEAAuE;IACvE,2DAA2D;IAC3D,MAAM,EAAEwF,QAAQ,EAAEC,aAAa,EAAEC,gBAAgB,EAAE,GACjD3F;IACF,MAAM4F,MAAM5E,wBAAAA,KAAM4E,GAAG;IACrB,sEAAsE;IACtE,qEAAqE;IACrE,iEAAiE;IACjE,8DAA8D;IAC9D,MAAMC,aAAalG,OAAuB;IAC1C,MAAMmG,kBAAkBtG,YACtB,CAACuG;YAkBiBN,uBAAAA;QAjBhB,kEAAkE;QAClE,mEAAmE;QACnE,WAAW;QACX,IAAI,OAAO1E,QAAQ,YAAYA,IAAIgF;aAC9B,IAAIhF,KAAK,AAACA,IAA6BiF,OAAO,GAAGD;QACtD,IAAI,CAACH,KAAK;QACV,IAAIG,mBAAmBE,SAAS;YAC9BJ,WAAWG,OAAO,GAAGD;YACrBL,cAAcE,KAAK;gBACjBA;gBACA5E,MAAM+E;gBACNG,YAAYjE;YACd;YACA;QACF;QACA,iEAAiE;QACjE,iDAAiD;QACjD,MAAM+D,UAAUP,6BAAAA,oBAAAA,SAAUO,OAAO,sBAAjBP,wBAAAA,iBAAmB,CAACG,IAAI,qBAAxBH,sBAA0BzE,IAAI;QAC9C,IAAI,CAAC6E,WAAWG,OAAO,IAAIA,YAAYH,WAAWG,OAAO,EAAE;YACzDL,iBAAiBC;QACnB;QACAC,WAAWG,OAAO,GAAG;IACvB,GACA;QAACjF;QAAK6E;QAAKH;QAAUC;QAAeC;KAAiB;IAEvD,MAAMQ,iBACJnF,CAAAA,wBAAAA,KAAMoF,WAAW,MAAK5H,4BACtBgH,aAAajH,aAAa8H,MAAM;IAClC,sEAAsE;IACtE,wEAAwE;IACxE,uEAAuE;IACvE,yEAAyE;IACzE,0EAA0E;IAC1E,4DAA4D;IAC5D,MAAMC,wBACJtF,CAAAA,wBAAAA,KAAM4E,GAAG,MAAKnH,gBACd,EAACuC,yBAAAA,cAAAA,KAAMY,KAAK,qBAAXZ,YAAauF,MAAM,KACpBf,aAAajH,aAAa8H,MAAM;IAElC,oEAAoE;IACpE,sEAAsE;IACtE,+BAA+B;IAC/B,MAAMG,kBAAkB/G,WAAWI;IACnC,MAAM4G,eAAehH,WAAWK;IAEhC,8DAA8D;IAC9D,oEAAoE;IACpE,+DAA+D;IAC/D,MAAM,CAAC4G,YAAY,GAAGzG,qBAAqB;IAC3C,MAAM,EAAE0G,SAAS,EAAEC,SAAS,EAAEC,cAAc,EAAE,GAAGpH,WAC/CG;IAEF,MAAMkH,aAAapH,QACjB;;eACEqH,OAAOC,OAAO,SAAChG,wBAAAA,KAAMF,KAAK,mBAAI,CAAC,GAAG2B,MAAM,CACtC,CAAC,GAAGwE,MAAM,GAAK,OAAOA,UAAU,YAAY3I,YAAY2I;OAE5D,oEAAoE;IACpE,uDAAuD;IACvD;QAACjG;QAAMkG,KAAKC,SAAS,SAACnG,wBAAAA,KAAMF,KAAK,mBAAI,CAAC;KAAG;IAE3C;;;;;;;;;;;;;KAaC,GACD,MAAMsG,oBAAoB1H,QACxB,IAAMzB,4BAA4B4I,iBAClC;QAACA;KAAe;IAElB,0EAA0E;IAC1E,yEAAyE;IACzE,MAAMQ,mBAAmB3H,QACvB,IAAM1B,+BAA+B6I,iBACrC;QAACA;KAAe;IAElB,wEAAwE;IACxE,yEAAyE;IACzE,MAAMS,gBAAgB5H,QACpB,IAAMxB,4BAA4B2I,iBAClC;QAACA;KAAe;IAElB,MAAMU,aAAa7H,QAAQ;kBAuCvBd;QAtCF,MAAM4I,mBAAmB9E,QAAQmE,kCAAAA,eAAgBN,MAAM;QACvD,IACE,CAACO,WAAWP,MAAM,IACjB,CAACQ,OAAOU,IAAI,CAACd,oBAAAA,YAAa,CAAC,GAAGJ,MAAM,IACnC,CAACQ,OAAOU,IAAI,CAACb,oBAAAA,YAAa,CAAC,GAAGL,MAAM,IACpC,CAACiB,kBACH;YACA,OAAOxG;QACT;QACA,MAAM0G,WAAoC,qBAAM1G,wBAAAA,KAAMF,KAAK,mBAAI,CAAC;QAChE,KAAK,MAAM,CAAC6G,KAAKV,MAAM,IAAIH,WAAY;YACrC,kEAAkE;YAClE,4DAA4D;YAC5DY,QAAQ,CAACC,IAAI,GACXjB,gBAAgB,QACZtI,qBACE6I,OACCN,oBAAAA,YAAa,CAAC,GACdC,oBAAAA,YAAa,CAAC,KAEjBjI,gBACEsI,OACCN,oBAAAA,YAAa,CAAC,GACdC,oBAAAA,YAAa,CAAC;QAEzB;QACA,MAAMgB,OAAO,aAAK5G;YAAMF,OAAO4G;;QAC/B,IAAI,CAACF,kBAAkB,OAAOI;QAC9B,sEAAsE;QACtE,sEAAsE;QACtE,uBAAuB;QACvB,EAAE;QACF,mEAAmE;QACnE,sEAAsE;QACtE,iEAAiE;QACjE,sEAAsE;QACtE,6CAA6C;QAC7C,QACEhJ,qCAAAA,2BACE;YAAE,CAACiJ,OAAO7G,wBAAAA,KAAM4E,GAAG,EAAE,EAAEgC;QAAY,GACnCf,gBACAH,gBAAgB,QAAQzE,YAAYmF,mBACpCV,gBAAgB,QAAQzE,YAAYoF,kBACpCX,gBAAgB,QAAQzE,YAAYqF,cACrC,CAACO,OAAO7G,wBAAAA,KAAM4E,GAAG,EAAE,YANpBhH,qCAMwBgJ;IAE5B,GAAG;QACD5G;QACA8F;QACAJ;QACAC;QACAC;QACAC;QACAO;QACAC;QACAC;KACD;IAED,yEAAyE;IACzE,wEAAwE;IACxE,qEAAqE;IACrE,8DAA8D;IAC9D,wEAAwE;IACxE,oEAAoE;IACpE,MAAMQ,mBAAmB3H,sBAAsBoH,YAAYT;IAE3D,yEAAyE;IACzE,yEAAyE;IACzE,0EAA0E;IAC1E,oEAAoE;IACpE,MAAMiB,sBAAsBrI,QAC1B,IACEgB,kBAAkBoH,kBAA2B9G,wBAAAA,KAAM4E,GAAG,EAAEa,eAC1D;QAACqB;QAAkB9G;QAAMyF;KAAa;IAGxC;;;;;;;KAOC,GACD,MAAMuB,mBAAmB5H,wBAAwB2H;IAEjD,qEAAqE;IACrE,0EAA0E;IAC1E,0EAA0E;IAC1E,uEAAuE;IACvE,qEAAqE;IACrE,gBAAgB;IAChB,MAAME,YAAY/H,2BAA2B8H;IAE7C;;;;;;;;;KASC,GACD,MAAME,gBAAgB5H,iBAAiBU;IACvC,MAAMmH,eAAe9H,gBAAgBW,MAAMkH;IAC3C,MAAME,cAAc1I,QAClB;YAIewI;eAHbA,gBACI;YACEG,QAAQH,cAAcI,OAAO,CAAC,EAAE;YAChCC,KAAK,GAAEL,yBAAAA,cAAcM,OAAO,qBAArBN,uBAAuBK,KAAK;YACnCE,eAAeP,cAAcO,aAAa;QAC5C,IACAxG;OACN;QAACiG;KAAc;IAGjB,wEAAwE;IACxE,uEAAuE;IACvE,yDAAyD;IACzD,EAAE;IACF,yEAAyE;IACzE,qEAAqE;IACrE,wEAAwE;IACxE,mEAAmE;IACnE,gEAAgE;IAChE,4CAA4C;IAC5C,MAAM,EAAEQ,WAAW,EAAEC,WAAW,EAAE,GAAGlJ,WAAWM;IAChD,MAAM6I,eAAelJ,QAAQ;kBAmBVsB;YAZbA;QANJ,IAAIA,CAAAA,wBAAAA,KAAMoF,WAAW,MAAKvH,gCAAgC;YACxD,OAAOoD;QACT;QACA,qEAAqE;QACrE,qEAAqE;QACrE,+CAA+C;QAC/C,IAAIjB,yBAAAA,cAAAA,KAAMY,KAAK,qBAAXZ,YAAauF,MAAM,EAAE,OAAOtE;QAChC,MAAMnB,gBAASE,wBAAAA,KAAMF,KAAK,mBAAI,CAAC;QAC/B,MAAM+H,aAAa/H,MAAMgI,KAAK,GAAGJ,+BAAAA,WAAa,CAAC5H,MAAMgI,KAAK,CAAC,GAAG7G;QAC9D,IAAI,EAAC4G,8BAAAA,WAAYjH,KAAK,KAAI,EAACiH,8BAAAA,WAAYhH,MAAM,GAAE,OAAOI;QACtD,wEAAwE;QACxE,wEAAwE;QACxE,sEAAsE;QACtE,sEAAsE;QACtE,wEAAwE;QACxE,sEAAsE;QACtE,yCAAyC;QACzC,MAA2DiF,cAAAA,KAAK6B,KAAK,CACnE7B,KAAKC,SAAS,EAACnG,cAAAA,KAAKF,KAAK,YAAVE,cAAc,CAAC,KAD1B,EAAEgI,WAAWC,iBAAiB,EAAoB,GAAG/B,aAAlBgC,iDAAkBhC;;;QAG3D,sEAAsE;QACtE,oEAAoE;QACpE,8CAA8C;QAC9C,MAAMiC,WAAWhL,8BACf;YACE,CAAC6C,KAAK4E,GAAG,CAAC,EAAE;gBACVA,KAAK5E,KAAK4E,GAAG;gBACbQ,aAAavH;gBACbiC,OAAOoI;eAIHlI,KAAKoI,cAAc,IAAI;gBACzBA,gBAAgBlC,KAAK6B,KAAK,CAAC7B,KAAKC,SAAS,CAACnG,KAAKoI,cAAc;YAC/D,GAMIpI,KAAKqI,aAAa,IAAI;gBACxBA,eAAenC,KAAK6B,KAAK,CAAC7B,KAAKC,SAAS,CAACnG,KAAKqI,aAAa;YAC7D;gBACAzH,OAAO,EAAE;;QAEb,GACA,iEAAiE;QACjE,0DAA0D;QAC1D,mEAAmE;QACnE,8DAA8D;QAC9D,gEAAgE;QAChE,2DAA2D;QAC3D,0CAA0C;QAC1C,EAAE;QACF,+DAA+D;QAC/D,iEAAiE;QACjE,gEAAgE;QAChE,oEAAoE;QACpE,EAAE;QACF,+DAA+D;QAC/D,kEAAkE;QAClE,+DAA+D;QAC/D8G;QAEF,sEAAsE;QACtE,+DAA+D;QAC/D,qEAAqE;QACrE,wEAAwE;QACxE,MAAMY,UAAUH,QAAQ,CAACnI,KAAK4E,GAAG,CAAC;QAClC,OAAO0D,WAAWA,QAAQlD,WAAW,KAAKvH,iCACtC8C,gBAAgBwH,UAAUnI,KAAK4E,GAAG,IAClC3D;IACJ,uEAAuE;IACvE,uDAAuD;IACzD,GAAG;QACDjB;QACAkG,KAAKC,SAAS,UAACnG,wBAAAA,KAAMF,KAAK,oBAAI,CAAC;QAC/BoG,KAAKC,SAAS,UAACnG,wBAAAA,KAAMoI,cAAc,oBAAI,CAAC;QACxClC,KAAKC,SAAS,UAACnG,wBAAAA,KAAMqI,aAAa,oBAAI,CAAC;QACvCX;KACD;IAED;;;;;;;;;;;;;;;;;;;;KAoBC,GACD,MAAMa,iBAAiB7J,QAAQ;YAUUsB;YARvBA,cAkBOmI,0BAAAA;QAnBvB,IAAInI,CAAAA,wBAAAA,KAAMoF,WAAW,MAAK/H,mBAAmB,OAAO4D;QACpD,MAAMuH,SAAUxI,yBAAAA,eAAAA,KAAMF,KAAK,qBAAZ,AAACE,aAAkDwI,MAAM;QACxE,IAAI,OAAOA,WAAW,YAAY,CAACA,QAAQ,OAAOvH;QAClD,MAAMkH,WAAWhL,8BACf;YACE,CAAC6C,KAAK4E,GAAG,CAAC,EAAE;gBACVA,KAAK5E,KAAK4E,GAAG;gBACbQ,aAAa/H;gBACb,wDAAwD;gBACxDyC,OAAOoG,KAAK6B,KAAK,CAAC7B,KAAKC,SAAS,EAACnG,cAAAA,KAAKF,KAAK,YAAVE,cAAc,CAAC;gBAChDY,OAAO,EAAE;YACX;QACF,GACA,kEAAkE;QAClE,mEAAmE;QACnE,qEAAqE;QACrE8G,aACA;YAAChK,oBAAoBiK;SAAoB;QAE3C,MAAMc,iBAAiBN,qBAAAA,QAAQ,CAACnI,KAAK4E,GAAG,CAAC,sBAAlBuD,2BAAAA,mBAAoBvH,KAAK,qBAA1B,AAACuH,wBAAwC,CAAC,EAAE;QAClE,OAAOM,gBACH9H,gBAAgBwH,UAAUM,iBAC1BxH;IACJ,6DAA6D;IAC7D,uDAAuD;IACzD,GAAG;QAACjB;QAAMkG,KAAKC,SAAS,UAACnG,wBAAAA,KAAMF,KAAK,oBAAI,CAAC;QAAI4H;QAAaC;KAAY;IAEtE,qBACE,KAAChI;QACCK,MAAMA;QACN0I,MAAMvK,SAASwK,QAAQ,CAACC,MAAM;QAC9BC,QAAQ9C,OAAO+C,MAAM,CAAC3K,SAASwK,QAAQ;QACvCI,iBAAiB,CAAC5K,SAAS6K,GAAG,CAACC,WAAW,CAACjJ;kBAE3C,cAAA,MAACjC;YACCgC,KAAK+E;YACL9E,MAAMiH;YACNiC,uBAAqB/K,SAASgL,KAAK,CAACC,cAAc,CAACpJ;YACnD,gEAAgE;YAChE,+DAA+D;YAC/D,6DAA6D;YAC7D,+DAA+D;YAC/D,0DAA0D;YAC1DqJ,8BACElL,SAASgL,KAAK,CAACG,0BAA0B,CAACtJ,QAAQ,KAAKiB;YAEzD,gEAAgE;YAChE,8DAA8D;YAC9D,gEAAgE;YAChE,+DAA+D;YAC/D,qCAAqC;YACrCsI,uBACE/J,mBAAmBQ,SACnBP,uBAAuBO,MAAMwF,mBACzB,KACAvE;YAENuI,oBAAkB1D,WAAWP,MAAM,GAAG,KAAKtE;WACvChB;;gBAIHsI,iBAAiB,OAAOnB,cACvB,iEAAiE;gBACjE,kEAAkE;gBAClE,+DAA+D;gBAC/D,sDAAsD;8BACtD,KAAC7H,oBAAoBkK,QAAQ;oBAACxD,OAAOmB;8BAClC9F;qBAGHA;gBAED6F,aAAa5B,MAAM,GAClB,+DAA+D;gBAC/D,6DAA6D;gBAC7D,gEAAgE;gBAChE,gCAAgC;8BAChC,KAAClH;oBACC2D,IAAI;wBAAE6B,eAAe;oBAAO;oBAC5B6F,6BAA0B;oBAC1B5H,aAAW;8BAEX,cAAA,KAAC9D,mBAAmByL,QAAQ;wBAACxD,OAAO3F;kCACjC6G,aAAa5F,GAAG,CAAC,CAACoI,qBACjB,KAAC1L;gCAAoB+B,MAAM2J;+BAAhBA,KAAK/E,GAAG;;qBAIvB;gBACHsC,8BACC,KAAC7D;oBACCC,OAAO4D,cAAc5D,KAAK;oBAC1BC,OAAO2D,cAAcI,OAAO,CAAC/B,MAAM;qBAEnC;gBACHgD,iBACC,+DAA+D;gBAC/D,mEAAmE;gBACnE,gEAAgE;8BAChE,KAAClK;oBAAI2D,IAAI;wBAAE6B,eAAe;oBAAO;oBAAG+F,2BAAwB;8BAC1D,cAAA,KAAC5L,mBAAmByL,QAAQ;wBAACxD,OAAO3F;kCAClC,cAAA,KAACrC;4BAAK+B,MAAMuI;;;qBAGd;gBACHX,eACC,4DAA4D;gBAC5D,yDAAyD;gBACzD,gEAAgE;gBAChE,4CAA4C;8BAC5C,KAACvJ;oBAAI2D,IAAI;wBAAE6B,eAAe;oBAAO;oBAAGgG,gCAA6B;8BAC/D,cAAA,KAAC7L,mBAAmByL,QAAQ;wBAACxD,OAAO3F;kCAClC,cAAA,KAACrC;4BAAK+B,MAAM4H;;;qBAGd;gBACHzC,+BACC,KAACvD;oBACCC,OAAO,EAAE7B,yBAAAA,cAAAA,KAAMF,KAAK,qBAAXE,WAAa,CAAC,UAAU;qBAEjC;gBACHsF,sCAAwB,KAAC1F,yBAAuB;;;;AAIzD,IACD;AACD2E,SAASlE,WAAW,GAAG;AACvBkE,SAASuF,KAAK,GAAG;AAEjB,eAAevF,SAAQ"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/besigner/feature/designer/src/lib/components/node-leaf.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2023 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 {\n buildComponentDefaultIconPaths,\n buildComponentDefaultTokens,\n buildComponentDefaultValues,\n composeReusableComponentNodes,\n displayBindingTokens,\n FORM_COMPONENT_ID,\n hasBindings,\n HostViewType,\n LAYOUT_SLOT_COMPONENT_ID,\n NODE_ROOT_ID,\n placedFormPlacement,\n resolveBindings,\n resolveComponentPropTokens,\n REUSABLE_INSTANCE_COMPONENT_ID,\n} from '@aglyn/aglyn'\nimport {\n Branch,\n Leaf,\n type LeafProps,\n RendererComponents,\n Stem,\n Trunk,\n} from '@aglyn/aglyn-node-renderer'\nimport * as Besigner from '@aglyn/besigner'\nimport { alpha, Box } from '@mui/material'\nimport { observer } from 'mobx-react-lite'\nimport { forwardRef, useCallback, useContext, useMemo, useRef } from 'react'\nimport BindingPickerContext from '../contexts/binding-picker-context'\nimport CanvasRevealContext, {\n CanvasMutedClassesContext,\n} from '../contexts/canvas-reveal-context'\nimport ComponentPromotionContext from '../contexts/component-promotion-context'\nimport { useRenderedCanvasElements } from '../contexts/rendered-canvas-elements'\nimport useAglynBesignerFlag from '../hooks/use-aglyn-besigner-flag'\nimport { useNodeWithMediaAssetFacts } from '../hooks/use-media-asset-facts-overlay'\nimport useNodeWithHostTokens from '../hooks/use-node-with-host-tokens'\nimport {\n useNodeWithRepeatRecord,\n useRepeatCopies,\n useRepeatPreview,\n} from '../hooks/use-repeat-preview'\nimport { RepeatRecordContext } from '../contexts/repeat-record-context'\nimport {\n isNodeHiddenOnSite,\n isNodeRevealedOnCanvas,\n} from '../utils/canvas-reveal'\nimport { stripMutedClasses } from '../utils/muted-classes'\nimport DraggableDroppable from './dnd/draggable-droppable'\nimport EmptyDocumentSlot from './empty-document-slot'\n\n/**\n * The plain `Leaf`, drawing a node the way the published page composes it: its\n * host variables filled in from the site being edited (AGL-2881), and a placed\n * image or film with its DAM asset's current facts (AGL-2838, AGL-2856).\n *\n * For the nodes the canvas renders that are not canvas nodes — a component\n * instance's definition, a placed form's design, the layout chrome — and so\n * never reach `NodeLeaf`. A footer's `{{host.businessName}}` still has to read\n * as the site's name, an asset inside one of them still has to show the shape\n * the published page gives it, and a film its poster.\n */\nexport const MediaFactsLeaf = forwardRef<any, LeafProps>((props, ref) => {\n const { node, ...rest } = props\n const withHostTokens = useNodeWithHostTokens(node)\n const withRecord = useNodeWithRepeatRecord(withHostTokens)\n const shown = useNodeWithMediaAssetFacts(withRecord)\n return <Leaf ref={ref} node={shown} {...rest} />\n})\nMediaFactsLeaf.displayName = 'MediaFactsLeaf'\n\n/**\n * Renderer overrides for the inside of a component instance (AGL-1251).\n *\n * The plain `Leaf` (through `MediaFactsLeaf`), deliberately, where the canvas\n * otherwise uses `NodeLeaf`: the definition's nodes are NOT in the canvas, so\n * anything that made them draggable, selectable or droppable would be reaching\n * for canvas state that has no entry for them. Rendering them inert is not a\n * restriction bolted on afterwards — it is the only thing these nodes can be.\n */\nconst INERT_RENDERER = {\n TrunkComponent: Trunk,\n StemComponent: Stem,\n BranchComponent: Branch,\n LeafComponent: MediaFactsLeaf,\n}\n\n/**\n * Nested-object form of a normalized node map, which is what `Branch` walks\n * (`node.children`). Cycle-safe via `seen`: a definition that somehow\n * referenced an ancestor would otherwise recurse until the stack gave out.\n */\nexport function denormalizeTree(\n nodes: Record<string, any>,\n rootId: string,\n seen: Set<string> = new Set(),\n): any {\n const node = nodes?.[rootId]\n if (!node || seen.has(rootId)) return undefined\n seen.add(rootId)\n const childIds = Array.isArray(node.nodes) ? node.nodes : []\n return {\n ...node,\n children: childIds\n .map((childId: string) => denormalizeTree(nodes, childId, seen))\n .filter(Boolean),\n }\n}\n\nexport interface NodeLeafProps extends LeafProps {}\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\n/**\n * Visible placement marker for the LayoutSlot while editing a layout. The\n * slot is a passthrough at runtime, so without this it disappears once it\n * has children and designers lose track of where screen content lands.\n */\nconst SlotMarker = ({ caption }: { caption?: string }) => (\n <Box\n aria-hidden\n data-aglyn-slot-marker=\"\"\n sx={{\n m: 1,\n p: 2,\n minHeight: 64,\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n gap: 0.5,\n borderWidth: 2,\n borderStyle: 'dashed',\n // Brand blue on a faint tint, not a grey dashed box: the slot is the\n // one region the layout does not own, and it should not read as just\n // another empty container.\n borderColor: SLOT_ACCENT,\n backgroundColor: alpha(SLOT_ACCENT, 0.06),\n borderRadius: 1,\n color: 'text.secondary',\n fontSize: 13,\n textAlign: 'center',\n }}\n >\n <Box component=\"span\" sx={{ color: SLOT_ACCENT, fontWeight: 700 }}>\n {'◇ layout-slot'}\n </Box>\n <Box component=\"span\">{caption || 'Screen content renders here'}</Box>\n </Box>\n)\n\n/**\n * The repeat badge (AGL-3111).\n *\n * Editor chrome, so it holds its accent literally for the reason\n * {@link SLOT_ACCENT} does: the canvas renders under the SITE's palette, and a\n * theme token here would repaint the editor's own furniture whenever a\n * subscriber restyled their site.\n *\n * It cannot reach a published page: only `NodeLeaf` draws it, and a published\n * page is composed from stored nodes and rendered through the plain `Leaf`.\n * The stored node is untouched either way — this is a render copy, like every\n * other thing the canvas lays over a node.\n */\nconst REPEAT_ACCENT = '#7C4DFF'\n\n/** The chip's own ink, for the same reason and read against that accent. */\nconst REPEAT_INK = '#FFFFFF'\n\nconst RepeatBadge = ({ label, count }: { label: string; count: number }) => (\n <Box\n aria-hidden\n data-aglyn-repeat-badge=\"\"\n sx={{\n // Out of the element's own layout: a badge that took part in a flex row\n // would move the design it is describing.\n position: 'absolute',\n top: 0,\n right: 0,\n zIndex: 2,\n // Never swallows a click — the element under it stays selectable.\n pointerEvents: 'none',\n px: 0.75,\n py: 0.25,\n maxWidth: '100%',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n borderBottomLeftRadius: 3,\n backgroundColor: REPEAT_ACCENT,\n color: REPEAT_INK,\n fontSize: 11,\n fontWeight: 700,\n lineHeight: 1.6,\n letterSpacing: 0.2,\n }}\n >\n {`⟳ ${label} · ${count} ${count === 1 ? 'record' : 'records'}`}\n </Box>\n)\n\nexport const NodeLeaf = observer(\n forwardRef<any, NodeLeafProps>((props, ref) => {\n const { node, children, ...rest } = props\n const [viewType] = useAglynBesignerFlag('viewType')\n // The canvas element registry, finally given a WRITER (AGL-2486).\n //\n // `RenderedCanvasElementsProvider` has existed since the registry was\n // introduced and `setElementRef`/`deleteElementRef` were called from\n // NOWHERE in the repo — `elements.current` was `{}` for the life of every\n // session. Everything that reads it therefore read an empty map and\n // failed the only way an empty map can: silently.\n //\n // Two features were void because of it, and neither logged anything:\n //\n // `usePresence`'s cursor broadcast resolves the canvas root through\n // this registry and bails with `if (!root) return`, so no editor has\n // EVER written a cursor position. Confirmed against production RTDB on\n // 2026-08-22: three live presence entries across three rooms, not one\n // carrying `cursorX`/`cursorY`.\n //\n // `CollaboratorOverlays` resolves both the canvas root and each peer's\n // selected node here, so `placements` was always empty and the overlay\n // returned null. Confirmed live in the running console: a collaborator\n // entry carrying both a cursor and a selection rendered its avatar and\n // drew no cursor, no selection box and no name label.\n //\n // Registering here rather than in the renderer keeps it to the canvas:\n // `NodeLeaf` is the besigner's leaf component, so component-definition\n // previews (which render the plain `Leaf` through `INERT_RENDERER`) stay\n // out of the registry — they are not in the canvas and must not be\n // addressable as though they were.\n //\n // `NODE_ROOT_ID` and `CANVAS_ROOT_ELEMENT_ID` are the same string\n // (`'_@_'`), so keying on `$id` registers the canvas root under the id\n // the cursor code already looks for, with no special case.\n const { elements, setElementRef, deleteElementRef } =\n useRenderedCanvasElements()\n const $id = node?.$id\n // What THIS leaf last put in the registry. A remount can hand the new\n // element its ref before the old one is cleared, so an unconditional\n // delete on `null` would evict the live registration and put the\n // registry back to the state this whole change exists to fix.\n const registered = useRef<Element | null>(null)\n const registerElement = useCallback(\n (element: unknown) => {\n // Forward FIRST and unconditionally: dnd and the renderer already\n // depend on this ref, and registration must never change what they\n // observe.\n if (typeof ref === 'function') ref(element)\n else if (ref) (ref as { current: unknown }).current = element\n if (!$id) return\n if (element instanceof Element) {\n registered.current = element\n setElementRef($id, {\n $id,\n node: element,\n dragHandle: undefined,\n } as never)\n return\n }\n // Cleared (or never a DOM element — a class instance, say). Only\n // withdraw the registration if it is still OURS.\n const current = elements?.current?.[$id]?.node\n if (!registered.current || current === registered.current) {\n deleteElementRef($id)\n }\n registered.current = null\n },\n [ref, $id, elements, setElementRef, deleteElementRef],\n )\n const showSlotMarker =\n node?.componentId === LAYOUT_SLOT_COMPONENT_ID &&\n viewType === HostViewType.LAYOUT\n // A document with no nodes has nothing to aim at. Wrapped in a shared\n // layout it is worse than empty — the slot passes its children straight\n // through, so the root collapses to a zero-height strip between locked\n // chrome and the nav renders directly into the footer. Hung off the ROOT\n // leaf, which is already inside `DraggableDroppable`, so giving it height\n // is all a drop needs to resolve against the document root.\n const showEmptyDocumentSlot =\n node?.$id === NODE_ROOT_ID &&\n !node?.nodes?.length &&\n viewType !== HostViewType.LAYOUT\n\n // Nodes the author has opened up for designing (AGL-592). Through a\n // context rather than the flag itself: the canvas subscribes once and\n // every leaf reads the result.\n const revealedNodeIds = useContext(CanvasRevealContext)\n const mutedClasses = useContext(CanvasMutedClassesContext)\n\n // WYSIWYG bindings (AGL-97): resolve variable/function tokens\n // live on the rendered copy (selection/dnd keep the original node).\n // Bound nodes are flagged either way so editors can spot them.\n const [resolveFlag] = useAglynBesignerFlag('resolveBindings')\n const { variables, functions, componentProps } = useContext(\n BindingPickerContext,\n )\n const boundProps = useMemo(\n () =>\n Object.entries(node?.props ?? {}).filter(\n ([, value]) => typeof value === 'string' && hasBindings(value),\n ),\n // MobX props are observable; the JSON string keys the memo cheaply.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [node, JSON.stringify(node?.props ?? {})],\n )\n /**\n * A component editor draws with the definition's OWN defaults\n * (AGL-2870), so `{{prop.headline}}` shows the headline and a film with a\n * default source actually plays.\n *\n * Only props that HAVE a default are substituted. One without stays a\n * visible token, which is the signal an author needs: the slot is real\n * and nothing fills it yet. Substituting `''` there would draw an empty\n * component and hide the very thing they still have to decide.\n *\n * On a screen this path does nothing — the props belong to the component\n * route, and an instance is composed through `composeReusableComponentNodes`\n * below, which is the single substitution path a published page uses.\n */\n const defaultPropTokens = useMemo(\n () => buildComponentDefaultTokens(componentProps),\n [componentProps],\n )\n // The paths of the default icons, which a field bound to an icon property\n // draws with — the editor has no reason to have loaded the icon catalog.\n const defaultIconPaths = useMemo(\n () => buildComponentDefaultIconPaths(componentProps),\n [componentProps],\n )\n // The values the defaults hand their bound fields (AGL-2893), so a list\n // stays a list and a theme multiple a number on the editor's canvas too.\n const defaultValues = useMemo(\n () => buildComponentDefaultValues(componentProps),\n [componentProps],\n )\n const renderNode = useMemo(() => {\n const hasDeclaredProps = Boolean(componentProps?.length)\n if (\n !boundProps.length ||\n (!Object.keys(variables ?? {}).length &&\n !Object.keys(functions ?? {}).length &&\n !hasDeclaredProps)\n ) {\n return node\n }\n const resolved: Record<string, unknown> = { ...(node?.props ?? {}) }\n for (const [key, value] of boundProps) {\n // Resolve toggle off → show friendly token text: id tokens map to\n // the referent's CURRENT name (AGL-186), never raw doc ids.\n resolved[key] =\n resolveFlag === false\n ? displayBindingTokens(\n value as string,\n (variables ?? {}) as any,\n (functions ?? {}) as any,\n )\n : resolveBindings(\n value as string,\n (variables ?? {}) as any,\n (functions ?? {}) as any,\n )\n }\n const next = { ...node, props: resolved }\n if (!hasDeclaredProps) return next\n // Through the same substitution a placed instance uses, keyed by this\n // node alone — one code path deciding what a token becomes, never two\n // that could disagree.\n //\n // The raw-token view substitutes no defaults: an author who turned\n // resolution OFF asked to see the tokens, and a default is a resolved\n // value like any other. A field bound to a Yes/no property still\n // receives a yes or a no there, because a switch has no way to show a\n // token and reads the token's text as a yes.\n return (\n resolveComponentPropTokens(\n { [String(node?.$id)]: next as any },\n componentProps,\n resolveFlag === false ? undefined : defaultPropTokens,\n resolveFlag === false ? undefined : defaultIconPaths,\n resolveFlag === false ? undefined : defaultValues,\n )[String(node?.$id)] ?? next\n )\n }, [\n node,\n boundProps,\n resolveFlag,\n variables,\n functions,\n componentProps,\n defaultPropTokens,\n defaultIconPaths,\n defaultValues,\n ])\n\n // Host variables (AGL-2881), filled in on the same render copy and after\n // the bindings and the component's own defaults, which is the order the\n // published page composes in: a default that names the host resolves\n // too. The raw-token view reaches this leaf as no site at all\n // (`CanvasHostTokensProvider`), so its tokens stay as written. Keyed by\n // `boundProps`, which follows the observable props this copy reads.\n const hostResolvedNode = useNodeWithHostTokens(renderNode, boundProps)\n\n // Classes switched off for comparison (AGL-2486). Composed onto the SAME\n // render copy the binding resolution builds, never onto the canvas node:\n // selection, the hierarchy and every save keep reading the element's real\n // class list, and the canvas paints without the switched-off names.\n const renderNodeUnclassed = useMemo(\n () =>\n stripMutedClasses(hostResolvedNode as never, node?.$id, mutedClasses),\n [hostResolvedNode, node, mutedClasses],\n )\n\n /**\n * The record a repeat above this one is drawing (AGL-3111).\n *\n * Applied in the same place, and for the same reason, as the host tokens\n * and the DAM facts above: the published page substitutes `{{item.*}}`\n * when it composes, so the canvas substitutes it on the render copy while\n * the node keeps the tokens every save writes.\n */\n const withRepeatRecord = useNodeWithRepeatRecord(renderNodeUnclassed)\n\n // A placed asset's shape, and a film's length and poster, as its DAM\n // document records them NOW (AGL-2838, AGL-2856). The published page lays\n // the asset over the node when it is composed, so the canvas lays it over\n // the same render copy: a replace shows here as it shows to a visitor,\n // while selection, the panels and every save keep reading the node's\n // stored props.\n const shownNode = useNodeWithMediaAssetFacts(withRepeatRecord)\n\n /**\n * What this element repeats over, and the copies it draws (AGL-3111).\n *\n * The first record's copy is the element's own children — real canvas\n * nodes an author selects, drags and edits — with that record laid over\n * their render copies through {@link RepeatRecordContext}. Records 2..n\n * are inert pictures of the same template, built by the page's own\n * expansion. Editing the template therefore edits every copy, because\n * there is only ever one.\n */\n const repeatPreview = useRepeatPreview(node)\n const repeatCopies = useRepeatCopies(node, repeatPreview)\n const firstRecord = useMemo(\n () =>\n repeatPreview\n ? {\n record: repeatPreview.records[0],\n model: repeatPreview.dataset?.model,\n datasetsByKey: repeatPreview.datasetsByKey,\n }\n : undefined,\n [repeatPreview],\n )\n\n // A component instance renders its definition (AGL-1251) instead of the\n // named dashed box. Authors placed a hero and saw a grey rectangle, so\n // the props they filled in were invisible until Preview.\n //\n // Rendered, never grafted: the definition's nodes stay out of the canvas\n // entirely. That is what makes this safe — there is nothing to lock,\n // nothing to strip before a save, and no way for a definition's node to\n // be persisted into the document that placed it. Same shape as the\n // binding resolution above, which renders a resolved copy while\n // selection and dnd keep the original node.\n const { definitions, formDesigns } = useContext(ComponentPromotionContext)\n const instanceTree = useMemo(() => {\n if (node?.componentId !== REUSABLE_INSTANCE_COMPONENT_ID) {\n return undefined\n }\n // Already expanded into this canvas — the layout-chrome store grafts\n // before it loads (AGL-1218), so its instances arrive with children.\n // Rendering here too would draw the nav twice.\n if (node?.nodes?.length) return undefined\n const props = (node?.props ?? {}) as { refId?: string }\n const definition = props.refId ? definitions?.[props.refId] : undefined\n if (!definition?.nodes || !definition?.rootId) return undefined\n // Plain snapshot: props are MobX observables and the graft reads nested\n // `propValues` off them. The placement's classes stay out of it, as its\n // `sx` does: this leaf already renders both on the element around the\n // preview, and the graft joins a placement's classes onto the root it\n // becomes. Handed them here, the preview's root would apply every class\n // a second time, and keep `aglyn-hidden` collapsed inside a placement\n // the canvas has revealed for designing.\n const { className: _placementClasses, ...snapshotProps } = JSON.parse(\n JSON.stringify(node.props ?? {}),\n ) as Record<string, unknown>\n // Reuse the real graft so the canvas resolves `{{prop.*}}` exactly as\n // the published page will — one substitution path, not a second one\n // that could disagree about which value wins.\n const composed = composeReusableComponentNodes(\n {\n [node.$id]: {\n $id: node.$id,\n componentId: REUSABLE_INSTANCE_COMPONENT_ID,\n props: snapshotProps,\n // Root style overrides (AGL-1306) ride the same snapshot so\n // the canvas renders the SAME merged root sx the published\n // page will — the graft is the one merge point.\n ...(node.styleOverrides && {\n styleOverrides: JSON.parse(JSON.stringify(node.styleOverrides)),\n }),\n // Attribute overrides ride the same snapshot for the same\n // reason (AGL-1899). Leaving them out is invisible rather than\n // broken: Preview and tenant SSR compose from the stored node\n // and WOULD apply them, so the canvas alone would draw the\n // component's own attributes and disagree with both.\n ...(node.attrOverrides && {\n attrOverrides: JSON.parse(JSON.stringify(node.attrOverrides)),\n }),\n nodes: [],\n } as any,\n },\n // The host's WHOLE definitions map, not just this instance's own\n // (AGL-1898). A definition may contain instances of OTHER\n // definitions, and `composeReusableComponentNodes` expands those —\n // but only the ones it is handed. Preview, tenant SSR and the\n // layout-chrome graft all pass the full map, so a one-entry map\n // here made the canvas the only surface that left a nested\n // component as an unexpanded placeholder.\n //\n // It also silently voided propagation for the inner component:\n // publishing a shared button re-rendered every canvas showing it\n // directly and none of the ones showing it through the nav that\n // contains it — which is the case a shared component is usually in.\n //\n // Safe to widen because the `nodes` argument above is this ONE\n // instance: the extra definitions can only be reached from inside\n // the subtree being grafted, never applied to some other node.\n definitions as any,\n )\n // The graft puts the definition's root IN the instance's place rather\n // than under it (AGL-2521), so the component to draw is at the\n // instance's own id. Reading `nodes[0]` here would render the root's\n // FIRST CHILD and drop the component's outer element with its siblings.\n const grafted = composed[node.$id]\n return grafted && grafted.componentId !== REUSABLE_INSTANCE_COMPONENT_ID\n ? denormalizeTree(composed, node.$id)\n : undefined\n // Observable props/overrides: the JSON strings key the memo, as above.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n node,\n JSON.stringify(node?.props ?? {}),\n JSON.stringify(node?.styleOverrides ?? {}),\n JSON.stringify(node?.attrOverrides ?? {}),\n definitions,\n ])\n\n /**\n * A placed form draws its ENTITY'S published fields, not the page's copy\n * of them (`docs/specs/reusable-forms.md`).\n *\n * The same render-copy treatment the instance above gets, and for the same\n * reason: the entity's nodes stay out of the canvas, so there is nothing to\n * lock, nothing to strip before a save, and no way for a form's fields to\n * be persisted into the screen that placed it.\n *\n * It runs the REAL graft rather than reading `design.nodes` directly, so a\n * component nested inside the form design expands here exactly as it will\n * on the published page — one resolution path, not a second that could\n * disagree.\n *\n * When it resolves, the node's own children are not rendered (below): the\n * published page discards them, and a canvas that drew both would show the\n * author a page that does not exist. Where the entity has no published\n * design the memo is `undefined`, the children render, and the form the\n * author drew is the form they see — which is every form built before the\n * entity existed.\n */\n const placedForm = useMemo(() => {\n if (node?.componentId !== FORM_COMPONENT_ID) return undefined\n const formId = (node?.props as { formId?: unknown } | undefined)?.formId\n if (typeof formId !== 'string' || !formId) return undefined\n const composed = composeReusableComponentNodes(\n {\n [node.$id]: {\n $id: node.$id,\n componentId: FORM_COMPONENT_ID,\n // Plain snapshot: props are MobX observables, as above.\n props: JSON.parse(JSON.stringify(node.props ?? {})),\n nodes: [],\n } as any,\n },\n // Component definitions ride along so an instance INSIDE the form\n // design expands; the map is safe to pass whole for the reason the\n // instance graft above states — the one node handed in is this form.\n definitions as any,\n [placedFormPlacement(formDesigns as any)],\n )\n /*\n * The composed REPLACEMENT is the node the published page draws in this\n * placement's place — the design's root, carrying the placement's id\n * (AGL-2521) — and its children are the design's own.\n *\n * Reading the replacement rather than the placement is what keeps the\n * canvas honest about the element itself. A real form design is a\n * container holding the `form` node, so the placement's `form` used to\n * be drawn AROUND the design's: two `<form>` elements nested, each\n * drawing its own send button, where a visitor sees one. Taking only\n * the first child also dropped everything a design put beside its form.\n */\n const replacement = composed[node.$id]\n const childIds = (replacement?.nodes as string[] | undefined) ?? []\n if (!replacement || !childIds.length) return undefined\n const trees = childIds\n .map((childId) => denormalizeTree(composed, childId))\n .filter(Boolean)\n return trees.length ? { replacement, trees } : undefined\n // Observable props: the JSON string keys the memo, as above.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [node, JSON.stringify(node?.props ?? {}), definitions, formDesigns])\n\n /**\n * The element this leaf DRAWS, once a placed form resolves.\n *\n * The published page renders the entity's root here, not the placement:\n * the two denote the same element and the graft merges them. The canvas\n * draws the same one, so an author sees the form their visitors will. As\n * with the DAM facts and the host tokens above, this is a render copy —\n * selection, the panels and every save keep reading `node`.\n */\n const shownLeafNode = useMemo(\n () =>\n placedForm\n ? ({\n ...(shownNode as Record<string, unknown>),\n componentId: placedForm.replacement.componentId,\n props: (placedForm.replacement.props ?? {}) as never,\n ...(placedForm.replacement.sx === undefined\n ? {}\n : { sx: placedForm.replacement.sx }),\n } as typeof shownNode)\n : shownNode,\n [placedForm, shownNode],\n )\n\n return (\n <DraggableDroppable\n node={node}\n type={Besigner.DragType.CANVAS}\n accept={Object.values(Besigner.DragType)}\n disableDragging={!Besigner.dnd.canDragNode(node)}\n >\n <Leaf\n ref={registerElement}\n node={shownLeafNode as typeof node}\n data-aglyn-selected={Besigner.focus.isNodeSelected(node)}\n // Present while the selection lives in this node's subtree (the\n // node itself or any descendant). Canvas-aware components (nav\n // menus, drawers) read this neutral leaf attribute to expand\n // only while they are being authored (AGL-571). Presence-based\n // (''/undefined) so unaffected leaves carry no attribute.\n data-aglyn-selected-within={\n Besigner.focus.isNodeOrDescendantSelected(node) ? '' : undefined\n }\n // Present on an element that carries the hidden class while the\n // canvas is showing it anyway (AGL-592) — the flag the canvas\n // stylesheet checks before collapsing it. Stamped on the hidden\n // element itself, so no wrapper a component renders around its\n // children can come between the two.\n data-aglyn-revealed={\n isNodeHiddenOnSite(node) &&\n isNodeRevealedOnCanvas(node, revealedNodeIds)\n ? ''\n : undefined\n }\n data-aglyn-bound={boundProps.length ? '' : undefined}\n {...rest}\n >\n {/* A resolved form entity REPLACES the page's own fields, exactly\n as the published page composes it — see `placedForm`. */}\n {placedForm ? null : firstRecord ? (\n // The template IS the first copy: the real children, drawing the\n // first record. Provided around them rather than applied to them,\n // so every descendant leaf resolves its own tokens the way the\n // page's substitution walks the whole cloned subtree.\n <RepeatRecordContext.Provider value={firstRecord}>\n {children}\n </RepeatRecordContext.Provider>\n ) : (\n children\n )}\n {repeatCopies.length ? (\n // Inert and click-through like the component and form previews\n // below, and for the same reason: a copy is a picture of the\n // template, not a document. Selecting one would offer an author\n // an element no save can reach.\n <Box\n sx={{ pointerEvents: 'none' }}\n data-aglyn-repeat-preview=\"\"\n aria-hidden\n >\n <RendererComponents.Provider value={INERT_RENDERER as any}>\n {repeatCopies.map((copy) => (\n <Stem key={copy.$id} node={copy} />\n ))}\n </RendererComponents.Provider>\n </Box>\n ) : null}\n {repeatPreview ? (\n <RepeatBadge\n label={repeatPreview.label}\n count={repeatPreview.records.length}\n />\n ) : null}\n {placedForm ? (\n // Inert and click-through like the instance preview below: the\n // fields belong to the form, and they are edited in the form's own\n // besigner, so the only selectable thing here is the placement.\n <Box sx={{ pointerEvents: 'none' }} data-aglyn-form-preview=\"\">\n <RendererComponents.Provider value={INERT_RENDERER as any}>\n {placedForm.trees.map((tree) => (\n <Stem key={tree.$id} node={tree} />\n ))}\n </RendererComponents.Provider>\n </Box>\n ) : null}\n {instanceTree ? (\n // `pointerEvents: none` so a click anywhere on the rendered\n // component still lands on the instance beneath it — the\n // instance is the only thing here that can be selected, and the\n // only thing whose Attributes are editable.\n <Box sx={{ pointerEvents: 'none' }} data-aglyn-component-preview=\"\">\n <RendererComponents.Provider value={INERT_RENDERER as any}>\n <Stem node={instanceTree} />\n </RendererComponents.Provider>\n </Box>\n ) : null}\n {showSlotMarker ? (\n <SlotMarker\n caption={node?.props?.['caption'] as string | undefined}\n />\n ) : null}\n {showEmptyDocumentSlot ? <EmptyDocumentSlot /> : null}\n </Leaf>\n </DraggableDroppable>\n )\n }),\n)\nNodeLeaf.displayName = 'BesignerLeafComponent'\nNodeLeaf.aglyn = true\n\nexport default NodeLeaf\n"],"names":["buildComponentDefaultIconPaths","buildComponentDefaultTokens","buildComponentDefaultValues","composeReusableComponentNodes","displayBindingTokens","FORM_COMPONENT_ID","hasBindings","HostViewType","LAYOUT_SLOT_COMPONENT_ID","NODE_ROOT_ID","placedFormPlacement","resolveBindings","resolveComponentPropTokens","REUSABLE_INSTANCE_COMPONENT_ID","Branch","Leaf","RendererComponents","Stem","Trunk","Besigner","alpha","Box","observer","forwardRef","useCallback","useContext","useMemo","useRef","BindingPickerContext","CanvasRevealContext","CanvasMutedClassesContext","ComponentPromotionContext","useRenderedCanvasElements","useAglynBesignerFlag","useNodeWithMediaAssetFacts","useNodeWithHostTokens","useNodeWithRepeatRecord","useRepeatCopies","useRepeatPreview","RepeatRecordContext","isNodeHiddenOnSite","isNodeRevealedOnCanvas","stripMutedClasses","DraggableDroppable","EmptyDocumentSlot","MediaFactsLeaf","props","ref","node","rest","withHostTokens","withRecord","shown","displayName","INERT_RENDERER","TrunkComponent","StemComponent","BranchComponent","LeafComponent","denormalizeTree","nodes","rootId","seen","Set","has","undefined","add","childIds","Array","isArray","children","map","childId","filter","Boolean","SLOT_ACCENT","SlotMarker","caption","aria-hidden","data-aglyn-slot-marker","sx","m","p","minHeight","display","flexDirection","alignItems","justifyContent","gap","borderWidth","borderStyle","borderColor","backgroundColor","borderRadius","color","fontSize","textAlign","component","fontWeight","REPEAT_ACCENT","REPEAT_INK","RepeatBadge","label","count","data-aglyn-repeat-badge","position","top","right","zIndex","pointerEvents","px","py","maxWidth","overflow","textOverflow","whiteSpace","borderBottomLeftRadius","lineHeight","letterSpacing","NodeLeaf","viewType","elements","setElementRef","deleteElementRef","$id","registered","registerElement","element","current","Element","dragHandle","showSlotMarker","componentId","LAYOUT","showEmptyDocumentSlot","length","revealedNodeIds","mutedClasses","resolveFlag","variables","functions","componentProps","boundProps","Object","entries","value","JSON","stringify","defaultPropTokens","defaultIconPaths","defaultValues","renderNode","hasDeclaredProps","keys","resolved","key","next","String","hostResolvedNode","renderNodeUnclassed","withRepeatRecord","shownNode","repeatPreview","repeatCopies","firstRecord","record","records","model","dataset","datasetsByKey","definitions","formDesigns","instanceTree","definition","refId","parse","className","_placementClasses","snapshotProps","composed","styleOverrides","attrOverrides","grafted","placedForm","formId","replacement","trees","shownLeafNode","type","DragType","CANVAS","accept","values","disableDragging","dnd","canDragNode","data-aglyn-selected","focus","isNodeSelected","data-aglyn-selected-within","isNodeOrDescendantSelected","data-aglyn-revealed","data-aglyn-bound","Provider","data-aglyn-repeat-preview","copy","data-aglyn-form-preview","tree","data-aglyn-component-preview","aglyn"],"mappings":"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"}
|
|
@@ -54,6 +54,19 @@ export interface NodeStyleTarget {
|
|
|
54
54
|
readonly overrideKey: string;
|
|
55
55
|
/** True when the target is a leaf inside the component, not its root. */
|
|
56
56
|
readonly isLeafOverride: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* True when `sx` above is a COMPOSITION of two stored records rather than
|
|
59
|
+
* one — a plain node that carries both a `props.sx` and a `node.sx`
|
|
60
|
+
* (AGL-3218).
|
|
61
|
+
*
|
|
62
|
+
* The panel needs this to say so. Everything about the split is correct
|
|
63
|
+
* and invisible while you stay in the UI, and misleading the moment you
|
|
64
|
+
* leave it: the stored node does not contain the document the editor
|
|
65
|
+
* shows, and a reader who opens `props.sx` alone — a Raw JSON pane, an
|
|
66
|
+
* export, a script auditing the corpus — sees an edit that looks like it
|
|
67
|
+
* never landed.
|
|
68
|
+
*/
|
|
69
|
+
readonly isComposed: boolean;
|
|
57
70
|
/** Replaces the target sx wholesale (MobX action inside). */
|
|
58
71
|
setSx(next: Record<string, any> | undefined): void;
|
|
59
72
|
}
|
|
@@ -155,6 +155,9 @@ const isPlainRecord = (value)=>typeof value === 'object' && value !== null && !A
|
|
|
155
155
|
isInstanceOverride: false,
|
|
156
156
|
overrideKey: '',
|
|
157
157
|
isLeafOverride: false,
|
|
158
|
+
get isComposed () {
|
|
159
|
+
return nodePropsSx(node) !== undefined;
|
|
160
|
+
},
|
|
158
161
|
get sx () {
|
|
159
162
|
// The COMPOSED record, not `node.sx` alone (AGL-1346) — see
|
|
160
163
|
// `composedNodeSx`. Identity is preserved when the node has no
|
|
@@ -178,6 +181,9 @@ const isPlainRecord = (value)=>typeof value === 'object' && value !== null && !A
|
|
|
178
181
|
isInstanceOverride: true,
|
|
179
182
|
overrideKey: key,
|
|
180
183
|
isLeafOverride: key !== STYLE_OVERRIDES_ROOT_KEY,
|
|
184
|
+
// An override slice is read uncomposed (see below), so what the editor
|
|
185
|
+
// shows IS the stored record and there is nothing to disclose.
|
|
186
|
+
isComposed: false,
|
|
181
187
|
get sx () {
|
|
182
188
|
var _node_styleOverrides;
|
|
183
189
|
return (_node_styleOverrides = node.styleOverrides) == null ? void 0 : _node_styleOverrides[key];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../libs/besigner/feature/designer/src/lib/utils/style-target.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type * as Aglyn from '@aglyn/aglyn'\nimport {\n mergeNodeSx,\n REUSABLE_INSTANCE_COMPONENT_ID,\n STYLE_OVERRIDES_ROOT_KEY,\n} from '@aglyn/aglyn'\nimport { expandSxAliases } from '@aglyn/shared-data-enums'\nimport isEqual from 'lodash-es/isEqual.js'\nimport { action, toJS } from 'mobx'\n\n/**\n * Where the Styles panel's edits land for the selected node (AGL-1306,\n * AGL-1332, AGL-1346).\n *\n * A plain node's edits read and write `node.sx`, exactly as they always\n * have. A reusable-component INSTANCE's edits read and write one slice of\n * `node.styleOverrides` instead — the root slice by default, or the slice\n * keyed by a DEFINITION-internal node id when the author has picked a leaf\n * inside the component. Either way the panel behaves like it does on a\n * fresh plain node (empty until this instance overrides something), and\n * the canvas/Preview/tenant render the override merged over the matching\n * component node via `composeReusableComponentNodes`. Mutating the\n * component itself from a placing document is exactly what this layer\n * exists to avoid — a leaf target styles the instance's copy of that leaf,\n * and never unlocks the component's content, which stays the component's.\n *\n * `sx` is a GETTER so observer components track the underlying MobX state\n * at read time; a snapshot captured when the target was built would go\n * stale after the first write.\n */\nexport interface NodeStyleTarget {\n /**\n * The sx record edits read from and write to (live view).\n *\n * For a plain node this is the COMPOSED record — `props.sx` with\n * `node.sx` merged over it (AGL-1346) — so what the panel displays is\n * what renders. Writes are split back across the two records by\n * {@link writeComposedNodeSx}.\n */\n readonly sx: Record<string, any> | undefined\n /** True when edits land in one of the instance's override slices. */\n readonly isInstanceOverride: boolean\n /**\n * The `styleOverrides` key being edited — `root`, or a definition-internal\n * node id. Empty string for a plain node, which has no override layer.\n */\n readonly overrideKey: string\n /** True when the target is a leaf inside the component, not its root. */\n readonly isLeafOverride: boolean\n /** Replaces the target sx wholesale (MobX action inside). */\n setSx(next: Record<string, any> | undefined): void\n}\n\nconst isPlainRecord = (value: unknown): value is Record<string, any> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\n/**\n * A node's `props.sx` — the SECOND sx record every node can carry\n * (AGL-1346).\n *\n * `Leaf` composes `mergeSxProps(sx, props.sx, node.sx)` and MUI's array\n * form lets later entries win, so `node.sx` shadows `props.sx` key by key\n * — but only the keys it names. Everything `props.sx` sets and `node.sx`\n * does not is rendering, unshadowed, and until this issue no panel could\n * see it: the Styles panel read `node.sx` alone and reported \"Margin:\n * default on all four edges\" over a `marginInline: auto` that was\n * genuinely in the layout. Presets authored the record (`props: { sx: … }`)\n * and nothing could unauthor it.\n *\n * Returned as a live read, undefined when the node carries no such record\n * or carries a non-record one (an array/callback sx composes but does not\n * merge).\n */\nfunction nodePropsSx(\n node: Aglyn.NodeSchema<any> | null | undefined,\n): Record<string, any> | undefined {\n const value = (node?.props as Record<string, any> | undefined)?.['sx']\n return isPlainRecord(value) ? value : undefined\n}\n\n/**\n * What the panel SHOWS for a plain node: `props.sx` with `node.sx` merged\n * over it — the same precedence the renderer applies, so the displayed\n * value is the effective one (AGL-1346).\n *\n * `mergeNodeSx` is the house merge (AGL-1306): responsive breakpoint\n * objects cascade mobile-first, `@scheme dark` and nested selector blocks\n * merge key by key, plain properties replace. Reusing it is what keeps the\n * panel's reading of a shadowed value identical to the graft's.\n *\n * A node with no `props.sx` — every node but the ones presets touched —\n * gets `node.sx` back BY IDENTITY, so nothing downstream re-renders or\n * re-computes because this function exists.\n */\nfunction composedNodeSx(\n node: Aglyn.NodeSchema<any> | null | undefined,\n): Record<string, any> | undefined {\n const nodeSx = node?.sx as Record<string, any> | undefined\n const propsSx = nodePropsSx(node)\n if (!propsSx) return nodeSx\n if (nodeSx !== undefined && !isPlainRecord(nodeSx)) return nodeSx\n return mergeNodeSx(toJS(propsSx), toJS(nodeSx)) as Record<string, any>\n}\n\n/**\n * Splits the panel's write back across the two records (AGL-1346).\n *\n * The panel hands us the whole intended sx — it always builds `next` from\n * `target.sx`, which is now the COMPOSED record — so a naive\n * `node.sx = next` would copy every `props.sx` key up into `node.sx` on the\n * first unrelated edit. That is the migration this issue explicitly does\n * NOT want: the mega-menu panels keep their entire positioning\n * (`position: absolute`, `top: 100%`, `zIndex: 1300`, `bgcolor`, `p`,\n * `marginInline`) in `props.sx`, and re-emitting those declarations from a\n * later slot changes shorthand/longhand resolution order. So the write is a\n * DIFF against the composed baseline:\n *\n * - a key already in `node.sx` keeps its position there and takes the new\n * value (or goes, if the author cleared it);\n * - a key whose new value is what `props.sx` already says is NOT copied up\n * — nothing changed, so nothing is written;\n * - a key the author genuinely changed is appended to `node.sx`, where it\n * wins exactly as the Styles panel's output has always won;\n * - a key the author CLEARED is deleted from `props.sx` as well, which is\n * the half that makes the panel able to unauthor what a preset wrote.\n *\n * That last rule is why this edits `props.sx` in place rather than writing\n * a neutralising override: there is no value that reliably means \"no\n * declaration\" across sx keys — `unset`/`revert` are cascade keywords with\n * their own meaning, and system props (`p`, `bgcolor`, `zIndex`) run\n * through theme transforms first. Deleting the key is exact, and it is the\n * only record involved when nothing shadows it.\n *\n * The invariant that makes this safe on 8,000 authored nodes: writing back\n * an UNCHANGED composed record leaves both stored records byte-identical,\n * key order included. Only a key the author actually touched moves.\n */\nfunction writeComposedNodeSx(\n node: Aglyn.NodeSchema<any>,\n next: Record<string, any> | undefined,\n): void {\n const propsSx = nodePropsSx(node)\n const current = node.sx as Record<string, any> | undefined\n if (!propsSx || (current !== undefined && !isPlainRecord(current))) {\n // No second record to reconcile (or an sx form that composes rather\n // than merges): the write is what it always was.\n node.sx = (next ?? {}) as any\n return\n }\n const nextSx = (next ?? {}) as Record<string, any>\n // Both stored records are read in the panel's spelling (AGL-2207). The\n // composed baseline the panel edits comes through `mergeNodeSx`, which\n // canonicalizes MUI's system-prop aliases, so diffing against the RAW\n // records would read every `p`/`bgcolor` as a key the author had just\n // cleared and migrate the positioning on the first unrelated edit — the\n // exact thing this function exists to prevent. Expanded, the byte-\n // identical invariant holds; `props.sx` is only ever REWRITTEN below,\n // when the author genuinely cleared one of its values.\n const currentSx = expandSxAliases(\n isPlainRecord(current) ? toJS(current) : {},\n { deep: true },\n )\n const propsRecord = expandSxAliases(toJS(propsSx), { deep: true })\n\n const sx: Record<string, any> = {}\n for (const key of Object.keys(currentSx)) {\n if (key in nextSx) sx[key] = nextSx[key]\n }\n for (const [key, value] of Object.entries(nextSx)) {\n if (key in sx) continue\n if (key in propsRecord && isEqual(propsRecord[key], value)) continue\n sx[key] = value\n }\n node.sx = sx as any\n\n const cleared = Object.keys(propsRecord).filter((key) => !(key in nextSx))\n if (!cleared.length) return\n const remaining = { ...propsRecord }\n for (const key of cleared) delete remaining[key]\n // Assigned as a fresh props object rather than mutated in place: the\n // Attributes form reads `node.props` as its initial values, and a\n // replaced reference is the signal it re-reads on (`updateNodeProps`\n // replaces it too, for the same reason). Not routed through\n // `updateNodeProps`, though — that records its own history entry, and\n // this write is already inside the panel's coalesced `transact` step.\n const props = { ...(toJS(node.props) as Record<string, any>) }\n if (Object.keys(remaining).length) props['sx'] = remaining\n else delete props['sx']\n node.props = props as any\n}\n\n/**\n * The style target for a selected node — see {@link NodeStyleTarget}.\n *\n * `overrideKey` selects WHICH slice of an instance's overrides is edited;\n * it is ignored for a plain node. A falsy key falls back to the root, so a\n * caller that has not resolved a definition yet still edits something real\n * rather than writing an `undefined`-keyed slice no renderer reads.\n */\nexport function getNodeStyleTarget(\n node: Aglyn.NodeSchema<any> | null | undefined,\n overrideKey?: string | null,\n): NodeStyleTarget {\n const isInstance =\n node?.componentId === REUSABLE_INSTANCE_COMPONENT_ID\n if (!node || !isInstance) {\n return {\n isInstanceOverride: false,\n overrideKey: '',\n isLeafOverride: false,\n get sx() {\n // The COMPOSED record, not `node.sx` alone (AGL-1346) — see\n // `composedNodeSx`. Identity is preserved when the node has no\n // `props.sx`, which is every node but the handful presets touched.\n return composedNodeSx(node)\n },\n setSx: action((next: Record<string, any> | undefined) => {\n if (!node) return\n writeComposedNodeSx(node, next)\n }),\n }\n }\n // An instance's target is deliberately NOT composed with anything\n // (AGL-1346): an override slice starts empty and empty MEANS \"whatever\n // the component does\" (AGL-1338) — merging a base into the reading is\n // exactly the distinction the override badge and the Background Fill\n // control exist to keep. A `props.sx` on a definition node is shown and\n // cleared where it lives, by selecting that node inside the component.\n const key = overrideKey || STYLE_OVERRIDES_ROOT_KEY\n return {\n isInstanceOverride: true,\n overrideKey: key,\n isLeafOverride: key !== STYLE_OVERRIDES_ROOT_KEY,\n get sx() {\n return (node.styleOverrides as Record<string, any> | undefined)?.[key]\n },\n setSx: action((next: Record<string, any> | undefined) => {\n const overrides: Record<string, any> = {\n ...toJS(node.styleOverrides ?? {}),\n }\n if (next && Object.keys(next).length > 0) {\n overrides[key] = next\n } else {\n // An emptied override is REMOVED, not stored as `{}`: the panel's\n // override badge and the graft's \"has an override\" check must both\n // read a cleared instance as clean. Only THIS slice goes — the\n // instance's other targets keep theirs.\n delete overrides[key]\n }\n node.styleOverrides =\n Object.keys(overrides).length > 0 ? overrides : undefined\n }),\n }\n}\n\nexport default getNodeStyleTarget\n"],"names":["mergeNodeSx","REUSABLE_INSTANCE_COMPONENT_ID","STYLE_OVERRIDES_ROOT_KEY","expandSxAliases","isEqual","action","toJS","isPlainRecord","value","Array","isArray","nodePropsSx","node","props","undefined","composedNodeSx","nodeSx","sx","propsSx","writeComposedNodeSx","next","current","nextSx","currentSx","deep","propsRecord","key","Object","keys","entries","cleared","filter","length","remaining","getNodeStyleTarget","overrideKey","isInstance","componentId","isInstanceOverride","isLeafOverride","setSx","styleOverrides","overrides"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/besigner/feature/designer/src/lib/utils/style-target.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type * as Aglyn from '@aglyn/aglyn'\nimport {\n mergeNodeSx,\n REUSABLE_INSTANCE_COMPONENT_ID,\n STYLE_OVERRIDES_ROOT_KEY,\n} from '@aglyn/aglyn'\nimport { expandSxAliases } from '@aglyn/shared-data-enums'\nimport isEqual from 'lodash-es/isEqual.js'\nimport { action, toJS } from 'mobx'\n\n/**\n * Where the Styles panel's edits land for the selected node (AGL-1306,\n * AGL-1332, AGL-1346).\n *\n * A plain node's edits read and write `node.sx`, exactly as they always\n * have. A reusable-component INSTANCE's edits read and write one slice of\n * `node.styleOverrides` instead — the root slice by default, or the slice\n * keyed by a DEFINITION-internal node id when the author has picked a leaf\n * inside the component. Either way the panel behaves like it does on a\n * fresh plain node (empty until this instance overrides something), and\n * the canvas/Preview/tenant render the override merged over the matching\n * component node via `composeReusableComponentNodes`. Mutating the\n * component itself from a placing document is exactly what this layer\n * exists to avoid — a leaf target styles the instance's copy of that leaf,\n * and never unlocks the component's content, which stays the component's.\n *\n * `sx` is a GETTER so observer components track the underlying MobX state\n * at read time; a snapshot captured when the target was built would go\n * stale after the first write.\n */\nexport interface NodeStyleTarget {\n /**\n * The sx record edits read from and write to (live view).\n *\n * For a plain node this is the COMPOSED record — `props.sx` with\n * `node.sx` merged over it (AGL-1346) — so what the panel displays is\n * what renders. Writes are split back across the two records by\n * {@link writeComposedNodeSx}.\n */\n readonly sx: Record<string, any> | undefined\n /** True when edits land in one of the instance's override slices. */\n readonly isInstanceOverride: boolean\n /**\n * The `styleOverrides` key being edited — `root`, or a definition-internal\n * node id. Empty string for a plain node, which has no override layer.\n */\n readonly overrideKey: string\n /** True when the target is a leaf inside the component, not its root. */\n readonly isLeafOverride: boolean\n /**\n * True when `sx` above is a COMPOSITION of two stored records rather than\n * one — a plain node that carries both a `props.sx` and a `node.sx`\n * (AGL-3218).\n *\n * The panel needs this to say so. Everything about the split is correct\n * and invisible while you stay in the UI, and misleading the moment you\n * leave it: the stored node does not contain the document the editor\n * shows, and a reader who opens `props.sx` alone — a Raw JSON pane, an\n * export, a script auditing the corpus — sees an edit that looks like it\n * never landed.\n */\n readonly isComposed: boolean\n /** Replaces the target sx wholesale (MobX action inside). */\n setSx(next: Record<string, any> | undefined): void\n}\n\nconst isPlainRecord = (value: unknown): value is Record<string, any> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\n/**\n * A node's `props.sx` — the SECOND sx record every node can carry\n * (AGL-1346).\n *\n * `Leaf` composes `mergeSxProps(sx, props.sx, node.sx)` and MUI's array\n * form lets later entries win, so `node.sx` shadows `props.sx` key by key\n * — but only the keys it names. Everything `props.sx` sets and `node.sx`\n * does not is rendering, unshadowed, and until this issue no panel could\n * see it: the Styles panel read `node.sx` alone and reported \"Margin:\n * default on all four edges\" over a `marginInline: auto` that was\n * genuinely in the layout. Presets authored the record (`props: { sx: … }`)\n * and nothing could unauthor it.\n *\n * Returned as a live read, undefined when the node carries no such record\n * or carries a non-record one (an array/callback sx composes but does not\n * merge).\n */\nfunction nodePropsSx(\n node: Aglyn.NodeSchema<any> | null | undefined,\n): Record<string, any> | undefined {\n const value = (node?.props as Record<string, any> | undefined)?.['sx']\n return isPlainRecord(value) ? value : undefined\n}\n\n/**\n * What the panel SHOWS for a plain node: `props.sx` with `node.sx` merged\n * over it — the same precedence the renderer applies, so the displayed\n * value is the effective one (AGL-1346).\n *\n * `mergeNodeSx` is the house merge (AGL-1306): responsive breakpoint\n * objects cascade mobile-first, `@scheme dark` and nested selector blocks\n * merge key by key, plain properties replace. Reusing it is what keeps the\n * panel's reading of a shadowed value identical to the graft's.\n *\n * A node with no `props.sx` — every node but the ones presets touched —\n * gets `node.sx` back BY IDENTITY, so nothing downstream re-renders or\n * re-computes because this function exists.\n */\nfunction composedNodeSx(\n node: Aglyn.NodeSchema<any> | null | undefined,\n): Record<string, any> | undefined {\n const nodeSx = node?.sx as Record<string, any> | undefined\n const propsSx = nodePropsSx(node)\n if (!propsSx) return nodeSx\n if (nodeSx !== undefined && !isPlainRecord(nodeSx)) return nodeSx\n return mergeNodeSx(toJS(propsSx), toJS(nodeSx)) as Record<string, any>\n}\n\n/**\n * Splits the panel's write back across the two records (AGL-1346).\n *\n * The panel hands us the whole intended sx — it always builds `next` from\n * `target.sx`, which is now the COMPOSED record — so a naive\n * `node.sx = next` would copy every `props.sx` key up into `node.sx` on the\n * first unrelated edit. That is the migration this issue explicitly does\n * NOT want: the mega-menu panels keep their entire positioning\n * (`position: absolute`, `top: 100%`, `zIndex: 1300`, `bgcolor`, `p`,\n * `marginInline`) in `props.sx`, and re-emitting those declarations from a\n * later slot changes shorthand/longhand resolution order. So the write is a\n * DIFF against the composed baseline:\n *\n * - a key already in `node.sx` keeps its position there and takes the new\n * value (or goes, if the author cleared it);\n * - a key whose new value is what `props.sx` already says is NOT copied up\n * — nothing changed, so nothing is written;\n * - a key the author genuinely changed is appended to `node.sx`, where it\n * wins exactly as the Styles panel's output has always won;\n * - a key the author CLEARED is deleted from `props.sx` as well, which is\n * the half that makes the panel able to unauthor what a preset wrote.\n *\n * That last rule is why this edits `props.sx` in place rather than writing\n * a neutralising override: there is no value that reliably means \"no\n * declaration\" across sx keys — `unset`/`revert` are cascade keywords with\n * their own meaning, and system props (`p`, `bgcolor`, `zIndex`) run\n * through theme transforms first. Deleting the key is exact, and it is the\n * only record involved when nothing shadows it.\n *\n * The invariant that makes this safe on 8,000 authored nodes: writing back\n * an UNCHANGED composed record leaves both stored records byte-identical,\n * key order included. Only a key the author actually touched moves.\n */\nfunction writeComposedNodeSx(\n node: Aglyn.NodeSchema<any>,\n next: Record<string, any> | undefined,\n): void {\n const propsSx = nodePropsSx(node)\n const current = node.sx as Record<string, any> | undefined\n if (!propsSx || (current !== undefined && !isPlainRecord(current))) {\n // No second record to reconcile (or an sx form that composes rather\n // than merges): the write is what it always was.\n node.sx = (next ?? {}) as any\n return\n }\n const nextSx = (next ?? {}) as Record<string, any>\n // Both stored records are read in the panel's spelling (AGL-2207). The\n // composed baseline the panel edits comes through `mergeNodeSx`, which\n // canonicalizes MUI's system-prop aliases, so diffing against the RAW\n // records would read every `p`/`bgcolor` as a key the author had just\n // cleared and migrate the positioning on the first unrelated edit — the\n // exact thing this function exists to prevent. Expanded, the byte-\n // identical invariant holds; `props.sx` is only ever REWRITTEN below,\n // when the author genuinely cleared one of its values.\n const currentSx = expandSxAliases(\n isPlainRecord(current) ? toJS(current) : {},\n { deep: true },\n )\n const propsRecord = expandSxAliases(toJS(propsSx), { deep: true })\n\n const sx: Record<string, any> = {}\n for (const key of Object.keys(currentSx)) {\n if (key in nextSx) sx[key] = nextSx[key]\n }\n for (const [key, value] of Object.entries(nextSx)) {\n if (key in sx) continue\n if (key in propsRecord && isEqual(propsRecord[key], value)) continue\n sx[key] = value\n }\n node.sx = sx as any\n\n const cleared = Object.keys(propsRecord).filter((key) => !(key in nextSx))\n if (!cleared.length) return\n const remaining = { ...propsRecord }\n for (const key of cleared) delete remaining[key]\n // Assigned as a fresh props object rather than mutated in place: the\n // Attributes form reads `node.props` as its initial values, and a\n // replaced reference is the signal it re-reads on (`updateNodeProps`\n // replaces it too, for the same reason). Not routed through\n // `updateNodeProps`, though — that records its own history entry, and\n // this write is already inside the panel's coalesced `transact` step.\n const props = { ...(toJS(node.props) as Record<string, any>) }\n if (Object.keys(remaining).length) props['sx'] = remaining\n else delete props['sx']\n node.props = props as any\n}\n\n/**\n * The style target for a selected node — see {@link NodeStyleTarget}.\n *\n * `overrideKey` selects WHICH slice of an instance's overrides is edited;\n * it is ignored for a plain node. A falsy key falls back to the root, so a\n * caller that has not resolved a definition yet still edits something real\n * rather than writing an `undefined`-keyed slice no renderer reads.\n */\nexport function getNodeStyleTarget(\n node: Aglyn.NodeSchema<any> | null | undefined,\n overrideKey?: string | null,\n): NodeStyleTarget {\n const isInstance =\n node?.componentId === REUSABLE_INSTANCE_COMPONENT_ID\n if (!node || !isInstance) {\n return {\n isInstanceOverride: false,\n overrideKey: '',\n isLeafOverride: false,\n get isComposed() {\n return nodePropsSx(node) !== undefined\n },\n get sx() {\n // The COMPOSED record, not `node.sx` alone (AGL-1346) — see\n // `composedNodeSx`. Identity is preserved when the node has no\n // `props.sx`, which is every node but the handful presets touched.\n return composedNodeSx(node)\n },\n setSx: action((next: Record<string, any> | undefined) => {\n if (!node) return\n writeComposedNodeSx(node, next)\n }),\n }\n }\n // An instance's target is deliberately NOT composed with anything\n // (AGL-1346): an override slice starts empty and empty MEANS \"whatever\n // the component does\" (AGL-1338) — merging a base into the reading is\n // exactly the distinction the override badge and the Background Fill\n // control exist to keep. A `props.sx` on a definition node is shown and\n // cleared where it lives, by selecting that node inside the component.\n const key = overrideKey || STYLE_OVERRIDES_ROOT_KEY\n return {\n isInstanceOverride: true,\n overrideKey: key,\n isLeafOverride: key !== STYLE_OVERRIDES_ROOT_KEY,\n // An override slice is read uncomposed (see below), so what the editor\n // shows IS the stored record and there is nothing to disclose.\n isComposed: false,\n get sx() {\n return (node.styleOverrides as Record<string, any> | undefined)?.[key]\n },\n setSx: action((next: Record<string, any> | undefined) => {\n const overrides: Record<string, any> = {\n ...toJS(node.styleOverrides ?? {}),\n }\n if (next && Object.keys(next).length > 0) {\n overrides[key] = next\n } else {\n // An emptied override is REMOVED, not stored as `{}`: the panel's\n // override badge and the graft's \"has an override\" check must both\n // read a cleared instance as clean. Only THIS slice goes — the\n // instance's other targets keep theirs.\n delete overrides[key]\n }\n node.styleOverrides =\n Object.keys(overrides).length > 0 ? overrides : undefined\n }),\n }\n}\n\nexport default getNodeStyleTarget\n"],"names":["mergeNodeSx","REUSABLE_INSTANCE_COMPONENT_ID","STYLE_OVERRIDES_ROOT_KEY","expandSxAliases","isEqual","action","toJS","isPlainRecord","value","Array","isArray","nodePropsSx","node","props","undefined","composedNodeSx","nodeSx","sx","propsSx","writeComposedNodeSx","next","current","nextSx","currentSx","deep","propsRecord","key","Object","keys","entries","cleared","filter","length","remaining","getNodeStyleTarget","overrideKey","isInstance","componentId","isInstanceOverride","isLeafOverride","isComposed","setSx","styleOverrides","overrides"],"mappings":"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"}
|