diff --git a/.changeset/chatty-paws-invent.md b/.changeset/chatty-paws-invent.md
new file mode 100644
index 0000000000..07c3a83d8e
--- /dev/null
+++ b/.changeset/chatty-paws-invent.md
@@ -0,0 +1,6 @@
+---
+'@swisspost/design-system-documentation': patch
+'@swisspost/design-system-components': patch
+---
+
+Aligned prop validation across the component library and replaced thrown errors with console errors.
diff --git a/packages/components/cypress/e2e/back-to-top.cy.ts b/packages/components/cypress/e2e/back-to-top.cy.ts
index 96e527b5b5..1e5b048920 100644
--- a/packages/components/cypress/e2e/back-to-top.cy.ts
+++ b/packages/components/cypress/e2e/back-to-top.cy.ts
@@ -10,17 +10,19 @@ describe('Back-to-top', () => {
cy.get('post-back-to-top').should('exist');
});
- it('should throw an error if the label is missing', () => {
- cy.on('uncaught:exception', err => {
- expect(err.message).to.include(
- 'The label property of the Back to Top component is required for accessibility purposes. Please ensure it is set.',
- );
- return false;
+ it('should log a message if the label is removed', () => {
+ cy.window().then(win => {
+ cy.spy(win.console, 'error').as('consoleError');
});
- cy.document().then(doc => {
- const element = doc.createElement('post-back-to-top');
- doc.body.appendChild(element);
+
+ cy.get('post-back-to-top').then($el => {
+ $el[0].removeAttribute('label');
});
+
+ cy.get('@consoleError').should(
+ 'be.calledWith',
+ 'The prop `label` of the `post-back-to-top` component is not defined.',
+ );
});
it('should hide the label visually', () => {
diff --git a/packages/components/cypress/e2e/card-control.cy.ts b/packages/components/cypress/e2e/card-control.cy.ts
index bbc699fafb..732fd465ee 100644
--- a/packages/components/cypress/e2e/card-control.cy.ts
+++ b/packages/components/cypress/e2e/card-control.cy.ts
@@ -58,8 +58,8 @@ describe('Card-Control', () => {
cy.get('@consoleError')
.invoke('getCalls')
.then(calls => {
- expect(calls[0].args[0].message).to.eq(
- 'The prop `label` of the `post-card-control` component is required.',
+ expect(calls[0].args[0]).to.eq(
+ 'The prop `label` of the `post-card-control` component is not defined.',
);
});
});
@@ -92,8 +92,8 @@ describe('Card-Control', () => {
cy.get('@consoleError')
.invoke('getCalls')
.then(calls => {
- expect(calls[0].args[0].message).to.eq(
- 'The prop `type` of the `post-card-control` component must be one of the following values: checkbox, radio.',
+ expect(calls[0].args[0]).to.eq(
+ 'The prop `type` of the `post-card-control` component is not defined.',
);
});
});
diff --git a/packages/components/cypress/e2e/list.cy.ts b/packages/components/cypress/e2e/list.cy.ts
index 6078019acb..4ec09d9f2d 100644
--- a/packages/components/cypress/e2e/list.cy.ts
+++ b/packages/components/cypress/e2e/list.cy.ts
@@ -22,17 +22,19 @@ describe('PostList Component', { baseUrl: null, includeShadowDom: false }, () =>
});
});
- it('should throw an error if the title is missing', () => {
- // Check for the mandatory title accessibility error if no title is provided
- cy.on('uncaught:exception', err => {
- expect(err.message).to.include(
- 'Please provide a title to the list component. Title is mandatory for accessibility purposes.',
- );
- return false;
+ it('should log an error if the title is missing', () => {
+ cy.window().then(win => {
+ cy.spy(win.console, 'error').as('consoleError');
});
+
cy.get('post-list').within(() => {
- cy.get('[slot="post-list-item"]').first().invoke('remove');
+ cy.get(':first-child').invoke('remove');
});
+
+ cy.get('@consoleError').should(
+ 'be.calledWith',
+ 'Please provide a title to the list component. Title is mandatory for accessibility purposes.',
+ );
});
it('should hide the title when title-hidden is set', () => {
diff --git a/packages/components/cypress/fixtures/post-back-to-top.test.html b/packages/components/cypress/fixtures/post-back-to-top.test.html
deleted file mode 100644
index 5e572ffe08..0000000000
--- a/packages/components/cypress/fixtures/post-back-to-top.test.html
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
- Document
-
-
-
-
-
-
diff --git a/packages/components/src/components.d.ts b/packages/components/src/components.d.ts
index 8b4c1d4fda..813f812145 100644
--- a/packages/components/src/components.d.ts
+++ b/packages/components/src/components.d.ts
@@ -85,7 +85,7 @@ export namespace Components {
/**
* The label to use for the close button of a dismissible banner.
*/
- "dismissLabel": string;
+ "dismissLabel"?: string;
/**
* If `true`, a close button (×) is displayed and the banner can be dismissed by the user.
*/
@@ -93,7 +93,7 @@ export namespace Components {
/**
* The icon to display in the banner. By default, the icon depends on the banner type. If `none`, no icon is displayed.
*/
- "icon": string;
+ "icon"?: string;
/**
* The type of the banner.
*/
@@ -126,7 +126,7 @@ export namespace Components {
/**
* Defines the description in the control-label.
*/
- "description": string;
+ "description"?: string;
/**
* Defines the `disabled` attribute of the control. If `true`, the user can not interact with the control and the controls value will not be included in the forms' data.
*/
@@ -138,7 +138,7 @@ export namespace Components {
/**
* Defines the icon `name` inside the card. If not set the icon will not show up.
*/
- "icon": string;
+ "icon"?: string;
/**
* Defines the text in the control-label.
*/
@@ -146,7 +146,7 @@ export namespace Components {
/**
* Defines the `name` attribute of the control. This is a required property, when the control should participate in a native `form`. If not specified, a native `form` will never contain this controls value. This is a required property, when the control is used with type `radio`.
*/
- "name": string;
+ "name"?: string;
/**
* A public method to reset the controls `checked` and `validity` state. The validity state is set to `null`, so it's neither valid nor invalid.
*/
@@ -158,11 +158,11 @@ export namespace Components {
/**
* Defines the validation `validity` of the control. To reset validity to an undefined state, simply remove the attribute from the control.
*/
- "validity": null | 'true' | 'false';
+ "validity"?: 'true' | 'false';
/**
* Defines the `value` attribute of the control. This is a required property, when the control is used with type `radio`.
*/
- "value": string;
+ "value"?: string;
}
interface PostClosebutton {
}
@@ -205,11 +205,11 @@ export namespace Components {
/**
* The name of the animation.
*/
- "animation"?: Animation | null;
+ "animation"?: Animation;
/**
* The base path, where the icons are located (must be a public url).
Leave this field empty to use the default cdn url.
*/
- "base"?: string | null;
+ "base"?: string;
/**
* When set to `true`, the icon will be flipped horizontally.
*/
@@ -225,17 +225,17 @@ export namespace Components {
/**
* The number of degree for the css rotate transformation.
*/
- "rotate"?: number | null;
+ "rotate"?: number;
/**
* The number for the css scale transformation.
*/
- "scale"?: number | null;
+ "scale"?: number;
}
interface PostLanguageOption {
/**
* If set to `true`, the language option is considered the current language for the page.
*/
- "active": boolean;
+ "active"?: boolean;
/**
* The ISO 639 language code, formatted according to [RFC 5646 (also known as BCP 47)](https://datatracker.ietf.org/doc/html/rfc5646). For example, "de".
*/
@@ -243,7 +243,7 @@ export namespace Components {
/**
* The full name of the language. For example, "Deutsch".
*/
- "name": string;
+ "name"?: string;
/**
* Selects the language option programmatically.
*/
@@ -251,11 +251,11 @@ export namespace Components {
/**
* The URL used for the href attribute of the internal anchor. This field is optional; if not provided, a button will be used internally instead of an anchor.
*/
- "url": string;
+ "url"?: string;
/**
* To communicate the variant prop from the parent (post-language-switch) component to the child (post-language-option) component. See parent docs for a description about the property itself.
*/
- "variant"?: SwitchVariant | null;
+ "variant"?: SwitchVariant;
}
interface PostLanguageSwitch {
/**
@@ -289,7 +289,7 @@ export namespace Components {
/**
* The URL to which the user is redirected upon clicking the logo.
*/
- "url": string | URL;
+ "url"?: string | URL;
}
interface PostMainnavigation {
}
@@ -444,7 +444,7 @@ export namespace Components {
/**
* The name of the panel that is initially shown. If not specified, it defaults to the panel associated with the first tab. **Changing this value after initialization has no effect.**
*/
- "activePanel": HTMLPostTabPanelElement['name'];
+ "activePanel"?: HTMLPostTabPanelElement['name'];
/**
* Shows the panel with the given name and selects its associated tab. Any other panel that was previously shown becomes hidden and its associated tab is unselected.
*/
@@ -454,15 +454,15 @@ export namespace Components {
/**
* Defines the icon `name` inside of the component. If not set the icon will not show up. To learn which icons are available, please visit our icon library.
*/
- "icon": null | string;
+ "icon": string;
/**
* Defines the size of the component.
*/
- "size": null | 'sm';
+ "size"?: 'sm';
/**
* Defines the color variant of the component.
*/
- "variant": 'white' | 'info' | 'success' | 'error' | 'warning' | 'yellow';
+ "variant"?: 'white' | 'info' | 'success' | 'error' | 'warning' | 'yellow';
}
interface PostTogglebutton {
/**
@@ -472,7 +472,7 @@ export namespace Components {
}
interface PostTooltip {
/**
- * Wheter or not to display a little pointer arrow
+ * Whether or not to display a little pointer arrow
*/
"arrow"?: boolean;
/**
@@ -985,7 +985,7 @@ declare namespace LocalJSX {
/**
* The URL for the home breadcrumb item.
*/
- "homeUrl"?: string;
+ "homeUrl": string;
}
interface PostBreadcrumbItem {
/**
@@ -1036,7 +1036,7 @@ declare namespace LocalJSX {
/**
* Defines the validation `validity` of the control. To reset validity to an undefined state, simply remove the attribute from the control.
*/
- "validity"?: null | 'true' | 'false';
+ "validity"?: 'true' | 'false';
/**
* Defines the `value` attribute of the control. This is a required property, when the control is used with type `radio`.
*/
@@ -1058,7 +1058,7 @@ declare namespace LocalJSX {
/**
* Link the trigger to a post-collapsible with this id
*/
- "for"?: string;
+ "for": string;
}
interface PostFooter {
/**
@@ -1079,11 +1079,11 @@ declare namespace LocalJSX {
/**
* The name of the animation.
*/
- "animation"?: Animation | null;
+ "animation"?: Animation;
/**
* The base path, where the icons are located (must be a public url).
Leave this field empty to use the default cdn url.
*/
- "base"?: string | null;
+ "base"?: string;
/**
* When set to `true`, the icon will be flipped horizontally.
*/
@@ -1099,11 +1099,11 @@ declare namespace LocalJSX {
/**
* The number of degree for the css rotate transformation.
*/
- "rotate"?: number | null;
+ "rotate"?: number;
/**
* The number for the css scale transformation.
*/
- "scale"?: number | null;
+ "scale"?: number;
}
interface PostLanguageOption {
/**
@@ -1133,17 +1133,17 @@ declare namespace LocalJSX {
/**
* To communicate the variant prop from the parent (post-language-switch) component to the child (post-language-option) component. See parent docs for a description about the property itself.
*/
- "variant"?: SwitchVariant | null;
+ "variant"?: SwitchVariant;
}
interface PostLanguageSwitch {
/**
* A title for the list of language options
*/
- "caption"?: string;
+ "caption": string;
/**
* A descriptive text for the list of language options
*/
- "description"?: string;
+ "description": string;
/**
* Whether the component is rendered as a list or a menu
*/
@@ -1271,13 +1271,13 @@ declare namespace LocalJSX {
/**
* The name of the panel controlled by the tab header.
*/
- "panel"?: HTMLPostTabPanelElement['name'];
+ "panel": HTMLPostTabPanelElement['name'];
}
interface PostTabPanel {
/**
* The name of the panel, used to associate it with a tab header.
*/
- "name"?: string;
+ "name": string;
}
interface PostTabs {
/**
@@ -1293,11 +1293,11 @@ declare namespace LocalJSX {
/**
* Defines the icon `name` inside of the component. If not set the icon will not show up. To learn which icons are available, please visit our icon library.
*/
- "icon"?: null | string;
+ "icon"?: string;
/**
* Defines the size of the component.
*/
- "size"?: null | 'sm';
+ "size"?: 'sm';
/**
* Defines the color variant of the component.
*/
@@ -1311,7 +1311,7 @@ declare namespace LocalJSX {
}
interface PostTooltip {
/**
- * Wheter or not to display a little pointer arrow
+ * Whether or not to display a little pointer arrow
*/
"arrow"?: boolean;
/**
diff --git a/packages/components/src/components/post-accordion/post-accordion.tsx b/packages/components/src/components/post-accordion/post-accordion.tsx
index a57dcbc1e6..c8e467f1b4 100644
--- a/packages/components/src/components/post-accordion/post-accordion.tsx
+++ b/packages/components/src/components/post-accordion/post-accordion.tsx
@@ -1,7 +1,7 @@
import { Component, Element, h, Host, Listen, Method, Prop, Watch } from '@stencil/core';
import { version } from '@root/package.json';
import { HEADING_LEVELS, HeadingLevel } from '@/types';
-import { checkOneOf } from '@/utils';
+import { checkOneOf, checkNonEmpty } from '@/utils';
import { eventGuard } from '@/utils/event-guard'; // Import eventGuard
/**
@@ -25,18 +25,19 @@ export class PostAccordion {
@Prop() readonly headingLevel!: HeadingLevel;
@Watch('headingLevel')
- validateHeadingLevel(newValue = this.headingLevel) {
- if (!newValue) return;
- checkOneOf(
- this,
- 'headingLevel',
- HEADING_LEVELS,
- 'The `heading-level` property of the `post-accordion` must be a number between 1 and 6.',
- );
-
- this.accordionItems.forEach(item => {
- item.setAttribute('heading-level', String(newValue));
- });
+ validateHeadingLevel() {
+ if (!checkNonEmpty(this, 'headingLevel')) {
+ checkOneOf(
+ this,
+ 'headingLevel',
+ HEADING_LEVELS,
+ 'The `heading-level` property of the `post-accordion` must be a number between 1 and 6.',
+ );
+
+ this.accordionItems.forEach(item => {
+ item.setAttribute('heading-level', String(this.headingLevel));
+ });
+ }
}
/**
@@ -44,14 +45,13 @@ export class PostAccordion {
*/
@Prop() readonly multiple: boolean = false;
- componentWillLoad() {
- this.registerAccordionItems();
+ componentDidLoad() {
this.validateHeadingLevel();
+ this.registerAccordionItems();
}
@Listen('postToggle')
collapseToggleHandler(event: CustomEvent) {
-
eventGuard(
this.host,
event,
@@ -74,10 +74,10 @@ export class PostAccordion {
.forEach(item => {
item.toggle(false);
});
- }
+ },
);
}
-
+
/**
* Toggles the `post-accordion-item` with the given id.
*/
diff --git a/packages/components/src/components/post-avatar/post-avatar.tsx b/packages/components/src/components/post-avatar/post-avatar.tsx
index 371e32436f..abda16ae8b 100644
--- a/packages/components/src/components/post-avatar/post-avatar.tsx
+++ b/packages/components/src/components/post-avatar/post-avatar.tsx
@@ -1,6 +1,6 @@
import { Component, Element, h, Host, Prop, State, Watch } from '@stencil/core';
import { version } from '@root/package.json';
-import { checkNonEmpty } from '@/utils';
+import { checkNonEmpty, checkEmptyOrType, checkEmptyOrPattern, checkType } from '@/utils';
// https://docs.gravatar.com/api/avatars/images/
const GRAVATAR_DEFAULT = '404';
@@ -9,6 +9,8 @@ const GRAVATAR_SIZE = 80;
const GRAVATAR_BASE_URL = `https://www.gravatar.com/avatar/{email}?s=${GRAVATAR_SIZE}&d=${GRAVATAR_DEFAULT}&r=${GRAVATAR_RATING}`;
+const emailPattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
+
enum AvatarType {
Slotted = 'slotted',
Image = 'image',
@@ -57,7 +59,24 @@ export class PostAvatar {
@Watch('firstname')
validateFirstname() {
- checkNonEmpty(this, 'firstname');
+ if (!checkNonEmpty(this, 'firstname')) {
+ checkType(this, 'firstname', 'string');
+ }
+ }
+
+ @Watch('lastname')
+ validateLastname() {
+ checkEmptyOrType(this, 'lastname', 'string');
+ }
+
+ @Watch('userid')
+ validateUserid() {
+ checkEmptyOrType(this, 'userid', 'string');
+ }
+
+ @Watch('email')
+ validateEmail() {
+ checkEmptyOrPattern(this, 'email', emailPattern);
}
private async getAvatar() {
@@ -166,6 +185,9 @@ export class PostAvatar {
componentDidLoad() {
this.validateFirstname();
+ this.validateLastname();
+ this.validateUserid();
+ this.validateEmail();
}
render() {
diff --git a/packages/components/src/components/post-back-to-top/post-back-to-top.tsx b/packages/components/src/components/post-back-to-top/post-back-to-top.tsx
index 7633054a0e..ed7b820c27 100644
--- a/packages/components/src/components/post-back-to-top/post-back-to-top.tsx
+++ b/packages/components/src/components/post-back-to-top/post-back-to-top.tsx
@@ -29,6 +29,13 @@ export class PostBackToTop {
this.belowFold = this.isBelowFold();
};
+ @Watch('label')
+ validateLabel() {
+ if (!checkNonEmpty(this, 'label')) {
+ checkType(this, 'label', 'string');
+ }
+ }
+
/*Watch for changes in belowFold to show/hide the back to top button*/
@Watch('belowFold')
watchBelowFold(newValue: boolean) {
@@ -87,13 +94,6 @@ export class PostBackToTop {
}
}
- // Validate the label
- @Watch('label')
- validateLabel() {
- checkNonEmpty(this, 'label');
- checkType(this, 'label', 'string');
- }
-
// Set the initial state
componentWillLoad() {
this.belowFold = this.isBelowFold();
diff --git a/packages/components/src/components/post-banner/post-banner.tsx b/packages/components/src/components/post-banner/post-banner.tsx
index 7c6912018f..ed4d601b53 100644
--- a/packages/components/src/components/post-banner/post-banner.tsx
+++ b/packages/components/src/components/post-banner/post-banner.tsx
@@ -12,7 +12,7 @@ import {
} from '@stencil/core';
import { version } from '@root/package.json';
import { fadeOut } from '@/animations';
-import { checkEmptyOrOneOf, checkEmptyOrPattern, checkNonEmpty, checkType } from '@/utils';
+import { checkEmptyOrOneOf, checkNonEmpty, checkEmptyOrType, checkType } from '@/utils';
import { BANNER_TYPES, BannerType } from './banner-types';
import { nanoid } from 'nanoid';
@@ -43,24 +43,28 @@ export class PostBanner {
@Watch('dismissible')
validateDismissible() {
- checkType(this, 'dismissible', 'boolean');
setTimeout(() => this.validateDismissLabel());
}
/**
* The label to use for the close button of a dismissible banner.
*/
- @Prop() readonly dismissLabel: string;
+ @Prop() readonly dismissLabel?: string;
@Watch('dismissLabel')
validateDismissLabel() {
if (this.dismissible) {
- checkNonEmpty(
- this,
- 'dismissLabel',
- 'Dismissible post-banner\'s require a "dismiss-label" prop.',
- );
+ if (
+ !checkNonEmpty(
+ this,
+ 'dismissLabel',
+ 'Dismissible post-banner\'s require a "dismiss-label" prop.',
+ )
+ ) {
+ checkType(this, 'dismissLabel', 'string');
+ }
}
+ checkEmptyOrType(this, 'dismissLabel', 'string');
}
/**
@@ -68,16 +72,11 @@ export class PostBanner {
*
* If `none`, no icon is displayed.
*/
- @Prop() readonly icon: string;
+ @Prop() readonly icon?: string;
@Watch('icon')
validateIcon() {
- checkEmptyOrPattern(
- this,
- 'icon',
- /\d{4}|none/,
- 'The post-banner "icon" prop should be a 4-digit string.',
- );
+ checkEmptyOrType(this, 'icon', 'string');
}
/**
diff --git a/packages/components/src/components/post-breadcrumb/post-breadcrumb.tsx b/packages/components/src/components/post-breadcrumb/post-breadcrumb.tsx
index 49eb678bcd..75c2bf4dbd 100644
--- a/packages/components/src/components/post-breadcrumb/post-breadcrumb.tsx
+++ b/packages/components/src/components/post-breadcrumb/post-breadcrumb.tsx
@@ -1,6 +1,6 @@
import { Component, Element, h, Host, Prop, State, Watch } from '@stencil/core';
import { version } from '@root/package.json';
-import { checkUrl, debounce } from '@/utils';
+import { checkEmptyOrType, checkNonEmpty, checkUrl, debounce } from '@/utils';
@Component({
tag: 'post-breadcrumb',
@@ -13,7 +13,7 @@ export class PostBreadcrumb {
/**
* The URL for the home breadcrumb item.
*/
- @Prop() homeUrl: string;
+ @Prop() homeUrl!: string;
/**
* The text label for the home breadcrumb item.
@@ -28,8 +28,15 @@ export class PostBreadcrumb {
private lastItem: { url: string; text: string };
@Watch('homeUrl')
- validateUrl() {
- checkUrl(this, 'homeUrl');
+ validateHomeUrl() {
+ if (!checkNonEmpty(this, 'homeUrl')) {
+ checkUrl(this, 'homeUrl');
+ }
+ }
+
+ @Watch('homeText')
+ validateHomeText() {
+ checkEmptyOrType(this, 'homeUrl', 'string');
}
componentWillLoad() {
@@ -37,6 +44,8 @@ export class PostBreadcrumb {
}
componentDidLoad() {
+ this.validateHomeUrl();
+ this.validateHomeText();
window.addEventListener('resize', this.handleResize);
this.waitForBreadcrumbRef();
}
diff --git a/packages/components/src/components/post-breadcrumb/readme.md b/packages/components/src/components/post-breadcrumb/readme.md
index 916ff74906..ba548dda6e 100644
--- a/packages/components/src/components/post-breadcrumb/readme.md
+++ b/packages/components/src/components/post-breadcrumb/readme.md
@@ -7,10 +7,10 @@
## Properties
-| Property | Attribute | Description | Type | Default |
-| ---------- | ----------- | -------------------------------------------- | -------- | ----------- |
-| `homeText` | `home-text` | The text label for the home breadcrumb item. | `string` | `'Home'` |
-| `homeUrl` | `home-url` | The URL for the home breadcrumb item. | `string` | `undefined` |
+| Property | Attribute | Description | Type | Default |
+| ---------------------- | ----------- | -------------------------------------------- | -------- | ----------- |
+| `homeText` | `home-text` | The text label for the home breadcrumb item. | `string` | `'Home'` |
+| `homeUrl` _(required)_ | `home-url` | The URL for the home breadcrumb item. | `string` | `undefined` |
## Dependencies
diff --git a/packages/components/src/components/post-card-control/post-card-control.tsx b/packages/components/src/components/post-card-control/post-card-control.tsx
index 7f10327547..14ec99107f 100644
--- a/packages/components/src/components/post-card-control/post-card-control.tsx
+++ b/packages/components/src/components/post-card-control/post-card-control.tsx
@@ -11,7 +11,7 @@ import {
State,
Watch,
} from '@stencil/core';
-import { checkNonEmpty, checkOneOf } from '@/utils';
+import { checkNonEmpty, checkOneOf, checkType, checkEmptyOrType, checkEmptyOrOneOf } from '@/utils';
import { version } from '@root/package.json';
let cardControlIds = 0;
@@ -70,7 +70,7 @@ export class PostCardControl {
/**
* Defines the description in the control-label.
*/
- @Prop() readonly description: string = null;
+ @Prop() readonly description?: string;
/**
* Defines the `type` attribute of the control.
@@ -82,12 +82,12 @@ export class PostCardControl {
* This is a required property, when the control should participate in a native `form`. If not specified, a native `form` will never contain this controls value.
* This is a required property, when the control is used with type `radio`.
*/
- @Prop() readonly name: string = null;
+ @Prop() readonly name?: string;
/**
* Defines the `value` attribute of the control. This is a required property, when the control is used with type `radio`.
*/
- @Prop() readonly value: string = null;
+ @Prop() readonly value?: string;
/**
* Defines the `checked` attribute of the control. If `true`, the control is selected at its value will be included in the forms' data.
@@ -103,13 +103,13 @@ export class PostCardControl {
* Defines the validation `validity` of the control.
* To reset validity to an undefined state, simply remove the attribute from the control.
*/
- @Prop({ mutable: true }) validity: null | 'true' | 'false' = null;
+ @Prop({ mutable: true }) validity?: 'true' | 'false';
/**
* Defines the icon `name` inside the card.
* If not set the icon will not show up.
*/
- @Prop() readonly icon: string = null;
+ @Prop() readonly icon?: string;
/**
* An event emitted whenever the components checked state is toggled.
@@ -130,7 +130,7 @@ export class PostCardControl {
*/
@Method()
async reset() {
- this.validity = null;
+ this.validity = undefined;
this.controlSetChecked(this.initialChecked);
}
@@ -145,12 +145,51 @@ export class PostCardControl {
@Watch('label')
validateControlLabel() {
- checkNonEmpty(this, 'label');
+ if (!checkNonEmpty(this, 'label')) {
+ checkType(this, 'label', 'string');
+ }
+ }
+
+ @Watch('description')
+ validateControlDescription() {
+ checkEmptyOrType(this, 'description', 'string');
}
@Watch('type')
validateControlType() {
- checkOneOf(this, 'type', ['checkbox', 'radio']);
+ if (!checkNonEmpty(this, 'type')) {
+ checkOneOf(this, 'type', ['checkbox', 'radio']);
+ }
+ }
+
+ @Watch('name')
+ validateControlName() {
+ if (this.type == 'radio') {
+ if (!checkNonEmpty(this, 'name')) {
+ checkType(this, 'name', 'string');
+ }
+ }
+ }
+
+ @Watch('value')
+ validateControlValue() {
+ if (this.type == 'radio') {
+ if (!checkNonEmpty(this, 'value')) {
+ checkType(this, 'value', 'string');
+ }
+ }
+ }
+
+ @Watch('validity')
+ validateValidity() {
+ checkEmptyOrOneOf(this, 'validity', ['true', 'false']);
+ }
+
+ @Watch('icon')
+ validateControlIcon() {
+ if (!checkNonEmpty(this, 'icon')) {
+ checkType(this, 'icon', 'string');
+ }
}
@Watch('checked')
@@ -348,7 +387,7 @@ export class PostCardControl {
'is-checked': this.checked,
'is-disabled': this.disabled,
'is-focused': this.focused,
- 'is-valid': this.validity !== null && this.validity !== 'false',
+ 'is-valid': this.validity !== undefined && this.validity !== 'false',
'is-invalid': this.validity === 'false',
}}
>
@@ -403,7 +442,11 @@ export class PostCardControl {
componentDidLoad() {
this.validateControlLabel();
+ this.validateControlName();
+ this.validateControlValue();
+ this.validateControlDescription();
this.validateControlType();
+ this.validateControlIcon();
}
formAssociatedCallback() {
diff --git a/packages/components/src/components/post-card-control/readme.md b/packages/components/src/components/post-card-control/readme.md
index e94733e68f..e2a191b4ed 100644
--- a/packages/components/src/components/post-card-control/readme.md
+++ b/packages/components/src/components/post-card-control/readme.md
@@ -1,7 +1,5 @@
# post-card-control
-
-
@@ -10,14 +8,14 @@
| Property | Attribute | Description | Type | Default |
| -------------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | ----------- |
| `checked` | `checked` | Defines the `checked` attribute of the control. If `true`, the control is selected at its value will be included in the forms' data. | `boolean` | `false` |
-| `description` | `description` | Defines the description in the control-label. | `string` | `null` |
+| `description` | `description` | Defines the description in the control-label. | `string` | `undefined` |
| `disabled` | `disabled` | Defines the `disabled` attribute of the control. If `true`, the user can not interact with the control and the controls value will not be included in the forms' data. | `boolean` | `false` |
-| `icon` | `icon` | Defines the icon `name` inside the card. If not set the icon will not show up. | `string` | `null` |
+| `icon` | `icon` | Defines the icon `name` inside the card. If not set the icon will not show up. | `string` | `undefined` |
| `label` _(required)_ | `label` | Defines the text in the control-label. | `string` | `undefined` |
-| `name` | `name` | Defines the `name` attribute of the control. This is a required property, when the control should participate in a native `form`. If not specified, a native `form` will never contain this controls value. This is a required property, when the control is used with type `radio`. | `string` | `null` |
+| `name` | `name` | Defines the `name` attribute of the control. This is a required property, when the control should participate in a native `form`. If not specified, a native `form` will never contain this controls value. This is a required property, when the control is used with type `radio`. | `string` | `undefined` |
| `type` _(required)_ | `type` | Defines the `type` attribute of the control. | `"checkbox" \| "radio"` | `undefined` |
-| `validity` | `validity` | Defines the validation `validity` of the control. To reset validity to an undefined state, simply remove the attribute from the control. | `"false" \| "true"` | `null` |
-| `value` | `value` | Defines the `value` attribute of the control. This is a required property, when the control is used with type `radio`. | `string` | `null` |
+| `validity` | `validity` | Defines the validation `validity` of the control. To reset validity to an undefined state, simply remove the attribute from the control. | `"false" \| "true"` | `undefined` |
+| `value` | `value` | Defines the `value` attribute of the control. This is a required property, when the control is used with type `radio`. | `string` | `undefined` |
## Events
diff --git a/packages/components/src/components/post-collapsible-trigger/post-collapsible-trigger.tsx b/packages/components/src/components/post-collapsible-trigger/post-collapsible-trigger.tsx
index 9e20bf2c09..b449d78caa 100644
--- a/packages/components/src/components/post-collapsible-trigger/post-collapsible-trigger.tsx
+++ b/packages/components/src/components/post-collapsible-trigger/post-collapsible-trigger.tsx
@@ -16,15 +16,17 @@ export class PostCollapsibleTrigger {
/**
* Link the trigger to a post-collapsible with this id
*/
- @Prop({ reflect: true }) for: string;
+
+ @Prop({ reflect: true }) for!: string;
/**
* Set the "aria-controls" and "aria-expanded" attributes on the trigger to match the state of the controlled post-collapsible
*/
@Watch('for')
validateAriaAttributes() {
- checkNonEmpty(this, 'for');
- checkType(this, 'for', 'string', 'The post-collapsible-trigger "for" prop should be a id.');
+ if (!checkNonEmpty(this, 'for')) {
+ checkType(this, 'for', 'string', 'The post-collapsible-trigger "for" prop should be a id.');
+ }
}
/**
diff --git a/packages/components/src/components/post-collapsible-trigger/readme.md b/packages/components/src/components/post-collapsible-trigger/readme.md
index 5ebbabbe44..cf349afcba 100644
--- a/packages/components/src/components/post-collapsible-trigger/readme.md
+++ b/packages/components/src/components/post-collapsible-trigger/readme.md
@@ -7,9 +7,9 @@
## Properties
-| Property | Attribute | Description | Type | Default |
-| -------- | --------- | --------------------------------------------------- | -------- | ----------- |
-| `for` | `for` | Link the trigger to a post-collapsible with this id | `string` | `undefined` |
+| Property | Attribute | Description | Type | Default |
+| ------------------ | --------- | --------------------------------------------------- | -------- | ----------- |
+| `for` _(required)_ | `for` | Link the trigger to a post-collapsible with this id | `string` | `undefined` |
## Methods
diff --git a/packages/components/src/components/post-collapsible/post-collapsible.tsx b/packages/components/src/components/post-collapsible/post-collapsible.tsx
index b991870091..5da2332e1c 100644
--- a/packages/components/src/components/post-collapsible/post-collapsible.tsx
+++ b/packages/components/src/components/post-collapsible/post-collapsible.tsx
@@ -11,7 +11,7 @@ import {
} from '@stencil/core';
import { version } from '@root/package.json';
import { collapse, expand } from '@/animations/collapse';
-import { checkEmptyOrType, isMotionReduced } from '@/utils';
+import { checkType, isMotionReduced } from '@/utils';
/**
* @slot default - Slot for placing content within the collapsible element.
@@ -35,7 +35,7 @@ export class PostCollapsible {
@Watch('collapsed')
collapsedChange() {
- checkEmptyOrType(this, 'collapsed', 'boolean');
+ checkType(this, 'collapsed', 'boolean');
void this.toggle(!this.collapsed);
}
diff --git a/packages/components/src/components/post-footer/post-footer.tsx b/packages/components/src/components/post-footer/post-footer.tsx
index 7972665fc5..f130e8b3cf 100644
--- a/packages/components/src/components/post-footer/post-footer.tsx
+++ b/packages/components/src/components/post-footer/post-footer.tsx
@@ -1,6 +1,7 @@
-import { Component, Element, h, Host, Prop, State } from '@stencil/core';
+import { Component, Element, h, Host, Prop, State, Watch } from '@stencil/core';
import { version } from '@root/package.json';
-import { breakpoint } from '@/utils';
+import { breakpoint } from '../../utils/breakpoints';
+import { checkNonEmpty, checkType } from '@/utils';
/**
* @slot grid-{1|2|3|4}-title - Slot for the accordion headers (mobile).
@@ -26,10 +27,21 @@ export class PostFooter {
@State() isMobile: boolean = breakpoint.get('name') === 'mobile';
+ @Watch('label')
+ validateLabel() {
+ if (!checkNonEmpty(this, 'label')) {
+ checkType(this, 'label', 'string');
+ }
+ }
+
connectedCallback() {
window.addEventListener('postBreakpoint:name', this.breakpointChange);
}
+ componentWillLoad() {
+ this.validateLabel();
+ }
+
disconnectedCallback() {
window.removeEventListener('postBreakpoint:name', this.breakpointChange);
}
diff --git a/packages/components/src/components/post-icon/post-icon.tsx b/packages/components/src/components/post-icon/post-icon.tsx
index 68bd6c05c4..cd5412612f 100644
--- a/packages/components/src/components/post-icon/post-icon.tsx
+++ b/packages/components/src/components/post-icon/post-icon.tsx
@@ -1,5 +1,7 @@
import { Component, Element, Host, h, Prop, Watch } from '@stencil/core';
-import { IS_BROWSER, checkNonEmpty, checkType, checkEmptyOrType, checkEmptyOrOneOf } from '@/utils';
+
+import { IS_BROWSER, checkType, checkEmptyOrType, checkEmptyOrOneOf, checkNonEmpty } from '@/utils';
+
import { version } from '@root/package.json';
type UrlDefinition = {
@@ -36,7 +38,7 @@ export class PostIcon {
/**
* The name of the animation.
*/
- @Prop() readonly animation?: Animation | null = null;
+ @Prop() readonly animation?: Animation;
@Watch('animation')
validateAnimation(newValue = this.animation) {
@@ -46,7 +48,7 @@ export class PostIcon {
/**
* The base path, where the icons are located (must be a public url).
Leave this field empty to use the default cdn url.
*/
- @Prop() readonly base?: string | null = null;
+ @Prop() readonly base?: string;
@Watch('base')
validateBase() {
@@ -58,21 +60,11 @@ export class PostIcon {
*/
@Prop() readonly flipH?: boolean = false;
- @Watch('flipH')
- validateFlipH() {
- checkEmptyOrType(this, 'flipH', 'boolean');
- }
-
/**
* When set to `true`, the icon will be flipped vertically.
*/
@Prop() readonly flipV?: boolean = false;
- @Watch('flipV')
- validateFlipV() {
- checkEmptyOrType(this, 'flipV', 'boolean');
- }
-
/**
* The name/id of the icon (e.g. 1000, 1001, ...).
*/
@@ -80,14 +72,15 @@ export class PostIcon {
@Watch('name')
validateName() {
- checkNonEmpty(this, 'name');
- checkType(this, 'name', 'string');
+ if (!checkNonEmpty(this, 'name')) {
+ checkType(this, 'name', 'string');
+ }
}
/**
* The number of degree for the css rotate transformation.
*/
- @Prop() readonly rotate?: number | null = null;
+ @Prop() readonly rotate?: number;
@Watch('rotate')
validateRotate() {
@@ -97,7 +90,7 @@ export class PostIcon {
/**
* The number for the css scale transformation.
*/
- @Prop() readonly scale?: number | null = null;
+ @Prop() readonly scale?: number;
@Watch('scale')
validateScale() {
@@ -188,8 +181,6 @@ export class PostIcon {
componentDidLoad() {
this.validateBase();
this.validateName();
- this.validateFlipH();
- this.validateFlipV();
this.validateScale();
this.validateRotate();
this.validateAnimation();
diff --git a/packages/components/src/components/post-icon/readme.md b/packages/components/src/components/post-icon/readme.md
index 4005f0b00d..c95dc0d4c7 100644
--- a/packages/components/src/components/post-icon/readme.md
+++ b/packages/components/src/components/post-icon/readme.md
@@ -9,13 +9,13 @@ some content
| Property | Attribute | Description | Type | Default |
| ------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | ----------- |
-| `animation` | `animation` | The name of the animation. | `"cylon" \| "cylon-vertical" \| "fade" \| "spin" \| "spin-reverse" \| "throb"` | `null` |
-| `base` | `base` | The base path, where the icons are located (must be a public url).
Leave this field empty to use the default cdn url. | `string` | `null` |
+| `animation` | `animation` | The name of the animation. | `"cylon" \| "cylon-vertical" \| "fade" \| "spin" \| "spin-reverse" \| "throb"` | `undefined` |
+| `base` | `base` | The base path, where the icons are located (must be a public url).
Leave this field empty to use the default cdn url. | `string` | `undefined` |
| `flipH` | `flip-h` | When set to `true`, the icon will be flipped horizontally. | `boolean` | `false` |
| `flipV` | `flip-v` | When set to `true`, the icon will be flipped vertically. | `boolean` | `false` |
| `name` _(required)_ | `name` | The name/id of the icon (e.g. 1000, 1001, ...). | `string` | `undefined` |
-| `rotate` | `rotate` | The number of degree for the css rotate transformation. | `number` | `null` |
-| `scale` | `scale` | The number for the css scale transformation. | `number` | `null` |
+| `rotate` | `rotate` | The number of degree for the css rotate transformation. | `number` | `undefined` |
+| `scale` | `scale` | The number for the css scale transformation. | `number` | `undefined` |
## Dependencies
diff --git a/packages/components/src/components/post-language-option/post-language-option.tsx b/packages/components/src/components/post-language-option/post-language-option.tsx
index 126dfb2892..6af7ccbf32 100644
--- a/packages/components/src/components/post-language-option/post-language-option.tsx
+++ b/packages/components/src/components/post-language-option/post-language-option.tsx
@@ -9,9 +9,15 @@ import {
Prop,
Watch,
} from '@stencil/core';
-import { checkEmptyOrType, checkType } from '@/utils';
+import {
+ checkEmptyOrType,
+ checkType,
+ checkNonEmpty,
+ checkEmptyOrOneOf,
+ checkEmptyOrUrl,
+} from '@/utils';
import { version } from '@root/package.json';
-import { SwitchVariant } from '../post-language-switch/switch-variants';
+import { SwitchVariant, SWITCH_VARIANTS } from '../post-language-switch/switch-variants';
/**
* @slot default - Slot for placing the content inside the anchor or button.
@@ -30,28 +36,30 @@ export class PostLanguageOption {
@Watch('code')
validateCode() {
- checkType(this, 'code', 'string');
+ if (!checkNonEmpty(this, 'code')) {
+ checkType(this, 'code', 'string');
+ }
}
/**
* If set to `true`, the language option is considered the current language for the page.
*/
- @Prop({ mutable: true, reflect: true }) active: boolean;
-
- @Watch('active')
- validateActiveProp() {
- checkEmptyOrType(this, 'active', 'boolean');
- }
+ @Prop({ mutable: true, reflect: true }) active?: boolean;
/**
* To communicate the variant prop from the parent (post-language-switch) component to the child (post-language-option) component. See parent docs for a description about the property itself.
*/
- @Prop() variant?: SwitchVariant | null;
+ @Prop() variant?: SwitchVariant;
+
+ @Watch('variant')
+ validateVariant() {
+ checkEmptyOrOneOf(this, 'variant', SWITCH_VARIANTS);
+ }
/**
* The full name of the language. For example, "Deutsch".
*/
- @Prop() name: string;
+ @Prop() name?: string;
@Watch('name')
validateName() {
@@ -62,16 +70,15 @@ export class PostLanguageOption {
* The URL used for the href attribute of the internal anchor.
* This field is optional; if not provided, a button will be used internally instead of an anchor.
*/
- @Prop() url: string;
+ @Prop() url?: string;
@Watch('url')
validateUrl() {
- checkEmptyOrType(this, 'url', 'string');
+ checkEmptyOrUrl(this, 'url');
}
componentDidLoad() {
this.validateCode();
- this.validateActiveProp();
this.validateName();
this.validateUrl();
@@ -114,7 +121,7 @@ export class PostLanguageOption {
}
render() {
- const lang = this.code.toLowerCase();
+ const lang: string = this.code ? this.code.toLowerCase() : '';
const emitOnKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Enter' || e.key === ' ') {
diff --git a/packages/components/src/components/post-language-switch/post-language-switch.tsx b/packages/components/src/components/post-language-switch/post-language-switch.tsx
index 8d7f747843..94b84a341a 100644
--- a/packages/components/src/components/post-language-switch/post-language-switch.tsx
+++ b/packages/components/src/components/post-language-switch/post-language-switch.tsx
@@ -1,5 +1,5 @@
import { Component, Element, Host, h, Prop, Watch, State, Listen } from '@stencil/core';
-import { checkEmptyOrOneOf, checkType, eventGuard } from '@/utils';
+import { checkEmptyOrOneOf, checkType, checkNonEmpty, eventGuard } from '@/utils';
import { version } from '@root/package.json';
import { SWITCH_VARIANTS, SwitchVariant } from './switch-variants';
import { nanoid } from 'nanoid';
@@ -22,21 +22,25 @@ export class PostLanguageSwitch {
/**
* A title for the list of language options
*/
- @Prop() caption: string;
+ @Prop() caption!: string;
@Watch('caption')
validateCaption() {
- checkType(this, 'caption', 'string');
+ if (!checkNonEmpty(this, 'caption')) {
+ checkType(this, 'caption', 'string');
+ }
}
/**
* A descriptive text for the list of language options
*/
- @Prop() description: string;
+ @Prop() description!: string;
@Watch('description')
validateDescription() {
- checkType(this, 'description', 'string');
+ if (!checkNonEmpty(this, 'description')) {
+ checkType(this, 'description', 'string');
+ }
}
/**
diff --git a/packages/components/src/components/post-language-switch/readme.md b/packages/components/src/components/post-language-switch/readme.md
index d00bb0c33a..42ef11d6fa 100644
--- a/packages/components/src/components/post-language-switch/readme.md
+++ b/packages/components/src/components/post-language-switch/readme.md
@@ -5,11 +5,11 @@
## Properties
-| Property | Attribute | Description | Type | Default |
-| ------------- | ------------- | ----------------------------------------------------- | ------------------ | ----------- |
-| `caption` | `caption` | A title for the list of language options | `string` | `undefined` |
-| `description` | `description` | A descriptive text for the list of language options | `string` | `undefined` |
-| `variant` | `variant` | Whether the component is rendered as a list or a menu | `"list" \| "menu"` | `'list'` |
+| Property | Attribute | Description | Type | Default |
+| -------------------------- | ------------- | ----------------------------------------------------- | ------------------ | ----------- |
+| `caption` _(required)_ | `caption` | A title for the list of language options | `string` | `undefined` |
+| `description` _(required)_ | `description` | A descriptive text for the list of language options | `string` | `undefined` |
+| `variant` | `variant` | Whether the component is rendered as a list or a menu | `"list" \| "menu"` | `'list'` |
## Dependencies
diff --git a/packages/components/src/components/post-logo/post-logo.tsx b/packages/components/src/components/post-logo/post-logo.tsx
index b05918a549..dfcdcc05a4 100644
--- a/packages/components/src/components/post-logo/post-logo.tsx
+++ b/packages/components/src/components/post-logo/post-logo.tsx
@@ -16,7 +16,7 @@ export class PostLogo {
/**
* The URL to which the user is redirected upon clicking the logo.
*/
- @Prop() url: string | URL;
+ @Prop() url?: string | URL;
@Watch('url')
validateUrl() {
diff --git a/packages/components/src/components/post-megadropdown-trigger/post-megadropdown-trigger.tsx b/packages/components/src/components/post-megadropdown-trigger/post-megadropdown-trigger.tsx
index ca713e0681..ccd98c3e92 100644
--- a/packages/components/src/components/post-megadropdown-trigger/post-megadropdown-trigger.tsx
+++ b/packages/components/src/components/post-megadropdown-trigger/post-megadropdown-trigger.tsx
@@ -1,6 +1,6 @@
import { Component, Element, Prop, h, Host, State, Watch } from '@stencil/core';
import { version } from '@root/package.json';
-import { checkType } from '@/utils';
+import { checkType, checkNonEmpty } from '@/utils';
import { eventGuard } from '@/utils/event-guard';
@Component({
@@ -39,7 +39,9 @@ export class PostMegadropdownTrigger {
*/
@Watch('for')
validateControlFor() {
- checkType(this, 'for', 'string');
+ if (!checkNonEmpty(this, 'for')) {
+ checkType(this, 'for', 'string');
+ }
}
private get megadropdown(): HTMLPostMegadropdownElement | null {
@@ -67,29 +69,26 @@ export class PostMegadropdownTrigger {
}
};
- private handleToggleMegadropdown = (event: CustomEvent<{ isVisible: boolean; focusParent: boolean }>) => {
- eventGuard(
- this.host,
- event,
- { targetLocalName: 'post-megadropdown' },
- () => {
- if ((event.target as HTMLPostMegadropdownElement).id === this.for) {
- this.ariaExpanded = event.detail.isVisible;
-
- // Focus on the trigger parent of the dropdown after it's closed if the close button had been clicked
- if (this.wasExpanded && !this.ariaExpanded && event.detail.focusParent) {
- setTimeout(() => {
- this.slottedButton?.focus();
- }, 100);
- }
- this.wasExpanded = this.ariaExpanded;
-
- if (this.slottedButton) {
- this.slottedButton.setAttribute('aria-expanded', this.ariaExpanded.toString());
- }
+ private handleToggleMegadropdown = (
+ event: CustomEvent<{ isVisible: boolean; focusParent: boolean }>,
+ ) => {
+ eventGuard(this.host, event, { targetLocalName: 'post-megadropdown' }, () => {
+ if ((event.target as HTMLPostMegadropdownElement).id === this.for) {
+ this.ariaExpanded = event.detail.isVisible;
+
+ // Focus on the trigger parent of the dropdown after it's closed if the close button had been clicked
+ if (this.wasExpanded && !this.ariaExpanded && event.detail.focusParent) {
+ setTimeout(() => {
+ this.slottedButton?.focus();
+ }, 100);
+ }
+ this.wasExpanded = this.ariaExpanded;
+
+ if (this.slottedButton) {
+ this.slottedButton.setAttribute('aria-expanded', this.ariaExpanded.toString());
}
}
- );
+ });
};
componentDidLoad() {
diff --git a/packages/components/src/components/post-menu-trigger/post-menu-trigger.tsx b/packages/components/src/components/post-menu-trigger/post-menu-trigger.tsx
index 96c0d0da61..ac9b233800 100644
--- a/packages/components/src/components/post-menu-trigger/post-menu-trigger.tsx
+++ b/packages/components/src/components/post-menu-trigger/post-menu-trigger.tsx
@@ -1,6 +1,6 @@
import { Component, Element, Prop, h, Host, State, Watch } from '@stencil/core';
import { version } from '@root/package.json';
-import { checkType, getRoot } from '@/utils';
+import { checkType, getRoot, checkNonEmpty } from '@/utils';
@Component({
tag: 'post-menu-trigger',
@@ -33,7 +33,9 @@ export class PostMenuTrigger {
*/
@Watch('for')
validateControlFor() {
- checkType(this, 'for', 'string');
+ if (!checkNonEmpty(this, 'for')) {
+ checkType(this, 'for', 'string');
+ }
}
private get menu(): HTMLPostMenuElement | null {
diff --git a/packages/components/src/components/post-menu/post-menu.tsx b/packages/components/src/components/post-menu/post-menu.tsx
index 3de903b7d7..eb78f9f9f8 100644
--- a/packages/components/src/components/post-menu/post-menu.tsx
+++ b/packages/components/src/components/post-menu/post-menu.tsx
@@ -8,11 +8,13 @@ import {
Method,
Prop,
State,
+ Watch,
} from '@stencil/core';
import { Placement } from '@floating-ui/dom';
+import { PLACEMENT_TYPES } from '@/types';
import { version } from '@root/package.json';
import { getFocusableChildren } from '@/utils/get-focusable-children';
-import { getRoot } from '@/utils';
+import { getRoot, checkEmptyOrOneOf } from '@/utils';
import { eventGuard } from '@/utils/event-guard';
@Component({
@@ -44,6 +46,11 @@ export class PostMenu {
*/
@Prop() readonly placement?: Placement = 'bottom';
+ @Watch('placement')
+ validatePlacement() {
+ checkEmptyOrOneOf(this, 'placement', PLACEMENT_TYPES);
+ }
+
/**
* Holds the current visibility state of the menu.
* This state is internally managed to track whether the menu is open (`true`) or closed (`false`),
@@ -72,6 +79,7 @@ export class PostMenu {
}
componentDidLoad() {
+ this.validatePlacement();
if (this.popoverRef) {
this.popoverRef.addEventListener('postToggle', this.handlePostToggle);
}
@@ -82,7 +90,6 @@ export class PostMenu {
*/
@Method()
async toggle(target: HTMLElement) {
-
if (this.popoverRef) {
await this.popoverRef.toggle(target);
} else {
@@ -149,7 +156,7 @@ export class PostMenu {
this.lastFocusedElement.focus();
}
});
- }
+ },
);
};
@@ -167,7 +174,7 @@ export class PostMenu {
}
let currentIndex = menuItems.findIndex(el => {
- // Check if the item is currently focused within its rendered scope (document or shadow root)
+ // Check if the item is currently focused within its rendered scope (document or shadow root)
return el === getRoot(el).activeElement;
});
diff --git a/packages/components/src/components/post-popover/post-popover.tsx b/packages/components/src/components/post-popover/post-popover.tsx
index eebd4f670d..a35cdb2c8b 100644
--- a/packages/components/src/components/post-popover/post-popover.tsx
+++ b/packages/components/src/components/post-popover/post-popover.tsx
@@ -1,7 +1,14 @@
-import { Component, Element, h, Host, Method, Prop } from '@stencil/core';
+import { Component, Element, h, Host, Method, Prop, Watch } from '@stencil/core';
import { Placement } from '@floating-ui/dom';
-import { IS_BROWSER, getAttributeObserver } from '@/utils';
+import { PLACEMENT_TYPES } from '@/types';
import { version } from '@root/package.json';
+import {
+ IS_BROWSER,
+ getAttributeObserver,
+ checkEmptyOrOneOf,
+ checkNonEmpty,
+ checkType,
+} from '@/utils';
/**
* @slot default - Slot for placing content inside the popover.
@@ -57,6 +64,18 @@ export class PostPopover {
// eslint-disable-next-line @stencil-community/ban-default-true
@Prop() readonly arrow?: boolean = true;
+ @Watch('placement')
+ validatePlacement() {
+ checkEmptyOrOneOf(this, 'placement', PLACEMENT_TYPES);
+ }
+
+ @Watch('closeButtonCaption')
+ validateCloseButtonCaption() {
+ if (!checkNonEmpty(this, 'closeButtonCaption')) {
+ checkType(this, 'closeButtonCaption', 'string');
+ }
+ }
+
constructor() {
this.localBeforeToggleHandler = this.beforeToggleHandler.bind(this);
}
@@ -79,6 +98,8 @@ export class PostPopover {
}
componentDidLoad() {
+ this.validatePlacement();
+ this.validateCloseButtonCaption();
this.popoverRef.addEventListener('beforetoggle', this.localBeforeToggleHandler);
}
diff --git a/packages/components/src/components/post-popovercontainer/post-popovercontainer.tsx b/packages/components/src/components/post-popovercontainer/post-popovercontainer.tsx
index 8a6a53b007..26a04973c8 100644
--- a/packages/components/src/components/post-popovercontainer/post-popovercontainer.tsx
+++ b/packages/components/src/components/post-popovercontainer/post-popovercontainer.tsx
@@ -1,5 +1,16 @@
-import { Component, Element, Event, EventEmitter, Host, Method, Prop, h } from '@stencil/core';
-import { IS_BROWSER } from '@/utils';
+import {
+ Component,
+ Element,
+ Event,
+ EventEmitter,
+ Host,
+ Method,
+ Prop,
+ h,
+ Watch,
+} from '@stencil/core';
+
+import { IS_BROWSER, checkEmptyOrOneOf, checkEmptyOrType } from '@/utils';
import { version } from '@root/package.json';
import {
@@ -15,6 +26,8 @@ import {
size,
} from '@floating-ui/dom';
+import { PLACEMENT_TYPES } from '@/types';
+
// Polyfill for popovers, can be removed when https://caniuse.com/?search=popover is green
import { apply, isSupported } from '@oddbird/popover-polyfill/dist/popover-fn.js';
@@ -95,6 +108,21 @@ export class PostPopovercontainer {
*/
@Prop({ reflect: true }) readonly safeSpace?: 'triangle' | 'trapezoid';
+ @Watch('placement')
+ validatePlacement() {
+ checkEmptyOrOneOf(this, 'placement', PLACEMENT_TYPES);
+ }
+
+ @Watch('edgeGap')
+ validateEdgeGap() {
+ checkEmptyOrType(this, 'edgeGap', 'number');
+ }
+
+ @Watch('safeSpace')
+ validateSafeSpace() {
+ checkEmptyOrOneOf(this, 'safeSpace', ['triangle', 'trapezoid']);
+ }
+
/**
* Updates cursor position for safe space feature when popover is open.
* Sets CSS custom properties for dynamic styling of safe area.
diff --git a/packages/components/src/components/post-rating/post-rating.tsx b/packages/components/src/components/post-rating/post-rating.tsx
index 9065e71153..6de8115b4c 100644
--- a/packages/components/src/components/post-rating/post-rating.tsx
+++ b/packages/components/src/components/post-rating/post-rating.tsx
@@ -1,5 +1,16 @@
-import { Component, Element, Event, EventEmitter, h, Host, Prop, State } from '@stencil/core';
+import {
+ Component,
+ Element,
+ Event,
+ EventEmitter,
+ h,
+ Host,
+ Prop,
+ State,
+ Watch,
+} from '@stencil/core';
import { version } from '@root/package.json';
+import { checkType, checkNonEmpty } from '@/utils';
@Component({
tag: 'post-rating',
@@ -46,6 +57,27 @@ export class PostRating {
*/
@Event() postChange: EventEmitter<{ value: number }>;
+ @Watch('label')
+ validateLabel() {
+ if (!checkNonEmpty(this, 'label')) {
+ checkType(this, 'label', 'string');
+ }
+ }
+
+ @Watch('stars')
+ validateStars() {
+ if (!checkNonEmpty(this, 'stars')) {
+ checkType(this, 'stars', 'number');
+ }
+ }
+
+ @Watch('currentRating')
+ validateCurrentRating() {
+ if (!checkNonEmpty(this, 'currentRating')) {
+ checkType(this, 'currentRating', 'number');
+ }
+ }
+
constructor() {
this.keydownHandler = this.keydownHandler.bind(this);
this.blurHandler = this.blurHandler.bind(this);
@@ -106,6 +138,12 @@ export class PostRating {
}
}
+ componentWillLoad() {
+ this.validateLabel();
+ this.validateStars();
+ this.validateCurrentRating();
+ }
+
render() {
return (
diff --git a/packages/components/src/components/post-tab-header/post-tab-header.tsx b/packages/components/src/components/post-tab-header/post-tab-header.tsx
index 587b054ceb..c2ee894cde 100644
--- a/packages/components/src/components/post-tab-header/post-tab-header.tsx
+++ b/packages/components/src/components/post-tab-header/post-tab-header.tsx
@@ -20,7 +20,7 @@ export class PostTabHeader {
/**
* The name of the panel controlled by the tab header.
*/
- @Prop({ reflect: true }) readonly panel: HTMLPostTabPanelElement['name'];
+ @Prop({ reflect: true }) readonly panel!: HTMLPostTabPanelElement['name'];
@Watch('panel')
validateFor() {
diff --git a/packages/components/src/components/post-tab-header/readme.md b/packages/components/src/components/post-tab-header/readme.md
index 48fb3378b2..6d09529475 100644
--- a/packages/components/src/components/post-tab-header/readme.md
+++ b/packages/components/src/components/post-tab-header/readme.md
@@ -7,9 +7,9 @@
## Properties
-| Property | Attribute | Description | Type | Default |
-| -------- | --------- | --------------------------------------------------- | -------- | ----------- |
-| `panel` | `panel` | The name of the panel controlled by the tab header. | `string` | `undefined` |
+| Property | Attribute | Description | Type | Default |
+| -------------------- | --------- | --------------------------------------------------- | -------- | ----------- |
+| `panel` _(required)_ | `panel` | The name of the panel controlled by the tab header. | `string` | `undefined` |
## Slots
diff --git a/packages/components/src/components/post-tab-panel/post-tab-panel.tsx b/packages/components/src/components/post-tab-panel/post-tab-panel.tsx
index 59f09acaf9..f736ab9c23 100644
--- a/packages/components/src/components/post-tab-panel/post-tab-panel.tsx
+++ b/packages/components/src/components/post-tab-panel/post-tab-panel.tsx
@@ -1,6 +1,7 @@
-import { Component, Element, h, Host, Prop, State } from '@stencil/core';
+import { Component, Element, h, Host, Prop, State, Watch } from '@stencil/core';
import { version } from '@root/package.json';
import { nanoid } from 'nanoid';
+import { checkNonEmpty, checkType } from '@/utils';
/**
* @slot default - Slot for placing the content of the tab panel.
@@ -19,9 +20,18 @@ export class PostTabPanel {
/**
* The name of the panel, used to associate it with a tab header.
*/
- @Prop({ reflect: true }) readonly name: string;
+
+ @Prop({ reflect: true }) readonly name!: string;
+
+ @Watch('name')
+ validateName() {
+ if (!checkNonEmpty(this, 'name')) {
+ checkType(this, 'name', 'string');
+ }
+ }
componentWillLoad() {
+ this.validateName();
// get the id set on the host element or use a random id by default
this.panelId = `panel-${this.host.id || nanoid(6)}`;
}
diff --git a/packages/components/src/components/post-tab-panel/readme.md b/packages/components/src/components/post-tab-panel/readme.md
index 440b621d52..7655490f14 100644
--- a/packages/components/src/components/post-tab-panel/readme.md
+++ b/packages/components/src/components/post-tab-panel/readme.md
@@ -7,9 +7,9 @@
## Properties
-| Property | Attribute | Description | Type | Default |
-| -------- | --------- | -------------------------------------------------------------- | -------- | ----------- |
-| `name` | `name` | The name of the panel, used to associate it with a tab header. | `string` | `undefined` |
+| Property | Attribute | Description | Type | Default |
+| ------------------- | --------- | -------------------------------------------------------------- | -------- | ----------- |
+| `name` _(required)_ | `name` | The name of the panel, used to associate it with a tab header. | `string` | `undefined` |
## Slots
diff --git a/packages/components/src/components/post-tabs/post-tabs.tsx b/packages/components/src/components/post-tabs/post-tabs.tsx
index 1ea3895b12..445346e1e2 100644
--- a/packages/components/src/components/post-tabs/post-tabs.tsx
+++ b/packages/components/src/components/post-tabs/post-tabs.tsx
@@ -22,10 +22,10 @@ export class PostTabs {
private isLoaded = false;
private get tabs(): HTMLPostTabHeaderElement[] {
- return Array.from(this.host.querySelectorAll('post-tab-header')).filter(tab =>
- tab.closest('post-tabs') === this.host
- );
- }
+ return Array.from(
+ this.host.querySelectorAll('post-tab-header'),
+ ).filter(tab => tab.closest('post-tabs') === this.host);
+ }
@Element() host: HTMLPostTabsElement;
@@ -35,7 +35,7 @@ export class PostTabs {
*
* **Changing this value after initialization has no effect.**
*/
- @Prop() readonly activePanel: HTMLPostTabPanelElement['name'];
+ @Prop() readonly activePanel?: HTMLPostTabPanelElement['name'];
/**
* An event emitted after the active tab changes, when the fade in transition of its associated panel is finished.
diff --git a/packages/components/src/components/post-tag/post-tag.tsx b/packages/components/src/components/post-tag/post-tag.tsx
index 0deb0216f0..80f36ba364 100644
--- a/packages/components/src/components/post-tag/post-tag.tsx
+++ b/packages/components/src/components/post-tag/post-tag.tsx
@@ -1,6 +1,6 @@
import { Component, Element, h, Host, Prop, State, Watch } from '@stencil/core';
import { version } from '@root/package.json';
-
+import { checkEmptyOrOneOf, checkEmptyOrType } from '@/utils';
/**
* @slot default - Content to place in the `default` slot.Markup accepted: inline content.
*/
@@ -17,19 +17,19 @@ export class PostTag {
/**
* Defines the color variant of the component.
*/
- @Prop() readonly variant: 'white' | 'info' | 'success' | 'error' | 'warning' | 'yellow';
+ @Prop() readonly variant?: 'white' | 'info' | 'success' | 'error' | 'warning' | 'yellow';
/**
* Defines the size of the component.
*/
- @Prop() readonly size: null | 'sm' = null;
+ @Prop() readonly size?: 'sm';
/**
* Defines the icon `name` inside of the component.
* If not set the icon will not show up.
* To learn which icons are available, please visit our icon library.
*/
- @Prop() readonly icon: null | string = null;
+ @Prop() readonly icon: string;
constructor() {
this.setClasses = this.setClasses.bind(this);
@@ -37,14 +37,21 @@ export class PostTag {
@Watch('variant')
variantChanged() {
+ checkEmptyOrOneOf(this, 'variant', ['white', 'info', 'success', 'error', 'warning', 'yellow']);
this.setClasses();
}
@Watch('size')
sizeChanged() {
+ checkEmptyOrOneOf(this, 'size', ['sm']);
this.setClasses();
}
+ @Watch('icon')
+ validateName() {
+ checkEmptyOrType(this, 'icon', 'string');
+ }
+
private setClasses() {
this.classes = [
'tag',
@@ -59,6 +66,12 @@ export class PostTag {
this.setClasses();
}
+ componentWillLoad() {
+ this.validateName();
+ this.variantChanged();
+ this.sizeChanged();
+ }
+
render() {
return (
diff --git a/packages/components/src/components/post-tag/readme.md b/packages/components/src/components/post-tag/readme.md
index 7596099d83..fbfb296e16 100644
--- a/packages/components/src/components/post-tag/readme.md
+++ b/packages/components/src/components/post-tag/readme.md
@@ -7,8 +7,8 @@
| Property | Attribute | Description | Type | Default |
| --------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | ----------- |
-| `icon` | `icon` | Defines the icon `name` inside of the component. If not set the icon will not show up. To learn which icons are available, please visit our icon library. | `string` | `null` |
-| `size` | `size` | Defines the size of the component. | `"sm"` | `null` |
+| `icon` | `icon` | Defines the icon `name` inside of the component. If not set the icon will not show up. To learn which icons are available, please visit our icon library. | `string` | `undefined` |
+| `size` | `size` | Defines the size of the component. | `"sm"` | `undefined` |
| `variant` | `variant` | Defines the color variant of the component. | `"error" \| "info" \| "success" \| "warning" \| "white" \| "yellow"` | `undefined` |
diff --git a/packages/components/src/components/post-togglebutton/post-togglebutton.tsx b/packages/components/src/components/post-togglebutton/post-togglebutton.tsx
index b310c9f2f3..16029d557b 100644
--- a/packages/components/src/components/post-togglebutton/post-togglebutton.tsx
+++ b/packages/components/src/components/post-togglebutton/post-togglebutton.tsx
@@ -1,6 +1,5 @@
-import { Component, Host, h, Prop, Watch, Element } from '@stencil/core';
+import { Component, Host, h, Prop, Element } from '@stencil/core';
import { version } from '@root/package.json';
-import { checkType } from '@/utils';
/**
* @slot default - Slot for the content of the button.
@@ -19,14 +18,7 @@ export class PostTogglebutton {
*/
@Prop({ mutable: true }) toggled: boolean = false;
- @Watch('toggled')
- validateToggled() {
- checkType(this, 'toggled', 'boolean');
- }
-
componentWillLoad() {
- this.validateToggled();
-
// add event listener to not override listener that might be set on the host
this.host.addEventListener('click', () => this.handleClick());
this.host.addEventListener('keydown', (e: KeyboardEvent) => this.handleKeydown(e));
diff --git a/packages/components/src/components/post-tooltip/post-tooltip.tsx b/packages/components/src/components/post-tooltip/post-tooltip.tsx
index 070d280680..6847e966d9 100644
--- a/packages/components/src/components/post-tooltip/post-tooltip.tsx
+++ b/packages/components/src/components/post-tooltip/post-tooltip.tsx
@@ -1,7 +1,9 @@
import { Component, Element, h, Host, Method, Prop, Watch } from '@stencil/core';
import { Placement } from '@floating-ui/dom';
+import { PLACEMENT_TYPES } from '@/types';
+import 'long-press-event';
import isFocusable from 'ally.js/is/focusable';
-import { IS_BROWSER, checkEmptyOrType, getAttributeObserver } from '@/utils';
+import { IS_BROWSER, getAttributeObserver, checkEmptyOrOneOf } from '@/utils';
import { version } from '@root/package.json';
if (IS_BROWSER) {
@@ -108,7 +110,7 @@ export class PostTooltip {
@Prop() readonly placement?: Placement = 'top';
/**
- * Wheter or not to display a little pointer arrow
+ * Whether or not to display a little pointer arrow
*/
@Prop() readonly arrow?: boolean = true;
@@ -117,13 +119,13 @@ export class PostTooltip {
*/
@Prop() readonly delayed: boolean = false;
- @Watch('delayed')
- validateDelayed() {
- checkEmptyOrType(this, 'delayed', 'boolean');
+ @Watch('placement')
+ validatePlacement() {
+ checkEmptyOrOneOf(this, 'placement', PLACEMENT_TYPES);
}
connectedCallback() {
- this.validateDelayed();
+ this.validatePlacement();
}
componentDidLoad() {
diff --git a/packages/components/src/components/post-tooltip/readme.md b/packages/components/src/components/post-tooltip/readme.md
index 4fd194c2fa..81e87a815a 100644
--- a/packages/components/src/components/post-tooltip/readme.md
+++ b/packages/components/src/components/post-tooltip/readme.md
@@ -9,7 +9,7 @@
| Property | Attribute | Description | Type | Default |
| ----------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
-| `arrow` | `arrow` | Wheter or not to display a little pointer arrow | `boolean` | `true` |
+| `arrow` | `arrow` | Whether or not to display a little pointer arrow | `boolean` | `true` |
| `delayed` | `delayed` | If `true`, the tooltip is displayed a few milliseconds after it is triggered | `boolean` | `false` |
| `placement` | `placement` | Defines the placement of the tooltip according to the floating-ui options available at https://floating-ui.com/docs/computePosition#placement. Tooltips are automatically flipped to the opposite side if there is not enough available space and are shifted towards the viewport if they would overlap edge boundaries. | `"bottom" \| "bottom-end" \| "bottom-start" \| "left" \| "left-end" \| "left-start" \| "right" \| "right-end" \| "right-start" \| "top" \| "top-end" \| "top-start"` | `'top'` |
diff --git a/packages/components/src/types/index.ts b/packages/components/src/types/index.ts
index d595ecd976..9bc68de2c1 100644
--- a/packages/components/src/types/index.ts
+++ b/packages/components/src/types/index.ts
@@ -1 +1,2 @@
export * from './heading-levels';
+export * from './placement';
diff --git a/packages/components/src/types/placement.ts b/packages/components/src/types/placement.ts
new file mode 100644
index 0000000000..50b33311e4
--- /dev/null
+++ b/packages/components/src/types/placement.ts
@@ -0,0 +1,14 @@
+export const PLACEMENT_TYPES = [
+ 'top',
+ 'right',
+ 'bottom',
+ 'left',
+ 'top-start',
+ 'top-end',
+ 'right-start',
+ 'right-end',
+ 'bottom-start',
+ 'bottom-end',
+ 'left-start',
+ 'left-end',
+] as const;
diff --git a/packages/components/src/utils/property-checkers/check-non-empty.ts b/packages/components/src/utils/property-checkers/check-non-empty.ts
index 9df7af2c01..220e45c19c 100644
--- a/packages/components/src/utils/property-checkers/check-non-empty.ts
+++ b/packages/components/src/utils/property-checkers/check-non-empty.ts
@@ -9,11 +9,12 @@ export function checkNonEmpty(
const value = component[prop];
const defaultMessage = `The prop \`${String(
prop,
- )}\` of the \`${componentName}\` component is required.`;
+ )}\` of the \`${componentName}\` component is not defined.`;
const message = customMessage || defaultMessage;
if (EMPTY_VALUES.some(v => v === value)) {
- throw new Error(message);
+ console.error(message);
}
+ return EMPTY_VALUES.some(v => v === value);
}
diff --git a/packages/components/src/utils/property-checkers/check-one-of.ts b/packages/components/src/utils/property-checkers/check-one-of.ts
index c71073eea4..d62c1dd25f 100644
--- a/packages/components/src/utils/property-checkers/check-one-of.ts
+++ b/packages/components/src/utils/property-checkers/check-one-of.ts
@@ -14,6 +14,6 @@ export function checkOneOf(
const message = customMessage || defaultMessage;
if (!possibleValues.includes(value)) {
- throw new Error(message);
+ console.error(message);
}
}
diff --git a/packages/components/src/utils/property-checkers/check-pattern.ts b/packages/components/src/utils/property-checkers/check-pattern.ts
index a556717628..de7fe7359e 100644
--- a/packages/components/src/utils/property-checkers/check-pattern.ts
+++ b/packages/components/src/utils/property-checkers/check-pattern.ts
@@ -2,7 +2,7 @@ export function checkPattern(
component: T,
prop: keyof T,
pattern: RegExp,
- customMessage: string,
+ customMessage?: string,
) {
const componentName = component.host.localName;
const value = component[prop];
@@ -13,6 +13,6 @@ export function checkPattern(
const message = customMessage || defaultMessage;
if (typeof value !== 'string' || !pattern.test(value)) {
- throw new Error(message);
+ console.error(message);
}
}
diff --git a/packages/components/src/utils/property-checkers/check-type.ts b/packages/components/src/utils/property-checkers/check-type.ts
index bf9180578d..7712ba11db 100644
--- a/packages/components/src/utils/property-checkers/check-type.ts
+++ b/packages/components/src/utils/property-checkers/check-type.ts
@@ -20,9 +20,9 @@ export function checkType(
if (typeIsArray || valueIsArray) {
if (valueIsArray !== typeIsArray) {
- throw new Error(message);
+ console.error(message);
}
- } else if (typeof value !== type) {
- throw new Error(message);
+ } else if (typeof value !== type || (typeof value == 'number' && isNaN(value))) {
+ console.error(message);
}
}
diff --git a/packages/components/src/utils/property-checkers/check-url.ts b/packages/components/src/utils/property-checkers/check-url.ts
index 2dad3e435c..76664448ba 100644
--- a/packages/components/src/utils/property-checkers/check-url.ts
+++ b/packages/components/src/utils/property-checkers/check-url.ts
@@ -5,19 +5,19 @@ export function checkUrl(
) {
const componentName = component.host.localName;
const value = component[prop];
-
const defaultMessage = `The prop \`${String(
prop,
)}\` of the \`${componentName}\` component is invalid.`;
const message = customMessage || defaultMessage;
if (typeof value !== 'string' && !(value instanceof URL)) {
- throw new Error(message);
+ console.error(message);
+ return;
}
try {
new URL(value, 'https://www.post.ch');
} catch {
- throw new Error(message);
+ console.error(message);
}
}
diff --git a/packages/components/src/utils/property-checkers/tests/check-non-empty.spec.ts b/packages/components/src/utils/property-checkers/tests/check-non-empty.spec.ts
index a2240ce007..5aca3afe9d 100644
--- a/packages/components/src/utils/property-checkers/tests/check-non-empty.spec.ts
+++ b/packages/components/src/utils/property-checkers/tests/check-non-empty.spec.ts
@@ -27,18 +27,24 @@ describe('checkNonEmpty', () => {
const error = 'Is empty!';
describe('empty value', () => {
- it('should not throw an error if the value is a non-empty value', () => {
+ it('should not log an error if the value is a non-empty value', () => {
+ const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
for (const value of NON_EMPTY_VALUES) {
const component = { host: { localName: 'post-component' } as HTMLElement, prop: value };
- expect(() => checkNonEmpty(component, 'prop', error)).not.toThrow();
+ checkNonEmpty(component, 'prop', error);
+ expect(consoleErrorSpy).not.toHaveBeenCalledWith(error);
}
+ consoleErrorSpy.mockRestore();
});
- it('should throw an error if the value is an empty value', () => {
+ it('should log an error if the value is an empty value', () => {
+ const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
for (const value of EMPTY_VALUES) {
const component = { host: { localName: 'post-component' } as HTMLElement, prop: value };
- expect(() => checkNonEmpty(component, 'prop', error)).toThrow(error);
+ checkNonEmpty(component, 'prop', error);
+ expect(consoleErrorSpy).toHaveBeenCalledWith(error);
}
+ consoleErrorSpy.mockRestore();
});
});
});
diff --git a/packages/components/src/utils/property-checkers/tests/check-one-of.spec.ts b/packages/components/src/utils/property-checkers/tests/check-one-of.spec.ts
index 1270ea238e..1437e55bf0 100644
--- a/packages/components/src/utils/property-checkers/tests/check-one-of.spec.ts
+++ b/packages/components/src/utils/property-checkers/tests/check-one-of.spec.ts
@@ -4,16 +4,22 @@ describe('checkOneOf', () => {
const possibleValues = ['A', 'B', 'C', 'D'];
const error = 'Is not one of.';
- const runCheckForValue = (value: string) => () => {
+ const runCheckForValue = (value: string) => {
const component = { host: { localName: 'post-component' } as HTMLElement, prop: value };
checkOneOf(component, 'prop', possibleValues, error);
};
- it('should not throw an error if the value is one of the possible values', () => {
- expect(runCheckForValue('A')).not.toThrow();
+ it('should not log an error if the value is one of the possible values', () => {
+ const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
+ runCheckForValue('A');
+ expect(consoleErrorSpy).not.toHaveBeenCalledWith(expect.stringContaining(error));
+ consoleErrorSpy.mockRestore();
});
- it('should throw the provided error if the value is not one of the possible values', () => {
- expect(runCheckForValue('E')).toThrow(error);
+ it('should log the provided error if the value is not one of the possible values', () => {
+ const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
+ runCheckForValue('E');
+ expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining(error));
+ consoleErrorSpy.mockRestore();
});
});
diff --git a/packages/components/src/utils/property-checkers/tests/check-pattern.spec.ts b/packages/components/src/utils/property-checkers/tests/check-pattern.spec.ts
index f7ddeb5f7c..07d7a6858b 100644
--- a/packages/components/src/utils/property-checkers/tests/check-pattern.spec.ts
+++ b/packages/components/src/utils/property-checkers/tests/check-pattern.spec.ts
@@ -4,16 +4,20 @@ describe('checkPattern', () => {
const pattern = /[a-z]{5}/;
const error = 'Does not match pattern.';
- const runCheckForValue = (value: unknown) => () => {
+ const runCheckForValue = (value: unknown) => {
const component = { host: { localName: 'post-component' } as HTMLElement, prop: value };
checkPattern(component, 'prop', pattern, error);
};
- it('should not throw an error if the value matches the provided pattern', () => {
- expect(runCheckForValue('hello')).not.toThrow();
+ it('should not log an error if the value matches the provided pattern', () => {
+ const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
+ runCheckForValue('hello');
+ expect(consoleErrorSpy).not.toHaveBeenCalledWith(expect.stringContaining(error));
+ consoleErrorSpy.mockRestore();
});
- it('should throw the provided error if the value is not a string', () => {
+ it('should log the provided error if the value is not a string', () => {
+ const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
[
undefined,
null,
@@ -26,11 +30,16 @@ describe('checkPattern', () => {
/* empty */
},
].forEach(notString => {
- expect(runCheckForValue(notString)).toThrow(error);
+ runCheckForValue(notString);
+ expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining(error));
});
+ consoleErrorSpy.mockRestore();
});
- it('should throw the provided error if the value does not match the provided pattern', () => {
- expect(runCheckForValue('WORLD')).toThrow(error);
+ it('should log the provided error if the value does not match the provided pattern', () => {
+ const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
+ runCheckForValue('WORLD');
+ expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining(error));
+ consoleErrorSpy.mockRestore();
});
});
diff --git a/packages/components/src/utils/property-checkers/tests/check-type.spec.ts b/packages/components/src/utils/property-checkers/tests/check-type.spec.ts
index 5d6d8eb955..431b9ff9c5 100644
--- a/packages/components/src/utils/property-checkers/tests/check-type.spec.ts
+++ b/packages/components/src/utils/property-checkers/tests/check-type.spec.ts
@@ -4,7 +4,7 @@ describe('checkType', () => {
let type: PropertyType;
let error: string;
- const runCheckForValue = (value: unknown) => () => {
+ const runCheckForValue = (value: unknown) => {
const component = { host: { localName: 'post-component' } as HTMLElement, prop: value };
checkType(component, 'prop', type, error);
};
@@ -15,13 +15,17 @@ describe('checkType', () => {
error = 'Not a boolean.';
});
- it('should not throw an error if the value is a boolean', () => {
+ it('should not log an error if the value is a boolean', () => {
+ const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
[true, false].forEach(boolean => {
- expect(runCheckForValue(boolean)).not.toThrow();
+ runCheckForValue(boolean);
+ expect(consoleErrorSpy).not.toHaveBeenCalledWith(expect.stringContaining(error));
});
+ consoleErrorSpy.mockRestore();
});
- it('should throw an error if the value is not a boolean', () => {
+ it('should log an error if the value is not a boolean', () => {
+ const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
[
undefined,
null,
@@ -34,8 +38,10 @@ describe('checkType', () => {
/* empty */
},
].forEach(nonBoolean => {
- expect(runCheckForValue(nonBoolean)).toThrow(error);
+ runCheckForValue(nonBoolean);
+ expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining(error));
});
+ consoleErrorSpy.mockRestore();
});
});
@@ -45,13 +51,17 @@ describe('checkType', () => {
error = 'Not a number.';
});
- it('should not throw an error if the value is a number', () => {
- [42, 4.2, 4_200, 2.4434634e9, NaN].forEach(number => {
- expect(runCheckForValue(number)).not.toThrow();
+ it('should not log an error if the value is a number', () => {
+ const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
+ [42, 4.2, 4_200, 2.4434634e9].forEach(number => {
+ runCheckForValue(number);
+ expect(consoleErrorSpy).not.toHaveBeenCalledWith(expect.stringContaining(error));
});
+ consoleErrorSpy.mockRestore();
});
- it('should throw an error if the value is not a number', () => {
+ it('should log an error if the value is not a number', () => {
+ const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
[
undefined,
null,
@@ -63,8 +73,10 @@ describe('checkType', () => {
/* empty */
},
].forEach(nonNumber => {
- expect(runCheckForValue(nonNumber)).toThrow(error);
+ runCheckForValue(nonNumber);
+ expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining(error));
});
+ consoleErrorSpy.mockRestore();
});
});
@@ -74,13 +86,17 @@ describe('checkType', () => {
error = 'Not a string.';
});
- it('should not throw an error if the value is a string', () => {
+ it('should not log an error if the value is a string', () => {
+ const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
['', 'string', '42', '¡¡Olé 🙌!!'].forEach(string => {
- expect(runCheckForValue(string)).not.toThrow();
+ runCheckForValue(string);
+ expect(consoleErrorSpy).not.toHaveBeenCalledWith(expect.stringContaining(error));
});
+ consoleErrorSpy.mockRestore();
});
- it('should throw an error if the value is not string', () => {
+ it('should log an error if the value is not a string', () => {
+ const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
[
undefined,
null,
@@ -93,8 +109,10 @@ describe('checkType', () => {
/* empty */
},
].forEach(nonString => {
- expect(runCheckForValue(nonString)).toThrow(error);
+ runCheckForValue(nonString);
+ expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining(error));
});
+ consoleErrorSpy.mockRestore();
});
});
@@ -104,13 +122,17 @@ describe('checkType', () => {
error = 'Not an array.';
});
- it('should not throw an error if the value is an array', () => {
+ it('should not log an error if the value is an array', () => {
+ const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
[[], [1, 'a']].forEach(array => {
- expect(runCheckForValue(array)).not.toThrow();
+ runCheckForValue(array);
+ expect(consoleErrorSpy).not.toHaveBeenCalledWith(expect.stringContaining(error));
});
+ consoleErrorSpy.mockRestore();
});
- it('should throw an error if the value is not an array', () => {
+ it('should log an error if the value is not an array', () => {
+ const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
[
undefined,
null,
@@ -123,8 +145,10 @@ describe('checkType', () => {
/* empty */
},
].forEach(nonArray => {
- expect(runCheckForValue(nonArray)).toThrow(error);
+ runCheckForValue(nonArray);
+ expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining(error));
});
+ consoleErrorSpy.mockRestore();
});
});
@@ -134,13 +158,17 @@ describe('checkType', () => {
error = 'Not an object.';
});
- it('should not throw an error if the value is an object', () => {
+ it('should not log an error if the value is an object', () => {
+ const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
[null, {}].forEach(object => {
- expect(runCheckForValue(object)).not.toThrow();
+ runCheckForValue(object);
+ expect(consoleErrorSpy).not.toHaveBeenCalledWith(expect.stringContaining(error));
});
+ consoleErrorSpy.mockRestore();
});
- it('should throw an error if the value is not an object', () => {
+ it('should log an error if the value is not an object', () => {
+ const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
[
undefined,
true,
@@ -151,8 +179,10 @@ describe('checkType', () => {
/* empty */
},
].forEach(nonObject => {
- expect(runCheckForValue(nonObject)).toThrow(error);
+ runCheckForValue(nonObject);
+ expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining(error));
});
+ consoleErrorSpy.mockRestore();
});
});
@@ -162,7 +192,8 @@ describe('checkType', () => {
error = 'Not a function.';
});
- it('should not throw an error if the value is a function', () => {
+ it('should not log an error if the value is a function', () => {
+ const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
[
function () {
/* empty */
@@ -171,14 +202,19 @@ describe('checkType', () => {
/* empty */
},
].forEach(fn => {
- expect(runCheckForValue(fn)).not.toThrow();
+ runCheckForValue(fn);
+ expect(consoleErrorSpy).not.toHaveBeenCalledWith(expect.stringContaining(error));
});
+ consoleErrorSpy.mockRestore();
});
- it('should throw an error if the value is not a function', () => {
+ it('should log an error if the value is not a function', () => {
+ const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
[undefined, null, true, 42, NaN, 'string', [], {}].forEach(nonFn => {
- expect(runCheckForValue(nonFn)).toThrow(error);
+ runCheckForValue(nonFn);
+ expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining(error));
});
+ consoleErrorSpy.mockRestore();
});
});
});
diff --git a/packages/components/src/utils/property-checkers/tests/check-url.spec.ts b/packages/components/src/utils/property-checkers/tests/check-url.spec.ts
index 0f5f5cb3ab..cdbe12255c 100644
--- a/packages/components/src/utils/property-checkers/tests/check-url.spec.ts
+++ b/packages/components/src/utils/property-checkers/tests/check-url.spec.ts
@@ -3,7 +3,8 @@ import { checkUrl } from '../check-url';
describe('checkUrl', () => {
const errorMessage = 'Invalid URL';
- test('should not throw an error if the value is an URL string or an URL object', () => {
+ test('should not log an error if the value is an URL string or an URL object', () => {
+ const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
[
'https://www.example.com',
new URL('https://www.example.com'),
@@ -15,11 +16,14 @@ describe('checkUrl', () => {
'localhost:3000',
].forEach(validUrl => {
const component = { host: { localName: 'post-component' } as HTMLElement, prop: validUrl };
- expect(() => checkUrl(component, 'prop', errorMessage)).not.toThrow();
+ checkUrl(component, 'prop', errorMessage);
+ expect(consoleErrorSpy).not.toHaveBeenCalledWith(expect.stringContaining(errorMessage));
});
+ consoleErrorSpy.mockRestore();
});
- test('should throw an error if the value is not an URL string or an URL object', () => {
+ test('should log an error if the value is not an URL string or an URL object', () => {
+ const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
[
123,
true,
@@ -31,7 +35,9 @@ describe('checkUrl', () => {
].forEach(invalidUrl => {
const component = { host: { localName: 'post-component' } as HTMLElement, prop: invalidUrl };
// Type casting because we know that these are not valid arguments, it's just for testing
- expect(() => checkUrl(component, 'prop', errorMessage)).toThrow(errorMessage);
+ checkUrl(component, 'prop', errorMessage);
+ expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining(errorMessage));
});
+ consoleErrorSpy.mockRestore();
});
});
diff --git a/packages/documentation/src/stories/components/back-to-top/back-to-top.stories.ts b/packages/documentation/src/stories/components/back-to-top/back-to-top.stories.ts
index 931351f537..0e1efccb0f 100644
--- a/packages/documentation/src/stories/components/back-to-top/back-to-top.stories.ts
+++ b/packages/documentation/src/stories/components/back-to-top/back-to-top.stories.ts
@@ -9,7 +9,6 @@ const meta: MetaComponent = {
component: 'post-back-to-top',
tags: ['package:WebComponents'],
parameters: {
- layout: 'fullscreen',
design: {
type: 'figma',
url: 'https://www.figma.com/design/JIT5AdGYqv6bDRpfBPV8XR/Foundations-%26-Components-Next-Level?node-id=18-11',
diff --git a/packages/documentation/src/stories/components/card-control/standard-html/card-control.stories.ts b/packages/documentation/src/stories/components/card-control/standard-html/card-control.stories.ts
index a612eba497..1ce9b6b2f9 100644
--- a/packages/documentation/src/stories/components/card-control/standard-html/card-control.stories.ts
+++ b/packages/documentation/src/stories/components/card-control/standard-html/card-control.stories.ts
@@ -146,6 +146,7 @@ export const Default = {
const cardClasses = mapClasses({
'checked': args.checked,
'disabled': args.disabled,
+ 'is-valid': args.validation === 'is-valid',
'is-invalid': args.validation === 'is-invalid',
'checkbox-button-card': args.type === 'checkbox',
'radio-button-card': args.type === 'radio',
diff --git a/packages/documentation/src/stories/components/popover/popover.stories.ts b/packages/documentation/src/stories/components/popover/popover.stories.ts
index 423cd3f824..cd8fd9c830 100644
--- a/packages/documentation/src/stories/components/popover/popover.stories.ts
+++ b/packages/documentation/src/stories/components/popover/popover.stories.ts
@@ -53,7 +53,7 @@ const meta: MetaComponent = {
'Value can either be in `vw`, `px` or `%`. If no max-width is defined, the popover will extend to the width of its content.',
table: {
category: 'General',
- defaultValue: { summary: '280px' }
+ defaultValue: { summary: '280px' },
},
},
palette: {
@@ -104,6 +104,7 @@ function render(args: Args) {
class="${args.palette}"
id="${args.id}"
placement="${args.placement}"
+ close-button-caption="${args.closeButtonCaption}"
?arrow="${args.arrow}"
style="${args.maxWidth ? '--post-popover-max-width: ' + args.maxWidth : ''}"
>
diff --git a/packages/styles/src/variables/components/_tag.scss b/packages/styles/src/variables/components/_tag.scss
index 5bef1bf8c8..e9684772ab 100644
--- a/packages/styles/src/variables/components/_tag.scss
+++ b/packages/styles/src/variables/components/_tag.scss
@@ -21,9 +21,9 @@ $tag-sm-icon-size: $tag-font-size;
$tag-default-background: color.$gray-10;
$tag-backgrounds: (
'white': color.$white,
- 'yellow': color.$yellow,
+ 'info': color.$info,
'success': color.$success,
+ 'error': color.$error,
'warning': color.$warning,
- 'danger': color.$error,
- 'info': color.$info,
+ 'yellow': color.$yellow,
);