@aglyn/besigner-ui 1.0.0-beta.174 → 1.0.0-beta.176
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/element-styles-form.component.js +7 -1
- package/src/lib/components/element-styles-form.component.js.map +1 -1
- package/src/lib/components/instance-attr-overrides.component.js +8 -1
- package/src/lib/components/instance-attr-overrides.component.js.map +1 -1
- package/src/lib/components/placement-parts-header.component.d.ts +6 -1
- package/src/lib/components/placement-parts-header.component.js +11 -13
- package/src/lib/components/placement-parts-header.component.js.map +1 -1
- package/src/lib/utils/placement-override-copy.d.ts +40 -8
- package/src/lib/utils/placement-override-copy.js +314 -54
- package/src/lib/utils/placement-override-copy.js.map +1 -1
|
@@ -27,7 +27,7 @@ import { ComponentPromotionContext } from "../contexts/component-promotion-conte
|
|
|
27
27
|
import { useDebouncedCommit } from "../hooks/use-debounced-commit.js";
|
|
28
28
|
import { besignerDocsUrl } from "../utils/docs-help.js";
|
|
29
29
|
import { countAttrChanges, getNodeAttrTarget, listInstanceAttrFields } from "../utils/attr-target.js";
|
|
30
|
-
import { placementCopy } from "../utils/placement-override-copy.js";
|
|
30
|
+
import { placementCopy, propDefaultsOf } from "../utils/placement-override-copy.js";
|
|
31
31
|
import { getPlacementPartPick, resolvePickedPart } from "../utils/placement-part-pick.js";
|
|
32
32
|
import { PlacementPartsHeader } from "./placement-parts-header.component.js";
|
|
33
33
|
/**
|
|
@@ -243,6 +243,12 @@ import { PlacementPartsHeader } from "./placement-parts-header.component.js";
|
|
|
243
243
|
placedFormResolves
|
|
244
244
|
]);
|
|
245
245
|
const pickedTarget = targets.find((entry)=>entry.key === overrideKey);
|
|
246
|
+
// The definition's declared defaults, so a part whose words come from a
|
|
247
|
+
// property this page leaves unset is named by what the page shows
|
|
248
|
+
// (AGL-3293).
|
|
249
|
+
const propDefaults = useMemo(()=>propDefaultsOf(definition), [
|
|
250
|
+
definition
|
|
251
|
+
]);
|
|
246
252
|
const definitionNodes = definition == null ? void 0 : definition.nodes;
|
|
247
253
|
const defNode = pickedTarget ? definitionNodes == null ? void 0 : definitionNodes[pickedTarget.componentInternalId] : undefined;
|
|
248
254
|
const attrFields = useMemo(()=>listInstanceAttrFields(defNode, node), [
|
|
@@ -317,6 +323,7 @@ import { PlacementPartsHeader } from "./placement-parts-header.component.js";
|
|
|
317
323
|
parts: targets,
|
|
318
324
|
definitionNodes: definitionNodes,
|
|
319
325
|
propValues: propValues,
|
|
326
|
+
propDefaults: propDefaults,
|
|
320
327
|
value: overrideKey,
|
|
321
328
|
onChange: handleTargetChange,
|
|
322
329
|
changedKeys: changedKeys,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../libs/besigner/feature/designer/src/lib/components/instance-attr-overrides.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 isPlacedFormNode,\n listInstanceStyleTargets,\n placementDefinitionFor,\n REUSABLE_INSTANCE_PROP_VALUES_KEY,\n STYLE_OVERRIDES_ROOT_KEY,\n} from '@aglyn/aglyn'\nimport { mdiRestore } from '@aglyn/shared-data-mdi'\nimport {\n FormRenderer,\n type FormRendererProps,\n type FormTemplateRenderProps,\n FormSpy,\n useFormApi,\n} from '@aglyn/shared-ui-jsx-forms'\nimport { MdiIcon } from '@aglyn/shared-ui-jsx'\nimport {\n Box,\n Chip,\n Grid,\n IconButton,\n Tooltip,\n Typography,\n} from '@mui/material'\nimport { observer } from 'mobx-react-lite'\nimport { toJS } from 'mobx'\nimport {\n Fragment,\n type KeyboardEvent,\n memo,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport { ComponentPromotionContext } from '../contexts/component-promotion-context'\nimport { useDebouncedCommit } from '../hooks/use-debounced-commit'\nimport { besignerDocsUrl } from '../utils/docs-help'\nimport {\n countAttrChanges,\n getNodeAttrTarget,\n listInstanceAttrFields,\n} from '../utils/attr-target'\nimport {\n type PartNode,\n type PlacementCopy,\n placementCopy,\n} from '../utils/placement-override-copy'\nimport {\n getPlacementPartPick,\n resolvePickedPart,\n} from '../utils/placement-part-pick'\nimport { PlacementPartsHeader } from './placement-parts-header.component'\n\n/**\n * Schedules a commit when the form's values change and it is dirty — the same\n * shape as the Attributes form's `AutoSaveOnChange`, and for the same reason:\n * MUI Select renders through a Portal, so its change never reaches the\n * `<form>` as a DOM event.\n *\n * The first render is skipped. Seeding the form is not an edit, and\n * committing on mount would write an override slice for every instance whose\n * panel was merely OPENED.\n */\nconst AttrOverrideAutoSave = memo(function AttrOverrideAutoSave({\n values,\n pristine,\n valid,\n onSchedule,\n}: {\n values: unknown\n pristine: boolean\n valid: boolean\n onSchedule: () => void\n}) {\n const isFirstRender = useRef(true)\n useEffect(() => {\n if (isFirstRender.current) {\n isFirstRender.current = false\n return\n }\n if (!pristine && valid) onSchedule()\n }, [values]) // eslint-disable-line react-hooks/exhaustive-deps\n return null\n})\n\n/** What each field row needs to say it was changed here, and undo it. */\ninterface ChangedFieldsProps {\n /** Names of the fields this page changes, on the picked part. */\n changed: ReadonlySet<string>\n /** Resets one field to the definition's value. */\n onReset: (name: string) => void\n copy: PlacementCopy\n}\n\n/**\n * One field's \"Changed here\" badge and its ↺ reset, drawn on the row under\n * the field (AGL-3288) — the per-field replacement for a row of chips that\n * named each change by its stored key.\n */\nfunction ChangedFieldRow({\n label,\n name,\n onReset,\n copy,\n}: {\n label: string\n name: string\n onReset: (name: string) => void\n copy: PlacementCopy\n}) {\n const resetLabel = copy.resetField(label)\n return (\n <Grid size={12} sx={{ mt: -1.5 }}>\n <Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>\n <Chip\n size=\"small\"\n color=\"secondary\"\n variant=\"outlined\"\n label={copy.changedBadge}\n sx={{ height: 20, fontSize: '0.7rem' }}\n />\n <Tooltip title={resetLabel}>\n <IconButton\n size=\"small\"\n aria-label={resetLabel}\n onClick={() => onReset(name)}\n >\n <MdiIcon path={mdiRestore.path} fontSize=\"small\" />\n </IconButton>\n </Tooltip>\n </Box>\n </Grid>\n )\n}\n\n/**\n * The per-page form's shell (AGL-1899).\n *\n * Autosaving on a debounce, exactly like the Attributes form's own template\n * (AGL-567) — a change here is an attribute edit and should not need a second\n * kind of Save button — but with NO submit button of its own: this form is\n * nested inside the Attributes panel, and a second `type=\"submit\"` control\n * beside \"Save Element\" is two buttons that look like they do the same thing\n * and do not.\n *\n * `onBlur` flushes, so switching selection or clicking away cannot strand an\n * edit inside the debounce window. Return in a text box also flushes, and is\n * stopped here: the section renders inside the Attributes panel's own\n * `<form>` (AGL-3288), where the browser would otherwise treat it as a submit\n * of THAT form.\n *\n * `formFields` arrive one per schema field, in schema order, so each changed\n * field's badge row is drawn directly under it.\n */\nconst InstanceAttrFormTemplate = ({\n formFields,\n schema,\n changed,\n onReset,\n copy,\n}: FormTemplateRenderProps & ChangedFieldsProps) => {\n const { handleSubmit } = useFormApi()\n const { schedule, flush } = useDebouncedCommit(handleSubmit)\n const handleKeyDown = useCallback(\n (event: KeyboardEvent<HTMLElement>) => {\n const target = event.target as HTMLElement\n if (event.key === 'Enter' && target.tagName === 'INPUT') {\n event.preventDefault()\n flush()\n }\n },\n [flush],\n )\n const fields = (schema?.fields ?? []) as Array<{\n name?: string\n label?: string\n }>\n const rendered = (formFields ?? []) as unknown as JSX.Node[]\n return (\n <Box onBlur={flush} onKeyDown={handleKeyDown}>\n {schema?.title}\n <Grid spacing={2} container>\n {rendered.map((field, index) => {\n const name = fields[index]?.name\n return (\n <Fragment key={name ?? index}>\n {field}\n {name && changed.has(name) ? (\n <ChangedFieldRow\n name={name}\n label={fields[index]?.label || name}\n onReset={onReset}\n copy={copy}\n />\n ) : null}\n </Fragment>\n )\n })}\n </Grid>\n <FormSpy subscription={{ values: true, pristine: true, valid: true }}>\n {({ values, pristine, valid }) => (\n <AttrOverrideAutoSave\n values={values}\n pristine={pristine}\n valid={valid}\n onSchedule={schedule}\n />\n )}\n </FormSpy>\n </Box>\n )\n}\n\nexport interface InstanceAttrOverridesProps {\n /** The selected node, as the panel holds it. */\n node?: Aglyn.NodeSchema<any>\n /**\n * Passed in rather than imported, so this file does not import the\n * Attributes form that renders it (a cycle) — and so a caller can narrow\n * the editors on offer without this component knowing about any of them.\n */\n componentMapper: FormRendererProps['componentMapper']\n}\n\n/**\n * Per-instance ATTRIBUTE overrides for a reusable-component instance\n * (AGL-1899) — the attribute-side twin of the Styles panel's override\n * section (AGL-1332).\n *\n * An instance renders the component's CURRENT nodes; this section layers one\n * placement's own prop values over one node inside it, so a single page's CTA\n * can be `outlined` while every other placement stays `contained` — without\n * the component declaring a prop for it, without detaching, and without\n * pinning the instance to a component version. The graft merges the slice per\n * named prop, so a prop the component ADDS later still reaches this instance\n * with the component's new default.\n *\n * Content is deliberately not editable here: a component's text rides its\n * declared `{{prop.*}}` props and the canvas's double-click editor (AGL-1304),\n * and `children`/`html` are refused by the writer so this panel never becomes\n * a second control writing the same rendered string.\n */\nexport const InstanceAttrOverrides = observer(function InstanceAttrOverrides({\n node: selectedNode,\n componentMapper,\n}: InstanceAttrOverridesProps) {\n // The node as the CANVAS currently holds it (AGL-2486 / `f68aadabe`).\n //\n // Undo, a co-edit apply and a draft restore all REPLACE the node map with\n // fresh instances. A panel holding the node OBJECT would go on writing to a\n // detached copy — the write lands on an orphan, nothing throws, and the\n // header still says UP TO DATE. Resolving by `$id` at the point of use is\n // the only form that cannot strand itself, and reading it inside an\n // `observer` is also what re-renders this section after an undo.\n const node = (selectedNode?.$id\n ? (canvas.getNode(selectedNode.$id) ?? selectedNode)\n : selectedNode) as Aglyn.NodeSchema<any> | undefined\n\n // A placed form is the same kind of placement (AGL-3285): its published\n // design is the tree, and a page may set a label or placeholder on its copy\n // — never anything that changes what the form submits.\n const { definitions, formDesigns } = useContext(ComponentPromotionContext)\n const definition = useMemo(\n () => placementDefinitionFor(node, { definitions, formDesigns }),\n [node, definitions, formDesigns],\n )\n const isPlacedForm = isPlacedFormNode(node)\n const placedFormResolves = isPlacedForm && Boolean(definition)\n const kind = isPlacedForm ? 'form' : 'component'\n const copy = placementCopy(kind)\n\n // The SAME target list the Styles panel offers, from the same walker, so\n // the two panels name the same parts of a component in the same order and\n // an override written by one is addressed identically by the other.\n const targets = useMemo(\n () => listInstanceStyleTargets(definition),\n [definition],\n )\n\n // Which part is being edited, held per selection rather than in an\n // effect: switching nodes must land on the whole placement, and deriving\n // it from the current `node.$id` cannot render one frame aimed at the\n // PREVIOUS instance's leaf. A click on the canvas newer than the menu's\n // last choice moves it (AGL-3288) — see `placement-part-pick.ts`.\n const canvasPick = getPlacementPartPick()\n const [picked, setPicked] = useState<{\n nodeId?: string\n key: string\n seq: number\n }>({ key: STYLE_OVERRIDES_ROOT_KEY, seq: 0 })\n const pickedKey = resolvePickedPart(node?.$id, picked, canvasPick)\n // A leaf the component no longer has falls back to the root rather than\n // aiming the panel at a slice nothing renders. An unloaded definition\n // offers nothing yet, so it is not evidence the key is stale.\n const overrideKey =\n !targets.length || targets.some((entry) => entry.key === pickedKey)\n ? pickedKey\n : STYLE_OVERRIDES_ROOT_KEY\n\n const target = useMemo(\n () => getNodeAttrTarget(node, overrideKey, { placedFormResolves }),\n [node, overrideKey, placedFormResolves],\n )\n\n const pickedTarget = targets.find((entry) => entry.key === overrideKey)\n const definitionNodes = definition?.nodes as\n | Record<string, PartNode>\n | undefined\n const defNode = pickedTarget\n ? (definitionNodes as Record<string, any> | undefined)?.[\n pickedTarget.componentInternalId\n ]\n : undefined\n const attrFields = useMemo(\n () => listInstanceAttrFields(defNode, node),\n [defNode, node],\n )\n\n // Read during render (observer): tracks the live slice, so the badges\n // update as edits land and as they are reset.\n const changedFields = new Set(Object.keys(target.attrs ?? {}))\n const changedKeys = new Set(\n Object.keys((node?.attrOverrides as Record<string, any> | undefined) ?? {}),\n )\n const changeCount = countAttrChanges(node)\n const propValues = (node?.props as Record<string, any> | undefined)?.[\n REUSABLE_INSTANCE_PROP_VALUES_KEY\n ] as Record<string, unknown> | undefined\n\n const canvasSeq = canvasPick.seq\n const handleTargetChange = useCallback(\n (key: string) => {\n setPicked({ nodeId: node?.$id, key, seq: canvasSeq })\n },\n [node?.$id, canvasSeq],\n )\n\n const handleSave = useCallback(\n (values: Record<string, unknown>) => {\n // One undoable step. `setAttrs` drops every non-override value by key,\n // so a form that hands back a field per declared attribute — most of\n // them empty — stores only what this instance actually chose.\n canvas.transact(() => target.setAttrs(values))\n },\n [target],\n )\n\n // Bumped by every reset so the form re-seeds from the slice as it now is:\n // the form owns its typed values, and one still holding a reset value\n // would write it straight back on the next edit.\n const [resetCount, setResetCount] = useState(0)\n\n const handleReset = useCallback(\n (prop: string) => {\n // Undoable and uncoalesced: a reset DISCARDS a change, which is the\n // one edit here that most needs a way back.\n canvas.transact(() => target.clearAttr(prop))\n setResetCount((count) => count + 1)\n },\n [target],\n )\n\n const handleResetAll = useCallback(() => {\n // One transaction for every part, so one undo brings them all back.\n canvas.transact(() => target.clearAll())\n setResetCount((count) => count + 1)\n }, [target])\n\n // Re-seeds the form when the selection, the part or a reset changes. NOT\n // keyed on the slice's contents: the form owns the typed value while the\n // author is in it, and re-seeding on every commit would fight the field for\n // the cursor — the shape behind an update loop, not merely a nuisance.\n const seedKey = `${node?.$id ?? ''}:${overrideKey}:${resetCount}`\n const initialValues = useMemo(\n () => (toJS(target.attrs) ?? {}) as Record<string, unknown>,\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [seedKey],\n )\n\n if (!target.isInstanceOverride || !targets.length) return null\n\n return (\n <Box sx={{ mt: 2, mb: 1 }}>\n <PlacementPartsHeader\n kind={kind}\n parts={targets}\n definitionNodes={definitionNodes}\n propValues={propValues}\n value={overrideKey}\n onChange={handleTargetChange}\n changedKeys={changedKeys}\n helperText={\n target.isLeafOverride ? copy.onePartHelper : copy.wholePartHelper\n }\n changeCount={changeCount}\n onResetAll={handleResetAll}\n helpExcerpt={copy.helpExcerpt}\n helpHref={besignerDocsUrl(\n 'reusableComponents',\n '#override-an-attribute-on-one-instance',\n )}\n />\n {attrFields.length ? (\n <FormRenderer\n key={seedKey}\n componentMapper={componentMapper}\n onSubmit={handleSave}\n initialValues={initialValues}\n schema={{ fields: attrFields.map((entry) => entry.field) }}\n >\n {(templateProps: FormTemplateRenderProps) => (\n <InstanceAttrFormTemplate\n {...templateProps}\n changed={changedFields}\n onReset={handleReset}\n copy={copy}\n />\n )}\n </FormRenderer>\n ) : (\n // Said out loud rather than rendering an empty box: a component made\n // of layout elements genuinely has nothing to change here, and an\n // author should not be left wondering whether the panel failed.\n <Typography variant=\"caption\" color=\"text.secondary\">\n {copy.nothingToChange}\n </Typography>\n )}\n </Box>\n )\n})\n\nexport default InstanceAttrOverrides\n"],"names":["canvas","isPlacedFormNode","listInstanceStyleTargets","placementDefinitionFor","REUSABLE_INSTANCE_PROP_VALUES_KEY","STYLE_OVERRIDES_ROOT_KEY","mdiRestore","FormRenderer","FormSpy","useFormApi","MdiIcon","Box","Chip","Grid","IconButton","Tooltip","Typography","observer","toJS","Fragment","memo","useCallback","useContext","useEffect","useMemo","useRef","useState","ComponentPromotionContext","useDebouncedCommit","besignerDocsUrl","countAttrChanges","getNodeAttrTarget","listInstanceAttrFields","placementCopy","getPlacementPartPick","resolvePickedPart","PlacementPartsHeader","AttrOverrideAutoSave","values","pristine","valid","onSchedule","isFirstRender","current","ChangedFieldRow","label","name","onReset","copy","resetLabel","resetField","size","sx","mt","display","alignItems","gap","color","variant","changedBadge","height","fontSize","title","aria-label","onClick","path","InstanceAttrFormTemplate","formFields","schema","changed","handleSubmit","schedule","flush","handleKeyDown","event","target","key","tagName","preventDefault","fields","rendered","onBlur","onKeyDown","spacing","container","map","field","index","has","subscription","InstanceAttrOverrides","node","selectedNode","componentMapper","$id","getNode","definitions","formDesigns","definition","isPlacedForm","placedFormResolves","Boolean","kind","targets","canvasPick","picked","setPicked","seq","pickedKey","overrideKey","length","some","entry","pickedTarget","find","definitionNodes","nodes","defNode","componentInternalId","undefined","attrFields","changedFields","Set","Object","keys","attrs","changedKeys","attrOverrides","changeCount","propValues","props","canvasSeq","handleTargetChange","nodeId","handleSave","transact","setAttrs","resetCount","setResetCount","handleReset","prop","clearAttr","count","handleResetAll","clearAll","seedKey","initialValues","isInstanceOverride","mb","parts","value","onChange","helperText","isLeafOverride","onePartHelper","wholePartHelper","onResetAll","helpExcerpt","helpHref","onSubmit","templateProps","nothingToChange"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/besigner/feature/designer/src/lib/components/instance-attr-overrides.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 isPlacedFormNode,\n listInstanceStyleTargets,\n placementDefinitionFor,\n REUSABLE_INSTANCE_PROP_VALUES_KEY,\n STYLE_OVERRIDES_ROOT_KEY,\n} from '@aglyn/aglyn'\nimport { mdiRestore } from '@aglyn/shared-data-mdi'\nimport {\n FormRenderer,\n type FormRendererProps,\n type FormTemplateRenderProps,\n FormSpy,\n useFormApi,\n} from '@aglyn/shared-ui-jsx-forms'\nimport { MdiIcon } from '@aglyn/shared-ui-jsx'\nimport {\n Box,\n Chip,\n Grid,\n IconButton,\n Tooltip,\n Typography,\n} from '@mui/material'\nimport { observer } from 'mobx-react-lite'\nimport { toJS } from 'mobx'\nimport {\n Fragment,\n type KeyboardEvent,\n memo,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport { ComponentPromotionContext } from '../contexts/component-promotion-context'\nimport { useDebouncedCommit } from '../hooks/use-debounced-commit'\nimport { besignerDocsUrl } from '../utils/docs-help'\nimport {\n countAttrChanges,\n getNodeAttrTarget,\n listInstanceAttrFields,\n} from '../utils/attr-target'\nimport {\n type PartNode,\n type PlacementCopy,\n placementCopy,\n propDefaultsOf,\n} from '../utils/placement-override-copy'\nimport {\n getPlacementPartPick,\n resolvePickedPart,\n} from '../utils/placement-part-pick'\nimport { PlacementPartsHeader } from './placement-parts-header.component'\n\n/**\n * Schedules a commit when the form's values change and it is dirty — the same\n * shape as the Attributes form's `AutoSaveOnChange`, and for the same reason:\n * MUI Select renders through a Portal, so its change never reaches the\n * `<form>` as a DOM event.\n *\n * The first render is skipped. Seeding the form is not an edit, and\n * committing on mount would write an override slice for every instance whose\n * panel was merely OPENED.\n */\nconst AttrOverrideAutoSave = memo(function AttrOverrideAutoSave({\n values,\n pristine,\n valid,\n onSchedule,\n}: {\n values: unknown\n pristine: boolean\n valid: boolean\n onSchedule: () => void\n}) {\n const isFirstRender = useRef(true)\n useEffect(() => {\n if (isFirstRender.current) {\n isFirstRender.current = false\n return\n }\n if (!pristine && valid) onSchedule()\n }, [values]) // eslint-disable-line react-hooks/exhaustive-deps\n return null\n})\n\n/** What each field row needs to say it was changed here, and undo it. */\ninterface ChangedFieldsProps {\n /** Names of the fields this page changes, on the picked part. */\n changed: ReadonlySet<string>\n /** Resets one field to the definition's value. */\n onReset: (name: string) => void\n copy: PlacementCopy\n}\n\n/**\n * One field's \"Changed here\" badge and its ↺ reset, drawn on the row under\n * the field (AGL-3288) — the per-field replacement for a row of chips that\n * named each change by its stored key.\n */\nfunction ChangedFieldRow({\n label,\n name,\n onReset,\n copy,\n}: {\n label: string\n name: string\n onReset: (name: string) => void\n copy: PlacementCopy\n}) {\n const resetLabel = copy.resetField(label)\n return (\n <Grid size={12} sx={{ mt: -1.5 }}>\n <Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>\n <Chip\n size=\"small\"\n color=\"secondary\"\n variant=\"outlined\"\n label={copy.changedBadge}\n sx={{ height: 20, fontSize: '0.7rem' }}\n />\n <Tooltip title={resetLabel}>\n <IconButton\n size=\"small\"\n aria-label={resetLabel}\n onClick={() => onReset(name)}\n >\n <MdiIcon path={mdiRestore.path} fontSize=\"small\" />\n </IconButton>\n </Tooltip>\n </Box>\n </Grid>\n )\n}\n\n/**\n * The per-page form's shell (AGL-1899).\n *\n * Autosaving on a debounce, exactly like the Attributes form's own template\n * (AGL-567) — a change here is an attribute edit and should not need a second\n * kind of Save button — but with NO submit button of its own: this form is\n * nested inside the Attributes panel, and a second `type=\"submit\"` control\n * beside \"Save Element\" is two buttons that look like they do the same thing\n * and do not.\n *\n * `onBlur` flushes, so switching selection or clicking away cannot strand an\n * edit inside the debounce window. Return in a text box also flushes, and is\n * stopped here: the section renders inside the Attributes panel's own\n * `<form>` (AGL-3288), where the browser would otherwise treat it as a submit\n * of THAT form.\n *\n * `formFields` arrive one per schema field, in schema order, so each changed\n * field's badge row is drawn directly under it.\n */\nconst InstanceAttrFormTemplate = ({\n formFields,\n schema,\n changed,\n onReset,\n copy,\n}: FormTemplateRenderProps & ChangedFieldsProps) => {\n const { handleSubmit } = useFormApi()\n const { schedule, flush } = useDebouncedCommit(handleSubmit)\n const handleKeyDown = useCallback(\n (event: KeyboardEvent<HTMLElement>) => {\n const target = event.target as HTMLElement\n if (event.key === 'Enter' && target.tagName === 'INPUT') {\n event.preventDefault()\n flush()\n }\n },\n [flush],\n )\n const fields = (schema?.fields ?? []) as Array<{\n name?: string\n label?: string\n }>\n const rendered = (formFields ?? []) as unknown as JSX.Node[]\n return (\n <Box onBlur={flush} onKeyDown={handleKeyDown}>\n {schema?.title}\n <Grid spacing={2} container>\n {rendered.map((field, index) => {\n const name = fields[index]?.name\n return (\n <Fragment key={name ?? index}>\n {field}\n {name && changed.has(name) ? (\n <ChangedFieldRow\n name={name}\n label={fields[index]?.label || name}\n onReset={onReset}\n copy={copy}\n />\n ) : null}\n </Fragment>\n )\n })}\n </Grid>\n <FormSpy subscription={{ values: true, pristine: true, valid: true }}>\n {({ values, pristine, valid }) => (\n <AttrOverrideAutoSave\n values={values}\n pristine={pristine}\n valid={valid}\n onSchedule={schedule}\n />\n )}\n </FormSpy>\n </Box>\n )\n}\n\nexport interface InstanceAttrOverridesProps {\n /** The selected node, as the panel holds it. */\n node?: Aglyn.NodeSchema<any>\n /**\n * Passed in rather than imported, so this file does not import the\n * Attributes form that renders it (a cycle) — and so a caller can narrow\n * the editors on offer without this component knowing about any of them.\n */\n componentMapper: FormRendererProps['componentMapper']\n}\n\n/**\n * Per-instance ATTRIBUTE overrides for a reusable-component instance\n * (AGL-1899) — the attribute-side twin of the Styles panel's override\n * section (AGL-1332).\n *\n * An instance renders the component's CURRENT nodes; this section layers one\n * placement's own prop values over one node inside it, so a single page's CTA\n * can be `outlined` while every other placement stays `contained` — without\n * the component declaring a prop for it, without detaching, and without\n * pinning the instance to a component version. The graft merges the slice per\n * named prop, so a prop the component ADDS later still reaches this instance\n * with the component's new default.\n *\n * Content is deliberately not editable here: a component's text rides its\n * declared `{{prop.*}}` props and the canvas's double-click editor (AGL-1304),\n * and `children`/`html` are refused by the writer so this panel never becomes\n * a second control writing the same rendered string.\n */\nexport const InstanceAttrOverrides = observer(function InstanceAttrOverrides({\n node: selectedNode,\n componentMapper,\n}: InstanceAttrOverridesProps) {\n // The node as the CANVAS currently holds it (AGL-2486 / `f68aadabe`).\n //\n // Undo, a co-edit apply and a draft restore all REPLACE the node map with\n // fresh instances. A panel holding the node OBJECT would go on writing to a\n // detached copy — the write lands on an orphan, nothing throws, and the\n // header still says UP TO DATE. Resolving by `$id` at the point of use is\n // the only form that cannot strand itself, and reading it inside an\n // `observer` is also what re-renders this section after an undo.\n const node = (selectedNode?.$id\n ? (canvas.getNode(selectedNode.$id) ?? selectedNode)\n : selectedNode) as Aglyn.NodeSchema<any> | undefined\n\n // A placed form is the same kind of placement (AGL-3285): its published\n // design is the tree, and a page may set a label or placeholder on its copy\n // — never anything that changes what the form submits.\n const { definitions, formDesigns } = useContext(ComponentPromotionContext)\n const definition = useMemo(\n () => placementDefinitionFor(node, { definitions, formDesigns }),\n [node, definitions, formDesigns],\n )\n const isPlacedForm = isPlacedFormNode(node)\n const placedFormResolves = isPlacedForm && Boolean(definition)\n const kind = isPlacedForm ? 'form' : 'component'\n const copy = placementCopy(kind)\n\n // The SAME target list the Styles panel offers, from the same walker, so\n // the two panels name the same parts of a component in the same order and\n // an override written by one is addressed identically by the other.\n const targets = useMemo(\n () => listInstanceStyleTargets(definition),\n [definition],\n )\n\n // Which part is being edited, held per selection rather than in an\n // effect: switching nodes must land on the whole placement, and deriving\n // it from the current `node.$id` cannot render one frame aimed at the\n // PREVIOUS instance's leaf. A click on the canvas newer than the menu's\n // last choice moves it (AGL-3288) — see `placement-part-pick.ts`.\n const canvasPick = getPlacementPartPick()\n const [picked, setPicked] = useState<{\n nodeId?: string\n key: string\n seq: number\n }>({ key: STYLE_OVERRIDES_ROOT_KEY, seq: 0 })\n const pickedKey = resolvePickedPart(node?.$id, picked, canvasPick)\n // A leaf the component no longer has falls back to the root rather than\n // aiming the panel at a slice nothing renders. An unloaded definition\n // offers nothing yet, so it is not evidence the key is stale.\n const overrideKey =\n !targets.length || targets.some((entry) => entry.key === pickedKey)\n ? pickedKey\n : STYLE_OVERRIDES_ROOT_KEY\n\n const target = useMemo(\n () => getNodeAttrTarget(node, overrideKey, { placedFormResolves }),\n [node, overrideKey, placedFormResolves],\n )\n\n const pickedTarget = targets.find((entry) => entry.key === overrideKey)\n // The definition's declared defaults, so a part whose words come from a\n // property this page leaves unset is named by what the page shows\n // (AGL-3293).\n const propDefaults = useMemo(() => propDefaultsOf(definition), [definition])\n const definitionNodes = definition?.nodes as\n | Record<string, PartNode>\n | undefined\n const defNode = pickedTarget\n ? (definitionNodes as Record<string, any> | undefined)?.[\n pickedTarget.componentInternalId\n ]\n : undefined\n const attrFields = useMemo(\n () => listInstanceAttrFields(defNode, node),\n [defNode, node],\n )\n\n // Read during render (observer): tracks the live slice, so the badges\n // update as edits land and as they are reset.\n const changedFields = new Set(Object.keys(target.attrs ?? {}))\n const changedKeys = new Set(\n Object.keys((node?.attrOverrides as Record<string, any> | undefined) ?? {}),\n )\n const changeCount = countAttrChanges(node)\n const propValues = (node?.props as Record<string, any> | undefined)?.[\n REUSABLE_INSTANCE_PROP_VALUES_KEY\n ] as Record<string, unknown> | undefined\n\n const canvasSeq = canvasPick.seq\n const handleTargetChange = useCallback(\n (key: string) => {\n setPicked({ nodeId: node?.$id, key, seq: canvasSeq })\n },\n [node?.$id, canvasSeq],\n )\n\n const handleSave = useCallback(\n (values: Record<string, unknown>) => {\n // One undoable step. `setAttrs` drops every non-override value by key,\n // so a form that hands back a field per declared attribute — most of\n // them empty — stores only what this instance actually chose.\n canvas.transact(() => target.setAttrs(values))\n },\n [target],\n )\n\n // Bumped by every reset so the form re-seeds from the slice as it now is:\n // the form owns its typed values, and one still holding a reset value\n // would write it straight back on the next edit.\n const [resetCount, setResetCount] = useState(0)\n\n const handleReset = useCallback(\n (prop: string) => {\n // Undoable and uncoalesced: a reset DISCARDS a change, which is the\n // one edit here that most needs a way back.\n canvas.transact(() => target.clearAttr(prop))\n setResetCount((count) => count + 1)\n },\n [target],\n )\n\n const handleResetAll = useCallback(() => {\n // One transaction for every part, so one undo brings them all back.\n canvas.transact(() => target.clearAll())\n setResetCount((count) => count + 1)\n }, [target])\n\n // Re-seeds the form when the selection, the part or a reset changes. NOT\n // keyed on the slice's contents: the form owns the typed value while the\n // author is in it, and re-seeding on every commit would fight the field for\n // the cursor — the shape behind an update loop, not merely a nuisance.\n const seedKey = `${node?.$id ?? ''}:${overrideKey}:${resetCount}`\n const initialValues = useMemo(\n () => (toJS(target.attrs) ?? {}) as Record<string, unknown>,\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [seedKey],\n )\n\n if (!target.isInstanceOverride || !targets.length) return null\n\n return (\n <Box sx={{ mt: 2, mb: 1 }}>\n <PlacementPartsHeader\n kind={kind}\n parts={targets}\n definitionNodes={definitionNodes}\n propValues={propValues}\n propDefaults={propDefaults}\n value={overrideKey}\n onChange={handleTargetChange}\n changedKeys={changedKeys}\n helperText={\n target.isLeafOverride ? copy.onePartHelper : copy.wholePartHelper\n }\n changeCount={changeCount}\n onResetAll={handleResetAll}\n helpExcerpt={copy.helpExcerpt}\n helpHref={besignerDocsUrl(\n 'reusableComponents',\n '#override-an-attribute-on-one-instance',\n )}\n />\n {attrFields.length ? (\n <FormRenderer\n key={seedKey}\n componentMapper={componentMapper}\n onSubmit={handleSave}\n initialValues={initialValues}\n schema={{ fields: attrFields.map((entry) => entry.field) }}\n >\n {(templateProps: FormTemplateRenderProps) => (\n <InstanceAttrFormTemplate\n {...templateProps}\n changed={changedFields}\n onReset={handleReset}\n copy={copy}\n />\n )}\n </FormRenderer>\n ) : (\n // Said out loud rather than rendering an empty box: a component made\n // of layout elements genuinely has nothing to change here, and an\n // author should not be left wondering whether the panel failed.\n <Typography variant=\"caption\" color=\"text.secondary\">\n {copy.nothingToChange}\n </Typography>\n )}\n </Box>\n )\n})\n\nexport default InstanceAttrOverrides\n"],"names":["canvas","isPlacedFormNode","listInstanceStyleTargets","placementDefinitionFor","REUSABLE_INSTANCE_PROP_VALUES_KEY","STYLE_OVERRIDES_ROOT_KEY","mdiRestore","FormRenderer","FormSpy","useFormApi","MdiIcon","Box","Chip","Grid","IconButton","Tooltip","Typography","observer","toJS","Fragment","memo","useCallback","useContext","useEffect","useMemo","useRef","useState","ComponentPromotionContext","useDebouncedCommit","besignerDocsUrl","countAttrChanges","getNodeAttrTarget","listInstanceAttrFields","placementCopy","propDefaultsOf","getPlacementPartPick","resolvePickedPart","PlacementPartsHeader","AttrOverrideAutoSave","values","pristine","valid","onSchedule","isFirstRender","current","ChangedFieldRow","label","name","onReset","copy","resetLabel","resetField","size","sx","mt","display","alignItems","gap","color","variant","changedBadge","height","fontSize","title","aria-label","onClick","path","InstanceAttrFormTemplate","formFields","schema","changed","handleSubmit","schedule","flush","handleKeyDown","event","target","key","tagName","preventDefault","fields","rendered","onBlur","onKeyDown","spacing","container","map","field","index","has","subscription","InstanceAttrOverrides","node","selectedNode","componentMapper","$id","getNode","definitions","formDesigns","definition","isPlacedForm","placedFormResolves","Boolean","kind","targets","canvasPick","picked","setPicked","seq","pickedKey","overrideKey","length","some","entry","pickedTarget","find","propDefaults","definitionNodes","nodes","defNode","componentInternalId","undefined","attrFields","changedFields","Set","Object","keys","attrs","changedKeys","attrOverrides","changeCount","propValues","props","canvasSeq","handleTargetChange","nodeId","handleSave","transact","setAttrs","resetCount","setResetCount","handleReset","prop","clearAttr","count","handleResetAll","clearAll","seedKey","initialValues","isInstanceOverride","mb","parts","value","onChange","helperText","isLeafOverride","onePartHelper","wholePartHelper","onResetAll","helpExcerpt","helpHref","onSubmit","templateProps","nothingToChange"],"mappings":"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"}
|
|
@@ -25,6 +25,11 @@ export interface PlacementPartsHeaderProps {
|
|
|
25
25
|
definitionNodes?: Record<string, PartNode>;
|
|
26
26
|
/** The placement's declared-property values, for part names that show them. */
|
|
27
27
|
propValues?: Record<string, unknown>;
|
|
28
|
+
/**
|
|
29
|
+
* The definition's declared defaults, for a part whose words come from a
|
|
30
|
+
* property this page leaves unset (AGL-3293).
|
|
31
|
+
*/
|
|
32
|
+
propDefaults?: Record<string, unknown>;
|
|
28
33
|
/** The picked part's key. */
|
|
29
34
|
value: string;
|
|
30
35
|
onChange: (key: string) => void;
|
|
@@ -48,5 +53,5 @@ export interface PlacementPartsHeaderProps {
|
|
|
48
53
|
* One component so the two tabs cannot word the same idea two ways — the
|
|
49
54
|
* mismatch the old "Style target" / "Override target" pair was.
|
|
50
55
|
*/
|
|
51
|
-
export declare function PlacementPartsHeader({ kind, parts, definitionNodes, propValues, value, onChange, changedKeys, helperText, changeCount, onResetAll, helpExcerpt, helpHref, }: PlacementPartsHeaderProps): import("react").JSX.Element;
|
|
56
|
+
export declare function PlacementPartsHeader({ kind, parts, definitionNodes, propValues, propDefaults, value, onChange, changedKeys, helperText, changeCount, onResetAll, helpExcerpt, helpHref, }: PlacementPartsHeaderProps): import("react").JSX.Element;
|
|
52
57
|
export default PlacementPartsHeader;
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
import { HelpTip } from "@aglyn/shared-ui-jsx";
|
|
18
18
|
import { Box, Link, MenuItem, TextField, Typography } from "@mui/material";
|
|
19
19
|
import { useCallback, useMemo } from "react";
|
|
20
|
-
import {
|
|
20
|
+
import { describeParts, placementCopy } from "../utils/placement-override-copy.js";
|
|
21
21
|
/**
|
|
22
22
|
* The top of the "Change it on this page only" section, shared by the Styles
|
|
23
23
|
* and Attributes tabs (AGL-3288): heading, one line of explanation, the
|
|
@@ -25,22 +25,20 @@ import { describePart, placementCopy } from "../utils/placement-override-copy.js
|
|
|
25
25
|
*
|
|
26
26
|
* One component so the two tabs cannot word the same idea two ways — the
|
|
27
27
|
* mismatch the old "Style target" / "Override target" pair was.
|
|
28
|
-
*/ export function PlacementPartsHeader({ kind, parts, definitionNodes, propValues, value, onChange, changedKeys, helperText, changeCount, onResetAll, helpExcerpt, helpHref }) {
|
|
28
|
+
*/ export function PlacementPartsHeader({ kind, parts, definitionNodes, propValues, propDefaults, value, onChange, changedKeys, helperText, changeCount, onResetAll, helpExcerpt, helpHref }) {
|
|
29
29
|
const copy = placementCopy(kind);
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
return byKey;
|
|
39
|
-
}, [
|
|
30
|
+
// Named as one list, so parts that would share a name — twelve cards'
|
|
31
|
+
// arrow icons — are told apart by the card each sits in (AGL-3293).
|
|
32
|
+
const labels = useMemo(()=>describeParts(parts, definitionNodes, {
|
|
33
|
+
kind,
|
|
34
|
+
propValues,
|
|
35
|
+
propDefaults
|
|
36
|
+
}), [
|
|
40
37
|
parts,
|
|
41
38
|
definitionNodes,
|
|
42
39
|
kind,
|
|
43
|
-
propValues
|
|
40
|
+
propValues,
|
|
41
|
+
propDefaults
|
|
44
42
|
]);
|
|
45
43
|
const handleChange = useCallback((event)=>onChange(event.target.value), [
|
|
46
44
|
onChange
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../libs/besigner/feature/designer/src/lib/components/placement-parts-header.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 { HelpTip } from '@aglyn/shared-ui-jsx'\nimport { Box, Link, MenuItem, TextField, Typography } from '@mui/material'\nimport { type ChangeEvent, useCallback, useMemo } from 'react'\nimport {\n
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/besigner/feature/designer/src/lib/components/placement-parts-header.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 { HelpTip } from '@aglyn/shared-ui-jsx'\nimport { Box, Link, MenuItem, TextField, Typography } from '@mui/material'\nimport { type ChangeEvent, useCallback, useMemo } from 'react'\nimport {\n describeParts,\n type PartNode,\n type PlacementKind,\n placementCopy,\n} from '../utils/placement-override-copy'\n\nexport interface PlacementPartsHeaderProps {\n /** Which kind of shared thing this is a placement of. */\n kind: PlacementKind\n /** The definition's parts, in tree order (`listInstanceStyleTargets`). */\n parts: readonly Aglyn.InstanceStyleTarget[]\n /** The definition's nodes, which the part names are read from. */\n definitionNodes?: Record<string, PartNode>\n /** The placement's declared-property values, for part names that show them. */\n propValues?: Record<string, unknown>\n /**\n * The definition's declared defaults, for a part whose words come from a\n * property this page leaves unset (AGL-3293).\n */\n propDefaults?: Record<string, unknown>\n /** The picked part's key. */\n value: string\n onChange: (key: string) => void\n /** Keys of the parts this page already changes. */\n changedKeys: ReadonlySet<string>\n /** The line under the picker. */\n helperText: string\n /** How many changes this page makes, across every part. */\n changeCount: number\n onResetAll: () => void\n /** The help tip's body. */\n helpExcerpt: string\n /** The help tip's docs link. */\n helpHref: string\n}\n\n/**\n * The top of the \"Change it on this page only\" section, shared by the Styles\n * and Attributes tabs (AGL-3288): heading, one line of explanation, the\n * \"Which part?\" picker and the \"N changes on this page · Reset all\" line.\n *\n * One component so the two tabs cannot word the same idea two ways — the\n * mismatch the old \"Style target\" / \"Override target\" pair was.\n */\nexport function PlacementPartsHeader({\n kind,\n parts,\n definitionNodes,\n propValues,\n propDefaults,\n value,\n onChange,\n changedKeys,\n helperText,\n changeCount,\n onResetAll,\n helpExcerpt,\n helpHref,\n}: PlacementPartsHeaderProps) {\n const copy = placementCopy(kind)\n // Named as one list, so parts that would share a name — twelve cards'\n // arrow icons — are told apart by the card each sits in (AGL-3293).\n const labels = useMemo(\n () =>\n describeParts(parts, definitionNodes, { kind, propValues, propDefaults }),\n [parts, definitionNodes, kind, propValues, propDefaults],\n )\n\n const handleChange = useCallback(\n (event: ChangeEvent<HTMLInputElement>) => onChange(event.target.value),\n [onChange],\n )\n\n return (\n <Box>\n <Typography\n variant=\"overline\"\n color=\"text.secondary\"\n component=\"div\"\n sx={{ display: 'flex', alignItems: 'center' }}\n >\n {copy.sectionTitle}\n <HelpTip\n title={copy.sectionTitle}\n excerpt={helpExcerpt}\n href={helpHref}\n sx={{ ml: 0.25, fontSize: '0.9em' }}\n />\n </Typography>\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n component=\"p\"\n sx={{ mt: 0, mb: 1 }}\n >\n {copy.intro}\n </Typography>\n {parts.length > 1 ? (\n <TextField\n select\n fullWidth\n size=\"small\"\n margin=\"dense\"\n label={copy.partPickerLabel}\n value={value}\n onChange={handleChange}\n helperText={helperText}\n slotProps={{\n select: {\n // The closed box shows the part's name only; the \"(changed)\"\n // mark is for choosing between parts, in the open menu.\n renderValue: (key) =>\n labels.get(key as string) ?? copy.wholePart,\n },\n }}\n >\n {parts.map((entry) => (\n <MenuItem\n key={entry.key}\n value={entry.key}\n // Nesting reads as nesting: the definition's tree is the only\n // map an author has of what is inside it.\n sx={{ pl: 2 + entry.depth * 1.5 }}\n >\n {labels.get(entry.key)}\n {changedKeys.has(entry.key) ? (\n <Box\n component=\"span\"\n sx={{\n display: 'inline-flex',\n alignItems: 'center',\n ml: 1,\n color: 'secondary.main',\n fontSize: '0.75rem',\n }}\n >\n <Box\n component=\"span\"\n aria-hidden\n sx={{\n width: 6,\n height: 6,\n borderRadius: '50%',\n bgcolor: 'secondary.main',\n mr: 0.5,\n }}\n />\n {copy.changedMark}\n </Box>\n ) : null}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n <Box\n sx={{\n display: 'flex',\n alignItems: 'center',\n flexWrap: 'wrap',\n gap: 0.75,\n mt: 0.5,\n mb: 1,\n }}\n >\n <Typography variant=\"body2\" color=\"text.secondary\" component=\"span\">\n {copy.summary(changeCount)}\n </Typography>\n {changeCount > 0 ? (\n <>\n <Typography\n variant=\"body2\"\n color=\"text.secondary\"\n component=\"span\"\n aria-hidden\n >\n {'·'}\n </Typography>\n <Link\n component=\"button\"\n type=\"button\"\n variant=\"body2\"\n onClick={onResetAll}\n aria-label={copy.resetAllAria}\n >\n {copy.resetAll}\n </Link>\n </>\n ) : null}\n </Box>\n </Box>\n )\n}\n\nexport default PlacementPartsHeader\n"],"names":["HelpTip","Box","Link","MenuItem","TextField","Typography","useCallback","useMemo","describeParts","placementCopy","PlacementPartsHeader","kind","parts","definitionNodes","propValues","propDefaults","value","onChange","changedKeys","helperText","changeCount","onResetAll","helpExcerpt","helpHref","copy","labels","handleChange","event","target","variant","color","component","sx","display","alignItems","sectionTitle","title","excerpt","href","ml","fontSize","mt","mb","intro","length","select","fullWidth","size","margin","label","partPickerLabel","slotProps","renderValue","key","get","wholePart","map","entry","pl","depth","has","aria-hidden","width","height","borderRadius","bgcolor","mr","changedMark","flexWrap","gap","summary","type","onClick","aria-label","resetAllAria","resetAll"],"mappings":"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"}
|
|
@@ -95,8 +95,10 @@ export interface PartNode {
|
|
|
95
95
|
parentId?: string | null;
|
|
96
96
|
nodes?: unknown;
|
|
97
97
|
props?: unknown;
|
|
98
|
+
/** The node's own styles: a fill, border or shadow makes a group a card. */
|
|
99
|
+
sx?: unknown;
|
|
98
100
|
}
|
|
99
|
-
/** Options for {@link describePart}. */
|
|
101
|
+
/** Options for {@link describePart} and {@link describeParts}. */
|
|
100
102
|
export interface DescribePartOptions {
|
|
101
103
|
/** Which kind of placement this is; decides "Whole component/form". */
|
|
102
104
|
kind?: PlacementKind;
|
|
@@ -105,6 +107,12 @@ export interface DescribePartOptions {
|
|
|
105
107
|
* a text that reads `{{prop.headline}}` is named by what THIS page shows.
|
|
106
108
|
*/
|
|
107
109
|
propValues?: Record<string, unknown>;
|
|
110
|
+
/**
|
|
111
|
+
* The definition's declared defaults ({@link propDefaultsOf}), for a token
|
|
112
|
+
* this page leaves unset: the part is named by the words the page actually
|
|
113
|
+
* shows, not by the property's name (AGL-3293).
|
|
114
|
+
*/
|
|
115
|
+
propDefaults?: Record<string, unknown>;
|
|
108
116
|
}
|
|
109
117
|
/** How long a preview may run before it is cut with an ellipsis. */
|
|
110
118
|
export declare const PART_PREVIEW_LIMIT = 32;
|
|
@@ -113,19 +121,43 @@ export declare function friendlyTypeName(componentId: string | undefined): strin
|
|
|
113
121
|
/** Collapses whitespace and cuts a preview at {@link PART_PREVIEW_LIMIT}. */
|
|
114
122
|
export declare function truncatePreview(text: string): string;
|
|
115
123
|
/**
|
|
116
|
-
* The
|
|
117
|
-
*
|
|
124
|
+
* The declared properties' defaults of a definition, by name. Reads the
|
|
125
|
+
* `props` a reusable component declares (AGL-1247); a placed form's design
|
|
126
|
+
* declares none, so it answers `{}`.
|
|
127
|
+
*/
|
|
128
|
+
export declare function propDefaultsOf(definition: unknown): Record<string, unknown>;
|
|
129
|
+
/**
|
|
130
|
+
* The picker's name for one part of a definition (AGL-3288, AGL-3293): what
|
|
131
|
+
* it IS in an author's words, and what it SHOWS.
|
|
118
132
|
*
|
|
119
133
|
* - The outermost element is "Whole component" / "Whole form".
|
|
120
134
|
* - A name the definition's author gave the element wins outright.
|
|
121
135
|
* - An element with words of its own reads `Button: Start free`, or
|
|
122
|
-
* `Text: "Design on a live canvas…"` for running text
|
|
123
|
-
*
|
|
124
|
-
* - An
|
|
125
|
-
*
|
|
126
|
-
*
|
|
136
|
+
* `Text: "Design on a live canvas…"` for running text — a declared
|
|
137
|
+
* property's token read as this page shows it, or as its default.
|
|
138
|
+
* - An icon reads by its icon: `Icon: Arrow top right`.
|
|
139
|
+
* - A group of like items is named by listing them:
|
|
140
|
+
* `Group: Besigner, Console, Commerce…`.
|
|
141
|
+
* - Any other container is named by the first words inside it, and one with
|
|
142
|
+
* a surface of its own is a Card: `Card: Besigner`.
|
|
143
|
+
* - A wordless container is named by what it holds: `Icon box: View grid`.
|
|
144
|
+
* - An element with nothing to read is placed by the nearest enclosing
|
|
145
|
+
* element that has words: `Group (in Besigner card)`.
|
|
146
|
+
*
|
|
147
|
+
* A single name can still have twins elsewhere in the component — twelve
|
|
148
|
+
* cards each have an "Icon: Arrow top right". {@link describeParts} names a
|
|
149
|
+
* whole list and tells the twins apart.
|
|
127
150
|
*/
|
|
128
151
|
export declare function describePart(entry: PartEntry, definitionNodes: Record<string, PartNode> | null | undefined, options?: DescribePartOptions): string;
|
|
152
|
+
/**
|
|
153
|
+
* Names for every part of a definition, keyed like the entries, with no two
|
|
154
|
+
* alike (AGL-3293): parts that would share a name are told apart by where
|
|
155
|
+
* they sit — "Icon: Arrow top right (in Besigner card)" — and, where even
|
|
156
|
+
* that repeats, by number.
|
|
157
|
+
*/
|
|
158
|
+
export declare function describeParts<E extends PartEntry & {
|
|
159
|
+
key: string;
|
|
160
|
+
}>(entries: readonly E[], definitionNodes: Record<string, PartNode> | null | undefined, options?: DescribePartOptions): Map<string, string>;
|
|
129
161
|
/**
|
|
130
162
|
* The name of one changed style setting, as the Styles tab labels its field
|
|
131
163
|
* — never the stored key. `fieldLabels` is the panel's own field-name →
|