@descope/web-components-ui 1.0.70 → 1.0.71
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|