@claspo/components 1.6.3 → 1.7.0-components-build-perf
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -2
- package/script/SysButtonComponent/SysButton.manifest.js +126 -0
- package/script/SysButtonComponent/SysButton.styles.js +64 -0
- package/script/SysButtonComponent/SysButtonComponent.js +231 -0
- package/script/SysColumnComponent/SysColumn.manifest.js +17 -0
- package/script/SysColumnComponent/SysColumnComponent.js +107 -0
- package/script/SysColumnsComponent/SysColumns.manifest.js +17 -0
- package/script/SysColumnsComponent/SysColumnsComponent.js +53 -0
- package/script/SysColumnsComponent/getStyleElement.js +23 -0
- package/script/SysContainerComponent/SysBaseContainerComponent.js +41 -0
- package/script/SysContainerComponent/SysContainer.manifest.js +18 -0
- package/script/SysContainerComponent/SysContainerComponent.js +86 -0
- package/script/SysImageComponent/SysImage.manifest.js +18 -0
- package/script/SysImageComponent/SysImageComponent.js +378 -0
- package/script/SysImageComponent/getStyleElement.js +18 -0
- package/script/SysInputComponent/EmailSuggesting.js +252 -0
- package/script/SysInputComponent/InputFormControl.js +136 -0
- package/script/SysInputComponent/SysInput.manifest.js +728 -0
- package/script/SysInputComponent/SysInputComponent.js +84 -0
- package/script/SysInputComponent/emailProvidersList.js +158 -0
- package/script/SysInputComponent/getOverlayStyles.js +220 -0
- package/script/SysInputComponent/getStyleElement.js +69 -0
- package/script/SysInputComponent/inputValidators.js +293 -0
- package/script/SysTextComponent/SysText.manifest.js +29 -0
- package/script/SysTextComponent/SysTextComponent.js +147 -0
- package/script/SysTextComponent/TextRoller.js +298 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import getOverlayStyles from './getOverlayStyles.js';
|
|
2
|
+
import waitForKeyboardHide from '@claspo/renderer/common/WaitForKeyboardHide';
|
|
3
|
+
import { createMenuOverlay, getMenuOverlayContentClassName } from '@claspo/renderer/sdk/OverlayUtils';
|
|
4
|
+
import emailProvidersList from './emailProvidersList.js';
|
|
5
|
+
|
|
6
|
+
class EmailSuggesting {
|
|
7
|
+
|
|
8
|
+
lastEmailSuggestionCheckedValue = null;
|
|
9
|
+
emailSuggestionOverlayBackdrop = null;
|
|
10
|
+
|
|
11
|
+
suggestEmail(
|
|
12
|
+
props,
|
|
13
|
+
inputElement,
|
|
14
|
+
shouldSkipLastEmailSuggestionCheck,
|
|
15
|
+
formService,
|
|
16
|
+
control,
|
|
17
|
+
getCurrentLanguageMap,
|
|
18
|
+
htmlDocumentObject,
|
|
19
|
+
) {
|
|
20
|
+
if (
|
|
21
|
+
!inputElement.value ||
|
|
22
|
+
(this.lastEmailSuggestionCheckedValue === inputElement.value && !shouldSkipLastEmailSuggestionCheck)
|
|
23
|
+
) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (props.control.validation && props.control.validation.restrictFreeDomains) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
this.lastEmailSuggestionCheckedValue = inputElement.value;
|
|
32
|
+
|
|
33
|
+
if (!control.elementRef.classList.contains('invalid')) {
|
|
34
|
+
if (emailProvidersList.includes(inputElement.value.toLowerCase().split('@')[1])) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const suggestion = EmailSuggesting.suggestEmailSync(inputElement.value);
|
|
39
|
+
const createSuggestionOverlay = (suggestion) => {
|
|
40
|
+
this.createSuggestionOverlay(
|
|
41
|
+
suggestion,
|
|
42
|
+
inputElement,
|
|
43
|
+
control,
|
|
44
|
+
getCurrentLanguageMap,
|
|
45
|
+
htmlDocumentObject,
|
|
46
|
+
);
|
|
47
|
+
EmailSuggesting.preventSubmit(formService);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
if (suggestion) {
|
|
51
|
+
createSuggestionOverlay(suggestion);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
emailValueChanged() {
|
|
57
|
+
this.lastEmailSuggestionCheckedValue = null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
hideEmailSuggestion() {
|
|
61
|
+
if (this.emailSuggestionOverlayBackdrop) {
|
|
62
|
+
this.emailSuggestionOverlayBackdrop.click();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
createSuggestionOverlay(
|
|
67
|
+
suggestion,
|
|
68
|
+
inputElement,
|
|
69
|
+
control,
|
|
70
|
+
getCurrentLanguageMap,
|
|
71
|
+
htmlDocumentObject
|
|
72
|
+
) {
|
|
73
|
+
this.hideEmailSuggestion();
|
|
74
|
+
|
|
75
|
+
waitForKeyboardHide(() => {
|
|
76
|
+
const overlay = createMenuOverlay({
|
|
77
|
+
triggerElement: inputElement,
|
|
78
|
+
overlayStyles: getOverlayStyles(getMenuOverlayContentClassName()),
|
|
79
|
+
createOverlayContent: (backdrop, overlayContentContainer) => {
|
|
80
|
+
EmailSuggesting.createOverlayContent(suggestion, backdrop, overlayContentContainer, control, getCurrentLanguageMap);
|
|
81
|
+
},
|
|
82
|
+
overlayWidth: 220,
|
|
83
|
+
overlayHeight: 90,
|
|
84
|
+
positionByDefault: 'top',
|
|
85
|
+
isHorizontallyCentered: true,
|
|
86
|
+
isBackdropDisabledOnUI: true,
|
|
87
|
+
onDestroy: () => {
|
|
88
|
+
this.emailSuggestionOverlayBackdrop = null;
|
|
89
|
+
},
|
|
90
|
+
offset: 10,
|
|
91
|
+
htmlDocumentObject: htmlDocumentObject,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
this.emailSuggestionOverlayBackdrop = overlay.backdrop;
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
static createOverlayContent(suggestion, backdrop, overlayContentContainer, control, getCurrentLanguageMap) {
|
|
99
|
+
const componentLanguageMap = getCurrentLanguageMap();
|
|
100
|
+
|
|
101
|
+
const textContainer = document.createElement('div');
|
|
102
|
+
textContainer.classList.add('suggestion-text-container');
|
|
103
|
+
const didYouMeanTextNode = document.createElement('div');
|
|
104
|
+
didYouMeanTextNode.classList.add('did-you-mean-text');
|
|
105
|
+
// Avoid injecting HTML from translations
|
|
106
|
+
didYouMeanTextNode.textContent = componentLanguageMap['content,suggestionLabel'];
|
|
107
|
+
const suggestionNode = document.createElement('div');
|
|
108
|
+
suggestionNode.classList.add('suggestion-text');
|
|
109
|
+
suggestionNode.textContent = `${suggestion}?`;
|
|
110
|
+
const acceptButtonNode = document.createElement('div');
|
|
111
|
+
acceptButtonNode.classList.add('accept-button');
|
|
112
|
+
const denyButtonNode = document.createElement('div');
|
|
113
|
+
denyButtonNode.classList.add('deny-button');
|
|
114
|
+
|
|
115
|
+
textContainer.appendChild(didYouMeanTextNode);
|
|
116
|
+
textContainer.appendChild(suggestionNode);
|
|
117
|
+
|
|
118
|
+
acceptButtonNode.addEventListener('click', () => {
|
|
119
|
+
const leftEmailPart = control.value.split('@')[0];
|
|
120
|
+
control.setValue(`${leftEmailPart}@${suggestion}`);
|
|
121
|
+
backdrop.click();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
denyButtonNode.addEventListener('click', () => {
|
|
125
|
+
backdrop.click();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
overlayContentContainer.appendChild(textContainer);
|
|
129
|
+
overlayContentContainer.appendChild(acceptButtonNode);
|
|
130
|
+
overlayContentContainer.appendChild(denyButtonNode);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
static preventSubmit(formService) {
|
|
134
|
+
const submitBlockTime = 200; // To prevent submit if suggestion exist
|
|
135
|
+
formService.setPreventSubmit(true);
|
|
136
|
+
setTimeout(() => formService.setPreventSubmit(false), submitBlockTime);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
static suggestEmailSync(inputValue) {
|
|
140
|
+
const parsedInputValue = inputValue.toLowerCase();
|
|
141
|
+
const domainPartOfInput = parsedInputValue.split('@')[1] || '';
|
|
142
|
+
|
|
143
|
+
// If the domain is already in the list, no suggestion needed
|
|
144
|
+
if (emailProvidersList.indexOf(domainPartOfInput) > -1) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Check for dot placement issues
|
|
149
|
+
const dotSuggestion = EmailSuggesting.getSuggestionAfterDotChecks(domainPartOfInput);
|
|
150
|
+
if (dotSuggestion) {
|
|
151
|
+
return dotSuggestion;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// If domain doesn't have a dot, it's likely incomplete
|
|
155
|
+
if (!domainPartOfInput.includes('.')) {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Calculate Levenshtein distance for each provider domain
|
|
160
|
+
const distances = emailProvidersList.map(provider => ({
|
|
161
|
+
provider,
|
|
162
|
+
distance: EmailSuggesting.getLevenshteinDistance(provider, domainPartOfInput)
|
|
163
|
+
}));
|
|
164
|
+
|
|
165
|
+
// Find the minimum distance
|
|
166
|
+
const minDistance = Math.min(...distances.map(item => item.distance));
|
|
167
|
+
|
|
168
|
+
// If the minimum distance is too large, don't suggest anything
|
|
169
|
+
if (minDistance > 2) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Get all providers with the minimum distance
|
|
174
|
+
const closestProviders = distances
|
|
175
|
+
.filter(item => item.distance === minDistance)
|
|
176
|
+
.map(item => item.provider);
|
|
177
|
+
|
|
178
|
+
// Check for character transposition (two characters in wrong order)
|
|
179
|
+
const twoCharsWrongOrderSuggestion = EmailSuggesting.isTwoCharsWrongOrder(closestProviders, domainPartOfInput);
|
|
180
|
+
|
|
181
|
+
// If distance is small enough, suggest the closest provider
|
|
182
|
+
if (minDistance <= 1) {
|
|
183
|
+
return closestProviders[0];
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// If distance is 2 but there's a character transposition, suggest it
|
|
187
|
+
if (minDistance === 2 && twoCharsWrongOrderSuggestion) {
|
|
188
|
+
return twoCharsWrongOrderSuggestion;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
static getSuggestionAfterDotChecks(inputValue) {
|
|
195
|
+
return emailProvidersList.find(provider => EmailSuggesting.isDotLocatedInWrongPlace(provider, inputValue));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
static isDotLocatedInWrongPlace(provider, inputValue) {
|
|
199
|
+
if (!inputValue) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return inputValue.replace('.', '') === provider.replace('.', '');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
static getLevenshteinDistance(s, t) { // Levenshtein Distance algorithm
|
|
207
|
+
if (!s.length) return t.length;
|
|
208
|
+
if (!t.length) return s.length;
|
|
209
|
+
const arr = [];
|
|
210
|
+
for (let i = 0; i <= t.length; i++) {
|
|
211
|
+
arr[i] = [i];
|
|
212
|
+
for (let j = 1; j <= s.length; j++) {
|
|
213
|
+
arr[i][j] =
|
|
214
|
+
i === 0
|
|
215
|
+
? j
|
|
216
|
+
: Math.min(
|
|
217
|
+
arr[i - 1][j] + 1,
|
|
218
|
+
arr[i][j - 1] + 1,
|
|
219
|
+
arr[i - 1][j - 1] + (s[j - 1] === t[i - 1] ? 0 : 1)
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return arr[t.length][s.length];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
static getAllMinimalDistanceDomains(minimalDistance, stringDistances, secondLevelDomainsList) {
|
|
227
|
+
const minimalDistanceIndexes = stringDistances.reduce((acc, el, i) => {
|
|
228
|
+
if (el === minimalDistance) {
|
|
229
|
+
acc.push(i);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return acc;
|
|
233
|
+
}, []);
|
|
234
|
+
|
|
235
|
+
return secondLevelDomainsList.filter((_, i) => minimalDistanceIndexes.includes(i));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
static isTwoCharsWrongOrder(suggestionCandidates, domain) {
|
|
239
|
+
return suggestionCandidates.find(candidate => {
|
|
240
|
+
const firstDifferentCharIndex = EmailSuggesting.findIndexOfFirstDiffInTwoStrings(domain, candidate);
|
|
241
|
+
|
|
242
|
+
return domain.charAt(firstDifferentCharIndex) === candidate.charAt(firstDifferentCharIndex + 1) &&
|
|
243
|
+
candidate.charAt(firstDifferentCharIndex) === domain.charAt(firstDifferentCharIndex + 1);
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
static findIndexOfFirstDiffInTwoStrings(str1, str2) {
|
|
248
|
+
return [...str1].findIndex((el, i) => el !== str2[i]);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export { EmailSuggesting };
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import InputValidators from './inputValidators.js';
|
|
2
|
+
import { EmailSuggesting } from './EmailSuggesting.js';
|
|
3
|
+
import { getTranslationsMap, getWidgetLanguages } from '@claspo/renderer/sdk/TranslationUtils';
|
|
4
|
+
|
|
5
|
+
class InputFormControl {
|
|
6
|
+
|
|
7
|
+
getModel = null;
|
|
8
|
+
getProps = null;
|
|
9
|
+
getRootElement = null;
|
|
10
|
+
formService = null;
|
|
11
|
+
configService = null;
|
|
12
|
+
utils = null;
|
|
13
|
+
emailSuggesting = new EmailSuggesting();
|
|
14
|
+
i18n = null;
|
|
15
|
+
forbiddenChar = '';
|
|
16
|
+
|
|
17
|
+
constructor(
|
|
18
|
+
controlFactory,
|
|
19
|
+
getModel,
|
|
20
|
+
getProps,
|
|
21
|
+
getRootElement,
|
|
22
|
+
formService,
|
|
23
|
+
configService,
|
|
24
|
+
i18n,
|
|
25
|
+
htmlDocumentObject,
|
|
26
|
+
) {
|
|
27
|
+
this.controlFactory = controlFactory;
|
|
28
|
+
this.componentId = getModel().id;
|
|
29
|
+
this.getProps = getProps;
|
|
30
|
+
this.getModel = getModel;
|
|
31
|
+
this.getRootElement = getRootElement;
|
|
32
|
+
this.formService = formService;
|
|
33
|
+
this.configService = configService;
|
|
34
|
+
this.i18n = i18n;
|
|
35
|
+
this.htmlDocumentObject = htmlDocumentObject;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
init() {
|
|
39
|
+
const props = this.getProps();
|
|
40
|
+
|
|
41
|
+
const rootElement = this.getRootElement();
|
|
42
|
+
rootElement.querySelector('.input-tooltip');
|
|
43
|
+
const inputElement = rootElement.querySelector('input');
|
|
44
|
+
|
|
45
|
+
if (props.control.name === 'first_name') {
|
|
46
|
+
inputElement.setAttribute('name', 'name');
|
|
47
|
+
inputElement.setAttribute('autocomplete', 'off');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (props.control.name === 'email') {
|
|
51
|
+
inputElement.setAttribute('name', 'email');
|
|
52
|
+
inputElement.setAttribute('autocomplete', 'on');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const validationMap = {
|
|
56
|
+
...Object
|
|
57
|
+
.keys(InputValidators.validationMap)
|
|
58
|
+
.reduce((acc, key) => {
|
|
59
|
+
acc[key] = (...args) => {
|
|
60
|
+
const result = InputValidators.validationMap[key](...args);
|
|
61
|
+
|
|
62
|
+
if (result.forbiddenChar !== undefined) {
|
|
63
|
+
this.forbiddenChar = result.forbiddenChar;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return result;
|
|
67
|
+
};
|
|
68
|
+
return acc;
|
|
69
|
+
}, {}),
|
|
70
|
+
EMAIL: (value) => InputValidators.validationMap.EMAIL(value, this.getProps()),
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const control = this.controlFactory([], {
|
|
74
|
+
validationMap,
|
|
75
|
+
validCallback: () => {
|
|
76
|
+
if (props.control.name === 'email' && rootElement.activeElement !== inputElement) {
|
|
77
|
+
this.suggestEmailFix(inputElement, control, true);
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
errorMessageMapper: (firstErrorKey, translatedValue) => {
|
|
81
|
+
if (firstErrorKey === InputValidators.validationErrorKeys.CONTAINS_FORBIDDEN_CHARACTERS) {
|
|
82
|
+
return translatedValue.replace('{{characters}}', this.forbiddenChar);
|
|
83
|
+
} else {
|
|
84
|
+
return translatedValue;
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
if (props.control.name === 'email') {
|
|
90
|
+
this.listenForEmailValueChanges(control);
|
|
91
|
+
this.suggestEmailFixOnFocusOut(inputElement, control);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
destroy() {
|
|
96
|
+
this.emailSuggesting.hideEmailSuggestion();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
listenForEmailValueChanges(control) {
|
|
100
|
+
control.on('valueChanged', value => {
|
|
101
|
+
this.emailSuggesting.emailValueChanged();
|
|
102
|
+
this.emailSuggesting.hideEmailSuggestion();
|
|
103
|
+
this.formatEmailValue(value, control);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
formatEmailValue(value, control) {
|
|
108
|
+
const formattedValue = value.trim().toLowerCase();
|
|
109
|
+
|
|
110
|
+
if (formattedValue !== value) {
|
|
111
|
+
control.setValue(value.trim().toLowerCase());
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
suggestEmailFixOnFocusOut(inputElement, control) {
|
|
116
|
+
inputElement.addEventListener('focusout', () => this.suggestEmailFix(inputElement, control));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
suggestEmailFix(inputElement, control, shouldSkipLastEmailSuggestionCheck) {
|
|
120
|
+
this.emailSuggesting.suggestEmail(
|
|
121
|
+
this.getProps(),
|
|
122
|
+
inputElement,
|
|
123
|
+
shouldSkipLastEmailSuggestionCheck,
|
|
124
|
+
this.formService,
|
|
125
|
+
control,
|
|
126
|
+
this.getCurrentLanguageMap.bind(this),
|
|
127
|
+
this.htmlDocumentObject,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
getCurrentLanguageMap() {
|
|
132
|
+
return getTranslationsMap(this.i18n, getWidgetLanguages(this.configService)).translations;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export { InputFormControl as default };
|