@kubex/zinc 1.1.104 → 1.1.106
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 +348 -74
- package/dist/vscode.html-custom-data.json +68 -11
- package/dist/web-types.json +142 -23
- package/dist/zn.d.ts +65 -1
- package/dist/zn.min.js +415 -409
- package/docs/pages/components/dialog.md +2 -0
- package/docs/pages/components/form-actions.md +71 -0
- package/docs/pages/components/slideout.md +2 -0
- package/package.json +1 -1
- package/src/components/button/button.scss +4 -0
- package/src/components/dialog/dialog.component.ts +14 -0
- package/src/components/form-actions/form-actions.component.ts +96 -0
- package/src/components/form-actions/form-actions.scss +9 -0
- package/src/components/form-actions/form-actions.test.ts +40 -0
- package/src/components/form-actions/index.ts +12 -0
- package/src/components/icon-picker/icon-picker.component.ts +31 -7
- package/src/components/slideout/slideout.component.ts +14 -0
- package/src/components/tile/tile.scss +1 -0
- package/src/zinc.ts +1 -0
|
@@ -378,6 +378,8 @@ You can also prevent close from specific sources by checking the `event.detail.s
|
|
|
378
378
|
|
|
379
379
|
Add the `dialog-closer` attribute to any element inside the dialog to make it close the dialog when clicked. This is commonly used with buttons in the footer.
|
|
380
380
|
|
|
381
|
+
The `modal-closer` attribute works the same way but closes the nearest containing modal of either kind — dialog or slideout — and, unlike `dialog-closer`, works from inside a nested component's shadow root. `<zn-form-actions>` uses it on its cancel button.
|
|
382
|
+
|
|
381
383
|
```html:preview
|
|
382
384
|
<zn-button id="dialog-closer-trigger">Open Dialog</zn-button>
|
|
383
385
|
<zn-dialog trigger="dialog-closer-trigger" label="Dialog Closer">
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
meta:
|
|
3
|
+
title: Form Actions
|
|
4
|
+
description: A standard action row for the bottom of a form, built up from a lone submit button.
|
|
5
|
+
layout: component
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Drops the standard action row into a form. The default is just a primary submit button — build it up with `with-cancel` and `with-reset` as the form needs. Place it inside the form it should act on.
|
|
9
|
+
|
|
10
|
+
```html:preview
|
|
11
|
+
<form>
|
|
12
|
+
<zn-form-actions></zn-form-actions>
|
|
13
|
+
</form>
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Examples
|
|
17
|
+
|
|
18
|
+
### With a Cancel Button
|
|
19
|
+
|
|
20
|
+
Use the `with-cancel` attribute to add a cancel button. When the form lives inside a dialog or slideout, cancelling closes it; it also emits a `zn-cancel` event for the container to handle.
|
|
21
|
+
|
|
22
|
+
```html:preview
|
|
23
|
+
<zn-dialog trigger="form-actions-dialog" label="New Ticket">
|
|
24
|
+
<form>
|
|
25
|
+
<zn-form-actions with-cancel></zn-form-actions>
|
|
26
|
+
</form>
|
|
27
|
+
</zn-dialog>
|
|
28
|
+
<zn-button id="form-actions-dialog">Open Dialog</zn-button>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### With a Reset Button
|
|
32
|
+
|
|
33
|
+
Use the `with-reset` attribute to add a button that resets the form's fields.
|
|
34
|
+
|
|
35
|
+
```html:preview
|
|
36
|
+
<form>
|
|
37
|
+
<input type="text" name="example" value="Change me, then reset">
|
|
38
|
+
<zn-form-actions with-reset></zn-form-actions>
|
|
39
|
+
</form>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Custom Text and Icons
|
|
43
|
+
|
|
44
|
+
Each button's text and icon can be overridden with `submit-text` / `submit-icon`, `cancel-text` / `cancel-icon` and `reset-text` / `reset-icon`.
|
|
45
|
+
|
|
46
|
+
```html:preview
|
|
47
|
+
<form>
|
|
48
|
+
<zn-form-actions with-cancel submit-text="Create Ticket" submit-icon="plus@lu" cancel-text="Discard" cancel-icon="trash-2@lu"></zn-form-actions>
|
|
49
|
+
</form>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Extra Actions
|
|
53
|
+
|
|
54
|
+
Extra buttons placed in the default slot appear before the standard buttons.
|
|
55
|
+
|
|
56
|
+
```html:preview
|
|
57
|
+
<form>
|
|
58
|
+
<zn-form-actions>
|
|
59
|
+
<zn-button color="transparent">Preview</zn-button>
|
|
60
|
+
</zn-form-actions>
|
|
61
|
+
</form>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Targeting a Form by Id
|
|
65
|
+
|
|
66
|
+
When the component can't live inside the form, point it at one with the `form` attribute.
|
|
67
|
+
|
|
68
|
+
```html:preview
|
|
69
|
+
<form id="standalone-form"></form>
|
|
70
|
+
<zn-form-actions form="standalone-form"></zn-form-actions>
|
|
71
|
+
```
|
|
@@ -248,6 +248,8 @@ You can also prevent close from specific sources by checking the `event.detail.s
|
|
|
248
248
|
|
|
249
249
|
Add the `slideout-closer` attribute to any element inside the slideout to make it close the slideout when clicked. This is commonly used with buttons in the footer.
|
|
250
250
|
|
|
251
|
+
The `modal-closer` attribute works the same way but closes the nearest containing modal of either kind — dialog or slideout — and, unlike `slideout-closer`, works from inside a nested component's shadow root. `<zn-form-actions>` uses it on its cancel button.
|
|
252
|
+
|
|
251
253
|
```html:preview
|
|
252
254
|
<zn-button id="slideout-closer-trigger">Open Slideout</zn-button>
|
|
253
255
|
<zn-slideout trigger="slideout-closer-trigger" label="Slideout Closer">
|
package/package.json
CHANGED
|
@@ -184,6 +184,20 @@ export default class ZnDialog extends ZincElement {
|
|
|
184
184
|
private closeClickHandler = (event: MouseEvent) => {
|
|
185
185
|
if (event.target instanceof HTMLElement && event.target.hasAttribute('dialog-closer')) {
|
|
186
186
|
this.hide();
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// `modal-closer` works from nested shadow roots (e.g. zn-form-actions' cancel button),
|
|
191
|
+
// where retargeting hides the source from event.target. Walk outward from the click:
|
|
192
|
+
// hitting another modal first means the closer belongs to it, not this dialog.
|
|
193
|
+
for (const el of event.composedPath()) {
|
|
194
|
+
if (el === this) return;
|
|
195
|
+
if (!(el instanceof HTMLElement)) continue;
|
|
196
|
+
if (el.hasAttribute('modal-closer')) {
|
|
197
|
+
this.hide();
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (el.localName === 'zn-dialog' || el.localName === 'zn-slideout') return;
|
|
187
201
|
}
|
|
188
202
|
}
|
|
189
203
|
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import {type CSSResultGroup, html, unsafeCSS} from 'lit';
|
|
2
|
+
import {idSelector} from "../../utilities/query";
|
|
3
|
+
import {property} from 'lit/decorators.js';
|
|
4
|
+
import ZincElement from '../../internal/zinc-element';
|
|
5
|
+
import ZnButton from "../button";
|
|
6
|
+
|
|
7
|
+
import styles from './form-actions.scss';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @summary Standard action row for the bottom of a form. Renders a submit button by default; opt in to more
|
|
11
|
+
* with `with-cancel` and `with-reset`.
|
|
12
|
+
* @documentation https://zinc.style/components/form-actions
|
|
13
|
+
* @status experimental
|
|
14
|
+
* @since 1.0
|
|
15
|
+
*
|
|
16
|
+
* @dependency zn-button
|
|
17
|
+
*
|
|
18
|
+
* @event zn-cancel - Emitted when the cancel button is clicked.
|
|
19
|
+
*
|
|
20
|
+
* @slot - Extra actions, placed before the buttons.
|
|
21
|
+
*
|
|
22
|
+
* @csspart cancel-button - The cancel button.
|
|
23
|
+
* @csspart reset-button - The reset button.
|
|
24
|
+
* @csspart submit-button - The submit button.
|
|
25
|
+
*/
|
|
26
|
+
export default class ZnFormActions extends ZincElement {
|
|
27
|
+
static styles: CSSResultGroup = unsafeCSS(styles);
|
|
28
|
+
static dependencies = {
|
|
29
|
+
'zn-button': ZnButton
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/** The submit button's text. */
|
|
33
|
+
@property({attribute: 'submit-text'}) submitText = 'Save';
|
|
34
|
+
|
|
35
|
+
/** The submit button's icon. */
|
|
36
|
+
@property({attribute: 'submit-icon'}) submitIcon = 'check@lu';
|
|
37
|
+
|
|
38
|
+
/** Adds a cancel button. Closes the containing dialog or slideout, if any, and emits `zn-cancel`. */
|
|
39
|
+
@property({attribute: 'with-cancel', type: Boolean}) withCancel = false;
|
|
40
|
+
|
|
41
|
+
/** The cancel button's text. */
|
|
42
|
+
@property({attribute: 'cancel-text'}) cancelText = 'Cancel';
|
|
43
|
+
|
|
44
|
+
/** The cancel button's icon. */
|
|
45
|
+
@property({attribute: 'cancel-icon'}) cancelIcon = 'x@lu';
|
|
46
|
+
|
|
47
|
+
/** Adds a reset button that resets the form. */
|
|
48
|
+
@property({attribute: 'with-reset', type: Boolean}) withReset = false;
|
|
49
|
+
|
|
50
|
+
/** The reset button's text. */
|
|
51
|
+
@property({attribute: 'reset-text'}) resetText = 'Reset';
|
|
52
|
+
|
|
53
|
+
/** The reset button's icon. */
|
|
54
|
+
@property({attribute: 'reset-icon'}) resetIcon = 'rotate-ccw@lu';
|
|
55
|
+
|
|
56
|
+
/** The id of the form to act on. If omitted, the closest containing form is used. */
|
|
57
|
+
@property() form: string;
|
|
58
|
+
|
|
59
|
+
// The buttons live in this component's shadow root, so zn-button's own
|
|
60
|
+
// submit/reset handling can't find the form — resolve it from the host.
|
|
61
|
+
private getForm(): HTMLFormElement | null {
|
|
62
|
+
if (this.form) {
|
|
63
|
+
const root = this.getRootNode() as Document | ShadowRoot;
|
|
64
|
+
return root.querySelector<HTMLFormElement>(idSelector(this.form));
|
|
65
|
+
}
|
|
66
|
+
return this.closest('form');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private handleCancel = () => {
|
|
70
|
+
this.emit('zn-cancel');
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
private handleReset = () => {
|
|
74
|
+
this.getForm()?.reset();
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
private handleSubmit = () => {
|
|
78
|
+
this.getForm()?.requestSubmit();
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
render() {
|
|
82
|
+
return html`
|
|
83
|
+
<slot></slot>
|
|
84
|
+
${this.withCancel ? html`
|
|
85
|
+
<zn-button part="cancel-button" panel-bg modal-closer icon="${this.cancelIcon}" @click="${this.handleCancel}">
|
|
86
|
+
${this.cancelText}
|
|
87
|
+
</zn-button>` : ''}
|
|
88
|
+
${this.withReset ? html`
|
|
89
|
+
<zn-button part="reset-button" panel-bg icon="${this.resetIcon}" @click="${this.handleReset}">
|
|
90
|
+
${this.resetText}
|
|
91
|
+
</zn-button>` : ''}
|
|
92
|
+
<zn-button part="submit-button" color="primary" icon="${this.submitIcon}" @click="${this.handleSubmit}">
|
|
93
|
+
${this.submitText}
|
|
94
|
+
</zn-button>`;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import '../../../dist/zn.min.js';
|
|
2
|
+
import { expect, fixture, html, oneEvent } from '@open-wc/testing';
|
|
3
|
+
|
|
4
|
+
// ZnButton overrides click() without dispatching a DOM event, so fire a real one.
|
|
5
|
+
const clickButton = (button: HTMLElement) =>
|
|
6
|
+
button.dispatchEvent(new MouseEvent('click', { bubbles: true, composed: true, cancelable: true }));
|
|
7
|
+
|
|
8
|
+
describe('<zn-form-actions>', () => {
|
|
9
|
+
it('should render a component', async () => {
|
|
10
|
+
const el = await fixture(html` <zn-form-actions></zn-form-actions> `);
|
|
11
|
+
|
|
12
|
+
expect(el).to.exist;
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('should emit zn-cancel when the cancel button is clicked', async () => {
|
|
16
|
+
const el = await fixture<HTMLElement>(html` <zn-form-actions with-cancel></zn-form-actions> `);
|
|
17
|
+
const cancel = el.shadowRoot!.querySelector<HTMLElement>('[part="cancel-button"]')!;
|
|
18
|
+
|
|
19
|
+
const listener = oneEvent(el, 'zn-cancel');
|
|
20
|
+
clickButton(cancel);
|
|
21
|
+
|
|
22
|
+
expect(await listener).to.exist;
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should close a containing dialog when the cancel button is clicked', async () => {
|
|
26
|
+
const dialog = await fixture<HTMLElement & { open: boolean }>(html`
|
|
27
|
+
<zn-dialog open label="Test">
|
|
28
|
+
<form>
|
|
29
|
+
<zn-form-actions with-cancel></zn-form-actions>
|
|
30
|
+
</form>
|
|
31
|
+
</zn-dialog>
|
|
32
|
+
`);
|
|
33
|
+
const actions = dialog.querySelector('zn-form-actions')!;
|
|
34
|
+
const cancel = actions.shadowRoot!.querySelector<HTMLElement>('[part="cancel-button"]')!;
|
|
35
|
+
|
|
36
|
+
clickButton(cancel);
|
|
37
|
+
|
|
38
|
+
expect(dialog.open).to.be.false;
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import ZnFormActions from './form-actions.component';
|
|
2
|
+
|
|
3
|
+
export * from './form-actions.component';
|
|
4
|
+
export default ZnFormActions;
|
|
5
|
+
|
|
6
|
+
ZnFormActions.define('zn-form-actions');
|
|
7
|
+
|
|
8
|
+
declare global {
|
|
9
|
+
interface HTMLElementTagNameMap {
|
|
10
|
+
'zn-form-actions': ZnFormActions;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {classMap} from 'lit/directives/class-map.js';
|
|
2
|
-
import {type CSSResultGroup, html, nothing, unsafeCSS} from 'lit';
|
|
2
|
+
import {type CSSResultGroup, html, nothing, type PropertyValues, unsafeCSS} from 'lit';
|
|
3
3
|
import {defaultValue} from '../../internal/default-value';
|
|
4
4
|
import {FormControlController} from '../../internal/form';
|
|
5
5
|
import {property, query, state} from 'lit/decorators.js';
|
|
@@ -244,6 +244,36 @@ export default class ZnIconPicker extends ZincElement implements ZincFormControl
|
|
|
244
244
|
}
|
|
245
245
|
}
|
|
246
246
|
|
|
247
|
+
// The [icon]/[library]/[color] hidden inputs must live in the light DOM —
|
|
248
|
+
// shadow DOM inputs are invisible to form submission.
|
|
249
|
+
private syncHiddenInputs() {
|
|
250
|
+
const fields: [string, string, boolean][] = [
|
|
251
|
+
['icon', this.icon, true],
|
|
252
|
+
['library', this.library, !this.noLibrary],
|
|
253
|
+
['color', this.color, !this.noColor],
|
|
254
|
+
];
|
|
255
|
+
for (const [key, value, enabled] of fields) {
|
|
256
|
+
const name = `${this.name}[${key}]`;
|
|
257
|
+
let input = this.querySelector<HTMLInputElement>(`input[type="hidden"][name="${name}"]`);
|
|
258
|
+
if (!this.name || !enabled) {
|
|
259
|
+
input?.remove();
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
if (!input) {
|
|
263
|
+
input = document.createElement('input');
|
|
264
|
+
input.type = 'hidden';
|
|
265
|
+
input.name = name;
|
|
266
|
+
this.appendChild(input);
|
|
267
|
+
}
|
|
268
|
+
input.value = value ?? '';
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
protected updated(changedProperties: PropertyValues) {
|
|
273
|
+
super.updated(changedProperties);
|
|
274
|
+
this.syncHiddenInputs();
|
|
275
|
+
}
|
|
276
|
+
|
|
247
277
|
private handleSearchInput(e: Event) {
|
|
248
278
|
const input = e.target as HTMLInputElement;
|
|
249
279
|
this._searchQuery = input.value.toLowerCase();
|
|
@@ -381,12 +411,6 @@ export default class ZnIconPicker extends ZincElement implements ZincFormControl
|
|
|
381
411
|
`}
|
|
382
412
|
</div>
|
|
383
413
|
|
|
384
|
-
${this.name ? html`
|
|
385
|
-
<input type="hidden" name="${this.name}[icon]" .value=${this.icon}>
|
|
386
|
-
${!this.noLibrary ? html`<input type="hidden" name="${this.name}[library]" .value=${this.library}>` : nothing}
|
|
387
|
-
${!this.noColor ? html`<input type="hidden" name="${this.name}[color]" .value=${this.color}>` : nothing}
|
|
388
|
-
` : nothing}
|
|
389
|
-
|
|
390
414
|
<div
|
|
391
415
|
part="form-control-help-text"
|
|
392
416
|
class="form-control__help-text"
|
|
@@ -157,6 +157,20 @@ export default class ZnSlideout extends ZincElement {
|
|
|
157
157
|
private closeClickHandler = (event: MouseEvent) => {
|
|
158
158
|
if (event.target instanceof HTMLElement && event.target.hasAttribute('slideout-closer')) {
|
|
159
159
|
this.hide();
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// `modal-closer` works from nested shadow roots (e.g. zn-form-actions' cancel button),
|
|
164
|
+
// where retargeting hides the source from event.target. Walk outward from the click:
|
|
165
|
+
// hitting another modal first means the closer belongs to it, not this slideout.
|
|
166
|
+
for (const el of event.composedPath()) {
|
|
167
|
+
if (el === this) return;
|
|
168
|
+
if (!(el instanceof HTMLElement)) continue;
|
|
169
|
+
if (el.hasAttribute('modal-closer')) {
|
|
170
|
+
void this.hide();
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (el.localName === 'zn-dialog' || el.localName === 'zn-slideout') return;
|
|
160
174
|
}
|
|
161
175
|
}
|
|
162
176
|
|
package/src/zinc.ts
CHANGED
|
@@ -63,6 +63,7 @@ export { default as Option } from './components/option';
|
|
|
63
63
|
export { default as Textarea } from './components/textarea';
|
|
64
64
|
export { default as Checkbox } from './components/checkbox';
|
|
65
65
|
export { default as Datepicker } from './components/datepicker';
|
|
66
|
+
export { default as FormActions } from './components/form-actions';
|
|
66
67
|
export { default as FormGroup } from './components/form-group';
|
|
67
68
|
export { default as InputGroup } from './components/input-group';
|
|
68
69
|
export { default as LinkedSelect } from './components/linked-select';
|