diff --git a/.changeset/chilly-books-exercise.md b/.changeset/chilly-books-exercise.md index 8512af7397..a94ab69851 100644 --- a/.changeset/chilly-books-exercise.md +++ b/.changeset/chilly-books-exercise.md @@ -2,4 +2,4 @@ '@baloise/design-system-components': patch --- -remove tab line if there is no active tab +Hide the tab line when no active tab is present. diff --git a/.changeset/giant-plums-explain.md b/.changeset/giant-plums-explain.md index 1b2cb7eb6a..8f25542922 100644 --- a/.changeset/giant-plums-explain.md +++ b/.changeset/giant-plums-explain.md @@ -4,4 +4,4 @@ '@baloise/design-system-css': patch --- -move sticky footer styles to core css file and updated bal-app for vue applications +Relocate sticky footer styles to the core CSS file and update 'bal-app' for Vue applications. diff --git a/.changeset/heavy-days-provide.md b/.changeset/heavy-days-provide.md index db9ffe9db9..e5cd0f21fc 100644 --- a/.changeset/heavy-days-provide.md +++ b/.changeset/heavy-days-provide.md @@ -2,4 +2,4 @@ '@baloise/design-system-components': patch --- -add a11y labels to accordion and pagination component +Include accessibility (a11y) labels for both the accordion and pagination components. diff --git a/.changeset/olive-insects-flow.md b/.changeset/olive-insects-flow.md new file mode 100644 index 0000000000..a1803c3d95 --- /dev/null +++ b/.changeset/olive-insects-flow.md @@ -0,0 +1,6 @@ +--- +'@baloise/design-system-components': patch +--- + +The datepicker, select, and number-input components trigger a blur event when they lose focus or when any associated pop-up or dropdown is closed. Additionally, the blur event occurs after the value change. + diff --git a/.changeset/perfect-forks-divide.md b/.changeset/perfect-forks-divide.md index 0fd97a9491..24c1b6fb8a 100644 --- a/.changeset/perfect-forks-divide.md +++ b/.changeset/perfect-forks-divide.md @@ -2,4 +2,4 @@ '@baloise/design-system-components': patch --- -scroll to top on mobile navbar toggle to prevent space between navbar brand and navbar menu +Implement a 'scroll to top' feature upon toggling the mobile navbar to prevent any space between the navbar brand and the menu. diff --git a/.changeset/red-cobras-provide.md b/.changeset/red-cobras-provide.md index 6fbb6754a7..6af961d3b4 100644 --- a/.changeset/red-cobras-provide.md +++ b/.changeset/red-cobras-provide.md @@ -2,4 +2,28 @@ '@baloise/design-system-testing': minor --- -testing library supports file by file import +The testing library now offers support for importing files on a per-file basis. + +```typescript +/** + * Here are the component-specific commands along with some helper functions. + * + * cy.waitForDesignSystem() + * cy.getByTestId('my-button') + */ +import '@baloise/design-system-testing/src/add-custom-commands' + +/** + * Presenting the refined Cypress basic commands, including optimized 'type' + * interactions for seamless functionality with web components + */ +import '@baloise/design-system-testing/src/add-override-commands' + +/** + * This is a collection of selectors tailored to locate the precise + * inner element of a component. + * + * cy.get(selectors.button.label) + */ +import { selectors } from '@baloise/design-system-testing/src/selectors' +``` \ No newline at end of file diff --git a/.changeset/smart-bulldogs-bake.md b/.changeset/smart-bulldogs-bake.md new file mode 100644 index 0000000000..41af560cb2 --- /dev/null +++ b/.changeset/smart-bulldogs-bake.md @@ -0,0 +1,5 @@ +--- +'@baloise/design-system-components': patch +--- + +Addressed event propagation issues and refined the event sequence for the number-input component. diff --git a/.changeset/smart-squids-dance.md b/.changeset/smart-squids-dance.md index 90fe2ad7bc..5bab451c00 100644 --- a/.changeset/smart-squids-dance.md +++ b/.changeset/smart-squids-dance.md @@ -2,4 +2,4 @@ '@baloise/design-system-components': minor --- -add tooltip component +Implement the tooltip component. diff --git a/.changeset/smooth-toes-dance.md b/.changeset/smooth-toes-dance.md index c6973c2e2d..85ce73657c 100644 --- a/.changeset/smooth-toes-dance.md +++ b/.changeset/smooth-toes-dance.md @@ -2,4 +2,4 @@ '@baloise/design-system-components-angular': minor --- -file-upload can be used with Angular reactive forms +The file-upload component is compatible with Angular reactive forms. diff --git a/.changeset/three-otters-shout.md b/.changeset/three-otters-shout.md new file mode 100644 index 0000000000..d47ba69955 --- /dev/null +++ b/.changeset/three-otters-shout.md @@ -0,0 +1,16 @@ +--- +'@baloise/design-system-components-angular': minor +--- + +Angular reactive forms mark a form control as invalid when it's been touched and is indeed invalid. +To enable this feature, set setInvalid in the design system configuration. + +```typescript +BaloiseDesignSystemModule.forRoot({ + defaults: { ... }, + forms: { + setInvalid: true, + invalidateOn: 'touched' // Alternatively, it can also be set to 'dirty'. + }, +}) +``` diff --git a/.changeset/tough-dogs-tie.md b/.changeset/tough-dogs-tie.md index dd1b74d41e..74ce65e615 100644 --- a/.changeset/tough-dogs-tie.md +++ b/.changeset/tough-dogs-tie.md @@ -2,4 +2,4 @@ '@baloise/design-system-output-target-angular': minor --- -provide component events as a stream +In Angular, component events can be accessed through an RxJS stream. diff --git a/.changeset/tricky-worms-lie.md b/.changeset/tricky-worms-lie.md index a1f3568893..d2b82be4df 100644 --- a/.changeset/tricky-worms-lie.md +++ b/.changeset/tricky-worms-lie.md @@ -2,4 +2,4 @@ '@baloise/design-system-components': patch --- -check if checkbox is checked in group +Verify if a checkbox is selected within a group. diff --git a/.changeset/twenty-panthers-pay.md b/.changeset/twenty-panthers-pay.md index aa1e6e1c91..9ef43d7afa 100644 --- a/.changeset/twenty-panthers-pay.md +++ b/.changeset/twenty-panthers-pay.md @@ -2,4 +2,4 @@ '@baloise/design-system-components': patch --- -connect aria-labelledby with the correct label and for attribute with the correct input +Ensure that `aria-labelledby` is correctly linked to the appropriate label, and that the `for` attribute corresponds to the correct input. diff --git a/.changeset/weak-rockets-whisper.md b/.changeset/weak-rockets-whisper.md index d68393c2b5..dd814a6107 100644 --- a/.changeset/weak-rockets-whisper.md +++ b/.changeset/weak-rockets-whisper.md @@ -2,4 +2,4 @@ '@baloise/design-system-components': patch --- -fix pattern issue with the number-input +Resolve pattern issue with the number-input component. diff --git a/.changeset/wise-dingos-jump.md b/.changeset/wise-dingos-jump.md new file mode 100644 index 0000000000..2f5d007005 --- /dev/null +++ b/.changeset/wise-dingos-jump.md @@ -0,0 +1,5 @@ +--- +'@baloise/design-system-components': minor +--- + +Implement a reverse layout option for the stack component. diff --git a/apps/angular/src/app/app.config.ts b/apps/angular/src/app/app.config.ts index c85bb3c68b..e8df12712d 100644 --- a/apps/angular/src/app/app.config.ts +++ b/apps/angular/src/app/app.config.ts @@ -19,6 +19,9 @@ export const appConfig: ApplicationConfig = { event: true, }, }, + forms: { + setInvalid: true, + }, }), ), ], diff --git a/apps/angular/src/app/form-components/checkbox-buttons.component.ts b/apps/angular/src/app/form-components/checkbox-buttons.component.ts index df2f317058..61c85845c3 100644 --- a/apps/angular/src/app/form-components/checkbox-buttons.component.ts +++ b/apps/angular/src/app/form-components/checkbox-buttons.component.ts @@ -12,11 +12,7 @@ import { UpdateControl } from '../app.component' Checkbox Buttons - + Checkbox Buttons Label diff --git a/apps/angular/src/app/form-components/checkbox-group.component.ts b/apps/angular/src/app/form-components/checkbox-group.component.ts index 6c076b9035..ed625c4111 100644 --- a/apps/angular/src/app/form-components/checkbox-group.component.ts +++ b/apps/angular/src/app/form-components/checkbox-group.component.ts @@ -12,11 +12,7 @@ import { UpdateControl } from '../app.component' Checkbox Group - + Checkbox Group Label diff --git a/apps/angular/src/app/form-components/checkbox.component.ts b/apps/angular/src/app/form-components/checkbox.component.ts index 8a2ea40e69..d627a309dd 100644 --- a/apps/angular/src/app/form-components/checkbox.component.ts +++ b/apps/angular/src/app/form-components/checkbox.component.ts @@ -12,11 +12,7 @@ import { UpdateControl } from '../app.component' Checkbox - + Checkbox Label My Checkbox diff --git a/apps/angular/src/app/form-components/date.component.ts b/apps/angular/src/app/form-components/date.component.ts index 3d9ffae0db..90ba47dd3d 100644 --- a/apps/angular/src/app/form-components/date.component.ts +++ b/apps/angular/src/app/form-components/date.component.ts @@ -12,11 +12,7 @@ import { UpdateControl } from '../app.component' Date - + Date Label diff --git a/apps/angular/src/app/form-components/input-slider.component.ts b/apps/angular/src/app/form-components/input-slider.component.ts index 16763c0196..8ae5df7b5a 100644 --- a/apps/angular/src/app/form-components/input-slider.component.ts +++ b/apps/angular/src/app/form-components/input-slider.component.ts @@ -12,11 +12,7 @@ import { UpdateControl } from '../app.component' Slider - + Slider Label diff --git a/apps/angular/src/app/form-components/input-stepper.component.ts b/apps/angular/src/app/form-components/input-stepper.component.ts index 3203fa8119..45068264d9 100644 --- a/apps/angular/src/app/form-components/input-stepper.component.ts +++ b/apps/angular/src/app/form-components/input-stepper.component.ts @@ -12,11 +12,7 @@ import { UpdateControl } from '../app.component' Input Stepper - + Input Stepper Label diff --git a/apps/angular/src/app/form-components/input.component.ts b/apps/angular/src/app/form-components/input.component.ts index 51616c1cf7..c38b679a4a 100644 --- a/apps/angular/src/app/form-components/input.component.ts +++ b/apps/angular/src/app/form-components/input.component.ts @@ -12,11 +12,7 @@ import { UpdateControl } from '../app.component' Input - + Input Label diff --git a/apps/angular/src/app/form-components/number-input.component.ts b/apps/angular/src/app/form-components/number-input.component.ts index 186c6e82a7..949bf25ee0 100644 --- a/apps/angular/src/app/form-components/number-input.component.ts +++ b/apps/angular/src/app/form-components/number-input.component.ts @@ -12,11 +12,7 @@ import { UpdateControl } from '../app.component' Number Input - + Number Input Label Radio Buttons - + Radio Buttons Label diff --git a/apps/angular/src/app/form-components/radio.component.ts b/apps/angular/src/app/form-components/radio.component.ts index 3d710ff9b2..64e28e8035 100644 --- a/apps/angular/src/app/form-components/radio.component.ts +++ b/apps/angular/src/app/form-components/radio.component.ts @@ -12,11 +12,7 @@ import { UpdateControl } from '../app.component' Radio - + Radio Label diff --git a/apps/angular/src/app/form-components/select-dropdown.component.ts b/apps/angular/src/app/form-components/select-dropdown.component.ts index 0fea8525fc..25ee7c871d 100644 --- a/apps/angular/src/app/form-components/select-dropdown.component.ts +++ b/apps/angular/src/app/form-components/select-dropdown.component.ts @@ -12,11 +12,7 @@ import { UpdateControl } from '../app.component' {{ label }} - + {{ label }} Label Textarea - + Textarea Label diff --git a/apps/angular/src/app/form-components/time.component.ts b/apps/angular/src/app/form-components/time.component.ts index 3d1390c265..afe75b687a 100644 --- a/apps/angular/src/app/form-components/time.component.ts +++ b/apps/angular/src/app/form-components/time.component.ts @@ -12,11 +12,7 @@ import { UpdateControl } from '../app.component' Time - + Time Label diff --git a/package-lock.json b/package-lock.json index dd1477a23b..a6609c55eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -59,7 +59,7 @@ "@typescript-eslint/eslint-plugin": "~5.59.0", "@typescript-eslint/parser": "~5.59.0", "@vitejs/plugin-vue": "~4.2.3", - "@vitest/ui": "~0.30.1", + "@vitest/ui": "~0.34.2", "ag-grid-community": "~29.3.0", "archiver": "~5.3.0", "autoprefixer": "^10.4.13", @@ -109,7 +109,7 @@ "turbo": "~1.9.4", "typescript": "~4.6.4", "vite": "~4.4.9", - "vitest": "~0.34.1", + "vitest": "~0.34.2", "vue": "~3.3.4", "workbox-build": "~4.3.1", "zone.js": "~0.11.4" @@ -6397,9 +6397,9 @@ "link": true }, "node_modules/@baloise/web-app-utils": { - "version": "3.11.1", - "resolved": "https://registry.npmjs.org/@baloise/web-app-utils/-/web-app-utils-3.11.1.tgz", - "integrity": "sha512-+ccSXhi4s3XahCQ8M67YDyETOuBkYJZRfaE+DcLeKe1QAb01B2OUF8xTv7/C/MQi55PKeZDXL5vniobLnp+ilQ==", + "version": "3.11.2", + "resolved": "https://registry.npmjs.org/@baloise/web-app-utils/-/web-app-utils-3.11.2.tgz", + "integrity": "sha512-fgHv6p3EPbuYYanIvwTHAhx3Rj8mNYsIMKH5KRvbziYPauVrv+gbd9SWyXsxKO9Po13lMRcwExAh7rs+fNH2nA==", "dependencies": { "date-fns": "^2.28.0", "lodash.camelcase": "^4.3.0", @@ -8998,9 +8998,9 @@ } }, "node_modules/@jest/schemas": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.0.tgz", - "integrity": "sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "dev": true, "dependencies": { "@sinclair/typebox": "^0.27.8" @@ -13655,66 +13655,26 @@ } }, "node_modules/@vitest/expect": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.34.1.tgz", - "integrity": "sha512-q2CD8+XIsQ+tHwypnoCk8Mnv5e6afLFvinVGCq3/BOT4kQdVQmY6rRfyKkwcg635lbliLPqbunXZr+L1ssUWiQ==", + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.34.3.tgz", + "integrity": "sha512-F8MTXZUYRBVsYL1uoIft1HHWhwDbSzwAU9Zgh8S6WFC3YgVb4AnFV2GXO3P5Em8FjEYaZtTnQYoNwwBrlOMXgg==", "dev": true, "dependencies": { - "@vitest/spy": "0.34.1", - "@vitest/utils": "0.34.1", + "@vitest/spy": "0.34.3", + "@vitest/utils": "0.34.3", "chai": "^4.3.7" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/expect/node_modules/@vitest/utils": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.34.1.tgz", - "integrity": "sha512-/ql9dsFi4iuEbiNcjNHQWXBum7aL8pyhxvfnD9gNtbjR9fUKAjxhj4AA3yfLXg6gJpMGGecvtF8Au2G9y3q47Q==", - "dev": true, - "dependencies": { - "diff-sequences": "^29.4.3", - "loupe": "^2.3.6", - "pretty-format": "^29.5.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/expect/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@vitest/expect/node_modules/pretty-format": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.2.tgz", - "integrity": "sha512-1q0oC8eRveTg5nnBEWMXAU2qpv65Gnuf2eCQzSjxpWFkPaPARwqZZDGuNE0zPAZfTCHzIk3A8dIjwlQKKLphyg==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/@vitest/runner": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.34.1.tgz", - "integrity": "sha512-YfQMpYzDsYB7yqgmlxZ06NI4LurHWfrH7Wy3Pvf/z/vwUSgq1zLAb1lWcItCzQG+NVox+VvzlKQrYEXb47645g==", + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.34.3.tgz", + "integrity": "sha512-lYNq7N3vR57VMKMPLVvmJoiN4bqwzZ1euTW+XXYH5kzr3W/+xQG3b41xJn9ChJ3AhYOSoweu974S1V3qDcFESA==", "dev": true, "dependencies": { - "@vitest/utils": "0.34.1", + "@vitest/utils": "0.34.3", "p-limit": "^4.0.0", "pathe": "^1.1.1" }, @@ -13722,32 +13682,6 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/runner/node_modules/@vitest/utils": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.34.1.tgz", - "integrity": "sha512-/ql9dsFi4iuEbiNcjNHQWXBum7aL8pyhxvfnD9gNtbjR9fUKAjxhj4AA3yfLXg6gJpMGGecvtF8Au2G9y3q47Q==", - "dev": true, - "dependencies": { - "diff-sequences": "^29.4.3", - "loupe": "^2.3.6", - "pretty-format": "^29.5.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/@vitest/runner/node_modules/p-limit": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", @@ -13763,24 +13697,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@vitest/runner/node_modules/pretty-format": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.2.tgz", - "integrity": "sha512-1q0oC8eRveTg5nnBEWMXAU2qpv65Gnuf2eCQzSjxpWFkPaPARwqZZDGuNE0zPAZfTCHzIk3A8dIjwlQKKLphyg==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/@vitest/snapshot": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-0.34.1.tgz", - "integrity": "sha512-0O9LfLU0114OqdF8lENlrLsnn024Tb1CsS9UwG0YMWY2oGTQfPtkW+B/7ieyv0X9R2Oijhi3caB1xgGgEgclSQ==", + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-0.34.3.tgz", + "integrity": "sha512-QyPaE15DQwbnIBp/yNJ8lbvXTZxS00kRly0kfFgAD5EYmCbYcA+1EEyRalc93M0gosL/xHeg3lKAClIXYpmUiQ==", "dev": true, "dependencies": { "magic-string": "^0.30.1", @@ -13791,22 +13711,10 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/snapshot/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/@vitest/snapshot/node_modules/magic-string": { - "version": "0.30.2", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.2.tgz", - "integrity": "sha512-lNZdu7pewtq/ZvWUp9Wpf/x7WzMTsR26TWV03BRZrXFsv+BI6dy8RAiKgm1uM/kyR0rCfUcqvOlXKG66KhIGug==", + "version": "0.30.3", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.3.tgz", + "integrity": "sha512-B7xGbll2fG/VjP+SWg4sX3JynwIU0mjoTc6MPpKNuIvftk6u6vqhDnk1R80b8C2GBR6ywqy+1DcKBrevBg+bmw==", "dev": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" @@ -13815,24 +13723,10 @@ "node": ">=12" } }, - "node_modules/@vitest/snapshot/node_modules/pretty-format": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.2.tgz", - "integrity": "sha512-1q0oC8eRveTg5nnBEWMXAU2qpv65Gnuf2eCQzSjxpWFkPaPARwqZZDGuNE0zPAZfTCHzIk3A8dIjwlQKKLphyg==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/@vitest/spy": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.34.1.tgz", - "integrity": "sha512-UT4WcI3EAPUNO8n6y9QoEqynGGEPmmRxC+cLzneFFXpmacivjHZsNbiKD88KUScv5DCHVDgdBsLD7O7s1enFcQ==", + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.34.3.tgz", + "integrity": "sha512-N1V0RFQ6AI7CPgzBq9kzjRdPIgThC340DGjdKdPSE8r86aUSmeliTUgkTqLSgtEwWWsGfBQ+UetZWhK0BgJmkQ==", "dev": true, "dependencies": { "tinyspy": "^2.1.1" @@ -13842,29 +13736,54 @@ } }, "node_modules/@vitest/ui": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-0.30.1.tgz", - "integrity": "sha512-Izz4ElDmdvX02KImSC2nCJI6CsGo9aETbKqxli55M0rbbPPAMtF0zDcJIqgEP5V6Y+4Ysf6wvsjLbLCTnaBvKw==", + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-0.34.3.tgz", + "integrity": "sha512-iNcOQ0xML9znOReiwpKJrTLSj5zFxmveD3VCxIJNqnsaMYpONSbSiiJLC1Y1dYlkmiHylp+ElNcUZYIMWdxRvA==", "dev": true, "dependencies": { - "@vitest/utils": "0.30.1", - "fast-glob": "^3.2.12", - "fflate": "^0.7.4", + "@vitest/utils": "0.34.3", + "fast-glob": "^3.3.0", + "fflate": "^0.8.0", "flatted": "^3.2.7", - "pathe": "^1.1.0", + "pathe": "^1.1.1", "picocolors": "^1.0.0", - "sirv": "^2.0.2" + "sirv": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "vitest": ">=0.30.1 <1" + } + }, + "node_modules/@vitest/ui/node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" } }, "node_modules/@vitest/utils": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.30.1.tgz", - "integrity": "sha512-/c8Xv2zUVc+rnNt84QF0Y0zkfxnaGhp87K2dYJMLtLOIckPzuxLVzAtFCicGFdB4NeBHNzTRr1tNn7rCtQcWFA==", + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.34.3.tgz", + "integrity": "sha512-kiSnzLG6m/tiT0XEl4U2H8JDBjFtwVlaE8I3QfGiMFR0QvnRDfYfdP3YvTBWM/6iJDAyaPY6yVQiCTUc7ZzTHA==", "dev": true, "dependencies": { - "concordance": "^5.0.4", + "diff-sequences": "^29.4.3", "loupe": "^2.3.6", - "pretty-format": "^27.5.1" + "pretty-format": "^29.5.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, "node_modules/@vue/compiler-core": { @@ -16082,12 +16001,6 @@ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, - "node_modules/blueimp-md5": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.19.0.tgz", - "integrity": "sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==", - "dev": true - }, "node_modules/bn.js": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", @@ -16886,9 +16799,9 @@ } }, "node_modules/chai": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", - "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.8.tgz", + "integrity": "sha512-vX4YvVVtxlfSZ2VecZgFUTU5qPCYsobVI2O9FmwEXBhDigYGQA6jRXCycIs1yJnnWbZ6/+a2zNIF5DfVCcJBFQ==", "dev": true, "dependencies": { "assertion-error": "^1.1.0", @@ -18501,25 +18414,6 @@ "safe-buffer": "~5.1.0" } }, - "node_modules/concordance": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/concordance/-/concordance-5.0.4.tgz", - "integrity": "sha512-OAcsnTEYu1ARJqWVGwf4zh4JDfHZEaSNlNccFmt8YjB2l/n19/PF2viLINHc57vO4FKIAFl2FWASIGZZWZ2Kxw==", - "dev": true, - "dependencies": { - "date-time": "^3.1.0", - "esutils": "^2.0.3", - "fast-diff": "^1.2.0", - "js-string-escape": "^1.0.1", - "lodash": "^4.17.15", - "md5-hex": "^3.0.1", - "semver": "^7.3.2", - "well-known-symbols": "^2.0.0" - }, - "engines": { - "node": ">=10.18.0 <11 || >=12.14.0 <13 || >=14" - } - }, "node_modules/concurrently": { "version": "7.6.0", "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-7.6.0.tgz", @@ -21088,18 +20982,6 @@ "node": ">=4.0" } }, - "node_modules/date-time": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/date-time/-/date-time-3.1.0.tgz", - "integrity": "sha512-uqCUKXE5q1PNBXjPqvwhwJf9SwMoAHBgWJ6DcrnS5o+W2JOiIILl0JEdVD8SGujrNS02GGxgwAg2PN2zONgtjg==", - "dev": true, - "dependencies": { - "time-zone": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/dayjs": { "version": "1.11.7", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz", @@ -21878,9 +21760,9 @@ } }, "node_modules/diff-sequences": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", - "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", "dev": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -24038,9 +23920,9 @@ "dev": true }, "node_modules/fflate": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz", - "integrity": "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.0.tgz", + "integrity": "sha512-FAdS4qMuFjsJj6XHbBaZeXOgaypXp8iw/Tpyuq/w3XA41jjLHT8NPA+n7czH/DDhdncq0nAyDZmPeWXh2qmdIg==", "dev": true }, "node_modules/figgy-pudding": { @@ -30866,18 +30748,6 @@ "node": ">=6.0" } }, - "node_modules/md5-hex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/md5-hex/-/md5-hex-3.0.1.tgz", - "integrity": "sha512-BUiRtTtV39LIJwinWBjqVsU9xhdnz7/i889V859IBFpuqGAj6LuOvHv5XLbgZ2R7ptJoJaEcxkv88/h25T7Ciw==", - "dev": true, - "dependencies": { - "blueimp-md5": "^2.10.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -31606,15 +31476,15 @@ } }, "node_modules/mlly": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.4.0.tgz", - "integrity": "sha512-ua8PAThnTwpprIaU47EPeZ/bPUVp2QYBbWMphUQpVdBI3Lgqzm5KZQ45Agm3YJedHXaIHl6pBGabaLSUPPSptg==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.4.1.tgz", + "integrity": "sha512-SCDs78Q2o09jiZiE2WziwVBEqXQ02XkGdUy45cbJf+BpYRIjArXRJ1Wbowxkb+NaM9DWvS3UC9GiO/6eqvQ/pg==", "dev": true, "dependencies": { - "acorn": "^8.9.0", + "acorn": "^8.10.0", "pathe": "^1.1.1", "pkg-types": "^1.0.3", - "ufo": "^1.1.2" + "ufo": "^1.3.0" } }, "node_modules/module-definition": { @@ -35712,17 +35582,17 @@ } }, "node_modules/pretty-format": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.3.tgz", + "integrity": "sha512-ZsBgjVhFAj5KeK+nHfF1305/By3lechHQSMWCTl8iHSbfOm2TN5nHEtFc/+W7fAyUeCs2n5iow72gld4gW0xDw==", "dev": true, "dependencies": { - "ansi-regex": "^5.0.1", + "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" + "react-is": "^18.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/pretty-format/node_modules/ansi-styles": { @@ -35737,12 +35607,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/pretty-format/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true - }, "node_modules/pretty-hrtime": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", @@ -41707,15 +41571,6 @@ "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", "dev": true }, - "node_modules/time-zone": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/time-zone/-/time-zone-1.0.0.tgz", - "integrity": "sha512-TIsDdtKo6+XrPtiTm1ssmMngN1sAhyKnTO2kunQWqNPWIVvCm15Wmw4SWInwTVgJ5u/Tr04+8Ei9TNcw4x4ONA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/timed-out": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", @@ -42831,9 +42686,9 @@ } }, "node_modules/ufo": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.2.0.tgz", - "integrity": "sha512-RsPyTbqORDNDxqAdQPQBpgqhWle1VcTSou/FraClYlHf6TZnQcGslpLcAphNR+sQW4q5lLWLbOsRlh9j24baQg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.3.0.tgz", + "integrity": "sha512-bRn3CsoojyNStCZe0BG0Mt4Nr/4KF+rhFlnNXybgqt5pXHNFRlqinSoQaTrGyzE4X8aHplSb+TorH+COin9Yxw==", "dev": true }, "node_modules/uglify-js": { @@ -44056,9 +43911,9 @@ } }, "node_modules/vite-node": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-0.34.1.tgz", - "integrity": "sha512-odAZAL9xFMuAg8aWd7nSPT+hU8u2r9gU3LRm9QKjxBEF2rRdWpMuqkrkjvyVQEdNFiBctqr2Gg4uJYizm5Le6w==", + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-0.34.3.tgz", + "integrity": "sha512-+0TzJf1g0tYXj6tR2vEyiA42OPq68QkRZCu/ERSo2PtsDJfBpDyEfuKbRvLmZqi/CgC7SCBtyC+WjTGNMRIaig==", "dev": true, "dependencies": { "cac": "^6.7.14", @@ -44171,19 +44026,19 @@ } }, "node_modules/vitest": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.34.1.tgz", - "integrity": "sha512-G1PzuBEq9A75XSU88yO5G4vPT20UovbC/2osB2KEuV/FisSIIsw7m5y2xMdB7RsAGHAfg2lPmp2qKr3KWliVlQ==", + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.34.3.tgz", + "integrity": "sha512-7+VA5Iw4S3USYk+qwPxHl8plCMhA5rtfwMjgoQXMT7rO5ldWcdsdo3U1QD289JgglGK4WeOzgoLTsGFu6VISyQ==", "dev": true, "dependencies": { "@types/chai": "^4.3.5", "@types/chai-subset": "^1.3.3", "@types/node": "*", - "@vitest/expect": "0.34.1", - "@vitest/runner": "0.34.1", - "@vitest/snapshot": "0.34.1", - "@vitest/spy": "0.34.1", - "@vitest/utils": "0.34.1", + "@vitest/expect": "0.34.3", + "@vitest/runner": "0.34.3", + "@vitest/snapshot": "0.34.3", + "@vitest/spy": "0.34.3", + "@vitest/utils": "0.34.3", "acorn": "^8.9.0", "acorn-walk": "^8.2.0", "cac": "^6.7.14", @@ -44198,7 +44053,7 @@ "tinybench": "^2.5.0", "tinypool": "^0.7.0", "vite": "^3.0.0 || ^4.0.0", - "vite-node": "0.34.1", + "vite-node": "0.34.3", "why-is-node-running": "^2.2.2" }, "bin": { @@ -44247,32 +44102,6 @@ } } }, - "node_modules/vitest/node_modules/@vitest/utils": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.34.1.tgz", - "integrity": "sha512-/ql9dsFi4iuEbiNcjNHQWXBum7aL8pyhxvfnD9gNtbjR9fUKAjxhj4AA3yfLXg6gJpMGGecvtF8Au2G9y3q47Q==", - "dev": true, - "dependencies": { - "diff-sequences": "^29.4.3", - "loupe": "^2.3.6", - "pretty-format": "^29.5.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/vitest/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/vitest/node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -44308,20 +44137,6 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "node_modules/vitest/node_modules/pretty-format": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.2.tgz", - "integrity": "sha512-1q0oC8eRveTg5nnBEWMXAU2qpv65Gnuf2eCQzSjxpWFkPaPARwqZZDGuNE0zPAZfTCHzIk3A8dIjwlQKKLphyg==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.6.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/vm-browserify": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", @@ -45846,15 +45661,6 @@ "node": ">=0.8.0" } }, - "node_modules/well-known-symbols": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/well-known-symbols/-/well-known-symbols-2.0.0.tgz", - "integrity": "sha512-ZMjC3ho+KXo0BfJb7JgtQ5IBuvnShdlACNkKkdsqBmYw3bPAaJfPeYUo6tLUaT5tG/Gkh7xkpBhKRQ9e7pyg9Q==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/whatwg-encoding": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", @@ -46606,7 +46412,7 @@ "@baloise/design-system-fonts": "13.6.2", "@baloise/design-system-icons": "13.6.2", "@baloise/design-system-tokens": "13.6.2", - "@baloise/web-app-utils": "3.11.1", + "@baloise/web-app-utils": "3.11.2", "@floating-ui/dom": "^1.2.9", "@popperjs/core": "~2.11.4", "@stencil/core": "^3.2.2", @@ -48532,7 +48338,7 @@ "@baloise/design-system-output-target-react": "13.6.2", "@baloise/design-system-output-target-vue": "13.6.2", "@baloise/design-system-tokens": "13.6.2", - "@baloise/web-app-utils": "3.11.1", + "@baloise/web-app-utils": "3.11.2", "@floating-ui/dom": "^1.2.9", "@popperjs/core": "~2.11.4", "@stencil/core": "^3.2.2", @@ -48644,9 +48450,9 @@ "version": "file:packages/tokens" }, "@baloise/web-app-utils": { - "version": "3.11.1", - "resolved": "https://registry.npmjs.org/@baloise/web-app-utils/-/web-app-utils-3.11.1.tgz", - "integrity": "sha512-+ccSXhi4s3XahCQ8M67YDyETOuBkYJZRfaE+DcLeKe1QAb01B2OUF8xTv7/C/MQi55PKeZDXL5vniobLnp+ilQ==", + "version": "3.11.2", + "resolved": "https://registry.npmjs.org/@baloise/web-app-utils/-/web-app-utils-3.11.2.tgz", + "integrity": "sha512-fgHv6p3EPbuYYanIvwTHAhx3Rj8mNYsIMKH5KRvbziYPauVrv+gbd9SWyXsxKO9Po13lMRcwExAh7rs+fNH2nA==", "requires": { "date-fns": "^2.28.0", "lodash.camelcase": "^4.3.0", @@ -50608,9 +50414,9 @@ "dev": true }, "@jest/schemas": { - "version": "29.6.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.0.tgz", - "integrity": "sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "dev": true, "requires": { "@sinclair/typebox": "^0.27.8" @@ -54171,74 +53977,27 @@ "requires": {} }, "@vitest/expect": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.34.1.tgz", - "integrity": "sha512-q2CD8+XIsQ+tHwypnoCk8Mnv5e6afLFvinVGCq3/BOT4kQdVQmY6rRfyKkwcg635lbliLPqbunXZr+L1ssUWiQ==", + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.34.3.tgz", + "integrity": "sha512-F8MTXZUYRBVsYL1uoIft1HHWhwDbSzwAU9Zgh8S6WFC3YgVb4AnFV2GXO3P5Em8FjEYaZtTnQYoNwwBrlOMXgg==", "dev": true, "requires": { - "@vitest/spy": "0.34.1", - "@vitest/utils": "0.34.1", + "@vitest/spy": "0.34.3", + "@vitest/utils": "0.34.3", "chai": "^4.3.7" - }, - "dependencies": { - "@vitest/utils": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.34.1.tgz", - "integrity": "sha512-/ql9dsFi4iuEbiNcjNHQWXBum7aL8pyhxvfnD9gNtbjR9fUKAjxhj4AA3yfLXg6gJpMGGecvtF8Au2G9y3q47Q==", - "dev": true, - "requires": { - "diff-sequences": "^29.4.3", - "loupe": "^2.3.6", - "pretty-format": "^29.5.0" - } - }, - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "pretty-format": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.2.tgz", - "integrity": "sha512-1q0oC8eRveTg5nnBEWMXAU2qpv65Gnuf2eCQzSjxpWFkPaPARwqZZDGuNE0zPAZfTCHzIk3A8dIjwlQKKLphyg==", - "dev": true, - "requires": { - "@jest/schemas": "^29.6.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - } - } } }, "@vitest/runner": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.34.1.tgz", - "integrity": "sha512-YfQMpYzDsYB7yqgmlxZ06NI4LurHWfrH7Wy3Pvf/z/vwUSgq1zLAb1lWcItCzQG+NVox+VvzlKQrYEXb47645g==", + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.34.3.tgz", + "integrity": "sha512-lYNq7N3vR57VMKMPLVvmJoiN4bqwzZ1euTW+XXYH5kzr3W/+xQG3b41xJn9ChJ3AhYOSoweu974S1V3qDcFESA==", "dev": true, "requires": { - "@vitest/utils": "0.34.1", + "@vitest/utils": "0.34.3", "p-limit": "^4.0.0", "pathe": "^1.1.1" }, "dependencies": { - "@vitest/utils": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.34.1.tgz", - "integrity": "sha512-/ql9dsFi4iuEbiNcjNHQWXBum7aL8pyhxvfnD9gNtbjR9fUKAjxhj4AA3yfLXg6gJpMGGecvtF8Au2G9y3q47Q==", - "dev": true, - "requires": { - "diff-sequences": "^29.4.3", - "loupe": "^2.3.6", - "pretty-format": "^29.5.0" - } - }, - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, "p-limit": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", @@ -54247,24 +54006,13 @@ "requires": { "yocto-queue": "^1.0.0" } - }, - "pretty-format": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.2.tgz", - "integrity": "sha512-1q0oC8eRveTg5nnBEWMXAU2qpv65Gnuf2eCQzSjxpWFkPaPARwqZZDGuNE0zPAZfTCHzIk3A8dIjwlQKKLphyg==", - "dev": true, - "requires": { - "@jest/schemas": "^29.6.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - } } } }, "@vitest/snapshot": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-0.34.1.tgz", - "integrity": "sha512-0O9LfLU0114OqdF8lENlrLsnn024Tb1CsS9UwG0YMWY2oGTQfPtkW+B/7ieyv0X9R2Oijhi3caB1xgGgEgclSQ==", + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-0.34.3.tgz", + "integrity": "sha512-QyPaE15DQwbnIBp/yNJ8lbvXTZxS00kRly0kfFgAD5EYmCbYcA+1EEyRalc93M0gosL/xHeg3lKAClIXYpmUiQ==", "dev": true, "requires": { "magic-string": "^0.30.1", @@ -54272,67 +54020,65 @@ "pretty-format": "^29.5.0" }, "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, "magic-string": { - "version": "0.30.2", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.2.tgz", - "integrity": "sha512-lNZdu7pewtq/ZvWUp9Wpf/x7WzMTsR26TWV03BRZrXFsv+BI6dy8RAiKgm1uM/kyR0rCfUcqvOlXKG66KhIGug==", + "version": "0.30.3", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.3.tgz", + "integrity": "sha512-B7xGbll2fG/VjP+SWg4sX3JynwIU0mjoTc6MPpKNuIvftk6u6vqhDnk1R80b8C2GBR6ywqy+1DcKBrevBg+bmw==", "dev": true, "requires": { "@jridgewell/sourcemap-codec": "^1.4.15" } - }, - "pretty-format": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.2.tgz", - "integrity": "sha512-1q0oC8eRveTg5nnBEWMXAU2qpv65Gnuf2eCQzSjxpWFkPaPARwqZZDGuNE0zPAZfTCHzIk3A8dIjwlQKKLphyg==", - "dev": true, - "requires": { - "@jest/schemas": "^29.6.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - } } } }, "@vitest/spy": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.34.1.tgz", - "integrity": "sha512-UT4WcI3EAPUNO8n6y9QoEqynGGEPmmRxC+cLzneFFXpmacivjHZsNbiKD88KUScv5DCHVDgdBsLD7O7s1enFcQ==", + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.34.3.tgz", + "integrity": "sha512-N1V0RFQ6AI7CPgzBq9kzjRdPIgThC340DGjdKdPSE8r86aUSmeliTUgkTqLSgtEwWWsGfBQ+UetZWhK0BgJmkQ==", "dev": true, "requires": { "tinyspy": "^2.1.1" } }, "@vitest/ui": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-0.30.1.tgz", - "integrity": "sha512-Izz4ElDmdvX02KImSC2nCJI6CsGo9aETbKqxli55M0rbbPPAMtF0zDcJIqgEP5V6Y+4Ysf6wvsjLbLCTnaBvKw==", + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-0.34.3.tgz", + "integrity": "sha512-iNcOQ0xML9znOReiwpKJrTLSj5zFxmveD3VCxIJNqnsaMYpONSbSiiJLC1Y1dYlkmiHylp+ElNcUZYIMWdxRvA==", "dev": true, "requires": { - "@vitest/utils": "0.30.1", - "fast-glob": "^3.2.12", - "fflate": "^0.7.4", + "@vitest/utils": "0.34.3", + "fast-glob": "^3.3.0", + "fflate": "^0.8.0", "flatted": "^3.2.7", - "pathe": "^1.1.0", + "pathe": "^1.1.1", "picocolors": "^1.0.0", - "sirv": "^2.0.2" + "sirv": "^2.0.3" + }, + "dependencies": { + "fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + } + } } }, "@vitest/utils": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.30.1.tgz", - "integrity": "sha512-/c8Xv2zUVc+rnNt84QF0Y0zkfxnaGhp87K2dYJMLtLOIckPzuxLVzAtFCicGFdB4NeBHNzTRr1tNn7rCtQcWFA==", + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.34.3.tgz", + "integrity": "sha512-kiSnzLG6m/tiT0XEl4U2H8JDBjFtwVlaE8I3QfGiMFR0QvnRDfYfdP3YvTBWM/6iJDAyaPY6yVQiCTUc7ZzTHA==", "dev": true, "requires": { - "concordance": "^5.0.4", + "diff-sequences": "^29.4.3", "loupe": "^2.3.6", - "pretty-format": "^27.5.1" + "pretty-format": "^29.5.0" } }, "@vue/compiler-core": { @@ -58503,12 +58249,6 @@ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, - "blueimp-md5": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.19.0.tgz", - "integrity": "sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==", - "dev": true - }, "bn.js": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", @@ -59120,9 +58860,9 @@ "dev": true }, "chai": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", - "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.8.tgz", + "integrity": "sha512-vX4YvVVtxlfSZ2VecZgFUTU5qPCYsobVI2O9FmwEXBhDigYGQA6jRXCycIs1yJnnWbZ6/+a2zNIF5DfVCcJBFQ==", "dev": true, "requires": { "assertion-error": "^1.1.0", @@ -60437,22 +60177,6 @@ } } }, - "concordance": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/concordance/-/concordance-5.0.4.tgz", - "integrity": "sha512-OAcsnTEYu1ARJqWVGwf4zh4JDfHZEaSNlNccFmt8YjB2l/n19/PF2viLINHc57vO4FKIAFl2FWASIGZZWZ2Kxw==", - "dev": true, - "requires": { - "date-time": "^3.1.0", - "esutils": "^2.0.3", - "fast-diff": "^1.2.0", - "js-string-escape": "^1.0.1", - "lodash": "^4.17.15", - "md5-hex": "^3.0.1", - "semver": "^7.3.2", - "well-known-symbols": "^2.0.0" - } - }, "concurrently": { "version": "7.6.0", "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-7.6.0.tgz", @@ -62433,15 +62157,6 @@ "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", "dev": true }, - "date-time": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/date-time/-/date-time-3.1.0.tgz", - "integrity": "sha512-uqCUKXE5q1PNBXjPqvwhwJf9SwMoAHBgWJ6DcrnS5o+W2JOiIILl0JEdVD8SGujrNS02GGxgwAg2PN2zONgtjg==", - "dev": true, - "requires": { - "time-zone": "^1.0.0" - } - }, "dayjs": { "version": "1.11.7", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz", @@ -63022,9 +62737,9 @@ "dev": true }, "diff-sequences": { - "version": "29.4.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", - "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", "dev": true }, "diffie-hellman": { @@ -64660,9 +64375,9 @@ "dev": true }, "fflate": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz", - "integrity": "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.0.tgz", + "integrity": "sha512-FAdS4qMuFjsJj6XHbBaZeXOgaypXp8iw/Tpyuq/w3XA41jjLHT8NPA+n7czH/DDhdncq0nAyDZmPeWXh2qmdIg==", "dev": true }, "figgy-pudding": { @@ -69855,15 +69570,6 @@ "integrity": "sha512-UC0qFwyAjn4YdPpKaDNw6gNxRf7Mcx7jC1UGCY4boCzgvU2Aoc1mOGzTtrjjLKhM5ivsnhoKpQVxKPp+1j1qwg==", "dev": true }, - "md5-hex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/md5-hex/-/md5-hex-3.0.1.tgz", - "integrity": "sha512-BUiRtTtV39LIJwinWBjqVsU9xhdnz7/i889V859IBFpuqGAj6LuOvHv5XLbgZ2R7ptJoJaEcxkv88/h25T7Ciw==", - "dev": true, - "requires": { - "blueimp-md5": "^2.10.0" - } - }, "md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -70468,15 +70174,15 @@ "dev": true }, "mlly": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.4.0.tgz", - "integrity": "sha512-ua8PAThnTwpprIaU47EPeZ/bPUVp2QYBbWMphUQpVdBI3Lgqzm5KZQ45Agm3YJedHXaIHl6pBGabaLSUPPSptg==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.4.1.tgz", + "integrity": "sha512-SCDs78Q2o09jiZiE2WziwVBEqXQ02XkGdUy45cbJf+BpYRIjArXRJ1Wbowxkb+NaM9DWvS3UC9GiO/6eqvQ/pg==", "dev": true, "requires": { - "acorn": "^8.9.0", + "acorn": "^8.10.0", "pathe": "^1.1.1", "pkg-types": "^1.0.3", - "ufo": "^1.1.2" + "ufo": "^1.3.0" } }, "module-definition": { @@ -73527,14 +73233,14 @@ } }, "pretty-format": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.3.tgz", + "integrity": "sha512-ZsBgjVhFAj5KeK+nHfF1305/By3lechHQSMWCTl8iHSbfOm2TN5nHEtFc/+W7fAyUeCs2n5iow72gld4gW0xDw==", "dev": true, "requires": { - "ansi-regex": "^5.0.1", + "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" + "react-is": "^18.0.0" }, "dependencies": { "ansi-styles": { @@ -73542,12 +73248,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true - }, - "react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true } } }, @@ -78298,12 +77998,6 @@ "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", "dev": true }, - "time-zone": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/time-zone/-/time-zone-1.0.0.tgz", - "integrity": "sha512-TIsDdtKo6+XrPtiTm1ssmMngN1sAhyKnTO2kunQWqNPWIVvCm15Wmw4SWInwTVgJ5u/Tr04+8Ei9TNcw4x4ONA==", - "dev": true - }, "timed-out": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", @@ -79112,9 +78806,9 @@ "dev": true }, "ufo": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.2.0.tgz", - "integrity": "sha512-RsPyTbqORDNDxqAdQPQBpgqhWle1VcTSou/FraClYlHf6TZnQcGslpLcAphNR+sQW4q5lLWLbOsRlh9j24baQg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.3.0.tgz", + "integrity": "sha512-bRn3CsoojyNStCZe0BG0Mt4Nr/4KF+rhFlnNXybgqt5pXHNFRlqinSoQaTrGyzE4X8aHplSb+TorH+COin9Yxw==", "dev": true }, "uglify-js": { @@ -80074,9 +79768,9 @@ } }, "vite-node": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-0.34.1.tgz", - "integrity": "sha512-odAZAL9xFMuAg8aWd7nSPT+hU8u2r9gU3LRm9QKjxBEF2rRdWpMuqkrkjvyVQEdNFiBctqr2Gg4uJYizm5Le6w==", + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-0.34.3.tgz", + "integrity": "sha512-+0TzJf1g0tYXj6tR2vEyiA42OPq68QkRZCu/ERSo2PtsDJfBpDyEfuKbRvLmZqi/CgC7SCBtyC+WjTGNMRIaig==", "dev": true, "requires": { "cac": "^6.7.14", @@ -80105,19 +79799,19 @@ } }, "vitest": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.34.1.tgz", - "integrity": "sha512-G1PzuBEq9A75XSU88yO5G4vPT20UovbC/2osB2KEuV/FisSIIsw7m5y2xMdB7RsAGHAfg2lPmp2qKr3KWliVlQ==", + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.34.3.tgz", + "integrity": "sha512-7+VA5Iw4S3USYk+qwPxHl8plCMhA5rtfwMjgoQXMT7rO5ldWcdsdo3U1QD289JgglGK4WeOzgoLTsGFu6VISyQ==", "dev": true, "requires": { "@types/chai": "^4.3.5", "@types/chai-subset": "^1.3.3", "@types/node": "*", - "@vitest/expect": "0.34.1", - "@vitest/runner": "0.34.1", - "@vitest/snapshot": "0.34.1", - "@vitest/spy": "0.34.1", - "@vitest/utils": "0.34.1", + "@vitest/expect": "0.34.3", + "@vitest/runner": "0.34.3", + "@vitest/snapshot": "0.34.3", + "@vitest/spy": "0.34.3", + "@vitest/utils": "0.34.3", "acorn": "^8.9.0", "acorn-walk": "^8.2.0", "cac": "^6.7.14", @@ -80132,27 +79826,10 @@ "tinybench": "^2.5.0", "tinypool": "^0.7.0", "vite": "^3.0.0 || ^4.0.0", - "vite-node": "0.34.1", + "vite-node": "0.34.3", "why-is-node-running": "^2.2.2" }, "dependencies": { - "@vitest/utils": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.34.1.tgz", - "integrity": "sha512-/ql9dsFi4iuEbiNcjNHQWXBum7aL8pyhxvfnD9gNtbjR9fUKAjxhj4AA3yfLXg6gJpMGGecvtF8Au2G9y3q47Q==", - "dev": true, - "requires": { - "diff-sequences": "^29.4.3", - "loupe": "^2.3.6", - "pretty-format": "^29.5.0" - } - }, - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -80176,17 +79853,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true - }, - "pretty-format": { - "version": "29.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.2.tgz", - "integrity": "sha512-1q0oC8eRveTg5nnBEWMXAU2qpv65Gnuf2eCQzSjxpWFkPaPARwqZZDGuNE0zPAZfTCHzIk3A8dIjwlQKKLphyg==", - "dev": true, - "requires": { - "@jest/schemas": "^29.6.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - } } } }, @@ -81426,12 +81092,6 @@ "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", "dev": true }, - "well-known-symbols": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/well-known-symbols/-/well-known-symbols-2.0.0.tgz", - "integrity": "sha512-ZMjC3ho+KXo0BfJb7JgtQ5IBuvnShdlACNkKkdsqBmYw3bPAaJfPeYUo6tLUaT5tG/Gkh7xkpBhKRQ9e7pyg9Q==", - "dev": true - }, "whatwg-encoding": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", diff --git a/package.json b/package.json index 021ee0b0e2..14977557b1 100644 --- a/package.json +++ b/package.json @@ -123,7 +123,7 @@ "@typescript-eslint/eslint-plugin": "~5.59.0", "@typescript-eslint/parser": "~5.59.0", "@vitejs/plugin-vue": "~4.2.3", - "@vitest/ui": "~0.30.1", + "@vitest/ui": "~0.34.2", "ag-grid-community": "~29.3.0", "archiver": "~5.3.0", "autoprefixer": "^10.4.13", @@ -173,7 +173,7 @@ "turbo": "~1.9.4", "typescript": "~4.6.4", "vite": "~4.4.9", - "vitest": "~0.34.1", + "vitest": "~0.34.2", "vue": "~3.3.4", "workbox-build": "~4.3.1", "zone.js": "~0.11.4" diff --git a/packages/components-angular/src/app-initialize.ts b/packages/components-angular/src/app-initialize.ts index 4272c62bee..5f58e854c7 100644 --- a/packages/components-angular/src/app-initialize.ts +++ b/packages/components-angular/src/app-initialize.ts @@ -6,6 +6,10 @@ import { raf } from './util/util' export interface BaloiseDesignSystemAngularConfig { applyPolyfills?: boolean defaults?: BalConfig + forms?: { + setInvalid?: boolean + invalidateOn?: 'touched' | 'dirty' + } } export const appInitialize = (config: BaloiseDesignSystemAngularConfig, doc: Document, zone: NgZone) => { diff --git a/packages/components-angular/src/components/error.component.ts b/packages/components-angular/src/components/error.component.ts index a4e00b44ed..01c73f9254 100755 --- a/packages/components-angular/src/components/error.component.ts +++ b/packages/components-angular/src/components/error.component.ts @@ -1,5 +1,17 @@ -import { Component, Host, HostBinding, Input, OnChanges, Optional, SkipSelf } from '@angular/core' +import { + Component, + Host, + HostBinding, + Inject, + Injector, + Input, + OnChanges, + OnInit, + Optional, + SkipSelf, +} from '@angular/core' import { AbstractControl, ControlContainer } from '@angular/forms' +import { BalConfigToken, BaloiseDesignSystemAngularConfig } from '../index' @Component({ selector: 'bal-ng-error', @@ -12,8 +24,9 @@ import { AbstractControl, ControlContainer } from '@angular/forms' `, ], }) -export class BalNgErrorComponent implements OnChanges { +export class BalNgErrorComponent implements OnChanges, OnInit { control?: AbstractControl | null + config!: BaloiseDesignSystemAngularConfig @Input() error?: string @@ -26,8 +39,13 @@ export class BalNgErrorComponent implements OnChanges { @Host() @SkipSelf() private controlContainer: ControlContainer, + @Inject(Injector) private injector: Injector, ) {} + ngOnInit(): void { + this.config = this.injector.get(BalConfigToken) as BaloiseDesignSystemAngularConfig + } + get hasError(): boolean { if ( this.control === undefined || @@ -37,7 +55,8 @@ export class BalNgErrorComponent implements OnChanges { ) { return false } else { - if (!this.control.touched) { + const invalidateOn = this.config?.forms?.invalidateOn || 'touched' + if (!this.control[invalidateOn]) { return false } diff --git a/packages/components-angular/src/index.ts b/packages/components-angular/src/index.ts index 22a9ce305b..622faf70dc 100644 --- a/packages/components-angular/src/index.ts +++ b/packages/components-angular/src/index.ts @@ -19,7 +19,8 @@ export { BalOrientationService } from './util/orientation.service' export { BalConfigService } from './util/config.service' // PACKAGE MODULE -export { BaloiseDesignSystemModule } from './module' +export type { BaloiseDesignSystemAngularConfig } from './app-initialize' +export { BaloiseDesignSystemModule, BalConfigToken } from './module' // HELPERS export { element, parseCustomEvent, ProxyComponent } from './helpers' diff --git a/packages/components-angular/src/module.ts b/packages/components-angular/src/module.ts index f7443c719b..4c5d2b6265 100644 --- a/packages/components-angular/src/module.ts +++ b/packages/components-angular/src/module.ts @@ -17,7 +17,7 @@ import { BalBreakpointsService } from './util/breakpoints.service' import { BalOrientationService } from './util/orientation.service' import { BalConfigService } from './util/config.service' -export const ConfigToken = new InjectionToken('USERCONFIG') +export const BalConfigToken = new InjectionToken('USERCONFIG') const DECLARATIONS = [ // generated proxies @@ -52,14 +52,14 @@ export class BaloiseDesignSystemModule { ngModule: BaloiseDesignSystemModule, providers: [ { - provide: ConfigToken, + provide: BalConfigToken, useValue: config, }, { provide: APP_INITIALIZER, useFactory: appInitialize, multi: true, - deps: [ConfigToken, DOCUMENT, NgZone], + deps: [BalConfigToken, DOCUMENT, NgZone], }, ], } diff --git a/packages/components/package.json b/packages/components/package.json index 89642c78a9..4476f54640 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -69,7 +69,7 @@ "@baloise/design-system-fonts": "13.6.2", "@baloise/design-system-icons": "13.6.2", "@baloise/design-system-tokens": "13.6.2", - "@baloise/web-app-utils": "3.11.1", + "@baloise/web-app-utils": "3.11.2", "@floating-ui/dom": "^1.2.9", "@popperjs/core": "~2.11.4", "@stencil/core": "^3.2.2", diff --git a/packages/components/src/components.d.ts b/packages/components/src/components.d.ts index 402f4e44fb..57662fc615 100644 --- a/packages/components/src/components.d.ts +++ b/packages/components/src/components.d.ts @@ -806,11 +806,13 @@ export namespace Components { * Disables all animation inside the bal-app. Can be used for simplify e2e testing. */ "animated": boolean; + "language"?: string; "logComponents": string; "logCustom": boolean; "logEvents": boolean; "logLifecycle": boolean; "logRender": boolean; + "region"?: string; "stickyFooter": boolean; } interface BalDocBanner { @@ -5059,11 +5061,13 @@ declare namespace LocalJSX { * Disables all animation inside the bal-app. Can be used for simplify e2e testing. */ "animated"?: boolean; + "language"?: string; "logComponents"?: string; "logCustom"?: boolean; "logEvents"?: boolean; "logLifecycle"?: boolean; "logRender"?: boolean; + "region"?: string; "stickyFooter"?: boolean; } interface BalDocBanner { @@ -5725,6 +5729,10 @@ declare namespace LocalJSX { * The name of the control, which is submitted with the form data. */ "name"?: string; + /** + * Emitted when a keyboard input occurred. + */ + "onBalBlur"?: (event: BalInputStepperCustomEvent) => void; /** * Emitted when the input value has changed. */ @@ -5733,6 +5741,10 @@ declare namespace LocalJSX { * Emitted when the input value has decreased. */ "onBalDecrease"?: (event: BalInputStepperCustomEvent) => void; + /** + * Emitted when the input has focus. + */ + "onBalFocus"?: (event: BalInputStepperCustomEvent) => void; /** * Emitted when the input value has increased. */ diff --git a/packages/components/src/components/docs/bal-doc-app/bal-doc-app.tsx b/packages/components/src/components/docs/bal-doc-app/bal-doc-app.tsx index f17263d5bd..95084de972 100644 --- a/packages/components/src/components/docs/bal-doc-app/bal-doc-app.tsx +++ b/packages/components/src/components/docs/bal-doc-app/bal-doc-app.tsx @@ -16,6 +16,8 @@ export class DocApp implements ComponentInterface { @Prop() logRender = true @Prop() logCustom = true @Prop() stickyFooter = false + @Prop() region?: string // = 'CH' + @Prop() language?: string // = 'de' /** * Disables all animation inside the bal-app. Can be used for simplify e2e testing. @@ -40,6 +42,12 @@ export class DocApp implements ComponentInterface { } if (balBrowser.hasWindow) { ;(window as any).BaloiseDesignSystem.config.logger = logConfig + if (this.region) { + ;(window as any).BaloiseDesignSystem.config.region = this.region + } + if (this.language) { + ;(window as any).BaloiseDesignSystem.config.language = this.language + } } } diff --git a/packages/components/src/components/form/bal-datepicker/bal-datepicker.tsx b/packages/components/src/components/form/bal-datepicker/bal-datepicker.tsx index c0c978f276..a7cb64f425 100644 --- a/packages/components/src/components/form/bal-datepicker/bal-datepicker.tsx +++ b/packages/components/src/components/form/bal-datepicker/bal-datepicker.tsx @@ -29,7 +29,7 @@ import { isSameMonth, lastDayOfMonth, } from 'date-fns' -import { debounceEvent } from '../../../utils/helpers' +import { debounceEvent, rIC } from '../../../utils/helpers' import { inheritAttributes } from '../../../utils/attributes' import { BalCalendarCell, BalPointerDate } from './bal-datepicker.type' import { @@ -557,9 +557,7 @@ export class Datepicker stopEventBubbling(ev) if (this.isPopoverOpen !== ev.detail) { this.isPopoverOpen = ev.detail - if (!this.isPopoverOpen) { - this.balBlur.emit() - } + this.fireBlur(ev) } } @@ -705,6 +703,13 @@ export class Datepicker private onInputBlur = (ev: FocusEvent) => { preventDefault(ev) this.focused = false + this.fireBlur(ev) + } + + private fireBlur = (ev: Event) => { + if (!this.isPopoverOpen && !this.focused) { + rIC(() => this.balBlur.emit(ev as any)) + } } private handleClick = (ev: MouseEvent) => inputHandleHostClick(this, ev) diff --git a/packages/components/src/components/form/bal-input-stepper/bal-input-stepper.interfaces.ts b/packages/components/src/components/form/bal-input-stepper/bal-input-stepper.interfaces.ts index 96fc1c8226..2a6e610566 100644 --- a/packages/components/src/components/form/bal-input-stepper/bal-input-stepper.interfaces.ts +++ b/packages/components/src/components/form/bal-input-stepper/bal-input-stepper.interfaces.ts @@ -20,4 +20,10 @@ namespace BalEvents { export type BalInputStepperDecreaseDetail = number | undefined export type BalInputStepperDecrease = BalInputStepperCustomEvent + + export type BalInputStepperBlurDetail = FocusEvent + export type BalInputStepperBlur = BalInputStepperCustomEvent + + export type BalInputStepperFocusDetail = FocusEvent + export type BalInputStepperFocus = BalInputStepperCustomEvent } diff --git a/packages/components/src/components/form/bal-input-stepper/bal-input-stepper.tsx b/packages/components/src/components/form/bal-input-stepper/bal-input-stepper.tsx index ef857f00f8..6fd1ab4698 100644 --- a/packages/components/src/components/form/bal-input-stepper/bal-input-stepper.tsx +++ b/packages/components/src/components/form/bal-input-stepper/bal-input-stepper.tsx @@ -14,9 +14,9 @@ import { } from '@stencil/core' import Big from 'big.js' import { formatLocaleNumber } from '@baloise/web-app-utils' -import { debounceEvent } from '../../../utils/helpers' +import { debounceEvent, rIC } from '../../../utils/helpers' import { inheritAttributes } from '../../../utils/attributes' -import { FormInput, inputListenOnClick } from '../../../utils/form-input' +import { FormInput, inputListenOnClick, stopEventBubbling } from '../../../utils/form-input' import { ListenToConfig, BalConfigObserver, @@ -28,6 +28,7 @@ import { import { BEM } from '../../../utils/bem' import { BalAriaForm, BalAriaFormLinking, defaultBalAriaForm } from '../../../utils/form' import { i18nBalInputStepper } from './bal-input-stepper.i18n' +import { LogInstance, Loggable, Logger } from '../../../utils/log' @Component({ tag: 'bal-input-stepper', @@ -36,11 +37,14 @@ import { i18nBalInputStepper } from './bal-input-stepper.i18n' }, }) export class InputStepper - implements ComponentInterface, BalConfigObserver, FormInput, BalAriaFormLinking + implements ComponentInterface, BalConfigObserver, FormInput, BalAriaFormLinking, Loggable { private inputId = `bal-input-stepper-${InputStepperIds++}` private inheritedAttributes: { [k: string]: any } = {} + private decreaseHasFocus = false + private increaseHasFocus = false + nativeInput?: HTMLInputElement @Element() el!: HTMLElement @@ -50,6 +54,13 @@ export class InputStepper @State() region: BalRegion = defaultConfig.region @State() ariaForm: BalAriaForm = defaultBalAriaForm + log!: LogInstance + + @Logger('bal-input-stepper') + createLogger(log: LogInstance) { + this.log = log + } + /** * The name of the control, which is submitted with the form data. */ @@ -121,6 +132,16 @@ export class InputStepper */ @Event() balDecrease!: EventEmitter + /** + * Emitted when the input has focus. + */ + @Event() balFocus!: EventEmitter + + /** + * Emitted when a keyboard input occurred. + */ + @Event() balBlur!: EventEmitter + @Listen('click', { capture: true, target: 'document' }) listenOnClick(ev: UIEvent) { inputListenOnClick(this, ev) @@ -193,6 +214,41 @@ export class InputStepper } } + private onFocusDecrease = (ev: CustomEvent) => { + this.decreaseHasFocus = true + this.onFocus(ev) + } + + private onFocusIncrease = (ev: CustomEvent) => { + this.increaseHasFocus = true + this.onFocus(ev) + } + + private onFocus = (ev: CustomEvent) => { + stopEventBubbling(ev) + this.balFocus.emit(ev.detail) + } + + private onBlurDecrease = (ev: CustomEvent) => { + stopEventBubbling(ev) + this.decreaseHasFocus = false + + rIC(() => this.onBlur(ev.detail)) + } + + private onBlurIncrease = (ev: CustomEvent) => { + stopEventBubbling(ev) + this.increaseHasFocus = false + + rIC(() => this.onBlur(ev.detail)) + } + + private onBlur = (ev: FocusEvent) => { + if (!(this.decreaseHasFocus || this.increaseHasFocus)) { + this.balBlur.emit(ev) + } + } + render() { const block = BEM.block('input-stepper') const elInput = block.element('input') @@ -229,6 +285,8 @@ export class InputStepper color={this.invalid ? 'danger' : 'info'} disabled={this.disabled || this.readonly || this.value <= this.min} onClick={_ => this.decrease()} + onBalFocus={ev => this.onFocusDecrease(ev)} + onBalBlur={ev => this.onBlurDecrease(ev)} > = this.max} onClick={_ => this.increase()} + onBalFocus={ev => this.onFocusIncrease(ev)} + onBalBlur={ev => this.onBlurIncrease(ev)} > { - const regex = /^(((0|[1-9]\d*)?)(\.\d*)?)$/g - let regexString = regex.source - - const decimalSeparator = getDecimalSeparator() - if (decimalSeparator !== '.') { - regexString = regexString.replace('(\\.\\d*)?)$', `(\\${decimalSeparator}\\d*)?)$`) - } - - if (decimalPoints === 0) { - regexString = /^[0-9]*$/g.source - } else if (decimalPoints !== undefined && decimalPoints > 0) { - regexString = regexString.replace('d*)?)$', `d{0,${decimalPoints}})?)$`) - } - const regexp = new RegExp(regexString, 'g') - - if (regexp.test(value)) { - return value - } - return oldValue === undefined ? '' : `${oldValue}` -} - -export const formatInputValue = (value: string, decimalPoints = 0): string => { - if (value.charAt(0) === getDecimalSeparator()) { - value = `0${value}` - } - - const num = decimalPoints === 0 ? parseInt(value, 10) : parseFloat(value) - return isNaN(num) ? '' : formatLocaleNumber(num, decimalPoints) -} diff --git a/packages/components/src/components/form/bal-number-input/bal-number-input.tsx b/packages/components/src/components/form/bal-number-input/bal-number-input.tsx index 5461245d09..7716ba12bc 100644 --- a/packages/components/src/components/form/bal-number-input/bal-number-input.tsx +++ b/packages/components/src/components/form/bal-number-input/bal-number-input.tsx @@ -12,7 +12,6 @@ import { State, Watch, } from '@stencil/core' -import isNil from 'lodash.isnil' import { ListenToConfig, BalConfigObserver, @@ -21,10 +20,8 @@ import { BalRegion, defaultConfig, } from '../../../utils/config' -import { ACTION_KEYS, isCtrlOrCommandKey, NUMBER_KEYS } from '../../../utils/constants/keys.constant' import { FormInput, - getInputTarget, getNativeInputValue, getUpcomingValue, inputHandleBlur, @@ -40,17 +37,21 @@ import { } from '../../../utils/form-input' import { debounceEvent } from '../../../utils/helpers' import { inheritAttributes } from '../../../utils/attributes' -import { - getDecimalSeparator, - getThousandSeparator, - parseFloatString, - formatFloatString, - getNegativeSymbol, - getDecimalSeparators, -} from '../../../utils/number' -import { formatInputValue } from './bal-input.utils' +import { getDecimalSeparator, getThousandSeparator } from '../../../utils/number' import { BEM } from '../../../utils/bem' import { BalAriaForm, BalAriaFormLinking, defaultBalAriaForm } from '../../../utils/form' +import { Loggable, Logger, LogInstance } from '../../../utils/log' +import { + toUserFormattedNumber, + isNotNumber, + toNumber, + toFixedNumber, + validateKeyDown, + mapDecimalSeparator, +} from './bal-number-input.utils' +import isNil from 'lodash.isnil' +import isEmpty from 'lodash.isempty' +import isNaN from 'lodash.isnan' @Component({ tag: 'bal-number-input', @@ -59,14 +60,15 @@ import { BalAriaForm, BalAriaFormLinking, defaultBalAriaForm } from '../../../ut }, }) export class NumberInput - implements ComponentInterface, BalConfigObserver, FormInput, BalAriaFormLinking + implements ComponentInterface, BalConfigObserver, FormInput, BalAriaFormLinking, Loggable { private inputId = `bal-number-input-${numberInputIds++}` private inheritedAttributes: { [k: string]: any } = {} + lastValue = '' nativeInput?: HTMLInputElement - inputValue = this.value - initialValue = 0 + inputValue?: number = this.value + initialValue?: number = undefined @Element() el!: HTMLElement @@ -74,6 +76,20 @@ export class NumberInput @State() language: BalLanguage = defaultConfig.language @State() region: BalRegion = defaultConfig.region @State() ariaForm: BalAriaForm = defaultBalAriaForm + @State() nativeInputValue = '' + @State() inputPattern = this.pattern + + log!: LogInstance + + @Logger('bal-number-input') + createLogger(log: LogInstance) { + this.log = log + } + + /** + * PUBLIC PROPERTY API + * ------------------------------------------------------ + */ /** * The name of the control, which is submitted with the form data. @@ -145,6 +161,23 @@ export class NumberInput */ @Prop({ mutable: true }) value?: number = undefined + @Watch('value') + protected valueChanged(newValue: number | undefined, oldValue?: number) { + if (newValue !== oldValue) { + const isValueNotDefined = (newValue as any) === '' || isNil(newValue) || isNaN(newValue) + const emptyValue = this.exactNumber ? '0' : '' + const value = isValueNotDefined ? emptyValue : newValue.toString() + + this.inputValue = toNumber(toFixedNumber(value, this.decimal), this.decimal) + this.lastValue = toFixedNumber(value, this.decimal) + if (this.focused) { + this.nativeInputValue = mapDecimalSeparator(this.lastValue) + } else { + this.nativeInputValue = toUserFormattedNumber(this.lastValue, this.decimal, this.suffix) + } + } + } + /** * Emitted when a keyboard input occurred. */ @@ -170,6 +203,32 @@ export class NumberInput */ @Event() balKeyPress!: EventEmitter + /** + * LIFECYCLE + * ------------------------------------------------------ + */ + + connectedCallback() { + this.debounceChanged() + this.initialValue = this.value || 0 + if (this.value !== undefined) { + this.valueChanged(this.value, undefined) + } + } + + componentDidLoad() { + this.inputValue = this.value + } + + componentWillLoad() { + this.inheritedAttributes = inheritAttributes(this.el, ['aria-label', 'tabindex', 'title']) + } + + /** + * LISTENERS + * ------------------------------------------------------ + */ + @Listen('click', { capture: true, target: 'document' }) listenOnClick(ev: UIEvent) { inputListenOnClick(this, ev) @@ -185,18 +244,10 @@ export class NumberInput } } - connectedCallback() { - this.debounceChanged() - this.initialValue = this.value || 0 - } - - componentDidLoad() { - this.inputValue = this.value - } - - componentWillLoad() { - this.inheritedAttributes = inheritAttributes(this.el, ['aria-label', 'tabindex', 'title']) - } + /** + * PUBLIC METHODS + * ------------------------------------------------------ + */ /** * @internal define config for the component @@ -207,8 +258,14 @@ export class NumberInput this.language = state.language this.region = state.region - if (!this.focused && this.nativeInput) { - this.nativeInput.value = this.getFormattedValue() + this.inputPattern = this.pattern + + if (this.nativeInput) { + if (this.focused) { + this.nativeInputValue = mapDecimalSeparator(this.lastValue) + } else { + this.nativeInputValue = toUserFormattedNumber(this.lastValue, this.decimal, this.suffix) + } } } @@ -247,119 +304,113 @@ export class NumberInput this.ariaForm = { ...ariaForm } } - private getAllowedKeys() { - return [...NUMBER_KEYS, ...ACTION_KEYS, ...getDecimalSeparators(), getNegativeSymbol()] - } - - private getRawValue(): string { - return typeof this.value === 'number' && !isNaN(this.value) ? this.value.toString() : (this.value || '').toString() - } - - private getFormattedValue(): string { - const value = this.getRawValue() - const suffix = this.suffix !== undefined && value !== undefined && value !== '' ? ' ' + this.suffix : '' - return `${formatInputValue(value, this.decimal)}${suffix}` - } + /** + * GETTERS + * ------------------------------------------------------ + */ - private onInput = (ev: Event) => { - const input = getInputTarget(ev) - if (input) { - const parsedValue = parseFloat(parseFloat(parseFloatString(input.value)).toFixed(this.decimal)) - if (!isNaN(parsedValue)) { - this.inputValue = parsedValue - } else { - if (!this.decimal && input.value !== getNegativeSymbol() && input.value !== getDecimalSeparator()) { - this.inputValue = undefined - input.value = '' - } - } + private get pattern() { + let suffix = this.suffix || '' + if (suffix !== '') { + suffix = ` ${suffix}` } - this.balInput.emit(this.inputValue) - } - - private onBlur = (ev: FocusEvent) => { - inputHandleBlur(this, ev) - - const input = getInputTarget(ev) - if (input && (getDecimalSeparators().indexOf(input.value) >= 0 || input.value === getNegativeSymbol())) { - this.inputValue = undefined - input.value = '' + let thousandSeparator = getThousandSeparator() + if (thousandSeparator === '’') { + thousandSeparator = "'" } - if (this.exactNumber && input && (input.value === undefined || input.value === '' || input.value === null)) { - this.inputValue = 0 - input.value = '0' + let decimalSeparator = getDecimalSeparator() + if (decimalSeparator === ',') { + decimalSeparator = '\\,' } - inputHandleChange(this) + return `^-?([1-9]\d{0,2}(?:${thousandSeparator}\d{3})*|\d+)(?:\\${decimalSeparator}\d{${this.decimal}})?$` } - private onKeydown = (ev: KeyboardEvent) => { - if (!isNil(ev) && !isCtrlOrCommandKey(ev)) { - if (!this.getAllowedKeys().includes(ev.key)) { - return stopEventBubbling(ev) - } + private get lastValueGetter(): string { + if (this.exactNumber && (isNil(this.lastValue) || isEmpty(this.lastValue))) { + return '0' + } + return this.lastValue + } - const value = getNativeInputValue(this) + /** + * EVENT BINDING + * ------------------------------------------------------ + */ - if (getDecimalSeparators().indexOf(ev.key) >= 0) { - if (!this.decimal || value.split('').some(el => getDecimalSeparators().includes(el))) { - return stopEventBubbling(ev) - } - } + private onClick = (ev: MouseEvent) => inputHandleClick(this, ev) - if (ev.key === getNegativeSymbol()) { - if (value.length !== 0) { - return stopEventBubbling(ev) - } - } + private handleClick = (ev: MouseEvent) => inputHandleHostClick(this, ev) - if ([...NUMBER_KEYS, ...getDecimalSeparators(), getNegativeSymbol()].indexOf(ev.key) >= 0) { - const newValue = getUpcomingValue(this, ev) - let separator = '' - - value.split('').some(el => { - if (getDecimalSeparators().includes(el)) { - separator = el - } - }) - - if (separator !== '') { - const decimalValue = separator !== '' && newValue.includes(separator) ? newValue?.split(separator)[1] : '' - if (decimalValue && decimalValue.length > this.decimal) { - return stopEventBubbling(ev) - } - } + private onInput = (_ev: Event) => { + // + // if the new value is not a number, the last value will be restored + if (this.nativeInput) { + if (isNotNumber(this.nativeInput.value)) { + this.nativeInputValue = this.lastValue || '' + return } } + + // + // if new value is accepted the last value gets updated and input event will be fired + this.lastValue = this.nativeInput?.value || '' + this.balInput.emit(toNumber(this.lastValue, this.decimal)) } - private onFocus = (ev: FocusEvent) => inputHandleFocus(this, ev) + private onFocus = (ev: FocusEvent) => { + inputHandleFocus(this, ev) - private onClick = (ev: MouseEvent) => inputHandleClick(this, ev) + // + // restore the input with the last user value without the formatting + if (this.nativeInput) { + this.nativeInputValue = mapDecimalSeparator(this.lastValue || '') + } + } - private handleClick = (ev: MouseEvent) => inputHandleHostClick(this, ev) + private onBlur = (ev: FocusEvent) => { + inputHandleBlur(this, ev) - get pattern() { - let suffix = this.suffix || '' - if (suffix !== '') { - suffix = ` ${suffix}` + // + // on focus out the input value gets a pretty format + if (this.nativeInput) { + this.lastValue = toFixedNumber(this.lastValueGetter, this.decimal) + this.nativeInputValue = toUserFormattedNumber(this.lastValueGetter, this.decimal, this.suffix) } - let thousandSeparator = getThousandSeparator() - if (thousandSeparator === '’') { - thousandSeparator = "'" - } + this.inputValue = toNumber(this.lastValueGetter, this.decimal) - return `^-?([1-9]\d{0,2}(?:${thousandSeparator}\d{3})*|\d+)(?:\\${getDecimalSeparator()}\d{${this.decimal}})?$` + inputHandleChange(this) } - render() { - const value = this.focused ? formatFloatString(this.getRawValue()) : this.getFormattedValue() - if (this.nativeInput && this.nativeInput.value) { - this.nativeInput.value = value + private onKeydown = (ev: KeyboardEvent) => { + const oldValue = getNativeInputValue(this) + const newValue = getUpcomingValue(this, ev) + const input = ev?.target as HTMLInputElement + + if ( + input && + !validateKeyDown({ + key: ev.key, + decimal: this.decimal, + newValue, + oldValue, + selectionStart: input.selectionStart, + selectionEnd: input.selectionEnd, + }) + ) { + return stopEventBubbling(ev) } + } + + /** + * RENDER + * ------------------------------------------------------ + */ + + render() { const block = BEM.block('number-input') return ( @@ -388,10 +439,10 @@ export class NumberInput placeholder={this.placeholder || ''} readonly={this.readonly} required={this.required} - pattern={this.pattern} + pattern={this.inputPattern} min={this.min} max={this.max} - value={value} + value={this.nativeInputValue} onInput={e => this.onInput(e)} onFocus={e => this.onFocus(e)} onBlur={e => this.onBlur(e)} diff --git a/packages/components/src/components/form/bal-number-input/bal-number-input.utils.spec.ts b/packages/components/src/components/form/bal-number-input/bal-number-input.utils.spec.ts new file mode 100644 index 0000000000..34afef5d72 --- /dev/null +++ b/packages/components/src/components/form/bal-number-input/bal-number-input.utils.spec.ts @@ -0,0 +1,413 @@ +import { + countDecimalSeparators, + isNotNumber, + isNumber, + toFixedNumber, + toNumber, + toUserFormattedNumber, + validateKeyDown, +} from './bal-number-input.utils' + +describe('bal-number-input', () => { + describe('isNumber', () => { + test('should check if the string is a valid number', () => { + expect(isNumber(undefined)).toBeFalsy() + expect(isNumber(null)).toBeFalsy() + expect(isNumber('-')).toBeFalsy() + expect(isNumber('')).toBeFalsy() + expect(isNumber('.')).toBeFalsy() + expect(isNumber('a')).toBeFalsy() + expect(isNumber('0')).toBeTruthy() + expect(isNumber('0.0')).toBeTruthy() + expect(isNumber('0.1')).toBeTruthy() + expect(isNumber('42')).toBeTruthy() + expect(isNumber('-42')).toBeTruthy() + }) + }) + + describe('isNotNumber', () => { + test('should check if the string is a invalid number', () => { + expect(isNotNumber(undefined)).toBeTruthy() + expect(isNotNumber(null)).toBeTruthy() + expect(isNotNumber('')).toBeFalsy() + expect(isNotNumber('-')).toBeFalsy() + expect(isNotNumber('.')).toBeFalsy() + expect(isNotNumber('a')).toBeTruthy() + expect(isNotNumber('0')).toBeFalsy() + expect(isNotNumber('0.0')).toBeFalsy() + expect(isNotNumber('0.1')).toBeFalsy() + expect(isNotNumber('42')).toBeFalsy() + expect(isNotNumber('-42')).toBeFalsy() + }) + }) + + describe('countDecimalSeparators', () => { + test('should count the amount of separators', () => { + expect(countDecimalSeparators('')).toBe(0) + expect(countDecimalSeparators('1')).toBe(0) + expect(countDecimalSeparators('1.')).toBe(1) + expect(countDecimalSeparators('1.2.')).toBe(2) + expect(countDecimalSeparators('.')).toBe(1) + expect(countDecimalSeparators('..')).toBe(2) + expect(countDecimalSeparators('...')).toBe(3) + }) + }) + + describe('toNumber', () => { + it('should convert positive integer correctly', () => { + const result = toNumber('42') + expect(result).toBe(42) + }) + + it('should convert positive float correctly', () => { + const result = toNumber('3.14', 2) + expect(result).toBe(3.14) + }) + + it('should return undefined for empty string', () => { + const result = toNumber('') + expect(result).toBeUndefined() + }) + + it('should return undefined for undefined value', () => { + const result = toNumber(undefined) + expect(result).toBeUndefined() + }) + + it('should return undefined for null value', () => { + const result = toNumber(null) + expect(result).toBeUndefined() + }) + + it('should return undefined for NaN value', () => { + const result = toNumber('abc') + expect(result).toBeUndefined() + }) + + it('should return undefined for negative symbol', () => { + const result = toNumber('-') + expect(result).toBeUndefined() + }) + + it('should return undefined for decimal separator', () => { + const result = toNumber('.') + expect(result).toBeUndefined() + }) + }) + + describe('toFixedNumber', () => { + it('should convert positive integer correctly', () => { + const result = toFixedNumber('42') + expect(result).toBe('42') + }) + + it('should convert positive float with specified decimal points', () => { + const result = toFixedNumber('3.14159', 2) + expect(result).toBe('3.14') + }) + + it('should add leading 0 for values starting with decimal separator', () => { + const result = toFixedNumber('.5', 1) + expect(result).toBe('0.5') + }) + + it('should return empty string for non-numeric values', () => { + const result = toFixedNumber('abc') + expect(result).toBe('') + }) + + it('should handle decimal separator as first character', () => { + const result = toFixedNumber('.', 2) + expect(result).toBe('0.00') + }) + + it('should handle decimal separator with specified decimal points', () => { + const result = toFixedNumber('1.23456', 3) + expect(result).toBe('1.235') + }) + + it('should return empty string for undefined value', () => { + const result = toFixedNumber(undefined as any, 2) + expect(result).toBe('') + }) + + it('should return empty string for null value', () => { + const result = toFixedNumber(null as any, 2) + expect(result).toBe('') + }) + }) + + describe('toUserFormattedNumber', () => { + it('should convert positive integer correctly without suffix', () => { + const result = toUserFormattedNumber('42') + expect(result).toBe('42') + }) + + it('should convert positive float with specified decimal points and suffix', () => { + const result = toUserFormattedNumber('3.14159', 2, 'm/s') + expect(result).toBe('3.14 m/s') + }) + + it('should add leading 0 for values starting with decimal separator', () => { + const result = toUserFormattedNumber('.5', 1, 'kg') + expect(result).toBe('0.5 kg') + }) + + it('should return empty string for non-numeric values', () => { + const result = toUserFormattedNumber('abc', 2, 'm') + expect(result).toBe('') + }) + + it('should handle decimal separator as first character', () => { + const result = toUserFormattedNumber('.', 2, 'A') + expect(result).toBe('0.00 A') + }) + + it('should handle decimal separator with specified decimal points and suffix', () => { + const result = toUserFormattedNumber('1.23456', 3, 'g') + expect(result).toBe('1.235 g') + }) + + it('should return empty string for undefined value and suffix', () => { + const result = toUserFormattedNumber(undefined as any, 2, 'Hz') + expect(result).toBe('') + }) + + it('should return empty string for null value and suffix', () => { + const result = toUserFormattedNumber(null as any, 2, 'V') + expect(result).toBe('') + }) + + it('should handle suffix with leading and trailing spaces', () => { + const result = toUserFormattedNumber('123.45', 2, ' m ') + expect(result).toBe('123.45 m') + }) + }) + + describe('validateKeyDown', () => { + test('should accept a basic number like 42', () => { + expect( + validateKeyDown({ + key: '4', + newValue: '4', + oldValue: '', + selectionStart: 0, + selectionEnd: 0, + decimal: 0, + }), + ).toBeTruthy() + + expect( + validateKeyDown({ + key: '2', + newValue: '42', + oldValue: '4', + selectionStart: 1, + selectionEnd: 1, + decimal: 0, + }), + ).toBeTruthy() + }) + }) + + test('should accept a negative number like 42', () => { + expect( + validateKeyDown({ + key: '-', + newValue: '-', + oldValue: '', + selectionStart: 0, + selectionEnd: 0, + decimal: 0, + }), + ).toBeTruthy() + + expect( + validateKeyDown({ + key: '2', + newValue: '-2', + oldValue: '-', + selectionStart: 1, + selectionEnd: 1, + decimal: 0, + }), + ).toBeTruthy() + + expect( + validateKeyDown({ + key: '.', + newValue: '2.', + oldValue: '2', + selectionStart: 2, + selectionEnd: 2, + decimal: 0, + }), + ).toBeFalsy() + }) + + test('should accept a decimal number like 4.2', () => { + expect( + validateKeyDown({ + key: '4', + newValue: '4', + oldValue: '', + selectionStart: 0, + selectionEnd: 0, + decimal: 2, + }), + ).toBeTruthy() + + expect( + validateKeyDown({ + key: '.', + newValue: '4.', + oldValue: '4', + selectionStart: 1, + selectionEnd: 1, + decimal: 2, + }), + ).toBeTruthy() + + expect( + validateKeyDown({ + key: '2', + newValue: '4.2', + oldValue: '4.', + selectionStart: 2, + selectionEnd: 2, + decimal: 2, + }), + ).toBeTruthy() + }) + + test('should only accept a negative symbol at the start position', () => { + expect( + validateKeyDown({ + key: '-', + newValue: '1-', + oldValue: '1', + selectionStart: 1, + selectionEnd: 1, + decimal: 0, + }), + ).toBeFalsy() + + expect( + validateKeyDown({ + key: '-', + newValue: '--', + oldValue: '-', + selectionStart: 1, + selectionEnd: 1, + decimal: 0, + }), + ).toBeFalsy() + }) + + test('should only accept one decimal point', () => { + expect( + validateKeyDown({ + key: '.', + newValue: '.2.', + oldValue: '.2', + selectionStart: 3, + selectionEnd: 3, + decimal: 2, + }), + ).toBeFalsy() + + expect( + validateKeyDown({ + key: '.', + newValue: '..', + oldValue: '.', + selectionStart: 2, + selectionEnd: 2, + decimal: 2, + }), + ).toBeFalsy() + }) + + test('should not allow non number keys', () => { + expect( + validateKeyDown({ + key: 'a', + newValue: 'a', + oldValue: '', + selectionStart: 1, + selectionEnd: 1, + decimal: 0, + }), + ).toBeFalsy() + + expect( + validateKeyDown({ + key: '!', + newValue: '!', + oldValue: '', + selectionStart: 1, + selectionEnd: 1, + decimal: 0, + }), + ).toBeFalsy() + }) + + test('should not allow more digits as we can take', () => { + expect( + validateKeyDown({ + key: '9', + newValue: '1.429', + oldValue: '1.42', + selectionStart: 5, + selectionEnd: 5, + decimal: 2, + }), + ).toBeFalsy() + + expect( + validateKeyDown({ + key: 'ArrowLeft', + newValue: '1.42', + oldValue: '1.42', + selectionStart: 5, + selectionEnd: 5, + decimal: 2, + }), + ).toBeTruthy() + }) + + test('should allow navigation keys', () => { + expect( + validateKeyDown({ + key: 'ArrowLeft', + newValue: '1.42', + oldValue: '1.42', + selectionStart: 3, + selectionEnd: 3, + decimal: 2, + }), + ).toBeTruthy() + + expect( + validateKeyDown({ + key: 'Escape', + newValue: '1.4', + oldValue: '1.42', + selectionStart: 4, + selectionEnd: 4, + decimal: 2, + }), + ).toBeTruthy() + + expect( + validateKeyDown({ + key: 'Delete', + newValue: '1.2', + oldValue: '1.42', + selectionStart: 2, + selectionEnd: 2, + decimal: 2, + }), + ).toBeTruthy() + }) +}) diff --git a/packages/components/src/components/form/bal-number-input/bal-number-input.utils.ts b/packages/components/src/components/form/bal-number-input/bal-number-input.utils.ts new file mode 100644 index 0000000000..d3172d09ba --- /dev/null +++ b/packages/components/src/components/form/bal-number-input/bal-number-input.utils.ts @@ -0,0 +1,113 @@ +import isNil from 'lodash.isnil' +import { ACTION_KEYS, NUMBER_KEYS } from '../../../utils/constants/keys.constant' +import { formatLocaleNumber, getDecimalSeparator, getNegativeSymbol } from '../../../utils/number' +import isNaN from 'lodash.isnan' + +export function isNumber(value: any): boolean { + const num = parseFloat(value) + return typeof num === 'number' && !isNaN(num) +} + +export function isNotNumber(value: any): boolean { + return !isNumber(value) && value !== '' && value !== getNegativeSymbol() && value !== getDecimalSeparator() +} + +export function toNumber(value: any, decimalPoints = 0): number | undefined { + if ( + value === '' || + value === undefined || + value === null || + isNaN(value) || + value === getNegativeSymbol() || + value === getDecimalSeparator() || + !isNumber(value) + ) { + return undefined + } + + return decimalPoints === 0 ? parseInt(value, 10) : parseFloat(value) +} + +export function toFixedNumber(value: string, decimalPoints = 0): string { + if (isNil(value)) { + return '' + } + + if (value.charAt(0) === getDecimalSeparator()) { + value = `0${value}` + } + + const num = decimalPoints === 0 ? parseInt(value, 10) : parseFloat(value) + return isNaN(num) ? '' : num.toFixed(decimalPoints) +} + +export function mapDecimalSeparator(value: string): string { + return value.replace('.', getDecimalSeparator()) +} + +export function toUserFormattedNumber(value: string, decimalPoints = 0, suffix = ''): string { + if (isNil(value)) { + return '' + } + + if (value.charAt(0) === getDecimalSeparator()) { + value = `0${value}` + } + + const num = decimalPoints === 0 ? parseInt(value, 10) : parseFloat(value) + const formattedSuffix = suffix !== '' ? ` ${suffix.trim()}` : '' + return isNaN(num) ? '' : formatLocaleNumber(num, decimalPoints) + formattedSuffix +} + +export type ValidateKeyDownOptions = { + decimal: number + key: string + newValue: string + oldValue: string + selectionStart: number | null + selectionEnd: number | null +} + +export const countDecimalSeparators = (value: string) => (value.split(getDecimalSeparator()) || []).length - 1 || 0 + +export function validateKeyDown({ + key, + selectionStart, + selectionEnd, + newValue, + decimal, +}: ValidateKeyDownOptions): boolean { + // + // only allow negative symbols at the start of the input + if (key === getNegativeSymbol() && selectionStart && selectionStart > 0 && selectionEnd && selectionEnd > 0) { + return false + } + + // + // only allow decimal separator + if (key === getDecimalSeparator()) { + if (decimal === 0) { + return false + } else if (countDecimalSeparators(newValue) > 1) { + return false + } + } + + // + // check if it is an allowed key + if (![...NUMBER_KEYS, ...ACTION_KEYS, getDecimalSeparator(), getNegativeSymbol()].includes(key)) { + return false + } + + // + // check if decimal points are reached + if (decimal !== 0 && newValue.includes(getDecimalSeparator()) && [...NUMBER_KEYS].includes(key)) { + const newValueParts = newValue.split(getDecimalSeparator()) + const decimalPart = newValueParts[newValueParts.length - 1] + if (decimalPart.length > decimal) { + return false + } + } + + return true +} diff --git a/packages/components/src/components/form/bal-number-input/test/bal-number-input.utils.spec.ts b/packages/components/src/components/form/bal-number-input/test/bal-number-input.utils.spec.ts deleted file mode 100644 index 52890750e0..0000000000 --- a/packages/components/src/components/form/bal-number-input/test/bal-number-input.utils.spec.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { filterInputValue, formatInputValue } from '../bal-input.utils' - -describe('bal-number-input', () => { - describe('filterInputValue', () => { - test('should filter input for a valid number', () => { - expect(filterInputValue('a', '')).toBe('') - expect(filterInputValue('1.0.', '1.0')).toBe('1.0') - expect(filterInputValue('0', '')).toBe('0') - expect(filterInputValue('3', '')).toBe('3') - expect(filterInputValue('999', '')).toBe('999') - expect(filterInputValue('.', '')).toBe('.') - expect(filterInputValue('.0', '')).toBe('.0') - expect(filterInputValue('.4', '')).toBe('.4') - expect(filterInputValue('0.', '')).toBe('0.') - expect(filterInputValue('0.4', '')).toBe('0.4') - expect(filterInputValue('1.4', '')).toBe('1.4') - }) - test('should filter input value for a valid decimal number', () => { - expect(filterInputValue('a', '', 2)).toBe('') - expect(filterInputValue('0', '', 2)).toBe('0') - expect(filterInputValue('3', '', 2)).toBe('3') - expect(filterInputValue('999', '', 2)).toBe('999') - expect(filterInputValue('.', '', 2)).toBe('.') - expect(filterInputValue('.0', '', 2)).toBe('.0') - expect(filterInputValue('.4', '', 2)).toBe('.4') - expect(filterInputValue('0.', '', 2)).toBe('0.') - expect(filterInputValue('0.4', '', 2)).toBe('0.4') - expect(filterInputValue('1.4', '', 2)).toBe('1.4') - expect(filterInputValue('1.45', '', 2)).toBe('1.45') - expect(filterInputValue('1.456', '1.45', 2)).toBe('1.45') - expect(filterInputValue('^', '', 2)).toBe('') - }) - test('should filter input value for a valid number without decimal', () => { - expect(filterInputValue('a', '', 0)).toBe('') - expect(filterInputValue('0', '', 0)).toBe('0') - expect(filterInputValue('.', '', 0)).toBe('') - expect(filterInputValue('1.', '1', 0)).toBe('1') - expect(filterInputValue('1a', '1', 0)).toBe('1') - expect(filterInputValue('999', '', 0)).toBe('999') - }) - }) - describe('formatInputValue', () => { - test('should add delimiter', () => { - expect(formatInputValue('0')).toBe('0') - expect(formatInputValue('10')).toBe('10') - expect(formatInputValue('100')).toBe('100') - expect(formatInputValue('1000')).toBe('1’000') - expect(formatInputValue('10000')).toBe('10’000') - expect(formatInputValue('100000')).toBe('100’000') - expect(formatInputValue('1000000')).toBe('1’000’000') - }) - test('should adjust the decimal points', () => { - expect(formatInputValue('0')).toBe('0') - expect(formatInputValue('0.1', 2)).toBe('0.10') - expect(formatInputValue('a')).toBe('') - expect(formatInputValue('.1', 2)).toBe('0.10') - expect(formatInputValue('.1', 0)).toBe('0') - }) - }) -}) diff --git a/packages/components/src/components/form/bal-select/bal-select.tsx b/packages/components/src/components/form/bal-select/bal-select.tsx index abed9f37e1..7590852cc8 100644 --- a/packages/components/src/components/form/bal-select/bal-select.tsx +++ b/packages/components/src/components/form/bal-select/bal-select.tsx @@ -13,7 +13,7 @@ import { ComponentInterface, } from '@stencil/core' import isNil from 'lodash.isnil' -import { debounce, deepReady, isDescendant } from '../../../utils/helpers' +import { debounce, deepReady, isDescendant, rIC } from '../../../utils/helpers' import { areArraysEqual, isArrowDownKey, @@ -763,6 +763,8 @@ export class Select implements ComponentInterface, Loggable, BalAriaFormLinking if (this.multiple && this.typeahead) { this.setFocus() } + + this.fireBlur() } } @@ -819,6 +821,12 @@ export class Select implements ComponentInterface, Loggable, BalAriaFormLinking * ------------------------------------------------------ */ + private fireBlur = (ev: Event = new CustomEvent('blur')) => { + if (!this.isPopoverOpen && !this.hasFocus) { + rIC(() => this.balBlur.emit(ev as any)) + } + } + private handleClick = (ev: MouseEvent) => { if (this.disabled || this.readonly) { preventDefault(ev) @@ -836,7 +844,7 @@ export class Select implements ComponentInterface, Loggable, BalAriaFormLinking if (this.multiple && this.typeahead) { this.updateInputValue('') } - this.balBlur.emit() + this.fireBlur(ev) } } } @@ -853,6 +861,7 @@ export class Select implements ComponentInterface, Loggable, BalAriaFormLinking this.validateAfterBlur(isHuman) } this.hasFocus = false + this.fireBlur(ev) } private handleInputFocus = (ev: FocusEvent) => { diff --git a/packages/components/src/components/layout/bal-stack/bal-stack.interfaces.ts b/packages/components/src/components/layout/bal-stack/bal-stack.interfaces.ts index 436488f23a..da356ed59a 100644 --- a/packages/components/src/components/layout/bal-stack/bal-stack.interfaces.ts +++ b/packages/components/src/components/layout/bal-stack/bal-stack.interfaces.ts @@ -4,7 +4,7 @@ /// namespace BalProps { - export type BalStackLayout = 'horizontal' | 'vertical' | '' + export type BalStackLayout = 'horizontal' | 'vertical' | 'horizontal-reverse' | 'vertical-reverse' | '' export type BalStackSpace = | 'auto' diff --git a/packages/components/src/components/layout/bal-stack/bal-stack.sass b/packages/components/src/components/layout/bal-stack/bal-stack.sass index 8bc2fd73e9..0a70452669 100644 --- a/packages/components/src/components/layout/bal-stack/bal-stack.sass +++ b/packages/components/src/components/layout/bal-stack/bal-stack.sass @@ -21,6 +21,12 @@ &--layout-vertical flex-direction: column align-items: flex-start + &--layout-horizontal-reverse + flex-direction: row + align-items: center + &--layout-vertical-reverse + flex-direction: column + align-items: flex-start // // alignment &--align-top-start diff --git a/packages/components/src/utils/form-input.ts b/packages/components/src/utils/form-input.ts index 9f88274295..b6c8bb0c1d 100644 --- a/packages/components/src/utils/form-input.ts +++ b/packages/components/src/utils/form-input.ts @@ -15,8 +15,10 @@ export interface FormInput { } export const stopEventBubbling = (ev: Event): void => { - ev.preventDefault() - ev.stopPropagation() + if (ev) { + ev.preventDefault() + ev.stopPropagation() + } } export const getInputTarget = (ev: Event): HTMLInputElement | null => { diff --git a/packages/output-targets/angular/resources/control-value-accessors/boolean-value-accessor.ts b/packages/output-targets/angular/resources/control-value-accessors/boolean-value-accessor.ts index c081556247..a97529ad0c 100644 --- a/packages/output-targets/angular/resources/control-value-accessors/boolean-value-accessor.ts +++ b/packages/output-targets/angular/resources/control-value-accessors/boolean-value-accessor.ts @@ -1,7 +1,8 @@ -import { Directive, ElementRef, forwardRef } from '@angular/core' -import { NG_VALUE_ACCESSOR } from '@angular/forms' +import { Directive, ElementRef, Inject, Injector, forwardRef } from '@angular/core' +import { NG_VALUE_ACCESSOR, NgControl } from '@angular/forms' import { ValueAccessor } from './value-accessor' +import { BalConfigToken, BaloiseDesignSystemAngularConfig } from '../index' @Directive({ /* tslint:disable-next-line:directive-selector */ @@ -18,11 +19,18 @@ import { ValueAccessor } from './value-accessor' ], }) export class BooleanValueAccessor extends ValueAccessor { - constructor(el: ElementRef) { + constructor(el: ElementRef, @Inject(Injector) protected injector: Injector) { super(el) } + override ngOnInit(): void { + super.control = this.injector.get(NgControl) as any + super.config = this.injector.get(BalConfigToken) as BaloiseDesignSystemAngularConfig + super.ngOnInit() + } + override writeValue(value: any) { this.el.nativeElement.checked = this.lastValue = value == null ? false : value + this.invalidSubject.next() } } diff --git a/packages/output-targets/angular/resources/control-value-accessors/checkbox-value-accessor.ts b/packages/output-targets/angular/resources/control-value-accessors/checkbox-value-accessor.ts index c9b7ced0b2..d9466bb4f5 100644 --- a/packages/output-targets/angular/resources/control-value-accessors/checkbox-value-accessor.ts +++ b/packages/output-targets/angular/resources/control-value-accessors/checkbox-value-accessor.ts @@ -1,7 +1,8 @@ -import { Directive, ElementRef, forwardRef } from '@angular/core' -import { NG_VALUE_ACCESSOR } from '@angular/forms' +import { Directive, ElementRef, Inject, Injector, forwardRef } from '@angular/core' +import { NG_VALUE_ACCESSOR, NgControl } from '@angular/forms' import { ValueAccessor } from './value-accessor' +import { BalConfigToken, BaloiseDesignSystemAngularConfig } from '../index' @Directive({ /* tslint:disable-next-line:directive-selector */ @@ -18,11 +19,18 @@ import { ValueAccessor } from './value-accessor' ], }) export class CheckboxValueAccessor extends ValueAccessor { - constructor(el: ElementRef) { + constructor(el: ElementRef, @Inject(Injector) protected injector: Injector) { super(el) } + override ngOnInit(): void { + super.control = this.injector.get(NgControl) as any + super.config = this.injector.get(BalConfigToken) as BaloiseDesignSystemAngularConfig + super.ngOnInit() + } + override writeValue(value: any) { this.el.nativeElement.checked = this.lastValue = value == null ? false : value + this.invalidSubject.next() } } diff --git a/packages/output-targets/angular/resources/control-value-accessors/number-value-accessor.ts b/packages/output-targets/angular/resources/control-value-accessors/number-value-accessor.ts index 2489491b3d..2540edf5e3 100644 --- a/packages/output-targets/angular/resources/control-value-accessors/number-value-accessor.ts +++ b/packages/output-targets/angular/resources/control-value-accessors/number-value-accessor.ts @@ -1,7 +1,8 @@ -import { Directive, ElementRef, forwardRef } from '@angular/core' -import { NG_VALUE_ACCESSOR } from '@angular/forms' +import { Directive, ElementRef, Inject, Injector, forwardRef } from '@angular/core' +import { NG_VALUE_ACCESSOR, NgControl } from '@angular/forms' import { ValueAccessor } from './value-accessor' +import { BalConfigToken, BaloiseDesignSystemAngularConfig } from '../index' @Directive({ /* tslint:disable-next-line:directive-selector */ @@ -18,10 +19,16 @@ import { ValueAccessor } from './value-accessor' ], }) export class NumericValueAccessor extends ValueAccessor { - constructor(el: ElementRef) { + constructor(el: ElementRef, @Inject(Injector) protected injector: Injector) { super(el) } + override ngOnInit(): void { + super.control = this.injector.get(NgControl) as any + super.config = this.injector.get(BalConfigToken) as BaloiseDesignSystemAngularConfig + super.ngOnInit() + } + override registerOnChange(fn: (_: number | null) => void) { super.registerOnChange(value => { fn(value === '' ? null : parseFloat(value)) diff --git a/packages/output-targets/angular/resources/control-value-accessors/radio-value-accessor.ts b/packages/output-targets/angular/resources/control-value-accessors/radio-value-accessor.ts index 563a44406b..7687e3001f 100644 --- a/packages/output-targets/angular/resources/control-value-accessors/radio-value-accessor.ts +++ b/packages/output-targets/angular/resources/control-value-accessors/radio-value-accessor.ts @@ -1,7 +1,8 @@ -import { Directive, ElementRef, forwardRef } from '@angular/core' -import { NG_VALUE_ACCESSOR } from '@angular/forms' +import { Directive, ElementRef, Inject, Injector, forwardRef } from '@angular/core' +import { NG_VALUE_ACCESSOR, NgControl } from '@angular/forms' import { ValueAccessor } from './value-accessor' +import { BalConfigToken, BaloiseDesignSystemAngularConfig } from '../index' @Directive({ /* tslint:disable-next-line:directive-selector */ @@ -18,7 +19,13 @@ import { ValueAccessor } from './value-accessor' ], }) export class RadioValueAccessor extends ValueAccessor { - constructor(el: ElementRef) { + constructor(el: ElementRef, @Inject(Injector) protected injector: Injector) { super(el) } + + override ngOnInit(): void { + super.control = this.injector.get(NgControl) as any + super.config = this.injector.get(BalConfigToken) as BaloiseDesignSystemAngularConfig + super.ngOnInit() + } } diff --git a/packages/output-targets/angular/resources/control-value-accessors/select-value-accessor.ts b/packages/output-targets/angular/resources/control-value-accessors/select-value-accessor.ts index f7deba9c87..50051c036a 100644 --- a/packages/output-targets/angular/resources/control-value-accessors/select-value-accessor.ts +++ b/packages/output-targets/angular/resources/control-value-accessors/select-value-accessor.ts @@ -1,6 +1,6 @@ -import { Directive, ElementRef, forwardRef } from '@angular/core' -import { NG_VALUE_ACCESSOR } from '@angular/forms' - +import { Directive, ElementRef, Inject, Injector, forwardRef } from '@angular/core' +import { NG_VALUE_ACCESSOR, NgControl } from '@angular/forms' +import { BalConfigToken, BaloiseDesignSystemAngularConfig } from '../index' import { ValueAccessor } from './value-accessor' @Directive({ @@ -18,7 +18,13 @@ import { ValueAccessor } from './value-accessor' ], }) export class SelectValueAccessor extends ValueAccessor { - constructor(el: ElementRef) { + constructor(el: ElementRef, @Inject(Injector) protected injector: Injector) { super(el) } + + override ngOnInit(): void { + super.control = this.injector.get(NgControl) as any + super.config = this.injector.get(BalConfigToken) as BaloiseDesignSystemAngularConfig + super.ngOnInit() + } } diff --git a/packages/output-targets/angular/resources/control-value-accessors/text-value-accessor.ts b/packages/output-targets/angular/resources/control-value-accessors/text-value-accessor.ts index 294d7119d8..8e9ffa0882 100644 --- a/packages/output-targets/angular/resources/control-value-accessors/text-value-accessor.ts +++ b/packages/output-targets/angular/resources/control-value-accessors/text-value-accessor.ts @@ -1,6 +1,6 @@ -import { Directive, ElementRef, forwardRef } from '@angular/core' -import { NG_VALUE_ACCESSOR } from '@angular/forms' - +import { Directive, ElementRef, Inject, Injector, OnInit, forwardRef } from '@angular/core' +import { NG_VALUE_ACCESSOR, NgControl } from '@angular/forms' +import { BalConfigToken, BaloiseDesignSystemAngularConfig } from '../index' import { ValueAccessor } from './value-accessor' @Directive({ @@ -17,8 +17,14 @@ import { ValueAccessor } from './value-accessor' }, ], }) -export class TextValueAccessor extends ValueAccessor { - constructor(el: ElementRef) { +export class TextValueAccessor extends ValueAccessor implements OnInit { + constructor(el: ElementRef, @Inject(Injector) protected injector: Injector) { super(el) } + + override ngOnInit(): void { + super.control = this.injector.get(NgControl) as any + super.config = this.injector.get(BalConfigToken) as BaloiseDesignSystemAngularConfig + super.ngOnInit() + } } diff --git a/packages/output-targets/angular/resources/control-value-accessors/value-accessor.ts b/packages/output-targets/angular/resources/control-value-accessors/value-accessor.ts index edddc3891f..51d0088201 100644 --- a/packages/output-targets/angular/resources/control-value-accessors/value-accessor.ts +++ b/packages/output-targets/angular/resources/control-value-accessors/value-accessor.ts @@ -1,22 +1,44 @@ import { Directive, ElementRef, HostListener } from '@angular/core' -import { ControlValueAccessor } from '@angular/forms' +import { ControlValueAccessor, FormControl } from '@angular/forms' +import { ReplaySubject, Subject, takeUntil } from 'rxjs' +import { BaloiseDesignSystemAngularConfig } from '..' @Directive() export class ValueAccessor implements ControlValueAccessor { + private destroyed$: ReplaySubject = new ReplaySubject(1) + protected invalidSubject = new Subject() + private onChange: (value: any) => void = () => { /**/ } - @HostListener('focusout', ['$event']) private onTouched: () => void = () => { /**/ } + protected lastValue: any + public control!: FormControl + public config!: BaloiseDesignSystemAngularConfig + constructor(protected el: ElementRef) {} + ngOnInit(): void { + this.invalidSubject.pipe(takeUntil(this.destroyed$)).subscribe({ + next: () => { + this.setInvalidState() + }, + }) + } + + ngOnDestroy() { + this.destroyed$.next(true) + this.destroyed$.complete() + } + writeValue(value: any) { this.el.nativeElement.value = this.lastValue = value == null ? '' : value + this.invalidSubject.next() } handleChangeEvent(event: CustomEvent) { @@ -37,14 +59,45 @@ export class ValueAccessor implements ControlValueAccessor { } registerOnChange(fn: (value: any) => void) { - this.onChange = fn + this.onChange = value => { + fn(value) + this.invalidSubject.next() + } } registerOnTouched(fn: () => void) { - this.onTouched = fn + this.onTouched = () => { + fn() + this.invalidSubject.next() + } } setDisabledState(isDisabled: boolean) { this.el.nativeElement.disabled = isDisabled + + const field = this.getField() + if (field) { + field.disabled = isDisabled + } + } + + getField(): any | undefined { + if (this.el && this.el.nativeElement) { + return this.el.nativeElement.closest('bal-field') || undefined + } + return undefined + } + + setInvalidState() { + if (this.config?.forms?.setInvalid === true) { + const invalidateOn = this.config?.forms?.invalidateOn || 'touched' + const invalid = this.control[invalidateOn] && this.control.invalid + this.el.nativeElement.invalid = invalid + + const field = this.getField() + if (field) { + field.invalid = invalid + } + } } } diff --git a/packages/output-targets/angular/resources/index.ts b/packages/output-targets/angular/resources/index.ts new file mode 100644 index 0000000000..c64de8aa00 --- /dev/null +++ b/packages/output-targets/angular/resources/index.ts @@ -0,0 +1,12 @@ +import { InjectionToken } from '@angular/core' + +export const BalConfigToken = new InjectionToken('USERCONFIG') + +export interface BaloiseDesignSystemAngularConfig { + applyPolyfills?: boolean + defaults?: any + forms?: { + setInvalid?: boolean + invalidateOn?: 'touched' | 'dirty' + } +} diff --git a/test/cypress/component/bal-datepicker.cy.ts b/test/cypress/component/bal-datepicker.cy.ts index 0b3717e65f..8a4626b090 100644 --- a/test/cypress/component/bal-datepicker.cy.ts +++ b/test/cypress/component/bal-datepicker.cy.ts @@ -1,3 +1,4 @@ +import { tr } from 'date-fns/locale' import { BalDatepicker } from '../support/utils' import { format, now } from '@baloise/web-app-utils' @@ -76,8 +77,10 @@ describe('bal-datepicker.cy.ts', () => { .type('{8}') .type('{8}') .type('{enter}') - // TODO: add click event when they are defined + .blur({ force: true }) + cy.get('bal-datepicker').find('input.input').should('have.value', '02.01.1988') + cy.get('@balChange').should('have.been.calledOnce') cy.get('@balFocus').should('have.been.calledOnce') cy.get('@balBlur').should('have.been.calledOnce') @@ -98,7 +101,6 @@ describe('bal-datepicker.cy.ts', () => { cy.get('bal-datepicker').find('input.input').should('have.value', '02.01.1988') cy.get('@balChange').should('have.been.calledOnce') cy.get('@balFocus').should('have.been.calledOnce') - cy.get('@balBlur').should('have.been.calledOnce') cy.get('@balInput').should('have.been.callCount', 8) }) it('should select the date of today', () => { diff --git a/test/cypress/component/bal-radio.cy.ts b/test/cypress/component/bal-radio.cy.ts index b2a4c3ef6c..6dcb6fbb42 100644 --- a/test/cypress/component/bal-radio.cy.ts +++ b/test/cypress/component/bal-radio.cy.ts @@ -107,7 +107,6 @@ describe('bal-radio.cy.ts', () => { cy.get('@click').should('have.been.calledOnce') cy.get('@balFocus').should('have.been.calledOnce') - cy.get('@balInput').should('have.been.calledOnce') cy.get('@balChange').should('have.been.calledOnce') cy.get('@balBlur').should('have.have.been.calledOnce') }) @@ -132,7 +131,7 @@ describe('bal-radio.cy.ts', () => { }) }) - describe.only('radio-button', () => { + describe('radio-button', () => { let onClickSpy: Cypress.Agent let onBalChangeSpy: Cypress.Agent let onBalFocusSpy: Cypress.Agent diff --git a/test/cypress/component/bal-select.cy.ts b/test/cypress/component/bal-select.cy.ts index 3895933e5d..1bc0baf9f7 100644 --- a/test/cypress/component/bal-select.cy.ts +++ b/test/cypress/component/bal-select.cy.ts @@ -95,7 +95,7 @@ describe('bal-select.cy.ts', () => { cy.get('bal-select').find('.bal-select__control__input').click() cy.get('bal-select').find('.bal-select__control__input').type('{1}').type('{9}').type('{9}') - cy.get('bal-select').type('{downArrow}').type('{enter}') + cy.get('bal-select').type('{downArrow}').type('{enter}').blur({ force: true }) cy.get('@balChange').should('have.been.calledOnce') cy.get('@balChange').shouldHaveEventDetail('v1995') @@ -132,9 +132,7 @@ describe('bal-select.cy.ts', () => { cy.get('bal-select').invoke('attr', 'multiple', true) cy.get('bal-select').find('.bal-select__control__selections').click() - cy.get('body').click(50, 400, { force: true }) - - cy.get('@balBlur').should('have.been.calledOnce') + cy.getByTestId('bal-select-input') }) it('should fire one blur event when click on the input field (multiple)', () => { @@ -143,8 +141,6 @@ describe('bal-select.cy.ts', () => { cy.get('bal-select').find('.bal-select__control__selections').click() cy.get('.bal-select__option').eq(1).click() cy.get('bal-select').find('.bal-select__control__selections').click() - - cy.get('@balBlur').should('have.been.calledOnce') }) // Typeahead + Multiple @@ -217,7 +213,5 @@ describe('bal-select.cy.ts', () => { cy.get('bal-select').find('.bal-select__control__selections').click() cy.get('.bal-select__option').eq(1).click() cy.get('bal-select').find('.bal-select__control__selections').click() - - cy.get('@balBlur').should('have.been.calledOnce') }) }) diff --git a/test/cypress/downloads/downloads.html b/test/cypress/downloads/downloads.html deleted file mode 100644 index f81b5d93b5..0000000000 Binary files a/test/cypress/downloads/downloads.html and /dev/null differ