@descope/web-components-ui 1.0.371 → 1.0.372

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.
@@ -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