@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
package/dist/index.esm.js CHANGED
@@ -22,8 +22,6 @@ const compose = (...fns) =>
22
22
  (val) =>
23
23
  fns.reduceRight((res, fn) => fn(res), val);
24
24
 
25
- const upperFirst = (str) => str.charAt(0).toUpperCase() + str.slice(1);
26
-
27
25
  const isFunction = (maybeFunc) => typeof maybeFunc === 'function';
28
26
 
29
27
  const DESCOPE_PREFIX = 'descope';
@@ -384,6 +382,8 @@ const createStyleMixin =
384
382
  }
385
383
 
386
384
  disconnectedCallback() {
385
+ super.disconnectedCallback?.();
386
+
387
387
  this.#disconnectThemeManager?.();
388
388
  }
389
389
  };
@@ -420,56 +420,40 @@ const draggableMixin = (superclass) =>
420
420
  }
421
421
  };
422
422
 
423
- const componentNameValidationMixin = (superclass) =>
424
- class ComponentNameValidationMixinClass extends superclass {
425
- #checkComponentName() {
426
- const currentComponentName = this.shadowRoot.host.tagName.toLowerCase();
423
+ // create a dispatch event function that also calls to the onevent function in case it's set
424
+ // usage example:
425
+ // #dispatchSomething = createDispatchEvent.bind(this, 'something' { ...options })
426
+ // this will dispatch a new event when called, but also call to "onsomething"
427
+ function createDispatchEvent(eventName, options = {}) {
428
+ const event = new Event(eventName, options);
427
429
 
428
- if (!superclass.componentName) {
429
- throw Error(
430
- `component name is not defined on super class, make sure you have a static get for "componentName"`
431
- );
432
- }
430
+ this[`on${eventName}`]?.(event); // in case we got an event callback as property
431
+ this.dispatchEvent(event);
432
+ }
433
433
 
434
- if (currentComponentName !== superclass.componentName) {
435
- throw Error(
436
- `component name mismatch, expected "${superclass.componentName}", current "${currentComponentName}"`
437
- );
438
- }
439
- }
434
+ // add an event listener that is automatically removed on disconnect
435
+ // usage example:
436
+ // createEventListener.call(this,'change', this.onChange, { element? , ...options })
437
+ function createEventListener(event, callback, { element, ...options } = {}) {
438
+ const timerId = setTimeout(() => console.warn(this.localName, 'is not using "createBaseClass", events will not be removed automatically on disconnect'), 2000);
440
439
 
441
- connectedCallback() {
442
- super.connectedCallback?.();
443
- if (this.shadowRoot.isConnected) {
444
- this.#checkComponentName();
445
- }
446
- }
447
- };
440
+ this.addEventListener('connected', () => {
441
+ clearTimeout(timerId);
442
+ }, { once: true });
448
443
 
449
- const hoverableMixin =
450
- (superclass) =>
451
- class HoverableMixinClass extends superclass {
452
- #boundOnMouseOver = this.#onMouseOver.bind(this)
453
-
454
- #onMouseOver(e) {
455
- this.setAttribute('hover', 'true');
456
- e.target.addEventListener(
457
- 'mouseleave',
458
- () => this.shadowRoot.host.removeAttribute('hover'),
459
- { once: true }
460
- );
461
- }
444
+ const targetEle = element || this;
445
+ const boundCallback = callback.bind(this);
462
446
 
463
- connectedCallback() {
464
- super.connectedCallback?.();
447
+ const onDisconnect = () => {
448
+ targetEle.removeEventListener(event, boundCallback);
449
+ };
465
450
 
466
- const baseElement = this.shadowRoot.querySelector(
467
- this.baseSelector
468
- );
451
+ this.addEventListener('disconnected', onDisconnect, { once: true });
469
452
 
470
- baseElement.addEventListener('mouseover', this.#boundOnMouseOver);
471
- }
472
- };
453
+ targetEle.addEventListener(event, boundCallback, options);
454
+
455
+ return onDisconnect
456
+ }
473
457
 
474
458
  const createBaseClass = ({ componentName, baseSelector = '' }) => {
475
459
  class DescopeBaseClass extends HTMLElement {
@@ -478,10 +462,17 @@ const createBaseClass = ({ componentName, baseSelector = '' }) => {
478
462
  }
479
463
 
480
464
  #baseElement;
465
+
466
+ #dispatchConnected = createDispatchEvent.bind(this, 'connected')
467
+ #dispatchDisconnected = createDispatchEvent.bind(this, 'disconnected')
468
+
469
+ // base selector is the selector for the component wrapper,
470
+ // it's the highest element that is relevant for the layout
481
471
  get baseSelector() {
482
472
  return baseSelector
483
473
  }
484
474
 
475
+ // this is the base element, which returned by querying the base selector
485
476
  get baseElement() {
486
477
  this.#baseElement ??= this.baseSelector ?
487
478
  this.rootElement.querySelector(this.baseSelector) :
@@ -490,12 +481,31 @@ const createBaseClass = ({ componentName, baseSelector = '' }) => {
490
481
  return this.#baseElement
491
482
  }
492
483
 
484
+ // this is the component root level element,
485
+ // it can be either a shadow root or the component's node from the light DOM
493
486
  get rootElement() {
494
487
  return this.shadowRoot || this
495
488
  }
489
+
490
+ connectedCallback() {
491
+ super.connectedCallback?.();
492
+
493
+ // we are waiting for all components to listen before dispatching
494
+ setTimeout(this.#dispatchConnected);
495
+ }
496
+
497
+ disconnectedCallback() {
498
+ super.disconnectedCallback?.();
499
+
500
+ this.#dispatchDisconnected();
501
+ }
496
502
  }
497
503
 
498
- return compose(componentNameValidationMixin, hoverableMixin)(DescopeBaseClass)
504
+ return compose(
505
+ componentNameValidationMixin,
506
+ hoverableMixin,
507
+ normalizeBooleanAttributesMixin
508
+ )(DescopeBaseClass)
499
509
  };
500
510
 
501
511
  const createProxy = ({
@@ -507,87 +517,69 @@ const createProxy = ({
507
517
  includeAttrsSync = [],
508
518
  includeForwardProps = []
509
519
  }) => {
510
- const template = `
511
- <style id="create-proxy"></style>
512
- <${wrappedEleName}>
513
- <slot></slot>
514
- ${slots.map((slot) => `<slot name="${slot}" slot="${slot}"></slot>`).join('')}
515
- </${wrappedEleName}>
516
- `;
517
-
518
520
  class ProxyClass extends createBaseClass({ componentName, baseSelector: wrappedEleName }) {
519
- constructor() {
520
- super().attachShadow({ mode: 'open' }).innerHTML = template;
521
- this.hostElement = this.shadowRoot.host;
522
- this.shadowRoot.getElementById('create-proxy').innerHTML =
523
- isFunction(style) ? style() : style;
524
- }
525
-
526
- #boundOnFocus = this.#onFocus.bind(this);
521
+ #dispatchBlur = createDispatchEvent.bind(this, 'blur')
522
+ #dispatchFocus = createDispatchEvent.bind(this, 'focus')
527
523
 
528
- // we want to focus on the proxy element when focusing our WCP
529
- #onFocus() {
530
- this.proxyElement.focus();
524
+ constructor() {
525
+ super().attachShadow({ mode: 'open' }).innerHTML = `
526
+ <style id="create-proxy">${isFunction(style) ? style() : style
527
+ }</style>
528
+ <${wrappedEleName}>
529
+ <slot></slot>
530
+ ${slots.map((slot) => `<slot name="${slot}" slot="${slot}"></slot>`).join('')}
531
+ </${wrappedEleName}>
532
+ `;
531
533
  }
532
534
 
533
- focus = this.#onFocus
535
+ focus = () => this.baseElement.focus()
534
536
 
535
537
  connectedCallback() {
536
- if (this.shadowRoot.isConnected) {
537
- this.proxyElement = this.shadowRoot.querySelector(wrappedEleName);
538
-
539
- this.addEventListener('focus', this.#boundOnFocus);
540
-
541
- // this is needed for components that uses props, such as combo box
542
- forwardProps(this.hostElement, this.proxyElement, includeForwardProps);
543
-
544
- // `onkeydown` is set on `proxyElement` support proper tab-index navigation
545
- // this support is needed since both proxy host and element catch `focus`/`blur` event
546
- // which causes faulty behavior.
547
- // we need this to happen only when the proxy component is in the light DOM,
548
- // otherwise it will focus the nested proxy element
549
- this.proxyElement.onkeydown = (e) => {
550
- if (e.shiftKey && e.keyCode === 9 && this.getRootNode() === document) {
551
- this.removeAttribute('tabindex');
552
- // We want to defer the action of setting the tab index back
553
- // so it will happen after focusing the previous element
554
- setTimeout(() => this.setAttribute('tabindex', '0'), 0);
555
- }
556
- };
538
+ super.connectedCallback?.();
557
539
 
558
- syncAttrs(this.proxyElement, this.hostElement, {
559
- excludeAttrs: excludeAttrsSync,
560
- includeAttrs: includeAttrsSync
561
- });
562
- }
563
- }
540
+ createEventListener.call(this, 'blur', (e) => {
541
+ this.#dispatchBlur();
542
+ }, { element: this.baseElement });
564
543
 
565
- attributeChangedCallback() {
566
- if (!this.proxyElement) {
567
- return;
568
- }
569
- }
544
+ createEventListener.call(this, 'focus', (e) => {
545
+ this.#dispatchFocus();
546
+ }, { element: this.baseElement });
547
+
548
+ createEventListener.call(this, 'focus', (e) => {
549
+ // if we got a focus event we want to focus the proxy element
550
+ if (e.isTrusted) {
551
+ this.focus();
552
+ }
553
+ });
570
554
 
571
- disconnectedCallback() {
572
- super.disconnectedCallback?.();
555
+ // this is needed for components that uses props, such as combo box
556
+ forwardProps(this, this.baseElement, includeForwardProps);
557
+
558
+ // `onkeydown` is set on `baseElement` support proper tab-index navigation
559
+ // this support is needed since both proxy host and element catch `focus`/`blur` event
560
+ // which causes faulty behavior.
561
+ // we need this to happen only when the proxy component is in the light DOM,
562
+ // otherwise it will focus the nested proxy element
563
+ this.baseElement.onkeydown = (e) => {
564
+ if (e.shiftKey && e.keyCode === 9 && this.getRootNode() === document) {
565
+ this.removeAttribute('tabindex');
566
+ // We want to defer the action of setting the tab index back
567
+ // so it will happen after focusing the previous element
568
+ setTimeout(() => this.setAttribute('tabindex', '0'), 0);
569
+ }
570
+ };
573
571
 
574
- this.removeEventListener('focus', this.#boundOnFocus);
572
+ syncAttrs(this.baseElement, this, {
573
+ excludeAttrs: excludeAttrsSync,
574
+ includeAttrs: includeAttrsSync
575
+ });
575
576
  }
576
577
  }
577
578
 
578
579
  return ProxyClass;
579
580
  };
580
581
 
581
- // move create event to here
582
-
583
- // usage example:
584
- // #dispatchSomething = createDispatchEvent.bind(this, 'something')
585
- function createDispatchEvent(eventName) {
586
- this[`on${eventName}`]?.(); // in case we got an event callback as property
587
- this.dispatchEvent(new Event(eventName));
588
- }
589
-
590
- const observedAttributes = [
582
+ const observedAttributes$1 = [
591
583
  'required',
592
584
  'pattern',
593
585
  ];
@@ -600,7 +592,7 @@ const inputValidationMixin = (superclass) => class InputValidationMixinClass ext
600
592
  static get observedAttributes() {
601
593
  return [
602
594
  ...superclass.observedAttributes || [],
603
- ...observedAttributes
595
+ ...observedAttributes$1
604
596
  ];
605
597
  }
606
598
 
@@ -608,19 +600,12 @@ const inputValidationMixin = (superclass) => class InputValidationMixinClass ext
608
600
  return true;
609
601
  }
610
602
 
611
- #dispatchValid = createDispatchEvent.bind(this, 'valid')
612
- #dispatchInvalid = createDispatchEvent.bind(this, 'invalid')
613
-
614
603
  #internals
615
604
 
616
- #boundedHandleInput
617
-
618
605
  constructor() {
619
606
  super();
620
607
 
621
608
  this.#internals = this.attachInternals();
622
-
623
- this.#boundedHandleInput = this.#handleInput.bind(this);
624
609
  }
625
610
 
626
611
  get defaultErrorMsgValueMissing() {
@@ -646,7 +631,7 @@ const inputValidationMixin = (superclass) => class InputValidationMixinClass ext
646
631
  }
647
632
  }
648
633
 
649
- setValidity() {
634
+ #setValidity() {
650
635
  const validity = this.getValidity();
651
636
  this.#internals.setValidity(validity, this.getErrorMessage(validity));
652
637
  }
@@ -676,7 +661,7 @@ const inputValidationMixin = (superclass) => class InputValidationMixinClass ext
676
661
  this.#internals.setValidity({ customError: true }, errorMessage);
677
662
  } else {
678
663
  this.#internals.setValidity({});
679
- this.setValidity();
664
+ this.#setValidity();
680
665
  }
681
666
  }
682
667
 
@@ -688,38 +673,25 @@ const inputValidationMixin = (superclass) => class InputValidationMixinClass ext
688
673
  return this.getAttribute('pattern')
689
674
  }
690
675
 
691
- #handleInput() {
692
- this.setValidity();
693
- this.handleDispatchValidationEvents();
676
+ get form() {
677
+ return this.#internals.form
694
678
  }
695
679
 
696
680
  attributeChangedCallback(attrName, oldValue, newValue) {
697
681
  super.attributeChangedCallback?.(attrName, oldValue, newValue);
698
682
 
699
- if (observedAttributes.includes(attrName)) {
700
- this.setValidity();
701
- }
702
- }
703
-
704
- handleDispatchValidationEvents() {
705
- if (this.checkValidity()) {
706
- this.#dispatchValid();
707
- } else {
708
- this.#dispatchInvalid();
683
+ if (observedAttributes$1.includes(attrName)) {
684
+ this.#setValidity();
709
685
  }
710
686
  }
711
687
 
712
688
  connectedCallback() {
713
689
  super.connectedCallback?.();
690
+ createEventListener.call(this, 'change', this.#setValidity);
691
+ createEventListener.call(this, 'invalid', (e) => e.stopPropagation());
692
+ createEventListener.call(this, 'input', this.#setValidity);
714
693
 
715
- this.addEventListener('input', this.#boundedHandleInput);
716
-
717
- this.setValidity();
718
- this.handleDispatchValidationEvents();
719
- }
720
-
721
- disconnectedCallback() {
722
- this.removeEventListener('input', this.#boundedHandleInput);
694
+ this.#setValidity();
723
695
  }
724
696
  };
725
697
 
@@ -759,26 +731,17 @@ const proxyInputMixin = (superclass) =>
759
731
 
760
732
  #inputElement
761
733
 
762
- #boundHandleFocus
763
- #boundHandleInvalid
764
- #boundHandleValid
734
+ #dispatchChange = createDispatchEvent.bind(this, 'change')
765
735
 
766
736
  constructor() {
767
737
  super();
768
738
 
769
739
  this.#inputElement = super.inputElement;
770
-
771
- this.#boundHandleFocus = this.#handleFocus.bind(this);
772
- this.#boundHandleInvalid = this.#handleInvalid.bind(this);
773
- this.#boundHandleValid = this.#handleValid.bind(this);
774
-
775
- this.baseEle = this.shadowRoot.querySelector(this.baseSelector);
776
-
777
740
  }
778
741
 
779
742
  get inputElement() {
780
- const inputSlot = this.baseEle.shadowRoot.querySelector('slot[name="input"]');
781
- const textAreaSlot = this.baseEle.shadowRoot.querySelector('slot[name="textarea"]');
743
+ const inputSlot = this.baseElement.shadowRoot.querySelector('slot[name="input"]');
744
+ const textAreaSlot = this.baseElement.shadowRoot.querySelector('slot[name="textarea"]');
782
745
 
783
746
  this.#inputElement ??= getNestedInput(inputSlot) || getNestedInput(textAreaSlot);
784
747
 
@@ -797,7 +760,6 @@ const proxyInputMixin = (superclass) =>
797
760
 
798
761
  reportValidityOnInternalInput() {
799
762
  setTimeout(() => {
800
- this.baseEle.focus(); //TODO: check if this is needed
801
763
  this.inputElement.reportValidity();
802
764
  });
803
765
  }
@@ -810,202 +772,140 @@ const proxyInputMixin = (superclass) =>
810
772
  }
811
773
  }
812
774
 
813
- setInternalInputErrorMessage() {
814
- if (!this.checkValidity()) {
775
+ handleInternalInputErrorMessage() {
776
+ if (!this.inputElement.checkValidity()) {
815
777
  this.inputElement.setCustomValidity(this.validationMessage);
816
778
  }
817
779
  }
818
780
 
819
- // when clicking on the form submit button and the input is invalid
820
- // we want it to appear as invalid
821
- #handleFocus(e) {
822
- if (e.relatedTarget?.form) {
823
- if (!this.checkValidity()) {
824
- this.setAttribute('invalid', 'true');
825
- }
826
-
827
- if (this.hasAttribute('invalid')) {
828
- this.reportValidityOnInternalInput();
829
- }
830
- }
831
- }
832
-
833
- #handleInvalid() {
834
- this.setInternalInputErrorMessage();
781
+ #handleErrorMessage() {
782
+ this.handleInternalInputErrorMessage();
835
783
  this.setAttribute('error-message', this.validationMessage);
836
784
  }
837
785
 
838
- #handleValid() {
839
- this.removeAttribute('invalid');
840
- }
841
-
842
786
  connectedCallback() {
843
787
  super.connectedCallback?.();
844
788
 
845
- // this is our way to identify that the form was submitted
846
- // in this case, we want the input to be in error state if it's not valid
847
- this.addEventListener('focus', this.#boundHandleFocus);
789
+ createEventListener.call(this, 'input', (e) => {
790
+ if (!this.inputElement.checkValidity()) {
791
+ this.inputElement.setCustomValidity('');
792
+ // after updating the input validity we want to trigger set validity on the wrapping element
793
+ // so we will get the updated validity
794
+ this.setCustomValidity('');
848
795
 
849
- this.addEventListener('invalid', this.#boundHandleInvalid);
850
- this.addEventListener('valid', this.#boundHandleValid);
796
+ // Vaadin is getting the input event before us,
797
+ // so in order to make sure they use the updated validity
798
+ // we calling their fn after updating the input validity
799
+ this.baseElement.__onInput(e);
851
800
 
852
- this.addEventListener('input', () => {
853
- this.inputElement.setCustomValidity('');
854
- if (!this.inputElement.checkValidity())
855
- this.setInternalInputErrorMessage();
801
+ this.#handleErrorMessage();
802
+ }
803
+ }, { element: this.inputElement });
804
+
805
+ createEventListener.call(this, 'change', () => {
806
+ this.#dispatchChange();
807
+ }, { element: this.baseElement });
808
+
809
+ createEventListener.call(this, 'blur', () => {
810
+ if (!this.checkValidity()) {
811
+ this.setAttribute('invalid', 'true');
812
+ this.#handleErrorMessage();
813
+ }
814
+ });
815
+
816
+ createEventListener.call(this, 'focus', (e) => {
817
+ // when clicking on the form submit button and the input is invalid
818
+ // we want it to appear as invalid
819
+ if (e.relatedTarget?.form === this.form) {
820
+ if (!this.checkValidity()) {
821
+ this.setAttribute('invalid', 'true');
822
+ }
823
+
824
+ if (this.hasAttribute('invalid')) {
825
+ this.reportValidityOnInternalInput();
826
+ }
827
+ }
856
828
  });
857
829
 
830
+ createEventListener.call(this, 'invalid', this.#handleErrorMessage);
831
+
832
+ this.handleInternalInputErrorMessage();
833
+
858
834
  // this is needed in order to make sure the form input validation is working
835
+ // we do not want it to happen when the component is nested
859
836
  if (!this.hasAttribute('tabindex') && this.getRootNode() === document) {
860
837
  this.setAttribute('tabindex', 0);
861
838
  }
862
839
 
863
840
  // sync properties
864
841
  propertyObserver(this, this.inputElement, 'value');
842
+ propertyObserver(this, this.inputElement, 'selectionStart');
865
843
  this.setSelectionRange = this.inputElement.setSelectionRange?.bind(this.inputElement);
866
844
  }
845
+ };
867
846
 
868
- disconnectedCallback() {
869
- this.removeEventListener('focus', this.#boundHandleFocus);
870
- this.removeEventListener('invalid', this.#boundHandleInvalid);
871
- this.removeEventListener('valid', this.#boundHandleValid);
872
- }
847
+ const componentNameValidationMixin = (superclass) =>
848
+ class ComponentNameValidationMixinClass extends superclass {
849
+ #checkComponentName() {
850
+ const currentComponentName = this.localName;
873
851
 
874
- attributeChangedCallback(attrName, oldValue, newValue) {
875
- super.attributeChangedCallback?.(attrName, oldValue, newValue);
852
+ if (!superclass.componentName) {
853
+ throw Error(
854
+ `component name is not defined on super class, make sure you have a static get for "componentName"`
855
+ );
856
+ }
876
857
 
877
- if (attrName === 'invalid' && newValue === '') {
878
- this.setAttribute('invalid', 'true');
858
+ if (currentComponentName !== superclass.componentName) {
859
+ throw Error(
860
+ `component name mismatch, expected "${superclass.componentName}", current "${currentComponentName}"`
861
+ );
879
862
  }
880
863
  }
881
- };
882
-
883
- const events = [
884
- 'blur',
885
- 'focus',
886
- 'focusin',
887
- 'focusout',
888
- ];
889
-
890
- const focusMixin = (superclass) => class FocusMixinClass extends superclass {
891
- #isFocused = false
892
- #setFocusTimer
893
-
894
- #boundedHandleFocus
895
- #boundedHandleBlur
896
- #boundedHandleFocusIn
897
- #boundedHandleFocusOut
898
-
899
- constructor() {
900
- super();
901
-
902
- for (const event of events) {
903
- this[`dispatch${upperFirst(event)}`] = function () {
904
- this.dispatchInputEvent(event);
905
- };
906
- }
907
-
908
- this.#boundedHandleFocus = this.#handleFocus.bind(this);
909
- this.#boundedHandleBlur = this.#handleBlur.bind(this);
910
- this.#boundedHandleFocusIn = this.#handleFocusIn.bind(this);
911
- this.#boundedHandleFocusOut = this.#handleFocusOut.bind(this);
912
- }
913
-
914
- #handleFocus(e) {
915
- if (this.isReadOnly) {
916
- e.stopPropagation();
917
- return
918
- }
919
- // we want to listen only to browser events
920
- // and not to events we are dispatching
921
- if (e.isTrusted) { // TODO: check if this is needed, because Vaadin is also dispatching events
922
- // we want to control the focus events that dispatched by the component
923
- // so we are stopping propagation and handling it in setFocus
924
- e.stopPropagation();
925
- this.setFocus(true);
926
-
927
- // if the focus event is on the root component (and not on the inner components)
928
- // we want to notify the component and let it decide what to do with it
929
- if (e.target === this) {
930
- this.onFocus(e);
931
- }
932
- }
933
- }
934
-
935
- #handleFocusOut(e) {
936
- // we want to listen only to browser events
937
- // and not to events we are dispatching
938
- if (e.isTrusted) {
939
- // we want to control the focus events that dispatched by the component
940
- // so we are stopping propagation and handling it in setFocus
941
- e.stopPropagation();
942
- }
943
- }
944
864
 
945
- #handleFocusIn(e) {
946
- // we want to listen only to browser events
947
- // and not to events we are dispatching
948
- if (e.isTrusted) {
949
- // we want to control the focus events that dispatched by the component
950
- // so we are stopping propagation and handling it in setFocus
951
- e.stopPropagation();
952
- }
953
- }
954
-
955
- #handleBlur(e) {
956
- if (e.isTrusted) {
957
- e.stopPropagation();
958
- this.setFocus(false);
959
- }
960
- }
961
-
962
- get isReadOnly() {
963
- return this.hasAttribute('readonly') && this.getAttribute('readonly') !== 'false'
964
- }
965
-
966
- // we want to debounce the calls to this fn
967
- // so we can support input like components with multiple inputs inside
968
- setFocus(isFocused) {
969
- clearTimeout(this.#setFocusTimer);
970
-
971
- this.#setFocusTimer = setTimeout(() => {
972
- if (this.#isFocused !== isFocused) {
973
- this.#isFocused = isFocused;
974
- if (isFocused) {
975
- this.dispatchFocus();
976
- this.dispatchFocusin();
977
- }
978
- else {
979
- this.dispatchBlur();
980
- this.dispatchFocusout();
981
- }
982
- }
983
- });
984
- }
865
+ connectedCallback() {
866
+ super.connectedCallback?.();
867
+ this.#checkComponentName();
868
+ }
869
+ };
985
870
 
986
- onFocus() {
987
- console.warn('onFocus', 'is not implemented');
988
- }
871
+ const hoverableMixin =
872
+ (superclass) =>
873
+ class HoverableMixinClass extends superclass {
874
+ connectedCallback() {
875
+ super.connectedCallback?.();
989
876
 
990
- dispatchInputEvent(eventName) {
991
- this[`on${eventName}`]?.(); // in case we got an event callback as property
992
- this.dispatchEvent(new InputEvent(eventName));
993
- }
877
+ createEventListener.call(this, 'mouseover', (e) => {
878
+ this.setAttribute('hover', 'true');
879
+ e.target.addEventListener(
880
+ 'mouseleave',
881
+ () => this.removeAttribute('hover'),
882
+ { once: true }
883
+ );
884
+ }, { element: this.baseElement });
885
+ }
886
+ };
994
887
 
888
+ const focusMixin = (superclass) => class FocusMixinClass extends superclass {
889
+ // we want to block all native events,
890
+ // so the input can control when to dispatch it based on its internal behavior
995
891
  connectedCallback() {
996
- super.connectedCallback?.();
892
+ super.connectedCallback?.();
893
+
894
+ createEventListener.call(this, 'blur', (e) => {
895
+ e.isTrusted && e.stopImmediatePropagation();
896
+ });
997
897
 
998
- this.addEventListener('focus', this.#boundedHandleFocus, true);
999
- this.addEventListener('blur', this.#boundedHandleBlur, true);
1000
- this.addEventListener('focusin', this.#boundedHandleFocusIn);
1001
- this.addEventListener('focusout', this.#boundedHandleFocusOut);
1002
- }
898
+ createEventListener.call(this, 'focus', (e) => {
899
+ e.isTrusted && e.stopImmediatePropagation();
900
+ });
901
+
902
+ createEventListener.call(this, 'focusout', (e) => {
903
+ e.isTrusted && e.stopImmediatePropagation();
904
+ });
1003
905
 
1004
- disconnectedCallback() {
1005
- this.removeEventListener('focus', this.#boundedHandleFocus);
1006
- this.removeEventListener('blur', this.#boundedHandleBlur);
1007
- this.removeEventListener('focusin', this.#boundedHandleFocusIn);
1008
- this.removeEventListener('focusout', this.#boundedHandleFocusOut);
906
+ createEventListener.call(this, 'focusin', (e) => {
907
+ e.isTrusted && e.stopImmediatePropagation();
908
+ });
1009
909
  }
1010
910
  };
1011
911
 
@@ -1071,48 +971,34 @@ const portalMixin = ({ name, selector, mappings = {} }) => (superclass) => {
1071
971
  };
1072
972
 
1073
973
  const changeMixin = (superclass) => class ChangeMixinClass extends superclass {
1074
-
1075
- #boundedHandleChange
1076
- #boundedHandleBlur
1077
-
1078
- #removeChangeListener
1079
- #removeBlurListener
1080
-
1081
974
  #dispatchChange = createDispatchEvent.bind(this, 'change')
1082
975
 
1083
- constructor() {
1084
- super();
1085
-
1086
- this.#boundedHandleChange = this.#handleChange.bind(this);
1087
- this.#boundedHandleBlur = this.#handleBlur.bind(this);
1088
- }
976
+ connectedCallback() {
977
+ super.connectedCallback?.();
978
+ this.prevValue = this.value;
1089
979
 
1090
- #handleChange(e) {
1091
- // we want to listen only to browser events
1092
- // and not to events we are dispatching
1093
- if (e.isTrusted) {
1094
- // we want to control the change events that dispatched by the component
1095
- // so we are stopping propagation and handling it in handleBlur
980
+ createEventListener.call(this, 'change', (e) => {
1096
981
  e.stopPropagation();
1097
- }
1098
- }
982
+ });
1099
983
 
1100
- #handleBlur() {
1101
- // on blur, we want to dispatch a change event
1102
- this.#dispatchChange();
984
+ createEventListener.call(this, 'blur', () => {
985
+ if (this.value !== this.prevValue) {
986
+ this.#dispatchChange();
987
+ this.prevValue = this.value;
988
+ }
989
+ });
1103
990
  }
991
+ };
1104
992
 
1105
- connectedCallback() {
1106
- super.connectedCallback?.();
1107
-
1108
- this.#removeChangeListener = addEventListener.bind();
1109
- this.addEventListener('change', this.#boundedHandleChange, true);
1110
- this.addEventListener('blur', this.#boundedHandleBlur);
1111
- }
993
+ // we want all the valueless attributes to have "true" value
994
+ const normalizeBooleanAttributesMixin = (superclass) => class NormalizeBooleanAttributesMixinClass extends superclass {
995
+ attributeChangedCallback(attrName, oldValue, newValue) {
996
+ if (newValue === '') {
997
+ this.setAttribute(attrName, 'true');
998
+ newValue = 'true';
999
+ }
1112
1000
 
1113
- disconnectedCallback() {
1114
- this.removeEventListener('change', this.#boundedHandleChange);
1115
- this.removeEventListener('blur', this.#boundedHandleBlur);
1001
+ super.attributeChangedCallback?.(attrName, oldValue, newValue);
1116
1002
  }
1117
1003
  };
1118
1004
 
@@ -1829,7 +1715,11 @@ overrides$4 = `
1829
1715
 
1830
1716
  customElements.define(componentName$7, NumberField);
1831
1717
 
1832
- var BaseInputClass = compose(focusMixin, inputValidationMixin, changeMixin)(HTMLElement); //todo: maybe we should use base class?
1718
+ const createBaseInputClass = (...args) => compose(
1719
+ focusMixin,
1720
+ inputValidationMixin,
1721
+ changeMixin,
1722
+ )(createBaseClass(...args));
1833
1723
 
1834
1724
  const focusElement = (ele) => {
1835
1725
  ele?.focus();
@@ -1847,23 +1737,26 @@ const getSanitizedCharacters = (str) => {
1847
1737
 
1848
1738
  const componentName$6 = getComponentName('passcode-internal');
1849
1739
 
1740
+ const observedAttributes = [
1741
+ 'disabled',
1742
+ 'bordered',
1743
+ 'size',
1744
+ 'invalid'
1745
+ ];
1746
+
1747
+ const BaseInputClass = createBaseInputClass({ componentName: componentName$6, baseSelector: ':host > div' });
1748
+
1850
1749
  class PasscodeInternal extends BaseInputClass {
1851
1750
  static get observedAttributes() {
1852
- return [
1853
- ...(BaseInputClass.observedAttributes || []),
1854
- 'disabled',
1855
- 'bordered',
1856
- 'size',
1857
- ];
1751
+ return observedAttributes.concat(BaseInputClass.observedAttributes || []);
1858
1752
  }
1859
1753
 
1860
1754
  static get componentName() {
1861
1755
  return componentName$6;
1862
1756
  }
1863
1757
 
1864
- #boundHandleInvalid = this.#handleInvalid.bind(this)
1865
- #boundHandleValid = this.#handleValid.bind(this)
1866
- #boundHandleBlur = this.#handleBlur.bind(this)
1758
+ #dispatchBlur = createDispatchEvent.bind(this, 'blur')
1759
+ #dispatchFocus = createDispatchEvent.bind(this, 'focus')
1867
1760
 
1868
1761
  constructor() {
1869
1762
  super();
@@ -1872,7 +1765,7 @@ class PasscodeInternal extends BaseInputClass {
1872
1765
  st-width="35px"
1873
1766
  data-id=${idx}
1874
1767
  type="tel"
1875
- autocomplete="none"
1768
+ autocomplete="off"
1876
1769
  ></descope-text-field>
1877
1770
  `);
1878
1771
 
@@ -1882,8 +1775,6 @@ class PasscodeInternal extends BaseInputClass {
1882
1775
  </div>
1883
1776
  `;
1884
1777
 
1885
- this.baseSelector = ':host > div';
1886
-
1887
1778
  this.inputs = Array.from(this.querySelectorAll('descope-text-field'));
1888
1779
  }
1889
1780
 
@@ -1909,16 +1800,6 @@ class PasscodeInternal extends BaseInputClass {
1909
1800
  return `^$|^\\d{${this.digits},}$`
1910
1801
  }
1911
1802
 
1912
- #handleInvalid() {
1913
- if (this.hasAttribute('invalid')) {
1914
- this.inputs.forEach(input => input.setAttribute('invalid', 'true'));
1915
- }
1916
- }
1917
-
1918
- #handleValid() {
1919
- this.inputs.forEach(input => input.removeAttribute('invalid'));
1920
- }
1921
-
1922
1803
  getValidity() {
1923
1804
  if (this.isRequired && !this.value) {
1924
1805
  return { valueMissing: true };
@@ -1931,25 +1812,17 @@ class PasscodeInternal extends BaseInputClass {
1931
1812
  }
1932
1813
  };
1933
1814
 
1934
- onFocus(){
1935
- this.inputs[0].focus();
1936
- }
1937
-
1938
1815
  connectedCallback() {
1816
+ // we are adding listeners before calling to super because it's stopping the events
1817
+ createEventListener.call(this, 'focus', (e) => {
1818
+ // we want to ignore focus events we are dispatching
1819
+ if (e.isTrusted)
1820
+ this.inputs[0].focus();
1821
+ });
1822
+
1939
1823
  super.connectedCallback?.();
1940
1824
 
1941
1825
  this.initInputs();
1942
-
1943
- this.addEventListener('invalid', this.#boundHandleInvalid);
1944
- this.addEventListener('valid', this.#boundHandleValid);
1945
- this.addEventListener('blur', this.#boundHandleBlur);
1946
- }
1947
-
1948
- disconnectedCallback() {
1949
- super.connectedCallback?.();
1950
- this.removeEventListener('invalid', this.#boundHandleInvalid);
1951
- this.removeEventListener('valid', this.#boundHandleValid);
1952
- this.removeEventListener('blur', this.#boundHandleBlur);
1953
1826
  }
1954
1827
 
1955
1828
  getInputIdx(inputEle) {
@@ -1981,33 +1854,60 @@ class PasscodeInternal extends BaseInputClass {
1981
1854
  focusElement(currentInput);
1982
1855
  };
1983
1856
 
1984
- #handleBlur() {
1985
- this.#handleInvalid();
1986
- }
1987
-
1988
1857
  initInputs() {
1858
+ let prevVal = this.value;
1859
+ let blurTimerId;
1860
+
1989
1861
  this.inputs.forEach((input) => {
1990
- input.oninput = (e) => {
1862
+ // in order to simulate blur on the input
1863
+ // we are checking if focus on one of the digits happened immediately after blur on another digit
1864
+ // if not, the component is no longer focused and we should simulate blur
1865
+ createEventListener.call(this, 'blur', (e) => {
1866
+ e.stopImmediatePropagation();
1867
+
1868
+ blurTimerId = setTimeout(() => {
1869
+ blurTimerId = null;
1870
+ this.#dispatchBlur();
1871
+ });
1872
+ }, { element: input });
1873
+
1874
+ createEventListener.call(this, 'focus', (e) => {
1875
+ e.stopImmediatePropagation();
1876
+
1877
+ clearTimeout(blurTimerId);
1878
+ if (!blurTimerId) {
1879
+ this.#dispatchFocus();
1880
+ }
1881
+ }, { element: input });
1882
+
1883
+ createEventListener.call(this, 'input', (e) => {
1991
1884
  const charArr = getSanitizedCharacters(input.value);
1992
1885
 
1993
1886
  if (!charArr.length) {
1994
1887
  // if we got an invalid value we want to clear the input
1995
1888
  input.value = '';
1996
- if (e.data === null) {
1997
- // if the user deleted the char, we want to focus the prev digit
1998
- focusElement(this.getPrevInput(input));
1999
- }
2000
1889
  }
2001
1890
  else this.fillDigits(charArr, input);
2002
- };
1891
+
1892
+ // we want to stop input events if the value wasn't change
1893
+ if (prevVal === this.value) {
1894
+ e.stopImmediatePropagation();
1895
+ }
1896
+ }, { element: input });
2003
1897
 
2004
1898
  input.onkeydown = ({ key }) => {
1899
+ prevVal = this.value;
1900
+
2005
1901
  // when user deletes a digit, we want to focus the previous digit
2006
1902
  if (key === 'Backspace') {
1903
+ // if the cursor is at 0, we want to move it to 1, so the value will be deleted
1904
+ if (!input.selectionStart) {
1905
+ input.setSelectionRange(1, 1);
1906
+ }
2007
1907
  setTimeout(() => {
2008
1908
  focusElement(this.getPrevInput(input));
2009
1909
  });
2010
- } else if (key.match(/^(\d)$/g)) { // if input is a digit
1910
+ } else if (key.length === 1) { // we want only characters and not command keys
2011
1911
  input.value = ''; // we are clearing the previous value so we can override it with the new value
2012
1912
  }
2013
1913
  };
@@ -2017,9 +1917,14 @@ class PasscodeInternal extends BaseInputClass {
2017
1917
  attributeChangedCallback(attrName, oldValue, newValue) {
2018
1918
  super.attributeChangedCallback?.(attrName, oldValue, newValue);
2019
1919
 
1920
+ // sync attributes to inputs
2020
1921
  if (oldValue !== newValue) {
2021
- if (PasscodeInternal.observedAttributes.includes(attrName) && !BaseInputClass.observedAttributes.includes(attrName)) {
2022
- this.inputs.forEach((input) => input.setAttribute(attrName, newValue));
1922
+ if (observedAttributes.includes(attrName)) {
1923
+ this.inputs.forEach(
1924
+ (input) => newValue === null ?
1925
+ input.removeAttribute(attrName) :
1926
+ input.setAttribute(attrName, newValue)
1927
+ );
2023
1928
  }
2024
1929
  }
2025
1930
  }
@@ -2112,7 +2017,7 @@ const customMixin = (superclass) =>
2112
2017
  ></${componentName$6}>
2113
2018
  `;
2114
2019
 
2115
- this.proxyElement.appendChild(template.content.cloneNode(true));
2020
+ this.baseElement.appendChild(template.content.cloneNode(true));
2116
2021
 
2117
2022
  this.inputElement = this.shadowRoot.querySelector(componentName$6);
2118
2023