From 01366007025bfadc52dbe3bf78c313c3f979c277 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Ortiz?= Date: Tue, 11 Jan 2022 13:02:04 -0300 Subject: [PATCH] Add Typescript support --- .editorconfig | 2 +- .eslintrc.js | 14 + .github/workflows/mergepr.yml | 2 +- .github/workflows/nodejs.yml | 2 +- analyze.config.js => analyze.config.ts | 2 +- babel.config.js | 1 + config/storybook/main.js | 2 +- index.html | 2 +- jest.config.js | 9 +- package-lock.json | 2775 ++++++++++++++--- package.json | 35 +- src/App.js | 3 - src/App.ts | 5 + src/App.vue | 6 +- src/components/ActionItem/ActionItem.js | 69 - ...nItem.stories.js => ActionItem.stories.ts} | 23 +- ...{ActionItem.test.js => ActionItem.test.ts} | 2 - src/components/ActionItem/ActionItem.ts | 57 + src/components/ActionItem/ActionItem.vue | 4 +- src/components/AmountInput/AmountInput.js | 202 -- ...nput.stories.js => AmountInput.stories.ts} | 6 +- ...mountInput.test.js => AmountInput.test.ts} | 9 +- src/components/AmountInput/AmountInput.ts | 198 ++ src/components/AmountInput/AmountInput.vue | 4 +- .../BankAccountNumberInputField.js | 158 - ...=> BankAccountNumberInputField.stories.ts} | 7 +- ...js => BankAccountNumberInputField.test.ts} | 73 +- .../BankAccountNumberInputField.ts | 171 + .../BankAccountNumberInputField.vue | 2 +- .../BoxContentItem/BoxContentItem.js | 75 - ...m.stories.js => BoxContentItem.stories.ts} | 13 +- .../BoxContentItem/BoxContentItem.test.ts | 91 + .../BoxContentItem/BoxContentItem.ts | 59 + .../BoxContentItem/BoxContentItem.vue | 4 +- src/components/Button/Button.js | 104 - .../{Button.stories.js => Button.stories.ts} | 7 +- .../Button/{Button.test.js => Button.test.ts} | 2 +- src/components/Button/Button.ts | 101 + src/components/Button/Button.vue | 4 +- .../UnitTestWrappers/ButtonTestWrapper.vue | 6 +- .../CallToActionScreen/CallToActionScreen.js | 58 - ...ories.js => CallToActionScreen.stories.ts} | 5 +- ...een.test.js => CallToActionScreen.test.ts} | 0 .../CallToActionScreen/CallToActionScreen.ts | 50 + .../CallToActionScreen/CallToActionScreen.vue | 4 +- src/components/Carousel/Carousel.js | 196 -- ...arousel.stories.js => Carousel.stories.ts} | 10 +- .../{Carousel.test.js => Carousel.test.ts} | 32 +- src/components/Carousel/Carousel.ts | 188 ++ src/components/Carousel/Carousel.vue | 7 +- src/components/Checkbox/Checkbox.js | 67 - ...heckbox.stories.js => Checkbox.stories.ts} | 6 +- .../{Checkbox.test.js => Checkbox.test.ts} | 4 +- src/components/Checkbox/Checkbox.ts | 57 + src/components/Checkbox/Checkbox.vue | 4 +- .../CircularProgress/CircularProgress.js | 118 - ...stories.js => CircularProgress.stories.ts} | 5 +- ...gress.test.js => CircularProgress.test.ts} | 6 +- .../CircularProgress/CircularProgress.ts | 122 + .../CircularProgress/CircularProgress.vue | 4 +- .../CodeInputField/CodeInputField.js | 125 - ...d.stories.js => CodeInputField.stories.ts} | 15 +- ...utField.test.js => CodeInputField.test.ts} | 17 +- .../CodeInputField/CodeInputField.ts | 130 + .../CodeInputField/CodeInputField.vue | 4 +- src/components/ConfettiOverlay/Confetti.vue | 19 +- .../ConfettiOverlay/ConfettiOverlay.js | 47 - ....stories.js => ConfettiOverlay.stories.ts} | 5 +- ...verlay.test.js => ConfettiOverlay.test.ts} | 3 +- .../ConfettiOverlay/ConfettiOverlay.ts | 41 + .../ConfettiOverlay/ConfettiOverlay.vue | 5 +- .../ConfirmationModalDialog.js | 79 - ....js => ConfirmationModalDialog.stories.ts} | 13 +- ...est.js => ConfirmationModalDialog.test.ts} | 48 +- .../ConfirmationModalDialog.ts | 79 + .../ConfirmationModalDialog.vue | 4 +- .../ConfirmationModalDialogWrapper.vue | 19 +- .../ConfirmationToastDialog.js | 80 - ....js => ConfirmationToastDialog.stories.ts} | 5 +- ...est.js => ConfirmationToastDialog.test.ts} | 25 +- .../ConfirmationToastDialog.ts | 80 + .../ConfirmationToastDialog.vue | 4 +- .../ConfirmationToastDialogWrapper.vue | 19 +- src/components/ContentItem/ContentItem.js | 92 - ...Item.stories.js => ContentItem.stories.ts} | 17 +- ...ontentItem.test.js => ContentItem.test.ts} | 4 +- src/components/ContentItem/ContentItem.ts | 79 + src/components/ContentItem/ContentItem.vue | 4 +- .../ContentRadioList/ContentRadioList.js | 64 - ...stories.js => ContentRadioList.stories.ts} | 5 +- ...oList.test.js => ContentRadioList.test.ts} | 35 +- .../ContentRadioList/ContentRadioList.ts | 62 + .../ContentRadioList/ContentRadioList.vue | 4 +- .../CopyToClipboardButton.js | 113 - ...es.js => CopyToClipboardButton.stories.ts} | 5 +- ....test.js => CopyToClipboardButton.test.ts} | 26 +- .../CopyToClipboardButton.ts | 109 + .../CopyToClipboardButton.vue | 4 +- src/components/CopyableList/CopyableList.js | 23 - ...ist.stories.js => CopyableList.stories.ts} | 6 +- ...yableList.test.js => CopyableList.test.ts} | 2 +- src/components/CopyableList/CopyableList.ts | 22 + src/components/CopyableList/CopyableList.vue | 4 +- .../UnitTestWrappers/CopyableListWrapper.vue | 7 +- .../CopyableListItem/CopyableListItem.js | 54 - ...stories.js => CopyableListItem.stories.ts} | 7 +- ...tItem.test.js => CopyableListItem.test.ts} | 16 +- .../CopyableListItem/CopyableListItem.ts | 44 + .../CopyableListItem/CopyableListItem.vue | 4 +- src/components/CurrencyField/CurrencyField.js | 173 - ...ld.stories.js => CurrencyField.stories.ts} | 7 +- ...ncyField.test.js => CurrencyField.test.ts} | 16 +- src/components/CurrencyField/CurrencyField.ts | 183 ++ .../CurrencyField/CurrencyField.vue | 4 +- .../WithErrorCurrencyFieldWrapper.vue | 8 +- .../CurrencyInput/CurrencyInput.vue | 29 +- src/components/DateField/DateField.js | 193 -- ...eField.stories.js => DateField.stories.ts} | 9 +- .../{DateField.test.js => DateField.test.ts} | 36 +- src/components/DateField/DateField.ts | 186 ++ src/components/DateField/DateField.vue | 4 +- src/components/FigureCard/FigureCard.js | 39 - ...eCard.stories.js => FigureCard.stories.ts} | 6 +- ...{FigureCard.test.js => FigureCard.test.ts} | 3 +- src/components/FigureCard/FigureCard.ts | 33 + src/components/FigureCard/FigureCard.vue | 4 +- src/components/FormField/FormField.js | 152 - ...mField.stories.js => FormField.stories.ts} | 7 +- .../{FormField.test.js => FormField.test.ts} | 9 +- src/components/FormField/FormField.ts | 165 + src/components/FormField/FormField.vue | 4 +- .../WithErrorFormFieldWrapper.vue | 8 +- src/components/ForwardButton/ForwardButton.js | 43 - ...on.stories.js => ForwardButton.stories.ts} | 5 +- .../ForwardButton/ForwardButton.test.js | 30 - .../ForwardButton/ForwardButton.test.ts | 30 + src/components/ForwardButton/ForwardButton.ts | 43 + .../ForwardButton/ForwardButton.vue | 4 +- src/components/Heading/Heading.js | 46 - ...{Heading.stories.js => Heading.stories.ts} | 10 +- src/components/Heading/Heading.ts | 44 + src/components/Heading/Heading.vue | 4 +- .../{Icons.stories.js => Icons.stories.ts} | 5 +- src/components/Infobox/Infobox.js | 22 - ...{Infobox.stories.js => Infobox.stories.ts} | 6 +- src/components/Infobox/Infobox.ts | 18 + src/components/Infobox/Infobox.vue | 4 +- .../{Issues.stories.js => Issues.stories.ts} | 6 +- .../LinearProgress/LinearProgress.js | 92 - ...s.stories.js => LinearProgress.stories.ts} | 7 +- ...rogress.test.js => LinearProgress.test.ts} | 6 +- .../LinearProgress/LinearProgress.ts | 95 + .../LinearProgress/LinearProgress.vue | 4 +- src/components/ListItem/ListItem.js | 80 - ...istItem.stories.js => ListItem.stories.ts} | 5 +- .../{ListItem.test.js => ListItem.test.ts} | 10 +- src/components/ListItem/ListItem.ts | 78 + src/components/ListItem/ListItem.vue | 4 +- .../LoadingSpinner/LoadingSpinner.js | 14 - ...r.stories.js => LoadingSpinner.stories.ts} | 6 +- ...Spinner.test.js => LoadingSpinner.test.ts} | 0 .../LoadingSpinner/LoadingSpinner.ts | 11 + .../LoadingSpinner/LoadingSpinner.vue | 4 +- .../PhoneNumberField/PhoneNumberField.js | 141 - ...stories.js => PhoneNumberField.stories.ts} | 9 +- ...Field.test.js => PhoneNumberField.test.ts} | 102 +- .../PhoneNumberField/PhoneNumberField.ts | 158 + .../PhoneNumberField/PhoneNumberField.vue | 4 +- src/components/PillProgress/PillProgress.js | 92 - ...ess.stories.js => PillProgress.stories.ts} | 7 +- ...lProgress.test.js => PillProgress.test.ts} | 6 +- src/components/PillProgress/PillProgress.ts | 95 + src/components/PillProgress/PillProgress.vue | 4 +- src/components/RadioList/RadioList.js | 64 - ...ioList.stories.js => RadioList.stories.ts} | 7 +- .../{RadioList.test.js => RadioList.test.ts} | 24 +- src/components/RadioList/RadioList.ts | 65 + src/components/RadioList/RadioList.vue | 4 +- src/components/Screen/Screen.js | 54 - .../{Screen.stories.js => Screen.stories.ts} | 5 +- .../Screen/{Screen.test.js => Screen.test.ts} | 26 +- src/components/Screen/Screen.ts | 59 + src/components/Screen/Screen.vue | 4 +- src/components/ScrollWrapper/ScrollWrapper.js | 45 - ...er.stories.js => ScrollWrapper.stories.ts} | 6 +- src/components/ScrollWrapper/ScrollWrapper.ts | 45 + .../ScrollWrapper/ScrollWrapper.vue | 4 +- src/components/SelectBox/SelectBox.js | 108 - ...ectBox.stories.js => SelectBox.stories.ts} | 9 +- .../{SelectBox.test.js => SelectBox.test.ts} | 23 +- src/components/SelectBox/SelectBox.ts | 121 + src/components/SelectBox/SelectBox.vue | 4 +- src/components/SelfieWebCam/SelfieWebCam.js | 240 -- ...Cam.stories.js => SelfieWebCam.stories.ts} | 6 +- src/components/SelfieWebCam/SelfieWebCam.ts | 225 ++ src/components/SelfieWebCam/SelfieWebCam.vue | 2 +- src/components/SignaturePad/SignaturePad.js | 97 - ...Pad.stories.js => SignaturePad.stories.ts} | 6 +- src/components/SignaturePad/SignaturePad.ts | 121 + src/components/SignaturePad/SignaturePad.vue | 4 +- src/components/SlideButton/SlideButton.js | 157 - ...tton.stories.js => SlideButton.stories.ts} | 5 +- src/components/SlideButton/SlideButton.ts | 159 + src/components/SlideButton/SlideButton.vue | 4 +- src/components/SpecCard/SpecCard.js | 27 - ...pecCard.stories.js => SpecCard.stories.ts} | 6 +- src/components/SpecCard/SpecCard.ts | 23 + src/components/SpecCard/SpecCard.vue | 4 +- src/components/Stepper/Stepper.js | 42 - ...{Stepper.stories.js => Stepper.stories.ts} | 6 +- .../{Stepper.test.js => Stepper.test.ts} | 2 +- src/components/Stepper/Stepper.ts | 42 + src/components/Stepper/Stepper.vue | 4 +- .../StorybookMobileDeviceSimulator.js | 91 - ...StorybookMobileDeviceSimulator.stories.ts} | 6 +- .../StorybookMobileDeviceSimulator.ts | 93 + .../StorybookMobileDeviceSimulator.vue | 4 +- src/components/TextField/TextField.js | 104 - ...tField.stories.js => TextField.stories.ts} | 7 +- .../{TextField.test.js => TextField.test.ts} | 8 +- src/components/TextField/TextField.ts | 123 + src/components/TextField/TextField.vue | 4 +- src/components/TextParagraph/TextParagraph.js | 69 - ...ph.stories.js => TextParagraph.stories.ts} | 14 +- ...aragraph.test.js => TextParagraph.test.ts} | 0 src/components/TextParagraph/TextParagraph.ts | 67 + .../TextParagraph/TextParagraph.vue | 4 +- src/components/Timeline/Timeline.js | 15 - ...imeline.stories.js => Timeline.stories.ts} | 6 +- .../{Timeline.test.js => Timeline.test.ts} | 0 src/components/Timeline/Timeline.ts | 13 + src/components/Timeline/Timeline.vue | 4 +- src/components/ToggleSwitch/ToggleSwitch.js | 66 - ...tch.stories.js => ToggleSwitch.stories.ts} | 6 +- ...gleSwitch.test.js => ToggleSwitch.test.ts} | 4 +- src/components/ToggleSwitch/ToggleSwitch.ts | 54 + src/components/ToggleSwitch/ToggleSwitch.vue | 4 +- src/components/TopBar/TopBar.js | 26 - .../{TopBar.stories.js => TopBar.stories.ts} | 6 +- .../TopBar/{TopBar.test.js => TopBar.test.ts} | 0 src/components/TopBar/TopBar.ts | 22 + src/components/TopBar/TopBar.vue | 4 +- src/components/WrappedButton/WrappedButton.js | 59 - ...on.stories.js => WrappedButton.stories.ts} | 5 +- ...edButton.test.js => WrappedButton.test.ts} | 0 src/components/WrappedButton/WrappedButton.ts | 63 + .../WrappedButton/WrappedButton.vue | 4 +- src/components/Wrapper/Wrapper.js | 16 - ...{Wrapper.stories.js => Wrapper.stories.ts} | 6 +- src/components/Wrapper/Wrapper.ts | 14 + src/components/Wrapper/Wrapper.vue | 4 +- ...dator.js => bankAccountNumberValidator.ts} | 18 +- ...abled => copyToClipboard.test.ts.disabled} | 0 ...{copyToClipboard.js => copyToClipboard.ts} | 4 +- src/lib/{dateHelper.js => dateHelper.ts} | 10 +- src/lib/{regexHelper.js => regexHelper.ts} | 4 +- src/lib/{renderString.js => renderString.ts} | 11 +- src/lib/sleepHelper.js | 5 - src/lib/sleepHelper.ts | 5 + ...torybookHelpers.js => storybookHelpers.ts} | 14 +- src/lib/{testUtils.js => testUtils.ts} | 6 +- src/lib/textHelper.js | 5 - src/lib/textHelper.ts | 5 + src/{library.js => library.ts} | 0 src/main.js | 7 - src/main.ts | 7 + src/shims-vue.d.ts | 6 + src/vite-env.d.ts | 1 + tsconfig.dts.json | 12 + tsconfig.eslint.json | 19 + tsconfig.json | 39 + vite.config.js => vite.config.ts | 2 +- 272 files changed, 7347 insertions(+), 5251 deletions(-) rename analyze.config.js => analyze.config.ts (95%) delete mode 100644 src/App.js create mode 100644 src/App.ts delete mode 100644 src/components/ActionItem/ActionItem.js rename src/components/ActionItem/{ActionItem.stories.js => ActionItem.stories.ts} (93%) rename src/components/ActionItem/{ActionItem.test.js => ActionItem.test.ts} (93%) create mode 100644 src/components/ActionItem/ActionItem.ts delete mode 100644 src/components/AmountInput/AmountInput.js rename src/components/AmountInput/{AmountInput.stories.js => AmountInput.stories.ts} (93%) rename src/components/AmountInput/{AmountInput.test.js => AmountInput.test.ts} (90%) create mode 100644 src/components/AmountInput/AmountInput.ts delete mode 100644 src/components/BankAccountNumberInputField/BankAccountNumberInputField.js rename src/components/BankAccountNumberInputField/{BankAccountNumberInputField.stories.js => BankAccountNumberInputField.stories.ts} (96%) rename src/components/BankAccountNumberInputField/{BankAccountNumberInputField.test.js => BankAccountNumberInputField.test.ts} (69%) create mode 100644 src/components/BankAccountNumberInputField/BankAccountNumberInputField.ts delete mode 100644 src/components/BoxContentItem/BoxContentItem.js rename src/components/BoxContentItem/{BoxContentItem.stories.js => BoxContentItem.stories.ts} (95%) create mode 100644 src/components/BoxContentItem/BoxContentItem.test.ts create mode 100644 src/components/BoxContentItem/BoxContentItem.ts delete mode 100644 src/components/Button/Button.js rename src/components/Button/{Button.stories.js => Button.stories.ts} (95%) rename src/components/Button/{Button.test.js => Button.test.ts} (99%) create mode 100644 src/components/Button/Button.ts delete mode 100644 src/components/CallToActionScreen/CallToActionScreen.js rename src/components/CallToActionScreen/{CallToActionScreen.stories.js => CallToActionScreen.stories.ts} (92%) rename src/components/CallToActionScreen/{CallToActionScreen.test.js => CallToActionScreen.test.ts} (100%) create mode 100644 src/components/CallToActionScreen/CallToActionScreen.ts delete mode 100644 src/components/Carousel/Carousel.js rename src/components/Carousel/{Carousel.stories.js => Carousel.stories.ts} (96%) rename src/components/Carousel/{Carousel.test.js => Carousel.test.ts} (90%) create mode 100644 src/components/Carousel/Carousel.ts delete mode 100644 src/components/Checkbox/Checkbox.js rename src/components/Checkbox/{Checkbox.stories.js => Checkbox.stories.ts} (91%) rename src/components/Checkbox/{Checkbox.test.js => Checkbox.test.ts} (93%) create mode 100644 src/components/Checkbox/Checkbox.ts delete mode 100644 src/components/CircularProgress/CircularProgress.js rename src/components/CircularProgress/{CircularProgress.stories.js => CircularProgress.stories.ts} (95%) rename src/components/CircularProgress/{CircularProgress.test.js => CircularProgress.test.ts} (91%) create mode 100644 src/components/CircularProgress/CircularProgress.ts delete mode 100644 src/components/CodeInputField/CodeInputField.js rename src/components/CodeInputField/{CodeInputField.stories.js => CodeInputField.stories.ts} (85%) rename src/components/CodeInputField/{CodeInputField.test.js => CodeInputField.test.ts} (82%) create mode 100644 src/components/CodeInputField/CodeInputField.ts delete mode 100644 src/components/ConfettiOverlay/ConfettiOverlay.js rename src/components/ConfettiOverlay/{ConfettiOverlay.stories.js => ConfettiOverlay.stories.ts} (92%) rename src/components/ConfettiOverlay/{ConfettiOverlay.test.js => ConfettiOverlay.test.ts} (79%) create mode 100644 src/components/ConfettiOverlay/ConfettiOverlay.ts delete mode 100644 src/components/ConfirmationModalDialog/ConfirmationModalDialog.js rename src/components/ConfirmationModalDialog/{ConfirmationModalDialog.stories.js => ConfirmationModalDialog.stories.ts} (96%) rename src/components/ConfirmationModalDialog/{ConfirmationModalDialog.test.js => ConfirmationModalDialog.test.ts} (85%) create mode 100644 src/components/ConfirmationModalDialog/ConfirmationModalDialog.ts delete mode 100644 src/components/ConfirmationToastDialog/ConfirmationToastDialog.js rename src/components/ConfirmationToastDialog/{ConfirmationToastDialog.stories.js => ConfirmationToastDialog.stories.ts} (97%) rename src/components/ConfirmationToastDialog/{ConfirmationToastDialog.test.js => ConfirmationToastDialog.test.ts} (91%) create mode 100644 src/components/ConfirmationToastDialog/ConfirmationToastDialog.ts delete mode 100644 src/components/ContentItem/ContentItem.js rename src/components/ContentItem/{ContentItem.stories.js => ContentItem.stories.ts} (94%) rename src/components/ContentItem/{ContentItem.test.js => ContentItem.test.ts} (98%) create mode 100644 src/components/ContentItem/ContentItem.ts delete mode 100644 src/components/ContentRadioList/ContentRadioList.js rename src/components/ContentRadioList/{ContentRadioList.stories.js => ContentRadioList.stories.ts} (96%) rename src/components/ContentRadioList/{ContentRadioList.test.js => ContentRadioList.test.ts} (80%) create mode 100644 src/components/ContentRadioList/ContentRadioList.ts delete mode 100644 src/components/CopyToClipboardButton/CopyToClipboardButton.js rename src/components/CopyToClipboardButton/{CopyToClipboardButton.stories.js => CopyToClipboardButton.stories.ts} (93%) rename src/components/CopyToClipboardButton/{CopyToClipboardButton.test.js => CopyToClipboardButton.test.ts} (89%) create mode 100644 src/components/CopyToClipboardButton/CopyToClipboardButton.ts delete mode 100644 src/components/CopyableList/CopyableList.js rename src/components/CopyableList/{CopyableList.stories.js => CopyableList.stories.ts} (94%) rename src/components/CopyableList/{CopyableList.test.js => CopyableList.test.ts} (97%) create mode 100644 src/components/CopyableList/CopyableList.ts delete mode 100644 src/components/CopyableListItem/CopyableListItem.js rename src/components/CopyableListItem/{CopyableListItem.stories.js => CopyableListItem.stories.ts} (94%) rename src/components/CopyableListItem/{CopyableListItem.test.js => CopyableListItem.test.ts} (83%) create mode 100644 src/components/CopyableListItem/CopyableListItem.ts delete mode 100644 src/components/CurrencyField/CurrencyField.js rename src/components/CurrencyField/{CurrencyField.stories.js => CurrencyField.stories.ts} (96%) rename src/components/CurrencyField/{CurrencyField.test.js => CurrencyField.test.ts} (91%) create mode 100644 src/components/CurrencyField/CurrencyField.ts delete mode 100644 src/components/DateField/DateField.js rename src/components/DateField/{DateField.stories.js => DateField.stories.ts} (95%) rename src/components/DateField/{DateField.test.js => DateField.test.ts} (88%) create mode 100644 src/components/DateField/DateField.ts delete mode 100644 src/components/FigureCard/FigureCard.js rename src/components/FigureCard/{FigureCard.stories.js => FigureCard.stories.ts} (91%) rename src/components/FigureCard/{FigureCard.test.js => FigureCard.test.ts} (93%) create mode 100644 src/components/FigureCard/FigureCard.ts delete mode 100644 src/components/FormField/FormField.js rename src/components/FormField/{FormField.stories.js => FormField.stories.ts} (96%) rename src/components/FormField/{FormField.test.js => FormField.test.ts} (94%) create mode 100644 src/components/FormField/FormField.ts delete mode 100644 src/components/ForwardButton/ForwardButton.js rename src/components/ForwardButton/{ForwardButton.stories.js => ForwardButton.stories.ts} (91%) delete mode 100644 src/components/ForwardButton/ForwardButton.test.js create mode 100644 src/components/ForwardButton/ForwardButton.test.ts create mode 100644 src/components/ForwardButton/ForwardButton.ts delete mode 100644 src/components/Heading/Heading.js rename src/components/Heading/{Heading.stories.js => Heading.stories.ts} (92%) create mode 100644 src/components/Heading/Heading.ts rename src/components/Icons/{Icons.stories.js => Icons.stories.ts} (92%) delete mode 100644 src/components/Infobox/Infobox.js rename src/components/Infobox/{Infobox.stories.js => Infobox.stories.ts} (87%) create mode 100644 src/components/Infobox/Infobox.ts rename src/components/Issues/{Issues.stories.js => Issues.stories.ts} (80%) delete mode 100644 src/components/LinearProgress/LinearProgress.js rename src/components/LinearProgress/{LinearProgress.stories.js => LinearProgress.stories.ts} (95%) rename src/components/LinearProgress/{LinearProgress.test.js => LinearProgress.test.ts} (86%) create mode 100644 src/components/LinearProgress/LinearProgress.ts delete mode 100644 src/components/ListItem/ListItem.js rename src/components/ListItem/{ListItem.stories.js => ListItem.stories.ts} (95%) rename src/components/ListItem/{ListItem.test.js => ListItem.test.ts} (96%) create mode 100644 src/components/ListItem/ListItem.ts delete mode 100644 src/components/LoadingSpinner/LoadingSpinner.js rename src/components/LoadingSpinner/{LoadingSpinner.stories.js => LoadingSpinner.stories.ts} (85%) rename src/components/LoadingSpinner/{LoadingSpinner.test.js => LoadingSpinner.test.ts} (100%) create mode 100644 src/components/LoadingSpinner/LoadingSpinner.ts delete mode 100644 src/components/PhoneNumberField/PhoneNumberField.js rename src/components/PhoneNumberField/{PhoneNumberField.stories.js => PhoneNumberField.stories.ts} (96%) rename src/components/PhoneNumberField/{PhoneNumberField.test.js => PhoneNumberField.test.ts} (72%) create mode 100644 src/components/PhoneNumberField/PhoneNumberField.ts delete mode 100644 src/components/PillProgress/PillProgress.js rename src/components/PillProgress/{PillProgress.stories.js => PillProgress.stories.ts} (96%) rename src/components/PillProgress/{PillProgress.test.js => PillProgress.test.ts} (83%) create mode 100644 src/components/PillProgress/PillProgress.ts delete mode 100644 src/components/RadioList/RadioList.js rename src/components/RadioList/{RadioList.stories.js => RadioList.stories.ts} (93%) rename src/components/RadioList/{RadioList.test.js => RadioList.test.ts} (84%) create mode 100644 src/components/RadioList/RadioList.ts delete mode 100644 src/components/Screen/Screen.js rename src/components/Screen/{Screen.stories.js => Screen.stories.ts} (90%) rename src/components/Screen/{Screen.test.js => Screen.test.ts} (73%) create mode 100644 src/components/Screen/Screen.ts delete mode 100644 src/components/ScrollWrapper/ScrollWrapper.js rename src/components/ScrollWrapper/{ScrollWrapper.stories.js => ScrollWrapper.stories.ts} (90%) create mode 100644 src/components/ScrollWrapper/ScrollWrapper.ts delete mode 100644 src/components/SelectBox/SelectBox.js rename src/components/SelectBox/{SelectBox.stories.js => SelectBox.stories.ts} (94%) rename src/components/SelectBox/{SelectBox.test.js => SelectBox.test.ts} (80%) create mode 100644 src/components/SelectBox/SelectBox.ts delete mode 100644 src/components/SelfieWebCam/SelfieWebCam.js rename src/components/SelfieWebCam/{SelfieWebCam.stories.js => SelfieWebCam.stories.ts} (93%) create mode 100644 src/components/SelfieWebCam/SelfieWebCam.ts delete mode 100644 src/components/SignaturePad/SignaturePad.js rename src/components/SignaturePad/{SignaturePad.stories.js => SignaturePad.stories.ts} (85%) create mode 100644 src/components/SignaturePad/SignaturePad.ts delete mode 100644 src/components/SlideButton/SlideButton.js rename src/components/SlideButton/{SlideButton.stories.js => SlideButton.stories.ts} (89%) create mode 100644 src/components/SlideButton/SlideButton.ts delete mode 100644 src/components/SpecCard/SpecCard.js rename src/components/SpecCard/{SpecCard.stories.js => SpecCard.stories.ts} (89%) create mode 100644 src/components/SpecCard/SpecCard.ts delete mode 100644 src/components/Stepper/Stepper.js rename src/components/Stepper/{Stepper.stories.js => Stepper.stories.ts} (89%) rename src/components/Stepper/{Stepper.test.js => Stepper.test.ts} (95%) create mode 100644 src/components/Stepper/Stepper.ts delete mode 100644 src/components/StorybookMobileDeviceSimulator/StorybookMobileDeviceSimulator.js rename src/components/StorybookMobileDeviceSimulator/{StorybookMobileDeviceSimulator.stories.js => StorybookMobileDeviceSimulator.stories.ts} (89%) create mode 100644 src/components/StorybookMobileDeviceSimulator/StorybookMobileDeviceSimulator.ts delete mode 100644 src/components/TextField/TextField.js rename src/components/TextField/{TextField.stories.js => TextField.stories.ts} (97%) rename src/components/TextField/{TextField.test.js => TextField.test.ts} (92%) create mode 100644 src/components/TextField/TextField.ts delete mode 100644 src/components/TextParagraph/TextParagraph.js rename src/components/TextParagraph/{TextParagraph.stories.js => TextParagraph.stories.ts} (93%) rename src/components/TextParagraph/{TextParagraph.test.js => TextParagraph.test.ts} (100%) create mode 100644 src/components/TextParagraph/TextParagraph.ts delete mode 100644 src/components/Timeline/Timeline.js rename src/components/Timeline/{Timeline.stories.js => Timeline.stories.ts} (93%) rename src/components/Timeline/{Timeline.test.js => Timeline.test.ts} (100%) create mode 100644 src/components/Timeline/Timeline.ts delete mode 100644 src/components/ToggleSwitch/ToggleSwitch.js rename src/components/ToggleSwitch/{ToggleSwitch.stories.js => ToggleSwitch.stories.ts} (92%) rename src/components/ToggleSwitch/{ToggleSwitch.test.js => ToggleSwitch.test.ts} (94%) create mode 100644 src/components/ToggleSwitch/ToggleSwitch.ts delete mode 100644 src/components/TopBar/TopBar.js rename src/components/TopBar/{TopBar.stories.js => TopBar.stories.ts} (85%) rename src/components/TopBar/{TopBar.test.js => TopBar.test.ts} (100%) create mode 100644 src/components/TopBar/TopBar.ts delete mode 100644 src/components/WrappedButton/WrappedButton.js rename src/components/WrappedButton/{WrappedButton.stories.js => WrappedButton.stories.ts} (93%) rename src/components/WrappedButton/{WrappedButton.test.js => WrappedButton.test.ts} (100%) create mode 100644 src/components/WrappedButton/WrappedButton.ts delete mode 100644 src/components/Wrapper/Wrapper.js rename src/components/Wrapper/{Wrapper.stories.js => Wrapper.stories.ts} (88%) create mode 100644 src/components/Wrapper/Wrapper.ts rename src/lib/{bankAccountNumberValidator.js => bankAccountNumberValidator.ts} (69%) rename src/lib/{copyToClipboard.test.js.disabled => copyToClipboard.test.ts.disabled} (100%) rename src/lib/{copyToClipboard.js => copyToClipboard.ts} (81%) rename src/lib/{dateHelper.js => dateHelper.ts} (85%) rename src/lib/{regexHelper.js => regexHelper.ts} (77%) rename src/lib/{renderString.js => renderString.ts} (76%) delete mode 100644 src/lib/sleepHelper.js create mode 100644 src/lib/sleepHelper.ts rename src/lib/{storybookHelpers.js => storybookHelpers.ts} (81%) rename src/lib/{testUtils.js => testUtils.ts} (72%) delete mode 100644 src/lib/textHelper.js create mode 100644 src/lib/textHelper.ts rename src/{library.js => library.ts} (100%) delete mode 100644 src/main.js create mode 100644 src/main.ts create mode 100644 src/shims-vue.d.ts create mode 100644 src/vite-env.d.ts create mode 100644 tsconfig.dts.json create mode 100644 tsconfig.eslint.json create mode 100644 tsconfig.json rename vite.config.js => vite.config.ts (94%) diff --git a/.editorconfig b/.editorconfig index c330281..ca01602 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,7 +5,7 @@ end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true -[*.{js,jsx,vue,html,css}] +[*.{ts,tsx,js,jsx,vue,html,css}] charset = utf-8 indent_style = space indent_size = 2 diff --git a/.eslintrc.js b/.eslintrc.js index f6861f1..3ea49d2 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,15 +1,19 @@ module.exports = { root: true, env: { + es6: true, node: true, }, extends: [ 'airbnb-base', + 'airbnb-typescript/base', 'plugin:vue/vue3-strongly-recommended', + 'plugin:@typescript-eslint/recommended', ], plugins: [ 'fp', '@getify/proper-ternary', + '@typescript-eslint', ], ignorePatterns: [ 'build/**/*', @@ -19,9 +23,13 @@ module.exports = { document: true, window: true, }, + parser: 'vue-eslint-parser', parserOptions: { + parser: '@typescript-eslint/parser', ecmaVersion: 2020, sourceType: 'module', + project: ['./tsconfig.eslint.json'], + extraFileExtensions: ['.vue'], }, overrides: [ { @@ -35,6 +43,11 @@ module.exports = { }, }, ], + settings: { + 'import/resolver': { + typescript: {}, + }, + }, rules: { 'no-console': 1, 'no-extra-boolean-cast': 0, @@ -67,6 +80,7 @@ module.exports = { 'fp/no-delete': 'error', 'fp/no-get-set': 'error', '@getify/proper-ternary/parens': ['error', { call: false, object: false }], + '@typescript-eslint/consistent-type-imports': 'error', 'vue/multi-word-component-names': ['error', { ignores: ['Button', 'Carousel', 'Checkbox', 'Confetti', 'Heading', 'Infobox', 'Screen', 'Stepper', 'Timeline', 'Wrapper'], }], diff --git a/.github/workflows/mergepr.yml b/.github/workflows/mergepr.yml index 326bec0..386d096 100644 --- a/.github/workflows/mergepr.yml +++ b/.github/workflows/mergepr.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: - node-version: [12.x] + node-version: [14.x] env: working-dir: ./ diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 21523cc..289c805 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -13,7 +13,7 @@ jobs: strategy: matrix: - node-version: [12.x] + node-version: [14.x] env: working-dir: ./ diff --git a/analyze.config.js b/analyze.config.ts similarity index 95% rename from analyze.config.js rename to analyze.config.ts index bd64940..1588502 100644 --- a/analyze.config.js +++ b/analyze.config.ts @@ -14,7 +14,7 @@ export default defineConfig({ ], build: { lib: { - entry: resolve(__dirname, 'src/library.js'), + entry: resolve(__dirname, 'src/library.ts'), name: 'b2cMappUiAssets', fileName: (format) => `b2c-mapp-ui.${format}.js`, }, diff --git a/babel.config.js b/babel.config.js index 31ae050..ab7c4be 100644 --- a/babel.config.js +++ b/babel.config.js @@ -10,6 +10,7 @@ const config = (api) => { exclude: ['transform-regenerator'], }, ], + '@babel/preset-typescript', ], }; return result; diff --git a/config/storybook/main.js b/config/storybook/main.js index 7caab87..a2a2be6 100644 --- a/config/storybook/main.js +++ b/config/storybook/main.js @@ -1,5 +1,5 @@ module.exports = { - stories: ['../../src/**/*.stories.@(js|jsx|mdx)'], + stories: ['../../src/**/*.stories.@(ts|js|jsx|mdx)'], addons: [ '@storybook/addon-docs', '@storybook/addon-controls', diff --git a/index.html b/index.html index 030a6ff..11603f8 100644 --- a/index.html +++ b/index.html @@ -8,6 +8,6 @@
- + diff --git a/jest.config.js b/jest.config.js index d62e4c5..c3fb649 100644 --- a/jest.config.js +++ b/jest.config.js @@ -2,11 +2,12 @@ const esModules = ['vue-contenteditable', 'lodash-es'].join('|'); module.exports = { moduleFileExtensions: [ 'js', + 'ts', 'vue', ], transform: { '^.+\\.vue$': 'vue-jest', - '^.+\\.js$': 'babel-jest', + '^.+\\.(j|t)s$': 'babel-jest', }, setupFiles: ['/setupTests.js'], roots: ['/src/components'], @@ -18,12 +19,12 @@ module.exports = { '/src/styles', ], testMatch: [ - '**/*.test.js', - '**/*.spec.js', + '**/*.test.ts', + '**/*.spec.ts', ], collectCoverageFrom: [ 'src/**/*.vue', - '!src/main.js', + '!src/main.ts', ], verbose: true, transformIgnorePatterns: [`/node_modules/(?!${esModules})`], diff --git a/package-lock.json b/package-lock.json index f3d1606..0eb9d35 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "@lana/b2c-mapp-ui", - "version": "9.4.2", + "version": "9.5.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@lana/b2c-mapp-ui", - "version": "9.4.2", + "version": "9.5.0", "license": "ISC", "dependencies": { - "@lana/b2c-mapp-ui-assets": "^5.5.0", + "@lana/b2c-mapp-ui-assets": "^5.6.0", "libphonenumber-js": "^1.9.42", "lodash-es": "^4.17.21", "vue": "^3.2.21", @@ -17,6 +17,7 @@ "vue-currency-input": "^2.1.2" }, "devDependencies": { + "@babel/preset-typescript": "^7.16.7", "@getify/eslint-plugin-proper-ternary": "^3.1.1", "@storybook/addon-actions": "^6.4.9", "@storybook/addon-controls": "^6.4.9", @@ -25,24 +26,36 @@ "@storybook/addon-links": "^6.4.9", "@storybook/storybook-deployer": "^2.8.10", "@storybook/vue3": "^6.4.9", - "@testing-library/dom": "^8.11.0", + "@testing-library/dom": "^8.11.1", "@testing-library/vue": "^6.4.2", + "@types/jest": "^27.4.0", + "@types/lodash-es": "^4.17.5", + "@types/node": "^17.0.2", + "@typescript-eslint/eslint-plugin": "^5.8.0", + "@typescript-eslint/parser": "^5.8.0", "@vitejs/plugin-vue": "^1.9.4", - "@vue/test-utils": "^2.0.0-rc.16", + "@vue/eslint-config-typescript": "^9.1.0", + "@vue/test-utils": "^2.0.0-rc.18", "babel-jest": "^26.6.3", "eslint": "^7.5.0", "eslint-config-airbnb-base": "^14.2.1", + "eslint-config-airbnb-typescript": "^16.1.0", + "eslint-import-resolver-typescript": "^2.5.0", "eslint-plugin-fp": "^2.3.0", - "eslint-plugin-import": "^2.23.4", + "eslint-plugin-import": "^2.25.3", "eslint-plugin-vue": "^8.1.1", "jest": "^26.6.3", "libphonenumber-metadata-generator": "^1.0.1", "rollup-plugin-visualizer": "^5.5.2", "sass": "^1.43.4", "storybook-builder-vite": "^0.1.11", + "ts-jest": "^26.5.6", + "ts-node": "^10.4.0", + "typescript": "^4.5.4", "vite": "^2.6.13", "vite-plugin-eslint": "^1.3.0", - "vue-jest": "^5.0.0-alpha.10" + "vue-jest": "^5.0.0-alpha.10", + "vue-tsc": "^0.30.2" }, "peerDependencies": { "vue": "^3.2.21" @@ -61,12 +74,12 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz", - "integrity": "sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", + "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", "dev": true, "dependencies": { - "@babel/highlight": "^7.16.0" + "@babel/highlight": "^7.16.7" }, "engines": { "node": ">=6.9.0" @@ -112,12 +125,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.5.tgz", - "integrity": "sha512-kIvCdjZqcdKqoDbVVdt5R99icaRtrtYhYK/xux5qiWCBmfdvEYMFZ68QCrpE5cbFM1JsuArUNs1ZkuKtTtUcZA==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.7.tgz", + "integrity": "sha512-/ST3Sg8MLGY5HVYmrjOgL60ENux/HfO/CsUh7y4MalThufhE/Ff/6EibFDHi4jiDCaWfJKoqbE6oTh21c5hrRg==", "dev": true, "dependencies": { - "@babel/types": "^7.16.0", + "@babel/types": "^7.16.7", "jsesc": "^2.5.1", "source-map": "^0.5.0" }, @@ -126,12 +139,12 @@ } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.0.tgz", - "integrity": "sha512-ItmYF9vR4zA8cByDocY05o0LGUkp1zhbTQOH1NFyl5xXEqlTJQCEJjieriw+aFpxo16swMxUnUiKS7a/r4vtHg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", + "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==", "dev": true, "dependencies": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.16.7" }, "engines": { "node": ">=6.9.0" @@ -169,18 +182,18 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.16.5.tgz", - "integrity": "sha512-NEohnYA7mkB8L5JhU7BLwcBdU3j83IziR9aseMueWGeAjblbul3zzb8UvJ3a1zuBiqCMObzCJHFqKIQE6hTVmg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.16.7.tgz", + "integrity": "sha512-kIFozAvVfK05DM4EVQYKK+zteWvY85BFdGBRQBytRyY3y+6PX0DkDOn/CZ3lEuczCfrCxEzwt0YtP/87YPTWSw==", "dev": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.16.0", - "@babel/helper-environment-visitor": "^7.16.5", - "@babel/helper-function-name": "^7.16.0", - "@babel/helper-member-expression-to-functions": "^7.16.5", - "@babel/helper-optimise-call-expression": "^7.16.0", - "@babel/helper-replace-supers": "^7.16.5", - "@babel/helper-split-export-declaration": "^7.16.0" + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-function-name": "^7.16.7", + "@babel/helper-member-expression-to-functions": "^7.16.7", + "@babel/helper-optimise-call-expression": "^7.16.7", + "@babel/helper-replace-supers": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7" }, "engines": { "node": ">=6.9.0" @@ -225,12 +238,12 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.5.tgz", - "integrity": "sha512-ODQyc5AnxmZWm/R2W7fzhamOk1ey8gSguo5SGvF0zcB3uUzRpTRmM/jmLSm9bDMyPlvbyJ+PwPEK0BWIoZ9wjg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", + "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==", "dev": true, "dependencies": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.16.7" }, "engines": { "node": ">=6.9.0" @@ -249,50 +262,50 @@ } }, "node_modules/@babel/helper-function-name": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.0.tgz", - "integrity": "sha512-BZh4mEk1xi2h4HFjWUXRQX5AEx4rvaZxHgax9gcjdLWdkjsY7MKt5p0otjsg5noXw+pB+clMCjw+aEVYADMjog==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", + "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", "dev": true, "dependencies": { - "@babel/helper-get-function-arity": "^7.16.0", - "@babel/template": "^7.16.0", - "@babel/types": "^7.16.0" + "@babel/helper-get-function-arity": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/types": "^7.16.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-get-function-arity": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.0.tgz", - "integrity": "sha512-ASCquNcywC1NkYh/z7Cgp3w31YW8aojjYIlNg4VeJiHkqyP4AzIvr4qx7pYDb4/s8YcsZWqqOSxgkvjUz1kpDQ==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", + "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", "dev": true, "dependencies": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.16.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.0.tgz", - "integrity": "sha512-1AZlpazjUR0EQZQv3sgRNfM9mEVWPK3M6vlalczA+EECcPz3XPh6VplbErL5UoMpChhSck5wAJHthlj1bYpcmg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", + "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", "dev": true, "dependencies": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.16.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.5.tgz", - "integrity": "sha512-7fecSXq7ZrLE+TWshbGT+HyCLkxloWNhTbU2QM1NTI/tDqyf0oZiMcEfYtDuUDCo528EOlt39G1rftea4bRZIw==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.7.tgz", + "integrity": "sha512-VtJ/65tYiU/6AbMTDwyoXGPKHgTsfRarivm+YbB5uAzKUyuPjgZSgAFeG87FCigc7KNHu2Pegh1XIT3lXjvz3Q==", "dev": true, "dependencies": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.16.7" }, "engines": { "node": ">=6.9.0" @@ -330,21 +343,21 @@ } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.0.tgz", - "integrity": "sha512-SuI467Gi2V8fkofm2JPnZzB/SUuXoJA5zXe/xzyPP2M04686RzFKFHPK6HDVN6JvWBIEW8tt9hPR7fXdn2Lgpw==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz", + "integrity": "sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w==", "dev": true, "dependencies": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.16.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.5.tgz", - "integrity": "sha512-59KHWHXxVA9K4HNF4sbHCf+eJeFe0Te/ZFGqBT4OjXhrwvA04sGfaEGsVTdsjoszq0YTP49RC9UKe5g8uN2RwQ==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", + "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", "dev": true, "engines": { "node": ">=6.9.0" @@ -365,16 +378,16 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.5.tgz", - "integrity": "sha512-ao3seGVa/FZCMCCNDuBcqnBFSbdr8N2EW35mzojx3TwfIbdPmNK+JV6+2d5bR0Z71W5ocLnQp9en/cTF7pBJiQ==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz", + "integrity": "sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.16.5", - "@babel/helper-member-expression-to-functions": "^7.16.5", - "@babel/helper-optimise-call-expression": "^7.16.0", - "@babel/traverse": "^7.16.5", - "@babel/types": "^7.16.0" + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-member-expression-to-functions": "^7.16.7", + "@babel/helper-optimise-call-expression": "^7.16.7", + "@babel/traverse": "^7.16.7", + "@babel/types": "^7.16.7" }, "engines": { "node": ">=6.9.0" @@ -405,30 +418,30 @@ } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.0.tgz", - "integrity": "sha512-0YMMRpuDFNGTHNRiiqJX19GjNXA4H0E8jZ2ibccfSxaCogbm3am5WN/2nQNj0YnQwGWM1J06GOcQ2qnh3+0paw==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", + "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", "dev": true, "dependencies": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.16.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", + "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", - "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", + "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", "dev": true, "engines": { "node": ">=6.9.0" @@ -464,12 +477,12 @@ } }, "node_modules/@babel/highlight": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz", - "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.7.tgz", + "integrity": "sha512-aKpPMfLvGO3Q97V0qhw/V2SWNWlwfJknuwAunU7wZLSfrM4xTBvg7E5opUVi1kJTBKihE38CPg4nBiqX83PWYw==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.15.7", + "@babel/helper-validator-identifier": "^7.16.7", "chalk": "^2.0.0", "js-tokens": "^4.0.0" }, @@ -478,9 +491,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.16.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.6.tgz", - "integrity": "sha512-Gr86ujcNuPDnNOY8mi383Hvi8IYrJVJYuf3XcuBM/Dgd+bINn/7tHqsj+tKkoreMbmGsFLsltI/JJd8fOFWGDQ==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.7.tgz", + "integrity": "sha512-sR4eaSrnM7BV7QPzGfEX5paG/6wrZM3I0HDzfIAK06ESvo9oy3xBuVBxE3MbQaKNhvg8g/ixjMWo2CGpzpHsDA==", "bin": { "parser": "bin/babel-parser.js" }, @@ -1063,12 +1076,12 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.0.tgz", - "integrity": "sha512-Xv6mEXqVdaqCBfJFyeab0fH2DnUoMsDmhamxsSi4j8nLd4Vtw213WMJr55xxqipC/YVWyPY3K0blJncPYji+dQ==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.7.tgz", + "integrity": "sha512-YhUIJHHGkqPgEcMYkPCKTyGUdoGKWtopIycQyjJH8OjvRgOYsXsaKehLVPScKJWAULPxMa4N1vCe6szREFlZ7A==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.7" }, "engines": { "node": ">=6.9.0" @@ -1662,14 +1675,14 @@ } }, "node_modules/@babel/plugin-transform-typescript": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.16.1.tgz", - "integrity": "sha512-NO4XoryBng06jjw/qWEU2LhcLJr1tWkhpMam/H4eas/CDKMX/b2/Ylb6EI256Y7+FVPCawwSM1rrJNOpDiz+Lg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.16.7.tgz", + "integrity": "sha512-Hzx1lvBtOCWuCEwMmYOfpQpO7joFeXLgoPuzZZBtTxXqSqUGUubvFGZv2ygo1tB5Bp9q6PXV3H0E/kf7KM0RLA==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.16.0", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/plugin-syntax-typescript": "^7.16.0" + "@babel/helper-create-class-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-typescript": "^7.16.7" }, "engines": { "node": ">=6.9.0" @@ -1851,14 +1864,14 @@ } }, "node_modules/@babel/preset-typescript": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.16.0.tgz", - "integrity": "sha512-txegdrZYgO9DlPbv+9QOVpMnKbOtezsLHWsnsRF4AjbSIsVaujrq1qg8HK0mxQpWv0jnejt0yEoW1uWpvbrDTg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.16.7.tgz", + "integrity": "sha512-WbVEmgXdIyvzB77AQjGBEyYPZx+8tTsO50XtfozQrkW8QB2rLJpH2lgx0TRw5EJrBxOZQ+wCcyPVQvS8tjEHpQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-validator-option": "^7.14.5", - "@babel/plugin-transform-typescript": "^7.16.0" + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-validator-option": "^7.16.7", + "@babel/plugin-transform-typescript": "^7.16.7" }, "engines": { "node": ">=6.9.0" @@ -1912,33 +1925,33 @@ } }, "node_modules/@babel/template": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.0.tgz", - "integrity": "sha512-MnZdpFD/ZdYhXwiunMqqgyZyucaYsbL0IrjoGjaVhGilz+x8YB++kRfygSOIj1yOtWKPlx7NBp+9I1RQSgsd5A==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", + "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.16.0", - "@babel/parser": "^7.16.0", - "@babel/types": "^7.16.0" + "@babel/code-frame": "^7.16.7", + "@babel/parser": "^7.16.7", + "@babel/types": "^7.16.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.5.tgz", - "integrity": "sha512-FOCODAzqUMROikDYLYxl4nmwiLlu85rNqBML/A5hKRVXG2LV8d0iMqgPzdYTcIpjZEBB7D6UDU9vxRZiriASdQ==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.16.0", - "@babel/generator": "^7.16.5", - "@babel/helper-environment-visitor": "^7.16.5", - "@babel/helper-function-name": "^7.16.0", - "@babel/helper-hoist-variables": "^7.16.0", - "@babel/helper-split-export-declaration": "^7.16.0", - "@babel/parser": "^7.16.5", - "@babel/types": "^7.16.0", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.7.tgz", + "integrity": "sha512-8KWJPIb8c2VvY8AJrydh6+fVRo2ODx1wYBU2398xJVq0JomuLBZmVQzLPBblJgHIGYG4znCpUZUZ0Pt2vdmVYQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.16.7", + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-function-name": "^7.16.7", + "@babel/helper-hoist-variables": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/parser": "^7.16.7", + "@babel/types": "^7.16.7", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -1947,12 +1960,12 @@ } }, "node_modules/@babel/types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.0.tgz", - "integrity": "sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.7.tgz", + "integrity": "sha512-E8HuV7FO9qLpx6OtoGfUQ2cjIYnbFwvZWYBS+87EwtdMvmUPJSwykpovFB+8insbpF0uJcpr8KMUi64XZntZcg==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.15.7", + "@babel/helper-validator-identifier": "^7.16.7", "to-fast-properties": "^2.0.0" }, "engines": { @@ -1987,6 +2000,27 @@ "node": ">=0.1.95" } }, + "node_modules/@cspotcode/source-map-consumer": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", + "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", + "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-consumer": "0.8.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@discoveryjs/json-ext": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.6.tgz", @@ -1996,6 +2030,30 @@ "node": ">=10.0.0" } }, + "node_modules/@emmetio/abbreviation": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@emmetio/abbreviation/-/abbreviation-2.2.2.tgz", + "integrity": "sha512-TtE/dBnkTCct8+LntkqVrwqQao6EnPAs1YN3cUgxOxTaBlesBCY37ROUAVZrRlG64GNnVShdl/b70RfAI3w5lw==", + "dev": true, + "dependencies": { + "@emmetio/scanner": "^1.0.0" + } + }, + "node_modules/@emmetio/css-abbreviation": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@emmetio/css-abbreviation/-/css-abbreviation-2.1.4.tgz", + "integrity": "sha512-qk9L60Y+uRtM5CPbB0y+QNl/1XKE09mSO+AhhSauIfr2YOx/ta3NJw2d8RtCFxgzHeRqFRr8jgyzThbu+MZ4Uw==", + "dev": true, + "dependencies": { + "@emmetio/scanner": "^1.0.0" + } + }, + "node_modules/@emmetio/scanner": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@emmetio/scanner/-/scanner-1.0.0.tgz", + "integrity": "sha512-8HqW8EVqjnCmWXVpqAOZf+EGESdkR27odcMMMGefgKXtar00SoYNSryGv//TELI4T3QFsECo78p+0lmalk/CFA==", + "dev": true + }, "node_modules/@emotion/cache": { "version": "10.0.29", "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.29.tgz", @@ -2819,12 +2877,12 @@ } }, "node_modules/@lana/b2c-mapp-ui-assets": { - "version": "5.5.0", - "resolved": "https://npm.pkg.github.com/download/@lana/b2c-mapp-ui-assets/5.5.0/b2db86642aa621493b8274c40e54e0a3f62a3c28ebd443f91e0d1f244bf8e8fb", - "integrity": "sha512-QcX1mH4rI1eChTIgs5LrWfDMp0+LAUt2sNo/nPdj9Rc/FqKVZ8M6zLxgtu9Mm3855B6Eokj/KNtrG6DDEeemYA==", + "version": "5.6.0", + "resolved": "https://npm.pkg.github.com/download/@lana/b2c-mapp-ui-assets/5.6.0/d0780f13aecda098b37e8def4ef57b3808d9a14bc721cd63db16fd5d68635978", + "integrity": "sha512-HfjgH+xP3eg6wAcDJLXjXqRUYxHAFD6tu1pD1FJso9QAivRiC4dCDEBP02CQSdSdApk19w3Y+KdGRM8QXCaazg==", "license": "ISC", - "peerDependencies": { - "vue": "^3.2.20" + "dependencies": { + "vue": "^3.2.26" } }, "node_modules/@mdx-js/loader": { @@ -4933,9 +4991,9 @@ } }, "node_modules/@testing-library/dom": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.11.0.tgz", - "integrity": "sha512-8Ay4UDiMlB5YWy+ZvCeRyFFofs53ebxrWnOFvCoM1HpMAX4cHyuSrCuIM9l2lVuUWUt+Gr3loz/nCwdrnG6ShQ==", + "version": "8.11.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.11.1.tgz", + "integrity": "sha512-3KQDyx9r0RKYailW2MiYrSSKEfH0GTkI51UGEvJenvcoDoeRYs0PZpi2SXqtnMClQvCqdtTTpOfFETDTVADpAg==", "dev": true, "dependencies": { "@babel/code-frame": "^7.10.4", @@ -5129,6 +5187,30 @@ "node": ">= 6" } }, + "node_modules/@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", + "dev": true + }, "node_modules/@types/aria-query": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz", @@ -5136,9 +5218,9 @@ "dev": true }, "node_modules/@types/babel__core": { - "version": "7.1.16", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.16.tgz", - "integrity": "sha512-EAEHtisTMM+KaKwfWdC3oyllIqswlznXCIVCt7/oRNrh+DhgT4UEBNC/jlADNjvw7UnfbcdkGQcPVZ1xYiLcrQ==", + "version": "7.1.18", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.18.tgz", + "integrity": "sha512-S7unDjm/C7z2A2R9NzfKCK1I+BAALDtxEmsJBwlB3EzNfb929ykjL++1CK9LO++EIp2fQrC8O+BwjKvz6UeDyQ==", "dev": true, "dependencies": { "@babel/parser": "^7.1.0", @@ -5149,9 +5231,9 @@ } }, "node_modules/@types/babel__generator": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.3.tgz", - "integrity": "sha512-/GWCmzJWqV7diQW54smJZzWbSFf4QYtF71WCKhcx6Ru/tFyQIY2eiiITcCAeuPbNSvT9YCGkVMqqvSk2Z0mXiA==", + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", "dev": true, "dependencies": { "@babel/types": "^7.0.0" @@ -5255,6 +5337,101 @@ "@types/istanbul-lib-report": "*" } }, + "node_modules/@types/jest": { + "version": "27.4.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.4.0.tgz", + "integrity": "sha512-gHl8XuC1RZ8H2j5sHv/JqsaxXkDDM9iDOgu0Wp8sjs4u/snb2PVehyWXJPr+ORA0RPpgw231mnutWI1+0hgjIQ==", + "dev": true, + "dependencies": { + "jest-diff": "^27.0.0", + "pretty-format": "^27.0.0" + } + }, + "node_modules/@types/jest/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@types/jest/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@types/jest/node_modules/diff-sequences": { + "version": "27.4.0", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.4.0.tgz", + "integrity": "sha512-YqiQzkrsmHMH5uuh8OdQFU9/ZpADnwzml8z0O5HvRNda+5UZsaX/xN+AAxfR2hWq1Y7HZnAzO9J5lJXOuDz2Ww==", + "dev": true, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@types/jest/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@types/jest/node_modules/jest-diff": { + "version": "27.4.2", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.4.2.tgz", + "integrity": "sha512-ujc9ToyUZDh9KcqvQDkk/gkbf6zSaeEg9AiBxtttXW59H/AcqEYp1ciXAtJp+jXWva5nAf/ePtSsgWwE5mqp4Q==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^27.4.0", + "jest-get-type": "^27.4.0", + "pretty-format": "^27.4.2" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@types/jest/node_modules/jest-get-type": { + "version": "27.4.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.4.0.tgz", + "integrity": "sha512-tk9o+ld5TWq41DkK14L4wox4s2D9MtTpKaAVzXfr5CUKm5ZK2ExcaFE0qls2W71zE/6R2TxxrK9w2r6svAFDBQ==", + "dev": true, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@types/jest/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@types/json-schema": { "version": "7.0.9", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", @@ -5267,6 +5444,21 @@ "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, + "node_modules/@types/lodash": { + "version": "4.14.178", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.178.tgz", + "integrity": "sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw==", + "dev": true + }, + "node_modules/@types/lodash-es": { + "version": "4.17.5", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.5.tgz", + "integrity": "sha512-SHBoI8/0aoMQWAgUHMQ599VM6ZiSKg8sh/0cFqqlQQMyY9uEplc0ULU5yQNzcvdR4ZKa0ey8+vFmahuRbOCT1A==", + "dev": true, + "dependencies": { + "@types/lodash": "*" + } + }, "node_modules/@types/mdast": { "version": "3.0.10", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz", @@ -5283,9 +5475,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "16.11.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.6.tgz", - "integrity": "sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w==", + "version": "17.0.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.5.tgz", + "integrity": "sha512-w3mrvNXLeDYV1GKTZorGJQivK6XLCoGwpnyJFbJVK/aTBQUxOCaa/GlFAAN3OTDFcb7h5tiFG+YXCO2By+riZw==", "dev": true }, "node_modules/@types/node-fetch": { @@ -5502,6 +5694,229 @@ "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", "dev": true }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.8.1.tgz", + "integrity": "sha512-wTZ5oEKrKj/8/366qTM366zqhIKAp6NCMweoRONtfuC07OAU9nVI2GZZdqQ1qD30WAAtcPdkH+npDwtRFdp4Rw==", + "dev": true, + "dependencies": { + "@typescript-eslint/experimental-utils": "5.8.1", + "@typescript-eslint/scope-manager": "5.8.1", + "debug": "^4.3.2", + "functional-red-black-tree": "^1.0.1", + "ignore": "^5.1.8", + "regexpp": "^3.2.0", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/experimental-utils": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.8.1.tgz", + "integrity": "sha512-fbodVnjIDU4JpeXWRDsG5IfIjYBxEvs8EBO8W1+YVdtrc2B9ppfof5sZhVEDOtgTfFHnYQJDI8+qdqLYO4ceww==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "@typescript-eslint/scope-manager": "5.8.1", + "@typescript-eslint/types": "5.8.1", + "@typescript-eslint/typescript-estree": "5.8.1", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/experimental-utils/node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.8.1.tgz", + "integrity": "sha512-K1giKHAjHuyB421SoXMXFHHVI4NdNY603uKw92++D3qyxSeYvC10CBJ/GE5Thpo4WTUvu1mmJI2/FFkz38F2Gw==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.8.1", + "@typescript-eslint/types": "5.8.1", + "@typescript-eslint/typescript-estree": "5.8.1", + "debug": "^4.3.2" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.8.1.tgz", + "integrity": "sha512-DGxJkNyYruFH3NIZc3PwrzwOQAg7vvgsHsHCILOLvUpupgkwDZdNq/cXU3BjF4LNrCsVg0qxEyWasys5AiJ85Q==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.8.1", + "@typescript-eslint/visitor-keys": "5.8.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.8.1.tgz", + "integrity": "sha512-L/FlWCCgnjKOLefdok90/pqInkomLnAcF9UAzNr+DSqMC3IffzumHTQTrINXhP1gVp9zlHiYYjvozVZDPleLcA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.8.1.tgz", + "integrity": "sha512-26lQ8l8tTbG7ri7xEcCFT9ijU5Fk+sx/KRRyyzCv7MQ+rZZlqiDPtMKWLC8P7o+dtCnby4c+OlxuX1tp8WfafQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.8.1", + "@typescript-eslint/visitor-keys": "5.8.1", + "debug": "^4.3.2", + "globby": "^11.0.4", + "is-glob": "^4.0.3", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.8.1.tgz", + "integrity": "sha512-SWgiWIwocK6NralrJarPZlWdr0hZnj5GXHIgfdm8hNkyKvpeQuFyLP6YjSIe9kf3YBIfU6OHSZLYkQ+smZwtNg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.8.1", + "eslint-visitor-keys": "^3.0.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.1.0.tgz", + "integrity": "sha512-yWJFpu4DtjsWKkt5GeNBBuZMlNcYVs6vRCLoCVEJrTjaSB6LC98gFipNK/erM2Heg/E8mIK+hXG/pJMLK+eRZA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/@vitejs/plugin-react": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-1.1.0.tgz", @@ -5533,13 +5948,158 @@ "vite": "^2.5.10" } }, + "node_modules/@volar/code-gen": { + "version": "0.30.2", + "resolved": "https://registry.npmjs.org/@volar/code-gen/-/code-gen-0.30.2.tgz", + "integrity": "sha512-75rlb3rw/O/HiXxrOsVJ8JhEssqtN4dJ0i6oG4kL0udr+QM0TwN0PqaEhoMRyMFV6kBPPSunBBJQ3XNAb0PtGA==", + "dev": true, + "dependencies": { + "@volar/shared": "0.30.2", + "@volar/source-map": "0.30.2" + } + }, + "node_modules/@volar/html2pug": { + "version": "0.30.2", + "resolved": "https://registry.npmjs.org/@volar/html2pug/-/html2pug-0.30.2.tgz", + "integrity": "sha512-k/DLGoXALaQgnacP1MoJ77AwnCHlKcsQKJJug8Qdou3+yOrzYjSSEP6uwG8BS0Fv1h4d4JYmlXsxW8gJPGXSQQ==", + "dev": true, + "dependencies": { + "domelementtype": "^2.2.0", + "domhandler": "^4.3.0", + "htmlparser2": "^7.2.0", + "pug": "^3.0.2" + } + }, + "node_modules/@volar/html2pug/node_modules/entities": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", + "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/@volar/html2pug/node_modules/htmlparser2": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.2.0.tgz", + "integrity": "sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.2", + "domutils": "^2.8.0", + "entities": "^3.0.1" + } + }, + "node_modules/@volar/shared": { + "version": "0.30.2", + "resolved": "https://registry.npmjs.org/@volar/shared/-/shared-0.30.2.tgz", + "integrity": "sha512-93Q7i758WjScg4ptvDcpk66r4Paz9StVMH/M5RCsU4/9F5a1xSCUJbkbpwE0zESkVzcuBatDqk79PaZ8TZKqRg==", + "dev": true, + "dependencies": { + "upath": "^2.0.1", + "vscode-jsonrpc": "^8.0.0-next.4", + "vscode-uri": "^3.0.2" + } + }, + "node_modules/@volar/shared/node_modules/upath": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz", + "integrity": "sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==", + "dev": true, + "engines": { + "node": ">=4", + "yarn": "*" + } + }, + "node_modules/@volar/source-map": { + "version": "0.30.2", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-0.30.2.tgz", + "integrity": "sha512-gwa9OiSjUOZWYutJX53m/KDU/zaF0yN3RP2B8J0aMVyT5dE/VaIfknSxPAW5QFC6AT79Ea1HKGtChuSBgqHCfA==", + "dev": true, + "dependencies": { + "@volar/shared": "0.30.2", + "vscode-languageserver-textdocument": "^1.0.3" + } + }, + "node_modules/@volar/transforms": { + "version": "0.30.2", + "resolved": "https://registry.npmjs.org/@volar/transforms/-/transforms-0.30.2.tgz", + "integrity": "sha512-bc+55NGlBbMLHkpChqAEgsblYJxjNHiKVMbVUMi52xKa2l9gOWXNbn5WRsHfz4AR+Cq/Zm0AvVJ10ehLQsGsow==", + "dev": true, + "dependencies": { + "@volar/shared": "0.30.2", + "vscode-languageserver-types": "^3.17.0-next.5" + } + }, + "node_modules/@volar/vue-code-gen": { + "version": "0.30.2", + "resolved": "https://registry.npmjs.org/@volar/vue-code-gen/-/vue-code-gen-0.30.2.tgz", + "integrity": "sha512-kJyVkQFhMvVQ32aDaC6h5DdXG1GJbJjeeAkCnjfCJfMmuYjM5R4QNZHDz1TI0dETgF9vP+kbAukQKNWecHK3qg==", + "dev": true, + "dependencies": { + "@volar/code-gen": "0.30.2", + "@volar/shared": "0.30.2", + "@volar/source-map": "0.30.2", + "@vue/compiler-core": "^3.2.26", + "@vue/compiler-dom": "^3.2.26", + "@vue/shared": "^3.2.26", + "upath": "^2.0.1" + } + }, + "node_modules/@volar/vue-code-gen/node_modules/upath": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz", + "integrity": "sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==", + "dev": true, + "engines": { + "node": ">=4", + "yarn": "*" + } + }, + "node_modules/@vscode/emmet-helper": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/@vscode/emmet-helper/-/emmet-helper-2.8.3.tgz", + "integrity": "sha512-dkTSL+BaBBS8gFgPm/GMOU+XfxaMyI+Fl1IUYxEi8Iv24RfHf9/q2eCpV2hs7sncLcoKWEbMYe5gv4Ppmp2Oxw==", + "dev": true, + "dependencies": { + "emmet": "^2.3.0", + "jsonc-parser": "^2.3.0", + "vscode-languageserver-textdocument": "^1.0.1", + "vscode-languageserver-types": "^3.15.1", + "vscode-nls": "^5.0.0", + "vscode-uri": "^2.1.2" + } + }, + "node_modules/@vscode/emmet-helper/node_modules/vscode-languageserver-types": { + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz", + "integrity": "sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==", + "dev": true + }, + "node_modules/@vscode/emmet-helper/node_modules/vscode-uri": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-2.1.2.tgz", + "integrity": "sha512-8TEXQxlldWAuIODdukIb+TR5s+9Ds40eSJrw+1iDDA9IFORPjMELarNQE3myz5XIkWWpdprmJjm1/SxMlWOC8A==", + "dev": true + }, "node_modules/@vue/compiler-core": { - "version": "3.2.21", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.21.tgz", - "integrity": "sha512-NhhiQZNG71KNq1h5pMW/fAXdTF7lJRaSI7LDm2edhHXVz1ROMICo8SreUmQnSf4Fet0UPBVqJ988eF4+936iDQ==", + "version": "3.2.26", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.26.tgz", + "integrity": "sha512-N5XNBobZbaASdzY9Lga2D9Lul5vdCIOXvUMd6ThcN8zgqQhPKfCV+wfAJNNJKQkSHudnYRO2gEB+lp0iN3g2Tw==", "dependencies": { - "@babel/parser": "^7.15.0", - "@vue/shared": "3.2.21", + "@babel/parser": "^7.16.4", + "@vue/shared": "3.2.26", "estree-walker": "^2.0.2", "source-map": "^0.6.1" } @@ -5553,25 +6113,25 @@ } }, "node_modules/@vue/compiler-dom": { - "version": "3.2.21", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.21.tgz", - "integrity": "sha512-gsJD3DpYZSYquiA7UIPsMDSlAooYWDvHPq9VRsqzJEk2PZtFvLvHPb4aaMD8Ufd62xzYn32cnnkzsEOJhyGilA==", + "version": "3.2.26", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.26.tgz", + "integrity": "sha512-smBfaOW6mQDxcT3p9TKT6mE22vjxjJL50GFVJiI0chXYGU/xzC05QRGrW3HHVuJrmLTLx5zBhsZ2dIATERbarg==", "dependencies": { - "@vue/compiler-core": "3.2.21", - "@vue/shared": "3.2.21" + "@vue/compiler-core": "3.2.26", + "@vue/shared": "3.2.26" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.2.21", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.21.tgz", - "integrity": "sha512-+yDlUSebKpz/ovxM2vLRRx7w/gVfY767pOfYTgbIhAs+ogvIV2BsIt4fpxlThnlCNChJ+yE0ERUNoROv2kEGEQ==", - "dependencies": { - "@babel/parser": "^7.15.0", - "@vue/compiler-core": "3.2.21", - "@vue/compiler-dom": "3.2.21", - "@vue/compiler-ssr": "3.2.21", - "@vue/ref-transform": "3.2.21", - "@vue/shared": "3.2.21", + "version": "3.2.26", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.26.tgz", + "integrity": "sha512-ePpnfktV90UcLdsDQUh2JdiTuhV0Skv2iYXxfNMOK/F3Q+2BO0AulcVcfoksOpTJGmhhfosWfMyEaEf0UaWpIw==", + "dependencies": { + "@babel/parser": "^7.16.4", + "@vue/compiler-core": "3.2.26", + "@vue/compiler-dom": "3.2.26", + "@vue/compiler-ssr": "3.2.26", + "@vue/reactivity-transform": "3.2.26", + "@vue/shared": "3.2.26", "estree-walker": "^2.0.2", "magic-string": "^0.25.7", "postcss": "^8.1.10", @@ -5609,74 +6169,92 @@ } }, "node_modules/@vue/compiler-ssr": { - "version": "3.2.21", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.21.tgz", - "integrity": "sha512-eU+A0iWYy+1zAo2CRIJ0zSVlv1iuGAIbNRCnllSJ31pV1lX3jypJYzGbJlSRAbB7VP6E+tYveVT1Oq8JKewa3g==", + "version": "3.2.26", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.26.tgz", + "integrity": "sha512-2mywLX0ODc4Zn8qBoA2PDCsLEZfpUGZcyoFRLSOjyGGK6wDy2/5kyDOWtf0S0UvtoyVq95OTSGIALjZ4k2q/ag==", + "dependencies": { + "@vue/compiler-dom": "3.2.26", + "@vue/shared": "3.2.26" + } + }, + "node_modules/@vue/eslint-config-typescript": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-9.1.0.tgz", + "integrity": "sha512-j/852/ZYQ5wDvCD3HE2q4uqJwJAceer2FwoEch1nFo+zTOsPrbzbE3cuWIs3kvu5hdFsGTMYwRwjI6fqZKDMxQ==", + "dev": true, "dependencies": { - "@vue/compiler-dom": "3.2.21", - "@vue/shared": "3.2.21" + "vue-eslint-parser": "^8.0.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^5.0.0", + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0", + "eslint-plugin-vue": "^8.0.1" } }, "node_modules/@vue/reactivity": { - "version": "3.2.21", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.21.tgz", - "integrity": "sha512-7C57zFm/5E3SSTUhVuYj1InDwuJ+GIVQ/z+H43C9sST85gIThGXVhksl1yWTAadf8Yz4T5lSbqi5Ds8U/ueWcw==", + "version": "3.2.26", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.26.tgz", + "integrity": "sha512-h38bxCZLW6oFJVDlCcAiUKFnXI8xP8d+eO0pcDxx+7dQfSPje2AO6M9S9QO6MrxQB7fGP0DH0dYQ8ksf6hrXKQ==", "dependencies": { - "@vue/shared": "3.2.21" + "@vue/shared": "3.2.26" } }, - "node_modules/@vue/ref-transform": { - "version": "3.2.21", - "resolved": "https://registry.npmjs.org/@vue/ref-transform/-/ref-transform-3.2.21.tgz", - "integrity": "sha512-uiEWWBsrGeun9O7dQExYWzXO3rHm/YdtFNXDVqCSoPypzOVxWxdiL+8hHeWzxMB58fVuV2sT80aUtIVyaBVZgQ==", + "node_modules/@vue/reactivity-transform": { + "version": "3.2.26", + "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.26.tgz", + "integrity": "sha512-XKMyuCmzNA7nvFlYhdKwD78rcnmPb7q46uoR00zkX6yZrUmcCQ5OikiwUEVbvNhL5hBJuvbSO95jB5zkUon+eQ==", "dependencies": { - "@babel/parser": "^7.15.0", - "@vue/compiler-core": "3.2.21", - "@vue/shared": "3.2.21", + "@babel/parser": "^7.16.4", + "@vue/compiler-core": "3.2.26", + "@vue/shared": "3.2.26", "estree-walker": "^2.0.2", "magic-string": "^0.25.7" } }, "node_modules/@vue/runtime-core": { - "version": "3.2.21", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.21.tgz", - "integrity": "sha512-7oOxKaU0D2IunOAMOOHZgJVrHg63xwng8BZx3fbgmakqEIMwHhQcp+5GV1sOg/sWW7R4UhaRDIUCukO2GRVK2Q==", + "version": "3.2.26", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.26.tgz", + "integrity": "sha512-BcYi7qZ9Nn+CJDJrHQ6Zsmxei2hDW0L6AB4vPvUQGBm2fZyC0GXd/4nVbyA2ubmuhctD5RbYY8L+5GUJszv9mQ==", "dependencies": { - "@vue/reactivity": "3.2.21", - "@vue/shared": "3.2.21" + "@vue/reactivity": "3.2.26", + "@vue/shared": "3.2.26" } }, "node_modules/@vue/runtime-dom": { - "version": "3.2.21", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.21.tgz", - "integrity": "sha512-apBdriD6QsI4ywbllY8kjr9/0scGuStDuvLbJULPQkFPtHzntd51bP5PQTQVAEIc9kwnTozmj6x6ZdX/cwo7xA==", + "version": "3.2.26", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.26.tgz", + "integrity": "sha512-dY56UIiZI+gjc4e8JQBwAifljyexfVCkIAu/WX8snh8vSOt/gMSEGwPRcl2UpYpBYeyExV8WCbgvwWRNt9cHhQ==", "dependencies": { - "@vue/runtime-core": "3.2.21", - "@vue/shared": "3.2.21", + "@vue/runtime-core": "3.2.26", + "@vue/shared": "3.2.26", "csstype": "^2.6.8" } }, "node_modules/@vue/server-renderer": { - "version": "3.2.21", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.21.tgz", - "integrity": "sha512-QBgYqVgI7XCSBCqGa4LduV9vpfQFdZBOodFmq5Txk5W/v1KrJ1LoOh2Q0RHiRgtoK/UR9uyvRVcYqOmwHkZNEg==", + "version": "3.2.26", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.26.tgz", + "integrity": "sha512-Jp5SggDUvvUYSBIvYEhy76t4nr1vapY/FIFloWmQzn7UxqaHrrBpbxrqPcTrSgGrcaglj0VBp22BKJNre4aA1w==", "dependencies": { - "@vue/compiler-ssr": "3.2.21", - "@vue/shared": "3.2.21" + "@vue/compiler-ssr": "3.2.26", + "@vue/shared": "3.2.26" }, "peerDependencies": { - "vue": "3.2.21" + "vue": "3.2.26" } }, "node_modules/@vue/shared": { - "version": "3.2.21", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.21.tgz", - "integrity": "sha512-5EQmIPK6gw4UVYUbM959B0uPsJ58+xoMESCZs3N89XyvJ9e+fX4pqEPrOGV8OroIk3SbEvJcC+eYc8BH9JQrHA==" + "version": "3.2.26", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.26.tgz", + "integrity": "sha512-vPV6Cq+NIWbH5pZu+V+2QHE9y1qfuTq49uNWw4f7FDEeZaDU2H2cx5jcUZOAKW7qTrUS4k6qZPbMy1x4N96nbA==" }, "node_modules/@vue/test-utils": { - "version": "2.0.0-rc.16", - "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.0.0-rc.16.tgz", - "integrity": "sha512-TubikDVkI2LuRKRPSLv3lYpbpvvucT2DIcGqfBVpvYs4W19u0EBTJEdmfwmSuLY7H1TyAr9Stur3PI1sWWvTGQ==", + "version": "2.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.0.0-rc.18.tgz", + "integrity": "sha512-aifolXjVdsogjaLmDoZ0FU8vN+R67aWmg9OuVeED4w5Ij5GFQLrlhM19uhWe/r5xXUL4fXMk3pX5wW6FJP1NcQ==", "dev": true, "peerDependencies": { "vue": "^3.0.1" @@ -6167,6 +6745,12 @@ "node": ">=10" } }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -7356,6 +7940,18 @@ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", "dev": true }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/bser": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", @@ -8711,6 +9307,12 @@ "sha.js": "^2.4.8" } }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -9127,6 +9729,15 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/diff-sequences": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", @@ -9433,6 +10044,16 @@ "url": "https://github.com/sindresorhus/emittery?sponsor=1" } }, + "node_modules/emmet": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/emmet/-/emmet-2.3.5.tgz", + "integrity": "sha512-LcWfTamJnXIdMfLvJEC5Ld3hY5/KHXgv1L1bp6I7eEvB0ZhacHZ1kX0BYovJ8FroEsreLcq7n7kZhRMsf6jkXQ==", + "dev": true, + "dependencies": { + "@emmetio/abbreviation": "^2.2.2", + "@emmetio/css-abbreviation": "^2.1.4" + } + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -10028,6 +10649,38 @@ "eslint-plugin-import": "^2.22.1" } }, + "node_modules/eslint-config-airbnb-typescript": { + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-16.1.0.tgz", + "integrity": "sha512-W5Cq20KpEx5ZLC54bnVrC37zq2+WD956Kp/Ma3nYFRjT1v9KM63v+DPkrrmmrVqrlDKaD0ivm/qeYmyHV6qKlw==", + "dev": true, + "dependencies": { + "eslint-config-airbnb-base": "^15.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^5.0.0", + "@typescript-eslint/parser": "^5.0.0" + } + }, + "node_modules/eslint-config-airbnb-typescript/node_modules/eslint-config-airbnb-base": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", + "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", + "dev": true, + "dependencies": { + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5", + "semver": "^6.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "peerDependencies": { + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.2" + } + }, "node_modules/eslint-import-resolver-node": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", @@ -10047,6 +10700,26 @@ "ms": "^2.1.1" } }, + "node_modules/eslint-import-resolver-typescript": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-2.5.0.tgz", + "integrity": "sha512-qZ6e5CFr+I7K4VVhQu3M/9xGv9/YmwsEXrsm3nimw8vWaVHRDrQRp26BgCypTxBp3vUp4o5aVEJRiy0F2DFddQ==", + "dev": true, + "dependencies": { + "debug": "^4.3.1", + "glob": "^7.1.7", + "is-glob": "^4.0.1", + "resolve": "^1.20.0", + "tsconfig-paths": "^3.9.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*" + } + }, "node_modules/eslint-module-utils": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.1.tgz", @@ -10168,9 +10841,9 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.25.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.25.2.tgz", - "integrity": "sha512-qCwQr9TYfoBHOFcVGKY9C9unq05uOxxdklmBXLVvcwo68y5Hta6/GzCZEMx2zQiu0woKNEER0LE7ZgaOfBU14g==", + "version": "2.25.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.25.3.tgz", + "integrity": "sha512-RzAVbby+72IB3iOEL8clzPLzL3wpDrlwjsTBAQXgyp5SeTqqY+0bFubwuo+y/HLhNZcXV4XqTBO4LGsfyHIDXg==", "dev": true, "dependencies": { "array-includes": "^3.1.4", @@ -10178,9 +10851,9 @@ "debug": "^2.6.9", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.6", - "eslint-module-utils": "^2.7.0", + "eslint-module-utils": "^2.7.1", "has": "^1.0.3", - "is-core-module": "^2.7.0", + "is-core-module": "^2.8.0", "is-glob": "^4.0.3", "minimatch": "^3.0.4", "object.values": "^1.1.5", @@ -15486,6 +16159,12 @@ "node": ">=6" } }, + "node_modules/jsonc-parser": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.3.1.tgz", + "integrity": "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==", + "dev": true + }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -15788,6 +16467,12 @@ "semver": "bin/semver" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -16500,15 +17185,6 @@ "safe-buffer": "~5.1.0" } }, - "node_modules/node-modules-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", - "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/node-notifier": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.2.tgz", @@ -17421,13 +18097,10 @@ } }, "node_modules/pirates": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", - "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.4.tgz", + "integrity": "sha512-ZIrVPH+A52Dw84R0L3/VS9Op04PuQ2SEoJL6bkshmiTic/HldyW9Tf7oH5mhJZBK7NmDx27vSMrYEXPXclpDKw==", "dev": true, - "dependencies": { - "node-modules-regexp": "^1.0.0" - }, "engines": { "node": ">= 6" } @@ -17759,12 +18432,12 @@ } }, "node_modules/pretty-format": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.3.1.tgz", - "integrity": "sha512-DR/c+pvFc52nLimLROYjnXPtolawm+uWDxr4FjuLDLUn+ktWnSN851KoHwHzzqq6rfCOjkzN8FLgDrSub6UDuA==", + "version": "27.4.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.4.2.tgz", + "integrity": "sha512-p0wNtJ9oLuvgOQDEIZ9zQjZffK7KtyR6Si0jnXULIDwrlNF8Cuir3AZP0hHv0jmKuNN/edOnbMjnzd4uTcmWiw==", "dev": true, "dependencies": { - "@jest/types": "^27.2.5", + "@jest/types": "^27.4.2", "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" @@ -17774,9 +18447,9 @@ } }, "node_modules/pretty-format/node_modules/@jest/types": { - "version": "27.2.5", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.2.5.tgz", - "integrity": "sha512-nmuM4VuDtCZcY+eTpw+0nvstwReMsjPoj7ZR80/BbixulhLaiX+fbv8oeLW8WZlJMcsGQsTmMKT/iTZu1Uy/lQ==", + "version": "27.4.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.4.2.tgz", + "integrity": "sha512-j35yw0PMTPpZsUoOBiuHzr1zTYoad1cVIE0ajEjcrJONxxrko/IRGKkXx3os0Nsi4Hu3+5VmDbVfq5WhG/pWAg==", "dev": true, "dependencies": { "@types/istanbul-lib-coverage": "^2.0.0", @@ -19399,6 +20072,12 @@ "node": ">=4" } }, + "node_modules/request-light": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/request-light/-/request-light-0.5.7.tgz", + "integrity": "sha512-i/wKzvcx7Er8tZnvqSxWuNO5ZGggu2UgZAqj/RyZ0si7lBTXL7kZiI/dWxzxnQjaY7s5HEy1qK21Do4Ncr6cVw==", + "dev": true + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -21930,6 +22609,58 @@ "integrity": "sha512-3IVX4nI6B5cc31/GFFE+i8ey/N2eA0CZDbo6n0yrz0zDX8ZJ8djmU1p+XRz7G3is0F3bB3pu2pAroFdAWQKU3w==", "dev": true }, + "node_modules/ts-jest": { + "version": "26.5.6", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.5.6.tgz", + "integrity": "sha512-rua+rCP8DxpA8b4DQD/6X2HQS8Zy/xzViVYfEs2OQu68tkCuKLV0Md8pmX55+W24uRIyAsf/BajRfxOs+R2MKA==", + "dev": true, + "dependencies": { + "bs-logger": "0.x", + "buffer-from": "1.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^26.1.0", + "json5": "2.x", + "lodash": "4.x", + "make-error": "1.x", + "mkdirp": "1.x", + "semver": "7.x", + "yargs-parser": "20.x" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": ">= 10" + }, + "peerDependencies": { + "jest": ">=26 <27", + "typescript": ">=3.8 <5.0" + } + }, + "node_modules/ts-jest/node_modules/semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-jest/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/ts-loader": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.3.0.tgz", @@ -22023,6 +22754,68 @@ "integrity": "sha512-vDWbsl26LIcPGmDpoVzjEP6+hvHZkBkLW7JpvwbCv/5IYPJlsbzCVXY3wsCeAxAUeTclNOUZxnLdGh3VBD/J6w==", "dev": true }, + "node_modules/ts-node": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.4.0.tgz", + "integrity": "sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "0.7.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/acorn": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ts-node/node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/ts-pnp": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.2.0.tgz", @@ -22106,6 +22899,27 @@ "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", "dev": true }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, "node_modules/tty-browserify": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", @@ -22170,6 +22984,19 @@ "is-typedarray": "^1.0.0" } }, + "node_modules/typescript": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz", + "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "node_modules/uglify-js": { "version": "3.14.5", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.14.5.tgz", @@ -23172,16 +23999,224 @@ "node": ">=0.10.0" } }, + "node_modules/vscode-css-languageservice": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/vscode-css-languageservice/-/vscode-css-languageservice-5.1.9.tgz", + "integrity": "sha512-/tFOWeZBL3Oc9Zc+2MAi3rEwiXJTSZsvjB+M7nSjWLbGPUIjukUA7YzLgsBoUfR35sPJYnXWUkL56PdfIYM8GA==", + "dev": true, + "dependencies": { + "vscode-languageserver-textdocument": "^1.0.1", + "vscode-languageserver-types": "^3.16.0", + "vscode-nls": "^5.0.0", + "vscode-uri": "^3.0.2" + } + }, + "node_modules/vscode-css-languageservice/node_modules/vscode-languageserver-types": { + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz", + "integrity": "sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==", + "dev": true + }, + "node_modules/vscode-html-languageservice": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/vscode-html-languageservice/-/vscode-html-languageservice-4.2.1.tgz", + "integrity": "sha512-PgaToZVXJ44nFWEBuSINdDgVV6EnpC3MnXBsysR3O5TKcAfywbYeRGRy+Y4dVR7YeUgDvtb+JkJoSkaYC0mxXQ==", + "dev": true, + "dependencies": { + "vscode-languageserver-textdocument": "^1.0.1", + "vscode-languageserver-types": "^3.16.0", + "vscode-nls": "^5.0.0", + "vscode-uri": "^3.0.2" + } + }, + "node_modules/vscode-html-languageservice/node_modules/vscode-languageserver-types": { + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz", + "integrity": "sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==", + "dev": true + }, + "node_modules/vscode-json-languageservice": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-4.1.10.tgz", + "integrity": "sha512-IHliMEEYSY0tJjJt0ECb8ESx/nRXpoy9kN42WVQXgaqGyizFAf3jibSiezDQTrrY7f3kywXggCU+kkJEM+OLZQ==", + "dev": true, + "dependencies": { + "jsonc-parser": "^3.0.0", + "vscode-languageserver-textdocument": "^1.0.1", + "vscode-languageserver-types": "^3.16.0", + "vscode-nls": "^5.0.0", + "vscode-uri": "^3.0.2" + } + }, + "node_modules/vscode-json-languageservice/node_modules/jsonc-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.0.0.tgz", + "integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==", + "dev": true + }, + "node_modules/vscode-json-languageservice/node_modules/vscode-languageserver-types": { + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz", + "integrity": "sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==", + "dev": true + }, + "node_modules/vscode-jsonrpc": { + "version": "8.0.0-next.4", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.0.0-next.4.tgz", + "integrity": "sha512-i+wvza5Wd0YV/t9qhnS8I+dJdhJ1fHIhRW4f262rXXM9Mgts5VZhYrRZufGcai4y99RlbZvwaZhplQ6diRXkaA==", + "dev": true, + "engines": { + "node": ">=8.0.0 || >=10.0.0" + } + }, + "node_modules/vscode-languageserver": { + "version": "8.0.0-next.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-8.0.0-next.5.tgz", + "integrity": "sha512-3E2W0eWtGKb6QAJqspOnD0thrBRRo8IGUMV5jpDNMcMKvmtkcxMwsBh0VxdvuWaZ51PiNyR4L+B+GUvkYsyFEg==", + "dev": true, + "dependencies": { + "vscode-languageserver-protocol": "3.17.0-next.11" + }, + "bin": { + "installServerIntoExtension": "bin/installServerIntoExtension" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.17.0-next.11", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.0-next.11.tgz", + "integrity": "sha512-9FqHT7XvM6tWFsnLvRfuQA7Zh7wZZYAwA9dK85lYthA8M1aXpXEP9drXVvO/Fe03MUeJpKVf2e4/NvDaFUnttg==", + "dev": true, + "dependencies": { + "vscode-jsonrpc": "8.0.0-next.4", + "vscode-languageserver-types": "3.17.0-next.5" + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.3.tgz", + "integrity": "sha512-ynEGytvgTb6HVSUwPJIAZgiHQmPCx8bZ8w5um5Lz+q5DjP0Zj8wTFhQpyg8xaMvefDytw2+HH5yzqS+FhsR28A==", + "dev": true + }, + "node_modules/vscode-languageserver-types": { + "version": "3.17.0-next.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.0-next.5.tgz", + "integrity": "sha512-Zcfaw8BznhlJWB09LDR0dscXyxn9+liREqJnPF4pigeUCHwKxYapYqizwuCpMHQ/oLYiAvKwU+f28hPleYu7pA==", + "dev": true + }, + "node_modules/vscode-nls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-5.0.0.tgz", + "integrity": "sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA==", + "dev": true + }, + "node_modules/vscode-pug-languageservice": { + "version": "0.30.2", + "resolved": "https://registry.npmjs.org/vscode-pug-languageservice/-/vscode-pug-languageservice-0.30.2.tgz", + "integrity": "sha512-YkrBodqSzNrtLaEIeMnRJAcnqCWysIiOdkzxF6XHuOc+wDvbZ1U4XgxoLvNNjQdzNQIEYKbsLW0ldq5TYphjiA==", + "dev": true, + "dependencies": { + "@volar/code-gen": "0.30.2", + "@volar/shared": "0.30.2", + "@volar/source-map": "0.30.2", + "@volar/transforms": "0.30.2", + "pug-lexer": "^5.0.1", + "pug-parser": "^6.0.0", + "vscode-languageserver-textdocument": "^1.0.3", + "vscode-languageserver-types": "^3.17.0-next.5" + } + }, + "node_modules/vscode-typescript-languageservice": { + "version": "0.30.2", + "resolved": "https://registry.npmjs.org/vscode-typescript-languageservice/-/vscode-typescript-languageservice-0.30.2.tgz", + "integrity": "sha512-5l+gMfbHTZnJy7V7xdH78ai1ViR/scrIVQT5KFraDWLKTYHjGBkHDZ1E9fF+jbeyEizyy2ayldTQ7kCz8jWqVA==", + "dev": true, + "dependencies": { + "@volar/shared": "0.30.2", + "semver": "^7.3.5", + "upath": "^2.0.1", + "vscode-languageserver-protocol": "^3.17.0-next.11", + "vscode-languageserver-textdocument": "^1.0.3", + "vscode-nls": "^5.0.0" + } + }, + "node_modules/vscode-typescript-languageservice/node_modules/semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/vscode-typescript-languageservice/node_modules/upath": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz", + "integrity": "sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==", + "dev": true, + "engines": { + "node": ">=4", + "yarn": "*" + } + }, + "node_modules/vscode-uri": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.3.tgz", + "integrity": "sha512-EcswR2S8bpR7fD0YPeS7r2xXExrScVMxg4MedACaWHEtx9ftCF/qHG1xGkolzTPcEmjTavCQgbVzHUIdTMzFGA==", + "dev": true + }, + "node_modules/vscode-vue-languageservice": { + "version": "0.30.2", + "resolved": "https://registry.npmjs.org/vscode-vue-languageservice/-/vscode-vue-languageservice-0.30.2.tgz", + "integrity": "sha512-P0g92JmnVkV2zrWhDbT2zxuOUp0X2kMM9VHlrT7ALZq8wAhPOW0B4nhhb9a6jKBh6qqFBDquNeQRvTEZp4NJMA==", + "dev": true, + "dependencies": { + "@volar/code-gen": "0.30.2", + "@volar/html2pug": "0.30.2", + "@volar/shared": "0.30.2", + "@volar/source-map": "0.30.2", + "@volar/transforms": "0.30.2", + "@volar/vue-code-gen": "0.30.2", + "@vscode/emmet-helper": "^2.8.3", + "@vue/reactivity": "^3.2.26", + "@vue/shared": "^3.2.26", + "request-light": "^0.5.6", + "upath": "^2.0.1", + "vscode-css-languageservice": "^5.1.9", + "vscode-html-languageservice": "^4.2.1", + "vscode-json-languageservice": "^4.1.10", + "vscode-languageserver": "^8.0.0-next.5", + "vscode-languageserver-protocol": "^3.17.0-next.11", + "vscode-languageserver-textdocument": "^1.0.3", + "vscode-pug-languageservice": "0.30.2", + "vscode-typescript-languageservice": "0.30.2" + } + }, + "node_modules/vscode-vue-languageservice/node_modules/upath": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz", + "integrity": "sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==", + "dev": true, + "engines": { + "node": ">=4", + "yarn": "*" + } + }, "node_modules/vue": { - "version": "3.2.21", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.21.tgz", - "integrity": "sha512-jpy7ckXdyclfRzqLjL4mtq81AkzQleE54KjZsJg/9OorNVurAxdlU5XpD49GpjKdnftuffKUvx2C5jDOrgc/zg==", + "version": "3.2.26", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.26.tgz", + "integrity": "sha512-KD4lULmskL5cCsEkfhERVRIOEDrfEL9CwAsLYpzptOGjaGFNWo3BQ9g8MAb7RaIO71rmVOziZ/uEN/rHwcUIhg==", "dependencies": { - "@vue/compiler-dom": "3.2.21", - "@vue/compiler-sfc": "3.2.21", - "@vue/runtime-dom": "3.2.21", - "@vue/server-renderer": "3.2.21", - "@vue/shared": "3.2.21" + "@vue/compiler-dom": "3.2.26", + "@vue/compiler-sfc": "3.2.26", + "@vue/runtime-dom": "3.2.26", + "@vue/server-renderer": "3.2.26", + "@vue/shared": "3.2.26" } }, "node_modules/vue-contenteditable": { @@ -23518,6 +24553,22 @@ "node": ">=8" } }, + "node_modules/vue-tsc": { + "version": "0.30.2", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-0.30.2.tgz", + "integrity": "sha512-A8KIk5KwQTbSdsrDxwJkFYLPqDJ1zM86w3X8cgpi6rveozKUGDMPt300awEz61sTuBM9fAfUhNRcsWbsJ1I+TQ==", + "dev": true, + "dependencies": { + "@volar/shared": "0.30.2", + "vscode-vue-languageservice": "0.30.2" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": "*" + } + }, "node_modules/w3c-hr-time": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", @@ -24877,6 +25928,15 @@ "node": ">=8" } }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -24908,12 +25968,12 @@ "dev": true }, "@babel/code-frame": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz", - "integrity": "sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", + "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", "dev": true, "requires": { - "@babel/highlight": "^7.16.0" + "@babel/highlight": "^7.16.7" } }, "@babel/compat-data": { @@ -24946,23 +26006,23 @@ } }, "@babel/generator": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.5.tgz", - "integrity": "sha512-kIvCdjZqcdKqoDbVVdt5R99icaRtrtYhYK/xux5qiWCBmfdvEYMFZ68QCrpE5cbFM1JsuArUNs1ZkuKtTtUcZA==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.7.tgz", + "integrity": "sha512-/ST3Sg8MLGY5HVYmrjOgL60ENux/HfO/CsUh7y4MalThufhE/Ff/6EibFDHi4jiDCaWfJKoqbE6oTh21c5hrRg==", "dev": true, "requires": { - "@babel/types": "^7.16.0", + "@babel/types": "^7.16.7", "jsesc": "^2.5.1", "source-map": "^0.5.0" } }, "@babel/helper-annotate-as-pure": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.0.tgz", - "integrity": "sha512-ItmYF9vR4zA8cByDocY05o0LGUkp1zhbTQOH1NFyl5xXEqlTJQCEJjieriw+aFpxo16swMxUnUiKS7a/r4vtHg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", + "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==", "dev": true, "requires": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.16.7" } }, "@babel/helper-builder-binary-assignment-operator-visitor": { @@ -24988,18 +26048,18 @@ } }, "@babel/helper-create-class-features-plugin": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.16.5.tgz", - "integrity": "sha512-NEohnYA7mkB8L5JhU7BLwcBdU3j83IziR9aseMueWGeAjblbul3zzb8UvJ3a1zuBiqCMObzCJHFqKIQE6hTVmg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.16.7.tgz", + "integrity": "sha512-kIFozAvVfK05DM4EVQYKK+zteWvY85BFdGBRQBytRyY3y+6PX0DkDOn/CZ3lEuczCfrCxEzwt0YtP/87YPTWSw==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.16.0", - "@babel/helper-environment-visitor": "^7.16.5", - "@babel/helper-function-name": "^7.16.0", - "@babel/helper-member-expression-to-functions": "^7.16.5", - "@babel/helper-optimise-call-expression": "^7.16.0", - "@babel/helper-replace-supers": "^7.16.5", - "@babel/helper-split-export-declaration": "^7.16.0" + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-function-name": "^7.16.7", + "@babel/helper-member-expression-to-functions": "^7.16.7", + "@babel/helper-optimise-call-expression": "^7.16.7", + "@babel/helper-replace-supers": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7" } }, "@babel/helper-create-regexp-features-plugin": { @@ -25029,12 +26089,12 @@ } }, "@babel/helper-environment-visitor": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.5.tgz", - "integrity": "sha512-ODQyc5AnxmZWm/R2W7fzhamOk1ey8gSguo5SGvF0zcB3uUzRpTRmM/jmLSm9bDMyPlvbyJ+PwPEK0BWIoZ9wjg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", + "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==", "dev": true, "requires": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.16.7" } }, "@babel/helper-explode-assignable-expression": { @@ -25047,41 +26107,41 @@ } }, "@babel/helper-function-name": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.0.tgz", - "integrity": "sha512-BZh4mEk1xi2h4HFjWUXRQX5AEx4rvaZxHgax9gcjdLWdkjsY7MKt5p0otjsg5noXw+pB+clMCjw+aEVYADMjog==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", + "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.16.0", - "@babel/template": "^7.16.0", - "@babel/types": "^7.16.0" + "@babel/helper-get-function-arity": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/types": "^7.16.7" } }, "@babel/helper-get-function-arity": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.0.tgz", - "integrity": "sha512-ASCquNcywC1NkYh/z7Cgp3w31YW8aojjYIlNg4VeJiHkqyP4AzIvr4qx7pYDb4/s8YcsZWqqOSxgkvjUz1kpDQ==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", + "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", "dev": true, "requires": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.16.7" } }, "@babel/helper-hoist-variables": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.0.tgz", - "integrity": "sha512-1AZlpazjUR0EQZQv3sgRNfM9mEVWPK3M6vlalczA+EECcPz3XPh6VplbErL5UoMpChhSck5wAJHthlj1bYpcmg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", + "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", "dev": true, "requires": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.16.7" } }, "@babel/helper-member-expression-to-functions": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.5.tgz", - "integrity": "sha512-7fecSXq7ZrLE+TWshbGT+HyCLkxloWNhTbU2QM1NTI/tDqyf0oZiMcEfYtDuUDCo528EOlt39G1rftea4bRZIw==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.7.tgz", + "integrity": "sha512-VtJ/65tYiU/6AbMTDwyoXGPKHgTsfRarivm+YbB5uAzKUyuPjgZSgAFeG87FCigc7KNHu2Pegh1XIT3lXjvz3Q==", "dev": true, "requires": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.16.7" } }, "@babel/helper-module-imports": { @@ -25110,18 +26170,18 @@ } }, "@babel/helper-optimise-call-expression": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.0.tgz", - "integrity": "sha512-SuI467Gi2V8fkofm2JPnZzB/SUuXoJA5zXe/xzyPP2M04686RzFKFHPK6HDVN6JvWBIEW8tt9hPR7fXdn2Lgpw==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz", + "integrity": "sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w==", "dev": true, "requires": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.16.7" } }, "@babel/helper-plugin-utils": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.5.tgz", - "integrity": "sha512-59KHWHXxVA9K4HNF4sbHCf+eJeFe0Te/ZFGqBT4OjXhrwvA04sGfaEGsVTdsjoszq0YTP49RC9UKe5g8uN2RwQ==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", + "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", "dev": true }, "@babel/helper-remap-async-to-generator": { @@ -25136,16 +26196,16 @@ } }, "@babel/helper-replace-supers": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.5.tgz", - "integrity": "sha512-ao3seGVa/FZCMCCNDuBcqnBFSbdr8N2EW35mzojx3TwfIbdPmNK+JV6+2d5bR0Z71W5ocLnQp9en/cTF7pBJiQ==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz", + "integrity": "sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw==", "dev": true, "requires": { - "@babel/helper-environment-visitor": "^7.16.5", - "@babel/helper-member-expression-to-functions": "^7.16.5", - "@babel/helper-optimise-call-expression": "^7.16.0", - "@babel/traverse": "^7.16.5", - "@babel/types": "^7.16.0" + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-member-expression-to-functions": "^7.16.7", + "@babel/helper-optimise-call-expression": "^7.16.7", + "@babel/traverse": "^7.16.7", + "@babel/types": "^7.16.7" } }, "@babel/helper-simple-access": { @@ -25167,24 +26227,24 @@ } }, "@babel/helper-split-export-declaration": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.0.tgz", - "integrity": "sha512-0YMMRpuDFNGTHNRiiqJX19GjNXA4H0E8jZ2ibccfSxaCogbm3am5WN/2nQNj0YnQwGWM1J06GOcQ2qnh3+0paw==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", + "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", "dev": true, "requires": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.16.7" } }, "@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", + "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", "dev": true }, "@babel/helper-validator-option": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", - "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", + "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", "dev": true }, "@babel/helper-wrap-function": { @@ -25211,20 +26271,20 @@ } }, "@babel/highlight": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz", - "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.7.tgz", + "integrity": "sha512-aKpPMfLvGO3Q97V0qhw/V2SWNWlwfJknuwAunU7wZLSfrM4xTBvg7E5opUVi1kJTBKihE38CPg4nBiqX83PWYw==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.15.7", + "@babel/helper-validator-identifier": "^7.16.7", "chalk": "^2.0.0", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.16.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.6.tgz", - "integrity": "sha512-Gr86ujcNuPDnNOY8mi383Hvi8IYrJVJYuf3XcuBM/Dgd+bINn/7tHqsj+tKkoreMbmGsFLsltI/JJd8fOFWGDQ==" + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.7.tgz", + "integrity": "sha512-sR4eaSrnM7BV7QPzGfEX5paG/6wrZM3I0HDzfIAK06ESvo9oy3xBuVBxE3MbQaKNhvg8g/ixjMWo2CGpzpHsDA==" }, "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { "version": "7.16.2", @@ -25606,12 +26666,12 @@ } }, "@babel/plugin-syntax-typescript": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.0.tgz", - "integrity": "sha512-Xv6mEXqVdaqCBfJFyeab0fH2DnUoMsDmhamxsSi4j8nLd4Vtw213WMJr55xxqipC/YVWyPY3K0blJncPYji+dQ==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.7.tgz", + "integrity": "sha512-YhUIJHHGkqPgEcMYkPCKTyGUdoGKWtopIycQyjJH8OjvRgOYsXsaKehLVPScKJWAULPxMa4N1vCe6szREFlZ7A==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-transform-arrow-functions": { @@ -25977,14 +27037,14 @@ } }, "@babel/plugin-transform-typescript": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.16.1.tgz", - "integrity": "sha512-NO4XoryBng06jjw/qWEU2LhcLJr1tWkhpMam/H4eas/CDKMX/b2/Ylb6EI256Y7+FVPCawwSM1rrJNOpDiz+Lg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.16.7.tgz", + "integrity": "sha512-Hzx1lvBtOCWuCEwMmYOfpQpO7joFeXLgoPuzZZBtTxXqSqUGUubvFGZv2ygo1tB5Bp9q6PXV3H0E/kf7KM0RLA==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.16.0", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/plugin-syntax-typescript": "^7.16.0" + "@babel/helper-create-class-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-typescript": "^7.16.7" } }, "@babel/plugin-transform-unicode-escapes": { @@ -26127,14 +27187,14 @@ } }, "@babel/preset-typescript": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.16.0.tgz", - "integrity": "sha512-txegdrZYgO9DlPbv+9QOVpMnKbOtezsLHWsnsRF4AjbSIsVaujrq1qg8HK0mxQpWv0jnejt0yEoW1uWpvbrDTg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.16.7.tgz", + "integrity": "sha512-WbVEmgXdIyvzB77AQjGBEyYPZx+8tTsO50XtfozQrkW8QB2rLJpH2lgx0TRw5EJrBxOZQ+wCcyPVQvS8tjEHpQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/helper-validator-option": "^7.14.5", - "@babel/plugin-transform-typescript": "^7.16.0" + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-validator-option": "^7.16.7", + "@babel/plugin-transform-typescript": "^7.16.7" } }, "@babel/register": { @@ -26170,41 +27230,41 @@ } }, "@babel/template": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.0.tgz", - "integrity": "sha512-MnZdpFD/ZdYhXwiunMqqgyZyucaYsbL0IrjoGjaVhGilz+x8YB++kRfygSOIj1yOtWKPlx7NBp+9I1RQSgsd5A==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", + "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", "dev": true, "requires": { - "@babel/code-frame": "^7.16.0", - "@babel/parser": "^7.16.0", - "@babel/types": "^7.16.0" + "@babel/code-frame": "^7.16.7", + "@babel/parser": "^7.16.7", + "@babel/types": "^7.16.7" } }, "@babel/traverse": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.5.tgz", - "integrity": "sha512-FOCODAzqUMROikDYLYxl4nmwiLlu85rNqBML/A5hKRVXG2LV8d0iMqgPzdYTcIpjZEBB7D6UDU9vxRZiriASdQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.16.0", - "@babel/generator": "^7.16.5", - "@babel/helper-environment-visitor": "^7.16.5", - "@babel/helper-function-name": "^7.16.0", - "@babel/helper-hoist-variables": "^7.16.0", - "@babel/helper-split-export-declaration": "^7.16.0", - "@babel/parser": "^7.16.5", - "@babel/types": "^7.16.0", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.7.tgz", + "integrity": "sha512-8KWJPIb8c2VvY8AJrydh6+fVRo2ODx1wYBU2398xJVq0JomuLBZmVQzLPBblJgHIGYG4znCpUZUZ0Pt2vdmVYQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.16.7", + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-function-name": "^7.16.7", + "@babel/helper-hoist-variables": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/parser": "^7.16.7", + "@babel/types": "^7.16.7", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.0.tgz", - "integrity": "sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.7.tgz", + "integrity": "sha512-E8HuV7FO9qLpx6OtoGfUQ2cjIYnbFwvZWYBS+87EwtdMvmUPJSwykpovFB+8insbpF0uJcpr8KMUi64XZntZcg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.15.7", + "@babel/helper-validator-identifier": "^7.16.7", "to-fast-properties": "^2.0.0" } }, @@ -26230,12 +27290,51 @@ "minimist": "^1.2.0" } }, + "@cspotcode/source-map-consumer": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", + "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", + "dev": true + }, + "@cspotcode/source-map-support": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", + "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "dev": true, + "requires": { + "@cspotcode/source-map-consumer": "0.8.0" + } + }, "@discoveryjs/json-ext": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.6.tgz", "integrity": "sha512-ws57AidsDvREKrZKYffXddNkyaF14iHNHm8VQnZH6t99E8gczjNN0GpvcGny0imC80yQ0tHz1xVUKk/KFQSUyA==", "dev": true }, + "@emmetio/abbreviation": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@emmetio/abbreviation/-/abbreviation-2.2.2.tgz", + "integrity": "sha512-TtE/dBnkTCct8+LntkqVrwqQao6EnPAs1YN3cUgxOxTaBlesBCY37ROUAVZrRlG64GNnVShdl/b70RfAI3w5lw==", + "dev": true, + "requires": { + "@emmetio/scanner": "^1.0.0" + } + }, + "@emmetio/css-abbreviation": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@emmetio/css-abbreviation/-/css-abbreviation-2.1.4.tgz", + "integrity": "sha512-qk9L60Y+uRtM5CPbB0y+QNl/1XKE09mSO+AhhSauIfr2YOx/ta3NJw2d8RtCFxgzHeRqFRr8jgyzThbu+MZ4Uw==", + "dev": true, + "requires": { + "@emmetio/scanner": "^1.0.0" + } + }, + "@emmetio/scanner": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@emmetio/scanner/-/scanner-1.0.0.tgz", + "integrity": "sha512-8HqW8EVqjnCmWXVpqAOZf+EGESdkR27odcMMMGefgKXtar00SoYNSryGv//TELI4T3QFsECo78p+0lmalk/CFA==", + "dev": true + }, "@emotion/cache": { "version": "10.0.29", "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.29.tgz", @@ -26886,10 +27985,12 @@ } }, "@lana/b2c-mapp-ui-assets": { - "version": "5.5.0", - "resolved": "https://npm.pkg.github.com/download/@lana/b2c-mapp-ui-assets/5.5.0/b2db86642aa621493b8274c40e54e0a3f62a3c28ebd443f91e0d1f244bf8e8fb", - "integrity": "sha512-QcX1mH4rI1eChTIgs5LrWfDMp0+LAUt2sNo/nPdj9Rc/FqKVZ8M6zLxgtu9Mm3855B6Eokj/KNtrG6DDEeemYA==", - "requires": {} + "version": "5.6.0", + "resolved": "https://npm.pkg.github.com/download/@lana/b2c-mapp-ui-assets/5.6.0/d0780f13aecda098b37e8def4ef57b3808d9a14bc721cd63db16fd5d68635978", + "integrity": "sha512-HfjgH+xP3eg6wAcDJLXjXqRUYxHAFD6tu1pD1FJso9QAivRiC4dCDEBP02CQSdSdApk19w3Y+KdGRM8QXCaazg==", + "requires": { + "vue": "^3.2.26" + } }, "@mdx-js/loader": { "version": "1.6.22", @@ -28347,9 +29448,9 @@ } }, "@testing-library/dom": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.11.0.tgz", - "integrity": "sha512-8Ay4UDiMlB5YWy+ZvCeRyFFofs53ebxrWnOFvCoM1HpMAX4cHyuSrCuIM9l2lVuUWUt+Gr3loz/nCwdrnG6ShQ==", + "version": "8.11.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.11.1.tgz", + "integrity": "sha512-3KQDyx9r0RKYailW2MiYrSSKEfH0GTkI51UGEvJenvcoDoeRYs0PZpi2SXqtnMClQvCqdtTTpOfFETDTVADpAg==", "dev": true, "requires": { "@babel/code-frame": "^7.10.4", @@ -28489,6 +29590,30 @@ "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", "dev": true }, + "@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", + "dev": true + }, + "@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", + "dev": true + }, + "@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", + "dev": true + }, + "@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", + "dev": true + }, "@types/aria-query": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz", @@ -28496,9 +29621,9 @@ "dev": true }, "@types/babel__core": { - "version": "7.1.16", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.16.tgz", - "integrity": "sha512-EAEHtisTMM+KaKwfWdC3oyllIqswlznXCIVCt7/oRNrh+DhgT4UEBNC/jlADNjvw7UnfbcdkGQcPVZ1xYiLcrQ==", + "version": "7.1.18", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.18.tgz", + "integrity": "sha512-S7unDjm/C7z2A2R9NzfKCK1I+BAALDtxEmsJBwlB3EzNfb929ykjL++1CK9LO++EIp2fQrC8O+BwjKvz6UeDyQ==", "dev": true, "requires": { "@babel/parser": "^7.1.0", @@ -28509,9 +29634,9 @@ } }, "@types/babel__generator": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.3.tgz", - "integrity": "sha512-/GWCmzJWqV7diQW54smJZzWbSFf4QYtF71WCKhcx6Ru/tFyQIY2eiiITcCAeuPbNSvT9YCGkVMqqvSk2Z0mXiA==", + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", "dev": true, "requires": { "@babel/types": "^7.0.0" @@ -28615,6 +29740,76 @@ "@types/istanbul-lib-report": "*" } }, + "@types/jest": { + "version": "27.4.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.4.0.tgz", + "integrity": "sha512-gHl8XuC1RZ8H2j5sHv/JqsaxXkDDM9iDOgu0Wp8sjs4u/snb2PVehyWXJPr+ORA0RPpgw231mnutWI1+0hgjIQ==", + "dev": true, + "requires": { + "jest-diff": "^27.0.0", + "pretty-format": "^27.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "diff-sequences": { + "version": "27.4.0", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.4.0.tgz", + "integrity": "sha512-YqiQzkrsmHMH5uuh8OdQFU9/ZpADnwzml8z0O5HvRNda+5UZsaX/xN+AAxfR2hWq1Y7HZnAzO9J5lJXOuDz2Ww==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "jest-diff": { + "version": "27.4.2", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.4.2.tgz", + "integrity": "sha512-ujc9ToyUZDh9KcqvQDkk/gkbf6zSaeEg9AiBxtttXW59H/AcqEYp1ciXAtJp+jXWva5nAf/ePtSsgWwE5mqp4Q==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^27.4.0", + "jest-get-type": "^27.4.0", + "pretty-format": "^27.4.2" + } + }, + "jest-get-type": { + "version": "27.4.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.4.0.tgz", + "integrity": "sha512-tk9o+ld5TWq41DkK14L4wox4s2D9MtTpKaAVzXfr5CUKm5ZK2ExcaFE0qls2W71zE/6R2TxxrK9w2r6svAFDBQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "@types/json-schema": { "version": "7.0.9", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", @@ -28627,6 +29822,21 @@ "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, + "@types/lodash": { + "version": "4.14.178", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.178.tgz", + "integrity": "sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw==", + "dev": true + }, + "@types/lodash-es": { + "version": "4.17.5", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.5.tgz", + "integrity": "sha512-SHBoI8/0aoMQWAgUHMQ599VM6ZiSKg8sh/0cFqqlQQMyY9uEplc0ULU5yQNzcvdR4ZKa0ey8+vFmahuRbOCT1A==", + "dev": true, + "requires": { + "@types/lodash": "*" + } + }, "@types/mdast": { "version": "3.0.10", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz", @@ -28643,9 +29853,9 @@ "dev": true }, "@types/node": { - "version": "16.11.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.6.tgz", - "integrity": "sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w==", + "version": "17.0.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.5.tgz", + "integrity": "sha512-w3mrvNXLeDYV1GKTZorGJQivK6XLCoGwpnyJFbJVK/aTBQUxOCaa/GlFAAN3OTDFcb7h5tiFG+YXCO2By+riZw==", "dev": true }, "@types/node-fetch": { @@ -28861,6 +30071,136 @@ "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", "dev": true }, + "@typescript-eslint/eslint-plugin": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.8.1.tgz", + "integrity": "sha512-wTZ5oEKrKj/8/366qTM366zqhIKAp6NCMweoRONtfuC07OAU9nVI2GZZdqQ1qD30WAAtcPdkH+npDwtRFdp4Rw==", + "dev": true, + "requires": { + "@typescript-eslint/experimental-utils": "5.8.1", + "@typescript-eslint/scope-manager": "5.8.1", + "debug": "^4.3.2", + "functional-red-black-tree": "^1.0.1", + "ignore": "^5.1.8", + "regexpp": "^3.2.0", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + }, + "dependencies": { + "ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "@typescript-eslint/experimental-utils": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.8.1.tgz", + "integrity": "sha512-fbodVnjIDU4JpeXWRDsG5IfIjYBxEvs8EBO8W1+YVdtrc2B9ppfof5sZhVEDOtgTfFHnYQJDI8+qdqLYO4ceww==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "@typescript-eslint/scope-manager": "5.8.1", + "@typescript-eslint/types": "5.8.1", + "@typescript-eslint/typescript-estree": "5.8.1", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" + }, + "dependencies": { + "eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^2.0.0" + } + } + } + }, + "@typescript-eslint/parser": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.8.1.tgz", + "integrity": "sha512-K1giKHAjHuyB421SoXMXFHHVI4NdNY603uKw92++D3qyxSeYvC10CBJ/GE5Thpo4WTUvu1mmJI2/FFkz38F2Gw==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.8.1", + "@typescript-eslint/types": "5.8.1", + "@typescript-eslint/typescript-estree": "5.8.1", + "debug": "^4.3.2" + } + }, + "@typescript-eslint/scope-manager": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.8.1.tgz", + "integrity": "sha512-DGxJkNyYruFH3NIZc3PwrzwOQAg7vvgsHsHCILOLvUpupgkwDZdNq/cXU3BjF4LNrCsVg0qxEyWasys5AiJ85Q==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.8.1", + "@typescript-eslint/visitor-keys": "5.8.1" + } + }, + "@typescript-eslint/types": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.8.1.tgz", + "integrity": "sha512-L/FlWCCgnjKOLefdok90/pqInkomLnAcF9UAzNr+DSqMC3IffzumHTQTrINXhP1gVp9zlHiYYjvozVZDPleLcA==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.8.1.tgz", + "integrity": "sha512-26lQ8l8tTbG7ri7xEcCFT9ijU5Fk+sx/KRRyyzCv7MQ+rZZlqiDPtMKWLC8P7o+dtCnby4c+OlxuX1tp8WfafQ==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.8.1", + "@typescript-eslint/visitor-keys": "5.8.1", + "debug": "^4.3.2", + "globby": "^11.0.4", + "is-glob": "^4.0.3", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + }, + "dependencies": { + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "@typescript-eslint/visitor-keys": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.8.1.tgz", + "integrity": "sha512-SWgiWIwocK6NralrJarPZlWdr0hZnj5GXHIgfdm8hNkyKvpeQuFyLP6YjSIe9kf3YBIfU6OHSZLYkQ+smZwtNg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.8.1", + "eslint-visitor-keys": "^3.0.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.1.0.tgz", + "integrity": "sha512-yWJFpu4DtjsWKkt5GeNBBuZMlNcYVs6vRCLoCVEJrTjaSB6LC98gFipNK/erM2Heg/E8mIK+hXG/pJMLK+eRZA==", + "dev": true + } + } + }, "@vitejs/plugin-react": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-1.1.0.tgz", @@ -28884,13 +30224,145 @@ "dev": true, "requires": {} }, + "@volar/code-gen": { + "version": "0.30.2", + "resolved": "https://registry.npmjs.org/@volar/code-gen/-/code-gen-0.30.2.tgz", + "integrity": "sha512-75rlb3rw/O/HiXxrOsVJ8JhEssqtN4dJ0i6oG4kL0udr+QM0TwN0PqaEhoMRyMFV6kBPPSunBBJQ3XNAb0PtGA==", + "dev": true, + "requires": { + "@volar/shared": "0.30.2", + "@volar/source-map": "0.30.2" + } + }, + "@volar/html2pug": { + "version": "0.30.2", + "resolved": "https://registry.npmjs.org/@volar/html2pug/-/html2pug-0.30.2.tgz", + "integrity": "sha512-k/DLGoXALaQgnacP1MoJ77AwnCHlKcsQKJJug8Qdou3+yOrzYjSSEP6uwG8BS0Fv1h4d4JYmlXsxW8gJPGXSQQ==", + "dev": true, + "requires": { + "domelementtype": "^2.2.0", + "domhandler": "^4.3.0", + "htmlparser2": "^7.2.0", + "pug": "^3.0.2" + }, + "dependencies": { + "entities": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", + "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", + "dev": true + }, + "htmlparser2": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.2.0.tgz", + "integrity": "sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==", + "dev": true, + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.2", + "domutils": "^2.8.0", + "entities": "^3.0.1" + } + } + } + }, + "@volar/shared": { + "version": "0.30.2", + "resolved": "https://registry.npmjs.org/@volar/shared/-/shared-0.30.2.tgz", + "integrity": "sha512-93Q7i758WjScg4ptvDcpk66r4Paz9StVMH/M5RCsU4/9F5a1xSCUJbkbpwE0zESkVzcuBatDqk79PaZ8TZKqRg==", + "dev": true, + "requires": { + "upath": "^2.0.1", + "vscode-jsonrpc": "^8.0.0-next.4", + "vscode-uri": "^3.0.2" + }, + "dependencies": { + "upath": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz", + "integrity": "sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==", + "dev": true + } + } + }, + "@volar/source-map": { + "version": "0.30.2", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-0.30.2.tgz", + "integrity": "sha512-gwa9OiSjUOZWYutJX53m/KDU/zaF0yN3RP2B8J0aMVyT5dE/VaIfknSxPAW5QFC6AT79Ea1HKGtChuSBgqHCfA==", + "dev": true, + "requires": { + "@volar/shared": "0.30.2", + "vscode-languageserver-textdocument": "^1.0.3" + } + }, + "@volar/transforms": { + "version": "0.30.2", + "resolved": "https://registry.npmjs.org/@volar/transforms/-/transforms-0.30.2.tgz", + "integrity": "sha512-bc+55NGlBbMLHkpChqAEgsblYJxjNHiKVMbVUMi52xKa2l9gOWXNbn5WRsHfz4AR+Cq/Zm0AvVJ10ehLQsGsow==", + "dev": true, + "requires": { + "@volar/shared": "0.30.2", + "vscode-languageserver-types": "^3.17.0-next.5" + } + }, + "@volar/vue-code-gen": { + "version": "0.30.2", + "resolved": "https://registry.npmjs.org/@volar/vue-code-gen/-/vue-code-gen-0.30.2.tgz", + "integrity": "sha512-kJyVkQFhMvVQ32aDaC6h5DdXG1GJbJjeeAkCnjfCJfMmuYjM5R4QNZHDz1TI0dETgF9vP+kbAukQKNWecHK3qg==", + "dev": true, + "requires": { + "@volar/code-gen": "0.30.2", + "@volar/shared": "0.30.2", + "@volar/source-map": "0.30.2", + "@vue/compiler-core": "^3.2.26", + "@vue/compiler-dom": "^3.2.26", + "@vue/shared": "^3.2.26", + "upath": "^2.0.1" + }, + "dependencies": { + "upath": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz", + "integrity": "sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==", + "dev": true + } + } + }, + "@vscode/emmet-helper": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/@vscode/emmet-helper/-/emmet-helper-2.8.3.tgz", + "integrity": "sha512-dkTSL+BaBBS8gFgPm/GMOU+XfxaMyI+Fl1IUYxEi8Iv24RfHf9/q2eCpV2hs7sncLcoKWEbMYe5gv4Ppmp2Oxw==", + "dev": true, + "requires": { + "emmet": "^2.3.0", + "jsonc-parser": "^2.3.0", + "vscode-languageserver-textdocument": "^1.0.1", + "vscode-languageserver-types": "^3.15.1", + "vscode-nls": "^5.0.0", + "vscode-uri": "^2.1.2" + }, + "dependencies": { + "vscode-languageserver-types": { + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz", + "integrity": "sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==", + "dev": true + }, + "vscode-uri": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-2.1.2.tgz", + "integrity": "sha512-8TEXQxlldWAuIODdukIb+TR5s+9Ds40eSJrw+1iDDA9IFORPjMELarNQE3myz5XIkWWpdprmJjm1/SxMlWOC8A==", + "dev": true + } + } + }, "@vue/compiler-core": { - "version": "3.2.21", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.21.tgz", - "integrity": "sha512-NhhiQZNG71KNq1h5pMW/fAXdTF7lJRaSI7LDm2edhHXVz1ROMICo8SreUmQnSf4Fet0UPBVqJ988eF4+936iDQ==", + "version": "3.2.26", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.26.tgz", + "integrity": "sha512-N5XNBobZbaASdzY9Lga2D9Lul5vdCIOXvUMd6ThcN8zgqQhPKfCV+wfAJNNJKQkSHudnYRO2gEB+lp0iN3g2Tw==", "requires": { - "@babel/parser": "^7.15.0", - "@vue/shared": "3.2.21", + "@babel/parser": "^7.16.4", + "@vue/shared": "3.2.26", "estree-walker": "^2.0.2", "source-map": "^0.6.1" }, @@ -28903,25 +30375,25 @@ } }, "@vue/compiler-dom": { - "version": "3.2.21", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.21.tgz", - "integrity": "sha512-gsJD3DpYZSYquiA7UIPsMDSlAooYWDvHPq9VRsqzJEk2PZtFvLvHPb4aaMD8Ufd62xzYn32cnnkzsEOJhyGilA==", + "version": "3.2.26", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.26.tgz", + "integrity": "sha512-smBfaOW6mQDxcT3p9TKT6mE22vjxjJL50GFVJiI0chXYGU/xzC05QRGrW3HHVuJrmLTLx5zBhsZ2dIATERbarg==", "requires": { - "@vue/compiler-core": "3.2.21", - "@vue/shared": "3.2.21" + "@vue/compiler-core": "3.2.26", + "@vue/shared": "3.2.26" } }, "@vue/compiler-sfc": { - "version": "3.2.21", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.21.tgz", - "integrity": "sha512-+yDlUSebKpz/ovxM2vLRRx7w/gVfY767pOfYTgbIhAs+ogvIV2BsIt4fpxlThnlCNChJ+yE0ERUNoROv2kEGEQ==", - "requires": { - "@babel/parser": "^7.15.0", - "@vue/compiler-core": "3.2.21", - "@vue/compiler-dom": "3.2.21", - "@vue/compiler-ssr": "3.2.21", - "@vue/ref-transform": "3.2.21", - "@vue/shared": "3.2.21", + "version": "3.2.26", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.26.tgz", + "integrity": "sha512-ePpnfktV90UcLdsDQUh2JdiTuhV0Skv2iYXxfNMOK/F3Q+2BO0AulcVcfoksOpTJGmhhfosWfMyEaEf0UaWpIw==", + "requires": { + "@babel/parser": "^7.16.4", + "@vue/compiler-core": "3.2.26", + "@vue/compiler-dom": "3.2.26", + "@vue/compiler-ssr": "3.2.26", + "@vue/reactivity-transform": "3.2.26", + "@vue/shared": "3.2.26", "estree-walker": "^2.0.2", "magic-string": "^0.25.7", "postcss": "^8.1.10", @@ -28951,71 +30423,80 @@ } }, "@vue/compiler-ssr": { - "version": "3.2.21", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.21.tgz", - "integrity": "sha512-eU+A0iWYy+1zAo2CRIJ0zSVlv1iuGAIbNRCnllSJ31pV1lX3jypJYzGbJlSRAbB7VP6E+tYveVT1Oq8JKewa3g==", + "version": "3.2.26", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.26.tgz", + "integrity": "sha512-2mywLX0ODc4Zn8qBoA2PDCsLEZfpUGZcyoFRLSOjyGGK6wDy2/5kyDOWtf0S0UvtoyVq95OTSGIALjZ4k2q/ag==", + "requires": { + "@vue/compiler-dom": "3.2.26", + "@vue/shared": "3.2.26" + } + }, + "@vue/eslint-config-typescript": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-9.1.0.tgz", + "integrity": "sha512-j/852/ZYQ5wDvCD3HE2q4uqJwJAceer2FwoEch1nFo+zTOsPrbzbE3cuWIs3kvu5hdFsGTMYwRwjI6fqZKDMxQ==", + "dev": true, "requires": { - "@vue/compiler-dom": "3.2.21", - "@vue/shared": "3.2.21" + "vue-eslint-parser": "^8.0.0" } }, "@vue/reactivity": { - "version": "3.2.21", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.21.tgz", - "integrity": "sha512-7C57zFm/5E3SSTUhVuYj1InDwuJ+GIVQ/z+H43C9sST85gIThGXVhksl1yWTAadf8Yz4T5lSbqi5Ds8U/ueWcw==", + "version": "3.2.26", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.26.tgz", + "integrity": "sha512-h38bxCZLW6oFJVDlCcAiUKFnXI8xP8d+eO0pcDxx+7dQfSPje2AO6M9S9QO6MrxQB7fGP0DH0dYQ8ksf6hrXKQ==", "requires": { - "@vue/shared": "3.2.21" + "@vue/shared": "3.2.26" } }, - "@vue/ref-transform": { - "version": "3.2.21", - "resolved": "https://registry.npmjs.org/@vue/ref-transform/-/ref-transform-3.2.21.tgz", - "integrity": "sha512-uiEWWBsrGeun9O7dQExYWzXO3rHm/YdtFNXDVqCSoPypzOVxWxdiL+8hHeWzxMB58fVuV2sT80aUtIVyaBVZgQ==", + "@vue/reactivity-transform": { + "version": "3.2.26", + "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.26.tgz", + "integrity": "sha512-XKMyuCmzNA7nvFlYhdKwD78rcnmPb7q46uoR00zkX6yZrUmcCQ5OikiwUEVbvNhL5hBJuvbSO95jB5zkUon+eQ==", "requires": { - "@babel/parser": "^7.15.0", - "@vue/compiler-core": "3.2.21", - "@vue/shared": "3.2.21", + "@babel/parser": "^7.16.4", + "@vue/compiler-core": "3.2.26", + "@vue/shared": "3.2.26", "estree-walker": "^2.0.2", "magic-string": "^0.25.7" } }, "@vue/runtime-core": { - "version": "3.2.21", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.21.tgz", - "integrity": "sha512-7oOxKaU0D2IunOAMOOHZgJVrHg63xwng8BZx3fbgmakqEIMwHhQcp+5GV1sOg/sWW7R4UhaRDIUCukO2GRVK2Q==", + "version": "3.2.26", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.26.tgz", + "integrity": "sha512-BcYi7qZ9Nn+CJDJrHQ6Zsmxei2hDW0L6AB4vPvUQGBm2fZyC0GXd/4nVbyA2ubmuhctD5RbYY8L+5GUJszv9mQ==", "requires": { - "@vue/reactivity": "3.2.21", - "@vue/shared": "3.2.21" + "@vue/reactivity": "3.2.26", + "@vue/shared": "3.2.26" } }, "@vue/runtime-dom": { - "version": "3.2.21", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.21.tgz", - "integrity": "sha512-apBdriD6QsI4ywbllY8kjr9/0scGuStDuvLbJULPQkFPtHzntd51bP5PQTQVAEIc9kwnTozmj6x6ZdX/cwo7xA==", + "version": "3.2.26", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.26.tgz", + "integrity": "sha512-dY56UIiZI+gjc4e8JQBwAifljyexfVCkIAu/WX8snh8vSOt/gMSEGwPRcl2UpYpBYeyExV8WCbgvwWRNt9cHhQ==", "requires": { - "@vue/runtime-core": "3.2.21", - "@vue/shared": "3.2.21", + "@vue/runtime-core": "3.2.26", + "@vue/shared": "3.2.26", "csstype": "^2.6.8" } }, "@vue/server-renderer": { - "version": "3.2.21", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.21.tgz", - "integrity": "sha512-QBgYqVgI7XCSBCqGa4LduV9vpfQFdZBOodFmq5Txk5W/v1KrJ1LoOh2Q0RHiRgtoK/UR9uyvRVcYqOmwHkZNEg==", + "version": "3.2.26", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.26.tgz", + "integrity": "sha512-Jp5SggDUvvUYSBIvYEhy76t4nr1vapY/FIFloWmQzn7UxqaHrrBpbxrqPcTrSgGrcaglj0VBp22BKJNre4aA1w==", "requires": { - "@vue/compiler-ssr": "3.2.21", - "@vue/shared": "3.2.21" + "@vue/compiler-ssr": "3.2.26", + "@vue/shared": "3.2.26" } }, "@vue/shared": { - "version": "3.2.21", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.21.tgz", - "integrity": "sha512-5EQmIPK6gw4UVYUbM959B0uPsJ58+xoMESCZs3N89XyvJ9e+fX4pqEPrOGV8OroIk3SbEvJcC+eYc8BH9JQrHA==" + "version": "3.2.26", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.26.tgz", + "integrity": "sha512-vPV6Cq+NIWbH5pZu+V+2QHE9y1qfuTq49uNWw4f7FDEeZaDU2H2cx5jcUZOAKW7qTrUS4k6qZPbMy1x4N96nbA==" }, "@vue/test-utils": { - "version": "2.0.0-rc.16", - "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.0.0-rc.16.tgz", - "integrity": "sha512-TubikDVkI2LuRKRPSLv3lYpbpvvucT2DIcGqfBVpvYs4W19u0EBTJEdmfwmSuLY7H1TyAr9Stur3PI1sWWvTGQ==", + "version": "2.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.0.0-rc.18.tgz", + "integrity": "sha512-aifolXjVdsogjaLmDoZ0FU8vN+R67aWmg9OuVeED4w5Ij5GFQLrlhM19uhWe/r5xXUL4fXMk3pX5wW6FJP1NcQ==", "dev": true, "requires": {} }, @@ -29438,6 +30919,12 @@ "readable-stream": "^3.6.0" } }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -30355,6 +31842,15 @@ } } }, + "bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "requires": { + "fast-json-stable-stringify": "2.x" + } + }, "bser": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", @@ -31462,6 +32958,12 @@ "sha.js": "^2.4.8" } }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -31792,6 +33294,12 @@ } } }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, "diff-sequences": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", @@ -32057,6 +33565,16 @@ "integrity": "sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ==", "dev": true }, + "emmet": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/emmet/-/emmet-2.3.5.tgz", + "integrity": "sha512-LcWfTamJnXIdMfLvJEC5Ld3hY5/KHXgv1L1bp6I7eEvB0ZhacHZ1kX0BYovJ8FroEsreLcq7n7kZhRMsf6jkXQ==", + "dev": true, + "requires": { + "@emmetio/abbreviation": "^2.2.2", + "@emmetio/css-abbreviation": "^2.1.4" + } + }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -32560,6 +34078,29 @@ "object.entries": "^1.1.2" } }, + "eslint-config-airbnb-typescript": { + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-16.1.0.tgz", + "integrity": "sha512-W5Cq20KpEx5ZLC54bnVrC37zq2+WD956Kp/Ma3nYFRjT1v9KM63v+DPkrrmmrVqrlDKaD0ivm/qeYmyHV6qKlw==", + "dev": true, + "requires": { + "eslint-config-airbnb-base": "^15.0.0" + }, + "dependencies": { + "eslint-config-airbnb-base": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", + "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", + "dev": true, + "requires": { + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5", + "semver": "^6.3.0" + } + } + } + }, "eslint-import-resolver-node": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", @@ -32581,6 +34122,19 @@ } } }, + "eslint-import-resolver-typescript": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-2.5.0.tgz", + "integrity": "sha512-qZ6e5CFr+I7K4VVhQu3M/9xGv9/YmwsEXrsm3nimw8vWaVHRDrQRp26BgCypTxBp3vUp4o5aVEJRiy0F2DFddQ==", + "dev": true, + "requires": { + "debug": "^4.3.1", + "glob": "^7.1.7", + "is-glob": "^4.0.1", + "resolve": "^1.20.0", + "tsconfig-paths": "^3.9.0" + } + }, "eslint-module-utils": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.1.tgz", @@ -32674,9 +34228,9 @@ } }, "eslint-plugin-import": { - "version": "2.25.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.25.2.tgz", - "integrity": "sha512-qCwQr9TYfoBHOFcVGKY9C9unq05uOxxdklmBXLVvcwo68y5Hta6/GzCZEMx2zQiu0woKNEER0LE7ZgaOfBU14g==", + "version": "2.25.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.25.3.tgz", + "integrity": "sha512-RzAVbby+72IB3iOEL8clzPLzL3wpDrlwjsTBAQXgyp5SeTqqY+0bFubwuo+y/HLhNZcXV4XqTBO4LGsfyHIDXg==", "dev": true, "requires": { "array-includes": "^3.1.4", @@ -32684,9 +34238,9 @@ "debug": "^2.6.9", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.6", - "eslint-module-utils": "^2.7.0", + "eslint-module-utils": "^2.7.1", "has": "^1.0.3", - "is-core-module": "^2.7.0", + "is-core-module": "^2.8.0", "is-glob": "^4.0.3", "minimatch": "^3.0.4", "object.values": "^1.1.5", @@ -36642,6 +38196,12 @@ "minimist": "^1.2.5" } }, + "jsonc-parser": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.3.1.tgz", + "integrity": "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==", + "dev": true + }, "jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -36887,6 +38447,12 @@ } } }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, "makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -37488,12 +39054,6 @@ } } }, - "node-modules-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", - "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", - "dev": true - }, "node-notifier": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.2.tgz", @@ -38211,13 +39771,10 @@ "dev": true }, "pirates": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", - "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", - "dev": true, - "requires": { - "node-modules-regexp": "^1.0.0" - } + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.4.tgz", + "integrity": "sha512-ZIrVPH+A52Dw84R0L3/VS9Op04PuQ2SEoJL6bkshmiTic/HldyW9Tf7oH5mhJZBK7NmDx27vSMrYEXPXclpDKw==", + "dev": true }, "pkg-dir": { "version": "5.0.0", @@ -38461,21 +40018,21 @@ } }, "pretty-format": { - "version": "27.3.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.3.1.tgz", - "integrity": "sha512-DR/c+pvFc52nLimLROYjnXPtolawm+uWDxr4FjuLDLUn+ktWnSN851KoHwHzzqq6rfCOjkzN8FLgDrSub6UDuA==", + "version": "27.4.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.4.2.tgz", + "integrity": "sha512-p0wNtJ9oLuvgOQDEIZ9zQjZffK7KtyR6Si0jnXULIDwrlNF8Cuir3AZP0hHv0jmKuNN/edOnbMjnzd4uTcmWiw==", "dev": true, "requires": { - "@jest/types": "^27.2.5", + "@jest/types": "^27.4.2", "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" }, "dependencies": { "@jest/types": { - "version": "27.2.5", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.2.5.tgz", - "integrity": "sha512-nmuM4VuDtCZcY+eTpw+0nvstwReMsjPoj7ZR80/BbixulhLaiX+fbv8oeLW8WZlJMcsGQsTmMKT/iTZu1Uy/lQ==", + "version": "27.4.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.4.2.tgz", + "integrity": "sha512-j35yw0PMTPpZsUoOBiuHzr1zTYoad1cVIE0ajEjcrJONxxrko/IRGKkXx3os0Nsi4Hu3+5VmDbVfq5WhG/pWAg==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", @@ -39772,6 +41329,12 @@ "integrity": "sha1-EwBR4qzligLqy/ydRIV3pzapJzo=", "dev": true }, + "request-light": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/request-light/-/request-light-0.5.7.tgz", + "integrity": "sha512-i/wKzvcx7Er8tZnvqSxWuNO5ZGggu2UgZAqj/RyZ0si7lBTXL7kZiI/dWxzxnQjaY7s5HEy1qK21Do4Ncr6cVw==", + "dev": true + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -41794,6 +43357,41 @@ "integrity": "sha512-3IVX4nI6B5cc31/GFFE+i8ey/N2eA0CZDbo6n0yrz0zDX8ZJ8djmU1p+XRz7G3is0F3bB3pu2pAroFdAWQKU3w==", "dev": true }, + "ts-jest": { + "version": "26.5.6", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.5.6.tgz", + "integrity": "sha512-rua+rCP8DxpA8b4DQD/6X2HQS8Zy/xzViVYfEs2OQu68tkCuKLV0Md8pmX55+W24uRIyAsf/BajRfxOs+R2MKA==", + "dev": true, + "requires": { + "bs-logger": "0.x", + "buffer-from": "1.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^26.1.0", + "json5": "2.x", + "lodash": "4.x", + "make-error": "1.x", + "mkdirp": "1.x", + "semver": "7.x", + "yargs-parser": "20.x" + }, + "dependencies": { + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true + } + } + }, "ts-loader": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.3.0.tgz", @@ -41858,6 +43456,40 @@ "integrity": "sha512-vDWbsl26LIcPGmDpoVzjEP6+hvHZkBkLW7JpvwbCv/5IYPJlsbzCVXY3wsCeAxAUeTclNOUZxnLdGh3VBD/J6w==", "dev": true }, + "ts-node": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.4.0.tgz", + "integrity": "sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "0.7.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "yn": "3.1.1" + }, + "dependencies": { + "acorn": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "dev": true + }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true + } + } + }, "ts-pnp": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.2.0.tgz", @@ -41925,6 +43557,23 @@ "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", "dev": true }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, "tty-browserify": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", @@ -41977,6 +43626,12 @@ "is-typedarray": "^1.0.0" } }, + "typescript": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz", + "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==", + "dev": true + }, "uglify-js": { "version": "3.14.5", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.14.5.tgz", @@ -42677,16 +44332,214 @@ "integrity": "sha1-YU9/v42AHwu18GYfWy9XhXUOTwk=", "dev": true }, + "vscode-css-languageservice": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/vscode-css-languageservice/-/vscode-css-languageservice-5.1.9.tgz", + "integrity": "sha512-/tFOWeZBL3Oc9Zc+2MAi3rEwiXJTSZsvjB+M7nSjWLbGPUIjukUA7YzLgsBoUfR35sPJYnXWUkL56PdfIYM8GA==", + "dev": true, + "requires": { + "vscode-languageserver-textdocument": "^1.0.1", + "vscode-languageserver-types": "^3.16.0", + "vscode-nls": "^5.0.0", + "vscode-uri": "^3.0.2" + }, + "dependencies": { + "vscode-languageserver-types": { + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz", + "integrity": "sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==", + "dev": true + } + } + }, + "vscode-html-languageservice": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/vscode-html-languageservice/-/vscode-html-languageservice-4.2.1.tgz", + "integrity": "sha512-PgaToZVXJ44nFWEBuSINdDgVV6EnpC3MnXBsysR3O5TKcAfywbYeRGRy+Y4dVR7YeUgDvtb+JkJoSkaYC0mxXQ==", + "dev": true, + "requires": { + "vscode-languageserver-textdocument": "^1.0.1", + "vscode-languageserver-types": "^3.16.0", + "vscode-nls": "^5.0.0", + "vscode-uri": "^3.0.2" + }, + "dependencies": { + "vscode-languageserver-types": { + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz", + "integrity": "sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==", + "dev": true + } + } + }, + "vscode-json-languageservice": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-4.1.10.tgz", + "integrity": "sha512-IHliMEEYSY0tJjJt0ECb8ESx/nRXpoy9kN42WVQXgaqGyizFAf3jibSiezDQTrrY7f3kywXggCU+kkJEM+OLZQ==", + "dev": true, + "requires": { + "jsonc-parser": "^3.0.0", + "vscode-languageserver-textdocument": "^1.0.1", + "vscode-languageserver-types": "^3.16.0", + "vscode-nls": "^5.0.0", + "vscode-uri": "^3.0.2" + }, + "dependencies": { + "jsonc-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.0.0.tgz", + "integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==", + "dev": true + }, + "vscode-languageserver-types": { + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz", + "integrity": "sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==", + "dev": true + } + } + }, + "vscode-jsonrpc": { + "version": "8.0.0-next.4", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.0.0-next.4.tgz", + "integrity": "sha512-i+wvza5Wd0YV/t9qhnS8I+dJdhJ1fHIhRW4f262rXXM9Mgts5VZhYrRZufGcai4y99RlbZvwaZhplQ6diRXkaA==", + "dev": true + }, + "vscode-languageserver": { + "version": "8.0.0-next.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-8.0.0-next.5.tgz", + "integrity": "sha512-3E2W0eWtGKb6QAJqspOnD0thrBRRo8IGUMV5jpDNMcMKvmtkcxMwsBh0VxdvuWaZ51PiNyR4L+B+GUvkYsyFEg==", + "dev": true, + "requires": { + "vscode-languageserver-protocol": "3.17.0-next.11" + } + }, + "vscode-languageserver-protocol": { + "version": "3.17.0-next.11", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.0-next.11.tgz", + "integrity": "sha512-9FqHT7XvM6tWFsnLvRfuQA7Zh7wZZYAwA9dK85lYthA8M1aXpXEP9drXVvO/Fe03MUeJpKVf2e4/NvDaFUnttg==", + "dev": true, + "requires": { + "vscode-jsonrpc": "8.0.0-next.4", + "vscode-languageserver-types": "3.17.0-next.5" + } + }, + "vscode-languageserver-textdocument": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.3.tgz", + "integrity": "sha512-ynEGytvgTb6HVSUwPJIAZgiHQmPCx8bZ8w5um5Lz+q5DjP0Zj8wTFhQpyg8xaMvefDytw2+HH5yzqS+FhsR28A==", + "dev": true + }, + "vscode-languageserver-types": { + "version": "3.17.0-next.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.0-next.5.tgz", + "integrity": "sha512-Zcfaw8BznhlJWB09LDR0dscXyxn9+liREqJnPF4pigeUCHwKxYapYqizwuCpMHQ/oLYiAvKwU+f28hPleYu7pA==", + "dev": true + }, + "vscode-nls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-5.0.0.tgz", + "integrity": "sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA==", + "dev": true + }, + "vscode-pug-languageservice": { + "version": "0.30.2", + "resolved": "https://registry.npmjs.org/vscode-pug-languageservice/-/vscode-pug-languageservice-0.30.2.tgz", + "integrity": "sha512-YkrBodqSzNrtLaEIeMnRJAcnqCWysIiOdkzxF6XHuOc+wDvbZ1U4XgxoLvNNjQdzNQIEYKbsLW0ldq5TYphjiA==", + "dev": true, + "requires": { + "@volar/code-gen": "0.30.2", + "@volar/shared": "0.30.2", + "@volar/source-map": "0.30.2", + "@volar/transforms": "0.30.2", + "pug-lexer": "^5.0.1", + "pug-parser": "^6.0.0", + "vscode-languageserver-textdocument": "^1.0.3", + "vscode-languageserver-types": "^3.17.0-next.5" + } + }, + "vscode-typescript-languageservice": { + "version": "0.30.2", + "resolved": "https://registry.npmjs.org/vscode-typescript-languageservice/-/vscode-typescript-languageservice-0.30.2.tgz", + "integrity": "sha512-5l+gMfbHTZnJy7V7xdH78ai1ViR/scrIVQT5KFraDWLKTYHjGBkHDZ1E9fF+jbeyEizyy2ayldTQ7kCz8jWqVA==", + "dev": true, + "requires": { + "@volar/shared": "0.30.2", + "semver": "^7.3.5", + "upath": "^2.0.1", + "vscode-languageserver-protocol": "^3.17.0-next.11", + "vscode-languageserver-textdocument": "^1.0.3", + "vscode-nls": "^5.0.0" + }, + "dependencies": { + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "upath": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz", + "integrity": "sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==", + "dev": true + } + } + }, + "vscode-uri": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.3.tgz", + "integrity": "sha512-EcswR2S8bpR7fD0YPeS7r2xXExrScVMxg4MedACaWHEtx9ftCF/qHG1xGkolzTPcEmjTavCQgbVzHUIdTMzFGA==", + "dev": true + }, + "vscode-vue-languageservice": { + "version": "0.30.2", + "resolved": "https://registry.npmjs.org/vscode-vue-languageservice/-/vscode-vue-languageservice-0.30.2.tgz", + "integrity": "sha512-P0g92JmnVkV2zrWhDbT2zxuOUp0X2kMM9VHlrT7ALZq8wAhPOW0B4nhhb9a6jKBh6qqFBDquNeQRvTEZp4NJMA==", + "dev": true, + "requires": { + "@volar/code-gen": "0.30.2", + "@volar/html2pug": "0.30.2", + "@volar/shared": "0.30.2", + "@volar/source-map": "0.30.2", + "@volar/transforms": "0.30.2", + "@volar/vue-code-gen": "0.30.2", + "@vscode/emmet-helper": "^2.8.3", + "@vue/reactivity": "^3.2.26", + "@vue/shared": "^3.2.26", + "request-light": "^0.5.6", + "upath": "^2.0.1", + "vscode-css-languageservice": "^5.1.9", + "vscode-html-languageservice": "^4.2.1", + "vscode-json-languageservice": "^4.1.10", + "vscode-languageserver": "^8.0.0-next.5", + "vscode-languageserver-protocol": "^3.17.0-next.11", + "vscode-languageserver-textdocument": "^1.0.3", + "vscode-pug-languageservice": "0.30.2", + "vscode-typescript-languageservice": "0.30.2" + }, + "dependencies": { + "upath": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz", + "integrity": "sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==", + "dev": true + } + } + }, "vue": { - "version": "3.2.21", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.21.tgz", - "integrity": "sha512-jpy7ckXdyclfRzqLjL4mtq81AkzQleE54KjZsJg/9OorNVurAxdlU5XpD49GpjKdnftuffKUvx2C5jDOrgc/zg==", + "version": "3.2.26", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.26.tgz", + "integrity": "sha512-KD4lULmskL5cCsEkfhERVRIOEDrfEL9CwAsLYpzptOGjaGFNWo3BQ9g8MAb7RaIO71rmVOziZ/uEN/rHwcUIhg==", "requires": { - "@vue/compiler-dom": "3.2.21", - "@vue/compiler-sfc": "3.2.21", - "@vue/runtime-dom": "3.2.21", - "@vue/server-renderer": "3.2.21", - "@vue/shared": "3.2.21" + "@vue/compiler-dom": "3.2.26", + "@vue/compiler-sfc": "3.2.26", + "@vue/runtime-dom": "3.2.26", + "@vue/server-renderer": "3.2.26", + "@vue/shared": "3.2.26" } }, "vue-contenteditable": { @@ -42925,6 +44778,16 @@ } } }, + "vue-tsc": { + "version": "0.30.2", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-0.30.2.tgz", + "integrity": "sha512-A8KIk5KwQTbSdsrDxwJkFYLPqDJ1zM86w3X8cgpi6rveozKUGDMPt300awEz61sTuBM9fAfUhNRcsWbsJ1I+TQ==", + "dev": true, + "requires": { + "@volar/shared": "0.30.2", + "vscode-vue-languageservice": "0.30.2" + } + }, "w3c-hr-time": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", @@ -44054,6 +45917,12 @@ "decamelize": "^1.2.0" } }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + }, "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index ecfe675..f8ce33e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lana/b2c-mapp-ui", - "version": "9.4.2", + "version": "9.5.0", "description": "Shared custom libraries for building µapps.", "bugs": { "url": "https://github.com/lana/b2c-mapp-ui/issues" @@ -16,12 +16,13 @@ }, "scripts": { "serve": "vite", + "prebuild": "vue-tsc", "build": "vite build", - "postbuild": "rm ./dist/index.html", - "analyze": "vite build --config analyze.config.js", + "postbuild": "vue-tsc -P tsconfig.dts.json && rm ./dist/index.html", + "analyze": "vite build --config analyze.config.ts", "prepare": "npm run build", - "lint": "eslint --ext .js,.vue src", - "lint:fix": "eslint --ext .js,.vue src --fix", + "lint": "eslint --ext .ts,.vue src", + "lint:fix": "eslint --ext .ts,.vue src --fix", "libphonenumber-metadata": "libphonenumber-metadata-generator data/libphonenumber-metadata.min.json --countries MX,CL,BR,ES --types mobile", "predeploy-storybook": "npm run build-storybook", "deploy-storybook": "storybook-to-ghpages", @@ -36,6 +37,7 @@ ], "main": "./dist/b2c-mapp-ui.umd.js", "module": "./dist/b2c-mapp-ui.es.js", + "types": "./dist/src/library.d.ts", "style": "./dist/style.css", "keywords": [ "ui", @@ -46,7 +48,7 @@ "dist/*" ], "dependencies": { - "@lana/b2c-mapp-ui-assets": "^5.5.0", + "@lana/b2c-mapp-ui-assets": "^5.6.0", "libphonenumber-js": "^1.9.42", "lodash-es": "^4.17.21", "vue": "^3.2.21", @@ -57,6 +59,7 @@ "vue": "^3.2.21" }, "devDependencies": { + "@babel/preset-typescript": "^7.16.7", "@getify/eslint-plugin-proper-ternary": "^3.1.1", "@storybook/addon-actions": "^6.4.9", "@storybook/addon-controls": "^6.4.9", @@ -65,23 +68,35 @@ "@storybook/addon-links": "^6.4.9", "@storybook/storybook-deployer": "^2.8.10", "@storybook/vue3": "^6.4.9", - "@testing-library/dom": "^8.11.0", + "@testing-library/dom": "^8.11.1", "@testing-library/vue": "^6.4.2", + "@types/jest": "^27.4.0", + "@types/lodash-es": "^4.17.5", + "@types/node": "^17.0.2", + "@typescript-eslint/eslint-plugin": "^5.8.0", + "@typescript-eslint/parser": "^5.8.0", "@vitejs/plugin-vue": "^1.9.4", - "@vue/test-utils": "^2.0.0-rc.16", + "@vue/eslint-config-typescript": "^9.1.0", + "@vue/test-utils": "^2.0.0-rc.18", "babel-jest": "^26.6.3", "eslint": "^7.5.0", "eslint-config-airbnb-base": "^14.2.1", + "eslint-config-airbnb-typescript": "^16.1.0", + "eslint-import-resolver-typescript": "^2.5.0", "eslint-plugin-fp": "^2.3.0", - "eslint-plugin-import": "^2.23.4", + "eslint-plugin-import": "^2.25.3", "eslint-plugin-vue": "^8.1.1", "jest": "^26.6.3", "libphonenumber-metadata-generator": "^1.0.1", "rollup-plugin-visualizer": "^5.5.2", "sass": "^1.43.4", "storybook-builder-vite": "^0.1.11", + "ts-jest": "^26.5.6", + "ts-node": "^10.4.0", + "typescript": "^4.5.4", "vite": "^2.6.13", "vite-plugin-eslint": "^1.3.0", - "vue-jest": "^5.0.0-alpha.10" + "vue-jest": "^5.0.0-alpha.10", + "vue-tsc": "^0.30.2" } } diff --git a/src/App.js b/src/App.js deleted file mode 100644 index e27c89f..0000000 --- a/src/App.js +++ /dev/null @@ -1,3 +0,0 @@ -const App = {}; - -export default App; diff --git a/src/App.ts b/src/App.ts new file mode 100644 index 0000000..b12752b --- /dev/null +++ b/src/App.ts @@ -0,0 +1,5 @@ +import { defineComponent } from 'vue'; + +const App = defineComponent({}); + +export default App; diff --git a/src/App.vue b/src/App.vue index 2223cdf..3dfc1a2 100644 --- a/src/App.vue +++ b/src/App.vue @@ -5,6 +5,6 @@ - + + diff --git a/src/components/ActionItem/ActionItem.js b/src/components/ActionItem/ActionItem.js deleted file mode 100644 index da31110..0000000 --- a/src/components/ActionItem/ActionItem.js +++ /dev/null @@ -1,69 +0,0 @@ -import { Comment } from 'vue'; - -import TextParagraph from '../TextParagraph/TextParagraph.vue'; - -const errorStatuses = [ - 'rejected', - 'expired', -]; - -const components = { - TextParagraph, -}; - -const props = { - title: { - type: String, - default: '', - }, - description: { - type: String, - default: '', - }, - status: { - type: String, - default: '', - }, - highlight: Boolean, - dataTestId: { - type: String, - default: 'action-item', - }, -}; - -const emits = ['click']; - -const computed = { - hasDefaultSlot() { - const result = this.$slots.default && this.$slots.default().findIndex((node) => (node.type !== Comment)) !== -1; - return result; - }, - hasErrorStatus() { - if (!this.status) { return; } - const result = errorStatuses.includes(this.status.toLowerCase()); - return result; - }, - descriptionTextColor() { - if (!this.hasErrorStatus) { return; } - return 'red-500'; - }, -}; - -const methods = { - onClick(event) { - this.$emit('click', event); - }, -}; - -const name = 'ActionItem'; - -const ActionItem = { - name, - components, - computed, - props, - emits, - methods, -}; - -export default ActionItem; diff --git a/src/components/ActionItem/ActionItem.stories.js b/src/components/ActionItem/ActionItem.stories.ts similarity index 93% rename from src/components/ActionItem/ActionItem.stories.js rename to src/components/ActionItem/ActionItem.stories.ts index 325fc73..1c3618c 100644 --- a/src/components/ActionItem/ActionItem.stories.js +++ b/src/components/ActionItem/ActionItem.stories.ts @@ -1,4 +1,5 @@ import { action } from '@storybook/addon-actions'; +import type { Meta, StoryFn } from '@storybook/vue3'; import { DocumentFilledIcon } from '@lana/b2c-mapp-ui-assets'; import ActionItem from './ActionItem.vue'; @@ -17,6 +18,7 @@ const ActionItemStories = { description: '', status: '', default: 'Default slot', + dataTestId: 'action-item', highlight: false, }, argTypes: { @@ -24,6 +26,7 @@ const ActionItemStories = { title: { control: { type: 'text' } }, description: { control: { type: 'text' } }, status: { control: { type: 'text' } }, + dataTestId: { control: { type: 'text' } }, highlight: { control: 'boolean' }, default: { control: { @@ -36,11 +39,18 @@ const ActionItemStories = { }, }, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample : StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), - setup() { return { ...args }; }, + setup() { + return { ...args }; + }, + computed: { + defaultSlot() { + return this.default; + }, + }, components: { ActionItem, RenderString, @@ -48,11 +58,6 @@ const defaultExample = (args, { argTypes }) => ({ methods: { onClick: action('Clicked!'), }, - computed: { - defaultSlot() { - return this.default; - }, - }, template: `
    ({ +const fullExample : StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/ActionItem/ActionItem.test.js b/src/components/ActionItem/ActionItem.test.ts similarity index 93% rename from src/components/ActionItem/ActionItem.test.js rename to src/components/ActionItem/ActionItem.test.ts index cedd427..b4e98e9 100644 --- a/src/components/ActionItem/ActionItem.test.js +++ b/src/components/ActionItem/ActionItem.test.ts @@ -1,7 +1,6 @@ import { render, fireEvent } from '@testing-library/vue'; import ActionItem from './ActionItem.vue'; -import { silenceDeprecationErrorsAndInnerComponentWarnings } from '../../lib/testUtils'; describe('ActionItem unit test', () => { const defaultClassname = 'CLASSNAME'; @@ -13,7 +12,6 @@ describe('ActionItem unit test', () => { }; beforeAll(() => { - silenceDeprecationErrorsAndInnerComponentWarnings(jest); }); it('Should apply given className class', () => { diff --git a/src/components/ActionItem/ActionItem.ts b/src/components/ActionItem/ActionItem.ts new file mode 100644 index 0000000..b832908 --- /dev/null +++ b/src/components/ActionItem/ActionItem.ts @@ -0,0 +1,57 @@ +import { defineComponent, Comment } from 'vue'; + +import TextParagraph from '../TextParagraph/TextParagraph.vue'; + +const errorStatuses = [ + 'rejected', + 'expired', +]; + +const ActionItem = defineComponent({ + name: 'ActionItem', + components: { + TextParagraph, + }, + props: { + title: { + type: String, + default: '', + }, + description: { + type: String, + default: '', + }, + status: { + type: String, + default: '', + }, + highlight: Boolean, + dataTestId: { + type: String, + default: 'action-item', + }, + }, + emits: ['click'], + computed: { + hasDefaultSlot() { + const result = this.$slots.default && this.$slots.default().findIndex((node) => (node.type !== Comment)) !== -1; + return result; + }, + hasErrorStatus() { + if (!this.status) { return; } + const result = errorStatuses.includes(this.status.toLowerCase()); + return result; + }, + descriptionTextColor() { + if (!this.hasErrorStatus) { return; } + return 'red-500'; + }, + }, + methods: { + onClick(event: Event) { + this.$emit('click', event); + }, + }, +}); + +export default ActionItem; diff --git a/src/components/ActionItem/ActionItem.vue b/src/components/ActionItem/ActionItem.vue index 7e2532f..70ba0e8 100644 --- a/src/components/ActionItem/ActionItem.vue +++ b/src/components/ActionItem/ActionItem.vue @@ -23,5 +23,5 @@ - + diff --git a/src/components/AmountInput/AmountInput.js b/src/components/AmountInput/AmountInput.js deleted file mode 100644 index 93d7a52..0000000 --- a/src/components/AmountInput/AmountInput.js +++ /dev/null @@ -1,202 +0,0 @@ -import { nextTick } from 'vue'; -import ContentEditable from 'vue-contenteditable'; - -import CurrencyInput from '../CurrencyInput/CurrencyInput.vue'; -import { escapeRegExp } from '../../lib/regexHelper'; - -const components = { - ContentEditable, - CurrencyInput, -}; - -const props = { - dataTestId: { - type: String, - default: 'amount-input', - }, - modelValue: { - type: [String, Number], - default: 0, - }, - currency: { - type: [String], - default: 'MXN', - }, - locale: { - type: [String], - default: 'es-MX', - }, - id: { - type: String, - default: 'amount-input-id', - }, - name: String, - errorLabel: String, - readonly: Boolean, - disabled: Boolean, - startFocused: Boolean, -}; - -const data = function () { - return { - isFocused: false, - inputValue: `${this.modelValue || '0'}`, - formattedValue: `${this.modelValue || '0'}`, - currencyValue: Number(this.modelValue || 0), - }; -}; - -const emits = ['update:modelValue', 'update:formattedValue', 'focus', 'blur', 'keypress', 'keyup', 'paste']; - -const computed = { - inputId() { - const result = (this.id || this.name); - return result; - }, - currencyParts() { - const parts = new Intl.NumberFormat(this.locale, { style: 'currency', currency: this.currency }).formatToParts(10001.55555555555); - const result = parts.reduce((accumulator, { type, value }) => ({ ...accumulator, [type]: value }), {}); - return result; - }, - currencySymbol() { - const { currency: result } = this.currencyParts; - return result; - }, - currencyGroup() { - const { group: result } = this.currencyParts; - return result; - }, - currencyPrecision() { - const { fraction = '' } = this.currencyParts; - const result = fraction.length; - return result; - }, - currencyOptions() { - const result = { - currency: this.currency, - locale: this.locale, - currencyDisplay: 'hidden', - distractionFree: false, - allowNegative: false, - }; - return result; - }, - truncatedInputValue() { - if (!this.inputValue) { return '0'; } - const symbolRegex = new RegExp(escapeRegExp(this.currencySymbol)); - const groupRegex = new RegExp(escapeRegExp(this.currencyGroup), 'g'); - const result = this.inputValue.replace(symbolRegex, '').replace(groupRegex, ''); - return result; - }, - numericInputValue() { - if (!this.inputValue) { return '0'; } - const result = Number(this.truncatedInputValue) || 0; - return result; - }, -}; - -const methods = { - toggleFocus(focus) { - this.isFocused = focus; - }, - focusIfNeeded() { - if (!this.$refs.content || !this.startFocused) { return; } - this.toggleFocus(true); - this.$refs.content.$el.focus(); - }, - blur() { - if (!this.$refs.input) { return; } - this.$refs.input.blur(); - }, - emitUpdateModelValueEvent() { - this.$emit('update:modelValue', this.currencyValue); - }, - emitFormattedEvent() { - this.$emit('update:formattedValue', this.formattedValue); - }, - onFocus(event) { - this.toggleFocus(true); - this.$emit('focus', event); - }, - onBlur(event) { - this.toggleFocus(false); - this.$emit('blur', event); - }, - onKeypress(event) { - const { keyCode } = event; - if ((keyCode > 31 && (keyCode < 48 || keyCode > 57)) && keyCode !== 46) { event.preventDefault(); return; } - this.$emit('keypress', event); - }, - onKeyup(event) { - this.$emit('keyup', event); - }, - onPaste(event) { - this.$emit('paste', event); - }, - async fitTextContainer() { - let inputFontSize = 48; - this.$refs.content.$el.style.fontSize = null; - await nextTick(); - let inputWidth = this.$refs.content.$el.offsetWidth; - const containerWidth = this.$refs.content.$el.parentElement.offsetWidth; - while (inputWidth > containerWidth) { - this.$refs.content.$el.style.fontSize = `${--inputFontSize}px`; - inputWidth = this.$refs.content.$el.offsetWidth; - if (inputFontSize < 16) { break; } - } - }, -}; - -const watch = { - inputValue() { - this.fitTextContainer(); - if (!this.isFocused) { return; } - this.currencyValue = this.numericInputValue; - }, - currencyValue() { - this.emitUpdateModelValueEvent(); - if (this.isFocused) { return; } - this.inputValue = `${this.currencyValue}`; - }, - formattedValue() { - this.emitFormattedEvent(); - }, - modelValue() { - if (!this.modelValue) { return; } - this.currencyValue = this.modelValue; - if (this.isFocused) { return; } - this.inputValue = this.formattedValue; - }, - isFocused() { - if (this.disabled || this.readonly) { return; } - this.currencyValue = this.numericInputValue; - if (!this.isFocused) { - if (!this.numericInputValue) { - this.currencyValue = 0; - } - this.inputValue = `${this.formattedValue}`; - return; - } - this.inputValue = `${this.numericInputValue}`; - }, -}; - -const mounted = function () { - this.focusIfNeeded(); -}; - -const name = 'AmountInput'; - -const AmountInput = { - name, - components, - props, - emits, - computed, - data, - methods, - watch, - mounted, -}; - -export default AmountInput; diff --git a/src/components/AmountInput/AmountInput.stories.js b/src/components/AmountInput/AmountInput.stories.ts similarity index 93% rename from src/components/AmountInput/AmountInput.stories.js rename to src/components/AmountInput/AmountInput.stories.ts index 0a138e1..5a1cc80 100644 --- a/src/components/AmountInput/AmountInput.stories.js +++ b/src/components/AmountInput/AmountInput.stories.ts @@ -1,3 +1,5 @@ +import type { Meta, StoryFn } from '@storybook/vue3'; + import { createDeviceDecorator } from '../../lib/storybookHelpers'; import AmountInput from './AmountInput.vue'; @@ -31,9 +33,9 @@ const AmountInputStories = { readonly: { name: 'Is read only?', control: 'boolean' }, startFocused: { name: 'Start focused?', control: 'boolean' }, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/AmountInput/AmountInput.test.js b/src/components/AmountInput/AmountInput.test.ts similarity index 90% rename from src/components/AmountInput/AmountInput.test.js rename to src/components/AmountInput/AmountInput.test.ts index 37704c4..8cec6c9 100644 --- a/src/components/AmountInput/AmountInput.test.js +++ b/src/components/AmountInput/AmountInput.test.ts @@ -13,7 +13,7 @@ describe('AmountInput unit test', () => { await wrapper.vm.$nextTick(); wrapper.vm.$options.watch.currencyValue.call(wrapper.vm, newValue); await wrapper.vm.$nextTick(); - expect(wrapper.vm.$data.inputValue).toBe(newValue); + expect(wrapper.vm.inputValue).toBe(newValue); }); it('Should emit input event when value changed', async () => { @@ -47,7 +47,8 @@ describe('AmountInput unit test', () => { await wrapper.vm.$nextTick(); wrapper.find('input').setValue(givenValue); await wrapper.vm.$nextTick(); - const inputEventValue = wrapper.emitted('update:modelValue')[0][0]; + const emittedModelValue = wrapper.emitted('update:modelValue') || [] as string[]; + const [inputEventValue] = emittedModelValue[0] as string[]; expect(inputEventValue).toBe(givenValue); }); @@ -57,7 +58,7 @@ describe('AmountInput unit test', () => { await wrapper.vm.$nextTick(); wrapper.find('input').setValue(givenValue); await wrapper.vm.$nextTick(); - const inputEventValue = wrapper.emitted('update:modelValue')[0][0]; + const [inputEventValue] = (wrapper.emitted('update:modelValue') || [])[0] as string[]; expect(inputEventValue).toBe(givenValue); }); @@ -67,7 +68,7 @@ describe('AmountInput unit test', () => { await wrapper.vm.$nextTick(); await wrapper.find('input').setValue(givenValue); const expectedValue = new Intl.NumberFormat('es-CL', { currency: 'CLP' }).format(10000); - const formattedValueEventValue = wrapper.emitted('update:formattedValue')[0][0]; + const [formattedValueEventValue] = (wrapper.emitted('update:formattedValue') || [])[0] as string[]; expect(formattedValueEventValue).toBe(expectedValue); }); diff --git a/src/components/AmountInput/AmountInput.ts b/src/components/AmountInput/AmountInput.ts new file mode 100644 index 0000000..4ea9003 --- /dev/null +++ b/src/components/AmountInput/AmountInput.ts @@ -0,0 +1,198 @@ +import { defineComponent, nextTick, ref } from 'vue'; +import ContentEditable from 'vue-contenteditable'; + +import CurrencyInput from '../CurrencyInput/CurrencyInput.vue'; +import { escapeRegExp } from '../../lib/regexHelper'; + +const AmountInput = defineComponent({ + name: 'AmountInput', + components: { + ContentEditable, + CurrencyInput, + }, + props: { + dataTestId: { + type: String, + default: 'amount-input', + }, + modelValue: { + type: [String, Number], + default: 0, + }, + currency: { + type: [String], + default: 'MXN', + }, + locale: { + type: [String], + default: 'es-MX', + }, + id: { + type: String, + default: 'amount-input-id', + }, + name: { + type: String, + default: 'amount-input', + }, + errorLabel: { + type: String, + default: '', + }, + readonly: Boolean, + disabled: Boolean, + startFocused: Boolean, + }, + emits: ['update:modelValue', 'update:formattedValue', 'focus', 'blur', 'keypress', 'keyup', 'paste'], + computed: { + inputId() { + const result = (this.id || this.name); + return result; + }, + currencyParts(): { currency: string, group: string, fraction: string } { + const parts = new Intl.NumberFormat(this.locale, { style: 'currency', currency: this.currency }).formatToParts(10001.55555555555); + const result = parts.reduce((accumulator, { type, value }) => ({ ...accumulator, [type]: value }), { currency: '', group: '', fraction: '' }); + return result; + }, + currencySymbol() { + const { currency: result } = this.currencyParts; + return result; + }, + currencyGroup() { + const { group: result } = this.currencyParts; + return result; + }, + currencyPrecision() { + const { fraction = '' } = this.currencyParts; + const result = fraction.length; + return result; + }, + currencyOptions() { + const result = { + currency: this.currency, + locale: this.locale, + currencyDisplay: 'hidden', + distractionFree: false, + allowNegative: false, + }; + return result; + }, + truncatedInputValue() { + if (!this.inputValue) { return '0'; } + const symbolRegex = new RegExp(escapeRegExp(this.currencySymbol)); + const groupRegex = new RegExp(escapeRegExp(this.currencyGroup), 'g'); + const result = this.inputValue.replace(symbolRegex, '').replace(groupRegex, ''); + return result; + }, + numericInputValue() { + if (!this.inputValue) { return 0; } + const result = Number(this.truncatedInputValue) || 0; + return result; + }, + }, + methods: { + toggleFocus(focus: boolean) { + this.isFocused = focus; + }, + focusIfNeeded() { + if (!this.$refs.content || !this.startFocused) { return; } + this.toggleFocus(true); + this.content.$el.focus(); + }, + blur() { + if (!this.content) { return; } + this.content.blur(); + }, + emitUpdateModelValueEvent() { + this.$emit('update:modelValue', this.currencyValue); + }, + emitFormattedEvent() { + this.$emit('update:formattedValue', this.formattedValue); + }, + onFocus(event: Event) { + this.toggleFocus(true); + this.$emit('focus', event); + }, + onBlur(event: Event) { + this.toggleFocus(false); + this.$emit('blur', event); + }, + onKeypress(event: KeyboardEvent) { + const { keyCode } = event; + if ((keyCode > 31 && (keyCode < 48 || keyCode > 57)) && keyCode !== 46) { event.preventDefault(); return; } + this.$emit('keypress', event); + }, + onKeyup(event: Event) { + this.$emit('keyup', event); + }, + onPaste(event: Event) { + this.$emit('paste', event); + }, + async fitTextContainer() { + let inputFontSize = 48; + this.content.$el.style.fontSize = null; + await nextTick(); + let inputWidth = this.content.$el.offsetWidth; + const containerWidth = this.content.$el.parentElement.offsetWidth; + while (inputWidth > containerWidth) { + this.content.$el.style.fontSize = `${--inputFontSize}px`; + inputWidth = this.content.$el.offsetWidth; + if (inputFontSize < 16) { break; } + } + }, + }, + setup(props) { + const content = ref(); + const inputRef = ref(); + const isFocused = ref(false); + const inputValue = ref(`${props.modelValue || '0'}`); + const formattedValue = ref(`${props.modelValue || '0'}`); + const currencyValue = ref(Number(props.modelValue || 0)); + return { + content, + inputRef, + isFocused, + inputValue, + formattedValue, + currencyValue, + }; + }, + watch: { + inputValue() { + this.fitTextContainer(); + if (!this.isFocused) { return; } + this.currencyValue = this.numericInputValue; + }, + currencyValue() { + this.emitUpdateModelValueEvent(); + if (this.isFocused) { return; } + this.inputValue = `${this.currencyValue}`; + }, + formattedValue() { + this.emitFormattedEvent(); + }, + modelValue() { + if (!this.modelValue) { return; } + this.currencyValue = Number(this.modelValue); + if (this.isFocused) { return; } + this.inputValue = this.formattedValue; + }, + isFocused() { + if (this.disabled || this.readonly) { return; } + this.currencyValue = this.numericInputValue; + if (!this.isFocused) { + if (!this.numericInputValue) { + this.currencyValue = 0; + } + this.inputValue = `${this.formattedValue}`; + return; + } + this.inputValue = `${this.numericInputValue}`; + }, + }, + mounted() { + this.focusIfNeeded(); + }, +}); + +export default AmountInput; diff --git a/src/components/AmountInput/AmountInput.vue b/src/components/AmountInput/AmountInput.vue index 8ff9cf0..4f6e09f 100644 --- a/src/components/AmountInput/AmountInput.vue +++ b/src/components/AmountInput/AmountInput.vue @@ -34,5 +34,5 @@ - + diff --git a/src/components/BankAccountNumberInputField/BankAccountNumberInputField.js b/src/components/BankAccountNumberInputField/BankAccountNumberInputField.js deleted file mode 100644 index 120329e..0000000 --- a/src/components/BankAccountNumberInputField/BankAccountNumberInputField.js +++ /dev/null @@ -1,158 +0,0 @@ -import FormField from '../FormField/FormField.vue'; -import { allSpacesRegexp, nonDigitsRegexp } from '../../lib/regexHelper'; -import { bankAccountNumberTemplateLookup, bankAccountNumberFormatter, validateBankAccountNumber } from '../../lib/bankAccountNumberValidator'; - -const defaultCountryCode = 'MX'; - -const components = { - FormField, -}; - -const props = { - dataTestId: { - type: String, - default: 'bank-account-field', - }, - modelValue: { - type: String, - default: '', - }, - errorLabel: { - type: String, - default: 'Invalid account number', - }, - label: String, - countryCode: String, - name: String, - disabled: Boolean, - readonly: Boolean, - startFocused: Boolean, - autoformat: Boolean, - showLengthHint: Boolean, - lengthHint: Number, - lengthHintLabel: String, - helpText: String, - hideClearButton: Boolean, - inputmode: String, - pattern: String, -}; - -const emits = ['update:modelValue', 'change', 'focus', 'blur']; - -const data = function () { - return { - inputValue: this.modelValue, - }; -}; - -const computed = { - countryCodeToUse() { - const result = (this.countryCode || defaultCountryCode); - return result; - }, - bankAccountTemplate() { - const result = bankAccountNumberTemplateLookup[this.countryCodeToUse]; - return result; - }, - accountNumberWithoutSpaces() { - if (!this.inputValue) { return ''; } - const result = this.inputValue.replace(allSpacesRegexp, ''); - return result; - }, - parsedAccountNumber() { - const payload = { - accountNumber: this.accountNumberWithoutSpaces, - template: this.bankAccountTemplate, - }; - const result = bankAccountNumberFormatter(payload); - return result; - }, - validation() { - if (!this.inputValue) { return; } - const templateLength = this.bankAccountTemplate.replace(allSpacesRegexp, '').length; - if (this.accountNumberWithoutSpaces.length > templateLength) { return; } - const payload = { - accountNumber: this.accountNumberWithoutSpaces, - countryCode: this.countryCodeToUse, - }; - const result = validateBankAccountNumber(payload); - return result; - }, - isValidBankAccount() { - const result = (this.validation && this.validation.isValid); - return result; - }, - errorLabelToShow() { - if (this.isValidBankAccount || !this.inputValue) { return ''; } - return this.errorLabel; - }, - maxLength() { - const result = this.bankAccountTemplate.length; - return result; - }, - lengthHintToUse() { - if (!this.showLengthHint) { return; } - const result = (this.lengthHint || this.maxLength); - return result; - }, -}; - -const methods = { - emitUpdateModelValueEvent() { - const payload = { - value: this.inputValue, - validation: this.validation, - }; - this.$emit('update:modelValue', this.inputValue); - this.$emit('change', payload); - }, - focus() { - this.$refs.field.focus(); - }, - onFocus(event) { - this.$emit('focus', event); - }, - onBlur(event) { - this.$emit('blur', event); - }, - onPaste(event) { - const rawPasteValue = (event.clipboardData || window.clipboardData).getData('text'); - const sanitizedValue = (rawPasteValue && rawPasteValue.replace(nonDigitsRegexp, '')); - this.inputValue = sanitizedValue; - event.preventDefault(); - }, - updateInputValueWithFormatting() { - if (!this.autoformat || (this.accountNumberWithoutSpaces.length > this.bankAccountTemplate.length)) { return; } - this.inputValue = this.parsedAccountNumber; - }, -}; - -const watch = { - inputValue() { - this.emitUpdateModelValueEvent(); - this.updateInputValueWithFormatting(); - }, - modelValue() { - this.inputValue = this.modelValue; - }, -}; - -const mounted = function () { - this.updateInputValueWithFormatting(); -}; - -const name = 'BankAccountNumberInputField'; - -const BankAccountNumberInputField = { - name, - components, - props, - emits, - data, - computed, - methods, - watch, - mounted, -}; - -export default BankAccountNumberInputField; diff --git a/src/components/BankAccountNumberInputField/BankAccountNumberInputField.stories.js b/src/components/BankAccountNumberInputField/BankAccountNumberInputField.stories.ts similarity index 96% rename from src/components/BankAccountNumberInputField/BankAccountNumberInputField.stories.js rename to src/components/BankAccountNumberInputField/BankAccountNumberInputField.stories.ts index db721ad..8622964 100644 --- a/src/components/BankAccountNumberInputField/BankAccountNumberInputField.stories.js +++ b/src/components/BankAccountNumberInputField/BankAccountNumberInputField.stories.ts @@ -1,4 +1,5 @@ import { action } from '@storybook/addon-actions'; +import type { Meta, StoryFn } from '@storybook/vue3'; import BankAccountNumberInputField from './BankAccountNumberInputField.vue'; import { createOptionalDeviceDecorator } from '../../lib/storybookHelpers'; @@ -41,9 +42,9 @@ const BankAccountNumberInputFieldStories = { inputmode: { control: 'text', name: 'Inputmode' }, pattern: { control: 'text', name: 'Pattern' }, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { @@ -111,7 +112,7 @@ defaultExample.parameters = { }, }; -const examples = (args, { argTypes }) => ({ +const examples: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/BankAccountNumberInputField/BankAccountNumberInputField.test.js b/src/components/BankAccountNumberInputField/BankAccountNumberInputField.test.ts similarity index 69% rename from src/components/BankAccountNumberInputField/BankAccountNumberInputField.test.js rename to src/components/BankAccountNumberInputField/BankAccountNumberInputField.test.ts index ef62d8f..d71d120 100644 --- a/src/components/BankAccountNumberInputField/BankAccountNumberInputField.test.js +++ b/src/components/BankAccountNumberInputField/BankAccountNumberInputField.test.ts @@ -1,12 +1,12 @@ import { render, fireEvent } from '@testing-library/vue'; -import { mount } from '@vue/test-utils'; +import { flushPromises, mount } from '@vue/test-utils'; import BankAccountNumberInputField from './BankAccountNumberInputField.vue'; -import { silenceDeprecationErrorsAndInnerComponentWarnings } from '../../lib/testUtils'; +import { silenceInnerComponentWarnings } from '../../lib/testUtils'; describe('BankAccountNumberInputField unit test:', () => { beforeAll(() => { - silenceDeprecationErrorsAndInnerComponentWarnings(jest); + silenceInnerComponentWarnings(jest); }); const defaultProps = { @@ -51,45 +51,39 @@ describe('BankAccountNumberInputField unit test:', () => { expect(isBlurEmitted).toBeTruthy(); }); - it('Should apply the correct format when autoformat is enabled, for the given modelValue: ', () => new Promise((resolve) => { + it('Should apply the correct format when autoformat is enabled, for the given modelValue: ', async () => { const wrapper = mount(BankAccountNumberInputField, { props: { ...defaultProps, autoformat: true, modelValue: '138211000000000127' } }); - setTimeout(() => { - const input = wrapper.find('input'); - expect(input.element.value).toEqual('138 211 00000000012 7'); - resolve(); - }); - })); + await flushPromises(); + const input = wrapper.find('input'); + expect(input.element.value).toEqual('138 211 00000000012 7'); + }); - it('Should apply the correct format when autoformat is disabled, for the given modelValue: ', () => new Promise((resolve) => { + it('Should apply the correct format when autoformat is disabled, for the given modelValue: ', async () => { const inputValue = '138211000000000127'; const wrapper = mount(BankAccountNumberInputField, { props: { ...defaultProps, modelValue: inputValue } }); - setTimeout(() => { - const input = wrapper.find('input'); - expect(input.element.value).toEqual(inputValue); - resolve(); - }); - })); + await flushPromises(); + const input = wrapper.find('input'); + expect(input.element.value).toEqual(inputValue); + }); - it('Should take given input modelValue: ', () => new Promise((resolve) => { + it('Should take given input modelValue: ', async () => { const newValue = '13821'; - const wrapper = mount(BankAccountNumberInputField, { props: { ...defaultProps, modelValue: '138211000000000127' } }); + const givenValue = '138211000000000127'; + const wrapper = mount(BankAccountNumberInputField, { props: { ...defaultProps, modelValue: givenValue } }); const input = wrapper.find('input'); - input.setValue('13821'); - input.trigger('input'); - wrapper.vm.$options.watch.modelValue.call(wrapper.vm, newValue); - setTimeout(() => { - expect(wrapper.vm.$data.inputValue).toEqual(newValue); - resolve(); - }); - })); + await input.setValue('13821'); + const emittedModelValue = wrapper.emitted('update:modelValue')?.[0] as string[]; + const [inputEventValue] = emittedModelValue as string[]; + expect(inputEventValue).toEqual(newValue); + }); it('Should not allow value with a length greater than its max-length: ', async () => { const newValue = '138 211 00000000012 79'; - const initialValue = '138211000000000127'; + const expectedValue = '138 211 00000000012 7'; const wrapper = mount(BankAccountNumberInputField, { props: { ...defaultProps, modelValue: '138211000000000127' } }); - wrapper.vm.$options.watch.inputValue.call(wrapper.vm, newValue); - await wrapper.vm.$nextTick(); - expect(wrapper.vm.$data.inputValue).toEqual(initialValue); + const input = wrapper.find('input'); + await input.setValue(newValue); + expect(wrapper.vm.inputValue).toEqual(expectedValue); }); it('Should allow showing the length hint value independently of the max-length: ', async () => { @@ -105,19 +99,16 @@ describe('BankAccountNumberInputField unit test:', () => { }); const lengthHint = wrapper.find('.help-text'); await wrapper.vm.$nextTick(); - const hasLengthHint = lengthHint.element.textContent.includes('10 foo'); + const hasLengthHint = lengthHint.element.textContent?.includes('10 foo'); expect(hasLengthHint).toBeTruthy(); - expect(wrapper.vm.$data.inputValue).toEqual(newValue); + expect(wrapper.vm.inputValue).toEqual(newValue); }); - it('Should apply right format even if max-length of the field is not reached', () => new Promise((resolve) => { + it('Should apply right format even if max-length of the field is not reached', async () => { const wrapper = mount(BankAccountNumberInputField, { props: { ...defaultProps, autoformat: true, modelValue: '138211000000000127' } }); const input = wrapper.find('input'); - input.setValue('13821'); - input.trigger('change'); - setTimeout(() => { - expect(input.element.value).toEqual('138 21'); - resolve(); - }); - })); + await input.setValue('13821'); + await input.trigger('change'); + expect(input.element.value).toEqual('138 21'); + }); }); diff --git a/src/components/BankAccountNumberInputField/BankAccountNumberInputField.ts b/src/components/BankAccountNumberInputField/BankAccountNumberInputField.ts new file mode 100644 index 0000000..92b7d79 --- /dev/null +++ b/src/components/BankAccountNumberInputField/BankAccountNumberInputField.ts @@ -0,0 +1,171 @@ +import type { PropType } from 'vue'; +import { defineComponent, ref } from 'vue'; + +import FormField from '../FormField/FormField.vue'; +import { allSpacesRegexp, nonDigitsRegexp } from '../../lib/regexHelper'; +import type { BankAccountNumberTemplate } from '../../lib/bankAccountNumberValidator'; +import { bankAccountNumberTemplateLookup, bankAccountNumberFormatter, validateBankAccountNumber } from '../../lib/bankAccountNumberValidator'; + +const defaultCountryCode = 'MX'; + +const BankAccountNumberInputField = defineComponent({ + name: 'BankAccountNumberInputField', + components: { + FormField, + }, + props: { + dataTestId: { + type: String, + default: 'bank-account-field', + }, + modelValue: { + type: String, + default: '', + }, + errorLabel: { + type: String, + default: 'Invalid account number', + }, + label: { + type: String, + default: '', + }, + countryCode: { + type: String as PropType, + default: 'MX', + validator: (countryCode: BankAccountNumberTemplate) => Object.keys(bankAccountNumberTemplateLookup).includes(countryCode), + }, + name: { + type: String, + default: '', + }, + disabled: Boolean, + readonly: Boolean, + startFocused: Boolean, + autoformat: Boolean, + showLengthHint: Boolean, + lengthHint: { + type: Number, + default: null, + }, + lengthHintLabel: { + type: String, + default: '', + }, + helpText: { + type: String, + default: '', + }, + hideClearButton: Boolean, + inputmode: { + type: String, + default: '', + }, + pattern: { + type: String, + default: '', + }, + }, + emits: ['update:modelValue', 'change', 'focus', 'blur'], + computed: { + countryCodeToUse() { + const result = (this.countryCode || defaultCountryCode); + return result; + }, + bankAccountTemplate() { + const result = bankAccountNumberTemplateLookup[this.countryCodeToUse] || ''; + return result; + }, + accountNumberWithoutSpaces() { + if (!this.inputValue) { return ''; } + const result = this.inputValue.replace(allSpacesRegexp, ''); + return result; + }, + parsedAccountNumber() { + const payload = { + accountNumber: this.accountNumberWithoutSpaces, + template: this.bankAccountTemplate, + }; + const result = bankAccountNumberFormatter(payload); + return result; + }, + validation() { + if (!this.inputValue) { return; } + const templateLength = this.bankAccountTemplate.replace(allSpacesRegexp, '').length; + if (this.accountNumberWithoutSpaces.length > templateLength) { return; } + const payload = { + accountNumber: this.accountNumberWithoutSpaces, + countryCode: this.countryCodeToUse, + }; + const result = validateBankAccountNumber(payload); + return result; + }, + isValidBankAccount() { + const result = (this.validation && this.validation.isValid); + return result; + }, + errorLabelToShow() { + if (this.isValidBankAccount || !this.inputValue) { return ''; } + return this.errorLabel; + }, + maxLength() { + const result = this.bankAccountTemplate.length; + return result; + }, + lengthHintToUse() { + if (!this.showLengthHint) { return; } + const result = (this.lengthHint || this.maxLength); + return result; + }, + }, + methods: { + emitUpdateModelValueEvent() { + const payload = { + value: this.inputValue, + validation: this.validation, + }; + this.$emit('update:modelValue', this.inputValue); + this.$emit('change', payload); + }, + focus() { + if (!this.field) { return; } + this.field.focus(); + }, + onFocus(event: Event) { + this.$emit('focus', event); + }, + onBlur(event: Event) { + this.$emit('blur', event); + }, + onPaste(event: ClipboardEvent) { + const rawPasteValue = event.clipboardData?.getData('text'); + const sanitizedValue = rawPasteValue?.replace(nonDigitsRegexp, '') || ''; + this.inputValue = sanitizedValue; + event.preventDefault(); + }, + updateInputValueWithFormatting() { + if (!this.autoformat || (this.accountNumberWithoutSpaces.length > this.bankAccountTemplate.length)) { return; } + this.inputValue = this.parsedAccountNumber; + }, + }, + watch: { + inputValue() { + this.inputValue = this.inputValue.substring(0, this.maxLength); + this.emitUpdateModelValueEvent(); + this.updateInputValueWithFormatting(); + }, + modelValue() { + this.inputValue = this.modelValue; + }, + }, + setup(props) { + const field = ref(); + const inputValue = ref(props.modelValue); + return { field, inputValue }; + }, + mounted() { + this.updateInputValueWithFormatting(); + }, +}); + +export default BankAccountNumberInputField; diff --git a/src/components/BankAccountNumberInputField/BankAccountNumberInputField.vue b/src/components/BankAccountNumberInputField/BankAccountNumberInputField.vue index 8d3aa29..3c8542d 100644 --- a/src/components/BankAccountNumberInputField/BankAccountNumberInputField.vue +++ b/src/components/BankAccountNumberInputField/BankAccountNumberInputField.vue @@ -22,4 +22,4 @@ /> - diff --git a/src/components/BoxContentItem/BoxContentItem.js b/src/components/BoxContentItem/BoxContentItem.js deleted file mode 100644 index d3bf37e..0000000 --- a/src/components/BoxContentItem/BoxContentItem.js +++ /dev/null @@ -1,75 +0,0 @@ -import Heading from '../Heading/Heading.vue'; -import TextParagraph from '../TextParagraph/TextParagraph.vue'; - -const components = { - Heading, - TextParagraph, -}; - -const props = { - dataTestId: { - type: String, - default: 'content-item', - }, - title: { - type: String, - default: '', - }, - metaText: { - type: String, - default: '', - }, - success: { - type: Boolean, - default: false, - }, - disabled: Boolean, -}; - -const emits = ['click']; - -const data = function () { - return { - isPressed: false, - }; -}; - -const computed = { - hasDefaultSlot() { - const result = this.$slots.default && this.$slots.default().findIndex((node) => (node.type !== Comment)) !== -1; - return result; - }, - hasIcon() { - const result = (this.success); - return result; - }, - hasMetaText() { - const result = (this.metaText || this.$slots.customMetaText()); - return result; - }, -}; - -const methods = { - toggleIsPressed() { - this.isPressed = !this.isPressed; - }, - emitClickEvent(event) { - if (this.disabled || this.success) { return; } - this.isPressed = false; - this.$emit('click', event); - }, -}; - -const name = 'BoxContentItem'; - -const BoxContentItem = { - name, - components, - props, - emits, - data, - computed, - methods, -}; - -export default BoxContentItem; diff --git a/src/components/BoxContentItem/BoxContentItem.stories.js b/src/components/BoxContentItem/BoxContentItem.stories.ts similarity index 95% rename from src/components/BoxContentItem/BoxContentItem.stories.js rename to src/components/BoxContentItem/BoxContentItem.stories.ts index baff036..a7d9bc6 100644 --- a/src/components/BoxContentItem/BoxContentItem.stories.js +++ b/src/components/BoxContentItem/BoxContentItem.stories.ts @@ -1,5 +1,6 @@ import { action } from '@storybook/addon-actions'; import { DocumentFilledIcon, PaymentsIcon, ColorWalletIcon, DiscountIcon } from '@lana/b2c-mapp-ui-assets'; +import type { Meta, StoryFn } from '@storybook/vue3'; import BoxContentItem from './BoxContentItem.vue'; import RenderString from '../../lib/renderString'; @@ -31,9 +32,9 @@ const BoxContentItemStories = { customTitle: { control: { type: 'text' }, table: { type: { summary: null } } }, customMetaText: { control: { type: 'text' }, table: { type: { summary: null } } }, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { @@ -89,7 +90,7 @@ defaultExample.parameters = { }, }; -const withImage = (args, { argTypes }) => ({ +const withImage: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { @@ -139,7 +140,7 @@ withImage.parameters = { }, }; -const withIcon = (args, { argTypes }) => ({ +const withIcon: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { @@ -190,7 +191,7 @@ withIcon.parameters = { }, }; -const successState = (args, { argTypes }) => ({ +const successState: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { @@ -244,7 +245,7 @@ successState.parameters = { }, }; -const listExample = (args, { argTypes }) => ({ +const listExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/BoxContentItem/BoxContentItem.test.ts b/src/components/BoxContentItem/BoxContentItem.test.ts new file mode 100644 index 0000000..b73fe05 --- /dev/null +++ b/src/components/BoxContentItem/BoxContentItem.test.ts @@ -0,0 +1,91 @@ +import { render, fireEvent } from '@testing-library/vue'; +import { mount } from '@vue/test-utils'; + +import BoxContentItem from './BoxContentItem.vue'; + +describe('BoxContentItem unit test', () => { + const defaultProps = { + dataTestId: 'box-content-item', + title: 'TITLE', + metaText: 'META', + disabled: false, + }; + + it('Should NOT show box-content-item-media-icon if media content its not given', () => { + const { queryAllByTestId } = render(BoxContentItem, { props: { ...defaultProps } }); + const mediaIconNotVisible = queryAllByTestId('box-content-item-media-icon').length === 0; + expect(mediaIconNotVisible).toBeTruthy(); + }); + + it('Should show given media content', () => { + const { getByTestId } = render(BoxContentItem, { slots: { default: "" }, props: { ...defaultProps } }); + const mediaIconVisible = getByTestId('box-content-item-media-icon'); + expect(mediaIconVisible).toBeTruthy(); + }); + + it('Should show given meta info', () => { + const { getByTestId } = render(BoxContentItem, { props: { ...defaultProps } }); + const metaInfoExist = getByTestId('box-content-item-meta-text').textContent?.includes('META'); + expect(metaInfoExist).toBeTruthy(); + }); + + it('Should NOT show meta information if is not given', () => { + const { queryAllByTestId } = render(BoxContentItem, { props: { ...defaultProps, metaText: null } }); + const metaInfoNotExist = queryAllByTestId('box-content-item-meta-text').length === 0; + expect(metaInfoNotExist).toBeTruthy(); + }); + + it('Should emit click event when box-content-item is clicked', () => { + const { getByTestId, emitted } = render(BoxContentItem, { props: { ...defaultProps } }); + const li = getByTestId('box-content-item'); + fireEvent.click(li); + const clicked = emitted().click; + expect(clicked).toBeTruthy(); + }); + + it('Should NOT emit click event when box-content-item is clicked and its disabled', () => { + const { getByTestId, emitted } = render(BoxContentItem, { props: { ...defaultProps, disabled: true } }); + const li = getByTestId('box-content-item'); + fireEvent.click(li); + const clicked = emitted().click; + expect(clicked).not.toBeTruthy(); + }); + + it('Should show success state if success prop is provided', () => { + const wrapper = mount(BoxContentItem, { props: { ...defaultProps, success: true } }); + const successStateIsApplied = wrapper.find('[data-testid="box-content-item"]').classes().includes('success'); + expect(successStateIsApplied).toBeTruthy(); + }); + + it('Should NOT emit click event when box-content-item is clicked and it has success state', () => { + const { getByTestId, emitted } = render(BoxContentItem, { props: { ...defaultProps, success: true } }); + const li = getByTestId('box-content-item'); + fireEvent.click(li); + const clicked = emitted().click; + expect(clicked).not.toBeTruthy(); + }); + + it('Should show custom title', () => { + const { getByTestId } = render( + BoxContentItem, + { + slots: { customTitle: 'Bold text' }, + props: { ...defaultProps }, + }, + ); + const customTitleVisible = getByTestId('custom-title'); + expect(customTitleVisible).toBeTruthy(); + }); + + it('Should show custom metaText', () => { + const { getByTestId } = render( + BoxContentItem, + { + slots: { customMetaText: 'Text
    newline
    ' }, + props: { ...defaultProps }, + }, + ); + const customMetaTextVisible = getByTestId('custom-meta-text'); + expect(customMetaTextVisible).toBeTruthy(); + }); +}); diff --git a/src/components/BoxContentItem/BoxContentItem.ts b/src/components/BoxContentItem/BoxContentItem.ts new file mode 100644 index 0000000..11d0c97 --- /dev/null +++ b/src/components/BoxContentItem/BoxContentItem.ts @@ -0,0 +1,59 @@ +import { defineComponent } from 'vue'; + +import Heading from '../Heading/Heading.vue'; +import TextParagraph from '../TextParagraph/TextParagraph.vue'; + +const BoxContentItem = defineComponent({ + name: 'BoxContentItem', + components: { + Heading, + TextParagraph, + }, + props: { + dataTestId: { + type: String, + default: 'content-item', + }, + title: { + type: String, + default: '', + }, + metaText: { + type: String, + default: '', + }, + success: { + type: Boolean, + default: false, + }, + disabled: Boolean, + }, + emits: ['click'], + data() { + return { + isPressed: false, + }; + }, + computed: { + hasDefaultSlot() { + const result = this.$slots.default && this.$slots.default().findIndex((node) => (node.type !== Comment)) !== -1; + return result; + }, + hasMetaText() { + const result = (this.metaText || (this.$slots.customMetaText && this.$slots.customMetaText().findIndex((node) => (node.type !== Comment)) !== -1)); + return result; + }, + }, + methods: { + toggleIsPressed() { + this.isPressed = !this.isPressed; + }, + emitClickEvent(event: Event) { + if (this.disabled || this.success) { return; } + this.isPressed = false; + this.$emit('click', event); + }, + }, +}); + +export default BoxContentItem; diff --git a/src/components/BoxContentItem/BoxContentItem.vue b/src/components/BoxContentItem/BoxContentItem.vue index 4d8d85e..0fe273c 100644 --- a/src/components/BoxContentItem/BoxContentItem.vue +++ b/src/components/BoxContentItem/BoxContentItem.vue @@ -31,5 +31,5 @@ - + diff --git a/src/components/Button/Button.js b/src/components/Button/Button.js deleted file mode 100644 index 3baecc8..0000000 --- a/src/components/Button/Button.js +++ /dev/null @@ -1,104 +0,0 @@ -import { MopIcon } from '@lana/b2c-mapp-ui-assets'; -import { debounce } from 'lodash-es'; - -const debounceSettings = { leading: true, trailing: false }; - -const components = { - MopIcon, -}; - -const availableTypes = [ // TODO: Consider removing this list entirely (in favor of new `secondary` prop) if we don't plan on adding any other types in the near future - 'secondary', -]; - -const props = { - type: { - type: String, - default: '', - validator(value) { return (!value || availableTypes.includes(value)); }, - }, - loadingText: { - type: String, - default: 'Cargando...', - }, - href: String, - link: Boolean, - loading: Boolean, - dataTestId: String, - disabled: Boolean, - dropShadow: Boolean, - id: String, - debounce: { - type: Boolean, - default: false, - }, - debounceDelay: { - type: Number, - default: 400, - }, -}; - -const emits = ['click']; - -const data = function () { - return { - isPressed: false, - }; -}; - -const computed = { - componentType() { - const result = (this.isLinkButton) ? 'a' : 'button'; - return result; - }, - buttonTypeAttribute() { - const result = (this.isLinkButton) ? '' : 'button'; - return result; - }, - baseDataTestIdToUse() { - const defaultDataTestId = (this.href) ? 'button-link' : 'button'; - const result = (this.dataTestId || defaultDataTestId); - return result; - }, - dataTestIdToUse() { - const result = (this.isLinkButton) ? this.baseDataTestIdToUse : `${this.baseDataTestIdToUse}-button`; - return result; - }, - isLinkButton() { - const result = (!!this.href || this.link); - return result; - }, - clickMethod() { - const result = (this.debounce) ? debounce(this.onClick, this.debounceDelay, debounceSettings) : this.onClick; - return result; - }, -}; - -const methods = { - setIsPressed(isPressed) { - this.isPressed = isPressed; - }, - onClick(event) { - if (this.disabled || this.loading) { return; } - if (this.isPressed) { this.isPressed = false; } - this.$emit('click', event); - }, -}; - -const name = 'UiButton'; - -const Button = { - name, - components, - props, - emits, - data, - computed, - methods, -}; - -export { - availableTypes, -}; - -export default Button; diff --git a/src/components/Button/Button.stories.js b/src/components/Button/Button.stories.ts similarity index 95% rename from src/components/Button/Button.stories.js rename to src/components/Button/Button.stories.ts index 5954bb5..e155ab5 100644 --- a/src/components/Button/Button.stories.js +++ b/src/components/Button/Button.stories.ts @@ -1,4 +1,5 @@ import { action } from '@storybook/addon-actions'; +import type { Meta, StoryFn } from '@storybook/vue3'; import Button from './Button.vue'; import { availableTypes } from './Button'; @@ -45,9 +46,9 @@ const ButtonStories = { }, }, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { @@ -100,7 +101,7 @@ defaultExample.parameters = { }, }; -const types = (args, { argTypes }) => ({ +const types: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/Button/Button.test.js b/src/components/Button/Button.test.ts similarity index 99% rename from src/components/Button/Button.test.js rename to src/components/Button/Button.test.ts index bc8cb7c..d0d45d7 100644 --- a/src/components/Button/Button.test.js +++ b/src/components/Button/Button.test.ts @@ -58,7 +58,7 @@ describe('Button unit test', () => { it('Should NOT render an anchor if href is not provided', () => { const { queryAllByTestId } = render(Button, { props: { ...defaultButtonProps } }); - const itsNotALink = !queryAllByTestId('button-link').length > 0; + const itsNotALink = !(queryAllByTestId('button-link').length > 0); expect(itsNotALink).toBeTruthy(); }); diff --git a/src/components/Button/Button.ts b/src/components/Button/Button.ts new file mode 100644 index 0000000..169762b --- /dev/null +++ b/src/components/Button/Button.ts @@ -0,0 +1,101 @@ +import { defineComponent, ref } from 'vue'; +import { MopIcon } from '@lana/b2c-mapp-ui-assets'; +import { debounce } from 'lodash-es'; + +const debounceSettings = { leading: true, trailing: false }; + +const availableTypes = [ // TODO: Consider removing this list entirely (in favor of new `secondary` prop) if we don't plan on adding any other types in the near future + 'secondary', +]; + +const Button = defineComponent({ + name: 'UiButton', + components: { + MopIcon, + }, + props: { + type: { + type: String, + default: '', + validator(value: string) { return (!value || availableTypes.includes(value)); }, + }, + loadingText: { + type: String, + default: 'Cargando...', + }, + href: { + type: String, + default: '', + }, + link: Boolean, + loading: Boolean, + dataTestId: { + type: String, + default: '', + }, + disabled: Boolean, + dropShadow: Boolean, + id: { + type: String, + default: '', + }, + debounce: { + type: Boolean, + default: false, + }, + debounceDelay: { + type: Number, + default: 400, + }, + }, + emits: ['click'], + computed: { + componentType() { + const result = (this.isLinkButton) ? 'a' : 'button'; + return result; + }, + buttonTypeAttribute() { + const result = (this.isLinkButton) ? '' : 'button'; + return result; + }, + baseDataTestIdToUse() { + const defaultDataTestId = (this.href) ? 'button-link' : 'button'; + const result = (this.dataTestId || defaultDataTestId); + return result; + }, + dataTestIdToUse() { + const result = (this.isLinkButton) ? this.baseDataTestIdToUse : `${this.baseDataTestIdToUse}-button`; + return result; + }, + isLinkButton() { + const result = (!!this.href || this.link); + return result; + }, + clickMethod() { + const result = (this.debounce) ? debounce(this.onClick, this.debounceDelay, debounceSettings) : this.onClick; + return result; + }, + }, + methods: { + setIsPressed(isPressed: boolean) { + this.isPressed = isPressed; + }, + onClick(event: Event) { + if (this.disabled || this.loading) { return; } + if (this.isPressed) { this.isPressed = false; } + this.$emit('click', event); + }, + }, + setup() { + const isPressed = ref(false); + return { + isPressed, + }; + }, +}); + +export { + availableTypes, +}; + +export default Button; diff --git a/src/components/Button/Button.vue b/src/components/Button/Button.vue index 85c4737..3112986 100644 --- a/src/components/Button/Button.vue +++ b/src/components/Button/Button.vue @@ -32,5 +32,5 @@ - + diff --git a/src/components/Button/UnitTestWrappers/ButtonTestWrapper.vue b/src/components/Button/UnitTestWrappers/ButtonTestWrapper.vue index 6852824..b7e57e8 100644 --- a/src/components/Button/UnitTestWrappers/ButtonTestWrapper.vue +++ b/src/components/Button/UnitTestWrappers/ButtonTestWrapper.vue @@ -5,9 +5,11 @@ diff --git a/src/components/CallToActionScreen/CallToActionScreen.js b/src/components/CallToActionScreen/CallToActionScreen.js deleted file mode 100644 index 4858056..0000000 --- a/src/components/CallToActionScreen/CallToActionScreen.js +++ /dev/null @@ -1,58 +0,0 @@ -import WrappedButton from '../WrappedButton/WrappedButton.vue'; -import Heading from '../Heading/Heading.vue'; -import Wrapper from '../Wrapper/Wrapper.vue'; -import Screen from '../Screen/Screen.vue'; -import TextParagraph from '../TextParagraph/TextParagraph.vue'; -import ScrollWrapper from '../ScrollWrapper/ScrollWrapper.vue'; - -const components = { - WrappedButton, - Heading, - Wrapper, - Screen, - TextParagraph, - ScrollWrapper, -}; - -const emits = ['click']; - -const props = { - dataTestId: { - type: String, - default: 'call-to-action', - }, - title: { - type: String, - default: '', - }, - description: { - type: String, - default: '', - }, - buttonText: { - type: String, - default: '', - }, - debounce: { - type: Boolean, - default: false, - }, -}; - -const methods = { - onClick(event) { - this.$emit('click', event); - }, -}; - -const name = 'CallToActionScreen'; - -const CallToActionScreen = { - name, - components, - emits, - props, - methods, -}; - -export default CallToActionScreen; diff --git a/src/components/CallToActionScreen/CallToActionScreen.stories.js b/src/components/CallToActionScreen/CallToActionScreen.stories.ts similarity index 92% rename from src/components/CallToActionScreen/CallToActionScreen.stories.js rename to src/components/CallToActionScreen/CallToActionScreen.stories.ts index 731cd8c..3a2af65 100644 --- a/src/components/CallToActionScreen/CallToActionScreen.stories.js +++ b/src/components/CallToActionScreen/CallToActionScreen.stories.ts @@ -1,4 +1,5 @@ import { action } from '@storybook/addon-actions'; +import type { Meta, StoryFn } from '@storybook/vue3'; import CallToActionScreen from './CallToActionScreen.vue'; import { createDeviceDecorator } from '../../lib/storybookHelpers'; @@ -44,9 +45,9 @@ const CallToActionScreenStories = { }, }, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/CallToActionScreen/CallToActionScreen.test.js b/src/components/CallToActionScreen/CallToActionScreen.test.ts similarity index 100% rename from src/components/CallToActionScreen/CallToActionScreen.test.js rename to src/components/CallToActionScreen/CallToActionScreen.test.ts diff --git a/src/components/CallToActionScreen/CallToActionScreen.ts b/src/components/CallToActionScreen/CallToActionScreen.ts new file mode 100644 index 0000000..1778650 --- /dev/null +++ b/src/components/CallToActionScreen/CallToActionScreen.ts @@ -0,0 +1,50 @@ +import { defineComponent } from 'vue'; + +import WrappedButton from '../WrappedButton/WrappedButton.vue'; +import Heading from '../Heading/Heading.vue'; +import Wrapper from '../Wrapper/Wrapper.vue'; +import Screen from '../Screen/Screen.vue'; +import TextParagraph from '../TextParagraph/TextParagraph.vue'; +import ScrollWrapper from '../ScrollWrapper/ScrollWrapper.vue'; + +const CallToActionScreen = defineComponent({ + name: 'CallToActionScreen', + components: { + WrappedButton, + Heading, + Wrapper, + Screen, + TextParagraph, + ScrollWrapper, + }, + emits: ['click'], + props: { + dataTestId: { + type: String, + default: 'call-to-action', + }, + title: { + type: String, + default: '', + }, + description: { + type: String, + default: '', + }, + buttonText: { + type: String, + default: '', + }, + debounce: { + type: Boolean, + default: false, + }, + }, + methods: { + onClick(event: Event) { + this.$emit('click', event); + }, + }, +}); + +export default CallToActionScreen; diff --git a/src/components/CallToActionScreen/CallToActionScreen.vue b/src/components/CallToActionScreen/CallToActionScreen.vue index ad1007a..8ed3183 100644 --- a/src/components/CallToActionScreen/CallToActionScreen.vue +++ b/src/components/CallToActionScreen/CallToActionScreen.vue @@ -18,5 +18,5 @@ - + diff --git a/src/components/Carousel/Carousel.js b/src/components/Carousel/Carousel.js deleted file mode 100644 index f8a5ad7..0000000 --- a/src/components/Carousel/Carousel.js +++ /dev/null @@ -1,196 +0,0 @@ -import { ref, nextTick } from 'vue'; -import { ChevronLeftIcon, ChevronRightIcon } from '@lana/b2c-mapp-ui-assets'; -import { debounce } from 'lodash-es'; - -const components = { - ChevronLeftIcon, - ChevronRightIcon, -}; - -const props = { - hideArrows: { - type: Boolean, - default: false, - }, - arrowIcons: { - type: Boolean, - default: false, - }, - hideNavigation: { - type: Boolean, - default: false, - }, - modelValue: { - type: Number, - default: 0, - }, - dataTestId: { - type: String, - default: 'carousel', - }, - scrollDebounce: { - type: Number, - default: 200, - }, -}; - -const emits = ['update:modelValue']; - -const data = function () { - return { - items: [], - itemsCount: 0, - currentIndex: this.modelValue, - initialX: 0, - scrollLeft: 0, - destinationScrollLeft: null, - isScrolling: false, - resizeObserver: null, - }; -}; - -const computed = { - isPreviousAvailable() { - if (!this.items) { return false; } - const result = (this.currentIndex - 1 >= 0); - return result; - }, - isNextAvailable() { - if (!this.items) { return false; } - const result = (this.currentIndex + 1 < this.items.length); - return result; - }, -}; - -const methods = { - getItems() { - const items = this.$slots.default().reduce((accumulator, node) => { - if (!node.componentInstance || !node.componentInstance.$refs) { return accumulator; } - if (node.componentInstance.$refs.carouselItem) { - accumulator.push(node.componentInstance.$refs.carouselItem); - } - return accumulator; - }, []); - const result = (items.length) ? items : [...this.carousel.children]; - return result; - }, - async setItems() { - await nextTick(); - if (!this.carousel) { return; } - this.items = this.getItems(); - this.updateScroll(this.currentIndex); - }, - updateScroll(index) { - if (!this.items[index] || !this.carousel) { return; } - this.carousel.style.scrollBehavior = 'auto'; - this.carousel.scrollTo({ - left: this.items[index].offsetLeft, - }); - this.carousel.style.scrollBehavior = ''; - }, - changeRenderedItem(direction) { - this.setCurrentIndex(this.currentIndex + direction); - }, - setCurrentIndex(index) { - if (!this.items[index] || index === this.currentIndex) { - return; - } - this.destinationScrollLeft = this.items[index].offsetLeft; - this.currentIndex = index; - this.carousel.scrollTo({ - left: this.items[index].offsetLeft, - behavior: 'smooth', - }); - }, - getMouseXPositionFromEvent(event) { - const result = (event.clientX || event.touches[0].pageX); - return result; - }, - handleGestureStart(event) { - this.initialX = this.getMouseXPositionFromEvent(event) - this.carousel.offsetLeft; - this.scrollLeft = this.carousel.scrollLeft; - this.isScrolling = true; - this.carousel.style.scrollSnapType = 'none'; - }, - handleGestureMove(event) { - if (!this.isScrolling) { return; } - const currentX = this.getMouseXPositionFromEvent(event) - this.carousel.offsetLeft; - const walk = (currentX - this.initialX) * 3; - this.carousel.scrollLeft = this.scrollLeft - walk; - }, - handleGestureEnd(event) { - if (!this.isScrolling) { return; } - this.isScrolling = false; - this.carousel.style.scrollSnapType = ''; - const currentX = this.getMouseXPositionFromEvent(event) - this.carousel.offsetLeft; - const walk = (currentX - this.initialX) * 3; - this.carousel.scrollLeft = this.scrollLeft - walk; - }, - handleScroll(event) { - const { scrollLeft, clientLeft } = event.target; - const correctedScrollLeft = scrollLeft + clientLeft; - const index = this.items.findIndex(({ offsetLeft: itemOffsetLeft }) => (Math.abs(itemOffsetLeft - correctedScrollLeft) < 1)); - if (index < 0) { return; } - if (this.destinationScrollLeft !== null && this.items[index].offsetLeft !== this.destinationScrollLeft) { return; } - this.destinationScrollLeft = null; - this.currentIndex = index; - }, - onObserverEvent() { - this.updateScroll(this.currentIndex); - }, - initObserver() { - const resizeObserver = new ResizeObserver(this.onObserverEvent); - resizeObserver.observe(this.carousel); - this.resizeObserver = resizeObserver; - }, -}; - -const watch = { - modelValue() { - this.setCurrentIndex(this.modelValue || 0); - }, - currentIndex() { - this.$emit('update:modelValue', this.currentIndex); - }, -}; - -const setup = function () { - const carousel = ref(null); - return { carousel }; -}; - -const created = function () { - this.debouncedChangeRenderedItem = debounce(this.changeRenderedItem, this.scrollDebounce); -}; - -const mounted = function () { - this.setItems(); - document.addEventListener('mousemove', this.handleGestureMove); - document.addEventListener('mouseup', this.handleGestureEnd); - this.initObserver(); -}; - -const beforeUnmount = function () { - document.removeEventListener('mousemove', this.handleGestureMove); - document.removeEventListener('mouseup', this.handleGestureEnd); - if (this.resizeObserver) { this.resizeObserver.unobserve(this.carousel); } -}; - -const name = 'Carousel'; - -const Carousel = { - name, - components, - props, - emits, - data, - computed, - methods, - setup, - watch, - created, - mounted, - beforeUnmount, -}; - -export default Carousel; diff --git a/src/components/Carousel/Carousel.stories.js b/src/components/Carousel/Carousel.stories.ts similarity index 96% rename from src/components/Carousel/Carousel.stories.js rename to src/components/Carousel/Carousel.stories.ts index 7ca0fdd..021b7c0 100644 --- a/src/components/Carousel/Carousel.stories.js +++ b/src/components/Carousel/Carousel.stories.ts @@ -1,3 +1,5 @@ +import type { Meta, StoryFn } from '@storybook/vue3'; + import Carousel from './Carousel.vue'; import RenderString from '../../lib/renderString'; import { createDeviceDecorator } from '../../lib/storybookHelpers'; @@ -92,9 +94,9 @@ const CarouselStories = { }, }, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { @@ -190,7 +192,7 @@ defaultExample.parameters = { }, }; -const simpleExample = (args) => ({ +const simpleExample: StoryFn = (args) => ({ setup() { return { ...args }; }, components: { Carousel, @@ -258,7 +260,7 @@ simpleExample.parameters = { }, }; -const innerWidthItemExample = (args, { argTypes }) => ({ +const innerWidthItemExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/Carousel/Carousel.test.js b/src/components/Carousel/Carousel.test.ts similarity index 90% rename from src/components/Carousel/Carousel.test.js rename to src/components/Carousel/Carousel.test.ts index 853a01f..d818a5a 100644 --- a/src/components/Carousel/Carousel.test.js +++ b/src/components/Carousel/Carousel.test.ts @@ -1,4 +1,5 @@ /* eslint-disable import/first */ +import type { VueWrapper } from '@vue/test-utils'; import { mount } from '@vue/test-utils'; jest.mock('lodash-es', () => ({ @@ -8,7 +9,7 @@ jest.mock('lodash-es', () => ({ import CarouselTestWrapper from './UnitTestWrappers/CarouselTestWrapper.vue'; import { silenceInnerComponentWarnings } from '../../lib/testUtils'; -const waitForDomUpdate = async (wrapper) => { +const waitForDomUpdate = async (wrapper: VueWrapper) => { await wrapper.vm.$nextTick(); await wrapper.vm.$forceUpdate(); }; @@ -108,35 +109,32 @@ describe('Carousel unit test', () => { const wrapper = mount(CarouselTestWrapper, { props: { ...defaultProps, modelValue: 1 } }); await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick(); - const navigationItemClasses = wrapper.findAll('button[data-testid="carousel-navigation-item"]')[1].find('span').classes(); + const navigationItemClasses = wrapper.findAll('button[data-testid="carousel-navigation-item"]')[1]?.find('span').classes(); expect(navigationItemClasses).toContain('active'); }); it('Should emit value = 1 when right arrow is clicked', async () => { const wrapper = mount(CarouselTestWrapper, { props: { ...defaultProps } }); - await wrapper.vm.$nextTick(); - await wrapper.vm.$nextTick(); - const rightArrow = wrapper.find('button[data-testid="carousel-right-arrow"]'); - rightArrow.trigger('click'); await waitForDomUpdate(wrapper); - expect(wrapper.emitted('update:modelValue')[0][0]).toBe(1); + const rightArrow = wrapper.find('button[data-testid="carousel-right-arrow"]'); + await rightArrow.trigger('click'); + const [emittedValue] = wrapper.emitted('update:modelValue')?.[0] as number[]; + expect(emittedValue).toBe(1); }); it('Should emit value = 0 when left arrow is clicked and initial value is 1', async () => { const wrapper = mount(CarouselTestWrapper, { props: { ...defaultProps, modelValue: 1 } }); - await wrapper.vm.$nextTick(); - await wrapper.vm.$nextTick(); - const leftArrow = wrapper.find('button[data-testid="carousel-left-arrow"]'); - leftArrow.trigger('click'); await waitForDomUpdate(wrapper); - expect(wrapper.emitted('update:modelValue')[0][0]).toBe(0); + const leftArrow = wrapper.find('button[data-testid="carousel-left-arrow"]'); + await leftArrow.trigger('click'); + const [emittedValue] = wrapper.emitted('update:modelValue')?.[0] as number[]; + expect(emittedValue).toBe(0); }); it('Should emit value = 2 when third navigation button is clicked', async () => { const wrapper = mount(CarouselTestWrapper, { props: { ...defaultProps } }); - await wrapper.vm.$nextTick(); - await wrapper.vm.$nextTick(); - const navigationItem = wrapper.findAll('button[data-testid="carousel-navigation-item"]')[2]; - navigationItem.trigger('click'); await waitForDomUpdate(wrapper); - expect(wrapper.emitted('update:modelValue')[0][0]).toBe(2); + const navigationItem = wrapper.findAll('button[data-testid="carousel-navigation-item"]')[2]; + await navigationItem?.trigger('click'); + const [emittedValue] = wrapper.emitted('update:modelValue')?.[0] as number[]; + expect(emittedValue).toBe(2); }); }); diff --git a/src/components/Carousel/Carousel.ts b/src/components/Carousel/Carousel.ts new file mode 100644 index 0000000..ccfd212 --- /dev/null +++ b/src/components/Carousel/Carousel.ts @@ -0,0 +1,188 @@ +import { ref, nextTick, defineComponent } from 'vue'; +import { ChevronLeftIcon, ChevronRightIcon } from '@lana/b2c-mapp-ui-assets'; +import { debounce } from 'lodash-es'; + +const isTouchEvent = (event: MouseEvent | TouchEvent): event is TouchEvent => (event as TouchEvent).touches !== undefined; + +const Carousel = defineComponent({ + name: 'Carousel', + components: { + ChevronLeftIcon, + ChevronRightIcon, + }, + props: { + hideArrows: { + type: Boolean, + default: false, + }, + arrowIcons: { + type: Boolean, + default: false, + }, + hideNavigation: { + type: Boolean, + default: false, + }, + modelValue: { + type: Number, + default: 0, + }, + dataTestId: { + type: String, + default: 'carousel', + }, + scrollDebounce: { + type: Number, + default: 200, + }, + }, + emits: ['update:modelValue'], + computed: { + isPreviousAvailable() { + if (!this.items) { return false; } + const result = (this.currentIndex - 1 >= 0); + return result; + }, + isNextAvailable() { + if (!this.items) { return false; } + const result = (this.currentIndex + 1 < this.items.length); + return result; + }, + }, + methods: { + getItems(): HTMLElement[] { + if (!this.$slots.default) { return []; } + const items = this.$slots.default().reduce((accumulator, node) => { + if (!node.component?.refs.componentInstance || !node.component.refs) { return accumulator; } + if (node.component.refs.carouselItem) { + accumulator.push(node.component.refs.carouselItem as HTMLElement); + } + return accumulator; + }, [] as HTMLElement[]); + const result = (items.length) ? items : [...this.carousel.children]; + return result; + }, + async setItems() { + await nextTick(); + if (!this.carousel) { return; } + this.items = this.getItems(); + this.updateScroll(this.currentIndex); + }, + updateScroll(index: number) { + if (!this.items[index] || !this.carousel) { return; } + this.carousel.style.scrollBehavior = 'auto'; + this.carousel.scrollTo({ + left: this.items[index]?.offsetLeft || 0, + }); + this.carousel.style.scrollBehavior = ''; + }, + changeRenderedItem(direction: number) { + this.setCurrentIndex(this.currentIndex + direction); + }, + setCurrentIndex(index: number) { + if (!this.items[index] || index === this.currentIndex) { + return; + } + this.destinationScrollLeft = this.items[index]?.offsetLeft || 0; + this.currentIndex = index; + this.carousel.scrollTo({ + left: this.items[index]?.offsetLeft || 0, + behavior: 'smooth', + }); + }, + getMouseXPositionFromEvent(event: MouseEvent | TouchEvent) { + if (isTouchEvent(event)) { + const touch = event.touches.item(0); + const result = touch?.pageX || 0; + return result; + } + const result = (event.clientX); + return result; + }, + handleGestureStart(event: MouseEvent | TouchEvent) { + this.initialX = this.getMouseXPositionFromEvent(event) - this.carousel.offsetLeft; + this.scrollLeft = this.carousel.scrollLeft; + this.isScrolling = true; + this.carousel.style.scrollSnapType = 'none'; + }, + handleGestureMove(event: MouseEvent | TouchEvent) { + if (!this.isScrolling) { return; } + const currentX = this.getMouseXPositionFromEvent(event) - this.carousel.offsetLeft; + const walk = (currentX - this.initialX) * 3; + this.carousel.scrollLeft = this.scrollLeft - walk; + }, + handleGestureEnd(event: MouseEvent | TouchEvent) { + if (!this.isScrolling) { return; } + this.isScrolling = false; + this.carousel.style.scrollSnapType = ''; + const currentX = this.getMouseXPositionFromEvent(event) - this.carousel.offsetLeft; + const walk = (currentX - this.initialX) * 3; + this.carousel.scrollLeft = this.scrollLeft - walk; + }, + handleScroll(event: Event) { + const { scrollLeft, clientLeft } = event.target as HTMLElement; + const correctedScrollLeft = scrollLeft + clientLeft; + const index = this.items.findIndex(({ offsetLeft: itemOffsetLeft }) => (Math.abs(itemOffsetLeft - correctedScrollLeft) < 1)); + if (index < 0) { return; } + if (this.destinationScrollLeft !== null && this.items[index]?.offsetLeft !== this.destinationScrollLeft) { return; } + this.destinationScrollLeft = -1; + this.currentIndex = index; + }, + onObserverEvent() { + this.updateScroll(this.currentIndex); + }, + initObserver() { + const resizeObserver = new ResizeObserver(this.onObserverEvent); + resizeObserver.observe(this.carousel); + this.resizeObserver = resizeObserver; + }, + }, + watch: { + modelValue() { + this.setCurrentIndex(this.modelValue || 0); + }, + currentIndex() { + this.$emit('update:modelValue', this.currentIndex); + }, + }, + setup(props) { + const carousel = ref(); + const items = ref([] as HTMLElement[]); + const itemsCount = ref(0); + const currentIndex = ref(props.modelValue); + const initialX = ref(0); + const scrollLeft = ref(0); + const destinationScrollLeft = ref(-1); + const isScrolling = ref(false); + const resizeObserver = ref(); + const debouncedChangeRenderedItem = ref<(direction: number) => void>(() => {}); + return { + carousel, + items, + itemsCount, + currentIndex, + initialX, + scrollLeft, + destinationScrollLeft, + isScrolling, + resizeObserver, + debouncedChangeRenderedItem, + }; + }, + created() { + this.debouncedChangeRenderedItem = debounce(this.changeRenderedItem, this.scrollDebounce); + }, + mounted() { + this.setItems(); + document.addEventListener('mousemove', this.handleGestureMove); + document.addEventListener('mouseup', this.handleGestureEnd); + this.initObserver(); + }, + beforeUnmount() { + document.removeEventListener('mousemove', this.handleGestureMove); + document.removeEventListener('mouseup', this.handleGestureEnd); + if (this.resizeObserver) { this.resizeObserver.unobserve(this.carousel); } + }, +}); + +export default Carousel; diff --git a/src/components/Carousel/Carousel.vue b/src/components/Carousel/Carousel.vue index 85da117..fb957f4 100644 --- a/src/components/Carousel/Carousel.vue +++ b/src/components/Carousel/Carousel.vue @@ -6,6 +6,9 @@ @mousedown="handleGestureStart" @mousemove="handleGestureMove" @mouseup="handleGestureEnd" + @touchstart="handleGestureStart" + @touchmove="handleGestureMove" + @touchend="handleGestureEnd" @scroll.passive="handleScroll" > @@ -75,5 +78,5 @@ - + diff --git a/src/components/Checkbox/Checkbox.js b/src/components/Checkbox/Checkbox.js deleted file mode 100644 index 523ea17..0000000 --- a/src/components/Checkbox/Checkbox.js +++ /dev/null @@ -1,67 +0,0 @@ -import { CheckboxCheckedIcon, CheckboxUncheckedIcon } from '@lana/b2c-mapp-ui-assets'; - -const components = { - CheckboxCheckedIcon, - CheckboxUncheckedIcon, -}; - -const props = { - dataTestId: { - type: String, - default: 'checkbox', - }, - label: { - type: String, - default: '', - }, - modelValue: Boolean, - id: String, - disabled: Boolean, - hasError: Boolean, -}; - -const emits = ['update:modelValue']; - -const data = function () { - return { - isChecked: this.modelValue, - fallbackId: `${this._uid}checkbox`, // eslint-disable-line no-underscore-dangle - }; -}; - -const computed = { - idToUse() { - const result = (this.id || this.fallbackId); - return result; - }, -}; - -const methods = { - emitUpdateModelValueEvent() { - this.$emit('update:modelValue', this.isChecked); - }, -}; - -const watch = { - isChecked() { - this.emitUpdateModelValueEvent(); - }, - modelValue() { - this.isChecked = this.modelValue; - }, -}; - -const name = 'Checkbox'; - -const Checkbox = { - name, - components, - props, - emits, - data, - computed, - methods, - watch, -}; - -export default Checkbox; diff --git a/src/components/Checkbox/Checkbox.stories.js b/src/components/Checkbox/Checkbox.stories.ts similarity index 91% rename from src/components/Checkbox/Checkbox.stories.js rename to src/components/Checkbox/Checkbox.stories.ts index 7c53ed3..11c80df 100644 --- a/src/components/Checkbox/Checkbox.stories.js +++ b/src/components/Checkbox/Checkbox.stories.ts @@ -1,3 +1,5 @@ +import type { Meta, StoryFn } from '@storybook/vue3'; + import Checkbox from './Checkbox.vue'; import { createDeviceDecorator } from '../../lib/storybookHelpers'; @@ -17,9 +19,9 @@ const CheckboxStories = { label: { name: 'Label', control: 'text' }, hasError: { name: 'Has Error?', control: 'boolean' }, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/Checkbox/Checkbox.test.js b/src/components/Checkbox/Checkbox.test.ts similarity index 93% rename from src/components/Checkbox/Checkbox.test.js rename to src/components/Checkbox/Checkbox.test.ts index 81945a3..5aa4a18 100644 --- a/src/components/Checkbox/Checkbox.test.js +++ b/src/components/Checkbox/Checkbox.test.ts @@ -41,7 +41,7 @@ describe('UI/forms/Checkbox', () => { await wrapper.vm.$nextTick(); wrapper.vm.$options.watch.isChecked.call(wrapper.vm); await wrapper.vm.$nextTick(); - const currentValueEmitted = wrapper.emitted('update:modelValue')[0][0]; + const [currentValueEmitted] = wrapper.emitted('update:modelValue')?.[0] as boolean[]; expect(currentValueEmitted).toBeTruthy(); }); @@ -50,7 +50,7 @@ describe('UI/forms/Checkbox', () => { await wrapper.vm.$nextTick(); wrapper.vm.$options.watch.modelValue.call(wrapper.vm); await wrapper.vm.$nextTick(); - const takesGivenValue = wrapper.vm.$data.isChecked; + const takesGivenValue = wrapper.vm.isChecked; expect(takesGivenValue).toBeTruthy(); }); }); diff --git a/src/components/Checkbox/Checkbox.ts b/src/components/Checkbox/Checkbox.ts new file mode 100644 index 0000000..8df9312 --- /dev/null +++ b/src/components/Checkbox/Checkbox.ts @@ -0,0 +1,57 @@ +import { CheckboxCheckedIcon, CheckboxUncheckedIcon } from '@lana/b2c-mapp-ui-assets'; +import { defineComponent, getCurrentInstance, ref } from 'vue'; + +const Checkbox = defineComponent({ + name: 'Checkbox', + components: { + CheckboxCheckedIcon, + CheckboxUncheckedIcon, + }, + props: { + dataTestId: { + type: String, + default: 'checkbox', + }, + label: { + type: String, + default: '', + }, + modelValue: Boolean, + id: { + type: String, + default: '', + }, + disabled: Boolean, + hasError: Boolean, + }, + emits: ['update:modelValue'], + setup(props) { + const isChecked = ref(props.modelValue); + const fallbackId = `checkbox-${getCurrentInstance()?.uid}`; + return { + isChecked, + fallbackId, + }; + }, + computed: { + idToUse() { + const result = (this.id || this.fallbackId); + return result; + }, + }, + methods: { + emitUpdateModelValueEvent() { + this.$emit('update:modelValue', this.isChecked); + }, + }, + watch: { + isChecked() { + this.emitUpdateModelValueEvent(); + }, + modelValue() { + this.isChecked = this.modelValue; + }, + }, +}); + +export default Checkbox; diff --git a/src/components/Checkbox/Checkbox.vue b/src/components/Checkbox/Checkbox.vue index ac73f3d..65db1ac 100644 --- a/src/components/Checkbox/Checkbox.vue +++ b/src/components/Checkbox/Checkbox.vue @@ -22,5 +22,5 @@ - + diff --git a/src/components/CircularProgress/CircularProgress.js b/src/components/CircularProgress/CircularProgress.js deleted file mode 100644 index d79ea1e..0000000 --- a/src/components/CircularProgress/CircularProgress.js +++ /dev/null @@ -1,118 +0,0 @@ -import Heading from '../Heading/Heading.vue'; -import TextParagraph from '../TextParagraph/TextParagraph.vue'; - -const components = { - Heading, - TextParagraph, -}; - -const availableColors = ['blue', 'brown', 'green', 'orange', 'pink', 'purple', 'red', 'yellow']; - -const props = { - dataTestId: { - type: String, - default: 'progress', - }, - title: { - type: String, - default: '', - }, - description: { - type: String, - default: '', - }, - progress: Number, - total: Number, - percentage: Number, - color: { - type: String, - default: 'blue', - validator(value) { return (!value || availableColors.includes(value)); }, - }, - animate: Boolean, - animationDuration: { - type: Number, - default: 1000, - }, - circularAnimation: Boolean, -}; - -const emits = ['animationend', 'error']; - -const computed = { - progressPercentage() { - const result = (this.percentage || ((this.progress / this.total) * 100) || 0); - switch (true) { - case result < 0: - return 0; - case result > 100: - return 100; - default: - return result; - } - }, - progressStyle() { - const result = { transform: `rotate(${45 + (this.progressPercentage * 1.8)}deg)` }; - return result; - }, - progressText() { - const result = (this.description || ((this.percentage) ? `${this.percentage}%` : `${this.progress} / ${this.total}`)); - return result; - }, - progressCircle() { - const result = { transform: `rotate(${(this.progressPercentage * 1.8)}deg)` }; - return result; - }, - cssVars() { - const percentagePosition = 100 - Math.ceil(((this.progressPercentage + 1) / 2)); - const result = { - '--progression-color-position': `${percentagePosition}%`, - }; - return result; - }, -}; - -const methods = { - startAnimation() { - try { - const { bar, circle, outsideBorder, insideBorder } = this.$refs; - const barAnimation = [ - { transform: 'rotate(45deg)' }, - { transform: `rotate(${45 + (this.progressPercentage * 1.8)}deg)` }, - ]; - const circleAnimation = [ - { transform: 'rotate(0deg)' }, - { transform: `rotate(${(this.progressPercentage * 1.8)}deg)` }, - ]; - const animation = bar.animate(barAnimation, { duration: this.animationDuration, iterations: (this.circularAnimation) ? Infinity : 1 }); - outsideBorder.animate(barAnimation, { duration: this.animationDuration, iterations: (this.circularAnimation) ? Infinity : 1 }); - insideBorder.animate(barAnimation, { duration: this.animationDuration, iterations: (this.circularAnimation) ? Infinity : 1 }); - circle.animate(circleAnimation, { duration: this.animationDuration, iterations: (this.circularAnimation) ? Infinity : 1 }); - animation.onfinish = this.emitEventFinish; - } catch (error) { - this.$emit('error', error); - } - }, - emitEventFinish(event) { - this.$emit('animationend', event); - }, -}; - -const mounted = function () { - if (!this.animate) { return; } - this.startAnimation(); -}; - -const name = 'CircularProgress'; - -const CircularProgress = { - name, - components, - props, - emits, - computed, - methods, - mounted, -}; - -export default CircularProgress; diff --git a/src/components/CircularProgress/CircularProgress.stories.js b/src/components/CircularProgress/CircularProgress.stories.ts similarity index 95% rename from src/components/CircularProgress/CircularProgress.stories.js rename to src/components/CircularProgress/CircularProgress.stories.ts index 83eeebf..b787ebf 100644 --- a/src/components/CircularProgress/CircularProgress.stories.js +++ b/src/components/CircularProgress/CircularProgress.stories.ts @@ -1,4 +1,5 @@ import { action } from '@storybook/addon-actions'; +import type { Meta, StoryFn } from '@storybook/vue3'; import CircularProgress from './CircularProgress.vue'; import RenderString from '../../lib/renderString'; @@ -60,9 +61,9 @@ const CircularProgressStories = { }, }, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/CircularProgress/CircularProgress.test.js b/src/components/CircularProgress/CircularProgress.test.ts similarity index 91% rename from src/components/CircularProgress/CircularProgress.test.js rename to src/components/CircularProgress/CircularProgress.test.ts index 55f207f..856822a 100644 --- a/src/components/CircularProgress/CircularProgress.test.js +++ b/src/components/CircularProgress/CircularProgress.test.ts @@ -28,21 +28,21 @@ describe('CircularProgress unit test', () => { const wrapper = mount(CircularProgress, { props: { ...defaultProps } }); await wrapper.vm.$nextTick(); const description = wrapper.find('div[data-testid="progress-circle"]'); - expect(description.element.style.transform).toContain(`rotate(${50 * 1.8}deg)`); + expect(description.attributes('style')).toContain(`transform: rotate(${50 * 1.8}deg)`); }); it('Percentage should override given progress/total percentage', async () => { const wrapper = mount(CircularProgress, { props: { ...defaultProps, percentage: 25 } }); await wrapper.vm.$nextTick(); const description = wrapper.find('div[data-testid="progress-circle"]'); - expect(description.element.style.transform).toContain(`rotate(${25 * 1.8}deg)`); + expect(description.attributes('style')).toContain(`transform: rotate(${25 * 1.8}deg)`); }); it('Should hide progress circle on 0 progress', async () => { const wrapper = mount(CircularProgress, { props: { ...defaultProps, progress: 0 } }); await wrapper.vm.$nextTick(); const circle = wrapper.find('div[data-testid="progress-circle"]'); - expect(circle.element.style.display).toContain('none'); + expect(circle.attributes('style')).toContain('display: none'); }); it('Should show custom title', async () => { diff --git a/src/components/CircularProgress/CircularProgress.ts b/src/components/CircularProgress/CircularProgress.ts new file mode 100644 index 0000000..794361d --- /dev/null +++ b/src/components/CircularProgress/CircularProgress.ts @@ -0,0 +1,122 @@ +import type { StyleValue } from 'vue'; +import { defineComponent, ref } from 'vue'; + +import Heading from '../Heading/Heading.vue'; +import TextParagraph from '../TextParagraph/TextParagraph.vue'; + +const availableColors = ['blue', 'brown', 'green', 'orange', 'pink', 'purple', 'red', 'yellow']; + +const CircularProgress = defineComponent({ + name: 'CircularProgress', + components: { + Heading, + TextParagraph, + }, + props: { + dataTestId: { + type: String, + default: 'progress', + }, + title: { + type: String, + default: '', + }, + description: { + type: String, + default: '', + }, + progress: { + type: Number, + default: null, + }, + total: { + type: Number, + default: null, + }, + percentage: { + type: Number, + default: null, + }, + color: { + type: String, + default: 'blue', + validator(value: string) { return (!value || availableColors.includes(value)); }, + }, + animate: Boolean, + animationDuration: { + type: Number, + default: 1000, + }, + circularAnimation: Boolean, + }, + emits: ['animationend', 'error'], + computed: { + progressPercentage() { + const result = (this.percentage || ((this.progress / this.total) * 100) || 0); + switch (true) { + case result < 0: + return 0; + case result > 100: + return 100; + default: + return result; + } + }, + progressStyle() { + const result = { transform: `rotate(${45 + (this.progressPercentage * 1.8)}deg)` }; + return result; + }, + progressText() { + const result = (this.description || ((this.percentage) ? `${this.percentage}%` : `${this.progress} / ${this.total}`)); + return result; + }, + progressCircle() { + const result = { transform: `rotate(${(this.progressPercentage * 1.8)}deg)` }; + return result; + }, + cssVars() { + const percentagePosition = 100 - Math.ceil(((this.progressPercentage + 1) / 2)); + const result = { + '--progression-color-position': `${percentagePosition}%`, + } as StyleValue; + return [result]; + }, + }, + methods: { + startAnimation() { + try { + const barAnimation = [ + { transform: 'rotate(45deg)' }, + { transform: `rotate(${45 + (this.progressPercentage * 1.8)}deg)` }, + ]; + const circleAnimation = [ + { transform: 'rotate(0deg)' }, + { transform: `rotate(${(this.progressPercentage * 1.8)}deg)` }, + ]; + const animation = this.bar.animate(barAnimation, { duration: this.animationDuration, iterations: (this.circularAnimation) ? Infinity : 1 }); + this.outsideBorder.animate(barAnimation, { duration: this.animationDuration, iterations: (this.circularAnimation) ? Infinity : 1 }); + this.insideBorder.animate(barAnimation, { duration: this.animationDuration, iterations: (this.circularAnimation) ? Infinity : 1 }); + this.circle.animate(circleAnimation, { duration: this.animationDuration, iterations: (this.circularAnimation) ? Infinity : 1 }); + animation.onfinish = this.emitEventFinish; + } catch (error) { + this.$emit('error', error); + } + }, + emitEventFinish(event: Event) { + this.$emit('animationend', event); + }, + }, + setup() { + const bar = ref(); + const circle = ref(); + const outsideBorder = ref(); + const insideBorder = ref(); + return { bar, circle, outsideBorder, insideBorder }; + }, + mounted() { + if (!this.animate) { return; } + this.startAnimation(); + }, +}); + +export default CircularProgress; diff --git a/src/components/CircularProgress/CircularProgress.vue b/src/components/CircularProgress/CircularProgress.vue index bc1e934..604dede 100644 --- a/src/components/CircularProgress/CircularProgress.vue +++ b/src/components/CircularProgress/CircularProgress.vue @@ -44,5 +44,5 @@ - + diff --git a/src/components/CodeInputField/CodeInputField.js b/src/components/CodeInputField/CodeInputField.js deleted file mode 100644 index 5aa5900..0000000 --- a/src/components/CodeInputField/CodeInputField.js +++ /dev/null @@ -1,125 +0,0 @@ -import { CloseBoldIcon } from '@lana/b2c-mapp-ui-assets'; - -import TextParagraph from '../TextParagraph/TextParagraph.vue'; -import { singleDigitRegexp } from '../../lib/regexHelper'; - -const isValidDigit = (digit) => singleDigitRegexp.test(digit); -const availableTypesLookup = { - smsCode: 'smsCode', - cardPin: 'cardPin', -}; -const availableTypes = [ - availableTypesLookup.cardPin, - availableTypesLookup.smsCode, -]; -const codeLengthByTypeLookup = { - [availableTypesLookup.cardPin]: 4, - [availableTypesLookup.smsCode]: 6, -}; - -const components = { - TextParagraph, - CloseBoldIcon, -}; - -const props = { - dataTestId: { - type: String, - default: 'code-input', - }, - type: { - type: String, - default: availableTypesLookup.smsCode, - validator(value) { return (!value || availableTypes.includes(value)); }, - }, - modelValue: { - type: String, - default: '', - }, - id: String, - disabled: Boolean, - errorMessage: String, - errorDescription: String, - autocomplete: { - type: String, - default: 'off', - }, -}; - -const emits = ['animationend', 'readyToCheckChanged', 'update:modelValue']; - -const data = function () { - return { - codeInput: this.modelValue, - }; -}; - -const computed = { - codeDigits() { - const result = this.codeInput.split(''); - return result; - }, - expectedCodeLength() { - const result = codeLengthByTypeLookup[this.type]; - return result; - }, - isCodeReadyToCheck() { - const result = !!(this.codeDigits && (this.codeDigits.length === this.expectedCodeLength) && this.codeDigits.every(isValidDigit)); - return result; - }, - isSmsCode() { - const result = (this.type === availableTypesLookup.smsCode); - return result; - }, - isCardPin() { - const result = (this.type === availableTypesLookup.cardPin); - return result; - }, -}; - -const methods = { - focus() { - this.$refs.oneTimeCodeField.focus(); - }, - onAnimationEnd() { - this.$emit('animationend'); - }, - onReadyToCheckChanged() { - this.$emit('readyToCheckChanged', this.isCodeReadyToCheck); - }, - emitUpdateModelValueEvent() { - this.$emit('update:modelValue', this.codeInput); - }, -}; - -const watch = { - codeInput() { - this.emitUpdateModelValueEvent(); - }, - modelValue() { - this.codeInput = this.modelValue; - }, - isCodeReadyToCheck() { - this.onReadyToCheckChanged(); - }, -}; - -const name = 'CodeInputField'; - -const CodeInputField = { - name, - components, - props, - emits, - data, - computed, - methods, - watch, -}; - -export { - availableTypes, - availableTypesLookup, -}; - -export default CodeInputField; diff --git a/src/components/CodeInputField/CodeInputField.stories.js b/src/components/CodeInputField/CodeInputField.stories.ts similarity index 85% rename from src/components/CodeInputField/CodeInputField.stories.js rename to src/components/CodeInputField/CodeInputField.stories.ts index 5608bc9..71e1779 100644 --- a/src/components/CodeInputField/CodeInputField.stories.js +++ b/src/components/CodeInputField/CodeInputField.stories.ts @@ -1,4 +1,5 @@ import { action } from '@storybook/addon-actions'; +import type { Meta, StoryFn } from '@storybook/vue3'; import CodeInputField from './CodeInputField.vue'; import { availableTypes } from './CodeInputField'; @@ -24,9 +25,9 @@ const CodeInputFieldStories = { errorMessage: { name: 'Error Message', control: 'text' }, errorDescription: { name: 'Error Description', control: 'text' }, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { @@ -66,14 +67,14 @@ const defaultExample = (args, { argTypes }) => ({ `, }); -const withError = (args, { argTypes }) => ({ +const withError: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { CodeInputField, }, template: ` - @@ -89,9 +90,9 @@ withError.parameters = { docs: { source: { code: ` -`, }, }, diff --git a/src/components/CodeInputField/CodeInputField.test.js b/src/components/CodeInputField/CodeInputField.test.ts similarity index 82% rename from src/components/CodeInputField/CodeInputField.test.js rename to src/components/CodeInputField/CodeInputField.test.ts index 46f67d9..aded54e 100644 --- a/src/components/CodeInputField/CodeInputField.test.js +++ b/src/components/CodeInputField/CodeInputField.test.ts @@ -1,17 +1,18 @@ +import type { VueWrapper } from '@vue/test-utils'; import { mount } from '@vue/test-utils'; import CodeInputField from './CodeInputField.vue'; import { availableTypesLookup } from './CodeInputField'; -import { silenceDeprecationErrorsAndInnerComponentWarnings } from '../../lib/testUtils'; +import { silenceInnerComponentWarnings } from '../../lib/testUtils'; describe('CodeInputField unit test', () => { - const waitForDomUpdate = async (wrapper) => { + const waitForDomUpdate = async (wrapper: VueWrapper) => { await wrapper.vm.$nextTick(); await wrapper.vm.$forceUpdate(); }; beforeAll(() => { - silenceDeprecationErrorsAndInnerComponentWarnings(jest); + silenceInnerComponentWarnings(jest); }); const defaultProps = { @@ -22,11 +23,11 @@ describe('CodeInputField unit test', () => { const wrapper = mount(CodeInputField, { props: { ...defaultProps, modelValue: '12345' } }); await waitForDomUpdate(wrapper); const digitFields = await wrapper.findAll('[data-testid="code-input-field"]'); - const isFirstDigitFilled = digitFields[0].element.textContent.includes('1'); - const isSecondDigitFilled = digitFields[1].element.textContent.includes('2'); - const isThirdDigitFilled = digitFields[2].element.textContent.includes('3'); - const isFourthDigitFilled = digitFields[3].element.textContent.includes('4'); - const isFifthDigitFilled = digitFields[4].element.textContent.includes('5'); + const isFirstDigitFilled = digitFields[0]?.element.textContent?.includes('1'); + const isSecondDigitFilled = digitFields[1]?.element.textContent?.includes('2'); + const isThirdDigitFilled = digitFields[2]?.element.textContent?.includes('3'); + const isFourthDigitFilled = digitFields[3]?.element.textContent?.includes('4'); + const isFifthDigitFilled = digitFields[4]?.element.textContent?.includes('5'); const areAllDigitFieldsFilled = isFirstDigitFilled && isSecondDigitFilled && isThirdDigitFilled diff --git a/src/components/CodeInputField/CodeInputField.ts b/src/components/CodeInputField/CodeInputField.ts new file mode 100644 index 0000000..5765339 --- /dev/null +++ b/src/components/CodeInputField/CodeInputField.ts @@ -0,0 +1,130 @@ +import type { PropType } from 'vue'; +import { defineComponent, ref } from 'vue'; +import { CloseBoldIcon } from '@lana/b2c-mapp-ui-assets'; + +import TextParagraph from '../TextParagraph/TextParagraph.vue'; +import { singleDigitRegexp } from '../../lib/regexHelper'; + +const isValidDigit = (digit: string) => singleDigitRegexp.test(digit); + +enum CodeType { + smsCode = 'smsCode', + cardPin = 'cardPin', +} +const availableTypesLookup: { [key: string]: CodeType } = { + smsCode: CodeType.smsCode, + cardPin: CodeType.cardPin, +}; + +const availableTypes = [ + availableTypesLookup.cardPin, + availableTypesLookup.smsCode, +]; +const codeLengthByTypeLookup: { [key in CodeType]: number } = { + [CodeType.cardPin]: 4, + [CodeType.smsCode]: 6, +}; + +const CodeInputField = defineComponent({ + name: 'CodeInputField', + components: { + TextParagraph, + CloseBoldIcon, + }, + props: { + dataTestId: { + type: String, + default: 'code-input', + }, + type: { + type: String as PropType, + default: availableTypesLookup.smsCode, + validator: (code: CodeType) => availableTypes.includes(code), + }, + modelValue: { + type: String, + default: '', + }, + id: { + type: String, + default: '', + }, + disabled: Boolean, + errorMessage: { + type: String, + default: '', + }, + errorDescription: { + type: String, + default: '', + }, + autocomplete: { + type: String, + default: 'off', + }, + }, + emits: ['animationend', 'readyToCheckChanged', 'update:modelValue'], + computed: { + codeDigits() { + const result = this.codeInput?.split(''); + return result; + }, + expectedCodeLength() { + const result = codeLengthByTypeLookup[this.type]; + return result; + }, + isCodeReadyToCheck() { + const result = !!(this.codeDigits && (this.codeDigits.length === this.expectedCodeLength) && this.codeDigits.every(isValidDigit)); + return result; + }, + isSmsCode() { + const result = (this.type === CodeType.smsCode); + return result; + }, + isCardPin() { + const result = (this.type === CodeType.cardPin); + return result; + }, + }, + methods: { + focus() { + this.oneTimeCodeField.focus(); + }, + onAnimationEnd() { + this.$emit('animationend'); + }, + onReadyToCheckChanged() { + this.$emit('readyToCheckChanged', this.isCodeReadyToCheck); + }, + emitUpdateModelValueEvent() { + this.$emit('update:modelValue', this.codeInput); + }, + }, + watch: { + codeInput() { + this.emitUpdateModelValueEvent(); + }, + modelValue() { + this.codeInput = this.modelValue; + }, + isCodeReadyToCheck() { + this.onReadyToCheckChanged(); + }, + }, + setup(props) { + const oneTimeCodeField = ref(); + const codeInput = ref(props.modelValue); + return { oneTimeCodeField, codeInput }; + }, +}); + +export type { + CodeType, +}; + +export { + availableTypes, + availableTypesLookup, +}; + +export default CodeInputField; diff --git a/src/components/CodeInputField/CodeInputField.vue b/src/components/CodeInputField/CodeInputField.vue index 89e2d55..837c1a8 100644 --- a/src/components/CodeInputField/CodeInputField.vue +++ b/src/components/CodeInputField/CodeInputField.vue @@ -54,5 +54,5 @@ - + diff --git a/src/components/ConfettiOverlay/Confetti.vue b/src/components/ConfettiOverlay/Confetti.vue index 49652d2..e19bc8c 100644 --- a/src/components/ConfettiOverlay/Confetti.vue +++ b/src/components/ConfettiOverlay/Confetti.vue @@ -4,10 +4,11 @@ - diff --git a/src/components/ConfettiOverlay/ConfettiOverlay.js b/src/components/ConfettiOverlay/ConfettiOverlay.js deleted file mode 100644 index ad2c043..0000000 --- a/src/components/ConfettiOverlay/ConfettiOverlay.js +++ /dev/null @@ -1,47 +0,0 @@ -import Confetti from './Confetti.vue'; - -const defaultCountOfParticles = 80; -const maximumCountOfParticlesForSmoothPerformance = 200; -const colors = ['blue', 'orange', 'pink', 'purple', 'red', 'yellow']; - -const components = { - Confetti, -}; - -const props = { - particles: { - type: [Number, String], - default: defaultCountOfParticles, - validator: (value) => { - const numericValue = Number.parseInt(value, 10); - const isValid = !Number.isNaN(numericValue); - const result = (isValid && (numericValue <= maximumCountOfParticlesForSmoothPerformance)); - return result; - }, - }, -}; - -const computed = { - countOfParticles() { - const numericValue = Number.parseInt(this.particles, 10); - const count = (numericValue < maximumCountOfParticlesForSmoothPerformance) ? numericValue : maximumCountOfParticlesForSmoothPerformance; - const result = [...Array(count).keys()].map((index) => ({ - id: index, - color: colors[Math.floor(Math.random() * colors.length)], - width: Math.random() * 10, - height: Math.random() * 100, - })); - return result; - }, -}; - -const name = 'ConfettiOverlay'; - -const ConfettiOverlay = { - name, - components, - props, - computed, -}; - -export default ConfettiOverlay; diff --git a/src/components/ConfettiOverlay/ConfettiOverlay.stories.js b/src/components/ConfettiOverlay/ConfettiOverlay.stories.ts similarity index 92% rename from src/components/ConfettiOverlay/ConfettiOverlay.stories.js rename to src/components/ConfettiOverlay/ConfettiOverlay.stories.ts index c8fc3ab..5044115 100644 --- a/src/components/ConfettiOverlay/ConfettiOverlay.stories.js +++ b/src/components/ConfettiOverlay/ConfettiOverlay.stories.ts @@ -1,4 +1,5 @@ import { nextTick } from 'vue'; +import type { Meta, StoryFn } from '@storybook/vue3'; import ConfettiOverlay from './ConfettiOverlay.vue'; import ScrollWrapper from '../ScrollWrapper/ScrollWrapper.vue'; @@ -21,9 +22,9 @@ const ConfettiStories = { ...deviceDecorator.argTypes, particles: { name: 'Count of Particles', control: 'number' }, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/ConfettiOverlay/ConfettiOverlay.test.js b/src/components/ConfettiOverlay/ConfettiOverlay.test.ts similarity index 79% rename from src/components/ConfettiOverlay/ConfettiOverlay.test.js rename to src/components/ConfettiOverlay/ConfettiOverlay.test.ts index f2fb387..86b9a4e 100644 --- a/src/components/ConfettiOverlay/ConfettiOverlay.test.js +++ b/src/components/ConfettiOverlay/ConfettiOverlay.test.ts @@ -1,9 +1,10 @@ +import type { VueWrapper } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils'; import ConfettiOverlay from './ConfettiOverlay.vue'; describe('ConfettiOverlay unit test', () => { - const waitForDomUpdate = async (wrapper) => { + const waitForDomUpdate = async (wrapper: VueWrapper) => { await wrapper.vm.$nextTick(); await wrapper.vm.$forceUpdate(); }; diff --git a/src/components/ConfettiOverlay/ConfettiOverlay.ts b/src/components/ConfettiOverlay/ConfettiOverlay.ts new file mode 100644 index 0000000..44d4afc --- /dev/null +++ b/src/components/ConfettiOverlay/ConfettiOverlay.ts @@ -0,0 +1,41 @@ +import { defineComponent } from 'vue'; + +import Confetti from './Confetti.vue'; + +const defaultCountOfParticles = 80; +const maximumCountOfParticlesForSmoothPerformance = 200; +const colors = ['blue', 'orange', 'pink', 'purple', 'red', 'yellow']; + +const ConfettiOverlay = defineComponent({ + name: 'ConfettiOverlay', + components: { + Confetti, + }, + props: { + particles: { + type: [Number, String], + default: defaultCountOfParticles, + validator: (value: number | string) => { + const numericValue = Number.parseInt(`${value}`, 10); + const isValid = !Number.isNaN(numericValue); + const result = (isValid && (numericValue <= maximumCountOfParticlesForSmoothPerformance)); + return result; + }, + }, + }, + computed: { + countOfParticles() { + const numericValue = Number.parseInt(`${this.particles}`, 10); + const count = (numericValue < maximumCountOfParticlesForSmoothPerformance) ? numericValue : maximumCountOfParticlesForSmoothPerformance; + const result = Array(count).fill(0).map((_, index) => ({ + id: index, + color: colors[Math.floor(Math.random() * colors.length)], + width: Math.random() * 10, + height: Math.random() * 100, + })); + return result; + }, + }, +}); + +export default ConfettiOverlay; diff --git a/src/components/ConfettiOverlay/ConfettiOverlay.vue b/src/components/ConfettiOverlay/ConfettiOverlay.vue index d0b428b..7c89696 100644 --- a/src/components/ConfettiOverlay/ConfettiOverlay.vue +++ b/src/components/ConfettiOverlay/ConfettiOverlay.vue @@ -8,5 +8,6 @@ /> - + diff --git a/src/components/ConfirmationModalDialog/ConfirmationModalDialog.js b/src/components/ConfirmationModalDialog/ConfirmationModalDialog.js deleted file mode 100644 index 6ce7bca..0000000 --- a/src/components/ConfirmationModalDialog/ConfirmationModalDialog.js +++ /dev/null @@ -1,79 +0,0 @@ -import Heading from '../Heading/Heading.vue'; -import ScrollWrapper from '../ScrollWrapper/ScrollWrapper.vue'; -import TextParagraph from '../TextParagraph/TextParagraph.vue'; - -const components = { - Heading, - ScrollWrapper, - TextParagraph, -}; - -const props = { - dataTestId: { - type: String, - default: 'dialog', - }, - title: String, - description: String, - confirmButtonText: String, - dismissButtonText: String, - modelValue: Boolean, - loading: Boolean, - confirmButtonDisabled: { - type: Boolean, - default: false, - }, - dismissButtonDisabled: { - type: Boolean, - default: false, - }, -}; - -const emits = ['update:modelValue', 'close', 'confirm', 'dismiss']; - -const data = function () { - return { - isShowing: this.modelValue, - }; -}; - -const methods = { - emitUpdateModelValueEvent() { - this.$emit('update:modelValue', this.isShowing); - if (!this.isShowing) { this.$emit('close'); } - }, - onConfirm() { - this.$emit('confirm'); - this.hideDialog(); - }, - onDismiss() { - this.$emit('dismiss'); - this.hideDialog(); - }, - hideDialog() { - this.isShowing = false; - }, -}; - -const watch = { - isShowing() { - this.emitUpdateModelValueEvent(); - }, - modelValue() { - this.isShowing = this.modelValue; - }, -}; - -const name = 'ConfirmationModalDialog'; - -const ConfirmationModalDialog = { - name, - components, - props, - emits, - data, - methods, - watch, -}; - -export default ConfirmationModalDialog; diff --git a/src/components/ConfirmationModalDialog/ConfirmationModalDialog.stories.js b/src/components/ConfirmationModalDialog/ConfirmationModalDialog.stories.ts similarity index 96% rename from src/components/ConfirmationModalDialog/ConfirmationModalDialog.stories.js rename to src/components/ConfirmationModalDialog/ConfirmationModalDialog.stories.ts index 49a7b18..b424c24 100644 --- a/src/components/ConfirmationModalDialog/ConfirmationModalDialog.stories.js +++ b/src/components/ConfirmationModalDialog/ConfirmationModalDialog.stories.ts @@ -1,5 +1,6 @@ import { action } from '@storybook/addon-actions'; import { CloseIcon } from '@lana/b2c-mapp-ui-assets'; +import type { Meta, StoryFn } from '@storybook/vue3'; import ConfirmationModalDialog from './ConfirmationModalDialog.vue'; import { createScreenDecorator } from '../../lib/storybookHelpers'; @@ -50,9 +51,9 @@ const ConfirmationModalDialogStories = { }, }, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { @@ -126,7 +127,7 @@ defaultExample.parameters = { }, }; -const withCustomContent = (args, { argTypes }) => ({ +const withCustomContent: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { @@ -213,7 +214,7 @@ withCustomContent.parameters = { }, }; -const withDismissButtonDisabled = (args, { argTypes }) => ({ +const withDismissButtonDisabled: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { @@ -303,7 +304,7 @@ withDismissButtonDisabled.parameters = { }, }; -const withConfirmButtonDisabled = (args, { argTypes }) => ({ +const withConfirmButtonDisabled: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { @@ -393,7 +394,7 @@ withConfirmButtonDisabled.parameters = { }, }; -const withCustomActionButton = (args, { argTypes }) => ({ +const withCustomActionButton: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/ConfirmationModalDialog/ConfirmationModalDialog.test.js b/src/components/ConfirmationModalDialog/ConfirmationModalDialog.test.ts similarity index 85% rename from src/components/ConfirmationModalDialog/ConfirmationModalDialog.test.js rename to src/components/ConfirmationModalDialog/ConfirmationModalDialog.test.ts index af1b54d..63ecdea 100644 --- a/src/components/ConfirmationModalDialog/ConfirmationModalDialog.test.js +++ b/src/components/ConfirmationModalDialog/ConfirmationModalDialog.test.ts @@ -3,13 +3,8 @@ import { mount } from '@vue/test-utils'; import ConfirmationModalDialog from './ConfirmationModalDialog.vue'; import ConfirmationModalDialogWrapper from './UnitTestWrappers/ConfirmationModalDialogWrapper.vue'; -import { silenceDeprecationErrorsAndInnerComponentWarnings } from '../../lib/testUtils'; describe('ConfirmationModalDialog unit test', () => { - beforeAll(() => { - silenceDeprecationErrorsAndInnerComponentWarnings(jest); - }); - const defaultProps = { title: 'title', description: 'description', @@ -31,27 +26,24 @@ describe('ConfirmationModalDialog unit test', () => { expect(dialogIsNotVisible).toBeTruthy(); }); - it('Should set to visible if given value is set to true', () => new Promise((resolve) => { + it('Should set to visible if given value is set to true', async () => { const { getByTestId } = render(ConfirmationModalDialogWrapper); const openModal = getByTestId('open-modal'); - fireEvent.click(openModal); - setTimeout(() => { - const dialog = getByTestId('dialog-section'); - const dialogIsVisible = dialog.className.includes('visible'); - expect(dialogIsVisible).toBeTruthy(); - resolve(); - }); - })); + await fireEvent.click(openModal); + const dialog = getByTestId('dialog-section'); + const dialogIsVisible = dialog.className.includes('visible'); + expect(dialogIsVisible).toBeTruthy(); + }); it('Should show title if provided', () => { const { getByTestId } = render(ConfirmationModalDialog, { props: { ...defaultProps } }); - const titleIsShown = getByTestId('dialog-title').textContent.includes('title'); + const titleIsShown = getByTestId('dialog-title').textContent?.includes('title'); expect(titleIsShown).toBeTruthy(); }); it('Should show description if provided', () => { const { getByTestId } = render(ConfirmationModalDialog, { props: { ...defaultProps } }); - const descriptionIsShown = getByTestId('dialog-description').textContent.includes('description'); + const descriptionIsShown = getByTestId('dialog-description').textContent?.includes('description'); expect(descriptionIsShown).toBeTruthy(); }); @@ -63,7 +55,7 @@ describe('ConfirmationModalDialog unit test', () => { it('Should show given children if description is not provided', () => { const { getByTestId } = render(ConfirmationModalDialog, { slots: { default: 'Hey!' }, props: { ...defaultProps, description: null } }); - const childrenIsShown = getByTestId('confirmation-modal-dialog-slot').textContent.includes('Hey!'); + const childrenIsShown = getByTestId('confirmation-modal-dialog-slot').textContent?.includes('Hey!'); expect(childrenIsShown).toBeTruthy(); }); @@ -82,25 +74,25 @@ describe('ConfirmationModalDialog unit test', () => { describe('Confirm actions behavior', () => { it('Should show action-confirm button when confirm is given', () => { const { getByTestId } = render(ConfirmationModalDialog, { props: { ...defaultProps } }); - const confirmIsShown = getByTestId('dialog-action-confirm-button').textContent.includes('confirm'); + const confirmIsShown = getByTestId('dialog-action-confirm-button').textContent?.includes('confirm'); expect(confirmIsShown).toBeTruthy(); }); it('Should be disabled if given confirmButtonDisabled is set to true', () => { const { getByTestId } = render(ConfirmationModalDialog, { props: { ...defaultProps, confirmButtonDisabled: true } }); - const confirmDisabled = getByTestId('dialog-action-confirm-button').getAttribute('disabled'); - expect(confirmDisabled).toBeDefined(); + const confirmDisabled = typeof getByTestId('dialog-action-confirm-button').getAttribute('disabled') === 'string'; + expect(confirmDisabled).toBeTruthy(); }); it('Should be NOT disabled if given confirmButtonDisabled is set to false', () => { const { getByTestId } = render(ConfirmationModalDialog, { props: { ...defaultProps, confirmButtonDisabled: false } }); - const confirmDisabled = getByTestId('dialog-action-confirm-button').getAttribute('disabled'); + const confirmDisabled = (typeof getByTestId('dialog-action-confirm-button').getAttribute('disabled') === 'string'); expect(confirmDisabled).toBeFalsy(); }); it('Should be NOT disabled if given confirmButtonDisabled by default', () => { const { getByTestId } = render(ConfirmationModalDialog, { props: { ...defaultProps } }); - const confirmDisabled = getByTestId('dialog-action-confirm-button').getAttribute('disabled'); + const confirmDisabled = (typeof getByTestId('dialog-action-confirm-button').getAttribute('disabled') === 'string'); expect(confirmDisabled).toBeFalsy(); }); @@ -113,11 +105,11 @@ describe('ConfirmationModalDialog unit test', () => { expect(confirmIsNotShown).toBeTruthy(); }); - it('Should emit an event when confirm is given and action-confirm is clicked', () => { - const { getByTestId, emitted } = render(ConfirmationModalDialog, { props: { ...defaultProps } }); - const confirmCTA = getByTestId('dialog-action-confirm-button'); - fireEvent.click(confirmCTA); - const clickEvent = emitted('confirm'); + it('Should emit an event when confirm is given and action-confirm is clicked', async () => { + const wrapper = mount(ConfirmationModalDialog, { props: { ...defaultProps } }); + const confirmCTA = wrapper.find('[data-testid="dialog-action-confirm-button"]'); + await confirmCTA.trigger('click'); + const clickEvent = wrapper.emitted('confirm'); expect(clickEvent).toBeTruthy(); }); }); @@ -125,7 +117,7 @@ describe('ConfirmationModalDialog unit test', () => { describe('Dismiss actions behavior', () => { it('Should show action-dismiss button when dismiss is given', () => { const { getByTestId } = render(ConfirmationModalDialog, { props: { ...defaultProps } }); - const dismissIsShown = getByTestId('dialog-action-dismiss-button').textContent.includes('dismiss'); + const dismissIsShown = getByTestId('dialog-action-dismiss-button').textContent?.includes('dismiss'); expect(dismissIsShown).toBeTruthy(); }); diff --git a/src/components/ConfirmationModalDialog/ConfirmationModalDialog.ts b/src/components/ConfirmationModalDialog/ConfirmationModalDialog.ts new file mode 100644 index 0000000..60983be --- /dev/null +++ b/src/components/ConfirmationModalDialog/ConfirmationModalDialog.ts @@ -0,0 +1,79 @@ +import { defineComponent } from 'vue'; + +import Heading from '../Heading/Heading.vue'; +import ScrollWrapper from '../ScrollWrapper/ScrollWrapper.vue'; +import TextParagraph from '../TextParagraph/TextParagraph.vue'; + +const ConfirmationModalDialog = defineComponent({ + name: 'ConfirmationModalDialog', + components: { + Heading, + ScrollWrapper, + TextParagraph, + }, + props: { + dataTestId: { + type: String, + default: 'dialog', + }, + title: { + type: String, + default: '', + }, + description: { + type: String, + default: '', + }, + confirmButtonText: { + type: String, + default: '', + }, + dismissButtonText: { + type: String, + default: '', + }, + modelValue: Boolean, + loading: Boolean, + confirmButtonDisabled: { + type: Boolean, + default: false, + }, + dismissButtonDisabled: { + type: Boolean, + default: false, + }, + }, + emits: ['update:modelValue', 'close', 'confirm', 'dismiss'], + data() { + return { + isShowing: this.modelValue, + }; + }, + methods: { + emitUpdateModelValueEvent() { + this.$emit('update:modelValue', this.isShowing); + if (!this.isShowing) { this.$emit('close'); } + }, + onConfirm() { + this.$emit('confirm'); + this.hideDialog(); + }, + onDismiss() { + this.$emit('dismiss'); + this.hideDialog(); + }, + hideDialog() { + this.isShowing = false; + }, + }, + watch: { + isShowing() { + this.emitUpdateModelValueEvent(); + }, + modelValue() { + this.isShowing = this.modelValue; + }, + }, +}); + +export default ConfirmationModalDialog; diff --git a/src/components/ConfirmationModalDialog/ConfirmationModalDialog.vue b/src/components/ConfirmationModalDialog/ConfirmationModalDialog.vue index 6a88a37..cf76cf8 100644 --- a/src/components/ConfirmationModalDialog/ConfirmationModalDialog.vue +++ b/src/components/ConfirmationModalDialog/ConfirmationModalDialog.vue @@ -47,5 +47,5 @@ - + diff --git a/src/components/ConfirmationModalDialog/UnitTestWrappers/ConfirmationModalDialogWrapper.vue b/src/components/ConfirmationModalDialog/UnitTestWrappers/ConfirmationModalDialogWrapper.vue index a483aff..9bfcb5c 100644 --- a/src/components/ConfirmationModalDialog/UnitTestWrappers/ConfirmationModalDialogWrapper.vue +++ b/src/components/ConfirmationModalDialog/UnitTestWrappers/ConfirmationModalDialogWrapper.vue @@ -9,24 +9,27 @@ Click Me - diff --git a/src/components/ConfirmationToastDialog/ConfirmationToastDialog.js b/src/components/ConfirmationToastDialog/ConfirmationToastDialog.js deleted file mode 100644 index 0731111..0000000 --- a/src/components/ConfirmationToastDialog/ConfirmationToastDialog.js +++ /dev/null @@ -1,80 +0,0 @@ -import Button from '../Button/Button.vue'; -import Heading from '../Heading/Heading.vue'; -import ScrollWrapper from '../ScrollWrapper/ScrollWrapper.vue'; -import TextParagraph from '../TextParagraph/TextParagraph.vue'; -import Wrapper from '../Wrapper/Wrapper.vue'; - -const components = { - Button, - Heading, - ScrollWrapper, - TextParagraph, - Wrapper, -}; - -const props = { - dataTestId: { - type: String, - default: 'bottom-dialog', - }, - title: String, - description: String, - confirmButtonText: String, - secondaryButtonText: String, - modelValue: Boolean, - loading: Boolean, - loadingText: { - type: String, - default: 'Cargando...', - }, - disabled: Boolean, -}; - -const emits = ['update:modelValue', 'dismiss', 'confirm', 'secondary']; - -const data = function () { - return { - isShowing: this.modelValue, - }; -}; - -const methods = { - emitUpdateModelValueEvent() { - this.$emit('update:modelValue', this.isShowing); - if (!this.isShowing) { this.$emit('dismiss'); } - }, - onConfirm() { - this.$emit('confirm'); - this.dismissDialog(); - }, - onSecondary() { - this.$emit('secondary'); - this.dismissDialog(); - }, - dismissDialog() { - this.isShowing = false; - }, -}; - -const watch = { - isShowing() { - this.emitUpdateModelValueEvent(); - }, - modelValue() { - this.isShowing = this.modelValue; - }, -}; - -const name = 'ConfirmationToastDialog'; - -const ConfirmationToastDialog = { - name, - components, - props, - emits, - data, - methods, - watch, -}; - -export default ConfirmationToastDialog; diff --git a/src/components/ConfirmationToastDialog/ConfirmationToastDialog.stories.js b/src/components/ConfirmationToastDialog/ConfirmationToastDialog.stories.ts similarity index 97% rename from src/components/ConfirmationToastDialog/ConfirmationToastDialog.stories.js rename to src/components/ConfirmationToastDialog/ConfirmationToastDialog.stories.ts index 95c3f74..cabaed0 100644 --- a/src/components/ConfirmationToastDialog/ConfirmationToastDialog.stories.js +++ b/src/components/ConfirmationToastDialog/ConfirmationToastDialog.stories.ts @@ -1,4 +1,5 @@ import { action } from '@storybook/addon-actions'; +import type { Meta, StoryFn } from '@storybook/vue3'; import ConfirmationToastDialog from './ConfirmationToastDialog.vue'; import RenderString from '../../lib/renderString'; @@ -49,9 +50,9 @@ const ConfirmationToastDialogStories = { }, }, }, -}; +} as Meta; -const Template = (args, { argTypes }) => ({ +const Template: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/ConfirmationToastDialog/ConfirmationToastDialog.test.js b/src/components/ConfirmationToastDialog/ConfirmationToastDialog.test.ts similarity index 91% rename from src/components/ConfirmationToastDialog/ConfirmationToastDialog.test.js rename to src/components/ConfirmationToastDialog/ConfirmationToastDialog.test.ts index 650d395..257d279 100644 --- a/src/components/ConfirmationToastDialog/ConfirmationToastDialog.test.js +++ b/src/components/ConfirmationToastDialog/ConfirmationToastDialog.test.ts @@ -32,27 +32,24 @@ describe('ConfirmationToastDialog unit test', () => { expect(dialogIsNotVisible).toBeTruthy(); }); - it('Should set to visible if given value is set to true', () => new Promise((resolve) => { + it('Should set to visible if given value is set to true', async () => { const { getByTestId } = render(ConfirmationToastDialogWrapper); const openModal = getByTestId('open-modal'); - fireEvent.click(openModal); - setTimeout(() => { - const dialog = getByTestId('bottom-dialog-section'); - const dialogIsVisible = dialog.className.includes('visible'); - expect(dialogIsVisible).toBeTruthy(); - resolve(); - }); - })); + await fireEvent.click(openModal); + const dialog = getByTestId('bottom-dialog-section'); + const dialogIsVisible = dialog.className.includes('visible'); + expect(dialogIsVisible).toBeTruthy(); + }); it('Should show title if provided', () => { const { getByTestId } = render(ConfirmationToastDialog, { props: { ...defaultProps } }); - const titleIsShown = getByTestId('bottom-dialog-title').textContent.includes('title'); + const titleIsShown = getByTestId('bottom-dialog-title').textContent?.includes('title'); expect(titleIsShown).toBeTruthy(); }); it('Should show description if provided', () => { const { getByTestId } = render(ConfirmationToastDialog, { props: { ...defaultProps } }); - const descriptionIsShown = getByTestId('bottom-dialog-description').textContent.includes('description'); + const descriptionIsShown = getByTestId('bottom-dialog-description').textContent?.includes('description'); expect(descriptionIsShown).toBeTruthy(); }); @@ -64,7 +61,7 @@ describe('ConfirmationToastDialog unit test', () => { it('Should show given children if description is not provided', () => { const { getByTestId } = render(ConfirmationToastDialog, { slots: { default: 'Hey!' }, props: { ...defaultProps, description: null } }); - const childrenIsShown = getByTestId('confirmation-modal-dialog-slot').textContent.includes('Hey!'); + const childrenIsShown = getByTestId('confirmation-modal-dialog-slot').textContent?.includes('Hey!'); expect(childrenIsShown).toBeTruthy(); }); @@ -79,7 +76,7 @@ describe('ConfirmationToastDialog unit test', () => { describe('Confirm actions behavior', () => { it('Should show action-confirm button when confirm is given', () => { const { getByTestId } = render(ConfirmationToastDialog, { props: { ...defaultProps } }); - const confirmIsShown = getByTestId('bottom-dialog-action-confirm-button').textContent.includes('confirm'); + const confirmIsShown = getByTestId('bottom-dialog-action-confirm-button').textContent?.includes('confirm'); expect(confirmIsShown).toBeTruthy(); }); @@ -104,7 +101,7 @@ describe('ConfirmationToastDialog unit test', () => { describe('Secondary actions behavior', () => { it('Should show bottom-dialog-action-secondary button when secondaryText is given', () => { const { getByTestId } = render(ConfirmationToastDialog, { props: { ...defaultProps } }); - const dismissIsShown = getByTestId('bottom-dialog-action-secondary').textContent.includes('secondary'); + const dismissIsShown = getByTestId('bottom-dialog-action-secondary').textContent?.includes('secondary'); expect(dismissIsShown).toBeTruthy(); }); diff --git a/src/components/ConfirmationToastDialog/ConfirmationToastDialog.ts b/src/components/ConfirmationToastDialog/ConfirmationToastDialog.ts new file mode 100644 index 0000000..fdac818 --- /dev/null +++ b/src/components/ConfirmationToastDialog/ConfirmationToastDialog.ts @@ -0,0 +1,80 @@ +import { defineComponent } from 'vue'; + +import Button from '../Button/Button.vue'; +import Heading from '../Heading/Heading.vue'; +import ScrollWrapper from '../ScrollWrapper/ScrollWrapper.vue'; +import TextParagraph from '../TextParagraph/TextParagraph.vue'; +import Wrapper from '../Wrapper/Wrapper.vue'; + +const ConfirmationToastDialog = defineComponent({ + name: 'ConfirmationToastDialog', + components: { + Button, + Heading, + ScrollWrapper, + TextParagraph, + Wrapper, + }, + props: { + dataTestId: { + type: String, + default: 'bottom-dialog', + }, + title: { + type: String, + default: '', + }, + description: { + type: String, + default: '', + }, + confirmButtonText: { + type: String, + default: '', + }, + secondaryButtonText: { + type: String, + default: '', + }, + modelValue: Boolean, + loading: Boolean, + loadingText: { + type: String, + default: 'Cargando...', + }, + disabled: Boolean, + }, + emits: ['update:modelValue', 'dismiss', 'confirm', 'secondary'], + data() { + return { + isShowing: this.modelValue, + }; + }, + methods: { + emitUpdateModelValueEvent() { + this.$emit('update:modelValue', this.isShowing); + if (!this.isShowing) { this.$emit('dismiss'); } + }, + onConfirm() { + this.$emit('confirm'); + this.dismissDialog(); + }, + onSecondary() { + this.$emit('secondary'); + this.dismissDialog(); + }, + dismissDialog() { + this.isShowing = false; + }, + }, + watch: { + isShowing() { + this.emitUpdateModelValueEvent(); + }, + modelValue() { + this.isShowing = this.modelValue; + }, + }, +}); + +export default ConfirmationToastDialog; diff --git a/src/components/ConfirmationToastDialog/ConfirmationToastDialog.vue b/src/components/ConfirmationToastDialog/ConfirmationToastDialog.vue index 8e8b34e..d2050a7 100644 --- a/src/components/ConfirmationToastDialog/ConfirmationToastDialog.vue +++ b/src/components/ConfirmationToastDialog/ConfirmationToastDialog.vue @@ -52,5 +52,5 @@ - + diff --git a/src/components/ConfirmationToastDialog/UnitTestWrappers/ConfirmationToastDialogWrapper.vue b/src/components/ConfirmationToastDialog/UnitTestWrappers/ConfirmationToastDialogWrapper.vue index ddf59d7..e352246 100644 --- a/src/components/ConfirmationToastDialog/UnitTestWrappers/ConfirmationToastDialogWrapper.vue +++ b/src/components/ConfirmationToastDialog/UnitTestWrappers/ConfirmationToastDialogWrapper.vue @@ -8,24 +8,27 @@ Click Me - diff --git a/src/components/ContentItem/ContentItem.js b/src/components/ContentItem/ContentItem.js deleted file mode 100644 index 74208d9..0000000 --- a/src/components/ContentItem/ContentItem.js +++ /dev/null @@ -1,92 +0,0 @@ -import { ChevronRightIcon, SuccessMicroillustration as Success } from '@lana/b2c-mapp-ui-assets'; - -import Heading from '../Heading/Heading.vue'; -import TextParagraph from '../TextParagraph/TextParagraph.vue'; - -const components = { - ChevronRightIcon, - Success, - Heading, - TextParagraph, -}; - -const props = { - dataTestId: { - type: String, - default: 'content-item', - }, - title: { - type: String, - default: '', - }, - metaText: { - type: String, - default: '', - }, - hasForwardButton: { - type: Boolean, - default: true, - }, - success: { - type: Boolean, - default: false, - }, - disabled: Boolean, - noBorder: Boolean, -}; - -const emits = ['click']; - -const data = function () { - return { - isPressed: false, - }; -}; - -const computed = { - hasDefaultSlot() { - const result = this.$slots.default && this.$slots.default().findIndex((node) => (node.type !== Comment)) !== -1; - return result; - }, - hasIcon() { - const result = (this.success || this.hasForwardButton); - return result; - }, - hasMetaText() { - const result = (this.metaText || (this.$slots.customMetaText && this.$slots.customMetaText().findIndex((node) => (node.type !== Comment)) !== -1)); - return result; - }, - iconName() { - const result = (this.hasForwardButton && !this.success) ? 'ChevronRightIcon' : 'Success'; - return result; - }, - iconDataTestId() { - const result = `${(this.success) ? 'success-' : ''}${this.dataTestId}-forward-icon`; - return result; - }, -}; - -const methods = { - toggleIsPressed() { - this.isPressed = !this.isPressed; - }, - emitClickEvent(event) { - if (this.disabled || this.success) { return; } - this.isPressed = false; - this.$emit('click', event); - }, -}; - -const name = 'ContentItem'; - -const ContentItem = { - name, - components, - props, - emits, - data, - computed, - methods, -}; - -export default ContentItem; diff --git a/src/components/ContentItem/ContentItem.stories.js b/src/components/ContentItem/ContentItem.stories.ts similarity index 94% rename from src/components/ContentItem/ContentItem.stories.js rename to src/components/ContentItem/ContentItem.stories.ts index f81c31e..702d4e9 100644 --- a/src/components/ContentItem/ContentItem.stories.js +++ b/src/components/ContentItem/ContentItem.stories.ts @@ -1,5 +1,6 @@ import { action } from '@storybook/addon-actions'; import { DocumentFilledIcon } from '@lana/b2c-mapp-ui-assets'; +import type { Meta, StoryFn } from '@storybook/vue3'; import ContentItem from './ContentItem.vue'; import { createDeviceDecorator } from '../../lib/storybookHelpers'; @@ -39,9 +40,9 @@ const ContentItemStories = { forwardIcon: { control: { type: 'text' }, table: { type: { summary: null } } }, extraItem: { control: { type: 'text' }, table: { type: { summary: null } } }, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { @@ -101,7 +102,7 @@ defaultExample.parameters = { }, }; -const withImage = (args, { argTypes }) => ({ +const withImage: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { @@ -153,7 +154,7 @@ withImage.parameters = { }, }; -const withIcon = (args, { argTypes }) => ({ +const withIcon: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { @@ -209,7 +210,7 @@ withIcon.parameters = { }, }; -const successState = (args, { argTypes }) => ({ +const successState: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { @@ -265,7 +266,7 @@ successState.parameters = { }, }; -const withCustomForwardIcon = (args, { argTypes }) => ({ +const withCustomForwardIcon: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { @@ -328,7 +329,7 @@ withCustomForwardIcon.parameters = { }, }; -const noImageAndCustomForwardIcon = (args, { argTypes }) => ({ +const noImageAndCustomForwardIcon: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { @@ -385,7 +386,7 @@ noImageAndCustomForwardIcon.parameters = { }, }; -const customTitleAndCustomMetaText = (args, { argTypes }) => ({ +const customTitleAndCustomMetaText: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/ContentItem/ContentItem.test.js b/src/components/ContentItem/ContentItem.test.ts similarity index 98% rename from src/components/ContentItem/ContentItem.test.js rename to src/components/ContentItem/ContentItem.test.ts index f6d26c8..dca2679 100644 --- a/src/components/ContentItem/ContentItem.test.js +++ b/src/components/ContentItem/ContentItem.test.ts @@ -31,7 +31,7 @@ describe('ContentItem unit test', () => { it('Should show given meta info', () => { const { getByTestId } = render(ContentItem, { props: { ...defaultProps } }); - const metaInfoExist = getByTestId('content-item-meta-text').textContent.includes('META'); + const metaInfoExist = getByTestId('content-item-meta-text').textContent?.includes('META'); expect(metaInfoExist).toBeTruthy(); }); @@ -97,7 +97,7 @@ describe('ContentItem unit test', () => { { slots: { forwardIcon: '' }, props: { ...defaultProps }, - stubs: ['ClockColorIcon'], + global: { stubs: ['ClockColorIcon'] }, }, ); const customForwardIconVisible = getByTestId('custom-forward-icon'); diff --git a/src/components/ContentItem/ContentItem.ts b/src/components/ContentItem/ContentItem.ts new file mode 100644 index 0000000..df01d27 --- /dev/null +++ b/src/components/ContentItem/ContentItem.ts @@ -0,0 +1,79 @@ +import { ChevronRightIcon, SuccessMicroillustration as Success } from '@lana/b2c-mapp-ui-assets'; +import { defineComponent } from 'vue'; + +import Heading from '../Heading/Heading.vue'; +import TextParagraph from '../TextParagraph/TextParagraph.vue'; + +const ContentItem = defineComponent({ + name: 'ContentItem', + components: { + ChevronRightIcon, + Success, + Heading, + TextParagraph, + }, + props: { + dataTestId: { + type: String, + default: 'content-item', + }, + title: { + type: String, + default: '', + }, + metaText: { + type: String, + default: '', + }, + hasForwardButton: { + type: Boolean, + default: true, + }, + success: { + type: Boolean, + default: false, + }, + disabled: Boolean, + noBorder: Boolean, + }, + emits: ['click'], + data() { + return { + isPressed: false, + }; + }, + computed: { + hasDefaultSlot() { + const result = this.$slots.default && this.$slots.default().findIndex((node) => (node.type !== Comment)) !== -1; + return result; + }, + hasIcon() { + const result = (this.success || this.hasForwardButton); + return result; + }, + hasMetaText() { + const result = (this.metaText || (this.$slots.customMetaText && this.$slots.customMetaText().findIndex((node) => (node.type !== Comment)) !== -1)); + return result; + }, + iconName() { + const result = (this.hasForwardButton && !this.success) ? 'ChevronRightIcon' : 'Success'; + return result; + }, + iconDataTestId() { + const result = `${(this.success) ? 'success-' : ''}${this.dataTestId}-forward-icon`; + return result; + }, + }, + methods: { + toggleIsPressed() { + this.isPressed = !this.isPressed; + }, + emitClickEvent(event: MouseEvent) { + if (this.disabled || this.success) { return; } + this.isPressed = false; + this.$emit('click', event); + }, + }, +}); + +export default ContentItem; diff --git a/src/components/ContentItem/ContentItem.vue b/src/components/ContentItem/ContentItem.vue index 5346637..03fe324 100644 --- a/src/components/ContentItem/ContentItem.vue +++ b/src/components/ContentItem/ContentItem.vue @@ -36,5 +36,5 @@ - + diff --git a/src/components/ContentRadioList/ContentRadioList.js b/src/components/ContentRadioList/ContentRadioList.js deleted file mode 100644 index f1e732e..0000000 --- a/src/components/ContentRadioList/ContentRadioList.js +++ /dev/null @@ -1,64 +0,0 @@ -import { ChevronRightIcon, SuccessMicroillustration as Success } from '@lana/b2c-mapp-ui-assets'; - -import Heading from '../Heading/Heading.vue'; -import TextParagraph from '../TextParagraph/TextParagraph.vue'; - -const components = { - ChevronRightIcon, - Success, - Heading, - TextParagraph, -}; - -const props = { - dataTestId: { - type: String, - default: 'selection-list', - }, - options: { - type: Array, - default: () => [], - }, - id: { - type: String, - required: true, - }, - modelValue: [String, Number], -}; - -const emits = ['update:modelValue']; - -const data = function () { - return { - selectedValue: this.modelValue, - }; -}; - -const methods = { - emitUpdateModelValueEvent() { - this.$emit('update:modelValue', this.selectedValue); - }, -}; - -const watch = { - selectedValue() { - this.emitUpdateModelValueEvent(); - }, - modelValue() { - this.selectedValue = this.modelValue; - }, -}; - -const name = 'ContentRadioList'; - -const ContentRadioList = { - name, - components, - props, - emits, - data, - methods, - watch, -}; - -export default ContentRadioList; diff --git a/src/components/ContentRadioList/ContentRadioList.stories.js b/src/components/ContentRadioList/ContentRadioList.stories.ts similarity index 96% rename from src/components/ContentRadioList/ContentRadioList.stories.js rename to src/components/ContentRadioList/ContentRadioList.stories.ts index 7fa4a31..9c6c5f4 100644 --- a/src/components/ContentRadioList/ContentRadioList.stories.js +++ b/src/components/ContentRadioList/ContentRadioList.stories.ts @@ -1,4 +1,5 @@ import { action } from '@storybook/addon-actions'; +import type { Meta, StoryFn } from '@storybook/vue3'; import ContentRadioList from './ContentRadioList.vue'; import RenderString from '../../lib/renderString'; @@ -42,9 +43,9 @@ const ContentRadioListStories = { }, }, }, -}; +} as Meta; -const Template = (args, { argTypes }) => ({ +const Template: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/ContentRadioList/ContentRadioList.test.js b/src/components/ContentRadioList/ContentRadioList.test.ts similarity index 80% rename from src/components/ContentRadioList/ContentRadioList.test.js rename to src/components/ContentRadioList/ContentRadioList.test.ts index 2afc711..996c865 100644 --- a/src/components/ContentRadioList/ContentRadioList.test.js +++ b/src/components/ContentRadioList/ContentRadioList.test.ts @@ -23,7 +23,7 @@ describe('UI/forms/ContentRadioList', () => { it('Should NOT apply selected given option initially as selected if theres a given value', () => { const { queryAllByTestId } = render(ContentRadioList, { props: { ...defaultProps, modelValue: 'option_2' } }); const selectedOptions = queryAllByTestId('selection-list-option'); - const firstOptionIsNotSelectedByDefault = !selectedOptions[0].classList.contains('checked'); + const firstOptionIsNotSelectedByDefault = !selectedOptions[0]?.classList.contains('checked'); expect(firstOptionIsNotSelectedByDefault).toBeTruthy(); }); @@ -33,22 +33,22 @@ describe('UI/forms/ContentRadioList', () => { await wrapper.vm.$nextTick(); wrapper.vm.$options.watch.modelValue.call(wrapper.vm, givenValue); await wrapper.vm.$nextTick(); - const appliedGivenValue = wrapper.vm.$data.selectedValue === givenValue; + const appliedGivenValue = wrapper.vm.selectedValue === givenValue; expect(appliedGivenValue).toBeTruthy(); }); it('Should apply selected option based on given value', () => { const { queryAllByTestId } = render(ContentRadioList, { props: { ...defaultProps, modelValue: 'option_2' } }); const selectedOptions = queryAllByTestId('selection-list-option'); - const secondOptionIsSelected = selectedOptions[1].className.includes('checked'); + const secondOptionIsSelected = selectedOptions[1]?.className.includes('checked'); expect(secondOptionIsSelected).toBeTruthy(); }); it('Should NOT apply selected option based on wrong given value', () => { const { queryAllByTestId } = render(ContentRadioList, { props: { ...defaultProps, modelValue: 'option_x' } }); const selectedOptions = queryAllByTestId('selection-list-option'); - const firstOptionIsNotSelected = !selectedOptions[0].classList.contains('checked'); - const secondOptionIsNotSelected = !selectedOptions[1].classList.contains('checked'); + const firstOptionIsNotSelected = !selectedOptions[0]?.classList.contains('checked'); + const secondOptionIsNotSelected = !selectedOptions[1]?.classList.contains('checked'); const optionsAreNotSelected = firstOptionIsNotSelected && secondOptionIsNotSelected; expect(optionsAreNotSelected).toBeTruthy(); }); @@ -63,8 +63,8 @@ describe('UI/forms/ContentRadioList', () => { ); await wrapper.vm.$nextTick(); const selectedOptions = wrapper.findAll('li[data-testid="selection-list-option"]'); - const customCheckedIconVisible = selectedOptions[1].find('span[data-testid="custom-checked-icon"]'); - const customCheckedIconNotVisibleOnUncheckedOption = selectedOptions[0].find('span[data-testid="custom-checked-icon"]'); + const customCheckedIconVisible = selectedOptions[1]?.find('span[data-testid="custom-checked-icon"]'); + const customCheckedIconNotVisibleOnUncheckedOption = selectedOptions[0]?.find('span[data-testid="custom-checked-icon"]'); expect(customCheckedIconVisible && customCheckedIconNotVisibleOnUncheckedOption).toBeTruthy(); }); it('Should show custom icon for uncheckedValue', async () => { @@ -77,8 +77,8 @@ describe('UI/forms/ContentRadioList', () => { ); await wrapper.vm.$nextTick(); const selectedOptions = wrapper.findAll('li[data-testid="selection-list-option"]'); - const customCheckedIconVisible = selectedOptions[0].find('span[data-testid="custom-unchecked-icon"]'); - const customCheckedIconNotVisibleOnUncheckedOption = selectedOptions[1].find('span[data-testid="custom-unchecked-icon"]'); + const customCheckedIconVisible = selectedOptions[0]?.find('span[data-testid="custom-unchecked-icon"]'); + const customCheckedIconNotVisibleOnUncheckedOption = selectedOptions[1]?.find('span[data-testid="custom-unchecked-icon"]'); expect(customCheckedIconVisible && customCheckedIconNotVisibleOnUncheckedOption).toBeTruthy(); }); @@ -90,10 +90,9 @@ describe('UI/forms/ContentRadioList', () => { const options = wrapper.findAll('li'); const firstOption = options[0]; const secondOption = options[1]; - firstOptionInput.setChecked(); - await wrapper.vm.$nextTick(); - const firstOptionSelected = firstOption.classes().includes('checked'); - const secondOptionNotSelected = !secondOption.classes().includes('checked'); + await firstOptionInput?.setValue(true); + const firstOptionSelected = firstOption?.classes().includes('checked'); + const secondOptionNotSelected = !secondOption?.classes().includes('checked'); const clickedOptionIsSelected = firstOptionSelected && secondOptionNotSelected; expect(clickedOptionIsSelected).toBeTruthy(); }); @@ -102,8 +101,7 @@ describe('UI/forms/ContentRadioList', () => { const wrapper = mount(ContentRadioList, { props: { ...defaultProps, modelValue: 'option_2' } }); await wrapper.vm.$nextTick(); const firstOptionInput = wrapper.findAll('input')[0]; - firstOptionInput.setChecked(); - await wrapper.vm.$nextTick(); + await firstOptionInput?.setValue(true); const clickEmitted = wrapper.emitted('update:modelValue'); expect(clickEmitted).toBeTruthy(); }); @@ -112,9 +110,8 @@ describe('UI/forms/ContentRadioList', () => { const wrapper = mount(ContentRadioList, { props: { ...defaultProps, modelValue: 'option_2' } }); await wrapper.vm.$nextTick(); const firstOptionInput = wrapper.findAll('input')[0]; - firstOptionInput.setChecked(); - await wrapper.vm.$nextTick(); - const emittedValue = wrapper.emitted('update:modelValue')[0][0] === 'option_1'; - expect(emittedValue).toBeTruthy(); + await firstOptionInput?.setValue(true); + const [emittedValue] = wrapper.emitted('update:modelValue')?.[0] as string[]; + expect(emittedValue).toBe('option_1'); }); }); diff --git a/src/components/ContentRadioList/ContentRadioList.ts b/src/components/ContentRadioList/ContentRadioList.ts new file mode 100644 index 0000000..9816e44 --- /dev/null +++ b/src/components/ContentRadioList/ContentRadioList.ts @@ -0,0 +1,62 @@ +import { ChevronRightIcon, SuccessMicroillustration as Success } from '@lana/b2c-mapp-ui-assets'; +import type { PropType } from 'vue'; +import { defineComponent } from 'vue'; + +import Heading from '../Heading/Heading.vue'; +import TextParagraph from '../TextParagraph/TextParagraph.vue'; + +interface ContentRadioOption { + title?: string, + metaText?: string, + value: string | number, + disabled?: boolean +} + +const ContentRadioList = defineComponent({ + name: 'ContentRadioList', + components: { + ChevronRightIcon, + Success, + Heading, + TextParagraph, + }, + props: { + dataTestId: { + type: String, + default: 'selection-list', + }, + options: { + type: Array as PropType, + default: () => [] as ContentRadioOption[], + }, + id: { + type: String, + required: true, + }, + modelValue: { + type: [String, Number], + default: null, + }, + }, + emits: ['update:modelValue'], + data() { + return { + selectedValue: this.modelValue, + }; + }, + methods: { + emitUpdateModelValueEvent() { + this.$emit('update:modelValue', this.selectedValue); + }, + }, + watch: { + selectedValue() { + this.emitUpdateModelValueEvent(); + }, + modelValue() { + this.selectedValue = this.modelValue; + }, + }, +}); + +export default ContentRadioList; diff --git a/src/components/ContentRadioList/ContentRadioList.vue b/src/components/ContentRadioList/ContentRadioList.vue index 2567746..0688447 100644 --- a/src/components/ContentRadioList/ContentRadioList.vue +++ b/src/components/ContentRadioList/ContentRadioList.vue @@ -48,5 +48,5 @@ - + diff --git a/src/components/CopyToClipboardButton/CopyToClipboardButton.js b/src/components/CopyToClipboardButton/CopyToClipboardButton.js deleted file mode 100644 index 646f9dc..0000000 --- a/src/components/CopyToClipboardButton/CopyToClipboardButton.js +++ /dev/null @@ -1,113 +0,0 @@ -import { copyTextToClipboard } from '../../lib/copyToClipboard'; - -const copyStatesLookup = { - toCopy: 'toCopy', - copying: 'copying', - copied: 'copied', -}; - -const props = { - dataTestId: { - type: String, - default: 'copy-to-clipboard', - }, - valueToCopy: { - type: String, - required: true, - }, - toCopyLabel: { - type: String, - default: 'Copiar', - }, - copyingLabel: { - type: String, - default: 'Copiando...', - }, - copiedLabel: { - type: String, - default: 'Copiado!', - }, - toCopyClass: { - type: String, - default: 'tocopy', - }, - copyingClass: { - type: String, - default: 'copying', - }, - copiedClass: { - type: String, - default: 'copied', - }, - copyingFeedbackDelay: { - type: Number, - default: 1000, - }, - copiedFeedbackDelay: { - type: Number, - default: 2500, - }, - disabled: Boolean, - id: String, - name: String, -}; - -const emits = ['click']; - -const data = function () { - return { - currentCopyState: copyStatesLookup.toCopy, - }; -}; - -const computed = { - copyStatusTextAndClass() { - switch (this.currentCopyState) { - case (copyStatesLookup.copying): return { text: this.copyingLabel, class: this.copyingClass }; - case (copyStatesLookup.copied): return { text: this.copiedLabel, class: this.copiedClass }; - default: - case (copyStatesLookup.toCopy): return { text: this.toCopyLabel, class: this.toCopyClass }; - } - }, - copyStatusText() { - const { text: result } = this.copyStatusTextAndClass; - return result; - }, - copyStatusClass() { - const { class: result } = this.copyStatusTextAndClass; - return result; - }, -}; - -const methods = { - resetCopyState() { - this.currentCopyState = copyStatesLookup.toCopy; - }, - updateTextAfterCopying() { - this.currentCopyState = copyStatesLookup.copied; - setTimeout(this.resetCopyState, this.copiedFeedbackDelay); - }, - copyValueToClipBoard() { - copyTextToClipboard(this.valueToCopy); - this.updateTextAfterCopying(); - }, - onClick(event) { - if (this.currentCopyState === copyStatesLookup.copying) { return; } - this.currentCopyState = copyStatesLookup.copying; - setTimeout(this.copyValueToClipBoard, this.copyingFeedbackDelay); - this.$emit('click', event); - }, -}; - -const name = 'CopyToClipboardButton'; - -const CopyToClipboardButton = { - name, - props, - emits, - data, - computed, - methods, -}; - -export default CopyToClipboardButton; diff --git a/src/components/CopyToClipboardButton/CopyToClipboardButton.stories.js b/src/components/CopyToClipboardButton/CopyToClipboardButton.stories.ts similarity index 93% rename from src/components/CopyToClipboardButton/CopyToClipboardButton.stories.js rename to src/components/CopyToClipboardButton/CopyToClipboardButton.stories.ts index 3cb3bfc..7517f6c 100644 --- a/src/components/CopyToClipboardButton/CopyToClipboardButton.stories.js +++ b/src/components/CopyToClipboardButton/CopyToClipboardButton.stories.ts @@ -1,4 +1,5 @@ import { action } from '@storybook/addon-actions'; +import type { Meta, StoryFn } from '@storybook/vue3'; import CopyToClipboardButton from './CopyToClipboardButton.vue'; import { createDeviceDecorator } from '../../lib/storybookHelpers'; @@ -29,9 +30,9 @@ const CopyToClipboardButtonStories = { copyingFeedbackDelay: { name: 'Delay for showing "Copying" in milliseconds', control: 'number' }, copiedFeedbackDelay: { name: 'Delay for showing "Copied" in milliseconds', control: 'number' }, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/CopyToClipboardButton/CopyToClipboardButton.test.js b/src/components/CopyToClipboardButton/CopyToClipboardButton.test.ts similarity index 89% rename from src/components/CopyToClipboardButton/CopyToClipboardButton.test.js rename to src/components/CopyToClipboardButton/CopyToClipboardButton.test.ts index b1991fc..6c2c016 100644 --- a/src/components/CopyToClipboardButton/CopyToClipboardButton.test.js +++ b/src/components/CopyToClipboardButton/CopyToClipboardButton.test.ts @@ -16,7 +16,7 @@ describe('CopyToClipboardButton unit test', () => { jest.useFakeTimers(); const wrapper = mount(CopyToClipboardButton, { props: { toCopyValue: 'myValue' } }); const button = wrapper.find('button[data-testid="copy-to-clipboard-button"]'); - button.trigger('click'); + await button.trigger('click'); jest.runAllTimers(); await wrapper.vm.$nextTick(); const clickIsEmitted = wrapper.emitted().click; @@ -42,9 +42,9 @@ describe('CopyToClipboardButton unit test', () => { jest.useFakeTimers(); const wrapper = mount(CopyToClipboardButton, { props: { toCopyValue: 'myValue', copyingLabel: givenLabel } }); const button = wrapper.find('button[data-testid="copy-to-clipboard-button"]'); - button.element.click(); + await button.trigger('click'); await wrapper.vm.$forceUpdate(); - jest.runTimersToTime('1000'); + jest.advanceTimersByTime(1000); const givenLabelIsShown = button.text().includes(givenLabel); expect(givenLabelIsShown).toBeTruthy(); }); @@ -54,11 +54,11 @@ describe('CopyToClipboardButton unit test', () => { jest.useFakeTimers(); const wrapper = mount(CopyToClipboardButton, { props: { toCopyValue: 'myValue', copyingLabel: givenLabel } }); const button = wrapper.find('button[data-testid="copy-to-clipboard-button"]'); - button.element.click(); + await button.trigger('click'); await wrapper.vm.$forceUpdate(); - button.element.click(); - jest.runTimersToTime('1000'); - const onlyOneClickEmitted = wrapper.emitted().click.length === 1; + await button.trigger('click'); + jest.advanceTimersByTime(1000); + const onlyOneClickEmitted = wrapper.emitted('click')?.length === 1; expect(onlyOneClickEmitted).toBeTruthy(); }); @@ -67,9 +67,9 @@ describe('CopyToClipboardButton unit test', () => { jest.useFakeTimers(); const wrapper = mount(CopyToClipboardButton, { props: { toCopyValue: 'myValue', copyingClass: givenClass } }); const button = wrapper.find('button[data-testid="copy-to-clipboard-button"]'); - button.element.click(); + await button.trigger('click'); await wrapper.vm.$forceUpdate(); - jest.runTimersToTime('1000'); + jest.advanceTimersByTime(1000); const givenClassIsShown = button.classes().includes(givenClass); expect(givenClassIsShown).toBeTruthy(); }); @@ -79,8 +79,8 @@ describe('CopyToClipboardButton unit test', () => { jest.useFakeTimers(); const wrapper = mount(CopyToClipboardButton, { props: { toCopyValue: 'myValue', copiedLabel: givenLabel } }); const button = wrapper.find('button[data-testid="copy-to-clipboard-button"]'); - button.element.click(); - jest.runTimersToTime('1500'); + await button.trigger('click'); + jest.advanceTimersByTime(1500); await wrapper.vm.$nextTick(); const givenLabelIsShown = button.text().includes(givenLabel); expect(givenLabelIsShown).toBeTruthy(); @@ -91,8 +91,8 @@ describe('CopyToClipboardButton unit test', () => { jest.useFakeTimers(); const wrapper = mount(CopyToClipboardButton, { props: { toCopyValue: 'myValue', copiedClass: givenClass } }); const button = wrapper.find('button[data-testid="copy-to-clipboard-button"]'); - button.element.click(); - jest.runTimersToTime('1500'); + await button.trigger('click'); + jest.advanceTimersByTime(1500); await wrapper.vm.$nextTick(); const givenClassIsApplied = button.classes().includes(givenClass); expect(givenClassIsApplied).toBeTruthy(); diff --git a/src/components/CopyToClipboardButton/CopyToClipboardButton.ts b/src/components/CopyToClipboardButton/CopyToClipboardButton.ts new file mode 100644 index 0000000..9acd0fa --- /dev/null +++ b/src/components/CopyToClipboardButton/CopyToClipboardButton.ts @@ -0,0 +1,109 @@ +import { defineComponent } from 'vue'; + +import { copyTextToClipboard } from '../../lib/copyToClipboard'; + +const copyStatesLookup = { + toCopy: 'toCopy', + copying: 'copying', + copied: 'copied', +}; + +const CopyToClipboardButton = defineComponent({ + name: 'CopyToClipboardButton', + props: { + dataTestId: { + type: String, + default: 'copy-to-clipboard', + }, + valueToCopy: { + type: String, + required: true, + }, + toCopyLabel: { + type: String, + default: 'Copiar', + }, + copyingLabel: { + type: String, + default: 'Copiando...', + }, + copiedLabel: { + type: String, + default: 'Copiado!', + }, + toCopyClass: { + type: String, + default: 'tocopy', + }, + copyingClass: { + type: String, + default: 'copying', + }, + copiedClass: { + type: String, + default: 'copied', + }, + copyingFeedbackDelay: { + type: Number, + default: 1000, + }, + copiedFeedbackDelay: { + type: Number, + default: 2500, + }, + disabled: Boolean, + id: { + type: String, + default: '', + }, + name: { + type: String, + default: '', + }, + }, + emits: ['click'], + data() { + return { + currentCopyState: copyStatesLookup.toCopy, + }; + }, + computed: { + copyStatusTextAndClass() { + switch (this.currentCopyState) { + case (copyStatesLookup.copying): return { text: this.copyingLabel, class: this.copyingClass }; + case (copyStatesLookup.copied): return { text: this.copiedLabel, class: this.copiedClass }; + default: + case (copyStatesLookup.toCopy): return { text: this.toCopyLabel, class: this.toCopyClass }; + } + }, + copyStatusText() { + const { text: result } = this.copyStatusTextAndClass; + return result; + }, + copyStatusClass() { + const { class: result } = this.copyStatusTextAndClass; + return result; + }, + }, + methods: { + resetCopyState() { + this.currentCopyState = copyStatesLookup.toCopy; + }, + updateTextAfterCopying() { + this.currentCopyState = copyStatesLookup.copied; + setTimeout(this.resetCopyState, this.copiedFeedbackDelay); + }, + copyValueToClipBoard() { + copyTextToClipboard(this.valueToCopy); + this.updateTextAfterCopying(); + }, + onClick(event: Event) { + if (this.currentCopyState === copyStatesLookup.copying) { return; } + this.currentCopyState = copyStatesLookup.copying; + setTimeout(this.copyValueToClipBoard, this.copyingFeedbackDelay); + this.$emit('click', event); + }, + }, +}); + +export default CopyToClipboardButton; diff --git a/src/components/CopyToClipboardButton/CopyToClipboardButton.vue b/src/components/CopyToClipboardButton/CopyToClipboardButton.vue index 1c1d303..b2786f4 100644 --- a/src/components/CopyToClipboardButton/CopyToClipboardButton.vue +++ b/src/components/CopyToClipboardButton/CopyToClipboardButton.vue @@ -11,5 +11,5 @@ - + diff --git a/src/components/CopyableList/CopyableList.js b/src/components/CopyableList/CopyableList.js deleted file mode 100644 index 1ae8619..0000000 --- a/src/components/CopyableList/CopyableList.js +++ /dev/null @@ -1,23 +0,0 @@ -import Heading from '../Heading/Heading.vue'; - -const components = { - Heading, -}; - -const props = { - dataTestId: { - type: String, - default: 'copyable-list', - }, - title: String, -}; - -const name = 'CopyableList'; - -const CopyableList = { - name, - components, - props, -}; - -export default CopyableList; diff --git a/src/components/CopyableList/CopyableList.stories.js b/src/components/CopyableList/CopyableList.stories.ts similarity index 94% rename from src/components/CopyableList/CopyableList.stories.js rename to src/components/CopyableList/CopyableList.stories.ts index ee63e87..232d21d 100644 --- a/src/components/CopyableList/CopyableList.stories.js +++ b/src/components/CopyableList/CopyableList.stories.ts @@ -1,3 +1,5 @@ +import type { Meta, StoryFn } from '@storybook/vue3'; + import CopyableList from './CopyableList.vue'; import { createDeviceDecorator } from '../../lib/storybookHelpers'; import RenderString from '../../lib/renderString'; @@ -20,9 +22,9 @@ const CopyableListStories = { default: { control: { type: 'text' }, table: { type: { summary: null } } }, content: { control: { type: 'text' }, table: { type: { summary: null } } }, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/CopyableList/CopyableList.test.js b/src/components/CopyableList/CopyableList.test.ts similarity index 97% rename from src/components/CopyableList/CopyableList.test.js rename to src/components/CopyableList/CopyableList.test.ts index e2df4d5..32dfa5c 100644 --- a/src/components/CopyableList/CopyableList.test.js +++ b/src/components/CopyableList/CopyableList.test.ts @@ -10,7 +10,7 @@ describe('CopyableList unit test', () => { it('Should show given title', () => { const { getByTestId } = render(CopyableListWrapper); - const titleContentFound = getByTestId('heading').textContent.includes('Title'); + const titleContentFound = getByTestId('heading').textContent?.includes('Title'); expect(titleContentFound).toBeTruthy(); }); diff --git a/src/components/CopyableList/CopyableList.ts b/src/components/CopyableList/CopyableList.ts new file mode 100644 index 0000000..5e70a6e --- /dev/null +++ b/src/components/CopyableList/CopyableList.ts @@ -0,0 +1,22 @@ +import { defineComponent } from 'vue'; + +import Heading from '../Heading/Heading.vue'; + +const CopyableList = defineComponent({ + name: 'CopyableList', + components: { + Heading, + }, + props: { + dataTestId: { + type: String, + default: 'copyable-list', + }, + title: { + type: String, + default: '', + }, + }, +}); + +export default CopyableList; diff --git a/src/components/CopyableList/CopyableList.vue b/src/components/CopyableList/CopyableList.vue index 1347e9a..288e294 100644 --- a/src/components/CopyableList/CopyableList.vue +++ b/src/components/CopyableList/CopyableList.vue @@ -10,5 +10,5 @@ - + diff --git a/src/components/CopyableList/UnitTestWrappers/CopyableListWrapper.vue b/src/components/CopyableList/UnitTestWrappers/CopyableListWrapper.vue index 600b0b9..a8f4f88 100644 --- a/src/components/CopyableList/UnitTestWrappers/CopyableListWrapper.vue +++ b/src/components/CopyableList/UnitTestWrappers/CopyableListWrapper.vue @@ -30,13 +30,14 @@ - diff --git a/src/components/CopyableListItem/CopyableListItem.js b/src/components/CopyableListItem/CopyableListItem.js deleted file mode 100644 index c173336..0000000 --- a/src/components/CopyableListItem/CopyableListItem.js +++ /dev/null @@ -1,54 +0,0 @@ -import TextParagraph from '../TextParagraph/TextParagraph.vue'; -import CopyToClipboardButton from '../CopyToClipboardButton/CopyToClipboardButton.vue'; - -const components = { - CopyToClipboardButton, - TextParagraph, -}; - -const props = { - dataTestId: { - type: String, - default: 'copyable-list-item', - }, - title: { - type: String, - default: '', - required: true, - }, - text: { - type: String, - default: '', - }, - hideButton: Boolean, - disabled: Boolean, -}; - -const emits = ['click']; - -const computed = { - uniqueId() { - const base = `${this.title}-${this.text}`; - const result = `${Array.from(base).reduce((s, c) => Math.imul(31, s) + c.charCodeAt(0) | 0, 0)}`; - return result; - }, -}; - -const methods = { - onClick(event) { - this.$emit('click', event); - }, -}; - -const name = 'CopyableListItem'; - -const CopyableListItem = { - name, - components, - props, - emits, - computed, - methods, -}; - -export default CopyableListItem; diff --git a/src/components/CopyableListItem/CopyableListItem.stories.js b/src/components/CopyableListItem/CopyableListItem.stories.ts similarity index 94% rename from src/components/CopyableListItem/CopyableListItem.stories.js rename to src/components/CopyableListItem/CopyableListItem.stories.ts index a6fcf59..edebbe9 100644 --- a/src/components/CopyableListItem/CopyableListItem.stories.js +++ b/src/components/CopyableListItem/CopyableListItem.stories.ts @@ -1,5 +1,6 @@ import { action } from '@storybook/addon-actions'; import { InfoIcon, DocumentFilledIcon } from '@lana/b2c-mapp-ui-assets'; +import type { Meta, StoryFn } from '@storybook/vue3'; import CopyableListItem from './CopyableListItem.vue'; import RenderString from '../../lib/renderString'; @@ -38,9 +39,9 @@ const CopyableListItemStories = { }, }, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { @@ -83,7 +84,7 @@ defaultExample.parameters = { }, }; -const moreExampleStates = (args, { argTypes }) => ({ +const moreExampleStates: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/CopyableListItem/CopyableListItem.test.js b/src/components/CopyableListItem/CopyableListItem.test.ts similarity index 83% rename from src/components/CopyableListItem/CopyableListItem.test.js rename to src/components/CopyableListItem/CopyableListItem.test.ts index 9f3ea88..f970291 100644 --- a/src/components/CopyableListItem/CopyableListItem.test.js +++ b/src/components/CopyableListItem/CopyableListItem.test.ts @@ -16,37 +16,37 @@ describe('CopyableListItem unit test', () => { const defaultSlot = ''; it('Should show given title', () => { - const { getByTestId } = render(CopyableListItem, { stubs: { default: defaultSlot }, props: { ...defaultProps, hideButton: false } }); - const titleIsShown = getByTestId('copyable-list-item-title').textContent.includes('title'); + const { getByTestId } = render(CopyableListItem, { slots: { default: defaultSlot }, props: { ...defaultProps, hideButton: false } }); + const titleIsShown = getByTestId('copyable-list-item-title').textContent?.includes('title'); expect(titleIsShown).toBeTruthy(); }); it('Should show given text to be copied', () => { - const { getByTestId } = render(CopyableListItem, { stubs: { default: defaultSlot }, props: { ...defaultProps, hideButton: false } }); - const textToBeCopiedIsShown = getByTestId('copyable-list-item-text').textContent.includes('text'); + const { getByTestId } = render(CopyableListItem, { slots: { default: defaultSlot }, props: { ...defaultProps, hideButton: false } }); + const textToBeCopiedIsShown = getByTestId('copyable-list-item-text').textContent?.includes('text'); expect(textToBeCopiedIsShown).toBeTruthy(); }); it('Should display copy to clipboard button if hide property is false for given option', () => { - const { queryAllByTestId } = render(CopyableListItem, { stubs: { default: defaultSlot }, props: { ...defaultProps, hideButton: false } }); + const { queryAllByTestId } = render(CopyableListItem, { slots: { default: defaultSlot }, props: { ...defaultProps, hideButton: false } }); const buttonExists = queryAllByTestId('copyable-list-item-copy-to-clipboard-button-button').length; expect(buttonExists).toBeTruthy(); }); it('Should NOT display copy to clipboard button if hide property is true for given option', () => { - const { queryAllByTestId } = render(CopyableListItem, { stubs: { default: defaultSlot }, props: { ...defaultProps, hideButton: true } }); + const { queryAllByTestId } = render(CopyableListItem, { slots: { default: defaultSlot }, props: { ...defaultProps, hideButton: true } }); const buttonNotExists = !queryAllByTestId('copyable-list-item-copy-to-clipboard-button-button').length; expect(buttonNotExists).toBeTruthy(); }); it('Should display copy to clipboard button if hide property is NOT given in option', () => { - const { queryAllByTestId } = render(CopyableListItem, { stubs: { default: defaultSlot }, props: { ...defaultProps } }); + const { queryAllByTestId } = render(CopyableListItem, { slots: { default: defaultSlot }, props: { ...defaultProps } }); const buttonExists = queryAllByTestId('copyable-list-item-copy-to-clipboard-button-button').length; expect(buttonExists).toBeTruthy(); }); it('should emit an event when is clicked', () => { - const { getByTestId, emitted } = render(CopyableListItem, { stubs: { default: defaultSlot }, props: { ...defaultProps } }); + const { getByTestId, emitted } = render(CopyableListItem, { slots: { default: defaultSlot }, props: { ...defaultProps } }); const element = getByTestId('copyable-list-item-copy-to-clipboard-button-button'); fireEvent.click(element); const isEmitted = emitted(); diff --git a/src/components/CopyableListItem/CopyableListItem.ts b/src/components/CopyableListItem/CopyableListItem.ts new file mode 100644 index 0000000..e5d51d8 --- /dev/null +++ b/src/components/CopyableListItem/CopyableListItem.ts @@ -0,0 +1,44 @@ +import { defineComponent } from 'vue'; + +import TextParagraph from '../TextParagraph/TextParagraph.vue'; +import CopyToClipboardButton from '../CopyToClipboardButton/CopyToClipboardButton.vue'; + +const CopyableListItem = defineComponent({ + name: 'CopyableListItem', + components: { + CopyToClipboardButton, + TextParagraph, + }, + props: { + dataTestId: { + type: String, + default: 'copyable-list-item', + }, + title: { + type: String, + default: '', + required: true, + }, + text: { + type: String, + default: '', + }, + hideButton: Boolean, + disabled: Boolean, + }, + emits: ['click'], + computed: { + uniqueId() { + const base = `${this.title}-${this.text}`; + const result = `${Array.from(base).reduce((s, c) => Math.imul(31, s) + c.charCodeAt(0) | 0, 0)}`; + return result; + }, + }, + methods: { + onClick(event: Event) { + this.$emit('click', event); + }, + }, +}); + +export default CopyableListItem; diff --git a/src/components/CopyableListItem/CopyableListItem.vue b/src/components/CopyableListItem/CopyableListItem.vue index 6404b42..124026b 100644 --- a/src/components/CopyableListItem/CopyableListItem.vue +++ b/src/components/CopyableListItem/CopyableListItem.vue @@ -32,5 +32,5 @@ - + diff --git a/src/components/CurrencyField/CurrencyField.js b/src/components/CurrencyField/CurrencyField.js deleted file mode 100644 index 9e81c07..0000000 --- a/src/components/CurrencyField/CurrencyField.js +++ /dev/null @@ -1,173 +0,0 @@ -import { CloseBoldIcon, WarningBoldIcon } from '@lana/b2c-mapp-ui-assets'; - -import TextParagraph from '../TextParagraph/TextParagraph.vue'; -import CurrencyInput from '../CurrencyInput/CurrencyInput.vue'; -import { sleep } from '../../lib/sleepHelper'; - -const components = { - CurrencyInput, - TextParagraph, - CloseBoldIcon, - WarningBoldIcon, -}; - -const props = { - dataTestId: { - type: String, - default: 'currency-input', - }, - maxLength: { - type: Number, - default: 100, - }, - modelValue: { - type: [String, Number], - default: null, - }, - id: String, - name: String, - label: String, - disabled: Boolean, - errorLabel: String, - readonly: Boolean, - startFocused: Boolean, - showPrefix: Boolean, - lengthHint: Number, - lengthHintLabel: String, - currency: { - type: String, - default: 'CLP', - }, - locale: { - type: String, - default: 'es-CL', - }, - helpText: String, - hideClearButton: Boolean, -}; - -const emits = ['update:modelValue', 'update:formattedValue', 'focus', 'blur', 'keypress', 'keyup', 'paste']; - -const data = function () { - return { - isFocused: false, - isClearing: false, - inputValue: this.modelValue, - formattedValue: this.modelValue, - }; -}; - -const computed = { - maxLengthToUse() { - const result = (this.maxLength || this.lengthHint); - return result; - }, - hasLabel() { - const result = (this.isClearing || this.showPrefix || !!this.inputValue || this.inputValue === 0 || this.readonly || this.isFocused); - return result; - }, - inputId() { - const result = (this.id || this.name); - return result; - }, - formattedLengthHint() { - const result = `${(this.lengthHint || '')} ${(this.lengthHintLabel || '')}`; - return result; - }, - errorLabelOrHelpText() { - const result = (this.errorLabel || this.helpText || this.formattedLengthHint || ''); - return result; - }, - currencyOptions() { - const result = { - currency: this.currency, - locale: this.locale, - distractionFree: false, - allowNegative: false, - }; - return result; - }, - isClearIconShowing() { - const result = (this.inputValue && !(this.hideClearButton || this.readonly || this.disabled)); - return result; - }, -}; - -const methods = { - toggleFocus() { - this.isFocused = !this.isFocused; - }, - focusIfNeeded() { - if (!(this.startFocused && this.$refs.input)) { return; } - this.toggleFocus(); - this.$refs.input.focus(); - }, - blur() { - if (!this.$refs.input) { return; } - this.$refs.input.blur(); - }, - emitUpdateModelValueEvent() { - this.$emit('update:modelValue', this.inputValue); - }, - onFocus(event) { - this.toggleFocus(); - this.$emit('focus', event); - }, - onBlur(event) { - this.toggleFocus(); - this.$emit('blur', event); - }, - onKeypress(event) { - this.$emit('keypress', event); - }, - onKeyup(event) { - this.$emit('keyup', event); - }, - onPaste(event) { - this.$emit('paste', event); - }, - focus() { - this.$refs.input.focus(); - }, - async clearValue() { - this.isClearing = true; - this.inputValue = null; - this.emitUpdateModelValueEvent(); - this.blur(); - await sleep(50); // NOTE: sleep must be used here because `this.$nextTick()` is not waiting long enough in this case - this.focus(); - this.isClearing = false; - }, -}; - -const watch = { - inputValue() { - this.emitUpdateModelValueEvent(); - }, - formattedValue() { - this.$emit('update:formattedValue', this.formattedValue); - }, - modelValue() { - this.inputValue = this.modelValue; - }, -}; - -const mounted = function () { - this.focusIfNeeded(); -}; - -const name = 'CurrencyField'; - -const CurrencyField = { - name, - components, - props, - emits, - data, - computed, - methods, - mounted, - watch, -}; - -export default CurrencyField; diff --git a/src/components/CurrencyField/CurrencyField.stories.js b/src/components/CurrencyField/CurrencyField.stories.ts similarity index 96% rename from src/components/CurrencyField/CurrencyField.stories.js rename to src/components/CurrencyField/CurrencyField.stories.ts index 690b219..432398d 100644 --- a/src/components/CurrencyField/CurrencyField.stories.js +++ b/src/components/CurrencyField/CurrencyField.stories.ts @@ -1,4 +1,5 @@ import { action } from '@storybook/addon-actions'; +import type { Meta, StoryFn } from '@storybook/vue3'; import CurrencyField from './CurrencyField.vue'; import { sleep } from '../../lib/sleepHelper'; @@ -43,9 +44,9 @@ const CurrencyFieldStories = { helpText: { name: 'Help text', control: 'text' }, default: { control: { type: 'text' }, table: { type: { summary: null } } }, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { @@ -136,7 +137,7 @@ defaultExample.parameters = { }, }; -const withPrefilledValue = (args, { argTypes }) => ({ +const withPrefilledValue: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/CurrencyField/CurrencyField.test.js b/src/components/CurrencyField/CurrencyField.test.ts similarity index 91% rename from src/components/CurrencyField/CurrencyField.test.js rename to src/components/CurrencyField/CurrencyField.test.ts index d9783d3..eb22f9d 100644 --- a/src/components/CurrencyField/CurrencyField.test.js +++ b/src/components/CurrencyField/CurrencyField.test.ts @@ -1,6 +1,5 @@ import { nextTick } from 'vue'; import { mount } from '@vue/test-utils'; -import { render } from '@testing-library/vue'; import CurrencyField from './CurrencyField.vue'; import WithErrorFormFieldWrapper from './UnitTestWrappers/WithErrorCurrencyFieldWrapper.vue'; @@ -21,15 +20,14 @@ describe('CurrencyField unit test', () => { await wrapper.vm.$nextTick(); wrapper.vm.$options.watch.modelValue.call(wrapper.vm, newValue); await wrapper.vm.$nextTick(); - expect(wrapper.vm.$data.inputValue).toBe(123); + expect(wrapper.vm.inputValue).toBe(123); }); it('Should have focus class if focus is triggered', async () => { const wrapper = mount(CurrencyField, { props: { ...defaultProps } }); const inputField = wrapper.find('input'); const fieldContainer = wrapper.find('.field-container'); - inputField.trigger('focus'); - await wrapper.vm.$forceUpdate(); + await inputField.trigger('focus'); const hasFocusClass = fieldContainer.element.className.includes('focus'); expect(hasFocusClass).toBeTruthy(); }); @@ -41,9 +39,9 @@ describe('CurrencyField unit test', () => { }); it('Should emit focus event if startFocused is true', async () => { - const { emitted } = render(CurrencyField, { props: { ...defaultProps, startFocused: true } }); + const wrapper = mount(CurrencyField, { props: { ...defaultProps, startFocused: true }, attachTo: document.body }); await nextTick(); - const focusEventEmitted = emitted('focus'); + const focusEventEmitted = wrapper.emitted('focus'); expect(focusEventEmitted).toBeTruthy(); }); @@ -103,16 +101,14 @@ describe('CurrencyField unit test', () => { wrapper.find('input').trigger('input'); await wrapper.vm.$nextTick(); const inputEventIsEmitted = wrapper.emitted('update:modelValue'); - expect(inputEventIsEmitted.length > 1).toBeTruthy(); + expect(inputEventIsEmitted).toBeTruthy(); }); it('Should provide current input value in the input event when value changed', async () => { const givenValue = 123; const wrapper = mount(CurrencyField, { props: { ...defaultProps, modelValue: '' } }); - await wrapper.vm.$nextTick(); await wrapper.find('input').setValue(givenValue); - await wrapper.vm.$nextTick(); - const inputEventValue = wrapper.emitted('update:modelValue')[1][0]; + const [inputEventValue] = wrapper.emitted('update:modelValue')?.[0] as number[]; expect(inputEventValue).toBe(123); }); diff --git a/src/components/CurrencyField/CurrencyField.ts b/src/components/CurrencyField/CurrencyField.ts new file mode 100644 index 0000000..62dd9cb --- /dev/null +++ b/src/components/CurrencyField/CurrencyField.ts @@ -0,0 +1,183 @@ +import { CloseBoldIcon, WarningBoldIcon } from '@lana/b2c-mapp-ui-assets'; +import { defineComponent, ref } from 'vue'; + +import TextParagraph from '../TextParagraph/TextParagraph.vue'; +import CurrencyInput from '../CurrencyInput/CurrencyInput.vue'; +import { sleep } from '../../lib/sleepHelper'; + +const CurrencyField = defineComponent({ + name: 'CurrencyField', + components: { + CurrencyInput, + TextParagraph, + CloseBoldIcon, + WarningBoldIcon, + }, + props: { + dataTestId: { + type: String, + default: 'currency-input', + }, + maxLength: { + type: Number, + default: 100, + }, + modelValue: { + type: [String, Number], + default: null, + }, + id: { + type: String, + default: '', + }, + name: { + type: String, + default: '', + }, + label: { + type: String, + default: '', + }, + disabled: Boolean, + errorLabel: { + type: String, + default: '', + }, + readonly: Boolean, + startFocused: Boolean, + showPrefix: Boolean, + lengthHint: { + type: Number, + default: null, + }, + lengthHintLabel: { + type: String, + default: '', + }, + currency: { + type: String, + default: 'CLP', + }, + locale: { + type: String, + default: 'es-CL', + }, + helpText: { + type: String, + default: '', + }, + hideClearButton: Boolean, + }, + emits: ['update:modelValue', 'update:formattedValue', 'focus', 'blur', 'keypress', 'keyup', 'paste'], + computed: { + maxLengthToUse() { + const result = (this.maxLength || this.lengthHint); + return result; + }, + hasLabel() { + const result = (this.isClearing || this.showPrefix || !!this.inputValue || this.inputValue === 0 || this.readonly || this.isFocused); + return result; + }, + inputId() { + const result = (this.id || this.name); + return result; + }, + formattedLengthHint() { + const result = `${(this.lengthHint || '')} ${(this.lengthHintLabel || '')}`; + return result; + }, + errorLabelOrHelpText() { + const result = (this.errorLabel || this.helpText || this.formattedLengthHint || ''); + return result; + }, + currencyOptions() { + const result = { + currency: this.currency, + locale: this.locale, + distractionFree: false, + allowNegative: false, + }; + return result; + }, + isClearIconShowing() { + const result = (this.inputValue && !(this.hideClearButton || this.readonly || this.disabled)); + return result; + }, + }, + methods: { + toggleFocus() { + this.isFocused = !this.isFocused; + }, + focusIfNeeded() { + if (!(this.startFocused && this.input)) { return; } + this.toggleFocus(); + this.input.focus(); + }, + blur() { + if (!this.input) { return; } + this.input.blur(); + }, + emitUpdateModelValueEvent() { + this.$emit('update:modelValue', this.inputValue); + }, + onFocus(event: Event) { + this.toggleFocus(); + this.$emit('focus', event); + }, + onBlur(event: Event) { + this.toggleFocus(); + this.$emit('blur', event); + }, + onKeypress(event: Event) { + this.$emit('keypress', event); + }, + onKeyup(event: Event) { + this.$emit('keyup', event); + }, + onPaste(event: Event) { + this.$emit('paste', event); + }, + focus() { + this.input.focus(); + }, + async clearValue() { + this.isClearing = true; + this.inputValue = null; + this.emitUpdateModelValueEvent(); + this.blur(); + await sleep(50); // NOTE: sleep must be used here because `this.$nextTick()` is not waiting long enough in this case + this.focus(); + this.isClearing = false; + }, + }, + watch: { + inputValue() { + this.emitUpdateModelValueEvent(); + }, + formattedValue() { + this.$emit('update:formattedValue', this.formattedValue); + }, + modelValue() { + this.inputValue = this.modelValue; + }, + }, + setup(props) { + const input = ref(); + const isFocused = ref(false); + const isClearing = ref(false); + const inputValue = ref(props.modelValue || null); + const formattedValue = ref(props.modelValue); + return { + input, + isFocused, + isClearing, + inputValue, + formattedValue, + }; + }, + mounted() { + this.focusIfNeeded(); + }, +}); + +export default CurrencyField; diff --git a/src/components/CurrencyField/CurrencyField.vue b/src/components/CurrencyField/CurrencyField.vue index 684f916..051cf01 100644 --- a/src/components/CurrencyField/CurrencyField.vue +++ b/src/components/CurrencyField/CurrencyField.vue @@ -39,5 +39,5 @@ - + diff --git a/src/components/CurrencyField/UnitTestWrappers/WithErrorCurrencyFieldWrapper.vue b/src/components/CurrencyField/UnitTestWrappers/WithErrorCurrencyFieldWrapper.vue index 99c64f3..f9a9d81 100644 --- a/src/components/CurrencyField/UnitTestWrappers/WithErrorCurrencyFieldWrapper.vue +++ b/src/components/CurrencyField/UnitTestWrappers/WithErrorCurrencyFieldWrapper.vue @@ -3,13 +3,15 @@ - diff --git a/src/components/CurrencyInput/CurrencyInput.vue b/src/components/CurrencyInput/CurrencyInput.vue index de2e12e..988964c 100644 --- a/src/components/CurrencyInput/CurrencyInput.vue +++ b/src/components/CurrencyInput/CurrencyInput.vue @@ -4,10 +4,13 @@ > - diff --git a/src/components/DateField/DateField.js b/src/components/DateField/DateField.js deleted file mode 100644 index ae63589..0000000 --- a/src/components/DateField/DateField.js +++ /dev/null @@ -1,193 +0,0 @@ -import { CalendarIcon } from '@lana/b2c-mapp-ui-assets'; - -import FormField from '../FormField/FormField.vue'; -import { getDateFromDateString, getFormattedStringFromDate, autoformatDate, isDateTextInputValid } from '../../lib/dateHelper'; -import { validDateRegexp } from '../../lib/regexHelper'; - -const expectedDateInputLength = 10; -const invalidDatePart = 'aN'; - -const components = { - CalendarIcon, - FormField, -}; - -const props = { - dataTestId: { - type: String, - default: 'date-field', - }, - maxLength: { - type: Number, - default: 10, - }, - modelValue: { - type: String, - default: '', - }, - errorLabel: { - type: String, - default: 'Fecha no válida (DD/MM/YYYY)', - }, - label: { - type: String, - default: '', - }, - id: String, - name: String, - autoformat: Boolean, - datePicker: Boolean, - readonly: Boolean, - startFocused: Boolean, - disabled: Boolean, - customValidation: Boolean, - helpText: String, -}; - -const emits = ['update:modelValue', 'change', 'validate', 'focus', 'blur', 'keypress', 'keyup', 'paste']; - -const data = function () { - return { - inputValue: this.modelValue, - datePickerValue: this.formattedDateText, - }; -}; - -const computed = { - formFieldId() { - if (!this.id) { return; } - const result = `${this.id}-date-input`; - return result; - }, - formFieldName() { - const prefix = 'date-field'; - if (!this.name) { return prefix; } - const result = `${prefix}-${this.name}`; - return result; - }, - calendarInputName() { - const prefix = 'input-date'; - if (!this.name) { return prefix; } - const result = `${prefix}-${this.name}`; - return result; - }, - errorLabelToShow() { - if (this.customValidation || !this.isValid) { return this.errorLabel; } - }, - hasError() { - return !!this.errorLabelToShow; - }, - formFieldFormattedDateText() { - if (!this.datePickerValue) { return ''; } - const result = getFormattedStringFromDate(new Date(this.datePickerValue)); - return result; - }, - autoformattedDate() { - if (!this.autoformat) { return this.inputValue; } - const result = autoformatDate(this.inputValue); - return result; - }, - datePickerValueToUse() { - if (!this.isValid) { return this.autoformattedDate; } - const result = getDateFromDateString(this.autoformattedDate); - return result; - }, - isExpectedInputValueLength() { - const result = (this.inputValue.length >= expectedDateInputLength); - return result; - }, - isValid() { - if (!this.inputValue) { return true; } - const result = (this.isExpectedInputValueLength && validDateRegexp.test(this.autoformattedDate) && isDateTextInputValid(this.autoformattedDate)); - return result; - }, - uniqueId() { - const base = (Math.random() + 1).toString(36).substring(7); - const result = `${Array.from(base).reduce((s, c) => Math.imul(31, s) + c.charCodeAt(0) | 0, 0)}`; - return result; - }, -}; - -const methods = { - emitUpdateModelValueChangeAndValidationEvents() { - const validationPayload = { - value: this.inputValue, - id: this.id, - }; - this.$emit('update:modelValue', this.inputValue); - this.$emit('change', this.formFieldFormattedDateText); - this.$emit('validate', validationPayload); - }, - updateInputValueWithFormatting() { - if (!(this.autoformat && this.inputValue)) { return; } - this.inputValue = this.autoformattedDate; - }, - updateInputValueFromDatePicker() { - if (this.formFieldFormattedDateText.includes(invalidDatePart)) { return; } - this.inputValue = this.formFieldFormattedDateText; - }, - updateCalendarValueIfNeeded() { - if (!(this.inputValue && this.isValid)) { return; } - this.datePickerValue = this.datePickerValueToUse; - }, - onFocus(event) { - this.$emit('focus', event); - }, - onBlur(event) { - this.$emit('blur', event); - }, - onKeypress(event) { - this.$emit('keypress', event); - }, - onKeyup(event) { - this.$emit('keyup', event); - }, - onPaste(event) { - this.$emit('paste', event); - }, - focus() { - if (!this.$refs.field) { return; } - this.$refs.field.focus(); - }, - blur() { - if (!this.$refs.field) { return; } - this.$refs.field.blur(); - }, - updateInputAndCalendarValuesAsNeeded() { - this.updateInputValueWithFormatting(); - this.updateCalendarValueIfNeeded(); - }, -}; - -const watch = { - inputValue() { - this.emitUpdateModelValueChangeAndValidationEvents(); - this.updateInputAndCalendarValuesAsNeeded(); - }, - datePickerValue() { - this.updateInputValueFromDatePicker(); - }, - modelValue() { - this.inputValue = this.modelValue; - }, -}; - -const mounted = function () { - if (this.inputValue) { this.updateInputAndCalendarValuesAsNeeded(); } -}; - -const name = 'DateField'; - -const DateField = { - name, - components, - props, - emits, - data, - computed, - mounted, - methods, - watch, -}; - -export default DateField; diff --git a/src/components/DateField/DateField.stories.js b/src/components/DateField/DateField.stories.ts similarity index 95% rename from src/components/DateField/DateField.stories.js rename to src/components/DateField/DateField.stories.ts index 9e85ae4..1b2f435 100644 --- a/src/components/DateField/DateField.stories.js +++ b/src/components/DateField/DateField.stories.ts @@ -1,4 +1,5 @@ import { action } from '@storybook/addon-actions'; +import type { Meta, StoryFn } from '@storybook/vue3'; import DateField from './DateField.vue'; import { createOptionalDeviceDecorator } from '../../lib/storybookHelpers'; @@ -33,9 +34,9 @@ const DateFieldStories = { customValidation: { control: 'boolean', name: 'Custom Validation?' }, helpText: { control: 'text', name: 'Help Text' }, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { @@ -43,7 +44,7 @@ const defaultExample = (args, { argTypes }) => ({ }, data() { return { - value: '', + value: this.modelValue || '', }; }, methods: { @@ -110,7 +111,7 @@ defaultExample.parameters = { }, }; -const validation = (args, { argTypes }) => ({ +const validation: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/DateField/DateField.test.js b/src/components/DateField/DateField.test.ts similarity index 88% rename from src/components/DateField/DateField.test.js rename to src/components/DateField/DateField.test.ts index 87a7030..476933b 100644 --- a/src/components/DateField/DateField.test.js +++ b/src/components/DateField/DateField.test.ts @@ -48,7 +48,6 @@ describe('DateField unit test', () => { const wrapper = mount(DateField, { props: { ...defaultProps } }); const dateInput = wrapper.find('input[data-testid="date-field-input"'); await dateInput.setValue(wrongDate); - await wrapper.vm.$nextTick(); const labelHasError = wrapper.find('div[data-testid="date-field-container"]').classes().includes('error'); expect(labelHasError).toBeTruthy(); }); @@ -58,7 +57,6 @@ describe('DateField unit test', () => { const wrapper = mount(DateField, { props: { ...defaultProps } }); const dateInput = wrapper.find('input[data-testid="date-field-input"'); await dateInput.setValue(wrongDate); - await wrapper.vm.$nextTick(); const labelHasError = wrapper.find('div[data-testid="date-field-container"]').classes().includes('error'); expect(labelHasError).toBeTruthy(); }); @@ -68,7 +66,6 @@ describe('DateField unit test', () => { const wrapper = mount(DateField, { props: { ...defaultProps } }); const dateInput = wrapper.find('input[data-testid="date-field-input"'); await dateInput.setValue(goodDate); - await wrapper.vm.$nextTick(); const labelHasNotError = !wrapper.find('label[data-testid="date-field-label"]').classes().includes('error'); expect(labelHasNotError).toBeTruthy(); }); @@ -79,9 +76,7 @@ describe('DateField unit test', () => { const wrapper = mount(DateField, { props: { ...defaultProps } }); const dateInput = wrapper.find('input[data-testid="date-field-input"'); await dateInput.setValue(unformattedDate); - await wrapper.vm.$nextTick(); - const autoFormatIsApplied = dateInput.element.value === formattedDate; - expect(autoFormatIsApplied).toBeTruthy(); + expect((dateInput.element as HTMLInputElement).value).toBe(formattedDate); }); it('Should show date picked using datapicker', async () => { @@ -89,9 +84,8 @@ describe('DateField unit test', () => { const wrapper = mount(DateField, { props: { ...defaultProps, datePicker: true } }); const datePicker = wrapper.find('input[data-testid="date-field-datepicker-input"]'); await datePicker.setValue(goodDate); - await wrapper.vm.$nextTick(); const dateInput = wrapper.find('input[data-testid="date-field-input"]'); - const hasGivenValueFromDatePicker = dateInput.element.value === datePicker.element.value; + const hasGivenValueFromDatePicker = dateInput.attributes('value') === datePicker.attributes('value'); expect(hasGivenValueFromDatePicker).toBeTruthy(); }); @@ -112,9 +106,9 @@ describe('DateField unit test', () => { const dateInput = wrapper.find('input[data-testid="date-field-input"'); await dateInput.setValue(goodDate); await wrapper.vm.$nextTick(); - const validationEvent = wrapper.emitted().validate[0][0]; - const validationEventValueIsCurrentDate = validationEvent.value === goodDate; - const validationEventIdIsGivenId = validationEvent.id === givenId; + const [validationEvent] = wrapper.emitted('validate')?.[0] as { value: string, id: string }[]; + const validationEventValueIsCurrentDate = validationEvent?.value === goodDate; + const validationEventIdIsGivenId = validationEvent?.id === givenId; const rightValidationEmittedEventValues = validationEventValueIsCurrentDate && validationEventIdIsGivenId; expect(rightValidationEmittedEventValues).toBeTruthy(); }); @@ -143,7 +137,7 @@ describe('DateField unit test', () => { const wrapper = mount(DateField, { props: { ...defaultProps, modelValue: newValue } }); wrapper.vm.$options.watch.modelValue.call(wrapper.vm, newValue); await wrapper.vm.$nextTick(); - expect(wrapper.vm.$data.inputValue).toEqual(newValue); + expect(wrapper.vm.inputValue).toEqual(newValue); }); it('Should apply given input value if autoformat is not given: ', async () => { @@ -151,7 +145,7 @@ describe('DateField unit test', () => { const wrapper = mount(DateField, { props: { ...defaultProps, autoformat: false, modelValue: newValue } }); wrapper.vm.$options.watch.modelValue.call(wrapper.vm, newValue); await wrapper.vm.$nextTick(); - expect(wrapper.vm.$data.inputValue).toEqual(newValue); + expect(wrapper.vm.inputValue).toEqual(newValue); }); describe('Datepicker behavior:', () => { @@ -160,7 +154,6 @@ describe('DateField unit test', () => { const wrapper = mount(DateField, { props: { ...defaultProps, datePicker: true } }); const datePicker = wrapper.find('input[data-testid="date-field-datepicker-input"]'); await datePicker.setValue(goodDate); - await wrapper.vm.$nextTick(); const validationEvent = wrapper.emitted().validate; expect(validationEvent).toBeTruthy(); }); @@ -172,10 +165,9 @@ describe('DateField unit test', () => { const wrapper = mount(DateField, { props: { ...defaultProps, datePicker: true, id: givenId } }); const datePicker = wrapper.find('input[data-testid="date-field-datepicker-input"]'); await datePicker.setValue(goodDate); - await wrapper.vm.$nextTick(); - const validationEvent = wrapper.emitted().validate[0][0]; - const validationEventValueIsCurrentDate = validationEvent.value === formattedDate; - const validationEventIdIsGivenId = validationEvent.id === givenId; + const [validationEvent] = wrapper.emitted('validate')?.[0] as { value: string, id: string }[]; + const validationEventValueIsCurrentDate = validationEvent?.value === formattedDate; + const validationEventIdIsGivenId = validationEvent?.id === givenId; const rightValidationEmittedEventValues = validationEventValueIsCurrentDate && validationEventIdIsGivenId; expect(rightValidationEmittedEventValues).toBeTruthy(); }); @@ -183,12 +175,10 @@ describe('DateField unit test', () => { it('Should get value from main date input', async () => { const goodDate = '20/10/2018'; const wrapper = mount(DateField, { props: { ...defaultProps, datePicker: true } }); - const dateInput = wrapper.find('input[data-testid="date-field-input"'); - const datePicker = wrapper.find('input[data-testid="date-field-datepicker-input"]'); + const dateInput = wrapper.find('input[data-testid="date-field-input"]'); await dateInput.setValue(goodDate); - await wrapper.vm.$nextTick(); - const calendarDateIsChanged = datePicker.element.value === '2018-10-20'; - expect(calendarDateIsChanged).toBeTruthy(); + const datePicker = wrapper.find('input[data-testid="date-field-datepicker-input"]'); + expect((datePicker.element as HTMLInputElement).value).toBe('2018-10-20'); }); it('Should apply given name and prefix to datepicker input field', async () => { diff --git a/src/components/DateField/DateField.ts b/src/components/DateField/DateField.ts new file mode 100644 index 0000000..0414a55 --- /dev/null +++ b/src/components/DateField/DateField.ts @@ -0,0 +1,186 @@ +import { CalendarIcon } from '@lana/b2c-mapp-ui-assets'; +import { defineComponent, ref } from 'vue'; + +import FormField from '../FormField/FormField.vue'; +import { getDateFromDateString, getFormattedStringFromDate, autoformatDate, isDateTextInputValid } from '../../lib/dateHelper'; +import { validDateRegexp } from '../../lib/regexHelper'; + +const expectedDateInputLength = 10; +const invalidDatePart = 'aN'; + +const DateField = defineComponent({ + name: 'DateField', + components: { + CalendarIcon, + FormField, + }, + props: { + dataTestId: { + type: String, + default: 'date-field', + }, + maxLength: { + type: Number, + default: 10, + }, + modelValue: { + type: String, + default: '', + }, + errorLabel: { + type: String, + default: 'Fecha no válida (DD/MM/YYYY)', + }, + label: { + type: String, + default: '', + }, + id: { + type: String, + default: '', + }, + name: { + type: String, + default: '', + }, + autoformat: Boolean, + datePicker: Boolean, + readonly: Boolean, + startFocused: Boolean, + disabled: Boolean, + customValidation: Boolean, + helpText: { + type: String, + default: '', + }, + }, + emits: ['update:modelValue', 'change', 'validate', 'focus', 'blur', 'keypress', 'keyup', 'paste'], + computed: { + formFieldId() { + if (!this.id) { return; } + const result = `${this.id}-date-input`; + return result; + }, + formFieldName() { + const prefix = 'date-field'; + if (!this.name) { return prefix; } + const result = `${prefix}-${this.name}`; + return result; + }, + calendarInputName() { + const prefix = 'input-date'; + if (!this.name) { return prefix; } + const result = `${prefix}-${this.name}`; + return result; + }, + errorLabelToShow() { + if (this.customValidation || !this.isValid) { return this.errorLabel; } + return ''; + }, + hasError() { + return !!this.errorLabelToShow; + }, + formFieldFormattedDateText() { + if (!this.datePickerValue) { return ''; } + const result = getFormattedStringFromDate(new Date(this.datePickerValue)); + return result; + }, + autoformattedDate() { + if (!this.autoformat) { return this.inputValue; } + const result = autoformatDate(this.inputValue); + return result; + }, + datePickerValueToUse() { + if (!this.isValid) { return this.autoformattedDate; } + const result = getDateFromDateString(this.autoformattedDate); + return result; + }, + isExpectedInputValueLength() { + const result = (this.inputValue.length >= expectedDateInputLength); + return result; + }, + isValid() { + if (!this.inputValue) { return true; } + const result = (this.isExpectedInputValueLength && validDateRegexp.test(this.autoformattedDate) && isDateTextInputValid(this.autoformattedDate)); + return result; + }, + uniqueId() { + const base = (Math.random() + 1).toString(36).substring(7); + const result = `${Array.from(base).reduce((s, c) => Math.imul(31, s) + c.charCodeAt(0) | 0, 0)}`; + return result; + }, + }, + methods: { + emitUpdateModelValueChangeAndValidationEvents() { + const validationPayload = { + value: this.inputValue, + id: this.id, + }; + this.$emit('update:modelValue', this.inputValue); + this.$emit('change', this.formFieldFormattedDateText); + this.$emit('validate', validationPayload); + }, + updateInputValueWithFormatting() { + if (!(this.autoformat && this.inputValue)) { return; } + this.inputValue = this.autoformattedDate; + }, + updateInputValueFromDatePicker() { + if (this.formFieldFormattedDateText.includes(invalidDatePart)) { return; } + this.inputValue = this.formFieldFormattedDateText; + }, + updateCalendarValueIfNeeded() { + if (!(this.inputValue && this.isValid)) { return; } + this.datePickerValue = this.datePickerValueToUse; + }, + onFocus(event: Event) { + this.$emit('focus', event); + }, + onBlur(event: Event) { + this.$emit('blur', event); + }, + onKeypress(event: Event) { + this.$emit('keypress', event); + }, + onKeyup(event: Event) { + this.$emit('keyup', event); + }, + onPaste(event: Event) { + this.$emit('paste', event); + }, + focus() { + if (!this.field) { return; } + this.field.focus(); + }, + blur() { + if (!this.field) { return; } + this.field.blur(); + }, + updateInputAndCalendarValuesAsNeeded() { + this.updateInputValueWithFormatting(); + this.updateCalendarValueIfNeeded(); + }, + }, + watch: { + inputValue() { + this.emitUpdateModelValueChangeAndValidationEvents(); + this.updateInputAndCalendarValuesAsNeeded(); + }, + datePickerValue() { + this.updateInputValueFromDatePicker(); + }, + modelValue() { + this.inputValue = this.modelValue; + }, + }, + setup(props) { + const field = ref(); + const inputValue = ref(props.modelValue); + const datePickerValue = ref(props.modelValue); + return { field, inputValue, datePickerValue }; + }, + mounted() { + if (this.inputValue) { this.updateInputAndCalendarValuesAsNeeded(); } + }, +}); + +export default DateField; diff --git a/src/components/DateField/DateField.vue b/src/components/DateField/DateField.vue index b47cd1b..19dd948 100644 --- a/src/components/DateField/DateField.vue +++ b/src/components/DateField/DateField.vue @@ -39,5 +39,5 @@ - + diff --git a/src/components/FigureCard/FigureCard.js b/src/components/FigureCard/FigureCard.js deleted file mode 100644 index 08bb18d..0000000 --- a/src/components/FigureCard/FigureCard.js +++ /dev/null @@ -1,39 +0,0 @@ -import TextParagraph from '../TextParagraph/TextParagraph.vue'; - -const components = { - TextParagraph, -}; - -const props = { - dataTestId: { - type: String, - default: 'figure-card', - }, - title: { - type: String, - default: '', - }, - imageSource: { - type: String, - default: '', - }, - titleAbove: Boolean, -}; - -const computed = { - backgroundStyle() { - const result = { backgroundImage: `url('${this.imageSource}')` }; - return result; - }, -}; - -const name = 'FigureCard'; - -const FigureCard = { - name, - components, - props, - computed, -}; - -export default FigureCard; diff --git a/src/components/FigureCard/FigureCard.stories.js b/src/components/FigureCard/FigureCard.stories.ts similarity index 91% rename from src/components/FigureCard/FigureCard.stories.js rename to src/components/FigureCard/FigureCard.stories.ts index 7f76c41..1a3db98 100644 --- a/src/components/FigureCard/FigureCard.stories.js +++ b/src/components/FigureCard/FigureCard.stories.ts @@ -1,3 +1,5 @@ +import type { Meta, StoryFn } from '@storybook/vue3'; + import FigureCard from './FigureCard.vue'; import { createDeviceDecorator } from '../../lib/storybookHelpers'; import RenderString from '../../lib/renderString'; @@ -22,9 +24,9 @@ const FigureCardStories = { imageSource: { control: 'text', name: 'Image Source (URL)' }, default: { control: { type: 'text' }, table: { type: { summary: null } } }, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/FigureCard/FigureCard.test.js b/src/components/FigureCard/FigureCard.test.ts similarity index 93% rename from src/components/FigureCard/FigureCard.test.js rename to src/components/FigureCard/FigureCard.test.ts index 07edbce..90feee0 100644 --- a/src/components/FigureCard/FigureCard.test.js +++ b/src/components/FigureCard/FigureCard.test.ts @@ -1,4 +1,5 @@ import { render } from '@testing-library/vue'; +import type { VueWrapper } from '@vue/test-utils'; import { mount } from '@vue/test-utils'; import FigureCard from './FigureCard.vue'; @@ -8,7 +9,7 @@ describe('FigureCard unit test', () => { beforeAll(() => { silenceDeprecationErrorsAndInnerComponentWarnings(jest); }); - const waitForDomUpdate = async (wrapper) => { + const waitForDomUpdate = async (wrapper: VueWrapper) => { await wrapper.vm.$nextTick(); await wrapper.vm.$forceUpdate(); }; diff --git a/src/components/FigureCard/FigureCard.ts b/src/components/FigureCard/FigureCard.ts new file mode 100644 index 0000000..2d06442 --- /dev/null +++ b/src/components/FigureCard/FigureCard.ts @@ -0,0 +1,33 @@ +import { defineComponent } from 'vue'; + +import TextParagraph from '../TextParagraph/TextParagraph.vue'; + +const FigureCard = defineComponent({ + name: 'FigureCard', + components: { + TextParagraph, + }, + props: { + dataTestId: { + type: String, + default: 'figure-card', + }, + title: { + type: String, + default: '', + }, + imageSource: { + type: String, + default: '', + }, + titleAbove: Boolean, + }, + computed: { + backgroundStyle() { + const result = { backgroundImage: `url('${this.imageSource}')` }; + return result; + }, + }, +}); + +export default FigureCard; diff --git a/src/components/FigureCard/FigureCard.vue b/src/components/FigureCard/FigureCard.vue index 56da6f8..3ca374a 100644 --- a/src/components/FigureCard/FigureCard.vue +++ b/src/components/FigureCard/FigureCard.vue @@ -20,5 +20,5 @@ - + diff --git a/src/components/FormField/FormField.js b/src/components/FormField/FormField.js deleted file mode 100644 index c16f240..0000000 --- a/src/components/FormField/FormField.js +++ /dev/null @@ -1,152 +0,0 @@ -import { nextTick } from 'vue'; -import { CloseBoldIcon, WarningBoldIcon } from '@lana/b2c-mapp-ui-assets'; - -import TextParagraph from '../TextParagraph/TextParagraph.vue'; -import { sleep } from '../../lib/sleepHelper'; - -const components = { - TextParagraph, - CloseBoldIcon, - WarningBoldIcon, -}; - -const props = { - dataTestId: { - type: String, - default: 'field', - }, - maxLength: { - type: Number, - default: 100, - }, - modelValue: { - type: String, - default: '', - }, - id: String, - name: String, - label: String, - type: String, - disabled: Boolean, - errorLabel: String, - readonly: Boolean, - startFocused: Boolean, - showPrefix: Boolean, - lengthHint: Number, - lengthHintLabel: String, - helpText: String, - hideClearButton: Boolean, - inputmode: String, - pattern: String, -}; - -const emits = ['update:modelValue', 'focus', 'blur', 'keypress', 'keyup', 'paste']; - -const data = function () { - return { - isFocused: false, - isClearing: false, - inputValue: this.modelValue, - }; -}; - -const computed = { - maxLengthToUse() { - const result = (this.maxLength || this.lengthHint); - return result; - }, - hasLabel() { - const result = (this.isClearing || this.showPrefix || !!this.inputValue || this.readonly || this.isFocused); - return result; - }, - inputId() { - const result = (this.id || this.name); - return result; - }, - formattedLengthHint() { - const result = `${(this.lengthHint || '')} ${(this.lengthHintLabel || '')}`; - return result; - }, - errorLabelOrHelpText() { - const result = (this.errorLabel || this.helpText || this.formattedLengthHint || ''); - return result; - }, - isClearIconShowing() { - const result = (this.inputValue && !(this.hideClearButton || this.readonly || this.disabled)); - return result; - }, -}; - -const methods = { - setFocus(focus) { - this.isFocused = focus; - }, - async focusIfNeeded() { - if (!(this.startFocused && this.$refs.input)) { return; } - await nextTick(); - this.$refs.input.focus(); - }, - blur() { - if (!this.$refs.input) { return; } - this.$refs.input.blur(); - }, - emitUpdateModelValueEvent() { - this.$emit('update:modelValue', this.inputValue); - }, - onFocus(event) { - this.setFocus(true); - this.$emit('focus', event); - }, - onBlur(event) { - this.setFocus(false); - this.$emit('blur', event); - }, - onKeypress(event) { - this.$emit('keypress', event); - }, - onKeyup(event) { - this.$emit('keyup', event); - }, - onPaste(event) { - this.$emit('paste', event); - }, - focus() { - this.$refs.input.focus(); - }, - async clearValue() { - this.isClearing = true; - this.inputValue = ''; - await sleep(50); // NOTE: sleep must be used here because `this.$nextTick()` is not waiting long enough in this case - this.focus(); - this.isClearing = false; - }, -}; - -const watch = { - inputValue() { - this.emitUpdateModelValueEvent(); - }, - modelValue() { - this.inputValue = this.modelValue; - }, -}; - -const mounted = function () { - this.focusIfNeeded(); -}; - -const name = 'FormField'; - -const FormField = { - name, - components, - props, - emits, - data, - computed, - methods, - mounted, - watch, -}; - -export default FormField; diff --git a/src/components/FormField/FormField.stories.js b/src/components/FormField/FormField.stories.ts similarity index 96% rename from src/components/FormField/FormField.stories.js rename to src/components/FormField/FormField.stories.ts index b858695..bda0743 100644 --- a/src/components/FormField/FormField.stories.js +++ b/src/components/FormField/FormField.stories.ts @@ -1,4 +1,5 @@ import { action } from '@storybook/addon-actions'; +import type { Meta, StoryFn } from '@storybook/vue3'; import FormField from './FormField.vue'; import { createOptionalDeviceDecorator } from '../../lib/storybookHelpers'; @@ -46,9 +47,9 @@ const FormFieldStories = { pattern: { control: 'text', name: 'Pattern' }, default: { control: { type: 'text' }, table: { type: { summary: null } } }, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { @@ -133,7 +134,7 @@ defaultExample.parameters = { }, }; -const examples = (args, { argTypes }) => ({ +const examples: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/FormField/FormField.test.js b/src/components/FormField/FormField.test.ts similarity index 94% rename from src/components/FormField/FormField.test.js rename to src/components/FormField/FormField.test.ts index d93e416..471e208 100644 --- a/src/components/FormField/FormField.test.js +++ b/src/components/FormField/FormField.test.ts @@ -1,6 +1,5 @@ import { nextTick } from 'vue'; import { mount } from '@vue/test-utils'; -import { render } from '@testing-library/vue'; import FormField from './FormField.vue'; import WithErrorFormFieldWrapper from './UnitTestWrappers/WithErrorFormFieldWrapper.vue'; @@ -22,7 +21,7 @@ describe('FormField unit test', () => { await wrapper.vm.$nextTick(); wrapper.vm.$options.watch.modelValue.call(wrapper.vm, newValue); await wrapper.vm.$nextTick(); - const appliedValue = wrapper.vm.$data.inputValue === newValue; + const appliedValue = wrapper.vm.inputValue === newValue; expect(appliedValue).toBeTruthy(); }); @@ -43,9 +42,9 @@ describe('FormField unit test', () => { }); it('Should emit focus event if startFocused is true', async () => { - const { emitted } = render(FormField, { props: { ...defaultProps, startFocused: true } }); + const wrapper = mount(FormField, { props: { ...defaultProps, startFocused: true }, attachTo: document.body }); await nextTick(); - const focusEventEmitted = emitted('focus'); + const focusEventEmitted = wrapper.emitted('focus'); expect(focusEventEmitted).toBeTruthy(); }); @@ -114,7 +113,7 @@ describe('FormField unit test', () => { wrapper.find('input').element.value = givenValue; wrapper.find('input').trigger('input'); await wrapper.vm.$nextTick(); - const inputEventValue = wrapper.emitted('update:modelValue')[0][0]; + const [inputEventValue] = wrapper.emitted('update:modelValue')?.[0] as string[]; const inputEmittedValueIsCurrent = inputEventValue === givenValue; expect(inputEmittedValueIsCurrent).toBeTruthy(); }); diff --git a/src/components/FormField/FormField.ts b/src/components/FormField/FormField.ts new file mode 100644 index 0000000..14e6a69 --- /dev/null +++ b/src/components/FormField/FormField.ts @@ -0,0 +1,165 @@ +import type { PropType } from 'vue'; +import { defineComponent, nextTick, ref } from 'vue'; +import { CloseBoldIcon, WarningBoldIcon } from '@lana/b2c-mapp-ui-assets'; + +import TextParagraph from '../TextParagraph/TextParagraph.vue'; +import { sleep } from '../../lib/sleepHelper'; + +const FormField = defineComponent({ + name: 'FormField', + components: { + TextParagraph, + CloseBoldIcon, + WarningBoldIcon, + }, + props: { + dataTestId: { + type: String, + default: 'field', + }, + maxLength: { + type: Number, + default: 100, + }, + modelValue: { + type: String, + default: '', + }, + id: { + type: String, + default: '', + }, + name: { + type: String, + default: '', + }, + label: { + type: String, + default: '', + }, + type: { + type: String, + default: '', + }, + disabled: Boolean, + errorLabel: { + type: String, + default: '', + }, + readonly: Boolean, + startFocused: Boolean, + showPrefix: Boolean, + lengthHint: { + type: Number, + default: null, + }, + lengthHintLabel: { + type: String, + default: '', + }, + helpText: { + type: String, + default: '', + }, + hideClearButton: Boolean, + inputmode: { + type: String as PropType<'search' | 'text' | 'decimal' | 'numeric' | 'none' | 'tel' | 'url' | 'email'>, + default: '', + }, + pattern: { + type: String, + default: '', + }, + }, + emits: ['update:modelValue', 'focus', 'blur', 'keypress', 'keyup', 'paste'], + computed: { + maxLengthToUse() { + const result = (this.maxLength || this.lengthHint); + return result; + }, + hasLabel() { + const result = (this.isClearing || this.showPrefix || !!this.inputValue || this.readonly || this.isFocused); + return result; + }, + inputId() { + const result = (this.id || this.name); + return result; + }, + formattedLengthHint() { + const result = `${(this.lengthHint || '')} ${(this.lengthHintLabel || '')}`; + return result; + }, + errorLabelOrHelpText() { + const result = (this.errorLabel || this.helpText || this.formattedLengthHint || ''); + return result; + }, + isClearIconShowing() { + const result = (this.inputValue && !(this.hideClearButton || this.readonly || this.disabled)); + return result; + }, + }, + methods: { + setFocus(focus: boolean) { + this.isFocused = focus; + }, + async focusIfNeeded() { + if (!(this.startFocused && this.input)) { return; } + await nextTick(); + this.input.focus(); + }, + blur() { + if (!this.input) { return; } + this.input.blur(); + }, + emitUpdateModelValueEvent() { + this.$emit('update:modelValue', this.inputValue); + }, + onFocus(event: Event) { + this.setFocus(true); + this.$emit('focus', event); + }, + onBlur(event: Event) { + this.setFocus(false); + this.$emit('blur', event); + }, + onKeypress(event: Event) { + this.$emit('keypress', event); + }, + onKeyup(event: Event) { + this.$emit('keyup', event); + }, + onPaste(event: Event) { + this.$emit('paste', event); + }, + focus() { + this.input.focus(); + }, + async clearValue() { + this.isClearing = true; + this.inputValue = ''; + await sleep(50); // NOTE: sleep must be used here because `this.$nextTick()` is not waiting long enough in this case + this.focus(); + this.isClearing = false; + }, + }, + watch: { + inputValue() { + this.emitUpdateModelValueEvent(); + }, + modelValue() { + this.inputValue = this.modelValue; + }, + }, + setup(props) { + const input = ref(); + const isFocused = ref(false); + const isClearing = ref(false); + const inputValue = ref(props.modelValue); + return { input, isFocused, isClearing, inputValue }; + }, + mounted() { + this.focusIfNeeded(); + }, +}); + +export default FormField; diff --git a/src/components/FormField/FormField.vue b/src/components/FormField/FormField.vue index 82e4584..40c579c 100644 --- a/src/components/FormField/FormField.vue +++ b/src/components/FormField/FormField.vue @@ -43,5 +43,5 @@ - + diff --git a/src/components/FormField/UnitTestWrappers/WithErrorFormFieldWrapper.vue b/src/components/FormField/UnitTestWrappers/WithErrorFormFieldWrapper.vue index 0d43b55..bc649fb 100644 --- a/src/components/FormField/UnitTestWrappers/WithErrorFormFieldWrapper.vue +++ b/src/components/FormField/UnitTestWrappers/WithErrorFormFieldWrapper.vue @@ -3,13 +3,15 @@ - diff --git a/src/components/ForwardButton/ForwardButton.js b/src/components/ForwardButton/ForwardButton.js deleted file mode 100644 index 409e5aa..0000000 --- a/src/components/ForwardButton/ForwardButton.js +++ /dev/null @@ -1,43 +0,0 @@ -import { ChevronRightIcon } from '@lana/b2c-mapp-ui-assets'; - -import Button from '../Button/Button.vue'; - -const components = { - Button, - ChevronRightIcon, -}; - -const props = { - dataTestId: { - type: String, - default: 'forward', - }, - disabled: Boolean, - id: String, - name: String, - debounce: { - type: Boolean, - default: false, - }, - debounceDelay: Number, -}; - -const emits = ['click']; - -const methods = { - onClick(event) { - this.$emit('click', event); - }, -}; - -const name = 'ForwardButton'; - -const ForwardButton = { - name, - components, - props, - emits, - methods, -}; - -export default ForwardButton; diff --git a/src/components/ForwardButton/ForwardButton.stories.js b/src/components/ForwardButton/ForwardButton.stories.ts similarity index 91% rename from src/components/ForwardButton/ForwardButton.stories.js rename to src/components/ForwardButton/ForwardButton.stories.ts index 44b508c..5488703 100644 --- a/src/components/ForwardButton/ForwardButton.stories.js +++ b/src/components/ForwardButton/ForwardButton.stories.ts @@ -1,4 +1,5 @@ import { action } from '@storybook/addon-actions'; +import type { Meta, StoryFn } from '@storybook/vue3'; import ForwardButton from './ForwardButton.vue'; import { createDeviceDecorator } from '../../lib/storybookHelpers'; @@ -24,9 +25,9 @@ const ForwardButtonStories = { debounceDelay: { control: { type: 'number', step: 100 }, name: 'Debounce Delay' }, default: { control: { type: 'text' }, table: { type: { summary: null } } }, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/ForwardButton/ForwardButton.test.js b/src/components/ForwardButton/ForwardButton.test.js deleted file mode 100644 index bb9589d..0000000 --- a/src/components/ForwardButton/ForwardButton.test.js +++ /dev/null @@ -1,30 +0,0 @@ -import { render, fireEvent } from '@testing-library/vue'; - -import ForwardButton from './ForwardButton.vue'; -import { silenceDeprecationErrorsAndInnerComponentWarnings } from '../../lib/testUtils'; - -describe('ForwardButton unit test', () => { - beforeAll(() => { - silenceDeprecationErrorsAndInnerComponentWarnings(jest); - }); - - it('Should wrap a Button with a section', () => { - const { getByTestId } = render(ForwardButton); - const sectionWrapperExist = getByTestId('forward-section'); - expect(sectionWrapperExist).toBeTruthy(); - }); - - it('Should add extra class if given', () => { - const { getByTestId } = render(ForwardButton, { props: { class: 'myClassName' } }); - const extraClassApplied = getByTestId('forward-section').className.includes('myClassName'); - expect(extraClassApplied).toBeTruthy(); - }); - - it('Should emit click event when its clicked', () => { - const { getByTestId, emitted } = render(ForwardButton); - const button = getByTestId('forward-button'); - fireEvent.click(button); - const clickEmitted = emitted().click; - expect(clickEmitted).toBeTruthy(); - }); -}); diff --git a/src/components/ForwardButton/ForwardButton.test.ts b/src/components/ForwardButton/ForwardButton.test.ts new file mode 100644 index 0000000..5531d4f --- /dev/null +++ b/src/components/ForwardButton/ForwardButton.test.ts @@ -0,0 +1,30 @@ +import { mount } from '@vue/test-utils'; + +import ForwardButton from './ForwardButton.vue'; +import { silenceDeprecationErrorsAndInnerComponentWarnings } from '../../lib/testUtils'; + +describe('ForwardButton unit test', () => { + beforeAll(() => { + silenceDeprecationErrorsAndInnerComponentWarnings(jest); + }); + + it('Should wrap a Button with a section', () => { + const wrapper = mount(ForwardButton); + const sectionWrapperExist = wrapper.find('[data-testid="forward-section"]'); + expect(sectionWrapperExist).toBeTruthy(); + }); + + it('Should add extra class if given', () => { + const wrapper = mount(ForwardButton, { props: { class: 'myClassName' } }); + const extraClassApplied = wrapper.find('[data-testid="forward-section"]').classes().includes('myClassName'); + expect(extraClassApplied).toBeTruthy(); + }); + + it('Should emit click event when its clicked', async () => { + const wrapper = mount(ForwardButton); + const button = wrapper.find('[data-testid="forward-button"]'); + await button.trigger('click'); + const clickEmitted = wrapper.emitted('click'); + expect(clickEmitted).toBeTruthy(); + }); +}); diff --git a/src/components/ForwardButton/ForwardButton.ts b/src/components/ForwardButton/ForwardButton.ts new file mode 100644 index 0000000..82cb348 --- /dev/null +++ b/src/components/ForwardButton/ForwardButton.ts @@ -0,0 +1,43 @@ +import { ChevronRightIcon } from '@lana/b2c-mapp-ui-assets'; +import { defineComponent } from 'vue'; + +import Button from '../Button/Button.vue'; + +const ForwardButton = defineComponent({ + name: 'ForwardButton', + components: { + Button, + ChevronRightIcon, + }, + props: { + dataTestId: { + type: String, + default: 'forward', + }, + disabled: Boolean, + id: { + type: String, + default: '', + }, + name: { + type: String, + default: '', + }, + debounce: { + type: Boolean, + default: false, + }, + debounceDelay: { + type: Number, + default: 0, + }, + }, + emits: ['click'], + methods: { + onClick(event: Event) { + this.$emit('click', event); + }, + }, +}); + +export default ForwardButton; diff --git a/src/components/ForwardButton/ForwardButton.vue b/src/components/ForwardButton/ForwardButton.vue index d30ef56..0bdadf1 100644 --- a/src/components/ForwardButton/ForwardButton.vue +++ b/src/components/ForwardButton/ForwardButton.vue @@ -15,5 +15,5 @@ - + diff --git a/src/components/Heading/Heading.js b/src/components/Heading/Heading.js deleted file mode 100644 index e234dff..0000000 --- a/src/components/Heading/Heading.js +++ /dev/null @@ -1,46 +0,0 @@ -const availableSizes = [ - 'xxxl', - 'xxl', - 'xl', - 'large', - 'medium', - 'small', - 'xsmall', -]; - -const availableWeights = [ - 'normal', - 'medium', - 'semibold', -]; - -const props = { - dataTestId: { - type: String, - default: 'heading', - }, - weight: { - type: String, - default: 'normal', - validator(value) { return (!value || availableWeights.includes(value)); }, - }, - size: { - type: String, - default: 'xl', - validator(value) { return (!value || availableSizes.includes(value)); }, - }, -}; - -const name = 'Heading'; - -const Heading = { - name, - props, -}; - -export { - availableSizes, - availableWeights, -}; - -export default Heading; diff --git a/src/components/Heading/Heading.stories.js b/src/components/Heading/Heading.stories.ts similarity index 92% rename from src/components/Heading/Heading.stories.js rename to src/components/Heading/Heading.stories.ts index 8e195ca..23b2250 100644 --- a/src/components/Heading/Heading.stories.js +++ b/src/components/Heading/Heading.stories.ts @@ -1,3 +1,5 @@ +import type { Meta, StoryFn } from '@storybook/vue3'; + import Heading from './Heading.vue'; import { availableSizes, availableWeights } from './Heading'; import { capitalizeFirstLetter } from '../../lib/textHelper'; @@ -22,9 +24,9 @@ const HeadingStories = { weight: { control: 'select', name: 'Weight', options: [...availableWeights, ''] }, default: { control: { type: 'text' }, table: { type: { summary: null } } }, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { @@ -60,7 +62,7 @@ defaultExample.parameters = { }, }; -const weights = (args, { argTypes }) => ({ +const weights: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { @@ -105,7 +107,7 @@ weights.parameters = { }, }; -const sizes = (args, { argTypes }) => ({ +const sizes: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/Heading/Heading.ts b/src/components/Heading/Heading.ts new file mode 100644 index 0000000..58e3e5d --- /dev/null +++ b/src/components/Heading/Heading.ts @@ -0,0 +1,44 @@ +import { defineComponent } from 'vue'; + +const availableSizes = [ + 'xxxl', + 'xxl', + 'xl', + 'large', + 'medium', + 'small', + 'xsmall', +]; + +const availableWeights = [ + 'normal', + 'medium', + 'semibold', +]; + +const Heading = defineComponent({ + name: 'Heading', + props: { + dataTestId: { + type: String, + default: 'heading', + }, + weight: { + type: String, + default: 'normal', + validator(value: string) { return (!value || availableWeights.includes(value)); }, + }, + size: { + type: String, + default: 'xl', + validator(value: string) { return (!value || availableSizes.includes(value)); }, + }, + }, +}); + +export { + availableSizes, + availableWeights, +}; + +export default Heading; diff --git a/src/components/Heading/Heading.vue b/src/components/Heading/Heading.vue index cf4b63d..9b61383 100644 --- a/src/components/Heading/Heading.vue +++ b/src/components/Heading/Heading.vue @@ -7,5 +7,5 @@ - + diff --git a/src/components/Icons/Icons.stories.js b/src/components/Icons/Icons.stories.ts similarity index 92% rename from src/components/Icons/Icons.stories.js rename to src/components/Icons/Icons.stories.ts index 6053429..c433b2b 100644 --- a/src/components/Icons/Icons.stories.js +++ b/src/components/Icons/Icons.stories.ts @@ -1,4 +1,5 @@ import * as AllIcons from '@lana/b2c-mapp-ui-assets'; +import type { Meta, StoryFn } from '@storybook/vue3'; const IconStories = { title: 'Components/Icons', @@ -8,9 +9,9 @@ const IconStories = { argTypes: { size: { name: 'Size', control: { type: 'number', min: 12, max: 72 } }, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/Infobox/Infobox.js b/src/components/Infobox/Infobox.js deleted file mode 100644 index 247dae9..0000000 --- a/src/components/Infobox/Infobox.js +++ /dev/null @@ -1,22 +0,0 @@ -import TextParagraph from '../TextParagraph/TextParagraph.vue'; - -const components = { - TextParagraph, -}; - -const props = { - dataTestId: { - type: String, - default: 'info', - }, -}; - -const name = 'Infobox'; - -const Infobox = { - name, - components, - props, -}; - -export default Infobox; diff --git a/src/components/Infobox/Infobox.stories.js b/src/components/Infobox/Infobox.stories.ts similarity index 87% rename from src/components/Infobox/Infobox.stories.js rename to src/components/Infobox/Infobox.stories.ts index e1d4fb7..b16a352 100644 --- a/src/components/Infobox/Infobox.stories.js +++ b/src/components/Infobox/Infobox.stories.ts @@ -1,3 +1,5 @@ +import type { Meta, StoryFn } from '@storybook/vue3'; + import Infobox from './Infobox.vue'; import { createDeviceDecorator } from '../../lib/storybookHelpers'; import RenderString from '../../lib/renderString'; @@ -16,9 +18,9 @@ const InfoboxStories = { ...deviceDecorator.argTypes, default: { control: { type: 'text' }, table: { type: { summary: null } } }, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/Infobox/Infobox.ts b/src/components/Infobox/Infobox.ts new file mode 100644 index 0000000..4028f18 --- /dev/null +++ b/src/components/Infobox/Infobox.ts @@ -0,0 +1,18 @@ +import { defineComponent } from 'vue'; + +import TextParagraph from '../TextParagraph/TextParagraph.vue'; + +const Infobox = defineComponent({ + name: 'Infobox', + components: { + TextParagraph, + }, + props: { + dataTestId: { + type: String, + default: 'info', + }, + }, +}); + +export default Infobox; diff --git a/src/components/Infobox/Infobox.vue b/src/components/Infobox/Infobox.vue index 6e18b31..ad8fc93 100644 --- a/src/components/Infobox/Infobox.vue +++ b/src/components/Infobox/Infobox.vue @@ -6,5 +6,5 @@ - + diff --git a/src/components/Issues/Issues.stories.js b/src/components/Issues/Issues.stories.ts similarity index 80% rename from src/components/Issues/Issues.stories.js rename to src/components/Issues/Issues.stories.ts index 252a9b5..4957928 100644 --- a/src/components/Issues/Issues.stories.js +++ b/src/components/Issues/Issues.stories.ts @@ -1,10 +1,12 @@ +import type { Meta, StoryFn } from '@storybook/vue3'; + const IssuesStories = { title: 'How to file issues', args: {}, argTypes: {}, -}; +} as Meta; -const howToFileIssues = (args, { argTypes }) => ({ +const howToFileIssues: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, template: ` diff --git a/src/components/LinearProgress/LinearProgress.js b/src/components/LinearProgress/LinearProgress.js deleted file mode 100644 index eb31dbf..0000000 --- a/src/components/LinearProgress/LinearProgress.js +++ /dev/null @@ -1,92 +0,0 @@ -const availableColors = ['blue', 'brown', 'green', 'orange', 'pink', 'purple', 'red', 'yellow']; - -const props = { - dataTestId: { - type: String, - default: 'linear-progress', - }, - progress: Number, - total: Number, - percentage: Number, - color: { - type: String, - default: 'blue', - validator(value) { return (!value || availableColors.includes(value)); }, - }, - animate: Boolean, - animationDuration: { - type: Number, - default: 1000, - }, - circularAnimation: Boolean, -}; - -const emits = ['animationend', 'error']; - -const computed = { - progressPercentage() { - const result = (this.percentage || ((this.progress / this.total) * 100) || 0); - switch (true) { - case result < 0: - return 0; - case result > 100: - return 100; - default: - return result; - } - }, - progressStyle() { - const result = { - width: `${this.progressPercentage}%`, - }; - return result; - }, - circleStyle() { - const result = { - left: `${this.progressPercentage}%`, - }; - return result; - }, -}; - -const methods = { - startAnimation() { - try { - const { bar, circle } = this.$refs; - const barAnimation = [ - { width: 0 }, - { width: `${this.progressPercentage}%` }, - ]; - const circleAnimation = [ - { left: 0 }, - { left: `${this.progressPercentage}%` }, - ]; - const animation = bar.animate(barAnimation, { duration: this.animationDuration, iterations: (this.circularAnimation) ? Infinity : 1 }); - circle.animate(circleAnimation, { duration: this.animationDuration, iterations: (this.circularAnimation) ? Infinity : 1 }); - animation.onfinish = this.emitEventFinish; - } catch (error) { - this.$emit('error', error); - } - }, - emitEventFinish(event) { - this.$emit('animationend', event); - }, -}; - -const mounted = function () { - if (!this.animate) { return; } - this.startAnimation(); -}; - -const name = 'LinearProgress'; - -const LinearProgress = { - name, - props, - emits, - computed, - methods, - mounted, -}; - -export default LinearProgress; diff --git a/src/components/LinearProgress/LinearProgress.stories.js b/src/components/LinearProgress/LinearProgress.stories.ts similarity index 95% rename from src/components/LinearProgress/LinearProgress.stories.js rename to src/components/LinearProgress/LinearProgress.stories.ts index b42319d..9fe4872 100644 --- a/src/components/LinearProgress/LinearProgress.stories.js +++ b/src/components/LinearProgress/LinearProgress.stories.ts @@ -1,5 +1,6 @@ import { action } from '@storybook/addon-actions'; import { MedalLevel0Icon } from '@lana/b2c-mapp-ui-assets'; +import type { Meta, StoryFn } from '@storybook/vue3'; import LinearProgress from './LinearProgress.vue'; import SpecCard from '../SpecCard/SpecCard.vue'; @@ -38,9 +39,9 @@ const LinearProgressStories = { animationDuration: { name: 'Animate duration', control: { type: 'number' } }, circularAnimation: { name: 'Infinite animation?', control: { type: 'boolean' } }, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { @@ -89,7 +90,7 @@ defaultExample.parameters = { }, }; -const withCardExample = (args, { argTypes }) => ({ +const withCardExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/LinearProgress/LinearProgress.test.js b/src/components/LinearProgress/LinearProgress.test.ts similarity index 86% rename from src/components/LinearProgress/LinearProgress.test.js rename to src/components/LinearProgress/LinearProgress.test.ts index 8b9db80..198948f 100644 --- a/src/components/LinearProgress/LinearProgress.test.js +++ b/src/components/LinearProgress/LinearProgress.test.ts @@ -12,21 +12,21 @@ describe('LinearProgress unit test', () => { const wrapper = mount(LinearProgress, { props: { ...defaultProps } }); await wrapper.vm.$nextTick(); const description = wrapper.find('div[data-testid="linear-progress-progress-bar"]'); - expect(description.element.style.width).toContain('50%'); + expect(description.attributes('style')).toContain('width: 50%'); }); it('Percentage should override given progress/total percentage', async () => { const wrapper = mount(LinearProgress, { props: { ...defaultProps, percentage: 25 } }); await wrapper.vm.$nextTick(); const description = wrapper.find('div[data-testid="linear-progress-progress-bar"]'); - expect(description.element.style.width).toContain('25%'); + expect(description.attributes('style')).toContain('width: 25%'); }); it('Should hide progress circle on 0 progress', async () => { const wrapper = mount(LinearProgress, { props: { ...defaultProps, progress: 0 } }); await wrapper.vm.$nextTick(); const circle = wrapper.find('div[data-testid="linear-progress-circle-bar"]'); - expect(circle.element.style.display).toContain('none'); + expect(circle.attributes('style')).toContain('display: none'); }); it('Should send error for test animation', async () => { diff --git a/src/components/LinearProgress/LinearProgress.ts b/src/components/LinearProgress/LinearProgress.ts new file mode 100644 index 0000000..22aac67 --- /dev/null +++ b/src/components/LinearProgress/LinearProgress.ts @@ -0,0 +1,95 @@ +import { defineComponent, ref } from 'vue'; + +const availableColors = ['blue', 'brown', 'green', 'orange', 'pink', 'purple', 'red', 'yellow']; + +const LinearProgress = defineComponent({ + name: 'LinearProgress', + props: { + dataTestId: { + type: String, + default: 'linear-progress', + }, + progress: { + type: Number, + default: null, + }, + total: { + type: Number, + default: null, + }, + percentage: { + type: Number, + default: 0, + }, + color: { + type: String, + default: 'blue', + validator(value: string) { return (!value || availableColors.includes(value)); }, + }, + animate: Boolean, + animationDuration: { + type: Number, + default: 1000, + }, + circularAnimation: Boolean, + }, + emits: ['animationend', 'error'], + computed: { + progressPercentage() { + const result = (this.percentage || ((this.progress / this.total) * 100) || 0); + switch (true) { + case result < 0: + return 0; + case result > 100: + return 100; + default: + return result; + } + }, + progressStyle() { + const result = { + width: `${this.progressPercentage}%`, + }; + return result; + }, + circleStyle() { + const result = { + left: `${this.progressPercentage}%`, + }; + return result; + }, + }, + methods: { + startAnimation() { + try { + const barAnimation = [ + { width: 0 }, + { width: `${this.progressPercentage}%` }, + ]; + const circleAnimation = [ + { left: 0 }, + { left: `${this.progressPercentage}%` }, + ]; + const animation = this.bar.animate(barAnimation, { duration: this.animationDuration, iterations: (this.circularAnimation) ? Infinity : 1 }); + this.circle.animate(circleAnimation, { duration: this.animationDuration, iterations: (this.circularAnimation) ? Infinity : 1 }); + animation.onfinish = this.emitEventFinish; + } catch (error) { + this.$emit('error', error); + } + }, + emitEventFinish(event: Event) { + this.$emit('animationend', event); + }, + }, + setup() { + const bar = ref(); + const circle = ref(); + return { bar, circle }; + }, + mounted() { + if (!this.animate) { return; } + this.startAnimation(); + }, +}); + +export default LinearProgress; diff --git a/src/components/LinearProgress/LinearProgress.vue b/src/components/LinearProgress/LinearProgress.vue index 52d72ee..87c298a 100644 --- a/src/components/LinearProgress/LinearProgress.vue +++ b/src/components/LinearProgress/LinearProgress.vue @@ -26,5 +26,5 @@ - + diff --git a/src/components/ListItem/ListItem.js b/src/components/ListItem/ListItem.js deleted file mode 100644 index 233d7ad..0000000 --- a/src/components/ListItem/ListItem.js +++ /dev/null @@ -1,80 +0,0 @@ -import Heading from '../Heading/Heading.vue'; -import TextParagraph from '../TextParagraph/TextParagraph.vue'; -import ToggleSwitch from '../ToggleSwitch/ToggleSwitch.vue'; -import Checkbox from '../Checkbox/Checkbox.vue'; - -const components = { - Checkbox, - Heading, - TextParagraph, - ToggleSwitch, -}; - -const props = { - dataTestId: { - type: String, - default: 'list-item', - }, - title: String, - description: String, - modelValue: Boolean, - transparent: Boolean, - linkTitle: String, - hasToggle: Boolean, - hasCheckbox: Boolean, - disabled: Boolean, - rightLabel: String, -}; - -const emits = ['update:modelValue', 'linkClick']; - -const data = function () { - return { - isChecked: this.modelValue, - }; -}; - -const methods = { - emitUpdateModelValueEvent() { - this.$emit('update:modelValue', this.isChecked); - }, - onLinkClick(event) { - if (this.disabled) { return; } - this.$emit('linkClick', event); - }, -}; - -const watch = { - isChecked() { - this.emitUpdateModelValueEvent(); - }, - modelValue() { - this.isChecked = this.modelValue; - }, -}; - -const computed = { - hasDefaultSlot() { - const result = this.$slots.default && this.$slots.default().findIndex((node) => (node.type !== Comment)) !== -1; - return result; - }, - isRightLabelShowing() { - const result = (!this.hasToggle && this.rightLabel); - return result; - }, -}; - -const name = 'ListItem'; - -const ListItem = { - name, - components, - props, - emits, - data, - methods, - watch, - computed, -}; - -export default ListItem; diff --git a/src/components/ListItem/ListItem.stories.js b/src/components/ListItem/ListItem.stories.ts similarity index 95% rename from src/components/ListItem/ListItem.stories.js rename to src/components/ListItem/ListItem.stories.ts index a22b212..70a560c 100644 --- a/src/components/ListItem/ListItem.stories.js +++ b/src/components/ListItem/ListItem.stories.ts @@ -1,4 +1,5 @@ import { action } from '@storybook/addon-actions'; +import type { Meta, StoryFn } from '@storybook/vue3'; import ListItem from './ListItem.vue'; import { createDeviceDecorator } from '../../lib/storybookHelpers'; @@ -34,9 +35,9 @@ const ListItemStories = { rightLabel: { control: 'text', name: 'Right label' }, default: { control: { type: 'text' }, table: { type: { summary: null } } }, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/ListItem/ListItem.test.js b/src/components/ListItem/ListItem.test.ts similarity index 96% rename from src/components/ListItem/ListItem.test.js rename to src/components/ListItem/ListItem.test.ts index a7f5dd4..797857d 100644 --- a/src/components/ListItem/ListItem.test.js +++ b/src/components/ListItem/ListItem.test.ts @@ -16,13 +16,13 @@ describe('ListItem', () => { describe('General behavior', () => { it('Should show given title', () => { const { getByTestId } = render(ListItem, { props: { ...defaultProps } }); - const titleExists = getByTestId('heading').textContent.includes('Main Title'); + const titleExists = getByTestId('heading').textContent?.includes('Main Title'); expect(titleExists).toBeTruthy(); }); it('Should show given description if given', () => { const { getByTestId } = render(ListItem, { props: { ...defaultProps, description: 'desc' } }); - const descriptionExists = getByTestId('list-item-description').textContent.includes('desc'); + const descriptionExists = getByTestId('list-item-description').textContent?.includes('desc'); expect(descriptionExists).toBeTruthy(); }); @@ -59,7 +59,7 @@ describe('ListItem', () => { it('Should show link content if linkTitle is given', () => { const { getByTestId } = render(ListItem, { props: { ...withLinkProps } }); - const linkExists = getByTestId('list-item-heading-link').textContent.includes('link text'); + const linkExists = getByTestId('list-item-heading-link').textContent?.includes('link text'); expect(linkExists).toBeTruthy(); }); @@ -121,7 +121,7 @@ describe('ListItem', () => { await wrapper.vm.$nextTick(); wrapper.vm.$options.watch.modelValue.call(wrapper.vm, true); await wrapper.vm.$nextTick(); - const givenValueIsTaken = wrapper.vm.$data.isChecked; + const givenValueIsTaken = wrapper.vm.isChecked; expect(givenValueIsTaken).toBeTruthy(); }); }); @@ -164,7 +164,7 @@ describe('ListItem', () => { await wrapper.vm.$nextTick(); wrapper.vm.$options.watch.modelValue.call(wrapper.vm, true); await wrapper.vm.$nextTick(); - const givenValueIsTaken = wrapper.vm.$data.isChecked; + const givenValueIsTaken = wrapper.vm.isChecked; expect(givenValueIsTaken).toBeTruthy(); }); }); diff --git a/src/components/ListItem/ListItem.ts b/src/components/ListItem/ListItem.ts new file mode 100644 index 0000000..31edb18 --- /dev/null +++ b/src/components/ListItem/ListItem.ts @@ -0,0 +1,78 @@ +import { defineComponent } from 'vue'; + +import Heading from '../Heading/Heading.vue'; +import TextParagraph from '../TextParagraph/TextParagraph.vue'; +import ToggleSwitch from '../ToggleSwitch/ToggleSwitch.vue'; +import Checkbox from '../Checkbox/Checkbox.vue'; + +const ListItem = defineComponent({ + name: 'ListItem', + components: { + Checkbox, + Heading, + TextParagraph, + ToggleSwitch, + }, + props: { + dataTestId: { + type: String, + default: 'list-item', + }, + title: { + type: String, + default: '', + }, + description: { + type: String, + default: '', + }, + modelValue: Boolean, + transparent: Boolean, + linkTitle: { + type: String, + default: '', + }, + hasToggle: Boolean, + hasCheckbox: Boolean, + disabled: Boolean, + rightLabel: { + type: String, + default: '', + }, + }, + emits: ['update:modelValue', 'linkClick'], + data() { + return { + isChecked: this.modelValue, + }; + }, + methods: { + emitUpdateModelValueEvent() { + this.$emit('update:modelValue', this.isChecked); + }, + onLinkClick(event: Event) { + if (this.disabled) { return; } + this.$emit('linkClick', event); + }, + }, + watch: { + isChecked() { + this.emitUpdateModelValueEvent(); + }, + modelValue() { + this.isChecked = this.modelValue; + }, + }, + computed: { + hasDefaultSlot() { + const result = this.$slots.default && this.$slots.default().findIndex((node) => (node.type !== Comment)) !== -1; + return result; + }, + isRightLabelShowing() { + const result = (!this.hasToggle && this.rightLabel); + return result; + }, + }, +}); + +export default ListItem; diff --git a/src/components/ListItem/ListItem.vue b/src/components/ListItem/ListItem.vue index 6f260ad..51af621 100644 --- a/src/components/ListItem/ListItem.vue +++ b/src/components/ListItem/ListItem.vue @@ -39,5 +39,5 @@ - + diff --git a/src/components/LoadingSpinner/LoadingSpinner.js b/src/components/LoadingSpinner/LoadingSpinner.js deleted file mode 100644 index 1f75ef2..0000000 --- a/src/components/LoadingSpinner/LoadingSpinner.js +++ /dev/null @@ -1,14 +0,0 @@ -import { MopSpinnerIcon } from '@lana/b2c-mapp-ui-assets'; - -const components = { - MopSpinnerIcon, -}; - -const name = 'LoadingSpinner'; - -const LoadingSpinner = { - name, - components, -}; - -export default LoadingSpinner; diff --git a/src/components/LoadingSpinner/LoadingSpinner.stories.js b/src/components/LoadingSpinner/LoadingSpinner.stories.ts similarity index 85% rename from src/components/LoadingSpinner/LoadingSpinner.stories.js rename to src/components/LoadingSpinner/LoadingSpinner.stories.ts index 327ff1a..eab4718 100644 --- a/src/components/LoadingSpinner/LoadingSpinner.stories.js +++ b/src/components/LoadingSpinner/LoadingSpinner.stories.ts @@ -1,3 +1,5 @@ +import type { Meta, StoryFn } from '@storybook/vue3'; + import LoadingSpinner from './LoadingSpinner.vue'; import { createDeviceDecorator } from '../../lib/storybookHelpers'; @@ -13,9 +15,9 @@ const LoadingSpinnerStories = { argTypes: { ...deviceDecorator.argTypes, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/LoadingSpinner/LoadingSpinner.test.js b/src/components/LoadingSpinner/LoadingSpinner.test.ts similarity index 100% rename from src/components/LoadingSpinner/LoadingSpinner.test.js rename to src/components/LoadingSpinner/LoadingSpinner.test.ts diff --git a/src/components/LoadingSpinner/LoadingSpinner.ts b/src/components/LoadingSpinner/LoadingSpinner.ts new file mode 100644 index 0000000..5565e26 --- /dev/null +++ b/src/components/LoadingSpinner/LoadingSpinner.ts @@ -0,0 +1,11 @@ +import { MopSpinnerIcon } from '@lana/b2c-mapp-ui-assets'; +import { defineComponent } from 'vue'; + +const LoadingSpinner = defineComponent({ + name: 'LoadingSpinner', + components: { + MopSpinnerIcon, + }, +}); + +export default LoadingSpinner; diff --git a/src/components/LoadingSpinner/LoadingSpinner.vue b/src/components/LoadingSpinner/LoadingSpinner.vue index 643cfa0..5fc6c00 100644 --- a/src/components/LoadingSpinner/LoadingSpinner.vue +++ b/src/components/LoadingSpinner/LoadingSpinner.vue @@ -2,5 +2,5 @@ - + diff --git a/src/components/PhoneNumberField/PhoneNumberField.js b/src/components/PhoneNumberField/PhoneNumberField.js deleted file mode 100644 index 2430faa..0000000 --- a/src/components/PhoneNumberField/PhoneNumberField.js +++ /dev/null @@ -1,141 +0,0 @@ -import { AsYouType, getCountryCallingCode, getCountries } from 'libphonenumber-js/custom'; - -import FormField from '../FormField/FormField.vue'; -import TextParagraph from '../TextParagraph/TextParagraph.vue'; -import phoneNumberMetadata from '../../../data/libphonenumber-metadata.min.json'; -import { onlyDigitsRegexp, nonDigitRegexp } from '../../lib/regexHelper'; - -const getAvailableCountryCodes = () => getCountries(phoneNumberMetadata); - -const components = { - FormField, - TextParagraph, -}; - -const props = { - dataTestId: { - type: String, - default: 'phone-field', - }, - modelValue: { - type: String, - default: '', - }, - countryCode: { - type: String, - required: true, - validator(value) { return getAvailableCountryCodes().includes(value); }, - }, - hideCountryCodeUntilFocus: Boolean, - startFocused: Boolean, - label: String, - errorLabel: String, - id: String, - name: String, - disabled: Boolean, - readonly: Boolean, - maxLength: Number, - lengthHint: Number, - lengthHintLabel: String, - helpText: String, - hideClearButton: Boolean, - maxPhoneNumberLength: { - type: Number, - default: 10, - }, -}; - -const emits = ['update:modelValue', 'update:formattedValue', 'update:isValid', 'focus', 'blur']; - -const data = function () { - return { - isFocused: false, - inputValue: this.modelValue, - isValid: false, - }; -}; - -const computed = { - cleanedInputValue() { - if (!this.inputValue) { return ''; } - const result = this.inputValue.replace(nonDigitRegexp, ''); - return result; - }, - formattedPhoneNumber() { - if (!(this.inputValue && this.countryCode)) { return ''; } - const asYouType = new AsYouType(this.countryCode, phoneNumberMetadata); - const result = asYouType.input(this.cleanedInputValue); - return result; - }, - prefix() { - if (!this.countryCode) { return; } - const result = `+${getCountryCallingCode(this.countryCode, phoneNumberMetadata)}`; - return result; - }, - hideCountryCode() { - if (!this.hideCountryCodeUntilFocus) { return; } - const result = (!this.isFocused && !this.inputValue); - return result; - }, -}; - -const methods = { - emitUpdateModelValueEvent() { - this.$emit('update:modelValue', this.cleanedInputValue); - }, - onFocus(event) { - this.isFocused = true; - this.$emit('focus', event); - }, - onBlur(event) { - this.isFocused = false; - this.$emit('blur', event); - }, - supressNonDigitCharacterEntry(event) { - if (onlyDigitsRegexp.test(event.key)) { return; } - event.preventDefault(); - }, - focus() { - this.$refs.field.focus(); - }, - isPhoneNumberValid() { - if (this.maxPhoneNumberLength && (this.cleanedInputValue.length > this.maxPhoneNumberLength)) { return false; } - const asYouType = new AsYouType(this.countryCode, phoneNumberMetadata); - asYouType.input((this.inputValue || '')); - const result = asYouType.isPossible(); - return result; - }, -}; - -const watch = { - inputValue() { - if (this.inputValue) { this.inputValue = this.formattedPhoneNumber; } - this.emitUpdateModelValueEvent(); - this.$emit('update:isValid', this.isPhoneNumberValid()); - }, - formattedPhoneNumber() { - this.$emit('update:formattedValue', this.formattedPhoneNumber); - }, - modelValue() { - this.inputValue = this.modelValue; - }, -}; - -const name = 'PhoneNumberField'; - -const PhoneNumberField = { - name, - components, - computed, - props, - emits, - data, - methods, - watch, -}; - -export { - getAvailableCountryCodes, -}; - -export default PhoneNumberField; diff --git a/src/components/PhoneNumberField/PhoneNumberField.stories.js b/src/components/PhoneNumberField/PhoneNumberField.stories.ts similarity index 96% rename from src/components/PhoneNumberField/PhoneNumberField.stories.js rename to src/components/PhoneNumberField/PhoneNumberField.stories.ts index e442316..4298dfd 100644 --- a/src/components/PhoneNumberField/PhoneNumberField.stories.js +++ b/src/components/PhoneNumberField/PhoneNumberField.stories.ts @@ -1,4 +1,5 @@ import { action } from '@storybook/addon-actions'; +import type { Meta, StoryFn } from '@storybook/vue3'; import PhoneNumberField from './PhoneNumberField.vue'; import { getAvailableCountryCodes } from './PhoneNumberField'; @@ -43,9 +44,9 @@ const PhoneNumberFieldStories = { helpText: { control: 'text', name: 'Help Text' }, hideClearButton: { control: 'boolean', name: 'Hide Clear Button?' }, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { @@ -110,7 +111,7 @@ const defaultExample = (args, { argTypes }) => ({ `, }); -const countryCodes = (args, { argTypes }) => ({ +const countryCodes: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { @@ -161,7 +162,7 @@ countryCodes.parameters = { }, }; -const moreExamples = (args, { argTypes }) => ({ +const moreExamples: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/PhoneNumberField/PhoneNumberField.test.js b/src/components/PhoneNumberField/PhoneNumberField.test.ts similarity index 72% rename from src/components/PhoneNumberField/PhoneNumberField.test.js rename to src/components/PhoneNumberField/PhoneNumberField.test.ts index 6038231..6186595 100644 --- a/src/components/PhoneNumberField/PhoneNumberField.test.js +++ b/src/components/PhoneNumberField/PhoneNumberField.test.ts @@ -2,11 +2,11 @@ import { mount } from '@vue/test-utils'; import { render } from '@testing-library/vue'; import PhoneNumberField from './PhoneNumberField.vue'; -import { silenceDeprecationErrorsAndInnerComponentWarnings } from '../../lib/testUtils'; +import { silenceInnerComponentWarnings } from '../../lib/testUtils'; describe('UI/forms/PhoneNumberField', () => { beforeAll(() => { - silenceDeprecationErrorsAndInnerComponentWarnings(jest); + silenceInnerComponentWarnings(jest); }); const defaultProps = { @@ -20,7 +20,7 @@ describe('UI/forms/PhoneNumberField', () => { await wrapper.vm.$nextTick(); wrapper.vm.$options.watch.modelValue.call(wrapper.vm, givenValue); await wrapper.vm.$nextTick(); - const takesGivenValue = wrapper.vm.$data.inputValue === givenValue; + const takesGivenValue = wrapper.vm.inputValue === givenValue; expect(takesGivenValue).toBeTruthy(); }); @@ -61,13 +61,13 @@ describe('UI/forms/PhoneNumberField', () => { it('Should show given errorLabel text content if given value is NOT valid', () => { const { getByTestId } = render(PhoneNumberField, { props: { ...defaultProps, errorLabel: 'Error!', modelValue: '551234123234324' } }); - const errorLabel = getByTestId('phone-field-helptext').textContent.includes('Error!'); + const errorLabel = getByTestId('phone-field-helptext').textContent?.includes('Error!'); expect(errorLabel).toBeTruthy(); }); it('Should show country code if hideCountryCodeUntilFocus is false', () => { const { getByTestId } = render(PhoneNumberField, { props: { ...defaultProps, hideCountryCodeUntilFocus: false, modelValue: '551234123234324' } }); - const prefixIsShown = getByTestId('phone-field-prefix').textContent.includes('+52'); + const prefixIsShown = getByTestId('phone-field-prefix').textContent?.includes('+52'); expect(prefixIsShown).toBeTruthy(); }); @@ -79,78 +79,60 @@ describe('UI/forms/PhoneNumberField', () => { it('Should show country code if given hideCountryCodeUntilFocus is true and there is a value', () => { const { getByTestId } = render(PhoneNumberField, { props: { ...defaultProps, hideCountryCodeUntilFocus: true, modelValue: '551234123234324' } }); - const prefixIsShown = getByTestId('phone-field-prefix').textContent.includes('+52'); + const prefixIsShown = getByTestId('phone-field-prefix').textContent?.includes('+52'); expect(prefixIsShown).toBeTruthy(); }); it('Should NOT show country code if is not given', () => { const { getByTestId } = render(PhoneNumberField, { props: { ...defaultProps, countryCode: null, modelValue: '551234123234324' } }); - const prefix = getByTestId('phone-field-prefix').textContent.replace(/\s+$/, ''); + const prefix = getByTestId('phone-field-prefix').textContent?.replace(/\s+$/, ''); const prefixIsNotShown = !prefix; expect(prefixIsNotShown).toBeTruthy(); }); - it('Should emit input event when value changes', () => new Promise((resolve) => { + it('Should emit input event when value changes', async () => { const wrapper = mount(PhoneNumberField, { props: { ...defaultProps, modelValue: '55 1234 1234' } }); - wrapper.vm.$nextTick(); + await wrapper.vm.$nextTick(); const phoneInput = wrapper.find('input[data-testid="phone-field-input"]'); - phoneInput.setValue('13821'); - wrapper.vm.$nextTick(); - setTimeout(() => { - const inputEmmitedEvent = wrapper.emitted('update:modelValue'); - expect(inputEmmitedEvent).toBeTruthy(); - resolve(); - }); - })); - - it('Should emit current value of the input in the input event when value changes', () => new Promise((resolve) => { + await phoneInput.setValue('13821'); + await wrapper.vm.$nextTick(); + const inputEmmitedEvent = wrapper.emitted('update:modelValue'); + expect(inputEmmitedEvent).toBeTruthy(); + }); + + it('Should emit current value of the input in the input event when value changes', async () => { const wrapper = mount(PhoneNumberField, { props: { ...defaultProps, modelValue: '55 1234 1234' } }); - wrapper.vm.$nextTick(); + await wrapper.vm.$nextTick(); const phoneInput = wrapper.find('input[data-testid="phone-field-input"]'); - phoneInput.setValue('13821'); - wrapper.vm.$nextTick(); - setTimeout(() => { - const emittedCurrentValue = wrapper.emitted('update:modelValue')[0][0] === '13821'; - expect(emittedCurrentValue).toBeTruthy(); - resolve(); - }); - })); - it('Should emit formatted value of the input in the input event when value changes', () => new Promise((resolve) => { + await phoneInput.setValue('13821'); + await wrapper.vm.$nextTick(); + const [emittedCurrentValue] = wrapper.emitted('update:modelValue')?.[0] as string[]; + expect(emittedCurrentValue).toBe('13821'); + }); + it('Should emit formatted value of the input in the input event when value changes', async () => { const wrapper = mount(PhoneNumberField, { props: { ...defaultProps, modelValue: '55 1234 1234' } }); - wrapper.vm.$nextTick(); + await wrapper.vm.$nextTick(); const phoneInput = wrapper.find('input[data-testid="phone-field-input"]'); - phoneInput.setValue('13821'); - wrapper.vm.$nextTick(); - setTimeout(() => { - const emittedCurrentValue = wrapper.emitted('update:formattedValue')[0][0] === '1382 1'; - expect(emittedCurrentValue).toBeTruthy(); - resolve(); - }); - })); - - it('Should emit a blur event when its blurred', () => new Promise((resolve) => { + await phoneInput.setValue('13821'); + const [emittedCurrentValue] = wrapper.emitted('update:formattedValue')?.[0] as string[]; + expect(emittedCurrentValue).toBe('1382 1'); + }); + + it('Should emit a blur event when its blurred', async () => { const wrapper = mount(PhoneNumberField, { props: { ...defaultProps, modelValue: '55 1234 1234' } }); - wrapper.vm.$nextTick(); + await wrapper.vm.$nextTick(); const phoneInput = wrapper.find('input[data-testid="phone-field-input"]'); - phoneInput.trigger('blur'); - wrapper.vm.$nextTick(); - setTimeout(() => { - const emittedBlur = wrapper.emitted().blur; - expect(emittedBlur).toBeTruthy(); - resolve(); - }); - })); - - it('Should emit a focus event when its focused', () => new Promise((resolve) => { + await phoneInput.trigger('blur'); + const emittedBlur = wrapper.emitted().blur; + expect(emittedBlur).toBeTruthy(); + }); + + it('Should emit a focus event when its focused', async () => { const wrapper = mount(PhoneNumberField, { props: { ...defaultProps, modelValue: '55 1234 1234' } }); - wrapper.vm.$nextTick(); + await wrapper.vm.$nextTick(); const phoneInput = wrapper.find('input[data-testid="phone-field-input"]'); - phoneInput.trigger('focus'); - wrapper.vm.$nextTick(); - setTimeout(() => { - const emittedFocus = wrapper.emitted().focus; - expect(emittedFocus).toBeTruthy(); - resolve(); - }); - })); + await phoneInput.trigger('focus'); + const emittedFocus = wrapper.emitted().focus; + expect(emittedFocus).toBeTruthy(); + }); }); diff --git a/src/components/PhoneNumberField/PhoneNumberField.ts b/src/components/PhoneNumberField/PhoneNumberField.ts new file mode 100644 index 0000000..14aa1c9 --- /dev/null +++ b/src/components/PhoneNumberField/PhoneNumberField.ts @@ -0,0 +1,158 @@ +import type { PropType } from 'vue'; +import { defineComponent, ref } from 'vue'; +import type { CountryCode, MetadataJson } from 'libphonenumber-js/custom'; +import { AsYouType, getCountryCallingCode, getCountries } from 'libphonenumber-js/custom'; + +import FormField from '../FormField/FormField.vue'; +import TextParagraph from '../TextParagraph/TextParagraph.vue'; +import { onlyDigitsRegexp, nonDigitRegexp } from '../../lib/regexHelper'; +import libphoneNumberMetadata from '../../../data/libphonenumber-metadata.min.json'; + +const phoneNumberMetadata = JSON.parse(JSON.stringify(libphoneNumberMetadata)) as MetadataJson; +const getAvailableCountryCodes = () => getCountries(phoneNumberMetadata); + +const PhoneNumberField = defineComponent({ + name: 'PhoneNumberField', + components: { + FormField, + TextParagraph, + }, + props: { + dataTestId: { + type: String, + default: 'phone-field', + }, + modelValue: { + type: String, + default: '', + }, + countryCode: { + type: String as PropType, + required: true, + validator: (value: CountryCode) => getAvailableCountryCodes().includes(value), + }, + hideCountryCodeUntilFocus: Boolean, + startFocused: Boolean, + label: { + type: String, + default: '', + }, + errorLabel: { + type: String, + default: '', + }, + id: { + type: String, + default: '', + }, + name: { + type: String, + default: '', + }, + disabled: Boolean, + readonly: Boolean, + maxLength: { + type: Number, + default: null, + }, + lengthHint: { + type: Number, + default: null, + }, + lengthHintLabel: { + type: String, + default: '', + }, + helpText: { + type: String, + default: '', + }, + hideClearButton: Boolean, + maxPhoneNumberLength: { + type: Number, + default: 10, + }, + }, + emits: ['update:modelValue', 'update:formattedValue', 'update:isValid', 'focus', 'blur'], + computed: { + cleanedInputValue() { + if (!this.inputValue) { return ''; } + const result = this.inputValue.replace(nonDigitRegexp, ''); + return result; + }, + formattedPhoneNumber() { + if (!(this.inputValue && this.countryCode)) { return ''; } + const asYouType = new AsYouType(this.countryCode, phoneNumberMetadata); + const result = asYouType.input(this.cleanedInputValue); + return result; + }, + prefix() { + if (!this.countryCode) { return; } + const result = `+${getCountryCallingCode(this.countryCode, phoneNumberMetadata)}`; + return result; + }, + hideCountryCode() { + if (!this.hideCountryCodeUntilFocus) { return; } + const result = (!this.isFocused && !this.inputValue); + return result; + }, + }, + methods: { + emitUpdateModelValueEvent() { + this.$emit('update:modelValue', this.cleanedInputValue); + }, + onFocus(event: Event) { + this.isFocused = true; + this.$emit('focus', event); + }, + onBlur(event: Event) { + this.isFocused = false; + this.$emit('blur', event); + }, + supressNonDigitCharacterEntry(event: KeyboardEvent) { + if (onlyDigitsRegexp.test(event.key)) { return; } + event.preventDefault(); + }, + focus() { + this.field.focus(); + }, + isPhoneNumberValid() { + if (this.maxPhoneNumberLength && (this.cleanedInputValue.length > this.maxPhoneNumberLength)) { return false; } + const asYouType = new AsYouType(this.countryCode, phoneNumberMetadata); + asYouType.input((this.inputValue || '')); + const result = asYouType.isPossible(); + return result; + }, + }, + watch: { + inputValue() { + if (this.inputValue) { this.inputValue = this.formattedPhoneNumber; } + this.emitUpdateModelValueEvent(); + this.$emit('update:isValid', this.isPhoneNumberValid()); + }, + formattedPhoneNumber() { + this.$emit('update:formattedValue', this.formattedPhoneNumber); + }, + modelValue() { + this.inputValue = this.modelValue; + }, + }, + setup(props) { + const field = ref(); + const isFocused = ref(false); + const inputValue = ref(props.modelValue); + const isValid = ref(false); + return { + field, + isFocused, + inputValue, + isValid, + }; + }, +}); + +export { + getAvailableCountryCodes, +}; + +export default PhoneNumberField; diff --git a/src/components/PhoneNumberField/PhoneNumberField.vue b/src/components/PhoneNumberField/PhoneNumberField.vue index 2fcffc4..0957c07 100644 --- a/src/components/PhoneNumberField/PhoneNumberField.vue +++ b/src/components/PhoneNumberField/PhoneNumberField.vue @@ -30,5 +30,5 @@ - + diff --git a/src/components/PillProgress/PillProgress.js b/src/components/PillProgress/PillProgress.js deleted file mode 100644 index dca7e87..0000000 --- a/src/components/PillProgress/PillProgress.js +++ /dev/null @@ -1,92 +0,0 @@ -const availableColors = ['blue', 'brown', 'green', 'orange', 'pink', 'purple', 'red', 'yellow']; - -const props = { - dataTestId: { - type: String, - default: 'pill-progress', - }, - progress: Number, - total: Number, - percentage: Number, - color: { - type: String, - default: 'blue', - validator(value) { return (!value || availableColors.includes(value)); }, - }, - animate: Boolean, - animationDuration: { - type: Number, - default: 1000, - }, - circularAnimation: Boolean, -}; - -const emits = ['animationend', 'error']; - -const computed = { - progressPercentage() { - const result = (this.percentage || ((this.progress / this.total) * 100) || 0); - switch (true) { - case result < 0: - return 0; - case result > 100: - return 100; - default: - return result; - } - }, - progressStyle() { - const result = { - width: `${this.progressPercentage}%`, - }; - return result; - }, - circleStyle() { - const result = { - left: `${this.progressPercentage}%`, - }; - return result; - }, -}; - -const methods = { - startAnimation() { - try { - const { bar, circle } = this.$refs; - const barAnimation = [ - { width: 0 }, - { width: `${this.progressPercentage}%` }, - ]; - const circleAnimation = [ - { left: 0 }, - { left: `${this.progressPercentage}%` }, - ]; - const animation = bar.animate(barAnimation, { duration: this.animationDuration, iterations: (this.circularAnimation) ? Infinity : 1 }); - circle.animate(circleAnimation, { duration: this.animationDuration, iterations: (this.circularAnimation) ? Infinity : 1 }); - animation.onfinish = this.emitEventFinish; - } catch (error) { - this.$emit('error', error); - } - }, - emitEventFinish(event) { - this.$emit('animationend', event); - }, -}; - -const mounted = function () { - if (!this.animate) { return; } - this.startAnimation(); -}; - -const name = 'LinearProgress'; - -const LinearProgress = { - name, - props, - emits, - computed, - methods, - mounted, -}; - -export default LinearProgress; diff --git a/src/components/PillProgress/PillProgress.stories.js b/src/components/PillProgress/PillProgress.stories.ts similarity index 96% rename from src/components/PillProgress/PillProgress.stories.js rename to src/components/PillProgress/PillProgress.stories.ts index f2d6565..35c06be 100644 --- a/src/components/PillProgress/PillProgress.stories.js +++ b/src/components/PillProgress/PillProgress.stories.ts @@ -1,5 +1,6 @@ import { action } from '@storybook/addon-actions'; import { MedalLevel5Icon, SheepRunningIcon } from '@lana/b2c-mapp-ui-assets'; +import type { Meta, StoryFn } from '@storybook/vue3'; import PillProgress from './PillProgress.vue'; import TextParagraph from '../TextParagraph/TextParagraph.vue'; @@ -35,9 +36,9 @@ const PillProgressStories = { animationDuration: { name: 'Animate duration', control: { type: 'number' } }, circularAnimation: { name: 'Infinite animation?', control: { type: 'boolean' } }, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { @@ -86,7 +87,7 @@ defaultExample.parameters = { }, }; -const withFlagsExample = (args, { argTypes }) => ({ +const withFlagsExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/PillProgress/PillProgress.test.js b/src/components/PillProgress/PillProgress.test.ts similarity index 83% rename from src/components/PillProgress/PillProgress.test.js rename to src/components/PillProgress/PillProgress.test.ts index 87a3c51..794f3d7 100644 --- a/src/components/PillProgress/PillProgress.test.js +++ b/src/components/PillProgress/PillProgress.test.ts @@ -12,19 +12,19 @@ describe('PillProgress unit test', () => { const wrapper = mount(PillProgress, { props: { ...defaultProps } }); await wrapper.vm.$nextTick(); const description = wrapper.find('div[data-testid="pill-progress-progress-bar"]'); - expect(description.element.style.width).toContain('50%'); + expect(description.attributes('style')).toContain('width: 50%'); }); it('Percentage should override given progress/total percentage', async () => { const wrapper = mount(PillProgress, { props: { ...defaultProps, percentage: 25 } }); await wrapper.vm.$nextTick(); const description = wrapper.find('div[data-testid="pill-progress-progress-bar"]'); - expect(description.element.style.width).toContain('25%'); + expect(description.attributes('style')).toContain('width: 25%'); }); it('Should send error for test animation', async () => { const wrapper = mount(PillProgress, { props: { ...defaultProps, progress: 100, animate: true, animationDuration: 1 } }); await wrapper.vm.$nextTick(); - expect(wrapper.emitted().error).toBeTruthy(); + expect(wrapper.emitted('error')).toBeTruthy(); }); }); diff --git a/src/components/PillProgress/PillProgress.ts b/src/components/PillProgress/PillProgress.ts new file mode 100644 index 0000000..02489a1 --- /dev/null +++ b/src/components/PillProgress/PillProgress.ts @@ -0,0 +1,95 @@ +import { defineComponent, ref } from 'vue'; + +const availableColors = ['blue', 'brown', 'green', 'orange', 'pink', 'purple', 'red', 'yellow']; + +const LinearProgress = defineComponent({ + name: 'LinearProgress', + props: { + dataTestId: { + type: String, + default: 'pill-progress', + }, + progress: { + type: Number, + default: 0, + }, + total: { + type: Number, + default: 0, + }, + percentage: { + type: Number, + default: 0, + }, + color: { + type: String, + default: 'blue', + validator(value: string) { return (!value || availableColors.includes(value)); }, + }, + animate: Boolean, + animationDuration: { + type: Number, + default: 1000, + }, + circularAnimation: Boolean, + }, + emits: ['animationend', 'error'], + computed: { + progressPercentage() { + const result = (this.percentage || ((this.progress / this.total) * 100) || 0); + switch (true) { + case result < 0: + return 0; + case result > 100: + return 100; + default: + return result; + } + }, + progressStyle() { + const result = { + width: `${this.progressPercentage}%`, + }; + return result; + }, + circleStyle() { + const result = { + left: `${this.progressPercentage}%`, + }; + return result; + }, + }, + methods: { + startAnimation() { + try { + const barAnimation = [ + { width: 0 }, + { width: `${this.progressPercentage}%` }, + ]; + const circleAnimation = [ + { left: 0 }, + { left: `${this.progressPercentage}%` }, + ]; + const animation = this.bar.animate(barAnimation, { duration: this.animationDuration, iterations: (this.circularAnimation) ? Infinity : 1 }); + this.circle.animate(circleAnimation, { duration: this.animationDuration, iterations: (this.circularAnimation) ? Infinity : 1 }); + animation.onfinish = this.emitEventFinish; + } catch (error) { + this.$emit('error', error); + } + }, + emitEventFinish(event: Event) { + this.$emit('animationend', event); + }, + }, + setup() { + const bar = ref(); + const circle = ref(); + return { bar, circle }; + }, + mounted() { + if (!this.animate) { return; } + this.startAnimation(); + }, +}); + +export default LinearProgress; diff --git a/src/components/PillProgress/PillProgress.vue b/src/components/PillProgress/PillProgress.vue index 346c6ad..e176cc1 100644 --- a/src/components/PillProgress/PillProgress.vue +++ b/src/components/PillProgress/PillProgress.vue @@ -22,5 +22,5 @@ - + diff --git a/src/components/RadioList/RadioList.js b/src/components/RadioList/RadioList.js deleted file mode 100644 index 3195da1..0000000 --- a/src/components/RadioList/RadioList.js +++ /dev/null @@ -1,64 +0,0 @@ -import Heading from '../Heading/Heading.vue'; - -const components = { - Heading, -}; - -const props = { - dataTestId: { - type: String, - default: 'selection-list', - }, - options: { - type: Array, - default: () => [], - }, - id: { - type: String, - required: true, - }, - modelValue: [String, Number], - title: String, - disabled: Boolean, - buttonMode: { - type: Boolean, - default: false, - }, -}; - -const emits = ['update:modelValue']; - -const data = function () { - return { - selectedValue: this.modelValue, - }; -}; - -const methods = { - emitUpdateModelValueEvent() { - this.$emit('update:modelValue', this.selectedValue); - }, -}; - -const watch = { - selectedValue() { - this.emitUpdateModelValueEvent(); - }, - modelValue() { - this.selectedValue = this.modelValue; - }, -}; - -const name = 'RadioList'; - -const RadioList = { - name, - components, - props, - emits, - data, - methods, - watch, -}; - -export default RadioList; diff --git a/src/components/RadioList/RadioList.stories.js b/src/components/RadioList/RadioList.stories.ts similarity index 93% rename from src/components/RadioList/RadioList.stories.js rename to src/components/RadioList/RadioList.stories.ts index 1965903..4507a32 100644 --- a/src/components/RadioList/RadioList.stories.js +++ b/src/components/RadioList/RadioList.stories.ts @@ -1,4 +1,5 @@ import { action } from '@storybook/addon-actions'; +import type { Meta, StoryFn } from '@storybook/vue3'; import RadioList from './RadioList.vue'; import { createDeviceDecorator } from '../../lib/storybookHelpers'; @@ -36,9 +37,9 @@ const RadioListStories = { buttonMode: { control: 'boolean', name: 'Button Mode?' }, options: { control: 'object', name: 'Options' }, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { @@ -85,7 +86,7 @@ defaultExample.parameters = { }, }; -const withHtmlLabel = (args, { argTypes }) => ({ +const withHtmlLabel: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/RadioList/RadioList.test.js b/src/components/RadioList/RadioList.test.ts similarity index 84% rename from src/components/RadioList/RadioList.test.js rename to src/components/RadioList/RadioList.test.ts index ad662e4..fa61225 100644 --- a/src/components/RadioList/RadioList.test.js +++ b/src/components/RadioList/RadioList.test.ts @@ -29,7 +29,7 @@ describe('UI/forms/RadioList', () => { it('Should NOT apply selected given option initially as selected if theres a given value', () => { const { queryAllByTestId } = render(RadioList, { props: { ...defaultProps, modelValue: 'option_2' } }); const selectedOptions = queryAllByTestId('selection-list-option'); - const firstOptionIsNotSelectedByDefault = !selectedOptions[0].getAttribute('data-checked'); + const firstOptionIsNotSelectedByDefault = !selectedOptions[0]?.getAttribute('data-checked'); expect(firstOptionIsNotSelectedByDefault).toBeTruthy(); }); @@ -39,22 +39,22 @@ describe('UI/forms/RadioList', () => { await wrapper.vm.$nextTick(); wrapper.vm.$options.watch.modelValue.call(wrapper.vm, givenValue); await wrapper.vm.$nextTick(); - const appliedGivenValue = wrapper.vm.$data.selectedValue === givenValue; + const appliedGivenValue = wrapper.vm.selectedValue === givenValue; expect(appliedGivenValue).toBeTruthy(); }); it('Should apply selected option based on given value', () => { const { queryAllByTestId } = render(RadioList, { props: { ...defaultProps, modelValue: 'option_2' } }); const selectedOptions = queryAllByTestId('selection-list-option'); - const secondOptionIsSelected = selectedOptions[1].className.includes('checked'); + const secondOptionIsSelected = selectedOptions[1]?.className.includes('checked'); expect(secondOptionIsSelected).toBeTruthy(); }); it('Should NOT apply selected option based on wrong given value', () => { const { queryAllByTestId } = render(RadioList, { props: { ...defaultProps, modelValue: 'option_x' } }); const selectedOptions = queryAllByTestId('selection-list-option'); - const firstOptionIsNotSelected = !selectedOptions[0].getAttribute('data-checked'); - const secondOptionIsNotSelected = !selectedOptions[1].getAttribute('data-checked'); + const firstOptionIsNotSelected = !selectedOptions[0]?.getAttribute('data-checked'); + const secondOptionIsNotSelected = !selectedOptions[1]?.getAttribute('data-checked'); const optionsAreNotSelected = firstOptionIsNotSelected && secondOptionIsNotSelected; expect(optionsAreNotSelected).toBeTruthy(); }); @@ -67,10 +67,10 @@ describe('UI/forms/RadioList', () => { const options = wrapper.findAll('li'); const firstOption = options[0]; const secondOption = options[1]; - firstOptionInput.setChecked(); + await firstOptionInput?.setValue(true); await wrapper.vm.$nextTick(); - const firstOptionSelected = firstOption.element.className.includes('checked'); - const secondOptionNotSelected = !secondOption.element.className.includes('checked'); + const firstOptionSelected = firstOption?.classes().includes('checked'); + const secondOptionNotSelected = !secondOption?.classes().includes('checked'); const clickedOptionIsSelected = firstOptionSelected && secondOptionNotSelected; expect(clickedOptionIsSelected).toBeTruthy(); }); @@ -79,7 +79,7 @@ describe('UI/forms/RadioList', () => { const wrapper = mount(RadioList, { props: { ...defaultProps, modelValue: 'option_2' } }); await wrapper.vm.$nextTick(); const firstOptionInput = wrapper.findAll('input')[0]; - firstOptionInput.setChecked(); + await firstOptionInput?.setValue(true); await wrapper.vm.$nextTick(); const clickEmitted = wrapper.emitted('update:modelValue'); expect(clickEmitted).toBeTruthy(); @@ -89,10 +89,10 @@ describe('UI/forms/RadioList', () => { const wrapper = mount(RadioList, { props: { ...defaultProps, modelValue: 'option_2' } }); await wrapper.vm.$nextTick(); const firstOptionInput = wrapper.findAll('input')[0]; - firstOptionInput.setChecked(); + await firstOptionInput?.setValue(true); await wrapper.vm.$nextTick(); - const emittedValue = wrapper.emitted('update:modelValue')[0][0] === 'option_1'; - expect(emittedValue).toBeTruthy(); + const [emittedValue] = wrapper.emitted('update:modelValue')?.[0] as string[]; + expect(emittedValue).toBe('option_1'); }); it('Should show label if is given', () => { diff --git a/src/components/RadioList/RadioList.ts b/src/components/RadioList/RadioList.ts new file mode 100644 index 0000000..a51cfa5 --- /dev/null +++ b/src/components/RadioList/RadioList.ts @@ -0,0 +1,65 @@ +import type { PropType } from 'vue'; +import { defineComponent } from 'vue'; + +import Heading from '../Heading/Heading.vue'; + +interface RadioOption { + label?: string, + htmlLabel?: string, + value: string | number, +} + +const RadioList = defineComponent({ + name: 'RadioList', + components: { + Heading, + }, + props: { + dataTestId: { + type: String, + default: 'selection-list', + }, + options: { + type: Array as PropType, + default: () => [] as RadioOption[], + }, + id: { + type: String, + required: true, + }, + modelValue: { + type: [String, Number], + default: null, + }, + title: { + type: String, + default: '', + }, + disabled: Boolean, + buttonMode: { + type: Boolean, + default: false, + }, + }, + emits: ['update:modelValue'], + data() { + return { + selectedValue: this.modelValue, + }; + }, + methods: { + emitUpdateModelValueEvent() { + this.$emit('update:modelValue', this.selectedValue); + }, + }, + watch: { + selectedValue() { + this.emitUpdateModelValueEvent(); + }, + modelValue() { + this.selectedValue = this.modelValue; + }, + }, +}); + +export default RadioList; diff --git a/src/components/RadioList/RadioList.vue b/src/components/RadioList/RadioList.vue index ac89574..19223c3 100644 --- a/src/components/RadioList/RadioList.vue +++ b/src/components/RadioList/RadioList.vue @@ -39,5 +39,5 @@ - + diff --git a/src/components/Screen/Screen.js b/src/components/Screen/Screen.js deleted file mode 100644 index 6b079a5..0000000 --- a/src/components/Screen/Screen.js +++ /dev/null @@ -1,54 +0,0 @@ -const emits = ['keyboardFocus', 'keyboardBlur']; - -const data = function () { - return { - lastClick: { - x: 0, - y: 0, - }, - viewport: { - width: window.innerWidth, - height: window.innerHeight, - }, - }; -}; - -const methods = { - recordClick({ clientX, clientY }) { - this.lastClick.x = clientX; - this.lastClick.y = clientY; - }, - onWindowResize({ target: { innerHeight: screenHeight } }) { - const payload = { - screenHeight, - viewport: this.viewport, - lastClick: this.lastClick, - }; - if (this.viewport.height > screenHeight) { - this.$emit('keyboardFocus', payload); - return; - } - this.$emit('keyboardBlur', payload); - }, -}; - -const mounted = function () { - window.addEventListener('resize', this.onWindowResize); -}; - -const beforeUnmount = function () { - window.removeEventListener('resize', this.onWindowResize); -}; - -const name = 'Screen'; - -const Screen = { - name, - emits, - data, - methods, - mounted, - beforeUnmount, -}; - -export default Screen; diff --git a/src/components/Screen/Screen.stories.js b/src/components/Screen/Screen.stories.ts similarity index 90% rename from src/components/Screen/Screen.stories.js rename to src/components/Screen/Screen.stories.ts index 932b11d..c75bacb 100644 --- a/src/components/Screen/Screen.stories.js +++ b/src/components/Screen/Screen.stories.ts @@ -1,4 +1,5 @@ import { action } from '@storybook/addon-actions'; +import type { Meta, StoryFn } from '@storybook/vue3'; import Screen from './Screen.vue'; import { createDeviceDecorator } from '../../lib/storybookHelpers'; @@ -18,9 +19,9 @@ const ScreenStories = { ...deviceDecorator.argTypes, default: { control: { type: 'text' }, table: { type: { summary: null } } }, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/Screen/Screen.test.js b/src/components/Screen/Screen.test.ts similarity index 73% rename from src/components/Screen/Screen.test.js rename to src/components/Screen/Screen.test.ts index db5cb81..318d320 100644 --- a/src/components/Screen/Screen.test.js +++ b/src/components/Screen/Screen.test.ts @@ -3,6 +3,7 @@ import { mount } from '@vue/test-utils'; import Screen from './Screen.vue'; import { silenceDeprecationErrorsAndInnerComponentWarnings } from '../../lib/testUtils'; +import type { ScreenKeyboardEvent } from './Screen'; describe('Screen unit test', () => { beforeAll(() => { @@ -23,34 +24,35 @@ describe('Screen unit test', () => { }); it('Should emit current screenHeight & lastClick values as payload for keyboardBlur event when window is resized', () => { - const { emitted } = render(Screen, { slots: { default: '

    Content

    ' } }); + const wrapper = mount(Screen, { slots: { default: '

    Content

    ' } }); window.dispatchEvent(new Event('resize')); - const keyboardBlurEvent = emitted().keyboardBlur[0][0]; - const hasScreenHeight = keyboardBlurEvent.screenHeight; - const hasLastClick = keyboardBlurEvent.lastClick; - const hasViewport = keyboardBlurEvent.viewport; + const [keyboardBlurEvent] = wrapper.emitted('keyboardBlur')?.[0] as ScreenKeyboardEvent[]; + const hasScreenHeight = keyboardBlurEvent?.screenHeight; + const hasLastClick = keyboardBlurEvent?.lastClick; + const hasViewport = keyboardBlurEvent?.viewport; const rightEventPayload = hasScreenHeight && hasLastClick && hasViewport; expect(rightEventPayload).toBeTruthy(); }); - it('Should record last click when its clicked', () => { + it('Should record last click when its clicked', async () => { const wrapper = mount(Screen, { slots: { default: '

    Content

    ' } }); const section = wrapper.find('section'); - section.trigger('click', { clientX: 200, clientY: 200 }); - const dataFromLastClick = wrapper.vm.$data.lastClick; + await section.trigger('click', { clientX: 200, clientY: 200 }); + const dataFromLastClick = wrapper.vm.lastClick; const savedLastClickX = dataFromLastClick.x === 200; const savedLastClickY = dataFromLastClick.y === 200; const savedLastClick = savedLastClickX && savedLastClickY; expect(savedLastClick).toBeTruthy(); }); - it('Should send recorded last clicks as payload for emitted event when window is resized', () => { + it('Should send recorded last clicks as payload for emitted event when window is resized', async () => { const wrapper = mount(Screen, { slots: { default: '

    Content

    ' } }); const section = wrapper.find('section'); - section.trigger('click', { clientX: 200, clientY: 200 }); + await section.trigger('click', { clientX: 200, clientY: 200 }); window.dispatchEvent(new Event('resize')); - const emittedLastClickX = wrapper.emitted().keyboardBlur[0][0].lastClick.x; - const emittedLastClickY = wrapper.emitted().keyboardBlur[0][0].lastClick.y; + const [keyboardBlurEvent] = wrapper.emitted('keyboardBlur')?.[0] as ScreenKeyboardEvent[]; + const emittedLastClickX = keyboardBlurEvent?.lastClick.x; + const emittedLastClickY = keyboardBlurEvent?.lastClick.y; const emittedRecordedValues = (emittedLastClickX === 200) && (emittedLastClickY === 200); expect(emittedRecordedValues).toBeTruthy(); }); diff --git a/src/components/Screen/Screen.ts b/src/components/Screen/Screen.ts new file mode 100644 index 0000000..6c8e553 --- /dev/null +++ b/src/components/Screen/Screen.ts @@ -0,0 +1,59 @@ +import { defineComponent } from 'vue'; + +interface ScreenKeyboardEvent { + screenHeight: number, + viewport: { + width: number, + height: number, + }, + lastClick: { + x: number, + y: number, + }, +} + +const Screen = defineComponent({ + name: 'Screen', + emits: ['keyboardFocus', 'keyboardBlur'], + data() { + return { + lastClick: { + x: 0, + y: 0, + }, + viewport: { + width: window.innerWidth, + height: window.innerHeight, + }, + }; + }, + methods: { + recordClick({ clientX, clientY }: MouseEvent) { + this.lastClick.x = clientX; + this.lastClick.y = clientY; + }, + onWindowResize({ target }: UIEvent) { + const { innerHeight: screenHeight } = target as Window; + const payload: ScreenKeyboardEvent = { + screenHeight, + viewport: this.viewport, + lastClick: this.lastClick, + }; + if (this.viewport.height > screenHeight) { + this.$emit('keyboardFocus', payload); + return; + } + this.$emit('keyboardBlur', payload); + }, + }, + mounted() { + window.addEventListener('resize', this.onWindowResize); + }, + beforeUnmount() { + window.removeEventListener('resize', this.onWindowResize); + }, +}); + +export type { ScreenKeyboardEvent }; + +export default Screen; diff --git a/src/components/Screen/Screen.vue b/src/components/Screen/Screen.vue index 6cb094a..3452dd2 100644 --- a/src/components/Screen/Screen.vue +++ b/src/components/Screen/Screen.vue @@ -4,5 +4,5 @@ - + diff --git a/src/components/ScrollWrapper/ScrollWrapper.js b/src/components/ScrollWrapper/ScrollWrapper.js deleted file mode 100644 index 7231f5d..0000000 --- a/src/components/ScrollWrapper/ScrollWrapper.js +++ /dev/null @@ -1,45 +0,0 @@ -const props = { - dataTestId: { - type: String, - default: '', - }, - position: Number, -}; - -const data = function () { - return { - lastClick: { - x: 0, - y: 0, - }, - viewport: { - width: window.innerWidth, - height: window.innerHeight, - }, - }; -}; - -const methods = { - scrollToPosition() { - if ((typeof this.position !== 'number') || !this.$refs.wrapper) { return; } - this.$refs.wrapper.scrollTo({ - top: this.position, - }); - }, -}; - -const mounted = function () { - this.scrollToPosition(); -}; - -const name = 'ScrollWrapper'; - -const ScrollWrapper = { - name, - props, - data, - methods, - mounted, -}; - -export default ScrollWrapper; diff --git a/src/components/ScrollWrapper/ScrollWrapper.stories.js b/src/components/ScrollWrapper/ScrollWrapper.stories.ts similarity index 90% rename from src/components/ScrollWrapper/ScrollWrapper.stories.js rename to src/components/ScrollWrapper/ScrollWrapper.stories.ts index bcff5c9..39c1d5b 100644 --- a/src/components/ScrollWrapper/ScrollWrapper.stories.js +++ b/src/components/ScrollWrapper/ScrollWrapper.stories.ts @@ -1,3 +1,5 @@ +import type { Meta, StoryFn } from '@storybook/vue3'; + import ScrollWrapper from './ScrollWrapper.vue'; import Screen from '../Screen/Screen.vue'; import { createDeviceDecorator } from '../../lib/storybookHelpers'; @@ -21,9 +23,9 @@ const ScrollWrapperStories = { position: { control: 'number' }, default: { control: { type: 'text' }, table: { type: { summary: null } } }, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/ScrollWrapper/ScrollWrapper.ts b/src/components/ScrollWrapper/ScrollWrapper.ts new file mode 100644 index 0000000..d7c0f45 --- /dev/null +++ b/src/components/ScrollWrapper/ScrollWrapper.ts @@ -0,0 +1,45 @@ +import { defineComponent, ref } from 'vue'; + +const ScrollWrapper = defineComponent({ + name: 'ScrollWrapper', + props: { + dataTestId: { + type: String, + default: '', + }, + position: { + type: Number, + default: null, + }, + }, + methods: { + scrollToPosition() { + if ((typeof this.position !== 'number') || !this.wrapper) { return; } + this.wrapper.scrollTo({ + top: this.position, + }); + }, + }, + setup() { + const wrapper = ref(); + const lastClick = ref({ + x: 0, + y: 0, + }); + const viewport = ref({ + width: window.innerWidth, + height: window.innerHeight, + }); + + return { + wrapper, + lastClick, + viewport, + }; + }, + mounted() { + this.scrollToPosition(); + }, +}); + +export default ScrollWrapper; diff --git a/src/components/ScrollWrapper/ScrollWrapper.vue b/src/components/ScrollWrapper/ScrollWrapper.vue index 97342a2..82e9696 100644 --- a/src/components/ScrollWrapper/ScrollWrapper.vue +++ b/src/components/ScrollWrapper/ScrollWrapper.vue @@ -9,5 +9,5 @@ - + diff --git a/src/components/SelectBox/SelectBox.js b/src/components/SelectBox/SelectBox.js deleted file mode 100644 index d8a2bd3..0000000 --- a/src/components/SelectBox/SelectBox.js +++ /dev/null @@ -1,108 +0,0 @@ -import { ExpandSmallIcon, WarningBoldIcon } from '@lana/b2c-mapp-ui-assets'; - -import TextParagraph from '../TextParagraph/TextParagraph.vue'; - -const components = { - TextParagraph, - ExpandSmallIcon, - WarningBoldIcon, -}; - -const props = { - dataTestId: { - type: String, - default: 'selector', - }, - options: { - type: Array, - default: () => [], - }, - modelValue: String, - label: String, - id: String, - name: String, - disabled: Boolean, - readonly: Boolean, - errorLabel: String, - helpText: String, -}; - -const emits = ['update:modelValue', 'focus', 'blur', 'keypress', 'keyup', 'paste']; - -const data = function () { - return { - selectedValue: this.modelValue, - isFocused: false, - }; -}; - -const computed = { - errorLabelOrHelpText() { - const result = (this.errorLabel || this.helpText); - return result; - }, - hasEmptyOption() { - if (!(this.options && this.options.length)) { return; } - const hasEmptyValue = ({ value }) => !value; - const result = this.options.some(hasEmptyValue); - return result; - }, -}; - -const methods = { - emitUpdateModelValueEvent() { - this.$emit('update:modelValue', this.selectedValue); - }, - toggleFocus() { - this.isFocused = !this.isFocused; - }, - onFocus(event) { - this.toggleFocus(); - this.$emit('focus', event); - }, - onBlur(event) { - this.toggleFocus(); - this.$emit('blur', event); - }, - onKeypress(event) { - this.$emit('keypress', event); - }, - onKeyup(event) { - this.$emit('keyup', event); - }, - onPaste(event) { - this.$emit('paste', event); - }, - focus() { - if (!this.$refs.input) { return; } - this.$refs.input.focus(); - }, - blur() { - if (!this.$refs.input) { return; } - this.$refs.input.blur(); - }, -}; - -const watch = { - selectedValue() { - this.emitUpdateModelValueEvent(); - }, - modelValue() { - this.selectedValue = this.modelValue; - }, -}; - -const name = 'SelectBox'; - -const SelectBox = { - name, - components, - props, - emits, - computed, - data, - methods, - watch, -}; - -export default SelectBox; diff --git a/src/components/SelectBox/SelectBox.stories.js b/src/components/SelectBox/SelectBox.stories.ts similarity index 94% rename from src/components/SelectBox/SelectBox.stories.js rename to src/components/SelectBox/SelectBox.stories.ts index 4f9334c..e82b182 100644 --- a/src/components/SelectBox/SelectBox.stories.js +++ b/src/components/SelectBox/SelectBox.stories.ts @@ -1,4 +1,5 @@ import { action } from '@storybook/addon-actions'; +import type { Meta, StoryFn } from '@storybook/vue3'; import SelectBox from './SelectBox.vue'; import { createDeviceDecorator } from '../../lib/storybookHelpers'; @@ -27,9 +28,9 @@ const SelectBoxStories = { helpText: { control: 'text', name: 'Help Text' }, options: { control: 'object', name: 'Options' }, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { @@ -112,7 +113,7 @@ defaultExample.parameters = { }, }; -const withPreselectedValue = (args, { argTypes }) => ({ +const withPreselectedValue: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { @@ -195,7 +196,7 @@ withPreselectedValue.parameters = { }, }; -const withNoValueInitiallySelected = (args, { argTypes }) => ({ +const withNoValueInitiallySelected: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/SelectBox/SelectBox.test.js b/src/components/SelectBox/SelectBox.test.ts similarity index 80% rename from src/components/SelectBox/SelectBox.test.js rename to src/components/SelectBox/SelectBox.test.ts index 75704cb..040a8ee 100644 --- a/src/components/SelectBox/SelectBox.test.js +++ b/src/components/SelectBox/SelectBox.test.ts @@ -2,11 +2,11 @@ import { render, fireEvent } from '@testing-library/vue'; import { mount } from '@vue/test-utils'; import SelectBox from './SelectBox.vue'; -import { silenceDeprecationErrorsAndInnerComponentWarnings } from '../../lib/testUtils'; +import { silenceInnerComponentWarnings } from '../../lib/testUtils'; describe('SelectBox unit test', () => { beforeAll(() => { - silenceDeprecationErrorsAndInnerComponentWarnings(jest); + silenceInnerComponentWarnings(jest); }); const defaultProps = { @@ -31,8 +31,7 @@ describe('SelectBox unit test', () => { it('Should apply focus class if is focused', async () => { const wrapper = mount(SelectBox, { props: { ...defaultProps, isFocused: true } }); - wrapper.find('select').trigger('focus'); - await wrapper.vm.$nextTick(); + await wrapper.find('select').trigger('focus'); const focusClass = wrapper.find('div.select-box-container').classes().includes('focus'); expect(focusClass).toBeTruthy(); }); @@ -49,8 +48,8 @@ describe('SelectBox unit test', () => { it('Should select given selected option in options if theres no given value', () => { const { queryAllByTestId } = render(SelectBox, { props: { ...defaultProps } }); const options = queryAllByTestId('selector-option'); - const firstOptionIsSelected = options[0].getAttribute('data-selected') === 'true'; - const secondOptionIsNotSelected = options[1].getAttribute('data-selected') === 'false'; + const firstOptionIsSelected = options[0]?.getAttribute('data-selected') === 'true' || false; + const secondOptionIsNotSelected = options[1]?.getAttribute('data-selected') === 'false' || false; const rightOptionSelected = firstOptionIsSelected && secondOptionIsNotSelected; expect(rightOptionSelected).toBeTruthy(); }); @@ -60,8 +59,8 @@ describe('SelectBox unit test', () => { const options = queryAllByTestId('selector-option'); const selector = getByTestId('selector-select'); fireEvent.focus(selector); - const firstOptionIsNotSelected = options[0].getAttribute('data-selected') === 'false'; - const secondOptionIsSelected = options[1].getAttribute('data-selected') === 'true'; + const firstOptionIsNotSelected = options[0]?.getAttribute('data-selected') === 'false' || false; + const secondOptionIsSelected = options[1]?.getAttribute('data-selected') === 'true' || false; const rightOptionSelected = firstOptionIsNotSelected && secondOptionIsSelected; expect(rightOptionSelected).toBeTruthy(); }); @@ -71,8 +70,8 @@ describe('SelectBox unit test', () => { const options = queryAllByTestId('selector-option'); const selector = getByTestId('selector-select'); fireEvent.focus(selector); - const firstOptionIsNotSelected = options[0].getAttribute('data-selected') === 'false'; - const secondOptionIsNotSelected = options[1].getAttribute('data-selected') === 'false'; + const firstOptionIsNotSelected = options[0]?.getAttribute('data-selected') === 'false' || false; + const secondOptionIsNotSelected = options[1]?.getAttribute('data-selected') === 'false' || false; const rightOptionSelected = firstOptionIsNotSelected && secondOptionIsNotSelected; expect(rightOptionSelected).toBeTruthy(); }); @@ -88,11 +87,11 @@ describe('SelectBox unit test', () => { it('Should apply given value as selectedValue', async () => { const givenValue = 'option_2'; - const wrapper = mount(SelectBox, { props: { ...defaultProps, modelValue: givenValue } }); + const wrapper = mount(SelectBox, { props: { ...defaultProps, modelValue: 'option_2' } }); await wrapper.vm.$nextTick(); wrapper.vm.$options.watch.modelValue.call(wrapper.vm, 'option_2'); await wrapper.vm.$nextTick(); - const takesGivenValue = wrapper.vm.$data.selectedValue === givenValue; + const takesGivenValue = wrapper.vm.selectedValue === givenValue; expect(takesGivenValue).toBeTruthy(); }); }); diff --git a/src/components/SelectBox/SelectBox.ts b/src/components/SelectBox/SelectBox.ts new file mode 100644 index 0000000..2146c9c --- /dev/null +++ b/src/components/SelectBox/SelectBox.ts @@ -0,0 +1,121 @@ +import { ExpandSmallIcon, WarningBoldIcon } from '@lana/b2c-mapp-ui-assets'; +import type { PropType } from 'vue'; +import { defineComponent, ref } from 'vue'; + +import TextParagraph from '../TextParagraph/TextParagraph.vue'; + +interface SelectBoxOption { + value: number | string, + label: string, + disabled: boolean, + selected: boolean, +} + +const SelectBox = defineComponent({ + name: 'SelectBox', + components: { + TextParagraph, + ExpandSmallIcon, + WarningBoldIcon, + }, + props: { + dataTestId: { + type: String, + default: 'selector', + }, + options: { + type: Array as PropType>, + default: () => [] as SelectBoxOption[], + }, + modelValue: { + type: String, + default: '', + }, + label: { + type: String, + default: '', + }, + id: { + type: String, + default: '', + }, + name: { + type: String, + default: '', + }, + disabled: Boolean, + readonly: Boolean, + errorLabel: { + type: String, + default: '', + }, + helpText: { + type: String, + default: '', + }, + }, + emits: ['update:modelValue', 'focus', 'blur', 'keypress', 'keyup', 'paste'], + computed: { + errorLabelOrHelpText() { + const result = (this.errorLabel || this.helpText); + return result; + }, + hasEmptyOption() { + if (!(this.options && this.options.length)) { return; } + const hasEmptyValue = ({ value }: { value: string | number }) => !value; + const result = this.options.some(hasEmptyValue); + return result; + }, + }, + methods: { + emitUpdateModelValueEvent() { + this.$emit('update:modelValue', this.selectedValue); + }, + toggleFocus() { + this.isFocused = !this.isFocused; + }, + onFocus(event: Event) { + this.toggleFocus(); + this.$emit('focus', event); + }, + onBlur(event: Event) { + this.toggleFocus(); + this.$emit('blur', event); + }, + onKeypress(event: Event) { + this.$emit('keypress', event); + }, + onKeyup(event: Event) { + this.$emit('keyup', event); + }, + onPaste(event: Event) { + this.$emit('paste', event); + }, + focus() { + if (!this.input) { return; } + this.input.focus(); + }, + blur() { + if (!this.input) { return; } + this.input.blur(); + }, + }, + watch: { + selectedValue() { + this.emitUpdateModelValueEvent(); + }, + modelValue() { + this.selectedValue = this.modelValue; + }, + }, + setup(props) { + const input = ref(); + const selectedValue = ref(props.modelValue); + const isFocused = ref(false); + return { input, selectedValue, isFocused }; + }, +}); + +export type { SelectBoxOption }; + +export default SelectBox; diff --git a/src/components/SelectBox/SelectBox.vue b/src/components/SelectBox/SelectBox.vue index 08b6480..7c7dc69 100644 --- a/src/components/SelectBox/SelectBox.vue +++ b/src/components/SelectBox/SelectBox.vue @@ -43,5 +43,5 @@ - + diff --git a/src/components/SelfieWebCam/SelfieWebCam.js b/src/components/SelfieWebCam/SelfieWebCam.js deleted file mode 100644 index 439ce4b..0000000 --- a/src/components/SelfieWebCam/SelfieWebCam.js +++ /dev/null @@ -1,240 +0,0 @@ -// NOTE: This is a customized fork from the `vue-web-cam` NPM package (see: https://github.com/VinceG/vue-web-cam) which was required in order to add support for selfie constraints - -const getGetUserMediaFromBrowser = () => ( - navigator.getUserMedia - || navigator.webkitGetUserMedia - || navigator.mozGetUserMedia - || navigator.msGetUserMedia - || navigator.oGetUserMedia -); - -const props = { - width: { - type: [Number, String], - default: '100%', - }, - height: { - type: [Number, String], - default: 500, - }, - autoplay: { - type: Boolean, - default: true, - }, - screenshotFormat: { - type: String, - default: 'image/jpeg', - }, - selectFirstDevice: { - type: Boolean, - default: false, - }, - deviceId: { - type: String, - default: '', - }, - playsinline: { - type: Boolean, - default: true, - }, - resolution: { - type: Object, - default: null, - validator: (value) => (value.height && value.width), - }, -}; - -const emits = ['cameras', 'notsupported', 'camera-change', 'video-live', 'started', 'stopped', 'error']; - -const data = function () { - return { - source: null, - canvas: null, - wasCamerasListEmitted: false, - cameras: [], - device: null, - }; -}; - -const computed = { - hasActiveVideo() { - const result = ((this.$refs.video !== null) && this.$refs.video.srcObject); - return result; - }, -}; - -const methods = { - legacyGetUserMediaSupport() { - const result = (constraints) => { - const getUserMedia = getGetUserMediaFromBrowser(); - if (!getUserMedia) { return Promise.reject(new Error('getUserMedia is not implemented in this browser')); } - return new Promise(((resolve, reject) => { getUserMedia.call(navigator, constraints, resolve, reject); })); - }; - return result; - }, - setupMedia() { - if (navigator.mediaDevices === undefined) { navigator.mediaDevices = {}; } - if (navigator.mediaDevices.getUserMedia === undefined) { navigator.mediaDevices.getUserMedia = this.legacyGetUserMediaSupport(); } - this.testMediaAccess(); - }, - async loadCameras() { - try { - const devices = await navigator.mediaDevices.enumerateDevices(); - const filterVideoInputDevices = ({ kind }) => (kind === 'videoinput'); - devices.filter(filterVideoInputDevices).forEach((device) => { this.cameras.push(device); }); - if (this.wasCamerasListEmitted) { return; } - if (this.selectFirstDevice && (this.cameras.length > 0)) { this.device = this.cameras[0].deviceId; } - this.$emit('cameras', this.cameras); - this.wasCamerasListEmitted = true; - } catch (error) { - this.$emit('notsupported', error); - } - }, - changeCamera(deviceId) { - this.stop(); - this.$emit('camera-change', deviceId); - this.loadCamera(deviceId); - }, - loadSourceStream(stream) { - if ('srcObject' in this.$refs.video) { // new browsers api - this.$refs.video.srcObject = stream; - } else { // old browsers - this.source = window.HTMLMediaElement.srcObject(stream); - } - this.$refs.video.onloadedmetadata = () => { this.$emit('video-live', stream); }; - this.$emit('started', stream); - }, - stopStreamedVideo({ srcObject: stream }) { - const tracks = stream.getTracks(); - const stopTrack = (track) => { - track.stop(); - this.$emit('stopped', stream); - this.$refs.video.srcObject = null; - this.source = null; - }; - tracks.forEach(stopTrack); - }, - stop() { - if (!this.hasActiveVideo) { return; } - this.stopStreamedVideo(this.$refs.video); - }, - start() { - if (!this.device && !this.deviceId) { return; } - this.loadCamera(this.device || this.deviceId); - }, - startSelfie() { - this.loadSelfieCamera(); - }, - pause() { - if (!this.hasActiveVideo) { return; } - this.$refs.video.pause(); - }, - resume() { - if (!this.hasActiveVideo) { return; } - this.$refs.video.play(); - }, - async testMediaAccess() { - const constraints = { video: true }; - if (this.resolution) { - constraints.video = { - height: this.resolution.height, - width: this.resolution.width, - }; - } - try { - const stopTrack = (track) => { track.stop(); }; - const stream = await navigator.mediaDevices.getUserMedia(constraints); - const tracks = stream.getTracks(); - tracks.forEach(stopTrack); - await this.loadCameras(); - } catch (error) { - this.$emit('error', error); - } - }, - async loadSelfieCamera() { - const constraints = { - audio: false, - video: - { - facingMode: 'user', - }, - }; - if (this.resolution) { - constraints.video.height = this.resolution.height; - constraints.video.width = this.resolution.width; - } - try { - const stream = await navigator.mediaDevices.getUserMedia(constraints); - this.loadSourceStream(stream); - } catch (error) { - this.$emit('error', error); - } - }, - async loadCamera(device) { - const constraints = { - video: - { - deviceId: { - exact: device, - }, - }, - }; - if (this.resolution) { - constraints.video.height = this.resolution.height; - constraints.video.width = this.resolution.width; - } - try { - const stream = await navigator.mediaDevices.getUserMedia(constraints); - this.loadSourceStream(stream); - } catch (error) { - this.$emit('error', error); - } - }, - capture() { - const result = this.getCanvas().toDataURL(this.screenshotFormat); - return result; - }, - getCanvas() { - const { video } = this.$refs; - if (!this.ctx) { - const canvas = document.createElement('canvas'); - canvas.height = video.videoHeight; - canvas.width = video.videoWidth; - this.canvas = canvas; - this.ctx = canvas.getContext('2d'); - } - const { ctx, canvas } = this; - ctx.drawImage(video, 0, 0, canvas.width, canvas.height); - return canvas; - }, -}; - -const watch = { - deviceId(id) { - this.changeCamera(id); - }, -}; - -const mounted = function () { - this.setupMedia(); -}; - -const beforeUnmount = function () { - this.stop(); -}; - -const name = 'SelfieWebCam'; - -const SelfieWebCam = { - name, - props, - emits, - data, - computed, - methods, - watch, - mounted, - beforeUnmount, -}; - -export default SelfieWebCam; diff --git a/src/components/SelfieWebCam/SelfieWebCam.stories.js b/src/components/SelfieWebCam/SelfieWebCam.stories.ts similarity index 93% rename from src/components/SelfieWebCam/SelfieWebCam.stories.js rename to src/components/SelfieWebCam/SelfieWebCam.stories.ts index e8cd9d8..4aaec9a 100644 --- a/src/components/SelfieWebCam/SelfieWebCam.stories.js +++ b/src/components/SelfieWebCam/SelfieWebCam.stories.ts @@ -1,3 +1,5 @@ +import type { Meta, StoryFn } from '@storybook/vue3'; + import SelfieWebCam from './SelfieWebCam.vue'; import { createDeviceDecorator } from '../../lib/storybookHelpers'; @@ -29,9 +31,9 @@ const SelfieWebCamStories = { playsinline: { control: 'boolean', name: 'Plays Inline?' }, resolution: { control: 'object', name: 'Resolution' }, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/SelfieWebCam/SelfieWebCam.ts b/src/components/SelfieWebCam/SelfieWebCam.ts new file mode 100644 index 0000000..2a2d358 --- /dev/null +++ b/src/components/SelfieWebCam/SelfieWebCam.ts @@ -0,0 +1,225 @@ +// NOTE: This is a customized fork from the `vue-web-cam` NPM package (see: https://github.com/VinceG/vue-web-cam) which was required in order to add support for selfie constraints +import type { PropType } from 'vue'; +import { defineComponent, ref } from 'vue'; + +interface CamResolution { + width: number, + height: number, +} + +const SelfieWebCam = defineComponent({ + name: 'SelfieWebCam', + props: { + width: { + type: [Number, String], + default: '100%', + }, + height: { + type: [Number, String], + default: 500, + }, + autoplay: { + type: Boolean, + default: true, + }, + screenshotFormat: { + type: String, + default: 'image/jpeg', + }, + selectFirstDevice: { + type: Boolean, + default: false, + }, + deviceId: { + type: String, + default: '', + }, + playsinline: { + type: Boolean, + default: true, + }, + resolution: { + type: Object as PropType, + default: null, + validator: (value: CamResolution) => (!!value.height && !!value.width), + }, + }, + emits: ['cameras', 'notsupported', 'camera-change', 'video-live', 'started', 'stopped', 'error'], + computed: { + hasActiveVideo(): boolean { + const result = ((this.video !== null) && !!this.video.srcObject); + return result; + }, + }, + methods: { + setupMedia() { + this.testMediaAccess(); + }, + async loadCameras() { + try { + const devices = await navigator.mediaDevices.enumerateDevices(); + const filterVideoInputDevices = ({ kind }: MediaDeviceInfo) => (kind === 'videoinput'); + devices.filter(filterVideoInputDevices).forEach((device) => { this.cameras.push(device); }); + if (this.wasCamerasListEmitted) { return; } + if (this.selectFirstDevice && (Array.isArray(this.cameras) && this.cameras.length > 0)) { this.device = this.cameras[0]?.deviceId || ''; } + this.$emit('cameras', this.cameras); + this.wasCamerasListEmitted = true; + } catch (error) { + this.$emit('notsupported', error); + } + }, + changeCamera(deviceId: string) { + this.stop(); + this.$emit('camera-change', deviceId); + this.loadCamera(deviceId); + }, + loadSourceStream(stream: MediaStream) { + if ('srcObject' in this.video) { // new browsers api + this.video.srcObject = stream; + } else { // old browsers + this.source = `${stream}`; + } + this.video.onloadedmetadata = () => { this.$emit('video-live', stream); }; + this.$emit('started', stream); + }, + stopStreamedVideo({ srcObject: stream }: HTMLVideoElement) { + const tracks = (stream as MediaStream).getTracks(); + const stopTrack = (track: MediaStreamTrack) => { + track.stop(); + this.$emit('stopped', stream); + this.video.srcObject = null; + this.source = ''; + }; + tracks.forEach(stopTrack); + }, + stop() { + if (!this.hasActiveVideo) { return; } + this.stopStreamedVideo(this.video); + }, + start() { + if (!this.device && !this.deviceId) { return; } + this.loadCamera(this.device || this.deviceId); + }, + startSelfie() { + this.loadSelfieCamera(); + }, + pause() { + if (!this.hasActiveVideo) { return; } + this.video.pause(); + }, + resume() { + if (!this.hasActiveVideo) { return; } + this.video.play(); + }, + async testMediaAccess() { + const constraints = { video: true } as MediaStreamConstraints; + if (this.resolution) { + constraints.video = { + ...constraints.video as MediaTrackConstraints, + height: this.resolution.height, + width: this.resolution.width, + }; + } + try { + const stopTrack = (track: MediaStreamTrack) => { track.stop(); }; + const stream = await navigator.mediaDevices.getUserMedia(constraints); + const tracks = stream.getTracks(); + tracks.forEach(stopTrack); + await this.loadCameras(); + } catch (error) { + this.$emit('error', error); + } + }, + async loadSelfieCamera() { + const constraints = { + audio: false, + video: + { + facingMode: 'user', + }, + } as MediaStreamConstraints; + if (this.resolution) { + constraints.video = { + ...constraints.video as MediaTrackConstraints, + height: this.resolution.height, + width: this.resolution.width, + }; + } + try { + const stream = await navigator.mediaDevices.getUserMedia(constraints); + this.loadSourceStream(stream); + } catch (error) { + this.$emit('error', error); + } + }, + async loadCamera(device: string) { + const constraints = { + video: + { + deviceId: { + exact: device, + }, + }, + } as MediaStreamConstraints; + if (this.resolution) { + constraints.video = { + ...constraints.video as MediaTrackConstraints, + height: this.resolution.height, + width: this.resolution.width, + }; + } + try { + const stream = await navigator.mediaDevices.getUserMedia(constraints); + this.loadSourceStream(stream); + } catch (error) { + this.$emit('error', error); + } + }, + capture() { + const result = this.getCanvas().toDataURL(this.screenshotFormat); + return result; + }, + getCanvas(): HTMLCanvasElement { + if (!this.ctx) { + const canvas = document.createElement('canvas'); + canvas.height = this.video.videoHeight; + canvas.width = this.video.videoWidth; + this.canvas = canvas; + this.ctx = canvas.getContext('2d'); + } + this.ctx.drawImage(this.video, 0, 0, this.canvas.width, this.canvas.height); + return this.canvas; + }, + }, + watch: { + deviceId(id) { + this.changeCamera(id); + }, + }, + setup() { + const video = ref(); + const canvas = ref(); + const ctx = ref(); + const source = ref(''); + const wasCamerasListEmitted = ref(false); + const cameras = ref([]); + const device = ref(''); + return { + video, + canvas, + ctx, + source, + wasCamerasListEmitted, + cameras, + device, + }; + }, + mounted() { + this.setupMedia(); + }, + beforeUnmount() { + this.stop(); + }, +}); + +export default SelfieWebCam; diff --git a/src/components/SelfieWebCam/SelfieWebCam.vue b/src/components/SelfieWebCam/SelfieWebCam.vue index 2f77fca..0ab4f32 100644 --- a/src/components/SelfieWebCam/SelfieWebCam.vue +++ b/src/components/SelfieWebCam/SelfieWebCam.vue @@ -8,4 +8,4 @@ /> - diff --git a/src/components/SignaturePad/SignaturePad.js b/src/components/SignaturePad/SignaturePad.js deleted file mode 100644 index 02b5268..0000000 --- a/src/components/SignaturePad/SignaturePad.js +++ /dev/null @@ -1,97 +0,0 @@ -const name = 'SignaturePad'; - -const props = { - modelValue: { - type: [String, null], - required: true, - }, -}; - -const data = function () { - return { - context: null, - isStarted: false, - previousX: 0, - previousY: 0, - image: null, - canvasWidth: 300, - canvasHeight: 200, - }; -}; - -const methods = { - getMouseXPositionFromEvent(event) { - const { offsetX, touches = [{ clientX: this.$refs.canvasContainer.offsetLeft }] } = event; - const result = (offsetX || (touches[0].clientX - this.$refs.canvasContainer.offsetLeft)); - return result; - }, - getMouseYPositionFromEvent(event) { - const { offsetY, touches = [{ clientY: this.$refs.canvasContainer.offsetTop }] } = event; - const result = (offsetY || (touches[0].clientY - this.$refs.canvasContainer.offsetTop)); - return result; - }, - startDraw(event) { - this.isStarted = true; - this.previousX = this.getMouseXPositionFromEvent(event); - this.previousY = this.getMouseYPositionFromEvent(event); - }, - continueDraw(event) { - if (!this.isStarted) { return; } - const currentX = this.getMouseXPositionFromEvent(event); - const currentY = this.getMouseYPositionFromEvent(event); - const initial = { x: this.previousX, y: this.previousY }; - const destination = { x: currentX, y: currentY }; - this.draw(initial, destination); - this.previousX = currentX; - this.previousY = currentY; - }, - endDraw() { - this.isStarted = false; - }, - draw({ x: initialX, y: initialY }, { x: destinationX, y: destinationY }) { - this.context.beginPath(); - this.context.moveTo(initialX, initialY); - this.context.lineTo(destinationX, destinationY); - this.context.closePath(); - this.context.stroke(); - - this.image = this.$refs.signatureCanvas.toDataURL('image/png').replace('image/png', 'image/octet-stream'); - }, -}; - -const watch = { - modelValue() { - if (this.modelValue) { return; } - this.context.clearRect(0, 0, this.$refs.signatureCanvas.width, this.$refs.signatureCanvas.height); - }, - image() { - this.$emit('update:modelValue', this.image); - }, -}; - -const mounted = function () { - this.canvasWidth = this.$refs.signatureCanvas.clientWidth; - this.canvasHeight = this.$refs.signatureCanvas.clientHeight; - this.context = this.$refs.signatureCanvas.getContext('2d'); - this.context.strokeStyle = 'black'; - this.context.lineWidth = 2; - document.addEventListener('mousemove', this.continueSlide); - document.addEventListener('mouseup', this.endSlide); -}; - -const unmounted = function () { - document.removeEventListener('mousemove', this.continueSlide); - document.removeEventListener('mouseup', this.endSlide); -}; - -const SignaturePad = { - name, - props, - data, - methods, - watch, - mounted, - unmounted, -}; - -export default SignaturePad; diff --git a/src/components/SignaturePad/SignaturePad.stories.js b/src/components/SignaturePad/SignaturePad.stories.ts similarity index 85% rename from src/components/SignaturePad/SignaturePad.stories.js rename to src/components/SignaturePad/SignaturePad.stories.ts index ba56464..ba54e26 100644 --- a/src/components/SignaturePad/SignaturePad.stories.js +++ b/src/components/SignaturePad/SignaturePad.stories.ts @@ -1,3 +1,5 @@ +import type { Meta, StoryFn } from '@storybook/vue3'; + import SignaturePad from './SignaturePad.vue'; import { createDeviceDecorator } from '../../lib/storybookHelpers'; @@ -13,9 +15,9 @@ const SignaturePadStories = { argTypes: { ...deviceDecorator.argTypes, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/SignaturePad/SignaturePad.ts b/src/components/SignaturePad/SignaturePad.ts new file mode 100644 index 0000000..d9caf54 --- /dev/null +++ b/src/components/SignaturePad/SignaturePad.ts @@ -0,0 +1,121 @@ +import { defineComponent, ref, watchEffect } from 'vue'; + +interface Coordinates { + x: number, + y: number, +} + +const isTouchEvent = (event: MouseEvent | TouchEvent): event is TouchEvent => (event as TouchEvent).touches !== undefined; + +const SignaturePad = defineComponent({ + name: 'SignaturePad', + props: { + modelValue: { + type: String, + required: true, + }, + }, + emits: ['update:modelValue'], + methods: { + getMouseXPositionFromEvent(event: MouseEvent | TouchEvent) { + if (isTouchEvent(event)) { + const touch = event.touches.item(0); + const result = ((touch?.clientX || 0) - this.canvasContainer.offsetLeft); + return result; + } + const result = event.offsetX; + return result; + }, + getMouseYPositionFromEvent(event: MouseEvent | TouchEvent) { + if (isTouchEvent(event)) { + const touch = event.touches.item(0); + const result = ((touch?.clientY || 0) - this.canvasContainer.offsetLeft); + return result; + } + const result = event.offsetY; + return result; + }, + startDraw(event: MouseEvent | TouchEvent) { + this.isStarted = true; + this.previousX = this.getMouseXPositionFromEvent(event); + this.previousY = this.getMouseYPositionFromEvent(event); + }, + continueDraw(event: MouseEvent | TouchEvent) { + if (!this.isStarted) { return; } + const currentX = this.getMouseXPositionFromEvent(event); + const currentY = this.getMouseYPositionFromEvent(event); + const initial = { x: this.previousX, y: this.previousY }; + const destination = { x: currentX, y: currentY }; + this.draw(initial, destination); + this.previousX = currentX; + this.previousY = currentY; + }, + endDraw() { + this.isStarted = false; + }, + draw({ x: initialX, y: initialY }: Coordinates, { x: destinationX, y: destinationY }: Coordinates) { + if (!this.context || !this.signatureCanvas) { return; } + this.context.beginPath(); + this.context.moveTo(initialX, initialY); + this.context.lineTo(destinationX, destinationY); + this.context.closePath(); + this.context.stroke(); + + this.image = this.signatureCanvas.toDataURL('image/png').replace('image/png', 'image/octet-stream'); + }, + }, + watch: { + modelValue() { + if (this.modelValue || !this.context || !this.signatureCanvas) { return; } + this.context.clearRect(0, 0, this.signatureCanvas.width, this.signatureCanvas.height); + }, + image() { + this.$emit('update:modelValue', this.image); + }, + }, + setup() { + const signatureCanvas = ref(); + const canvasContainer = ref(); + const context = ref(); + const isStarted = ref(false); + const previousX = ref(0); + const previousY = ref(0); + const image = ref(''); + const canvasWidth = ref(300); + const canvasHeight = ref(200); + + watchEffect(() => { + if (!signatureCanvas.value) { return; } + canvasWidth.value = signatureCanvas.value.clientWidth; + canvasHeight.value = signatureCanvas.value.clientHeight; + context.value = signatureCanvas.value.getContext('2d'); + context.value.strokeStyle = 'black'; + context.value.lineWidth = 2; + }, + { + flush: 'post', + }); + + return { + signatureCanvas, + canvasContainer, + context, + isStarted, + previousX, + previousY, + image, + canvasWidth, + canvasHeight, + }; + }, + mounted() { + document.addEventListener('mousemove', this.continueDraw); + document.addEventListener('mouseup', this.endDraw); + }, + unmounted() { + document.removeEventListener('mousemove', this.continueDraw); + document.removeEventListener('mouseup', this.endDraw); + }, +}); + +export default SignaturePad; diff --git a/src/components/SignaturePad/SignaturePad.vue b/src/components/SignaturePad/SignaturePad.vue index 0025c4b..5797b20 100644 --- a/src/components/SignaturePad/SignaturePad.vue +++ b/src/components/SignaturePad/SignaturePad.vue @@ -14,5 +14,5 @@ - + diff --git a/src/components/SlideButton/SlideButton.js b/src/components/SlideButton/SlideButton.js deleted file mode 100644 index 27d541a..0000000 --- a/src/components/SlideButton/SlideButton.js +++ /dev/null @@ -1,157 +0,0 @@ -import { ChevronsRightIcon } from '@lana/b2c-mapp-ui-assets'; - -const gapWidth = -25; - -const components = { - ChevronsRightIcon, -}; - -const props = { - initialInstructionLabel: { - type: String, - default: 'Desliza para confirmar', - }, - completedLabel: { - type: String, - default: '', - }, -}; - -const data = function () { - return { - initialMouseX: 0, - currentMouseX: 0, - isStarted: false, - isCompleted: false, - endPoint: 500, - initialSliderWidth: 0, - initialSlideButtonPosition: 0, - buttonCenterPoint: 0, - overlayStyle: { - width: '0', - }, - slideButtonStyle: { - left: '0', - }, - }; -}; - -const emits = ['actionConfirmed']; - -const computed = { - instructionLabel() { - if (this.isCompleted) { return this.completedLabel; } - return this.initialInstructionLabel; - }, -}; - -const methods = { - startSlide(event) { - if (this.isCompleted) { return; } - this.initialMouseX = this.getMouseXPositionFromEvent(event); - this.endPoint = this.getEndingPoint(); - this.calculateSliderInitialWidth(); - this.calculateSlideButtonInitialPosition(); - this.updateSlideButton(0); - this.updateSlider(); - this.isStarted = true; - }, - getEndingPoint() { - const [{ right: endingPoint }] = this.$refs.slider.getClientRects(); - return endingPoint; - }, - calculateSliderInitialWidth() { - const [{ x: leftSliderPosition }] = this.$refs.slider.getClientRects(); - this.initialSliderWidth = this.initialMouseX - leftSliderPosition; - if (this.initialSliderWidth < 0) { this.initialSliderWidth = 0; } - }, - calculateSlideButtonInitialPosition() { - const [{ x: sliderPosition }] = this.$refs.slider.getClientRects(); - this.initialSlideButtonPosition = sliderPosition; - }, - continueSlide(event) { - if (!this.isStarted) { return; } - this.currentMouseX = this.getMouseXPositionFromEvent(event); - const delta = this.currentMouseX - this.initialMouseX; - this.updateSlider(); - this.updateSlideButton(delta); - if (this.hasSliderReachedEndPoint()) { this.endSlide(); } - }, - endSlide() { - if (!this.isStarted) { return; } - this.isStarted = false; - if (this.hasSliderReachedEndPoint()) { - const overlayWidth = (this.buttonCenterPoint - gapWidth); - this.overlayStyle.width = `${overlayWidth}px`; - this.actionConfirmed(); - return; - } - this.sliderClass = ''; - this.overlayStyle.width = '0'; - this.slideButtonStyle.left = '0'; - }, - getMouseXPositionFromEvent(event) { - this.buttonCenterPoint = (this.$refs.slideButton.offsetLeft + (this.$refs.slideButton.offsetWidth / 2)); - const result = (event.clientX || event.touches[0].pageX); - return result; - }, - updateSlider() { - const sliderWidth = this.getSliderWidth(); - let newWidth = (this.buttonCenterPoint - gapWidth); - if (newWidth > sliderWidth) { newWidth = sliderWidth; } - this.overlayStyle.width = `${newWidth}px`; - }, - getSliderWidth() { - const [{ width: sliderWidth }] = this.$refs.slider.getClientRects(); - return sliderWidth; - }, - updateSlideButton(delta) { - if (delta < 0) { return; } - this.slideButtonStyle.left = `${delta}px`; - if (!this.hasSliderReachedEndPoint()) { return; } - const buttonLeftPosition = (this.getSliderWidth() - this.getButtonWidth()); - this.slideButtonStyle.left = `${buttonLeftPosition}px`; - }, - getButtonWidth() { - if (!this.$refs.slideButton) { return; } - const [{ width }] = this.$refs.slideButton.getClientRects(); - return width; - }, - hasSliderReachedEndPoint() { - if (!this.$refs.slideButton) { return; } - const [{ right }] = this.$refs.slideButton.getClientRects(); - const result = (right >= this.endPoint); - return result; - }, - actionConfirmed() { - if (this.isCompleted) { return; } - this.isCompleted = true; - this.$emit('actionConfirmed'); - }, -}; - -const mounted = function () { - document.addEventListener('mousemove', this.continueSlide); - document.addEventListener('mouseup', this.endSlide); -}; - -const unmounted = function () { - document.removeEventListener('mousemove', this.continueSlide); - document.removeEventListener('mouseup', this.endSlide); -}; - -const name = 'SlideButton'; - -const SlideButton = { - name, - components, - props, - emits, - data, - computed, - methods, - mounted, - unmounted, -}; - -export default SlideButton; diff --git a/src/components/SlideButton/SlideButton.stories.js b/src/components/SlideButton/SlideButton.stories.ts similarity index 89% rename from src/components/SlideButton/SlideButton.stories.js rename to src/components/SlideButton/SlideButton.stories.ts index c7e001f..a862304 100644 --- a/src/components/SlideButton/SlideButton.stories.js +++ b/src/components/SlideButton/SlideButton.stories.ts @@ -1,4 +1,5 @@ import { action } from '@storybook/addon-actions'; +import type { Meta, StoryFn } from '@storybook/vue3'; import SlideButton from './SlideButton.vue'; import { createDeviceDecorator } from '../../lib/storybookHelpers'; @@ -19,9 +20,9 @@ const SlideButtonStories = { initialInstructionLabel: { control: 'text', name: 'Initial instruction label' }, completedLabel: { control: 'text', name: 'Label after completion' }, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/SlideButton/SlideButton.ts b/src/components/SlideButton/SlideButton.ts new file mode 100644 index 0000000..c3b64f8 --- /dev/null +++ b/src/components/SlideButton/SlideButton.ts @@ -0,0 +1,159 @@ +import { ChevronsRightIcon } from '@lana/b2c-mapp-ui-assets'; +import { defineComponent, ref } from 'vue'; + +const isTouchEvent = (event: MouseEvent | TouchEvent): event is TouchEvent => (event as TouchEvent).touches !== undefined; + +const gapWidth = -25; + +const SlideButton = defineComponent({ + name: 'SlideButton', + components: { + ChevronsRightIcon, + }, + props: { + initialInstructionLabel: { + type: String, + default: 'Desliza para confirmar', + }, + completedLabel: { + type: String, + default: '', + }, + }, + emits: ['actionConfirmed'], + computed: { + instructionLabel() { + if (this.isCompleted) { return this.completedLabel; } + return this.initialInstructionLabel; + }, + }, + methods: { + startSlide(event: MouseEvent | TouchEvent) { + if (this.isCompleted) { return; } + this.initialMouseX = this.getMouseXPositionFromEvent(event); + this.endPoint = this.getEndingPoint(); + this.calculateSliderInitialWidth(); + this.calculateSlideButtonInitialPosition(); + this.updateSlideButton(0); + this.updateSlider(); + this.isStarted = true; + }, + getEndingPoint() { + const [{ right: endingPoint }] = this.slider.getClientRects(); + return endingPoint; + }, + calculateSliderInitialWidth() { + const [{ x: leftSliderPosition }] = this.slider.getClientRects(); + this.initialSliderWidth = this.initialMouseX - leftSliderPosition; + if (this.initialSliderWidth < 0) { this.initialSliderWidth = 0; } + }, + calculateSlideButtonInitialPosition() { + const [{ x: sliderPosition }] = this.slider.getClientRects(); + this.initialSlideButtonPosition = sliderPosition; + }, + continueSlide(event: MouseEvent | TouchEvent) { + if (!this.isStarted) { return; } + this.currentMouseX = this.getMouseXPositionFromEvent(event); + const delta = this.currentMouseX - this.initialMouseX; + this.updateSlider(); + this.updateSlideButton(delta); + if (this.hasSliderReachedEndPoint()) { this.endSlide(); } + }, + endSlide() { + if (!this.isStarted) { return; } + this.isStarted = false; + if (this.hasSliderReachedEndPoint()) { + const overlayWidth = (this.buttonCenterPoint - gapWidth); + this.overlayStyle.width = `${overlayWidth}px`; + this.actionConfirmed(); + return; + } + this.sliderClass = ''; + this.overlayStyle.width = '0'; + this.slideButtonStyle.left = '0'; + }, + getMouseXPositionFromEvent(event: MouseEvent | TouchEvent) { + this.buttonCenterPoint = (this.slideButton.offsetLeft + (this.slideButton.offsetWidth / 2)); + if (isTouchEvent(event)) { + const touch = event.touches.item(0); + const result = touch?.pageX || 0; + return result; + } + const result = event.clientX; + return result; + }, + updateSlider() { + const sliderWidth = this.getSliderWidth(); + let newWidth = (this.buttonCenterPoint - gapWidth); + if (newWidth > sliderWidth) { newWidth = sliderWidth; } + this.overlayStyle.width = `${newWidth}px`; + }, + getSliderWidth() { + const [{ width: sliderWidth }] = this.slider.getClientRects(); + return sliderWidth; + }, + updateSlideButton(delta: number) { + if (delta < 0) { return; } + this.slideButtonStyle.left = `${delta}px`; + if (!this.hasSliderReachedEndPoint()) { return; } + const buttonLeftPosition = (this.getSliderWidth() - this.getButtonWidth()); + this.slideButtonStyle.left = `${buttonLeftPosition}px`; + }, + getButtonWidth() { + if (!this.slideButton) { return; } + const [{ width }] = this.slideButton.getClientRects(); + return width; + }, + hasSliderReachedEndPoint() { + if (!this.slideButton) { return; } + const [{ right }] = this.slideButton.getClientRects(); + const result = (right >= this.endPoint); + return result; + }, + actionConfirmed() { + if (this.isCompleted) { return; } + this.isCompleted = true; + this.$emit('actionConfirmed'); + }, + }, + setup() { + const slideButton = ref(); + const slider = ref(); + const initialMouseX = ref(0); + const currentMouseX = ref(0); + const isStarted = ref(false); + const isCompleted = ref(false); + const endPoint = ref(500); + const initialSliderWidth = ref(0); + const initialSlideButtonPosition = ref(0); + const buttonCenterPoint = ref(0); + const sliderClass = ref(''); + const overlayStyle = ref({ width: '0' }); + const slideButtonStyle = ref({ left: '0' }); + return { + slideButton, + slider, + initialMouseX, + currentMouseX, + isStarted, + isCompleted, + endPoint, + initialSliderWidth, + initialSlideButtonPosition, + buttonCenterPoint, + sliderClass, + overlayStyle, + slideButtonStyle, + }; + }, + mounted() { + document.addEventListener('mousemove', this.continueSlide); + document.addEventListener('mouseup', this.endSlide); + }, + unmounted() { + document.removeEventListener('mousemove', this.continueSlide); + document.removeEventListener('mouseup', this.endSlide); + }, +}); + +export default SlideButton; diff --git a/src/components/SlideButton/SlideButton.vue b/src/components/SlideButton/SlideButton.vue index c18abaf..b2e6bb2 100644 --- a/src/components/SlideButton/SlideButton.vue +++ b/src/components/SlideButton/SlideButton.vue @@ -23,5 +23,5 @@ - + diff --git a/src/components/SpecCard/SpecCard.js b/src/components/SpecCard/SpecCard.js deleted file mode 100644 index feb3f9b..0000000 --- a/src/components/SpecCard/SpecCard.js +++ /dev/null @@ -1,27 +0,0 @@ -import TextParagraph from '../TextParagraph/TextParagraph.vue'; - -const components = { - TextParagraph, -}; - -const props = { - dataTestId: { - type: String, - default: 'spec-card', - }, - title: { - type: String, - default: '', - }, - titleAbove: Boolean, -}; - -const name = 'SpecCard'; - -const SpecCard = { - name, - components, - props, -}; - -export default SpecCard; diff --git a/src/components/SpecCard/SpecCard.stories.js b/src/components/SpecCard/SpecCard.stories.ts similarity index 89% rename from src/components/SpecCard/SpecCard.stories.js rename to src/components/SpecCard/SpecCard.stories.ts index a0771af..354ef09 100644 --- a/src/components/SpecCard/SpecCard.stories.js +++ b/src/components/SpecCard/SpecCard.stories.ts @@ -1,3 +1,5 @@ +import type { Meta, StoryFn } from '@storybook/vue3'; + import SpecCard from './SpecCard.vue'; import { createDeviceDecorator } from '../../lib/storybookHelpers'; import RenderString from '../../lib/renderString'; @@ -18,9 +20,9 @@ const SpecCardStories = { title: { control: 'text', name: 'Title' }, default: { control: { type: 'text' }, table: { type: { summary: null } } }, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/SpecCard/SpecCard.ts b/src/components/SpecCard/SpecCard.ts new file mode 100644 index 0000000..77d4046 --- /dev/null +++ b/src/components/SpecCard/SpecCard.ts @@ -0,0 +1,23 @@ +import { defineComponent } from 'vue'; + +import TextParagraph from '../TextParagraph/TextParagraph.vue'; + +const SpecCard = defineComponent({ + name: 'SpecCard', + components: { + TextParagraph, + }, + props: { + dataTestId: { + type: String, + default: 'spec-card', + }, + title: { + type: String, + default: '', + }, + titleAbove: Boolean, + }, +}); + +export default SpecCard; diff --git a/src/components/SpecCard/SpecCard.vue b/src/components/SpecCard/SpecCard.vue index 0a63eb0..68e148d 100644 --- a/src/components/SpecCard/SpecCard.vue +++ b/src/components/SpecCard/SpecCard.vue @@ -7,5 +7,5 @@ - + diff --git a/src/components/Stepper/Stepper.js b/src/components/Stepper/Stepper.js deleted file mode 100644 index a688a55..0000000 --- a/src/components/Stepper/Stepper.js +++ /dev/null @@ -1,42 +0,0 @@ -import Heading from '../Heading/Heading.vue'; - -const components = { - Heading, -}; - -const props = { - dataTestId: { - type: String, - default: 'navigation-stepper', - }, - title: { - type: String, - default: '', - }, - hideActiveStep: Boolean, - modelValue: Number, - countOfSteps: Number, -}; - -const computed = { - availableSteps() { - if (!this.countOfSteps) { return []; } - const result = [...Array(this.countOfSteps).keys()].map((step, index) => ({ - isActive: ((index === this.modelValue) && !this.hideActiveStep), - isInactive: (index > this.modelValue), // WARNING: `Inactive` in this context is not the same thing as `!isActive` - stepNumber: (index + 1), - })); - return result; - }, -}; - -const name = 'Stepper'; - -const Stepper = { - name, - components, - props, - computed, -}; - -export default Stepper; diff --git a/src/components/Stepper/Stepper.stories.js b/src/components/Stepper/Stepper.stories.ts similarity index 89% rename from src/components/Stepper/Stepper.stories.js rename to src/components/Stepper/Stepper.stories.ts index c5a7d19..3f0fda6 100644 --- a/src/components/Stepper/Stepper.stories.js +++ b/src/components/Stepper/Stepper.stories.ts @@ -1,3 +1,5 @@ +import type { Meta, StoryFn } from '@storybook/vue3'; + import Stepper from './Stepper.vue'; import { createDeviceDecorator } from '../../lib/storybookHelpers'; @@ -21,9 +23,9 @@ const StepperStories = { hideActiveStep: { control: 'boolean', name: 'Hide the active step?' }, value: { control: 'number', name: 'Active Step Number' }, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/Stepper/Stepper.test.js b/src/components/Stepper/Stepper.test.ts similarity index 95% rename from src/components/Stepper/Stepper.test.js rename to src/components/Stepper/Stepper.test.ts index 242f6e3..89bcd6b 100644 --- a/src/components/Stepper/Stepper.test.js +++ b/src/components/Stepper/Stepper.test.ts @@ -31,7 +31,7 @@ describe('Stepper unit test', () => { const wrapper = mount(Stepper, { props: { ...defaultProps } }); await wrapper.vm.$nextTick(); const activeStep = wrapper.findAll('li')[0]; - const hasActiveClass = activeStep.classes().includes('active'); + const hasActiveClass = activeStep?.classes().includes('active'); expect(hasActiveClass).toBeTruthy(); }); diff --git a/src/components/Stepper/Stepper.ts b/src/components/Stepper/Stepper.ts new file mode 100644 index 0000000..eb75107 --- /dev/null +++ b/src/components/Stepper/Stepper.ts @@ -0,0 +1,42 @@ +import { defineComponent } from 'vue'; + +import Heading from '../Heading/Heading.vue'; + +const Stepper = defineComponent({ + name: 'Stepper', + components: { + Heading, + }, + props: { + dataTestId: { + type: String, + default: 'navigation-stepper', + }, + title: { + type: String, + default: '', + }, + hideActiveStep: Boolean, + modelValue: { + type: Number, + default: 0, + }, + countOfSteps: { + type: Number, + default: 0, + }, + }, + computed: { + availableSteps() { + if (!this.countOfSteps) { return []; } + const result = Array(this.countOfSteps).fill(0).map((_step, index) => ({ + isActive: ((index === this.modelValue) && !this.hideActiveStep), + isInactive: (index > this.modelValue), // WARNING: `Inactive` in this context is not the same thing as `!isActive` + stepNumber: (index + 1), + })); + return result; + }, + }, +}); + +export default Stepper; diff --git a/src/components/Stepper/Stepper.vue b/src/components/Stepper/Stepper.vue index 7374f6c..2bd5e56 100644 --- a/src/components/Stepper/Stepper.vue +++ b/src/components/Stepper/Stepper.vue @@ -16,5 +16,5 @@ - + diff --git a/src/components/StorybookMobileDeviceSimulator/StorybookMobileDeviceSimulator.js b/src/components/StorybookMobileDeviceSimulator/StorybookMobileDeviceSimulator.js deleted file mode 100644 index 5669ece..0000000 --- a/src/components/StorybookMobileDeviceSimulator/StorybookMobileDeviceSimulator.js +++ /dev/null @@ -1,91 +0,0 @@ -// TODO: Add more devices below (get a list of the most common devices and their resolutions from the Mobile team) - -const deviceNameLookup = { - default: 'default', - small: 'Small', - medium: 'Medium', - large: 'Large', - extraLarge: 'Extra Large', - pixel2: 'Pixel 2', - galaxyS5: 'Galaxy S5', - iphone5: 'iPhone 5', - samsungA30: 'Samsung A30', - samsungA50: 'Samsung A50', - huaweiMate20Lite: 'Huawei Mate 20 Lite', -}; - -const getAvailableDevices = () => Object.values(deviceNameLookup); - -const deviceNameToResolutionLookup = { - [deviceNameLookup.default]: { - width: '420px', - height: '640px', - }, - [deviceNameLookup.small]: { - width: '320px', - height: '568px', - }, - [deviceNameLookup.medium]: { - width: '360px', - height: '640px', - }, - [deviceNameLookup.large]: { - width: '360px', - height: '780px', - }, - [deviceNameLookup.extraLarge]: { - width: '412px', - height: '892px', - }, - [deviceNameLookup.pixel2]: { - width: '411px', - height: '731px', - }, - [deviceNameLookup.galaxyS5]: { - width: '360px', - height: '640px', - }, - [deviceNameLookup.iphone5]: { - width: '320px', - height: '568px', - }, - [deviceNameLookup.samsungA30]: { - width: '412px', - height: '892px', - }, - [deviceNameLookup.samsungA50]: { - width: '412px', - height: '892px', - }, - [deviceNameLookup.huaweiMate20Lite]: { - width: '360px', - height: '780px', - }, -}; - -const props = { - device: { - type: String, - default: deviceNameLookup.default, - validator(value) { return (getAvailableDevices().includes(value)); }, - }, -}; - -const computed = { - resolution() { - const result = (deviceNameToResolutionLookup[this.device] || deviceNameToResolutionLookup[deviceNameLookup.default]); - return result; - }, -}; - -const name = 'StorybookMobileDeviceSimulator'; - -const StorybookMobileDeviceSimulator = { - name, - props, - computed, -}; - -export { getAvailableDevices }; - -export default StorybookMobileDeviceSimulator; diff --git a/src/components/StorybookMobileDeviceSimulator/StorybookMobileDeviceSimulator.stories.js b/src/components/StorybookMobileDeviceSimulator/StorybookMobileDeviceSimulator.stories.ts similarity index 89% rename from src/components/StorybookMobileDeviceSimulator/StorybookMobileDeviceSimulator.stories.js rename to src/components/StorybookMobileDeviceSimulator/StorybookMobileDeviceSimulator.stories.ts index 275fdc9..7918dc3 100644 --- a/src/components/StorybookMobileDeviceSimulator/StorybookMobileDeviceSimulator.stories.js +++ b/src/components/StorybookMobileDeviceSimulator/StorybookMobileDeviceSimulator.stories.ts @@ -1,3 +1,5 @@ +import type { Meta, StoryFn } from '@storybook/vue3'; + import StorybookMobileDeviceSimulator from './StorybookMobileDeviceSimulator.vue'; import { getAvailableDevices } from './StorybookMobileDeviceSimulator'; import { createScreenDecorator } from '../../lib/storybookHelpers'; @@ -18,9 +20,9 @@ const StorybookMobileDeviceSimulatorStories = { device: { control: 'select', name: 'Simulated Mobile Device', options: [...availableDevices] }, default: { control: { type: 'text' }, table: { type: { summary: null } } }, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/StorybookMobileDeviceSimulator/StorybookMobileDeviceSimulator.ts b/src/components/StorybookMobileDeviceSimulator/StorybookMobileDeviceSimulator.ts new file mode 100644 index 0000000..38d406b --- /dev/null +++ b/src/components/StorybookMobileDeviceSimulator/StorybookMobileDeviceSimulator.ts @@ -0,0 +1,93 @@ +import type { PropType } from 'vue'; +import { defineComponent } from 'vue'; + +interface Device { + width: string, + height: string, +} + +// TODO: Add more devices below (get a list of the most common devices and their resolutions from the Mobile team) +enum DeviceName { + default = 'default', + small = 'Small', + medium = 'Medium', + large = 'Large', + extraLarge = 'Extra Large', + pixel2 = 'Pixel 2', + galaxyS5 = 'Galaxy S5', + iphone5 = 'iPhone 5', + samsungA30 = 'Samsung A30', + samsungA50 = 'Samsung A50', + huaweiMate20Lite = 'Huawei Mate 20 Lite', +} + +const getAvailableDevices = () => Object.values(DeviceName); + +const deviceNameToResolutionLookup: { [key in DeviceName]: Device } = { + [DeviceName.default]: { + width: '420px', + height: '640px', + }, + [DeviceName.small]: { + width: '320px', + height: '568px', + }, + [DeviceName.medium]: { + width: '360px', + height: '640px', + }, + [DeviceName.large]: { + width: '360px', + height: '780px', + }, + [DeviceName.extraLarge]: { + width: '412px', + height: '892px', + }, + [DeviceName.pixel2]: { + width: '411px', + height: '731px', + }, + [DeviceName.galaxyS5]: { + width: '360px', + height: '640px', + }, + [DeviceName.iphone5]: { + width: '320px', + height: '568px', + }, + [DeviceName.samsungA30]: { + width: '412px', + height: '892px', + }, + [DeviceName.samsungA50]: { + width: '412px', + height: '892px', + }, + [DeviceName.huaweiMate20Lite]: { + width: '360px', + height: '780px', + }, +}; + +const StorybookMobileDeviceSimulator = defineComponent({ + name: 'StorybookMobileDeviceSimulator', + props: { + device: { + type: String as PropType, + default: DeviceName.default, + validator: (value: DeviceName) => getAvailableDevices().includes(value), + }, + }, + computed: { + resolution(): Device { + const result = (deviceNameToResolutionLookup[this.device] || deviceNameToResolutionLookup[DeviceName.default]); + return result; + }, + }, +}); + +export type { DeviceName }; +export { getAvailableDevices }; + +export default StorybookMobileDeviceSimulator; diff --git a/src/components/StorybookMobileDeviceSimulator/StorybookMobileDeviceSimulator.vue b/src/components/StorybookMobileDeviceSimulator/StorybookMobileDeviceSimulator.vue index 6668f0b..5e0f3db 100644 --- a/src/components/StorybookMobileDeviceSimulator/StorybookMobileDeviceSimulator.vue +++ b/src/components/StorybookMobileDeviceSimulator/StorybookMobileDeviceSimulator.vue @@ -6,5 +6,5 @@ - + diff --git a/src/components/TextField/TextField.js b/src/components/TextField/TextField.js deleted file mode 100644 index 8e23691..0000000 --- a/src/components/TextField/TextField.js +++ /dev/null @@ -1,104 +0,0 @@ -import FormField from '../FormField/FormField.vue'; -import TextParagraph from '../TextParagraph/TextParagraph.vue'; - -const components = { - FormField, - TextParagraph, -}; - -const props = { - dataTestId: { - type: String, - default: 'text-field', - }, - type: { - type: String, - default: 'text', - }, - maxLength: Number, - modelValue: { - type: [String, Number], - default: '', - }, - id: String, - name: String, - label: String, - disabled: Boolean, - errorLabel: String, - readonly: Boolean, - startFocused: Boolean, - lengthHint: Number, - lengthHintLabel: String, - helpText: String, - hideClearButton: Boolean, - inputmode: String, - pattern: String, -}; - -const emits = ['update:modelValue', 'focus', 'blur', 'keypress', 'keyup', 'paste']; - -const data = function () { - return { - inputValue: this.modelValue, - }; -}; - -const computed = { - maxLengthToUse() { - const result = (this.maxLength || this.lengthHint); - return result; - }, -}; - -const methods = { - emitUpdateModelValueEvent() { - this.$emit('update:modelValue', this.inputValue); - }, - focus() { - if (!this.$refs.field) { return; } - this.$refs.field.focus(); - }, - blur() { - if (!this.$refs.field) { return; } - this.$refs.field.blur(); - }, - onFocus(event) { - this.$emit('focus', event); - }, - onBlur(event) { - this.$emit('blur', event); - }, - onKeypress(event) { - this.$emit('keypress', event); - }, - onKeyup(event) { - this.$emit('keyup', event); - }, - onPaste(event) { - this.$emit('paste', event); - }, -}; - -const watch = { - inputValue() { - this.emitUpdateModelValueEvent(); - }, - modelValue() { - this.inputValue = this.modelValue; - }, -}; - -const name = 'TextField'; - -const TextField = { - name, - components, - computed, - props, - emits, - data, - methods, - watch, -}; - -export default TextField; diff --git a/src/components/TextField/TextField.stories.js b/src/components/TextField/TextField.stories.ts similarity index 97% rename from src/components/TextField/TextField.stories.js rename to src/components/TextField/TextField.stories.ts index aa97915..9724ff3 100644 --- a/src/components/TextField/TextField.stories.js +++ b/src/components/TextField/TextField.stories.ts @@ -1,4 +1,5 @@ import { action } from '@storybook/addon-actions'; +import type { Meta, StoryFn } from '@storybook/vue3'; import TextField from './TextField.vue'; import { createOptionalDeviceDecorator } from '../../lib/storybookHelpers'; @@ -43,9 +44,9 @@ const TextFieldStories = { inputmode: { control: 'text', name: 'Inputmode' }, pattern: { control: 'text', name: 'Pattern' }, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { @@ -121,7 +122,7 @@ defaultExample.parameters = { }, }; -const examples = (args, { argTypes }) => ({ +const examples: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/TextField/TextField.test.js b/src/components/TextField/TextField.test.ts similarity index 92% rename from src/components/TextField/TextField.test.js rename to src/components/TextField/TextField.test.ts index 18b67b1..19f81ef 100644 --- a/src/components/TextField/TextField.test.js +++ b/src/components/TextField/TextField.test.ts @@ -15,7 +15,7 @@ describe('UI/forms/TextField', () => { const givenValue = 'given value'; const wrapper = mount(TextField, { props: { ...defaultProps, modelValue: givenValue } }); await wrapper.vm.$nextTick(); - const takesGivenValue = wrapper.vm.$data.inputValue === givenValue; + const takesGivenValue = wrapper.vm.inputValue === givenValue; expect(takesGivenValue).toBeTruthy(); }); @@ -24,7 +24,7 @@ describe('UI/forms/TextField', () => { const wrapper = mount(TextField, { props: { ...defaultProps, modelValue: givenValue } }); wrapper.vm.$options.watch.modelValue.call(wrapper.vm, givenValue); await wrapper.vm.$nextTick(); - const takesGivenValue = wrapper.vm.$data.inputValue === givenValue; + const takesGivenValue = wrapper.vm.inputValue === givenValue; expect(takesGivenValue).toBeTruthy(); }); @@ -42,8 +42,8 @@ describe('UI/forms/TextField', () => { const wrapper = mount(TextField, { props: { ...defaultProps, modelValue: givenValue } }); wrapper.vm.$options.watch.inputValue.call(wrapper.vm, givenValue); await wrapper.vm.$nextTick(); - const inputValueEmitted = wrapper.emitted('update:modelValue')[0][0] === givenValue; - expect(inputValueEmitted).toBeTruthy(); + const [inputValueEmitted] = wrapper.emitted('update:modelValue')?.[0] as string[]; + expect(inputValueEmitted).toBe(givenValue); }); it('Should emit blur event when its blurred', async () => { diff --git a/src/components/TextField/TextField.ts b/src/components/TextField/TextField.ts new file mode 100644 index 0000000..2ffd1dc --- /dev/null +++ b/src/components/TextField/TextField.ts @@ -0,0 +1,123 @@ +import { defineComponent, ref } from 'vue'; + +import FormField from '../FormField/FormField.vue'; +import TextParagraph from '../TextParagraph/TextParagraph.vue'; + +const TextField = defineComponent({ + name: 'TextField', + components: { + FormField, + TextParagraph, + }, + props: { + dataTestId: { + type: String, + default: 'text-field', + }, + type: { + type: String, + default: 'text', + }, + maxLength: { + type: Number, + default: null, + }, + modelValue: { + type: [String, Number], + default: '', + }, + id: { + type: String, + default: '', + }, + name: { + type: String, + default: '', + }, + label: { + type: String, + default: '', + }, + disabled: Boolean, + errorLabel: { + type: String, + default: '', + }, + readonly: Boolean, + startFocused: Boolean, + lengthHint: { + type: Number, + default: null, + }, + lengthHintLabel: { + type: String, + default: '', + }, + helpText: { + type: String, + default: '', + }, + hideClearButton: Boolean, + inputmode: { + type: String, + default: '', + }, + pattern: { + type: String, + default: '', + }, + }, + emits: ['update:modelValue', 'focus', 'blur', 'keypress', 'keyup', 'paste'], + computed: { + maxLengthToUse() { + const result = (this.maxLength || this.lengthHint); + return result; + }, + }, + methods: { + emitUpdateModelValueEvent() { + this.$emit('update:modelValue', this.inputValue); + }, + focus() { + if (!this.field) { return; } + this.field.focus(); + }, + blur() { + if (!this.field) { return; } + this.field.blur(); + }, + onFocus(event: Event) { + this.$emit('focus', event); + }, + onBlur(event: Event) { + this.$emit('blur', event); + }, + onKeypress(event: Event) { + this.$emit('keypress', event); + }, + onKeyup(event: Event) { + this.$emit('keyup', event); + }, + onPaste(event: Event) { + this.$emit('paste', event); + }, + }, + watch: { + inputValue() { + this.emitUpdateModelValueEvent(); + }, + modelValue() { + this.inputValue = this.modelValue; + }, + }, + setup(props) { + const field = ref(); + const inputValue = ref(props.modelValue); + return { + field, + inputValue, + }; + }, +}); + +export default TextField; diff --git a/src/components/TextField/TextField.vue b/src/components/TextField/TextField.vue index ffdf721..047f494 100644 --- a/src/components/TextField/TextField.vue +++ b/src/components/TextField/TextField.vue @@ -26,5 +26,5 @@ - + diff --git a/src/components/TextParagraph/TextParagraph.js b/src/components/TextParagraph/TextParagraph.js deleted file mode 100644 index b1a2d74..0000000 --- a/src/components/TextParagraph/TextParagraph.js +++ /dev/null @@ -1,69 +0,0 @@ -const availableWeights = [ - 'bold', - 'medium', - 'strong', -]; - -const availableSizes = [ - 'xsmall', - 'small', - 'medium', - 'large', - 'xl', - 'xxl', - 'xxxl', -]; - -const availableColors = [ - 'black-500', - 'black-700', - 'blue-500', - 'blue-700', - 'brown-500', - 'brown-700', - 'green-500', - 'green-700', - 'purple-500', - 'purple-700', - 'red-500', - 'red-700', - 'yellow-500', - 'yellow-700', -]; - -const props = { - size: { - type: String, - default: '', - validator(value) { return (!value || availableSizes.includes(value)); }, - }, - color: { - type: String, - default: '', - validator(value) { return (!value || availableColors.includes(value)); }, - }, - weight: { - type: String, - default: '', - validator(value) { return (!value || availableWeights.includes(value)); }, - }, - dataTestId: { - type: String, - default: 'text', - }, -}; - -const name = 'TextParagraph'; - -const TextParagraph = { - name, - props, -}; - -export { - availableWeights, - availableColors, - availableSizes, -}; - -export default TextParagraph; diff --git a/src/components/TextParagraph/TextParagraph.stories.js b/src/components/TextParagraph/TextParagraph.stories.ts similarity index 93% rename from src/components/TextParagraph/TextParagraph.stories.js rename to src/components/TextParagraph/TextParagraph.stories.ts index 913db0b..46f1f36 100644 --- a/src/components/TextParagraph/TextParagraph.stories.js +++ b/src/components/TextParagraph/TextParagraph.stories.ts @@ -1,3 +1,5 @@ +import type { Meta, StoryFn } from '@storybook/vue3'; + import TextParagraph from './TextParagraph.vue'; import { availableSizes, availableColors, availableWeights } from './TextParagraph'; import { capitalizeFirstLetter } from '../../lib/textHelper'; @@ -24,9 +26,9 @@ const TextParagraphStories = { size: { control: 'select', name: 'Size', options: [...availableSizes, ''] }, default: { control: { type: 'text' }, table: { type: { summary: null } } }, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { @@ -61,7 +63,7 @@ defaultExample.parameters = { }, }; -const examples = (args, { argTypes }) => ({ +const examples: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { @@ -118,7 +120,7 @@ examples.parameters = { }, }; -const weights = (args, { argTypes }) => ({ +const weights: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { @@ -165,7 +167,7 @@ weights.parameters = { }, }; -const sizes = (args, { argTypes }) => ({ +const sizes: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { @@ -212,7 +214,7 @@ sizes.parameters = { }, }; -const colors = (args, { argTypes }) => ({ +const colors: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/TextParagraph/TextParagraph.test.js b/src/components/TextParagraph/TextParagraph.test.ts similarity index 100% rename from src/components/TextParagraph/TextParagraph.test.js rename to src/components/TextParagraph/TextParagraph.test.ts diff --git a/src/components/TextParagraph/TextParagraph.ts b/src/components/TextParagraph/TextParagraph.ts new file mode 100644 index 0000000..cf38edc --- /dev/null +++ b/src/components/TextParagraph/TextParagraph.ts @@ -0,0 +1,67 @@ +import { defineComponent } from 'vue'; + +const availableWeights = [ + 'bold', + 'medium', + 'strong', +]; + +const availableSizes = [ + 'xsmall', + 'small', + 'medium', + 'large', + 'xl', + 'xxl', + 'xxxl', +]; + +const availableColors = [ + 'black-500', + 'black-700', + 'blue-500', + 'blue-700', + 'brown-500', + 'brown-700', + 'green-500', + 'green-700', + 'purple-500', + 'purple-700', + 'red-500', + 'red-700', + 'yellow-500', + 'yellow-700', +]; + +const TextParagraph = defineComponent({ + name: 'TextParagraph', + props: { + size: { + type: String, + default: '', + validator(value: string) { return (!value || availableSizes.includes(value)); }, + }, + color: { + type: String, + default: '', + validator(value: string) { return (!value || availableColors.includes(value)); }, + }, + weight: { + type: String, + default: '', + validator(value: string) { return (!value || availableWeights.includes(value)); }, + }, + dataTestId: { + type: String, + default: 'text', + }, + }, +}); + +export { + availableWeights, + availableColors, + availableSizes, +}; + +export default TextParagraph; diff --git a/src/components/TextParagraph/TextParagraph.vue b/src/components/TextParagraph/TextParagraph.vue index b726910..927a8c6 100644 --- a/src/components/TextParagraph/TextParagraph.vue +++ b/src/components/TextParagraph/TextParagraph.vue @@ -7,5 +7,5 @@

    - + diff --git a/src/components/Timeline/Timeline.js b/src/components/Timeline/Timeline.js deleted file mode 100644 index 5f90535..0000000 --- a/src/components/Timeline/Timeline.js +++ /dev/null @@ -1,15 +0,0 @@ -const props = { - dataTestId: { - type: String, - default: 'timeline', - }, -}; - -const name = 'Timeline'; - -const Timeline = { - name, - props, -}; - -export default Timeline; diff --git a/src/components/Timeline/Timeline.stories.js b/src/components/Timeline/Timeline.stories.ts similarity index 93% rename from src/components/Timeline/Timeline.stories.js rename to src/components/Timeline/Timeline.stories.ts index 2237a07..aa16d5a 100644 --- a/src/components/Timeline/Timeline.stories.js +++ b/src/components/Timeline/Timeline.stories.ts @@ -1,3 +1,5 @@ +import type { Meta, StoryFn } from '@storybook/vue3'; + import Timeline from './Timeline.vue'; import Screen from '../Screen/Screen.vue'; import ScrollWrapper from '../ScrollWrapper/ScrollWrapper.vue'; @@ -36,9 +38,9 @@ const TimelineStories = { }, }, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/Timeline/Timeline.test.js b/src/components/Timeline/Timeline.test.ts similarity index 100% rename from src/components/Timeline/Timeline.test.js rename to src/components/Timeline/Timeline.test.ts diff --git a/src/components/Timeline/Timeline.ts b/src/components/Timeline/Timeline.ts new file mode 100644 index 0000000..5a18014 --- /dev/null +++ b/src/components/Timeline/Timeline.ts @@ -0,0 +1,13 @@ +import { defineComponent } from 'vue'; + +const Timeline = defineComponent({ + name: 'Timeline', + props: { + dataTestId: { + type: String, + default: 'timeline', + }, + }, +}); + +export default Timeline; diff --git a/src/components/Timeline/Timeline.vue b/src/components/Timeline/Timeline.vue index bfefd93..f4684ca 100644 --- a/src/components/Timeline/Timeline.vue +++ b/src/components/Timeline/Timeline.vue @@ -9,5 +9,5 @@ - + diff --git a/src/components/ToggleSwitch/ToggleSwitch.js b/src/components/ToggleSwitch/ToggleSwitch.js deleted file mode 100644 index 9a016fe..0000000 --- a/src/components/ToggleSwitch/ToggleSwitch.js +++ /dev/null @@ -1,66 +0,0 @@ -import Button from '../Button/Button.vue'; - -const components = { - Button, -}; - -const props = { - dataTestId: { - type: String, - default: 'toggle', - }, - modelValue: Boolean, - disabled: Boolean, - buttons: Boolean, - trueButtonLabel: { - type: String, - default: '', - }, - falseButtonLabel: { - type: String, - default: '', - }, -}; - -const emits = ['update:modelValue']; - -const data = function () { - return { - isChecked: this.modelValue, - }; -}; - -const methods = { - emitUpdateModelValueEvent() { - this.$emit('update:modelValue', this.isChecked); - }, - uncheck() { - this.isChecked = false; - }, - check() { - this.isChecked = true; - }, -}; - -const watch = { - isChecked() { - this.emitUpdateModelValueEvent(); - }, - modelValue() { - this.isChecked = this.modelValue; - }, -}; - -const name = 'ToggleSwitch'; - -const ToggleSwitch = { - name, - components, - props, - emits, - data, - methods, - watch, -}; - -export default ToggleSwitch; diff --git a/src/components/ToggleSwitch/ToggleSwitch.stories.js b/src/components/ToggleSwitch/ToggleSwitch.stories.ts similarity index 92% rename from src/components/ToggleSwitch/ToggleSwitch.stories.js rename to src/components/ToggleSwitch/ToggleSwitch.stories.ts index 8811c41..b775473 100644 --- a/src/components/ToggleSwitch/ToggleSwitch.stories.js +++ b/src/components/ToggleSwitch/ToggleSwitch.stories.ts @@ -1,3 +1,5 @@ +import type { Meta, StoryFn } from '@storybook/vue3'; + import ToggleSwitch from './ToggleSwitch.vue'; import { createDeviceDecorator } from '../../lib/storybookHelpers'; @@ -21,9 +23,9 @@ const ToggleSwitchStories = { falseButtonLabel: { control: 'text', name: 'False button label' }, disabled: { control: 'boolean', name: 'Is disabled?' }, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/ToggleSwitch/ToggleSwitch.test.js b/src/components/ToggleSwitch/ToggleSwitch.test.ts similarity index 94% rename from src/components/ToggleSwitch/ToggleSwitch.test.js rename to src/components/ToggleSwitch/ToggleSwitch.test.ts index 3fe9c98..14c1910 100644 --- a/src/components/ToggleSwitch/ToggleSwitch.test.js +++ b/src/components/ToggleSwitch/ToggleSwitch.test.ts @@ -41,7 +41,7 @@ describe('UI/forms/ToggleSwitch', () => { await wrapper.vm.$nextTick(); wrapper.vm.$options.watch.isChecked.call(wrapper.vm); await wrapper.vm.$nextTick(); - const currentValueEmitted = wrapper.emitted('update:modelValue')[0][0]; + const [currentValueEmitted] = wrapper.emitted('update:modelValue')?.[0] as boolean[]; expect(currentValueEmitted).toBeTruthy(); }); @@ -50,7 +50,7 @@ describe('UI/forms/ToggleSwitch', () => { await wrapper.vm.$nextTick(); wrapper.vm.$options.watch.modelValue.call(wrapper.vm); await wrapper.vm.$nextTick(); - const takesGivenValue = wrapper.vm.$data.isChecked; + const takesGivenValue = wrapper.vm.isChecked; expect(takesGivenValue).toBeTruthy(); }); }); diff --git a/src/components/ToggleSwitch/ToggleSwitch.ts b/src/components/ToggleSwitch/ToggleSwitch.ts new file mode 100644 index 0000000..91b47bd --- /dev/null +++ b/src/components/ToggleSwitch/ToggleSwitch.ts @@ -0,0 +1,54 @@ +import { defineComponent } from 'vue'; + +import Button from '../Button/Button.vue'; + +const ToggleSwitch = defineComponent({ + name: 'ToggleSwitch', + components: { + Button, + }, + props: { + dataTestId: { + type: String, + default: 'toggle', + }, + modelValue: Boolean, + disabled: Boolean, + buttons: Boolean, + trueButtonLabel: { + type: String, + default: '', + }, + falseButtonLabel: { + type: String, + default: '', + }, + }, + emits: ['update:modelValue'], + data() { + return { + isChecked: this.modelValue, + }; + }, + methods: { + emitUpdateModelValueEvent() { + this.$emit('update:modelValue', this.isChecked); + }, + uncheck() { + this.isChecked = false; + }, + check() { + this.isChecked = true; + }, + }, + watch: { + isChecked() { + this.emitUpdateModelValueEvent(); + }, + modelValue() { + this.isChecked = this.modelValue; + }, + }, +}); + +export default ToggleSwitch; diff --git a/src/components/ToggleSwitch/ToggleSwitch.vue b/src/components/ToggleSwitch/ToggleSwitch.vue index 6cf6607..0587d7e 100644 --- a/src/components/ToggleSwitch/ToggleSwitch.vue +++ b/src/components/ToggleSwitch/ToggleSwitch.vue @@ -33,5 +33,5 @@ - + diff --git a/src/components/TopBar/TopBar.js b/src/components/TopBar/TopBar.js deleted file mode 100644 index 1f9034a..0000000 --- a/src/components/TopBar/TopBar.js +++ /dev/null @@ -1,26 +0,0 @@ -import Heading from '../Heading/Heading.vue'; - -const components = { - Heading, -}; - -const props = { - dataTestId: { - type: String, - default: 'topbar-header', - }, - title: { - type: String, - default: '', - }, -}; - -const name = 'TopBar'; - -const TopBar = { - name, - components, - props, -}; - -export default TopBar; diff --git a/src/components/TopBar/TopBar.stories.js b/src/components/TopBar/TopBar.stories.ts similarity index 85% rename from src/components/TopBar/TopBar.stories.js rename to src/components/TopBar/TopBar.stories.ts index 90e6a2b..13ec620 100644 --- a/src/components/TopBar/TopBar.stories.js +++ b/src/components/TopBar/TopBar.stories.ts @@ -1,3 +1,5 @@ +import type { Meta, StoryFn } from '@storybook/vue3'; + import TopBar from './TopBar.vue'; import { createDeviceDecorator } from '../../lib/storybookHelpers'; @@ -15,9 +17,9 @@ const TopBarStories = { ...deviceDecorator.argTypes, title: { control: 'text', name: 'Title' }, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/TopBar/TopBar.test.js b/src/components/TopBar/TopBar.test.ts similarity index 100% rename from src/components/TopBar/TopBar.test.js rename to src/components/TopBar/TopBar.test.ts diff --git a/src/components/TopBar/TopBar.ts b/src/components/TopBar/TopBar.ts new file mode 100644 index 0000000..3f57f68 --- /dev/null +++ b/src/components/TopBar/TopBar.ts @@ -0,0 +1,22 @@ +import { defineComponent } from 'vue'; + +import Heading from '../Heading/Heading.vue'; + +const TopBar = defineComponent({ + name: 'TopBar', + components: { + Heading, + }, + props: { + dataTestId: { + type: String, + default: 'topbar-header', + }, + title: { + type: String, + default: '', + }, + }, +}); + +export default TopBar; diff --git a/src/components/TopBar/TopBar.vue b/src/components/TopBar/TopBar.vue index 71d5ec4..22247ca 100644 --- a/src/components/TopBar/TopBar.vue +++ b/src/components/TopBar/TopBar.vue @@ -6,5 +6,5 @@ - + diff --git a/src/components/WrappedButton/WrappedButton.js b/src/components/WrappedButton/WrappedButton.js deleted file mode 100644 index f8fe5e1..0000000 --- a/src/components/WrappedButton/WrappedButton.js +++ /dev/null @@ -1,59 +0,0 @@ -import Button from '../Button/Button.vue'; - -const components = { - Button, -}; - -const availableTypes = [ - 'secondary', -]; - -const props = { - dataTestId: { - type: String, - default: 'button-wrapped', - }, - type: { - type: String, - default: '', - validator(value) { return (!value || availableTypes.includes(value)); }, - }, - href: String, - loading: Boolean, - loadingText: { - type: String, - default: 'Cargando...', - }, - disabled: Boolean, - debounce: { - type: Boolean, - default: false, - }, - debounceDelay: Number, - id: String, - name: String, -}; - -const emits = ['click']; - -const methods = { - onClick(event) { - this.$emit('click', event); - }, -}; - -const name = 'WrappedButton'; - -const WrappedButton = { - name, - components, - props, - emits, - methods, -}; - -export { - availableTypes, -}; - -export default WrappedButton; diff --git a/src/components/WrappedButton/WrappedButton.stories.js b/src/components/WrappedButton/WrappedButton.stories.ts similarity index 93% rename from src/components/WrappedButton/WrappedButton.stories.js rename to src/components/WrappedButton/WrappedButton.stories.ts index 1b1e713..d7bb7da 100644 --- a/src/components/WrappedButton/WrappedButton.stories.js +++ b/src/components/WrappedButton/WrappedButton.stories.ts @@ -1,4 +1,5 @@ import { action } from '@storybook/addon-actions'; +import type { Meta, StoryFn } from '@storybook/vue3'; import WrappedButton from './WrappedButton.vue'; import { availableTypes } from './WrappedButton'; @@ -33,9 +34,9 @@ const WrappedButtonStories = { href: { control: 'text', name: 'href' }, default: { control: { type: 'text' }, table: { type: { summary: null } } }, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/WrappedButton/WrappedButton.test.js b/src/components/WrappedButton/WrappedButton.test.ts similarity index 100% rename from src/components/WrappedButton/WrappedButton.test.js rename to src/components/WrappedButton/WrappedButton.test.ts diff --git a/src/components/WrappedButton/WrappedButton.ts b/src/components/WrappedButton/WrappedButton.ts new file mode 100644 index 0000000..a6a07ad --- /dev/null +++ b/src/components/WrappedButton/WrappedButton.ts @@ -0,0 +1,63 @@ +import { defineComponent } from 'vue'; + +import Button from '../Button/Button.vue'; + +const availableTypes = [ + 'secondary', +]; + +const WrappedButton = defineComponent({ + name: 'WrappedButton', + components: { + Button, + }, + props: { + dataTestId: { + type: String, + default: 'button-wrapped', + }, + type: { + type: String, + default: '', + validator(value: string) { return (!value || availableTypes.includes(value)); }, + }, + href: { + type: String, + default: '', + }, + loading: Boolean, + loadingText: { + type: String, + default: 'Cargando...', + }, + disabled: Boolean, + debounce: { + type: Boolean, + default: false, + }, + debounceDelay: { + type: Number, + default: 0, + }, + id: { + type: String, + default: '', + }, + name: { + type: String, + default: '', + }, + }, + emits: ['click'], + methods: { + onClick(event: Event) { + this.$emit('click', event); + }, + }, +}); + +export { + availableTypes, +}; + +export default WrappedButton; diff --git a/src/components/WrappedButton/WrappedButton.vue b/src/components/WrappedButton/WrappedButton.vue index b451b0b..ca36552 100644 --- a/src/components/WrappedButton/WrappedButton.vue +++ b/src/components/WrappedButton/WrappedButton.vue @@ -16,5 +16,5 @@ - + diff --git a/src/components/Wrapper/Wrapper.js b/src/components/Wrapper/Wrapper.js deleted file mode 100644 index 6946b17..0000000 --- a/src/components/Wrapper/Wrapper.js +++ /dev/null @@ -1,16 +0,0 @@ -const props = { - dataTestId: { - type: String, - default: '', - }, - modal: Boolean, -}; - -const name = 'Wrapper'; - -const Wrapper = { - name, - props, -}; - -export default Wrapper; diff --git a/src/components/Wrapper/Wrapper.stories.js b/src/components/Wrapper/Wrapper.stories.ts similarity index 88% rename from src/components/Wrapper/Wrapper.stories.js rename to src/components/Wrapper/Wrapper.stories.ts index 081e903..00accae 100644 --- a/src/components/Wrapper/Wrapper.stories.js +++ b/src/components/Wrapper/Wrapper.stories.ts @@ -1,3 +1,5 @@ +import type { Meta, StoryFn } from '@storybook/vue3'; + import Wrapper from './Wrapper.vue'; import { createDeviceDecorator } from '../../lib/storybookHelpers'; import RenderString from '../../lib/renderString'; @@ -18,9 +20,9 @@ const WrapperStories = { modal: { control: 'boolean', name: 'Modal style?' }, default: { control: { type: 'text' }, table: { type: { summary: null } } }, }, -}; +} as Meta; -const defaultExample = (args, { argTypes }) => ({ +const defaultExample: StoryFn = (args, { argTypes }) => ({ props: Object.keys(argTypes), setup() { return { ...args }; }, components: { diff --git a/src/components/Wrapper/Wrapper.ts b/src/components/Wrapper/Wrapper.ts new file mode 100644 index 0000000..f8e1244 --- /dev/null +++ b/src/components/Wrapper/Wrapper.ts @@ -0,0 +1,14 @@ +import { defineComponent } from 'vue'; + +const Wrapper = defineComponent({ + name: 'Wrapper', + props: { + dataTestId: { + type: String, + default: '', + }, + modal: Boolean, + }, +}); + +export default Wrapper; diff --git a/src/components/Wrapper/Wrapper.vue b/src/components/Wrapper/Wrapper.vue index 2fcc671..5c4df46 100644 --- a/src/components/Wrapper/Wrapper.vue +++ b/src/components/Wrapper/Wrapper.vue @@ -4,5 +4,5 @@ - + diff --git a/src/lib/bankAccountNumberValidator.js b/src/lib/bankAccountNumberValidator.ts similarity index 69% rename from src/lib/bankAccountNumberValidator.js rename to src/lib/bankAccountNumberValidator.ts index 98b8037..1e4bf8f 100644 --- a/src/lib/bankAccountNumberValidator.js +++ b/src/lib/bankAccountNumberValidator.ts @@ -1,5 +1,7 @@ import { allSpacesRegexp } from './regexHelper'; +type BankAccountNumberTemplate = 'MX'; + const digitPattern = 'X'; const bankAccountNumberTemplateLookup = { @@ -7,10 +9,10 @@ const bankAccountNumberTemplateLookup = { }; const validations = { - MX: (account) => { + MX: (account: string) => { const accountValue = account.split(''); - const controlDigitIn = Number.parseInt(accountValue.pop(), 10); - const reduceValue = (result, digit, index) => ((result + (digit * [3, 7, 1][index % 3])) % 10); + const controlDigitIn = Number.parseInt(accountValue.pop() || '', 10); + const reduceValue = (result: number, digit: string, index: number) => ((result + (Number.parseInt(digit, 10) * ([3, 7, 1][index % 3] || 3))) % 10); const sum = accountValue.reduce(reduceValue, 0); const controlDigitOut = (10 - (sum % 10)) % 10; const result = (controlDigitIn === controlDigitOut); @@ -18,7 +20,7 @@ const validations = { }, }; -const validateBankAccountNumber = ({ accountNumber, countryCode: countryCodeToValidate }) => { +const validateBankAccountNumber = ({ accountNumber, countryCode: countryCodeToValidate }: { accountNumber: string, countryCode: 'MX' }) => { const templateLength = bankAccountNumberTemplateLookup[countryCodeToValidate].replace(allSpacesRegexp, '').length; const customValidation = validations[countryCodeToValidate](accountNumber); const isMaxLength = (accountNumber.length === templateLength); @@ -27,9 +29,9 @@ const validateBankAccountNumber = ({ accountNumber, countryCode: countryCodeToVa return result; }; -const bankAccountNumberFormatter = ({ accountNumber, template }) => { +const bankAccountNumberFormatter = ({ accountNumber, template }: { accountNumber: string, template: string }) => { let accountDigitIndex = 0; - const generateAccountValue = (result, patternCharacter) => { + const generateAccountValue = (result: string, patternCharacter: string) => { if (!accountNumber[accountDigitIndex]) { return result; } const nextCharacter = (patternCharacter === digitPattern) ? accountNumber[accountDigitIndex] : patternCharacter; if (patternCharacter === digitPattern) { accountDigitIndex++; } @@ -41,7 +43,9 @@ const bankAccountNumberFormatter = ({ accountNumber, template }) => { .reduce(generateAccountValue, ''); return result; }; - +export type { + BankAccountNumberTemplate, +}; export { bankAccountNumberTemplateLookup, bankAccountNumberFormatter, diff --git a/src/lib/copyToClipboard.test.js.disabled b/src/lib/copyToClipboard.test.ts.disabled similarity index 100% rename from src/lib/copyToClipboard.test.js.disabled rename to src/lib/copyToClipboard.test.ts.disabled diff --git a/src/lib/copyToClipboard.js b/src/lib/copyToClipboard.ts similarity index 81% rename from src/lib/copyToClipboard.js rename to src/lib/copyToClipboard.ts index 19ff3fd..f0a6f22 100644 --- a/src/lib/copyToClipboard.js +++ b/src/lib/copyToClipboard.ts @@ -1,4 +1,4 @@ -const getHiddenInputElement = (textToCopy) => { +const getHiddenInputElement = (textToCopy: string) => { const result = document.createElement('input'); result.type = 'text'; result.style.height = '0'; @@ -7,7 +7,7 @@ const getHiddenInputElement = (textToCopy) => { return result; }; -const copyTextToClipboard = (textToCopy) => { +const copyTextToClipboard = (textToCopy: string) => { if (typeof window === 'undefined') { return; } const hiddenInputElement = getHiddenInputElement(textToCopy); document.body.appendChild(hiddenInputElement); diff --git a/src/lib/dateHelper.js b/src/lib/dateHelper.ts similarity index 85% rename from src/lib/dateHelper.js rename to src/lib/dateHelper.ts index dd8f5c0..dc21ac3 100644 --- a/src/lib/dateHelper.js +++ b/src/lib/dateHelper.ts @@ -1,6 +1,6 @@ import { nonDigitSlashRegexp, nonDigitRegexp } from './regexHelper'; -const getFormattedStringFromDate = (date) => { +const getFormattedStringFromDate = (date: Date) => { const datePart = `0${date.getUTCDate()}`.slice(-2); const monthPart = `0${date.getUTCMonth() + 1}`.slice(-2); const yearPart = date.getUTCFullYear(); @@ -8,9 +8,9 @@ const getFormattedStringFromDate = (date) => { return result; }; -const getDateFromDateString = (date) => `${date.slice(6)}-${date.slice(3, 5)}-${date.slice(0, 2)}`; +const getDateFromDateString = (date: string) => `${date.slice(6)}-${date.slice(3, 5)}-${date.slice(0, 2)}`; -const checkDateValue = (value, max) => { +const checkDateValue = (value: string, max: number) => { if (!((value.charAt(0) !== '0') || (value === '00'))) { return value; } let parsedValue = parseInt(value, 10); if (Number.isNaN(parsedValue) || (parsedValue <= 0)) { parsedValue = 1; } @@ -19,7 +19,7 @@ const checkDateValue = (value, max) => { return result; }; -const autoformatDate = (input) => { +const autoformatDate = (input: string) => { const sanitizedInput = (nonDigitSlashRegexp.test(input)) ? input.substr(0, input.length - 2) : input; const values = sanitizedInput.split('/').map((value) => value.replace(nonDigitRegexp, '')); if (values[0]) { values[0] = checkDateValue(values[0], 31); } @@ -29,7 +29,7 @@ const autoformatDate = (input) => { return result; }; -const isDateTextInputValid = (input) => { +const isDateTextInputValid = (input: string) => { const day = Number(input.slice(0, 2)); const month = (Number(input.slice(3, 5)) - 1); const year = Number(input.slice(6)); diff --git a/src/lib/regexHelper.js b/src/lib/regexHelper.ts similarity index 77% rename from src/lib/regexHelper.js rename to src/lib/regexHelper.ts index 94fdd46..4f7a81f 100644 --- a/src/lib/regexHelper.js +++ b/src/lib/regexHelper.ts @@ -1,4 +1,4 @@ -const nonDigitsRegexp = /(\D\s+)/g; +const nonDigitsRegexp = /(\D+)/g; const allSpacesRegexp = / /g; const nonDigitSlashRegexp = /\D\/$/; const nonDigitRegexp = /\D/g; @@ -6,7 +6,7 @@ const validDateRegexp = /^(0?[1-9]|[12][0-9]|3[01])[/](0?[1-9]|1[012])[/]\d{4}$/ const onlyDigitsRegexp = /\d+/; const singleDigitRegexp = /^\d+$/; -const escapeRegExp = (string) => string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +const escapeRegExp = (string: string) => string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); export { nonDigitsRegexp, diff --git a/src/lib/renderString.js b/src/lib/renderString.ts similarity index 76% rename from src/lib/renderString.js rename to src/lib/renderString.ts index 4015bc3..7501e2e 100644 --- a/src/lib/renderString.js +++ b/src/lib/renderString.ts @@ -1,9 +1,9 @@ -import { h } from 'vue'; +import { defineComponent, h } from 'vue'; import * as AllIcons from '@lana/b2c-mapp-ui-assets'; import * as AllComponents from '../library'; -const RenderString = { +const RenderString = defineComponent({ props: { string: { required: true, @@ -13,7 +13,10 @@ const RenderString = { type: Boolean, default: false, }, - customProps: Object, + customProps: { + type: Object, + default: () => ({}), + }, }, render() { const render = { @@ -26,6 +29,6 @@ const RenderString = { }; return h(render, { ...this.customProps }); }, -}; +}); export default RenderString; diff --git a/src/lib/sleepHelper.js b/src/lib/sleepHelper.js deleted file mode 100644 index a903b91..0000000 --- a/src/lib/sleepHelper.js +++ /dev/null @@ -1,5 +0,0 @@ -const sleep = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)); - -export { - sleep, -}; diff --git a/src/lib/sleepHelper.ts b/src/lib/sleepHelper.ts new file mode 100644 index 0000000..12d003e --- /dev/null +++ b/src/lib/sleepHelper.ts @@ -0,0 +1,5 @@ +const sleep = (milliseconds: number) => new Promise((resolve) => setTimeout(resolve, milliseconds)); + +export { + sleep, +}; diff --git a/src/lib/storybookHelpers.js b/src/lib/storybookHelpers.ts similarity index 81% rename from src/lib/storybookHelpers.js rename to src/lib/storybookHelpers.ts index 362339c..c6003de 100644 --- a/src/lib/storybookHelpers.js +++ b/src/lib/storybookHelpers.ts @@ -1,10 +1,12 @@ +import type { StoryFn } from '@storybook/vue3'; + import StorybookMobileDeviceSimulator from '../components/StorybookMobileDeviceSimulator/StorybookMobileDeviceSimulator.vue'; import { getAvailableDevices } from '../components/StorybookMobileDeviceSimulator/StorybookMobileDeviceSimulator'; const availableDevices = getAvailableDevices(); -const createDeviceDecorator = (title, subtitle = '') => { - const deviceDecorator = (story, { argTypes, args }) => ({ +const createDeviceDecorator = (title: string, subtitle = '') => { + const deviceDecorator: StoryFn = (story, { argTypes, args }) => ({ props: Object.keys(argTypes), components: { StorybookMobileDeviceSimulator, story }, setup() { return { ...args }; }, @@ -27,8 +29,8 @@ const createDeviceDecorator = (title, subtitle = '') => { return deviceDecorator; }; -const createOptionalDeviceDecorator = (title) => { - const deviceDecorator = (story, { argTypes, args }) => ({ +const createOptionalDeviceDecorator = (title: string) => { + const deviceDecorator: StoryFn = (story, { argTypes, args }) => ({ props: Object.keys(argTypes), components: { StorybookMobileDeviceSimulator, story }, setup() { return { ...args }; }, @@ -51,8 +53,8 @@ const createOptionalDeviceDecorator = (title) => { return deviceDecorator; }; -const createScreenDecorator = (title) => { - const deviceDecorator = (story, { argTypes, args }) => ({ +const createScreenDecorator = (title: string) => { + const deviceDecorator: StoryFn = (story, { argTypes, args }) => ({ props: Object.keys(argTypes), components: { story }, setup() { return { ...args }; }, diff --git a/src/lib/testUtils.js b/src/lib/testUtils.ts similarity index 72% rename from src/lib/testUtils.js rename to src/lib/testUtils.ts index 4835e59..cf68bfe 100644 --- a/src/lib/testUtils.js +++ b/src/lib/testUtils.ts @@ -1,10 +1,12 @@ -const silenceDeprecationErrorsAndInnerComponentWarnings = (jest) => { +type Jest = typeof jest; + +const silenceDeprecationErrorsAndInnerComponentWarnings = (jest: Jest) => { console.error = jest.fn(); // eslint-disable-line no-console console.warn = jest.fn(); // eslint-disable-line no-console console.dir = jest.fn(); // eslint-disable-line no-console }; -const silenceInnerComponentWarnings = (jest) => { +const silenceInnerComponentWarnings = (jest: Jest) => { console.warn = jest.fn(); // eslint-disable-line no-console console.dir = jest.fn(); // eslint-disable-line no-console }; diff --git a/src/lib/textHelper.js b/src/lib/textHelper.js deleted file mode 100644 index 4c76848..0000000 --- a/src/lib/textHelper.js +++ /dev/null @@ -1,5 +0,0 @@ -const capitalizeFirstLetter = (originalText) => `${originalText.charAt(0).toUpperCase()}${originalText.slice(1)}`; - -export { - capitalizeFirstLetter, -}; diff --git a/src/lib/textHelper.ts b/src/lib/textHelper.ts new file mode 100644 index 0000000..6bb42bf --- /dev/null +++ b/src/lib/textHelper.ts @@ -0,0 +1,5 @@ +const capitalizeFirstLetter = (originalText: string) => `${originalText.charAt(0).toUpperCase()}${originalText.slice(1)}`; + +export { + capitalizeFirstLetter, +}; diff --git a/src/library.js b/src/library.ts similarity index 100% rename from src/library.js rename to src/library.ts diff --git a/src/main.js b/src/main.js deleted file mode 100644 index 24868da..0000000 --- a/src/main.js +++ /dev/null @@ -1,7 +0,0 @@ -import { createApp } from 'vue'; - -import App from './App.vue'; - -const app = createApp(App).mount('#app'); -app.config.productionTip = false; -app.config.devtools = false; diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..03c9f5e --- /dev/null +++ b/src/main.ts @@ -0,0 +1,7 @@ +import { createApp } from 'vue'; + +import App from './App.vue'; + +const app = createApp(App); +app.mount('#app'); +app.config.performance = import.meta.env.DEV; diff --git a/src/shims-vue.d.ts b/src/shims-vue.d.ts new file mode 100644 index 0000000..917e64f --- /dev/null +++ b/src/shims-vue.d.ts @@ -0,0 +1,6 @@ +declare module '*.vue' { + import type { defineComponent } from 'vue'; + + const component: ReturnType; + export default component; +} diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/tsconfig.dts.json b/tsconfig.dts.json new file mode 100644 index 0000000..7c034f0 --- /dev/null +++ b/tsconfig.dts.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": false, + "declaration": true, + "declarationDir": "dist", + "emitDeclarationOnly": true + }, + "include": [ + "src/library.ts" + ] +} diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json new file mode 100644 index 0000000..0cfc643 --- /dev/null +++ b/tsconfig.eslint.json @@ -0,0 +1,19 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "types": ["@types/node"], + "noEmit": true, + "allowJs": true + }, + "include": [ + "src/**/*.ts", + "src/**/*.d.ts", + "src/**/*.tsx", + "src/**/**/*.vue", + "src/**/*.vue", + ".eslintrc.js", + "setupTest.js", + "jest.config.js", + ], + "exclude": [] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..9d87c81 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,39 @@ +{ + "compilerOptions": { + "target": "esnext", + "module": "esnext", + "strict": true, + "jsx": "preserve", + "importHelpers": true, + "moduleResolution": "node", + "isolatedModules": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowJs": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "sourceMap": true, + "resolveJsonModule": true, + "types": ["vite/client", "node"], + "noImplicitAny": true, + "noEmit": true, + "lib": ["dom", "dom.iterable", "esnext"], + }, + "include": [ + "src/**/*.ts", + "src/**/*.d.ts", + "src/**/*.vue", + ], + "exclude": [ + "node_modules", + "src/**/*.test.ts", + "src/**/*.stories.ts", + "src/lib/testUtils.ts", + "src/lib/storybookHelpers.ts" + ] +} diff --git a/vite.config.js b/vite.config.ts similarity index 94% rename from vite.config.js rename to vite.config.ts index e194f36..de8e292 100644 --- a/vite.config.js +++ b/vite.config.ts @@ -11,7 +11,7 @@ export default defineConfig({ ], build: { lib: { - entry: resolve(__dirname, 'src/library.js'), + entry: resolve(__dirname, 'src/library.ts'), name: 'b2cMappUi', fileName: (format) => `b2c-mapp-ui.${format}.js`, },