@kubex/zinc 1.1.183 → 1.1.185
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 +113 -5
- package/dist/vscode.html-custom-data.json +24 -2
- package/dist/web-types.json +43 -2
- package/dist/zn.d.ts +16 -4
- package/dist/zn.min.js +167 -162
- package/docs/pages/components/filter-wrapper.md +20 -0
- package/package.json +1 -1
- package/src/components/button/button.component.ts +14 -3
- package/src/components/button/button.scss +25 -0
- package/src/components/cols/cols.component.ts +7 -3
- package/src/components/cols/cols.scss +24 -13
- package/src/components/cols/cols.test.ts +16 -0
- package/src/components/data-table/data-table.component.ts +2 -2
- package/src/components/filter-wrapper/filter-wrapper.component.ts +45 -10
- package/src/components/filter-wrapper/filter-wrapper.test.ts +58 -2
- package/src/components/tile/tile.component.ts +5 -4
- package/src/components/tile/tile.test.ts +11 -0
|
@@ -98,6 +98,26 @@ Provide your own submit button by adding the `submit` attribute to a button.
|
|
|
98
98
|
</zn-filter-wrapper>
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
+
### Submit on Enter
|
|
102
|
+
|
|
103
|
+
Add the `submit-on-enter` attribute to submit the filters when Enter is pressed in a text field.
|
|
104
|
+
|
|
105
|
+
```html:preview
|
|
106
|
+
<zn-filter-wrapper submit-on-enter>
|
|
107
|
+
<zn-input name="email" label="Email" placeholder="Press Enter to search"></zn-input>
|
|
108
|
+
</zn-filter-wrapper>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Loading Text
|
|
112
|
+
|
|
113
|
+
While results load, the submit button shows a spinner and "Searching...". Change the text with `loading-text`.
|
|
114
|
+
|
|
115
|
+
```html:preview
|
|
116
|
+
<zn-filter-wrapper loading-text="Filtering...">
|
|
117
|
+
<zn-input name="search" label="Search"></zn-input>
|
|
118
|
+
</zn-filter-wrapper>
|
|
119
|
+
```
|
|
120
|
+
|
|
101
121
|
### Omit Empty Values
|
|
102
122
|
|
|
103
123
|
Use the `omit-empty` attribute on inputs to exclude them from the filter when empty.
|
package/package.json
CHANGED
|
@@ -141,6 +141,12 @@ export default class ZnButton extends ZincElement implements ZincFormControl {
|
|
|
141
141
|
}) loadingTextPosition: 'left' | 'right' | 'center' | string = 'center';
|
|
142
142
|
@property({type: Boolean}) loading = false;
|
|
143
143
|
|
|
144
|
+
/** Shows a spinner and ignores clicks, e.g. while a request is in flight. */
|
|
145
|
+
@property({type: Boolean, reflect: true}) processing = false;
|
|
146
|
+
|
|
147
|
+
/** Replaces the label while processing. */
|
|
148
|
+
@property({attribute: 'processing-text'}) processingText = '';
|
|
149
|
+
|
|
144
150
|
get validity() {
|
|
145
151
|
if (this._isButton() && this.button) {
|
|
146
152
|
return (this.button as HTMLButtonElement).validity;
|
|
@@ -242,7 +248,7 @@ export default class ZnButton extends ZincElement implements ZincFormControl {
|
|
|
242
248
|
}
|
|
243
249
|
|
|
244
250
|
handleClick = () => {
|
|
245
|
-
if (this.disabled) {
|
|
251
|
+
if (this.disabled || this.processing) {
|
|
246
252
|
return;
|
|
247
253
|
}
|
|
248
254
|
|
|
@@ -378,7 +384,7 @@ export default class ZnButton extends ZincElement implements ZincFormControl {
|
|
|
378
384
|
const isLink = this._isLink();
|
|
379
385
|
const showCancel = this.loading && this.autoClick;
|
|
380
386
|
const resolvedIconSize = this.iconSize ? this.iconSize : 20;
|
|
381
|
-
const icon = this.icon && !this.loading ? html`
|
|
387
|
+
const icon = this.icon && !this.loading && !this.processing ? html`
|
|
382
388
|
<zn-icon part="icon" src="${this.icon}" id="xy2" size="${resolvedIconSize}"
|
|
383
389
|
color="${ifDefined(this.iconColor)}" fill="${ifDefined(this.iconFill)}"
|
|
384
390
|
library="${ifDefined(this.iconLibrary)}"></zn-icon>`
|
|
@@ -431,6 +437,7 @@ export default class ZnButton extends ZincElement implements ZincFormControl {
|
|
|
431
437
|
'button--has-notification': notification !== undefined && notification !== 0,
|
|
432
438
|
'button--muted-notification': this.mutedNotifications || notification === -2,
|
|
433
439
|
'button--notification-dot': notification !== undefined && notification < 0,
|
|
440
|
+
'button--processing': this.processing,
|
|
434
441
|
})}"
|
|
435
442
|
type="${ifDefined(this.type)}"
|
|
436
443
|
href="${ifDefined(this.href)}"
|
|
@@ -440,15 +447,19 @@ export default class ZnButton extends ZincElement implements ZincFormControl {
|
|
|
440
447
|
gaid="${ifDefined(this.gaid)}"
|
|
441
448
|
aria-label="${ifDefined(ariaLabel)}"
|
|
442
449
|
aria-pressed="${ifDefined(isLink ? undefined : this.pressed)}"
|
|
450
|
+
aria-busy="${this.processing ? 'true' : nothing}"
|
|
443
451
|
data-notification="${ifDefined(notification)}"
|
|
444
452
|
disabled="${this.disabled || nothing}"
|
|
445
453
|
style=${styleMap(buttonStyles)}
|
|
446
454
|
@click="${this.handleClick}">
|
|
447
455
|
${this.iconPosition === 'left' ? icon : ''}
|
|
456
|
+
${this.processing ? html`<span part="spinner" class="button__spinner"></span>` : ''}
|
|
457
|
+
${this.processing && this.processingText ? html`<span class="button__processing-text">${this.processingText}</span>` : ''}
|
|
448
458
|
<slot part="label"
|
|
449
459
|
class="${classMap({
|
|
450
460
|
'button__label': true,
|
|
451
|
-
'button__label--hidden': this.loading
|
|
461
|
+
'button__label--hidden': this.loading,
|
|
462
|
+
'button__label--replaced': this.processing && !!this.processingText
|
|
452
463
|
})}"
|
|
453
464
|
style="--loading-duration: ${this.autoClickDelay / 1000}s">
|
|
454
465
|
${this.content}
|
|
@@ -424,6 +424,31 @@
|
|
|
424
424
|
}
|
|
425
425
|
}
|
|
426
426
|
|
|
427
|
+
.button--processing {
|
|
428
|
+
cursor: progress;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
.button__spinner {
|
|
432
|
+
width: 1em;
|
|
433
|
+
height: 1em;
|
|
434
|
+
margin-inline-end: 8px;
|
|
435
|
+
flex-shrink: 0;
|
|
436
|
+
border: 2px solid currentColor;
|
|
437
|
+
border-right-color: transparent;
|
|
438
|
+
border-radius: 50%;
|
|
439
|
+
animation: button-spin .7s linear infinite;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
.button__label--replaced {
|
|
443
|
+
display: none;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
@keyframes button-spin {
|
|
447
|
+
to {
|
|
448
|
+
transform: rotate(360deg);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
427
452
|
.button--relative-wrapper {
|
|
428
453
|
position: relative;
|
|
429
454
|
display: inline-flex;
|
|
@@ -85,13 +85,16 @@ export default class ZnCols extends ZincElement {
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
/**
|
|
88
|
-
* Flags each child that ends its row as laid out, so `divide` only draws
|
|
89
|
-
* however the columns have wrapped.
|
|
88
|
+
* Flags each child that ends its row, and each child on the last row, as laid out, so `divide` only draws
|
|
89
|
+
* between neighbours however the columns have wrapped.
|
|
90
90
|
*/
|
|
91
91
|
private markRowEnds() {
|
|
92
92
|
const children = Array.from(this.querySelectorAll(':scope > *:not([slot])'));
|
|
93
93
|
if (!this.divide) {
|
|
94
|
-
children.forEach((child) =>
|
|
94
|
+
children.forEach((child) => {
|
|
95
|
+
child.removeAttribute('data-row-end');
|
|
96
|
+
child.removeAttribute('data-last-row');
|
|
97
|
+
});
|
|
95
98
|
return;
|
|
96
99
|
}
|
|
97
100
|
|
|
@@ -101,6 +104,7 @@ export default class ZnCols extends ZincElement {
|
|
|
101
104
|
const rowEnd = !rects.some((other) =>
|
|
102
105
|
other !== rect && other.top < rect.bottom && other.bottom > rect.top && other.left > rect.left);
|
|
103
106
|
child.toggleAttribute('data-row-end', rowEnd);
|
|
107
|
+
child.toggleAttribute('data-last-row', !rects.some((other) => other.top >= rect.bottom));
|
|
104
108
|
});
|
|
105
109
|
}
|
|
106
110
|
|
|
@@ -67,6 +67,22 @@
|
|
|
67
67
|
::slotted([data-row-end]):after {
|
|
68
68
|
display: none;
|
|
69
69
|
}
|
|
70
|
+
|
|
71
|
+
::slotted(:not([data-last-row])) {
|
|
72
|
+
position: relative;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Border mode owns :before for its own row rules.
|
|
77
|
+
&--divide:not(.cols--border) ::slotted(:not([data-last-row])):before {
|
|
78
|
+
content: "";
|
|
79
|
+
position: absolute;
|
|
80
|
+
display: block;
|
|
81
|
+
height: 1px;
|
|
82
|
+
left: 0;
|
|
83
|
+
right: 0;
|
|
84
|
+
bottom: calc((var(--zn-spacing-small) / 2) * -1);
|
|
85
|
+
background-color: rgb(var(--zn-border-color));
|
|
70
86
|
}
|
|
71
87
|
|
|
72
88
|
&--no-gap {
|
|
@@ -74,8 +90,14 @@
|
|
|
74
90
|
}
|
|
75
91
|
|
|
76
92
|
// Without a gap to sit in, the divider goes on the column's own edge.
|
|
77
|
-
&--no-gap.cols--divide
|
|
78
|
-
|
|
93
|
+
&--no-gap.cols--divide {
|
|
94
|
+
::slotted(*:not(:last-child)):after {
|
|
95
|
+
right: 0;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
::slotted(:not([data-last-row])):before {
|
|
99
|
+
bottom: 0;
|
|
100
|
+
}
|
|
79
101
|
}
|
|
80
102
|
|
|
81
103
|
// The row rule below each child is drawn by a pseudo reaching to -100vw, so border mode has to clip. Clipping
|
|
@@ -155,17 +177,6 @@ $stack-sizes: (sm, smp, ph, md, lg, hd, '3k', '4k');
|
|
|
155
177
|
.cols--divide ::slotted(*:not(:last-child)):after {
|
|
156
178
|
display: none;
|
|
157
179
|
}
|
|
158
|
-
|
|
159
|
-
// Edge-to-edge columns keep their separation as a rule between the stacked rows
|
|
160
|
-
.cols--no-gap.cols--divide ::slotted(*:not(:last-child)):after {
|
|
161
|
-
display: block;
|
|
162
|
-
top: auto;
|
|
163
|
-
right: 0;
|
|
164
|
-
bottom: 0;
|
|
165
|
-
left: 0;
|
|
166
|
-
width: auto;
|
|
167
|
-
height: 1px;
|
|
168
|
-
}
|
|
169
180
|
}
|
|
170
181
|
}
|
|
171
182
|
}
|
|
@@ -165,5 +165,21 @@ describe('<zn-cols>', () => {
|
|
|
165
165
|
|
|
166
166
|
expect(dividerOf(el.querySelector('#a'))).to.equal('none');
|
|
167
167
|
});
|
|
168
|
+
|
|
169
|
+
it('should rule between wrapped rows', async () => {
|
|
170
|
+
const el = await cols(`
|
|
171
|
+
<zn-cols layout="1,1" divide style="width: 600px">
|
|
172
|
+
<div id="a" style="height: 20px"></div>
|
|
173
|
+
<div id="b" style="height: 20px"></div>
|
|
174
|
+
<div id="c" style="height: 20px"></div>
|
|
175
|
+
<div id="d" style="height: 20px"></div>
|
|
176
|
+
</zn-cols>`);
|
|
177
|
+
const ruleOf = (id: string) => getComputedStyle(el.querySelector(id)!, '::before').content;
|
|
178
|
+
|
|
179
|
+
expect(ruleOf('#a')).to.not.equal('none');
|
|
180
|
+
expect(ruleOf('#b')).to.not.equal('none');
|
|
181
|
+
expect(ruleOf('#c')).to.equal('none');
|
|
182
|
+
expect(ruleOf('#d')).to.equal('none');
|
|
183
|
+
});
|
|
168
184
|
});
|
|
169
185
|
});
|
|
@@ -431,10 +431,10 @@ export default class ZnDataTable extends ZincElement {
|
|
|
431
431
|
|
|
432
432
|
requestParams: Record<string, any> = {};
|
|
433
433
|
|
|
434
|
-
refresh() {
|
|
434
|
+
refresh(): Promise<void> {
|
|
435
435
|
// Allow manual refresh to trigger the first data load when no-initial-load is set
|
|
436
436
|
this._initialLoad = false;
|
|
437
|
-
this._dataTask.run()
|
|
437
|
+
return this._dataTask.run();
|
|
438
438
|
}
|
|
439
439
|
|
|
440
440
|
// Headers that make up the visible columns, in header order
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import {type CSSResultGroup, html, unsafeCSS} from 'lit';
|
|
2
|
-
import {property} from "lit/decorators.js";
|
|
2
|
+
import {property, state} from "lit/decorators.js";
|
|
3
3
|
import {trimControlValue} from '../../internal/form';
|
|
4
4
|
import ZincElement from '../../internal/zinc-element';
|
|
5
5
|
|
|
6
6
|
import type ZnDataSelect from "../data-select";
|
|
7
|
+
import type ZnButton from "../button";
|
|
7
8
|
import type ZnDataTable from "../data-table";
|
|
8
9
|
import type ZnInput from "../input";
|
|
9
10
|
import type ZnQueryBuilder from "../query-builder";
|
|
@@ -42,12 +43,20 @@ export default class ZnFilterWrapper extends ZincElement {
|
|
|
42
43
|
|
|
43
44
|
@property() button: string = 'Filter';
|
|
44
45
|
|
|
45
|
-
|
|
46
|
+
/** Submits the filters when Enter is pressed in a text field. */
|
|
47
|
+
@property({attribute: 'submit-on-enter', type: Boolean}) submitOnEnter = false;
|
|
48
|
+
|
|
49
|
+
/** Shown on the submit button while results load. */
|
|
50
|
+
@property({attribute: 'loading-text'}) loadingText = 'Searching...';
|
|
51
|
+
|
|
52
|
+
@state() private loading = false;
|
|
53
|
+
|
|
54
|
+
private submitButton: ZnButton | null = null;
|
|
46
55
|
|
|
47
56
|
handleSubmit = (event: Event) => {
|
|
48
57
|
event.preventDefault();
|
|
49
58
|
const dataTable: ZnDataTable | null = this.closest('zn-data-table');
|
|
50
|
-
if (dataTable) {
|
|
59
|
+
if (dataTable && !this.loading) {
|
|
51
60
|
const allowedInputs = ['zn-input', 'zn-select', 'zn-query-builder', 'zn-data-select', 'zn-datepicker', 'input', 'select', 'textarea'];
|
|
52
61
|
const inputs = Array.from(this.querySelectorAll(allowedInputs.join(','))) as AllowedInputElement[];
|
|
53
62
|
const params: Record<string, any> = {};
|
|
@@ -60,17 +69,41 @@ export default class ZnFilterWrapper extends ZincElement {
|
|
|
60
69
|
});
|
|
61
70
|
|
|
62
71
|
dataTable.requestParams = params;
|
|
63
|
-
|
|
72
|
+
this.setLoading(true);
|
|
73
|
+
Promise.resolve(dataTable.refresh()).finally(() => this.setLoading(false));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private setLoading(loading: boolean) {
|
|
78
|
+
this.loading = loading;
|
|
79
|
+
if (this.submitButton) {
|
|
80
|
+
this.submitButton.processingText = this.loadingText;
|
|
81
|
+
this.submitButton.processing = loading;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
handleKeyDown = (event: KeyboardEvent) => {
|
|
86
|
+
if (!this.submitOnEnter || event.key !== 'Enter' || event.defaultPrevented || event.isComposing
|
|
87
|
+
|| event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Only plain text fields: selects, buttons and textareas use Enter themselves
|
|
92
|
+
if (event.composedPath()[0] instanceof HTMLInputElement) {
|
|
93
|
+
this.handleSubmit(event);
|
|
64
94
|
}
|
|
65
95
|
}
|
|
66
96
|
|
|
67
97
|
connectedCallback() {
|
|
68
98
|
super.connectedCallback();
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
99
|
+
this.addEventListener('keydown', this.handleKeyDown);
|
|
100
|
+
this.submitButton = this.querySelector('zn-button[submit]');
|
|
101
|
+
this.submitButton?.addEventListener('click', this.handleSubmit);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
disconnectedCallback() {
|
|
105
|
+
super.disconnectedCallback();
|
|
106
|
+
this.removeEventListener('keydown', this.handleKeyDown);
|
|
74
107
|
}
|
|
75
108
|
|
|
76
109
|
private renderDefaultButton() {
|
|
@@ -78,6 +111,8 @@ export default class ZnFilterWrapper extends ZincElement {
|
|
|
78
111
|
<zn-button part="search-button"
|
|
79
112
|
class="filter-wrapper__submit"
|
|
80
113
|
type="submit"
|
|
114
|
+
?processing="${this.loading}"
|
|
115
|
+
processing-text="${this.loadingText}"
|
|
81
116
|
@click="${this.handleSubmit}">
|
|
82
117
|
${this.button}
|
|
83
118
|
</zn-button>
|
|
@@ -87,7 +122,7 @@ export default class ZnFilterWrapper extends ZincElement {
|
|
|
87
122
|
render() {
|
|
88
123
|
return html`
|
|
89
124
|
<slot></slot>
|
|
90
|
-
${this.
|
|
125
|
+
${this.submitButton ? '' : this.renderDefaultButton()}
|
|
91
126
|
`;
|
|
92
127
|
}
|
|
93
128
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import '../../../dist/zn.min.js';
|
|
2
2
|
import { expect, fixture, html } from '@open-wc/testing';
|
|
3
3
|
import { sendKeys } from '@web/test-runner-commands';
|
|
4
|
+
import type ZnButton from '../button';
|
|
4
5
|
import type ZnDataTable from '../data-table';
|
|
5
6
|
import type ZnFilterWrapper from './filter-wrapper.component';
|
|
6
7
|
import type ZnInput from '../input';
|
|
@@ -20,7 +21,7 @@ describe('<zn-filter-wrapper>', () => {
|
|
|
20
21
|
</zn-filter-wrapper>
|
|
21
22
|
</zn-data-table>
|
|
22
23
|
`);
|
|
23
|
-
table.refresh = () => undefined;
|
|
24
|
+
table.refresh = async () => undefined;
|
|
24
25
|
const wrapper = table.querySelector<ZnFilterWrapper>('zn-filter-wrapper')!;
|
|
25
26
|
const input = table.querySelector<ZnInput>('zn-input')!;
|
|
26
27
|
await input.updateComplete;
|
|
@@ -40,7 +41,7 @@ describe('<zn-filter-wrapper>', () => {
|
|
|
40
41
|
</zn-filter-wrapper>
|
|
41
42
|
</zn-data-table>
|
|
42
43
|
`);
|
|
43
|
-
table.refresh = () => undefined;
|
|
44
|
+
table.refresh = async () => undefined;
|
|
44
45
|
const [input, clean] = table.querySelectorAll<ZnInput>('zn-input');
|
|
45
46
|
await input.updateComplete;
|
|
46
47
|
|
|
@@ -53,4 +54,59 @@ describe('<zn-filter-wrapper>', () => {
|
|
|
53
54
|
expect(table.requestParams.email).to.equal('a@b.com');
|
|
54
55
|
expect(input.input.scrollWidth).to.equal(clean.input.scrollWidth);
|
|
55
56
|
});
|
|
57
|
+
|
|
58
|
+
it('submits on Enter only when submit-on-enter is set', async () => {
|
|
59
|
+
const table = await fixture<ZnDataTable>(html`
|
|
60
|
+
<zn-data-table>
|
|
61
|
+
<zn-filter-wrapper slot="filter-top">
|
|
62
|
+
<zn-input name="email"></zn-input>
|
|
63
|
+
</zn-filter-wrapper>
|
|
64
|
+
</zn-data-table>
|
|
65
|
+
`);
|
|
66
|
+
let refreshes = 0;
|
|
67
|
+
table.refresh = async () => { refreshes++; };
|
|
68
|
+
const wrapper = table.querySelector<ZnFilterWrapper>('zn-filter-wrapper')!;
|
|
69
|
+
const input = table.querySelector<ZnInput>('zn-input')!;
|
|
70
|
+
await input.updateComplete;
|
|
71
|
+
|
|
72
|
+
input.focus();
|
|
73
|
+
await sendKeys({type: 'a@b.com'});
|
|
74
|
+
await sendKeys({press: 'Enter'});
|
|
75
|
+
expect(refreshes).to.equal(0);
|
|
76
|
+
|
|
77
|
+
wrapper.submitOnEnter = true;
|
|
78
|
+
await sendKeys({press: 'Enter'});
|
|
79
|
+
expect(refreshes).to.equal(1);
|
|
80
|
+
expect(table.requestParams).to.deep.equal({email: 'a@b.com'});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('shows the submit button as processing until results load', async () => {
|
|
84
|
+
const table = await fixture<ZnDataTable>(html`
|
|
85
|
+
<zn-data-table>
|
|
86
|
+
<zn-filter-wrapper slot="filter-top">
|
|
87
|
+
<zn-input name="email"></zn-input>
|
|
88
|
+
<zn-button submit>Submit</zn-button>
|
|
89
|
+
</zn-filter-wrapper>
|
|
90
|
+
</zn-data-table>
|
|
91
|
+
`);
|
|
92
|
+
let finish!: () => void;
|
|
93
|
+
let refreshes = 0;
|
|
94
|
+
table.refresh = () => {
|
|
95
|
+
refreshes++;
|
|
96
|
+
return new Promise<void>(resolve => { finish = resolve; });
|
|
97
|
+
};
|
|
98
|
+
const button = table.querySelector<ZnButton>('zn-button')!;
|
|
99
|
+
await button.updateComplete;
|
|
100
|
+
const base = button.shadowRoot!.querySelector<HTMLButtonElement>('[part="base"]')!;
|
|
101
|
+
|
|
102
|
+
base.click();
|
|
103
|
+
base.click();
|
|
104
|
+
expect(refreshes).to.equal(1);
|
|
105
|
+
expect(button.processing).to.be.true;
|
|
106
|
+
expect(button.processingText).to.equal('Searching...');
|
|
107
|
+
|
|
108
|
+
finish();
|
|
109
|
+
await new Promise(resolve => setTimeout(resolve));
|
|
110
|
+
expect(button.processing).to.be.false;
|
|
111
|
+
});
|
|
56
112
|
});
|
|
@@ -28,7 +28,7 @@ import styles from './tile.scss';
|
|
|
28
28
|
export default class ZnTile extends ZincElement {
|
|
29
29
|
static styles: CSSResultGroup = unsafeCSS(styles);
|
|
30
30
|
|
|
31
|
-
private readonly hasSlotController = new HasSlotController(this, '[default]', 'properties', 'actions', 'image');
|
|
31
|
+
private readonly hasSlotController = new HasSlotController(this, '[default]', 'caption', 'description', 'properties', 'actions', 'image');
|
|
32
32
|
|
|
33
33
|
@property({attribute: 'caption'}) caption: string;
|
|
34
34
|
|
|
@@ -84,8 +84,9 @@ export default class ZnTile extends ZincElement {
|
|
|
84
84
|
render() {
|
|
85
85
|
const isLink = this._isLink();
|
|
86
86
|
const tag = isLink ? literal`a` : literal`div`;
|
|
87
|
-
const hasCaption = this.caption && this.caption.length > 0;
|
|
88
|
-
const
|
|
87
|
+
const hasCaption = (this.caption && this.caption.length > 0) || this.hasSlotController.test('caption');
|
|
88
|
+
const hasDescriptionAttr = this.description && this.description.length > 0;
|
|
89
|
+
const hasDescription = hasDescriptionAttr || this.hasSlotController.test('description');
|
|
89
90
|
const hasProperties = this.hasSlotController.test('properties');
|
|
90
91
|
const hasActions = this.hasSlotController.test('actions');
|
|
91
92
|
const hasImage = this.hasSlotController.test('image');
|
|
@@ -122,7 +123,7 @@ export default class ZnTile extends ZincElement {
|
|
|
122
123
|
<p part="caption" class="tile__caption">
|
|
123
124
|
<slot name="caption">${this.caption}</slot>
|
|
124
125
|
</p>
|
|
125
|
-
${
|
|
126
|
+
${hasDescriptionAttr ? html`
|
|
126
127
|
<p part="description" class="tile__description">
|
|
127
128
|
${this.description}</p>` : html`<slot name="description" class="tile__description"></slot>`}
|
|
128
129
|
</div>
|
|
@@ -9,6 +9,17 @@ describe('<zn-tile>', () => {
|
|
|
9
9
|
expect(el).to.exist;
|
|
10
10
|
});
|
|
11
11
|
|
|
12
|
+
it('should render a slotted caption and image without caption or description attributes', async () => {
|
|
13
|
+
const el = await fixture<ZnTile>(html`
|
|
14
|
+
<zn-tile>
|
|
15
|
+
<zn-icon slot="image" src="user@lu"></zn-icon>
|
|
16
|
+
<span slot="caption">Leslie Alexander</span>
|
|
17
|
+
</zn-tile>`);
|
|
18
|
+
|
|
19
|
+
expect(el.shadowRoot!.querySelector('slot[name="caption"]')).to.exist;
|
|
20
|
+
expect(el.shadowRoot!.querySelector('slot[name="image"]')).to.exist;
|
|
21
|
+
});
|
|
22
|
+
|
|
12
23
|
it('should render its content as a link when href is set', async () => {
|
|
13
24
|
const el = await fixture<ZnTile>(html`
|
|
14
25
|
<zn-tile href="/users/leslie" caption="Leslie Alexander"></zn-tile>`);
|