@descope/web-components-ui 1.0.371 → 1.0.372

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,86 @@
1
+ // since on load we can only sample the color of the placeholder,
2
+ // we need to temporarily populate the input in order to sample the value color
3
+ const getValueColor = (ele, computedStyle) => {
4
+ // eslint-disable-next-line no-param-reassign
5
+ ele.value = '_';
6
+
7
+ const valueColor = computedStyle.getPropertyValue('color');
8
+
9
+ // eslint-disable-next-line no-param-reassign
10
+ ele.value = '';
11
+
12
+ return valueColor;
13
+ };
14
+
15
+ export const createExternalInputSlot = (slotName, targetSlotName) => {
16
+ const slotEle = document.createElement('slot');
17
+
18
+ slotEle.setAttribute('name', slotName);
19
+ slotEle.setAttribute('slot', targetSlotName);
20
+
21
+ return slotEle;
22
+ };
23
+
24
+ export const createExternalInputEle = (targetSlotName, type, autocompleteType) => {
25
+ const inputEle = document.createElement('input');
26
+
27
+ inputEle.setAttribute('slot', targetSlotName);
28
+ inputEle.setAttribute('type', type);
29
+ inputEle.setAttribute('data-hidden-input', 'true');
30
+ inputEle.setAttribute('autocomplete', autocompleteType);
31
+
32
+ return inputEle;
33
+ };
34
+
35
+ // We apply the original input's style to the external-input.
36
+ // Eventually, the user should interact directly with the external input.
37
+ // We keep the original input
38
+ export const applyExternalInputStyles = (sourceInputEle, targetInputEle, labelType) => {
39
+ // We set a timeout here to avoid "Double Print" cases,
40
+ // caused by sampling the computed style before it's ready.
41
+ setTimeout(() => {
42
+ const computedStyle = getComputedStyle(sourceInputEle);
43
+ // Get minimal set of computed theme properties to set on external input
44
+ const height = computedStyle.getPropertyValue('height');
45
+ const paddingLeft = computedStyle.getPropertyValue('padding-left');
46
+ const paddingRight = computedStyle.getPropertyValue('padding-right');
47
+ const fontSize = computedStyle.getPropertyValue('font-size');
48
+ const fontFamily = computedStyle.getPropertyValue('font-family');
49
+ const letterSpacing = computedStyle.getPropertyValue('letter-spacing');
50
+ const caretColor = computedStyle.getPropertyValue('caret-color');
51
+
52
+ const valueColor = getValueColor(sourceInputEle, computedStyle);
53
+
54
+ const commonThemeStyles = [
55
+ ['all', 'unset'],
56
+ ['position', 'absolute'],
57
+ ['background-color', 'transparent'],
58
+ ['height', height],
59
+ ['left', paddingLeft],
60
+ ['right', paddingRight],
61
+ ['font-size', fontSize],
62
+ ['font-family', fontFamily],
63
+ ['letter-spacing', letterSpacing],
64
+ ['caret-color', caretColor], // this is for seeing caret when focusing on external input
65
+ ['color', valueColor],
66
+ ];
67
+
68
+ commonThemeStyles.forEach(([key, val]) =>
69
+ targetInputEle.style.setProperty(key, val, 'important')
70
+ );
71
+
72
+ // Handle floating label theme properties
73
+ if (labelType === 'floating') {
74
+ const marginBottom = computedStyle.getPropertyValue('margin-bottom');
75
+ targetInputEle.style.setProperty('margin-bottom', marginBottom, 'important');
76
+ }
77
+
78
+ // sample and apply width only after original input is ready and fully rendered
79
+ const width = computedStyle.getPropertyValue('width');
80
+ targetInputEle.style.setProperty(
81
+ 'width',
82
+ `calc(${width} - ${paddingLeft} - ${paddingRight}`,
83
+ 'important'
84
+ );
85
+ }, 0);
86
+ };
@@ -0,0 +1,165 @@
1
+ import { syncAttrs } from '../helpers/componentHelpers';
2
+ import {
3
+ applyExternalInputStyles,
4
+ createExternalInputEle,
5
+ createExternalInputSlot,
6
+ } from './externalInputHelpers';
7
+
8
+ export const externalInputMixin =
9
+ ({ inputType, autocompleteType, includeAttrs = [], noBlurDispatch = false }) =>
10
+ (superclass) =>
11
+ class ExternalInputMixinClass extends superclass {
12
+ #timers = [];
13
+
14
+ get isExternalInput() {
15
+ return this.getAttribute('external-input') === 'true';
16
+ }
17
+
18
+ createExternalInput() {
19
+ if (!this.isExternalInput) {
20
+ return null;
21
+ }
22
+
23
+ // use original input element as reference
24
+ const origInput = this.baseElement.querySelector('input');
25
+
26
+ // to avoid focus loop between external-input and origInput
27
+ // we set origInput's tabindex to -1
28
+ // otherwise, shift-tab will never leave the component focus
29
+ origInput.setAttribute('tabindex', '-1');
30
+
31
+ // create external slot
32
+ const externalInputSlot = createExternalInputSlot('external-input', 'suffix');
33
+
34
+ // append external slot to base element
35
+ this.baseElement.appendChild(externalInputSlot);
36
+
37
+ this.externalInput = createExternalInputEle(
38
+ 'external-input',
39
+ inputType,
40
+ this.getAutocompleteType()
41
+ );
42
+
43
+ // apply original input's styles to external input
44
+ setTimeout(() => {
45
+ applyExternalInputStyles(origInput, this.externalInput, this.getAttribute('label-type'));
46
+ });
47
+
48
+ // 1Password catches the internal input, so we forward the value to the external input
49
+ this.forwardInputValue(origInput, this.externalInput);
50
+
51
+ syncAttrs(origInput, this.externalInput, {
52
+ includeAttrs,
53
+ });
54
+
55
+ // We disable Vaadin's original `_setFocused` function, and use handleFocusEvents
56
+ // and handleBlurEvents functions
57
+ this.baseElement
58
+ .querySelector('input')
59
+ .addEventListener('focusout', (e) => e.stopImmediatePropagation(), true);
60
+
61
+ // In order to manage focus/blur events when moving between parts of the component
62
+ // we're managing the event dispatching by ourselves, with the following strategy:
63
+ // - If one of the component parts is focused - it means that the component is still focused - so we stop the blur events.
64
+ // - When none of the component parts is focused, we dispatch blur event.
65
+ this.handleFocusEvents();
66
+ this.handleBlurEvents();
67
+
68
+ // sync input value
69
+ this.handlelInputEvents(this.externalInput);
70
+
71
+ // append external input to component's DOM
72
+ this.appendChild(this.externalInput);
73
+
74
+ return this.externalInput;
75
+ }
76
+
77
+ clearBlurTimers() {
78
+ this.#timers.forEach((timer) => clearTimeout(timer));
79
+ this.#timers.length = 0;
80
+ }
81
+
82
+ dispatchBlur() {
83
+ return setTimeout(() => {
84
+ this.dispatchEvent(new Event('blur', { bubbles: true, composed: true }));
85
+ this.removeAttribute('focused');
86
+ });
87
+ }
88
+
89
+ handleFocusEvents() {
90
+ // on baseElement `focus` we forward the focus to the external input.
91
+ // also, in order to avoid any blur within the component, we clear the blur timers.
92
+ this.baseElement.addEventListener('focus', () => {
93
+ this.externalInput.focus();
94
+ this.clearBlurTimers();
95
+ });
96
+
97
+ // on `focus` of the external input, we manually set the `focused` attribute
98
+ this.externalInput.addEventListener('focus', () => {
99
+ this.clearBlurTimers();
100
+ setTimeout(() => this.baseElement.setAttribute('focused', 'true'));
101
+ });
102
+ }
103
+
104
+ handleBlurEvents() {
105
+ this.baseElement.addEventListener(
106
+ 'blur',
107
+ (e) => {
108
+ e.stopImmediatePropagation();
109
+ // some components do not require this synthetic blur dispatch (e.g. Password)
110
+ // so we allow them to override this dispatch
111
+ if (noBlurDispatch) return;
112
+ this.#timers.push(this.dispatchBlur());
113
+ },
114
+ true
115
+ );
116
+
117
+ this.externalInput.addEventListener(
118
+ 'blur',
119
+ (e) => {
120
+ e.stopImmediatePropagation();
121
+ this.#timers.push(this.dispatchBlur());
122
+ },
123
+ true
124
+ );
125
+ }
126
+
127
+ handlelInputEvents(externalInput) {
128
+ // sync value of insible input back to original input
129
+ externalInput.addEventListener('input', (e) => {
130
+ this.value = e.target.value;
131
+ });
132
+
133
+ // handle has-value attr
134
+ externalInput.addEventListener('input', (e) => {
135
+ if (!e.target.value) {
136
+ this.removeAttribute('has-value');
137
+ } else {
138
+ this.setAttribute('has-value', 'true');
139
+ }
140
+ });
141
+ }
142
+
143
+ getAutocompleteType() {
144
+ return this.getAttribute('autocomplete') || autocompleteType;
145
+ }
146
+
147
+ forwardInputValue(source, target) {
148
+ // set internal sync events
149
+ const valueDescriptor = Object.getOwnPropertyDescriptor(
150
+ this.inputElement.constructor.prototype,
151
+ 'value'
152
+ );
153
+
154
+ Object.defineProperty(source, 'value', {
155
+ ...valueDescriptor,
156
+
157
+ set(v) {
158
+ valueDescriptor.set.call(this, v);
159
+ // eslint-disable-next-line no-param-reassign
160
+ target.value = v;
161
+ },
162
+ configurable: true,
163
+ });
164
+ }
165
+ };
@@ -10,3 +10,4 @@ export { changeMixin } from './changeMixin';
10
10
  export { normalizeBooleanAttributesMixin } from './normalizeBooleanAttributesMixin';
11
11
  export { lifecycleEventsMixin } from './lifecycleEventsMixin';
12
12
  export { inputEventsDispatchingMixin } from './inputEventsDispatchingMixin';
13
+ export { externalInputMixin } from './externalInputMixin';
@@ -42,7 +42,7 @@ const proxyInputMixin =
42
42
  ({
43
43
  proxyProps = [],
44
44
  // useProxyTargets flag allows to forwardProps to other targets, other than
45
- // `this.inputElement`.
45
+ // `this.inputElement`. It's to be used within `external-input` components,
46
46
  // if needed
47
47
  useProxyTargets = false,
48
48
  // allows us to set the event that should trigger validation
@@ -181,7 +181,10 @@ const proxyInputMixin =
181
181
 
182
182
  // sync properties
183
183
  proxyProps.forEach((prop) => {
184
- const proxyTargets = useProxyTargets ? [this.baseElement].filter(Boolean) : [];
184
+ const externalInput = this.querySelector('input[slot="external-input"]') || null;
185
+ const proxyTargets = useProxyTargets
186
+ ? [this.baseElement, externalInput].filter(Boolean)
187
+ : [];
185
188
  forwardProps(this, [this.inputElement, ...proxyTargets], prop);
186
189
  });
187
190