Skip to content

Commit

Permalink
feat(text-input): validation attrs (#2606)
Browse files Browse the repository at this point in the history
* feat(text-input): validation attrs

* fix(text-input): formStateRestor

* style: whitespace

* fix(text-input): update elements/pf-text-input/pf-text-input.ts

Co-authored-by: Ivana Rodriguez <55894965+eyevana@users.noreply.github.com>

* docs: update elements/pf-text-input/demo/validation.html

Co-authored-by: Ivana Rodriguez <55894965+eyevana@users.noreply.github.com>

---------

Co-authored-by: Ivana Rodriguez <55894965+eyevana@users.noreply.github.com>
  • Loading branch information
bennypowers and eyevana committed Oct 29, 2023
1 parent a194ba7 commit 2c019ac
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 4 deletions.
14 changes: 14 additions & 0 deletions .changeset/pf-text-input-error-text.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
"@patternfly/elements": minor
---
`<pf-text-input>`: adds `helper-text`, `error-text`, and `validate-on` attributes. Forwards `pattern` attribute

```html
<pf-text-input id="validated"
error-text="Enter a three digit integer"
helper-text="How much wood could a woodchuck chuck?"
validate-on="blur"
pattern="\d{3}"
required></pf-text-input>
<pf-button id="validate">Validate</pf-button>
```
26 changes: 26 additions & 0 deletions elements/pf-text-input/demo/validation.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<fieldset>
<legend>Invalid</legend>
<label>Validate on Blur? <pf-switch id="onblur"></pf-switch></label>
<pf-text-input id="validated"
error-text="Enter a three digit integer"
pattern="\d{3}"
required></pf-text-input>
<pf-button id="validate">Validate</pf-button>
</fieldset>

<script type="module">
import '@patternfly/elements/pf-button/pf-button.js';
import '@patternfly/elements/pf-switch/pf-switch.js';
import '@patternfly/elements/pf-text-input/pf-text-input.js';
const onblur = document.getElementById('onblur');
const input = document.getElementById('validated');
const validate = document.getElementById('validate');
onblur.addEventListener('change', function() {
validated.validateOn = this.checked ? 'blur' : null;
});
validate.addEventListener('click', function() {
validated.checkValidity();
});
</script>

<link rel="stylesheet" href="demo.css">
6 changes: 6 additions & 0 deletions elements/pf-text-input/pf-text-input.css
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@
--pf-c-form-control--m-icon-sprite__select--success--BackgroundPosition: calc(100% - var(--pf-global--spacer--md, 1rem) + 1px - var(--pf-global--spacer--lg, 1.5rem));
--pf-c-form-control--m-icon-sprite__select--m-warning--BackgroundPosition: calc(100% - var(--pf-global--spacer--md, 1rem) - var(--pf-global--spacer--lg, 1.5rem) + 0.0625rem);
--pf-c-form-control--m-icon-sprite__select--invalid--BackgroundPosition: calc(100% - var(--pf-global--spacer--md, 1rem) - var(--pf-global--spacer--lg, 1.5rem));
/* NB: this var doesn't exist in pf react */
--pf-c-form-control__error-text--m-status--Color: var(--pf-global--danger-color--100, #c9190b);

display: inline-block;
/* adjust the host to fit the input */
Expand Down Expand Up @@ -163,6 +165,10 @@ input::placeholder {
color: var(--pf-c-form-control--placeholder--Color);
}

#error-text {
color: var(--pf-c-form-control__error-text--m-status--Color);
}

:host([left-truncated]) {
position: relative;
}
Expand Down
53 changes: 49 additions & 4 deletions elements/pf-text-input/pf-text-input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ function getLabelText(label: HTMLElement) {
* @cssprop --pf-c-form-control--m-icon-sprite__select--success--BackgroundPosition - {@default calc(100% - var(--pf-global--spacer--md, 1rem) + 1px - var(--pf-global--spacer--lg, 1.5rem))}
* @cssprop --pf-c-form-control--m-icon-sprite__select--m-warning--BackgroundPosition - {@default calc(100% - var(--pf-global--spacer--md, 1rem) - var(--pf-global--spacer--lg, 1.5rem) + 0.0625rem)}
* @cssprop --pf-c-form-control--m-icon-sprite__select--invalid--BackgroundPosition - {@default calc(100% - var(--pf-global--spacer--md, 1rem) - var(--pf-global--spacer--lg, 1.5rem))}
* @cssprop --pf-c-form-control__error-text--m-status--Color - {@default var(--pf-global--danger-color--100, #c9190b)}
*/
@customElement('pf-text-input')
export class PfTextInput extends LitElement {
Expand Down Expand Up @@ -185,9 +186,21 @@ export class PfTextInput extends LitElement {
/** Flag to show if the input is required. */
@property({ type: Boolean, reflect: true }) required = false;

/** Validation pattern, like `<input>` */
@property() pattern?: string;

/** Flag to show if the input is read only. */
@property({ type: Boolean, reflect: true }) readonly = false;

/** Helper text is text below a form field that helps a user provide the right information, like "Enter a unique name". */
@property({ attribute: 'helper-text' }) helperText?: string;

/** If set to 'blur', will validate when focus leaves the input */
@property({ attribute: 'validate-on' }) validateOn?: 'blur';

/** Displayed when validation fails */
@property({ attribute: 'error-text' }) errorText?: string;

/** Input placeholder. */
@property() placeholder?: string;

Expand All @@ -198,6 +211,8 @@ export class PfTextInput extends LitElement {

#derivedLabel = '';

#touched = false;

get #input() {
return this.shadowRoot?.getElementById('input') as HTMLInputElement ?? null;
}
Expand All @@ -213,48 +228,78 @@ export class PfTextInput extends LitElement {
}

override render() {
const { valid } = this.#internals.validity;
return html`
<input id="input"
.placeholder="${this.placeholder ?? ''}"
.value="${this.value}"
.pattern="${this.pattern as string}"
@input="${this.#onInput}"
@blur="${this.#onBlur}"
?disabled="${this.matches(':disabled') || this.disabled}"
?readonly="${this.readonly}"
?required="${this.required}"
aria-label="${this.#derivedLabel}"
placeholder="${ifDefined(this.placeholder)}"
type="${ifDefined(this.type)}"
.value="${this.value}"
style="${ifDefined(this.customIconUrl && styleMap({
backgroundImage: `url('${this.customIconUrl}')`,
backgroundSize: this.customIconDimensions,
}))}">
<span id="helper-text" ?hidden="${!this.helperText ?? valid}">${this.helperText}</span>
<span id="error-text" ?hidden="${valid}">${this.#internals.validationMessage}</span>
`;
}

#onInput(event: Event & { target: HTMLInputElement }) {
const { value } = event.target;
this.value = value;
this.#internals.setFormValue(value);
if (this.#touched && !this.#internals.validity.valid) {
this.#onBlur();
}
this.#touched = true;
}

#onBlur() {
if (this.validateOn === 'blur') {
this.checkValidity();
}
}

#setValidityFromInput() {
this.#internals.setValidity(
this.#input?.validity,
this.#input.validationMessage,
this.errorText ?? this.#input.validationMessage,
);
this.requestUpdate();
}

async formStateRestoreCallback(state: string, mode: string) {
if (mode === 'restore') {
const [controlMode, value] = state.split('/');
this.value = value ?? controlMode;
this.requestUpdate();
await this.updateComplete;
this.#setValidityFromInput();
}
}


async formDisabledCallback() {
await this.updateComplete;
this.requestUpdate();
}

setCustomValidity(message: string) {
this.#internals.setValidity({}, message);
this.requestUpdate();
}

checkValidity() {
this.#setValidityFromInput();
return this.#internals.checkValidity();
const validity = this.#internals.checkValidity();
this.requestUpdate();
return validity;
}

reportValidity() {
Expand Down

0 comments on commit 2c019ac

Please sign in to comment.