@aglyn/aglyn 1.0.0-beta.172 → 1.0.0-beta.174
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 +10 -10
- package/src/lib/app-utils/campaign-membership.d.ts +10 -8
- package/src/lib/app-utils/campaign-membership.js +11 -9
- package/src/lib/app-utils/campaign-membership.js.map +1 -1
- package/src/lib/app-utils/compose-layout-props.d.ts +6 -2
- package/src/lib/app-utils/compose-layout-props.js +8 -3
- package/src/lib/app-utils/compose-layout-props.js.map +1 -1
- package/src/lib/app-utils/compose-reusable-components.d.ts +85 -1
- package/src/lib/app-utils/compose-reusable-components.js +137 -11
- package/src/lib/app-utils/compose-reusable-components.js.map +1 -1
- package/src/lib/app-utils/crm.d.ts +3 -3
- package/src/lib/app-utils/crm.js.map +1 -1
- package/src/lib/app-utils/docs-help.generated.d.ts +1 -1
- package/src/lib/app-utils/docs-help.generated.js +1 -0
- package/src/lib/app-utils/docs-help.generated.js.map +1 -1
- package/src/lib/app-utils/docs-index.generated.js +11 -4
- package/src/lib/app-utils/docs-index.generated.js.map +1 -1
- package/src/lib/app-utils/element-color-scheme.d.ts +43 -0
- package/src/lib/app-utils/element-color-scheme.js +41 -0
- package/src/lib/app-utils/element-color-scheme.js.map +1 -0
- package/src/lib/app-utils/layout-style-overrides.d.ts +106 -0
- package/src/lib/app-utils/layout-style-overrides.js +158 -0
- package/src/lib/app-utils/layout-style-overrides.js.map +1 -0
- package/src/lib/app-utils/server.d.ts +2 -0
- package/src/lib/app-utils/server.js +4 -0
- package/src/lib/app-utils/server.js.map +1 -1
- package/src/lib/canvas-manager/canvas-manager.d.ts +5 -0
- package/src/lib/canvas-manager/canvas-manager.js +7 -0
- package/src/lib/canvas-manager/canvas-manager.js.map +1 -1
- package/src/lib/foundation/definitions/platform.types.d.ts +9 -0
- package/src/lib/foundation/definitions/platform.types.js.map +1 -1
- package/src/lib/plugin-manager/first-party-plugins.generated.d.ts +6 -1
- package/src/lib/plugin-manager/first-party-plugins.generated.js +17 -11
- package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
- package/src/lib/plugin-manager/plugin-host-collections.d.ts +52 -1
- package/src/lib/plugin-manager/plugin-host-collections.js +23 -6
- package/src/lib/plugin-manager/plugin-host-collections.js.map +1 -1
- package/src/lib/types/nodes.d.ts +12 -0
- package/src/lib/types/nodes.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aglyn/aglyn",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.174",
|
|
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.
|
|
37
|
-
"@aglyn/shared-data-mdi": "1.0.0-beta.
|
|
38
|
-
"@aglyn/shared-data-types": "1.0.0-beta.
|
|
39
|
-
"@aglyn/shared-util-email": "1.0.0-beta.
|
|
40
|
-
"@aglyn/shared-util-http": "1.0.0-beta.
|
|
41
|
-
"@aglyn/shared-util-logger": "1.0.0-beta.
|
|
42
|
-
"@aglyn/shared-util-timestamp": "1.0.0-beta.
|
|
43
|
-
"@aglyn/shared-util-tools": "1.0.0-beta.
|
|
44
|
-
"@aglyn/shared-util-vendor": "1.0.0-beta.
|
|
36
|
+
"@aglyn/shared-data-enums": "1.0.0-beta.174",
|
|
37
|
+
"@aglyn/shared-data-mdi": "1.0.0-beta.174",
|
|
38
|
+
"@aglyn/shared-data-types": "1.0.0-beta.174",
|
|
39
|
+
"@aglyn/shared-util-email": "1.0.0-beta.174",
|
|
40
|
+
"@aglyn/shared-util-http": "1.0.0-beta.174",
|
|
41
|
+
"@aglyn/shared-util-logger": "1.0.0-beta.174",
|
|
42
|
+
"@aglyn/shared-util-timestamp": "1.0.0-beta.174",
|
|
43
|
+
"@aglyn/shared-util-tools": "1.0.0-beta.174",
|
|
44
|
+
"@aglyn/shared-util-vendor": "1.0.0-beta.174",
|
|
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",
|
|
@@ -46,8 +46,9 @@ export declare function normalizeCampaignIds(raw: unknown): string[];
|
|
|
46
46
|
* The campaigns a HOST resource — a form, a screen — belongs to.
|
|
47
47
|
*
|
|
48
48
|
* Host resources carry the field at the top of the document: they belong to
|
|
49
|
-
* one site,
|
|
50
|
-
* second holder to keep the edge
|
|
49
|
+
* one site, and whoever may read the resource may read which of the org's
|
|
50
|
+
* campaigns it is filed under, so there is no second holder to keep the edge
|
|
51
|
+
* away from.
|
|
51
52
|
*/
|
|
52
53
|
export declare function readCampaignIds(resource: Record<string, unknown> | null | undefined): string[];
|
|
53
54
|
/**
|
|
@@ -100,12 +101,13 @@ export declare function campaignMembershipUnchanged(stored: readonly string[], s
|
|
|
100
101
|
* detach fails the build rather than shipping a campaign whose removal leaves
|
|
101
102
|
* that collection pointing at nothing.
|
|
102
103
|
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
104
|
+
* `leads` is here for the site rows the lead migration (AGL-3276) has not
|
|
105
|
+
* reached. A live lead is an org row (`orgs/{orgId}/leads/{personKey}`) that
|
|
106
|
+
* carries the field at the top of its document like a form does, and the
|
|
107
|
+
* deletion walks that collection by name beside this list. A lead joins a
|
|
108
|
+
* campaign by hand — the New lead drawer, the CSV import, the list's bulk
|
|
109
|
+
* bar — or by being enrolled in a sequence that is in one, and it hands the
|
|
110
|
+
* membership to the contact it converts into.
|
|
109
111
|
*
|
|
110
112
|
* Contacts are deliberately NOT here. They live on the org
|
|
111
113
|
* (`orgs/{orgId}/contacts`), not the host, and carry the field inside a
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
*
|
|
20
20
|
* ## The edge is a field on the RESOURCE, not a list on the campaign
|
|
21
21
|
*
|
|
22
|
-
* A campaign container lives at `
|
|
22
|
+
* A campaign container lives at `orgs/{orgId}/emailCampaigns/{campaignId}`
|
|
23
23
|
* and names nothing outside itself. A send joins one by carrying
|
|
24
24
|
* `emailCampaignId`; every other member joins the same way, through
|
|
25
25
|
* {@link CAMPAIGN_MEMBERSHIP_FIELD} on its own document.
|
|
@@ -98,8 +98,9 @@
|
|
|
98
98
|
* The campaigns a HOST resource — a form, a screen — belongs to.
|
|
99
99
|
*
|
|
100
100
|
* Host resources carry the field at the top of the document: they belong to
|
|
101
|
-
* one site,
|
|
102
|
-
* second holder to keep the edge
|
|
101
|
+
* one site, and whoever may read the resource may read which of the org's
|
|
102
|
+
* campaigns it is filed under, so there is no second holder to keep the edge
|
|
103
|
+
* away from.
|
|
103
104
|
*/ export function readCampaignIds(resource) {
|
|
104
105
|
return normalizeCampaignIds((resource != null ? resource : {})[CAMPAIGN_MEMBERSHIP_FIELD]);
|
|
105
106
|
}
|
|
@@ -164,12 +165,13 @@
|
|
|
164
165
|
* detach fails the build rather than shipping a campaign whose removal leaves
|
|
165
166
|
* that collection pointing at nothing.
|
|
166
167
|
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
168
|
+
* `leads` is here for the site rows the lead migration (AGL-3276) has not
|
|
169
|
+
* reached. A live lead is an org row (`orgs/{orgId}/leads/{personKey}`) that
|
|
170
|
+
* carries the field at the top of its document like a form does, and the
|
|
171
|
+
* deletion walks that collection by name beside this list. A lead joins a
|
|
172
|
+
* campaign by hand — the New lead drawer, the CSV import, the list's bulk
|
|
173
|
+
* bar — or by being enrolled in a sequence that is in one, and it hands the
|
|
174
|
+
* membership to the contact it converts into.
|
|
173
175
|
*
|
|
174
176
|
* Contacts are deliberately NOT here. They live on the org
|
|
175
177
|
* (`orgs/{orgId}/contacts`), not the host, and carry the field inside a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/campaign-membership.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\n/**\n * WHAT ELSE IS IN A CAMPAIGN: the edge between a campaign and the forms,\n * screens and people a push is coordinated across.\n *\n * ## The edge is a field on the RESOURCE, not a list on the campaign\n *\n * A campaign container lives at `hosts/{hostId}/emailCampaigns/{campaignId}`\n * and names nothing outside itself. A send joins one by carrying\n * `emailCampaignId`; every other member joins the same way, through\n * {@link CAMPAIGN_MEMBERSHIP_FIELD} on its own document.\n *\n * Three properties decide it, and all three point the same way:\n *\n * - **Reading is free where it is asked.** A form's own page reads the form\n * document to draw anything at all, so the campaign comes with it. A\n * membership collection under the campaign would make \"which campaign is\n * this in\" a query issued on mount by every resource page, for a fact one\n * field already carries.\n * - **The reduction is already gated this way.** `campaign-manage.ts` clears\n * `emailCampaignId` from every send before it removes a container, so no\n * send is left naming one that is gone. Extending that pass to the\n * membership field is the SAME mechanism; a membership collection would be\n * a second, different deletion story beside it.\n * - **Deleting a MEMBER can leave nothing behind.** The campaign holds no\n * list, so a deleted form takes its own edge with it. This matters most on\n * a contact: an erasure removes the document, and a membership row stored\n * anywhere else would survive it holding the id of a person who asked to\n * be forgotten.\n *\n * ## Plural, because a landing page outlives one campaign\n *\n * The field is an ARRAY. A signup form built for the spring push is the same\n * form the summer push places, and a single-valued field would make the\n * second assignment silently erase the first — a campaign losing a member\n * with nothing on screen to say so. The automation step that has always\n * written this edge (`assignCampaign`) already used `arrayUnion`, so the\n * product has treated it as many-to-many from the start.\n *\n * ## Ids, never names\n *\n * A campaign's name is editable, and every reader here resolves the name from\n * the campaign document at read time. Renaming a campaign therefore moves no\n * membership: the stored value is the container's document id, which nothing\n * rewrites.\n */\n\nimport { contactFacetPath, readContactFacet } from './contacts'\n\n/**\n * The field naming the campaigns a resource belongs to.\n *\n * Not `emailCampaignId`, which is the SEND's field: on a send document the\n * word `campaignId` already means the send's own id — it is what `cid`\n * carries into every unsubscribe footer — so that collection needed a\n * different word. A form, a screen and a contact have no second meaning for\n * it, and the plural says the resource may be in more than one.\n */\nexport const CAMPAIGN_MEMBERSHIP_FIELD = 'campaignIds'\n\n/**\n * How many campaigns one resource may name.\n *\n * A ceiling on the FIELD, not on the product: an array Firestore has to index\n * on every write is not the place to discover that a script has been adding\n * an id a day for a year. Twenty is far past what a person assigns by hand\n * and far short of anything that costs a write.\n */\nexport const CAMPAIGN_MEMBERSHIP_CAP = 20\n\n/**\n * A stored membership array as a clean list of ids.\n *\n * Deduped, trimmed, non-strings dropped, capped. Every reader goes through\n * this because the field is written by three consoles and one automation\n * step, and a surface that trusted the raw value would render `undefined` as\n * a chip the first time one of them wrote a blank.\n */\nexport function normalizeCampaignIds(raw: unknown): string[] {\n if (!Array.isArray(raw)) return []\n const seen: string[] = []\n for (const entry of raw) {\n if (typeof entry !== 'string') continue\n const id = entry.trim()\n if (!id || seen.includes(id)) continue\n seen.push(id)\n if (seen.length >= CAMPAIGN_MEMBERSHIP_CAP) break\n }\n return seen\n}\n\n/**\n * The campaigns a HOST resource — a form, a screen — belongs to.\n *\n * Host resources carry the field at the top of the document: they belong to\n * one site, which is the same site the campaign belongs to, so there is no\n * second holder to keep the edge away from.\n */\nexport function readCampaignIds(\n resource: Record<string, unknown> | null | undefined,\n): string[] {\n return normalizeCampaignIds((resource ?? {})[CAMPAIGN_MEMBERSHIP_FIELD])\n}\n\n/**\n * The dotted path to a contact's membership, inside ONE holder's facet.\n *\n * A contact document is shared by every site in the org — one human who\n * touched two sites is one row — and almost nothing on it is legitimately\n * shared. Which campaigns a merchant has filed a person under is that\n * merchant's business record on exactly the footing their notes and tags are,\n * so it lives at `facets.{groupId}.campaignIds` and not at the top of the\n * document, where every other site in an agency's account could read it.\n */\nexport function contactCampaignFieldPath(groupId: string): string {\n return contactFacetPath(groupId, CAMPAIGN_MEMBERSHIP_FIELD)\n}\n\n/** The campaigns ONE holder has filed this contact under. */\nexport function readContactCampaignIds(\n contact: Record<string, unknown> | null | undefined,\n groupId: string,\n): string[] {\n return normalizeCampaignIds(readContactFacet(contact, groupId).campaignIds)\n}\n\n/**\n * The membership a save should write, given what is on screen.\n *\n * Pure, and shared by the three consoles that offer the picker, because the\n * decision each of them makes is identical and is not obvious: an EMPTY\n * selection has to be stored as an empty array rather than as a removed\n * field.\n *\n * Removing the field would read the same to every surface here — the readers\n * above answer `[]` either way — and would be wrong for the one reader that\n * is not a surface. `campaign-manage.ts` finds a campaign's members with\n * `array-contains`, which matches on the automatic single-field index; that\n * index has an entry only while the array does, so the two shapes are the\n * same to a reader and different to a writer only in cost. Storing `[]` keeps\n * one shape for \"belongs to no campaign\" across every writer, which is the\n * property the send collection's own detach comment argues for one field\n * over.\n *\n * @returns the value to store under {@link CAMPAIGN_MEMBERSHIP_FIELD}.\n */\nexport function campaignMembershipValue(selected: readonly string[]): string[] {\n return normalizeCampaignIds(selected)\n}\n\n/**\n * Whether a document's stored membership already says what a save would.\n *\n * Order-insensitive: a picker hands back its options' order and the stored\n * array is in the order it was written, so comparing them literally would\n * report a change on every open and leave Save permanently enabled.\n */\nexport function campaignMembershipUnchanged(\n stored: readonly string[],\n selected: readonly string[],\n): boolean {\n if (stored.length !== selected.length) return false\n const sortedStored = [...stored].sort()\n const sortedSelected = [...selected].sort()\n return sortedStored.every((id, index) => id === sortedSelected[index])\n}\n\n/**\n * The host subcollections whose documents may name a campaign.\n *\n * The list a campaign's deletion walks, and the list this feature's coverage\n * spec reads — so a collection that grows the picker without growing the\n * detach fails the build rather than shipping a campaign whose removal leaves\n * that collection pointing at nothing.\n *\n * A lead (`hosts/{hostId}/leads/{personKey}`, AGL-3254) carries the field at\n * the top of its document like a form does: a lead is one site's own record,\n * captured there and worked there, so there is no second holder to keep the\n * edge away from. It joins a campaign by hand — the New lead drawer, the CSV\n * import, the list's bulk bar — or by being enrolled in a sequence that is\n * in one, and it hands the membership to the contact it converts into.\n *\n * Contacts are deliberately NOT here. They live on the org\n * (`orgs/{orgId}/contacts`), not the host, and carry the field inside a\n * per-holder facet — so they are detached by their own pass, against a field\n * path that names the group. Nor are a plugin's own members — a sequence and\n * its enrollments live under the org, in collections the core does not name\n * — so a plugin registers a detacher for those\n * (`plugin-manager/plugin-membership-detach.ts`) and the deletion runs it.\n */\nexport const CAMPAIGN_MEMBER_HOST_COLLECTIONS = ['forms', 'screens', 'leads'] as const\n\nexport type CampaignMemberHostCollection =\n (typeof CAMPAIGN_MEMBER_HOST_COLLECTIONS)[number]\n"],"names":["contactFacetPath","readContactFacet","CAMPAIGN_MEMBERSHIP_FIELD","CAMPAIGN_MEMBERSHIP_CAP","normalizeCampaignIds","raw","Array","isArray","seen","entry","id","trim","includes","push","length","readCampaignIds","resource","contactCampaignFieldPath","groupId","readContactCampaignIds","contact","campaignIds","campaignMembershipValue","selected","campaignMembershipUnchanged","stored","sortedStored","sort","sortedSelected","every","index","CAMPAIGN_MEMBER_HOST_COLLECTIONS"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4CC,GAED,SAASA,gBAAgB,EAAEC,gBAAgB,QAAQ,gBAAY;AAE/D;;;;;;;;CAQC,GACD,OAAO,MAAMC,4BAA4B,cAAa;AAEtD;;;;;;;CAOC,GACD,OAAO,MAAMC,0BAA0B,GAAE;AAEzC;;;;;;;CAOC,GACD,OAAO,SAASC,qBAAqBC,GAAY;IAC/C,IAAI,CAACC,MAAMC,OAAO,CAACF,MAAM,OAAO,EAAE;IAClC,MAAMG,OAAiB,EAAE;IACzB,KAAK,MAAMC,SAASJ,IAAK;QACvB,IAAI,OAAOI,UAAU,UAAU;QAC/B,MAAMC,KAAKD,MAAME,IAAI;QACrB,IAAI,CAACD,MAAMF,KAAKI,QAAQ,CAACF,KAAK;QAC9BF,KAAKK,IAAI,CAACH;QACV,IAAIF,KAAKM,MAAM,IAAIX,yBAAyB;IAC9C;IACA,OAAOK;AACT;AAEA;;;;;;CAMC,GACD,OAAO,SAASO,gBACdC,QAAoD;IAEpD,OAAOZ,qBAAqB,CAACY,mBAAAA,WAAY,CAAC,EAAE,CAACd,0BAA0B;AACzE;AAEA;;;;;;;;;CASC,GACD,OAAO,SAASe,yBAAyBC,OAAe;IACtD,OAAOlB,iBAAiBkB,SAAShB;AACnC;AAEA,2DAA2D,GAC3D,OAAO,SAASiB,uBACdC,OAAmD,EACnDF,OAAe;IAEf,OAAOd,qBAAqBH,iBAAiBmB,SAASF,SAASG,WAAW;AAC5E;AAEA;;;;;;;;;;;;;;;;;;;CAmBC,GACD,OAAO,SAASC,wBAAwBC,QAA2B;IACjE,OAAOnB,qBAAqBmB;AAC9B;AAEA;;;;;;CAMC,GACD,OAAO,SAASC,4BACdC,MAAyB,EACzBF,QAA2B;IAE3B,IAAIE,OAAOX,MAAM,KAAKS,SAAST,MAAM,EAAE,OAAO;IAC9C,MAAMY,eAAe;WAAID;KAAO,CAACE,IAAI;IACrC,MAAMC,iBAAiB;WAAIL;KAAS,CAACI,IAAI;IACzC,OAAOD,aAAaG,KAAK,CAAC,CAACnB,IAAIoB,QAAUpB,OAAOkB,cAAc,CAACE,MAAM;AACvE;AAEA;;;;;;;;;;;;;;;;;;;;;;CAsBC,GACD,OAAO,MAAMC,mCAAmC;IAAC;IAAS;IAAW;CAAQ,CAAS"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/campaign-membership.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\n/**\n * WHAT ELSE IS IN A CAMPAIGN: the edge between a campaign and the forms,\n * screens and people a push is coordinated across.\n *\n * ## The edge is a field on the RESOURCE, not a list on the campaign\n *\n * A campaign container lives at `orgs/{orgId}/emailCampaigns/{campaignId}`\n * and names nothing outside itself. A send joins one by carrying\n * `emailCampaignId`; every other member joins the same way, through\n * {@link CAMPAIGN_MEMBERSHIP_FIELD} on its own document.\n *\n * Three properties decide it, and all three point the same way:\n *\n * - **Reading is free where it is asked.** A form's own page reads the form\n * document to draw anything at all, so the campaign comes with it. A\n * membership collection under the campaign would make \"which campaign is\n * this in\" a query issued on mount by every resource page, for a fact one\n * field already carries.\n * - **The reduction is already gated this way.** `campaign-manage.ts` clears\n * `emailCampaignId` from every send before it removes a container, so no\n * send is left naming one that is gone. Extending that pass to the\n * membership field is the SAME mechanism; a membership collection would be\n * a second, different deletion story beside it.\n * - **Deleting a MEMBER can leave nothing behind.** The campaign holds no\n * list, so a deleted form takes its own edge with it. This matters most on\n * a contact: an erasure removes the document, and a membership row stored\n * anywhere else would survive it holding the id of a person who asked to\n * be forgotten.\n *\n * ## Plural, because a landing page outlives one campaign\n *\n * The field is an ARRAY. A signup form built for the spring push is the same\n * form the summer push places, and a single-valued field would make the\n * second assignment silently erase the first — a campaign losing a member\n * with nothing on screen to say so. The automation step that has always\n * written this edge (`assignCampaign`) already used `arrayUnion`, so the\n * product has treated it as many-to-many from the start.\n *\n * ## Ids, never names\n *\n * A campaign's name is editable, and every reader here resolves the name from\n * the campaign document at read time. Renaming a campaign therefore moves no\n * membership: the stored value is the container's document id, which nothing\n * rewrites.\n */\n\nimport { contactFacetPath, readContactFacet } from './contacts'\n\n/**\n * The field naming the campaigns a resource belongs to.\n *\n * Not `emailCampaignId`, which is the SEND's field: on a send document the\n * word `campaignId` already means the send's own id — it is what `cid`\n * carries into every unsubscribe footer — so that collection needed a\n * different word. A form, a screen and a contact have no second meaning for\n * it, and the plural says the resource may be in more than one.\n */\nexport const CAMPAIGN_MEMBERSHIP_FIELD = 'campaignIds'\n\n/**\n * How many campaigns one resource may name.\n *\n * A ceiling on the FIELD, not on the product: an array Firestore has to index\n * on every write is not the place to discover that a script has been adding\n * an id a day for a year. Twenty is far past what a person assigns by hand\n * and far short of anything that costs a write.\n */\nexport const CAMPAIGN_MEMBERSHIP_CAP = 20\n\n/**\n * A stored membership array as a clean list of ids.\n *\n * Deduped, trimmed, non-strings dropped, capped. Every reader goes through\n * this because the field is written by three consoles and one automation\n * step, and a surface that trusted the raw value would render `undefined` as\n * a chip the first time one of them wrote a blank.\n */\nexport function normalizeCampaignIds(raw: unknown): string[] {\n if (!Array.isArray(raw)) return []\n const seen: string[] = []\n for (const entry of raw) {\n if (typeof entry !== 'string') continue\n const id = entry.trim()\n if (!id || seen.includes(id)) continue\n seen.push(id)\n if (seen.length >= CAMPAIGN_MEMBERSHIP_CAP) break\n }\n return seen\n}\n\n/**\n * The campaigns a HOST resource — a form, a screen — belongs to.\n *\n * Host resources carry the field at the top of the document: they belong to\n * one site, and whoever may read the resource may read which of the org's\n * campaigns it is filed under, so there is no second holder to keep the edge\n * away from.\n */\nexport function readCampaignIds(\n resource: Record<string, unknown> | null | undefined,\n): string[] {\n return normalizeCampaignIds((resource ?? {})[CAMPAIGN_MEMBERSHIP_FIELD])\n}\n\n/**\n * The dotted path to a contact's membership, inside ONE holder's facet.\n *\n * A contact document is shared by every site in the org — one human who\n * touched two sites is one row — and almost nothing on it is legitimately\n * shared. Which campaigns a merchant has filed a person under is that\n * merchant's business record on exactly the footing their notes and tags are,\n * so it lives at `facets.{groupId}.campaignIds` and not at the top of the\n * document, where every other site in an agency's account could read it.\n */\nexport function contactCampaignFieldPath(groupId: string): string {\n return contactFacetPath(groupId, CAMPAIGN_MEMBERSHIP_FIELD)\n}\n\n/** The campaigns ONE holder has filed this contact under. */\nexport function readContactCampaignIds(\n contact: Record<string, unknown> | null | undefined,\n groupId: string,\n): string[] {\n return normalizeCampaignIds(readContactFacet(contact, groupId).campaignIds)\n}\n\n/**\n * The membership a save should write, given what is on screen.\n *\n * Pure, and shared by the three consoles that offer the picker, because the\n * decision each of them makes is identical and is not obvious: an EMPTY\n * selection has to be stored as an empty array rather than as a removed\n * field.\n *\n * Removing the field would read the same to every surface here — the readers\n * above answer `[]` either way — and would be wrong for the one reader that\n * is not a surface. `campaign-manage.ts` finds a campaign's members with\n * `array-contains`, which matches on the automatic single-field index; that\n * index has an entry only while the array does, so the two shapes are the\n * same to a reader and different to a writer only in cost. Storing `[]` keeps\n * one shape for \"belongs to no campaign\" across every writer, which is the\n * property the send collection's own detach comment argues for one field\n * over.\n *\n * @returns the value to store under {@link CAMPAIGN_MEMBERSHIP_FIELD}.\n */\nexport function campaignMembershipValue(selected: readonly string[]): string[] {\n return normalizeCampaignIds(selected)\n}\n\n/**\n * Whether a document's stored membership already says what a save would.\n *\n * Order-insensitive: a picker hands back its options' order and the stored\n * array is in the order it was written, so comparing them literally would\n * report a change on every open and leave Save permanently enabled.\n */\nexport function campaignMembershipUnchanged(\n stored: readonly string[],\n selected: readonly string[],\n): boolean {\n if (stored.length !== selected.length) return false\n const sortedStored = [...stored].sort()\n const sortedSelected = [...selected].sort()\n return sortedStored.every((id, index) => id === sortedSelected[index])\n}\n\n/**\n * The host subcollections whose documents may name a campaign.\n *\n * The list a campaign's deletion walks, and the list this feature's coverage\n * spec reads — so a collection that grows the picker without growing the\n * detach fails the build rather than shipping a campaign whose removal leaves\n * that collection pointing at nothing.\n *\n * `leads` is here for the site rows the lead migration (AGL-3276) has not\n * reached. A live lead is an org row (`orgs/{orgId}/leads/{personKey}`) that\n * carries the field at the top of its document like a form does, and the\n * deletion walks that collection by name beside this list. A lead joins a\n * campaign by hand — the New lead drawer, the CSV import, the list's bulk\n * bar — or by being enrolled in a sequence that is in one, and it hands the\n * membership to the contact it converts into.\n *\n * Contacts are deliberately NOT here. They live on the org\n * (`orgs/{orgId}/contacts`), not the host, and carry the field inside a\n * per-holder facet — so they are detached by their own pass, against a field\n * path that names the group. Nor are a plugin's own members — a sequence and\n * its enrollments live under the org, in collections the core does not name\n * — so a plugin registers a detacher for those\n * (`plugin-manager/plugin-membership-detach.ts`) and the deletion runs it.\n */\nexport const CAMPAIGN_MEMBER_HOST_COLLECTIONS = ['forms', 'screens', 'leads'] as const\n\nexport type CampaignMemberHostCollection =\n (typeof CAMPAIGN_MEMBER_HOST_COLLECTIONS)[number]\n"],"names":["contactFacetPath","readContactFacet","CAMPAIGN_MEMBERSHIP_FIELD","CAMPAIGN_MEMBERSHIP_CAP","normalizeCampaignIds","raw","Array","isArray","seen","entry","id","trim","includes","push","length","readCampaignIds","resource","contactCampaignFieldPath","groupId","readContactCampaignIds","contact","campaignIds","campaignMembershipValue","selected","campaignMembershipUnchanged","stored","sortedStored","sort","sortedSelected","every","index","CAMPAIGN_MEMBER_HOST_COLLECTIONS"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4CC,GAED,SAASA,gBAAgB,EAAEC,gBAAgB,QAAQ,gBAAY;AAE/D;;;;;;;;CAQC,GACD,OAAO,MAAMC,4BAA4B,cAAa;AAEtD;;;;;;;CAOC,GACD,OAAO,MAAMC,0BAA0B,GAAE;AAEzC;;;;;;;CAOC,GACD,OAAO,SAASC,qBAAqBC,GAAY;IAC/C,IAAI,CAACC,MAAMC,OAAO,CAACF,MAAM,OAAO,EAAE;IAClC,MAAMG,OAAiB,EAAE;IACzB,KAAK,MAAMC,SAASJ,IAAK;QACvB,IAAI,OAAOI,UAAU,UAAU;QAC/B,MAAMC,KAAKD,MAAME,IAAI;QACrB,IAAI,CAACD,MAAMF,KAAKI,QAAQ,CAACF,KAAK;QAC9BF,KAAKK,IAAI,CAACH;QACV,IAAIF,KAAKM,MAAM,IAAIX,yBAAyB;IAC9C;IACA,OAAOK;AACT;AAEA;;;;;;;CAOC,GACD,OAAO,SAASO,gBACdC,QAAoD;IAEpD,OAAOZ,qBAAqB,CAACY,mBAAAA,WAAY,CAAC,EAAE,CAACd,0BAA0B;AACzE;AAEA;;;;;;;;;CASC,GACD,OAAO,SAASe,yBAAyBC,OAAe;IACtD,OAAOlB,iBAAiBkB,SAAShB;AACnC;AAEA,2DAA2D,GAC3D,OAAO,SAASiB,uBACdC,OAAmD,EACnDF,OAAe;IAEf,OAAOd,qBAAqBH,iBAAiBmB,SAASF,SAASG,WAAW;AAC5E;AAEA;;;;;;;;;;;;;;;;;;;CAmBC,GACD,OAAO,SAASC,wBAAwBC,QAA2B;IACjE,OAAOnB,qBAAqBmB;AAC9B;AAEA;;;;;;CAMC,GACD,OAAO,SAASC,4BACdC,MAAyB,EACzBF,QAA2B;IAE3B,IAAIE,OAAOX,MAAM,KAAKS,SAAST,MAAM,EAAE,OAAO;IAC9C,MAAMY,eAAe;WAAID;KAAO,CAACE,IAAI;IACrC,MAAMC,iBAAiB;WAAIL;KAAS,CAACI,IAAI;IACzC,OAAOD,aAAaG,KAAK,CAAC,CAACnB,IAAIoB,QAAUpB,OAAOkB,cAAc,CAACE,MAAM;AACvE;AAEA;;;;;;;;;;;;;;;;;;;;;;;CAuBC,GACD,OAAO,MAAMC,mCAAmC;IAAC;IAAS;IAAW;CAAQ,CAAS"}
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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,
|
|
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
|
|
983
|
-
const value = (
|
|
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
|
|
1002
|
-
const value = (
|
|
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'], (
|
|
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
|