@crowdstrike/glide-core 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/checkbox-group.d.ts +3 -0
  2. package/dist/checkbox-group.js +43 -1
  3. package/dist/checkbox-group.styles.js +12 -0
  4. package/dist/checkbox-group.test.validity.js +69 -0
  5. package/dist/checkbox.d.ts +3 -1
  6. package/dist/checkbox.js +135 -1
  7. package/dist/checkbox.styles.js +12 -0
  8. package/dist/checkbox.test.validity.js +77 -1
  9. package/dist/dropdown.d.ts +3 -0
  10. package/dist/dropdown.js +195 -1
  11. package/dist/dropdown.styles.js +18 -6
  12. package/dist/dropdown.test.events.single.js +19 -0
  13. package/dist/dropdown.test.interactions.js +0 -18
  14. package/dist/dropdown.test.interactions.multiple.js +8 -10
  15. package/dist/dropdown.test.interactions.single.js +18 -0
  16. package/dist/dropdown.test.validity.js +172 -1
  17. package/dist/input.d.ts +4 -0
  18. package/dist/input.js +155 -1
  19. package/dist/input.styles.js +8 -0
  20. package/dist/input.test.validity.js +140 -62
  21. package/dist/menu.d.ts +2 -0
  22. package/dist/menu.js +1 -1
  23. package/dist/menu.test.basics.d.ts +1 -1
  24. package/dist/menu.test.basics.js +12 -27
  25. package/dist/menu.test.interactions.js +90 -0
  26. package/dist/radio-group.d.ts +3 -0
  27. package/dist/radio-group.js +45 -1
  28. package/dist/radio-group.styles.js +12 -0
  29. package/dist/radio-group.test.validity.js +82 -0
  30. package/dist/styles/variables.css +1 -1
  31. package/dist/tag.styles.js +1 -1
  32. package/dist/textarea.d.ts +3 -0
  33. package/dist/textarea.js +58 -2
  34. package/dist/textarea.styles.js +8 -0
  35. package/dist/textarea.test.events.js +0 -114
  36. package/dist/textarea.test.validity.js +64 -72
  37. package/dist/tooltip.d.ts +2 -1
  38. package/dist/tooltip.js +1 -1
  39. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable @typescript-eslint/no-unused-expressions */
2
2
  import './dropdown.option.js';
3
- import { expect, fixture, html } from '@open-wc/testing';
3
+ import { elementUpdated, expect, fixture, html } from '@open-wc/testing';
4
4
  import GlideCoreDropdown from './dropdown.js';
5
5
  GlideCoreDropdown.shadowRootOptions.mode = 'open';
6
6
  it('is valid if not required and no option is selected', async () => {
@@ -28,6 +28,23 @@ it('is invalid if required and no option is selected', async () => {
28
28
  expect(component.checkValidity()).to.be.false;
29
29
  expect(component.reportValidity()).to.be.false;
30
30
  });
31
+ it('is invalid if required and no option is selected when `filterable`', async () => {
32
+ const component = await fixture(html `<glide-core-dropdown
33
+ label="Label"
34
+ placeholder="Placeholder"
35
+ filterable
36
+ required
37
+ >
38
+ <glide-core-dropdown-option
39
+ label="Label"
40
+ value="value"
41
+ ></glide-core-dropdown-option>
42
+ </glide-core-dropdown>`);
43
+ expect(component.validity.valid).to.be.false;
44
+ expect(component.validity?.valueMissing).to.be.true;
45
+ expect(component.checkValidity()).to.be.false;
46
+ expect(component.reportValidity()).to.be.false;
47
+ });
31
48
  it('is both invalid and valid if required but disabled and no option is selected', async () => {
32
49
  const component = await fixture(html `<glide-core-dropdown
33
50
  label="Label"
@@ -45,3 +62,157 @@ it('is both invalid and valid if required but disabled and no option is selected
45
62
  expect(component.checkValidity()).to.be.true;
46
63
  expect(component.reportValidity()).to.be.true;
47
64
  });
65
+ it('sets the validity message with `setCustomValidity()`', async () => {
66
+ const component = await fixture(html `<glide-core-dropdown label="Label">
67
+ <glide-core-dropdown-option
68
+ label="Label"
69
+ value="value"
70
+ ></glide-core-dropdown-option>
71
+ </glide-core-dropdown>`);
72
+ component.setCustomValidity('validity message');
73
+ expect(component.validity?.valid).to.be.false;
74
+ expect(component.validity?.customError).to.be.true;
75
+ expect(component.checkValidity()).to.be.false;
76
+ await elementUpdated(component);
77
+ // Like native, the validity message shouldn't display until `reportValidity()` is called.
78
+ expect(component.shadowRoot?.querySelector('[data-test="validity-message"]')
79
+ ?.textContent).to.be.undefined;
80
+ expect(component.reportValidity()).to.be.false;
81
+ await elementUpdated(component);
82
+ expect(component.shadowRoot?.querySelector('[data-test="validity-message"]')
83
+ ?.textContent).to.equal('validity message');
84
+ });
85
+ it('removes a validity message with an empty argument to `setCustomValidity()`', async () => {
86
+ const component = await fixture(html `<glide-core-dropdown label="Label">
87
+ <glide-core-dropdown-option
88
+ label="Label"
89
+ value="value"
90
+ ></glide-core-dropdown-option>
91
+ </glide-core-dropdown>`);
92
+ component.setCustomValidity('validity message');
93
+ component.reportValidity();
94
+ await elementUpdated(component);
95
+ component.setCustomValidity('');
96
+ await elementUpdated(component);
97
+ expect(component.shadowRoot?.querySelector('[data-test="validity-message"]')
98
+ ?.textContent).to.be.undefined;
99
+ });
100
+ it('is invalid when `setValidity()` is called', async () => {
101
+ const component = await fixture(html `<glide-core-dropdown label="Label">
102
+ <glide-core-dropdown-option
103
+ label="Label"
104
+ value="value"
105
+ ></glide-core-dropdown-option>
106
+ </glide-core-dropdown>`);
107
+ component.setValidity({ customError: true }, 'validity message');
108
+ expect(component.validity.valid).to.be.false;
109
+ await elementUpdated(component);
110
+ // Like native, the validity message shouldn't display until `reportValidity()` is called.
111
+ expect(component.shadowRoot?.querySelector('[data-test="validity-message"]')
112
+ ?.textContent).to.be.undefined;
113
+ expect(component.validity?.customError).to.be.true;
114
+ component.reportValidity();
115
+ await elementUpdated(component);
116
+ expect(component.shadowRoot?.querySelector('[data-test="validity-message"]')
117
+ ?.textContent).to.equal('validity message');
118
+ });
119
+ it('is valid when `setValidity()` is called', async () => {
120
+ const component = await fixture(html `<glide-core-dropdown label="Label">
121
+ <glide-core-dropdown-option
122
+ label="Label"
123
+ value="value"
124
+ ></glide-core-dropdown-option>
125
+ </glide-core-dropdown>`);
126
+ component.setValidity({ customError: true }, 'validity message');
127
+ component.setValidity({});
128
+ await elementUpdated(component);
129
+ expect(component.validity.valid).to.be.true;
130
+ expect(component.validity.customError).to.be.false;
131
+ expect(component.reportValidity()).to.be.true;
132
+ await elementUpdated(component);
133
+ expect(component.shadowRoot?.querySelector('[data-test="validity-message"]')
134
+ ?.textContent).to.be.undefined;
135
+ });
136
+ it('sets the validity message with `setCustomValidity()` when `filterable`', async () => {
137
+ const component = await fixture(html `<glide-core-dropdown label="Label" filterable>
138
+ <glide-core-dropdown-option
139
+ label="Label"
140
+ value="value"
141
+ ></glide-core-dropdown-option>
142
+ </glide-core-dropdown>`);
143
+ component.setCustomValidity('validity message');
144
+ expect(component.validity?.valid).to.be.false;
145
+ expect(component.validity?.customError).to.be.true;
146
+ expect(component.checkValidity()).to.be.false;
147
+ await elementUpdated(component);
148
+ // Like native, the validity message shouldn't display until `reportValidity()` is called.
149
+ expect(component.shadowRoot?.querySelector('[data-test="validity-message"]')
150
+ ?.textContent).to.be.undefined;
151
+ expect(component.reportValidity()).to.be.false;
152
+ await elementUpdated(component);
153
+ expect(component.shadowRoot?.querySelector('[data-test="validity-message"]')
154
+ ?.textContent).to.equal('validity message');
155
+ });
156
+ it('removes a validity message with an empty argument to `setCustomValidity()` when `filterable`', async () => {
157
+ const component = await fixture(html `<glide-core-dropdown label="Label" filterable>
158
+ <glide-core-dropdown-option
159
+ label="Label"
160
+ value="value"
161
+ ></glide-core-dropdown-option>
162
+ </glide-core-dropdown>`);
163
+ component.setCustomValidity('validity message');
164
+ component.reportValidity();
165
+ await elementUpdated(component);
166
+ component.setCustomValidity('');
167
+ await elementUpdated(component);
168
+ expect(component.shadowRoot?.querySelector('[data-test="validity-message"]')
169
+ ?.textContent).to.be.undefined;
170
+ });
171
+ it('is invalid when `setValidity()` is called when `filterable`', async () => {
172
+ const component = await fixture(html `<glide-core-dropdown label="Label" filterable>
173
+ <glide-core-dropdown-option
174
+ label="Label"
175
+ value="value"
176
+ ></glide-core-dropdown-option>
177
+ </glide-core-dropdown>`);
178
+ component.setValidity({ customError: true }, 'validity message');
179
+ expect(component.validity.valid).to.be.false;
180
+ await elementUpdated(component);
181
+ // Like native, the validity message shouldn't display until `reportValidity()` is called.
182
+ expect(component.shadowRoot?.querySelector('[data-test="validity-message"]')
183
+ ?.textContent).to.be.undefined;
184
+ expect(component.validity?.customError).to.be.true;
185
+ component.reportValidity();
186
+ await elementUpdated(component);
187
+ expect(component.shadowRoot?.querySelector('[data-test="validity-message"]')
188
+ ?.textContent).to.equal('validity message');
189
+ });
190
+ it('is valid when `setValidity()` is called when `filterable`', async () => {
191
+ const component = await fixture(html `<glide-core-dropdown label="Label" filterable>
192
+ <glide-core-dropdown-option
193
+ label="Label"
194
+ value="value"
195
+ ></glide-core-dropdown-option>
196
+ </glide-core-dropdown>`);
197
+ component.setValidity({ customError: true }, 'validity message');
198
+ component.setValidity({});
199
+ await elementUpdated(component);
200
+ expect(component.validity.valid).to.be.true;
201
+ expect(component.validity.customError).to.be.false;
202
+ expect(component.reportValidity()).to.be.true;
203
+ await elementUpdated(component);
204
+ expect(component.shadowRoot?.querySelector('[data-test="validity-message"]')
205
+ ?.textContent).to.be.undefined;
206
+ });
207
+ it('retains existing validity state when `setCustomValidity()` is called', async () => {
208
+ const component = await fixture(html `<glide-core-dropdown label="Label" required>
209
+ <glide-core-dropdown-option
210
+ label="Label"
211
+ value="value"
212
+ ></glide-core-dropdown-option>
213
+ </glide-core-dropdown>`);
214
+ component.setCustomValidity('validity message');
215
+ expect(component.validity?.valid).to.be.false;
216
+ expect(component.validity?.customError).to.be.true;
217
+ expect(component.validity?.valueMissing).to.be.true;
218
+ });
package/dist/input.d.ts CHANGED
@@ -29,6 +29,7 @@ export default class GlideCoreInput extends LitElement {
29
29
  label?: string;
30
30
  hideLabel: boolean;
31
31
  orientation: 'horizontal' | 'vertical';
32
+ pattern?: string;
32
33
  placeholder?: string;
33
34
  clearable: boolean;
34
35
  spellcheck: boolean;
@@ -53,11 +54,14 @@ export default class GlideCoreInput extends LitElement {
53
54
  get isClearIconVisible(): boolean;
54
55
  render(): import("lit").TemplateResult<1>;
55
56
  reportValidity(): boolean;
57
+ setCustomValidity(message: string): void;
58
+ setValidity(flags?: ValidityStateFlags, message?: string): void;
56
59
  constructor();
57
60
  private hasFocus;
58
61
  private isBlurring;
59
62
  private isCheckingValidity;
60
63
  private isReportValidityOrSubmit;
61
64
  private passwordVisible;
65
+ private validityMessage?;
62
66
  }
63
67
  export {};
package/dist/input.js CHANGED
@@ -1 +1,155 @@
1
- var __decorate=this&&this.__decorate||function(e,t,i,o){var r,s=arguments.length,a=s<3?t:null===o?o=Object.getOwnPropertyDescriptor(t,i):o;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)a=Reflect.decorate(e,t,i,o);else for(var l=e.length-1;l>=0;l--)(r=e[l])&&(a=(s<3?r(a):s>3?r(t,i,a):r(t,i))||a);return s>3&&a&&Object.defineProperty(t,i,a),a};import"./icon-button.js";import"./label.js";import{LitElement,html,nothing}from"lit";import{LocalizeController}from"./library/localize.js";import{classMap}from"lit/directives/class-map.js";import{createRef,ref}from"lit/directives/ref.js";import{customElement,property,state}from"lit/decorators.js";import{ifDefined}from"lit/directives/if-defined.js";import magnifyingGlassIcon from"./icons/magnifying-glass.js";import ow from"./library/ow.js";import styles from"./input.styles.js";export const SUPPORTED_TYPES=["email","number","password","search","tel","text","url"];let GlideCoreInput=class GlideCoreInput extends LitElement{static{this.formAssociated=!0}static{this.shadowRootOptions={...LitElement.shadowRootOptions,mode:"closed",delegatesFocus:!0}}static{this.styles=styles}get form(){return this.#e.form}get validity(){return!this.required||this.value||this.disabled?this.#t?this.#e.setValidity({tooLong:!0}," ",this.#i.value):this.#e.setValidity({}):this.#e.setValidity({valueMissing:!0}," ",this.#i.value),this.#e.validity}get willValidate(){return this.#e.willValidate}blur(){this.#i.value?.blur()}checkValidity(){this.isCheckingValidity=!0;const e=this.#e.checkValidity();return this.isCheckingValidity=!1,e}disconnectedCallback(){super.disconnectedCallback(),this.form?.removeEventListener("formdata",this.#o)}formAssociatedCallback(){this.form?.addEventListener("formdata",this.#o)}formResetCallback(){this.value=this.getAttribute("value")??""}get hasClearIcon(){return this.clearable&&!this.disabled&&!this.readonly}get isClearIconVisible(){return this.hasClearIcon&&this.value.length>0}render(){return html`<glide-core-private-label class="${classMap({left:"left"===this.privateSplit,middle:"middle"===this.privateSplit})}" orientation="${this.orientation}" split="${ifDefined(this.privateSplit??void 0)}" ?disabled="${this.disabled}" ?error="${this.#r||this.#t}" ?hide="${this.hideLabel}" ?required="${this.required}"><slot name="tooltip" slot="tooltip"></slot><label for="input">${this.label}</label><div class="${classMap({"input-container":!0,focused:this.hasFocus,empty:""===this.value,disabled:this.disabled,readonly:this.readonly&&!this.disabled,error:this.#r||this.#t})}" slot="control"><slot name="prefix-icon"></slot><input aria-describedby="meta" aria-invalid="${this.#r||this.#t}" id="input" type="${"password"===this.type&&this.passwordVisible?"text":this.type}" .value="${this.value}" placeholder="${ifDefined(this.placeholder)}" autocapitalize="${this.autocapitalize}" autocomplete="${this.autocomplete}" spellcheck="${this.spellcheck}" ?required="${this.required}" ?readonly="${this.readonly}" ?disabled="${this.disabled}" @focus="${this.#s}" @blur="${this.#a}" @change="${this.#l}" @input="${this.#n}" @keydown="${this.#d}" ${ref(this.#i)}> ${this.hasClearIcon?html`<glide-core-icon-button variant="tertiary" class="${classMap({"clear-icon-button":!0,"clear-icon-button--visible":this.isClearIconVisible})}" data-test="clear-button" label="${this.#h.term("clearEntry",this.label)}" @click="${this.#p}"><svg aria-hidden="true" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M6 6L18 18" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M18 6L6 18" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></glide-core-icon-button>`:nothing} ${"password"===this.type&&this.passwordToggle&&!this.disabled?html`<glide-core-icon-button variant="tertiary" class="password-toggle" data-test="password-toggle" label="${this.passwordVisible?"Hide password":"Show password"}" aria-controls="input" aria-expanded="${this.passwordVisible?"true":"false"}" @click="${this.#c}">${this.passwordVisible?html`<svg aria-hidden="true" width="16" height="16" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M3.98 8.223A10.477 10.477 0 0 0 1.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.451 10.451 0 0 1 12 4.5c4.756 0 8.773 3.162 10.065 7.498a10.522 10.522 0 0 1-4.293 5.774M6.228 6.228 3 3m3.228 3.228 3.65 3.65m7.894 7.894L21 21m-3.228-3.228-3.65-3.65m0 0a3 3 0 1 0-4.243-4.243m4.242 4.242L9.88 9.88"/></svg>`:html`<svg aria-hidden="true" width="16" height="16" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z"/><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"/></svg>`}</glide-core-icon-button>`:nothing}<div class="suffix-icon">${"search"===this.type?magnifyingGlassIcon:html`<slot name="suffix-icon"></slot>`}</div></div><div class="meta" id="meta" slot="description"><slot class="description" name="description"></slot>${this.maxlength?html`<div class="${classMap({"character-count":!0,error:this.#t})}" data-test="character-count-container"><span aria-hidden="true" data-test="character-count-text">${this.#h.term("displayedCharacterCount",this.#u,this.maxlength)} </span><span class="hidden" data-test="character-count-announcement">${this.#h.term("announcedCharacterCount",this.#u,this.maxlength)}</span></div>`:nothing}</div></glide-core-private-label>`}reportValidity(){this.isReportValidityOrSubmit=!0;const e=this.#e.reportValidity();return this.requestUpdate(),e}constructor(){super(),this.type="text",this.name="",this.value="",this.hideLabel=!1,this.orientation="horizontal",this.clearable=!1,this.spellcheck=!1,this.autocapitalize="on",this.autocomplete="on",this.passwordToggle=!1,this.required=!1,this.readonly=!1,this.disabled=!1,this.hasFocus=!1,this.isBlurring=!1,this.isCheckingValidity=!1,this.isReportValidityOrSubmit=!1,this.passwordVisible=!1,this.#i=createRef(),this.#h=new LocalizeController(this),this.#o=({formData:e})=>{this.name&&this.value&&!this.disabled&&e.append(this.name,this.value)},this.#e=this.attachInternals(),this.addEventListener("invalid",(e=>{if(e?.preventDefault(),this.isCheckingValidity||this.isBlurring)return;this.isReportValidityOrSubmit=!0;this.form?.querySelector(":invalid")===this&&this.focus()}))}#i;#e;#h;get#u(){return this.value.length}#o;get#t(){return Boolean(!this.disabled&&!this.readonly&&this.maxlength&&this.#u>this.maxlength)}get#r(){return!this.disabled&&!this.validity?.valid&&this.isReportValidityOrSubmit}#a(){this.isBlurring=!0,this.reportValidity(),this.isBlurring=!1,this.hasFocus=!1}#l(e){ow(this.#i.value,ow.object.instanceOf(HTMLInputElement)),this.value=this.#i.value?.value,this.dispatchEvent(new Event(e.type,e))}#p(e){this.value="",this.dispatchEvent(new Event("clear",{bubbles:!0})),this.#i.value?.focus(),e.stopPropagation()}#s(){this.hasFocus=!0}#n(){ow(this.#i.value,ow.object.instanceOf(HTMLInputElement)),this.value=this.#i.value.value}#d(e){"Enter"===e.key&&this.form?.requestSubmit()}#c(){this.passwordVisible=!this.passwordVisible}};__decorate([property()],GlideCoreInput.prototype,"type",void 0),__decorate([property({reflect:!0})],GlideCoreInput.prototype,"name",void 0),__decorate([property()],GlideCoreInput.prototype,"value",void 0),__decorate([property({reflect:!0})],GlideCoreInput.prototype,"label",void 0),__decorate([property({attribute:"hide-label",type:Boolean})],GlideCoreInput.prototype,"hideLabel",void 0),__decorate([property({reflect:!0})],GlideCoreInput.prototype,"orientation",void 0),__decorate([property({reflect:!0})],GlideCoreInput.prototype,"placeholder",void 0),__decorate([property({type:Boolean})],GlideCoreInput.prototype,"clearable",void 0),__decorate([property({reflect:!0,type:Boolean})],GlideCoreInput.prototype,"spellcheck",void 0),__decorate([property({reflect:!0})],GlideCoreInput.prototype,"autocapitalize",void 0),__decorate([property({reflect:!0})],GlideCoreInput.prototype,"autocomplete",void 0),__decorate([property({attribute:"password-toggle",type:Boolean})],GlideCoreInput.prototype,"passwordToggle",void 0),__decorate([property({reflect:!0,type:Boolean})],GlideCoreInput.prototype,"required",void 0),__decorate([property({type:Boolean})],GlideCoreInput.prototype,"readonly",void 0),__decorate([property({reflect:!0,type:Boolean})],GlideCoreInput.prototype,"disabled",void 0),__decorate([property()],GlideCoreInput.prototype,"privateSplit",void 0),__decorate([property({type:Number,converter:e=>e&&Number.parseInt(e,10),reflect:!0})],GlideCoreInput.prototype,"maxlength",void 0),__decorate([state()],GlideCoreInput.prototype,"hasFocus",void 0),__decorate([state()],GlideCoreInput.prototype,"isBlurring",void 0),__decorate([state()],GlideCoreInput.prototype,"isCheckingValidity",void 0),__decorate([state()],GlideCoreInput.prototype,"isReportValidityOrSubmit",void 0),__decorate([state()],GlideCoreInput.prototype,"passwordVisible",void 0),GlideCoreInput=__decorate([customElement("glide-core-input")],GlideCoreInput);export default GlideCoreInput;
1
+ var __decorate=this&&this.__decorate||function(t,e,i,s){var a,r=arguments.length,o=r<3?e:null===s?s=Object.getOwnPropertyDescriptor(e,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(t,e,i,s);else for(var l=t.length-1;l>=0;l--)(a=t[l])&&(o=(r<3?a(o):r>3?a(e,i,o):a(e,i))||o);return r>3&&o&&Object.defineProperty(e,i,o),o};import"./icon-button.js";import"./label.js";import{LitElement,html,nothing}from"lit";import{LocalizeController}from"./library/localize.js";import{classMap}from"lit/directives/class-map.js";import{createRef,ref}from"lit/directives/ref.js";import{customElement,property,state}from"lit/decorators.js";import{ifDefined}from"lit/directives/if-defined.js";import{unsafeHTML}from"lit/directives/unsafe-html.js";import{when}from"lit/directives/when.js";import magnifyingGlassIcon from"./icons/magnifying-glass.js";import ow from"./library/ow.js";import styles from"./input.styles.js";export const SUPPORTED_TYPES=["email","number","password","search","tel","text","url"];let GlideCoreInput=class GlideCoreInput extends LitElement{static{this.formAssociated=!0}static{this.shadowRootOptions={...LitElement.shadowRootOptions,mode:"closed",delegatesFocus:!0}}static{this.styles=styles}get form(){return this.#t.form}get validity(){return this.pattern?(this.#t.setValidity({customError:Boolean(this.validityMessage),patternMismatch:!new RegExp(this.pattern).test(this.value),valueMissing:Boolean(this.required&&!this.value)}," ",this.#e.value),this.#t.validity):!this.pattern&&this.#t.validity.patternMismatch?(this.#t.setValidity({}),this.#t.validity):!this.required||this.value||this.disabled?this.required&&this.#t.validity.valueMissing&&this.value?(this.#t.setValidity({}),this.#t.validity):(this.required||!this.#t.validity.valueMissing||this.value||this.#t.setValidity({}),this.#t.validity):(this.#t.setValidity({customError:Boolean(this.validityMessage),valueMissing:!0}," ",this.#e.value),this.#t.validity)}get willValidate(){return this.#t.willValidate}blur(){this.#e.value?.blur()}checkValidity(){this.isCheckingValidity=!0;const t=this.#t.checkValidity();return this.isCheckingValidity=!1,t}disconnectedCallback(){super.disconnectedCallback(),this.form?.removeEventListener("formdata",this.#i)}formAssociatedCallback(){this.form?.addEventListener("formdata",this.#i)}formResetCallback(){this.value=this.getAttribute("value")??""}get hasClearIcon(){return this.clearable&&!this.disabled&&!this.readonly}get isClearIconVisible(){return this.hasClearIcon&&this.value.length>0}render(){return html`
2
+ <glide-core-private-label
3
+ class=${classMap({left:"left"===this.privateSplit,middle:"middle"===this.privateSplit})}
4
+ orientation=${this.orientation}
5
+ split=${ifDefined(this.privateSplit??void 0)}
6
+ ?disabled=${this.disabled}
7
+ ?error=${this.#s||this.#a}
8
+ ?hide=${this.hideLabel}
9
+ ?required=${this.required}
10
+ >
11
+ <slot name="tooltip" slot="tooltip"></slot>
12
+ <label for="input"> ${this.label} </label>
13
+
14
+ <div
15
+ class=${classMap({"input-container":!0,focused:this.hasFocus,empty:""===this.value,disabled:this.disabled,readonly:this.readonly&&!this.disabled,error:this.#s||this.#a})}
16
+ slot="control"
17
+ >
18
+ <slot name="prefix-icon"></slot>
19
+
20
+ <input
21
+ aria-describedby="meta"
22
+ aria-invalid=${this.#s||this.#a}
23
+ id="input"
24
+ type=${"password"===this.type&&this.passwordVisible?"text":this.type}
25
+ .value=${this.value}
26
+ placeholder=${ifDefined(this.placeholder)}
27
+ autocapitalize=${this.autocapitalize}
28
+ autocomplete=${this.autocomplete}
29
+ spellcheck=${this.spellcheck}
30
+ ?required=${this.required}
31
+ ?readonly=${this.readonly}
32
+ ?disabled=${this.disabled}
33
+ @focus=${this.#r}
34
+ @blur=${this.#o}
35
+ @change=${this.#l}
36
+ @input=${this.#n}
37
+ @keydown=${this.#d}
38
+ ${ref(this.#e)}
39
+ />
40
+
41
+ ${this.hasClearIcon?html`
42
+ <glide-core-icon-button
43
+ variant="tertiary"
44
+ class=${classMap({"clear-icon-button":!0,"clear-icon-button--visible":this.isClearIconVisible})}
45
+ data-test="clear-button"
46
+ label=${this.#h.term("clearEntry",this.label)}
47
+ @click=${this.#p}
48
+ >
49
+ <!-- X icon -->
50
+ <svg
51
+ aria-hidden="true"
52
+ width="16"
53
+ height="16"
54
+ viewBox="0 0 24 24"
55
+ fill="none"
56
+ stroke="currentColor"
57
+ >
58
+ <path
59
+ d="M6 6L18 18"
60
+ stroke-width="2"
61
+ stroke-linecap="round"
62
+ stroke-linejoin="round"
63
+ />
64
+ <path
65
+ d="M18 6L6 18"
66
+ stroke-width="2"
67
+ stroke-linecap="round"
68
+ stroke-linejoin="round"
69
+ />
70
+ </svg>
71
+ </glide-core-icon-button>
72
+ `:nothing}
73
+ ${"password"===this.type&&this.passwordToggle&&!this.disabled?html`
74
+ <glide-core-icon-button
75
+ variant="tertiary"
76
+ class="password-toggle"
77
+ data-test="password-toggle"
78
+ label=${this.passwordVisible?"Hide password":"Show password"}
79
+ aria-controls="input"
80
+ aria-expanded=${this.passwordVisible?"true":"false"}
81
+ @click=${this.#c}
82
+ >
83
+ ${this.passwordVisible?html`<svg
84
+ aria-hidden="true"
85
+ width="16"
86
+ height="16"
87
+ fill="none"
88
+ viewBox="0 0 24 24"
89
+ stroke-width="1.5"
90
+ stroke="currentColor"
91
+ >
92
+ <path
93
+ stroke-linecap="round"
94
+ stroke-linejoin="round"
95
+ d="M3.98 8.223A10.477 10.477 0 0 0 1.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.451 10.451 0 0 1 12 4.5c4.756 0 8.773 3.162 10.065 7.498a10.522 10.522 0 0 1-4.293 5.774M6.228 6.228 3 3m3.228 3.228 3.65 3.65m7.894 7.894L21 21m-3.228-3.228-3.65-3.65m0 0a3 3 0 1 0-4.243-4.243m4.242 4.242L9.88 9.88"
96
+ />
97
+ </svg> `:html`<svg
98
+ aria-hidden="true"
99
+ width="16"
100
+ height="16"
101
+ fill="none"
102
+ viewBox="0 0 24 24"
103
+ stroke-width="1.5"
104
+ stroke="currentColor"
105
+ >
106
+ <path
107
+ stroke-linecap="round"
108
+ stroke-linejoin="round"
109
+ d="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z"
110
+ />
111
+ <path
112
+ stroke-linecap="round"
113
+ stroke-linejoin="round"
114
+ d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"
115
+ />
116
+ </svg>`}
117
+ </glide-core-icon-button>
118
+ `:nothing}
119
+
120
+ <div class="suffix-icon">
121
+ ${"search"===this.type?magnifyingGlassIcon:html`<slot name="suffix-icon"></slot>`}
122
+ </div>
123
+ </div>
124
+
125
+ <div class="meta" id="meta" slot="description">
126
+ <slot
127
+ class=${classMap({description:!0,hidden:Boolean(this.#s&&this.validityMessage)})}
128
+ name="description"
129
+ ></slot>
130
+
131
+ ${when(this.#s&&this.validityMessage,(()=>html`<span class="validity-message" data-test="validity-message"
132
+ >${unsafeHTML(this.validityMessage)}</span
133
+ >`))}
134
+ ${this.maxlength?html`
135
+ <div
136
+ class=${classMap({"character-count":!0,error:this.#a})}
137
+ data-test="character-count-container"
138
+ >
139
+ <!--
140
+ "aria-hidden" is used here so that the character counter
141
+ is not read aloud to screenreaders twice as we have a
142
+ more accesible, verbose description below.
143
+ -->
144
+ <span aria-hidden="true" data-test="character-count-text">
145
+ ${this.#h.term("displayedCharacterCount",this.#u,this.maxlength)}
146
+ </span>
147
+
148
+ <span class="hidden" data-test="character-count-announcement"
149
+ >${this.#h.term("announcedCharacterCount",this.#u,this.maxlength)}</span
150
+ >
151
+ </div>
152
+ `:nothing}
153
+ </div>
154
+ </glide-core-private-label>
155
+ `}reportValidity(){this.isReportValidityOrSubmit=!0;const t=this.#t.reportValidity();return this.requestUpdate(),t}setCustomValidity(t){this.validityMessage=t,""===t?this.#t.setValidity({customError:!1},"",this.#e.value):this.#t.setValidity({customError:!0,patternMismatch:this.#t.validity.patternMismatch,valueMissing:this.#t.validity.valueMissing}," ",this.#e.value)}setValidity(t,e){this.validityMessage=e,this.#t.setValidity(t," ",this.#e.value)}constructor(){super(),this.type="text",this.name="",this.value="",this.hideLabel=!1,this.orientation="horizontal",this.clearable=!1,this.spellcheck=!1,this.autocapitalize="on",this.autocomplete="on",this.passwordToggle=!1,this.required=!1,this.readonly=!1,this.disabled=!1,this.hasFocus=!1,this.isBlurring=!1,this.isCheckingValidity=!1,this.isReportValidityOrSubmit=!1,this.passwordVisible=!1,this.#e=createRef(),this.#h=new LocalizeController(this),this.#i=({formData:t})=>{this.name&&this.value&&!this.disabled&&t.append(this.name,this.value)},this.#t=this.attachInternals(),this.addEventListener("invalid",(t=>{if(t?.preventDefault(),this.isCheckingValidity||this.isBlurring)return;this.isReportValidityOrSubmit=!0;this.form?.querySelector(":invalid")===this&&this.focus()}))}#e;#t;#h;get#u(){return this.value.length}#i;get#a(){return Boolean(!this.disabled&&!this.readonly&&this.maxlength&&this.#u>this.maxlength)}get#s(){return!this.disabled&&!this.validity?.valid&&this.isReportValidityOrSubmit}#o(){this.isBlurring=!0,this.reportValidity(),this.isBlurring=!1,this.hasFocus=!1}#l(t){ow(this.#e.value,ow.object.instanceOf(HTMLInputElement)),this.value=this.#e.value?.value,this.dispatchEvent(new Event(t.type,t))}#p(t){this.value="",this.dispatchEvent(new Event("clear",{bubbles:!0})),this.#e.value?.focus(),t.stopPropagation()}#r(){this.hasFocus=!0}#n(){ow(this.#e.value,ow.object.instanceOf(HTMLInputElement)),this.value=this.#e.value.value}#d(t){"Enter"===t.key&&this.form?.requestSubmit()}#c(){this.passwordVisible=!this.passwordVisible}};__decorate([property()],GlideCoreInput.prototype,"type",void 0),__decorate([property({reflect:!0})],GlideCoreInput.prototype,"name",void 0),__decorate([property()],GlideCoreInput.prototype,"value",void 0),__decorate([property({reflect:!0})],GlideCoreInput.prototype,"label",void 0),__decorate([property({attribute:"hide-label",type:Boolean})],GlideCoreInput.prototype,"hideLabel",void 0),__decorate([property({reflect:!0})],GlideCoreInput.prototype,"orientation",void 0),__decorate([property({reflect:!0})],GlideCoreInput.prototype,"pattern",void 0),__decorate([property({reflect:!0})],GlideCoreInput.prototype,"placeholder",void 0),__decorate([property({type:Boolean})],GlideCoreInput.prototype,"clearable",void 0),__decorate([property({reflect:!0,type:Boolean})],GlideCoreInput.prototype,"spellcheck",void 0),__decorate([property({reflect:!0})],GlideCoreInput.prototype,"autocapitalize",void 0),__decorate([property({reflect:!0})],GlideCoreInput.prototype,"autocomplete",void 0),__decorate([property({attribute:"password-toggle",type:Boolean})],GlideCoreInput.prototype,"passwordToggle",void 0),__decorate([property({reflect:!0,type:Boolean})],GlideCoreInput.prototype,"required",void 0),__decorate([property({type:Boolean})],GlideCoreInput.prototype,"readonly",void 0),__decorate([property({reflect:!0,type:Boolean})],GlideCoreInput.prototype,"disabled",void 0),__decorate([property()],GlideCoreInput.prototype,"privateSplit",void 0),__decorate([property({type:Number,converter:t=>t&&Number.parseInt(t,10),reflect:!0})],GlideCoreInput.prototype,"maxlength",void 0),__decorate([state()],GlideCoreInput.prototype,"hasFocus",void 0),__decorate([state()],GlideCoreInput.prototype,"isBlurring",void 0),__decorate([state()],GlideCoreInput.prototype,"isCheckingValidity",void 0),__decorate([state()],GlideCoreInput.prototype,"isReportValidityOrSubmit",void 0),__decorate([state()],GlideCoreInput.prototype,"passwordVisible",void 0),__decorate([state()],GlideCoreInput.prototype,"validityMessage",void 0),GlideCoreInput=__decorate([customElement("glide-core-input")],GlideCoreInput);export default GlideCoreInput;
@@ -11,6 +11,14 @@ import{css}from"lit";import visuallyHidden from"./styles/visually-hidden.js";exp
11
11
 
12
12
  .description {
13
13
  display: block;
14
+
15
+ &.hidden {
16
+ display: none;
17
+ }
18
+ }
19
+
20
+ .validity-message {
21
+ display: block;
14
22
  }
15
23
 
16
24
  .character-count {
@@ -50,67 +50,6 @@ it('is invalid after value is cleared when required', async () => {
50
50
  await elementUpdated(component);
51
51
  expect(component.shadowRoot?.querySelector('input')?.getAttribute('aria-invalid')).to.equal('true');
52
52
  });
53
- it('is valid when empty and does not exceed `maxlength`', async () => {
54
- const component = await fixture(html `<glide-core-input maxlength="3"></glide-core-input>`);
55
- expect(component.validity?.valid).to.be.true;
56
- expect(component.validity?.valueMissing).to.be.false;
57
- expect(component.validity?.tooLong).to.be.false;
58
- expect(component.checkValidity()).to.be.true;
59
- expect(component.reportValidity()).to.be.true;
60
- await elementUpdated(component);
61
- expect(component.shadowRoot?.querySelector('input')?.getAttribute('aria-invalid')).to.equal('false');
62
- });
63
- it('is valid when filled in and does not exceed `maxlength`', async () => {
64
- const component = await fixture(html `<glide-core-input maxlength="3"></glide-core-input>`);
65
- component.focus();
66
- await sendKeys({ type: 'val' });
67
- expect(component.validity?.valid).to.be.true;
68
- expect(component.validity?.valueMissing).to.be.false;
69
- expect(component.validity?.tooLong).to.be.false;
70
- expect(component.checkValidity()).to.be.true;
71
- expect(component.reportValidity()).to.be.true;
72
- await elementUpdated(component);
73
- expect(component.shadowRoot?.querySelector('input')?.getAttribute('aria-invalid')).to.equal('false');
74
- });
75
- it('is valid when filled in, disabled, and value exceeds `maxlength`', async () => {
76
- const component = await fixture(html `<glide-core-input
77
- value="value"
78
- maxlength="3"
79
- disabled
80
- ></glide-core-input>`);
81
- expect(component.validity?.valid).to.be.true;
82
- expect(component.validity?.valueMissing).to.be.false;
83
- expect(component.validity?.tooLong).to.be.false;
84
- expect(component.checkValidity()).to.be.true;
85
- expect(component.reportValidity()).to.be.true;
86
- await elementUpdated(component);
87
- expect(component.shadowRoot?.querySelector('input')?.getAttribute('aria-invalid')).to.equal('false');
88
- });
89
- it('is valid when filled in, readonly, and value exceeds `maxlength`', async () => {
90
- const component = await fixture(html `<glide-core-input
91
- value="value"
92
- maxlength="3"
93
- readonly
94
- ></glide-core-input>`);
95
- expect(component.validity?.valid).to.be.true;
96
- expect(component.validity?.valueMissing).to.be.false;
97
- expect(component.validity?.tooLong).to.be.false;
98
- expect(component.checkValidity()).to.be.true;
99
- expect(component.reportValidity()).to.be.true;
100
- await elementUpdated(component);
101
- expect(component.shadowRoot?.querySelector('input')?.getAttribute('aria-invalid')).to.equal('false');
102
- });
103
- it('is invalid when filled in and exceeds `maxlength`', async () => {
104
- const component = await fixture(html `<glide-core-input maxlength="3"></glide-core-input>`);
105
- component.focus();
106
- await sendKeys({ type: 'value' });
107
- expect(component.validity?.valid).to.be.false;
108
- expect(component.validity?.tooLong).to.be.true;
109
- expect(component.checkValidity()).to.be.false;
110
- expect(component.reportValidity()).to.be.false;
111
- await elementUpdated(component);
112
- expect(component.shadowRoot?.querySelector('input')?.getAttribute('aria-invalid')).to.equal('true');
113
- });
114
53
  it('is valid if no value and required but disabled', async () => {
115
54
  const component = await fixture(html `<glide-core-input disabled required></glide-core-input>`);
116
55
  expect(component.validity?.valid).to.be.true;
@@ -162,7 +101,7 @@ it('is invalid when `value` is empty and `required` is set to `true` programmati
162
101
  expect(component.reportValidity()).to.be.false;
163
102
  expect(component.shadowRoot?.querySelector('input')?.getAttribute('aria-invalid')).to.equal('true');
164
103
  });
165
- it('is valid when `value` is empty and `required` is set to `false` programmatically', async () => {
104
+ it('is valid when `value` is empty and `required` is updated to `false` programmatically', async () => {
166
105
  const component = await fixture(html `<glide-core-input label="Label" required></glide-core-input>`);
167
106
  expect(component.validity?.valid).to.be.false;
168
107
  expect(component.validity?.valueMissing).to.be.true;
@@ -178,3 +117,142 @@ it('is valid when `value` is empty and `required` is set to `false` programmatic
178
117
  expect(component.reportValidity()).to.be.true;
179
118
  expect(component.shadowRoot?.querySelector('input')?.getAttribute('aria-invalid')).to.equal('false');
180
119
  });
120
+ it('is valid when the `value` attribute matches `pattern`', async () => {
121
+ const component = await fixture(html `<glide-core-input
122
+ label="Label"
123
+ pattern="[a-z]{4,8}"
124
+ value="value"
125
+ ></glide-core-input>`);
126
+ expect(component.validity?.valid).to.be.true;
127
+ expect(component.validity?.patternMismatch).to.be.false;
128
+ expect(component.checkValidity()).to.be.true;
129
+ expect(component.reportValidity()).to.be.true;
130
+ expect(component.shadowRoot?.querySelector('input')?.getAttribute('aria-invalid')).to.equal('false');
131
+ });
132
+ it('is valid when `value` matches `pattern` after being set programmatically', async () => {
133
+ const component = await fixture(html `<glide-core-input
134
+ label="Label"
135
+ pattern="[a-z]{4,8}"
136
+ ></glide-core-input>`);
137
+ component.value = 'value';
138
+ await elementUpdated(component);
139
+ expect(component.validity?.valid).to.be.true;
140
+ expect(component.validity?.patternMismatch).to.be.false;
141
+ expect(component.checkValidity()).to.be.true;
142
+ expect(component.reportValidity()).to.be.true;
143
+ expect(component.shadowRoot?.querySelector('input')?.getAttribute('aria-invalid')).to.equal('false');
144
+ });
145
+ it('is invalid when `value` does not match `pattern`', async () => {
146
+ const component = await fixture(html `<glide-core-input
147
+ label="Label"
148
+ pattern="[a-z]{4,8}"
149
+ value="1234"
150
+ ></glide-core-input>`);
151
+ expect(component.validity?.valid).to.be.false;
152
+ expect(component.validity?.patternMismatch).to.be.true;
153
+ expect(component.checkValidity()).to.be.false;
154
+ expect(component.reportValidity()).to.be.false;
155
+ await elementUpdated(component);
156
+ expect(component.shadowRoot?.querySelector('input')?.getAttribute('aria-invalid')).to.equal('true');
157
+ });
158
+ it('is invalid when a programmatically set `value` does not match `pattern`', async () => {
159
+ const component = await fixture(html `<glide-core-input
160
+ label="Label"
161
+ pattern="[a-z]{4,8}"
162
+ ></glide-core-input>`);
163
+ component.value = 'val';
164
+ expect(component.validity?.valid).to.be.false;
165
+ expect(component.validity?.patternMismatch).to.be.true;
166
+ expect(component.checkValidity()).to.be.false;
167
+ expect(component.reportValidity()).to.be.false;
168
+ await elementUpdated(component);
169
+ expect(component.shadowRoot?.querySelector('input')?.getAttribute('aria-invalid')).to.equal('true');
170
+ });
171
+ it('is invalid when `required` and `value` does not match `pattern`', async () => {
172
+ const component = await fixture(html `<glide-core-input
173
+ label="Label"
174
+ pattern="[a-z]{4,8}"
175
+ required
176
+ ></glide-core-input>`);
177
+ expect(component.validity?.valid).to.be.false;
178
+ expect(component.validity?.patternMismatch).to.be.true;
179
+ expect(component.validity?.valueMissing).to.be.true;
180
+ });
181
+ it('is valid when `pattern` is programmatically removed', async () => {
182
+ const component = await fixture(html `<glide-core-input
183
+ label="Label"
184
+ pattern="[a-z]{4,8}"
185
+ ></glide-core-input>`);
186
+ component.pattern = undefined;
187
+ await elementUpdated(component);
188
+ expect(component.validity?.valid).to.be.true;
189
+ expect(component.validity?.patternMismatch).to.be.false;
190
+ expect(component.checkValidity()).to.be.true;
191
+ expect(component.reportValidity()).to.be.true;
192
+ expect(component.shadowRoot?.querySelector('input')?.getAttribute('aria-invalid')).to.equal('false');
193
+ });
194
+ it('sets the validity message with `setCustomValidity()`', async () => {
195
+ const component = await fixture(html `<glide-core-input label="Label"></glide-core-input>`);
196
+ component.setCustomValidity('validity message');
197
+ expect(component.validity?.valid).to.be.false;
198
+ expect(component.validity?.customError).to.be.true;
199
+ expect(component.checkValidity()).to.be.false;
200
+ await elementUpdated(component);
201
+ // Like native, the validity message shouldn't display until `reportValidity()` is called.
202
+ expect(component.shadowRoot?.querySelector('[data-test="validity-message"]')
203
+ ?.textContent).to.be.undefined;
204
+ expect(component.reportValidity()).to.be.false;
205
+ await elementUpdated(component);
206
+ expect(component.shadowRoot?.querySelector('input')?.getAttribute('aria-invalid')).to.equal('true');
207
+ expect(component.shadowRoot?.querySelector('[data-test="validity-message"]')
208
+ ?.textContent).to.equal('validity message');
209
+ });
210
+ it('removes a validity message with an empty argument to `setCustomValidity()`', async () => {
211
+ const component = await fixture(html `<glide-core-input label="Label"></glide-core-input>`);
212
+ component.setCustomValidity('validity message');
213
+ component.reportValidity();
214
+ await elementUpdated(component);
215
+ component.setCustomValidity('');
216
+ await elementUpdated(component);
217
+ expect(component.shadowRoot?.querySelector('[data-test="validity-message"]')
218
+ ?.textContent).to.be.undefined;
219
+ });
220
+ it('is invalid when `setValidity()` is called', async () => {
221
+ const component = await fixture(html `<glide-core-input label="Label"></glide-core-input>`);
222
+ component.setValidity({ customError: true }, 'validity message');
223
+ expect(component.validity.valid).to.be.false;
224
+ await elementUpdated(component);
225
+ // Like native, the validity message shouldn't display until `reportValidity()` is called.
226
+ expect(component.shadowRoot?.querySelector('[data-test="validity-message"]')
227
+ ?.textContent).to.be.undefined;
228
+ expect(component.validity?.customError).to.be.true;
229
+ component.reportValidity();
230
+ await elementUpdated(component);
231
+ expect(component.shadowRoot?.querySelector('input')?.getAttribute('aria-invalid')).to.equal('true');
232
+ expect(component.shadowRoot?.querySelector('[data-test="validity-message"]')
233
+ ?.textContent).to.equal('validity message');
234
+ });
235
+ it('is valid when `setValidity()` is called', async () => {
236
+ const component = await fixture(html `<glide-core-input label="Label"></glide-core-input>`);
237
+ component.setValidity({ customError: true }, 'validity message');
238
+ component.setValidity({});
239
+ await elementUpdated(component);
240
+ expect(component.validity.valid).to.be.true;
241
+ expect(component.validity.customError).to.be.false;
242
+ expect(component.reportValidity()).to.be.true;
243
+ await elementUpdated(component);
244
+ expect(component.shadowRoot?.querySelector('[data-test="validity-message"]')
245
+ ?.textContent).to.be.undefined;
246
+ });
247
+ it('retains existing validity state when `setCustomValidity()` is called', async () => {
248
+ const component = await fixture(html `<glide-core-input
249
+ label="Label"
250
+ pattern="[a-z]{4,8}"
251
+ required
252
+ ></glide-core-input>`);
253
+ component.setCustomValidity('validity message');
254
+ expect(component.validity?.valid).to.be.false;
255
+ expect(component.validity?.customError).to.be.true;
256
+ expect(component.validity?.patternMismatch).to.be.true;
257
+ expect(component.validity?.valueMissing).to.be.true;
258
+ });
package/dist/menu.d.ts CHANGED
@@ -13,6 +13,8 @@ export default class GlideCoreMenu extends LitElement {
13
13
  #private;
14
14
  static shadowRootOptions: ShadowRootInit;
15
15
  static styles: import("lit").CSSResult[];
16
+ get offset(): number;
17
+ set offset(offset: number);
16
18
  get open(): boolean;
17
19
  set open(isOpen: boolean);
18
20
  placement: Placement;