@descope/web-components-ui 1.0.67 → 1.0.69

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. package/dist/cjs/index.cjs.js.map +1 -1
  2. package/dist/index.esm.js +510 -395
  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-combo-box/ComboBox.js +18 -4
  29. package/src/components/descope-container/Container.js +17 -25
  30. package/src/components/descope-divider/Divider.js +32 -40
  31. package/src/components/descope-link/Link.js +8 -17
  32. package/src/components/descope-loader-linear/LoaderLinear.js +24 -29
  33. package/src/components/descope-loader-radial/LoaderRadial.js +18 -26
  34. package/src/components/descope-logo/Logo.js +4 -12
  35. package/src/components/descope-passcode/Passcode.js +4 -11
  36. package/src/components/descope-passcode/descope-passcode-internal/PasscodeInternal.js +41 -53
  37. package/src/components/descope-text/Text.js +4 -12
  38. package/src/helpers/index.js +2 -0
  39. package/src/helpers/mixinsHelpers.js +18 -0
  40. package/src/mixins/changeMixin.js +47 -0
  41. package/src/mixins/componentNameValidationMixin.js +1 -1
  42. package/src/mixins/createProxy.js +26 -28
  43. package/src/mixins/createStyleMixin/helpers.js +3 -3
  44. package/src/mixins/createStyleMixin/index.js +10 -9
  45. package/src/mixins/draggableMixin.js +1 -1
  46. package/src/mixins/focusMixin.js +130 -0
  47. package/src/mixins/hoverableMixin.js +14 -13
  48. package/src/mixins/index.js +3 -1
  49. package/src/mixins/{inputMixin.js → inputValidationMixin.js} +17 -58
  50. package/src/mixins/portalMixin.js +11 -7
  51. package/src/mixins/proxyInputMixin.js +50 -45
  52. package/src/theme/components/comboBox.js +2 -9
  53. package/dist/umd/860.js +0 -1
  54. 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.hasAttribute('readonly') && 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,10 +855,168 @@ const componentNameValidationMixin = (superclass) =>
879
855
  }
880
856
  };
881
857
 
882
- const sanitizeSelector = (selector) => selector.replace(/[^\w\s]/gi, '');
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
+ ];
883
889
 
884
- const appendSuffixToKeys = (obj, suffix) => Object.keys(obj).reduce((acc, key) =>
885
- Object.assign(acc, { [suffix + upperFirst(key)]: obj[key] }), {});
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
+
1012
+ // this is needed because we might generate the same css vars names
1013
+ // e.g. "overlayColor" attribute in style mixin's mapping,
1014
+ // will generate the same var as "color" attribute in portals's mapping
1015
+ // when the portal name is "overlay".
1016
+ // because of that, we are adding this separator that is also used as a differentiator
1017
+ const DISPLAY_NAME_SEPARATOR = '_';
1018
+
1019
+ const sanitizeSelector = (selector) => selector.replace(/[^\w\s]/gi, '');
886
1020
 
887
1021
  const getDomNode = (maybeDomNode) => maybeDomNode.host || maybeDomNode;
888
1022
 
@@ -897,7 +1031,7 @@ const portalMixin = ({ name, selector, mappings = {} }) => (superclass) => {
897
1031
  static get cssVarList() {
898
1032
  return {
899
1033
  ...BaseClass.cssVarList,
900
- ...createCssVarsList(superclass.componentName, appendSuffixToKeys(mappings, eleDisplayName))
1034
+ [eleDisplayName]: createCssVarsList(kebabCaseJoin(superclass.componentName, DISPLAY_NAME_SEPARATOR + eleDisplayName), mappings)
901
1035
  };
902
1036
  }
903
1037
 
@@ -907,14 +1041,14 @@ const portalMixin = ({ name, selector, mappings = {} }) => (superclass) => {
907
1041
  // we cannot use "this" before calling "super"
908
1042
  const getRootElement = (that) => {
909
1043
  const baseEle = that.shadowRoot.querySelector(that.baseSelector);
910
- const portal = baseEle.shadowRoot.querySelector(selector);
1044
+ const portal = selector ? baseEle.shadowRoot.querySelector(selector) : baseEle;
911
1045
 
912
1046
  return portal.shadowRoot || portal
913
1047
  };
914
1048
 
915
1049
  super({
916
1050
  getRootElement,
917
- componentNameSuffix: eleDisplayName,
1051
+ componentNameSuffix: DISPLAY_NAME_SEPARATOR + eleDisplayName,
918
1052
  themeSection: PORTAL_THEME_PREFIX + eleDisplayName,
919
1053
  baseSelector: ':host'
920
1054
  });
@@ -936,6 +1070,52 @@ const portalMixin = ({ name, selector, mappings = {} }) => (superclass) => {
936
1070
  }
937
1071
  };
938
1072
 
1073
+ const changeMixin = (superclass) => class ChangeMixinClass extends superclass {
1074
+
1075
+ #boundedHandleChange
1076
+ #boundedHandleBlur
1077
+
1078
+ #removeChangeListener
1079
+ #removeBlurListener
1080
+
1081
+ #dispatchChange = createDispatchEvent.bind(this, 'change')
1082
+
1083
+ constructor() {
1084
+ super();
1085
+
1086
+ this.#boundedHandleChange = this.#handleChange.bind(this);
1087
+ this.#boundedHandleBlur = this.#handleBlur.bind(this);
1088
+ }
1089
+
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
1096
+ e.stopPropagation();
1097
+ }
1098
+ }
1099
+
1100
+ #handleBlur() {
1101
+ // on blur, we want to dispatch a change event
1102
+ this.#dispatchChange();
1103
+ }
1104
+
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
+ }
1112
+
1113
+ disconnectedCallback() {
1114
+ this.removeEventListener('change', this.#boundedHandleChange);
1115
+ this.removeEventListener('blur', this.#boundedHandleBlur);
1116
+ }
1117
+ };
1118
+
939
1119
  const componentName$i = getComponentName('button');
940
1120
 
941
1121
  const editorOverrides = `vaadin-button::part(label) { pointer-events: none; }`;
@@ -1063,41 +1243,36 @@ customElements.define(componentName$h, Checkbox);
1063
1243
 
1064
1244
  const componentName$g = getComponentName('loader-linear');
1065
1245
 
1066
- class RawLoaderLinear extends DescopeBaseClass {
1246
+ class RawLoaderLinear extends createBaseClass({ componentName: componentName$g, baseSelector: ':host > div' }) {
1067
1247
  static get componentName() {
1068
1248
  return componentName$g;
1069
1249
  }
1070
1250
  constructor() {
1071
1251
  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
-
1090
- :host > div {
1091
- width: 100%;
1092
- }
1093
- </style>
1094
- <div></div>
1095
- `;
1096
1252
 
1097
- this.attachShadow({ mode: 'open' });
1098
- this.shadowRoot.appendChild(template.content.cloneNode(true));
1253
+ this.attachShadow({ mode: 'open' }).innerHTML = `
1254
+ <style>
1255
+ @keyframes tilt {
1256
+ 0% { transform: translateX(0); }
1257
+ 50% { transform: translateX(400%); }
1258
+ }
1259
+ :host {
1260
+ position: relative;
1261
+ display: inline-block
1262
+ }
1263
+ div::after {
1264
+ content: '';
1265
+ animation-name: tilt;
1266
+ position: absolute;
1267
+ left: 0;
1268
+ }
1099
1269
 
1100
- this.baseSelector = ':host > div';
1270
+ :host > div {
1271
+ width: 100%;
1272
+ }
1273
+ </style>
1274
+ <div></div>
1275
+ `;
1101
1276
  }
1102
1277
  }
1103
1278
 
@@ -1132,34 +1307,26 @@ customElements.define(componentName$g, LoaderLinear);
1132
1307
 
1133
1308
  const componentName$f = getComponentName('loader-radial');
1134
1309
 
1135
- class RawLoaderRadial extends DescopeBaseClass {
1136
- static get componentName() {
1137
- return componentName$f;
1138
- }
1310
+ class RawLoaderRadial extends createBaseClass({ componentName: componentName$f, baseSelector: ':host > div' }) {
1139
1311
  constructor() {
1140
1312
  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
1313
 
1159
- this.attachShadow({ mode: 'open' });
1160
- this.shadowRoot.appendChild(template.content.cloneNode(true));
1161
-
1162
- this.baseSelector = ':host > div';
1314
+ this.attachShadow({ mode: 'open' }).innerHTML = `
1315
+ <style>
1316
+ @keyframes spin {
1317
+ 0% { transform: rotate(0deg); }
1318
+ 100% { transform: rotate(360deg); }
1319
+ }
1320
+ :host {
1321
+ position: relative;
1322
+ display: inline-flex;
1323
+ }
1324
+ :host > div {
1325
+ animation-name: spin;
1326
+ }
1327
+ </style>
1328
+ <div></div>
1329
+ `;
1163
1330
  }
1164
1331
  }
1165
1332
 
@@ -1190,33 +1357,25 @@ customElements.define(componentName$f, LoaderRadial);
1190
1357
 
1191
1358
  const componentName$e = getComponentName('container');
1192
1359
 
1193
- class RawContainer extends DescopeBaseClass {
1194
- static get componentName() {
1195
- return componentName$e;
1196
- }
1360
+ class RawContainer extends createBaseClass({componentName: componentName$e, baseSelector: ':host > slot'}) {
1197
1361
  constructor() {
1198
1362
  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
-
1216
- this.attachShadow({ mode: 'open' });
1217
- this.shadowRoot.appendChild(template.content.cloneNode(true));
1218
1363
 
1219
- this.baseSelector = ':host > slot';
1364
+ this.attachShadow({ mode: 'open' }).innerHTML = `
1365
+ <style>
1366
+ :host > slot {
1367
+ box-sizing: border-box;
1368
+ width: 100%;
1369
+ height: 100%;
1370
+ display: flex;
1371
+ overflow: auto;
1372
+ }
1373
+ :host {
1374
+ display: inline-block;
1375
+ }
1376
+ </style>
1377
+ <slot></slot>
1378
+ `;
1220
1379
  }
1221
1380
  }
1222
1381
 
@@ -1273,51 +1432,43 @@ const DatePicker = compose(
1273
1432
  customElements.define(componentName$d, DatePicker);
1274
1433
 
1275
1434
  const componentName$c = getComponentName('divider');
1276
- class RawDivider extends DescopeBaseClass {
1277
- static get componentName() {
1278
- return componentName$c;
1279
- }
1435
+ class RawDivider extends createBaseClass({ componentName: componentName$c, baseSelector: ':host > div' }) {
1280
1436
  constructor() {
1281
1437
  super();
1282
1438
 
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
- }
1439
+ this.attachShadow({ mode: 'open' }).innerHTML = `
1440
+ <style>
1441
+ :host > div {
1442
+ display: flex;
1443
+ height: 100%;
1444
+ width: 100%;
1445
+ }
1446
+ :host > div::before,
1447
+ :host > div::after {
1448
+ content: '';
1449
+ flex-grow: 1;
1450
+ }
1301
1451
 
1302
- :host(:empty) descope-text {
1303
- display: none;
1304
- }
1452
+ descope-text {
1453
+ flex-grow: 0;
1454
+ flex-shrink: 0;
1455
+ }
1305
1456
 
1306
- :host([vertical="true"]) div {
1307
- width: fit-content;
1308
- }
1457
+ :host(:empty) descope-text {
1458
+ display: none;
1459
+ }
1309
1460
 
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));
1461
+ :host([vertical="true"]) div {
1462
+ width: fit-content;
1463
+ }
1319
1464
 
1320
- this.baseSelector = ':host > div';
1465
+ </style>
1466
+ <div>
1467
+ <descope-text>
1468
+ <slot></slot>
1469
+ </descope-text>
1470
+ </div>
1471
+ `;
1321
1472
 
1322
1473
  this.textComponent = this.shadowRoot.querySelector('descope-text');
1323
1474
 
@@ -1345,7 +1496,7 @@ const Divider = compose(
1345
1496
  alignItems: root,
1346
1497
  alignSelf: root,
1347
1498
  flexDirection: root,
1348
- textPadding: {...text$2, property: 'padding'},
1499
+ textPadding: { ...text$2, property: 'padding' },
1349
1500
  width: host$1,
1350
1501
  padding: host$1,
1351
1502
  backgroundColor: [before, after],
@@ -1361,14 +1512,11 @@ const Divider = compose(
1361
1512
 
1362
1513
  const componentName$b = getComponentName('text');
1363
1514
 
1364
- class RawText extends DescopeBaseClass {
1365
- static get componentName() {
1366
- return componentName$b;
1367
- }
1515
+ class RawText extends createBaseClass({ componentName: componentName$b, baseSelector: ':host > slot' }) {
1368
1516
  constructor() {
1369
1517
  super();
1370
- const template = document.createElement('template');
1371
- template.innerHTML = `
1518
+
1519
+ this.attachShadow({ mode: 'open' }).innerHTML = `
1372
1520
  <style>
1373
1521
  :host {
1374
1522
  display: inline-block;
@@ -1380,11 +1528,6 @@ class RawText extends DescopeBaseClass {
1380
1528
  </style>
1381
1529
  <slot></slot>
1382
1530
  `;
1383
-
1384
- this.attachShadow({ mode: 'open' });
1385
- this.shadowRoot.appendChild(template.content.cloneNode(true));
1386
-
1387
- this.baseSelector = ':host > slot';
1388
1531
  }
1389
1532
  }
1390
1533
 
@@ -1516,15 +1659,12 @@ overrides$5 = `
1516
1659
  customElements.define(componentName$a, EmailField);
1517
1660
 
1518
1661
  const componentName$9 = getComponentName('link');
1519
- class RawLink extends DescopeBaseClass {
1520
- static get componentName() {
1521
- return componentName$9;
1522
- }
1662
+ class RawLink extends createBaseClass({ componentName: componentName$9, baseSelector: ':host a' }) {
1523
1663
  constructor() {
1524
1664
  super();
1525
- const template = document.createElement('template');
1665
+ document.createElement('template');
1526
1666
 
1527
- template.innerHTML = `
1667
+ this.attachShadow({ mode: 'open' }).innerHTML = `
1528
1668
  <style>
1529
1669
  :host {
1530
1670
  display: inline-block;
@@ -1542,9 +1682,6 @@ class RawLink extends DescopeBaseClass {
1542
1682
  </div>
1543
1683
  `;
1544
1684
 
1545
- this.attachShadow({ mode: 'open' });
1546
- this.shadowRoot.appendChild(template.content.cloneNode(true));
1547
-
1548
1685
  forwardAttrs(this, this.shadowRoot.querySelector('a'), {
1549
1686
  includeAttrs: ['href', 'target', 'tooltip'],
1550
1687
  mapAttrs: {
@@ -1555,24 +1692,23 @@ class RawLink extends DescopeBaseClass {
1555
1692
  forwardAttrs(this, this.shadowRoot.querySelector('descope-text'), {
1556
1693
  includeAttrs: ['mode', 'variant'],
1557
1694
  });
1558
-
1559
- this.baseSelector = ':host > div';
1560
1695
  }
1561
1696
  }
1562
1697
 
1563
1698
  const selectors$2 = {
1564
1699
  host: { selector: () => 'host' },
1565
- anchor: { selector: '> a' },
1566
- text: { selector: Text.componentName }
1700
+ anchor: {},
1701
+ wrapper: {selector: () => ':host > div'},
1702
+ text: { selector: () => Text.componentName }
1567
1703
  };
1568
1704
 
1569
- const { anchor, text: text$1, host } = selectors$2;
1705
+ const { anchor, text: text$1, host, wrapper } = selectors$2;
1570
1706
 
1571
1707
  const Link = compose(
1572
1708
  createStyleMixin({
1573
1709
  mappings: {
1574
1710
  width: host,
1575
- textAlign: {},
1711
+ textAlign: wrapper,
1576
1712
  color: [anchor, { ...text$1, property: Text.cssVarList.color }],
1577
1713
  cursor: anchor,
1578
1714
  borderBottomWidth: anchor,
@@ -1580,7 +1716,6 @@ const Link = compose(
1580
1716
  borderBottomColor: anchor
1581
1717
  },
1582
1718
  }),
1583
- hoverableMixin(anchor.selector),
1584
1719
  draggableMixin,
1585
1720
  componentNameValidationMixin
1586
1721
  )(RawLink);
@@ -1592,23 +1727,15 @@ const componentName$8 = getComponentName('logo');
1592
1727
  let style;
1593
1728
  const getStyle = () => style;
1594
1729
 
1595
- class RawLogo extends DescopeBaseClass {
1596
- static get componentName() {
1597
- return componentName$8;
1598
- }
1730
+ class RawLogo extends createBaseClass({ componentName: componentName$8, baseSelector: ':host > div' }) {
1599
1731
  constructor() {
1600
1732
  super();
1601
- const template = document.createElement('template');
1602
- template.innerHTML = `
1733
+
1734
+ this.attachShadow({ mode: 'open' }).innerHTML = `
1603
1735
  <style>
1604
1736
  ${getStyle()}
1605
1737
  </style>
1606
1738
  <div></div>`;
1607
-
1608
- this.attachShadow({ mode: 'open' });
1609
- this.shadowRoot.appendChild(template.content.cloneNode(true));
1610
-
1611
- this.baseSelector = ':host > div';
1612
1739
  }
1613
1740
  }
1614
1741
 
@@ -1702,7 +1829,7 @@ overrides$4 = `
1702
1829
 
1703
1830
  customElements.define(componentName$7, NumberField);
1704
1831
 
1705
- var BaseInputClass = inputMixin(HTMLElement); //todo: maybe we should use base class?
1832
+ var BaseInputClass = compose(focusMixin, inputValidationMixin, changeMixin)(HTMLElement); //todo: maybe we should use base class?
1706
1833
 
1707
1834
  const focusElement = (ele) => {
1708
1835
  ele?.focus();
@@ -1723,11 +1850,10 @@ const componentName$6 = getComponentName('passcode-internal');
1723
1850
  class PasscodeInternal extends BaseInputClass {
1724
1851
  static get observedAttributes() {
1725
1852
  return [
1726
- ...BaseInputClass.observedAttributes,
1853
+ ...(BaseInputClass.observedAttributes || []),
1727
1854
  'disabled',
1728
1855
  'bordered',
1729
1856
  'size',
1730
- 'readonly'
1731
1857
  ];
1732
1858
  }
1733
1859
 
@@ -1735,10 +1861,12 @@ class PasscodeInternal extends BaseInputClass {
1735
1861
  return componentName$6;
1736
1862
  }
1737
1863
 
1864
+ #boundHandleInvalid = this.#handleInvalid.bind(this)
1865
+ #boundHandleValid = this.#handleValid.bind(this)
1866
+ #boundHandleBlur = this.#handleBlur.bind(this)
1867
+
1738
1868
  constructor() {
1739
1869
  super();
1740
- const template = document.createElement('template');
1741
-
1742
1870
  const inputs = [...Array(this.digits).keys()].map((idx) => `
1743
1871
  <descope-text-field
1744
1872
  st-width="35px"
@@ -1748,14 +1876,12 @@ class PasscodeInternal extends BaseInputClass {
1748
1876
  ></descope-text-field>
1749
1877
  `);
1750
1878
 
1751
- template.innerHTML = `
1879
+ this.innerHTML = `
1752
1880
  <div>
1753
1881
  ${inputs.join('')}
1754
1882
  </div>
1755
1883
  `;
1756
1884
 
1757
- this.appendChild(template.content.cloneNode(true));
1758
-
1759
1885
  this.baseSelector = ':host > div';
1760
1886
 
1761
1887
  this.inputs = Array.from(this.querySelectorAll('descope-text-field'));
@@ -1783,15 +1909,13 @@ class PasscodeInternal extends BaseInputClass {
1783
1909
  return `^$|^\\d{${this.digits},}$`
1784
1910
  }
1785
1911
 
1786
- handleInputsInvalid() {
1787
- setTimeout(() => {
1788
- if (this.hasAttribute('invalid')) {
1789
- this.inputs.forEach(input => input.setAttribute('invalid', 'true'));
1790
- }
1791
- });
1912
+ #handleInvalid() {
1913
+ if (this.hasAttribute('invalid')) {
1914
+ this.inputs.forEach(input => input.setAttribute('invalid', 'true'));
1915
+ }
1792
1916
  }
1793
1917
 
1794
- handleInputsValid() {
1918
+ #handleValid() {
1795
1919
  this.inputs.forEach(input => input.removeAttribute('invalid'));
1796
1920
  }
1797
1921
 
@@ -1807,17 +1931,25 @@ class PasscodeInternal extends BaseInputClass {
1807
1931
  }
1808
1932
  };
1809
1933
 
1810
- handleFocus() {
1934
+ onFocus(){
1811
1935
  this.inputs[0].focus();
1812
1936
  }
1813
1937
 
1814
- async connectedCallback() {
1815
- super.connectedCallback();
1938
+ connectedCallback() {
1939
+ super.connectedCallback?.();
1816
1940
 
1817
1941
  this.initInputs();
1818
1942
 
1819
- this.addEventListener('invalid', this.handleInputsInvalid);
1820
- this.addEventListener('valid', this.handleInputsValid);
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);
1821
1953
  }
1822
1954
 
1823
1955
  getInputIdx(inputEle) {
@@ -1841,66 +1973,49 @@ class PasscodeInternal extends BaseInputClass {
1841
1973
  currentInput.value = charArr[i] ?? '';
1842
1974
 
1843
1975
  const nextInput = this.getNextInput(currentInput);
1976
+
1844
1977
  if (nextInput === currentInput) break;
1845
1978
  currentInput = nextInput;
1846
1979
  }
1847
1980
 
1848
- !currentInput.hasAttribute('focused') && focusElement(currentInput);
1981
+ focusElement(currentInput);
1849
1982
  };
1850
1983
 
1851
- handleBlur() {
1852
- this.handleInputsInvalid();
1984
+ #handleBlur() {
1985
+ this.#handleInvalid();
1853
1986
  }
1854
1987
 
1855
1988
  initInputs() {
1856
1989
  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
1990
  input.oninput = (e) => {
1873
- e.stopPropagation();
1874
1991
  const charArr = getSanitizedCharacters(input.value);
1875
1992
 
1876
- if (!charArr.length) input.value = ''; // if we got an invalid value we want to clear the input
1993
+ if (!charArr.length) {
1994
+ // if we got an invalid value we want to clear the input
1995
+ 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
+ }
1877
2001
  else this.fillDigits(charArr, input);
1878
-
1879
- this.dispatchInput();
1880
2002
  };
1881
2003
 
1882
2004
  input.onkeydown = ({ key }) => {
2005
+ // when user deletes a digit, we want to focus the previous digit
1883
2006
  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);
2007
+ setTimeout(() => {
2008
+ focusElement(this.getPrevInput(input));
1891
2009
  });
1892
-
1893
- this.dispatchInput();
1894
2010
  } else if (key.match(/^(\d)$/g)) { // if input is a digit
1895
2011
  input.value = ''; // we are clearing the previous value so we can override it with the new value
1896
2012
  }
1897
-
1898
2013
  };
1899
2014
  });
1900
2015
  }
1901
2016
 
1902
2017
  attributeChangedCallback(attrName, oldValue, newValue) {
1903
- super.attributeChangedCallback(attrName, oldValue, newValue);
2018
+ super.attributeChangedCallback?.(attrName, oldValue, newValue);
1904
2019
 
1905
2020
  if (oldValue !== newValue) {
1906
2021
  if (PasscodeInternal.observedAttributes.includes(attrName) && !BaseInputClass.observedAttributes.includes(attrName)) {
@@ -1999,21 +2114,10 @@ const customMixin = (superclass) =>
1999
2114
 
2000
2115
  this.proxyElement.appendChild(template.content.cloneNode(true));
2001
2116
 
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
2117
  this.inputElement = this.shadowRoot.querySelector(componentName$6);
2007
2118
 
2008
2119
  forwardAttrs(this.shadowRoot.host, this.inputElement, { includeAttrs: ['required', 'pattern'] });
2009
2120
 
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
2121
  }
2018
2122
  };
2019
2123
 
@@ -2050,6 +2154,10 @@ const Passcode = compose(
2050
2154
  display: inline-block;
2051
2155
  }
2052
2156
 
2157
+ :host([readonly]) descope-passcode-internal > div {
2158
+ pointer-events: none;
2159
+ }
2160
+
2053
2161
  descope-passcode-internal {
2054
2162
  -webkit-mask-image: none;
2055
2163
  display: flex;
@@ -3231,9 +3339,21 @@ const ComboBoxMixin = (superclass) => class ComboBoxMixinClass extends superclas
3231
3339
  };
3232
3340
  }
3233
3341
 
3342
+ // the default vaadin behavior is to attach the overlay to the body when opened
3343
+ // we do not want that because it's difficult to style the overlay in this way
3344
+ // so we override it to open inside the shadow DOM
3345
+ #overrideOverlaySettings() {
3346
+ const overlay = this.baseElement.shadowRoot.querySelector('vaadin-combo-box-overlay');
3347
+
3348
+ overlay._attachOverlay = function () { this.bringToFront(); };
3349
+ overlay._detachOverlay = function () { };
3350
+ overlay._enterModalState = function () { };
3351
+ }
3352
+
3234
3353
  connectedCallback() {
3235
3354
  super.connectedCallback?.();
3236
3355
 
3356
+ this.#overrideOverlaySettings();
3237
3357
  observeChildren(this, this.#onChildrenChange.bind(this));
3238
3358
  }
3239
3359
  };
@@ -3251,15 +3371,17 @@ const ComboBox = compose(
3251
3371
  borderWidth: input,
3252
3372
  cursor: toggle,
3253
3373
  height: input,
3374
+ // overlayBackground: { property: () => ComboBox.cssVarList.overlay.backgroundColor },
3375
+ // overlayBorder: { property: () => ComboBox.cssVarList.overlay.border }
3254
3376
  }
3255
3377
  }),
3256
3378
  draggableMixin,
3257
3379
  portalMixin({
3258
3380
  name: 'overlay',
3259
- selector: 'vaadin-combo-box-scroller',
3381
+ selector: '',
3260
3382
  mappings: {
3261
- border: { selector: () => '::slotted(*)' },
3262
- backgroundColor: {},
3383
+ // border: { selector: 'vaadin-combo-box-scroller' },
3384
+ // backgroundColor: { selector: 'vaadin-combo-box-item' },
3263
3385
  }
3264
3386
  }),
3265
3387
  proxyInputMixin,
@@ -3296,15 +3418,8 @@ const comboBox = {
3296
3418
  [vars.borderWidth]: '0',
3297
3419
  [vars.cursor]: 'pointer',
3298
3420
  [vars.padding]: '0',
3299
-
3300
- '@overlay': {
3301
- [vars.overlayBackgroundColor] : 'red',
3302
- [vars.overlayBorder]: '3px solid blue',
3303
-
3304
- _hover: {
3305
- [vars.overlayBackgroundColor] : 'blue',
3306
- }
3307
- }
3421
+ // [vars.overlayBackground]: 'blue',
3422
+ // [vars.overlayBorder]: '3px solid red',
3308
3423
  };
3309
3424
 
3310
3425
  var components = {