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 символов
+
+ Заглавная латинская буква
+
+ Строчная латинская буква
+
+ Цифра
+
+ Только латинские буквы, цифры, пробелы и спецсимволы
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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"