@zywave/zui-formfield 4.3.0-pre.0 → 4.3.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/CHANGELOG.md +9 -0
- package/dist/custom-elements.json +19 -0
- package/dist/zui-formfield-css.js +1 -1
- package/dist/zui-formfield-css.js.map +1 -1
- package/dist/zui-formfield.d.ts +5 -0
- package/dist/zui-formfield.js +10 -1
- package/dist/zui-formfield.js.map +1 -1
- package/docs/demo.html +14 -0
- package/lab.html +23 -4
- package/package.json +4 -3
- package/src/zui-formfield-css.js +1 -1
- package/src/zui-formfield.scss +15 -1
- package/src/zui-formfield.ts +9 -1
- package/test/zui-formfield.test.ts +76 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,15 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# [4.3.0](https://gitlab.com/zywave/inner-source/booster/zui/compare/@zywave/zui-formfield@4.3.0-pre.1...@zywave/zui-formfield@4.3.0) (2026-03-04)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* hide 'No results' essage during async select loading ([bc8e139](https://gitlab.com/zywave/inner-source/booster/zui/commit/bc8e139))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
6
15
|
## [4.2.1](https://gitlab.com/zywave/inner-source/booster/zui/compare/@zywave/zui-formfield@4.2.1-pre.2...@zywave/zui-formfield@4.2.1) (2025-09-19)
|
|
7
16
|
|
|
8
17
|
**Note:** Version bump only for package @zywave/zui-formfield
|
|
@@ -47,6 +47,16 @@
|
|
|
47
47
|
"description": "(optional): Provide a valid CSS selector to help `zui-formfield` find the correct form control. Defaults to the first child element.",
|
|
48
48
|
"attribute": "control-selector"
|
|
49
49
|
},
|
|
50
|
+
{
|
|
51
|
+
"kind": "field",
|
|
52
|
+
"name": "tooltip",
|
|
53
|
+
"type": {
|
|
54
|
+
"text": "string | null"
|
|
55
|
+
},
|
|
56
|
+
"default": "null",
|
|
57
|
+
"description": "(optional): Tooltip text to display in a help icon next to the label.",
|
|
58
|
+
"attribute": "tooltip"
|
|
59
|
+
},
|
|
50
60
|
{
|
|
51
61
|
"kind": "field",
|
|
52
62
|
"name": "_slotEl",
|
|
@@ -150,6 +160,15 @@
|
|
|
150
160
|
"default": "'*'",
|
|
151
161
|
"description": "(optional): Provide a valid CSS selector to help `zui-formfield` find the correct form control. Defaults to the first child element.",
|
|
152
162
|
"fieldName": "controlSelector"
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"name": "tooltip",
|
|
166
|
+
"type": {
|
|
167
|
+
"text": "string | null"
|
|
168
|
+
},
|
|
169
|
+
"default": "null",
|
|
170
|
+
"description": "(optional): Tooltip text to display in a help icon next to the label.",
|
|
171
|
+
"fieldName": "tooltip"
|
|
153
172
|
}
|
|
154
173
|
],
|
|
155
174
|
"superclass": {
|
|
@@ -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)}:host label.required::after{content:"*";margin-left:.5ch;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 label,:host .container ::slotted(label){width:100%;align-items:center;gap:1rem}:host .container label .label-text,:host .container ::slotted(label) .label-text{flex:0 1 auto}:host .container label zui-tooltip,:host .container ::slotted(label) zui-tooltip{flex:0 0 auto;margin-left:.5rem;line-height:1}: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 .label-text::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,g4BAAg4B,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 label,:host .container ::slotted(label){width:100%;align-items:center;gap:1rem}:host .container label .label-text,:host .container ::slotted(label) .label-text{flex:0 1 auto}:host .container label zui-tooltip,:host .container ::slotted(label) zui-tooltip{flex:0 0 auto;margin-left:.5rem;line-height:1}: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 .label-text::after{content:\"*\";margin-left:.5ch;color:var(--zui-red-500)}`;\n"]}
|
package/dist/zui-formfield.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ZuiBaseElement } from '@zywave/zui-base';
|
|
2
2
|
import type { PropertyValues } from 'lit';
|
|
3
|
+
import '@zywave/zui-tooltip';
|
|
3
4
|
/**
|
|
4
5
|
* `<zui-formfield>` provides a standardized way of labeling and styling form controls.
|
|
5
6
|
*
|
|
@@ -18,6 +19,10 @@ export declare class ZuiFormField extends ZuiBaseElement {
|
|
|
18
19
|
* (optional): Provide a valid CSS selector to help `zui-formfield` find the correct form control. Defaults to the first child element.
|
|
19
20
|
*/
|
|
20
21
|
controlSelector: string;
|
|
22
|
+
/**
|
|
23
|
+
* (optional): Tooltip text to display in a help icon next to the label.
|
|
24
|
+
*/
|
|
25
|
+
tooltip: string | null;
|
|
21
26
|
_slotEl: HTMLSlotElement;
|
|
22
27
|
static get styles(): (import("lit").CSSResult | import("lit").CSSResultArray)[];
|
|
23
28
|
firstUpdated(changedProps: PropertyValues): Promise<void>;
|
package/dist/zui-formfield.js
CHANGED
|
@@ -9,6 +9,7 @@ import { findAssignedElement } from '@zywave/zui-base/dist/utils/find-assigned-e
|
|
|
9
9
|
import { html, nothing } from 'lit';
|
|
10
10
|
import { property, query } from 'lit/decorators.js';
|
|
11
11
|
import { style } from './zui-formfield-css.js';
|
|
12
|
+
import '@zywave/zui-tooltip';
|
|
12
13
|
/**
|
|
13
14
|
* `<zui-formfield>` provides a standardized way of labeling and styling form controls.
|
|
14
15
|
*
|
|
@@ -28,6 +29,10 @@ export class ZuiFormField extends ZuiBaseElement {
|
|
|
28
29
|
* (optional): Provide a valid CSS selector to help `zui-formfield` find the correct form control. Defaults to the first child element.
|
|
29
30
|
*/
|
|
30
31
|
this.controlSelector = '*';
|
|
32
|
+
/**
|
|
33
|
+
* (optional): Tooltip text to display in a help icon next to the label.
|
|
34
|
+
*/
|
|
35
|
+
this.tooltip = null;
|
|
31
36
|
this.#isRequired = false;
|
|
32
37
|
}
|
|
33
38
|
#validationMessageField;
|
|
@@ -81,7 +86,8 @@ export class ZuiFormField extends ZuiBaseElement {
|
|
|
81
86
|
${this.label
|
|
82
87
|
? html `
|
|
83
88
|
<label @click="${this.#onLabelClick}" part="label" class="${this.#isRequired ? 'required' : ''}">
|
|
84
|
-
|
|
89
|
+
<span class="label-text">${this.label}</span>
|
|
90
|
+
${this.tooltip ? html `<zui-tooltip position="right"> ${this.tooltip} </zui-tooltip>` : nothing}
|
|
85
91
|
</label>
|
|
86
92
|
`
|
|
87
93
|
: nothing}
|
|
@@ -136,6 +142,9 @@ __decorate([
|
|
|
136
142
|
__decorate([
|
|
137
143
|
property({ type: String, attribute: 'control-selector' })
|
|
138
144
|
], ZuiFormField.prototype, "controlSelector", void 0);
|
|
145
|
+
__decorate([
|
|
146
|
+
property({ type: String })
|
|
147
|
+
], ZuiFormField.prototype, "tooltip", void 0);
|
|
139
148
|
__decorate([
|
|
140
149
|
query('slot')
|
|
141
150
|
], ZuiFormField.prototype, "_slotEl", void 0);
|
|
@@ -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 #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"]}
|
|
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';\nimport '@zywave/zui-tooltip';\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 /**\n * (optional): Tooltip text to display in a help icon next to the label.\n */\n @property({ type: String })\n tooltip: string | null = null;\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 <span class=\"label-text\">${this.label}</span>\n ${this.tooltip ? html`<zui-tooltip position=\"right\"> ${this.tooltip} </zui-tooltip>` : nothing}\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/docs/demo.html
CHANGED
|
@@ -17,6 +17,20 @@
|
|
|
17
17
|
</zui-formfield>
|
|
18
18
|
</section>
|
|
19
19
|
|
|
20
|
+
<section component="zui-formfield" heading="Formfield with tooltip - explaining technical term">
|
|
21
|
+
<zui-formfield label="API Key" tooltip="A unique identifier used to authenticate requests to external services">
|
|
22
|
+
<zui-input name="api-key"></zui-input>
|
|
23
|
+
</zui-formfield>
|
|
24
|
+
</section>
|
|
25
|
+
|
|
26
|
+
<section component="zui-formfield" heading="Tooltip explaining field purpose">
|
|
27
|
+
<zui-formfield
|
|
28
|
+
label="Cost center code"
|
|
29
|
+
tooltip="The accounting code your department uses for budget tracking and expense allocation">
|
|
30
|
+
<zui-input name="cost-center"></zui-input>
|
|
31
|
+
</zui-formfield>
|
|
32
|
+
</section>
|
|
33
|
+
|
|
20
34
|
<section component="zui-formfield" heading="Specify control selector">
|
|
21
35
|
<zui-formfield class="zui-formfield-demo-control-selector" label="North America phone number" control-selector="zui-input[name=areaCode]">
|
|
22
36
|
<zui-input name="countryCode" value="+1" class="xsmall"></zui-input>
|
package/lab.html
CHANGED
|
@@ -69,9 +69,6 @@
|
|
|
69
69
|
display: none;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
.part-demo::part(label) {
|
|
73
|
-
color: var(--zui-blue);
|
|
74
|
-
}
|
|
75
72
|
</style>
|
|
76
73
|
</head>
|
|
77
74
|
|
|
@@ -86,6 +83,12 @@
|
|
|
86
83
|
<zui-card>
|
|
87
84
|
<h2>Native form controls</h2>
|
|
88
85
|
<form id="form-native-element">
|
|
86
|
+
<zui-formfield label="Native select with tooltip" tooltip="Select one of the available options">
|
|
87
|
+
<select name="Native-select">
|
|
88
|
+
<option>Native1</option>
|
|
89
|
+
<option>Native2</option>
|
|
90
|
+
</select>
|
|
91
|
+
</zui-formfield>
|
|
89
92
|
<zui-formfield label="Native select">
|
|
90
93
|
<select name="Native-select">
|
|
91
94
|
<option>Native1</option>
|
|
@@ -162,7 +165,11 @@
|
|
|
162
165
|
<zui-option value="test-5">Test5</zui-option>
|
|
163
166
|
</zui-select>
|
|
164
167
|
</zui-formfield>
|
|
165
|
-
<zui-formfield
|
|
168
|
+
<zui-formfield
|
|
169
|
+
class="part-demo"
|
|
170
|
+
label="ZUI input with tooltip"
|
|
171
|
+
tooltip="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec suscipit auctor dui, at volutpat metus"
|
|
172
|
+
>
|
|
166
173
|
<zui-input
|
|
167
174
|
name="zui-input"
|
|
168
175
|
required=""
|
|
@@ -170,6 +177,18 @@
|
|
|
170
177
|
validation-message-minlength="Please use at least 2 characters."
|
|
171
178
|
></zui-input>
|
|
172
179
|
</zui-formfield>
|
|
180
|
+
<zui-formfield
|
|
181
|
+
label="NAICS Code"
|
|
182
|
+
tooltip="Enter a 2 to 6-digit NAICS code (e.g., '54' for Professional Services or '541511' for Programming).">
|
|
183
|
+
<zui-input
|
|
184
|
+
type="text"
|
|
185
|
+
inputmode="numeric"
|
|
186
|
+
minlength="2"
|
|
187
|
+
maxlength="6"
|
|
188
|
+
regex="^[0-9]{2,6}$"
|
|
189
|
+
placeholder="Enter 2-6 NAICS code">
|
|
190
|
+
</zui-input>
|
|
191
|
+
</zui-formfield>
|
|
173
192
|
<zui-formfield label="ZUI date input">
|
|
174
193
|
<zui-input name="zui-input-date" type="date"></zui-input>
|
|
175
194
|
</zui-formfield>
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zywave/zui-formfield",
|
|
3
|
-
"version": "4.3.0
|
|
3
|
+
"version": "4.3.0",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"module": "dist/index.js",
|
|
6
6
|
"license": "UNLICENSED",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@zywave/zui-base": "^4.6.1
|
|
8
|
+
"@zywave/zui-base": "^4.6.1",
|
|
9
|
+
"@zywave/zui-tooltip": "^4.3.3"
|
|
9
10
|
},
|
|
10
11
|
"scripts": {
|
|
11
12
|
"build": "npm run build:scss && npm run build:ts",
|
|
@@ -24,5 +25,5 @@
|
|
|
24
25
|
"access": "public"
|
|
25
26
|
},
|
|
26
27
|
"customElements": "dist/custom-elements.json",
|
|
27
|
-
"gitHead": "
|
|
28
|
+
"gitHead": "6ccacba5d09f8f9b2548272f06482a8c392af6c9"
|
|
28
29
|
}
|
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)}:host label.required::after{content:"*";margin-left:.5ch;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 label,:host .container ::slotted(label){width:100%;align-items:center;gap:1rem}:host .container label .label-text,:host .container ::slotted(label) .label-text{flex:0 1 auto}:host .container label zui-tooltip,:host .container ::slotted(label) zui-tooltip{flex:0 0 auto;margin-left:.5rem;line-height:1}: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 .label-text::after{content:"*";margin-left:.5ch;color:var(--zui-red-500)}`;
|
package/src/zui-formfield.scss
CHANGED
|
@@ -11,6 +11,20 @@
|
|
|
11
11
|
label,
|
|
12
12
|
::slotted(label) {
|
|
13
13
|
@extend %label;
|
|
14
|
+
width: 100%;
|
|
15
|
+
|
|
16
|
+
align-items: center;
|
|
17
|
+
gap: 1rem;
|
|
18
|
+
|
|
19
|
+
.label-text {
|
|
20
|
+
flex: 0 1 auto;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
zui-tooltip {
|
|
24
|
+
flex: 0 0 auto;
|
|
25
|
+
margin-left: rem(8);
|
|
26
|
+
line-height: 1;
|
|
27
|
+
}
|
|
14
28
|
}
|
|
15
29
|
|
|
16
30
|
::slotted(*:not(zui-toggle):not(zui-checkbox):not(zui-radio)) {
|
|
@@ -30,7 +44,7 @@
|
|
|
30
44
|
color: var(--zui-red-500);
|
|
31
45
|
}
|
|
32
46
|
|
|
33
|
-
label.required::after {
|
|
47
|
+
label.required .label-text::after {
|
|
34
48
|
content: '*';
|
|
35
49
|
margin-left: 0.5ch;
|
|
36
50
|
color: var(--zui-red-500);
|
package/src/zui-formfield.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { html, nothing } from 'lit';
|
|
|
4
4
|
import { property, query } from 'lit/decorators.js';
|
|
5
5
|
import { style } from './zui-formfield-css.js';
|
|
6
6
|
import type { PropertyValues } from 'lit';
|
|
7
|
+
import '@zywave/zui-tooltip';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* `<zui-formfield>` provides a standardized way of labeling and styling form controls.
|
|
@@ -26,6 +27,12 @@ export class ZuiFormField extends ZuiBaseElement {
|
|
|
26
27
|
@property({ type: String, attribute: 'control-selector' })
|
|
27
28
|
controlSelector = '*';
|
|
28
29
|
|
|
30
|
+
/**
|
|
31
|
+
* (optional): Tooltip text to display in a help icon next to the label.
|
|
32
|
+
*/
|
|
33
|
+
@property({ type: String })
|
|
34
|
+
tooltip: string | null = null;
|
|
35
|
+
|
|
29
36
|
@query('slot')
|
|
30
37
|
_slotEl: HTMLSlotElement;
|
|
31
38
|
|
|
@@ -93,7 +100,8 @@ export class ZuiFormField extends ZuiBaseElement {
|
|
|
93
100
|
${this.label
|
|
94
101
|
? html`
|
|
95
102
|
<label @click="${this.#onLabelClick}" part="label" class="${this.#isRequired ? 'required' : ''}">
|
|
96
|
-
|
|
103
|
+
<span class="label-text">${this.label}</span>
|
|
104
|
+
${this.tooltip ? html`<zui-tooltip position="right"> ${this.tooltip} </zui-tooltip>` : nothing}
|
|
97
105
|
</label>
|
|
98
106
|
`
|
|
99
107
|
: nothing}
|
|
@@ -226,4 +226,80 @@ suite('zui-formfield', () => {
|
|
|
226
226
|
);
|
|
227
227
|
});
|
|
228
228
|
});
|
|
229
|
+
|
|
230
|
+
suite('tooltip functionality', () => {
|
|
231
|
+
test('renders zui-tooltip when tooltip attribute is provided', async () => {
|
|
232
|
+
const tooltipText = 'This is help text';
|
|
233
|
+
formField.tooltip = tooltipText;
|
|
234
|
+
|
|
235
|
+
await formField.updateComplete;
|
|
236
|
+
|
|
237
|
+
const tooltip = formField.shadowRoot.querySelector('zui-tooltip') as HTMLElement;
|
|
238
|
+
assert.exists(tooltip, 'zui-tooltip should exist');
|
|
239
|
+
assert.equal(tooltip.textContent.trim(), tooltipText);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test('does not render zui-tooltip when tooltip attribute is not provided', async () => {
|
|
243
|
+
await formField.updateComplete;
|
|
244
|
+
|
|
245
|
+
const tooltip = formField.shadowRoot.querySelector('zui-tooltip');
|
|
246
|
+
assert.notExists(tooltip, 'zui-tooltip should not exist');
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test('tooltip position is set to right', async () => {
|
|
250
|
+
formField.tooltip = 'Help text';
|
|
251
|
+
|
|
252
|
+
await formField.updateComplete;
|
|
253
|
+
|
|
254
|
+
const tooltip = formField.shadowRoot.querySelector('zui-tooltip') as HTMLElement;
|
|
255
|
+
assert.equal(tooltip.getAttribute('position'), 'right');
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
test('does not render tooltip when label is not provided', async () => {
|
|
259
|
+
const formFieldNoLabel = document.createElement('zui-formfield') as ZuiFormField;
|
|
260
|
+
formFieldNoLabel.tooltip = 'Help text';
|
|
261
|
+
const inputNoLabel = document.createElement('input');
|
|
262
|
+
formFieldNoLabel.appendChild(inputNoLabel);
|
|
263
|
+
document.body.appendChild(formFieldNoLabel);
|
|
264
|
+
|
|
265
|
+
await formFieldNoLabel.updateComplete;
|
|
266
|
+
|
|
267
|
+
const tooltip = formFieldNoLabel.shadowRoot.querySelector('zui-tooltip');
|
|
268
|
+
assert.notExists(tooltip, 'tooltip should not render without a label');
|
|
269
|
+
|
|
270
|
+
formFieldNoLabel.remove();
|
|
271
|
+
inputNoLabel.remove();
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test('tooltip updates when tooltip property changes', async () => {
|
|
275
|
+
const initialText = 'Initial help text';
|
|
276
|
+
const updatedText = 'Updated help text';
|
|
277
|
+
|
|
278
|
+
formField.tooltip = initialText;
|
|
279
|
+
await formField.updateComplete;
|
|
280
|
+
|
|
281
|
+
let tooltip = formField.shadowRoot.querySelector('zui-tooltip') as HTMLElement;
|
|
282
|
+
assert.equal(tooltip.textContent.trim(), initialText);
|
|
283
|
+
|
|
284
|
+
formField.tooltip = updatedText;
|
|
285
|
+
await formField.updateComplete;
|
|
286
|
+
|
|
287
|
+
tooltip = formField.shadowRoot.querySelector('zui-tooltip') as HTMLElement;
|
|
288
|
+
assert.equal(tooltip.textContent.trim(), updatedText);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
test('tooltip is removed when tooltip property is set to null', async () => {
|
|
292
|
+
formField.tooltip = 'Help text';
|
|
293
|
+
await formField.updateComplete;
|
|
294
|
+
|
|
295
|
+
let tooltip = formField.shadowRoot.querySelector('zui-tooltip');
|
|
296
|
+
assert.exists(tooltip, 'tooltip should exist initially');
|
|
297
|
+
|
|
298
|
+
formField.tooltip = null;
|
|
299
|
+
await formField.updateComplete;
|
|
300
|
+
|
|
301
|
+
tooltip = formField.shadowRoot.querySelector('zui-tooltip');
|
|
302
|
+
assert.notExists(tooltip, 'tooltip should not exist after being set to null');
|
|
303
|
+
});
|
|
304
|
+
});
|
|
229
305
|
});
|