@descope/web-components-ui 1.0.70 → 1.0.72

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.
Files changed (48) hide show
  1. package/dist/index.esm.js +420 -464
  2. package/dist/index.esm.js.map +1 -1
  3. package/dist/umd/809.js +1 -0
  4. package/dist/umd/descope-button-index-js.js +1 -1
  5. package/dist/umd/descope-checkbox-index-js.js +1 -1
  6. package/dist/umd/descope-combo-box-index-js.js +1 -1
  7. package/dist/umd/descope-container-index-js.js +1 -1
  8. package/dist/umd/descope-date-picker-index-js.js +1 -1
  9. package/dist/umd/descope-divider-index-js.js +1 -1
  10. package/dist/umd/descope-email-field-index-js.js +1 -1
  11. package/dist/umd/descope-image-index-js.js +1 -0
  12. package/dist/umd/descope-link-index-js.js +1 -1
  13. package/dist/umd/descope-loader-linear-index-js.js +1 -1
  14. package/dist/umd/descope-loader-radial-index-js.js +1 -1
  15. package/dist/umd/descope-logo-index-js.js +1 -1
  16. package/dist/umd/descope-number-field-index-js.js +1 -1
  17. package/dist/umd/descope-passcode-descope-passcode-internal-index-js.js +1 -1
  18. package/dist/umd/descope-passcode-index-js.js +1 -1
  19. package/dist/umd/descope-password-field-index-js.js +1 -1
  20. package/dist/umd/descope-switch-toggle-index-js.js +1 -1
  21. package/dist/umd/descope-text-area-index-js.js +1 -1
  22. package/dist/umd/descope-text-field-index-js.js +1 -1
  23. package/dist/umd/descope-text-index-js.js +1 -1
  24. package/dist/umd/index.js +1 -1
  25. package/package.json +1 -1
  26. package/src/baseClasses/createBaseClass.js +29 -3
  27. package/src/baseClasses/createBaseInputClass.js +9 -0
  28. package/src/components/descope-image/Image.js +53 -0
  29. package/src/components/descope-image/index.js +5 -0
  30. package/src/components/descope-passcode/Passcode.js +1 -1
  31. package/src/components/descope-passcode/descope-passcode-internal/PasscodeInternal.js +67 -51
  32. package/src/helpers/mixinsHelpers.js +26 -9
  33. package/src/index.js +1 -0
  34. package/src/mixins/changeMixin.js +13 -39
  35. package/src/mixins/componentNameValidationMixin.js +2 -4
  36. package/src/mixins/createProxy.js +45 -53
  37. package/src/mixins/createStyleMixin/index.js +2 -0
  38. package/src/mixins/focusMixin.js +20 -125
  39. package/src/mixins/hoverableMixin.js +10 -16
  40. package/src/mixins/index.js +1 -0
  41. package/src/mixins/inputValidationMixin.js +10 -30
  42. package/src/mixins/normalizeBooleanAttributesMixin.js +11 -0
  43. package/src/mixins/proxyInputMixin.js +51 -58
  44. package/src/theme/components/container.js +1 -1
  45. package/src/theme/components/image.js +7 -0
  46. package/src/theme/components/index.js +3 -1
  47. package/dist/umd/775.js +0 -1
  48. package/src/baseClasses/BaseInputClass.js +0 -4
@@ -1,26 +1,30 @@
1
- import BaseInputClass from '../../../baseClasses/BaseInputClass';
1
+ import { createBaseInputClass } from '../../../baseClasses/createBaseInputClass';
2
2
  import { getComponentName } from '../../../helpers/componentHelpers';
3
+ import { createDispatchEvent, createEventListener } from '../../../helpers/mixinsHelpers';
3
4
  import { getSanitizedCharacters, focusElement } from './helpers';
4
5
 
5
6
  export const componentName = getComponentName('passcode-internal');
6
7
 
8
+ const observedAttributes = [
9
+ 'disabled',
10
+ 'bordered',
11
+ 'size',
12
+ 'invalid'
13
+ ]
14
+
15
+ const BaseInputClass = createBaseInputClass({ componentName, baseSelector: ':host > div' })
16
+
7
17
  class PasscodeInternal extends BaseInputClass {
8
18
  static get observedAttributes() {
9
- return [
10
- ...(BaseInputClass.observedAttributes || []),
11
- 'disabled',
12
- 'bordered',
13
- 'size',
14
- ];
19
+ return observedAttributes.concat(BaseInputClass.observedAttributes || []);
15
20
  }
16
21
 
17
22
  static get componentName() {
18
23
  return componentName;
19
24
  }
20
25
 
21
- #boundHandleInvalid = this.#handleInvalid.bind(this)
22
- #boundHandleValid = this.#handleValid.bind(this)
23
- #boundHandleBlur = this.#handleBlur.bind(this)
26
+ #dispatchBlur = createDispatchEvent.bind(this, 'blur')
27
+ #dispatchFocus = createDispatchEvent.bind(this, 'focus')
24
28
 
25
29
  constructor() {
26
30
  super();
@@ -29,7 +33,7 @@ class PasscodeInternal extends BaseInputClass {
29
33
  st-width="35px"
30
34
  data-id=${idx}
31
35
  type="tel"
32
- autocomplete="none"
36
+ autocomplete="off"
33
37
  ></descope-text-field>
34
38
  `)
35
39
 
@@ -39,8 +43,6 @@ class PasscodeInternal extends BaseInputClass {
39
43
  </div>
40
44
  `;
41
45
 
42
- this.baseSelector = ':host > div';
43
-
44
46
  this.inputs = Array.from(this.querySelectorAll('descope-text-field'))
45
47
  }
46
48
 
@@ -66,16 +68,6 @@ class PasscodeInternal extends BaseInputClass {
66
68
  return `^$|^\\d{${this.digits},}$`
67
69
  }
68
70
 
69
- #handleInvalid() {
70
- if (this.hasAttribute('invalid')) {
71
- this.inputs.forEach(input => input.setAttribute('invalid', 'true'))
72
- }
73
- }
74
-
75
- #handleValid() {
76
- this.inputs.forEach(input => input.removeAttribute('invalid'))
77
- }
78
-
79
71
  getValidity() {
80
72
  if (this.isRequired && !this.value) {
81
73
  return { valueMissing: true };
@@ -88,25 +80,17 @@ class PasscodeInternal extends BaseInputClass {
88
80
  }
89
81
  };
90
82
 
91
- onFocus(){
92
- this.inputs[0].focus()
93
- }
94
-
95
83
  connectedCallback() {
84
+ // we are adding listeners before calling to super because it's stopping the events
85
+ createEventListener.call(this, 'focus', (e) => {
86
+ // we want to ignore focus events we are dispatching
87
+ if (e.isTrusted)
88
+ this.inputs[0].focus()
89
+ })
90
+
96
91
  super.connectedCallback?.();
97
92
 
98
93
  this.initInputs()
99
-
100
- this.addEventListener('invalid', this.#boundHandleInvalid)
101
- this.addEventListener('valid', this.#boundHandleValid)
102
- this.addEventListener('blur', this.#boundHandleBlur)
103
- }
104
-
105
- disconnectedCallback() {
106
- super.connectedCallback?.();
107
- this.removeEventListener('invalid', this.#boundHandleInvalid)
108
- this.removeEventListener('valid', this.#boundHandleValid)
109
- this.removeEventListener('blur', this.#boundHandleBlur)
110
94
  }
111
95
 
112
96
  getInputIdx(inputEle) {
@@ -138,33 +122,60 @@ class PasscodeInternal extends BaseInputClass {
138
122
  focusElement(currentInput);
139
123
  };
140
124
 
141
- #handleBlur() {
142
- this.#handleInvalid()
143
- }
144
-
145
125
  initInputs() {
126
+ let prevVal = this.value
127
+ let blurTimerId
128
+
146
129
  this.inputs.forEach((input) => {
147
- input.oninput = (e) => {
130
+ // in order to simulate blur on the input
131
+ // we are checking if focus on one of the digits happened immediately after blur on another digit
132
+ // if not, the component is no longer focused and we should simulate blur
133
+ createEventListener.call(this, 'blur', (e) => {
134
+ e.stopImmediatePropagation();
135
+
136
+ blurTimerId = setTimeout(() => {
137
+ blurTimerId = null
138
+ this.#dispatchBlur()
139
+ });
140
+ }, { element: input })
141
+
142
+ createEventListener.call(this, 'focus', (e) => {
143
+ e.stopImmediatePropagation();
144
+
145
+ clearTimeout(blurTimerId)
146
+ if (!blurTimerId) {
147
+ this.#dispatchFocus()
148
+ }
149
+ }, { element: input })
150
+
151
+ createEventListener.call(this, 'input', (e) => {
148
152
  const charArr = getSanitizedCharacters(input.value);
149
153
 
150
154
  if (!charArr.length) {
151
155
  // if we got an invalid value we want to clear the input
152
156
  input.value = '';
153
- if (e.data === null) {
154
- // if the user deleted the char, we want to focus the prev digit
155
- focusElement(this.getPrevInput(input));
156
- }
157
157
  }
158
158
  else this.fillDigits(charArr, input);
159
- };
159
+
160
+ // we want to stop input events if the value wasn't change
161
+ if (prevVal === this.value) {
162
+ e.stopImmediatePropagation();
163
+ }
164
+ }, { element: input })
160
165
 
161
166
  input.onkeydown = ({ key }) => {
167
+ prevVal = this.value
168
+
162
169
  // when user deletes a digit, we want to focus the previous digit
163
170
  if (key === 'Backspace') {
171
+ // if the cursor is at 0, we want to move it to 1, so the value will be deleted
172
+ if (!input.selectionStart) {
173
+ input.setSelectionRange(1, 1);
174
+ }
164
175
  setTimeout(() => {
165
176
  focusElement(this.getPrevInput(input));
166
177
  });
167
- } else if (key.match(/^(\d)$/g)) { // if input is a digit
178
+ } else if (key.length === 1) { // we want only characters and not command keys
168
179
  input.value = ''; // we are clearing the previous value so we can override it with the new value
169
180
  }
170
181
  };
@@ -174,9 +185,14 @@ class PasscodeInternal extends BaseInputClass {
174
185
  attributeChangedCallback(attrName, oldValue, newValue) {
175
186
  super.attributeChangedCallback?.(attrName, oldValue, newValue)
176
187
 
188
+ // sync attributes to inputs
177
189
  if (oldValue !== newValue) {
178
- if (PasscodeInternal.observedAttributes.includes(attrName) && !BaseInputClass.observedAttributes.includes(attrName)) {
179
- this.inputs.forEach((input) => input.setAttribute(attrName, newValue))
190
+ if (observedAttributes.includes(attrName)) {
191
+ this.inputs.forEach(
192
+ (input) => newValue === null ?
193
+ input.removeAttribute(attrName) :
194
+ input.setAttribute(attrName, newValue)
195
+ )
180
196
  }
181
197
  }
182
198
  }
@@ -1,18 +1,35 @@
1
- // move create event to here
2
1
 
2
+ // create a dispatch event function that also calls to the onevent function in case it's set
3
3
  // usage example:
4
- // #dispatchSomething = createDispatchEvent.bind(this, 'something')
5
- export function createDispatchEvent(eventName) {
6
- this[`on${eventName}`]?.(); // in case we got an event callback as property
7
- this.dispatchEvent(new Event(eventName));
4
+ // #dispatchSomething = createDispatchEvent.bind(this, 'something' { ...options })
5
+ // this will dispatch a new event when called, but also call to "onsomething"
6
+ export function createDispatchEvent(eventName, options = {}) {
7
+ const event = new Event(eventName, options)
8
+
9
+ this[`on${eventName}`]?.(event); // in case we got an event callback as property
10
+ this.dispatchEvent(event);
8
11
  }
9
12
 
13
+ // add an event listener that is automatically removed on disconnect
10
14
  // usage example:
11
- // #removeChangeListener = createEventListener.call(this,'change', this.onChange)
12
- export function createEventListener(event, callback) {
15
+ // createEventListener.call(this,'change', this.onChange, { element? , ...options })
16
+ export function createEventListener(event, callback, { element, ...options } = {}) {
17
+ const timerId = setTimeout(() => console.warn(this.localName, 'is not using "createBaseClass", events will not be removed automatically on disconnect'), 2000)
18
+
19
+ this.addEventListener('connected', () => {
20
+ clearTimeout(timerId);
21
+ }, { once: true })
22
+
23
+ const targetEle = element || this;
13
24
  const boundCallback = callback.bind(this);
14
25
 
15
- this.addEventListener(event, boundCallback);
26
+ const onDisconnect = () => {
27
+ targetEle.removeEventListener(event, boundCallback);
28
+ }
29
+
30
+ this.addEventListener('disconnected', onDisconnect, { once: true })
31
+
32
+ targetEle.addEventListener(event, boundCallback, options);
16
33
 
17
- return () => this.removeEventListener(event, boundCallback)
34
+ return onDisconnect
18
35
  }
package/src/index.js CHANGED
@@ -15,6 +15,7 @@ import './components/descope-switch-toggle';
15
15
  import './components/descope-text';
16
16
  import './components/descope-text-area';
17
17
  import './components/descope-text-field';
18
+ import './components/descope-image';
18
19
 
19
20
  export {
20
21
  globalsThemeToStyle,
@@ -1,47 +1,21 @@
1
- import { createDispatchEvent } from "../helpers/mixinsHelpers";
1
+ import { createDispatchEvent, createEventListener } from "../helpers/mixinsHelpers";
2
2
 
3
3
  export const changeMixin = (superclass) => class ChangeMixinClass extends superclass {
4
-
5
- #boundedHandleChange
6
- #boundedHandleBlur
7
-
8
- #removeChangeListener
9
- #removeBlurListener
10
-
11
4
  #dispatchChange = createDispatchEvent.bind(this, 'change')
12
5
 
13
- constructor() {
14
- super();
15
-
16
- this.#boundedHandleChange = this.#handleChange.bind(this);
17
- this.#boundedHandleBlur = this.#handleBlur.bind(this);
18
- }
19
-
20
- #handleChange(e) {
21
- // we want to listen only to browser events
22
- // and not to events we are dispatching
23
- if (e.isTrusted) {
24
- // we want to control the change events that dispatched by the component
25
- // so we are stopping propagation and handling it in handleBlur
26
- e.stopPropagation()
27
- }
28
- }
29
-
30
- #handleBlur() {
31
- // on blur, we want to dispatch a change event
32
- this.#dispatchChange()
33
- }
34
-
35
6
  connectedCallback() {
36
7
  super.connectedCallback?.();
37
-
38
- this.#removeChangeListener = addEventListener.bind()
39
- this.addEventListener('change', this.#boundedHandleChange, true)
40
- this.addEventListener('blur', this.#boundedHandleBlur)
41
- }
42
-
43
- disconnectedCallback() {
44
- this.removeEventListener('change', this.#boundedHandleChange)
45
- this.removeEventListener('blur', this.#boundedHandleBlur)
8
+ this.prevValue = this.value
9
+
10
+ createEventListener.call(this, 'change', (e) => {
11
+ e.stopPropagation();
12
+ })
13
+
14
+ createEventListener.call(this, 'blur', () => {
15
+ if (this.value !== this.prevValue) {
16
+ this.#dispatchChange()
17
+ this.prevValue = this.value
18
+ }
19
+ })
46
20
  }
47
21
  }
@@ -1,7 +1,7 @@
1
1
  export const componentNameValidationMixin = (superclass) =>
2
2
  class ComponentNameValidationMixinClass extends superclass {
3
3
  #checkComponentName() {
4
- const currentComponentName = this.shadowRoot.host.tagName.toLowerCase();
4
+ const currentComponentName = this.localName;
5
5
 
6
6
  if (!superclass.componentName) {
7
7
  throw Error(
@@ -18,8 +18,6 @@ export const componentNameValidationMixin = (superclass) =>
18
18
 
19
19
  connectedCallback() {
20
20
  super.connectedCallback?.();
21
- if (this.shadowRoot.isConnected) {
22
- this.#checkComponentName();
23
- }
21
+ this.#checkComponentName();
24
22
  }
25
23
  };
@@ -1,6 +1,7 @@
1
1
  import { createBaseClass } from '../baseClasses/createBaseClass';
2
2
  import { isFunction } from '../helpers';
3
3
  import { forwardProps, syncAttrs } from '../helpers/componentHelpers';
4
+ import { createDispatchEvent, createEventListener } from '../helpers/mixinsHelpers';
4
5
 
5
6
  export const createProxy = ({
6
7
  componentName,
@@ -11,71 +12,62 @@ export const createProxy = ({
11
12
  includeAttrsSync = [],
12
13
  includeForwardProps = []
13
14
  }) => {
14
- const template = `
15
- <style id="create-proxy"></style>
16
- <${wrappedEleName}>
17
- <slot></slot>
18
- ${slots.map((slot) => `<slot name="${slot}" slot="${slot}"></slot>`).join('')}
19
- </${wrappedEleName}>
20
- `;
21
-
22
15
  class ProxyClass extends createBaseClass({ componentName, baseSelector: wrappedEleName }) {
23
- constructor() {
24
- super().attachShadow({ mode: 'open' }).innerHTML = template;
25
- this.hostElement = this.shadowRoot.host;
26
- this.shadowRoot.getElementById('create-proxy').innerHTML =
27
- isFunction(style) ? style() : style;
28
- }
29
-
30
- #boundOnFocus = this.#onFocus.bind(this);
16
+ #dispatchBlur = createDispatchEvent.bind(this, 'blur')
17
+ #dispatchFocus = createDispatchEvent.bind(this, 'focus')
31
18
 
32
- // we want to focus on the proxy element when focusing our WCP
33
- #onFocus() {
34
- this.proxyElement.focus();
19
+ constructor() {
20
+ super().attachShadow({ mode: 'open' }).innerHTML = `
21
+ <style id="create-proxy">${isFunction(style) ? style() : style
22
+ }</style>
23
+ <${wrappedEleName}>
24
+ <slot></slot>
25
+ ${slots.map((slot) => `<slot name="${slot}" slot="${slot}"></slot>`).join('')}
26
+ </${wrappedEleName}>
27
+ `;
35
28
  }
36
29
 
37
- focus = this.#onFocus
30
+ focus = () => this.baseElement.focus()
38
31
 
39
32
  connectedCallback() {
40
- if (this.shadowRoot.isConnected) {
41
- this.proxyElement = this.shadowRoot.querySelector(wrappedEleName);
42
-
43
- this.addEventListener('focus', this.#boundOnFocus);
33
+ super.connectedCallback?.();
44
34
 
45
- // this is needed for components that uses props, such as combo box
46
- forwardProps(this.hostElement, this.proxyElement, includeForwardProps)
35
+ createEventListener.call(this, 'blur', (e) => {
36
+ this.#dispatchBlur()
37
+ }, { element: this.baseElement })
47
38
 
48
- // `onkeydown` is set on `proxyElement` support proper tab-index navigation
49
- // this support is needed since both proxy host and element catch `focus`/`blur` event
50
- // which causes faulty behavior.
51
- // we need this to happen only when the proxy component is in the light DOM,
52
- // otherwise it will focus the nested proxy element
53
- this.proxyElement.onkeydown = (e) => {
54
- if (e.shiftKey && e.keyCode === 9 && this.getRootNode() === document) {
55
- this.removeAttribute('tabindex');
56
- // We want to defer the action of setting the tab index back
57
- // so it will happen after focusing the previous element
58
- setTimeout(() => this.setAttribute('tabindex', '0'), 0);
59
- }
60
- };
39
+ createEventListener.call(this, 'focus', (e) => {
40
+ this.#dispatchFocus()
41
+ }, { element: this.baseElement })
61
42
 
62
- syncAttrs(this.proxyElement, this.hostElement, {
63
- excludeAttrs: excludeAttrsSync,
64
- includeAttrs: includeAttrsSync
65
- });
66
- }
67
- }
43
+ createEventListener.call(this, 'focus', (e) => {
44
+ // if we got a focus event we want to focus the proxy element
45
+ if (e.isTrusted) {
46
+ this.focus();
47
+ }
48
+ })
68
49
 
69
- attributeChangedCallback() {
70
- if (!this.proxyElement) {
71
- return;
72
- }
73
- }
50
+ // this is needed for components that uses props, such as combo box
51
+ forwardProps(this, this.baseElement, includeForwardProps)
74
52
 
75
- disconnectedCallback() {
76
- super.disconnectedCallback?.()
53
+ // `onkeydown` is set on `baseElement` support proper tab-index navigation
54
+ // this support is needed since both proxy host and element catch `focus`/`blur` event
55
+ // which causes faulty behavior.
56
+ // we need this to happen only when the proxy component is in the light DOM,
57
+ // otherwise it will focus the nested proxy element
58
+ this.baseElement.onkeydown = (e) => {
59
+ if (e.shiftKey && e.keyCode === 9 && this.getRootNode() === document) {
60
+ this.removeAttribute('tabindex');
61
+ // We want to defer the action of setting the tab index back
62
+ // so it will happen after focusing the previous element
63
+ setTimeout(() => this.setAttribute('tabindex', '0'), 0);
64
+ }
65
+ };
77
66
 
78
- this.removeEventListener('focus', this.#boundOnFocus);
67
+ syncAttrs(this.baseElement, this, {
68
+ excludeAttrs: excludeAttrsSync,
69
+ includeAttrs: includeAttrsSync
70
+ });
79
71
  }
80
72
  }
81
73
 
@@ -131,6 +131,8 @@ export const createStyleMixin =
131
131
  }
132
132
 
133
133
  disconnectedCallback() {
134
+ super.disconnectedCallback?.();
135
+
134
136
  this.#disconnectThemeManager?.();
135
137
  }
136
138
  };
@@ -1,130 +1,25 @@
1
- import { upperFirst } from "../helpers";
2
-
3
- const events = [
4
- 'blur',
5
- 'focus',
6
- 'focusin',
7
- 'focusout',
8
- ]
1
+ import { createEventListener } from "../helpers/mixinsHelpers"
9
2
 
10
3
  export const focusMixin = (superclass) => class FocusMixinClass extends superclass {
11
- #isFocused = false
12
- #setFocusTimer
13
-
14
- #boundedHandleFocus
15
- #boundedHandleBlur
16
- #boundedHandleFocusIn
17
- #boundedHandleFocusOut
18
-
19
- constructor() {
20
- super();
21
-
22
- for (const event of events) {
23
- this[`dispatch${upperFirst(event)}`] = function () {
24
- this.dispatchInputEvent(event)
25
- }
26
- }
27
-
28
- this.#boundedHandleFocus = this.#handleFocus.bind(this);
29
- this.#boundedHandleBlur = this.#handleBlur.bind(this);
30
- this.#boundedHandleFocusIn = this.#handleFocusIn.bind(this);
31
- this.#boundedHandleFocusOut = this.#handleFocusOut.bind(this);
32
- }
33
-
34
- #handleFocus(e) {
35
- if (this.isReadOnly) {
36
- e.stopPropagation()
37
- return
38
- }
39
- // we want to listen only to browser events
40
- // and not to events we are dispatching
41
- if (e.isTrusted) { // TODO: check if this is needed, because Vaadin is also dispatching events
42
- // we want to control the focus events that dispatched by the component
43
- // so we are stopping propagation and handling it in setFocus
44
- e.stopPropagation()
45
- this.setFocus(true)
46
-
47
- // if the focus event is on the root component (and not on the inner components)
48
- // we want to notify the component and let it decide what to do with it
49
- if (e.target === this) {
50
- this.onFocus(e)
51
- }
52
- }
53
- }
54
-
55
- #handleFocusOut(e) {
56
- // we want to listen only to browser events
57
- // and not to events we are dispatching
58
- if (e.isTrusted) {
59
- // we want to control the focus events that dispatched by the component
60
- // so we are stopping propagation and handling it in setFocus
61
- e.stopPropagation()
62
- }
63
- }
64
-
65
- #handleFocusIn(e) {
66
- // we want to listen only to browser events
67
- // and not to events we are dispatching
68
- if (e.isTrusted) {
69
- // we want to control the focus events that dispatched by the component
70
- // so we are stopping propagation and handling it in setFocus
71
- e.stopPropagation()
72
- }
73
- }
74
-
75
- #handleBlur(e) {
76
- if (e.isTrusted) {
77
- e.stopPropagation()
78
- this.setFocus(false)
79
- }
80
- }
81
-
82
- get isReadOnly() {
83
- return this.hasAttribute('readonly') && this.getAttribute('readonly') !== 'false'
84
- }
85
-
86
- // we want to debounce the calls to this fn
87
- // so we can support input like components with multiple inputs inside
88
- setFocus(isFocused) {
89
- clearTimeout(this.#setFocusTimer)
90
-
91
- this.#setFocusTimer = setTimeout(() => {
92
- if (this.#isFocused !== isFocused) {
93
- this.#isFocused = isFocused
94
- if (isFocused) {
95
- this.dispatchFocus();
96
- this.dispatchFocusin();
97
- }
98
- else {
99
- this.dispatchBlur()
100
- this.dispatchFocusout();
101
- }
102
- }
103
- })
104
- }
105
-
106
- onFocus() {
107
- console.warn('onFocus', 'is not implemented')
108
- }
109
-
110
- dispatchInputEvent(eventName) {
111
- this[`on${eventName}`]?.(); // in case we got an event callback as property
112
- this.dispatchEvent(new InputEvent(eventName));
113
- }
114
-
4
+ // we want to block all native events,
5
+ // so the input can control when to dispatch it based on its internal behavior
115
6
  connectedCallback() {
116
- super.connectedCallback?.();
117
-
118
- this.addEventListener('focus', this.#boundedHandleFocus, true)
119
- this.addEventListener('blur', this.#boundedHandleBlur, true)
120
- this.addEventListener('focusin', this.#boundedHandleFocusIn)
121
- this.addEventListener('focusout', this.#boundedHandleFocusOut)
122
- }
123
-
124
- disconnectedCallback() {
125
- this.removeEventListener('focus', this.#boundedHandleFocus)
126
- this.removeEventListener('blur', this.#boundedHandleBlur)
127
- this.removeEventListener('focusin', this.#boundedHandleFocusIn)
128
- this.removeEventListener('focusout', this.#boundedHandleFocusOut)
7
+ super.connectedCallback?.();
8
+
9
+ createEventListener.call(this, 'blur', (e) => {
10
+ e.isTrusted && e.stopImmediatePropagation()
11
+ })
12
+
13
+ createEventListener.call(this, 'focus', (e) => {
14
+ e.isTrusted && e.stopImmediatePropagation();
15
+ })
16
+
17
+ createEventListener.call(this, 'focusout', (e) => {
18
+ e.isTrusted && e.stopImmediatePropagation();
19
+ })
20
+
21
+ createEventListener.call(this, 'focusin', (e) => {
22
+ e.isTrusted && e.stopImmediatePropagation();
23
+ })
129
24
  }
130
25
  }
@@ -1,24 +1,18 @@
1
+ import { createEventListener } from "../helpers/mixinsHelpers";
2
+
1
3
  export const hoverableMixin =
2
4
  (superclass) =>
3
5
  class HoverableMixinClass extends superclass {
4
- #boundOnMouseOver = this.#onMouseOver.bind(this)
5
-
6
- #onMouseOver(e) {
7
- this.setAttribute('hover', 'true');
8
- e.target.addEventListener(
9
- 'mouseleave',
10
- () => this.shadowRoot.host.removeAttribute('hover'),
11
- { once: true }
12
- );
13
- }
14
-
15
6
  connectedCallback() {
16
7
  super.connectedCallback?.();
17
8
 
18
- const baseElement = this.shadowRoot.querySelector(
19
- this.baseSelector
20
- );
21
-
22
- baseElement.addEventListener('mouseover', this.#boundOnMouseOver);
9
+ createEventListener.call(this, 'mouseover', (e) => {
10
+ this.setAttribute('hover', 'true');
11
+ e.target.addEventListener(
12
+ 'mouseleave',
13
+ () => this.removeAttribute('hover'),
14
+ { once: true }
15
+ );
16
+ }, { element: this.baseElement })
23
17
  }
24
18
  };
@@ -8,3 +8,4 @@ export { focusMixin } from './focusMixin'
8
8
  export { inputValidationMixin } from './inputValidationMixin'
9
9
  export { portalMixin } from './portalMixin'
10
10
  export { changeMixin } from './changeMixin'
11
+ export { normalizeBooleanAttributesMixin } from './normalizeBooleanAttributesMixin'