@zywave/zui-formfield 4.2.0-pre.0 → 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.
- package/dist/zui-formfield.js +4 -2
- package/dist/zui-formfield.js.map +1 -1
- package/package.json +3 -3
- package/src/zui-formfield.ts +10 -4
- package/test/zui-formfield.test.ts +56 -1
package/dist/zui-formfield.js
CHANGED
|
@@ -87,13 +87,15 @@ export class ZuiFormField extends ZuiBaseElement {
|
|
|
87
87
|
return;
|
|
88
88
|
}
|
|
89
89
|
control.addEventListener('invalid', async (event) => {
|
|
90
|
-
await this.#extractValidationMessage(control);
|
|
91
90
|
event.preventDefault();
|
|
92
|
-
|
|
91
|
+
await this.#extractValidationMessage(control);
|
|
92
|
+
}, { capture: true });
|
|
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":";;;;;;AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,MAAM,sDAAsD,CAAC;AAC3F,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAG/C;;;;;;;GAOG;AACH,MAAM,OAAO,YAAa,SAAQ,cAAc;IAAhD;;QACE;;WAEG;QAEH,UAAK,GAAkB,IAAI,CAAC;QAE5B;;WAEG;QAEH,oBAAe,GAAG,GAAG,CAAC;
|
|
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.
|
|
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.
|
|
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": "
|
|
27
|
+
"gitHead": "db52a4851519bc33f001de80857a202c489f9b28"
|
|
28
28
|
}
|
package/src/zui-formfield.ts
CHANGED
|
@@ -100,16 +100,22 @@ export class ZuiFormField extends ZuiBaseElement {
|
|
|
100
100
|
return;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
control.addEventListener(
|
|
104
|
-
|
|
105
|
-
event
|
|
106
|
-
|
|
103
|
+
control.addEventListener(
|
|
104
|
+
'invalid',
|
|
105
|
+
async (event) => {
|
|
106
|
+
event.preventDefault();
|
|
107
|
+
await this.#extractValidationMessage(control);
|
|
108
|
+
},
|
|
109
|
+
{ capture: true }
|
|
110
|
+
);
|
|
107
111
|
|
|
108
112
|
control.addEventListener('blur', async () => await this.#extractValidationMessage(control));
|
|
109
113
|
control.addEventListener('input', async () => await this.#extractValidationMessage(control));
|
|
110
114
|
control.addEventListener('change', async () => await this.#extractValidationMessage(control));
|
|
115
|
+
control.addEventListener('validitystatechange', async () => await this.#extractValidationMessage(control));
|
|
111
116
|
|
|
112
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
|
|
113
119
|
if (!control.tagName.includes('-')) {
|
|
114
120
|
window.clearInterval(this.#validationIntervalId);
|
|
115
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
|
});
|