bootstrap-italia
Version:
Bootstrap Italia è un tema Bootstrap 5 per la creazione di applicazioni web nel pieno rispetto delle linee guida di design per i siti internet e i servizi digitali della PA
492 lines (433 loc) • 15.1 kB
JavaScript
import BaseComponent from './base-component.js';
import EventHandler from './dom/event-handler.js';
import SelectorEngine from './dom/selector-engine.js';
import Manipulator from './dom/manipulator.js';
import InputLabel from './input-label.js';
/**
* --------------------------------------------------------------------------
* Bootstrap Italia (https://italia.github.io/bootstrap-italia/)
* Authors: https://github.com/italia/bootstrap-italia/blob/main/AUTHORS
* Licensed under BSD-3-Clause license (https://github.com/italia/bootstrap-italia/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
const NAME = 'inputpassword';
const DATA_KEY = 'bs.inputpassword';
const EVENT_KEY = `.${DATA_KEY}`;
const DATA_API_KEY = '.data-api';
const Default = {
shortPass: 'Password troppo breve',
badPass: 'Password debole.',
goodPass: 'Password abbastanza sicura.',
strongPass: 'Password sicura.',
minimumLength: 8,
suggestionsLabel: 'Suggerimenti per una buona password:',
suggestionFollowed: 'suggerimenti seguito',
suggestionFollowedPlural: 'suggerimenti seguiti',
suggestionOf: 'di',
suggestionMetLabel: 'Soddisfatto: ',
suggestionMetIconPath: `
M9.6 16.9 4 11.4l.8-.7 4.8 4.8 8.5-8.4.7.7-9.2 9.1z
`,
suggestionLength: 'Almeno {minLength} caratteri.',
suggestionUppercase: 'Una o più maiuscole.',
suggestionLowercase: 'Una o più minuscole.',
suggestionNumber: 'Uno o più numeri.',
suggestionSpecial: 'Uno o più caratteri speciali.',
};
const EVENT_CLICK = `click${EVENT_KEY}`;
const EVENT_KEYUP = `keyup${EVENT_KEY}`;
const EVENT_SCORE = `score${EVENT_KEY}`;
const EVENT_TEXT = `text${EVENT_KEY}`;
const EVENT_SUGGS = `suggs${EVENT_KEY}`;
const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`;
const EVENT_MOUSEDOWN_DATA_API = `mousedown${EVENT_KEY}${DATA_API_KEY}`;
const EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY}${DATA_API_KEY}`;
const CLASS_NAME_PASSWORD = 'input-password';
const SELECTOR_PASSWORD = 'input[data-bs-input][type="password"]';
const SELECTOR_BTN_SHOW_PWD = '.password-icon';
const SELECTOR_METER = '.password-strength-meter';
const SELECTOR_METER_GRAYBAR = '.password-meter';
const SELECTOR_METER_COLBAR = '.progress-bar';
const SELECTOR_METER_TEXT = '.strength-meter-info';
const SELECTOR_METER_SUGGS = '.strenght-meter-suggestions';
class InputPassword extends BaseComponent {
constructor(element, config) {
super(element);
this._config = this._getConfig(config);
this._isCustom = this._element.classList.contains(CLASS_NAME_PASSWORD);
this._meter = this._element.parentNode.querySelector(SELECTOR_METER);
this._grayBarElement = null;
this._colorBarElement = null;
this._textElement = null;
this._suggsElement = null;
this._showPwdElement = null;
this._text = {};
this._label = new InputLabel(element);
this._suggestions = [
{
key: 'length',
text: (config) => config.suggestionLength.replace('{minLength}', config.minimumLength.toString()),
test: (password, config) => password.length >= config.minimumLength,
},
{
key: 'uppercase',
text: (config) => config.suggestionUppercase,
test: (password) => /[A-Z]/.test(password),
},
{
key: 'lowercase',
text: (config) => config.suggestionLowercase,
test: (password) => /[a-z]/.test(password),
},
{
key: 'number',
text: (config) => config.suggestionNumber,
test: (password) => /[0-9]/.test(password),
},
{
key: 'special',
text: (config) => config.suggestionSpecial,
test: (password) => /[^A-Za-z0-9]/.test(password),
},
];
this._init();
this._bindEvents();
}
// Getters
static get NAME() {
return NAME
}
// Private
_getConfig(config) {
config = {
...Default,
...Manipulator.getDataAttributes(this._element),
...(typeof config === 'object' ? config : {}),
};
return config
}
_init() {
if (this._isCustom) {
this._handleAutofill();
this._showPwdElement = SelectorEngine.findOne(SELECTOR_BTN_SHOW_PWD, this._element.parentNode);
if (this._meter) {
this._grayBarElement = this._meter.querySelector(SELECTOR_METER_GRAYBAR);
this._colorBarElement = this._meter.querySelector(SELECTOR_METER_COLBAR);
this._textElement = this._meter.querySelector(SELECTOR_METER_TEXT);
this._suggsElement = this._meter.querySelector(SELECTOR_METER_SUGGS);
if (this._textElement) {
this._config = Object.assign({}, this._config, { ...Manipulator.getDataAttributes(this._textElement) });
}
if (this._suggsElement) {
this._createsuggestionsList();
}
}
}
}
_bindEvents() {
if (this._isCustom) {
if (this._showPwdElement) {
EventHandler.on(this._showPwdElement, EVENT_CLICK, () => this._toggleShowPassword());
}
if (this._meter) {
EventHandler.on(this._element, EVENT_KEYUP, () => this._checkPassword());
EventHandler.on(this._element, 'input', () => this._checkPassword());
}
}
}
_handleAutofill() {
const checkAndActivate = () => {
if (this._element.value !== '') {
this._label._labelOut();
this._checkPassword();
}
};
checkAndActivate();
setTimeout(checkAndActivate, 100);
EventHandler.on(this._element, 'animationstart', (event) => {
if (event.animationName === 'onAutoFillStart') {
checkAndActivate();
}
});
}
_toggleShowPassword() {
const toShow = this._element.getAttribute('type') === 'password';
SelectorEngine.find('[class^="password-icon"]', this._showPwdElement).forEach((icon) => icon.classList.toggle('d-none'));
this._element.setAttribute('type', toShow ? 'text' : 'password');
this._showPwdElement.setAttribute('aria-checked', toShow.toString());
}
_checkPassword() {
const password = this._element.value;
const score = this._calculateScore(password);
this._updateMeter(score);
this._updateText(score, password);
this._updateSuggestions(password);
}
_updateMeter(score) {
const perc = score < 0 ? 0 : score;
if (this._colorBarElement) {
this._colorBarElement.classList.forEach((className) => {
if (className.match(/(^|\s)bg-\S+/g)) {
this._colorBarElement.classList.remove(className);
}
});
this._colorBarElement.classList.add(`bg-${this._scoreColor(score)}`);
this._colorBarElement.style.width = `${perc}%`;
this._colorBarElement.setAttribute('aria-valuenow', perc);
}
EventHandler.trigger(this._element, EVENT_SCORE);
}
_updateText(score, password) {
if (this._textElement) {
let text = this._scoreText(score);
if (this._suggsElement) {
const { completedCount, totalCount } = this._getCompletedSuggestions(password);
const suggestionText = completedCount === 1 ? this._config.suggestionFollowed : this._config.suggestionFollowedPlural;
text += ` ${completedCount} ${this._config.suggestionOf} ${totalCount} ${suggestionText}.`;
}
if (this._textElement.textContent !== text) {
this._textElement.textContent = text;
this._textElement.classList.forEach((className) => {
if (className.match(/(^|\s)text-\S+/g)) {
this._textElement.classList.remove(className);
}
});
this._textElement.classList.add(`text-${this._scoreColor(score)}`);
EventHandler.trigger(this._element, EVENT_TEXT);
}
}
}
_updateSuggestions(password) {
if (this._suggsElement) {
this._updateSuggestionsList(password);
EventHandler.trigger(this._element, EVENT_SUGGS);
}
}
_getCompletedSuggestions(password) {
let completedCount = 0;
const totalCount = this._suggestions.length;
this._suggestions.forEach((sugg) => {
if (sugg.test(password, this._config)) completedCount++;
});
return { completedCount, totalCount }
}
_createsuggestionsList() {
if (this._suggsElement.querySelector('.password-suggestions')) {
return
}
const suggLabel = document.createElement('label');
suggLabel.className = 'visually-hidden';
suggLabel.htmlFor = 'suggestions';
suggLabel.textContent = Default.suggestionsLabel;
const suggContainer = document.createElement('div');
suggContainer.id = 'suggestions';
suggContainer.className = 'password-suggestions';
this._suggestions.forEach((sugg) => {
const suggElement = document.createElement('div');
suggElement.className = 'suggestion';
suggElement.dataset.suggestion = sugg.key;
const checkIcon = this._createIconCheck();
const textSpan = document.createElement('span');
textSpan.textContent = sugg.text(this._config);
suggElement.appendChild(checkIcon);
suggElement.appendChild(textSpan);
suggContainer.appendChild(suggElement);
});
this._suggsElement.appendChild(suggLabel);
this._suggsElement.appendChild(suggContainer);
}
_createIconCheck() {
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('class', `icon icon-xs me-1 d-none`);
svg.setAttribute('aria-label', this._config.suggestionMetLabel);
svg.setAttribute('viewBox', '0 0 24 24');
svg.style.width = '1em';
svg.style.height = '1em';
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
path.setAttribute('d', this._config.suggestionMetIconPath.trim());
svg.appendChild(path);
return svg
}
_updateSuggestionsList(password) {
if (!this._suggsElement) return
this._suggestions.forEach((sugg) => {
const suggElement = this._suggsElement.querySelector(`[data-suggestion="${sugg.key}"]`);
if (suggElement) {
const isMet = sugg.test(password, this._config);
const checkIcon = suggElement.querySelector('.icon');
if (checkIcon) {
checkIcon.classList.toggle('d-none', !isMet);
}
}
});
}
/**
* Returns strings based on the score given.
*
* @param int score Score base.
* @return string
*/
_scoreText(score) {
if (score === -1) {
return this._config.shortPass
}
if (score === -2) {
return ''
}
score = score < 0 ? 0 : score;
if (score < 26) {
return this._config.badPass
}
if (score < 51) {
return this._config.badPass
}
if (score < 76) {
return this._config.goodPass
}
return this._config.strongPass
}
_scoreColor(score) {
if (score === -1 || score === -2 || score < 26) {
return 'danger'
}
if (score < 51) {
return 'warning'
}
if (score < 76) {
return 'success'
}
return 'success'
}
/**
* Returns a value between -1 and 100 to score
* the user's password.
*
* @param string password The password to be checked.
* @return int
*/
_calculateScore(password) {
var score = 0;
// empty password
if (password.trim().length === 0) {
return -2
}
// password < this._config.minimumLength
if (password.length < this._config.minimumLength) {
return -1
}
// password length
score += password.length * 4;
score += this._checkRepetition(1, password).length - password.length;
score += this._checkRepetition(2, password).length - password.length;
score += this._checkRepetition(3, password).length - password.length;
score += this._checkRepetition(4, password).length - password.length;
// password has 3 numbers
if (password.match(/(.*[0-9].*[0-9].*[0-9])/)) {
score += 5;
}
// password has at least 2 symbols
var symbols = '.*[!,@,#,$,%,^,&,*,?,_,~]';
symbols = new RegExp('(' + symbols + symbols + ')');
if (password.match(symbols)) {
score += 5;
}
// password has Upper and Lower chars
if (password.match(/([a-z].*[A-Z])|([A-Z].*[a-z])/)) {
score += 10;
}
// password has number and chars
if (password.match(/([a-zA-Z])/) && password.match(/([0-9])/)) {
score += 15;
}
// password has number and symbol
if (password.match(/([!,@,#,$,%,^,&,*,?,_,~])/) && password.match(/([0-9])/)) {
score += 15;
}
// password has char and symbol
if (password.match(/([!,@,#,$,%,^,&,*,?,_,~])/) && password.match(/([a-zA-Z])/)) {
score += 15;
}
// password is just numbers or chars
if (password.match(/^\w+$/) || password.match(/^\d+$/)) {
score -= 10;
}
if (score > 100) {
score = 100;
}
if (score < 0) {
score = 0;
}
return score
}
/**
* Checks for repetition of characters in
* a string
*
* @param int rLen Repetition length.
* @param string str The string to be checked.
* @return string
*/
_checkRepetition(rLen, str) {
var res = '',
repeated = false;
for (var i = 0; i < str.length; i++) {
repeated = true;
for (var j = 0; j < rLen && j + i + rLen < str.length; j++) {
repeated = repeated && str.charAt(j + i) === str.charAt(j + i + rLen);
}
if (j < rLen) {
repeated = false;
}
if (repeated) {
i += rLen - 1;
repeated = false;
} else {
res += str.charAt(i);
}
}
return res
}
}
/**
* ------------------------------------------------------------------------
* Data Api implementation
* ------------------------------------------------------------------------
*/
if (typeof window !== 'undefined' && typeof document !== 'undefined') {
const createInput = (element) => {
if (element && element.matches(SELECTOR_PASSWORD)) {
return InputPassword.getOrCreateInstance(element)
}
return null
};
const initInputPassword = () => {
const element = SelectorEngine.findOne(SELECTOR_PASSWORD);
if (element) {
InputPassword.getOrCreateInstance(element);
}
};
if (document.readyState !== 'loading') {
initInputPassword();
} else {
document.addEventListener('DOMContentLoaded', initInputPassword);
}
EventHandler.on(document, EVENT_MOUSEDOWN_DATA_API, SELECTOR_PASSWORD + ', label', function () {
const target = InputLabel.getInputFromLabel(this) || this;
createInput(target);
});
EventHandler.on(document, EVENT_KEYUP_DATA_API, SELECTOR_PASSWORD + ', label', function () {
const target = InputLabel.getInputFromLabel(this) || this;
const element = createInput(target);
if (element && element._label) {
element._label._labelOut();
}
});
EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_BTN_SHOW_PWD, function () {
const target = this.parentNode && this.parentNode.querySelector(SELECTOR_PASSWORD);
if (target) {
InputPassword.getOrCreateInstance(target);
}
});
}
export { InputPassword as default };
//# sourceMappingURL=input-password.js.map