@zywave/zui-formfield 4.2.2-pre.0 → 4.3.0-pre.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.
- package/dist/custom-elements.json +14 -0
- package/dist/zui-formfield-css.js +1 -1
- package/dist/zui-formfield-css.js.map +1 -1
- package/dist/zui-formfield.js +19 -1
- package/dist/zui-formfield.js.map +1 -1
- package/package.json +2 -2
- package/src/zui-formfield-css.js +1 -1
- package/src/zui-formfield.scss +6 -0
- package/src/zui-formfield.ts +21 -1
- package/test/zui-formfield.test.ts +75 -0
|
@@ -70,6 +70,15 @@
|
|
|
70
70
|
"text": "number | undefined | undefined"
|
|
71
71
|
}
|
|
72
72
|
},
|
|
73
|
+
{
|
|
74
|
+
"kind": "field",
|
|
75
|
+
"name": "#isRequired",
|
|
76
|
+
"privacy": "private",
|
|
77
|
+
"type": {
|
|
78
|
+
"text": "boolean"
|
|
79
|
+
},
|
|
80
|
+
"default": "false"
|
|
81
|
+
},
|
|
73
82
|
{
|
|
74
83
|
"kind": "field",
|
|
75
84
|
"name": "#validationMessage",
|
|
@@ -81,6 +90,11 @@
|
|
|
81
90
|
"privacy": "private",
|
|
82
91
|
"readonly": true
|
|
83
92
|
},
|
|
93
|
+
{
|
|
94
|
+
"kind": "method",
|
|
95
|
+
"name": "#checkRequired",
|
|
96
|
+
"privacy": "private"
|
|
97
|
+
},
|
|
84
98
|
{
|
|
85
99
|
"kind": "method",
|
|
86
100
|
"name": "#onLabelClick",
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { css } from 'lit';
|
|
2
|
-
export const style = css `:host .container label,:host .container ::slotted(label){display:inline-flex;margin:0;font-weight:600;color:var(--zui-gray-800)}:host{contain:none}:host .container{display:flex;flex-direction:column;margin-bottom:1.25rem}:host .container ::slotted(*:not(zui-toggle):not(zui-checkbox):not(zui-radio)){margin-top:.25rem !important}:host .container ::slotted(zui-toggle){margin:.9375rem 0}:host .validation-message:empty{display:none}:host .validation-message{color:var(--zui-red-500)}`;
|
|
2
|
+
export const style = css `:host .container label,:host .container ::slotted(label){display:inline-flex;margin:0;font-weight:600;color:var(--zui-gray-800)}:host{contain:none}:host .container{display:flex;flex-direction:column;margin-bottom:1.25rem}:host .container ::slotted(*:not(zui-toggle):not(zui-checkbox):not(zui-radio)){margin-top:.25rem !important}:host .container ::slotted(zui-toggle){margin:.9375rem 0}:host .validation-message:empty{display:none}:host .validation-message{color:var(--zui-red-500)}:host label.required::after{content:"*";margin-left:.5ch;color:var(--zui-red-500)}`;
|
|
3
3
|
//# sourceMappingURL=zui-formfield-css.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"zui-formfield-css.js","sourceRoot":"","sources":["../src/zui-formfield-css.js"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAE1B,MAAM,CAAC,MAAM,KAAK,GAAG,GAAG,CAAA,
|
|
1
|
+
{"version":3,"file":"zui-formfield-css.js","sourceRoot":"","sources":["../src/zui-formfield-css.js"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAE1B,MAAM,CAAC,MAAM,KAAK,GAAG,GAAG,CAAA,sjBAAsjB,CAAC","sourcesContent":["import { css } from 'lit';\n\nexport const style = css`:host .container label,:host .container ::slotted(label){display:inline-flex;margin:0;font-weight:600;color:var(--zui-gray-800)}:host{contain:none}:host .container{display:flex;flex-direction:column;margin-bottom:1.25rem}:host .container ::slotted(*:not(zui-toggle):not(zui-checkbox):not(zui-radio)){margin-top:.25rem !important}:host .container ::slotted(zui-toggle){margin:.9375rem 0}:host .validation-message:empty{display:none}:host .validation-message{color:var(--zui-red-500)}:host label.required::after{content:\"*\";margin-left:.5ch;color:var(--zui-red-500)}`;\n"]}
|
package/dist/zui-formfield.js
CHANGED
|
@@ -28,9 +28,11 @@ export class ZuiFormField extends ZuiBaseElement {
|
|
|
28
28
|
* (optional): Provide a valid CSS selector to help `zui-formfield` find the correct form control. Defaults to the first child element.
|
|
29
29
|
*/
|
|
30
30
|
this.controlSelector = '*';
|
|
31
|
+
this.#isRequired = false;
|
|
31
32
|
}
|
|
32
33
|
#validationMessageField;
|
|
33
34
|
#validationIntervalId;
|
|
35
|
+
#isRequired;
|
|
34
36
|
get #validationMessage() {
|
|
35
37
|
return this.#validationMessageField;
|
|
36
38
|
}
|
|
@@ -44,12 +46,21 @@ export class ZuiFormField extends ZuiBaseElement {
|
|
|
44
46
|
get #control() {
|
|
45
47
|
return findAssignedElement(this._slotEl, this.controlSelector);
|
|
46
48
|
}
|
|
49
|
+
#checkRequired() {
|
|
50
|
+
const control = this.#control;
|
|
51
|
+
const wasRequired = this.#isRequired;
|
|
52
|
+
this.#isRequired = control?.hasAttribute('required') ?? false;
|
|
53
|
+
if (wasRequired !== this.#isRequired) {
|
|
54
|
+
this.requestUpdate();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
47
57
|
static get styles() {
|
|
48
58
|
return [super.styles, style];
|
|
49
59
|
}
|
|
50
60
|
async firstUpdated(changedProps) {
|
|
51
61
|
super.firstUpdated(changedProps);
|
|
52
62
|
if (this.#control) {
|
|
63
|
+
this.#checkRequired();
|
|
53
64
|
this.#control.tagName.includes('-')
|
|
54
65
|
? customElements
|
|
55
66
|
.whenDefined(this.#control.tagName.toLowerCase())
|
|
@@ -67,7 +78,13 @@ export class ZuiFormField extends ZuiBaseElement {
|
|
|
67
78
|
render() {
|
|
68
79
|
return html `
|
|
69
80
|
<div class="container" part="container">
|
|
70
|
-
${this.label
|
|
81
|
+
${this.label
|
|
82
|
+
? html `
|
|
83
|
+
<label @click="${this.#onLabelClick}" part="label" class="${this.#isRequired ? 'required' : ''}">
|
|
84
|
+
${this.label}
|
|
85
|
+
</label>
|
|
86
|
+
`
|
|
87
|
+
: nothing}
|
|
71
88
|
<slot @slotchange="${this.#onSlotChange}"></slot>
|
|
72
89
|
<div class="validation-message">${this.#validationMessage ?? ''}</div>
|
|
73
90
|
</div>
|
|
@@ -79,6 +96,7 @@ export class ZuiFormField extends ZuiBaseElement {
|
|
|
79
96
|
}
|
|
80
97
|
#onSlotChange() {
|
|
81
98
|
if (this.#control) {
|
|
99
|
+
this.#checkRequired();
|
|
82
100
|
this.#configureValidation(this.#control);
|
|
83
101
|
}
|
|
84
102
|
}
|
|
@@ -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 #isRequired = false;\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 #checkRequired() {\n const control = this.#control;\n const wasRequired = this.#isRequired;\n this.#isRequired = control?.hasAttribute('required') ?? false;\n\n if (wasRequired !== this.#isRequired) {\n this.requestUpdate();\n }\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.#checkRequired();\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\n ? html`\n <label @click=\"${this.#onLabelClick}\" part=\"label\" class=\"${this.#isRequired ? 'required' : ''}\">\n ${this.label}\n </label>\n `\n : 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.#checkRequired();\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 if (control.matches(':user-invalid,:user-valid')) {\n await this.#extractValidationMessage(control);\n }\n }, 100);\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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zywave/zui-formfield",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.3.0-pre.0",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"module": "dist/index.js",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -24,5 +24,5 @@
|
|
|
24
24
|
"access": "public"
|
|
25
25
|
},
|
|
26
26
|
"customElements": "dist/custom-elements.json",
|
|
27
|
-
"gitHead": "
|
|
27
|
+
"gitHead": "cdf7b9622b4f662734912310f3582350ab1afd75"
|
|
28
28
|
}
|
package/src/zui-formfield-css.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { css } from 'lit';
|
|
2
2
|
|
|
3
|
-
export const style = css`:host .container label,:host .container ::slotted(label){display:inline-flex;margin:0;font-weight:600;color:var(--zui-gray-800)}:host{contain:none}:host .container{display:flex;flex-direction:column;margin-bottom:1.25rem}:host .container ::slotted(*:not(zui-toggle):not(zui-checkbox):not(zui-radio)){margin-top:.25rem !important}:host .container ::slotted(zui-toggle){margin:.9375rem 0}:host .validation-message:empty{display:none}:host .validation-message{color:var(--zui-red-500)}`;
|
|
3
|
+
export const style = css`:host .container label,:host .container ::slotted(label){display:inline-flex;margin:0;font-weight:600;color:var(--zui-gray-800)}:host{contain:none}:host .container{display:flex;flex-direction:column;margin-bottom:1.25rem}:host .container ::slotted(*:not(zui-toggle):not(zui-checkbox):not(zui-radio)){margin-top:.25rem !important}:host .container ::slotted(zui-toggle){margin:.9375rem 0}:host .validation-message:empty{display:none}:host .validation-message{color:var(--zui-red-500)}:host label.required::after{content:"*";margin-left:.5ch;color:var(--zui-red-500)}`;
|
package/src/zui-formfield.scss
CHANGED
package/src/zui-formfield.ts
CHANGED
|
@@ -33,6 +33,8 @@ export class ZuiFormField extends ZuiBaseElement {
|
|
|
33
33
|
|
|
34
34
|
#validationIntervalId?: number | undefined;
|
|
35
35
|
|
|
36
|
+
#isRequired = false;
|
|
37
|
+
|
|
36
38
|
get #validationMessage() {
|
|
37
39
|
return this.#validationMessageField;
|
|
38
40
|
}
|
|
@@ -49,6 +51,16 @@ export class ZuiFormField extends ZuiBaseElement {
|
|
|
49
51
|
return findAssignedElement(this._slotEl, this.controlSelector);
|
|
50
52
|
}
|
|
51
53
|
|
|
54
|
+
#checkRequired() {
|
|
55
|
+
const control = this.#control;
|
|
56
|
+
const wasRequired = this.#isRequired;
|
|
57
|
+
this.#isRequired = control?.hasAttribute('required') ?? false;
|
|
58
|
+
|
|
59
|
+
if (wasRequired !== this.#isRequired) {
|
|
60
|
+
this.requestUpdate();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
52
64
|
static get styles() {
|
|
53
65
|
return [super.styles, style];
|
|
54
66
|
}
|
|
@@ -57,6 +69,7 @@ export class ZuiFormField extends ZuiBaseElement {
|
|
|
57
69
|
super.firstUpdated(changedProps);
|
|
58
70
|
|
|
59
71
|
if (this.#control) {
|
|
72
|
+
this.#checkRequired();
|
|
60
73
|
this.#control.tagName.includes('-')
|
|
61
74
|
? customElements
|
|
62
75
|
.whenDefined(this.#control.tagName.toLowerCase())
|
|
@@ -77,7 +90,13 @@ export class ZuiFormField extends ZuiBaseElement {
|
|
|
77
90
|
render() {
|
|
78
91
|
return html`
|
|
79
92
|
<div class="container" part="container">
|
|
80
|
-
${this.label
|
|
93
|
+
${this.label
|
|
94
|
+
? html`
|
|
95
|
+
<label @click="${this.#onLabelClick}" part="label" class="${this.#isRequired ? 'required' : ''}">
|
|
96
|
+
${this.label}
|
|
97
|
+
</label>
|
|
98
|
+
`
|
|
99
|
+
: nothing}
|
|
81
100
|
<slot @slotchange="${this.#onSlotChange}"></slot>
|
|
82
101
|
<div class="validation-message">${this.#validationMessage ?? ''}</div>
|
|
83
102
|
</div>
|
|
@@ -91,6 +110,7 @@ export class ZuiFormField extends ZuiBaseElement {
|
|
|
91
110
|
|
|
92
111
|
#onSlotChange() {
|
|
93
112
|
if (this.#control) {
|
|
113
|
+
this.#checkRequired();
|
|
94
114
|
this.#configureValidation(this.#control as ValidatableLitElement);
|
|
95
115
|
}
|
|
96
116
|
}
|
|
@@ -151,4 +151,79 @@ suite('zui-formfield', () => {
|
|
|
151
151
|
|
|
152
152
|
newForm.remove();
|
|
153
153
|
});
|
|
154
|
+
|
|
155
|
+
suite('required indicator', () => {
|
|
156
|
+
test('adds required class to label when control has required attribute', async () => {
|
|
157
|
+
formFieldInput.setAttribute('required', '');
|
|
158
|
+
|
|
159
|
+
// Manually trigger the slot change handler
|
|
160
|
+
const slot = formField.shadowRoot.querySelector('slot') as HTMLSlotElement;
|
|
161
|
+
slot.dispatchEvent(new Event('slotchange'));
|
|
162
|
+
|
|
163
|
+
await formField.updateComplete;
|
|
164
|
+
|
|
165
|
+
const label = formField.shadowRoot.querySelector('label') as HTMLElement;
|
|
166
|
+
assert.isTrue(
|
|
167
|
+
label?.classList.contains('required'),
|
|
168
|
+
'label should have required class when control has required attribute'
|
|
169
|
+
);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test('does not add required class to label when control does not have required attribute', async () => {
|
|
173
|
+
await formField.updateComplete;
|
|
174
|
+
|
|
175
|
+
const label = formField.shadowRoot.querySelector('label') as HTMLElement;
|
|
176
|
+
assert.isFalse(
|
|
177
|
+
label?.classList.contains('required'),
|
|
178
|
+
'label should not have required class when control does not have required attribute'
|
|
179
|
+
);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test('updates required class when required attribute is added dynamically', async () => {
|
|
183
|
+
await formField.updateComplete;
|
|
184
|
+
|
|
185
|
+
let label = formField.shadowRoot.querySelector('label') as HTMLElement;
|
|
186
|
+
assert.isFalse(label?.classList.contains('required'), 'label should not have required class initially');
|
|
187
|
+
|
|
188
|
+
formFieldInput.setAttribute('required', '');
|
|
189
|
+
|
|
190
|
+
// Manually trigger the slot change handler
|
|
191
|
+
const slot = formField.shadowRoot.querySelector('slot') as HTMLSlotElement;
|
|
192
|
+
slot.dispatchEvent(new Event('slotchange'));
|
|
193
|
+
|
|
194
|
+
await formField.updateComplete;
|
|
195
|
+
|
|
196
|
+
label = formField.shadowRoot.querySelector('label') as HTMLElement;
|
|
197
|
+
assert.isTrue(
|
|
198
|
+
label?.classList.contains('required'),
|
|
199
|
+
'label should have required class after required attribute is added'
|
|
200
|
+
);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test('updates required class when required attribute is removed dynamically', async () => {
|
|
204
|
+
formFieldInput.setAttribute('required', '');
|
|
205
|
+
|
|
206
|
+
// Manually trigger the slot change handler to apply required class
|
|
207
|
+
const slot = formField.shadowRoot.querySelector('slot') as HTMLSlotElement;
|
|
208
|
+
slot.dispatchEvent(new Event('slotchange'));
|
|
209
|
+
|
|
210
|
+
await formField.updateComplete;
|
|
211
|
+
|
|
212
|
+
let label = formField.shadowRoot.querySelector('label') as HTMLElement;
|
|
213
|
+
assert.isTrue(label?.classList.contains('required'), 'label should have required class initially');
|
|
214
|
+
|
|
215
|
+
formFieldInput.removeAttribute('required');
|
|
216
|
+
|
|
217
|
+
// Manually trigger the slot change handler again to remove required class
|
|
218
|
+
slot.dispatchEvent(new Event('slotchange'));
|
|
219
|
+
|
|
220
|
+
await formField.updateComplete;
|
|
221
|
+
|
|
222
|
+
label = formField.shadowRoot.querySelector('label') as HTMLElement;
|
|
223
|
+
assert.isFalse(
|
|
224
|
+
label?.classList.contains('required'),
|
|
225
|
+
'label should not have required class after required attribute is removed'
|
|
226
|
+
);
|
|
227
|
+
});
|
|
228
|
+
});
|
|
154
229
|
});
|