@aglyn/aglyn 1.0.0-beta.172 → 1.0.0-beta.173

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aglyn/aglyn",
3
- "version": "1.0.0-beta.172",
3
+ "version": "1.0.0-beta.173",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -33,15 +33,15 @@
33
33
  "./package.json": "./package.json"
34
34
  },
35
35
  "dependencies": {
36
- "@aglyn/shared-data-enums": "1.0.0-beta.172",
37
- "@aglyn/shared-data-mdi": "1.0.0-beta.172",
38
- "@aglyn/shared-data-types": "1.0.0-beta.172",
39
- "@aglyn/shared-util-email": "1.0.0-beta.172",
40
- "@aglyn/shared-util-http": "1.0.0-beta.172",
41
- "@aglyn/shared-util-logger": "1.0.0-beta.172",
42
- "@aglyn/shared-util-timestamp": "1.0.0-beta.172",
43
- "@aglyn/shared-util-tools": "1.0.0-beta.172",
44
- "@aglyn/shared-util-vendor": "1.0.0-beta.172",
36
+ "@aglyn/shared-data-enums": "1.0.0-beta.173",
37
+ "@aglyn/shared-data-mdi": "1.0.0-beta.173",
38
+ "@aglyn/shared-data-types": "1.0.0-beta.173",
39
+ "@aglyn/shared-util-email": "1.0.0-beta.173",
40
+ "@aglyn/shared-util-http": "1.0.0-beta.173",
41
+ "@aglyn/shared-util-logger": "1.0.0-beta.173",
42
+ "@aglyn/shared-util-timestamp": "1.0.0-beta.173",
43
+ "@aglyn/shared-util-tools": "1.0.0-beta.173",
44
+ "@aglyn/shared-util-vendor": "1.0.0-beta.173",
45
45
  "@data-driven-forms/react-form-renderer": "^4.2.0",
46
46
  "@msgpack/msgpack": "^3.1.3",
47
47
  "@types/unist": "^3.0.3",
@@ -61,11 +61,15 @@ export declare function applyLayoutProps<N extends AglynNodeSchema = AglynNodeSc
61
61
  /**
62
62
  * A screen grafted through its whole layout chain, innermost first, with each
63
63
  * layout's properties applied from the screen's values for that layout
64
- * (`layoutPropValues`, keyed by layout id).
64
+ * (`layoutPropValues`, keyed by layout id), and then the screen's per-page
65
+ * restyling of that layout's elements (`layoutStyleOverrides`, AGL-3286) —
66
+ * after the properties, so a page's style wins over whatever a property
67
+ * bound, and before the graft, so an element the layout hides for this page
68
+ * is simply not there to be styled.
65
69
  *
66
70
  * The one composition the published page, the besigner's Preview and its
67
71
  * layout chrome all run, so none of them can disagree about which value a
68
72
  * layout renders with.
69
73
  */
70
- export declare function composeLayoutChainWithProps<N extends AglynNodeSchema = AglynNodeSchema>(chain: ReadonlyArray<LayoutChainEntry<N> | null | undefined>, screenNodes: NormalizedNodes<N>, layoutPropValues?: unknown): NormalizedNodes<N>;
74
+ export declare function composeLayoutChainWithProps<N extends AglynNodeSchema = AglynNodeSchema>(chain: ReadonlyArray<LayoutChainEntry<N> | null | undefined>, screenNodes: NormalizedNodes<N>, layoutPropValues?: unknown, layoutStyleOverrides?: unknown): NormalizedNodes<N>;
71
75
  export default composeLayoutChainWithProps;
@@ -16,6 +16,7 @@
16
16
  */ import { NODE_ROOT_ID } from "../canvas-manager/canvas-manager.js";
17
17
  import { composeLayoutChainAndScreenNodes } from "./compose-layout-nodes.js";
18
18
  import { applyDeclaredProps } from "./compose-reusable-components.js";
19
+ import { applyLayoutStyleOverrides, layoutStyleOverridesFor } from "./layout-style-overrides.js";
19
20
  /**
20
21
  * Screen-version key holding the screen's values for the layouts it renders
21
22
  * inside, beside the `layoutId` binding. Persisted in screen version documents
@@ -48,13 +49,17 @@ import { applyDeclaredProps } from "./compose-reusable-components.js";
48
49
  /**
49
50
  * A screen grafted through its whole layout chain, innermost first, with each
50
51
  * layout's properties applied from the screen's values for that layout
51
- * (`layoutPropValues`, keyed by layout id).
52
+ * (`layoutPropValues`, keyed by layout id), and then the screen's per-page
53
+ * restyling of that layout's elements (`layoutStyleOverrides`, AGL-3286) —
54
+ * after the properties, so a page's style wins over whatever a property
55
+ * bound, and before the graft, so an element the layout hides for this page
56
+ * is simply not there to be styled.
52
57
  *
53
58
  * The one composition the published page, the besigner's Preview and its
54
59
  * layout chrome all run, so none of them can disagree about which value a
55
60
  * layout renders with.
56
- */ export function composeLayoutChainWithProps(chain, screenNodes, layoutPropValues) {
57
- return composeLayoutChainAndScreenNodes(chain.map((entry)=>applyLayoutProps(entry == null ? void 0 : entry.nodes, entry == null ? void 0 : entry.props, layoutPropValuesFor(layoutPropValues, entry == null ? void 0 : entry.layoutId))), screenNodes);
61
+ */ export function composeLayoutChainWithProps(chain, screenNodes, layoutPropValues, layoutStyleOverrides) {
62
+ return composeLayoutChainAndScreenNodes(chain.map((entry)=>applyLayoutStyleOverrides(applyLayoutProps(entry == null ? void 0 : entry.nodes, entry == null ? void 0 : entry.props, layoutPropValuesFor(layoutPropValues, entry == null ? void 0 : entry.layoutId)), layoutStyleOverridesFor(layoutStyleOverrides, entry == null ? void 0 : entry.layoutId))), screenNodes);
58
63
  }
59
64
  export default composeLayoutChainWithProps;
60
65
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/compose-layout-props.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 {\n AglynNodeSchema,\n NodeId,\n ReusableComponentProp,\n} from '../foundation'\nimport { NODE_ROOT_ID } from '../canvas-manager/canvas-manager'\nimport { composeLayoutChainAndScreenNodes } from './compose-layout-nodes'\nimport { applyDeclaredProps } from './compose-reusable-components'\n\n/**\n * Layout properties (AGL-2893): a shared layout declares properties exactly as\n * a reusable component does, binds its own nodes to them with `{{prop.*}}`,\n * and each screen rendering inside it sets the values.\n *\n * The values are applied to each layout's OWN nodes before the screen is\n * grafted into its slot, so a `{{prop.*}}` the screen's own copy happens to\n * contain is never touched, and a component the layout places receives the\n * values it is handed before it is grafted.\n */\n\ntype NormalizedNodes<N extends AglynNodeSchema> = Record<NodeId, N>\n\n/**\n * Screen-version key holding the screen's values for the layouts it renders\n * inside, beside the `layoutId` binding. Persisted in screen version documents\n * — never rename.\n */\nexport const SCREEN_LAYOUT_PROP_VALUES_KEY = 'layoutPropValues'\n\n/** One layout of a screen's chain, as composition reads it. */\nexport interface LayoutChainEntry<N extends AglynNodeSchema = AglynNodeSchema> {\n /** The layout document's id, which a screen's values are keyed by. */\n layoutId?: string | null\n /** The published version's nodes. */\n nodes?: NormalizedNodes<N> | null\n /** The published version's declared properties. */\n props?: ReusableComponentProp[] | null\n}\n\n/**\n * A screen's values for one layout of its chain, or `undefined` where it set\n * none. Anything that is not a map of values is read as none.\n */\nexport function layoutPropValuesFor(\n layoutPropValues: unknown,\n layoutId: string | null | undefined,\n): Record<string, unknown> | undefined {\n if (!layoutId || !layoutPropValues || typeof layoutPropValues !== 'object') {\n return undefined\n }\n const values = (layoutPropValues as Record<string, unknown>)[layoutId]\n return values && typeof values === 'object' && !Array.isArray(values)\n ? (values as Record<string, unknown>)\n : undefined\n}\n\n/**\n * A layout's nodes with its declared properties applied for one screen: each\n * `{{prop.*}}` takes the screen's value or the property's default, a field\n * bound to one property receives the value its kind holds, and the parts the\n * screen hides (`hideIf` / `hideUnless`) are pruned — the sequence a reusable\n * component's graft runs, over the layout's root.\n *\n * A layout with no root is returned as it is, for the reason\n * `composeLayoutAndScreenNodes` gives: a broken layout degrades to the bare\n * screen rather than taking a page down.\n */\nexport function applyLayoutProps<N extends AglynNodeSchema = AglynNodeSchema>(\n nodes: NormalizedNodes<N> | null | undefined,\n props: ReusableComponentProp[] | null | undefined,\n values: Readonly<Record<string, unknown>> | null | undefined,\n): NormalizedNodes<N> | undefined {\n if (!nodes || !nodes[NODE_ROOT_ID]) return nodes ?? undefined\n return applyDeclaredProps(nodes, props, values, NODE_ROOT_ID)\n}\n\n/**\n * A screen grafted through its whole layout chain, innermost first, with each\n * layout's properties applied from the screen's values for that layout\n * (`layoutPropValues`, keyed by layout id).\n *\n * The one composition the published page, the besigner's Preview and its\n * layout chrome all run, so none of them can disagree about which value a\n * layout renders with.\n */\nexport function composeLayoutChainWithProps<\n N extends AglynNodeSchema = AglynNodeSchema,\n>(\n chain: ReadonlyArray<LayoutChainEntry<N> | null | undefined>,\n screenNodes: NormalizedNodes<N>,\n layoutPropValues?: unknown,\n): NormalizedNodes<N> {\n return composeLayoutChainAndScreenNodes(\n chain.map((entry) =>\n applyLayoutProps(\n entry?.nodes,\n entry?.props,\n layoutPropValuesFor(layoutPropValues, entry?.layoutId),\n ),\n ),\n screenNodes,\n )\n}\n\nexport default composeLayoutChainWithProps\n"],"names":["NODE_ROOT_ID","composeLayoutChainAndScreenNodes","applyDeclaredProps","SCREEN_LAYOUT_PROP_VALUES_KEY","layoutPropValuesFor","layoutPropValues","layoutId","undefined","values","Array","isArray","applyLayoutProps","nodes","props","composeLayoutChainWithProps","chain","screenNodes","map","entry"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAOD,SAASA,YAAY,QAAQ,sCAAkC;AAC/D,SAASC,gCAAgC,QAAQ,4BAAwB;AACzE,SAASC,kBAAkB,QAAQ,mCAA+B;AAelE;;;;CAIC,GACD,OAAO,MAAMC,gCAAgC,mBAAkB;AAY/D;;;CAGC,GACD,OAAO,SAASC,oBACdC,gBAAyB,EACzBC,QAAmC;IAEnC,IAAI,CAACA,YAAY,CAACD,oBAAoB,OAAOA,qBAAqB,UAAU;QAC1E,OAAOE;IACT;IACA,MAAMC,SAAS,AAACH,gBAA4C,CAACC,SAAS;IACtE,OAAOE,UAAU,OAAOA,WAAW,YAAY,CAACC,MAAMC,OAAO,CAACF,UACzDA,SACDD;AACN;AAEA;;;;;;;;;;CAUC,GACD,OAAO,SAASI,iBACdC,KAA4C,EAC5CC,KAAiD,EACjDL,MAA4D;IAE5D,IAAI,CAACI,SAAS,CAACA,KAAK,CAACZ,aAAa,EAAE,OAAOY,gBAAAA,QAASL;IACpD,OAAOL,mBAAmBU,OAAOC,OAAOL,QAAQR;AAClD;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASc,4BAGdC,KAA4D,EAC5DC,WAA+B,EAC/BX,gBAA0B;IAE1B,OAAOJ,iCACLc,MAAME,GAAG,CAAC,CAACC,QACTP,iBACEO,yBAAAA,MAAON,KAAK,EACZM,yBAAAA,MAAOL,KAAK,EACZT,oBAAoBC,kBAAkBa,yBAAAA,MAAOZ,QAAQ,KAGzDU;AAEJ;AAEA,eAAeF,4BAA2B"}
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/compose-layout-props.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 {\n AglynNodeSchema,\n NodeId,\n ReusableComponentProp,\n} from '../foundation'\nimport { NODE_ROOT_ID } from '../canvas-manager/canvas-manager'\nimport { composeLayoutChainAndScreenNodes } from './compose-layout-nodes'\nimport { applyDeclaredProps } from './compose-reusable-components'\nimport {\n applyLayoutStyleOverrides,\n layoutStyleOverridesFor,\n} from './layout-style-overrides'\n\n/**\n * Layout properties (AGL-2893): a shared layout declares properties exactly as\n * a reusable component does, binds its own nodes to them with `{{prop.*}}`,\n * and each screen rendering inside it sets the values.\n *\n * The values are applied to each layout's OWN nodes before the screen is\n * grafted into its slot, so a `{{prop.*}}` the screen's own copy happens to\n * contain is never touched, and a component the layout places receives the\n * values it is handed before it is grafted.\n */\n\ntype NormalizedNodes<N extends AglynNodeSchema> = Record<NodeId, N>\n\n/**\n * Screen-version key holding the screen's values for the layouts it renders\n * inside, beside the `layoutId` binding. Persisted in screen version documents\n * — never rename.\n */\nexport const SCREEN_LAYOUT_PROP_VALUES_KEY = 'layoutPropValues'\n\n/** One layout of a screen's chain, as composition reads it. */\nexport interface LayoutChainEntry<N extends AglynNodeSchema = AglynNodeSchema> {\n /** The layout document's id, which a screen's values are keyed by. */\n layoutId?: string | null\n /** The published version's nodes. */\n nodes?: NormalizedNodes<N> | null\n /** The published version's declared properties. */\n props?: ReusableComponentProp[] | null\n}\n\n/**\n * A screen's values for one layout of its chain, or `undefined` where it set\n * none. Anything that is not a map of values is read as none.\n */\nexport function layoutPropValuesFor(\n layoutPropValues: unknown,\n layoutId: string | null | undefined,\n): Record<string, unknown> | undefined {\n if (!layoutId || !layoutPropValues || typeof layoutPropValues !== 'object') {\n return undefined\n }\n const values = (layoutPropValues as Record<string, unknown>)[layoutId]\n return values && typeof values === 'object' && !Array.isArray(values)\n ? (values as Record<string, unknown>)\n : undefined\n}\n\n/**\n * A layout's nodes with its declared properties applied for one screen: each\n * `{{prop.*}}` takes the screen's value or the property's default, a field\n * bound to one property receives the value its kind holds, and the parts the\n * screen hides (`hideIf` / `hideUnless`) are pruned — the sequence a reusable\n * component's graft runs, over the layout's root.\n *\n * A layout with no root is returned as it is, for the reason\n * `composeLayoutAndScreenNodes` gives: a broken layout degrades to the bare\n * screen rather than taking a page down.\n */\nexport function applyLayoutProps<N extends AglynNodeSchema = AglynNodeSchema>(\n nodes: NormalizedNodes<N> | null | undefined,\n props: ReusableComponentProp[] | null | undefined,\n values: Readonly<Record<string, unknown>> | null | undefined,\n): NormalizedNodes<N> | undefined {\n if (!nodes || !nodes[NODE_ROOT_ID]) return nodes ?? undefined\n return applyDeclaredProps(nodes, props, values, NODE_ROOT_ID)\n}\n\n/**\n * A screen grafted through its whole layout chain, innermost first, with each\n * layout's properties applied from the screen's values for that layout\n * (`layoutPropValues`, keyed by layout id), and then the screen's per-page\n * restyling of that layout's elements (`layoutStyleOverrides`, AGL-3286) —\n * after the properties, so a page's style wins over whatever a property\n * bound, and before the graft, so an element the layout hides for this page\n * is simply not there to be styled.\n *\n * The one composition the published page, the besigner's Preview and its\n * layout chrome all run, so none of them can disagree about which value a\n * layout renders with.\n */\nexport function composeLayoutChainWithProps<\n N extends AglynNodeSchema = AglynNodeSchema,\n>(\n chain: ReadonlyArray<LayoutChainEntry<N> | null | undefined>,\n screenNodes: NormalizedNodes<N>,\n layoutPropValues?: unknown,\n layoutStyleOverrides?: unknown,\n): NormalizedNodes<N> {\n return composeLayoutChainAndScreenNodes(\n chain.map((entry) =>\n applyLayoutStyleOverrides(\n applyLayoutProps(\n entry?.nodes,\n entry?.props,\n layoutPropValuesFor(layoutPropValues, entry?.layoutId),\n ),\n layoutStyleOverridesFor(layoutStyleOverrides, entry?.layoutId),\n ),\n ),\n screenNodes,\n )\n}\n\nexport default composeLayoutChainWithProps\n"],"names":["NODE_ROOT_ID","composeLayoutChainAndScreenNodes","applyDeclaredProps","applyLayoutStyleOverrides","layoutStyleOverridesFor","SCREEN_LAYOUT_PROP_VALUES_KEY","layoutPropValuesFor","layoutPropValues","layoutId","undefined","values","Array","isArray","applyLayoutProps","nodes","props","composeLayoutChainWithProps","chain","screenNodes","layoutStyleOverrides","map","entry"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAOD,SAASA,YAAY,QAAQ,sCAAkC;AAC/D,SAASC,gCAAgC,QAAQ,4BAAwB;AACzE,SAASC,kBAAkB,QAAQ,mCAA+B;AAClE,SACEC,yBAAyB,EACzBC,uBAAuB,QAClB,8BAA0B;AAejC;;;;CAIC,GACD,OAAO,MAAMC,gCAAgC,mBAAkB;AAY/D;;;CAGC,GACD,OAAO,SAASC,oBACdC,gBAAyB,EACzBC,QAAmC;IAEnC,IAAI,CAACA,YAAY,CAACD,oBAAoB,OAAOA,qBAAqB,UAAU;QAC1E,OAAOE;IACT;IACA,MAAMC,SAAS,AAACH,gBAA4C,CAACC,SAAS;IACtE,OAAOE,UAAU,OAAOA,WAAW,YAAY,CAACC,MAAMC,OAAO,CAACF,UACzDA,SACDD;AACN;AAEA;;;;;;;;;;CAUC,GACD,OAAO,SAASI,iBACdC,KAA4C,EAC5CC,KAAiD,EACjDL,MAA4D;IAE5D,IAAI,CAACI,SAAS,CAACA,KAAK,CAACd,aAAa,EAAE,OAAOc,gBAAAA,QAASL;IACpD,OAAOP,mBAAmBY,OAAOC,OAAOL,QAAQV;AAClD;AAEA;;;;;;;;;;;;CAYC,GACD,OAAO,SAASgB,4BAGdC,KAA4D,EAC5DC,WAA+B,EAC/BX,gBAA0B,EAC1BY,oBAA8B;IAE9B,OAAOlB,iCACLgB,MAAMG,GAAG,CAAC,CAACC,QACTlB,0BACEU,iBACEQ,yBAAAA,MAAOP,KAAK,EACZO,yBAAAA,MAAON,KAAK,EACZT,oBAAoBC,kBAAkBc,yBAAAA,MAAOb,QAAQ,IAEvDJ,wBAAwBe,sBAAsBE,yBAAAA,MAAOb,QAAQ,KAGjEU;AAEJ;AAEA,eAAeF,4BAA2B"}
@@ -17,11 +17,53 @@
17
17
  import type { AglynNodeSchema, NodeId, ReusableComponentIcon, ReusableComponentProp } from '../foundation';
18
18
  export * from './reusable-component-keys';
19
19
  export * from './reusable-prop-values';
20
+ /**
21
+ * Whether a node is a placed form that names a form entity — the second kind
22
+ * of node that stands for a tree stored on another document (see
23
+ * {@link PlacementKind}). A `form` node with no `formId` is an ordinary form
24
+ * whose fields are its own children, and has no override layer.
25
+ *
26
+ * The ref test matches the graft's own resolution rule (a non-empty string),
27
+ * so the editor and the renderer agree about which forms are placements.
28
+ */
29
+ export declare function isPlacedFormNode(node: {
30
+ componentId?: string;
31
+ props?: unknown;
32
+ } | undefined | null): boolean;
33
+ /**
34
+ * Whether a node is a placement that GRAFTS a definition — a reusable
35
+ * component instance or a placed form — and therefore carries the two
36
+ * per-placement override layers (`styleOverrides`, `attrOverrides`, AGL-3285).
37
+ *
38
+ * One predicate for both kinds on purpose: the graft already expands them in
39
+ * one pass, and an override layer that one kind honoured and the other
40
+ * silently ignored would be the canvas/Preview/tenant disagreement this file
41
+ * exists to prevent.
42
+ */
43
+ export declare function isDefinitionPlacement(node: {
44
+ componentId?: string;
45
+ props?: unknown;
46
+ } | undefined | null): boolean;
47
+ /**
48
+ * The tree a placement grafts, from whichever map its kind reads — the
49
+ * component definitions for an instance, the published form designs for a
50
+ * placed form — or `undefined` when it is not a placement or its ref resolves
51
+ * to nothing (AGL-3285). The editor panels use it to offer the same targets
52
+ * the graft will consult.
53
+ */
54
+ export declare function placementDefinitionFor<N extends AglynNodeSchema = AglynNodeSchema>(node: {
55
+ componentId?: string;
56
+ props?: unknown;
57
+ } | undefined | null, sources: {
58
+ definitions?: Record<string, ReusableComponentTree<N> | undefined>;
59
+ formDesigns?: Record<string, Pick<ReusableComponentTree<N>, 'rootId' | 'nodes'> | undefined>;
60
+ }): Pick<ReusableComponentTree<N>, 'rootId' | 'nodes'> | undefined;
20
61
  /**
21
62
  * Every per-instance style override an instance node carries, keyed the way
22
63
  * it is stored (AGL-1332): {@link STYLE_OVERRIDES_ROOT_KEY} for the
23
64
  * component root, and a DEFINITION-internal node id for each overridden
24
- * leaf. Empty for a node that is not an instance or carries none.
65
+ * leaf. Empty for a node that is not a placement ({@link
66
+ * isDefinitionPlacement}: an instance or a placed form) or carries none.
25
67
  *
26
68
  * Keyed on the definition's own id, never the grafted `cmp__…` id: the
27
69
  * graft id is DERIVED from the instance id, so keying on it would break the
@@ -39,6 +81,7 @@ export * from './reusable-prop-values';
39
81
  */
40
82
  export declare function getInstanceStyleOverrides(node: {
41
83
  componentId?: string;
84
+ props?: unknown;
42
85
  styleOverrides?: unknown;
43
86
  } | undefined | null): Record<string, Record<string, unknown>>;
44
87
  /**
@@ -48,8 +91,48 @@ export declare function getInstanceStyleOverrides(node: {
48
91
  */
49
92
  export declare function getInstanceRootStyleOverride(node: {
50
93
  componentId?: string;
94
+ props?: unknown;
51
95
  styleOverrides?: unknown;
52
96
  } | undefined | null): Record<string, unknown> | undefined;
97
+ /**
98
+ * Props a placed form's attribute overrides must never address, on any node
99
+ * of its design (AGL-3285).
100
+ *
101
+ * A form is edited once and every page placing it follows — that is the
102
+ * whole point of the form entity. Its submission list, its dataset columns,
103
+ * its consent tick and its routing are all keyed to what the ENTITY declares,
104
+ * so a page that renamed a field, retyped it, made it optional, rewrote its
105
+ * choices or re-pointed where the form sends would file answers the form's
106
+ * own schema does not describe, from one page only, with nothing on the
107
+ * Forms page saying so. Per page, a form may LOOK different; it may not
108
+ * collect something different.
109
+ *
110
+ * - The form node's identity and submission config: `formId`, `formName`,
111
+ * `datasetId`, `datasetName`, and the after-submit outcome
112
+ * (`afterSubmit`, `redirectScreenId`, `redirectUrl`, `revealNodeId`).
113
+ * - Each field's contract: `fieldName` (the submission key),
114
+ * `datasetFieldId`, `fieldType`, `options` (a ticked option's TEXT is the
115
+ * posted value, and the consent check reads it) and `required`.
116
+ * - Element attributes a browser submission reads directly: `name`, `type`
117
+ * (a submit button made a plain button no longer submits), `value`,
118
+ * `action`, `method`, and `disabled` (a disabled input is not posted).
119
+ * - The hide directives: a hidden field is pruned from the page, so its
120
+ * value is never sent.
121
+ *
122
+ * Label, placeholder, help text, the submit label and success message, and
123
+ * everything about styling stay overridable.
124
+ */
125
+ export declare const PLACED_FORM_REFUSED_ATTR_PROPS: ReadonlySet<string>;
126
+ /**
127
+ * Whether `prop` may never be an attribute override on this placement — `sx`
128
+ * on every placement, plus {@link PLACED_FORM_REFUSED_ATTR_PROPS} on a placed
129
+ * form. The render layer and the editor's writer both ask this, so a refused
130
+ * prop can neither be offered nor take effect.
131
+ */
132
+ export declare function isAttrOverrideRefused(placement: {
133
+ componentId?: string;
134
+ props?: unknown;
135
+ } | undefined | null, prop: string): boolean;
53
136
  /**
54
137
  * Every per-instance ATTRIBUTE override an instance node carries, keyed the
55
138
  * way {@link getInstanceStyleOverrides} keys styles — `root` for the
@@ -62,6 +145,7 @@ export declare function getInstanceRootStyleOverride(node: {
62
145
  */
63
146
  export declare function getInstanceAttrOverrides(node: {
64
147
  componentId?: string;
148
+ props?: unknown;
65
149
  attrOverrides?: unknown;
66
150
  } | undefined | null): Record<string, Record<string, unknown>>;
67
151
  /** One styleable target inside an instance — see {@link listInstanceStyleTargets}. */
@@ -20,6 +20,7 @@ import { iconPathPropName } from "@aglyn/shared-data-mdi/utils/icon-path-prop-na
20
20
  import { reusablePropValueClass } from "../foundation/definitions/property-kinds.js";
21
21
  import { COMPONENT_NODE_ID_PREFIX, COMPONENT_PROP_TOKEN_PREFIX, INSTANCE_CARRIED_NODE_FIELDS, INSTANCE_CARRIED_PROPS, NODE_HIDE_IF_PROP, NODE_HIDE_UNLESS_PROP, REUSABLE_INSTANCE_COMPONENT_ID, REUSABLE_INSTANCE_PROP_VALUES_KEY, STYLE_OVERRIDES_ROOT_KEY } from "./reusable-component-keys.js";
22
22
  import { REPEAT_DIRECTIVE_PROPS, REPEAT_SELF_PROP, repeatKey, withoutRepeatDirective } from "./expand-repeatables.js";
23
+ import { FORM_COMPONENT_ID, FORM_ID_PROP } from "./forms.js";
23
24
  import { mergeNodeSx } from "./merge-node-sx.js";
24
25
  import { hasUrlScheme, isSafeNodeUrl, NODE_URL_PROPS } from "./node-url-policy.js";
25
26
  import { readInstanceIconValue, readYesNoValue, resolveReusablePropValues } from "./reusable-prop-values.js";
@@ -36,11 +37,60 @@ const isStyleRecord = (value)=>typeof value === 'object' && value !== null && !A
36
37
  ...new Set(names)
37
38
  ].join(' ') : undefined;
38
39
  }
40
+ /**
41
+ * Whether a node is a placed form that names a form entity — the second kind
42
+ * of node that stands for a tree stored on another document (see
43
+ * {@link PlacementKind}). A `form` node with no `formId` is an ordinary form
44
+ * whose fields are its own children, and has no override layer.
45
+ *
46
+ * The ref test matches the graft's own resolution rule (a non-empty string),
47
+ * so the editor and the renderer agree about which forms are placements.
48
+ */ export function isPlacedFormNode(node) {
49
+ var _node_props;
50
+ if ((node == null ? void 0 : node.componentId) !== FORM_COMPONENT_ID) return false;
51
+ const formId = (_node_props = node.props) == null ? void 0 : _node_props[FORM_ID_PROP];
52
+ return typeof formId === 'string' && formId !== '';
53
+ }
54
+ /**
55
+ * Whether a node is a placement that GRAFTS a definition — a reusable
56
+ * component instance or a placed form — and therefore carries the two
57
+ * per-placement override layers (`styleOverrides`, `attrOverrides`, AGL-3285).
58
+ *
59
+ * One predicate for both kinds on purpose: the graft already expands them in
60
+ * one pass, and an override layer that one kind honoured and the other
61
+ * silently ignored would be the canvas/Preview/tenant disagreement this file
62
+ * exists to prevent.
63
+ */ export function isDefinitionPlacement(node) {
64
+ return (node == null ? void 0 : node.componentId) === REUSABLE_INSTANCE_COMPONENT_ID || isPlacedFormNode(node);
65
+ }
66
+ /**
67
+ * The tree a placement grafts, from whichever map its kind reads — the
68
+ * component definitions for an instance, the published form designs for a
69
+ * placed form — or `undefined` when it is not a placement or its ref resolves
70
+ * to nothing (AGL-3285). The editor panels use it to offer the same targets
71
+ * the graft will consult.
72
+ */ export function placementDefinitionFor(node, sources) {
73
+ const props = node == null ? void 0 : node.props;
74
+ if ((node == null ? void 0 : node.componentId) === REUSABLE_INSTANCE_COMPONENT_ID) {
75
+ var _sources_definitions;
76
+ const refId = props == null ? void 0 : props['refId'];
77
+ return typeof refId === 'string' && refId ? (_sources_definitions = sources.definitions) == null ? void 0 : _sources_definitions[refId] : undefined;
78
+ }
79
+ if (isPlacedFormNode(node)) {
80
+ var _sources_formDesigns, _design_nodes;
81
+ const design = (_sources_formDesigns = sources.formDesigns) == null ? void 0 : _sources_formDesigns[props == null ? void 0 : props[FORM_ID_PROP]];
82
+ // The graft's resolution rule: a design whose root is not among its own
83
+ // nodes grafts nothing, so it offers nothing to override either.
84
+ return (design == null ? void 0 : design.rootId) && ((_design_nodes = design.nodes) == null ? void 0 : _design_nodes[design.rootId]) ? design : undefined;
85
+ }
86
+ return undefined;
87
+ }
39
88
  /**
40
89
  * Every per-instance style override an instance node carries, keyed the way
41
90
  * it is stored (AGL-1332): {@link STYLE_OVERRIDES_ROOT_KEY} for the
42
91
  * component root, and a DEFINITION-internal node id for each overridden
43
- * leaf. Empty for a node that is not an instance or carries none.
92
+ * leaf. Empty for a node that is not a placement ({@link
93
+ * isDefinitionPlacement}: an instance or a placed form) or carries none.
44
94
  *
45
95
  * Keyed on the definition's own id, never the grafted `cmp__…` id: the
46
96
  * graft id is DERIVED from the instance id, so keying on it would break the
@@ -56,7 +106,7 @@ const isStyleRecord = (value)=>typeof value === 'object' && value !== null && !A
56
106
  * {@link composeReusableComponentNodes}, so they cannot disagree about
57
107
  * what an override means.
58
108
  */ export function getInstanceStyleOverrides(node) {
59
- if ((node == null ? void 0 : node.componentId) !== REUSABLE_INSTANCE_COMPONENT_ID) return {};
109
+ if (!isDefinitionPlacement(node)) return {};
60
110
  const overrides = node == null ? void 0 : node.styleOverrides;
61
111
  if (!isStyleRecord(overrides)) return {};
62
112
  const clean = {};
@@ -117,6 +167,65 @@ const isStyleRecord = (value)=>typeof value === 'object' && value !== null && !A
117
167
  */ const ATTR_OVERRIDE_REFUSED_PROPS = new Set([
118
168
  'sx'
119
169
  ]);
170
+ /**
171
+ * Props a placed form's attribute overrides must never address, on any node
172
+ * of its design (AGL-3285).
173
+ *
174
+ * A form is edited once and every page placing it follows — that is the
175
+ * whole point of the form entity. Its submission list, its dataset columns,
176
+ * its consent tick and its routing are all keyed to what the ENTITY declares,
177
+ * so a page that renamed a field, retyped it, made it optional, rewrote its
178
+ * choices or re-pointed where the form sends would file answers the form's
179
+ * own schema does not describe, from one page only, with nothing on the
180
+ * Forms page saying so. Per page, a form may LOOK different; it may not
181
+ * collect something different.
182
+ *
183
+ * - The form node's identity and submission config: `formId`, `formName`,
184
+ * `datasetId`, `datasetName`, and the after-submit outcome
185
+ * (`afterSubmit`, `redirectScreenId`, `redirectUrl`, `revealNodeId`).
186
+ * - Each field's contract: `fieldName` (the submission key),
187
+ * `datasetFieldId`, `fieldType`, `options` (a ticked option's TEXT is the
188
+ * posted value, and the consent check reads it) and `required`.
189
+ * - Element attributes a browser submission reads directly: `name`, `type`
190
+ * (a submit button made a plain button no longer submits), `value`,
191
+ * `action`, `method`, and `disabled` (a disabled input is not posted).
192
+ * - The hide directives: a hidden field is pruned from the page, so its
193
+ * value is never sent.
194
+ *
195
+ * Label, placeholder, help text, the submit label and success message, and
196
+ * everything about styling stay overridable.
197
+ */ export const PLACED_FORM_REFUSED_ATTR_PROPS = new Set([
198
+ FORM_ID_PROP,
199
+ 'formName',
200
+ 'datasetId',
201
+ 'datasetName',
202
+ 'afterSubmit',
203
+ 'redirectScreenId',
204
+ 'redirectUrl',
205
+ 'revealNodeId',
206
+ 'fieldName',
207
+ 'datasetFieldId',
208
+ 'fieldType',
209
+ 'options',
210
+ 'required',
211
+ 'name',
212
+ 'type',
213
+ 'value',
214
+ 'action',
215
+ 'method',
216
+ 'disabled',
217
+ NODE_HIDE_IF_PROP,
218
+ NODE_HIDE_UNLESS_PROP
219
+ ]);
220
+ /**
221
+ * Whether `prop` may never be an attribute override on this placement — `sx`
222
+ * on every placement, plus {@link PLACED_FORM_REFUSED_ATTR_PROPS} on a placed
223
+ * form. The render layer and the editor's writer both ask this, so a refused
224
+ * prop can neither be offered nor take effect.
225
+ */ export function isAttrOverrideRefused(placement, prop) {
226
+ if (ATTR_OVERRIDE_REFUSED_PROPS.has(prop)) return true;
227
+ return isPlacedFormNode(placement) && PLACED_FORM_REFUSED_ATTR_PROPS.has(prop);
228
+ }
120
229
  /**
121
230
  * Every per-instance ATTRIBUTE override an instance node carries, keyed the
122
231
  * way {@link getInstanceStyleOverrides} keys styles — `root` for the
@@ -127,7 +236,7 @@ const isStyleRecord = (value)=>typeof value === 'object' && value !== null && !A
127
236
  * that reached storage before a refusal existed (or by any path that did not
128
237
  * go through the panel) still cannot take effect.
129
238
  */ export function getInstanceAttrOverrides(node) {
130
- if ((node == null ? void 0 : node.componentId) !== REUSABLE_INSTANCE_COMPONENT_ID) return {};
239
+ if (!isDefinitionPlacement(node)) return {};
131
240
  const overrides = node == null ? void 0 : node.attrOverrides;
132
241
  if (!isStyleRecord(overrides)) return {};
133
242
  const clean = {};
@@ -135,7 +244,7 @@ const isStyleRecord = (value)=>typeof value === 'object' && value !== null && !A
135
244
  if (!key || !isStyleRecord(value)) continue;
136
245
  const slice = {};
137
246
  for (const [prop, propValue] of Object.entries(value)){
138
- if (ATTR_OVERRIDE_REFUSED_PROPS.has(prop)) continue;
247
+ if (isAttrOverrideRefused(node, prop)) continue;
139
248
  slice[prop] = propValue;
140
249
  }
141
250
  if (Object.keys(slice).length > 0) clean[key] = slice;
@@ -850,6 +959,9 @@ function hasVisibilityDirective(props) {
850
959
  // Per-leaf is not a nicety: a component's text leaves usually set
851
960
  // their OWN colour, so a root-level `color` never reaches them and an
852
961
  // instance given a white background renders white-on-white (AGL-1318).
962
+ //
963
+ // A placed form carries the same two layers (AGL-3285), so one page can
964
+ // restyle a field's label without editing the form for every page.
853
965
  const styleOverrides = getInstanceStyleOverrides(instanceNode);
854
966
  // Attribute overrides (AGL-1899) ride the same graft for the same
855
967
  // reason: this is the one point where per-instance scope exists, so
@@ -909,6 +1021,7 @@ function hasVisibilityDirective(props) {
909
1021
  const rootNamesItself = Boolean(grafting) && refIdOf(definitionRoot) === grafting;
910
1022
  const graftedRoot = next[graftedRootId];
911
1023
  if (rootNamesItself && graftedRoot) {
1024
+ var _instanceNode_props;
912
1025
  const adopted = Array.isArray(graftedRoot.nodes) ? graftedRoot.nodes : [];
913
1026
  for (const childId of adopted){
914
1027
  if (next[childId]) {
@@ -918,11 +1031,24 @@ function hasVisibilityDirective(props) {
918
1031
  }
919
1032
  }
920
1033
  delete next[graftedRootId];
921
- next[instanceId] = _extends({}, instanceNode, {
1034
+ /*
1035
+ * The root's own override slices land on the instance, because the
1036
+ * instance IS the root here (AGL-3285). The grafted root carried them
1037
+ * and has just been dropped; without this a placed form's `root`
1038
+ * target would save and never render. Same precedence as the merge
1039
+ * branch below: the slice first, the placement's own `sx` over it.
1040
+ */ const rootSx = styleOverrides[STYLE_OVERRIDES_ROOT_KEY];
1041
+ const rootAttrs = attrOverrides[STYLE_OVERRIDES_ROOT_KEY];
1042
+ const unwrappedSx = rootSx ? mergeNodeSx(rootSx, instanceNode.sx) : undefined;
1043
+ next[instanceId] = _extends({}, instanceNode, unwrappedSx === undefined ? {} : {
1044
+ sx: unwrappedSx
1045
+ }, rootAttrs ? {
1046
+ props: _extends({}, (_instanceNode_props = instanceNode.props) != null ? _instanceNode_props : {}, rootAttrs)
1047
+ } : {}, {
922
1048
  nodes: adopted
923
1049
  });
924
1050
  } else if (graftedRoot) {
925
- var _graftedRoot_props, _instanceNode_props;
1051
+ var _graftedRoot_props, _instanceNode_props1;
926
1052
  /*
927
1053
  * THE INSTANCE AND THE DEFINITION'S ROOT ARE ONE ELEMENT (AGL-2521).
928
1054
  *
@@ -979,8 +1105,8 @@ function hasVisibilityDirective(props) {
979
1105
  ];
980
1106
  const carried = {};
981
1107
  for (const key of INSTANCE_CARRIED_PROPS){
982
- var _instanceNode_props1;
983
- const value = (_instanceNode_props1 = instanceNode.props) == null ? void 0 : _instanceNode_props1[key];
1108
+ var _instanceNode_props2;
1109
+ const value = (_instanceNode_props2 = instanceNode.props) == null ? void 0 : _instanceNode_props2[key];
984
1110
  if (value !== undefined) carried[key] = value;
985
1111
  }
986
1112
  /*
@@ -998,8 +1124,8 @@ function hasVisibilityDirective(props) {
998
1124
  */ const repeatsPlacement = repeatKey(instanceNode) !== '';
999
1125
  if (repeatsPlacement) {
1000
1126
  for (const key of REPEAT_DIRECTIVE_PROPS){
1001
- var _instanceNode_props2;
1002
- const value = (_instanceNode_props2 = instanceNode.props) == null ? void 0 : _instanceNode_props2[key];
1127
+ var _instanceNode_props3;
1128
+ const value = (_instanceNode_props3 = instanceNode.props) == null ? void 0 : _instanceNode_props3[key];
1003
1129
  if (value !== undefined) carried[key] = value;
1004
1130
  }
1005
1131
  carried[REPEAT_SELF_PROP] = true;
@@ -1014,7 +1140,7 @@ function hasVisibilityDirective(props) {
1014
1140
  * element answering to the placement's id, which after the merge is
1015
1141
  * this one. Keeping either list alone drops the other author's names,
1016
1142
  * so the two are joined, each name once, in `props` and on the node.
1017
- */ const propsClassName = joinClassNames((_graftedRoot_props = graftedRoot.props) == null ? void 0 : _graftedRoot_props['className'], (_instanceNode_props = instanceNode.props) == null ? void 0 : _instanceNode_props['className']);
1143
+ */ const propsClassName = joinClassNames((_graftedRoot_props = graftedRoot.props) == null ? void 0 : _graftedRoot_props['className'], (_instanceNode_props1 = instanceNode.props) == null ? void 0 : _instanceNode_props1['className']);
1018
1144
  if (propsClassName) carried['className'] = propsClassName;
1019
1145
  const nodeClassName = joinClassNames(graftedRoot.className, instanceNode.className);
1020
1146
  // The override slices and the reference are DOCUMENT state living on