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