diff --git a/src/Umbraco.Web.UI.Client/.storybook/main.js b/src/Umbraco.Web.UI.Client/.storybook/main.js
index 283705d121..2dda838ca8 100644
--- a/src/Umbraco.Web.UI.Client/.storybook/main.js
+++ b/src/Umbraco.Web.UI.Client/.storybook/main.js
@@ -1,6 +1,6 @@
module.exports = {
stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
- addons: ['@storybook/addon-links', '@storybook/addon-essentials'],
+ addons: ['@storybook/addon-links', '@storybook/addon-essentials', '@storybook/addon-a11y'],
framework: '@storybook/web-components',
core: {
builder: '@storybook/builder-vite',
diff --git a/src/Umbraco.Web.UI.Client/.storybook/preview.js b/src/Umbraco.Web.UI.Client/.storybook/preview.js
index 6fac879af1..84d7f64230 100644
--- a/src/Umbraco.Web.UI.Client/.storybook/preview.js
+++ b/src/Umbraco.Web.UI.Client/.storybook/preview.js
@@ -1,6 +1,17 @@
import '@umbraco-ui/uui';
import '@umbraco-ui/uui-css/dist/uui-css.css';
+import { initialize, mswDecorator } from 'msw-storybook-addon';
+
+import { onUnhandledRequest } from '../src/mocks/browser';
+import { handlers } from '../src/mocks/handlers';
+
+// Initialize MSW
+initialize({onUnhandledRequest});
+
+// Provide the MSW addon decorator globally
+export const decorators = [mswDecorator];
+
export const parameters = {
actions: { argTypesRegex: '^on.*' },
controls: {
@@ -10,4 +21,9 @@ export const parameters = {
date: /Date$/,
},
},
+ msw: {
+ handlers: {
+ global: handlers
+ }
+ }
};
diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json
index 0fe25ed30e..6395c8a501 100644
--- a/src/Umbraco.Web.UI.Client/package-lock.json
+++ b/src/Umbraco.Web.UI.Client/package-lock.json
@@ -19,6 +19,7 @@
"devDependencies": {
"@babel/core": "^7.18.10",
"@open-wc/testing": "^3.1.6",
+ "@storybook/addon-a11y": "^6.5.9",
"@storybook/addon-actions": "^6.5.9",
"@storybook/addon-essentials": "^6.5.9",
"@storybook/addon-links": "^6.5.9",
@@ -41,6 +42,7 @@
"eslint-plugin-storybook": "^0.6.2",
"lit-html": "^2.2.7",
"msw": "^0.44.2",
+ "msw-storybook-addon": "^1.6.3",
"prettier": "2.7.1",
"typescript": "^4.7.4",
"vite": "^3.0.3"
@@ -2828,6 +2830,46 @@
"node": ">= 8.0.0"
}
},
+ "node_modules/@storybook/addon-a11y": {
+ "version": "6.5.9",
+ "resolved": "https://registry.npmjs.org/@storybook/addon-a11y/-/addon-a11y-6.5.9.tgz",
+ "integrity": "sha512-jRiuJ2xlN8quVq2lOqpxqyuwAj8xLcgVBPy+Mf220u7AZmmbS/0sONyHKROfEBjJoHQAQYqn2vSAeuQZuTWyVA==",
+ "dev": true,
+ "dependencies": {
+ "@storybook/addons": "6.5.9",
+ "@storybook/api": "6.5.9",
+ "@storybook/channels": "6.5.9",
+ "@storybook/client-logger": "6.5.9",
+ "@storybook/components": "6.5.9",
+ "@storybook/core-events": "6.5.9",
+ "@storybook/csf": "0.0.2--canary.4566f4d.1",
+ "@storybook/theming": "6.5.9",
+ "axe-core": "^4.2.0",
+ "core-js": "^3.8.2",
+ "global": "^4.4.0",
+ "lodash": "^4.17.21",
+ "react-sizeme": "^3.0.1",
+ "regenerator-runtime": "^0.13.7",
+ "ts-dedent": "^2.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/storybook"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "react": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@storybook/addon-actions": {
"version": "6.5.9",
"resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-6.5.9.tgz",
@@ -8280,6 +8322,12 @@
}
]
},
+ "node_modules/batch-processor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/batch-processor/-/batch-processor-1.0.0.tgz",
+ "integrity": "sha512-xoLQD8gmmR32MeuBHgH0Tzd5PuSZx71ZsbhVxOCRbgktZEPe4SQy7s9Z50uPp0F/f7iw2XmkHN2xkgbMfckMDA==",
+ "dev": true
+ },
"node_modules/better-opn": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/better-opn/-/better-opn-2.1.1.tgz",
@@ -11120,6 +11168,15 @@
"resolved": "https://registry.npmjs.org/element-internals-polyfill/-/element-internals-polyfill-1.1.6.tgz",
"integrity": "sha512-PW63CzI6GKewCYfX7hZ5gFJO5uib+oAy/AwELrOEfyBI3SlYOXFjVhDEIxlg0CV4kIa4ixjO+5XFhP/Cx1Lurw=="
},
+ "node_modules/element-resize-detector": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/element-resize-detector/-/element-resize-detector-1.2.4.tgz",
+ "integrity": "sha512-Fl5Ftk6WwXE0wqCgNoseKWndjzZlDCwuPTcoVZfCP9R3EHQF8qUtr3YUPNETegRBOKqQKPW3n4kiIWngGi8tKg==",
+ "dev": true,
+ "dependencies": {
+ "batch-processor": "1.0.0"
+ }
+ },
"node_modules/elliptic": {
"version": "6.5.4",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
@@ -14828,6 +14885,15 @@
"node": ">= 0.10"
}
},
+ "node_modules/invariant": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
+ "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
+ "dev": true,
+ "dependencies": {
+ "loose-envify": "^1.0.0"
+ }
+ },
"node_modules/ip": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz",
@@ -17243,6 +17309,19 @@
}
}
},
+ "node_modules/msw-storybook-addon": {
+ "version": "1.6.3",
+ "resolved": "https://registry.npmjs.org/msw-storybook-addon/-/msw-storybook-addon-1.6.3.tgz",
+ "integrity": "sha512-Ps80WdRmXsmenoTwfrgKMNpQD8INUUFyUFyZOecx8QjuqSlL++UYrLaGyACXN2goOn+/VS6rb0ZapbjrasPClg==",
+ "dev": true,
+ "dependencies": {
+ "@storybook/addons": "^6.0.0",
+ "is-node-process": "^1.0.1"
+ },
+ "peerDependencies": {
+ "msw": ">=0.35.0 <1.0.0"
+ }
+ },
"node_modules/msw/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@@ -19395,6 +19474,18 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-sizeme": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/react-sizeme/-/react-sizeme-3.0.2.tgz",
+ "integrity": "sha512-xOIAOqqSSmKlKFJLO3inBQBdymzDuXx4iuwkNcJmC96jeiOg5ojByvL+g3MW9LPEsojLbC6pf68zOfobK8IPlw==",
+ "dev": true,
+ "dependencies": {
+ "element-resize-detector": "^1.2.2",
+ "invariant": "^2.2.4",
+ "shallowequal": "^1.1.0",
+ "throttle-debounce": "^3.0.1"
+ }
+ },
"node_modules/react-syntax-highlighter": {
"version": "15.5.0",
"resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.5.0.tgz",
@@ -20788,6 +20879,12 @@
"node": ">=8"
}
},
+ "node_modules/shallowequal": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
+ "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==",
+ "dev": true
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -22317,6 +22414,15 @@
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
"dev": true
},
+ "node_modules/throttle-debounce": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-3.0.1.tgz",
+ "integrity": "sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/through": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
@@ -26759,6 +26865,30 @@
"picomatch": "^2.2.2"
}
},
+ "@storybook/addon-a11y": {
+ "version": "6.5.9",
+ "resolved": "https://registry.npmjs.org/@storybook/addon-a11y/-/addon-a11y-6.5.9.tgz",
+ "integrity": "sha512-jRiuJ2xlN8quVq2lOqpxqyuwAj8xLcgVBPy+Mf220u7AZmmbS/0sONyHKROfEBjJoHQAQYqn2vSAeuQZuTWyVA==",
+ "dev": true,
+ "requires": {
+ "@storybook/addons": "6.5.9",
+ "@storybook/api": "6.5.9",
+ "@storybook/channels": "6.5.9",
+ "@storybook/client-logger": "6.5.9",
+ "@storybook/components": "6.5.9",
+ "@storybook/core-events": "6.5.9",
+ "@storybook/csf": "0.0.2--canary.4566f4d.1",
+ "@storybook/theming": "6.5.9",
+ "axe-core": "^4.2.0",
+ "core-js": "^3.8.2",
+ "global": "^4.4.0",
+ "lodash": "^4.17.21",
+ "react-sizeme": "^3.0.1",
+ "regenerator-runtime": "^0.13.7",
+ "ts-dedent": "^2.0.0",
+ "util-deprecate": "^1.0.2"
+ }
+ },
"@storybook/addon-actions": {
"version": "6.5.9",
"resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-6.5.9.tgz",
@@ -31024,6 +31154,12 @@
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"dev": true
},
+ "batch-processor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/batch-processor/-/batch-processor-1.0.0.tgz",
+ "integrity": "sha512-xoLQD8gmmR32MeuBHgH0Tzd5PuSZx71ZsbhVxOCRbgktZEPe4SQy7s9Z50uPp0F/f7iw2XmkHN2xkgbMfckMDA==",
+ "dev": true
+ },
"better-opn": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/better-opn/-/better-opn-2.1.1.tgz",
@@ -33283,6 +33419,15 @@
"resolved": "https://registry.npmjs.org/element-internals-polyfill/-/element-internals-polyfill-1.1.6.tgz",
"integrity": "sha512-PW63CzI6GKewCYfX7hZ5gFJO5uib+oAy/AwELrOEfyBI3SlYOXFjVhDEIxlg0CV4kIa4ixjO+5XFhP/Cx1Lurw=="
},
+ "element-resize-detector": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/element-resize-detector/-/element-resize-detector-1.2.4.tgz",
+ "integrity": "sha512-Fl5Ftk6WwXE0wqCgNoseKWndjzZlDCwuPTcoVZfCP9R3EHQF8qUtr3YUPNETegRBOKqQKPW3n4kiIWngGi8tKg==",
+ "dev": true,
+ "requires": {
+ "batch-processor": "1.0.0"
+ }
+ },
"elliptic": {
"version": "6.5.4",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
@@ -36063,6 +36208,15 @@
"integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==",
"dev": true
},
+ "invariant": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
+ "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
+ "dev": true,
+ "requires": {
+ "loose-envify": "^1.0.0"
+ }
+ },
"ip": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz",
@@ -37979,6 +38133,16 @@
}
}
},
+ "msw-storybook-addon": {
+ "version": "1.6.3",
+ "resolved": "https://registry.npmjs.org/msw-storybook-addon/-/msw-storybook-addon-1.6.3.tgz",
+ "integrity": "sha512-Ps80WdRmXsmenoTwfrgKMNpQD8INUUFyUFyZOecx8QjuqSlL++UYrLaGyACXN2goOn+/VS6rb0ZapbjrasPClg==",
+ "dev": true,
+ "requires": {
+ "@storybook/addons": "^6.0.0",
+ "is-node-process": "^1.0.1"
+ }
+ },
"mute-stream": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
@@ -39580,6 +39744,18 @@
"integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==",
"dev": true
},
+ "react-sizeme": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/react-sizeme/-/react-sizeme-3.0.2.tgz",
+ "integrity": "sha512-xOIAOqqSSmKlKFJLO3inBQBdymzDuXx4iuwkNcJmC96jeiOg5ojByvL+g3MW9LPEsojLbC6pf68zOfobK8IPlw==",
+ "dev": true,
+ "requires": {
+ "element-resize-detector": "^1.2.2",
+ "invariant": "^2.2.4",
+ "shallowequal": "^1.1.0",
+ "throttle-debounce": "^3.0.1"
+ }
+ },
"react-syntax-highlighter": {
"version": "15.5.0",
"resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.5.0.tgz",
@@ -40687,6 +40863,12 @@
"kind-of": "^6.0.2"
}
},
+ "shallowequal": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
+ "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==",
+ "dev": true
+ },
"shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -41898,6 +42080,12 @@
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
"dev": true
},
+ "throttle-debounce": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-3.0.1.tgz",
+ "integrity": "sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg==",
+ "dev": true
+ },
"through": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json
index fdd1d48ec1..405fe97e3b 100644
--- a/src/Umbraco.Web.UI.Client/package.json
+++ b/src/Umbraco.Web.UI.Client/package.json
@@ -44,6 +44,7 @@
"devDependencies": {
"@babel/core": "^7.18.10",
"@open-wc/testing": "^3.1.6",
+ "@storybook/addon-a11y": "^6.5.9",
"@storybook/addon-actions": "^6.5.9",
"@storybook/addon-essentials": "^6.5.9",
"@storybook/addon-links": "^6.5.9",
@@ -66,6 +67,7 @@
"eslint-plugin-storybook": "^0.6.2",
"lit-html": "^2.2.7",
"msw": "^0.44.2",
+ "msw-storybook-addon": "^1.6.3",
"prettier": "2.7.1",
"typescript": "^4.7.4",
"vite": "^3.0.3"
diff --git a/src/Umbraco.Web.UI.Client/src/core/context/context-provider.element.ts b/src/Umbraco.Web.UI.Client/src/core/context/context-provider.element.ts
new file mode 100644
index 0000000000..172c8aa476
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/core/context/context-provider.element.ts
@@ -0,0 +1,36 @@
+import { html, LitElement } from 'lit';
+import { customElement, property } from 'lit/decorators.js';
+
+import { UmbContextProviderMixin } from './context-provider.mixin';
+
+@customElement('umb-context-provider')
+export class UmbContextProviderElement extends UmbContextProviderMixin(LitElement) {
+ /**
+ * The value to provide to the context.
+ * @required
+ */
+ @property({ type: Object })
+ value!: unknown;
+
+ /**
+ * The key to provide to the context.
+ * @required
+ */
+ @property({ type: String })
+ key!: string;
+
+ connectedCallback() {
+ super.connectedCallback();
+ if (!this.key) {
+ throw new Error('The key property is required.');
+ }
+ if (!this.value) {
+ throw new Error('The value property is required.');
+ }
+ this.provideContext(this.key, this.value);
+ }
+
+ render() {
+ return html`