@kubex/zinc 1.1.59 → 1.1.61
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 +122 -76
- package/dist/vscode.html-custom-data.json +22 -12
- package/dist/web-types.json +48 -24
- package/dist/zn.d.ts +9 -0
- package/dist/zn.min.js +36 -27
- package/docs/pages/components/input.md +30 -0
- package/docs/pages/components/toggle.md +29 -0
- package/package.json +1 -1
- package/src/components/input/input.component.ts +9 -2
- package/src/components/input/input.test.ts +55 -0
- package/src/components/toggle/toggle.component.ts +19 -3
- package/src/components/toggle/toggle.scss +18 -4
- package/src/components/toggle/toggle.test.ts +11 -0
- package/src/internal/form-navigation.ts +5 -0
|
@@ -416,6 +416,36 @@ Form navigation works with all Zinc form controls including selects, radios, che
|
|
|
416
416
|
**Note:** While you can navigate TO textareas and search inputs using Enter, pressing Enter while inside these controls uses the Enter key for their own functionality (new lines and search triggers, respectively). Use Tab to navigate away from these controls.
|
|
417
417
|
:::
|
|
418
418
|
|
|
419
|
+
### Trigger Submit
|
|
420
|
+
|
|
421
|
+
Add the `trigger-submit` attribute to make pressing Enter in the input always submit the surrounding form. Inputs already submit on Enter by default, so this is only needed on forms using `data-enter-navigation`, where Enter would otherwise move focus to the next field.
|
|
422
|
+
|
|
423
|
+
```html:preview
|
|
424
|
+
<form class="trigger-submit-form" data-enter-navigation>
|
|
425
|
+
<zn-input name="firstName" label="First Name" required></zn-input>
|
|
426
|
+
<br />
|
|
427
|
+
<zn-input name="search" label="Quick Search (Enter submits)" trigger-submit></zn-input>
|
|
428
|
+
<br />
|
|
429
|
+
<zn-button type="submit" variant="primary">Submit</zn-button>
|
|
430
|
+
</form>
|
|
431
|
+
|
|
432
|
+
<script type="module">
|
|
433
|
+
const form = document.querySelector('.trigger-submit-form');
|
|
434
|
+
|
|
435
|
+
await Promise.all([
|
|
436
|
+
customElements.whenDefined('zn-button'),
|
|
437
|
+
customElements.whenDefined('zn-input')
|
|
438
|
+
]).then(() => {
|
|
439
|
+
form.addEventListener('submit', (e) => {
|
|
440
|
+
e.preventDefault();
|
|
441
|
+
const formData = new FormData(form);
|
|
442
|
+
const data = Object.fromEntries(formData);
|
|
443
|
+
alert('Form submitted!\n\n' + JSON.stringify(data, null, 2));
|
|
444
|
+
});
|
|
445
|
+
});
|
|
446
|
+
</script>
|
|
447
|
+
```
|
|
448
|
+
|
|
419
449
|
### Customizing Label Position
|
|
420
450
|
|
|
421
451
|
Use [CSS parts](#css-parts) to customize the way form controls are drawn. This example uses CSS grid to position the
|
|
@@ -62,6 +62,35 @@ Use the `label` attribute to add a form control label above the toggle.
|
|
|
62
62
|
<zn-toggle label="User Preferences">Enable email notifications</zn-toggle>
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
+
### Description
|
|
66
|
+
|
|
67
|
+
Use the `description` attribute to add a description under the label. If you need to display HTML, use the `description` slot instead.
|
|
68
|
+
|
|
69
|
+
```html:preview
|
|
70
|
+
<zn-toggle label="Email notifications" description="Receive an email whenever someone mentions you."></zn-toggle>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The description stays under the label at every `label-position`, and can be combined with `help-text`, which always appears below the toggle.
|
|
74
|
+
|
|
75
|
+
```html:preview
|
|
76
|
+
<zn-toggle
|
|
77
|
+
label="Top label (default)"
|
|
78
|
+
description="Receive an email whenever someone mentions you."
|
|
79
|
+
help-text="You can change this at any time"></zn-toggle>
|
|
80
|
+
<br />
|
|
81
|
+
<zn-toggle
|
|
82
|
+
label="Left label"
|
|
83
|
+
label-position="left"
|
|
84
|
+
description="Receive an email whenever someone mentions you."
|
|
85
|
+
help-text="You can change this at any time"></zn-toggle>
|
|
86
|
+
<br />
|
|
87
|
+
<zn-toggle
|
|
88
|
+
label="Right label"
|
|
89
|
+
label-position="right"
|
|
90
|
+
description="Receive an email whenever someone mentions you."
|
|
91
|
+
help-text="You can change this at any time"></zn-toggle>
|
|
92
|
+
```
|
|
93
|
+
|
|
65
94
|
### Label Position
|
|
66
95
|
|
|
67
96
|
Use the `label-position` attribute to control where the label sits relative to the toggle. Available positions are `top` (default), `left`, and `right`.
|
package/package.json
CHANGED
|
@@ -212,6 +212,12 @@ export default class ZnInput extends ZincElement implements ZincFormControl {
|
|
|
212
212
|
*/
|
|
213
213
|
@property() inputmode: 'none' | 'text' | 'decimal' | 'numeric' | 'tel' | 'search' | 'email' | 'url';
|
|
214
214
|
|
|
215
|
+
/**
|
|
216
|
+
* When enabled, pressing enter will always submit the surrounding form, even when the form uses
|
|
217
|
+
* enter-navigation to move between fields.
|
|
218
|
+
*/
|
|
219
|
+
@property({type: Boolean, attribute: 'trigger-submit'}) triggerSubmit = false;
|
|
220
|
+
|
|
215
221
|
//
|
|
216
222
|
// NOTE: We use an in-memory input for these getters/setters instead of the one in the template because the properties
|
|
217
223
|
// can be set before the component is rendered.
|
|
@@ -561,9 +567,10 @@ export default class ZnInput extends ZincElement implements ZincFormControl {
|
|
|
561
567
|
// When using a Input Method Editor (IME), pressing enter will cause the form to submit unexpectedly. One way
|
|
562
568
|
// to check for this is to look at event.isComposing, which will be true when the IME is open.
|
|
563
569
|
if (!event.defaultPrevented && !event.isComposing) {
|
|
564
|
-
// Check if form has navigation enabled - if so, let FormNavigationController handle it
|
|
570
|
+
// Check if form has navigation enabled - if so, let FormNavigationController handle it,
|
|
571
|
+
// unless trigger-submit is set which always submits on enter
|
|
565
572
|
const form = this.formControlController.getForm();
|
|
566
|
-
if (form?.hasAttribute('enter-navigation') || form?.hasAttribute('data-enter-navigation')) {
|
|
573
|
+
if (!this.triggerSubmit && (form?.hasAttribute('enter-navigation') || form?.hasAttribute('data-enter-navigation'))) {
|
|
567
574
|
return; // Let FormNavigationController handle it
|
|
568
575
|
}
|
|
569
576
|
|
|
@@ -42,6 +42,61 @@ describe('<zn-input>', () => {
|
|
|
42
42
|
});
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
+
describe('trigger-submit', () => {
|
|
46
|
+
const pressEnter = (el: ZnInput) => {
|
|
47
|
+
el.input.dispatchEvent(new KeyboardEvent('keydown', {
|
|
48
|
+
key: 'Enter',
|
|
49
|
+
bubbles: true,
|
|
50
|
+
composed: true,
|
|
51
|
+
cancelable: true
|
|
52
|
+
}));
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
it('submits an enter-navigation form on enter when trigger-submit is set', async () => {
|
|
56
|
+
const form = await fixture<HTMLFormElement>(html`
|
|
57
|
+
<form data-enter-navigation>
|
|
58
|
+
<zn-input name="a" trigger-submit></zn-input>
|
|
59
|
+
<zn-input name="b"></zn-input>
|
|
60
|
+
</form>
|
|
61
|
+
`);
|
|
62
|
+
const el = form.querySelector('zn-input')!;
|
|
63
|
+
await el.updateComplete;
|
|
64
|
+
|
|
65
|
+
let submitted = false;
|
|
66
|
+
form.addEventListener('submit', e => {
|
|
67
|
+
e.preventDefault();
|
|
68
|
+
submitted = true;
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
el.focus();
|
|
72
|
+
pressEnter(el);
|
|
73
|
+
|
|
74
|
+
expect(submitted).to.be.true;
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('does not submit an enter-navigation form on enter without trigger-submit', async () => {
|
|
78
|
+
const form = await fixture<HTMLFormElement>(html`
|
|
79
|
+
<form data-enter-navigation>
|
|
80
|
+
<zn-input name="a"></zn-input>
|
|
81
|
+
<zn-input name="b"></zn-input>
|
|
82
|
+
</form>
|
|
83
|
+
`);
|
|
84
|
+
const el = form.querySelector('zn-input')!;
|
|
85
|
+
await el.updateComplete;
|
|
86
|
+
|
|
87
|
+
let submitted = false;
|
|
88
|
+
form.addEventListener('submit', e => {
|
|
89
|
+
e.preventDefault();
|
|
90
|
+
submitted = true;
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
el.focus();
|
|
94
|
+
pressEnter(el);
|
|
95
|
+
|
|
96
|
+
expect(submitted).to.be.false;
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
45
100
|
describe('persisted state', () => {
|
|
46
101
|
const formStoreKey = 'input-test-form-state';
|
|
47
102
|
const elementStoreKey = 'input-test-element-state';
|
|
@@ -21,17 +21,19 @@ import styles from './toggle.scss';
|
|
|
21
21
|
* @event zn-input - Emitted when the toggle receives input.
|
|
22
22
|
*
|
|
23
23
|
* @slot - The toggle's label.
|
|
24
|
+
* @slot description - A description of the toggle's label. Alternatively, you can use the `description` attribute.
|
|
24
25
|
* @slot help-text - Text that describes how to use the toggle. Alternatively, you can use the `help-text` attribute.
|
|
25
26
|
*
|
|
26
27
|
* @csspart base - The component's base wrapper containing the toggle switch.
|
|
27
28
|
* @csspart control - The toggle switch control (the circular button that slides).
|
|
29
|
+
* @csspart description - The container that wraps the toggle's description.
|
|
28
30
|
*
|
|
29
31
|
* @cssproperty --zn-toggle-margin - The margin around the toggle switch. Defaults to `8px 0`.
|
|
30
32
|
*/
|
|
31
33
|
export default class ZnToggle extends ZincElement implements ZincFormControl {
|
|
32
34
|
static styles: CSSResultGroup = unsafeCSS(styles);
|
|
33
35
|
|
|
34
|
-
private readonly hasSlotController = new HasSlotController(this, 'help-text');
|
|
36
|
+
private readonly hasSlotController = new HasSlotController(this, 'help-text', 'description');
|
|
35
37
|
|
|
36
38
|
private readonly formControlController = new FormControlController(this, {
|
|
37
39
|
value: (control: ZnToggle) => (control.checked ? control.value || 'on' : undefined),
|
|
@@ -73,6 +75,9 @@ export default class ZnToggle extends ZincElement implements ZincFormControl {
|
|
|
73
75
|
|
|
74
76
|
@property() label: string = '';
|
|
75
77
|
|
|
78
|
+
/** The toggle's description, displayed under the label. If you need to display HTML, use the `description` slot instead. */
|
|
79
|
+
@property() description: string = '';
|
|
80
|
+
|
|
76
81
|
@property({ attribute: 'label-position', reflect: true }) labelPosition: 'top' | 'left' | 'right' = 'top';
|
|
77
82
|
|
|
78
83
|
@property({ type: Boolean }) inline: boolean = false;
|
|
@@ -174,6 +179,7 @@ export default class ZnToggle extends ZincElement implements ZincFormControl {
|
|
|
174
179
|
const tooltipContent = this.checked ? this.onText : this.offText;
|
|
175
180
|
const showTooltip = !!tooltipContent;
|
|
176
181
|
const hasHelpText = this.helpText ? true : this.hasSlotController.test('help-text');
|
|
182
|
+
const hasDescription = this.description ? true : this.hasSlotController.test('description');
|
|
177
183
|
|
|
178
184
|
const toggle = html`
|
|
179
185
|
<div class="switch__input-wrapper" part="base">
|
|
@@ -189,7 +195,7 @@ export default class ZnToggle extends ZincElement implements ZincFormControl {
|
|
|
189
195
|
.required=${this.required}
|
|
190
196
|
role="switch"
|
|
191
197
|
aria-checked=${this.checked ? 'true' : 'false'}
|
|
192
|
-
aria-describedby="help-text"
|
|
198
|
+
aria-describedby="description help-text"
|
|
193
199
|
@click=${this.handleClick}
|
|
194
200
|
@input=${this.handleInput}
|
|
195
201
|
@invalid=${this.handleInvalid}
|
|
@@ -208,6 +214,7 @@ export default class ZnToggle extends ZincElement implements ZincFormControl {
|
|
|
208
214
|
'form-control--large': this.size === 'large',
|
|
209
215
|
'form-control--has-help-text': hasHelpText,
|
|
210
216
|
'switch__wrapper': true,
|
|
217
|
+
'switch__wrapper--has-description': hasDescription,
|
|
211
218
|
'switch__wrapper--disabled': this.disabled,
|
|
212
219
|
'switch__wrapper--has-focus': this.hasFocus,
|
|
213
220
|
'switch__wrapper--small': this.size === 'small',
|
|
@@ -218,7 +225,16 @@ export default class ZnToggle extends ZincElement implements ZincFormControl {
|
|
|
218
225
|
'switch__wrapper--label-right': this.labelPosition === 'right'
|
|
219
226
|
})}">
|
|
220
227
|
<label>
|
|
221
|
-
${this.label ? html
|
|
228
|
+
${this.label || hasDescription ? html`
|
|
229
|
+
<div class="switch__label-wrapper">
|
|
230
|
+
${this.label ? html`<p class="switch-label">${this.label}</p>` : ''}
|
|
231
|
+
<div part="description"
|
|
232
|
+
id="description"
|
|
233
|
+
class="switch__description"
|
|
234
|
+
aria-hidden=${hasDescription ? 'false' : 'true'}>
|
|
235
|
+
<slot name="description">${this.description}</slot>
|
|
236
|
+
</div>
|
|
237
|
+
</div>` : ''}
|
|
222
238
|
${!showTooltip ? html`
|
|
223
239
|
${toggle}` : html`
|
|
224
240
|
<zn-tooltip content="${tooltipContent}">
|
|
@@ -58,17 +58,31 @@
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
.switch-label {
|
|
61
|
+
@include wc.text-style(input-label);
|
|
62
|
+
|
|
61
63
|
margin: 0;
|
|
62
|
-
color: var(--zn-input-label-color);
|
|
63
|
-
font-size: var(--zn-input-label-font-size-medium);
|
|
64
|
-
font-weight: var(--zn-input-label-font-weight);
|
|
65
|
-
line-height: var(--zn-input-label-line-height);
|
|
66
64
|
}
|
|
67
65
|
|
|
68
66
|
.switch__wrapper--small .switch-label {
|
|
69
67
|
font-size: var(--zn-input-label-font-size-small);
|
|
70
68
|
}
|
|
71
69
|
|
|
70
|
+
.switch__description {
|
|
71
|
+
display: none;
|
|
72
|
+
margin-top: var(--zn-spacing-2x-small);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.switch__wrapper--has-description .switch__description {
|
|
76
|
+
@include wc.text-style(paragraph);
|
|
77
|
+
|
|
78
|
+
display: block;
|
|
79
|
+
color: rgb(var(--zn-color-muted-text));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.switch__wrapper--small .switch__description {
|
|
83
|
+
font-size: var(--zn-font-size-x-small);
|
|
84
|
+
}
|
|
85
|
+
|
|
72
86
|
.switch__wrapper--large .switch-label {
|
|
73
87
|
font-size: var(--zn-input-label-font-size-large);
|
|
74
88
|
}
|
|
@@ -7,4 +7,15 @@ describe('<zn-toggle>', () => {
|
|
|
7
7
|
|
|
8
8
|
expect(el).to.exist;
|
|
9
9
|
});
|
|
10
|
+
|
|
11
|
+
it('should render the description under the label', async () => {
|
|
12
|
+
const el = await fixture<HTMLElement>(html`
|
|
13
|
+
<zn-toggle label="Email notifications" description="Receive an email when mentioned."></zn-toggle> `);
|
|
14
|
+
|
|
15
|
+
const label = el.shadowRoot!.querySelector('.switch-label')!;
|
|
16
|
+
const description = el.shadowRoot!.querySelector('[part="description"]')!;
|
|
17
|
+
|
|
18
|
+
expect(description.textContent).to.include('Receive an email when mentioned.');
|
|
19
|
+
expect(label.nextElementSibling).to.equal(description);
|
|
20
|
+
});
|
|
10
21
|
});
|
|
@@ -46,6 +46,11 @@ export class FormNavigationController {
|
|
|
46
46
|
return;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
// Controls with trigger-submit handle submitting the form themselves
|
|
50
|
+
if (target.hasAttribute('trigger-submit')) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
49
54
|
// Check if we're in a group (radio-group or checkbox-group)
|
|
50
55
|
const group = this.findParentGroup(target);
|
|
51
56
|
if (group) {
|