@descope/web-components-ui 1.0.70 → 1.0.71

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 (41) hide show
  1. package/dist/index.esm.js +307 -402
  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-link-index-js.js +1 -1
  12. package/dist/umd/descope-loader-linear-index-js.js +1 -1
  13. package/dist/umd/descope-loader-radial-index-js.js +1 -1
  14. package/dist/umd/descope-logo-index-js.js +1 -1
  15. package/dist/umd/descope-number-field-index-js.js +1 -1
  16. package/dist/umd/descope-passcode-descope-passcode-internal-index-js.js +1 -1
  17. package/dist/umd/descope-passcode-index-js.js +1 -1
  18. package/dist/umd/descope-password-field-index-js.js +1 -1
  19. package/dist/umd/descope-switch-toggle-index-js.js +1 -1
  20. package/dist/umd/descope-text-area-index-js.js +1 -1
  21. package/dist/umd/descope-text-field-index-js.js +1 -1
  22. package/dist/umd/descope-text-index-js.js +1 -1
  23. package/dist/umd/index.js +1 -1
  24. package/package.json +1 -1
  25. package/src/baseClasses/createBaseClass.js +29 -3
  26. package/src/baseClasses/createBaseInputClass.js +9 -0
  27. package/src/components/descope-passcode/Passcode.js +1 -1
  28. package/src/components/descope-passcode/descope-passcode-internal/PasscodeInternal.js +67 -51
  29. package/src/helpers/mixinsHelpers.js +26 -9
  30. package/src/mixins/changeMixin.js +13 -39
  31. package/src/mixins/componentNameValidationMixin.js +2 -4
  32. package/src/mixins/createProxy.js +45 -53
  33. package/src/mixins/createStyleMixin/index.js +2 -0
  34. package/src/mixins/focusMixin.js +20 -125
  35. package/src/mixins/hoverableMixin.js +10 -16
  36. package/src/mixins/index.js +1 -0
  37. package/src/mixins/inputValidationMixin.js +10 -30
  38. package/src/mixins/normalizeBooleanAttributesMixin.js +11 -0
  39. package/src/mixins/proxyInputMixin.js +51 -58
  40. package/dist/umd/775.js +0 -1
  41. package/src/baseClasses/BaseInputClass.js +0 -4
@@ -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'
@@ -1,4 +1,4 @@
1
- import { createDispatchEvent } from "../helpers/mixinsHelpers";
1
+ import { createEventListener } from "../helpers/mixinsHelpers";
2
2
 
3
3
  const observedAttributes = [
4
4
  'required',
@@ -21,19 +21,12 @@ export const inputValidationMixin = (superclass) => class InputValidationMixinCl
21
21
  return true;
22
22
  }
23
23
 
24
- #dispatchValid = createDispatchEvent.bind(this, 'valid')
25
- #dispatchInvalid = createDispatchEvent.bind(this, 'invalid')
26
-
27
24
  #internals
28
25
 
29
- #boundedHandleInput
30
-
31
26
  constructor() {
32
27
  super();
33
28
 
34
29
  this.#internals = this.attachInternals();
35
-
36
- this.#boundedHandleInput = this.#handleInput.bind(this);
37
30
  }
38
31
 
39
32
  get defaultErrorMsgValueMissing() {
@@ -59,7 +52,7 @@ export const inputValidationMixin = (superclass) => class InputValidationMixinCl
59
52
  }
60
53
  }
61
54
 
62
- setValidity() {
55
+ #setValidity() {
63
56
  const validity = this.getValidity()
64
57
  this.#internals.setValidity(validity, this.getErrorMessage(validity))
65
58
  }
@@ -89,7 +82,7 @@ export const inputValidationMixin = (superclass) => class InputValidationMixinCl
89
82
  this.#internals.setValidity({ customError: true }, errorMessage)
90
83
  } else {
91
84
  this.#internals.setValidity({})
92
- this.setValidity()
85
+ this.#setValidity()
93
86
  }
94
87
  }
95
88
 
@@ -101,37 +94,24 @@ export const inputValidationMixin = (superclass) => class InputValidationMixinCl
101
94
  return this.getAttribute('pattern')
102
95
  }
103
96
 
104
- #handleInput() {
105
- this.setValidity()
106
- this.handleDispatchValidationEvents()
97
+ get form() {
98
+ return this.#internals.form
107
99
  }
108
100
 
109
101
  attributeChangedCallback(attrName, oldValue, newValue) {
110
102
  super.attributeChangedCallback?.(attrName, oldValue, newValue);
111
103
 
112
104
  if (observedAttributes.includes(attrName)) {
113
- this.setValidity();
114
- }
115
- }
116
-
117
- handleDispatchValidationEvents() {
118
- if (this.checkValidity()) {
119
- this.#dispatchValid()
120
- } else {
121
- this.#dispatchInvalid()
105
+ this.#setValidity();
122
106
  }
123
107
  }
124
108
 
125
109
  connectedCallback() {
126
110
  super.connectedCallback?.();
111
+ createEventListener.call(this, 'change', this.#setValidity)
112
+ createEventListener.call(this, 'invalid', (e) => e.stopPropagation())
113
+ createEventListener.call(this, 'input', this.#setValidity)
127
114
 
128
- this.addEventListener('input', this.#boundedHandleInput)
129
-
130
- this.setValidity();
131
- this.handleDispatchValidationEvents();
132
- }
133
-
134
- disconnectedCallback() {
135
- this.removeEventListener('input', this.#boundedHandleInput)
115
+ this.#setValidity();
136
116
  }
137
117
  }
@@ -0,0 +1,11 @@
1
+ // we want all the valueless attributes to have "true" value
2
+ export const normalizeBooleanAttributesMixin = (superclass) => class NormalizeBooleanAttributesMixinClass extends superclass {
3
+ attributeChangedCallback(attrName, oldValue, newValue) {
4
+ if (newValue === '') {
5
+ this.setAttribute(attrName, 'true')
6
+ newValue = 'true'
7
+ }
8
+
9
+ super.attributeChangedCallback?.(attrName, oldValue, newValue)
10
+ }
11
+ }
@@ -1,3 +1,4 @@
1
+ import { createDispatchEvent, createEventListener } from "../helpers/mixinsHelpers";
1
2
  import { inputValidationMixin } from "./inputValidationMixin";
2
3
 
3
4
  const errorAttrs = ['invalid', 'has-error-message'];
@@ -36,26 +37,17 @@ export const proxyInputMixin = (superclass) =>
36
37
 
37
38
  #inputElement
38
39
 
39
- #boundHandleFocus
40
- #boundHandleInvalid
41
- #boundHandleValid
40
+ #dispatchChange = createDispatchEvent.bind(this, 'change')
42
41
 
43
42
  constructor() {
44
43
  super();
45
44
 
46
45
  this.#inputElement = super.inputElement
47
-
48
- this.#boundHandleFocus = this.#handleFocus.bind(this);
49
- this.#boundHandleInvalid = this.#handleInvalid.bind(this);
50
- this.#boundHandleValid = this.#handleValid.bind(this);
51
-
52
- this.baseEle = this.shadowRoot.querySelector(this.baseSelector);
53
-
54
46
  }
55
47
 
56
48
  get inputElement() {
57
- const inputSlot = this.baseEle.shadowRoot.querySelector('slot[name="input"]')
58
- const textAreaSlot = this.baseEle.shadowRoot.querySelector('slot[name="textarea"]')
49
+ const inputSlot = this.baseElement.shadowRoot.querySelector('slot[name="input"]')
50
+ const textAreaSlot = this.baseElement.shadowRoot.querySelector('slot[name="textarea"]')
59
51
 
60
52
  this.#inputElement ??= getNestedInput(inputSlot) || getNestedInput(textAreaSlot)
61
53
 
@@ -74,7 +66,6 @@ export const proxyInputMixin = (superclass) =>
74
66
 
75
67
  reportValidityOnInternalInput() {
76
68
  setTimeout(() => {
77
- this.baseEle.focus() //TODO: check if this is needed
78
69
  this.inputElement.reportValidity()
79
70
  })
80
71
  }
@@ -87,72 +78,74 @@ export const proxyInputMixin = (superclass) =>
87
78
  }
88
79
  }
89
80
 
90
- setInternalInputErrorMessage() {
91
- if (!this.checkValidity()) {
81
+ handleInternalInputErrorMessage() {
82
+ if (!this.inputElement.checkValidity()) {
92
83
  this.inputElement.setCustomValidity(this.validationMessage)
93
84
  }
94
85
  }
95
86
 
96
- // when clicking on the form submit button and the input is invalid
97
- // we want it to appear as invalid
98
- #handleFocus(e) {
99
- if (e.relatedTarget?.form) {
100
- if (!this.checkValidity()) {
101
- this.setAttribute('invalid', 'true');
102
- }
103
-
104
- if (this.hasAttribute('invalid')) {
105
- this.reportValidityOnInternalInput()
106
- }
107
- }
108
- }
109
-
110
- #handleInvalid() {
111
- this.setInternalInputErrorMessage()
87
+ #handleErrorMessage() {
88
+ this.handleInternalInputErrorMessage()
112
89
  this.setAttribute('error-message', this.validationMessage)
113
90
  }
114
91
 
115
- #handleValid() {
116
- this.removeAttribute('invalid')
117
- }
118
-
119
92
  connectedCallback() {
120
93
  super.connectedCallback?.();
121
94
 
122
- // this is our way to identify that the form was submitted
123
- // in this case, we want the input to be in error state if it's not valid
124
- this.addEventListener('focus', this.#boundHandleFocus)
95
+ createEventListener.call(this, 'input', (e) => {
96
+ if (!this.inputElement.checkValidity()) {
97
+ this.inputElement.setCustomValidity('')
98
+ // after updating the input validity we want to trigger set validity on the wrapping element
99
+ // so we will get the updated validity
100
+ this.setCustomValidity('')
101
+
102
+ // Vaadin is getting the input event before us,
103
+ // so in order to make sure they use the updated validity
104
+ // we calling their fn after updating the input validity
105
+ this.baseElement.__onInput(e)
106
+
107
+ this.#handleErrorMessage()
108
+ }
109
+ }, { element: this.inputElement })
125
110
 
126
- this.addEventListener('invalid', this.#boundHandleInvalid)
127
- this.addEventListener('valid', this.#boundHandleValid)
111
+ createEventListener.call(this, 'change', () => {
112
+ this.#dispatchChange()
113
+ }, { element: this.baseElement })
128
114
 
129
- this.addEventListener('input', () => {
130
- this.inputElement.setCustomValidity('')
131
- if (!this.inputElement.checkValidity())
132
- this.setInternalInputErrorMessage()
115
+ createEventListener.call(this, 'blur', () => {
116
+ if (!this.checkValidity()) {
117
+ this.setAttribute('invalid', 'true')
118
+ this.#handleErrorMessage()
119
+ }
133
120
  })
134
121
 
122
+ createEventListener.call(this, 'focus', (e) => {
123
+ // when clicking on the form submit button and the input is invalid
124
+ // we want it to appear as invalid
125
+ if (e.relatedTarget?.form === this.form) {
126
+ if (!this.checkValidity()) {
127
+ this.setAttribute('invalid', 'true');
128
+ }
129
+
130
+ if (this.hasAttribute('invalid')) {
131
+ this.reportValidityOnInternalInput()
132
+ }
133
+ }
134
+ })
135
+
136
+ createEventListener.call(this, 'invalid', this.#handleErrorMessage)
137
+
138
+ this.handleInternalInputErrorMessage()
139
+
135
140
  // this is needed in order to make sure the form input validation is working
141
+ // we do not want it to happen when the component is nested
136
142
  if (!this.hasAttribute('tabindex') && this.getRootNode() === document) {
137
143
  this.setAttribute('tabindex', 0);
138
144
  }
139
145
 
140
146
  // sync properties
141
147
  propertyObserver(this, this.inputElement, 'value');
148
+ propertyObserver(this, this.inputElement, 'selectionStart');
142
149
  this.setSelectionRange = this.inputElement.setSelectionRange?.bind(this.inputElement);
143
150
  }
144
-
145
- disconnectedCallback() {
146
- this.removeEventListener('focus', this.#boundHandleFocus)
147
- this.removeEventListener('invalid', this.#boundHandleInvalid)
148
- this.removeEventListener('valid', this.#boundHandleValid)
149
- }
150
-
151
- attributeChangedCallback(attrName, oldValue, newValue) {
152
- super.attributeChangedCallback?.(attrName, oldValue, newValue);
153
-
154
- if (attrName === 'invalid' && newValue === '') {
155
- this.setAttribute('invalid', 'true')
156
- }
157
- }
158
151
  };