Skip to content

Commit 2c00a6c

Browse files
committed
Merge remote-tracking branch 'origin/dev' into feature/uui-dialog-layout
2 parents f81cd0c + 7c62877 commit 2c00a6c

22 files changed

+509
-75162
lines changed

package-lock.json

Lines changed: 0 additions & 75059 deletions
This file was deleted.

packages/uui-base/lib/mixins/FormControlMixin.ts

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ export declare abstract class FormControlMixinInterface {
1414
protected _value: FormDataEntryValue;
1515
protected _internals: any;
1616
protected abstract getFormElement(): HTMLElement | undefined;
17+
pristine: boolean;
18+
required: boolean;
19+
requiredMessage: string;
20+
error: boolean;
21+
errorMessage: string;
1722
}
1823

1924
type FlagTypes =
@@ -85,6 +90,15 @@ export const FormControlMixin = <T extends Constructor<LitElement>>(
8590
// Validation
8691
private _validityState: any = {};
8792

93+
/**
94+
* Determines wether the form control has been touched or interacted with, this determines wether the validation-status of this form control should be made visible.
95+
* @type {boolean}
96+
* @attr
97+
* @default false
98+
*/
99+
@property({ type: Boolean, reflect: true })
100+
pristine: boolean = true;
101+
88102
/**
89103
* Apply validation rule for requiring a value of this form control.
90104
* @type {boolean}
@@ -140,6 +154,10 @@ export const FormControlMixin = <T extends Constructor<LitElement>>(
140154
() => this.errorMessage,
141155
() => this.error
142156
);
157+
158+
this.addEventListener('blur', () => {
159+
this.pristine = false;
160+
});
143161
}
144162

145163
public hasValue(): boolean {
@@ -148,27 +166,13 @@ export const FormControlMixin = <T extends Constructor<LitElement>>(
148166

149167
protected abstract getFormElement(): HTMLElement | undefined;
150168

151-
connectedCallback(): void {
152-
super.connectedCallback();
153-
this._removeFormListeners();
154-
// TODO: try using formAssociatedCallback
155-
this._form = this._internals.form;
156-
if (this._form) {
157-
if (this._form.hasAttribute('hide-validation')) {
158-
this.setAttribute('hide-validation', '');
159-
}
160-
this._form.addEventListener('submit', this._onFormSubmit);
161-
this._form.addEventListener('reset', this._onFormReset);
162-
}
163-
}
164169
disconnectedCallback(): void {
165170
super.disconnectedCallback();
166171
this._removeFormListeners();
167172
}
168173
private _removeFormListeners() {
169174
if (this._form) {
170175
this._form.removeEventListener('submit', this._onFormSubmit);
171-
this._form.removeEventListener('reset', this._onFormReset);
172176
}
173177
}
174178

@@ -211,17 +215,22 @@ export const FormControlMixin = <T extends Constructor<LitElement>>(
211215
}
212216

213217
private _onFormSubmit = () => {
214-
if (this._form && this._form.checkValidity() === false) {
215-
this.removeAttribute('hide-validation');
216-
} else {
217-
this.setAttribute('hide-validation', '');
218-
}
219-
};
220-
private _onFormReset = () => {
221-
this.setAttribute('hide-validation', '');
218+
this.pristine = false;
222219
};
223220

221+
public formAssociatedCallback() {
222+
this._removeFormListeners();
223+
this._form = this._internals.form;
224+
if (this._form) {
225+
// This relies on the form begin a 'uui-form':
226+
if (this._form.hasAttribute('invalid-submit')) {
227+
this.pristine = false;
228+
}
229+
this._form.addEventListener('submit', this._onFormSubmit);
230+
}
231+
}
224232
public formResetCallback() {
233+
this.pristine = true;
225234
this.value = this.getAttribute('value') || '';
226235
}
227236

packages/uui-boolean-input/lib/uui-boolean-input.element.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ export abstract class UUIBooleanInputElement extends FormControlMixin(
178178
}
179179

180180
private _onInputChange() {
181+
this.pristine = false;
181182
this.checked = this._input.checked;
182183
this.dispatchEvent(new UUIBooleanInputEvent(UUIBooleanInputEvent.CHANGE));
183184
}

packages/uui-checkbox/lib/uui-checkbox.element.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -134,15 +134,15 @@ export class UUICheckboxElement extends UUIBooleanInputElement {
134134
transform: scale(0.9);
135135
}
136136
137-
:host(:not([hide-validation]):invalid) #ticker,
138-
:host(:not([hide-validation]):invalid) label:hover #ticker,
139-
:host(:not([hide-validation]):invalid) label:hover input:checked:not([disabled]) + #ticker,
140-
:host(:not([hide-validation]):invalid) label:focus input:checked + #ticker,
137+
:host(:not([pristine]):invalid) #ticker,
138+
:host(:not([pristine]):invalid) label:hover #ticker,
139+
:host(:not([pristine]):invalid) label:hover input:checked:not([disabled]) + #ticker,
140+
:host(:not([pristine]):invalid) label:focus input:checked + #ticker,
141141
/* polyfill support */
142-
:host(:not([hide-validation])[internals-invalid]) #ticker,
143-
:host(:not([hide-validation])[internals-invalid]) label:hover #ticker,
144-
:host(:not([hide-validation])[internals-invalid]) label:hover input:checked:not([disabled]) + #ticker,
145-
:host(:not([hide-validation])[internals-invalid]) label:focus input:checked + #ticker {
142+
:host(:not([pristine])[internals-invalid]) #ticker,
143+
:host(:not([pristine])[internals-invalid]) label:hover #ticker,
144+
:host(:not([pristine])[internals-invalid]) label:hover input:checked:not([disabled]) + #ticker,
145+
:host(:not([pristine])[internals-invalid]) label:focus input:checked + #ticker {
146146
border: 1px solid var(--uui-look-danger-border);
147147
}
148148

packages/uui-form/README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# uui-form
2+
3+
![npm](https://img.shields.io/npm/v/@umbraco-ui/uui-form?logoColor=%231B264F)
4+
5+
Umbraco style form component.
6+
7+
## Installation
8+
9+
### ES imports
10+
11+
```zsh
12+
npm i @umbraco-ui/uui-form
13+
```
14+
15+
Import the registration of `<form is="uui-form">` via:
16+
17+
```javascript
18+
import '@umbraco-ui/uui-form/lib';
19+
```
20+
21+
When looking to leverage the `UUIFormElement` base class as a type and/or for extension purposes, do so via:
22+
23+
```javascript
24+
import { UUIFormElement } from '@umbraco-ui/uui-form/lib';
25+
```
26+
27+
### CDN
28+
29+
The component is available via CDN. This means it can be added to your application without the need of any bundler configuration. Here is how to use it with jsDelivr.
30+
31+
```html
32+
<!-- Latest Version -->
33+
<script src="https://cdn.jsdelivr.net/npm/@umbraco-ui/uui-form@latest/dist/uui-form.min.js"></script>
34+
35+
<!-- Specific version -->
36+
<script src="https://cdn.jsdelivr.net/npm/@umbraco-ui/[email protected]/dist/uui-form.min.js"></script>
37+
```
38+
39+
## Usage
40+
41+
```html
42+
<form is="uui-form"></form>
43+
```

packages/uui-form/lib/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { UUIFormElement } from './uui-form.element';
2+
import { defineElement } from '@umbraco-ui/uui-base/lib/registration';
3+
4+
defineElement('uui-form', UUIFormElement, { extends: 'form' });
5+
6+
export * from './uui-form.element';
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/**
2+
* @element uui-form
3+
*/
4+
export class UUIFormElement extends HTMLFormElement {
5+
constructor() {
6+
super();
7+
this.setAttribute('novalidate', '');
8+
this.addEventListener('submit', this._onSubmit);
9+
this.addEventListener('reset', this._onReset);
10+
}
11+
12+
private _onSubmit(event: Event) {
13+
event.preventDefault();
14+
15+
const isValid = this.checkValidity();
16+
17+
if (!isValid) {
18+
this.setAttribute('submit-invalid', '');
19+
return;
20+
}
21+
this.removeAttribute('submit-invalid');
22+
23+
const formData = new FormData(this);
24+
25+
for (const value of formData.values()) {
26+
console.log(value);
27+
}
28+
}
29+
30+
private _onReset() {
31+
this.removeAttribute('submit-invalid');
32+
}
33+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { Story } from '@storybook/web-components';
2+
import { html } from 'lit-html';
3+
import '@umbraco-ui/uui-form/lib/index';
4+
import '@umbraco-ui/uui-checkbox/lib';
5+
import '@umbraco-ui/uui-slider/lib';
6+
import '@umbraco-ui/uui-radio/lib';
7+
import '@umbraco-ui/uui-toggle/lib';
8+
import { UUIRadioGroupEvent } from '@umbraco-ui/uui-radio/lib/UUIRadioGroupEvent';
9+
10+
export default {
11+
id: 'uui-form',
12+
title: 'Inputs/Form',
13+
component: 'uui-form',
14+
};
15+
16+
const _onRadioGroupChanged = (e: UUIRadioGroupEvent) => {
17+
e.target.error = e.target.value !== 'radio2';
18+
};
19+
20+
export const Overview: Story = () => html` <form
21+
is="uui-form"
22+
style="max-width: 800px;">
23+
<div style="margin-bottom: 15px;">
24+
<uui-checkbox
25+
name="checkbox"
26+
value="Bike"
27+
label="This is my checked checkbox"
28+
checked
29+
required>
30+
This is my checked checkbox
31+
</uui-checkbox>
32+
</div>
33+
34+
<div style="margin-bottom: 15px;">
35+
<uui-toggle name="toggle" label="This is my toggle" required>
36+
This is my toggle
37+
</uui-toggle>
38+
</div>
39+
40+
<div style="margin-bottom: 15px;">
41+
<uui-radio-group
42+
name="radio"
43+
label="This is my radio"
44+
required
45+
@change=${_onRadioGroupChanged}>
46+
<uui-radio value="radio1" label="radio1" name="radio1">Label</uui-radio>
47+
<uui-radio value="radio2" label="radio2" name="radio2">Label</uui-radio>
48+
<uui-radio value="radio3" label="radio3" name="radio3">Label</uui-radio>
49+
</uui-radio-group>
50+
</div>
51+
52+
<div style="margin-bottom: 15px;">
53+
<uui-input name="email" type="text" label="Email" required> </uui-input>
54+
</div>
55+
56+
<div style="margin-bottom: 15px;">
57+
<uui-input
58+
type="password"
59+
name="password"
60+
value="MyPassword"
61+
label="Password"
62+
required>
63+
</uui-input>
64+
</div>
65+
66+
<div style="margin-bottom: 15px;">
67+
<uui-slider
68+
label="Slider"
69+
name="slider"
70+
value="5.5"
71+
min="0"
72+
max="10"
73+
step="1"
74+
required>
75+
</uui-slider>
76+
</div>
77+
78+
<div style="margin-bottom: 15px;">
79+
<input
80+
name="nativeCheckbox"
81+
label="Native input text"
82+
type="checkbox"
83+
value="NativeCheckboxValue"
84+
placeholder="native text input"
85+
checked
86+
required />
87+
</div>
88+
89+
<div style="margin-bottom: 15px;">
90+
<input
91+
name="nativeInput"
92+
label="Native input text"
93+
type="text"
94+
default-value="default test value"
95+
value="test value"
96+
placeholder="native text input"
97+
required />
98+
</div>
99+
100+
<div style="margin-bottom: 15px;">
101+
<input
102+
name="nativeInputNumber"
103+
label="Native input number"
104+
type="number"
105+
value=""
106+
placeholder="native number input"
107+
min="0"
108+
max="10"
109+
required />
110+
</div>
111+
<div>
112+
<uui-button type="submit" label="Submit" look="positive">
113+
Submit
114+
</uui-button>
115+
116+
<uui-button type="reset" label="Reset" look="secondary"> Reset </uui-button>
117+
</div>
118+
</form>`;

0 commit comments

Comments
 (0)