+ This page only shows how to link to the routed modal that is placed on a tab on the "Modal Dashboard".
+ Clicking this link will not load the slots inside the modal, however, going to the "Modal Dashboard", clicking
+ on tab 2 and opening the modal from there will work.
+
+ `;
+ }
+
+ static override styles = [UmbTextStyles, css``];
+}
+
+export default UmbDashboardTab2Element;
+
+declare global {
+ interface UmbDashboardTab2Element {
+ 'umb-dashboard-tab2': UmbDashboardTab2Element;
+ }
+}
diff --git a/examples/ufm-custom-component/index.ts b/examples/ufm-custom-component/index.ts
index 67d3281921..69cda7ce89 100644
--- a/examples/ufm-custom-component/index.ts
+++ b/examples/ufm-custom-component/index.ts
@@ -7,8 +7,8 @@ export const manifests: Array = [
name: 'Custom UFM Component',
api: () => import('./custom-ufm-component.js'),
meta: {
- marker: '%',
alias: 'myCustomComponent',
+ marker: '%'
},
},
];
diff --git a/package-lock.json b/package-lock.json
index 4ca5630445..ffc2c48c1c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,21 +12,20 @@
"./src/packages/*"
],
"dependencies": {
- "@tiptap/core": "^2.8.0",
- "@tiptap/extension-image": "^2.8.0",
- "@tiptap/extension-link": "^2.8.0",
- "@tiptap/extension-placeholder": "^2.8.0",
- "@tiptap/extension-subscript": "^2.8.0",
- "@tiptap/extension-superscript": "^2.8.0",
- "@tiptap/extension-table": "^2.8.0",
- "@tiptap/extension-table-cell": "^2.8.0",
- "@tiptap/extension-table-header": "^2.8.0",
- "@tiptap/extension-table-row": "^2.8.0",
- "@tiptap/extension-text-align": "^2.8.0",
- "@tiptap/extension-text-style": "^2.8.0",
- "@tiptap/extension-underline": "^2.8.0",
- "@tiptap/pm": "^2.8.0",
- "@tiptap/starter-kit": "^2.8.0",
+ "@tiptap/core": "^2.9.1",
+ "@tiptap/extension-image": "^2.9.1",
+ "@tiptap/extension-link": "^2.9.1",
+ "@tiptap/extension-placeholder": "^2.9.1",
+ "@tiptap/extension-subscript": "^2.9.1",
+ "@tiptap/extension-superscript": "^2.9.1",
+ "@tiptap/extension-table": "^2.9.1",
+ "@tiptap/extension-table-cell": "^2.9.1",
+ "@tiptap/extension-table-header": "^2.9.1",
+ "@tiptap/extension-table-row": "^2.9.1",
+ "@tiptap/extension-text-align": "^2.9.1",
+ "@tiptap/extension-underline": "^2.9.1",
+ "@tiptap/pm": "^2.9.1",
+ "@tiptap/starter-kit": "^2.9.1",
"@types/diff": "^5.2.1",
"@types/dompurify": "^3.0.5",
"@types/uuid": "^10.0.0",
@@ -49,7 +48,7 @@
"@eslint/js": "^9.9.1",
"@hey-api/openapi-ts": "^0.52.11",
"@open-wc/testing": "^4.0.0",
- "@playwright/test": "^1.45.3",
+ "@playwright/test": "^1.48.2",
"@rollup/plugin-commonjs": "^26.0.1",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.2.3",
@@ -1332,12 +1331,12 @@
}
},
"node_modules/@playwright/test": {
- "version": "1.46.1",
- "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.46.1.tgz",
- "integrity": "sha512-Fq6SwLujA/DOIvNC2EL/SojJnkKf/rAwJ//APpJJHRyMi1PdKrY3Az+4XNQ51N4RTbItbIByQ0jgd1tayq1aeA==",
+ "version": "1.48.2",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.48.2.tgz",
+ "integrity": "sha512-54w1xCWfXuax7dz4W2M9uw0gDyh+ti/0K/MxcCUxChFh37kkdxPdfZDw5QBbuPUJHr1CiHJ1hXgSs+GgeQc5Zw==",
"dev": true,
"dependencies": {
- "playwright": "1.46.1"
+ "playwright": "1.48.2"
},
"bin": {
"playwright": "cli.js"
@@ -2318,9 +2317,9 @@
}
},
"node_modules/@tiptap/core": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.8.0.tgz",
- "integrity": "sha512-xsqDI4BNzYRWRtBq7+/38ThhqEr7uG9Njip1x+9/wgR3vWPBFnBkYJTz6jSxS35NRE6BSnERm4/B/vrLuY1Hdw==",
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.9.1.tgz",
+ "integrity": "sha512-tifnLL/ARzQ6/FGEJjVwj9UT3v+pENdWHdk9x6F3X0mB1y0SeCjV21wpFLYESzwNdBPAj8NMp8Behv7dBnhIfw==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@@ -2330,9 +2329,9 @@
}
},
"node_modules/@tiptap/extension-blockquote": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-2.8.0.tgz",
- "integrity": "sha512-m3CKrOIvV7fY1Ak2gYf5LkKiz6AHxHpg6wxfVaJvdBqXgLyVtHo552N+A4oSHOSRbB4AG9EBQ2NeBM8cdEQ4MA==",
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-2.9.1.tgz",
+ "integrity": "sha512-Y0jZxc/pdkvcsftmEZFyG+73um8xrx6/DMfgUcNg3JAM63CISedNcr+OEI11L0oFk1KFT7/aQ9996GM6Kubdqg==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@@ -2342,9 +2341,9 @@
}
},
"node_modules/@tiptap/extension-bold": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.8.0.tgz",
- "integrity": "sha512-U1YkZBxDkSLNvPNiqxB5g42IeJHr27C7zDb/yGQN2xL4UBeg4O9xVhCFfe32f6tLwivSL0dar4ScElpaCJuqow==",
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.9.1.tgz",
+ "integrity": "sha512-e2P1zGpnnt4+TyxTC5pX/lPxPasZcuHCYXY0iwQ3bf8qRQQEjDfj3X7EI+cXqILtnhOiviEOcYmeu5op2WhQDg==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@@ -2354,23 +2353,21 @@
}
},
"node_modules/@tiptap/extension-bullet-list": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-2.8.0.tgz",
- "integrity": "sha512-H4O2X0ozbc/ce9/XF1H98sqWVUdtt7jzy7hMBunwmY8ZxI4dHtcRkeg81CZbpKTqOqRrMCLWjE3M2tgiDXrDkA==",
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-2.9.1.tgz",
+ "integrity": "sha512-0hizL/0j9PragJObjAWUVSuGhN1jKjCFnhLQVRxtx4HutcvS/lhoWMvFg6ZF8xqWgIa06n6A7MaknQkqhTdhKA==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
- "@tiptap/core": "^2.7.0",
- "@tiptap/extension-list-item": "^2.7.0",
- "@tiptap/extension-text-style": "^2.7.0"
+ "@tiptap/core": "^2.7.0"
}
},
"node_modules/@tiptap/extension-code": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-2.8.0.tgz",
- "integrity": "sha512-VSFn3sFF6qPpOGkXFhik8oYRH5iByVJpFEFd/duIEftmS0MdPzkbSItOpN3mc9xsJ5dCX80LYaResSj5hr5zkA==",
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-2.9.1.tgz",
+ "integrity": "sha512-WQqcVGe7i/E+yO3wz5XQteU1ETNZ00euUEl4ylVVmH2NM4Dh0KDjEhbhHlCM0iCfLUo7jhjC7dmS+hMdPUb+Tg==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@@ -2380,9 +2377,9 @@
}
},
"node_modules/@tiptap/extension-code-block": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-2.8.0.tgz",
- "integrity": "sha512-POuA5Igx+Dto0DTazoBFAQTj/M/FCdkqRVD9Uhsxhv49swPyANTJRr05vgbgtHB+NDDsZfCawVh7pI0IAD/O0w==",
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-2.9.1.tgz",
+ "integrity": "sha512-A/50wPWDqEUUUPhrwRKILP5gXMO5UlQ0F6uBRGYB9CEVOREam9yIgvONOnZVJtszHqOayjIVMXbH/JMBeq11/g==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@@ -2393,9 +2390,9 @@
}
},
"node_modules/@tiptap/extension-document": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.8.0.tgz",
- "integrity": "sha512-mp7Isx1sVc/ifeW4uW/PexGQ9exN3NRUOebSpnLfqXeWYk4y1RS1PA/3+IHkOPVetbnapgPjFx/DswlCP3XLjA==",
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.9.1.tgz",
+ "integrity": "sha512-1a+HCoDPnBttjqExfYLwfABq8MYdiowhy/wp8eCxVb6KGFEENO53KapstISvPzqH7eOi+qRjBB1KtVYb/ZXicg==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@@ -2405,9 +2402,9 @@
}
},
"node_modules/@tiptap/extension-dropcursor": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.8.0.tgz",
- "integrity": "sha512-rAFvx44YuT6dtS1c+ALw0ROAGI16l5L1HxquL4hR1gtxDcTieST5xhw5bkshXlmrlfotZXPrhokzqA7qjhZtJw==",
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.9.1.tgz",
+ "integrity": "sha512-wJZspSmJRkDBtPkzFz1g7gvZOEOayk8s93UHsgbJxcV4VWHYleZ5XhT74sZunSjefNDm3qC6v2BSgLp3vNHVKQ==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@@ -2418,9 +2415,9 @@
}
},
"node_modules/@tiptap/extension-gapcursor": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.8.0.tgz",
- "integrity": "sha512-Be1LWCmvteQInOnNVN+HTqc1XWsj1bCl+Q7et8qqNjtGtTaCbdCp8ppcH1SKJxNTM/RLUtPyJ8FDgOTj51ixCA==",
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.9.1.tgz",
+ "integrity": "sha512-jsRBmX01vr+5H02GljiHMo0n5H1vzoMLmFarxe0Yq2d2l9G/WV2VWX2XnGliqZAYWd1bI0phs7uLQIN3mxGQTw==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@@ -2431,9 +2428,9 @@
}
},
"node_modules/@tiptap/extension-hard-break": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.8.0.tgz",
- "integrity": "sha512-vqiIfviNiCmy/pJTHuDSCAGL2O4QDEdDmAvGJu8oRmElUrnlg8DbJUfKvn6DWQHNSQwRb+LDrwWlzAYj1K9u6A==",
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.9.1.tgz",
+ "integrity": "sha512-fCuaOD/b7nDjm47PZ58oanq7y4ccS2wjPh42Qm0B0yipu/1fmC8eS1SmaXmk28F89BLtuL6uOCtR1spe+lZtlQ==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@@ -2443,9 +2440,9 @@
}
},
"node_modules/@tiptap/extension-heading": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-2.8.0.tgz",
- "integrity": "sha512-4inWgrTPiqlivPmEHFOM5ck2UsmOsbKKPtqga6bALvWPmCv24S6/EBwFp8Jz4YABabXDnkviihmGu0LpP9D69w==",
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-2.9.1.tgz",
+ "integrity": "sha512-SjZowzLixOFaCrV2cMaWi1mp8REK0zK1b3OcVx7bCZfVSmsOETJyrAIUpCKA8o60NwF7pwhBg0MN8oXlNKMeFw==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@@ -2455,9 +2452,9 @@
}
},
"node_modules/@tiptap/extension-history": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.8.0.tgz",
- "integrity": "sha512-u5YS0J5Egsxt8TUWMMAC3QhPZaak+IzQeyHch4gtqxftx96tprItY7AD/A3pGDF2uCSnN+SZrk6yVexm6EncDw==",
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.9.1.tgz",
+ "integrity": "sha512-wp9qR1NM+LpvyLZFmdNaAkDq0d4jDJ7z7Fz7icFQPu31NVxfQYO3IXNmvJDCNu8hFAbImpA5aG8MBuwzRo0H9w==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@@ -2468,9 +2465,9 @@
}
},
"node_modules/@tiptap/extension-horizontal-rule": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.8.0.tgz",
- "integrity": "sha512-Sn/MI8WVFBoIYSIHA9NJryJIyCEzZdRysau8pC5TFnfifre0QV1ksPz2bgF+DyCD69ozQiRdBBHDEwKe47ZbfQ==",
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.9.1.tgz",
+ "integrity": "sha512-ydUhABeaBI1CoJp+/BBqPhXINfesp1qMNL/jiDcMsB66fsD4nOyphpAJT7FaRFZFtQVF06+nttBtFZVkITQVqg==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@@ -2481,9 +2478,9 @@
}
},
"node_modules/@tiptap/extension-image": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/@tiptap/extension-image/-/extension-image-2.8.0.tgz",
- "integrity": "sha512-5CReomgHGTUgxaX8P3i6qiC9VRWcWQgVoYtds4ZM52LVx/oGwMxQ4ECyzdVYKaRW+6PrNnAe6ew3Qpd5Wk0cIg==",
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-image/-/extension-image-2.9.1.tgz",
+ "integrity": "sha512-aGqJnsuS8oagIhsx7wetm8jw4NEDsOV0OSx4FQ4VPlUqWlnzK0N+erFKKJmXTdAxL8PGzoPSlITFH63MV3eV3Q==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@@ -2493,9 +2490,9 @@
}
},
"node_modules/@tiptap/extension-italic": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.8.0.tgz",
- "integrity": "sha512-PwwSE2LTYiHI47NJnsfhBmPiLE8IXZYqaSoNPU6flPrk1KxEzqvRI1joKZBmD9wuqzmHJ93VFIeZcC+kfwi8ZA==",
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.9.1.tgz",
+ "integrity": "sha512-VkNA6Vz96+/+7uBlsgM7bDXXx4b62T1fDam/3UKifA72aD/fZckeWrbT7KrtdUbzuIniJSbA0lpTs5FY29+86Q==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@@ -2505,9 +2502,9 @@
}
},
"node_modules/@tiptap/extension-link": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-2.8.0.tgz",
- "integrity": "sha512-p67hCG/pYCiOK/oCTPZnlkw9Ei7KJ7kCKFaluTcAmr5j8IBdYfDqSMDNCT4vGXBvKFh4X6xD7S7QvOqcH0Gn9A==",
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-2.9.1.tgz",
+ "integrity": "sha512-yG+e3e8cCCN9dZjX4ttEe3e2xhh58ryi3REJV4MdiEkOT9QF75Bl5pUbMIS4tQ8HkOr04QBFMHKM12kbSxg1BA==",
"dependencies": {
"linkifyjs": "^4.1.0"
},
@@ -2521,9 +2518,9 @@
}
},
"node_modules/@tiptap/extension-list-item": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.8.0.tgz",
- "integrity": "sha512-o7OGymGxB0B9x3x2prp3KBDYFuBYGc5sW69O672jk8G52DqhzzndgPnkk0qUn8nXAUKuDGbJmpmHVA2kagqnRg==",
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.9.1.tgz",
+ "integrity": "sha512-6O4NtYNR5N2Txi4AC0/4xMRJq9xd4+7ShxCZCDVL0WDVX37IhaqMO7LGQtA6MVlYyNaX4W1swfdJaqrJJ5HIUw==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@@ -2533,23 +2530,21 @@
}
},
"node_modules/@tiptap/extension-ordered-list": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.8.0.tgz",
- "integrity": "sha512-sCvNbcTS1+5QTTXwUPFa10vf5I1pr8sGcOTIh0G+a5ZkS5+6FxT12k7VLzPt39QyNbOi+77U2o4Xr4XyaEkfSg==",
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.9.1.tgz",
+ "integrity": "sha512-6J9jtv1XP8dW7/JNSH/K4yiOABc92tBJtgCsgP8Ep4+fjfjdj4HbjS1oSPWpgItucF2Fp/VF8qg55HXhjxHjTw==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
- "@tiptap/core": "^2.7.0",
- "@tiptap/extension-list-item": "^2.7.0",
- "@tiptap/extension-text-style": "^2.7.0"
+ "@tiptap/core": "^2.7.0"
}
},
"node_modules/@tiptap/extension-paragraph": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.8.0.tgz",
- "integrity": "sha512-XgxxNNbuBF48rAGwv7/s6as92/xjm/lTZIGTq9aG13ClUKFtgdel7C33SpUCcxg3cO2WkEyllXVyKUiauFZw/A==",
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.9.1.tgz",
+ "integrity": "sha512-JOmT0xd4gd3lIhLwrsjw8lV+ZFROKZdIxLi0Ia05XSu4RLrrvWj0zdKMSB+V87xOWfSB3Epo95zAvnPox5Q16A==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@@ -2559,9 +2554,9 @@
}
},
"node_modules/@tiptap/extension-placeholder": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/@tiptap/extension-placeholder/-/extension-placeholder-2.8.0.tgz",
- "integrity": "sha512-BMqv/C9Tcjd7L1/OphUAJTZhWfpWs0rTQJ0bs3RRGsC8L+K20Fg+li45vw7M0teojpfrw57zwJogJd/m23Zr1Q==",
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-placeholder/-/extension-placeholder-2.9.1.tgz",
+ "integrity": "sha512-Q/w3OOg/C6jGBf4QKEWKF9k+iaCQCgPoaIg2IDTPx8QmaxRfgoVE5Csd+oTOY/brdmSNXOxykZWEci6OJP+MbA==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@@ -2572,9 +2567,9 @@
}
},
"node_modules/@tiptap/extension-strike": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.8.0.tgz",
- "integrity": "sha512-ezkDiXxQ3ME/dDMMM7tAMkKRi6UWw7tIu+Mx7Os0z8HCGpVBk1gFhLlhEd8I5rJaPZr4tK1wtSehMA9bscFGQw==",
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.9.1.tgz",
+ "integrity": "sha512-V5aEXdML+YojlPhastcu7w4biDPwmzy/fWq0T2qjfu5Te/THcqDmGYVBKESBm5x6nBy5OLkanw2O+KHu2quDdg==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@@ -2584,9 +2579,9 @@
}
},
"node_modules/@tiptap/extension-subscript": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/@tiptap/extension-subscript/-/extension-subscript-2.8.0.tgz",
- "integrity": "sha512-m14K5M7E+SqqrBul+B9t5sjN4zqTddV+Q+vd+RIm+OHG6AQhwewNoFyghZz5dGZ2Xj7HqiEyusBN+iHwfgJpmg==",
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-subscript/-/extension-subscript-2.9.1.tgz",
+ "integrity": "sha512-jjfuHmF2dCUAtHmJH2K/7HhOCleM3aPVOI/UsBBYa8xM4mDU4xuW1O5sLAr2JWcB1xxyk9YKcBWwyRq+b1ENFA==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@@ -2596,9 +2591,9 @@
}
},
"node_modules/@tiptap/extension-superscript": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/@tiptap/extension-superscript/-/extension-superscript-2.8.0.tgz",
- "integrity": "sha512-3rAVyRvzhoM51vaeIAEXmr2PkucIwv7ptgyxg6zx6STxcyzMchafGee0LJL7Kcn9uE/n7Yt7ek6bDqo8jU8CtA==",
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-superscript/-/extension-superscript-2.9.1.tgz",
+ "integrity": "sha512-7cgAPpUNgO/3QdvCN9/6dWP6JQC641o8dSgkyv0XzVv0nxISck4SU+2eADRYQLyP2s4M3xuSEFhCCiKZleK2yA==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@@ -2608,9 +2603,9 @@
}
},
"node_modules/@tiptap/extension-table": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/@tiptap/extension-table/-/extension-table-2.8.0.tgz",
- "integrity": "sha512-dm9CitjacXyJuE5SZfV2lUc3uOiP2sxo6fygIzMz7iuxHqQueyONWG+TBkK7HjqzXOiMPsvOf/25NazzIG8HMg==",
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-table/-/extension-table-2.9.1.tgz",
+ "integrity": "sha512-OmWZFZOSZwSSEvoVUkDsRFyCXTYei/pV396Xjv9pfFzXQkVbfq/CjTp61zvb/9mmEz3rcfvfG7G39eRlZTvBNg==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@@ -2621,9 +2616,9 @@
}
},
"node_modules/@tiptap/extension-table-cell": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/@tiptap/extension-table-cell/-/extension-table-cell-2.8.0.tgz",
- "integrity": "sha512-IZpxONWyOd474L8+k4bHrFNRhbsl9eRwbNs5O877JkVFItc2WUz1DIhbJzjmBRsqExtWQJuOsiqWFab1kpiwGQ==",
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-table-cell/-/extension-table-cell-2.9.1.tgz",
+ "integrity": "sha512-/wrcniLdhMhs5M2NDetFcfq510N5to7YKK+52KOXNotBI8K/GjMmGmtwWEKPITD0/RgYrXzpMcta/O+/0OCOPQ==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@@ -2633,9 +2628,9 @@
}
},
"node_modules/@tiptap/extension-table-header": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/@tiptap/extension-table-header/-/extension-table-header-2.8.0.tgz",
- "integrity": "sha512-B67A96yMQlG96IFzZBc7D5dnn7O29hcjuDLtjyZkKvU5D/RlFKPMmC9nVphCV3CnbkvEOZUdK9pNaOpen64naw==",
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-table-header/-/extension-table-header-2.9.1.tgz",
+ "integrity": "sha512-KtI01636Du1IB/I3pe9ZJWKkOc6INqAaIw+RFirRCnd8Xnik7tJfAwdhXzoPRcer6ViZmlzSrM2dkwaZCF7gcw==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@@ -2645,9 +2640,9 @@
}
},
"node_modules/@tiptap/extension-table-row": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/@tiptap/extension-table-row/-/extension-table-row-2.8.0.tgz",
- "integrity": "sha512-Iezej6l7X+WqKzGLmCgAwmpL+QsfjFv1g8yVH5d0/3Pkcj3G9nDn+GSm4bZnbfYFyqInHG94PZ5PMReiALrJtA==",
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-table-row/-/extension-table-row-2.9.1.tgz",
+ "integrity": "sha512-Wq7QlI/S5iX4UCAdX+ok/szegVMbvrM3H8o6jwO+G4p8JJt6iv7ZmEnJ19xIINhmiKsrdanqH9FFK4tQ3yvQ0A==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@@ -2657,9 +2652,9 @@
}
},
"node_modules/@tiptap/extension-text": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.8.0.tgz",
- "integrity": "sha512-EDAdFFzWOvQfVy7j3qkKhBpOeE5thkJaBemSWfXI93/gMVc0ZCdLi24mDvNNgUHlT+RjlIoQq908jZaaxLKN2A==",
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.9.1.tgz",
+ "integrity": "sha512-3wo9uCrkLVLQFgbw2eFU37QAa1jq1/7oExa+FF/DVxdtHRS9E2rnUZ8s2hat/IWzvPUHXMwo3Zg2XfhoamQpCA==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@@ -2669,9 +2664,9 @@
}
},
"node_modules/@tiptap/extension-text-align": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/@tiptap/extension-text-align/-/extension-text-align-2.8.0.tgz",
- "integrity": "sha512-Y6s/DF+P4lxpAnvSrnmt4xGwQT/AJJJm0aA1wu5GuPKpAQ+K4C7K6rE6uGNAXtR39GlewC7KdmcvA+CYhL8xlw==",
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-text-align/-/extension-text-align-2.9.1.tgz",
+ "integrity": "sha512-oUp0XnwJpAImcOVV68vsY2CpkHpRZ3gzWfIRTuy+aYitQim3xDKis/qfWQUWZsANp9/TZ0VyjtkZxNMwOfcu1g==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@@ -2681,9 +2676,9 @@
}
},
"node_modules/@tiptap/extension-text-style": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/@tiptap/extension-text-style/-/extension-text-style-2.8.0.tgz",
- "integrity": "sha512-jJp0vcZ2Ty7RvIL0VU6dm1y+fTfXq1lN2GwtYzYM0ueFuESa+Qo8ticYOImyWZ3wGJGVrjn7OV9r0ReW0/NYkQ==",
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-text-style/-/extension-text-style-2.9.1.tgz",
+ "integrity": "sha512-LAxc0SeeiPiAVBwksczeA7BJSZb6WtVpYhy5Esvy9K0mK5kttB4KxtnXWeQzMIJZQbza65yftGKfQlexf/Y7yg==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@@ -2693,9 +2688,9 @@
}
},
"node_modules/@tiptap/extension-underline": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-2.8.0.tgz",
- "integrity": "sha512-1ouuHwZJphT8OosAmp6x8e+Wly3cUd1pNWBiOutJX+6QRGBXJnIKFCzn8YOTlWhg1YQigisG7dNF3YdlyuRNHw==",
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-2.9.1.tgz",
+ "integrity": "sha512-IrUsIqKPgD7GcAjr4D+RC0WvLHUDBTMkD8uPNEoeD1uH9t9zFyDfMRPnx/z3/6Gf6fTh3HzLcHGibiW2HiMi2A==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
@@ -2705,9 +2700,9 @@
}
},
"node_modules/@tiptap/pm": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.8.0.tgz",
- "integrity": "sha512-eMGpRooUMvKz/vOpnKKppApMSoNM325HxTdAJvTlVAmuHp5bOY5kyY1kfUlePRiVx1t1UlFcXs3kecFwkkBD3Q==",
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.9.1.tgz",
+ "integrity": "sha512-mvV86fr7kEuDYEApQ2uMPCKL2uagUE0BsXiyyz3KOkY1zifyVm1fzdkscb24Qy1GmLzWAIIihA+3UHNRgYdOlQ==",
"dependencies": {
"prosemirror-changeset": "^2.2.1",
"prosemirror-collab": "^1.3.1",
@@ -2726,7 +2721,7 @@
"prosemirror-tables": "^1.4.0",
"prosemirror-trailing-node": "^3.0.0",
"prosemirror-transform": "^1.10.0",
- "prosemirror-view": "^1.33.10"
+ "prosemirror-view": "^1.34.3"
},
"funding": {
"type": "github",
@@ -2734,30 +2729,31 @@
}
},
"node_modules/@tiptap/starter-kit": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-2.8.0.tgz",
- "integrity": "sha512-r7UwaTrECkQoheWVZKFDqtL5tBx07x7IFT+prfgnsVlYFutGWskVVqzCDvD3BDmrg5PzeCWYZrQGlPaLib7tjg==",
- "dependencies": {
- "@tiptap/core": "^2.8.0",
- "@tiptap/extension-blockquote": "^2.8.0",
- "@tiptap/extension-bold": "^2.8.0",
- "@tiptap/extension-bullet-list": "^2.8.0",
- "@tiptap/extension-code": "^2.8.0",
- "@tiptap/extension-code-block": "^2.8.0",
- "@tiptap/extension-document": "^2.8.0",
- "@tiptap/extension-dropcursor": "^2.8.0",
- "@tiptap/extension-gapcursor": "^2.8.0",
- "@tiptap/extension-hard-break": "^2.8.0",
- "@tiptap/extension-heading": "^2.8.0",
- "@tiptap/extension-history": "^2.8.0",
- "@tiptap/extension-horizontal-rule": "^2.8.0",
- "@tiptap/extension-italic": "^2.8.0",
- "@tiptap/extension-list-item": "^2.8.0",
- "@tiptap/extension-ordered-list": "^2.8.0",
- "@tiptap/extension-paragraph": "^2.8.0",
- "@tiptap/extension-strike": "^2.8.0",
- "@tiptap/extension-text": "^2.8.0",
- "@tiptap/pm": "^2.8.0"
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-2.9.1.tgz",
+ "integrity": "sha512-nsw6UF/7wDpPfHRhtGOwkj1ipIEiWZS1VGw+c14K61vM1CNj0uQ4jogbHwHZqN1dlL5Hh+FCqUHDPxG6ECbijg==",
+ "dependencies": {
+ "@tiptap/core": "^2.9.1",
+ "@tiptap/extension-blockquote": "^2.9.1",
+ "@tiptap/extension-bold": "^2.9.1",
+ "@tiptap/extension-bullet-list": "^2.9.1",
+ "@tiptap/extension-code": "^2.9.1",
+ "@tiptap/extension-code-block": "^2.9.1",
+ "@tiptap/extension-document": "^2.9.1",
+ "@tiptap/extension-dropcursor": "^2.9.1",
+ "@tiptap/extension-gapcursor": "^2.9.1",
+ "@tiptap/extension-hard-break": "^2.9.1",
+ "@tiptap/extension-heading": "^2.9.1",
+ "@tiptap/extension-history": "^2.9.1",
+ "@tiptap/extension-horizontal-rule": "^2.9.1",
+ "@tiptap/extension-italic": "^2.9.1",
+ "@tiptap/extension-list-item": "^2.9.1",
+ "@tiptap/extension-ordered-list": "^2.9.1",
+ "@tiptap/extension-paragraph": "^2.9.1",
+ "@tiptap/extension-strike": "^2.9.1",
+ "@tiptap/extension-text": "^2.9.1",
+ "@tiptap/extension-text-style": "^2.9.1",
+ "@tiptap/pm": "^2.9.1"
},
"funding": {
"type": "github",
@@ -13332,12 +13328,12 @@
}
},
"node_modules/playwright": {
- "version": "1.46.1",
- "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.46.1.tgz",
- "integrity": "sha512-oPcr1yqoXLCkgKtD5eNUPLiN40rYEM39odNpIb6VE6S7/15gJmA1NzVv6zJYusV0e7tzvkU/utBFNa/Kpxmwng==",
+ "version": "1.48.2",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.48.2.tgz",
+ "integrity": "sha512-NjYvYgp4BPmiwfe31j4gHLa3J7bD2WiBz8Lk2RoSsmX38SVIARZ18VYjxLjAcDsAhA+F4iSEXTSGgjua0rrlgQ==",
"dev": true,
"dependencies": {
- "playwright-core": "1.46.1"
+ "playwright-core": "1.48.2"
},
"bin": {
"playwright": "cli.js"
@@ -13350,9 +13346,9 @@
}
},
"node_modules/playwright-core": {
- "version": "1.46.1",
- "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.46.1.tgz",
- "integrity": "sha512-h9LqIQaAv+CYvWzsZ+h3RsrqCStkBHlgo6/TJlFst3cOTlLghBQlJwPOZKQJTKNaD3QIB7aAVQ+gfWbN3NXB7A==",
+ "version": "1.48.2",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.48.2.tgz",
+ "integrity": "sha512-sjjw+qrLFlriJo64du+EK0kJgZzoQPsabGF4lBvsid+3CNIZIYLgnMj9V6JY5VhM2Peh20DJWIVpVljLLnlawA==",
"dev": true,
"bin": {
"playwright-core": "cli.js"
@@ -13814,9 +13810,9 @@
}
},
"node_modules/prosemirror-view": {
- "version": "1.34.2",
- "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.34.2.tgz",
- "integrity": "sha512-tPX/V2Xd70vrAGQ/V9CppJtPKnQyQMypJGlLylvdI94k6JaG+4P6fVmXPR1zc1eVTW0gq3c6zsfqwJKCRLaG9Q==",
+ "version": "1.35.0",
+ "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.35.0.tgz",
+ "integrity": "sha512-Umtbh22fmUlpZpRTiOVXA0PpdRZeYEeXQsLp51VfnMhjkJrqJ0n8APinIZrRAD5Jr3UxH8FnOaUqRylSuMsqHA==",
"dependencies": {
"prosemirror-model": "^1.20.0",
"prosemirror-state": "^1.0.0",
diff --git a/package.json b/package.json
index 168c6ae73d..1c7df1a50b 100644
--- a/package.json
+++ b/package.json
@@ -198,21 +198,20 @@
"npm": ">=10.1 < 11"
},
"dependencies": {
- "@tiptap/core": "^2.8.0",
- "@tiptap/extension-image": "^2.8.0",
- "@tiptap/extension-link": "^2.8.0",
- "@tiptap/extension-placeholder": "^2.8.0",
- "@tiptap/extension-subscript": "^2.8.0",
- "@tiptap/extension-superscript": "^2.8.0",
- "@tiptap/extension-table": "^2.8.0",
- "@tiptap/extension-table-cell": "^2.8.0",
- "@tiptap/extension-table-header": "^2.8.0",
- "@tiptap/extension-table-row": "^2.8.0",
- "@tiptap/extension-text-align": "^2.8.0",
- "@tiptap/extension-text-style": "^2.8.0",
- "@tiptap/extension-underline": "^2.8.0",
- "@tiptap/pm": "^2.8.0",
- "@tiptap/starter-kit": "^2.8.0",
+ "@tiptap/core": "^2.9.1",
+ "@tiptap/extension-image": "^2.9.1",
+ "@tiptap/extension-link": "^2.9.1",
+ "@tiptap/extension-placeholder": "^2.9.1",
+ "@tiptap/extension-subscript": "^2.9.1",
+ "@tiptap/extension-superscript": "^2.9.1",
+ "@tiptap/extension-table": "^2.9.1",
+ "@tiptap/extension-table-cell": "^2.9.1",
+ "@tiptap/extension-table-header": "^2.9.1",
+ "@tiptap/extension-table-row": "^2.9.1",
+ "@tiptap/extension-text-align": "^2.9.1",
+ "@tiptap/extension-underline": "^2.9.1",
+ "@tiptap/pm": "^2.9.1",
+ "@tiptap/starter-kit": "^2.9.1",
"@types/diff": "^5.2.1",
"@types/dompurify": "^3.0.5",
"@types/uuid": "^10.0.0",
@@ -235,7 +234,7 @@
"@eslint/js": "^9.9.1",
"@hey-api/openapi-ts": "^0.52.11",
"@open-wc/testing": "^4.0.0",
- "@playwright/test": "^1.45.3",
+ "@playwright/test": "^1.48.2",
"@rollup/plugin-commonjs": "^26.0.1",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.2.3",
diff --git a/src/assets/lang/de-ch.ts b/src/assets/lang/de-ch.ts
new file mode 100644
index 0000000000..cd33f22eab
--- /dev/null
+++ b/src/assets/lang/de-ch.ts
@@ -0,0 +1,17 @@
+/**
+ * Creator Name: The Umbraco community
+ * Creator Link: https://docs.umbraco.com/umbraco-cms/extending/language-files
+ *
+ * Language Alias: de_CH
+ * Language Int Name: German Switzerland (DE-CH)
+ * Language Local Name: Deutsch Schweiz (DE-CH)
+ * Language LCID: 7
+ * Language Culture: de-CH
+ */
+import de_de from './de-de.js';
+import type { UmbLocalizationDictionary } from '@umbraco-cms/backoffice/localization-api';
+
+export default {
+ // NOTE: Imports and re-exports the German (Germany) localizations, so that any German (Switzerland) localizations can be override them. [LK]
+ ...de_de,
+} as UmbLocalizationDictionary;
diff --git a/src/assets/lang/en.ts b/src/assets/lang/en.ts
index 27fb01708b..e5a5eeadc2 100644
--- a/src/assets/lang/en.ts
+++ b/src/assets/lang/en.ts
@@ -1978,7 +1978,7 @@ export default {
passwordCurrent: 'Current password',
passwordInvalid: 'Invalid current password',
passwordIsDifferent:
- 'There was a difference between the new password and the confirmed password. Please\n try again!\n ',
+ 'There was a difference between the new password and the confirmed password. Please try again!',
passwordMismatch: "The confirmed password doesn't match the new password!",
passwordRequiresDigit: "The password must have at least one digit ('0'-'9')",
passwordRequiresLower: "The password must have at least one lowercase ('a'-'z')",
diff --git a/src/assets/lang/fr-ch.ts b/src/assets/lang/fr-ch.ts
new file mode 100644
index 0000000000..712e9fc0fd
--- /dev/null
+++ b/src/assets/lang/fr-ch.ts
@@ -0,0 +1,17 @@
+/**
+ * Creator Name: The Umbraco community
+ * Creator Link: https://docs.umbraco.com/umbraco-cms/extending/language-files
+ *
+ * Language Alias: fr_ch
+ * Language Int Name: French Switzerland (FR-CH)
+ * Language Local Name: Français Suisse (FR-CH)
+ * Language LCID: 12
+ * Language Culture: fr-CH
+ */
+import fr_fr from './fr-fr.js';
+import type { UmbLocalizationDictionary } from '@umbraco-cms/backoffice/localization-api';
+
+export default {
+ // NOTE: Imports and re-exports the French (France) localizations, so that any French (Switzerland) localizations can be override them. [LK]
+ ...fr_fr,
+} as UmbLocalizationDictionary;
diff --git a/src/assets/lang/it-ch.ts b/src/assets/lang/it-ch.ts
new file mode 100644
index 0000000000..bdba6cf8c1
--- /dev/null
+++ b/src/assets/lang/it-ch.ts
@@ -0,0 +1,17 @@
+/**
+ * Creator Name: The Umbraco community
+ * Creator Link: https://docs.umbraco.com/umbraco-cms/extending/language-files
+ *
+ * Language Alias: it_ch
+ * Language Int Name: Italian Switzerland (IT-CH)
+ * Language Local Name: Italiano Svizerra (IT-CH)
+ * Language LCID: 16
+ * Language Culture: it-CH
+ */
+import it_it from './it-it.js';
+import type { UmbLocalizationDictionary } from '@umbraco-cms/backoffice/localization-api';
+
+export default {
+ // NOTE: Imports and re-exports the Italian (Italy) localizations, so that any Italian (Switzerland) localizations can be override them. [LK]
+ ...it_it,
+} as UmbLocalizationDictionary;
diff --git a/src/external/router-slot/router-slot.ts b/src/external/router-slot/router-slot.ts
index 06fddd335b..b93152ba63 100644
--- a/src/external/router-slot/router-slot.ts
+++ b/src/external/router-slot/router-slot.ts
@@ -162,9 +162,11 @@ export class RouterSlot extends HTMLElement implements IRouter
this._setParent(null);
}
}
- if (this.parent && this.parent.match !== null && this.match === null) {
+ if (this.parent) {
requestAnimationFrame(() => {
- this.render();
+ if (this.parent && this.parent.match !== null && this.match === null) {
+ this.render();
+ }
});
}
}
diff --git a/src/libs/context-api/consume/context-consumer.controller.ts b/src/libs/context-api/consume/context-consumer.controller.ts
index 892264047d..573cb5aadd 100644
--- a/src/libs/context-api/consume/context-consumer.controller.ts
+++ b/src/libs/context-api/consume/context-consumer.controller.ts
@@ -19,7 +19,7 @@ export class UmbContextConsumerController,
callback?: UmbContextCallback,
) {
- super(host.getHostElement(), contextAlias, callback);
+ super(() => host.getHostElement(), contextAlias, callback);
this.#host = host;
host.addUmbController(this);
}
diff --git a/src/libs/context-api/consume/context-consumer.test.ts b/src/libs/context-api/consume/context-consumer.test.ts
index e45ea4170a..5e110b74ae 100644
--- a/src/libs/context-api/consume/context-consumer.test.ts
+++ b/src/libs/context-api/consume/context-consumer.test.ts
@@ -2,12 +2,12 @@ import { UmbContextProvider } from '../provide/context-provider.js';
import { UmbContextToken } from '../token/context-token.js';
import { UmbContextConsumer } from './context-consumer.js';
import type { UmbContextRequestEventImplementation } from './context-request.event.js';
-import { UMB_CONTENT_REQUEST_EVENT_TYPE } from './context-request.event.js';
+import { UMB_CONTEXT_REQUEST_EVENT_TYPE } from './context-request.event.js';
import { expect, oneEvent } from '@open-wc/testing';
const testContextAlias = 'my-test-context';
const testContextAliasAndApiAlias = 'my-test-context#testApi';
-const testContextAliasAndNotExstingApiAlias = 'my-test-context#notExistingTestApi';
+const testContextAliasAndNotExistingApiAlias = 'my-test-context#notExistingTestApi';
class UmbTestContextConsumerClass {
public prop: string = 'value from provider';
@@ -38,13 +38,13 @@ describe('UmbContextConsumer', () => {
describe('events', () => {
it('dispatches context request event when constructed', async () => {
- const listener = oneEvent(window, UMB_CONTENT_REQUEST_EVENT_TYPE);
+ const listener = oneEvent(window, UMB_CONTEXT_REQUEST_EVENT_TYPE);
consumer.hostConnected();
const event = (await listener) as unknown as UmbContextRequestEventImplementation;
expect(event).to.exist;
- expect(event.type).to.eq(UMB_CONTENT_REQUEST_EVENT_TYPE);
+ expect(event.type).to.eq(UMB_CONTEXT_REQUEST_EVENT_TYPE);
expect(event.contextAlias).to.eq(testContextAlias);
consumer.hostDisconnected();
});
@@ -74,6 +74,47 @@ describe('UmbContextConsumer', () => {
localConsumer.hostConnected();
});
+ it('works with host as a method', (done) => {
+ const provider = new UmbContextProvider(document.body, testContextAlias, new UmbTestContextConsumerClass());
+ provider.hostConnected();
+
+ const element = document.createElement('div');
+ document.body.appendChild(element);
+
+ const localConsumer = new UmbContextConsumer(
+ () => element,
+ testContextAlias,
+ (_instance: UmbTestContextConsumerClass | undefined) => {
+ if (_instance) {
+ expect(_instance.prop).to.eq('value from provider');
+ done();
+ localConsumer.hostDisconnected();
+ provider.hostDisconnected();
+ }
+ },
+ );
+ localConsumer.hostConnected();
+ });
+
+ it('works with host method returning undefined', async () => {
+ const element = undefined;
+
+ const localConsumer = new UmbContextConsumer(
+ () => element,
+ testContextAlias,
+ (_instance: UmbTestContextConsumerClass | undefined) => {
+ if (_instance) {
+ expect.fail('Callback should not be called when never permitted');
+ }
+ },
+ );
+ localConsumer.hostConnected();
+
+ await Promise.resolve();
+
+ localConsumer.hostDisconnected();
+ });
+
/*
Unprovided feature is out commented currently. I'm not sure there is a use case. So lets leave the code around until we know for sure.
it('acts to Context API disconnected', (done) => {
@@ -139,7 +180,7 @@ describe('UmbContextConsumer', () => {
const element = document.createElement('div');
document.body.appendChild(element);
- const localConsumer = new UmbContextConsumer(element, testContextAliasAndNotExstingApiAlias, () => {
+ const localConsumer = new UmbContextConsumer(element, testContextAliasAndNotExistingApiAlias, () => {
expect(false).to.be.true;
});
localConsumer.hostConnected();
diff --git a/src/libs/context-api/consume/context-consumer.ts b/src/libs/context-api/consume/context-consumer.ts
index f4eab04f67..0230a3f1fd 100644
--- a/src/libs/context-api/consume/context-consumer.ts
+++ b/src/libs/context-api/consume/context-consumer.ts
@@ -3,11 +3,13 @@ import { isUmbContextProvideEventType, UMB_CONTEXT_PROVIDE_EVENT_TYPE } from '..
import type { UmbContextCallback } from './context-request.event.js';
import { UmbContextRequestEventImplementation } from './context-request.event.js';
+type HostElementMethod = () => Element | undefined;
+
/**
* @class UmbContextConsumer
*/
export class UmbContextConsumer {
- protected _host: Element;
+ protected _retrieveHost: HostElementMethod;
#skipHost?: boolean;
#stopAtContextMatch = true;
@@ -33,11 +35,15 @@ export class UmbContextConsumer,
callback?: UmbContextCallback,
) {
- this._host = host;
+ if (typeof host === 'function') {
+ this._retrieveHost = host;
+ } else {
+ this._retrieveHost = () => host;
+ }
const idSplit = contextIdentifier.toString().split('#');
this.#contextAlias = idSplit[0];
this.#apiAlias = idSplit[1] ?? 'default';
@@ -72,6 +78,10 @@ export class UmbContextConsumer = (instance: T) => void;
@@ -29,7 +34,7 @@ export class UmbContextRequestEventImplementation
public readonly callback: (context: ResultType) => boolean,
public readonly stopAtContextMatch: boolean = true,
) {
- super(UMB_CONTENT_REQUEST_EVENT_TYPE, { bubbles: true, composed: true, cancelable: true });
+ super(UMB_CONTEXT_REQUEST_EVENT_TYPE, { bubbles: true, composed: true, cancelable: true });
}
clone() {
diff --git a/src/libs/context-api/provide/context-boundary.controller.ts b/src/libs/context-api/provide/context-boundary.controller.ts
new file mode 100644
index 0000000000..6fa5b7d1b1
--- /dev/null
+++ b/src/libs/context-api/provide/context-boundary.controller.ts
@@ -0,0 +1,40 @@
+import type { UmbContextToken } from '../token/index.js';
+import { UmbContextBoundary } from './context-boundary.js';
+import type { UmbControllerHost, UmbController } from '@umbraco-cms/backoffice/controller-api';
+
+export class UmbContextBoundaryController extends UmbContextBoundary implements UmbController {
+ #host: UmbControllerHost;
+ #controllerAlias: string;
+
+ public get controllerAlias(): string {
+ return this.#controllerAlias;
+ }
+
+ constructor(host: UmbControllerHost, contextAlias: string | UmbContextToken) {
+ super(host.getHostElement(), contextAlias);
+ this.#host = host;
+ // Makes the controllerAlias unique for this instance, this enables multiple Contexts to be provided under the same name. (This only makes sense cause of Context Token Discriminators)
+ // This does mean that if someone provides a context with the same name, but with a different instance, it will not override the previous instance. But its good since it enables extensions to provide contexts at the same scope of other contexts.
+ this.#controllerAlias = 'umbContextBoundary_' + contextAlias.toString();
+
+ // If this API is already provided with this alias? Then we do not want to register this controller:
+ const existingControllers = host.getUmbControllers((x) => x.controllerAlias === this.controllerAlias);
+ if (existingControllers.length > 0) {
+ // This just an additional awareness feature to make devs Aware, the alternative would be adding it anyway, but that would destroy existing controller of this alias.
+ // Back out, this instance is already provided, by another controller.
+ throw new Error(
+ `Context API: The context boundary of '${this.controllerAlias}' is already provided by another Context Provider Controller.`,
+ );
+ } else {
+ host.addUmbController(this);
+ }
+ }
+
+ public override destroy(): void {
+ if (this.#host) {
+ this.#host.removeUmbController(this);
+ (this.#host as unknown) = undefined;
+ }
+ super.destroy();
+ }
+}
diff --git a/src/libs/context-api/provide/context-boundary.ts b/src/libs/context-api/provide/context-boundary.ts
new file mode 100644
index 0000000000..56244997c6
--- /dev/null
+++ b/src/libs/context-api/provide/context-boundary.ts
@@ -0,0 +1,60 @@
+import type { UmbContextRequestEvent } from '../consume/context-request.event.js';
+import type { UmbContextToken } from '../token/index.js';
+import { UMB_CONTEXT_REQUEST_EVENT_TYPE } from '../consume/context-request.event.js';
+import { UmbContextProvideEventImplementation } from './context-provide.event.js';
+
+/**
+ * @class UmbContextBoundary
+ */
+export class UmbContextBoundary {
+ #eventTarget: EventTarget;
+
+ #contextAlias: string;
+
+ /**
+ * Creates an instance of UmbContextBoundary.
+ * @param {EventTarget} eventTarget - the host element for this context provider
+ * @param {string | UmbContextToken} contextIdentifier - a string or token to identify the context
+ * @param {*} instance - the instance to provide
+ * @memberof UmbContextBoundary
+ */
+ constructor(eventTarget: EventTarget, contextIdentifier: string | UmbContextToken) {
+ this.#eventTarget = eventTarget;
+
+ const idSplit = contextIdentifier.toString().split('#');
+ this.#contextAlias = idSplit[0];
+
+ this.#eventTarget.addEventListener(UMB_CONTEXT_REQUEST_EVENT_TYPE, this.#handleContextRequest);
+ }
+
+ /**
+ * @private
+ * @param {UmbContextRequestEvent} event - the event to handle
+ * @memberof UmbContextBoundary
+ */
+ #handleContextRequest = ((event: UmbContextRequestEvent): void => {
+ if (event.contextAlias !== this.#contextAlias) return;
+
+ if (event.stopAtContextMatch) {
+ // Since the alias matches, we will stop it from bubbling further up. But we still allow it to ask the other Contexts of the element. Hence not calling `event.stopImmediatePropagation();`
+ event.stopPropagation();
+ }
+ }) as EventListener;
+
+ /**
+ * @memberof UmbContextBoundary
+ */
+ public hostConnected(): void {
+ //this.hostElement.addEventListener(UMB_CONTENT_REQUEST_EVENT_TYPE, this.#handleContextRequest);
+ this.#eventTarget.dispatchEvent(new UmbContextProvideEventImplementation(this.#contextAlias));
+ }
+
+ /**
+ * @memberof UmbContextBoundary
+ */
+ public hostDisconnected(): void {}
+
+ destroy(): void {
+ (this.#eventTarget as unknown) = undefined;
+ }
+}
diff --git a/src/libs/context-api/provide/context-provider.ts b/src/libs/context-api/provide/context-provider.ts
index 31f62cdc8e..56f231a2d2 100644
--- a/src/libs/context-api/provide/context-provider.ts
+++ b/src/libs/context-api/provide/context-provider.ts
@@ -1,6 +1,6 @@
import type { UmbContextRequestEvent } from '../consume/context-request.event.js';
import type { UmbContextToken } from '../token/index.js';
-import { UMB_CONTENT_REQUEST_EVENT_TYPE, UMB_DEBUG_CONTEXT_EVENT_TYPE } from '../consume/context-request.event.js';
+import { UMB_CONTEXT_REQUEST_EVENT_TYPE, UMB_DEBUG_CONTEXT_EVENT_TYPE } from '../consume/context-request.event.js';
import { UmbContextProvideEventImplementation } from './context-provide.event.js';
/**
@@ -41,12 +41,12 @@ export class UmbContextProvider {
@@ -68,7 +68,7 @@ export class UmbContextProvider {
describe('Manifest without conditions', () => {
- //let hostElement: UmbControllerHostElement;
+ let hostElement: UmbControllerHostElement;
let extensionRegistry: UmbExtensionRegistry;
let manifest: ManifestWithDynamicConditions;
beforeEach(async () => {
- //hostElement = await fixture(html``);
+ hostElement = await fixture(html``);
extensionRegistry = new UmbExtensionRegistry();
manifest = {
type: 'section',
@@ -74,7 +74,7 @@ describe('UmbBaseExtensionController', () => {
extensionRegistry.register(manifest);
});
- /*
+
it('permits when there is no conditions', (done) => {
const extensionController = new UmbTestExtensionController(
hostElement,
@@ -92,16 +92,15 @@ describe('UmbBaseExtensionController', () => {
},
);
});
- */
});
describe('Manifest with empty conditions', () => {
- //let hostElement: UmbControllerHostElement;
+ let hostElement: UmbControllerHostElement;
let extensionRegistry: UmbExtensionRegistry;
let manifest: ManifestWithDynamicConditions;
beforeEach(async () => {
- //hostElement = await fixture(html``);
+ hostElement = await fixture(html``);
extensionRegistry = new UmbExtensionRegistry();
manifest = {
type: 'section',
@@ -113,7 +112,6 @@ describe('UmbBaseExtensionController', () => {
extensionRegistry.register(manifest);
});
- /*
it('permits when there is empty conditions', (done) => {
const extensionController = new UmbTestExtensionController(
hostElement,
@@ -124,7 +122,7 @@ describe('UmbBaseExtensionController', () => {
if (extensionController.permitted) {
expect(extensionController?.manifest?.alias).to.eq('Umb.Test.Section.1');
- // Also verifying that the promise gets resolved.
+ // Also verifying that the promise gets resolved. [NL]
extensionController.asPromise().then(() => {
done();
});
@@ -132,7 +130,6 @@ describe('UmbBaseExtensionController', () => {
},
);
});
- */
});
describe('Manifest with valid conditions', () => {
@@ -225,14 +222,14 @@ describe('UmbBaseExtensionController', () => {
if (isPermitted) {
count++;
if (count === 1) {
- // First time render, there is no conditions.
+ // First time render, there is no conditions. [NL]
expect(extensionController.manifest?.weight).to.be.equal(2);
expect(extensionController.manifest?.conditions?.length).to.be.equal(1);
} else if (count === 2) {
- // Second time render, there is conditions and weight is 22.
+ // Second time render, there is conditions and weight is 22. [NL]
expect(extensionController.manifest?.weight).to.be.equal(22);
expect(extensionController.manifest?.conditions?.length).to.be.equal(1);
- // Check that the promise has been resolved for the first render to ensure timing is right.
+ // Check that the promise has been resolved for the first render to ensure timing is right. [NL]
expect(initialPromiseResolved).to.be.true;
done();
extensionController.destroy();
@@ -270,14 +267,14 @@ describe('UmbBaseExtensionController', () => {
if (isPermitted) {
count++;
if (count === 1) {
- // First time render, there is no conditions.
+ // First time render, there is no conditions. [NL]
expect(extensionController.manifest?.weight).to.be.equal(3);
expect(extensionController.manifest?.conditions?.length).to.be.equal(0);
} else if (count === 2) {
- // Second time render, there is conditions and weight is 33.
+ // Second time render, there is conditions and weight is 33. [NL]
expect(extensionController.manifest?.weight).to.be.equal(33);
expect(extensionController.manifest?.conditions?.length).to.be.equal(0);
- // Check that the promise has been resolved for the first render to ensure timing is right.
+ // Check that the promise has been resolved for the first render to ensure timing is right. [NL]
expect(initialPromiseResolved).to.be.true;
done();
extensionController.destroy();
@@ -315,14 +312,14 @@ describe('UmbBaseExtensionController', () => {
if (isPermitted) {
count++;
if (count === 1) {
- // First time render, there is no conditions.
+ // First time render, there is no conditions. [NL]
expect(extensionController.manifest?.weight).to.be.equal(4);
expect(extensionController.manifest?.conditions?.length).to.be.equal(0);
} else if (count === 2) {
- // Second time render, there is conditions and weight is 33.
+ // Second time render, there is conditions and weight is updated. [NL]
expect(extensionController.manifest?.weight).to.be.equal(44);
expect(extensionController.manifest?.conditions?.length).to.be.equal(1);
- // Check that the promise has been resolved for the first render to ensure timing is right.
+ // Check that the promise has been resolved for the first render to ensure timing is right. [NL]
expect(initialPromiseResolved).to.be.true;
done();
extensionController.destroy();
@@ -370,14 +367,14 @@ describe('UmbBaseExtensionController', () => {
if (isPermitted) {
count++;
if (count === 1) {
- // First time render, there is no conditions.
+ // First time render, there is no conditions. [NL]
expect(extensionController.manifest?.weight).to.be.undefined;
expect(extensionController.manifest?.conditions?.length).to.be.equal(0);
} else if (count === 2) {
- // Second time render, there is a matching kind and then weight is 123.
+ // Second time render, there is a matching kind and then weight is 123. [NL]
expect(extensionController.manifest?.weight).to.be.equal(123);
expect(extensionController.manifest?.conditions?.length).to.be.equal(0);
- // Check that the promise has been resolved for the first render to ensure timing is right.
+ // Check that the promise has been resolved for the first render to ensure timing is right. [NL]
expect(initialPromiseResolved).to.be.true;
done();
extensionController.destroy();
@@ -433,7 +430,7 @@ describe('UmbBaseExtensionController', () => {
'Umb.Test.Section.1',
() => {
// This should not be called.
- expect(true).to.be.false;
+ expect.fail('Callback should not be called when never permitted');
},
);
Promise.resolve().then(() => {
@@ -451,7 +448,7 @@ describe('UmbBaseExtensionController', () => {
'Umb.Test.Section.1',
() => {
// This should not be called.
- expect(true).to.be.false;
+ expect.fail('Callback should not be called when never permitted');
},
);
@@ -531,7 +528,7 @@ describe('UmbBaseExtensionController', () => {
'Umb.Test.Section.1',
async () => {
count++;
- // We want the controller callback to first fire when conditions are initialized.
+ // We want the controller callback to first fire when conditions are initialized. [NL]
expect(extensionController.manifest?.conditions?.length).to.be.equal(1);
expect(extensionController?.manifest?.alias).to.eq('Umb.Test.Section.1');
if (count === 1) {
@@ -596,29 +593,30 @@ describe('UmbBaseExtensionController', () => {
'Umb.Test.Section.1',
async (isPermitted) => {
count++;
- // We want the controller callback to first fire when conditions are initialized.
+ // We want the controller callback to first fire when conditions are initialized. [NL]
expect(extensionController.manifest?.conditions?.length).to.be.equal(2);
expect(extensionController?.manifest?.alias).to.eq('Umb.Test.Section.1');
if (count === 1) {
expect(isPermitted).to.be.true;
expect(extensionController?.permitted).to.be.true;
- // Hack to double check that its two conditions that make up the state:
+ // Hack to double check that its two conditions that make up the state: [NL]
expect(
extensionController.getUmbControllers((controller) => (controller as any).permitted).length,
).to.equal(2);
} else if (count === 2) {
expect(isPermitted).to.be.false;
expect(extensionController?.permitted).to.be.false;
- // Hack to double check that its two conditions that make up the state, in this case its one, cause we already got the callback when one of the conditions changed. meaning in this split second one is still good:
+ // Hack to double check that its two conditions that make up the state, in this case its one, cause we already got the callback when one of the conditions changed. meaning in this split second one is still good: [NL]
expect(
extensionController.getUmbControllers((controller) => (controller as any).permitted).length,
).to.equal(1);
// Then we are done:
extensionController.destroy(); // End this test.
- setTimeout(() => done(), 60); // Lets wait another round of the conditions approve/disapprove, just to see if the destroy stopped the conditions. (60ms, as that should be enough to test that another round does not happen.)
+ setTimeout(() => done(), 60); // Lets wait another round of the conditions approve/disapprove, just to see if the destroy stopped the conditions. (60ms, as that should be enough to test that another round does not happen.) [NL]
} else if (count === 5) {
- expect(false).to.be.true; // This should not be called.
+ // This should not be called.
+ expect.fail('Callback should not be called when never permitted');
}
},
);
diff --git a/src/libs/extension-api/controller/base-extension-initializer.controller.ts b/src/libs/extension-api/controller/base-extension-initializer.controller.ts
index cf5fcf63b7..52cff378b7 100644
--- a/src/libs/extension-api/controller/base-extension-initializer.controller.ts
+++ b/src/libs/extension-api/controller/base-extension-initializer.controller.ts
@@ -8,7 +8,10 @@ import type {
ManifestWithDynamicConditions,
UmbExtensionRegistry,
} from '@umbraco-cms/backoffice/extension-api';
-import type { UmbObserverController } from '@umbraco-cms/backoffice/observable-api';
+import { jsonStringComparison, type UmbObserverController } from '@umbraco-cms/backoffice/observable-api';
+
+const observeConditionsCtrlAlias = Symbol();
+const observeExtensionsCtrlAlias = Symbol();
/**
* This abstract Controller holds the core to manage a single Extension.
@@ -73,8 +76,6 @@ export abstract class UmbBaseExtensionInitializer<
this.#manifestObserver = this.observe(
this.#extensionRegistry.byAlias(this.#alias),
(extensionManifest) => {
- this.#clearPermittedState();
- this.#manifest = extensionManifest;
if (extensionManifest) {
if (extensionManifest.overwrites) {
if (typeof extensionManifest.overwrites === 'string') {
@@ -83,13 +84,15 @@ export abstract class UmbBaseExtensionInitializer<
this.#overwrites = extensionManifest.overwrites;
}
}
- this.#gotManifest();
+ this.#gotManifest(extensionManifest);
} else {
+ this.#manifest = undefined;
+ this.#clearPermittedState();
this.#overwrites = [];
this.#cleanConditions();
}
},
- '_observeExtensionManifest',
+ observeExtensionsCtrlAlias,
);
}
@@ -107,19 +110,22 @@ export abstract class UmbBaseExtensionInitializer<
if (this.#conditionControllers === undefined || this.#conditionControllers.length === 0) return;
this.#conditionControllers.forEach((controller) => controller.destroy());
this.#conditionControllers = [];
- this.removeUmbControllerByAlias('_observeConditions');
+ this.removeUmbControllerByAlias(observeConditionsCtrlAlias);
}
- #gotManifest() {
- if (!this.#manifest) return;
- const conditionConfigs = this.#manifest.conditions ?? [];
+ #gotManifest(manifest: ManifestType) {
+ const conditionConfigs = manifest.conditions ?? [];
+ const oldManifest = this.#manifest;
+ this.#manifest = manifest;
- // As conditionConfigs might have been configured as something else than an array, then we ignorer them.
+ /*
+ // As conditionConfigs might have been configured as something else than an array, then we ignorer them. [NL]
if (conditionConfigs.length === 0) {
this.#cleanConditions();
this.#onConditionsChangedCallback();
return;
}
+ */
const conditionAliases = conditionConfigs
.map((condition) => condition.alias)
@@ -130,32 +136,39 @@ export abstract class UmbBaseExtensionInitializer<
this.#conditionControllers = this.#conditionControllers.filter((current) => {
const continueExistence = conditionConfigs.find((config) => config === current.config);
if (!continueExistence) {
- // Destroy condition that is no longer needed.
+ // Destroy condition that is no longer needed. [NL]
current.destroy();
}
return continueExistence;
});
- // Check if there was no change in conditions:
- // First check if any got removed(old amount equal controllers after clean-up)
- // && check if any new is about to be added(old equal new amount):
- const noChangeInConditions =
- oldAmountOfControllers === this.#conditionControllers.length &&
- oldAmountOfControllers === conditionConfigs.length;
-
if (conditionConfigs.length > 0) {
- // Observes the conditions and initialize as they come in.
+ // Observes the conditions and initialize as they come in. [NL]
this.observe(
this.#extensionRegistry.byTypeAndAliases('condition', conditionAliases),
this.#gotConditions,
- '_observeConditions',
+ observeConditionsCtrlAlias,
);
} else {
- this.removeUmbControllerByAlias('_observeConditions');
+ this.removeUmbControllerByAlias(observeConditionsCtrlAlias);
}
- if (noChangeInConditions) {
- // There was not change in the amount of conditions, but the manifest was changed, this means this.#isPermitted is set to undefined and this will always fire the callback:
+ // If permitted we want to fire an update because we got a new manifest. [NL]
+ if (this.#isPermitted) {
+ // Check if there was no change in conditions:
+ // First check if any got removed(old amount equal controllers after clean-up)
+ // && check if any new is about to be added(old equal new amount): [NL]
+ // The reason for this is because we will get an update via the code above if there is a change in conditions. But if not we will trigger it here [NL]
+ const noChangeInConditions =
+ oldAmountOfControllers === this.#conditionControllers.length &&
+ oldAmountOfControllers === conditionConfigs.length;
+ if (noChangeInConditions) {
+ if (jsonStringComparison(oldManifest, manifest) === false) {
+ // There was not change in the amount of conditions, but the manifest was changed, this means this.#isPermitted is set to undefined and this will always fire the callback: [NL]
+ this.#onPermissionChanged?.(this.#isPermitted, this as any);
+ }
+ }
+ } else {
this.#onConditionsChangedCallback();
}
}
@@ -189,9 +202,9 @@ export abstract class UmbBaseExtensionInitializer<
newConditionControllers
.filter((x) => x !== undefined)
.forEach((emerging) => {
- // TODO: All of this could use a refactor at one point, when someone is fresh in their mind.
- // Niels Notes: Current problem being that we are not aware about what is in the making, so we don't know if we end up creating the same condition multiple times.
- // Because it took some time to create the conditions, it maybe have already gotten created by another cycle, so lets test again.
+ // TODO: All of this could use a refactor at one point, when someone is fresh in their mind. [NL]
+ // Niels Notes: Current problem being that we are not aware about what is in the making, so we don't know if we end up creating the same condition multiple times. [NL]
+ // Because it took some time to create the conditions, it maybe have already gotten created by another cycle, so lets test again. [NL]
const existing = this.#conditionControllers.find((existing) => existing.config === emerging?.config);
if (!existing) {
this.#conditionControllers.push(emerging!);
@@ -200,7 +213,7 @@ export abstract class UmbBaseExtensionInitializer<
}
});
- // If a change to amount of condition controllers, this will make sure that when new conditions are added, the callback is fired, so the extensions can be re-evaluated, starting out as bad.
+ // If a change to amount of condition controllers, this will make sure that when new conditions are added, the callback is fired, so the extensions can be re-evaluated, starting out as bad. [NL]
if (oldLength !== this.#conditionControllers.length) {
this.#onConditionsChangedCallback();
}
@@ -230,14 +243,19 @@ export abstract class UmbBaseExtensionInitializer<
#conditionsAreInitialized() {
// Not good if we don't have a manifest.
- // Only good if conditions of manifest is equal to the amount of condition controllers (one for each condition).
+ // Only good if conditions of manifest is equal to the amount of condition controllers (one for each condition). [NL]
return (
this.#manifest !== undefined && this.#conditionControllers.length === (this.#manifest.conditions ?? []).length
);
}
#onConditionsChangedCallback = async () => {
- // We will collect old value here, but we need to re-collect it after a async method have been called, as it could have changed in the mean time.
+ if (this.#manifest === undefined) {
+ // This is cause by this controller begin destroyed in the mean time. [NL]
+ // When writing this the only plausible case is a call from the conditionController to the onChange callback.
+ return;
+ }
+ // We will collect old value here, but we need to re-collect it after a async method have been called, as it could have changed in the mean time. [NL]
let oldValue = this.#isPermitted ?? false;
// Find a condition that is not permitted (Notice how no conditions, means that this extension is permitted)
@@ -250,13 +268,13 @@ export abstract class UmbBaseExtensionInitializer<
if (isPositive === true) {
if (this.#isPermitted !== true) {
const newPermission = await this._conditionsAreGood();
- // Only set new permission if we are still positive, otherwise it means that we have been destroyed in the mean time.
+ // Only set new permission if we are still positive, otherwise it means that we have been destroyed in the mean time. [NL]
if (newPermission === false || this._isConditionsPositive === false) {
// Then we need to revert the above work:
this._conditionsAreBad();
return;
}
- // We update the oldValue as this point, cause in this way we are sure its the value at this point, when doing async code someone else might have changed the state in the mean time.
+ // We update the oldValue as this point, cause in this way we are sure its the value at this point, when doing async code someone else might have changed the state in the mean time. [NL]
oldValue = this.#isPermitted ?? false;
this.#isPermitted = newPermission;
}
@@ -264,11 +282,11 @@ export abstract class UmbBaseExtensionInitializer<
// Clean up:
await this._conditionsAreBad();
- // Only continue if we are still negative, otherwise it means that something changed in the mean time.
+ // Only continue if we are still negative, otherwise it means that something changed in the mean time. [NL]
if (this._isConditionsPositive === true) {
return;
}
- // We update the oldValue as this point, cause in this way we are sure its the value at this point, when doing async code someone else might have changed the state in the mean time.
+ // We update the oldValue as this point, cause in this way we are sure its the value at this point, when doing async code someone else might have changed the state in the mean time. [NL]
oldValue = this.#isPermitted ?? false;
this.#isPermitted = false;
}
@@ -286,7 +304,7 @@ export abstract class UmbBaseExtensionInitializer<
protected abstract _conditionsAreBad(): Promise;
public equal(otherClass: UmbBaseExtensionInitializer | undefined): boolean {
- return otherClass?.manifest === this.manifest;
+ return otherClass?.manifest === this.#manifest;
}
/*
@@ -296,6 +314,7 @@ export abstract class UmbBaseExtensionInitializer<
}
*/
+ /*
public override hostDisconnected(): void {
super.hostDisconnected();
this._isConditionsPositive = false;
@@ -305,6 +324,7 @@ export abstract class UmbBaseExtensionInitializer<
this.#onPermissionChanged?.(false, this as unknown as SubClassType);
}
}
+ */
#clearPermittedState() {
if (this.#isPermitted === true) {
@@ -318,7 +338,7 @@ export abstract class UmbBaseExtensionInitializer<
if (!this.#extensionRegistry) return;
this.#manifest = undefined;
this.#promiseResolvers = [];
- this.#clearPermittedState(); // This fires the callback as not permitted, if it was permitted before.
+ this.#clearPermittedState(); // This fires the callback as not permitted, if it was permitted before. [NL]
this.#isPermitted = undefined;
this._isConditionsPositive = false;
this.#overwrites = [];
diff --git a/src/libs/extension-api/controller/base-extensions-initializer.controller.test.ts b/src/libs/extension-api/controller/base-extensions-initializer.controller.test.ts
index abcf3326db..2b12443731 100644
--- a/src/libs/extension-api/controller/base-extensions-initializer.controller.test.ts
+++ b/src/libs/extension-api/controller/base-extensions-initializer.controller.test.ts
@@ -13,6 +13,8 @@ import { customElement, html } from '@umbraco-cms/backoffice/external/lit';
class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {}
class UmbTestExtensionController extends UmbBaseExtensionInitializer {
+ testInsidesIsDestroyed?: boolean;
+
constructor(
host: UmbControllerHost,
extensionRegistry: UmbExtensionRegistry,
@@ -28,7 +30,8 @@ class UmbTestExtensionController extends UmbBaseExtensionInitializer {
}
protected async _conditionsAreBad() {
- // Destroy the element/class.
+ // Destroy the element/class. (or in the case of this test, then we just set a flag) [NL]
+ this.testInsidesIsDestroyed = true;
}
}
@@ -111,7 +114,7 @@ describe('UmbBaseExtensionsController', () => {
it('exposes both manifests', (done) => {
let count = 0;
- const extensionController = new UmbTestExtensionsController(
+ const extensionsInitController = new UmbTestExtensionsController(
hostElement,
testExtensionRegistry,
'extension-type',
@@ -120,7 +123,7 @@ describe('UmbBaseExtensionsController', () => {
count++;
if (count === 1) {
expect(permitted.length).to.eq(2);
- extensionController.destroy();
+ extensionsInitController.destroy();
} else if (count === 2) {
done();
}
@@ -138,7 +141,7 @@ describe('UmbBaseExtensionsController', () => {
testExtensionRegistry.register(manifestExtra);
let count = 0;
- const extensionController = new UmbTestExtensionsController(
+ const extensionsInitController = new UmbTestExtensionsController(
hostElement,
testExtensionRegistry,
['extension-type', 'extension-type-extra'],
@@ -151,9 +154,9 @@ describe('UmbBaseExtensionsController', () => {
expect(permitted[1].alias).to.eq('Umb.Test.Extension.B');
expect(permitted[2].alias).to.eq('Umb.Test.Extension.Extra');
- extensionController.destroy();
+ extensionsInitController.destroy();
} else if (count === 2) {
- // Cause we destroyed there will be a last call to reset permitted controllers:
+ // Cause we destroyed there will be a last call to reset permitted controllers: [NL]
expect(permitted.length).to.eq(0);
done();
}
@@ -189,7 +192,7 @@ describe('UmbBaseExtensionsController', () => {
it('exposes just one manifests', (done) => {
let count = 0;
- const extensionController = new UmbTestExtensionsController(
+ const extensionsInitController = new UmbTestExtensionsController(
hostElement,
testExtensionRegistry,
'extension-type',
@@ -197,17 +200,17 @@ describe('UmbBaseExtensionsController', () => {
(permitted) => {
count++;
if (count === 1) {
- // Still just equal one, as the second one overwrites the first one.
+ // Still just equal one, as the second one overwrites the first one. [NL]
expect(permitted.length).to.eq(1);
expect(permitted[0].alias).to.eq('Umb.Test.Extension.B');
- // lets remove the overwriting extension to see the original coming back.
+ // lets remove the overwriting extension to see the original coming back. [NL]
testExtensionRegistry.unregister('Umb.Test.Extension.B');
} else if (count === 2) {
expect(permitted.length).to.eq(1);
expect(permitted[0].alias).to.eq('Umb.Test.Extension.A');
done();
- extensionController.destroy();
+ extensionsInitController.destroy();
}
},
);
@@ -255,7 +258,8 @@ describe('UmbBaseExtensionsController', () => {
it('exposes only the overwriting manifest', (done) => {
let count = 0;
- const extensionController = new UmbTestExtensionsController(
+ let lastPermitted: PermittedControllerType[] = [];
+ const extensionsInitController = new UmbTestExtensionsController(
hostElement,
testExtensionRegistry,
'extension-type',
@@ -263,24 +267,30 @@ describe('UmbBaseExtensionsController', () => {
(permitted) => {
count++;
if (count === 1) {
- // Still just equal one, as the second one overwrites the first one.
+ // Still just equal one, as the second one overwrites the first one. [NL]
expect(permitted.length).to.eq(1);
expect(permitted[0].alias).to.eq('Umb.Test.Extension.B');
- // lets remove the overwriting extension to see the original coming back.
+ // lets remove the overwriting extension to see the original coming back. [NL]
testExtensionRegistry.unregister('Umb.Test.Extension.B');
} else if (count === 2) {
expect(permitted.length).to.eq(1);
expect(permitted[0].alias).to.eq('Umb.Test.Extension.A');
- extensionController.destroy();
+ // CHecks that the controller that got overwritten is destroyed. [NL]
+ expect(lastPermitted[0].testInsidesIsDestroyed).to.be.true;
+ // Then continue the test and destroy the initializer. [NL]
+ extensionsInitController.destroy();
+ // And then checks that the controller is destroyed. [NL]
+ expect(permitted[0].testInsidesIsDestroyed).to.be.true;
} else if (count === 3) {
- // Expect that destroy will only result in one last callback with no permitted controllers.
+ // Expect that destroy will only result in one last callback with no permitted controllers. [NL]
expect(permitted.length).to.eq(0);
- Promise.resolve().then(() => done()); // This wrap is to enable the test to detect if more callbacks are fired.
+ Promise.resolve().then(() => done()); // This wrap is to enable the test to detect if more callbacks are fired. [NL]
} else if (count === 4) {
- // This should not happen, we do only want one last callback when destroyed.
+ // This should not happen, we do only want one last callback when destroyed. [NL]
expect(false).to.eq(true);
}
+ lastPermitted = permitted;
},
);
});
@@ -308,7 +318,7 @@ describe('UmbBaseExtensionsController', () => {
],
};
- // Register opposite order, to ensure B is there when A comes around. A fix to be able to test this. Cause a late registration of B would not cause a change that is test able.
+ // Register opposite order, to ensure B is there when A comes around. A fix to be able to test this. Cause a late registration of B would not cause a change that is test able. [NL]
testExtensionRegistry.register(manifestB);
testExtensionRegistry.register(manifestA);
testExtensionRegistry.register({
@@ -329,7 +339,7 @@ describe('UmbBaseExtensionsController', () => {
it('exposes only the original manifest', (done) => {
let count = 0;
- const extensionController = new UmbTestExtensionsController(
+ const extensionsInitController = new UmbTestExtensionsController(
hostElement,
testExtensionRegistry,
'extension-type',
@@ -338,11 +348,11 @@ describe('UmbBaseExtensionsController', () => {
count++;
if (count === 1) {
- // First callback gives just one. We need to make a feature to gather changes to only reply after a computation cycle if we like to avoid this.
+ // First callback gives just one. We need to make a feature to gather changes to only reply after a computation cycle if we like to avoid this. [NL]
expect(permitted.length).to.eq(1);
expect(permitted[0].alias).to.eq('Umb.Test.Extension.A');
done();
- extensionController.destroy();
+ extensionsInitController.destroy();
}
},
);
diff --git a/src/libs/observable-api/utils/push-at-to-unique-array.function.ts b/src/libs/observable-api/utils/push-at-to-unique-array.function.ts
index a46b329639..f31ab8c332 100644
--- a/src/libs/observable-api/utils/push-at-to-unique-array.function.ts
+++ b/src/libs/observable-api/utils/push-at-to-unique-array.function.ts
@@ -5,6 +5,7 @@
* @param {T} entry - The object to insert or replace with.
* @param {getUniqueMethod: (entry: T) => unknown} [getUniqueMethod] - Method to get the unique value of an entry.
* @description - Append or replaces an item of an Array.
+ * @returns {T[]} - Returns a new array with the updated entry.
* @example
Example append new entry for a Array. Where the key is unique and the item will be updated if matched with existing.
* const entry = {key: 'myKey', value: 'myValue'};
* const newDataSet = pushToUniqueArray([], entry, x => x.key === key, 1);
diff --git a/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/manifests.ts b/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/manifests.ts
index 10eb00ca0a..315fd1ff4d 100644
--- a/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/manifests.ts
+++ b/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/manifests.ts
@@ -1,7 +1,6 @@
import { manifests as workspaceViewManifests } from './views/manifests.js';
import { UMB_BLOCK_GRID_AREA_TYPE_WORKSPACE_ALIAS } from './index.js';
-import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace';
-import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace';
+import { UmbSubmitWorkspaceAction, UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace';
export const manifests: Array = [
...workspaceViewManifests,
diff --git a/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/views/manifests.ts b/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/views/manifests.ts
index ba7238c442..3bdf1271b9 100644
--- a/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/views/manifests.ts
+++ b/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/views/manifests.ts
@@ -1,5 +1,5 @@
-import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace';
import { UMB_BLOCK_GRID_AREA_TYPE_WORKSPACE_ALIAS } from '../index.js';
+import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace';
import type { ManifestWorkspaceView } from '@umbraco-cms/backoffice/workspace';
export const workspaceViews: Array = [
diff --git a/src/packages/block/block-grid/components/block-grid-areas-container/block-grid-areas-container.element.ts b/src/packages/block/block-grid/components/block-grid-areas-container/block-grid-areas-container.element.ts
index 1f93598b92..adb7ceea0d 100644
--- a/src/packages/block/block-grid/components/block-grid-areas-container/block-grid-areas-container.element.ts
+++ b/src/packages/block/block-grid/components/block-grid-areas-container/block-grid-areas-container.element.ts
@@ -1,7 +1,8 @@
+import type { UmbBlockGridTypeAreaType } from '../../types.js';
+import { UMB_BLOCK_GRID_ENTRY_CONTEXT } from '../../context/index.js';
import { UMB_BLOCK_GRID_MANAGER_CONTEXT } from '../../context/block-grid-manager.context-token.js';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { css, customElement, html, repeat, state } from '@umbraco-cms/backoffice/external/lit';
-import { UMB_BLOCK_GRID_ENTRY_CONTEXT, type UmbBlockGridTypeAreaType } from '@umbraco-cms/backoffice/block-grid';
import '../block-grid-entries/index.js';
/**
diff --git a/src/packages/block/block-grid/components/block-grid-block-inline/block-grid-block-inline.element.ts b/src/packages/block/block-grid/components/block-grid-block-inline/block-grid-block-inline.element.ts
index 82e3f4333f..06156a1b8f 100644
--- a/src/packages/block/block-grid/components/block-grid-block-inline/block-grid-block-inline.element.ts
+++ b/src/packages/block/block-grid/components/block-grid-block-inline/block-grid-block-inline.element.ts
@@ -1,4 +1,6 @@
import { UMB_BLOCK_GRID_ENTRY_CONTEXT } from '../../context/block-grid-entry.context-token.js';
+import type { UmbBlockGridWorkspaceOriginData } from '../../workspace/block-grid-workspace.modal-token.js';
+import { UMB_BLOCK_GRID_ENTRIES_CONTEXT } from '../../context/block-grid-entries.context-token.js';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { css, customElement, html, property, state } from '@umbraco-cms/backoffice/external/lit';
import type { UmbPropertyTypeModel } from '@umbraco-cms/backoffice/content-type';
@@ -32,6 +34,8 @@ export class UmbBlockGridBlockInlineElement extends UmbLitElement {
#blockContext?: typeof UMB_BLOCK_GRID_ENTRY_CONTEXT.TYPE;
#workspaceContext?: typeof UMB_BLOCK_WORKSPACE_CONTEXT.TYPE;
#contentKey?: string;
+ #parentUnique?: string | null;
+ #areaKey?: string | null;
@property({ attribute: false })
config?: UmbBlockEditorCustomViewConfiguration;
@@ -71,6 +75,10 @@ export class UmbBlockGridBlockInlineElement extends UmbLitElement {
'observeContentKey',
);
});
+ this.consumeContext(UMB_BLOCK_GRID_ENTRIES_CONTEXT, (entriesContext) => {
+ this.#parentUnique = entriesContext.getParentUnique();
+ this.#areaKey = entriesContext.getAreaKey();
+ });
new UmbExtensionApiInitializer(
this,
umbExtensionsRegistry,
@@ -79,7 +87,15 @@ export class UmbBlockGridBlockInlineElement extends UmbLitElement {
(permitted, ctrl) => {
const context = ctrl.api as typeof UMB_BLOCK_WORKSPACE_CONTEXT.TYPE;
if (permitted && context) {
+ // Risky business, cause here we are lucky that it seems to be consumed and set before this is called and there for this is acceptable for now. [NL]
+ if (this.#parentUnique === undefined || this.#areaKey === undefined) {
+ throw new Error('Parent unique and area key must be defined');
+ }
this.#workspaceContext = context;
+ context.setOriginData({
+ areaKey: this.#areaKey,
+ parentUnique: this.#parentUnique,
+ } as UmbBlockGridWorkspaceOriginData);
this.#workspaceContext.establishLiveSync();
this.#load();
@@ -277,6 +293,19 @@ export class UmbBlockGridBlockInlineElement extends UmbLitElement {
margin-right: var(--uui-size-1);
}
+ #info {
+ display: flex;
+ flex-direction: column;
+ align-items: start;
+ justify-content: center;
+ height: 100%;
+ padding-left: var(--uui-size-2, 6px);
+ }
+
+ #name {
+ font-weight: 700;
+ }
+
:host(:not([disabled])) #open-part:hover #icon {
color: var(--uui-color-interactive-emphasis);
}
@@ -294,6 +323,7 @@ export class UmbBlockGridBlockInlineElement extends UmbLitElement {
#inside {
position: relative;
display: block;
+ padding: calc(var(--uui-size-layout-1));
}
`,
];
diff --git a/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts b/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts
index 6c4a76df0e..7e763cf82c 100644
--- a/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts
+++ b/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts
@@ -1,5 +1,6 @@
import { UmbBlockGridEntriesContext } from '../../context/block-grid-entries.context.js';
import type { UmbBlockGridEntryElement } from '../block-grid-entry/index.js';
+import type { UmbBlockGridLayoutModel } from '../../types.js';
import {
getAccumulatedValueOfIndex,
getInterpolatedIndexOfPositionInWeightMap,
@@ -16,7 +17,6 @@ import {
type UmbFormControlValidatorConfig,
} from '@umbraco-cms/backoffice/validation';
import type { UmbNumberRangeValueType } from '@umbraco-cms/backoffice/models';
-import type { UmbBlockGridLayoutModel } from '@umbraco-cms/backoffice/block-grid';
/**
* Notice this utility method is not really shareable with others as it also takes areas into account. [NL]
diff --git a/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts b/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts
index 4e39f774d5..e291af4aeb 100644
--- a/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts
+++ b/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts
@@ -1,10 +1,11 @@
import { UmbBlockGridEntryContext } from '../../context/block-grid-entry.context.js';
-import { UmbLitElement, umbDestroyOnDisconnect } from '@umbraco-cms/backoffice/lit-element';
+import type { UmbBlockGridLayoutModel } from '../../types.js';
+import { UMB_BLOCK_GRID } from '../../constants.js';
+import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { html, css, customElement, property, state, nothing } from '@umbraco-cms/backoffice/external/lit';
import type { PropertyValueMap } from '@umbraco-cms/backoffice/external/lit';
import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/property-editor';
import { stringOrStringArrayContains } from '@umbraco-cms/backoffice/utils';
-import { UMB_BLOCK_GRID, type UmbBlockGridLayoutModel } from '@umbraco-cms/backoffice/block-grid';
import '../block-grid-block-inline/index.js';
import '../block-grid-block/index.js';
@@ -414,8 +415,8 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper
.unpublished=${!this._exposed}
.config=${this._blockViewProps.config}
.content=${this._blockViewProps.content}
- .settings=${this._blockViewProps.settings}
- ${umbDestroyOnDisconnect()}>`;
+ .settings=${this._blockViewProps.settings}>`;
+ //TODO: investigate if we should have ${umbDestroyOnDisconnect()} here. Note how it works for drag n' drop in grid between areas and areas-root
}
#renderRefBlock() {
@@ -426,8 +427,8 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper
.unpublished=${!this._exposed}
.config=${this._blockViewProps.config}
.content=${this._blockViewProps.content}
- .settings=${this._blockViewProps.settings}
- ${umbDestroyOnDisconnect()}>`;
+ .settings=${this._blockViewProps.settings}>`;
+ //TODO: investigate if we should have ${umbDestroyOnDisconnect()} here. Note how it works for drag n' drop in grid between areas and areas-root
}
#renderBlock() {
@@ -598,15 +599,15 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper
inset: 0;
border: 1px solid transparent;
border-radius: var(--uui-border-radius);
- box-shadow:
- 0 0 0 1px rgba(255, 255, 255, 0.7),
- inset 0 0 0 1px rgba(255, 255, 255, 0.7);
transition: border-color 240ms ease-in;
}
:host(:hover):not(:drop)::after {
display: block;
border-color: var(--uui-color-interactive-emphasis);
+ box-shadow:
+ 0 0 0 1px rgba(255, 255, 255, 0.7),
+ inset 0 0 0 1px rgba(255, 255, 255, 0.7);
}
:host([drag-placeholder])::after {
diff --git a/src/packages/block/block-grid/components/block-scale-handler/block-scale-handler.element.ts b/src/packages/block/block-grid/components/block-scale-handler/block-scale-handler.element.ts
index b8c25d1732..31c59b5002 100644
--- a/src/packages/block/block-grid/components/block-scale-handler/block-scale-handler.element.ts
+++ b/src/packages/block/block-grid/components/block-scale-handler/block-scale-handler.element.ts
@@ -1,7 +1,6 @@
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { html, css, customElement } from '@umbraco-cms/backoffice/external/lit';
import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/property-editor';
-import '../block-grid-block/index.js';
/**
* @element umb-block-scale-handler
diff --git a/src/packages/block/block-grid/context/block-grid-entries.context.ts b/src/packages/block/block-grid/context/block-grid-entries.context.ts
index b69f1a2853..1850c2dd14 100644
--- a/src/packages/block/block-grid/context/block-grid-entries.context.ts
+++ b/src/packages/block/block-grid/context/block-grid-entries.context.ts
@@ -99,6 +99,10 @@ export class UmbBlockGridEntriesContext
this.#catalogueModal.setUniquePathValue('parentUnique', pathFolderName(contentKey ?? 'null'));
}
+ getParentUnique(): string | null | undefined {
+ return this.#parentUnique;
+ }
+
setAreaKey(areaKey: string | null) {
this.#areaKey = areaKey;
this.#workspaceModal.setUniquePathValue('areaKey', areaKey ?? 'null');
@@ -110,6 +114,10 @@ export class UmbBlockGridEntriesContext
// If not, we want to set the layoutDataPath to a base one.
}
+ getAreaKey(): string | null | undefined {
+ return this.#areaKey;
+ }
+
setLayoutColumns(columns: number | undefined) {
this.#layoutColumns.setValue(columns);
}
@@ -157,7 +165,11 @@ export class UmbBlockGridEntriesContext
blocks: this.#allowedBlockTypes.getValue(),
blockGroups: this._manager.getBlockGroups() ?? [],
openClipboard: routingInfo.view === 'clipboard',
- originData: { index: index, areaKey: this.#areaKey, parentUnique: this.#parentUnique },
+ originData: {
+ index: index,
+ areaKey: this.#areaKey,
+ parentUnique: this.#parentUnique,
+ } as UmbBlockGridWorkspaceOriginData,
createBlockInWorkspace: this._manager.getInlineEditingMode() === false,
},
};
@@ -195,7 +207,12 @@ export class UmbBlockGridEntriesContext
data: {
entityType: 'block',
preset: {},
- originData: { areaKey: this.#areaKey, parentUnique: this.#parentUnique, baseDataPath: this._dataPath },
+ originData: {
+ index: -1,
+ areaKey: this.#areaKey,
+ parentUnique: this.#parentUnique,
+ baseDataPath: this._dataPath,
+ } as UmbBlockGridWorkspaceOriginData,
},
modal: { size: 'medium' },
};
diff --git a/src/packages/block/block-grid/context/block-grid-manager.context.ts b/src/packages/block/block-grid/context/block-grid-manager.context.ts
index 2f411cfd2b..cd4b63e0a5 100644
--- a/src/packages/block/block-grid/context/block-grid-manager.context.ts
+++ b/src/packages/block/block-grid/context/block-grid-manager.context.ts
@@ -1,18 +1,19 @@
import type { UmbBlockGridLayoutModel, UmbBlockGridTypeModel } from '../types.js';
import type { UmbBlockGridWorkspaceOriginData } from '../index.js';
import {
- UmbArrayState,
- UmbBooleanState,
appendToFrozenArray,
pushAtToUniqueArray,
+ UmbArrayState,
+ UmbBooleanState,
} from '@umbraco-cms/backoffice/observable-api';
-import { removeLastSlashFromPath, transformServerPathToClientPath } from '@umbraco-cms/backoffice/utils';
-import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
+import { transformServerPathToClientPath } from '@umbraco-cms/backoffice/utils';
+import { UmbBlockManagerContext } from '@umbraco-cms/backoffice/block';
import { UMB_APP_CONTEXT } from '@umbraco-cms/backoffice/app';
-import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor';
-import { type UmbBlockDataModel, UmbBlockManagerContext } from '@umbraco-cms/backoffice/block';
+import type { UmbBlockDataModel } from '@umbraco-cms/backoffice/block';
import type { UmbBlockTypeGroup } from '@umbraco-cms/backoffice/block-type';
+import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import type { UmbNumberRangeValueType } from '@umbraco-cms/backoffice/models';
+import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor';
export const UMB_BLOCK_GRID_DEFAULT_LAYOUT_STYLESHEET = '/umbraco/backoffice/css/umbraco-blockgridlayout.css';
@@ -34,7 +35,9 @@ export class UmbBlockGridManagerContext<
}
#initAppUrl: Promise;
- #appUrl?: string;
+
+ #serverUrl?: string;
+
#blockGroups = new UmbArrayState(>[], (x) => x.key);
public readonly blockGroups = this.#blockGroups.asObservable();
@@ -45,7 +48,8 @@ export class UmbBlockGridManagerContext<
if (layoutStylesheet) {
// Cause we await initAppUrl in setting the _editorConfiguration, we can trust the appUrl begin here.
- return removeLastSlashFromPath(this.#appUrl!) + transformServerPathToClientPath(layoutStylesheet);
+ const url = new URL(transformServerPathToClientPath(layoutStylesheet), this.#serverUrl);
+ return url.href;
}
return undefined;
});
@@ -85,7 +89,7 @@ export class UmbBlockGridManagerContext<
super(host);
this.#initAppUrl = this.getContext(UMB_APP_CONTEXT).then((appContext) => {
- this.#appUrl = appContext.getServerUrl() + appContext.getBackofficePath();
+ this.#serverUrl = appContext.getServerUrl();
});
}
diff --git a/src/packages/block/block-grid/workspace/views/manifests.ts b/src/packages/block/block-grid/workspace/views/manifests.ts
index 8722b9f764..e14984f6c2 100644
--- a/src/packages/block/block-grid/workspace/views/manifests.ts
+++ b/src/packages/block/block-grid/workspace/views/manifests.ts
@@ -1,5 +1,5 @@
-import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace';
import { UMB_BLOCK_GRID_TYPE_WORKSPACE_ALIAS } from '../index.js';
+import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace';
export const manifests: Array = [
{
diff --git a/src/packages/block/block-list/components/block-list-entry/block-list-entry.element.ts b/src/packages/block/block-list/components/block-list-entry/block-list-entry.element.ts
index f23b039e1b..d3b58036f7 100644
--- a/src/packages/block/block-list/components/block-list-entry/block-list-entry.element.ts
+++ b/src/packages/block/block-list/components/block-list-entry/block-list-entry.element.ts
@@ -300,22 +300,24 @@ export class UmbBlockListEntryElement extends UmbLitElement implements UmbProper
}
#renderBlock() {
- return html`
- ${this._inlineEditingMode ? this.#renderInlineBlock() : this.#renderRefBlock()}
-
- ${this.#renderEditContentAction()} ${this.#renderEditSettingsAction()} ${this.#renderDeleteAction()}
-
- ${!this._showContentEdit && this._contentInvalid
- ? html`!`
- : nothing}
- `;
+ return this.contentKey && this._contentTypeAlias
+ ? html`
+ ${this._inlineEditingMode ? this.#renderInlineBlock() : this.#renderRefBlock()}
+
+ ${this.#renderEditContentAction()} ${this.#renderEditSettingsAction()} ${this.#renderDeleteAction()}
+
+ ${!this._showContentEdit && this._contentInvalid
+ ? html`!`
+ : nothing}
+ `
+ : nothing;
}
#renderEditContentAction() {
diff --git a/src/packages/block/block-list/components/inline-list-block/inline-list-block.element.ts b/src/packages/block/block-list/components/inline-list-block/inline-list-block.element.ts
index b0685a4828..f6749f63d3 100644
--- a/src/packages/block/block-list/components/inline-list-block/inline-list-block.element.ts
+++ b/src/packages/block/block-list/components/inline-list-block/inline-list-block.element.ts
@@ -271,6 +271,19 @@ export class UmbInlineListBlockElement extends UmbLitElement {
margin-right: var(--uui-size-1);
}
+ #info {
+ display: flex;
+ flex-direction: column;
+ align-items: start;
+ justify-content: center;
+ height: 100%;
+ padding-left: var(--uui-size-2, 6px);
+ }
+
+ #name {
+ font-weight: 700;
+ }
+
:host(:not([disabled])) #open-part:hover #icon {
color: var(--uui-color-interactive-emphasis);
}
diff --git a/src/packages/block/block-list/workspace/views/manifests.ts b/src/packages/block/block-list/workspace/views/manifests.ts
index 04d75d39ab..a019927e9c 100644
--- a/src/packages/block/block-list/workspace/views/manifests.ts
+++ b/src/packages/block/block-list/workspace/views/manifests.ts
@@ -1,5 +1,5 @@
-import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace';
import { UMB_BLOCK_LIST_TYPE_WORKSPACE_ALIAS } from '../index.js';
+import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace';
export const manifests: Array = [
{
diff --git a/src/packages/block/block-rte/workspace/views/manifests.ts b/src/packages/block/block-rte/workspace/views/manifests.ts
index 5b8abf2041..f075a1e992 100644
--- a/src/packages/block/block-rte/workspace/views/manifests.ts
+++ b/src/packages/block/block-rte/workspace/views/manifests.ts
@@ -1,5 +1,5 @@
-import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace';
import { UMB_BLOCK_RTE_TYPE_WORKSPACE_ALIAS } from '../index.js';
+import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace';
export const manifests: Array = [
{
diff --git a/src/packages/block/block-type/workspace/block-type-workspace.context.ts b/src/packages/block/block-type/workspace/block-type-workspace.context.ts
index 1d17eb81ec..4b99ed1c3b 100644
--- a/src/packages/block/block-type/workspace/block-type-workspace.context.ts
+++ b/src/packages/block/block-type/workspace/block-type-workspace.context.ts
@@ -54,7 +54,7 @@ export class UmbBlockTypeWorkspaceContext = [
{
diff --git a/src/packages/block/block/validation/block-element-values-validation-path-translator.controller.ts b/src/packages/block/block/validation/block-element-values-validation-path-translator.controller.ts
index 2c2b9da88c..4eaa8b431c 100644
--- a/src/packages/block/block/validation/block-element-values-validation-path-translator.controller.ts
+++ b/src/packages/block/block/validation/block-element-values-validation-path-translator.controller.ts
@@ -4,9 +4,11 @@ import {
UmbDataPathPropertyValueQuery,
} from '@umbraco-cms/backoffice/validation';
+const ctrlAlias = Symbol();
+
export class UmbBlockElementValuesDataValidationPathTranslator extends UmbAbstractArrayValidationPathTranslator {
constructor(host: UmbControllerHost) {
- super(host, '$.values[', UmbDataPathPropertyValueQuery);
+ super(host, '$.values[', UmbDataPathPropertyValueQuery, ctrlAlias);
}
getDataFromIndex(index: number) {
diff --git a/src/packages/block/block/workspace/block-workspace.context.ts b/src/packages/block/block/workspace/block-workspace.context.ts
index 31cf151c32..d7f83662fe 100644
--- a/src/packages/block/block/workspace/block-workspace.context.ts
+++ b/src/packages/block/block/workspace/block-workspace.context.ts
@@ -16,13 +16,12 @@ import {
observeMultiple,
} from '@umbraco-cms/backoffice/observable-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
-import { UMB_MODAL_CONTEXT, type UmbModalContext } from '@umbraco-cms/backoffice/modal';
+import { UMB_MODAL_CONTEXT } from '@umbraco-cms/backoffice/modal';
import { decodeFilePath, UmbReadOnlyVariantStateManager } from '@umbraco-cms/backoffice/utils';
import {
UMB_BLOCK_ENTRIES_CONTEXT,
UMB_BLOCK_MANAGER_CONTEXT,
type UmbBlockWorkspaceOriginData,
- type UmbBlockWorkspaceData,
UMB_BLOCK_ENTRY_CONTEXT,
} from '@umbraco-cms/backoffice/block';
import { UmbVariantId } from '@umbraco-cms/backoffice/variant';
@@ -40,7 +39,11 @@ export class UmbBlockWorkspaceContext;
+ #originData?: UmbBlockWorkspaceOriginData;
+ // Set the origin data for this workspace. Example used by inline editing which setups the workspace context it self.
+ setOriginData(data: UmbBlockWorkspaceOriginData) {
+ this.#originData = data;
+ }
#retrieveModalContext;
#entityType: string;
@@ -80,7 +83,7 @@ export class UmbBlockWorkspaceContext {
- this.#modalContext = context as any;
+ this.#originData = context?.data.originData;
context.onSubmit().catch(this.#modalRejected);
}).asPromise();
@@ -176,7 +179,7 @@ export class UmbBlockWorkspaceContext {
if (layoutData) {
- this.#blockManager?.setOneLayout(
- layoutData,
- this.#modalContext?.data.originData as UmbBlockWorkspaceOriginData,
- );
+ if (initialLayoutSet) {
+ initialLayoutSet = false;
+ return;
+ }
+ this.#blockManager?.setOneLayout(layoutData, this.#originData);
}
},
'observeThisLayout',
@@ -430,7 +432,7 @@ export class UmbBlockWorkspaceContext = [
{
diff --git a/src/packages/language/collection/views/table/column-layouts/boolean/language-table-boolean-column-layout.element.ts b/src/packages/core/collection/components/boolean-table-column-view/boolean-table-column-view.element.ts
similarity index 62%
rename from src/packages/language/collection/views/table/column-layouts/boolean/language-table-boolean-column-layout.element.ts
rename to src/packages/core/collection/components/boolean-table-column-view/boolean-table-column-view.element.ts
index 479bc70b2e..856e202deb 100644
--- a/src/packages/language/collection/views/table/column-layouts/boolean/language-table-boolean-column-layout.element.ts
+++ b/src/packages/core/collection/components/boolean-table-column-view/boolean-table-column-view.element.ts
@@ -1,8 +1,8 @@
import { html, nothing, customElement, property } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
-@customElement('umb-language-table-boolean-column-layout')
-export class UmbLanguageTableBooleanColumnLayoutElement extends UmbLitElement {
+@customElement('umb-boolean-table-column-view')
+export class UmbBooleanTableColumnViewElement extends UmbLitElement {
@property({ attribute: false })
value = false;
@@ -13,6 +13,6 @@ export class UmbLanguageTableBooleanColumnLayoutElement extends UmbLitElement {
declare global {
interface HTMLElementTagNameMap {
- 'umb-language-table-boolean-column-layout': UmbLanguageTableBooleanColumnLayoutElement;
+ 'umb-boolean-table-column-view': UmbBooleanTableColumnViewElement;
}
}
diff --git a/src/packages/core/collection/components/index.ts b/src/packages/core/collection/components/index.ts
index 7abcffff90..a440030db1 100644
--- a/src/packages/core/collection/components/index.ts
+++ b/src/packages/core/collection/components/index.ts
@@ -1,3 +1,4 @@
+import './boolean-table-column-view/boolean-table-column-view.element.js';
import './collection-action-bundle.element.js';
import './collection-filter-field.element.js';
import './collection-selection-actions.element.js';
diff --git a/src/packages/core/collection/components/pagination/collection-pagination.element.ts b/src/packages/core/collection/components/pagination/collection-pagination.element.ts
index 7ee0688115..8bd77abf64 100644
--- a/src/packages/core/collection/components/pagination/collection-pagination.element.ts
+++ b/src/packages/core/collection/components/pagination/collection-pagination.element.ts
@@ -63,6 +63,10 @@ export class UmbCollectionPaginationElement extends UmbLitElement {
UmbTextStyles,
css`
:host {
+ display: contents;
+ }
+
+ uui-pagination {
display: block;
margin-top: var(--uui-size-layout-1);
}
diff --git a/src/packages/core/collection/default/collection-default.element.ts b/src/packages/core/collection/default/collection-default.element.ts
index c1d0f54116..4350fdf0a7 100644
--- a/src/packages/core/collection/default/collection-default.element.ts
+++ b/src/packages/core/collection/default/collection-default.element.ts
@@ -113,11 +113,11 @@ export class UmbCollectionDefaultElement extends UmbLitElement {
}
#router {
- display: none;
+ visibility: hidden;
}
.has-items #router {
- display: block;
+ visibility: visible;
}
#empty-state {
diff --git a/src/packages/core/content-type/index.ts b/src/packages/core/content-type/index.ts
index 79f883296b..2a3cd65724 100644
--- a/src/packages/core/content-type/index.ts
+++ b/src/packages/core/content-type/index.ts
@@ -1,7 +1,12 @@
-export * from './components/index.js';
export * from './composition/index.js';
export * from './modals/index.js';
export * from './repository/index.js';
export * from './structure/index.js';
export * from './workspace/index.js';
export type * from './types.js';
+
+/**
+ * @deprecated Use `UmbPropertyTypeBasedPropertyElement` from `@umbraco-cms/backoffice/content` instead.
+ * Export will be removed in version 17.
+ */
+export { UmbPropertyTypeBasedPropertyElement } from '../content/components/property-type-based-property/property-type-based-property.element.js';
diff --git a/src/packages/core/content-type/workspace/views/design/content-type-design-editor-group.element.ts b/src/packages/core/content-type/workspace/views/design/content-type-design-editor-group.element.ts
index ead29d145b..160866835c 100644
--- a/src/packages/core/content-type/workspace/views/design/content-type-design-editor-group.element.ts
+++ b/src/packages/core/content-type/workspace/views/design/content-type-design-editor-group.element.ts
@@ -1,12 +1,9 @@
+import type { UmbContentTypeModel, UmbPropertyTypeContainerModel } from '../../../types.js';
+import type { UmbContentTypeContainerStructureHelper } from '../../../structure/index.js';
import { css, customElement, html, nothing, property, repeat, state, when } from '@umbraco-cms/backoffice/external/lit';
import { umbConfirmModal } from '@umbraco-cms/backoffice/modal';
import { UmbLitElement, umbFocus } from '@umbraco-cms/backoffice/lit-element';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
-import type {
- UmbContentTypeContainerStructureHelper,
- UmbContentTypeModel,
- UmbPropertyTypeContainerModel,
-} from '@umbraco-cms/backoffice/content-type';
import type { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui';
import './content-type-design-editor-properties.element.js';
diff --git a/src/packages/core/content-type/workspace/views/design/content-type-design-editor-properties.element.ts b/src/packages/core/content-type/workspace/views/design/content-type-design-editor-properties.element.ts
index 65449ac606..932df6aad2 100644
--- a/src/packages/core/content-type/workspace/views/design/content-type-design-editor-properties.element.ts
+++ b/src/packages/core/content-type/workspace/views/design/content-type-design-editor-properties.element.ts
@@ -1,4 +1,6 @@
import { UMB_CONTENT_TYPE_WORKSPACE_CONTEXT } from '../../content-type-workspace.context-token.js';
+import type { UmbContentTypeModel, UmbPropertyTypeModel } from '../../../types.js';
+import { UmbContentTypePropertyStructureHelper } from '../../../structure/index.js';
import { UMB_CONTENT_TYPE_DESIGN_EDITOR_CONTEXT } from './content-type-design-editor.context.js';
import type { UmbContentTypeDesignEditorPropertyElement } from './content-type-design-editor-property.element.js';
import {
@@ -13,17 +15,15 @@ import {
} from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
-import { UmbContentTypePropertyStructureHelper } from '@umbraco-cms/backoffice/content-type';
-import type { UmbContentTypeModel, UmbPropertyTypeModel } from '@umbraco-cms/backoffice/content-type';
import { type UmbSorterConfig, UmbSorterController } from '@umbraco-cms/backoffice/sorter';
import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router';
-
-import './content-type-design-editor-property.element.js';
import {
UMB_CREATE_PROPERTY_TYPE_WORKSPACE_PATH_PATTERN,
UMB_PROPERTY_TYPE_WORKSPACE_MODAL,
} from '@umbraco-cms/backoffice/property-type';
+import './content-type-design-editor-property.element.js';
+
const SORTER_CONFIG: UmbSorterConfig = {
getUniqueOfElement: (element) => {
return element.getAttribute('data-umb-property-id');
@@ -193,7 +193,7 @@ export class UmbContentTypeDesignEditorPropertiesElement extends UmbLitElement {
}
preset.sortOrder = sortOrderInt;
}
- return { data: { contentTypeUnique: this._ownerContentTypeUnique, preset: undefined } };
+ return { data: { contentTypeUnique: this._ownerContentTypeUnique, preset: preset } };
})
.observeRouteBuilder((routeBuilder) => {
this._newPropertyPath =
diff --git a/src/packages/core/content-type/workspace/views/design/content-type-design-editor-property.element.ts b/src/packages/core/content-type/workspace/views/design/content-type-design-editor-property.element.ts
index 02374f4f8a..571b9a3c69 100644
--- a/src/packages/core/content-type/workspace/views/design/content-type-design-editor-property.element.ts
+++ b/src/packages/core/content-type/workspace/views/design/content-type-design-editor-property.element.ts
@@ -1,3 +1,5 @@
+import type { UmbContentTypePropertyStructureHelper } from '../../../structure/index.js';
+import type { UmbContentTypeModel, UmbPropertyTypeModel, UmbPropertyTypeScaffoldModel } from '../../../types.js';
import { UmbPropertyTypeContext } from './content-type-design-editor-property.context.js';
import { css, html, customElement, property, state, nothing } from '@umbraco-cms/backoffice/external/lit';
import { generateAlias } from '@umbraco-cms/backoffice/utils';
@@ -6,12 +8,6 @@ import { UmbDataTypeDetailRepository } from '@umbraco-cms/backoffice/data-type';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { UMB_EDIT_PROPERTY_TYPE_WORKSPACE_PATH_PATTERN } from '@umbraco-cms/backoffice/property-type';
-import type {
- UmbContentTypeModel,
- UmbContentTypePropertyStructureHelper,
- UmbPropertyTypeModel,
- UmbPropertyTypeScaffoldModel,
-} from '@umbraco-cms/backoffice/content-type';
import type { UUIInputElement, UUIInputLockElement, UUIInputEvent } from '@umbraco-cms/backoffice/external/uui';
/**
diff --git a/src/packages/core/content-type/workspace/views/design/content-type-design-editor-tab.element.ts b/src/packages/core/content-type/workspace/views/design/content-type-design-editor-tab.element.ts
index 3b18576327..363f959008 100644
--- a/src/packages/core/content-type/workspace/views/design/content-type-design-editor-tab.element.ts
+++ b/src/packages/core/content-type/workspace/views/design/content-type-design-editor-tab.element.ts
@@ -1,13 +1,13 @@
import { UMB_CONTENT_TYPE_WORKSPACE_CONTEXT } from '../../content-type-workspace.context-token.js';
+import type { UmbContentTypeModel, UmbPropertyTypeContainerModel } from '../../../types.js';
+import { UmbContentTypeContainerStructureHelper } from '../../../structure/index.js';
import { UMB_CONTENT_TYPE_DESIGN_EDITOR_CONTEXT } from './content-type-design-editor.context.js';
import type { UmbContentTypeWorkspaceViewEditGroupElement } from './content-type-design-editor-group.element.js';
import { css, customElement, html, nothing, property, repeat, state } from '@umbraco-cms/backoffice/external/lit';
-import { UmbContentTypeContainerStructureHelper } from '@umbraco-cms/backoffice/content-type';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router';
import { UmbSorterController } from '@umbraco-cms/backoffice/sorter';
import { UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/workspace';
-import type { UmbContentTypeModel, UmbPropertyTypeContainerModel } from '@umbraco-cms/backoffice/content-type';
import type { UmbSorterConfig } from '@umbraco-cms/backoffice/sorter';
import './content-type-design-editor-properties.element.js';
diff --git a/src/packages/core/content-type/workspace/views/design/content-type-design-editor.element.ts b/src/packages/core/content-type/workspace/views/design/content-type-design-editor.element.ts
index 9105d91fd0..50d10eef50 100644
--- a/src/packages/core/content-type/workspace/views/design/content-type-design-editor.element.ts
+++ b/src/packages/core/content-type/workspace/views/design/content-type-design-editor.element.ts
@@ -1,15 +1,14 @@
import { UMB_CONTENT_TYPE_WORKSPACE_CONTEXT } from '../../content-type-workspace.context-token.js';
+import type { UmbContentTypeModel, UmbPropertyTypeContainerModel } from '../../../types.js';
+import {
+ UmbContentTypeContainerStructureHelper,
+ UmbContentTypeMoveRootGroupsIntoFirstTabHelper,
+} from '../../../structure/index.js';
+import { UMB_COMPOSITION_PICKER_MODAL } from '../../../modals/index.js';
import type { UmbContentTypeDesignEditorTabElement } from './content-type-design-editor-tab.element.js';
import { UmbContentTypeDesignEditorContext } from './content-type-design-editor.context.js';
import { css, html, customElement, state, repeat, ifDefined, nothing } from '@umbraco-cms/backoffice/external/lit';
import type { UUIInputElement, UUIInputEvent, UUITabElement } from '@umbraco-cms/backoffice/external/uui';
-import {
- UMB_COMPOSITION_PICKER_MODAL,
- UmbContentTypeContainerStructureHelper,
- UmbContentTypeMoveRootGroupsIntoFirstTabHelper,
- type UmbContentTypeModel,
- type UmbPropertyTypeContainerModel,
-} from '@umbraco-cms/backoffice/content-type';
import { encodeFolderName } from '@umbraco-cms/backoffice/router';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { CompositionTypeModel } from '@umbraco-cms/backoffice/external/backend-api';
diff --git a/src/packages/core/content-type/components/index.ts b/src/packages/core/content/components/index.ts
similarity index 100%
rename from src/packages/core/content-type/components/index.ts
rename to src/packages/core/content/components/index.ts
diff --git a/src/packages/core/content-type/components/property-type-based-property/index.ts b/src/packages/core/content/components/property-type-based-property/index.ts
similarity index 100%
rename from src/packages/core/content-type/components/property-type-based-property/index.ts
rename to src/packages/core/content/components/property-type-based-property/index.ts
diff --git a/src/packages/core/content-type/components/property-type-based-property/property-type-based-property.element.ts b/src/packages/core/content/components/property-type-based-property/property-type-based-property.element.ts
similarity index 95%
rename from src/packages/core/content-type/components/property-type-based-property/property-type-based-property.element.ts
rename to src/packages/core/content/components/property-type-based-property/property-type-based-property.element.ts
index 475188cd24..745b97c7c0 100644
--- a/src/packages/core/content-type/components/property-type-based-property/property-type-based-property.element.ts
+++ b/src/packages/core/content/components/property-type-based-property/property-type-based-property.element.ts
@@ -1,7 +1,6 @@
-import type { UmbPropertyEditorConfig } from '../../../property-editor/index.js';
-import type { UmbPropertyTypeModel } from '../../types.js';
+import { UmbContentPropertyContext } from '../../content-property.context.js';
+import type { UmbPropertyEditorConfig } from '@umbraco-cms/backoffice/property-editor';
import { css, customElement, html, ifDefined, property, state } from '@umbraco-cms/backoffice/external/lit';
-import { UmbContentPropertyContext } from '@umbraco-cms/backoffice/content';
import { UmbDataTypeDetailRepository } from '@umbraco-cms/backoffice/data-type';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
@@ -9,6 +8,7 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import type { UmbDataTypeDetailModel } from '@umbraco-cms/backoffice/data-type';
import type { UmbObserverController } from '@umbraco-cms/backoffice/observable-api';
import { UMB_UNSUPPORTED_EDITOR_SCHEMA_ALIASES } from '@umbraco-cms/backoffice/property';
+import type { UmbPropertyTypeModel } from '@umbraco-cms/backoffice/content-type';
@customElement('umb-property-type-based-property')
export class UmbPropertyTypeBasedPropertyElement extends UmbLitElement {
diff --git a/src/packages/core/content/constants.ts b/src/packages/core/content/constants.ts
index cecd2baf55..05a27ced5d 100644
--- a/src/packages/core/content/constants.ts
+++ b/src/packages/core/content/constants.ts
@@ -1 +1 @@
-export const UMB_CONTENT_SECTION_ALIAS = 'Umb.Section.Content';
\ No newline at end of file
+export const UMB_CONTENT_SECTION_ALIAS = 'Umb.Section.Content';
diff --git a/src/packages/core/content/controller/merge-content-variant-data.controller.ts b/src/packages/core/content/controller/merge-content-variant-data.controller.ts
index aa50029af8..ae4e35af83 100644
--- a/src/packages/core/content/controller/merge-content-variant-data.controller.ts
+++ b/src/packages/core/content/controller/merge-content-variant-data.controller.ts
@@ -1,5 +1,5 @@
+import type { UmbContentLikeDetailModel, UmbPotentialContentValueModel } from '../types.js';
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
-import type { UmbContentLikeDetailModel, UmbPotentialContentValueModel } from '@umbraco-cms/backoffice/content';
import { createExtensionApi } from '@umbraco-cms/backoffice/extension-api';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
import { UmbVariantId, type UmbVariantDataModel } from '@umbraco-cms/backoffice/variant';
diff --git a/src/packages/core/content/index.ts b/src/packages/core/content/index.ts
index 1fe642380a..338c9631c1 100644
--- a/src/packages/core/content/index.ts
+++ b/src/packages/core/content/index.ts
@@ -1,9 +1,12 @@
export { UMB_CONTENT_PROPERTY_CONTEXT } from './content-property.context-token.js';
export { UmbContentPropertyContext } from './content-property.context.js';
+
export * from './collection/index.js';
+export * from './components/index.js';
export * from './constants.js';
export * from './controller/merge-content-variant-data.controller.js';
export * from './manager/index.js';
export * from './property-dataset-context/index.js';
+export * from './variant-picker/index.js';
export * from './workspace/index.js';
export type * from './types.js';
diff --git a/src/packages/core/content/manager/content-data-manager.ts b/src/packages/core/content/manager/content-data-manager.ts
index 3c6601037f..d8d375eb4c 100644
--- a/src/packages/core/content/manager/content-data-manager.ts
+++ b/src/packages/core/content/manager/content-data-manager.ts
@@ -1,5 +1,5 @@
+import type { UmbContentDetailModel } from '../types.js';
import { UmbElementWorkspaceDataManager } from './element-data-manager.js';
-import type { UmbContentDetailModel } from '@umbraco-cms/backoffice/content';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { appendToFrozenArray, jsonStringComparison } from '@umbraco-cms/backoffice/observable-api';
import { UmbVariantId, type UmbEntityVariantModel } from '@umbraco-cms/backoffice/variant';
@@ -14,11 +14,20 @@ export class UmbContentWorkspaceDataManager<
//#repository;
#variantScaffold?: ModelVariantType;
- constructor(host: UmbControllerHost, variantScaffold: ModelVariantType) {
+ constructor(host: UmbControllerHost, variantScaffold?: ModelVariantType) {
super(host);
this.#variantScaffold = variantScaffold;
}
+ /**
+ * Sets the variant scaffold data
+ * @param {ModelVariantType} variantScaffold The variant scaffold data
+ * @memberof UmbContentWorkspaceDataManager
+ */
+ setVariantScaffold(variantScaffold: ModelVariantType) {
+ this.#variantScaffold = variantScaffold;
+ }
+
ensureVariantData(variantId: UmbVariantId) {
this.updateVariantData(variantId);
}
diff --git a/src/packages/core/content/manager/element-data-manager.ts b/src/packages/core/content/manager/element-data-manager.ts
index 7dd3ddc91e..dfac318c5d 100644
--- a/src/packages/core/content/manager/element-data-manager.ts
+++ b/src/packages/core/content/manager/element-data-manager.ts
@@ -1,5 +1,5 @@
import { UmbMergeContentVariantDataController } from '../controller/merge-content-variant-data.controller.js';
-import type { UmbElementDetailModel } from '@umbraco-cms/backoffice/content';
+import type { UmbElementDetailModel } from '../types.js';
import { UmbVariantId } from '@umbraco-cms/backoffice/variant';
import { UmbEntityWorkspaceDataManager, type UmbWorkspaceDataManager } from '@umbraco-cms/backoffice/workspace';
diff --git a/src/packages/core/content/property-dataset-context/element-property-dataset.context.ts b/src/packages/core/content/property-dataset-context/element-property-dataset.context.ts
index 0490888eb7..bf9657c99a 100644
--- a/src/packages/core/content/property-dataset-context/element-property-dataset.context.ts
+++ b/src/packages/core/content/property-dataset-context/element-property-dataset.context.ts
@@ -215,7 +215,7 @@ export abstract class UmbElementPropertyDatasetContext<
override destroy() {
super.destroy();
- this.#propertyVariantIdMap.destroy();
+ this.#propertyVariantIdMap?.destroy();
(this.#propertyVariantIdMap as unknown) = undefined;
}
}
diff --git a/src/packages/core/content/types.ts b/src/packages/core/content/types.ts
index a1e7c8cd01..169c1d43b0 100644
--- a/src/packages/core/content/types.ts
+++ b/src/packages/core/content/types.ts
@@ -19,10 +19,11 @@ export interface UmbPotentialContentValueModel extends UmbP
segment?: string | null;
}
-export interface UmbContentDetailModel extends UmbElementDetailModel {
+export interface UmbContentDetailModel
+ extends UmbElementDetailModel {
unique: string;
entityType: string;
- variants: Array;
+ variants: Array;
}
export interface UmbContentLikeDetailModel
diff --git a/src/packages/core/content/variant-picker/index.ts b/src/packages/core/content/variant-picker/index.ts
new file mode 100644
index 0000000000..d4702960d5
--- /dev/null
+++ b/src/packages/core/content/variant-picker/index.ts
@@ -0,0 +1 @@
+export * from './types.js';
diff --git a/src/packages/core/content/variant-picker/types.ts b/src/packages/core/content/variant-picker/types.ts
new file mode 100644
index 0000000000..42a4467c92
--- /dev/null
+++ b/src/packages/core/content/variant-picker/types.ts
@@ -0,0 +1,8 @@
+export interface UmbContentVariantPickerData {
+ options: Array;
+ pickableFilter?: (variantOption: VariantOptionModelType) => boolean;
+}
+
+export interface UmbContentVariantPickerValue {
+ selection: Array;
+}
diff --git a/src/packages/core/content/workspace/content-detail-workspace-base.ts b/src/packages/core/content/workspace/content-detail-workspace-base.ts
new file mode 100644
index 0000000000..3ae802cc25
--- /dev/null
+++ b/src/packages/core/content/workspace/content-detail-workspace-base.ts
@@ -0,0 +1,621 @@
+import type { UmbContentDetailModel, UmbElementValueModel } from '../types.js';
+import { UmbContentWorkspaceDataManager } from '../manager/index.js';
+import { UmbMergeContentVariantDataController } from '../controller/merge-content-variant-data.controller.js';
+import type { UmbContentVariantPickerData, UmbContentVariantPickerValue } from '../variant-picker/index.js';
+import type { UmbContentPropertyDatasetContext } from '../property-dataset-context/index.js';
+import type { UmbContentWorkspaceContext } from './content-workspace-context.interface.js';
+import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
+import type { UmbDetailRepository, UmbDetailRepositoryConstructor } from '@umbraco-cms/backoffice/repository';
+import {
+ UmbEntityDetailWorkspaceContextBase,
+ UmbWorkspaceSplitViewManager,
+ type UmbEntityDetailWorkspaceContextArgs,
+ type UmbEntityDetailWorkspaceContextCreateArgs,
+} from '@umbraco-cms/backoffice/workspace';
+import {
+ UmbContentTypeStructureManager,
+ type UmbContentTypeModel,
+ type UmbPropertyTypeModel,
+} from '@umbraco-cms/backoffice/content-type';
+import {
+ UMB_INVARIANT_CULTURE,
+ UmbVariantId,
+ type UmbEntityVariantModel,
+ type UmbEntityVariantOptionModel,
+} from '@umbraco-cms/backoffice/variant';
+import { UmbReadOnlyVariantStateManager } from '@umbraco-cms/backoffice/utils';
+import { UmbDataTypeItemRepositoryManager } from '@umbraco-cms/backoffice/data-type';
+import { appendToFrozenArray, mergeObservables, UmbArrayState } from '@umbraco-cms/backoffice/observable-api';
+import { UmbLanguageCollectionRepository, type UmbLanguageDetailModel } from '@umbraco-cms/backoffice/language';
+import type { Observable } from '@umbraco-cms/backoffice/external/rxjs';
+import { firstValueFrom } from '@umbraco-cms/backoffice/external/rxjs';
+import {
+ UMB_VALIDATION_CONTEXT,
+ UMB_VALIDATION_EMPTY_LOCALIZATION_KEY,
+ UmbDataPathVariantQuery,
+ UmbValidationContext,
+ UmbVariantsValidationPathTranslator,
+ UmbVariantValuesValidationPathTranslator,
+} from '@umbraco-cms/backoffice/validation';
+import type { UmbModalToken } from '@umbraco-cms/backoffice/modal';
+import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
+import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
+import {
+ UmbRequestReloadChildrenOfEntityEvent,
+ UmbRequestReloadStructureForEntityEvent,
+} from '@umbraco-cms/backoffice/entity-action';
+
+export interface UmbContentDetailWorkspaceContextArgs<
+ DetailModelType extends UmbContentDetailModel,
+ ContentTypeDetailModelType extends UmbContentTypeModel = UmbContentTypeModel,
+ VariantModelType extends UmbEntityVariantModel = DetailModelType extends { variants: UmbEntityVariantModel[] }
+ ? DetailModelType['variants'][0]
+ : never,
+ VariantOptionModelType extends UmbEntityVariantOptionModel = UmbEntityVariantOptionModel,
+> extends UmbEntityDetailWorkspaceContextArgs {
+ contentTypeDetailRepository: UmbDetailRepositoryConstructor;
+ contentVariantScaffold: VariantModelType;
+ saveModalToken?: UmbModalToken, UmbContentVariantPickerValue>;
+}
+
+export abstract class UmbContentDetailWorkspaceContextBase<
+ DetailModelType extends UmbContentDetailModel,
+ DetailRepositoryType extends UmbDetailRepository = UmbDetailRepository,
+ ContentTypeDetailModelType extends UmbContentTypeModel = UmbContentTypeModel,
+ VariantModelType extends UmbEntityVariantModel = DetailModelType extends { variants: UmbEntityVariantModel[] }
+ ? DetailModelType['variants'][0]
+ : never,
+ VariantOptionModelType extends UmbEntityVariantOptionModel = UmbEntityVariantOptionModel,
+ CreateArgsType extends
+ UmbEntityDetailWorkspaceContextCreateArgs = UmbEntityDetailWorkspaceContextCreateArgs,
+ >
+ extends UmbEntityDetailWorkspaceContextBase
+ implements UmbContentWorkspaceContext
+{
+ public readonly IS_CONTENT_WORKSPACE_CONTEXT = true as const;
+
+ public readonly readOnlyState = new UmbReadOnlyVariantStateManager(this);
+
+ /* Content Data */
+ protected override readonly _data = new UmbContentWorkspaceDataManager(this);
+ public override readonly entityType = this._data.createObservablePartOfCurrent((data) => data?.entityType);
+ public override readonly unique = this._data.createObservablePartOfCurrent((data) => data?.unique);
+ public readonly values = this._data.createObservablePartOfCurrent((data) => data?.values);
+ public readonly variants = this._data.createObservablePartOfCurrent((data) => data?.variants ?? []);
+
+ /* Content Type (Structure) Data */
+ public readonly structure;
+ public readonly variesByCulture;
+ public readonly variesBySegment;
+ public readonly varies;
+
+ abstract readonly contentTypeUnique: Observable;
+
+ /* Data Type */
+ readonly #dataTypeItemManager = new UmbDataTypeItemRepositoryManager(this);
+ /**
+ * Data Type Schema Map is used for lookup, this should make coder simpler and give better performance. [NL]
+ */
+ #dataTypeSchemaAliasMap = new Map();
+
+ #varies?: boolean;
+ #variesByCulture?: boolean;
+ #variesBySegment?: boolean;
+
+ /* Split View */
+ readonly splitView = new UmbWorkspaceSplitViewManager();
+
+ /* Variant Options */
+ // TODO: Optimize this so it uses either a App Language Context? [NL]
+ #languageRepository = new UmbLanguageCollectionRepository(this);
+ #languages = new UmbArrayState([], (x) => x.unique);
+ /**
+ * @private
+ * @description - Should not be used by external code.
+ */
+ public readonly languages = this.#languages.asObservable();
+
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore
+ // TODO: fix type error
+ public readonly variantOptions;
+
+ #saveModalToken?: UmbModalToken, UmbContentVariantPickerValue>;
+
+ constructor(
+ host: UmbControllerHost,
+ args: UmbContentDetailWorkspaceContextArgs<
+ DetailModelType,
+ ContentTypeDetailModelType,
+ VariantModelType,
+ VariantOptionModelType
+ >,
+ ) {
+ super(host, args);
+
+ this._data.setVariantScaffold(args.contentVariantScaffold);
+ this.#saveModalToken = args.saveModalToken;
+
+ const contentTypeDetailRepository = new args.contentTypeDetailRepository(this);
+ this.structure = new UmbContentTypeStructureManager(this, contentTypeDetailRepository);
+ this.variesByCulture = this.structure.ownerContentTypeObservablePart((x) => x?.variesByCulture);
+ this.variesBySegment = this.structure.ownerContentTypeObservablePart((x) => x?.variesBySegment);
+ this.varies = this.structure.ownerContentTypeObservablePart((x) =>
+ x ? x.variesByCulture || x.variesBySegment : undefined,
+ );
+
+ this.variantOptions = mergeObservables(
+ [this.varies, this.variants, this.languages],
+ ([varies, variants, languages]) => {
+ // TODO: When including segments, when be aware about the case of segment varying when not culture varying. [NL]
+ if (varies === true) {
+ return languages.map((language) => {
+ return {
+ variant: variants.find((x) => x.culture === language.unique),
+ language,
+ // TODO: When including segments, this object should be updated to include a object for the segment. [NL]
+ // TODO: When including segments, the unique should be updated to include the segment as well. [NL]
+ unique: language.unique, // This must be a variantId string!
+ culture: language.unique,
+ segment: null,
+ } as VariantOptionModelType;
+ });
+ } else if (varies === false) {
+ return [
+ {
+ variant: variants.find((x) => x.culture === null),
+ language: languages.find((x) => x.isDefault),
+ culture: null,
+ segment: null,
+ unique: UMB_INVARIANT_CULTURE, // This must be a variantId string!
+ } as VariantOptionModelType,
+ ];
+ }
+ return [] as Array;
+ },
+ );
+
+ this.addValidationContext(new UmbValidationContext(this));
+ new UmbVariantValuesValidationPathTranslator(this);
+ new UmbVariantsValidationPathTranslator(this);
+
+ this.observe(
+ this.varies,
+ (varies) => {
+ this._data.setVaries(varies);
+ this.#varies = varies;
+ },
+ null,
+ );
+ this.observe(
+ this.variesByCulture,
+ (varies) => {
+ this._data.setVariesByCulture(varies);
+ this.#variesByCulture = varies;
+ },
+ null,
+ );
+ this.observe(
+ this.variesBySegment,
+ (varies) => {
+ this._data.setVariesBySegment(varies);
+ this.#variesBySegment = varies;
+ },
+ null,
+ );
+ this.observe(
+ this.structure.contentTypeDataTypeUniques,
+ (dataTypeUniques: Array) => {
+ this.#dataTypeItemManager.setUniques(dataTypeUniques);
+ },
+ null,
+ );
+ this.observe(
+ this.#dataTypeItemManager.items,
+ (dataTypes) => {
+ // Make a map of the data type unique and editorAlias
+ this.#dataTypeSchemaAliasMap = new Map(
+ dataTypes.map((dataType) => {
+ return [dataType.unique, dataType.propertyEditorSchemaAlias];
+ }),
+ );
+ },
+ null,
+ );
+
+ this.loadLanguages();
+ }
+
+ public async loadLanguages() {
+ // TODO: If we don't end up having a Global Context for languages, then we should at least change this into using a asObservable which should be returned from the repository. [Nl]
+ const { data } = await this.#languageRepository.requestCollection({});
+ this.#languages.setValue(data?.items ?? []);
+ }
+
+ /**
+ * Get the name of a variant
+ * @param {UmbVariantId } [variantId] - The variant id
+ * @returns { string | undefined} - The name of the variant
+ * @memberof UmbContentDetailWorkspaceContextBase
+ */
+ public getName(variantId?: UmbVariantId): string | undefined {
+ const variants = this._data.getCurrent()?.variants;
+ if (!variants) return;
+ if (variantId) {
+ return variants.find((x) => variantId.compare(x))?.name;
+ } else {
+ return variants[0]?.name;
+ }
+ }
+
+ /**
+ * Set the name of a variant
+ * @param {string} name - The name of the variant
+ * @param {UmbVariantId} [variantId] - The variant id
+ * @memberof UmbContentDetailWorkspaceContextBase
+ */
+ public setName(name: string, variantId?: UmbVariantId): void {
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore
+ // TODO: fix type error
+ this._data.updateVariantData(variantId ?? UmbVariantId.CreateInvariant(), { name });
+ }
+
+ /**
+ * Get an observable for the name of a variant
+ * @param {UmbVariantId} [variantId] - The variant id
+ * @returns {Observable} - The name of the variant
+ * @memberof UmbContentDetailWorkspaceContextBase
+ */
+ public name(variantId?: UmbVariantId): Observable {
+ return this._data.createObservablePartOfCurrent(
+ (data) => data?.variants?.find((x) => variantId?.compare(x))?.name ?? '',
+ );
+ }
+
+ /* Variants */
+
+ /**
+ * Get whether the content varies by culture
+ * @returns { boolean | undefined } - If the content varies by culture
+ * @memberof UmbContentDetailWorkspaceContextBase
+ */
+ public getVariesByCulture(): boolean | undefined {
+ return this.#variesByCulture;
+ }
+
+ /**
+ * Get whether the content varies by segment
+ * @returns {boolean | undefined} - If the content varies by segment
+ * @memberof UmbContentDetailWorkspaceContextBase
+ */
+ public getVariesBySegment(): boolean | undefined {
+ return this.#variesBySegment;
+ }
+
+ /**
+ * Get whether the content varies
+ * @returns { boolean | undefined } - If the content varies
+ * @memberof UmbContentDetailWorkspaceContextBase
+ */
+ public getVaries(): boolean | undefined {
+ return this.#varies;
+ }
+
+ /**
+ * Get the variant by the given variantId
+ * @param {UmbVariantId} variantId - The variant id
+ * @returns { Observable } - The variant or undefined if not found
+ * @memberof UmbContentDetailWorkspaceContextBase
+ */
+ public variantById(variantId: UmbVariantId): Observable {
+ return this._data.createObservablePartOfCurrent((data) => data?.variants?.find((x) => variantId.compare(x)));
+ }
+
+ /**
+ * Get the variant by the given variantId
+ * @param {UmbVariantId} variantId - The variant id
+ * @returns { VariantModelType | undefined } - The variant or undefined if not found
+ * @memberof UmbContentDetailWorkspaceContextBase
+ */
+ public getVariant(variantId: UmbVariantId): VariantModelType | undefined {
+ return this._data.getCurrent()?.variants?.find((x) => variantId.compare(x));
+ }
+
+ /**
+ * Observe the property type
+ * @param {string} propertyId - The id of the property
+ * @returns {Promise>} - An observable for the property type
+ * @memberof UmbContentDetailWorkspaceContextBase
+ */
+ public async propertyStructureById(propertyId: string): Promise> {
+ return this.structure.propertyStructureById(propertyId);
+ }
+
+ /* Values */
+
+ /**
+ * Get the values of the content
+ * @returns {Array | undefined} - The values of the content
+ * @memberof UmbContentDetailWorkspaceContextBase
+ */
+ public getValues(): Array | undefined {
+ return this._data.getCurrent()?.values;
+ }
+
+ /**
+ * @function propertyValueByAlias
+ * @param {string} propertyAlias - The alias of the property
+ * @param {UmbVariantId} variantId - The variant
+ * @returns {Promise | undefined>} - An observable for the value of the property
+ * @description Get an Observable for the value of this property.
+ */
+ public async propertyValueByAlias(
+ propertyAlias: string,
+ variantId?: UmbVariantId,
+ ): Promise | undefined> {
+ return this._data.createObservablePartOfCurrent(
+ (data) =>
+ data?.values?.find((x) => x?.alias === propertyAlias && (variantId ? variantId.compare(x) : true))
+ ?.value as PropertyValueType,
+ );
+ }
+
+ /**
+ * Get the current value of the property with the given alias and variantId.
+ * @param {string} alias - The alias of the property
+ * @param {UmbVariantId | undefined} variantId - The variant id of the property
+ * @returns {ReturnType | undefined} The value or undefined if not set or found.
+ */
+ public getPropertyValue(alias: string, variantId?: UmbVariantId) {
+ const currentData = this._data.getCurrent();
+ if (currentData) {
+ const newDataSet = currentData.values?.find(
+ (x) => x.alias === alias && (variantId ? variantId.compare(x) : true),
+ );
+ return newDataSet?.value as ReturnType;
+ }
+ return undefined;
+ }
+
+ /**
+ * Set the value of the property with the given alias and variantId.
+ * @template ValueType
+ * @param {string} alias - The alias of the property
+ * @param {ValueType} value - The value to set
+ * @param {UmbVariantId} [variantId] - The variant id of the property
+ * @memberof UmbContentDetailWorkspaceContextBase
+ */
+ public async setPropertyValue(alias: string, value: ValueType, variantId?: UmbVariantId) {
+ this.initiatePropertyValueChange();
+ variantId ??= UmbVariantId.CreateInvariant();
+ const property = await this.structure.getPropertyStructureByAlias(alias);
+
+ if (!property) {
+ throw new Error(`Property alias "${alias}" not found.`);
+ }
+
+ const editorAlias = this.#dataTypeSchemaAliasMap.get(property.dataType.unique);
+ if (!editorAlias) {
+ throw new Error(`Editor Alias of "${property.dataType.unique}" not found.`);
+ }
+
+ // Notice the order of the properties is important for our JSON String Compare function. [NL]
+ const entry = { editorAlias, alias, ...variantId.toObject(), value } as UmbElementValueModel;
+
+ const currentData = this.getData();
+ if (currentData) {
+ const values = appendToFrozenArray(
+ currentData.values ?? [],
+ entry,
+ (x) => x.alias === alias && variantId!.compare(x),
+ );
+
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore
+ // TODO: fix type error
+ this._data.updateCurrent({ values });
+
+ // TODO: Ideally we should move this type of logic to the act of saving [NL]
+ this._data.ensureVariantData(variantId);
+ }
+ this.finishPropertyValueChange();
+ }
+
+ public initiatePropertyValueChange() {
+ this._data.initiatePropertyValueChange();
+ }
+
+ public finishPropertyValueChange = () => {
+ this._data.finishPropertyValueChange();
+ };
+
+ protected async _determineVariantOptions() {
+ const options = await firstValueFrom(this.variantOptions);
+
+ const activeVariants = this.splitView.getActiveVariants();
+ const activeVariantIds = activeVariants.map((activeVariant) => UmbVariantId.Create(activeVariant));
+ const changedVariantIds = this._data.getChangedVariants();
+ const selectedVariantIds = activeVariantIds.concat(changedVariantIds);
+
+ // Selected can contain entries that are not part of the options, therefor the modal filters selection based on options.
+ const readOnlyCultures = this.readOnlyState.getStates().map((s) => s.variantId.culture);
+ let selected = selectedVariantIds.map((x) => x.toString()).filter((v, i, a) => a.indexOf(v) === i);
+ selected = selected.filter((x) => readOnlyCultures.includes(x) === false);
+
+ return {
+ options,
+ selected,
+ };
+ }
+
+ protected _readOnlyLanguageVariantsFilter = (option: VariantOptionModelType) => {
+ const readOnlyCultures = this.readOnlyState.getStates().map((s) => s.variantId.culture);
+ return readOnlyCultures.includes(option.culture) === false;
+ };
+
+ /* validation */
+ protected async _runMandatoryValidationForSaveData(saveData: DetailModelType) {
+ // Check that the data is valid before we save it.
+ // Check variants have a name:
+ const variantsWithoutAName = saveData.variants.filter((x) => !x.name);
+ if (variantsWithoutAName.length > 0) {
+ const validationContext = await this.getContext(UMB_VALIDATION_CONTEXT);
+ variantsWithoutAName.forEach((variant) => {
+ validationContext.messages.addMessage(
+ 'client',
+ `$.variants[${UmbDataPathVariantQuery(variant)}].name`,
+ UMB_VALIDATION_EMPTY_LOCALIZATION_KEY,
+ );
+ });
+ throw new Error('All variants must have a name');
+ }
+ }
+
+ /**
+ * Request a submit of the workspace, in the case of Content Workspaces the validation does not need to be valid for this to be submitted.
+ * @returns {Promise} a promise which resolves once it has been completed.
+ */
+ public override requestSubmit() {
+ return this.#handleSubmit();
+ }
+
+ public override submit() {
+ return this.#handleSubmit();
+ }
+
+ // Because we do not make validation prevent submission this also submits the workspace. [NL]
+ public override invalidSubmit() {
+ return this.#handleSubmit();
+ }
+
+ async #handleSubmit() {
+ const data = this.getData();
+ if (!data) {
+ throw new Error('Data is missing');
+ }
+
+ const { options, selected } = await this._determineVariantOptions();
+
+ let variantIds: Array = [];
+
+ // If there is only one variant, we don't need to open the modal.
+ if (options.length === 0) {
+ throw new Error('No variants are available');
+ } else if (options.length === 1) {
+ // If only one option we will skip ahead and save the content with the only variant available:
+ variantIds.push(UmbVariantId.Create(options[0]));
+ } else if (this.#saveModalToken) {
+ // If there are multiple variants, we will open the modal to let the user pick which variants to save.
+ const modalManagerContext = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
+ const result = await modalManagerContext
+ .open(this, this.#saveModalToken, {
+ data: {
+ options,
+ pickableFilter: this._readOnlyLanguageVariantsFilter,
+ },
+ value: { selection: selected },
+ })
+ .onSubmit()
+ .catch(() => undefined);
+
+ if (!result?.selection.length) return;
+
+ variantIds = result?.selection.map((x) => UmbVariantId.FromString(x)) ?? [];
+ } else {
+ throw new Error('No variant picker modal token is set. There are multiple variants to save. Cannot proceed.');
+ }
+
+ const saveData = await this._data.constructData(variantIds);
+ await this._runMandatoryValidationForSaveData(saveData);
+ await this._performCreateOrUpdate(variantIds, saveData);
+ }
+
+ protected async _performCreateOrUpdate(variantIds: Array, saveData: DetailModelType) {
+ if (this.getIsNew()) {
+ await this.#create(variantIds, saveData);
+ } else {
+ await this.#update(variantIds, saveData);
+ }
+ }
+
+ async #create(variantIds: Array, saveData: DetailModelType) {
+ if (!this._detailRepository) throw new Error('Detail repository is not set');
+
+ const parent = this.getParent();
+ if (!parent) throw new Error('Parent is not set');
+
+ const { data, error } = await this._detailRepository.create(saveData, parent.unique);
+ if (!data || error) {
+ throw new Error('Error creating content');
+ }
+
+ this._data.setPersisted(data);
+
+ const currentData = this._data.getCurrent();
+
+ const variantIdsIncludingInvariant = [...variantIds, UmbVariantId.CreateInvariant()];
+
+ // Retrieve a data set which only contains updates from the selected variants + invariant. [NL]
+ const newCurrentData = await new UmbMergeContentVariantDataController(this).process(
+ currentData,
+ data,
+ variantIds,
+ variantIdsIncludingInvariant,
+ );
+
+ this._data.setCurrent(newCurrentData);
+
+ const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
+ const event = new UmbRequestReloadChildrenOfEntityEvent({
+ entityType: parent.entityType,
+ unique: parent.unique,
+ });
+ eventContext.dispatchEvent(event);
+ this.setIsNew(false);
+ }
+
+ async #update(variantIds: Array, saveData: DetailModelType) {
+ if (!this._detailRepository) throw new Error('Detail repository is not set');
+
+ const { data, error } = await this._detailRepository.save(saveData);
+ if (!data || error) {
+ throw new Error('Error saving content');
+ }
+
+ this._data.setPersisted(data);
+ // TODO: Only update the variants that was chosen to be saved:
+ const currentData = this._data.getCurrent();
+
+ const variantIdsIncludingInvariant = [...variantIds, UmbVariantId.CreateInvariant()];
+
+ const newCurrentData = await new UmbMergeContentVariantDataController(this).process(
+ currentData,
+ data,
+ variantIds,
+ variantIdsIncludingInvariant,
+ );
+ this._data.setCurrent(newCurrentData);
+
+ const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
+ const event = new UmbRequestReloadStructureForEntityEvent({
+ entityType: this.getEntityType(),
+ unique: this.getUnique()!,
+ });
+
+ eventContext.dispatchEvent(event);
+ }
+
+ abstract getContentTypeUnique(): string | undefined;
+
+ abstract createPropertyDatasetContext(
+ host: UmbControllerHost,
+ variantId: UmbVariantId,
+ ): UmbContentPropertyDatasetContext;
+
+ public override destroy(): void {
+ this.structure.destroy();
+ this.#languageRepository.destroy();
+ super.destroy();
+ }
+}
diff --git a/src/packages/core/content/workspace/content-workspace-context.interface.ts b/src/packages/core/content/workspace/content-workspace-context.interface.ts
index ecaeca41a7..ba1b17c59c 100644
--- a/src/packages/core/content/workspace/content-workspace-context.interface.ts
+++ b/src/packages/core/content/workspace/content-workspace-context.interface.ts
@@ -1,4 +1,5 @@
-import type { UmbContentDetailModel, UmbElementPropertyDataOwner } from '@umbraco-cms/backoffice/content';
+import type { UmbContentDetailModel } from '../types.js';
+import type { UmbElementPropertyDataOwner } from '../property-dataset-context/index.js';
import type { UmbContentTypeModel } from '@umbraco-cms/backoffice/content-type';
import type { Observable } from '@umbraco-cms/backoffice/external/rxjs';
import type { UmbVariantId, UmbEntityVariantModel } from '@umbraco-cms/backoffice/variant';
diff --git a/src/packages/core/content/workspace/index.ts b/src/packages/core/content/workspace/index.ts
index 4c72bcced7..2d6e425ec3 100644
--- a/src/packages/core/content/workspace/index.ts
+++ b/src/packages/core/content/workspace/index.ts
@@ -1,3 +1,4 @@
-export type * from './content-workspace-context.interface.js';
+export * from './content-detail-workspace-base.js';
export * from './content-workspace.context-token.js';
export * from './views/edit/index.js';
+export type * from './content-workspace-context.interface.js';
diff --git a/src/packages/core/entity-action/global-components/entity-actions-table-column-view/entity-actions-table-column-view.element.ts b/src/packages/core/entity-action/global-components/entity-actions-table-column-view/entity-actions-table-column-view.element.ts
new file mode 100644
index 0000000000..453664f142
--- /dev/null
+++ b/src/packages/core/entity-action/global-components/entity-actions-table-column-view/entity-actions-table-column-view.element.ts
@@ -0,0 +1,41 @@
+import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity';
+import { html, nothing, customElement, property, state } from '@umbraco-cms/backoffice/external/lit';
+import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
+
+const elementName = 'umb-entity-actions-table-column-view';
+@customElement(elementName)
+export class UmbEntityActionsTableColumnViewElement extends UmbLitElement {
+ @property({ attribute: false })
+ value?: UmbEntityModel;
+
+ @state()
+ _isOpen = false;
+
+ #onActionExecuted() {
+ this._isOpen = false;
+ }
+
+ #onClick(event: Event) {
+ event.stopPropagation();
+ }
+
+ override render() {
+ if (!this.value) return nothing;
+
+ return html`
+
+
+
+
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ [elementName]: UmbEntityActionsTableColumnViewElement;
+ }
+}
diff --git a/src/packages/core/entity-action/global-components/index.ts b/src/packages/core/entity-action/global-components/index.ts
new file mode 100644
index 0000000000..841ca85af1
--- /dev/null
+++ b/src/packages/core/entity-action/global-components/index.ts
@@ -0,0 +1 @@
+import './entity-actions-table-column-view/entity-actions-table-column-view.element.js';
diff --git a/src/packages/core/entity-action/index.ts b/src/packages/core/entity-action/index.ts
index 60881da438..bf8c1a0d9b 100644
--- a/src/packages/core/entity-action/index.ts
+++ b/src/packages/core/entity-action/index.ts
@@ -1,3 +1,5 @@
+import './global-components/index.js';
+
export * from './common/index.js';
export * from './default/index.js';
export * from './entity-action-base.js';
diff --git a/src/packages/core/localization/manifests.ts b/src/packages/core/localization/manifests.ts
index 576c677f65..e8741a538a 100644
--- a/src/packages/core/localization/manifests.ts
+++ b/src/packages/core/localization/manifests.ts
@@ -61,6 +61,16 @@ export const manifests: Array = [
},
js: () => import('../../../assets/lang/de-de.js'),
},
+ {
+ type: 'localization',
+ alias: 'Umb.Localization.De-CH',
+ weight: -100,
+ name: 'Deutsch (Schweiz)',
+ meta: {
+ culture: 'de-ch',
+ },
+ js: () => import('../../../assets/lang/de-ch.js'),
+ },
{
type: 'localization',
alias: 'Umb.Localization.En-GB',
@@ -101,6 +111,16 @@ export const manifests: Array = [
},
js: () => import('../../../assets/lang/fr-fr.js'),
},
+ {
+ type: 'localization',
+ alias: 'Umb.Localization.Fr-CH',
+ weight: -100,
+ name: 'Français (Suisse)',
+ meta: {
+ culture: 'fr-ch',
+ },
+ js: () => import('../../../assets/lang/fr-ch.js'),
+ },
{
type: 'localization',
alias: 'Umb.Localization.He-IL',
@@ -131,6 +151,16 @@ export const manifests: Array = [
},
js: () => import('../../../assets/lang/it-it.js'),
},
+ {
+ type: 'localization',
+ alias: 'Umb.Localization.It-CH',
+ weight: -100,
+ name: 'Italiano (Svizerra)',
+ meta: {
+ culture: 'it-ch',
+ },
+ js: () => import('../../../assets/lang/it-ch.js'),
+ },
{
type: 'localization',
alias: 'Umb.Localization.Ja-JP',
diff --git a/src/packages/core/modal/component/modal.element.ts b/src/packages/core/modal/component/modal.element.ts
index 2a52a72b50..a192488d3a 100644
--- a/src/packages/core/modal/component/modal.element.ts
+++ b/src/packages/core/modal/component/modal.element.ts
@@ -14,10 +14,14 @@ import {
type UUIModalDialogElement,
type UUIModalSidebarElement,
} from '@umbraco-cms/backoffice/external/uui';
-import type { UmbRouterSlotElement } from '@umbraco-cms/backoffice/router';
+import { UMB_ROUTE_CONTEXT, type UmbRouterSlotElement } from '@umbraco-cms/backoffice/router';
import { createExtensionElement, loadManifestElement } from '@umbraco-cms/backoffice/extension-api';
import type { UmbContextRequestEvent } from '@umbraco-cms/backoffice/context-api';
-import { UMB_CONTENT_REQUEST_EVENT_TYPE, UmbContextProvider } from '@umbraco-cms/backoffice/context-api';
+import {
+ UMB_CONTEXT_REQUEST_EVENT_TYPE,
+ UmbContextBoundary,
+ UmbContextProvider,
+} from '@umbraco-cms/backoffice/context-api';
@customElement('umb-modal')
export class UmbModalElement extends UmbLitElement {
@@ -33,7 +37,6 @@ export class UmbModalElement extends UmbLitElement {
this.destroy();
return;
}
-
}
public element?: UUIModalDialogElement | UUIModalSidebarElement | UUIModalElement;
@@ -41,7 +44,7 @@ export class UmbModalElement extends UmbLitElement {
#innerElement = new UmbBasicState(undefined);
#modalExtensionObserver?: UmbObserverController;
- #modalRouterElement: UmbRouterSlotElement = document.createElement('umb-router-slot');
+ #modalRouterElement?: HTMLDivElement | UmbRouterSlotElement;
#onClose = () => {
this.element?.removeEventListener(UUIModalCloseEvent, this.#onClose);
@@ -59,7 +62,7 @@ export class UmbModalElement extends UmbLitElement {
// The following code is the context api proxy.
// It re-dispatches the context api request event to the origin target of this modal, in other words the element that initiated the modal. [NL]
- this.element.addEventListener(UMB_CONTENT_REQUEST_EVENT_TYPE, ((event: UmbContextRequestEvent) => {
+ this.element.addEventListener(UMB_CONTEXT_REQUEST_EVENT_TYPE, ((event: UmbContextRequestEvent) => {
if (!this.#modalContext) return;
// Note for this hack (The if-sentence): [NL]
// We do not currently have a good enough control to ensure that the proxy is last, meaning if another context is provided at this element, it might respond after the proxy event has been dispatched.
@@ -85,6 +88,7 @@ export class UmbModalElement extends UmbLitElement {
*
*/
if (this.#modalContext.router) {
+ this.#modalRouterElement = document.createElement('umb-router-slot');
this.#modalRouterElement.routes = [
{
path: '',
@@ -92,9 +96,16 @@ export class UmbModalElement extends UmbLitElement {
},
];
this.#modalRouterElement.parent = this.#modalContext.router;
+ } else {
+ this.#modalRouterElement = document.createElement('div');
+ // Notice inline styling here is used cause the element is not appended into this elements shadowDom but outside and there by gets into the element via a slot.
+ this.#modalRouterElement.style.position = 'relative';
+ this.#modalRouterElement.style.height = '100%';
+ new UmbContextBoundary(this.#modalRouterElement, UMB_ROUTE_CONTEXT).hostConnected();
}
this.element.appendChild(this.#modalRouterElement);
+
this.#observeModal(this.#modalContext.alias.toString());
const provider = new UmbContextProvider(this.element, UMB_MODAL_CONTEXT, this.#modalContext);
@@ -102,10 +113,8 @@ export class UmbModalElement extends UmbLitElement {
}
async #createContainerElement() {
-
- if(this.#modalContext!.type == 'custom' && this.#modalContext?.element)
- {
- var customWrapperElementCtor = await loadManifestElement(this.#modalContext.element);
+ if (this.#modalContext!.type == 'custom' && this.#modalContext?.element) {
+ const customWrapperElementCtor = await loadManifestElement(this.#modalContext.element);
return new customWrapperElementCtor!();
}
@@ -158,14 +167,14 @@ export class UmbModalElement extends UmbLitElement {
}
#appendInnerElement(element: HTMLElement) {
- this.#modalRouterElement.appendChild(element);
+ this.#modalRouterElement!.appendChild(element);
this.#innerElement.setValue(element);
}
#removeInnerElement() {
const innerElement = this.#innerElement.getValue();
if (innerElement) {
- this.#modalRouterElement.removeChild(innerElement);
+ this.#modalRouterElement!.removeChild(innerElement);
this.#innerElement.setValue(undefined);
}
}
diff --git a/src/packages/core/property-type/workspace/property-type-workspace.context.ts b/src/packages/core/property-type/workspace/property-type-workspace.context.ts
index b9bcfe9751..20afdc3bb3 100644
--- a/src/packages/core/property-type/workspace/property-type-workspace.context.ts
+++ b/src/packages/core/property-type/workspace/property-type-workspace.context.ts
@@ -72,7 +72,7 @@ export class UmbPropertyTypeWorkspaceContext {
+ new (host: UmbControllerHost): UmbDetailRepository;
+}
+
export interface UmbDetailRepository extends UmbReadDetailRepository, UmbApi {
createScaffold(preset?: Partial): Promise>;
create(data: DetailModelType, parentUnique: string | null): Promise>;
diff --git a/src/packages/core/repository/detail/index.ts b/src/packages/core/repository/detail/index.ts
index f190db5763..3e13f2e154 100644
--- a/src/packages/core/repository/detail/index.ts
+++ b/src/packages/core/repository/detail/index.ts
@@ -9,3 +9,5 @@ export type { UmbReadDetailRepository } from './read/read-detail-repository.inte
export type { UmbDetailDataSource, UmbDetailDataSourceConstructor } from './detail-data-source.interface.js';
export { UmbDetailRepositoryBase } from './detail-repository-base.js';
export type { UmbDetailRepository } from './detail-repository.interface.js';
+
+export * from './detail-repository.interface.js';
diff --git a/src/packages/core/router/modal-registration/modal-route-registration.controller.ts b/src/packages/core/router/modal-registration/modal-route-registration.controller.ts
index d487de0fff..1ee307a27a 100644
--- a/src/packages/core/router/modal-registration/modal-route-registration.controller.ts
+++ b/src/packages/core/router/modal-registration/modal-route-registration.controller.ts
@@ -259,9 +259,9 @@ export class UmbModalRouteRegistrationController<
}
public open(params: { [key: string]: string | number }, prepend?: string) {
- if (this.active) return;
+ if (this.active || !this.#routeBuilder) return;
- window.history.pushState({}, '', this.#routeBuilder?.(params) + (prepend ? `${prepend}` : ''));
+ window.history.pushState({}, '', this.#routeBuilder(params) + (prepend ? `${prepend}` : ''));
}
/**
@@ -277,6 +277,7 @@ export class UmbModalRouteRegistrationController<
return this;
}
public _internal_setRouteBuilder(urlBuilder: UmbModalRouteBuilder) {
+ if (!this.#routeContext) return;
this.#routeBuilder = urlBuilder;
this.#urlBuilderCallback?.(urlBuilder);
}
diff --git a/src/packages/core/router/route.context.ts b/src/packages/core/router/route.context.ts
index 7fda170903..f8f15291d4 100644
--- a/src/packages/core/router/route.context.ts
+++ b/src/packages/core/router/route.context.ts
@@ -123,7 +123,7 @@ export class UmbRouteContext extends UmbContextBase {
if (this.#activeModalPath) {
// If if there is a modal using the old path.
const activeModal = this.#modalRegistrations.find((registration) => {
- return registration.generateModalPath() === this.#activeModalPath;
+ return '/' + registration.generateModalPath() === this.#activeModalPath;
});
if (activeModal) {
this.#modalContext?.close(activeModal.key);
diff --git a/src/packages/core/router/router-slot.element.ts b/src/packages/core/router/router-slot.element.ts
index 6b45549d73..a78f0e2ac6 100644
--- a/src/packages/core/router/router-slot.element.ts
+++ b/src/packages/core/router/router-slot.element.ts
@@ -94,9 +94,7 @@ export class UmbRouterSlotElement extends UmbLitElement {
protected override firstUpdated(_changedProperties: PropertyValueMap | Map): void {
super.firstUpdated(_changedProperties);
- this._routerPath = this._constructAbsoluteRouterPath();
- this.#routeContext._internal_routerGotBasePath(this._routerPath);
- this.dispatchEvent(new UmbRouterSlotInitEvent());
+ this._updateRouterPath();
}
protected _updateRouterPath() {
@@ -124,7 +122,7 @@ export class UmbRouterSlotElement extends UmbLitElement {
this.dispatchEvent(new UmbRouterSlotChangeEvent());
}
} else if (event.detail.slot === this.#modalRouter) {
- const newActiveModalLocalPath = this.#modalRouter.match?.fragments.consumed ?? '';
+ const newActiveModalLocalPath = this.#modalRouter.match?.route.path ?? '';
this.#routeContext._internal_modalRouterChanged(newActiveModalLocalPath);
}
};
diff --git a/src/packages/core/validation/controllers/bind-server-validation-to-form-control.controller.ts b/src/packages/core/validation/controllers/bind-server-validation-to-form-control.controller.ts
index e2951a2b78..5410e8caa0 100644
--- a/src/packages/core/validation/controllers/bind-server-validation-to-form-control.controller.ts
+++ b/src/packages/core/validation/controllers/bind-server-validation-to-form-control.controller.ts
@@ -12,7 +12,6 @@ const observeSymbol = Symbol();
* This controller will add a custom error to the form control if the validation context has any messages for the specified data path.
*/
export class UmbBindServerValidationToFormControl extends UmbControllerBase {
-
#context?: typeof UMB_VALIDATION_CONTEXT.TYPE;
#control: UmbFormControlMixinInterface;
@@ -41,7 +40,7 @@ export class UmbBindServerValidationToFormControl extends UmbControllerBase {
}
constructor(host: UmbControllerHost, formControl: UmbFormControlMixinInterface, dataPath: string) {
- super(host,'umbFormControlValidation_'+simpleHashCode(dataPath));
+ super(host, 'umbFormControlValidation_' + simpleHashCode(dataPath));
this.#control = formControl;
this.consumeContext(UMB_VALIDATION_CONTEXT, (context) => {
this.#context = context;
diff --git a/src/packages/core/validation/controllers/validation.controller.ts b/src/packages/core/validation/controllers/validation.controller.ts
index feaf76897a..e535dc933d 100644
--- a/src/packages/core/validation/controllers/validation.controller.ts
+++ b/src/packages/core/validation/controllers/validation.controller.ts
@@ -79,13 +79,16 @@ export class UmbValidationController extends UmbControllerBase implements UmbVal
this.messages.removeTranslator(translator);
}
+ #currentProvideHost?: UmbClassInterface;
/**
* Provide this validation context to a specific controller host.
* This can be used to Host a validation context in a Workspace, but provide it on a certain scope, like a specific Workspace View.
* @param controllerHost {UmbClassInterface}
*/
provideAt(controllerHost: UmbClassInterface): void {
+ if (this.#currentProvideHost === controllerHost) return;
this.#providerCtrl?.destroy();
+ this.#currentProvideHost = controllerHost;
this.#providerCtrl = controllerHost.provideContext(UMB_VALIDATION_CONTEXT, this);
}
diff --git a/src/packages/core/validation/translators/abstract-array-path-translator.controller.ts b/src/packages/core/validation/translators/abstract-array-path-translator.controller.ts
index 121310af55..4e7200a340 100644
--- a/src/packages/core/validation/translators/abstract-array-path-translator.controller.ts
+++ b/src/packages/core/validation/translators/abstract-array-path-translator.controller.ts
@@ -1,12 +1,17 @@
import { UmbValidationPathTranslatorBase } from './validation-path-translator-base.controller.js';
-import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
+import type { UmbControllerAlias, UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
export abstract class UmbAbstractArrayValidationPathTranslator extends UmbValidationPathTranslatorBase {
#initialPathToMatch: string;
#queryMethod: (data: unknown) => string;
- constructor(host: UmbControllerHost, initialPathToMatch: string, queryMethod: (data: any) => string) {
- super(host);
+ constructor(
+ host: UmbControllerHost,
+ initialPathToMatch: string,
+ queryMethod: (data: any) => string,
+ ctrlAlias?: UmbControllerAlias,
+ ) {
+ super(host, ctrlAlias);
this.#initialPathToMatch = initialPathToMatch;
this.#queryMethod = queryMethod;
diff --git a/src/packages/core/validation/translators/validation-path-translator-base.controller.ts b/src/packages/core/validation/translators/validation-path-translator-base.controller.ts
index 27dda12276..b9a9bacb8e 100644
--- a/src/packages/core/validation/translators/validation-path-translator-base.controller.ts
+++ b/src/packages/core/validation/translators/validation-path-translator-base.controller.ts
@@ -1,6 +1,6 @@
import { UMB_VALIDATION_CONTEXT } from '../index.js';
import type { UmbValidationMessageTranslator } from './validation-message-path-translator.interface.js';
-import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
+import type { UmbControllerAlias, UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
export abstract class UmbValidationPathTranslatorBase
@@ -10,8 +10,8 @@ export abstract class UmbValidationPathTranslatorBase
//
protected _context?: typeof UMB_VALIDATION_CONTEXT.TYPE;
- constructor(host: UmbControllerHost) {
- super(host);
+ constructor(host: UmbControllerHost, ctrlAlias?: UmbControllerAlias) {
+ super(host, ctrlAlias);
this.consumeContext(UMB_VALIDATION_CONTEXT, (context) => {
this._context?.removeTranslator(this);
diff --git a/src/packages/core/workspace/components/workspace-split-view/index.ts b/src/packages/core/workspace/components/workspace-split-view/index.ts
index 1312849754..509b219054 100644
--- a/src/packages/core/workspace/components/workspace-split-view/index.ts
+++ b/src/packages/core/workspace/components/workspace-split-view/index.ts
@@ -1,2 +1,3 @@
export * from './workspace-split-view.context.js';
export * from './workspace-split-view.element.js';
+export * from './workspace-split-view-variant-selector.element.js';
diff --git a/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts b/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts
index f32f98bc68..efbce1d52c 100644
--- a/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts
+++ b/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts
@@ -5,29 +5,43 @@ import {
UUIInputEvent,
type UUIPopoverContainerElement,
} from '@umbraco-cms/backoffice/external/uui';
-import { css, html, nothing, customElement, state, query, ifDefined } from '@umbraco-cms/backoffice/external/lit';
-import { DocumentVariantStateModel } from '@umbraco-cms/backoffice/external/backend-api';
-import type { UmbDocumentVariantOptionModel, UmbDocumentWorkspaceContext } from '@umbraco-cms/backoffice/document';
-import { UmbVariantId } from '@umbraco-cms/backoffice/variant';
+import {
+ css,
+ html,
+ nothing,
+ customElement,
+ state,
+ query,
+ ifDefined,
+ type TemplateResult,
+} from '@umbraco-cms/backoffice/external/lit';
+import {
+ UmbVariantId,
+ type UmbEntityVariantModel,
+ type UmbEntityVariantOptionModel,
+} from '@umbraco-cms/backoffice/variant';
import { UMB_PROPERTY_DATASET_CONTEXT, isNameablePropertyDatasetContext } from '@umbraco-cms/backoffice/property';
import { UmbLitElement, umbFocus } from '@umbraco-cms/backoffice/lit-element';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import type { UmbVariantState } from '@umbraco-cms/backoffice/utils';
import { UmbDataPathVariantQuery, umbBindToValidation } from '@umbraco-cms/backoffice/validation';
+import type { UmbContentWorkspaceContext } from '@umbraco-cms/backoffice/content';
const elementName = 'umb-workspace-split-view-variant-selector';
@customElement(elementName)
-export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement {
+export class UmbWorkspaceSplitViewVariantSelectorElement<
+ VariantOptionModelType extends
+ UmbEntityVariantOptionModel = UmbEntityVariantOptionModel,
+> extends UmbLitElement {
@query('#variant-selector-popover')
private _popoverElement?: UUIPopoverContainerElement;
@state()
- private _variantOptions: Array = [];
+ private _variantOptions: Array = [];
@state()
private _readOnlyStates: Array = [];
- // TODO: Stop using document context specific ActiveVariant type.
@state()
_activeVariants: Array = [];
@@ -41,7 +55,7 @@ export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement {
private _name?: string;
@state()
- private _activeVariant?: UmbDocumentVariantOptionModel;
+ private _activeVariant?: VariantOptionModelType;
@state()
private _variantId?: UmbVariantId;
@@ -52,11 +66,9 @@ export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement {
@state()
private _readOnlyCultures: string[] = [];
- #publishStateLocalizationMap = {
- [DocumentVariantStateModel.DRAFT]: 'content_unpublished',
- [DocumentVariantStateModel.PUBLISHED]: 'content_published',
- [DocumentVariantStateModel.PUBLISHED_PENDING_CHANGES]: 'content_publishedPendingChanges',
- [DocumentVariantStateModel.NOT_CREATED]: 'content_notCreated',
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ protected _variantSorter = (a: VariantOptionModelType, b: VariantOptionModelType) => {
+ return 0;
};
constructor() {
@@ -65,9 +77,7 @@ export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement {
this.consumeContext(UMB_WORKSPACE_SPLIT_VIEW_CONTEXT, (instance) => {
this.#splitViewContext = instance;
- // NOTICE: This is hacky (the TypeScript casting), we can only accept doing this so far because we currently only use the Variant Selector on Document Workspace. [NL]
- // This would need a refactor to enable the code below to work with different ContentTypes. Main problem here is the state, which is not generic for them all. [NL]
- const workspaceContext = this.#splitViewContext.getWorkspaceContext() as unknown as UmbDocumentWorkspaceContext;
+ const workspaceContext = this.#splitViewContext.getWorkspaceContext() as unknown as UmbContentWorkspaceContext;
if (!workspaceContext) throw new Error('Split View Workspace context not found');
this.#observeVariants(workspaceContext);
@@ -83,18 +93,18 @@ export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement {
});
}
- async #observeVariants(workspaceContext: UmbDocumentWorkspaceContext) {
+ async #observeVariants(workspaceContext: UmbContentWorkspaceContext) {
this.observe(
workspaceContext.variantOptions,
(variantOptions) => {
- this._variantOptions = variantOptions;
+ this._variantOptions = (variantOptions as Array).sort(this._variantSorter);
this.#setReadOnlyCultures();
},
'_observeVariantOptions',
);
}
- async #observeReadOnlyStates(workspaceContext: UmbDocumentWorkspaceContext) {
+ async #observeReadOnlyStates(workspaceContext: UmbContentWorkspaceContext) {
this.observe(
workspaceContext.readOnlyState.states,
(states) => {
@@ -105,7 +115,7 @@ export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement {
);
}
- async #observeActiveVariants(workspaceContext: UmbDocumentWorkspaceContext) {
+ async #observeActiveVariants(workspaceContext: UmbContentWorkspaceContext) {
this.observe(
workspaceContext.splitView.activeVariantsInfo,
(activeVariants) => {
@@ -131,7 +141,7 @@ export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement {
async #observeCurrentVariant() {
if (!this.#datasetContext || !this.#splitViewContext) return;
- const workspaceContext = this.#splitViewContext.getWorkspaceContext() as unknown as UmbDocumentWorkspaceContext;
+ const workspaceContext = this.#splitViewContext.getWorkspaceContext() as unknown as UmbContentWorkspaceContext;
if (!workspaceContext) return;
this._variantId = this.#datasetContext.getVariantId();
@@ -140,7 +150,7 @@ export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement {
workspaceContext.variantOptions,
(options) => {
const option = options.find((option) => option.language.unique === this._variantId?.culture);
- this._activeVariant = option;
+ this._activeVariant = option as VariantOptionModelType;
},
'_currentLanguage',
);
@@ -160,11 +170,11 @@ export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement {
}
}
- #switchVariant(variant: UmbDocumentVariantOptionModel) {
+ #switchVariant(variant: VariantOptionModelType) {
this.#splitViewContext?.switchVariant(UmbVariantId.Create(variant));
}
- #openSplitView(variant: UmbDocumentVariantOptionModel) {
+ #openSplitView(variant: VariantOptionModelType) {
this.#splitViewContext?.openSplitView(UmbVariantId.Create(variant));
}
@@ -176,7 +186,7 @@ export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement {
return culture !== null ? this._activeVariantsCultures.includes(culture) : true;
}
- #isCreateMode(variantOption: UmbDocumentVariantOptionModel) {
+ #isCreateMode(variantOption: VariantOptionModelType) {
return !variantOption.variant && !this.#isVariantActive(variantOption.culture);
}
@@ -231,7 +241,8 @@ export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement {
compact
slot="append"
popovertarget="variant-selector-popover"
- title=${ifDefined(this._activeVariant?.language.name)}>
+ title=${ifDefined(this._activeVariant?.language.name)}
+ label="Select a variant">
${this._activeVariant?.language.name} ${this.#renderReadOnlyTag(this._activeVariant?.culture)}
@@ -270,7 +281,7 @@ export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement {
: nothing;
}
- #renderListItem(variantOption: UmbDocumentVariantOptionModel) {
+ #renderListItem(variantOption: VariantOptionModelType) {
return html`