@descope/web-components-ui 1.0.371 → 1.0.372

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.
@@ -2229,7 +2229,7 @@ const proxyInputMixin =
2229
2229
  ({
2230
2230
  proxyProps = [],
2231
2231
  // useProxyTargets flag allows to forwardProps to other targets, other than
2232
- // `this.inputElement`.
2232
+ // `this.inputElement`. It's to be used within `external-input` components,
2233
2233
  // if needed
2234
2234
  useProxyTargets = false,
2235
2235
  // allows us to set the event that should trigger validation
@@ -2368,7 +2368,10 @@ const proxyInputMixin =
2368
2368
 
2369
2369
  // sync properties
2370
2370
  proxyProps.forEach((prop) => {
2371
- const proxyTargets = useProxyTargets ? [this.baseElement].filter(Boolean) : [];
2371
+ const externalInput = this.querySelector('input[slot="external-input"]') || null;
2372
+ const proxyTargets = useProxyTargets
2373
+ ? [this.baseElement, externalInput].filter(Boolean)
2374
+ : [];
2372
2375
  forwardProps(this, [this.inputElement, ...proxyTargets], prop);
2373
2376
  });
2374
2377
 
@@ -2618,6 +2621,252 @@ const inputEventsDispatchingMixin = (superclass) =>
2618
2621
  }
2619
2622
  };
2620
2623
 
2624
+ // since on load we can only sample the color of the placeholder,
2625
+ // we need to temporarily populate the input in order to sample the value color
2626
+ const getValueColor = (ele, computedStyle) => {
2627
+ // eslint-disable-next-line no-param-reassign
2628
+ ele.value = '_';
2629
+
2630
+ const valueColor = computedStyle.getPropertyValue('color');
2631
+
2632
+ // eslint-disable-next-line no-param-reassign
2633
+ ele.value = '';
2634
+
2635
+ return valueColor;
2636
+ };
2637
+
2638
+ const createExternalInputSlot = (slotName, targetSlotName) => {
2639
+ const slotEle = document.createElement('slot');
2640
+
2641
+ slotEle.setAttribute('name', slotName);
2642
+ slotEle.setAttribute('slot', targetSlotName);
2643
+
2644
+ return slotEle;
2645
+ };
2646
+
2647
+ const createExternalInputEle = (targetSlotName, type, autocompleteType) => {
2648
+ const inputEle = document.createElement('input');
2649
+
2650
+ inputEle.setAttribute('slot', targetSlotName);
2651
+ inputEle.setAttribute('type', type);
2652
+ inputEle.setAttribute('data-hidden-input', 'true');
2653
+ inputEle.setAttribute('autocomplete', autocompleteType);
2654
+
2655
+ return inputEle;
2656
+ };
2657
+
2658
+ // We apply the original input's style to the external-input.
2659
+ // Eventually, the user should interact directly with the external input.
2660
+ // We keep the original input
2661
+ const applyExternalInputStyles = (sourceInputEle, targetInputEle, labelType) => {
2662
+ // We set a timeout here to avoid "Double Print" cases,
2663
+ // caused by sampling the computed style before it's ready.
2664
+ setTimeout(() => {
2665
+ const computedStyle = getComputedStyle(sourceInputEle);
2666
+ // Get minimal set of computed theme properties to set on external input
2667
+ const height = computedStyle.getPropertyValue('height');
2668
+ const paddingLeft = computedStyle.getPropertyValue('padding-left');
2669
+ const paddingRight = computedStyle.getPropertyValue('padding-right');
2670
+ const fontSize = computedStyle.getPropertyValue('font-size');
2671
+ const fontFamily = computedStyle.getPropertyValue('font-family');
2672
+ const letterSpacing = computedStyle.getPropertyValue('letter-spacing');
2673
+ const caretColor = computedStyle.getPropertyValue('caret-color');
2674
+
2675
+ const valueColor = getValueColor(sourceInputEle, computedStyle);
2676
+
2677
+ const commonThemeStyles = [
2678
+ ['all', 'unset'],
2679
+ ['position', 'absolute'],
2680
+ ['background-color', 'transparent'],
2681
+ ['height', height],
2682
+ ['left', paddingLeft],
2683
+ ['right', paddingRight],
2684
+ ['font-size', fontSize],
2685
+ ['font-family', fontFamily],
2686
+ ['letter-spacing', letterSpacing],
2687
+ ['caret-color', caretColor], // this is for seeing caret when focusing on external input
2688
+ ['color', valueColor],
2689
+ ];
2690
+
2691
+ commonThemeStyles.forEach(([key, val]) =>
2692
+ targetInputEle.style.setProperty(key, val, 'important')
2693
+ );
2694
+
2695
+ // Handle floating label theme properties
2696
+ if (labelType === 'floating') {
2697
+ const marginBottom = computedStyle.getPropertyValue('margin-bottom');
2698
+ targetInputEle.style.setProperty('margin-bottom', marginBottom, 'important');
2699
+ }
2700
+
2701
+ // sample and apply width only after original input is ready and fully rendered
2702
+ const width = computedStyle.getPropertyValue('width');
2703
+ targetInputEle.style.setProperty(
2704
+ 'width',
2705
+ `calc(${width} - ${paddingLeft} - ${paddingRight}`,
2706
+ 'important'
2707
+ );
2708
+ }, 0);
2709
+ };
2710
+
2711
+ const externalInputMixin =
2712
+ ({ inputType, autocompleteType, includeAttrs = [], noBlurDispatch = false }) =>
2713
+ (superclass) =>
2714
+ class ExternalInputMixinClass extends superclass {
2715
+ #timers = [];
2716
+
2717
+ get isExternalInput() {
2718
+ return this.getAttribute('external-input') === 'true';
2719
+ }
2720
+
2721
+ createExternalInput() {
2722
+ if (!this.isExternalInput) {
2723
+ return null;
2724
+ }
2725
+
2726
+ // use original input element as reference
2727
+ const origInput = this.baseElement.querySelector('input');
2728
+
2729
+ // to avoid focus loop between external-input and origInput
2730
+ // we set origInput's tabindex to -1
2731
+ // otherwise, shift-tab will never leave the component focus
2732
+ origInput.setAttribute('tabindex', '-1');
2733
+
2734
+ // create external slot
2735
+ const externalInputSlot = createExternalInputSlot('external-input', 'suffix');
2736
+
2737
+ // append external slot to base element
2738
+ this.baseElement.appendChild(externalInputSlot);
2739
+
2740
+ this.externalInput = createExternalInputEle(
2741
+ 'external-input',
2742
+ inputType,
2743
+ this.getAutocompleteType()
2744
+ );
2745
+
2746
+ // apply original input's styles to external input
2747
+ setTimeout(() => {
2748
+ applyExternalInputStyles(origInput, this.externalInput, this.getAttribute('label-type'));
2749
+ });
2750
+
2751
+ // 1Password catches the internal input, so we forward the value to the external input
2752
+ this.forwardInputValue(origInput, this.externalInput);
2753
+
2754
+ syncAttrs(origInput, this.externalInput, {
2755
+ includeAttrs,
2756
+ });
2757
+
2758
+ // We disable Vaadin's original `_setFocused` function, and use handleFocusEvents
2759
+ // and handleBlurEvents functions
2760
+ this.baseElement
2761
+ .querySelector('input')
2762
+ .addEventListener('focusout', (e) => e.stopImmediatePropagation(), true);
2763
+
2764
+ // In order to manage focus/blur events when moving between parts of the component
2765
+ // we're managing the event dispatching by ourselves, with the following strategy:
2766
+ // - If one of the component parts is focused - it means that the component is still focused - so we stop the blur events.
2767
+ // - When none of the component parts is focused, we dispatch blur event.
2768
+ this.handleFocusEvents();
2769
+ this.handleBlurEvents();
2770
+
2771
+ // sync input value
2772
+ this.handlelInputEvents(this.externalInput);
2773
+
2774
+ // append external input to component's DOM
2775
+ this.appendChild(this.externalInput);
2776
+
2777
+ return this.externalInput;
2778
+ }
2779
+
2780
+ clearBlurTimers() {
2781
+ this.#timers.forEach((timer) => clearTimeout(timer));
2782
+ this.#timers.length = 0;
2783
+ }
2784
+
2785
+ dispatchBlur() {
2786
+ return setTimeout(() => {
2787
+ this.dispatchEvent(new Event('blur', { bubbles: true, composed: true }));
2788
+ this.removeAttribute('focused');
2789
+ });
2790
+ }
2791
+
2792
+ handleFocusEvents() {
2793
+ // on baseElement `focus` we forward the focus to the external input.
2794
+ // also, in order to avoid any blur within the component, we clear the blur timers.
2795
+ this.baseElement.addEventListener('focus', () => {
2796
+ this.externalInput.focus();
2797
+ this.clearBlurTimers();
2798
+ });
2799
+
2800
+ // on `focus` of the external input, we manually set the `focused` attribute
2801
+ this.externalInput.addEventListener('focus', () => {
2802
+ this.clearBlurTimers();
2803
+ setTimeout(() => this.baseElement.setAttribute('focused', 'true'));
2804
+ });
2805
+ }
2806
+
2807
+ handleBlurEvents() {
2808
+ this.baseElement.addEventListener(
2809
+ 'blur',
2810
+ (e) => {
2811
+ e.stopImmediatePropagation();
2812
+ // some components do not require this synthetic blur dispatch (e.g. Password)
2813
+ // so we allow them to override this dispatch
2814
+ if (noBlurDispatch) return;
2815
+ this.#timers.push(this.dispatchBlur());
2816
+ },
2817
+ true
2818
+ );
2819
+
2820
+ this.externalInput.addEventListener(
2821
+ 'blur',
2822
+ (e) => {
2823
+ e.stopImmediatePropagation();
2824
+ this.#timers.push(this.dispatchBlur());
2825
+ },
2826
+ true
2827
+ );
2828
+ }
2829
+
2830
+ handlelInputEvents(externalInput) {
2831
+ // sync value of insible input back to original input
2832
+ externalInput.addEventListener('input', (e) => {
2833
+ this.value = e.target.value;
2834
+ });
2835
+
2836
+ // handle has-value attr
2837
+ externalInput.addEventListener('input', (e) => {
2838
+ if (!e.target.value) {
2839
+ this.removeAttribute('has-value');
2840
+ } else {
2841
+ this.setAttribute('has-value', 'true');
2842
+ }
2843
+ });
2844
+ }
2845
+
2846
+ getAutocompleteType() {
2847
+ return this.getAttribute('autocomplete') || autocompleteType;
2848
+ }
2849
+
2850
+ forwardInputValue(source, target) {
2851
+ // set internal sync events
2852
+ const valueDescriptor = Object.getOwnPropertyDescriptor(
2853
+ this.inputElement.constructor.prototype,
2854
+ 'value'
2855
+ );
2856
+
2857
+ Object.defineProperty(source, 'value', {
2858
+ ...valueDescriptor,
2859
+
2860
+ set(v) {
2861
+ valueDescriptor.set.call(this, v);
2862
+ // eslint-disable-next-line no-param-reassign
2863
+ target.value = v;
2864
+ },
2865
+ configurable: true,
2866
+ });
2867
+ }
2868
+ };
2869
+
2621
2870
  const getFileExtension = (path) => {
2622
2871
  const match = path.match(/\.([0-9a-z]+)(?:[\\?#]|$)/i);
2623
2872
  return match ? match[1] : null;
@@ -3057,6 +3306,10 @@ const {
3057
3306
  disabledPlaceholder,
3058
3307
  inputDisabled,
3059
3308
  inputIcon,
3309
+ externalInput,
3310
+ externalInputDisabled,
3311
+ externalPlaceholder,
3312
+ externalDisabledPlaceholder,
3060
3313
  } = {
3061
3314
  host: { selector: () => ':host' },
3062
3315
  label: { selector: '::part(label)' },
@@ -3073,6 +3326,10 @@ const {
3073
3326
  helperText: { selector: '::part(helper-text)' },
3074
3327
  errorMessage: { selector: '::part(error-message)' },
3075
3328
  inputIcon: { selector: 'vaadin-icon' },
3329
+ externalInput: { selector: () => '::slotted(input)' },
3330
+ externalInputDisabled: { selector: () => '::slotted(input:disabled)' },
3331
+ externalPlaceholder: { selector: () => '::slotted(input:placeholder-shown)' },
3332
+ externalDisabledPlaceholder: { selector: () => '::slotted(input:disabled::placeholder)' },
3076
3333
  };
3077
3334
 
3078
3335
  var textFieldMappings = {
@@ -3105,8 +3362,12 @@ var textFieldMappings = {
3105
3362
  inputValueTextColor: [
3106
3363
  { ...inputField$6, property: 'color' },
3107
3364
  { ...inputDisabled, property: '-webkit-text-fill-color' },
3365
+ { ...externalInputDisabled, property: '-webkit-text-fill-color' },
3366
+ ],
3367
+ inputCaretTextColor: [
3368
+ { ...input, property: 'caret-color' },
3369
+ { ...externalInput, property: 'caret-color' },
3108
3370
  ],
3109
- inputCaretTextColor: [{ ...input, property: 'color' }],
3110
3371
 
3111
3372
  labelRequiredIndicator: { ...requiredIndicator$b, property: 'content' },
3112
3373
 
@@ -3119,6 +3380,8 @@ var textFieldMappings = {
3119
3380
  inputHorizontalPadding: [
3120
3381
  { ...input, property: 'padding-left' },
3121
3382
  { ...input, property: 'padding-right' },
3383
+ { ...externalInput, property: 'padding-left' },
3384
+ { ...externalInput, property: 'padding-right' },
3122
3385
  ],
3123
3386
 
3124
3387
  inputOutlineColor: { ...inputField$6, property: 'outline-color' },
@@ -3126,12 +3389,17 @@ var textFieldMappings = {
3126
3389
  inputOutlineWidth: { ...inputField$6, property: 'outline-width' },
3127
3390
  inputOutlineOffset: { ...inputField$6, property: 'outline-offset' },
3128
3391
 
3129
- inputTextAlign: [{ ...input, property: 'text-align' }],
3392
+ inputTextAlign: [
3393
+ { ...input, property: 'text-align' },
3394
+ { ...externalInput, property: 'text-align' },
3395
+ ],
3130
3396
 
3131
3397
  inputPlaceholderColor: [
3132
3398
  { selector: () => ':host input:placeholder-shown', property: 'color' },
3399
+ { ...externalPlaceholder, property: 'color' },
3133
3400
  { ...placeholder$3, property: 'color' },
3134
3401
  { ...disabledPlaceholder, property: '-webkit-text-fill-color' },
3402
+ { ...externalDisabledPlaceholder, property: '-webkit-text-fill-color' },
3135
3403
  ],
3136
3404
 
3137
3405
  labelPosition: { ...label$9, property: 'position' },
@@ -3143,10 +3411,22 @@ var textFieldMappings = {
3143
3411
  inputTransformY: { ...label$9, property: 'transform' },
3144
3412
  inputTransition: { ...label$9, property: 'transition' },
3145
3413
  marginInlineStart: { ...label$9, property: 'margin-inline-start' },
3146
- placeholderOpacity: [{ selector: '> input:placeholder-shown', property: 'opacity' }],
3147
- inputVerticalAlignment: [{ ...inputField$6, property: 'align-items' }],
3148
- valueInputHeight: [{ ...input, property: 'height' }],
3149
- valueInputMarginBottom: [{ ...input, property: 'margin-bottom' }],
3414
+ placeholderOpacity: [
3415
+ { selector: '> input:placeholder-shown', property: 'opacity' },
3416
+ { ...externalPlaceholder, property: 'opacity' },
3417
+ ],
3418
+ inputVerticalAlignment: [
3419
+ { ...inputField$6, property: 'align-items' },
3420
+ { ...externalInput, property: 'align-items' },
3421
+ ],
3422
+ valueInputHeight: [
3423
+ { ...input, property: 'height' },
3424
+ { ...externalInput, property: 'height' },
3425
+ ],
3426
+ valueInputMarginBottom: [
3427
+ { ...input, property: 'margin-bottom' },
3428
+ { ...externalInput, property: 'margin-bottom' },
3429
+ ],
3150
3430
 
3151
3431
  inputIconOffset: [
3152
3432
  { ...inputIcon, property: 'margin-right' },
@@ -3658,6 +3938,15 @@ const customMixin$b = (superclass) =>
3658
3938
  this.origSetPasswordVisible = this.baseElement._setPasswordVisible;
3659
3939
  this.origSetFocused = this.baseElement._setFocused;
3660
3940
  this.baseElement._setFocused = this.setFocus.bind(this);
3941
+
3942
+ this.initExternalInput();
3943
+ }
3944
+
3945
+ initExternalInput() {
3946
+ const externalInput = this.createExternalInput();
3947
+ if (externalInput) {
3948
+ this.handlePasswordVisibility(externalInput);
3949
+ }
3661
3950
  }
3662
3951
 
3663
3952
  get caretPosition() {
@@ -3672,6 +3961,7 @@ const customMixin$b = (superclass) =>
3672
3961
  setTimeout(() => {
3673
3962
  origTogglePasswordVisibility();
3674
3963
  this.inputElement.setSelectionRange(this.caretPosition, this.caretPosition);
3964
+ return false;
3675
3965
  });
3676
3966
  };
3677
3967
  }
@@ -3698,6 +3988,44 @@ const customMixin$b = (superclass) =>
3698
3988
  attributeChangedCallback(attrName, oldValue, newValue) {
3699
3989
  super.attributeChangedCallback?.(attrName, oldValue, newValue);
3700
3990
  }
3991
+
3992
+ // override vaadin's password visibility toggle.
3993
+ // we need this override in order to to resolve the external input `focus` race condition,
3994
+ // which is caused due to the focus sync between the two inputs.
3995
+ handlePasswordVisibility(externalInput) {
3996
+ // disable vaadin's `__boundRevealButtonMouseDown` mouse-down event lisetener
3997
+ const origBoundRevealButtonMouseDown = this.baseElement.__boundRevealButtonMouseDown;
3998
+ this.baseElement
3999
+ .querySelector('vaadin-password-field-button')
4000
+ .removeEventListener('mousedown', origBoundRevealButtonMouseDown);
4001
+
4002
+ // disable vaadin's `_passwordVisibleChanged` observer
4003
+ this.baseElement._passwordVisibleChanged = () => {};
4004
+
4005
+ // override vaadin's `_togglePasswordVisibility`
4006
+ this.baseElement._togglePasswordVisibility = () => {
4007
+ const currVisibility = externalInput.getAttribute('type');
4008
+ if (currVisibility === 'password') {
4009
+ this.showPasswordVisibility(externalInput);
4010
+ } else {
4011
+ this.hidePasswordVisibility(externalInput);
4012
+ }
4013
+ };
4014
+ }
4015
+
4016
+ showPasswordVisibility(input) {
4017
+ // handle input element's type
4018
+ input.setAttribute('type', 'text');
4019
+ // handle vaadin's `password-visible` attribute
4020
+ this.setAttribute('password-visible', 'true');
4021
+ }
4022
+
4023
+ hidePasswordVisibility(input) {
4024
+ // handle input element's type
4025
+ input.setAttribute('type', 'password');
4026
+ // handle vaadin's `password-visible` attribute
4027
+ this.setAttribute('password-visible', 'false');
4028
+ }
3701
4029
  };
3702
4030
 
3703
4031
  const {
@@ -3792,7 +4120,12 @@ const PasswordClass = compose(
3792
4120
  },
3793
4121
  }),
3794
4122
  draggableMixin,
3795
- composedProxyInputMixin({ proxyProps: ['value', 'selectionStart'] }),
4123
+ externalInputMixin({
4124
+ inputType: 'password',
4125
+ includeAttrs: ['disabled', 'readonly', 'pattern', 'type', 'autocomplete'],
4126
+ noBlurDispatch: true,
4127
+ }),
4128
+ composedProxyInputMixin({ proxyProps: ['value', 'selectionStart'], useProxyTargets: true }),
3796
4129
  componentNameValidationMixin,
3797
4130
  passwordDraggableMixin,
3798
4131
  customMixin$b
@@ -3856,6 +4189,10 @@ const PasswordClass = compose(
3856
4189
  ::part(reveal-button) {
3857
4190
  align-self: center;
3858
4191
  }
4192
+
4193
+ vaadin-password-field[external-input="true"] > input:not(:placeholder-shown) {
4194
+ opacity: 0;
4195
+ }
3859
4196
  `,
3860
4197
  excludeAttrsSync: ['tabindex'],
3861
4198
  componentName: componentName$R,
@@ -4007,6 +4344,8 @@ const customMixin$a = (superclass) =>
4007
4344
  if (!this.getAttribute('autocomplete')) {
4008
4345
  this.setAttribute('autocomplete', defaultAutocomplete);
4009
4346
  }
4347
+
4348
+ this.createExternalInput();
4010
4349
  }
4011
4350
  };
4012
4351
 
@@ -4015,6 +4354,11 @@ const EmailFieldClass = compose(
4015
4354
  mappings: textFieldMappings,
4016
4355
  }),
4017
4356
  draggableMixin,
4357
+ externalInputMixin({
4358
+ inputType: 'text',
4359
+ autocompleteType: 'username',
4360
+ includeAttrs: ['disabled', 'readonly', 'pattern'],
4361
+ }),
4018
4362
  composedProxyInputMixin({ proxyProps: ['value', 'selectionStart'], useProxyTargets: true }),
4019
4363
  componentNameValidationMixin,
4020
4364
  customMixin$a
@@ -4046,6 +4390,10 @@ const EmailFieldClass = compose(
4046
4390
  :host ::slotted(*) {
4047
4391
  -webkit-mask-image: none;
4048
4392
  }
4393
+
4394
+ vaadin-email-field[external-input="true"] > input:not(:placeholder-shown) {
4395
+ opacity: 0;
4396
+ }
4049
4397
  `,
4050
4398
  excludeAttrsSync: ['tabindex'],
4051
4399
  componentName: componentName$P,
@@ -8608,11 +8956,14 @@ const customMixin$6 = (superclass) =>
8608
8956
 
8609
8957
  const template = document.createElement('template');
8610
8958
 
8959
+ const externalInputAttr = this.getAttribute('external-input');
8960
+
8611
8961
  template.innerHTML = `
8612
8962
  <${componentName$s}
8613
8963
  name="new-password"
8614
8964
  tabindex="-1"
8615
8965
  slot="input"
8966
+ external-input="${externalInputAttr}"
8616
8967
  >
8617
8968
  </${componentName$s}>
8618
8969
  `;
@@ -8621,6 +8972,10 @@ const customMixin$6 = (superclass) =>
8621
8972
 
8622
8973
  this.inputElement = this.shadowRoot.querySelector(componentName$s);
8623
8974
 
8975
+ if (this.getAttribute('external-input') === 'true') {
8976
+ this.initExternalInput();
8977
+ }
8978
+
8624
8979
  forwardAttrs(this, this.inputElement, {
8625
8980
  includeAttrs: [
8626
8981
  'password-label',
@@ -8645,6 +9000,34 @@ const customMixin$6 = (superclass) =>
8645
9000
  ],
8646
9001
  });
8647
9002
  }
9003
+
9004
+ createSlottedExternalInput(node, slotName) {
9005
+ const externalInput = node.querySelector('input');
9006
+ const slotEle = document.createElement('slot');
9007
+
9008
+ const targetSlot = externalInput.getAttribute('slot');
9009
+
9010
+ slotEle.setAttribute('name', slotName);
9011
+ slotEle.setAttribute('slot', targetSlot);
9012
+
9013
+ node.appendChild(slotEle);
9014
+
9015
+ // move external input
9016
+ externalInput.setAttribute('slot', slotName);
9017
+ externalInput.setAttribute('data-hidden-input', 'true');
9018
+
9019
+ this.appendChild(externalInput);
9020
+ }
9021
+
9022
+ initExternalInput() {
9023
+ // get descope input components
9024
+ this.passwordInput = this.inputElement.querySelector('[data-id="password"]');
9025
+ this.confirmInput = this.inputElement.querySelector('[data-id="confirm"]');
9026
+
9027
+ // create slots for external password input
9028
+ this.createSlottedExternalInput(this.passwordInput, 'external-password-input');
9029
+ this.createSlottedExternalInput(this.confirmInput, 'external-confirm-input');
9030
+ }
8648
9031
  };
8649
9032
 
8650
9033
  const {