@kubex/zinc 1.1.97 → 1.1.98
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/chunks/zn.R2EAU6OS.js +1 -0
- package/dist/custom-elements.json +1422 -61
- package/dist/vscode.html-custom-data.json +188 -2
- package/dist/web-types.json +462 -5
- package/dist/zn.d.ts +326 -2
- package/dist/zn.min.js +582 -457
- package/docs/pages/components/button.md +11 -1
- package/docs/pages/components/chart.md +165 -3
- package/docs/pages/components/icon-picker.md +8 -0
- package/docs/pages/components/thumbnail-group.md +216 -0
- package/docs/pages/components/thumbnail.md +349 -0
- package/docs/pages/components/well.md +34 -0
- package/package.json +1 -1
- package/src/components/button/button.component.ts +8 -0
- package/src/components/chart/builders.test.ts +190 -1
- package/src/components/chart/builders.ts +246 -4
- package/src/components/chart/chart.component.ts +33 -1
- package/src/components/chart/chart.test.ts +66 -1
- package/src/components/chart/echarts-loader.ts +10 -0
- package/src/components/collapsible/collapsible.component.ts +49 -4
- package/src/components/collapsible/collapsible.scss +33 -14
- package/src/components/header/header.scss +1 -1
- package/src/components/icon-picker/icon-picker.component.ts +3 -0
- package/src/components/icon-picker/lucide-icons.ts +1756 -0
- package/src/components/page/page.scss +1 -1
- package/src/components/remarkd-editor/remarkd-editor.test.ts +39 -0
- package/src/components/split-pane/split-pane.scss +21 -15
- package/src/components/thumbnail/index.ts +12 -0
- package/src/components/thumbnail/thumbnail.component.ts +460 -0
- package/src/components/thumbnail/thumbnail.scss +360 -0
- package/src/components/thumbnail/thumbnail.test.ts +379 -0
- package/src/components/thumbnail-group/index.ts +12 -0
- package/src/components/thumbnail-group/thumbnail-group.component.ts +268 -0
- package/src/components/thumbnail-group/thumbnail-group.scss +87 -0
- package/src/components/thumbnail-group/thumbnail-group.test.ts +151 -0
- package/src/components/well/well.component.ts +22 -4
- package/src/components/well/well.scss +24 -0
- package/src/components/well/well.test.ts +43 -0
- package/src/zinc.ts +2 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
@use "../../wc";
|
|
2
|
+
|
|
3
|
+
:host {
|
|
4
|
+
@include wc.scrollbars;
|
|
5
|
+
|
|
6
|
+
display: block;
|
|
7
|
+
width: 100%;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.thumbnail-group__header {
|
|
11
|
+
display: flex;
|
|
12
|
+
align-items: center;
|
|
13
|
+
justify-content: space-between;
|
|
14
|
+
gap: var(--zn-spacing-small);
|
|
15
|
+
margin-bottom: var(--zn-spacing-x-small);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.thumbnail-group__caption {
|
|
19
|
+
@include wc.text-style(h1);
|
|
20
|
+
|
|
21
|
+
min-width: 0;
|
|
22
|
+
overflow: hidden;
|
|
23
|
+
text-overflow: ellipsis;
|
|
24
|
+
white-space: nowrap;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.thumbnail-group__header-actions {
|
|
28
|
+
display: flex;
|
|
29
|
+
align-items: center;
|
|
30
|
+
flex: 0 0 auto;
|
|
31
|
+
gap: var(--zn-spacing-x-small);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.thumbnail-group__toggle {
|
|
35
|
+
@include wc.text-style(subheading);
|
|
36
|
+
|
|
37
|
+
padding: 0;
|
|
38
|
+
background: none;
|
|
39
|
+
border: none;
|
|
40
|
+
cursor: pointer;
|
|
41
|
+
white-space: nowrap;
|
|
42
|
+
|
|
43
|
+
&:hover {
|
|
44
|
+
color: rgb(var(--zn-text));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
&:focus-visible {
|
|
48
|
+
outline: var(--zn-focus-ring);
|
|
49
|
+
outline-offset: var(--zn-focus-ring-offset);
|
|
50
|
+
border-radius: var(--zn-border-radius-small);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* Collapsed — one row, scrolled horizontally */
|
|
55
|
+
|
|
56
|
+
.thumbnail-group__items {
|
|
57
|
+
display: grid;
|
|
58
|
+
gap: var(--zn-thumbnail-gap, var(--zn-spacing-small));
|
|
59
|
+
grid-auto-flow: column;
|
|
60
|
+
grid-auto-columns: var(--zn-thumbnail-width, 180px);
|
|
61
|
+
overflow-x: auto;
|
|
62
|
+
overflow-y: hidden;
|
|
63
|
+
// Keeps the scrollbar off the captions.
|
|
64
|
+
padding-bottom: var(--zn-spacing-2x-small);
|
|
65
|
+
scroll-snap-type: x proximity;
|
|
66
|
+
overscroll-behavior-x: contain;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* Expanded — wrapping grid, no row restriction */
|
|
70
|
+
|
|
71
|
+
.thumbnail-group--expanded .thumbnail-group__items {
|
|
72
|
+
grid-auto-flow: row;
|
|
73
|
+
grid-auto-columns: auto;
|
|
74
|
+
grid-template-columns: repeat(auto-fill, minmax(var(--zn-thumbnail-width, 180px), 1fr));
|
|
75
|
+
overflow: visible;
|
|
76
|
+
padding-bottom: 0;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// The slot has to disappear from the grid so the slotted thumbnails become the grid items.
|
|
80
|
+
slot {
|
|
81
|
+
display: contents;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
::slotted(*) {
|
|
85
|
+
scroll-snap-align: start;
|
|
86
|
+
min-width: 0;
|
|
87
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import '../../../dist/zn.min.js';
|
|
2
|
+
import {expect, fixture, html, waitUntil} from '@open-wc/testing';
|
|
3
|
+
import type ZnThumbnail from '../thumbnail/thumbnail.component';
|
|
4
|
+
import type ZnThumbnailGroup from './thumbnail-group.component';
|
|
5
|
+
|
|
6
|
+
const thumbnails = (count: number) =>
|
|
7
|
+
Array.from({length: count}, (_, i) => `<zn-thumbnail value="v${i}" caption="Item ${i}"></zn-thumbnail>`).join('');
|
|
8
|
+
|
|
9
|
+
describe('<zn-thumbnail-group>', () => {
|
|
10
|
+
it('should render a component', async () => {
|
|
11
|
+
const el = await fixture(html`
|
|
12
|
+
<zn-thumbnail-group></zn-thumbnail-group>`);
|
|
13
|
+
|
|
14
|
+
expect(el).to.exist;
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should render the caption', async () => {
|
|
18
|
+
const el = await fixture<ZnThumbnailGroup>(html`
|
|
19
|
+
<zn-thumbnail-group caption="Landscape"></zn-thumbnail-group>`);
|
|
20
|
+
|
|
21
|
+
expect(el.shadowRoot!.querySelector('.thumbnail-group__caption')!.textContent).to.contain('Landscape');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should not render the toggle when the thumbnails fit on one row', async () => {
|
|
25
|
+
const el = await fixture<ZnThumbnailGroup>(html`
|
|
26
|
+
<zn-thumbnail-group caption="Landscape" style="width: 800px;">
|
|
27
|
+
<zn-thumbnail caption="One"></zn-thumbnail>
|
|
28
|
+
<zn-thumbnail caption="Two"></zn-thumbnail>
|
|
29
|
+
</zn-thumbnail-group>`);
|
|
30
|
+
|
|
31
|
+
expect(el.shadowRoot!.querySelector('.thumbnail-group__toggle')).to.not.exist;
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should render the toggle when the row overflows', async () => {
|
|
35
|
+
const el = await fixture<ZnThumbnailGroup>(
|
|
36
|
+
`<zn-thumbnail-group caption="Landscape" style="width: 400px;">${thumbnails(12)}</zn-thumbnail-group>`);
|
|
37
|
+
|
|
38
|
+
await waitUntil(() => el.shadowRoot!.querySelector('.thumbnail-group__toggle'));
|
|
39
|
+
|
|
40
|
+
const toggle = el.shadowRoot!.querySelector<HTMLButtonElement>('.thumbnail-group__toggle')!;
|
|
41
|
+
expect(toggle.textContent!.trim()).to.equal('Show All (12)');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should use the total attribute in the toggle label', async () => {
|
|
45
|
+
const el = await fixture<ZnThumbnailGroup>(html`
|
|
46
|
+
<zn-thumbnail-group caption="Landscape" total="79" style="width: 800px;">
|
|
47
|
+
<zn-thumbnail caption="One"></zn-thumbnail>
|
|
48
|
+
</zn-thumbnail-group>`);
|
|
49
|
+
|
|
50
|
+
const toggle = el.shadowRoot!.querySelector<HTMLButtonElement>('.thumbnail-group__toggle')!;
|
|
51
|
+
expect(toggle.textContent!.trim()).to.equal('Show All (79)');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should expand and collapse from the toggle', async () => {
|
|
55
|
+
const el = await fixture<ZnThumbnailGroup>(html`
|
|
56
|
+
<zn-thumbnail-group caption="Landscape" always-toggle>
|
|
57
|
+
<zn-thumbnail caption="One"></zn-thumbnail>
|
|
58
|
+
</zn-thumbnail-group>`);
|
|
59
|
+
|
|
60
|
+
const expanded: string[] = [];
|
|
61
|
+
el.addEventListener('zn-expand', () => expanded.push('expand'));
|
|
62
|
+
el.addEventListener('zn-collapse', () => expanded.push('collapse'));
|
|
63
|
+
|
|
64
|
+
el.shadowRoot!.querySelector<HTMLButtonElement>('.thumbnail-group__toggle')!.click();
|
|
65
|
+
await el.updateComplete;
|
|
66
|
+
|
|
67
|
+
expect(el.expanded).to.be.true;
|
|
68
|
+
expect(el.shadowRoot!.querySelector<HTMLButtonElement>('.thumbnail-group__toggle')!.textContent!.trim())
|
|
69
|
+
.to.equal('Show Less');
|
|
70
|
+
|
|
71
|
+
el.shadowRoot!.querySelector<HTMLButtonElement>('.thumbnail-group__toggle')!.click();
|
|
72
|
+
await el.updateComplete;
|
|
73
|
+
|
|
74
|
+
expect(el.expanded).to.be.false;
|
|
75
|
+
expect(expanded).to.deep.equal(['expand', 'collapse']);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should move selection between thumbnails when selectable', async () => {
|
|
79
|
+
const el = await fixture<ZnThumbnailGroup>(html`
|
|
80
|
+
<zn-thumbnail-group selectable>
|
|
81
|
+
<zn-thumbnail value="a" caption="A" selected></zn-thumbnail>
|
|
82
|
+
<zn-thumbnail value="b" caption="B"></zn-thumbnail>
|
|
83
|
+
</zn-thumbnail-group>`);
|
|
84
|
+
|
|
85
|
+
const [a, b] = Array.from(el.querySelectorAll<ZnThumbnail>('zn-thumbnail'));
|
|
86
|
+
expect(el.value).to.equal('a');
|
|
87
|
+
|
|
88
|
+
let changed = false;
|
|
89
|
+
el.addEventListener('zn-change', () => (changed = true));
|
|
90
|
+
|
|
91
|
+
b.shadowRoot!.querySelector<HTMLElement>('.thumbnail__link')!.click();
|
|
92
|
+
await el.updateComplete;
|
|
93
|
+
|
|
94
|
+
expect(el.value).to.equal('b');
|
|
95
|
+
expect(a.selected).to.be.false;
|
|
96
|
+
expect(b.selected).to.be.true;
|
|
97
|
+
expect(changed).to.be.true;
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should mirror the shorthand attributes onto host custom properties', async () => {
|
|
101
|
+
const el = await fixture<ZnThumbnailGroup>(html`
|
|
102
|
+
<zn-thumbnail-group aspect-ratio="1 / 1" thumbnail-width="120px" gap="4px">
|
|
103
|
+
<zn-thumbnail caption="One"></zn-thumbnail>
|
|
104
|
+
</zn-thumbnail-group>`);
|
|
105
|
+
|
|
106
|
+
expect(el.style.getPropertyValue('--zn-thumbnail-aspect-ratio')).to.equal('1 / 1');
|
|
107
|
+
expect(el.style.getPropertyValue('--zn-thumbnail-width')).to.equal('120px');
|
|
108
|
+
expect(el.style.getPropertyValue('--zn-thumbnail-gap')).to.equal('4px');
|
|
109
|
+
|
|
110
|
+
// Thumbnails are light-DOM children, so they inherit the properties from the host.
|
|
111
|
+
const thumbnail = el.querySelector<ZnThumbnail>('zn-thumbnail')!;
|
|
112
|
+
const rect = thumbnail.shadowRoot!.querySelector<HTMLElement>('.thumbnail__media')!.getBoundingClientRect();
|
|
113
|
+
expect(rect.width).to.be.closeTo(120, 1);
|
|
114
|
+
expect(rect.height).to.be.closeTo(120, 1);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should not clear a custom property set inline by the consumer', async () => {
|
|
118
|
+
const el = await fixture<ZnThumbnailGroup>(html`
|
|
119
|
+
<zn-thumbnail-group style="--zn-thumbnail-width: 90px;">
|
|
120
|
+
<zn-thumbnail caption="One"></zn-thumbnail>
|
|
121
|
+
</zn-thumbnail-group>`);
|
|
122
|
+
|
|
123
|
+
expect(el.style.getPropertyValue('--zn-thumbnail-width')).to.equal('90px');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should clear a shorthand property once its attribute is removed', async () => {
|
|
127
|
+
const el = await fixture<ZnThumbnailGroup>(html`
|
|
128
|
+
<zn-thumbnail-group thumbnail-width="120px">
|
|
129
|
+
<zn-thumbnail caption="One"></zn-thumbnail>
|
|
130
|
+
</zn-thumbnail-group>`);
|
|
131
|
+
|
|
132
|
+
el.thumbnailWidth = '';
|
|
133
|
+
await el.updateComplete;
|
|
134
|
+
|
|
135
|
+
expect(el.style.getPropertyValue('--zn-thumbnail-width')).to.equal('');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should not manage selection when selectable is not set', async () => {
|
|
139
|
+
const el = await fixture<ZnThumbnailGroup>(html`
|
|
140
|
+
<zn-thumbnail-group>
|
|
141
|
+
<zn-thumbnail value="a" caption="A"></zn-thumbnail>
|
|
142
|
+
</zn-thumbnail-group>`);
|
|
143
|
+
|
|
144
|
+
const a = el.querySelector<ZnThumbnail>('zn-thumbnail')!;
|
|
145
|
+
a.shadowRoot!.querySelector<HTMLElement>('.thumbnail__link')!.click();
|
|
146
|
+
await el.updateComplete;
|
|
147
|
+
|
|
148
|
+
expect(a.selected).to.be.false;
|
|
149
|
+
expect(el.value).to.equal('');
|
|
150
|
+
});
|
|
151
|
+
});
|
|
@@ -14,6 +14,7 @@ import styles from './well.scss';
|
|
|
14
14
|
* @since 1.0
|
|
15
15
|
*
|
|
16
16
|
* @slot - The default slot.
|
|
17
|
+
* @slot action - Content displayed on the right hand side of the well.
|
|
17
18
|
*/
|
|
18
19
|
export default class ZnWell extends ZincElement {
|
|
19
20
|
static styles: CSSResultGroup = unsafeCSS(styles);
|
|
@@ -21,9 +22,21 @@ export default class ZnWell extends ZincElement {
|
|
|
21
22
|
@property() icon: string = '';
|
|
22
23
|
@property({attribute: 'inline', type: Boolean, reflect: true}) inline: boolean;
|
|
23
24
|
|
|
25
|
+
/** Renders the default slot inside a `pre` element, preserving whitespace using a monospace font. */
|
|
26
|
+
@property({attribute: 'pre', type: Boolean, reflect: true}) pre: boolean;
|
|
27
|
+
|
|
28
|
+
/** Breaks long unbroken words, so they wrap instead of forcing the well wider. */
|
|
29
|
+
@property({attribute: 'break-long', type: Boolean, reflect: true}) breakLong: boolean;
|
|
30
|
+
|
|
24
31
|
private readonly hasSlotController = new HasSlotController(this, '[default]', 'action');
|
|
25
32
|
|
|
26
33
|
render() {
|
|
34
|
+
const contentClasses = classMap({
|
|
35
|
+
'well__content': true,
|
|
36
|
+
'well__content--pre': this.pre,
|
|
37
|
+
'well__content--break-long': this.breakLong,
|
|
38
|
+
});
|
|
39
|
+
|
|
27
40
|
return html`
|
|
28
41
|
<div class="${classMap({
|
|
29
42
|
'well': true,
|
|
@@ -31,10 +44,15 @@ export default class ZnWell extends ZincElement {
|
|
|
31
44
|
})}">
|
|
32
45
|
${this.icon ? html`
|
|
33
46
|
<zn-icon src="${this.icon}" size="18"></zn-icon>` : ''}
|
|
34
|
-
${this.hasSlotController.test('[default]')
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
47
|
+
${this.hasSlotController.test('[default]')
|
|
48
|
+
? (this.pre
|
|
49
|
+
? html`
|
|
50
|
+
<pre class="${contentClasses}"><slot></slot></pre>`
|
|
51
|
+
: html`
|
|
52
|
+
<div class="${contentClasses}">
|
|
53
|
+
<slot></slot>
|
|
54
|
+
</div>`)
|
|
55
|
+
: ''}
|
|
38
56
|
${this.hasSlotController.test('action') ? html`
|
|
39
57
|
<slot name="action" class="well__action"></slot>` : ''}
|
|
40
58
|
</div>
|
|
@@ -17,3 +17,27 @@
|
|
|
17
17
|
.well--inline {
|
|
18
18
|
display: inline-flex;
|
|
19
19
|
}
|
|
20
|
+
|
|
21
|
+
// Without this the content's max-content width becomes the well's minimum size,
|
|
22
|
+
// so a single long unbroken string (a token, a URL) widens the whole well.
|
|
23
|
+
.well__content {
|
|
24
|
+
min-width: 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.well__content--pre {
|
|
28
|
+
margin: 0;
|
|
29
|
+
font-family: var(--zn-font-mono, 'SFMono-Regular', 'Consolas', 'Liberation Mono', 'Menlo', monospace);
|
|
30
|
+
white-space: pre;
|
|
31
|
+
overflow-x: auto;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.well__content--break-long {
|
|
35
|
+
overflow-wrap: anywhere;
|
|
36
|
+
word-break: break-word;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Long lines have to be allowed to wrap before they can be broken.
|
|
40
|
+
.well__content--pre.well__content--break-long {
|
|
41
|
+
white-space: pre-wrap;
|
|
42
|
+
overflow-x: visible;
|
|
43
|
+
}
|
|
@@ -7,4 +7,47 @@ describe('<zn-well>', () => {
|
|
|
7
7
|
<zn-well></zn-well> `);
|
|
8
8
|
expect(el).to.exist;
|
|
9
9
|
});
|
|
10
|
+
|
|
11
|
+
it('should render the default slot in a div by default', async () => {
|
|
12
|
+
const el = await fixture(html`
|
|
13
|
+
<zn-well>content</zn-well> `);
|
|
14
|
+
const content = el.shadowRoot!.querySelector('.well__content')!;
|
|
15
|
+
expect(content.tagName).to.equal('DIV');
|
|
16
|
+
expect(content.classList.contains('well__content--pre')).to.be.false;
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should render the default slot in a pre when pre is set', async () => {
|
|
20
|
+
const el = await fixture(html`
|
|
21
|
+
<zn-well pre>content</zn-well> `);
|
|
22
|
+
const content = el.shadowRoot!.querySelector('.well__content')!;
|
|
23
|
+
expect(content.tagName).to.equal('PRE');
|
|
24
|
+
expect(getComputedStyle(content).whiteSpace).to.equal('pre');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should break long words when break-long is set', async () => {
|
|
28
|
+
const el = await fixture(html`
|
|
29
|
+
<zn-well break-long>content</zn-well> `);
|
|
30
|
+
const content = el.shadowRoot!.querySelector('.well__content')!;
|
|
31
|
+
expect(content.classList.contains('well__content--break-long')).to.be.true;
|
|
32
|
+
expect(getComputedStyle(content).overflowWrap).to.equal('anywhere');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should wrap preformatted content when pre and break-long are combined', async () => {
|
|
36
|
+
const el = await fixture(html`
|
|
37
|
+
<zn-well pre break-long>content</zn-well> `);
|
|
38
|
+
const content = el.shadowRoot!.querySelector('.well__content')!;
|
|
39
|
+
expect(content.tagName).to.equal('PRE');
|
|
40
|
+
expect(getComputedStyle(content).whiteSpace).to.equal('pre-wrap');
|
|
41
|
+
expect(getComputedStyle(content).overflowWrap).to.equal('anywhere');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should not let long unbroken content widen the well when break-long is set', async () => {
|
|
45
|
+
const token = 'a'.repeat(400);
|
|
46
|
+
const container = await fixture(html`
|
|
47
|
+
<div style="width: 300px">
|
|
48
|
+
<zn-well break-long>${token}</zn-well>
|
|
49
|
+
</div> `);
|
|
50
|
+
const el = container.querySelector('zn-well')!;
|
|
51
|
+
expect(el.getBoundingClientRect().width).to.be.at.most(300);
|
|
52
|
+
});
|
|
10
53
|
});
|
package/src/zinc.ts
CHANGED
|
@@ -115,6 +115,8 @@ export { default as ThemeEditor } from './components/theme-editor';
|
|
|
115
115
|
export { default as SlashMenu } from './components/slash-menu';
|
|
116
116
|
export { default as SlashItem } from './components/slash-item';
|
|
117
117
|
export { default as ScheduleBuilder } from './components/schedule-builder';
|
|
118
|
+
export { default as Thumbnail } from './components/thumbnail';
|
|
119
|
+
export { default as ThumbnailGroup } from './components/thumbnail-group';
|
|
118
120
|
/* plop:component */
|
|
119
121
|
|
|
120
122
|
// Base Component
|