@descope/web-components-ui 1.0.66 → 1.0.68

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 (51) hide show
  1. package/dist/cjs/index.cjs.js.map +1 -1
  2. package/dist/index.esm.js +481 -377
  3. package/dist/index.esm.js.map +1 -1
  4. package/dist/umd/135.js +1 -0
  5. package/dist/umd/descope-button-index-js.js +1 -1
  6. package/dist/umd/descope-checkbox-index-js.js +1 -1
  7. package/dist/umd/descope-combo-box-index-js.js +1 -1
  8. package/dist/umd/descope-container-index-js.js +1 -1
  9. package/dist/umd/descope-date-picker-index-js.js +1 -1
  10. package/dist/umd/descope-divider-index-js.js +1 -1
  11. package/dist/umd/descope-email-field-index-js.js +1 -1
  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/BaseInputClass.js +3 -2
  27. package/src/baseClasses/createBaseClass.js +29 -0
  28. package/src/components/descope-container/Container.js +17 -25
  29. package/src/components/descope-divider/Divider.js +32 -40
  30. package/src/components/descope-link/Link.js +8 -17
  31. package/src/components/descope-loader-linear/LoaderLinear.js +24 -29
  32. package/src/components/descope-loader-radial/LoaderRadial.js +18 -26
  33. package/src/components/descope-logo/Logo.js +4 -12
  34. package/src/components/descope-passcode/Passcode.js +4 -11
  35. package/src/components/descope-passcode/descope-passcode-internal/PasscodeInternal.js +41 -53
  36. package/src/components/descope-text/Text.js +4 -12
  37. package/src/helpers/index.js +2 -0
  38. package/src/helpers/mixinsHelpers.js +18 -0
  39. package/src/mixins/changeMixin.js +47 -0
  40. package/src/mixins/componentNameValidationMixin.js +1 -1
  41. package/src/mixins/createProxy.js +26 -28
  42. package/src/mixins/createStyleMixin/helpers.js +3 -3
  43. package/src/mixins/createStyleMixin/index.js +10 -9
  44. package/src/mixins/draggableMixin.js +1 -1
  45. package/src/mixins/focusMixin.js +130 -0
  46. package/src/mixins/hoverableMixin.js +14 -13
  47. package/src/mixins/index.js +3 -1
  48. package/src/mixins/{inputMixin.js → inputValidationMixin.js} +17 -58
  49. package/src/mixins/proxyInputMixin.js +50 -45
  50. package/dist/umd/860.js +0 -1
  51. package/src/baseClasses/DescopeBaseClass.js +0 -1
package/dist/index.esm.js CHANGED
@@ -24,6 +24,8 @@ const compose = (...fns) =>
24
24
 
25
25
  const upperFirst = (str) => str.charAt(0).toUpperCase() + str.slice(1);
26
26
 
27
+ const isFunction = (maybeFunc) => typeof maybeFunc === 'function';
28
+
27
29
  const DESCOPE_PREFIX = 'descope';
28
30
  const CSS_SELECTOR_SPECIFIER_MULTIPLY = 3;
29
31
  const BASE_THEME_SECTION = 'host';
@@ -175,7 +177,7 @@ const createCssSelector = (
175
177
  baseSelector = '',
176
178
  relativeSelectorOrSelectorFn = ''
177
179
  ) =>
178
- typeof relativeSelectorOrSelectorFn === 'function'
180
+ isFunction(relativeSelectorOrSelectorFn)
179
181
  ? relativeSelectorOrSelectorFn(baseSelector)
180
182
  : `${baseSelector}${/^[A-Za-z]/.test(relativeSelectorOrSelectorFn)
181
183
  ? ` ${relativeSelectorOrSelectorFn}`
@@ -232,7 +234,7 @@ const createStyle = (componentName, baseSelector, mappings) => {
232
234
  ({ selector: relativeSelectorOrSelectorFn, property }) => {
233
235
  style.add(
234
236
  createCssSelector(baseSelector, relativeSelectorOrSelectorFn),
235
- property,
237
+ isFunction(property) ? property() : property,
236
238
  createCssVarFallback(cssVarName)
237
239
  );
238
240
  }
@@ -315,7 +317,7 @@ const createStyleMixin =
315
317
  this.#onComponentThemeChange();
316
318
  }
317
319
 
318
- #createAttrOverrideStyle() {
320
+ #createOverridesStyle() {
319
321
  this.#overrideStyleEle = document.createElement('style');
320
322
  this.#overrideStyleEle.id = 'style-mixin-overrides';
321
323
 
@@ -325,7 +327,7 @@ const createStyleMixin =
325
327
  this.#rootElement.append(this.#overrideStyleEle);
326
328
  }
327
329
 
328
- #setAttrOverrideStyle(attrName, value) {
330
+ #setAttrOverride(attrName, value) {
329
331
  const style = this.#overrideStyleEle?.sheet?.cssRules[0].style;
330
332
  if (!style) return;
331
333
 
@@ -338,10 +340,10 @@ const createStyleMixin =
338
340
  else style?.removeProperty(varName);
339
341
  }
340
342
 
341
- #updateOverrideStyle(attrs = []) {
343
+ #updateOverridesStyle(attrs = []) {
342
344
  for (const attr of attrs) {
343
345
  if (this.#styleAttributes.includes(attr)) {
344
- this.#setAttrOverrideStyle(attr, this.getAttribute(attr));
346
+ this.#setAttrOverride(attr, this.getAttribute(attr));
345
347
  }
346
348
  }
347
349
 
@@ -349,7 +351,7 @@ const createStyleMixin =
349
351
  this.#overrideStyleEle.innerHTML = this.#overrideStyleEle?.sheet?.cssRules[0].cssText;
350
352
  }
351
353
 
352
- #createComponentStyle() {
354
+ #createMappingStyle() {
353
355
  const themeStyle = document.createElement('style');
354
356
  themeStyle.id = 'style-mixin-mappings';
355
357
  themeStyle.innerHTML = createStyle(
@@ -370,13 +372,14 @@ const createStyleMixin =
370
372
 
371
373
  this.#addClassName(superclass.componentName);
372
374
 
373
- this.#createComponentStyle();
375
+ // TODO: we should do everything we can on the constructor
376
+ // when dragging & dropping these styles are created over & over
377
+ this.#createMappingStyle();
374
378
  this.#createComponentTheme();
375
- this.#createAttrOverrideStyle();
379
+ this.#createOverridesStyle();
376
380
 
377
381
  // this is instead attributeChangedCallback because we cannot use static methods in this case
378
- observeAttributes(this, this.#updateOverrideStyle.bind(this), {});
379
-
382
+ observeAttributes(this, this.#updateOverridesStyle.bind(this), {});
380
383
  }
381
384
  }
382
385
 
@@ -398,7 +401,7 @@ const draggableMixin = (superclass) =>
398
401
  super();
399
402
 
400
403
  this.#styleEle = document.createElement('style');
401
- this.#styleEle.innerText = `${this.baseSelector} { cursor: inherit }`;
404
+ this.#styleEle.innerText = `* { cursor: inherit!important }`;
402
405
  }
403
406
 
404
407
  #handleDraggableStyle(isDraggable) {
@@ -417,31 +420,32 @@ const draggableMixin = (superclass) =>
417
420
  }
418
421
  };
419
422
 
420
- class DescopeBaseClass extends HTMLElement {}
423
+ const createBaseClass = ({ componentName, baseSelector = '' }) => {
424
+ class DescopeBaseClass extends HTMLElement {
425
+ static get componentName() {
426
+ return componentName;
427
+ }
421
428
 
422
- const hoverableMixin =
423
- (relativeSelector = '') =>
424
- (superclass) =>
425
- class HovrerableMixinClass extends superclass {
426
- connectedCallback() {
427
- super.connectedCallback?.();
429
+ #baseElement;
430
+ get baseSelector() {
431
+ return baseSelector
432
+ }
428
433
 
429
- const onMouseOver = (e) => {
430
- this.shadowRoot.host.setAttribute('hover', 'true');
431
- e.target.addEventListener(
432
- 'mouseleave',
433
- () => this.shadowRoot.host.removeAttribute('hover'),
434
- { once: true }
435
- );
436
- };
434
+ get baseElement() {
435
+ this.#baseElement ??= this.baseSelector ?
436
+ this.rootElement.querySelector(this.baseSelector) :
437
+ this;
437
438
 
438
- const baseElement = this.shadowRoot.querySelector(
439
- this.baseSelector + relativeSelector
440
- );
439
+ return this.#baseElement
440
+ }
441
441
 
442
- baseElement.addEventListener('mouseover', onMouseOver);
443
- }
444
- };
442
+ get rootElement() {
443
+ return this.shadowRoot || this
444
+ }
445
+ }
446
+
447
+ return compose(componentNameValidationMixin, hoverableMixin)(DescopeBaseClass)
448
+ };
445
449
 
446
450
  const createProxy = ({
447
451
  componentName,
@@ -460,38 +464,39 @@ const createProxy = ({
460
464
  </${wrappedEleName}>
461
465
  `;
462
466
 
463
- class ProxyElement extends DescopeBaseClass {
464
- static get componentName() {
465
- return componentName;
466
- }
467
-
467
+ class ProxyClass extends createBaseClass({ componentName, baseSelector: wrappedEleName }) {
468
468
  constructor() {
469
469
  super().attachShadow({ mode: 'open' }).innerHTML = template;
470
470
  this.hostElement = this.shadowRoot.host;
471
- this.baseSelector = wrappedEleName;
472
471
  this.shadowRoot.getElementById('create-proxy').innerHTML =
473
- typeof style === 'function' ? style() : style;
472
+ isFunction(style) ? style() : style;
474
473
  }
475
474
 
475
+ #boundOnFocus = this.#onFocus.bind(this);
476
+
477
+ // we want to focus on the proxy element when focusing our WCP
478
+ #onFocus() {
479
+ this.proxyElement.focus();
480
+ }
481
+
482
+ focus = this.#onFocus
483
+
476
484
  connectedCallback() {
477
485
  if (this.shadowRoot.isConnected) {
478
486
  this.proxyElement = this.shadowRoot.querySelector(wrappedEleName);
479
487
 
488
+ this.addEventListener('focus', this.#boundOnFocus);
489
+
480
490
  // this is needed for components that uses props, such as combo box
481
491
  forwardProps(this.hostElement, this.proxyElement, includeForwardProps);
482
492
 
483
- this.setAttribute('tabindex', '0');
484
-
485
- // we want to focus on the proxy element when focusing our WC
486
- this.addEventListener('focus', () => {
487
- this.proxyElement.focus();
488
- });
489
-
490
493
  // `onkeydown` is set on `proxyElement` support proper tab-index navigation
491
494
  // this support is needed since both proxy host and element catch `focus`/`blur` event
492
- // which causes faulty behaviour.
495
+ // which causes faulty behavior.
496
+ // we need this to happen only when the proxy component is in the light DOM,
497
+ // otherwise it will focus the nested proxy element
493
498
  this.proxyElement.onkeydown = (e) => {
494
- if (e.shiftKey && e.keyCode === 9) {
499
+ if (e.shiftKey && e.keyCode === 9 && this.getRootNode() === document) {
495
500
  this.removeAttribute('tabindex');
496
501
  // We want to defer the action of setting the tab index back
497
502
  // so it will happen after focusing the previous element
@@ -499,10 +504,6 @@ const createProxy = ({
499
504
  }
500
505
  };
501
506
 
502
- // sync events
503
- this.addEventListener = (...args) =>
504
- this.proxyElement.addEventListener(...args);
505
-
506
507
  syncAttrs(this.proxyElement, this.hostElement, {
507
508
  excludeAttrs: excludeAttrsSync,
508
509
  includeAttrs: includeAttrsSync
@@ -510,28 +511,30 @@ const createProxy = ({
510
511
  }
511
512
  }
512
513
 
513
- disconnectedCallback() {
514
- this.proxyElement.removeEventListener('mouseover', this.mouseoverCbRef);
515
- }
516
-
517
514
  attributeChangedCallback() {
518
515
  if (!this.proxyElement) {
519
516
  return;
520
517
  }
521
518
  }
519
+
520
+ disconnectedCallback() {
521
+ super.disconnectedCallback?.();
522
+
523
+ this.removeEventListener('focus', this.#boundOnFocus);
524
+ }
522
525
  }
523
526
 
524
- return compose(hoverableMixin())(ProxyElement);
527
+ return ProxyClass;
525
528
  };
526
529
 
527
- const events = [
528
- 'change',
529
- 'input',
530
- 'blur',
531
- 'focus',
532
- 'invalid',
533
- 'valid',
534
- ];
530
+ // move create event to here
531
+
532
+ // usage example:
533
+ // #dispatchSomething = createDispatchEvent.bind(this, 'something')
534
+ function createDispatchEvent(eventName) {
535
+ this[`on${eventName}`]?.(); // in case we got an event callback as property
536
+ this.dispatchEvent(new Event(eventName));
537
+ }
535
538
 
536
539
  const observedAttributes = [
537
540
  'required',
@@ -542,7 +545,7 @@ const errorAttributes = {
542
545
  valueMissing: 'data-errormessage-value-missing',
543
546
  patternMismatch: 'data-errormessage-pattern-mismatch'
544
547
  };
545
- const inputMixin = (superclass) => class InputMixinClass extends superclass {
548
+ const inputValidationMixin = (superclass) => class InputValidationMixinClass extends superclass {
546
549
  static get observedAttributes() {
547
550
  return [
548
551
  ...superclass.observedAttributes || [],
@@ -554,18 +557,19 @@ const inputMixin = (superclass) => class InputMixinClass extends superclass {
554
557
  return true;
555
558
  }
556
559
 
560
+ #dispatchValid = createDispatchEvent.bind(this, 'valid')
561
+ #dispatchInvalid = createDispatchEvent.bind(this, 'invalid')
562
+
557
563
  #internals
558
564
 
565
+ #boundedHandleInput
566
+
559
567
  constructor() {
560
568
  super();
561
569
 
562
570
  this.#internals = this.attachInternals();
563
571
 
564
- for (const event of events) {
565
- this[`dispatch${upperFirst(event)}`] = function () {
566
- this.dispatchInputEvent(event);
567
- };
568
- }
572
+ this.#boundedHandleInput = this.#handleInput.bind(this);
569
573
  }
570
574
 
571
575
  get defaultErrorMsgValueMissing() {
@@ -591,13 +595,7 @@ const inputMixin = (superclass) => class InputMixinClass extends superclass {
591
595
  }
592
596
  }
593
597
 
594
- get isReadOnly() {
595
- return this.getAttribute('readonly') !== 'false'
596
- }
597
-
598
598
  setValidity() {
599
- if (this.isReadOnly) return;
600
-
601
599
  const validity = this.getValidity();
602
600
  this.#internals.setValidity(validity, this.getErrorMessage(validity));
603
601
  }
@@ -607,7 +605,7 @@ const inputMixin = (superclass) => class InputMixinClass extends superclass {
607
605
  }
608
606
 
609
607
  getValidity() {
610
- throw Error('getValidity', 'is not implemented')
608
+ console.warn('getValidity', 'is not implemented');
611
609
  }
612
610
 
613
611
  checkValidity() {
@@ -639,36 +637,11 @@ const inputMixin = (superclass) => class InputMixinClass extends superclass {
639
637
  return this.getAttribute('pattern')
640
638
  }
641
639
 
642
- get value() {
643
- throw Error('get value', 'is not implemented')
644
- }
645
-
646
- set value(value) {
647
- throw Error('set value', 'is not implemented')
648
- }
649
-
650
- handleFocus() {
651
- throw Error('handleFocus', 'is not implemented')
652
- }
653
-
654
- handleInput() {
640
+ #handleInput() {
655
641
  this.setValidity();
656
642
  this.handleDispatchValidationEvents();
657
643
  }
658
644
 
659
- handleBlur() {
660
- throw Error('handleBlur', 'is not implemented')
661
- }
662
-
663
- handleChange() {
664
- throw Error('handleChange', 'is not implemented')
665
- }
666
-
667
- dispatchInputEvent(eventName) {
668
- this[`on${eventName}`]?.(); // in case we got an event callback as property
669
- this.dispatchEvent(new InputEvent(eventName));
670
- }
671
-
672
645
  attributeChangedCallback(attrName, oldValue, newValue) {
673
646
  super.attributeChangedCallback?.(attrName, oldValue, newValue);
674
647
 
@@ -679,25 +652,23 @@ const inputMixin = (superclass) => class InputMixinClass extends superclass {
679
652
 
680
653
  handleDispatchValidationEvents() {
681
654
  if (this.checkValidity()) {
682
- this.dispatchValid();
655
+ this.#dispatchValid();
683
656
  } else {
684
- this.dispatchInvalid();
657
+ this.#dispatchInvalid();
685
658
  }
686
659
  }
687
660
 
688
661
  connectedCallback() {
689
662
  super.connectedCallback?.();
690
663
 
664
+ this.addEventListener('input', this.#boundedHandleInput);
665
+
691
666
  this.setValidity();
692
667
  this.handleDispatchValidationEvents();
668
+ }
693
669
 
694
- // create proxy replace the addEventListener fn
695
- // and we want to be able to access the focus events
696
- // of this element and not the nested element's events
697
- this.onfocus = this.handleFocus.bind(this);
698
- this.addEventListener('input', this.handleInput.bind(this));
699
- this.addEventListener('blur', this.handleBlur.bind(this));
700
- this.addEventListener('change', this.handleBlur.bind(this));
670
+ disconnectedCallback() {
671
+ this.removeEventListener('input', this.#boundedHandleInput);
701
672
  }
702
673
  };
703
674
 
@@ -730,18 +701,28 @@ const getNestedInput = (ele) => {
730
701
  };
731
702
 
732
703
  const proxyInputMixin = (superclass) =>
733
- class proxyInputMixinClass extends inputMixin(superclass) {
704
+ class proxyInputMixinClass extends inputValidationMixin(superclass) {
734
705
  static get observedAttributes() {
735
706
  return [...superclass.observedAttributes || [], ...errorAttrs];
736
707
  }
737
708
 
738
709
  #inputElement
739
710
 
711
+ #boundHandleFocus
712
+ #boundHandleInvalid
713
+ #boundHandleValid
714
+
740
715
  constructor() {
741
716
  super();
742
717
 
743
718
  this.#inputElement = super.inputElement;
744
719
 
720
+ this.#boundHandleFocus = this.#handleFocus.bind(this);
721
+ this.#boundHandleInvalid = this.#handleInvalid.bind(this);
722
+ this.#boundHandleValid = this.#handleValid.bind(this);
723
+
724
+ this.baseEle = this.shadowRoot.querySelector(this.baseSelector);
725
+
745
726
  }
746
727
 
747
728
  get inputElement() {
@@ -749,7 +730,7 @@ const proxyInputMixin = (superclass) =>
749
730
  const textAreaSlot = this.baseEle.shadowRoot.querySelector('slot[name="textarea"]');
750
731
 
751
732
  this.#inputElement ??= getNestedInput(inputSlot) || getNestedInput(textAreaSlot);
752
-
733
+
753
734
  if (!this.#inputElement) throw Error('no input was found');
754
735
 
755
736
  return this.#inputElement
@@ -765,27 +746,17 @@ const proxyInputMixin = (superclass) =>
765
746
 
766
747
  reportValidityOnInternalInput() {
767
748
  setTimeout(() => {
749
+ this.baseEle.focus(); //TODO: check if this is needed
768
750
  this.inputElement.reportValidity();
769
- }, 0);
770
- }
771
-
772
- handleBlur() { }
773
-
774
- handleFocus() {
775
- this.inputElement.focus();
776
- if (this.hasAttribute('invalid')) {
777
- this.reportValidityOnInternalInput();
778
- }
751
+ });
779
752
  }
780
753
 
781
754
  // we want reportValidity to behave like form submission
782
755
  reportValidity() {
783
- const isValid = super.reportValidity();
784
756
  if (!isValid) {
785
757
  this.setAttribute('invalid', 'true');
786
- this.inputElement.focus();
758
+ this.reportValidityOnInternalInput();
787
759
  }
788
- this.reportValidityOnInternalInput();
789
760
  }
790
761
 
791
762
  setInternalInputErrorMessage() {
@@ -794,48 +765,47 @@ const proxyInputMixin = (superclass) =>
794
765
  }
795
766
  }
796
767
 
797
- connectedCallback() {
798
- this.baseEle = this.shadowRoot.querySelector(this.baseSelector);
799
-
800
- // this is our way to identify that the form was submitted
801
- // in this case, we want the input to be in error state if it's not valid
802
- this.addEventListener('focus', (e) => {
803
- if (e.relatedTarget?.form) {
804
- if (!this.checkValidity()) {
805
- this.setAttribute('invalid', 'true');
806
- }
768
+ // when clicking on the form submit button and the input is invalid
769
+ // we want it to appear as invalid
770
+ #handleFocus(e) {
771
+ if (e.relatedTarget?.form) {
772
+ if (!this.checkValidity()) {
773
+ this.setAttribute('invalid', 'true');
774
+ }
807
775
 
808
- if (this.hasAttribute('invalid')) {
809
- this.reportValidityOnInternalInput();
810
- }
776
+ if (this.hasAttribute('invalid')) {
777
+ this.reportValidityOnInternalInput();
811
778
  }
812
- });
779
+ }
780
+ }
813
781
 
814
- this.addEventListener('invalid', () => {
815
- this.setInternalInputErrorMessage();
816
- this.setAttribute('error-message', this.validationMessage);
817
- });
782
+ #handleInvalid() {
783
+ this.setInternalInputErrorMessage();
784
+ this.setAttribute('error-message', this.validationMessage);
785
+ }
818
786
 
819
- this.addEventListener('valid', () => {
820
- this.removeAttribute('invalid');
821
- });
787
+ #handleValid() {
788
+ this.removeAttribute('invalid');
789
+ }
822
790
 
791
+ connectedCallback() {
823
792
  super.connectedCallback?.();
824
793
 
825
- this.inputElement.addEventListener('input', () => {
794
+ // this is our way to identify that the form was submitted
795
+ // in this case, we want the input to be in error state if it's not valid
796
+ this.addEventListener('focus', this.#boundHandleFocus);
797
+
798
+ this.addEventListener('invalid', this.#boundHandleInvalid);
799
+ this.addEventListener('valid', this.#boundHandleValid);
800
+
801
+ this.addEventListener('input', () => {
826
802
  this.inputElement.setCustomValidity('');
827
803
  if (!this.inputElement.checkValidity())
828
804
  this.setInternalInputErrorMessage();
829
805
  });
830
806
 
831
- this.inputElement.addEventListener('invalid', () => {
832
- this.setValidity();
833
- this.setInternalInputErrorMessage();
834
- this.setAttribute('error-message', this.validationMessage);
835
- });
836
-
837
807
  // this is needed in order to make sure the form input validation is working
838
- if (!this.hasAttribute('tabindex')) {
808
+ if (!this.hasAttribute('tabindex') && this.getRootNode() === document) {
839
809
  this.setAttribute('tabindex', 0);
840
810
  }
841
811
 
@@ -844,6 +814,12 @@ const proxyInputMixin = (superclass) =>
844
814
  this.setSelectionRange = this.inputElement.setSelectionRange?.bind(this.inputElement);
845
815
  }
846
816
 
817
+ disconnectedCallback() {
818
+ this.removeEventListener('focus', this.#boundHandleFocus);
819
+ this.removeEventListener('invalid', this.#boundHandleInvalid);
820
+ this.removeEventListener('valid', this.#boundHandleValid);
821
+ }
822
+
847
823
  attributeChangedCallback(attrName, oldValue, newValue) {
848
824
  super.attributeChangedCallback?.(attrName, oldValue, newValue);
849
825
 
@@ -854,7 +830,7 @@ const proxyInputMixin = (superclass) =>
854
830
  };
855
831
 
856
832
  const componentNameValidationMixin = (superclass) =>
857
- class DraggableMixinClass extends superclass {
833
+ class ComponentNameValidationMixinClass extends superclass {
858
834
  #checkComponentName() {
859
835
  const currentComponentName = this.shadowRoot.host.tagName.toLowerCase();
860
836
 
@@ -879,6 +855,160 @@ const componentNameValidationMixin = (superclass) =>
879
855
  }
880
856
  };
881
857
 
858
+ const hoverableMixin =
859
+ (superclass) =>
860
+ class HoverableMixinClass extends superclass {
861
+ #boundOnMouseOver = this.#onMouseOver.bind(this)
862
+
863
+ #onMouseOver(e) {
864
+ this.setAttribute('hover', 'true');
865
+ e.target.addEventListener(
866
+ 'mouseleave',
867
+ () => this.shadowRoot.host.removeAttribute('hover'),
868
+ { once: true }
869
+ );
870
+ }
871
+
872
+ connectedCallback() {
873
+ super.connectedCallback?.();
874
+
875
+ const baseElement = this.shadowRoot.querySelector(
876
+ this.baseSelector
877
+ );
878
+
879
+ baseElement.addEventListener('mouseover', this.#boundOnMouseOver);
880
+ }
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
+
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
+ }
985
+
986
+ onFocus() {
987
+ console.warn('onFocus', 'is not implemented');
988
+ }
989
+
990
+ dispatchInputEvent(eventName) {
991
+ this[`on${eventName}`]?.(); // in case we got an event callback as property
992
+ this.dispatchEvent(new InputEvent(eventName));
993
+ }
994
+
995
+ connectedCallback() {
996
+ super.connectedCallback?.();
997
+
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
+ }
1003
+
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);
1009
+ }
1010
+ };
1011
+
882
1012
  const sanitizeSelector = (selector) => selector.replace(/[^\w\s]/gi, '');
883
1013
 
884
1014
  const appendSuffixToKeys = (obj, suffix) => Object.keys(obj).reduce((acc, key) =>
@@ -936,6 +1066,52 @@ const portalMixin = ({ name, selector, mappings = {} }) => (superclass) => {
936
1066
  }
937
1067
  };
938
1068
 
1069
+ const changeMixin = (superclass) => class ChangeMixinClass extends superclass {
1070
+
1071
+ #boundedHandleChange
1072
+ #boundedHandleBlur
1073
+
1074
+ #removeChangeListener
1075
+ #removeBlurListener
1076
+
1077
+ #dispatchChange = createDispatchEvent.bind(this, 'change')
1078
+
1079
+ constructor() {
1080
+ super();
1081
+
1082
+ this.#boundedHandleChange = this.#handleChange.bind(this);
1083
+ this.#boundedHandleBlur = this.#handleBlur.bind(this);
1084
+ }
1085
+
1086
+ #handleChange(e) {
1087
+ // we want to listen only to browser events
1088
+ // and not to events we are dispatching
1089
+ if (e.isTrusted) {
1090
+ // we want to control the change events that dispatched by the component
1091
+ // so we are stopping propagation and handling it in handleBlur
1092
+ e.stopPropagation();
1093
+ }
1094
+ }
1095
+
1096
+ #handleBlur() {
1097
+ // on blur, we want to dispatch a change event
1098
+ this.#dispatchChange();
1099
+ }
1100
+
1101
+ connectedCallback() {
1102
+ super.connectedCallback?.();
1103
+
1104
+ this.#removeChangeListener = addEventListener.bind();
1105
+ this.addEventListener('change', this.#boundedHandleChange, true);
1106
+ this.addEventListener('blur', this.#boundedHandleBlur);
1107
+ }
1108
+
1109
+ disconnectedCallback() {
1110
+ this.removeEventListener('change', this.#boundedHandleChange);
1111
+ this.removeEventListener('blur', this.#boundedHandleBlur);
1112
+ }
1113
+ };
1114
+
939
1115
  const componentName$i = getComponentName('button');
940
1116
 
941
1117
  const editorOverrides = `vaadin-button::part(label) { pointer-events: none; }`;
@@ -1063,41 +1239,36 @@ customElements.define(componentName$h, Checkbox);
1063
1239
 
1064
1240
  const componentName$g = getComponentName('loader-linear');
1065
1241
 
1066
- class RawLoaderLinear extends DescopeBaseClass {
1242
+ class RawLoaderLinear extends createBaseClass({ componentName: componentName$g, baseSelector: ':host > div' }) {
1067
1243
  static get componentName() {
1068
1244
  return componentName$g;
1069
1245
  }
1070
1246
  constructor() {
1071
1247
  super();
1072
- const template = document.createElement('template');
1073
- template.innerHTML = `
1074
- <style>
1075
- @keyframes tilt {
1076
- 0% { transform: translateX(0); }
1077
- 50% { transform: translateX(400%); }
1078
- }
1079
- :host {
1080
- position: relative;
1081
- display: inline-block
1082
- }
1083
- div::after {
1084
- content: '';
1085
- animation-name: tilt;
1086
- position: absolute;
1087
- left: 0;
1088
- }
1089
1248
 
1090
- :host > div {
1091
- width: 100%;
1092
- }
1093
- </style>
1094
- <div></div>
1095
- `;
1096
-
1097
- this.attachShadow({ mode: 'open' });
1098
- this.shadowRoot.appendChild(template.content.cloneNode(true));
1249
+ this.attachShadow({ mode: 'open' }).innerHTML = `
1250
+ <style>
1251
+ @keyframes tilt {
1252
+ 0% { transform: translateX(0); }
1253
+ 50% { transform: translateX(400%); }
1254
+ }
1255
+ :host {
1256
+ position: relative;
1257
+ display: inline-block
1258
+ }
1259
+ div::after {
1260
+ content: '';
1261
+ animation-name: tilt;
1262
+ position: absolute;
1263
+ left: 0;
1264
+ }
1099
1265
 
1100
- this.baseSelector = ':host > div';
1266
+ :host > div {
1267
+ width: 100%;
1268
+ }
1269
+ </style>
1270
+ <div></div>
1271
+ `;
1101
1272
  }
1102
1273
  }
1103
1274
 
@@ -1132,34 +1303,26 @@ customElements.define(componentName$g, LoaderLinear);
1132
1303
 
1133
1304
  const componentName$f = getComponentName('loader-radial');
1134
1305
 
1135
- class RawLoaderRadial extends DescopeBaseClass {
1136
- static get componentName() {
1137
- return componentName$f;
1138
- }
1306
+ class RawLoaderRadial extends createBaseClass({ componentName: componentName$f, baseSelector: ':host > div' }) {
1139
1307
  constructor() {
1140
1308
  super();
1141
- const template = document.createElement('template');
1142
- template.innerHTML = `
1143
- <style>
1144
- @keyframes spin {
1145
- 0% { transform: rotate(0deg); }
1146
- 100% { transform: rotate(360deg); }
1147
- }
1148
- :host {
1149
- position: relative;
1150
- display: inline-flex;
1151
- }
1152
- :host > div {
1153
- animation-name: spin;
1154
- }
1155
- </style>
1156
- <div></div>
1157
- `;
1158
-
1159
- this.attachShadow({ mode: 'open' });
1160
- this.shadowRoot.appendChild(template.content.cloneNode(true));
1161
1309
 
1162
- this.baseSelector = ':host > div';
1310
+ this.attachShadow({ mode: 'open' }).innerHTML = `
1311
+ <style>
1312
+ @keyframes spin {
1313
+ 0% { transform: rotate(0deg); }
1314
+ 100% { transform: rotate(360deg); }
1315
+ }
1316
+ :host {
1317
+ position: relative;
1318
+ display: inline-flex;
1319
+ }
1320
+ :host > div {
1321
+ animation-name: spin;
1322
+ }
1323
+ </style>
1324
+ <div></div>
1325
+ `;
1163
1326
  }
1164
1327
  }
1165
1328
 
@@ -1190,33 +1353,25 @@ customElements.define(componentName$f, LoaderRadial);
1190
1353
 
1191
1354
  const componentName$e = getComponentName('container');
1192
1355
 
1193
- class RawContainer extends DescopeBaseClass {
1194
- static get componentName() {
1195
- return componentName$e;
1196
- }
1356
+ class RawContainer extends createBaseClass({componentName: componentName$e, baseSelector: ':host > slot'}) {
1197
1357
  constructor() {
1198
1358
  super();
1199
- const template = document.createElement('template');
1200
- template.innerHTML = `
1201
- <style>
1202
- :host > slot {
1203
- box-sizing: border-box;
1204
- width: 100%;
1205
- height: 100%;
1206
- display: flex;
1207
- overflow: auto;
1208
- }
1209
- :host {
1210
- display: inline-block;
1211
- }
1212
- </style>
1213
- <slot></slot>
1214
- `;
1215
1359
 
1216
- this.attachShadow({ mode: 'open' });
1217
- this.shadowRoot.appendChild(template.content.cloneNode(true));
1218
-
1219
- this.baseSelector = ':host > slot';
1360
+ this.attachShadow({ mode: 'open' }).innerHTML = `
1361
+ <style>
1362
+ :host > slot {
1363
+ box-sizing: border-box;
1364
+ width: 100%;
1365
+ height: 100%;
1366
+ display: flex;
1367
+ overflow: auto;
1368
+ }
1369
+ :host {
1370
+ display: inline-block;
1371
+ }
1372
+ </style>
1373
+ <slot></slot>
1374
+ `;
1220
1375
  }
1221
1376
  }
1222
1377
 
@@ -1273,51 +1428,43 @@ const DatePicker = compose(
1273
1428
  customElements.define(componentName$d, DatePicker);
1274
1429
 
1275
1430
  const componentName$c = getComponentName('divider');
1276
- class RawDivider extends DescopeBaseClass {
1277
- static get componentName() {
1278
- return componentName$c;
1279
- }
1431
+ class RawDivider extends createBaseClass({ componentName: componentName$c, baseSelector: ':host > div' }) {
1280
1432
  constructor() {
1281
1433
  super();
1282
1434
 
1283
- const template = document.createElement('template');
1284
- template.innerHTML = `
1285
- <style>
1286
- :host > div {
1287
- display: flex;
1288
- height: 100%;
1289
- width: 100%;
1290
- }
1291
- :host > div::before,
1292
- :host > div::after {
1293
- content: '';
1294
- flex-grow: 1;
1295
- }
1296
-
1297
- descope-text {
1298
- flex-grow: 0;
1299
- flex-shrink: 0;
1300
- }
1435
+ this.attachShadow({ mode: 'open' }).innerHTML = `
1436
+ <style>
1437
+ :host > div {
1438
+ display: flex;
1439
+ height: 100%;
1440
+ width: 100%;
1441
+ }
1442
+ :host > div::before,
1443
+ :host > div::after {
1444
+ content: '';
1445
+ flex-grow: 1;
1446
+ }
1301
1447
 
1302
- :host(:empty) descope-text {
1303
- display: none;
1304
- }
1448
+ descope-text {
1449
+ flex-grow: 0;
1450
+ flex-shrink: 0;
1451
+ }
1305
1452
 
1306
- :host([vertical="true"]) div {
1307
- width: fit-content;
1308
- }
1453
+ :host(:empty) descope-text {
1454
+ display: none;
1455
+ }
1309
1456
 
1310
- </style>
1311
- <div>
1312
- <descope-text>
1313
- <slot></slot>
1314
- </descope-text>
1315
- </div>
1316
- `;
1317
- this.attachShadow({ mode: 'open' });
1318
- this.shadowRoot.appendChild(template.content.cloneNode(true));
1457
+ :host([vertical="true"]) div {
1458
+ width: fit-content;
1459
+ }
1319
1460
 
1320
- this.baseSelector = ':host > div';
1461
+ </style>
1462
+ <div>
1463
+ <descope-text>
1464
+ <slot></slot>
1465
+ </descope-text>
1466
+ </div>
1467
+ `;
1321
1468
 
1322
1469
  this.textComponent = this.shadowRoot.querySelector('descope-text');
1323
1470
 
@@ -1345,7 +1492,7 @@ const Divider = compose(
1345
1492
  alignItems: root,
1346
1493
  alignSelf: root,
1347
1494
  flexDirection: root,
1348
- textPadding: {...text$2, property: 'padding'},
1495
+ textPadding: { ...text$2, property: 'padding' },
1349
1496
  width: host$1,
1350
1497
  padding: host$1,
1351
1498
  backgroundColor: [before, after],
@@ -1361,14 +1508,11 @@ const Divider = compose(
1361
1508
 
1362
1509
  const componentName$b = getComponentName('text');
1363
1510
 
1364
- class RawText extends DescopeBaseClass {
1365
- static get componentName() {
1366
- return componentName$b;
1367
- }
1511
+ class RawText extends createBaseClass({ componentName: componentName$b, baseSelector: ':host > slot' }) {
1368
1512
  constructor() {
1369
1513
  super();
1370
- const template = document.createElement('template');
1371
- template.innerHTML = `
1514
+
1515
+ this.attachShadow({ mode: 'open' }).innerHTML = `
1372
1516
  <style>
1373
1517
  :host {
1374
1518
  display: inline-block;
@@ -1380,11 +1524,6 @@ class RawText extends DescopeBaseClass {
1380
1524
  </style>
1381
1525
  <slot></slot>
1382
1526
  `;
1383
-
1384
- this.attachShadow({ mode: 'open' });
1385
- this.shadowRoot.appendChild(template.content.cloneNode(true));
1386
-
1387
- this.baseSelector = ':host > slot';
1388
1527
  }
1389
1528
  }
1390
1529
 
@@ -1516,15 +1655,12 @@ overrides$5 = `
1516
1655
  customElements.define(componentName$a, EmailField);
1517
1656
 
1518
1657
  const componentName$9 = getComponentName('link');
1519
- class RawLink extends DescopeBaseClass {
1520
- static get componentName() {
1521
- return componentName$9;
1522
- }
1658
+ class RawLink extends createBaseClass({ componentName: componentName$9, baseSelector: ':host a' }) {
1523
1659
  constructor() {
1524
1660
  super();
1525
- const template = document.createElement('template');
1661
+ document.createElement('template');
1526
1662
 
1527
- template.innerHTML = `
1663
+ this.attachShadow({ mode: 'open' }).innerHTML = `
1528
1664
  <style>
1529
1665
  :host {
1530
1666
  display: inline-block;
@@ -1542,9 +1678,6 @@ class RawLink extends DescopeBaseClass {
1542
1678
  </div>
1543
1679
  `;
1544
1680
 
1545
- this.attachShadow({ mode: 'open' });
1546
- this.shadowRoot.appendChild(template.content.cloneNode(true));
1547
-
1548
1681
  forwardAttrs(this, this.shadowRoot.querySelector('a'), {
1549
1682
  includeAttrs: ['href', 'target', 'tooltip'],
1550
1683
  mapAttrs: {
@@ -1555,24 +1688,23 @@ class RawLink extends DescopeBaseClass {
1555
1688
  forwardAttrs(this, this.shadowRoot.querySelector('descope-text'), {
1556
1689
  includeAttrs: ['mode', 'variant'],
1557
1690
  });
1558
-
1559
- this.baseSelector = ':host > div';
1560
1691
  }
1561
1692
  }
1562
1693
 
1563
1694
  const selectors$2 = {
1564
1695
  host: { selector: () => 'host' },
1565
- anchor: { selector: '> a' },
1566
- text: { selector: Text.componentName }
1696
+ anchor: {},
1697
+ wrapper: {selector: () => ':host > div'},
1698
+ text: { selector: () => Text.componentName }
1567
1699
  };
1568
1700
 
1569
- const { anchor, text: text$1, host } = selectors$2;
1701
+ const { anchor, text: text$1, host, wrapper } = selectors$2;
1570
1702
 
1571
1703
  const Link = compose(
1572
1704
  createStyleMixin({
1573
1705
  mappings: {
1574
1706
  width: host,
1575
- textAlign: {},
1707
+ textAlign: wrapper,
1576
1708
  color: [anchor, { ...text$1, property: Text.cssVarList.color }],
1577
1709
  cursor: anchor,
1578
1710
  borderBottomWidth: anchor,
@@ -1580,7 +1712,6 @@ const Link = compose(
1580
1712
  borderBottomColor: anchor
1581
1713
  },
1582
1714
  }),
1583
- hoverableMixin(anchor.selector),
1584
1715
  draggableMixin,
1585
1716
  componentNameValidationMixin
1586
1717
  )(RawLink);
@@ -1592,23 +1723,15 @@ const componentName$8 = getComponentName('logo');
1592
1723
  let style;
1593
1724
  const getStyle = () => style;
1594
1725
 
1595
- class RawLogo extends DescopeBaseClass {
1596
- static get componentName() {
1597
- return componentName$8;
1598
- }
1726
+ class RawLogo extends createBaseClass({ componentName: componentName$8, baseSelector: ':host > div' }) {
1599
1727
  constructor() {
1600
1728
  super();
1601
- const template = document.createElement('template');
1602
- template.innerHTML = `
1729
+
1730
+ this.attachShadow({ mode: 'open' }).innerHTML = `
1603
1731
  <style>
1604
1732
  ${getStyle()}
1605
1733
  </style>
1606
1734
  <div></div>`;
1607
-
1608
- this.attachShadow({ mode: 'open' });
1609
- this.shadowRoot.appendChild(template.content.cloneNode(true));
1610
-
1611
- this.baseSelector = ':host > div';
1612
1735
  }
1613
1736
  }
1614
1737
 
@@ -1702,7 +1825,7 @@ overrides$4 = `
1702
1825
 
1703
1826
  customElements.define(componentName$7, NumberField);
1704
1827
 
1705
- var BaseInputClass = inputMixin(HTMLElement); //todo: maybe we should use base class?
1828
+ var BaseInputClass = compose(focusMixin, inputValidationMixin, changeMixin)(HTMLElement); //todo: maybe we should use base class?
1706
1829
 
1707
1830
  const focusElement = (ele) => {
1708
1831
  ele?.focus();
@@ -1723,11 +1846,10 @@ const componentName$6 = getComponentName('passcode-internal');
1723
1846
  class PasscodeInternal extends BaseInputClass {
1724
1847
  static get observedAttributes() {
1725
1848
  return [
1726
- ...BaseInputClass.observedAttributes,
1849
+ ...(BaseInputClass.observedAttributes || []),
1727
1850
  'disabled',
1728
1851
  'bordered',
1729
1852
  'size',
1730
- 'readonly'
1731
1853
  ];
1732
1854
  }
1733
1855
 
@@ -1735,10 +1857,12 @@ class PasscodeInternal extends BaseInputClass {
1735
1857
  return componentName$6;
1736
1858
  }
1737
1859
 
1860
+ #boundHandleInvalid = this.#handleInvalid.bind(this)
1861
+ #boundHandleValid = this.#handleValid.bind(this)
1862
+ #boundHandleBlur = this.#handleBlur.bind(this)
1863
+
1738
1864
  constructor() {
1739
1865
  super();
1740
- const template = document.createElement('template');
1741
-
1742
1866
  const inputs = [...Array(this.digits).keys()].map((idx) => `
1743
1867
  <descope-text-field
1744
1868
  st-width="35px"
@@ -1748,14 +1872,12 @@ class PasscodeInternal extends BaseInputClass {
1748
1872
  ></descope-text-field>
1749
1873
  `);
1750
1874
 
1751
- template.innerHTML = `
1875
+ this.innerHTML = `
1752
1876
  <div>
1753
1877
  ${inputs.join('')}
1754
1878
  </div>
1755
1879
  `;
1756
1880
 
1757
- this.appendChild(template.content.cloneNode(true));
1758
-
1759
1881
  this.baseSelector = ':host > div';
1760
1882
 
1761
1883
  this.inputs = Array.from(this.querySelectorAll('descope-text-field'));
@@ -1783,15 +1905,13 @@ class PasscodeInternal extends BaseInputClass {
1783
1905
  return `^$|^\\d{${this.digits},}$`
1784
1906
  }
1785
1907
 
1786
- handleInputsInvalid() {
1787
- setTimeout(() => {
1788
- if (this.hasAttribute('invalid')) {
1789
- this.inputs.forEach(input => input.setAttribute('invalid', 'true'));
1790
- }
1791
- });
1908
+ #handleInvalid() {
1909
+ if (this.hasAttribute('invalid')) {
1910
+ this.inputs.forEach(input => input.setAttribute('invalid', 'true'));
1911
+ }
1792
1912
  }
1793
1913
 
1794
- handleInputsValid() {
1914
+ #handleValid() {
1795
1915
  this.inputs.forEach(input => input.removeAttribute('invalid'));
1796
1916
  }
1797
1917
 
@@ -1807,17 +1927,25 @@ class PasscodeInternal extends BaseInputClass {
1807
1927
  }
1808
1928
  };
1809
1929
 
1810
- handleFocus() {
1930
+ onFocus(){
1811
1931
  this.inputs[0].focus();
1812
1932
  }
1813
1933
 
1814
- async connectedCallback() {
1815
- super.connectedCallback();
1934
+ connectedCallback() {
1935
+ super.connectedCallback?.();
1816
1936
 
1817
1937
  this.initInputs();
1818
1938
 
1819
- this.addEventListener('invalid', this.handleInputsInvalid);
1820
- this.addEventListener('valid', this.handleInputsValid);
1939
+ this.addEventListener('invalid', this.#boundHandleInvalid);
1940
+ this.addEventListener('valid', this.#boundHandleValid);
1941
+ this.addEventListener('blur', this.#boundHandleBlur);
1942
+ }
1943
+
1944
+ disconnectedCallback() {
1945
+ super.connectedCallback?.();
1946
+ this.removeEventListener('invalid', this.#boundHandleInvalid);
1947
+ this.removeEventListener('valid', this.#boundHandleValid);
1948
+ this.removeEventListener('blur', this.#boundHandleBlur);
1821
1949
  }
1822
1950
 
1823
1951
  getInputIdx(inputEle) {
@@ -1841,66 +1969,49 @@ class PasscodeInternal extends BaseInputClass {
1841
1969
  currentInput.value = charArr[i] ?? '';
1842
1970
 
1843
1971
  const nextInput = this.getNextInput(currentInput);
1972
+
1844
1973
  if (nextInput === currentInput) break;
1845
1974
  currentInput = nextInput;
1846
1975
  }
1847
1976
 
1848
- !currentInput.hasAttribute('focused') && focusElement(currentInput);
1977
+ focusElement(currentInput);
1849
1978
  };
1850
1979
 
1851
- handleBlur() {
1852
- this.handleInputsInvalid();
1980
+ #handleBlur() {
1981
+ this.#handleInvalid();
1853
1982
  }
1854
1983
 
1855
1984
  initInputs() {
1856
1985
  this.inputs.forEach((input) => {
1857
-
1858
- // in order to simulate blur on the input
1859
- // we are checking if focus on one of the digits happened immediately after blur on another digit
1860
- // if not, the component is no longer focused and we should simulate blur
1861
- input.addEventListener('blur', (e) => {
1862
- e.stopPropagation();
1863
- const timerId = setTimeout(() => {
1864
- this.dispatchBlur();
1865
- });
1866
-
1867
- this.inputs.forEach((ele) =>
1868
- ele.addEventListener('focus', () => clearTimeout(timerId), { once: true })
1869
- );
1870
- });
1871
-
1872
1986
  input.oninput = (e) => {
1873
- e.stopPropagation();
1874
1987
  const charArr = getSanitizedCharacters(input.value);
1875
1988
 
1876
- if (!charArr.length) input.value = ''; // if we got an invalid value we want to clear the input
1989
+ if (!charArr.length) {
1990
+ // if we got an invalid value we want to clear the input
1991
+ input.value = '';
1992
+ if (e.data === null) {
1993
+ // if the user deleted the char, we want to focus the prev digit
1994
+ focusElement(this.getPrevInput(input));
1995
+ }
1996
+ }
1877
1997
  else this.fillDigits(charArr, input);
1878
-
1879
- this.dispatchInput();
1880
1998
  };
1881
1999
 
1882
2000
  input.onkeydown = ({ key }) => {
2001
+ // when user deletes a digit, we want to focus the previous digit
1883
2002
  if (key === 'Backspace') {
1884
- input.value = '';
1885
-
1886
- // if the user deleted the digit we want to focus the previous digit
1887
- const prevInput = this.getPrevInput(input);
1888
-
1889
- !prevInput.hasAttribute('focused') && setTimeout(() => {
1890
- focusElement(prevInput);
2003
+ setTimeout(() => {
2004
+ focusElement(this.getPrevInput(input));
1891
2005
  });
1892
-
1893
- this.dispatchInput();
1894
2006
  } else if (key.match(/^(\d)$/g)) { // if input is a digit
1895
2007
  input.value = ''; // we are clearing the previous value so we can override it with the new value
1896
2008
  }
1897
-
1898
2009
  };
1899
2010
  });
1900
2011
  }
1901
2012
 
1902
2013
  attributeChangedCallback(attrName, oldValue, newValue) {
1903
- super.attributeChangedCallback(attrName, oldValue, newValue);
2014
+ super.attributeChangedCallback?.(attrName, oldValue, newValue);
1904
2015
 
1905
2016
  if (oldValue !== newValue) {
1906
2017
  if (PasscodeInternal.observedAttributes.includes(attrName) && !BaseInputClass.observedAttributes.includes(attrName)) {
@@ -1999,21 +2110,10 @@ const customMixin = (superclass) =>
1999
2110
 
2000
2111
  this.proxyElement.appendChild(template.content.cloneNode(true));
2001
2112
 
2002
- // we want to control when the element is out of focus
2003
- // so the validations will be triggered blur event is dispatched from descope-passcode internal (and not every time focusing a digit)
2004
- this.proxyElement._setFocused = () => { };
2005
-
2006
2113
  this.inputElement = this.shadowRoot.querySelector(componentName$6);
2007
2114
 
2008
2115
  forwardAttrs(this.shadowRoot.host, this.inputElement, { includeAttrs: ['required', 'pattern'] });
2009
2116
 
2010
- // we want to trigger validation only when dispatching a blur event from the descope-passcode-internal
2011
- this.inputElement.addEventListener('blur', (e) => {
2012
- // we do not want native blur events, only the ones that we are sending
2013
- if (!e.isTrusted){
2014
- this.proxyElement.validate();
2015
- }
2016
- });
2017
2117
  }
2018
2118
  };
2019
2119
 
@@ -2050,6 +2150,10 @@ const Passcode = compose(
2050
2150
  display: inline-block;
2051
2151
  }
2052
2152
 
2153
+ :host([readonly]) descope-passcode-internal > div {
2154
+ pointer-events: none;
2155
+ }
2156
+
2053
2157
  descope-passcode-internal {
2054
2158
  -webkit-mask-image: none;
2055
2159
  display: flex;