@zywave/zui-formfield 4.2.0-pre.1 → 4.2.0-pre.2

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.
@@ -93,7 +93,9 @@ export class ZuiFormField extends ZuiBaseElement {
93
93
  control.addEventListener('blur', async () => await this.#extractValidationMessage(control));
94
94
  control.addEventListener('input', async () => await this.#extractValidationMessage(control));
95
95
  control.addEventListener('change', async () => await this.#extractValidationMessage(control));
96
+ control.addEventListener('validitystatechange', async () => await this.#extractValidationMessage(control));
96
97
  // workaround native elements not necessarily raising events for direct manipulation (i.e. input.value = 'foo' doesn't trigger events)
98
+ // see https://github.com/whatwg/html/issues/9878
97
99
  if (!control.tagName.includes('-')) {
98
100
  window.clearInterval(this.#validationIntervalId);
99
101
  this.#validationIntervalId = window.setInterval(async () => {
@@ -1 +1 @@
1
- {"version":3,"file":"zui-formfield.js","sourceRoot":"","sources":["../src/zui-formfield.ts"],"names":[],"mappings":"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","sourcesContent":["import { ZuiBaseElement } from '@zywave/zui-base';\nimport { findAssignedElement } from '@zywave/zui-base/dist/utils/find-assigned-element.js';\nimport { html, nothing } from 'lit';\nimport { property, query } from 'lit/decorators.js';\nimport { style } from './zui-formfield-css.js';\nimport type { PropertyValues } from 'lit';\n\n/**\n * `<zui-formfield>` provides a standardized way of labeling and styling form controls.\n *\n * @slot - Default, unnamed slot; for inserting form controls, such as `<select>`, `<input>`, `<zui-input>`, `<zui-select>`, etc., into `<zui-formfield>`\n *\n * @csspart container - The container for form fields inserted into `zui-formfield`; this is exposed as a CSS shadow part and can be accessed with `::part(container)`.\n * @csspart label - The label for `zui-formfield`; this is exposed as a CSS shadow part and can be accessed with `::part(label)`.\n */\nexport class ZuiFormField extends ZuiBaseElement {\n /**\n * (optional): Label text, for the form control. Alternatively can slot in label text instead. If necessary, can be styled with `::part(label)`.\n */\n @property({ type: String })\n label: string | null = null;\n\n /**\n * (optional): Provide a valid CSS selector to help `zui-formfield` find the correct form control. Defaults to the first child element.\n */\n @property({ type: String, attribute: 'control-selector' })\n controlSelector = '*';\n\n @query('slot')\n _slotEl: HTMLSlotElement;\n\n #validationMessageField?: string | null | undefined;\n\n #validationIntervalId?: number | undefined;\n\n get #validationMessage() {\n return this.#validationMessageField;\n }\n\n set #validationMessage(msg: string | null | undefined) {\n const oldValue = this.#validationMessageField;\n if (oldValue !== msg) {\n this.#validationMessageField = msg?.trim();\n this.requestUpdate();\n }\n }\n\n get #control() {\n return findAssignedElement(this._slotEl, this.controlSelector);\n }\n\n static get styles() {\n return [super.styles, style];\n }\n\n async firstUpdated(changedProps: PropertyValues) {\n super.firstUpdated(changedProps);\n\n if (this.#control) {\n this.#control.tagName.includes('-')\n ? customElements\n .whenDefined(this.#control.tagName.toLowerCase())\n .then(() => this.#configureValidation(this.#control as ValidatableLitElement))\n : this.#configureValidation(this.#control as ValidatableLitElement);\n }\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n\n if (this.#validationIntervalId) {\n clearInterval(this.#validationIntervalId);\n this.#validationIntervalId = undefined;\n }\n }\n\n render() {\n return html`\n <div class=\"container\" part=\"container\">\n ${this.label ? html` <label @click=\"${this.#onLabelClick}\" part=\"label\">${this.label}</label> ` : nothing}\n <slot @slotchange=\"${this.#onSlotChange}\"></slot>\n <div class=\"validation-message\">${this.#validationMessage ?? ''}</div>\n </div>\n `;\n }\n\n #onLabelClick() {\n this.#control?.focus();\n this.#control?.click();\n }\n\n #onSlotChange() {\n if (this.#control) {\n this.#configureValidation(this.#control as ValidatableLitElement);\n }\n }\n\n #configureValidation(control: ValidatableLitElement) {\n if (!control || typeof control.checkValidity !== 'function') {\n return;\n }\n\n control.addEventListener(\n 'invalid',\n async (event) => {\n event.preventDefault();\n await this.#extractValidationMessage(control);\n },\n { capture: true }\n );\n\n control.addEventListener('blur', async () => await this.#extractValidationMessage(control));\n control.addEventListener('input', async () => await this.#extractValidationMessage(control));\n control.addEventListener('change', async () => await this.#extractValidationMessage(control));\n\n // workaround native elements not necessarily raising events for direct manipulation (i.e. input.value = 'foo' doesn't trigger events)\n if (!control.tagName.includes('-')) {\n window.clearInterval(this.#validationIntervalId);\n\n this.#validationIntervalId = window.setInterval(async () => {\n await this.#extractValidationMessage(control);\n }, 1000);\n }\n }\n\n async #extractValidationMessage(control: ValidatableLitElement) {\n if (control.updateComplete) {\n await control.updateComplete;\n }\n this.#validationMessage = control.validationMessage;\n }\n}\n\nwindow.customElements.define('zui-formfield', ZuiFormField);\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'zui-formfield': ZuiFormField;\n }\n}\n\ntype ValidatableLitElement = ValidatableHTMLElement & {\n updateComplete?: Promise<void>;\n};\n\ntype ValidatableHTMLElement = HTMLElement & {\n setCustomValidity(message: string): void;\n checkValidity(): boolean;\n reportValidity(): boolean;\n validationMessage: string;\n validity: ValidityState;\n};\n"]}
1
+ {"version":3,"file":"zui-formfield.js","sourceRoot":"","sources":["../src/zui-formfield.ts"],"names":[],"mappings":"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","sourcesContent":["import { ZuiBaseElement } from '@zywave/zui-base';\nimport { findAssignedElement } from '@zywave/zui-base/dist/utils/find-assigned-element.js';\nimport { html, nothing } from 'lit';\nimport { property, query } from 'lit/decorators.js';\nimport { style } from './zui-formfield-css.js';\nimport type { PropertyValues } from 'lit';\n\n/**\n * `<zui-formfield>` provides a standardized way of labeling and styling form controls.\n *\n * @slot - Default, unnamed slot; for inserting form controls, such as `<select>`, `<input>`, `<zui-input>`, `<zui-select>`, etc., into `<zui-formfield>`\n *\n * @csspart container - The container for form fields inserted into `zui-formfield`; this is exposed as a CSS shadow part and can be accessed with `::part(container)`.\n * @csspart label - The label for `zui-formfield`; this is exposed as a CSS shadow part and can be accessed with `::part(label)`.\n */\nexport class ZuiFormField extends ZuiBaseElement {\n /**\n * (optional): Label text, for the form control. Alternatively can slot in label text instead. If necessary, can be styled with `::part(label)`.\n */\n @property({ type: String })\n label: string | null = null;\n\n /**\n * (optional): Provide a valid CSS selector to help `zui-formfield` find the correct form control. Defaults to the first child element.\n */\n @property({ type: String, attribute: 'control-selector' })\n controlSelector = '*';\n\n @query('slot')\n _slotEl: HTMLSlotElement;\n\n #validationMessageField?: string | null | undefined;\n\n #validationIntervalId?: number | undefined;\n\n get #validationMessage() {\n return this.#validationMessageField;\n }\n\n set #validationMessage(msg: string | null | undefined) {\n const oldValue = this.#validationMessageField;\n if (oldValue !== msg) {\n this.#validationMessageField = msg?.trim();\n this.requestUpdate();\n }\n }\n\n get #control() {\n return findAssignedElement(this._slotEl, this.controlSelector);\n }\n\n static get styles() {\n return [super.styles, style];\n }\n\n async firstUpdated(changedProps: PropertyValues) {\n super.firstUpdated(changedProps);\n\n if (this.#control) {\n this.#control.tagName.includes('-')\n ? customElements\n .whenDefined(this.#control.tagName.toLowerCase())\n .then(() => this.#configureValidation(this.#control as ValidatableLitElement))\n : this.#configureValidation(this.#control as ValidatableLitElement);\n }\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n\n if (this.#validationIntervalId) {\n clearInterval(this.#validationIntervalId);\n this.#validationIntervalId = undefined;\n }\n }\n\n render() {\n return html`\n <div class=\"container\" part=\"container\">\n ${this.label ? html` <label @click=\"${this.#onLabelClick}\" part=\"label\">${this.label}</label> ` : nothing}\n <slot @slotchange=\"${this.#onSlotChange}\"></slot>\n <div class=\"validation-message\">${this.#validationMessage ?? ''}</div>\n </div>\n `;\n }\n\n #onLabelClick() {\n this.#control?.focus();\n this.#control?.click();\n }\n\n #onSlotChange() {\n if (this.#control) {\n this.#configureValidation(this.#control as ValidatableLitElement);\n }\n }\n\n #configureValidation(control: ValidatableLitElement) {\n if (!control || typeof control.checkValidity !== 'function') {\n return;\n }\n\n control.addEventListener(\n 'invalid',\n async (event) => {\n event.preventDefault();\n await this.#extractValidationMessage(control);\n },\n { capture: true }\n );\n\n control.addEventListener('blur', async () => await this.#extractValidationMessage(control));\n control.addEventListener('input', async () => await this.#extractValidationMessage(control));\n control.addEventListener('change', async () => await this.#extractValidationMessage(control));\n control.addEventListener('validitystatechange', async () => await this.#extractValidationMessage(control));\n\n // workaround native elements not necessarily raising events for direct manipulation (i.e. input.value = 'foo' doesn't trigger events)\n // see https://github.com/whatwg/html/issues/9878\n if (!control.tagName.includes('-')) {\n window.clearInterval(this.#validationIntervalId);\n\n this.#validationIntervalId = window.setInterval(async () => {\n await this.#extractValidationMessage(control);\n }, 1000);\n }\n }\n\n async #extractValidationMessage(control: ValidatableLitElement) {\n if (control.updateComplete) {\n await control.updateComplete;\n }\n this.#validationMessage = control.validationMessage;\n }\n}\n\nwindow.customElements.define('zui-formfield', ZuiFormField);\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'zui-formfield': ZuiFormField;\n }\n}\n\ntype ValidatableLitElement = ValidatableHTMLElement & {\n updateComplete?: Promise<void>;\n};\n\ntype ValidatableHTMLElement = HTMLElement & {\n setCustomValidity(message: string): void;\n checkValidity(): boolean;\n reportValidity(): boolean;\n validationMessage: string;\n validity: ValidityState;\n};\n"]}
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@zywave/zui-formfield",
3
- "version": "4.2.0-pre.1",
3
+ "version": "4.2.0-pre.2",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.js",
6
6
  "license": "UNLICENSED",
7
7
  "dependencies": {
8
- "@zywave/zui-base": "^4.5.0-pre.0"
8
+ "@zywave/zui-base": "^4.5.0-pre.1"
9
9
  },
10
10
  "scripts": {
11
11
  "build": "npm run build:scss && npm run build:ts",
@@ -24,5 +24,5 @@
24
24
  "access": "public"
25
25
  },
26
26
  "customElements": "dist/custom-elements.json",
27
- "gitHead": "a2679c6a86ff8b68928d985580df4ce676d24095"
27
+ "gitHead": "db52a4851519bc33f001de80857a202c489f9b28"
28
28
  }
@@ -112,8 +112,10 @@ export class ZuiFormField extends ZuiBaseElement {
112
112
  control.addEventListener('blur', async () => await this.#extractValidationMessage(control));
113
113
  control.addEventListener('input', async () => await this.#extractValidationMessage(control));
114
114
  control.addEventListener('change', async () => await this.#extractValidationMessage(control));
115
+ control.addEventListener('validitystatechange', async () => await this.#extractValidationMessage(control));
115
116
 
116
117
  // workaround native elements not necessarily raising events for direct manipulation (i.e. input.value = 'foo' doesn't trigger events)
118
+ // see https://github.com/whatwg/html/issues/9878
117
119
  if (!control.tagName.includes('-')) {
118
120
  window.clearInterval(this.#validationIntervalId);
119
121
 
@@ -2,9 +2,11 @@
2
2
  /// <reference path="../../../../test/src/custom_typings/chai.d.ts" />
3
3
  /* eslint-disable no-undef */
4
4
  import { ZuiFormField } from '@zywave/zui-formfield';
5
+ import { ZuiFormAssociatedElement } from '@zywave/zui-base';
6
+ import { html } from 'lit';
5
7
  import { assert } from '@esm-bundle/chai';
6
8
  import { buildForm } from '../../../../test/src/util/form-helpers.js';
7
- import { sleep } from '../../../../test/src/util/helpers.js';
9
+ import { sleep, awaitEvent } from '../../../../test/src/util/helpers.js';
8
10
 
9
11
  suite('zui-formfield', () => {
10
12
  let form: HTMLFormElement;
@@ -94,4 +96,57 @@ suite('zui-formfield', () => {
94
96
  validationMessage = formField.shadowRoot.querySelector('.validation-message') as HTMLElement;
95
97
  assert.isEmpty(validationMessage?.textContent, 'validation message should be empty for valid control');
96
98
  });
99
+
100
+ test('clears validation message when setCustomValidity is called from a ZuiFormAssociatedElement', async () => {
101
+ const tagName = 'test-face-setcustomvalidity';
102
+ class SetCustomValidityElement extends ZuiFormAssociatedElement {
103
+ get _focusControlSelector() {
104
+ return 'input';
105
+ }
106
+
107
+ render() {
108
+ return html`<input />`;
109
+ }
110
+ }
111
+ window.customElements.define(tagName, SetCustomValidityElement);
112
+
113
+ const newFormfield = document.createElement('zui-formfield');
114
+ newFormfield.label = 'Custom Validity Test';
115
+ const newInput = document.createElement(tagName) as SetCustomValidityElement;
116
+ newInput.setAttribute('name', 'input2');
117
+ newFormfield.appendChild(newInput);
118
+
119
+ const newForm = buildForm({
120
+ appendChildren: [newFormfield],
121
+ numInputs: 0,
122
+ });
123
+
124
+ await newInput.updateComplete;
125
+
126
+ await Promise.allSettled([
127
+ awaitEvent(newInput, 'validitystatechange'),
128
+ newInput.setCustomValidity('Custom error message'),
129
+ newFormfield.updateComplete,
130
+ ]);
131
+ newForm.requestSubmit();
132
+
133
+ await newFormfield.updateComplete;
134
+
135
+ let validationMessage = newFormfield.shadowRoot.querySelector('.validation-message') as HTMLElement;
136
+ assert.isNotEmpty(validationMessage?.textContent, 'validation message should not be empty after setCustomValidity');
137
+
138
+ await Promise.allSettled([
139
+ awaitEvent(newInput, 'validitystatechange'),
140
+ newInput.setCustomValidity(''),
141
+ newFormfield.updateComplete,
142
+ ]);
143
+
144
+ newForm.requestSubmit();
145
+ await newFormfield.updateComplete;
146
+
147
+ validationMessage = newFormfield.shadowRoot.querySelector('.validation-message') as HTMLElement;
148
+ assert.isEmpty(validationMessage?.textContent, 'validation message should be empty after clearing custom validity');
149
+
150
+ newForm.remove();
151
+ });
97
152
  });