diff --git a/package.json b/package.json index c8814f14f..d9ae4c106 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "@angular/core": "^13.1.0", "@angular/forms": "^13.1.0", "@angular/platform-browser": "^13.1.0", - "@ptsecurity/mosaic-icons": "6.1.1", + "@ptsecurity/mosaic-icons": "6.2.0", "core-js": "^3.6.5", "rxjs": "^6.6.7", "tslib": "^2.3.1", diff --git a/packages/docs/src/styles/default-theme/_variables.scss b/packages/docs/src/styles/default-theme/_variables.scss index fa39fa357..e558f258a 100644 --- a/packages/docs/src/styles/default-theme/_variables.scss +++ b/packages/docs/src/styles/default-theme/_variables.scss @@ -10,6 +10,8 @@ $light-color-scheme-warning-default: #a26e0c; $light-color-scheme-foreground-text: #19252f; $light-color-scheme-foreground-text-less-contrast: #6d7a86; $light-color-scheme-foreground-text-disabled: #8c99a5; +$light-color-scheme-foreground-text-error: #db3c55; +$light-color-scheme-foreground-text-success: #016b37; $light-color-scheme-foreground-divider: #d7dee4; $light-color-scheme-foreground-border: #bdc7d1; $light-color-scheme-foreground-icon: #8c99a5; @@ -32,6 +34,8 @@ $dark-color-scheme-warning-default: #7e5406; $dark-color-scheme-foreground-text: #f2f5f9; $dark-color-scheme-foreground-text-less-contrast: #8c99a5; $dark-color-scheme-foreground-text-disabled: #6d7a86; +$dark-color-scheme-foreground-text-error: #ea5868; +$dark-color-scheme-foreground-text-success: #319d5c; $dark-color-scheme-foreground-divider: #333f4a; $dark-color-scheme-foreground-border: #515e69; $dark-color-scheme-foreground-icon: #8c99a5; @@ -532,6 +536,21 @@ $form-field-size-button-width: 32px; $form-field-font-default: body; $form-field-hint-size-margin-top: 4px; $form-field-hint-font-default: caption; +$form-field-password-hint-light-color-scheme-text-color: #19252f; +$form-field-password-hint-light-color-scheme-icon-color: #19252f; +$form-field-password-hint-light-color-scheme-states-invalid-icon-color: #db3c55; +$form-field-password-hint-light-color-scheme-states-invalid-text-color: #19252f; +$form-field-password-hint-light-color-scheme-states-valid-text-color: #016b37; +$form-field-password-hint-light-color-scheme-states-valid-icon-color: #016b37; +$form-field-password-hint-dark-color-scheme-text-color: #f2f5f9; +$form-field-password-hint-dark-color-scheme-icon-color: #f2f5f9; +$form-field-password-hint-dark-color-scheme-states-invalid-icon-color: #ea5868; +$form-field-password-hint-dark-color-scheme-states-invalid-text-color: #f2f5f9; +$form-field-password-hint-dark-color-scheme-states-valid-text-color: #319d5c; +$form-field-password-hint-dark-color-scheme-states-valid-icon-color: #319d5c; +$form-field-password-hint-size-margin-top: 8px; +$form-field-password-hint-size-icon-margin: 4px; +$form-field-password-hint-font-default: caption; $forms-light-color-scheme-label: #6d7a86; $forms-light-color-scheme-legend: #19252f; $forms-dark-color-scheme-label: #8c99a5; diff --git a/packages/docs/src/styles/default-theme/css-tokens.css b/packages/docs/src/styles/default-theme/css-tokens.css index 4dbdcd33f..d69787c0e 100644 --- a/packages/docs/src/styles/default-theme/css-tokens.css +++ b/packages/docs/src/styles/default-theme/css-tokens.css @@ -76,6 +76,8 @@ --mc-form-field-size-border-radius: 3px; --mc-form-field-size-button-width: 32px; --mc-form-field-hint-size-margin-top: 4px; + --mc-form-field-password-hint-size-margin-top: 8px; + --mc-form-field-password-hint-size-icon-margin: 4px; --mc-forms-size-horizontal-row-margin-bottom: 20px; --mc-forms-size-horizontal-label-padding-top: 6px; --mc-forms-size-horizontal-label-padding-bottom: 0; diff --git a/packages/docs/src/styles/default-theme/tokens.ts b/packages/docs/src/styles/default-theme/tokens.ts index f449d4e77..dbd6e896f 100644 --- a/packages/docs/src/styles/default-theme/tokens.ts +++ b/packages/docs/src/styles/default-theme/tokens.ts @@ -17,6 +17,8 @@ export const LightColorSchemeWarningPalette = {"40":{"value":"#fff4dd","filePath export const LightColorSchemeForegroundText = "#19252f"; export const LightColorSchemeForegroundTextLessContrast = "#6d7a86"; export const LightColorSchemeForegroundTextDisabled = "#8c99a5"; +export const LightColorSchemeForegroundTextError = "#db3c55"; +export const LightColorSchemeForegroundTextSuccess = "#016b37"; export const LightColorSchemeForegroundDivider = "#d7dee4"; export const LightColorSchemeForegroundBorder = "#bdc7d1"; export const LightColorSchemeForegroundIcon = "#8c99a5"; @@ -45,6 +47,8 @@ export const DarkColorSchemeWarningPalette = {"40":{"value":"#fff4dd","filePath" export const DarkColorSchemeForegroundText = "#f2f5f9"; export const DarkColorSchemeForegroundTextLessContrast = "#8c99a5"; export const DarkColorSchemeForegroundTextDisabled = "#6d7a86"; +export const DarkColorSchemeForegroundTextError = "#ea5868"; +export const DarkColorSchemeForegroundTextSuccess = "#319d5c"; export const DarkColorSchemeForegroundDivider = "#333f4a"; export const DarkColorSchemeForegroundBorder = "#515e69"; export const DarkColorSchemeForegroundIcon = "#8c99a5"; @@ -654,6 +658,21 @@ export const FormFieldSizeButtonWidth = "32px"; export const FormFieldFontDefault = "body"; export const FormFieldHintSizeMarginTop = "4px"; export const FormFieldHintFontDefault = "caption"; +export const FormFieldPasswordHintLightColorSchemeTextColor = "#19252f"; +export const FormFieldPasswordHintLightColorSchemeIconColor = "#19252f"; +export const FormFieldPasswordHintLightColorSchemeStatesInvalidIconColor = "#db3c55"; +export const FormFieldPasswordHintLightColorSchemeStatesInvalidTextColor = "#19252f"; +export const FormFieldPasswordHintLightColorSchemeStatesValidTextColor = "#016b37"; +export const FormFieldPasswordHintLightColorSchemeStatesValidIconColor = "#016b37"; +export const FormFieldPasswordHintDarkColorSchemeTextColor = "#f2f5f9"; +export const FormFieldPasswordHintDarkColorSchemeIconColor = "#f2f5f9"; +export const FormFieldPasswordHintDarkColorSchemeStatesInvalidIconColor = "#ea5868"; +export const FormFieldPasswordHintDarkColorSchemeStatesInvalidTextColor = "#f2f5f9"; +export const FormFieldPasswordHintDarkColorSchemeStatesValidTextColor = "#319d5c"; +export const FormFieldPasswordHintDarkColorSchemeStatesValidIconColor = "#319d5c"; +export const FormFieldPasswordHintSizeMarginTop = "8px"; +export const FormFieldPasswordHintSizeIconMargin = "4px"; +export const FormFieldPasswordHintFontDefault = "caption"; export const FormsLightColorSchemeLabel = "#6d7a86"; export const FormsLightColorSchemeLegend = "#19252f"; export const FormsDarkColorSchemeLabel = "#8c99a5"; diff --git a/packages/docs/src/styles/green-theme/_variables.scss b/packages/docs/src/styles/green-theme/_variables.scss index d12766526..ef6bca32b 100644 --- a/packages/docs/src/styles/green-theme/_variables.scss +++ b/packages/docs/src/styles/green-theme/_variables.scss @@ -10,6 +10,8 @@ $light-color-scheme-warning-default: #a26e0c; $light-color-scheme-foreground-text: #19252f; $light-color-scheme-foreground-text-less-contrast: #6d7a86; $light-color-scheme-foreground-text-disabled: #8c99a5; +$light-color-scheme-foreground-text-error: #db3c55; +$light-color-scheme-foreground-text-success: #016b37; $light-color-scheme-foreground-divider: #d7dee4; $light-color-scheme-foreground-border: #bdc7d1; $light-color-scheme-foreground-icon: #8c99a5; @@ -32,6 +34,8 @@ $dark-color-scheme-warning-default: #7e5406; $dark-color-scheme-foreground-text: #f2f5f9; $dark-color-scheme-foreground-text-less-contrast: #8c99a5; $dark-color-scheme-foreground-text-disabled: #6d7a86; +$dark-color-scheme-foreground-text-error: #ea5868; +$dark-color-scheme-foreground-text-success: #319d5c; $dark-color-scheme-foreground-divider: #333f4a; $dark-color-scheme-foreground-border: #515e69; $dark-color-scheme-foreground-icon: #8c99a5; @@ -532,6 +536,21 @@ $form-field-size-button-width: 32px; $form-field-font-default: body; $form-field-hint-size-margin-top: 4px; $form-field-hint-font-default: caption; +$form-field-password-hint-light-color-scheme-text-color: #19252f; +$form-field-password-hint-light-color-scheme-icon-color: #19252f; +$form-field-password-hint-light-color-scheme-states-invalid-icon-color: #db3c55; +$form-field-password-hint-light-color-scheme-states-invalid-text-color: #19252f; +$form-field-password-hint-light-color-scheme-states-valid-text-color: #016b37; +$form-field-password-hint-light-color-scheme-states-valid-icon-color: #016b37; +$form-field-password-hint-dark-color-scheme-text-color: #f2f5f9; +$form-field-password-hint-dark-color-scheme-icon-color: #f2f5f9; +$form-field-password-hint-dark-color-scheme-states-invalid-icon-color: #ea5868; +$form-field-password-hint-dark-color-scheme-states-invalid-text-color: #f2f5f9; +$form-field-password-hint-dark-color-scheme-states-valid-text-color: #319d5c; +$form-field-password-hint-dark-color-scheme-states-valid-icon-color: #319d5c; +$form-field-password-hint-size-margin-top: 8px; +$form-field-password-hint-size-icon-margin: 4px; +$form-field-password-hint-font-default: caption; $forms-light-color-scheme-label: #6d7a86; $forms-light-color-scheme-legend: #19252f; $forms-dark-color-scheme-label: #8c99a5; diff --git a/packages/docs/src/styles/green-theme/css-tokens.css b/packages/docs/src/styles/green-theme/css-tokens.css index 4dbdcd33f..d69787c0e 100644 --- a/packages/docs/src/styles/green-theme/css-tokens.css +++ b/packages/docs/src/styles/green-theme/css-tokens.css @@ -76,6 +76,8 @@ --mc-form-field-size-border-radius: 3px; --mc-form-field-size-button-width: 32px; --mc-form-field-hint-size-margin-top: 4px; + --mc-form-field-password-hint-size-margin-top: 8px; + --mc-form-field-password-hint-size-icon-margin: 4px; --mc-forms-size-horizontal-row-margin-bottom: 20px; --mc-forms-size-horizontal-label-padding-top: 6px; --mc-forms-size-horizontal-label-padding-bottom: 0; diff --git a/packages/docs/src/styles/green-theme/tokens.ts b/packages/docs/src/styles/green-theme/tokens.ts index b27da477b..9126b5139 100644 --- a/packages/docs/src/styles/green-theme/tokens.ts +++ b/packages/docs/src/styles/green-theme/tokens.ts @@ -17,6 +17,8 @@ export const LightColorSchemeWarningPalette = {"40":{"value":"#fff4dd","filePath export const LightColorSchemeForegroundText = "#19252f"; export const LightColorSchemeForegroundTextLessContrast = "#6d7a86"; export const LightColorSchemeForegroundTextDisabled = "#8c99a5"; +export const LightColorSchemeForegroundTextError = "#db3c55"; +export const LightColorSchemeForegroundTextSuccess = "#016b37"; export const LightColorSchemeForegroundDivider = "#d7dee4"; export const LightColorSchemeForegroundBorder = "#bdc7d1"; export const LightColorSchemeForegroundIcon = "#8c99a5"; @@ -45,6 +47,8 @@ export const DarkColorSchemeWarningPalette = {"40":{"value":"#fff4dd","filePath" export const DarkColorSchemeForegroundText = "#f2f5f9"; export const DarkColorSchemeForegroundTextLessContrast = "#8c99a5"; export const DarkColorSchemeForegroundTextDisabled = "#6d7a86"; +export const DarkColorSchemeForegroundTextError = "#ea5868"; +export const DarkColorSchemeForegroundTextSuccess = "#319d5c"; export const DarkColorSchemeForegroundDivider = "#333f4a"; export const DarkColorSchemeForegroundBorder = "#515e69"; export const DarkColorSchemeForegroundIcon = "#8c99a5"; @@ -654,6 +658,21 @@ export const FormFieldSizeButtonWidth = "32px"; export const FormFieldFontDefault = "body"; export const FormFieldHintSizeMarginTop = "4px"; export const FormFieldHintFontDefault = "caption"; +export const FormFieldPasswordHintLightColorSchemeTextColor = "#19252f"; +export const FormFieldPasswordHintLightColorSchemeIconColor = "#19252f"; +export const FormFieldPasswordHintLightColorSchemeStatesInvalidIconColor = "#db3c55"; +export const FormFieldPasswordHintLightColorSchemeStatesInvalidTextColor = "#19252f"; +export const FormFieldPasswordHintLightColorSchemeStatesValidTextColor = "#016b37"; +export const FormFieldPasswordHintLightColorSchemeStatesValidIconColor = "#016b37"; +export const FormFieldPasswordHintDarkColorSchemeTextColor = "#f2f5f9"; +export const FormFieldPasswordHintDarkColorSchemeIconColor = "#f2f5f9"; +export const FormFieldPasswordHintDarkColorSchemeStatesInvalidIconColor = "#ea5868"; +export const FormFieldPasswordHintDarkColorSchemeStatesInvalidTextColor = "#f2f5f9"; +export const FormFieldPasswordHintDarkColorSchemeStatesValidTextColor = "#319d5c"; +export const FormFieldPasswordHintDarkColorSchemeStatesValidIconColor = "#319d5c"; +export const FormFieldPasswordHintSizeMarginTop = "8px"; +export const FormFieldPasswordHintSizeIconMargin = "4px"; +export const FormFieldPasswordHintFontDefault = "caption"; export const FormsLightColorSchemeLabel = "#6d7a86"; export const FormsLightColorSchemeLegend = "#19252f"; export const FormsDarkColorSchemeLabel = "#8c99a5"; diff --git a/packages/docs/src/styles/red-theme/_variables.scss b/packages/docs/src/styles/red-theme/_variables.scss index eb4dbc512..cdd603f80 100644 --- a/packages/docs/src/styles/red-theme/_variables.scss +++ b/packages/docs/src/styles/red-theme/_variables.scss @@ -10,6 +10,8 @@ $light-color-scheme-warning-default: #a26e0c; $light-color-scheme-foreground-text: #19252f; $light-color-scheme-foreground-text-less-contrast: #6d7a86; $light-color-scheme-foreground-text-disabled: #8c99a5; +$light-color-scheme-foreground-text-error: #db3c55; +$light-color-scheme-foreground-text-success: #016b37; $light-color-scheme-foreground-divider: #d7dee4; $light-color-scheme-foreground-border: #bdc7d1; $light-color-scheme-foreground-icon: #8c99a5; @@ -32,6 +34,8 @@ $dark-color-scheme-warning-default: #7e5406; $dark-color-scheme-foreground-text: #f2f5f9; $dark-color-scheme-foreground-text-less-contrast: #8c99a5; $dark-color-scheme-foreground-text-disabled: #6d7a86; +$dark-color-scheme-foreground-text-error: #ea5868; +$dark-color-scheme-foreground-text-success: #319d5c; $dark-color-scheme-foreground-divider: #333f4a; $dark-color-scheme-foreground-border: #515e69; $dark-color-scheme-foreground-icon: #8c99a5; @@ -532,6 +536,21 @@ $form-field-size-button-width: 32px; $form-field-font-default: body; $form-field-hint-size-margin-top: 4px; $form-field-hint-font-default: caption; +$form-field-password-hint-light-color-scheme-text-color: #19252f; +$form-field-password-hint-light-color-scheme-icon-color: #19252f; +$form-field-password-hint-light-color-scheme-states-invalid-icon-color: #db3c55; +$form-field-password-hint-light-color-scheme-states-invalid-text-color: #19252f; +$form-field-password-hint-light-color-scheme-states-valid-text-color: #016b37; +$form-field-password-hint-light-color-scheme-states-valid-icon-color: #016b37; +$form-field-password-hint-dark-color-scheme-text-color: #f2f5f9; +$form-field-password-hint-dark-color-scheme-icon-color: #f2f5f9; +$form-field-password-hint-dark-color-scheme-states-invalid-icon-color: #ea5868; +$form-field-password-hint-dark-color-scheme-states-invalid-text-color: #f2f5f9; +$form-field-password-hint-dark-color-scheme-states-valid-text-color: #319d5c; +$form-field-password-hint-dark-color-scheme-states-valid-icon-color: #319d5c; +$form-field-password-hint-size-margin-top: 8px; +$form-field-password-hint-size-icon-margin: 4px; +$form-field-password-hint-font-default: caption; $forms-light-color-scheme-label: #6d7a86; $forms-light-color-scheme-legend: #19252f; $forms-dark-color-scheme-label: #8c99a5; diff --git a/packages/docs/src/styles/red-theme/css-tokens.css b/packages/docs/src/styles/red-theme/css-tokens.css index 4dbdcd33f..d69787c0e 100644 --- a/packages/docs/src/styles/red-theme/css-tokens.css +++ b/packages/docs/src/styles/red-theme/css-tokens.css @@ -76,6 +76,8 @@ --mc-form-field-size-border-radius: 3px; --mc-form-field-size-button-width: 32px; --mc-form-field-hint-size-margin-top: 4px; + --mc-form-field-password-hint-size-margin-top: 8px; + --mc-form-field-password-hint-size-icon-margin: 4px; --mc-forms-size-horizontal-row-margin-bottom: 20px; --mc-forms-size-horizontal-label-padding-top: 6px; --mc-forms-size-horizontal-label-padding-bottom: 0; diff --git a/packages/docs/src/styles/red-theme/tokens.ts b/packages/docs/src/styles/red-theme/tokens.ts index b98f275dd..1089adc2a 100644 --- a/packages/docs/src/styles/red-theme/tokens.ts +++ b/packages/docs/src/styles/red-theme/tokens.ts @@ -17,6 +17,8 @@ export const LightColorSchemeWarningPalette = {"40":{"value":"#fff4dd","filePath export const LightColorSchemeForegroundText = "#19252f"; export const LightColorSchemeForegroundTextLessContrast = "#6d7a86"; export const LightColorSchemeForegroundTextDisabled = "#8c99a5"; +export const LightColorSchemeForegroundTextError = "#db3c55"; +export const LightColorSchemeForegroundTextSuccess = "#016b37"; export const LightColorSchemeForegroundDivider = "#d7dee4"; export const LightColorSchemeForegroundBorder = "#bdc7d1"; export const LightColorSchemeForegroundIcon = "#8c99a5"; @@ -45,6 +47,8 @@ export const DarkColorSchemeWarningPalette = {"40":{"value":"#fff4dd","filePath" export const DarkColorSchemeForegroundText = "#f2f5f9"; export const DarkColorSchemeForegroundTextLessContrast = "#8c99a5"; export const DarkColorSchemeForegroundTextDisabled = "#6d7a86"; +export const DarkColorSchemeForegroundTextError = "#ea5868"; +export const DarkColorSchemeForegroundTextSuccess = "#319d5c"; export const DarkColorSchemeForegroundDivider = "#333f4a"; export const DarkColorSchemeForegroundBorder = "#515e69"; export const DarkColorSchemeForegroundIcon = "#8c99a5"; @@ -654,6 +658,21 @@ export const FormFieldSizeButtonWidth = "32px"; export const FormFieldFontDefault = "body"; export const FormFieldHintSizeMarginTop = "4px"; export const FormFieldHintFontDefault = "caption"; +export const FormFieldPasswordHintLightColorSchemeTextColor = "#19252f"; +export const FormFieldPasswordHintLightColorSchemeIconColor = "#19252f"; +export const FormFieldPasswordHintLightColorSchemeStatesInvalidIconColor = "#db3c55"; +export const FormFieldPasswordHintLightColorSchemeStatesInvalidTextColor = "#19252f"; +export const FormFieldPasswordHintLightColorSchemeStatesValidTextColor = "#016b37"; +export const FormFieldPasswordHintLightColorSchemeStatesValidIconColor = "#016b37"; +export const FormFieldPasswordHintDarkColorSchemeTextColor = "#f2f5f9"; +export const FormFieldPasswordHintDarkColorSchemeIconColor = "#f2f5f9"; +export const FormFieldPasswordHintDarkColorSchemeStatesInvalidIconColor = "#ea5868"; +export const FormFieldPasswordHintDarkColorSchemeStatesInvalidTextColor = "#f2f5f9"; +export const FormFieldPasswordHintDarkColorSchemeStatesValidTextColor = "#319d5c"; +export const FormFieldPasswordHintDarkColorSchemeStatesValidIconColor = "#319d5c"; +export const FormFieldPasswordHintSizeMarginTop = "8px"; +export const FormFieldPasswordHintSizeIconMargin = "4px"; +export const FormFieldPasswordHintFontDefault = "caption"; export const FormsLightColorSchemeLabel = "#6d7a86"; export const FormsLightColorSchemeLegend = "#19252f"; export const FormsDarkColorSchemeLabel = "#8c99a5"; diff --git a/packages/docs/src/styles/yellow-theme/_variables.scss b/packages/docs/src/styles/yellow-theme/_variables.scss index 87503d886..9a9f1650f 100644 --- a/packages/docs/src/styles/yellow-theme/_variables.scss +++ b/packages/docs/src/styles/yellow-theme/_variables.scss @@ -10,6 +10,8 @@ $light-color-scheme-warning-default: #a26e0c; $light-color-scheme-foreground-text: #19252f; $light-color-scheme-foreground-text-less-contrast: #6d7a86; $light-color-scheme-foreground-text-disabled: #8c99a5; +$light-color-scheme-foreground-text-error: #db3c55; +$light-color-scheme-foreground-text-success: #016b37; $light-color-scheme-foreground-divider: #d7dee4; $light-color-scheme-foreground-border: #bdc7d1; $light-color-scheme-foreground-icon: #8c99a5; @@ -32,6 +34,8 @@ $dark-color-scheme-warning-default: #7e5406; $dark-color-scheme-foreground-text: #f2f5f9; $dark-color-scheme-foreground-text-less-contrast: #8c99a5; $dark-color-scheme-foreground-text-disabled: #6d7a86; +$dark-color-scheme-foreground-text-error: #ea5868; +$dark-color-scheme-foreground-text-success: #319d5c; $dark-color-scheme-foreground-divider: #333f4a; $dark-color-scheme-foreground-border: #515e69; $dark-color-scheme-foreground-icon: #8c99a5; @@ -532,6 +536,21 @@ $form-field-size-button-width: 32px; $form-field-font-default: body; $form-field-hint-size-margin-top: 4px; $form-field-hint-font-default: caption; +$form-field-password-hint-light-color-scheme-text-color: #19252f; +$form-field-password-hint-light-color-scheme-icon-color: #19252f; +$form-field-password-hint-light-color-scheme-states-invalid-icon-color: #db3c55; +$form-field-password-hint-light-color-scheme-states-invalid-text-color: #19252f; +$form-field-password-hint-light-color-scheme-states-valid-text-color: #016b37; +$form-field-password-hint-light-color-scheme-states-valid-icon-color: #016b37; +$form-field-password-hint-dark-color-scheme-text-color: #f2f5f9; +$form-field-password-hint-dark-color-scheme-icon-color: #f2f5f9; +$form-field-password-hint-dark-color-scheme-states-invalid-icon-color: #ea5868; +$form-field-password-hint-dark-color-scheme-states-invalid-text-color: #f2f5f9; +$form-field-password-hint-dark-color-scheme-states-valid-text-color: #319d5c; +$form-field-password-hint-dark-color-scheme-states-valid-icon-color: #319d5c; +$form-field-password-hint-size-margin-top: 8px; +$form-field-password-hint-size-icon-margin: 4px; +$form-field-password-hint-font-default: caption; $forms-light-color-scheme-label: #6d7a86; $forms-light-color-scheme-legend: #19252f; $forms-dark-color-scheme-label: #8c99a5; diff --git a/packages/docs/src/styles/yellow-theme/css-tokens.css b/packages/docs/src/styles/yellow-theme/css-tokens.css index 4dbdcd33f..d69787c0e 100644 --- a/packages/docs/src/styles/yellow-theme/css-tokens.css +++ b/packages/docs/src/styles/yellow-theme/css-tokens.css @@ -76,6 +76,8 @@ --mc-form-field-size-border-radius: 3px; --mc-form-field-size-button-width: 32px; --mc-form-field-hint-size-margin-top: 4px; + --mc-form-field-password-hint-size-margin-top: 8px; + --mc-form-field-password-hint-size-icon-margin: 4px; --mc-forms-size-horizontal-row-margin-bottom: 20px; --mc-forms-size-horizontal-label-padding-top: 6px; --mc-forms-size-horizontal-label-padding-bottom: 0; diff --git a/packages/docs/src/styles/yellow-theme/tokens.ts b/packages/docs/src/styles/yellow-theme/tokens.ts index 8142dacac..e6f6117da 100644 --- a/packages/docs/src/styles/yellow-theme/tokens.ts +++ b/packages/docs/src/styles/yellow-theme/tokens.ts @@ -17,6 +17,8 @@ export const LightColorSchemeWarningPalette = {"40":{"value":"#fff4dd","filePath export const LightColorSchemeForegroundText = "#19252f"; export const LightColorSchemeForegroundTextLessContrast = "#6d7a86"; export const LightColorSchemeForegroundTextDisabled = "#8c99a5"; +export const LightColorSchemeForegroundTextError = "#db3c55"; +export const LightColorSchemeForegroundTextSuccess = "#016b37"; export const LightColorSchemeForegroundDivider = "#d7dee4"; export const LightColorSchemeForegroundBorder = "#bdc7d1"; export const LightColorSchemeForegroundIcon = "#8c99a5"; @@ -45,6 +47,8 @@ export const DarkColorSchemeWarningPalette = {"40":{"value":"#fff4dd","filePath" export const DarkColorSchemeForegroundText = "#f2f5f9"; export const DarkColorSchemeForegroundTextLessContrast = "#8c99a5"; export const DarkColorSchemeForegroundTextDisabled = "#6d7a86"; +export const DarkColorSchemeForegroundTextError = "#ea5868"; +export const DarkColorSchemeForegroundTextSuccess = "#319d5c"; export const DarkColorSchemeForegroundDivider = "#333f4a"; export const DarkColorSchemeForegroundBorder = "#515e69"; export const DarkColorSchemeForegroundIcon = "#8c99a5"; @@ -654,6 +658,21 @@ export const FormFieldSizeButtonWidth = "32px"; export const FormFieldFontDefault = "body"; export const FormFieldHintSizeMarginTop = "4px"; export const FormFieldHintFontDefault = "caption"; +export const FormFieldPasswordHintLightColorSchemeTextColor = "#19252f"; +export const FormFieldPasswordHintLightColorSchemeIconColor = "#19252f"; +export const FormFieldPasswordHintLightColorSchemeStatesInvalidIconColor = "#db3c55"; +export const FormFieldPasswordHintLightColorSchemeStatesInvalidTextColor = "#19252f"; +export const FormFieldPasswordHintLightColorSchemeStatesValidTextColor = "#016b37"; +export const FormFieldPasswordHintLightColorSchemeStatesValidIconColor = "#016b37"; +export const FormFieldPasswordHintDarkColorSchemeTextColor = "#f2f5f9"; +export const FormFieldPasswordHintDarkColorSchemeIconColor = "#f2f5f9"; +export const FormFieldPasswordHintDarkColorSchemeStatesInvalidIconColor = "#ea5868"; +export const FormFieldPasswordHintDarkColorSchemeStatesInvalidTextColor = "#f2f5f9"; +export const FormFieldPasswordHintDarkColorSchemeStatesValidTextColor = "#319d5c"; +export const FormFieldPasswordHintDarkColorSchemeStatesValidIconColor = "#319d5c"; +export const FormFieldPasswordHintSizeMarginTop = "8px"; +export const FormFieldPasswordHintSizeIconMargin = "4px"; +export const FormFieldPasswordHintFontDefault = "caption"; export const FormsLightColorSchemeLabel = "#6d7a86"; export const FormsLightColorSchemeLegend = "#19252f"; export const FormsDarkColorSchemeLabel = "#8c99a5"; diff --git a/packages/mosaic-dev/input/module.ts b/packages/mosaic-dev/input/module.ts index 708cd530d..71a042c71 100644 --- a/packages/mosaic-dev/input/module.ts +++ b/packages/mosaic-dev/input/module.ts @@ -1,9 +1,17 @@ +/* tslint:disable:no-magic-numbers */ import { Component, NgModule, ViewEncapsulation } from '@angular/core'; -import { FormsModule } from '@angular/forms'; +import { + FormControl, + FormsModule, + ReactiveFormsModule, + Validators +} from '@angular/forms'; import { BrowserModule } from '@angular/platform-browser'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { McButtonModule } from '@ptsecurity/mosaic/button'; +import { McToolTipModule } from '@ptsecurity/mosaic/tooltip'; -import { McFormFieldModule } from '../../mosaic/form-field'; +import { McFormFieldModule, PasswordRules } from '../../mosaic/form-field'; import { McIconModule } from '../../mosaic/icon'; import { McInputModule } from '../../mosaic/input/'; @@ -11,10 +19,13 @@ import { McInputModule } from '../../mosaic/input/'; @Component({ selector: 'app', templateUrl: './template.html', - styleUrls: ['./styles.scss'], + styleUrls: ['../main.scss', './styles.scss'], encapsulation: ViewEncapsulation.None }) export class InputDemoComponent { + passwordRules = PasswordRules; + password = new FormControl('456', Validators.required); + value: string = ''; numberValue: number | null = null; min = -5; @@ -22,19 +33,19 @@ export class InputDemoComponent { @NgModule({ - declarations: [ - InputDemoComponent - ], + declarations: [InputDemoComponent], imports: [ + BrowserAnimationsModule, BrowserModule, + FormsModule, + ReactiveFormsModule, + + McFormFieldModule, McButtonModule, McInputModule, - McFormFieldModule, - FormsModule, + McToolTipModule, McIconModule ], - bootstrap: [ - InputDemoComponent - ] + bootstrap: [InputDemoComponent] }) export class DemoModule {} diff --git a/packages/mosaic-dev/input/styles.scss b/packages/mosaic-dev/input/styles.scss index 7a5759d7c..4a853db16 100644 --- a/packages/mosaic-dev/input/styles.scss +++ b/packages/mosaic-dev/input/styles.scss @@ -1,17 +1,6 @@ -$mc-icons-font-path: "~@ptsecurity/mosaic-icons/dist/fonts"; -@import '~@ptsecurity/mosaic-icons/dist/styles/mc-icons'; +@import '../../mosaic/core/visual/prebuilt/default-visual'; -//@import '../../mosaic/core/theming/prebuilt/default-theme'; -@import '../../mosaic/core/theming/prebuilt/dark-theme'; - -@include mc-core(); .container .mc-form-field { width: 200px; } - -header { - $config: mc-typography-config(); - - @include mc-typography-level-to-styles($config, caption); -} diff --git a/packages/mosaic-dev/input/template.html b/packages/mosaic-dev/input/template.html index 51fe38473..bde2d428f 100644 --- a/packages/mosaic-dev/input/template.html +++ b/packages/mosaic-dev/input/template.html @@ -4,10 +4,49 @@

- - - + + + + + От 8 до 15 символов + + Заглавная латинская буква + + Строчная латинская буква + + Цифра + + Только латинские буквы, цифры, пробелы и спецсимволы + + +

+ +
With clear-button:
+ + + + + + + +

+ +
With prefix:
+ + + + + + + +

+ +
With suffix:
+ + + + +
diff --git a/packages/mosaic-examples/mosaic/input/index.ts b/packages/mosaic-examples/mosaic/input/index.ts index 4c024cd90..d20103ed0 100644 --- a/packages/mosaic-examples/mosaic/input/index.ts +++ b/packages/mosaic-examples/mosaic/input/index.ts @@ -6,16 +6,19 @@ import { McInputModule } from '@ptsecurity/mosaic/input'; import { InputNumberOverviewExample } from './input-number-overview/input-number-overview-example'; import { InputOverviewExample } from './input-overview/input-overview-example'; +import { InputPasswordOverviewExample } from './input-password-overview/input-password-overview-example'; export { InputOverviewExample, - InputNumberOverviewExample + InputNumberOverviewExample, + InputPasswordOverviewExample }; const EXAMPLES = [ InputOverviewExample, - InputNumberOverviewExample + InputNumberOverviewExample, + InputPasswordOverviewExample ]; @NgModule({ diff --git a/packages/mosaic-examples/mosaic/input/input-password-overview/input-password-overview-example.css b/packages/mosaic-examples/mosaic/input/input-password-overview/input-password-overview-example.css new file mode 100644 index 000000000..7cfb5c15a --- /dev/null +++ b/packages/mosaic-examples/mosaic/input/input-password-overview/input-password-overview-example.css @@ -0,0 +1,3 @@ +.mc-password-item-example { + width: 250px; +} diff --git a/packages/mosaic-examples/mosaic/input/input-password-overview/input-password-overview-example.html b/packages/mosaic-examples/mosaic/input/input-password-overview/input-password-overview-example.html new file mode 100644 index 000000000..52c8077f5 --- /dev/null +++ b/packages/mosaic-examples/mosaic/input/input-password-overview/input-password-overview-example.html @@ -0,0 +1,15 @@ + + + + + + От 8 до 15 символов + + Заглавная латинская буква + + Строчная латинская буква + + Цифра + + Только латинские буквы, цифры, пробелы и спецсимволы + diff --git a/packages/mosaic-examples/mosaic/input/input-password-overview/input-password-overview-example.ts b/packages/mosaic-examples/mosaic/input/input-password-overview/input-password-overview-example.ts new file mode 100644 index 000000000..b93f6eb4d --- /dev/null +++ b/packages/mosaic-examples/mosaic/input/input-password-overview/input-password-overview-example.ts @@ -0,0 +1,17 @@ +import { Component } from '@angular/core'; +import { PasswordRules } from '@ptsecurity/mosaic/form-field'; + + +/** + * @title Password Input + */ +@Component({ + selector: 'input-password-overview-example', + templateUrl: 'input-password-overview-example.html', + styleUrls: ['input-password-overview-example.css'] +}) +export class InputPasswordOverviewExample { + passwordRules = PasswordRules; + + value = ''; +} diff --git a/packages/mosaic/autocomplete/autocomplete.spec.ts b/packages/mosaic/autocomplete/autocomplete.spec.ts index 4e7664d99..20290ef01 100644 --- a/packages/mosaic/autocomplete/autocomplete.spec.ts +++ b/packages/mosaic/autocomplete/autocomplete.spec.ts @@ -210,11 +210,13 @@ describe('McAutocomplete', () => { it('should close the panel when an option is clicked', fakeAsync(() => { dispatchFakeEvent(input, 'focusin'); fixture.detectChanges(); + flush(); zone.simulateZoneExit(); const option = overlayContainerElement.querySelector('mc-option') as HTMLElement; option.click(); fixture.detectChanges(); + flush(); expect(fixture.componentInstance.trigger.panelOpen) .toBe(false, `Expected clicking an option to set the panel state to closed.`); @@ -1028,6 +1030,7 @@ describe('McAutocomplete', () => { dispatchKeyboardEvent(document.body, 'keydown', ESCAPE); fixture.detectChanges(); + flush(); expect(document.activeElement).toBe(input, 'Expected input to continue to be focused.'); expect(trigger.panelOpen).toBe(false, 'Expected panel to be closed.'); @@ -1036,6 +1039,7 @@ describe('McAutocomplete', () => { it('should prevent the default action when pressing escape', fakeAsync(() => { const escapeEvent = dispatchKeyboardEvent(input, 'keydown', ESCAPE); fixture.detectChanges(); + flush(); expect(escapeEvent.defaultPrevented).toBe(true); })); @@ -1054,6 +1058,7 @@ describe('McAutocomplete', () => { dispatchEvent(document.body, upArrowEvent); fixture.detectChanges(); + flush(); expect(document.activeElement).toBe(input, 'Expected input to continue to be focused.'); expect(trigger.panelOpen).toBe(false, 'Expected panel to be closed.'); @@ -1071,6 +1076,7 @@ describe('McAutocomplete', () => { dispatchKeyboardEvent(input, 'keydown', TAB); fixture.detectChanges(); + flush(); expect(overlayContainerElement.querySelector('.mc-autocomplete-panel')) .toBeFalsy('Expected panel to be removed.'); @@ -1081,7 +1087,7 @@ describe('McAutocomplete', () => { trigger.openPanel(); fixture.detectChanges(); - tick(); + flush(); expect(trigger.panelOpen).toBe(true, 'Expected panel to be open.'); expect(!!trigger.activeOption).toBe(false, 'Expected no active option.'); @@ -1098,7 +1104,7 @@ describe('McAutocomplete', () => { expect(!!trigger.activeOption).toBe(true, 'Expected to find an active option.'); dispatchKeyboardEvent(document.body, 'keydown', ESCAPE); - tick(); + flush(); expect(!!trigger.activeOption).toBe(false, 'Expected no active options.'); })); diff --git a/packages/mosaic/core/theming/_components-theming.scss b/packages/mosaic/core/theming/_components-theming.scss index 97de2f0b8..c12040868 100644 --- a/packages/mosaic/core/theming/_components-theming.scss +++ b/packages/mosaic/core/theming/_components-theming.scss @@ -199,6 +199,17 @@ state-invalid-background: $form-field-light-color-scheme-states-invalid-background ); + $form-field-password-hint: ( + text-color: $form-field-password-hint-light-color-scheme-text-color, + icon-color: $form-field-password-hint-light-color-scheme-text-color, + + state-invalid-text-color: $form-field-password-hint-light-color-scheme-states-invalid-text-color, + state-invalid-icon-color: $form-field-password-hint-light-color-scheme-states-invalid-icon-color, + + state-valid-text-color: $form-field-password-hint-light-color-scheme-states-valid-text-color, + state-valid-icon-color: $form-field-password-hint-light-color-scheme-states-valid-icon-color + ); + $forms: ( label: $forms-light-color-scheme-label, legend: $forms-light-color-scheme-legend, @@ -371,6 +382,7 @@ checkbox: $checkbox, datepicker: $datepicker, form-field: $form-field, + form-field-password-hint: $form-field-password-hint, forms: $forms, link: $link, modal: $modal, @@ -585,6 +597,17 @@ state-invalid-background: $form-field-dark-color-scheme-states-invalid-background ); + $form-field-password-hint: ( + text-color: $form-field-password-hint-dark-color-scheme-text-color, + icon-color: $form-field-password-hint-dark-color-scheme-text-color, + + state-invalid-text-color: $form-field-password-hint-dark-color-scheme-states-invalid-text-color, + state-invalid-icon-color: $form-field-password-hint-dark-color-scheme-states-invalid-icon-color, + + state-valid-text-color: $form-field-password-hint-dark-color-scheme-states-valid-text-color, + state-valid-icon-color: $form-field-password-hint-dark-color-scheme-states-valid-icon-color + ); + $forms: ( label: $forms-dark-color-scheme-label, legend: $forms-dark-color-scheme-legend, @@ -757,6 +780,7 @@ checkbox: $checkbox, datepicker: $datepicker, form-field: $form-field, + form-field-password-hint: $form-field-password-hint, forms: $forms, link: $link, modal: $modal, diff --git a/packages/mosaic/design-tokens/legacy-2017/_variables.scss b/packages/mosaic/design-tokens/legacy-2017/_variables.scss index f470c925e..2e0ffb2a5 100644 --- a/packages/mosaic/design-tokens/legacy-2017/_variables.scss +++ b/packages/mosaic/design-tokens/legacy-2017/_variables.scss @@ -10,6 +10,8 @@ $light-color-scheme-warning-default: #F0D49B; $light-color-scheme-foreground-text: #4D4D4D; $light-color-scheme-foreground-text-less-contrast: #999999; $light-color-scheme-foreground-text-disabled: #B3B3B3; +$light-color-scheme-foreground-text-error: #E04D36; +$light-color-scheme-foreground-text-success: #449327; $light-color-scheme-foreground-divider: #E6E6E6; $light-color-scheme-foreground-border: #B3B3B3; $light-color-scheme-foreground-icon: #999999; @@ -31,6 +33,8 @@ $dark-color-scheme-warning-default: #DFA739; $dark-color-scheme-foreground-text: #F0F0F0; $dark-color-scheme-foreground-text-less-contrast: #999999; $dark-color-scheme-foreground-text-disabled: #999999; +$dark-color-scheme-foreground-text-error: #E76E5C; +$dark-color-scheme-foreground-text-success: #6FBA53; $dark-color-scheme-foreground-divider: #666666; $dark-color-scheme-foreground-border: #808080; $dark-color-scheme-foreground-icon: #999999; @@ -474,6 +478,21 @@ $form-field-size-button-width: 32px; $form-field-font-default: body; $form-field-hint-size-margin-top: 4px; $form-field-hint-font-default: caption; +$form-field-password-hint-light-color-scheme-text-color: #4D4D4D; +$form-field-password-hint-light-color-scheme-icon-color: #4D4D4D; +$form-field-password-hint-light-color-scheme-states-invalid-icon-color: #E04D36; +$form-field-password-hint-light-color-scheme-states-invalid-text-color: #4D4D4D; +$form-field-password-hint-light-color-scheme-states-valid-text-color: #449327; +$form-field-password-hint-light-color-scheme-states-valid-icon-color: #449327; +$form-field-password-hint-dark-color-scheme-text-color: #F0F0F0; +$form-field-password-hint-dark-color-scheme-icon-color: #F0F0F0; +$form-field-password-hint-dark-color-scheme-states-invalid-icon-color: #E76E5C; +$form-field-password-hint-dark-color-scheme-states-invalid-text-color: #F0F0F0; +$form-field-password-hint-dark-color-scheme-states-valid-text-color: #6FBA53; +$form-field-password-hint-dark-color-scheme-states-valid-icon-color: #6FBA53; +$form-field-password-hint-size-margin-top: 8px; +$form-field-password-hint-size-icon-margin: 4px; +$form-field-password-hint-font-default: caption; $forms-light-color-scheme-label: #999999; $forms-light-color-scheme-legend: #4D4D4D; $forms-dark-color-scheme-label: #999999; diff --git a/packages/mosaic/design-tokens/legacy-2017/css-tokens.css b/packages/mosaic/design-tokens/legacy-2017/css-tokens.css index 4dbdcd33f..d69787c0e 100644 --- a/packages/mosaic/design-tokens/legacy-2017/css-tokens.css +++ b/packages/mosaic/design-tokens/legacy-2017/css-tokens.css @@ -76,6 +76,8 @@ --mc-form-field-size-border-radius: 3px; --mc-form-field-size-button-width: 32px; --mc-form-field-hint-size-margin-top: 4px; + --mc-form-field-password-hint-size-margin-top: 8px; + --mc-form-field-password-hint-size-icon-margin: 4px; --mc-forms-size-horizontal-row-margin-bottom: 20px; --mc-forms-size-horizontal-label-padding-top: 6px; --mc-forms-size-horizontal-label-padding-bottom: 0; diff --git a/packages/mosaic/design-tokens/legacy-2017/tokens.ts b/packages/mosaic/design-tokens/legacy-2017/tokens.ts index f760c095c..f08f323dc 100644 --- a/packages/mosaic/design-tokens/legacy-2017/tokens.ts +++ b/packages/mosaic/design-tokens/legacy-2017/tokens.ts @@ -17,6 +17,8 @@ export const LightColorSchemeWarningPalette = {"40":{"value":"#FDFAF3","filePath export const LightColorSchemeForegroundText = "#4D4D4D"; export const LightColorSchemeForegroundTextLessContrast = "#999999"; export const LightColorSchemeForegroundTextDisabled = "#B3B3B3"; +export const LightColorSchemeForegroundTextError = "#E04D36"; +export const LightColorSchemeForegroundTextSuccess = "#449327"; export const LightColorSchemeForegroundDivider = "#E6E6E6"; export const LightColorSchemeForegroundBorder = "#B3B3B3"; export const LightColorSchemeForegroundIcon = "#999999"; @@ -44,6 +46,8 @@ export const DarkColorSchemeWarningPalette = {"40":{"value":"#FDFAF3","filePath" export const DarkColorSchemeForegroundText = "#F0F0F0"; export const DarkColorSchemeForegroundTextLessContrast = "#999999"; export const DarkColorSchemeForegroundTextDisabled = "#999999"; +export const DarkColorSchemeForegroundTextError = "#E76E5C"; +export const DarkColorSchemeForegroundTextSuccess = "#6FBA53"; export const DarkColorSchemeForegroundDivider = "#666666"; export const DarkColorSchemeForegroundBorder = "#808080"; export const DarkColorSchemeForegroundIcon = "#999999"; @@ -594,6 +598,21 @@ export const FormFieldSizeButtonWidth = "32px"; export const FormFieldFontDefault = "body"; export const FormFieldHintSizeMarginTop = "4px"; export const FormFieldHintFontDefault = "caption"; +export const FormFieldPasswordHintLightColorSchemeTextColor = "#4D4D4D"; +export const FormFieldPasswordHintLightColorSchemeIconColor = "#4D4D4D"; +export const FormFieldPasswordHintLightColorSchemeStatesInvalidIconColor = "#E04D36"; +export const FormFieldPasswordHintLightColorSchemeStatesInvalidTextColor = "#4D4D4D"; +export const FormFieldPasswordHintLightColorSchemeStatesValidTextColor = "#449327"; +export const FormFieldPasswordHintLightColorSchemeStatesValidIconColor = "#449327"; +export const FormFieldPasswordHintDarkColorSchemeTextColor = "#F0F0F0"; +export const FormFieldPasswordHintDarkColorSchemeIconColor = "#F0F0F0"; +export const FormFieldPasswordHintDarkColorSchemeStatesInvalidIconColor = "#E76E5C"; +export const FormFieldPasswordHintDarkColorSchemeStatesInvalidTextColor = "#F0F0F0"; +export const FormFieldPasswordHintDarkColorSchemeStatesValidTextColor = "#6FBA53"; +export const FormFieldPasswordHintDarkColorSchemeStatesValidIconColor = "#6FBA53"; +export const FormFieldPasswordHintSizeMarginTop = "8px"; +export const FormFieldPasswordHintSizeIconMargin = "4px"; +export const FormFieldPasswordHintFontDefault = "caption"; export const FormsLightColorSchemeLabel = "#999999"; export const FormsLightColorSchemeLegend = "#4D4D4D"; export const FormsDarkColorSchemeLabel = "#999999"; diff --git a/packages/mosaic/design-tokens/legacy-2017/tokens/components/form-field.json5 b/packages/mosaic/design-tokens/legacy-2017/tokens/components/form-field.json5 index 66294e008..d73bf3fe8 100644 --- a/packages/mosaic/design-tokens/legacy-2017/tokens/components/form-field.json5 +++ b/packages/mosaic/design-tokens/legacy-2017/tokens/components/form-field.json5 @@ -48,5 +48,44 @@ font: { default: { value: 'caption' } } + }, + 'form-field-password-hint': { + 'light-color-scheme': { + 'text-color': { value: '{light-color-scheme.foreground.text.value}' }, + 'icon-color': { value: '{light-color-scheme.foreground.text.value}' }, + + states: { + invalid: { + 'icon-color': { value: '{light-color-scheme.foreground.text-error.value}' }, + 'text-color': { value: '{light-color-scheme.foreground.text.value}' } + }, + valid: { + 'text-color': { value: '{light-color-scheme.foreground.text-success.value}' }, + 'icon-color': { value: '{light-color-scheme.foreground.text-success.value}' } + } + } + }, + 'dark-color-scheme': { + 'text-color': { value: '{dark-color-scheme.foreground.text.value}' }, + 'icon-color': { value: '{dark-color-scheme.foreground.text.value}' }, + + states: { + invalid: { + 'icon-color': { value: '{dark-color-scheme.foreground.text-error.value}' }, + 'text-color': { value: '{dark-color-scheme.foreground.text.value}' } + }, + valid: { + 'text-color': { value: '{dark-color-scheme.foreground.text-success.value}' }, + 'icon-color': { value: '{dark-color-scheme.foreground.text-success.value}' } + } + } + }, + size: { + 'margin-top': { value: '8px' }, + 'icon-margin': { value: '4px' } + }, + font: { + default: { value: 'caption' } + } } } diff --git a/packages/mosaic/design-tokens/legacy-2017/tokens/properties/colors.json5 b/packages/mosaic/design-tokens/legacy-2017/tokens/properties/colors.json5 index a0c8597c9..ac610d613 100644 --- a/packages/mosaic/design-tokens/legacy-2017/tokens/properties/colors.json5 +++ b/packages/mosaic/design-tokens/legacy-2017/tokens/properties/colors.json5 @@ -34,6 +34,8 @@ text: { value: '{palette.grey.700.value}' }, 'text-less-contrast': { value: '{palette.grey.400.value}'}, 'text-disabled': { value: '{palette.grey.300.value}' }, + 'text-error': { value: '{light-color-scheme.error.palette.value.500.value}' }, + 'text-success': { value: '{light-color-scheme.success.palette.value.560.value}' }, divider: { value: '{palette.grey.100.value}' }, border: { value: '{palette.grey.300.value}' }, icon: { value: '{palette.grey.400.value}' } @@ -88,6 +90,8 @@ text: { value: '{palette.grey.60.value}' }, 'text-less-contrast': { value: '{palette.grey.400.value}'}, 'text-disabled': { value: '{palette.grey.400.value}' }, + 'text-error': { value: '{dark-color-scheme.error.palette.value.400.value}' }, + 'text-success': { value: '{dark-color-scheme.success.palette.value.400.value}' }, divider: { value: '{palette.grey.600.value}' }, border: { value: '{palette.grey.500.value}' }, icon: { value: '{palette.grey.400.value}' } diff --git a/packages/mosaic/design-tokens/pt-2022/_variables.scss b/packages/mosaic/design-tokens/pt-2022/_variables.scss index fa39fa357..e558f258a 100644 --- a/packages/mosaic/design-tokens/pt-2022/_variables.scss +++ b/packages/mosaic/design-tokens/pt-2022/_variables.scss @@ -10,6 +10,8 @@ $light-color-scheme-warning-default: #a26e0c; $light-color-scheme-foreground-text: #19252f; $light-color-scheme-foreground-text-less-contrast: #6d7a86; $light-color-scheme-foreground-text-disabled: #8c99a5; +$light-color-scheme-foreground-text-error: #db3c55; +$light-color-scheme-foreground-text-success: #016b37; $light-color-scheme-foreground-divider: #d7dee4; $light-color-scheme-foreground-border: #bdc7d1; $light-color-scheme-foreground-icon: #8c99a5; @@ -32,6 +34,8 @@ $dark-color-scheme-warning-default: #7e5406; $dark-color-scheme-foreground-text: #f2f5f9; $dark-color-scheme-foreground-text-less-contrast: #8c99a5; $dark-color-scheme-foreground-text-disabled: #6d7a86; +$dark-color-scheme-foreground-text-error: #ea5868; +$dark-color-scheme-foreground-text-success: #319d5c; $dark-color-scheme-foreground-divider: #333f4a; $dark-color-scheme-foreground-border: #515e69; $dark-color-scheme-foreground-icon: #8c99a5; @@ -532,6 +536,21 @@ $form-field-size-button-width: 32px; $form-field-font-default: body; $form-field-hint-size-margin-top: 4px; $form-field-hint-font-default: caption; +$form-field-password-hint-light-color-scheme-text-color: #19252f; +$form-field-password-hint-light-color-scheme-icon-color: #19252f; +$form-field-password-hint-light-color-scheme-states-invalid-icon-color: #db3c55; +$form-field-password-hint-light-color-scheme-states-invalid-text-color: #19252f; +$form-field-password-hint-light-color-scheme-states-valid-text-color: #016b37; +$form-field-password-hint-light-color-scheme-states-valid-icon-color: #016b37; +$form-field-password-hint-dark-color-scheme-text-color: #f2f5f9; +$form-field-password-hint-dark-color-scheme-icon-color: #f2f5f9; +$form-field-password-hint-dark-color-scheme-states-invalid-icon-color: #ea5868; +$form-field-password-hint-dark-color-scheme-states-invalid-text-color: #f2f5f9; +$form-field-password-hint-dark-color-scheme-states-valid-text-color: #319d5c; +$form-field-password-hint-dark-color-scheme-states-valid-icon-color: #319d5c; +$form-field-password-hint-size-margin-top: 8px; +$form-field-password-hint-size-icon-margin: 4px; +$form-field-password-hint-font-default: caption; $forms-light-color-scheme-label: #6d7a86; $forms-light-color-scheme-legend: #19252f; $forms-dark-color-scheme-label: #8c99a5; diff --git a/packages/mosaic/design-tokens/pt-2022/css-tokens.css b/packages/mosaic/design-tokens/pt-2022/css-tokens.css index 4dbdcd33f..d69787c0e 100644 --- a/packages/mosaic/design-tokens/pt-2022/css-tokens.css +++ b/packages/mosaic/design-tokens/pt-2022/css-tokens.css @@ -76,6 +76,8 @@ --mc-form-field-size-border-radius: 3px; --mc-form-field-size-button-width: 32px; --mc-form-field-hint-size-margin-top: 4px; + --mc-form-field-password-hint-size-margin-top: 8px; + --mc-form-field-password-hint-size-icon-margin: 4px; --mc-forms-size-horizontal-row-margin-bottom: 20px; --mc-forms-size-horizontal-label-padding-top: 6px; --mc-forms-size-horizontal-label-padding-bottom: 0; diff --git a/packages/mosaic/design-tokens/pt-2022/tokens.ts b/packages/mosaic/design-tokens/pt-2022/tokens.ts index f449d4e77..dbd6e896f 100644 --- a/packages/mosaic/design-tokens/pt-2022/tokens.ts +++ b/packages/mosaic/design-tokens/pt-2022/tokens.ts @@ -17,6 +17,8 @@ export const LightColorSchemeWarningPalette = {"40":{"value":"#fff4dd","filePath export const LightColorSchemeForegroundText = "#19252f"; export const LightColorSchemeForegroundTextLessContrast = "#6d7a86"; export const LightColorSchemeForegroundTextDisabled = "#8c99a5"; +export const LightColorSchemeForegroundTextError = "#db3c55"; +export const LightColorSchemeForegroundTextSuccess = "#016b37"; export const LightColorSchemeForegroundDivider = "#d7dee4"; export const LightColorSchemeForegroundBorder = "#bdc7d1"; export const LightColorSchemeForegroundIcon = "#8c99a5"; @@ -45,6 +47,8 @@ export const DarkColorSchemeWarningPalette = {"40":{"value":"#fff4dd","filePath" export const DarkColorSchemeForegroundText = "#f2f5f9"; export const DarkColorSchemeForegroundTextLessContrast = "#8c99a5"; export const DarkColorSchemeForegroundTextDisabled = "#6d7a86"; +export const DarkColorSchemeForegroundTextError = "#ea5868"; +export const DarkColorSchemeForegroundTextSuccess = "#319d5c"; export const DarkColorSchemeForegroundDivider = "#333f4a"; export const DarkColorSchemeForegroundBorder = "#515e69"; export const DarkColorSchemeForegroundIcon = "#8c99a5"; @@ -654,6 +658,21 @@ export const FormFieldSizeButtonWidth = "32px"; export const FormFieldFontDefault = "body"; export const FormFieldHintSizeMarginTop = "4px"; export const FormFieldHintFontDefault = "caption"; +export const FormFieldPasswordHintLightColorSchemeTextColor = "#19252f"; +export const FormFieldPasswordHintLightColorSchemeIconColor = "#19252f"; +export const FormFieldPasswordHintLightColorSchemeStatesInvalidIconColor = "#db3c55"; +export const FormFieldPasswordHintLightColorSchemeStatesInvalidTextColor = "#19252f"; +export const FormFieldPasswordHintLightColorSchemeStatesValidTextColor = "#016b37"; +export const FormFieldPasswordHintLightColorSchemeStatesValidIconColor = "#016b37"; +export const FormFieldPasswordHintDarkColorSchemeTextColor = "#f2f5f9"; +export const FormFieldPasswordHintDarkColorSchemeIconColor = "#f2f5f9"; +export const FormFieldPasswordHintDarkColorSchemeStatesInvalidIconColor = "#ea5868"; +export const FormFieldPasswordHintDarkColorSchemeStatesInvalidTextColor = "#f2f5f9"; +export const FormFieldPasswordHintDarkColorSchemeStatesValidTextColor = "#319d5c"; +export const FormFieldPasswordHintDarkColorSchemeStatesValidIconColor = "#319d5c"; +export const FormFieldPasswordHintSizeMarginTop = "8px"; +export const FormFieldPasswordHintSizeIconMargin = "4px"; +export const FormFieldPasswordHintFontDefault = "caption"; export const FormsLightColorSchemeLabel = "#6d7a86"; export const FormsLightColorSchemeLegend = "#19252f"; export const FormsDarkColorSchemeLabel = "#8c99a5"; diff --git a/packages/mosaic/design-tokens/pt-2022/tokens/components/form-field.json5 b/packages/mosaic/design-tokens/pt-2022/tokens/components/form-field.json5 index 28b2eb803..a5319b93f 100644 --- a/packages/mosaic/design-tokens/pt-2022/tokens/components/form-field.json5 +++ b/packages/mosaic/design-tokens/pt-2022/tokens/components/form-field.json5 @@ -48,5 +48,44 @@ font: { default: { value: 'caption' } } + }, + 'form-field-password-hint': { + 'light-color-scheme': { + 'text-color': { value: '{light-color-scheme.foreground.text.value}' }, + 'icon-color': { value: '{light-color-scheme.foreground.text.value}' }, + + states: { + invalid: { + 'icon-color': { value: '{light-color-scheme.foreground.text-error.value}' }, + 'text-color': { value: '{light-color-scheme.foreground.text.value}' } + }, + valid: { + 'text-color': { value: '{light-color-scheme.foreground.text-success.value}' }, + 'icon-color': { value: '{light-color-scheme.foreground.text-success.value}' } + } + } + }, + 'dark-color-scheme': { + 'text-color': { value: '{dark-color-scheme.foreground.text.value}' }, + 'icon-color': { value: '{dark-color-scheme.foreground.text.value}' }, + + states: { + invalid: { + 'icon-color': { value: '{dark-color-scheme.foreground.text-error.value}' }, + 'text-color': { value: '{dark-color-scheme.foreground.text.value}' } + }, + valid: { + 'text-color': { value: '{dark-color-scheme.foreground.text-success.value}' }, + 'icon-color': { value: '{dark-color-scheme.foreground.text-success.value}' } + } + } + }, + size: { + 'margin-top': { value: '8px' }, + 'icon-margin': { value: '4px' } + }, + font: { + default: { value: 'caption' } + } } -} \ No newline at end of file +} diff --git a/packages/mosaic/design-tokens/pt-2022/tokens/properties/colors.json5 b/packages/mosaic/design-tokens/pt-2022/tokens/properties/colors.json5 index 94d047591..f57526fda 100644 --- a/packages/mosaic/design-tokens/pt-2022/tokens/properties/colors.json5 +++ b/packages/mosaic/design-tokens/pt-2022/tokens/properties/colors.json5 @@ -34,6 +34,8 @@ text: { value: '{palette.grey.900.value}' }, 'text-less-contrast': { value: '{palette.grey.500.value}'}, 'text-disabled': { value: '{palette.grey.300.value}' }, + 'text-error': { value: '{light-color-scheme.error.palette.value.500.value}' }, + 'text-success': { value: '{light-color-scheme.success.palette.value.560.value}' }, divider: { value: '{palette.grey.100.value}' }, border: { value: '{palette.grey.200.value}' }, icon: { value: '{palette.grey.300.value}' } @@ -89,6 +91,8 @@ text: { value: '{palette.grey.40.value}' }, 'text-less-contrast': { value: '{palette.grey.300.value}'}, 'text-disabled': { value: '{palette.grey.500.value}' }, + 'text-error': { value: '{dark-color-scheme.error.palette.value.400.value}' }, + 'text-success': { value: '{dark-color-scheme.success.palette.value.400.value}' }, divider: { value: '{palette.grey.700.value}' }, border: { value: '{palette.grey.560.value}' }, icon: { value: '{palette.grey.300.value}' } diff --git a/packages/mosaic/form-field/_form-field-theme.scss b/packages/mosaic/form-field/_form-field-theme.scss index 94871f7df..b817d4136 100644 --- a/packages/mosaic/form-field/_form-field-theme.scss +++ b/packages/mosaic/form-field/_form-field-theme.scss @@ -41,12 +41,12 @@ } } - & .mc-form-field__hint { + & .mc-hint { color: mc-color($error); } } - &.mc-focused { + &.cdk-focused { &:not(.ng-invalid) .mc-form-field__container { border-color: map-get(map-get($theme, states), focused-color); box-shadow: 0 0 0 1px map-get(map-get($theme, states), focused-color); @@ -76,6 +76,32 @@ /* stylelint-enable no-descending-specificity */ } } + + .mc-password-hint { + $password-hint: map-get(map-get($theme, components), form-field-password-hint); + + color: map-get($password-hint, text-color); + + & .mc-password-hint__icon { + color: map-get($password-hint, icon-color); + } + + &.mc-password-hint_valid { + color: map-get($password-hint, state-valid-text-color); + + & .mc-password-hint__icon { + color: map-get($password-hint, state-valid-icon-color); + } + } + + &.mc-password-hint_invalid { + color: map-get($password-hint, state-invalid-text-color); + + & .mc-password-hint__icon { + color: map-get($password-hint, state-invalid-icon-color); + } + } + } } @mixin mc-form-field-typography($config) { diff --git a/packages/mosaic/form-field/form-field.html b/packages/mosaic/form-field/form-field.html index 24bcf4851..8a91b5d28 100644 --- a/packages/mosaic/form-field/form-field.html +++ b/packages/mosaic/form-field/form-field.html @@ -18,9 +18,11 @@ + +
- +
diff --git a/packages/mosaic/form-field/form-field.module.ts b/packages/mosaic/form-field/form-field.module.ts index 69f3a2a0e..455f1ff17 100644 --- a/packages/mosaic/form-field/form-field.module.ts +++ b/packages/mosaic/form-field/form-field.module.ts @@ -5,6 +5,7 @@ import { McIconModule } from '@ptsecurity/mosaic/icon'; import { McCleaner } from './cleaner'; import { McFormField, McFormFieldWithoutBorders } from './form-field'; import { McHint } from './hint'; +import { McPasswordHint } from './password-hint'; import { McPrefix } from './prefix'; import { McStepper } from './stepper'; import { McSuffix } from './suffix'; @@ -15,6 +16,7 @@ import { McSuffix } from './suffix'; McFormField, McFormFieldWithoutBorders, McHint, + McPasswordHint, McPrefix, McSuffix, McCleaner, @@ -25,11 +27,11 @@ import { McSuffix } from './suffix'; McFormField, McFormFieldWithoutBorders, McHint, + McPasswordHint, McPrefix, McSuffix, McCleaner, McStepper ] }) -export class McFormFieldModule { -} +export class McFormFieldModule {} diff --git a/packages/mosaic/form-field/form-field.scss b/packages/mosaic/form-field/form-field.scss index 6701ea4c6..64289c508 100644 --- a/packages/mosaic/form-field/form-field.scss +++ b/packages/mosaic/form-field/form-field.scss @@ -16,10 +16,34 @@ } } +.mc-form-field-type-input-password { + & .mc-input { + padding-right: var(--mc-form-field-size-button-width, $form-field-size-button-width); + } +} + .mc-hint { display: block; } +.mc-password-hint { + display: block; + + padding-left: calc( + 16px + var(--mc-form-field-password-hint-size-icon-margin, $form-field-password-hint-size-icon-margin) + ); + + & .mc-icon { + position: absolute; + + left: 0; + } +} + +.mc-form-field__hint > .mc-password-hint { + margin-top: var(--mc-form-field-password-hint-size-margin-top, $form-field-password-hint-size-margin-top); +} + .mc-form-field__hint > .mc-hint { margin-top: var(--mc-form-field-hint-size-margin-top, $form-field-hint-size-margin-top); } diff --git a/packages/mosaic/form-field/form-field.ts b/packages/mosaic/form-field/form-field.ts index c52f4d294..6bc9b153d 100644 --- a/packages/mosaic/form-field/form-field.ts +++ b/packages/mosaic/form-field/form-field.ts @@ -1,3 +1,4 @@ +import { FocusMonitor } from '@angular/cdk/a11y'; import { AfterContentChecked, AfterContentInit, @@ -15,7 +16,7 @@ import { ViewEncapsulation } from '@angular/core'; import { NgControl } from '@angular/forms'; -import { ESCAPE } from '@ptsecurity/cdk/keycodes'; +import { ESCAPE, F8 } from '@ptsecurity/cdk/keycodes'; import { CanColor, CanColorCtor, mixinColor } from '@ptsecurity/mosaic/core'; import { EMPTY, merge, Subject } from 'rxjs'; import { startWith, takeUntil } from 'rxjs/operators'; @@ -27,6 +28,7 @@ import { getMcFormFieldYouCanNotUseCleanerInNumberInputError } from './form-field-errors'; import { McHint } from './hint'; +import { McPasswordHint } from './password-hint'; import { McPrefix } from './prefix'; import { McStepper } from './stepper'; import { McSuffix } from './suffix'; @@ -63,8 +65,9 @@ export const McFormFieldMixinBase: CanColorCtor & typeof McFormFieldBase = mixin '[class.mc-form-field_has-suffix]': 'hasSuffix', '[class.mc-form-field_has-cleaner]': 'canShowCleaner', '[class.mc-form-field_has-stepper]': 'canShowStepper', + '[class.mc-disabled]': 'control.disabled', - '[class.mc-focused]': 'control.focused', + '[class.ng-untouched]': 'shouldForward("untouched")', '[class.ng-touched]': 'shouldForward("touched")', '[class.ng-pristine]': 'shouldForward("pristine")', @@ -72,6 +75,7 @@ export const McFormFieldMixinBase: CanColorCtor & typeof McFormFieldBase = mixin '[class.ng-valid]': 'shouldForward("valid")', '[class.ng-invalid]': 'shouldForward("invalid")', '[class.ng-pending]': 'shouldForward("pending")', + '(keydown)': 'onKeyDown($event)', '(mouseenter)': 'onHoverChanged(true)', '(mouseleave)': 'onHoverChanged(false)' @@ -88,6 +92,7 @@ export class McFormField extends McFormFieldMixinBase implements @ContentChild(McCleaner, { static: false }) cleaner: McCleaner | null; @ContentChildren(McHint) hint: QueryList; + @ContentChildren(McPasswordHint) passwordHints: QueryList; @ContentChildren(McSuffix) suffix: QueryList; @ContentChildren(McPrefix) prefix: QueryList; @@ -103,15 +108,19 @@ export class McFormField extends McFormFieldMixinBase implements private $unsubscribe = new Subject(); get hasHint(): boolean { - return this.hint && this.hint.length > 0; + return this.hint?.length > 0; + } + + get hasPasswordStrengthError(): boolean { + return this.passwordHints?.some((hint) => hint.hasError); } get hasSuffix(): boolean { - return this.suffix && this.suffix.length > 0; + return this.suffix?.length > 0; } get hasPrefix(): boolean { - return this.prefix && this.prefix.length > 0; + return this.prefix?.length > 0; } get hasCleaner(): boolean { @@ -124,14 +133,13 @@ export class McFormField extends McFormFieldMixinBase implements get canShowCleaner(): boolean { return this.hasCleaner && - this.control && - this.control.ngControl + this.control?.ngControl ? this.control.ngControl.value && !this.control.disabled : false; } get disabled(): boolean { - return this.control && this.control.disabled; + return this.control?.disabled; } get canShowStepper(): boolean { @@ -140,9 +148,15 @@ export class McFormField extends McFormFieldMixinBase implements (this.control?.focused || this.hovered); } - // tslint:disable-next-line:naming-convention - constructor(public _elementRef: ElementRef, private _changeDetectorRef: ChangeDetectorRef) { + constructor( + // tslint:disable-next-line:naming-convention + public _elementRef: ElementRef, + private _changeDetectorRef: ChangeDetectorRef, + private focusMonitor: FocusMonitor + ) { super(_elementRef); + + this.runFocusMonitor(); } ngAfterContentInit() { @@ -160,7 +174,11 @@ export class McFormField extends McFormFieldMixinBase implements // Subscribe to changes in the child control state in order to update the form field UI. this.control.stateChanges .pipe(startWith()) - .subscribe(() => { + .subscribe((state: any) => { + if (!state?.focused && this.hasPasswordStrengthError) { + this.control.ngControl?.control?.setErrors({ passwordStrength: true }); + } + this._changeDetectorRef.markForCheck(); }); @@ -169,7 +187,7 @@ export class McFormField extends McFormFieldMixinBase implements } // Run change detection if the value changes. - const valueChanges = this.control.ngControl && this.control.ngControl.valueChanges || EMPTY; + const valueChanges = this.control.ngControl?.valueChanges || EMPTY; merge(valueChanges) .pipe(takeUntil(this.$unsubscribe)) @@ -188,10 +206,8 @@ export class McFormField extends McFormFieldMixinBase implements clearValue($event) { $event.stopPropagation(); - if (this.control && this.control.ngControl) { - this.control.ngControl.reset(); - this.control.focus(); - } + this.control?.ngControl?.reset(); + this.control?.focus(); } onContainerClick($event) { @@ -201,11 +217,13 @@ export class McFormField extends McFormFieldMixinBase implements } onKeyDown(event: KeyboardEvent): void { + // tslint:disable-next-line:deprecation + if (this.control.controlType === 'input-password' && event.altKey && event.keyCode === F8) { + (this.control as unknown as { toggleType(): void }).toggleType(); + } // tslint:disable-next-line:deprecation if (this.canCleanerClearByEsc && event.keyCode === ESCAPE && this.control.focused && this.hasCleaner) { - if (this.control && this.control.ngControl) { - this.control.ngControl.reset(); - } + this.control?.ngControl?.reset(); event.preventDefault(); } @@ -236,6 +254,8 @@ export class McFormField extends McFormFieldMixinBase implements ngOnDestroy(): void { this.$unsubscribe.next(); this.$unsubscribe.complete(); + + this.stopFocusMonitor(); } /** Throws an error if the form field's control is missing. */ @@ -244,6 +264,14 @@ export class McFormField extends McFormFieldMixinBase implements throw getMcFormFieldMissingControlError(); } } + + private runFocusMonitor() { + this.focusMonitor.monitor(this._elementRef.nativeElement, true); + } + + private stopFocusMonitor() { + this.focusMonitor.stopMonitoring(this._elementRef.nativeElement); + } } @Directive({ diff --git a/packages/mosaic/form-field/hint.ts b/packages/mosaic/form-field/hint.ts index 38e141841..3aee8cb3d 100644 --- a/packages/mosaic/form-field/hint.ts +++ b/packages/mosaic/form-field/hint.ts @@ -1,7 +1,7 @@ import { Directive, Input } from '@angular/core'; -let nextUniqueId = 0; +let nextHintUniqueId = 0; @Directive({ selector: 'mc-hint', @@ -11,5 +11,5 @@ let nextUniqueId = 0; } }) export class McHint { - @Input() id: string = `mc-hint-${nextUniqueId++}`; + @Input() id: string = `mc-hint-${nextHintUniqueId++}`; } diff --git a/packages/mosaic/form-field/password-hint.ts b/packages/mosaic/form-field/password-hint.ts new file mode 100644 index 000000000..31f20ec0f --- /dev/null +++ b/packages/mosaic/form-field/password-hint.ts @@ -0,0 +1,119 @@ +import { + AfterContentInit, + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + Input +} from '@angular/core'; + +import { McFormField } from './form-field'; + + +let nextPasswordHintUniqueId = 0; + +export enum PasswordRules { + Length, + UpperLatin, + LowerLatin, + Digit, + SpecialSymbols +} + +export const regExpPasswordValidator = { + [PasswordRules.LowerLatin]: RegExp(/^(?=.*?[a-z])/), + [PasswordRules.UpperLatin]: RegExp(/^(?=.*?[A-Z])/), + [PasswordRules.Digit]: RegExp(/^(?=.*?[0-9])/), + [PasswordRules.SpecialSymbols]: RegExp(/^(?=.*?[" !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~"])/) +}; + + +@Component({ + selector: 'mc-password-hint', + template: ` + + + + + + + `, + host: { + class: 'mc-password-hint', + '[class.mc-password-hint_valid]': 'checked', + '[class.mc-password-hint_invalid]': 'hasError', + '[attr.id]': 'id' + }, + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class McPasswordHint implements AfterContentInit { + @Input() id: string = `mc-hint-${nextPasswordHintUniqueId++}`; + + @Input() rule: PasswordRules | any; + + @Input() min: number; + @Input() max: number; + @Input() regex: RegExp | null; + + hasError: boolean = false; + checked: boolean = false; + + private checkRule: (value: string) => boolean; + + private get control() { + return this.formField.control; + } + + private lastControlValue: string; + + constructor(private changeDetectorRef: ChangeDetectorRef, private formField: McFormField) {} + + ngAfterContentInit(): void { + if (this.rule === null) { + throw Error('You should set [rule] name'); + } + + if (this.rule === PasswordRules.Length && (this.min || this.max) === null) { + throw Error('For [rule] "Length" need set [min] and [max]'); + } + + if (this.rule === PasswordRules.Length) { + this.checkRule = this.checkLengthRule; + } else if ( + [PasswordRules.UpperLatin, PasswordRules.LowerLatin, PasswordRules.Digit, PasswordRules.SpecialSymbols] + .includes(this.rule) + ) { + this.regex = this.regex || regExpPasswordValidator[this.rule]; + this.checkRule = this.checkRegexRule; + } else { + throw Error(`Unknown [rule]=${this.rule}`); + } + + this.formField.control.stateChanges + .subscribe(this.checkValue); + } + + private checkValue = () => { + if (this.control.focused && this.isValueChanged()) { + this.hasError = false; + + this.checked = this.checkRule(this.control.value); + } else if (!this.control.focused && !this.isValueChanged()) { + this.hasError = !this.checkRule(this.control.value); + } + + this.lastControlValue = this.control.value; + this.changeDetectorRef.markForCheck(); + } + + private checkLengthRule(value: string): boolean { + return value.length >= this.min && value.length <= this.max; + } + + private checkRegexRule(value: string): boolean { + return !!this.regex?.test(value); + } + + private isValueChanged(): boolean { + return this.lastControlValue !== this.formField.control.value; + } +} diff --git a/packages/mosaic/form-field/public-api.ts b/packages/mosaic/form-field/public-api.ts index 72dc7d3c3..8c76101e9 100644 --- a/packages/mosaic/form-field/public-api.ts +++ b/packages/mosaic/form-field/public-api.ts @@ -3,6 +3,7 @@ export * from './form-field'; export * from './form-field-control'; export * from './form-field-errors'; export * from './hint'; +export * from './password-hint'; export * from './suffix'; export * from './prefix'; export * from './cleaner'; diff --git a/packages/mosaic/input/_input-theme.scss b/packages/mosaic/input/_input-theme.scss index c149c2557..17b450f30 100644 --- a/packages/mosaic/input/_input-theme.scss +++ b/packages/mosaic/input/_input-theme.scss @@ -4,6 +4,7 @@ @mixin mc-input-theme($theme) { $foreground: map-get($theme, foreground); + $background: map-get($theme, background); .mc-input { color: map-get($foreground, text); @@ -20,6 +21,25 @@ color: map-get($foreground, text-disabled); } } + + .mc-password-toggle { + color: map-get($foreground, icon); + + &:hover { + color: map-get($foreground, text); + } + + &[disabled] { + cursor: default; + + color: map-get($foreground, text-disabled); + } + + &.cdk-keyboard-focused { + border-color: map-get(map-get($theme, states), focused-color); + box-shadow: 0 0 0 1px map-get(map-get($theme, states), focused-color); + } + } } @mixin mc-input-typography($config) { diff --git a/packages/mosaic/input/input-number.spec.ts b/packages/mosaic/input/input-number.spec.ts index fd46f6b1c..13e7c2508 100644 --- a/packages/mosaic/input/input-number.spec.ts +++ b/packages/mosaic/input/input-number.spec.ts @@ -190,6 +190,7 @@ describe('McNumberInput', () => { dispatchFakeEvent(iconUp.nativeElement, 'mousedown'); fixture.detectChanges(); + flush(); expect(fixture.componentInstance.formControl.value).toBe(11); })); @@ -213,6 +214,7 @@ describe('McNumberInput', () => { dispatchFakeEvent(iconDown.nativeElement, 'mousedown'); fixture.detectChanges(); + flush(); expect(fixture.componentInstance.formControl.value).toBe(9); })); @@ -238,6 +240,7 @@ describe('McNumberInput', () => { dispatchFakeEvent(iconUp.nativeElement, 'mousedown'); fixture.detectChanges(); + flush(); expect(fixture.componentInstance.reactiveForm.value['reactiveInputValue']).toBe(11); })); @@ -261,6 +264,7 @@ describe('McNumberInput', () => { dispatchFakeEvent(iconDown.nativeElement, 'mousedown'); fixture.detectChanges(); + flush(); expect(fixture.componentInstance.reactiveForm.value['reactiveInputValue']).toBe(9); })); @@ -284,6 +288,7 @@ describe('McNumberInput', () => { dispatchFakeEvent(iconUp.nativeElement, 'mousedown'); fixture.detectChanges(); + flush(); expect(fixture.componentInstance.value).toBe(1); })); @@ -305,6 +310,7 @@ describe('McNumberInput', () => { dispatchFakeEvent(iconDown.nativeElement, 'mousedown'); fixture.detectChanges(); + flush(); expect(fixture.componentInstance.value).toBe(1); })); @@ -330,6 +336,7 @@ describe('McNumberInput', () => { dispatchFakeEvent(iconUp.nativeElement, 'mousedown'); fixture.detectChanges(); + flush(); expect(fixture.componentInstance.value).toBe(3.5); })); @@ -356,6 +363,7 @@ describe('McNumberInput', () => { dispatchFakeEvent(iconDown.nativeElement, 'mousedown'); fixture.detectChanges(); + flush(); expect(fixture.componentInstance.value).toBe(3); })); @@ -383,6 +391,7 @@ describe('McNumberInput', () => { dispatchFakeEvent(iconUp.nativeElement, 'mousedown'); fixture.detectChanges(); + flush(); expect(fixture.componentInstance.value).toBe(min); })); @@ -417,6 +426,7 @@ describe('McNumberInput', () => { dispatchFakeEvent(stepUp.nativeElement, 'mousedown'); fixture.detectChanges(); + flush(); expect(fixture.componentInstance.value).toBe(max); })); @@ -446,6 +456,7 @@ describe('McNumberInput', () => { dispatchFakeEvent(iconDown.nativeElement, 'mousedown'); fixture.detectChanges(); + flush(); expect(fixture.componentInstance.value).toBe(3); })); @@ -469,6 +480,7 @@ describe('McNumberInput', () => { dispatchEvent(inputElementDebug.nativeElement, event); fixture.detectChanges(); + flush(); expect(fixture.componentInstance.value).toBe(8); })); @@ -495,6 +507,7 @@ describe('McNumberInput', () => { dispatchFakeEvent(iconUp.nativeElement, 'mousedown'); fixture.detectChanges(); + flush(); expect(fixture.componentInstance.value).toBe(2); })); @@ -519,6 +532,7 @@ describe('McNumberInput', () => { dispatchFakeEvent(iconDown.nativeElement, 'mousedown'); fixture.detectChanges(); + flush(); expect(fixture.componentInstance.value).toBe(0); })); @@ -538,6 +552,7 @@ describe('McNumberInput', () => { dispatchKeyboardEvent(inputElementDebug.nativeElement, 'keydown', UP_ARROW); fixture.detectChanges(); + flush(); expect(fixture.componentInstance.value).toBe(2); })); @@ -555,6 +570,7 @@ describe('McNumberInput', () => { dispatchKeyboardEvent(inputElementDebug.nativeElement, 'keydown', DOWN_ARROW); fixture.detectChanges(); + flush(); expect(fixture.componentInstance.value).toBe(0); })); @@ -574,6 +590,7 @@ describe('McNumberInput', () => { dispatchEvent(inputElementDebug.nativeElement, event); fixture.detectChanges(); + flush(); expect(fixture.componentInstance.value).toBe(7); })); @@ -593,6 +610,7 @@ describe('McNumberInput', () => { dispatchEvent(inputElementDebug.nativeElement, event); fixture.detectChanges(); + flush(); expect(fixture.componentInstance.value).toBe(4); })); @@ -672,6 +690,7 @@ describe('McNumberInput', () => { dispatchFakeEvent(iconUp.nativeElement, 'mousedown'); fixture.detectChanges(); + flush(); expect(fixture.componentInstance.value).toBe(10); })); @@ -696,6 +715,7 @@ describe('McNumberInput', () => { dispatchFakeEvent(iconDown.nativeElement, 'mousedown'); fixture.detectChanges(); + flush(); expect(fixture.componentInstance.value).toBe(3); })); diff --git a/packages/mosaic/input/input-password.spec.ts b/packages/mosaic/input/input-password.spec.ts new file mode 100644 index 000000000..b5a1f9550 --- /dev/null +++ b/packages/mosaic/input/input-password.spec.ts @@ -0,0 +1,185 @@ +/* tslint:disable */ +import { Component, Provider, Type } from '@angular/core'; +import { + ComponentFixture, + fakeAsync, + TestBed, + ComponentFixtureAutoDetect, + flush +} from '@angular/core/testing'; +import { FormBuilder, FormControl, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { By } from '@angular/platform-browser'; +import { dispatchFakeEvent } from '@ptsecurity/cdk/testing'; +import { McFormFieldModule, PasswordRules } from '@ptsecurity/mosaic/form-field'; +import { McToolTipModule } from '@ptsecurity/mosaic/tooltip'; + +import { McInputModule, McInputPassword, McPasswordToggle } from './index'; + + +function createComponent(component: Type, imports: any[] = [], providers: Provider[] = []): ComponentFixture { + TestBed.resetTestingModule(); + + TestBed.configureTestingModule({ + imports: [ + FormsModule, + ReactiveFormsModule, + McFormFieldModule, + McInputModule, + McToolTipModule, + ...imports + ], + declarations: [component], + providers: [ + { provide: ComponentFixtureAutoDetect, useValue: true }, + ...providers + ] + }).compileComponents(); + + return TestBed.createComponent(component); +} + + +@Component({ + template: ` + + + + + От 8 до 64 символов + + Заглавная латинская буква + + Строчная латинская буква + + Цифра + + Только латинские буквы, цифры, пробелы и спецсимволы + + ` +}) +class McPasswordInputDefault { + passwordRules = PasswordRules; + + value: any = '1'; +} + +@Component({ + template: ` + + + + ` +}) +class McPasswordInputWithFormControl { + formControl = new FormControl(''); +} + +@Component({ + template: ` +
+ + + +
+ ` +}) +class McPasswordInputWithFormControlName { + reactiveForm: FormGroup; + + constructor(private formBuilder: FormBuilder) { + this.reactiveForm = this.formBuilder.group({ + reactiveInputValue: new FormControl('') + }); + } +} + + +describe('McPasswordInput', () => { + it('should have toggle', fakeAsync(() => { + const fixture = createComponent(McPasswordInputDefault); + fixture.detectChanges(); + + const mcPasswordToggle = fixture.debugElement.query(By.css('.mc-password-toggle')); + + expect(mcPasswordToggle).not.toBeNull(); + })); + + it('toggle should change input type', fakeAsync(() => { + const fixture = createComponent(McPasswordInputDefault); + fixture.detectChanges(); + + const passwordToggle = fixture.debugElement.query(By.directive(McPasswordToggle)).nativeElement; + const passwordInput = fixture.debugElement.query(By.directive(McInputPassword)).nativeElement; + + expect(passwordInput.getAttribute('type')).toBe('password'); + + dispatchFakeEvent(passwordToggle, 'click'); + fixture.detectChanges(); + + expect(passwordInput.getAttribute('type')).toBe('text'); + + dispatchFakeEvent(passwordToggle, 'click'); + fixture.detectChanges(); + + expect(passwordInput.getAttribute('type')).toBe('password'); + })); + + it('should have password hints', fakeAsync(() => { + const fixture = createComponent(McPasswordInputDefault); + fixture.detectChanges(); + + const mcPasswordHints = fixture.debugElement.queryAll(By.css('.mc-password-hint')); + + expect(mcPasswordHints.length).toBe(5); + })); + + it('password length rule', fakeAsync(() => { + const fixture = createComponent(McPasswordInputDefault); + fixture.detectChanges(); + flush(); + // + // const formFieldElement = fixture.debugElement.query(By.directive(McFormField)).nativeElement; + // const passwordLengthHint: DebugElement = fixture.debugElement.queryAll(By.directive(McPasswordHint))[0]; + // const passwordInput: any = fixture.debugElement.query(By.directive(McInputPassword)); + // const input = passwordInput.nativeElement; + // const passwordToggle = fixture.debugElement.query(By.directive(McPasswordToggle)).nativeElement; + // console.log('mcPasswordInput: ', passwordInput); + // + // expect(formFieldElement.classList.contains('ng-valid')).toBe(true); + // + // expect(passwordLengthHint.nativeElement.classList.contains('mc-password-hint__icon_error')).toBe(false); + // + // console.log('input.value: ', input.value); + // console.log('passwordInput.value: ', passwordInput.value); + // passwordInput.value = '5'; + // dispatchFakeEvent(input, 'input'); + // dispatchFakeEvent(input, 'focus'); + // dispatchFakeEvent(passwordToggle, 'click'); + // fixture.detectChanges(); + // + // flush(); + // fixture.detectChanges(); + // + // console.log('formFieldElement.classList', formFieldElement.classList); + // console.log('mcPasswordInput.classList: ', input.classList); + // console.log('mcPasswordLengthHint.classList: ', passwordLengthHint.nativeElement.classList); + // + // expect(passwordLengthHint.nativeElement.classList.contains('mc-password-hint__icon_error')).toBe(true); + })); + + describe('formControl', () => { + it('should step up', fakeAsync(() => { + const fixture = createComponent(McPasswordInputWithFormControl); + fixture.detectChanges(); + })); + }); + + describe('formControlName', () => { + it('should step up', fakeAsync(() => { + const fixture = createComponent(McPasswordInputWithFormControlName); + fixture.detectChanges(); + })); + }); + + describe('empty value', () => {}); +}); diff --git a/packages/mosaic/input/input-password.ts b/packages/mosaic/input/input-password.ts new file mode 100644 index 000000000..cd8ea29da --- /dev/null +++ b/packages/mosaic/input/input-password.ts @@ -0,0 +1,423 @@ +import { FocusMonitor } from '@angular/cdk/a11y'; +import { Directionality } from '@angular/cdk/bidi'; +import { coerceBooleanProperty, coerceNumberProperty } from '@angular/cdk/coercion'; +import { Overlay, ScrollDispatcher } from '@angular/cdk/overlay'; +import { + AfterContentInit, + ChangeDetectionStrategy, + Component, + Directive, + DoCheck, + ElementRef, + EventEmitter, + Inject, + Input, + NgZone, + OnChanges, + OnDestroy, + Optional, + Self, + TemplateRef, + ViewContainerRef, + ViewEncapsulation +} from '@angular/core'; +import { + FormControlName, + FormGroupDirective, + NG_VALIDATORS, + NgControl, + NgForm, + NgModel, + Validator +} from '@angular/forms'; +import { + CanUpdateErrorState, + ErrorStateMatcher, + MC_VALIDATION, + McValidationOptions, + PopUpTriggers, + setMosaicValidation +} from '@ptsecurity/mosaic/core'; +import { McFormField, McFormFieldControl } from '@ptsecurity/mosaic/form-field'; +import { MC_TOOLTIP_SCROLL_STRATEGY, McTooltipTrigger } from '@ptsecurity/mosaic/tooltip'; +import { Subject } from 'rxjs'; + +import { McInputMixinBase } from './input'; +import { MC_INPUT_VALUE_ACCESSOR } from './input-value-accessor'; + + +let nextUniqueId = 0; + + +@Component({ + selector: `mc-password-toggle`, + exportAs: 'mcPasswordToggle', + template: '', + host: { + class: 'mc-password-toggle mc', + '[class.mc-eye_16]': 'hidden', + '[class.mc-eye-crossed_16]': '!hidden', + + '[attr.tabindex]': 'disabled ? null : tabIndex', + '[attr.disabled]': 'disabled || null', + + '(click)': 'toggle()', + '(keydown.ENTER)': 'toggle()', + '(keydown.SPACE)': 'toggle()' + }, + changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None +}) +export class McPasswordToggle extends McTooltipTrigger implements OnDestroy { + @Input('mcTooltipNotHidden') + get content(): string | TemplateRef { + return (this.formField.control as McInputPassword).elementType === 'password' ? + this.mcTooltipHidden : + this._content; + } + + set content(content: string | TemplateRef) { + this._content = content; + + this.updateData(); + } + + @Input() mcTooltipHidden: string | TemplateRef; + + @Input() + get disabled() { + return this._disabled === undefined ? this.formField.disabled : this._disabled; + } + + set disabled(value: any) { + this._disabled = coerceBooleanProperty(value); + + this._disabled ? this.stopFocusMonitor() : this.runFocusMonitor(); + } + + // tslint:disable-next-line:naming-convention + protected _disabled: boolean; + + @Input() + get tabIndex(): number { + return this.disabled ? -1 : this._tabIndex; + } + + set tabIndex(value: number) { + // If the specified tabIndex value is null or undefined, fall back to the default value. + this._tabIndex = value != null ? coerceNumberProperty(value) : 0; + } + + private _tabIndex: number = 0; + + get hidden(): boolean { + return (this.formField.control as McInputPassword).elementType === 'password'; + } + + constructor( + overlay: Overlay, + elementRef: ElementRef, + ngZone: NgZone, + scrollDispatcher: ScrollDispatcher, + hostView: ViewContainerRef, + @Inject(MC_TOOLTIP_SCROLL_STRATEGY) scrollStrategy, + @Optional() direction: Directionality, + + private focusMonitor: FocusMonitor, + private formField: McFormField + ) { + super(overlay, elementRef, ngZone, scrollDispatcher, hostView, scrollStrategy, direction); + + this.trigger = `${PopUpTriggers.Hover}`; + + this.runFocusMonitor(); + } + + ngOnDestroy() { + this.stopFocusMonitor(); + } + + toggle() { + if (this.disabled) { return; } + + this.hide(); + + const input = this.formField.control as McInputPassword; + + input.toggleType(); + + this.updateData(); + } + + private runFocusMonitor() { + this.focusMonitor.monitor(this.elementRef) + .subscribe((origin) => { + if (origin === 'keyboard') { + this.show(); + } else if (origin === null) { + this.hide(); + } + }); + } + + private stopFocusMonitor() { + this.focusMonitor.stopMonitoring(this.elementRef); + } +} + +@Directive({ + selector: `input[mcInputPassword]`, + exportAs: 'mcInputPassword', + host: { + class: 'mc-input mc-input-password', + // Native input properties that are overwritten by Angular inputs need to be synced with + // the native input element. Otherwise property bindings for those don't work. + '[attr.id]': 'id', + '[attr.type]': 'elementType', + '[attr.placeholder]': 'placeholder', + '[attr.disabled]': 'disabled || null', + '[required]': 'required', + + '(blur)': 'onBlur()', + '(focus)': 'focusChanged(true)', + '(input)': 'onInput()' + }, + providers: [{ + provide: McFormFieldControl, useExisting: McInputPassword + }] +}) +export class McInputPassword extends McInputMixinBase implements McFormFieldControl, OnChanges, OnDestroy, DoCheck, + CanUpdateErrorState, AfterContentInit, OnChanges { + + /** An object used to control when error messages are shown. */ + @Input() errorStateMatcher: ErrorStateMatcher; + + /** + * Implemented as part of McFormFieldControl. + * @docs-private + */ + focused: boolean = false; + + /** + * Implemented as part of McFormFieldControl. + * @docs-private + */ + readonly stateChanges: Subject = new Subject(); + + /** + * Implemented as part of McFormFieldControl. + * @docs-private + */ + controlType: string = 'input-password'; + + elementType: string = 'password'; + + /** + * Implemented as part of McFormFieldControl. + * @docs-private + */ + @Input() placeholder: string; + + protected uid = `mc-input-${nextUniqueId++}`; + protected previousNativeValue: any; + + /** + * Implemented as part of McFormFieldControl. + * @docs-private + */ + @Input() + get disabled(): boolean { + if (this.ngControl?.disabled !== null) { + return this.ngControl.disabled; + } + + return this._disabled; + } + + set disabled(value: boolean) { + this._disabled = coerceBooleanProperty(value); + + // Browsers may not fire the blur event if the input is disabled too quickly. + // Reset from here to ensure that the element doesn't become stuck. + if (this.focused) { + this.focused = false; + this.stateChanges.next(); + } + } + + private _disabled = false; + + /** + * Implemented as part of McFormFieldControl. + * @docs-private + */ + @Input() + get id(): string { + return this._id; + } + + set id(value: string) { + this._id = value || this.uid; + } + + private _id: string; + + /** + * Implemented as part of McFormFieldControl. + * @docs-private + */ + @Input() + get required(): boolean { + return this._required; + } + + set required(value: boolean) { + this._required = coerceBooleanProperty(value); + } + + private _required = false; + + // this.elementRef.nativeElement.type = this._type; + + /** + * Implemented as part of McFormFieldControl. + * @docs-private + */ + @Input() + get value(): string { + return this._inputValueAccessor.value; + } + + set value(value: string) { + if (value === this.value) { return; } + + this._inputValueAccessor.value = value; + this.stateChanges.next(); + } + + // tslint:disable-next-line: orthodox-getter-and-setter + private _inputValueAccessor: { value: any }; + + // tslint:disable-next-line: naming-convention + constructor( + protected elementRef: ElementRef, + @Optional() @Self() @Inject(NG_VALIDATORS) public rawValidators: Validator[], + @Optional() @Inject(MC_VALIDATION) private mcValidation: McValidationOptions, + @Optional() @Self() ngControl: NgControl, + @Optional() @Self() public ngModel: NgModel, + @Optional() @Self() public formControlName: FormControlName, + @Optional() parentForm: NgForm, + @Optional() parentFormGroup: FormGroupDirective, + defaultErrorStateMatcher: ErrorStateMatcher, + @Optional() @Self() @Inject(MC_INPUT_VALUE_ACCESSOR) inputValueAccessor: any + ) { + super(defaultErrorStateMatcher, parentForm, parentFormGroup, ngControl); + + // If no input value accessor was explicitly specified, use the element as the input value + // accessor. + this._inputValueAccessor = inputValueAccessor || this.elementRef.nativeElement; + + this.previousNativeValue = this.value; + + // Force setter to be called in case id was not specified. + this.id = this.id; + } + + ngAfterContentInit(): void { + if (!this.ngControl) { return; } + + if (this.mcValidation.useValidation) { + setMosaicValidation(this); + } + } + + ngOnChanges() { + this.stateChanges.next(); + } + + ngOnDestroy() { + this.stateChanges.complete(); + } + + ngDoCheck() { + if (this.ngControl) { + // We need to re-evaluate this on every change detection cycle, because there are some + // error triggers that we can't subscribe to (e.g. parent form submissions). This means + // that whatever logic is in here has to be super lean or we risk destroying the performance. + this.updateErrorState(); + } + + // We need to dirty-check the native element's value, because there are some cases where + // we won't be notified when it changes (e.g. the consumer isn't using forms or they're + // updating the value using `emitEvent: false`). + this.dirtyCheckNativeValue(); + } + + toggleType() { + this.elementType = this.elementType === 'password' ? 'text' : 'password'; + } + + /** Focuses the input. */ + focus(): void { + this.elementRef.nativeElement.focus(); + } + + onBlur(): void { + if (this.ngControl?.control) { + const control = this.ngControl.control; + + control.updateValueAndValidity({ emitEvent: false }); + (control.statusChanges as EventEmitter).emit(control.status); + } + + this.focusChanged(false); + } + + /** Callback for the cases where the focused state of the input changes. */ + focusChanged(isFocused: boolean) { + if (isFocused === this.focused) { return; } + + this.focused = isFocused; + this.stateChanges.next({ focused: this.focused }); + } + + onInput() { + // This is a noop function and is used to let Angular know whenever the value changes. + // Angular will run a new change detection each time the `input` event has been dispatched. + // It's necessary that Angular recognizes the value change, because when floatingLabel + // is set to false and Angular forms aren't used, the placeholder won't recognize the + // value changes and will not disappear. + // Listening to the input event wouldn't be necessary when the input is using the + // FormsModule or ReactiveFormsModule, because Angular forms also listens to input events. + } + + /** + * Implemented as part of McFormFieldControl. + * @docs-private + */ + get empty(): boolean { + return !this.elementRef.nativeElement.value && !this.isBadInput(); + } + + /** + * Implemented as part of McFormFieldControl. + * @docs-private + */ + onContainerClick() { + this.focus(); + } + + /** Does some manual dirty checking on the native input `value` property. */ + protected dirtyCheckNativeValue() { + if (this.previousNativeValue !== this.value) { + this.previousNativeValue = this.value; + this.stateChanges.next(); + } + } + + /** Checks whether the input is invalid based on the native validation. */ + protected isBadInput() { + // The `validity` property won't be present on platform-server. + return (this.elementRef.nativeElement as HTMLInputElement).validity?.badInput; + } +} diff --git a/packages/mosaic/input/input.md b/packages/mosaic/input/input.md index 4d7ceb056..eb0e8b91b 100644 --- a/packages/mosaic/input/input.md +++ b/packages/mosaic/input/input.md @@ -1,5 +1,8 @@ -#### With default parameters +### With default parameters -#### Numeric input +### Numeric input + +### Password input + diff --git a/packages/mosaic/input/input.module.ts b/packages/mosaic/input/input.module.ts index 8a717566d..042014b3c 100644 --- a/packages/mosaic/input/input.module.ts +++ b/packages/mosaic/input/input.module.ts @@ -7,11 +7,33 @@ import { McCommonModule } from '@ptsecurity/mosaic/core'; import { McInput, McInputMono } from './input'; import { McNumberInput } from './input-number'; import { MaxValidator, MinValidator } from './input-number-validators'; +import { McInputPassword, McPasswordToggle } from './input-password'; @NgModule({ - imports: [CommonModule, A11yModule, McCommonModule, FormsModule], - exports: [McInput, McNumberInput, McInputMono, MinValidator, MaxValidator], - declarations: [McInput, McNumberInput, McInputMono, MinValidator, MaxValidator ] + imports: [ + CommonModule, + A11yModule, + McCommonModule, + FormsModule + ], + declarations: [ + McInput, + McNumberInput, + McInputPassword, + McPasswordToggle, + McInputMono, + MinValidator, + MaxValidator + ], + exports: [ + McInput, + McNumberInput, + McInputPassword, + McPasswordToggle, + McInputMono, + MinValidator, + MaxValidator + ] }) export class McInputModule {} diff --git a/packages/mosaic/input/input.scss b/packages/mosaic/input/input.scss index bc363fe94..5ef07fe8c 100644 --- a/packages/mosaic/input/input.scss +++ b/packages/mosaic/input/input.scss @@ -20,3 +20,33 @@ input.mc-input[type="number"]::-webkit-outer-spin-button { input.mc-input:invalid { box-shadow: unset; } + +.mc-password-toggle { + display: flex; + + position: absolute; + + top: -1px; + right: -1px; + + border: 1px solid transparent; + + width: 32px; + height: 32px; + + align-items: center; + justify-content: center; + + cursor: pointer; + + border-top-right-radius: var(--mc-form-field-size-border-radius, $form-field-size-border-radius); + border-bottom-right-radius: var(--mc-form-field-size-border-radius, $form-field-size-border-radius); + + &::-moz-focus-inner { + border: 0; + } + + &:focus { + outline: none; + } +} diff --git a/packages/mosaic/input/public-api.ts b/packages/mosaic/input/public-api.ts index 4fd2c0667..197baf057 100644 --- a/packages/mosaic/input/public-api.ts +++ b/packages/mosaic/input/public-api.ts @@ -1,5 +1,6 @@ export * from './input.module'; export * from './input'; export * from './input-number'; +export * from './input-password'; export * from './input-value-accessor'; export * from './input-number-validators'; diff --git a/packages/mosaic/select/select.component.spec.ts b/packages/mosaic/select/select.component.spec.ts index 7916f08be..7b46c9889 100644 --- a/packages/mosaic/select/select.component.spec.ts +++ b/packages/mosaic/select/select.component.spec.ts @@ -1005,6 +1005,7 @@ describe('McSelect', () => { 'Expected value from fourth option to have been set on the model.'); dispatchKeyboardEvent(select, 'keydown', UP_ARROW); + flush(); expect(options[1].selected).toBe(true, 'Expected second option to be selected.'); expect(formControl.value).toBe(options[1].value, @@ -1029,6 +1030,7 @@ describe('McSelect', () => { dispatchKeyboardEvent(select, 'keydown', DOWN_ARROW); fixture.detectChanges(); + flush(); expect(formControl.value).toBe(options[4].value); })); @@ -1054,6 +1056,7 @@ describe('McSelect', () => { 'Expected value from fourth option to have been set on the model.'); dispatchKeyboardEvent(select, 'keydown', LEFT_ARROW); + flush(); expect(options[1].selected).toBe(true, 'Expected second option to be selected.'); expect(formControl.value).toBe(options[1].value, @@ -1070,6 +1073,7 @@ describe('McSelect', () => { Object.defineProperty(event, 'altKey', { get: () => true }); dispatchEvent(select, event); + flush(); expect(selectInstance.panelOpen).toBe(true, 'Expected select to be open.'); expect(formControl.value).toBeFalsy('Expected value not to have changed.'); @@ -1085,6 +1089,7 @@ describe('McSelect', () => { Object.defineProperty(event, 'altKey', { get: () => true }); dispatchEvent(select, event); + flush(); expect(selectInstance.panelOpen).toBe(true, 'Expected select to be open.'); expect(formControl.value).toBeFalsy('Expected value not to have changed.'); @@ -1101,6 +1106,7 @@ describe('McSelect', () => { Object.defineProperty(event, 'altKey', { get: () => true }); dispatchEvent(select, event); + flush(); expect(selectInstance.panelOpen).toBe(false, 'Expected select to be closed.'); expect(event.defaultPrevented).toBe(true, 'Expected default action to be prevented.'); @@ -1117,6 +1123,7 @@ describe('McSelect', () => { Object.defineProperty(event, 'altKey', { get: () => true }); dispatchEvent(select, event); + flush(); expect(selectInstance.panelOpen).toBe(false, 'Expected select to be closed.'); expect(event.defaultPrevented).toBe(true, 'Expected default action to be prevented.'); @@ -1158,6 +1165,7 @@ describe('McSelect', () => { expect(instance.select.panelOpen).toBe(false, 'Expected panel to be closed.'); const event = dispatchKeyboardEvent(select, 'keydown', DOWN_ARROW); + flush(); expect(instance.select.panelOpen).toBe(true, 'Expected panel to be open.'); expect(instance.control.value).toBe(initialValue, 'Expected value to stay the same.'); @@ -1179,6 +1187,7 @@ describe('McSelect', () => { expect(instance.select.panelOpen).toBe(false, 'Expected panel to be closed.'); const event = dispatchKeyboardEvent(select, 'keydown', RIGHT_ARROW); + flush(); expect(instance.select.panelOpen).toBe(true, 'Expected panel to be open.'); expect(instance.control.value).toBe(initialValue, 'Expected value to stay the same.'); @@ -1199,6 +1208,7 @@ describe('McSelect', () => { expect(instance.select.panelOpen).toBe(false, 'Expected panel to be closed.'); dispatchEvent(select, createKeyboardEvent('keydown', 80, undefined, 'p')); + flush(); expect(instance.select.panelOpen).toBe(false, 'Expected panel to stay closed.'); expect(instance.control.value).toBe(initialValue, 'Expected value to stay the same.'); @@ -1223,6 +1233,7 @@ describe('McSelect', () => { formControl.setValue('eggs-5'); dispatchKeyboardEvent(select, 'keydown', DOWN_ARROW); + flush(); expect(formControl.value).toBe('pasta-6'); expect(fixture.componentInstance.options.toArray()[6].selected).toBe(true); @@ -1256,6 +1267,7 @@ describe('McSelect', () => { formControl.disable(); dispatchKeyboardEvent(select, 'keydown', DOWN_ARROW); + flush(); expect(formControl.value).toBe('eggs-5', 'Expected value to remain unchaged.'); })); @@ -1270,6 +1282,7 @@ describe('McSelect', () => { expect(lastOption.selected).toBe(true, 'Expected last option to be selected.'); dispatchKeyboardEvent(select, 'keydown', DOWN_ARROW); + flush(); expect(lastOption.selected).toBe(true, 'Expected last option to stay selected.'); })); @@ -1286,6 +1299,7 @@ describe('McSelect', () => { .toBe(false, 'Expected panel to be closed initially.'); dispatchKeyboardEvent(select, 'keydown', TAB); + flush(); expect(multiFixture.componentInstance.select.panelOpen) .toBe(false, 'Expected panel to stay closed.'); @@ -1312,6 +1326,8 @@ describe('McSelect', () => { expect(multiFixture.componentInstance.select.value).toEqual(['pizza-1']); dispatchEvent(select, event); + flush(); + expect(multiFixture.componentInstance.select.value).toEqual(['pizza-1', 'tacos-2']); })); @@ -1347,6 +1363,8 @@ describe('McSelect', () => { it('should prevent the default action when pressing space', fakeAsync(() => { const event = dispatchKeyboardEvent(select, 'keydown', SPACE); + flush(); + expect(event.defaultPrevented).toBe(true); })); @@ -1358,6 +1376,7 @@ describe('McSelect', () => { dispatchKeyboardEvent(select, 'keydown', DOWN_ARROW); expect(spy).toHaveBeenCalledWith(true); + flush(); subscription.unsubscribe(); })); @@ -1548,6 +1567,7 @@ describe('McSelect', () => { const event = dispatchKeyboardEvent(trigger, 'keydown', HOME); fixture.detectChanges(); + flush(); expect(fixture.componentInstance.select.keyManager.activeItemIndex).toBe(0); expect(event.defaultPrevented).toBe(true); @@ -1563,6 +1583,7 @@ describe('McSelect', () => { const event = dispatchKeyboardEvent(trigger, 'keydown', END); fixture.detectChanges(); + flush(); expect(fixture.componentInstance.select.keyManager.activeItemIndex).toBe(7); expect(event.defaultPrevented).toBe(true); @@ -1586,6 +1607,7 @@ describe('McSelect', () => { const option = overlayContainerElement.querySelector('mc-option') as Node; const event = dispatchKeyboardEvent(option, 'keydown', SPACE); + flush(); expect(event.defaultPrevented).toBe(true); })); @@ -1597,6 +1619,7 @@ describe('McSelect', () => { const option = overlayContainerElement.querySelector('mc-option') as Node; const event = dispatchKeyboardEvent(option, 'keydown', ENTER); + flush(); expect(event.defaultPrevented).toBe(true); })); @@ -2179,6 +2202,7 @@ describe('McSelect', () => { [1, 2, 3].forEach(() => { dispatchKeyboardEvent(host, 'keydown', DOWN_ARROW); }); + flush(); expect(panel.scrollTop) .toBe(initialScrollPosition, 'Expected scroll position not to change'); @@ -2188,6 +2212,7 @@ describe('McSelect', () => { for (let i = 0; i < 15; i++) { dispatchKeyboardEvent(host, 'keydown', DOWN_ARROW); } + flush(); expect(panel.scrollTop).toBe(316, 'Expected scroll to be at the 16th option.'); })); @@ -2201,6 +2226,7 @@ describe('McSelect', () => { for (let i = 0; i < 20; i++) { dispatchKeyboardEvent(host, 'keydown', UP_ARROW); } + flush(); expect(panel.scrollTop).toBe(252, 'Expected scroll to be at the 9th option.'); })); @@ -2221,6 +2247,7 @@ describe('McSelect', () => { for (let i = 0; i < 5; i++) { dispatchKeyboardEvent(host, 'keydown', DOWN_ARROW); } + flush(); // Note that we press down 5 times, but it will skip // 3 options because the second group is disabled. @@ -2237,6 +2264,7 @@ describe('McSelect', () => { dispatchKeyboardEvent(host, 'keydown', HOME); fixture.detectChanges(); + flush(); expect(panel.scrollTop).toBe(0, 'Expected panel to be scrolled to the top'); })); @@ -2244,6 +2272,7 @@ describe('McSelect', () => { it('should scroll to the bottom of the panel when pressing END', fakeAsync(() => { dispatchKeyboardEvent(host, 'keydown', END); fixture.detectChanges(); + flush(); expect(panel.scrollTop).toBe(728, 'Expected panel to be scrolled to the bottom'); })); @@ -2451,6 +2480,7 @@ describe('McSelect', () => { it('should only emit one event when pressing arrow keys on closed select', fakeAsync(() => { const select = fixture.debugElement.query(By.css('mc-select')).nativeElement; dispatchKeyboardEvent(select, 'keydown', DOWN_ARROW); + flush(); expect(fixture.componentInstance.changeListener).toHaveBeenCalledTimes(1); })); diff --git a/packages/mosaic/tags/tag-list.component.spec.ts b/packages/mosaic/tags/tag-list.component.spec.ts index d3290c196..e3a631db8 100644 --- a/packages/mosaic/tags/tag-list.component.spec.ts +++ b/packages/mosaic/tags/tag-list.component.spec.ts @@ -846,14 +846,14 @@ describe('McTagList', () => { flush(); fixture.detectChanges(); - expect(formField.classList).toContain('mc-focused'); + expect(formField.classList).toContain('cdk-focused'); nativeTags[0].blur(); fixture.detectChanges(); zone.simulateZoneExit(); fixture.detectChanges(); - expect(formField.classList).not.toContain('mc-focused'); + expect(formField.classList).not.toContain('cdk-focused'); })); }); @@ -1099,7 +1099,7 @@ describe('McTagList', () => { fixture.detectChanges(); dispatchKeyboardEvent(nativeInput, 'keydown', ENTER); fixture.detectChanges(); - tick(); + flush(); expect(document.activeElement).toBe(nativeInput, 'Expected input to remain focused.'); })); diff --git a/packages/mosaic/tree-select/tree-select.component.spec.ts b/packages/mosaic/tree-select/tree-select.component.spec.ts index 1d0d38edc..1130c068e 100644 --- a/packages/mosaic/tree-select/tree-select.component.spec.ts +++ b/packages/mosaic/tree-select/tree-select.component.spec.ts @@ -3201,7 +3201,7 @@ describe('McTreeSelect', () => { const select = fixture.debugElement.query(By.css('mc-tree-select')).nativeElement; dispatchKeyboardEvent(select, 'keydown', DOWN_ARROW); - tick(); + flush(); expect(fixture.componentInstance.changeListener).toHaveBeenCalledTimes(1); })); diff --git a/tools/public_api_guard/mosaic/form-field.api.md b/tools/public_api_guard/mosaic/form-field.api.md index dadd9fdbc..b8cf3cd61 100644 --- a/tools/public_api_guard/mosaic/form-field.api.md +++ b/tools/public_api_guard/mosaic/form-field.api.md @@ -12,9 +12,10 @@ import { CanColorCtor } from '@ptsecurity/mosaic/core'; import { ChangeDetectorRef } from '@angular/core'; import { ElementRef } from '@angular/core'; import { EventEmitter } from '@angular/core'; +import { FocusMonitor } from '@angular/cdk/a11y'; import * as i0 from '@angular/core'; -import * as i7 from '@angular/common'; -import * as i8 from '@ptsecurity/mosaic/icon'; +import * as i8 from '@angular/common'; +import * as i9 from '@ptsecurity/mosaic/icon'; import { NgControl } from '@angular/forms'; import { Observable } from 'rxjs'; import { OnDestroy } from '@angular/core'; @@ -39,7 +40,7 @@ export class McCleaner { // @public (undocumented) export class McFormField extends McFormFieldMixinBase implements AfterContentInit, AfterContentChecked, AfterViewInit, CanColor, OnDestroy { - constructor(_elementRef: ElementRef, _changeDetectorRef: ChangeDetectorRef); + constructor(_elementRef: ElementRef, _changeDetectorRef: ChangeDetectorRef, focusMonitor: FocusMonitor); // (undocumented) canCleanerClearByEsc: boolean; // (undocumented) @@ -64,6 +65,8 @@ export class McFormField extends McFormFieldMixinBase implements AfterContentIni // (undocumented) get hasHint(): boolean; // (undocumented) + get hasPasswordStrengthError(): boolean; + // (undocumented) get hasPrefix(): boolean; // (undocumented) get hasStepper(): boolean; @@ -90,6 +93,8 @@ export class McFormField extends McFormFieldMixinBase implements AfterContentIni // (undocumented) onKeyDown(event: KeyboardEvent): void; // (undocumented) + passwordHints: QueryList; + // (undocumented) prefix: QueryList; shouldForward(prop: keyof NgControl): boolean; // (undocumented) @@ -98,7 +103,7 @@ export class McFormField extends McFormFieldMixinBase implements AfterContentIni suffix: QueryList; protected validateControlChild(): void; // (undocumented) - static ɵcmp: i0.ɵɵComponentDeclaration; + static ɵcmp: i0.ɵɵComponentDeclaration; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; } @@ -143,9 +148,10 @@ export class McFormFieldModule { // Warning: (ae-forgotten-export) The symbol "i4" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "i5" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "i6" needs to be exported by the entry point index.d.ts + // Warning: (ae-forgotten-export) The symbol "i7" needs to be exported by the entry point index.d.ts // // (undocumented) - static ɵmod: i0.ɵɵNgModuleDeclaration; + static ɵmod: i0.ɵɵNgModuleDeclaration; } // @public (undocumented) @@ -166,6 +172,31 @@ export class McHint { static ɵfac: i0.ɵɵFactoryDeclaration; } +// @public (undocumented) +export class McPasswordHint implements AfterContentInit { + constructor(changeDetectorRef: ChangeDetectorRef, formField: McFormField); + // (undocumented) + checked: boolean; + // (undocumented) + hasError: boolean; + // (undocumented) + id: string; + // (undocumented) + max: number; + // (undocumented) + min: number; + // (undocumented) + ngAfterContentInit(): void; + // (undocumented) + regex: RegExp | null; + // (undocumented) + rule: PasswordRules | any; + // (undocumented) + static ɵcmp: i0.ɵɵComponentDeclaration; + // (undocumented) + static ɵfac: i0.ɵɵFactoryDeclaration; +} + // @public (undocumented) export class McPrefix { // (undocumented) @@ -200,6 +231,28 @@ export class McSuffix { static ɵfac: i0.ɵɵFactoryDeclaration; } +// @public (undocumented) +export enum PasswordRules { + // (undocumented) + Digit = 3, + // (undocumented) + Length = 0, + // (undocumented) + LowerLatin = 2, + // (undocumented) + SpecialSymbols = 4, + // (undocumented) + UpperLatin = 1 +} + +// @public (undocumented) +export const regExpPasswordValidator: { + 2: RegExp; + 1: RegExp; + 3: RegExp; + 4: RegExp; +}; + // (No @packageDocumentation comment for this package) ``` diff --git a/tools/public_api_guard/mosaic/input.api.md b/tools/public_api_guard/mosaic/input.api.md index 811c1ca21..dc9295cd4 100644 --- a/tools/public_api_guard/mosaic/input.api.md +++ b/tools/public_api_guard/mosaic/input.api.md @@ -8,29 +8,38 @@ import { AbstractControl } from '@angular/forms'; import { AfterContentInit } from '@angular/core'; import { CanUpdateErrorState } from '@ptsecurity/mosaic/core'; import { CanUpdateErrorStateCtor } from '@ptsecurity/mosaic/core'; +import { Directionality } from '@angular/cdk/bidi'; import { DoCheck } from '@angular/core'; import { ElementRef } from '@angular/core'; import { ErrorStateMatcher } from '@ptsecurity/mosaic/core'; +import { FocusMonitor } from '@angular/cdk/a11y'; import { FormControlName } from '@angular/forms'; import { FormGroupDirective } from '@angular/forms'; import * as i0 from '@angular/core'; -import * as i4 from '@angular/common'; -import * as i5 from '@angular/cdk/a11y'; -import * as i6 from '@ptsecurity/mosaic/core'; -import * as i7 from '@angular/forms'; +import * as i5 from '@angular/common'; +import * as i6 from '@angular/cdk/a11y'; +import * as i7 from '@ptsecurity/mosaic/core'; +import * as i8 from '@angular/forms'; import { InjectionToken } from '@angular/core'; +import { McFormField } from '@ptsecurity/mosaic/form-field'; import { McFormFieldControl } from '@ptsecurity/mosaic/form-field'; +import { McTooltipTrigger } from '@ptsecurity/mosaic/tooltip'; import { McValidationOptions } from '@ptsecurity/mosaic/core'; import { NgControl } from '@angular/forms'; import { NgForm } from '@angular/forms'; import { NgModel } from '@angular/forms'; +import { NgZone } from '@angular/core'; import { OnChanges } from '@angular/core'; import { OnDestroy } from '@angular/core'; +import { Overlay } from '@angular/cdk/overlay'; import { Provider } from '@angular/core'; +import { ScrollDispatcher } from '@angular/cdk/overlay'; import { SimpleChanges } from '@angular/core'; import { Subject } from 'rxjs'; +import { TemplateRef } from '@angular/core'; import { ValidationErrors } from '@angular/forms'; import { Validator } from '@angular/forms'; +import { ViewContainerRef } from '@angular/core'; // @public (undocumented) export function add(value1: number, value2: number): number; @@ -159,9 +168,10 @@ export class McInputModule { // Warning: (ae-forgotten-export) The symbol "i1" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "i2" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "i3" needs to be exported by the entry point index.d.ts + // Warning: (ae-forgotten-export) The symbol "i4" needs to be exported by the entry point index.d.ts // // (undocumented) - static ɵmod: i0.ɵɵNgModuleDeclaration; + static ɵmod: i0.ɵɵNgModuleDeclaration; } // @public (undocumented) @@ -172,6 +182,62 @@ export class McInputMono { static ɵfac: i0.ɵɵFactoryDeclaration; } +// @public (undocumented) +export class McInputPassword extends McInputMixinBase implements McFormFieldControl, OnChanges, OnDestroy, DoCheck, CanUpdateErrorState, AfterContentInit, OnChanges { + constructor(elementRef: ElementRef, rawValidators: Validator[], mcValidation: McValidationOptions, ngControl: NgControl, ngModel: NgModel, formControlName: FormControlName, parentForm: NgForm, parentFormGroup: FormGroupDirective, defaultErrorStateMatcher: ErrorStateMatcher, inputValueAccessor: any); + controlType: string; + protected dirtyCheckNativeValue(): void; + get disabled(): boolean; + set disabled(value: boolean); + // (undocumented) + protected elementRef: ElementRef; + // (undocumented) + elementType: string; + get empty(): boolean; + errorStateMatcher: ErrorStateMatcher; + focus(): void; + focusChanged(isFocused: boolean): void; + focused: boolean; + // (undocumented) + formControlName: FormControlName; + get id(): string; + set id(value: string); + protected isBadInput(): boolean; + // (undocumented) + ngAfterContentInit(): void; + // (undocumented) + ngDoCheck(): void; + // (undocumented) + ngModel: NgModel; + // (undocumented) + ngOnChanges(): void; + // (undocumented) + ngOnDestroy(): void; + // (undocumented) + onBlur(): void; + onContainerClick(): void; + // (undocumented) + onInput(): void; + placeholder: string; + // (undocumented) + protected previousNativeValue: any; + // (undocumented) + rawValidators: Validator[]; + get required(): boolean; + set required(value: boolean); + readonly stateChanges: Subject; + // (undocumented) + toggleType(): void; + // (undocumented) + protected uid: string; + get value(): string; + set value(value: string); + // (undocumented) + static ɵdir: i0.ɵɵDirectiveDeclaration; + // (undocumented) + static ɵfac: i0.ɵɵFactoryDeclaration; +} + // @public (undocumented) export class McNumberInput { constructor(elementRef: ElementRef, ngControl: NgControl, step: string, bigStep: string, min: string, max: string); @@ -207,6 +273,34 @@ export class McNumberInput { static ɵfac: i0.ɵɵFactoryDeclaration; } +// @public (undocumented) +export class McPasswordToggle extends McTooltipTrigger implements OnDestroy { + constructor(overlay: Overlay, elementRef: ElementRef, ngZone: NgZone, scrollDispatcher: ScrollDispatcher, hostView: ViewContainerRef, scrollStrategy: any, direction: Directionality, focusMonitor: FocusMonitor, formField: McFormField); + // (undocumented) + get content(): string | TemplateRef; + set content(content: string | TemplateRef); + // (undocumented) + get disabled(): any; + set disabled(value: any); + // (undocumented) + protected _disabled: boolean; + // (undocumented) + get hidden(): boolean; + // (undocumented) + mcTooltipHidden: string | TemplateRef; + // (undocumented) + ngOnDestroy(): void; + // (undocumented) + get tabIndex(): number; + set tabIndex(value: number); + // (undocumented) + toggle(): void; + // (undocumented) + static ɵcmp: i0.ɵɵComponentDeclaration; + // (undocumented) + static ɵfac: i0.ɵɵFactoryDeclaration; +} + // @public (undocumented) export const MIN_VALIDATOR: Provider; diff --git a/yarn.lock b/yarn.lock index f16e16d0a..7e8a7990a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1931,10 +1931,10 @@ resolved "https://registry.yarnpkg.com/@ptsecurity/commitlint-config/-/commitlint-config-1.0.0.tgz#3939ccb03de8a0f2165d6185de0de794d2851ccc" integrity sha512-Cih2McIfrIkZL6iTvkzRVhPSjersHixQ8wbbWnTNG3s4pu0l7Kd0Fh4lL+vqIKMK8Wxibb2SG7RnMb6Ax6uVWw== -"@ptsecurity/mosaic-icons@6.1.1": - version "6.1.1" - resolved "https://registry.yarnpkg.com/@ptsecurity/mosaic-icons/-/mosaic-icons-6.1.1.tgz#266b4149b57a8ba66b63d8cfc6147b71bc0b5869" - integrity sha512-b9U4wiMl12fuuYZAHTME/x+KpiDe3NF8+KtqS7jBG/vMZls6MzAPDBrzU4Uf21Tb4Cc7RSwxBvMpHsbZd3BF1w== +"@ptsecurity/mosaic-icons@6.2.0": + version "6.2.0" + resolved "https://registry.yarnpkg.com/@ptsecurity/mosaic-icons/-/mosaic-icons-6.2.0.tgz#a0b355236d51c3bce6c96be42fe46fb17ef8a3e8" + integrity sha512-//C6bkAU3UawwZRjt5Hfx91AosnrAnO7J8aoB4T9Q4E60gdzrDWAfwG8WglILh5DLi1ItnaeYuwVure+4+BVXw== "@ptsecurity/tslint-config@0.13.1": version "0.13.1"