@kubex/zinc 1.1.62 → 1.1.64
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 +3141 -1885
- package/dist/vscode.html-custom-data.json +102 -3
- package/dist/web-types.json +224 -6
- package/dist/zn.d.ts +235 -0
- package/dist/zn.min.js +795 -679
- package/docs/data/preview-frame-payload-error.json +4 -0
- package/docs/data/preview-frame-payload.json +6 -0
- package/docs/pages/components/form-group.md +12 -0
- package/docs/pages/components/preview-frame-demo.njk +138 -0
- package/docs/pages/components/preview-frame.md +105 -0
- package/docs/pages/components/remarkd-editor.md +103 -0
- package/docs/pages/components/toggle.md +2 -2
- package/package.json +3 -2
- package/src/components/file/file.component.ts +16 -1
- package/src/components/form-group/form-group.component.ts +24 -12
- package/src/components/form-group/form-group.scss +10 -0
- package/src/components/form-group/form-group.test.ts +19 -0
- package/src/components/preview-frame/index.ts +12 -0
- package/src/components/preview-frame/preview-frame.component.ts +246 -0
- package/src/components/preview-frame/preview-frame.scss +27 -0
- package/src/components/preview-frame/preview-frame.test.ts +268 -0
- package/src/components/query-builder/query-builder.component.ts +14 -6
- package/src/components/remarkd-editor/index.ts +12 -0
- package/src/components/remarkd-editor/remarkd-editor.component.ts +752 -0
- package/src/components/remarkd-editor/remarkd-editor.scss +241 -0
- package/src/components/remarkd-editor/remarkd-editor.test.ts +147 -0
- package/src/components/select/select.component.ts +4 -0
- package/src/components/select/select.test.ts +29 -0
- package/src/components/sp/sp.scss +5 -1
- package/src/components/tabs/tabs.component.ts +9 -4
- package/src/components/tabs/tabs.test.ts +42 -1
- package/src/components/tile/tile.scss +5 -1
- package/src/components/toggle/toggle.component.ts +3 -12
- package/src/components/toggle/toggle.test.ts +29 -0
- package/src/events/zn-error.ts +1 -1
- package/src/zinc.ts +2 -0
|
@@ -48,6 +48,18 @@ Add descriptive help text to a form group with the `help-text` attribute. For he
|
|
|
48
48
|
</zn-form-group>
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
+
### Chip
|
|
52
|
+
|
|
53
|
+
Use the `chip` slot to display a chip under the form group's help text.
|
|
54
|
+
|
|
55
|
+
```html:preview
|
|
56
|
+
<zn-form-group label="Team Members" help-text="Manage who has access to this project">
|
|
57
|
+
<zn-chip slot="chip" type="success">Pro Plan</zn-chip>
|
|
58
|
+
<zn-input label="Name" placeholder="Team member name"></zn-input>
|
|
59
|
+
<zn-input label="Email" type="email" placeholder="email@example.com"></zn-input>
|
|
60
|
+
</zn-form-group>
|
|
61
|
+
```
|
|
62
|
+
|
|
51
63
|
### Label with Tooltip
|
|
52
64
|
|
|
53
65
|
Use the `label-tooltip` attribute to add text that appears in a tooltip triggered by an info icon next to the label.
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
---
|
|
2
|
+
meta:
|
|
3
|
+
title: Preview Frame — Demo Embed
|
|
4
|
+
description: A demo embed page implementing the hp-preview postMessage protocol, for iframing into zn-preview-frame examples.
|
|
5
|
+
layout: full-page
|
|
6
|
+
toc: false
|
|
7
|
+
eleventyExcludeFromCollections: true
|
|
8
|
+
permalink: /components/preview-frame-demo/index.html
|
|
9
|
+
---
|
|
10
|
+
{% raw %}
|
|
11
|
+
<div class="shell">
|
|
12
|
+
<p id="waiting">Waiting for hp-preview:config…</p>
|
|
13
|
+
|
|
14
|
+
<div class="card" id="card" hidden>
|
|
15
|
+
<div class="card__accent"></div>
|
|
16
|
+
<h2 id="merchant"></h2>
|
|
17
|
+
<p class="card__amount" id="amount"></p>
|
|
18
|
+
<label>Card number
|
|
19
|
+
<input type="text" placeholder="4242 4242 4242 4242" disabled>
|
|
20
|
+
</label>
|
|
21
|
+
<div class="card__row">
|
|
22
|
+
<label>Expiry
|
|
23
|
+
<input type="text" placeholder="12/28" disabled>
|
|
24
|
+
</label>
|
|
25
|
+
<label>CVC
|
|
26
|
+
<input type="text" placeholder="123" disabled>
|
|
27
|
+
</label>
|
|
28
|
+
</div>
|
|
29
|
+
<button id="pay" type="button"></button>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<style>
|
|
34
|
+
.shell {
|
|
35
|
+
height: 100%;
|
|
36
|
+
display: flex;
|
|
37
|
+
align-items: center;
|
|
38
|
+
justify-content: center;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
#waiting {
|
|
42
|
+
color: rgb(var(--zn-color-muted-text));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.card {
|
|
46
|
+
--accent: #6936f5;
|
|
47
|
+
width: 320px;
|
|
48
|
+
padding: 24px;
|
|
49
|
+
border: 1px solid rgb(var(--zn-border-color));
|
|
50
|
+
border-radius: 12px;
|
|
51
|
+
background: rgb(var(--zn-panel));
|
|
52
|
+
display: flex;
|
|
53
|
+
flex-direction: column;
|
|
54
|
+
gap: 12px;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.card__accent {
|
|
58
|
+
height: 6px;
|
|
59
|
+
border-radius: 3px;
|
|
60
|
+
background: var(--accent);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.card h2 {
|
|
64
|
+
margin: 0;
|
|
65
|
+
font-size: 1.125rem;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.card__amount {
|
|
69
|
+
margin: 0;
|
|
70
|
+
font-size: 1.75rem;
|
|
71
|
+
font-weight: 700;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.card label {
|
|
75
|
+
display: flex;
|
|
76
|
+
flex-direction: column;
|
|
77
|
+
gap: 4px;
|
|
78
|
+
font-size: 0.75rem;
|
|
79
|
+
font-weight: 600;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.card input {
|
|
83
|
+
padding: 8px 10px;
|
|
84
|
+
border: 1px solid rgb(var(--zn-border-color));
|
|
85
|
+
border-radius: 6px;
|
|
86
|
+
font: inherit;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.card__row {
|
|
90
|
+
display: flex;
|
|
91
|
+
gap: 12px;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.card__row label {
|
|
95
|
+
flex: 1;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/* id selector: zn.min.css targets native buttons at higher specificity */
|
|
99
|
+
.card #pay {
|
|
100
|
+
padding: 10px;
|
|
101
|
+
border: 0;
|
|
102
|
+
border-radius: 6px;
|
|
103
|
+
background: var(--accent);
|
|
104
|
+
color: #fff;
|
|
105
|
+
font: inherit;
|
|
106
|
+
font-weight: 600;
|
|
107
|
+
cursor: pointer;
|
|
108
|
+
}
|
|
109
|
+
</style>
|
|
110
|
+
|
|
111
|
+
<script>
|
|
112
|
+
// The embed side of the hp-preview protocol: announce readiness, wait for the
|
|
113
|
+
// config payload, render it, then report rendered (or error). A real embed
|
|
114
|
+
// should use the host's origin instead of '*'.
|
|
115
|
+
const post = message => window.parent.postMessage(message, '*');
|
|
116
|
+
|
|
117
|
+
window.addEventListener('message', e => {
|
|
118
|
+
const data = e.data;
|
|
119
|
+
if (data?.type !== 'hp-preview:config') return;
|
|
120
|
+
|
|
121
|
+
if (data.fail) {
|
|
122
|
+
post({type: 'hp-preview:error', message: data.message || 'Preview failed to render'});
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
document.getElementById('waiting').hidden = true;
|
|
127
|
+
const card = document.getElementById('card');
|
|
128
|
+
card.hidden = false;
|
|
129
|
+
card.style.setProperty('--accent', data.accent || '#6936f5');
|
|
130
|
+
document.getElementById('merchant').textContent = data.merchant || 'Merchant';
|
|
131
|
+
document.getElementById('amount').textContent = data.amount || '';
|
|
132
|
+
document.getElementById('pay').textContent = data.buttonLabel || 'Pay now';
|
|
133
|
+
post({type: 'hp-preview:rendered'});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
post({type: 'hp-preview:ready'});
|
|
137
|
+
</script>
|
|
138
|
+
{% endraw %}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
---
|
|
2
|
+
meta:
|
|
3
|
+
title: Preview Frame
|
|
4
|
+
description: Embeds a live preview iframe and keeps it in sync with surrounding forms via the hp-preview postMessage protocol.
|
|
5
|
+
layout: component
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
The frame loads the embed page from `src`, waits for it to post `hp-preview:ready`, fetches the JSON payload from `data-uri` and posts it back into the frame as `hp-preview:config`. The embed then reports `hp-preview:rendered` or `hp-preview:error`.
|
|
9
|
+
|
|
10
|
+
The example below embeds the [demo embed page](/components/preview-frame-demo/), which implements the embed side of the protocol. `frame-origin` must match the embed's origin exactly — messages from any other origin are ignored — so the example sets it at runtime since the docs demo is same-origin.
|
|
11
|
+
|
|
12
|
+
```html:preview
|
|
13
|
+
<zn-preview-frame
|
|
14
|
+
id="preview-frame-demo"
|
|
15
|
+
src="/components/preview-frame-demo/"
|
|
16
|
+
data-uri="/data/preview-frame-payload.json"
|
|
17
|
+
watch="#preview-frame-demo-form"></zn-preview-frame>
|
|
18
|
+
|
|
19
|
+
<script>
|
|
20
|
+
document.getElementById('preview-frame-demo').frameOrigin = location.origin;
|
|
21
|
+
</script>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
:::tip
|
|
25
|
+
`watch` defaults to `form[data-auto-save]`, so only forms explicitly opted in via a `data-auto-save` attribute are watched — unmarked forms keep normal submit behavior and are never intercepted. Override `watch` with your own selector to widen or change the scope — the examples here point it at a form that doesn't exist. Watched forms are auto-saved via a POST to their `action` on change, which needs a real endpoint, so it isn't demonstrated here.
|
|
26
|
+
:::
|
|
27
|
+
|
|
28
|
+
The frame always fills the panel; `zoom` (0–1, default `1`) zooms the previewed page out browser-style — e.g. `zoom="0.4"` renders the content at 40% size with correspondingly more of the page visible. `min-height` (default `480`) sets the visible panel height in pixels.
|
|
29
|
+
|
|
30
|
+
## Live Form Updates
|
|
31
|
+
|
|
32
|
+
In a real deployment, editing a watched form auto-saves it and the preview refreshes with the newly saved config. This docs site is static, so the example simulates the save: form changes are encoded into a `data:` payload URI and `refresh()` re-runs the fetch → `hp-preview:config` cycle — the same path a real save triggers.
|
|
33
|
+
|
|
34
|
+
```html:preview
|
|
35
|
+
<form id="preview-frame-live-form" class="preview-frame-live-form">
|
|
36
|
+
<zn-input name="merchant" label="Merchant" value="Acme Donuts"></zn-input>
|
|
37
|
+
<zn-input name="amount" label="Amount" value="£24.99"></zn-input>
|
|
38
|
+
<zn-input name="buttonLabel" label="Button label" value="Pay £24.99"></zn-input>
|
|
39
|
+
<label class="preview-frame-live-form__color">Accent
|
|
40
|
+
<input type="color" name="accent" value="#6936f5">
|
|
41
|
+
</label>
|
|
42
|
+
</form>
|
|
43
|
+
|
|
44
|
+
<zn-preview-frame
|
|
45
|
+
id="preview-frame-live"
|
|
46
|
+
src="/components/preview-frame-demo/"
|
|
47
|
+
watch="#preview-frame-live-none"></zn-preview-frame>
|
|
48
|
+
|
|
49
|
+
<script>
|
|
50
|
+
customElements.whenDefined('zn-preview-frame').then(() => {
|
|
51
|
+
const frame = document.getElementById('preview-frame-live');
|
|
52
|
+
const form = document.getElementById('preview-frame-live-form');
|
|
53
|
+
frame.frameOrigin = location.origin;
|
|
54
|
+
|
|
55
|
+
const update = () => {
|
|
56
|
+
const payload = Object.fromEntries(new FormData(form));
|
|
57
|
+
frame.dataUri = 'data:application/json,' + encodeURIComponent(JSON.stringify(payload));
|
|
58
|
+
frame.refresh();
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
form.addEventListener('zn-input', update);
|
|
62
|
+
form.addEventListener('input', update);
|
|
63
|
+
update();
|
|
64
|
+
});
|
|
65
|
+
</script>
|
|
66
|
+
|
|
67
|
+
<style>
|
|
68
|
+
.preview-frame-live-form {
|
|
69
|
+
display: flex;
|
|
70
|
+
flex-wrap: wrap;
|
|
71
|
+
gap: 16px;
|
|
72
|
+
align-items: flex-end;
|
|
73
|
+
margin-bottom: 16px;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.preview-frame-live-form zn-input {
|
|
77
|
+
flex: 1;
|
|
78
|
+
min-width: 140px;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.preview-frame-live-form__color {
|
|
82
|
+
display: flex;
|
|
83
|
+
flex-direction: column;
|
|
84
|
+
gap: 4px;
|
|
85
|
+
font-size: 0.875rem;
|
|
86
|
+
font-weight: 500;
|
|
87
|
+
}
|
|
88
|
+
</style>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Error Overlay
|
|
92
|
+
|
|
93
|
+
When the embed reports `hp-preview:error` (or fetching the payload fails), the message is shown in an overlay and `zn-error` is emitted. This example uses a payload that asks the demo embed to fail.
|
|
94
|
+
|
|
95
|
+
```html:preview
|
|
96
|
+
<zn-preview-frame
|
|
97
|
+
id="preview-frame-demo-error"
|
|
98
|
+
src="/components/preview-frame-demo/"
|
|
99
|
+
data-uri="/data/preview-frame-payload-error.json"
|
|
100
|
+
watch="#preview-frame-demo-error-form"></zn-preview-frame>
|
|
101
|
+
|
|
102
|
+
<script>
|
|
103
|
+
document.getElementById('preview-frame-demo-error').frameOrigin = location.origin;
|
|
104
|
+
</script>
|
|
105
|
+
```
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
---
|
|
2
|
+
meta:
|
|
3
|
+
title: Remarkd Editor
|
|
4
|
+
description: A Notion-style block editor for remarkd content — blocks render inline and are edited in place.
|
|
5
|
+
layout: component
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
`zn-remarkd-editor` is a Notion-style editor for
|
|
9
|
+
[remarkd](https://github.com/packaged/remarkd) content. The document is a list
|
|
10
|
+
of blocks rendered inline with remarkd — there is no separate preview. Click a
|
|
11
|
+
block to edit its raw remarkd source in place; it re-renders when you click
|
|
12
|
+
away (or press Escape / Ctrl+Enter). The `value` is always plain remarkd
|
|
13
|
+
source, submitted with the surrounding form.
|
|
14
|
+
|
|
15
|
+
```html:preview
|
|
16
|
+
<zn-remarkd-editor
|
|
17
|
+
name="content"
|
|
18
|
+
value="# Product Guide
|
|
19
|
+
|
|
20
|
+
This is a paragraph with **strong text**, __emphasis__, and a [link](https://example.com).
|
|
21
|
+
|
|
22
|
+
- [ ] Draft the guide
|
|
23
|
+
- [x] Review the output
|
|
24
|
+
|
|
25
|
+
NOTE: Click any block to edit its source."
|
|
26
|
+
></zn-remarkd-editor>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Examples
|
|
30
|
+
|
|
31
|
+
### Adding and Moving Blocks
|
|
32
|
+
|
|
33
|
+
Hover a block to reveal its gutter actions: add a block below it, or grab the
|
|
34
|
+
handle to drag the block somewhere else in the document. Clicking the empty
|
|
35
|
+
area at the end of the document starts a new block. A block committed empty
|
|
36
|
+
is removed.
|
|
37
|
+
|
|
38
|
+
```html:preview
|
|
39
|
+
<zn-remarkd-editor
|
|
40
|
+
name="content"
|
|
41
|
+
value="Hover me to see the block actions."
|
|
42
|
+
></zn-remarkd-editor>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Images
|
|
46
|
+
|
|
47
|
+
Adding an image opens a dialog. With `attachment-url` set it contains a
|
|
48
|
+
`zn-file` drop area; the file's metadata is POSTed to the endpoint, which must
|
|
49
|
+
respond with `{uploadPath, uploadUrl}`; the file is then PUT to `uploadUrl`
|
|
50
|
+
and `uploadPath` is inserted as an image block. Without `attachment-url` the
|
|
51
|
+
dialog asks for an image URL instead. Dropping an image file straight onto the
|
|
52
|
+
editor uploads it directly.
|
|
53
|
+
|
|
54
|
+
```html
|
|
55
|
+
<zn-remarkd-editor name="content" attachment-url="/upload"></zn-remarkd-editor>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Remarkd Blocks
|
|
59
|
+
|
|
60
|
+
Remarkd's block syntax — hints, containers, code fences — renders with the
|
|
61
|
+
official remarkd styles. Fenced content stays a single block, blank lines and
|
|
62
|
+
all.
|
|
63
|
+
|
|
64
|
+
````html:preview
|
|
65
|
+
<zn-remarkd-editor name="content" value="TIP: Hint blocks are remarkd-specific.
|
|
66
|
+
|
|
67
|
+
====
|
|
68
|
+
An example **container** block.
|
|
69
|
+
====
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
a code fence
|
|
73
|
+
|
|
74
|
+
with a blank line inside
|
|
75
|
+
```"
|
|
76
|
+
></zn-remarkd-editor>
|
|
77
|
+
````
|
|
78
|
+
|
|
79
|
+
### Form Integration
|
|
80
|
+
|
|
81
|
+
`zn-remarkd-editor` is a [form control](/getting-started/form-controls); its
|
|
82
|
+
remarkd source is submitted under `name`, and `required` is supported.
|
|
83
|
+
|
|
84
|
+
```html:preview
|
|
85
|
+
<form class="remarkd-editor-form">
|
|
86
|
+
<zn-remarkd-editor name="content" required value="Edit me, then submit."></zn-remarkd-editor>
|
|
87
|
+
<br />
|
|
88
|
+
<zn-button type="submit" color="success">Submit</zn-button>
|
|
89
|
+
</form>
|
|
90
|
+
|
|
91
|
+
<script type="module">
|
|
92
|
+
const form = document.querySelector('.remarkd-editor-form');
|
|
93
|
+
|
|
94
|
+
await customElements.whenDefined('zn-button');
|
|
95
|
+
await customElements.whenDefined('zn-remarkd-editor');
|
|
96
|
+
|
|
97
|
+
form.addEventListener('submit', (e) => {
|
|
98
|
+
e.preventDefault();
|
|
99
|
+
const data = Object.fromEntries(new FormData(form));
|
|
100
|
+
alert('Submitted!\n\n' + JSON.stringify(data, null, 2));
|
|
101
|
+
});
|
|
102
|
+
</script>
|
|
103
|
+
```
|
|
@@ -170,7 +170,7 @@ Toggles support keyboard navigation using arrow keys. Press the left arrow to tu
|
|
|
170
170
|
|
|
171
171
|
### Form Integration
|
|
172
172
|
|
|
173
|
-
Toggles work seamlessly with forms and
|
|
173
|
+
Toggles work seamlessly with forms and always submit a value: the toggle's `value` (or `on`) when checked, and `off` when unchecked. Use the `fallback` attribute to customize the unchecked value.
|
|
174
174
|
|
|
175
175
|
```html:preview
|
|
176
176
|
<form id="toggle-form">
|
|
@@ -197,7 +197,7 @@ Toggles work seamlessly with forms and will be submitted with form data.
|
|
|
197
197
|
|
|
198
198
|
### Fallback Value
|
|
199
199
|
|
|
200
|
-
|
|
200
|
+
The toggle always submits a value, even when unchecked — `off` by default. Use the `fallback` attribute to submit a specific value instead when the toggle is unchecked.
|
|
201
201
|
|
|
202
202
|
```html:preview
|
|
203
203
|
<form id="fallback-form">
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubex/zinc",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.64",
|
|
4
4
|
"description": "A collection of web components for building web applications based off of @shoelace-style/Shoelace",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"web components",
|
|
@@ -55,7 +55,8 @@
|
|
|
55
55
|
"lit": "^3.3.3",
|
|
56
56
|
"lucide": "^1.17.0",
|
|
57
57
|
"marked": "^18.0.2",
|
|
58
|
-
"quill": "^2.0.3"
|
|
58
|
+
"quill": "^2.0.3",
|
|
59
|
+
"remarkd-js": "github:packaged/remarkd#fc482a4260740e0d61ab1cd50b056545fe195d94"
|
|
59
60
|
},
|
|
60
61
|
"devDependencies": {
|
|
61
62
|
"@custom-elements-manifest/analyzer": "^0.10.3",
|
|
@@ -76,9 +76,17 @@ export default class ZnFile extends ZincElement implements ZincFormControl {
|
|
|
76
76
|
|
|
77
77
|
private readonly formControlController = new FormControlController(this, {
|
|
78
78
|
assumeInteractionOn: ['zn-change'],
|
|
79
|
-
value
|
|
79
|
+
// An explicitly cleared control submits an empty value so the server can
|
|
80
|
+
// remove the stored upload; an untouched empty control submits nothing.
|
|
81
|
+
value: (el: ZnFile) => {
|
|
82
|
+
if (el.files?.length) return el.files;
|
|
83
|
+
return el.clearedByUser ? '' : undefined;
|
|
84
|
+
}
|
|
80
85
|
})
|
|
81
86
|
|
|
87
|
+
/** Set when the user clears the control; reset when a file is chosen. */
|
|
88
|
+
private clearedByUser = false;
|
|
89
|
+
|
|
82
90
|
private readonly hasSlotController = new HasSlotController(this, 'help-text', 'label');
|
|
83
91
|
|
|
84
92
|
private readonly localize = new LocalizeController(this);
|
|
@@ -110,6 +118,9 @@ export default class ZnFile extends ZincElement implements ZincFormControl {
|
|
|
110
118
|
set files(v: FileList | null) {
|
|
111
119
|
if (this.input) {
|
|
112
120
|
this.input.files = v;
|
|
121
|
+
if (v?.length) {
|
|
122
|
+
this.clearedByUser = false;
|
|
123
|
+
}
|
|
113
124
|
this.updatePreview();
|
|
114
125
|
}
|
|
115
126
|
}
|
|
@@ -481,6 +492,9 @@ export default class ZnFile extends ZincElement implements ZincFormControl {
|
|
|
481
492
|
const dataTransfer = new DataTransfer();
|
|
482
493
|
files.forEach(f => dataTransfer.items.add(f));
|
|
483
494
|
this.files = dataTransfer.files;
|
|
495
|
+
if (!dataTransfer.files.length) {
|
|
496
|
+
this.clearedByUser = true;
|
|
497
|
+
}
|
|
484
498
|
this.input.dispatchEvent(new Event('change'));
|
|
485
499
|
}
|
|
486
500
|
|
|
@@ -501,6 +515,7 @@ export default class ZnFile extends ZincElement implements ZincFormControl {
|
|
|
501
515
|
if (!hadLocalFile) {
|
|
502
516
|
this.src = '';
|
|
503
517
|
}
|
|
518
|
+
this.clearedByUser = true;
|
|
504
519
|
this.clearConfirmDialog?.hide();
|
|
505
520
|
this.input.dispatchEvent(new Event('change'));
|
|
506
521
|
this.emit('zn-clear');
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {classMap} from "lit/directives/class-map.js";
|
|
2
|
-
import {type CSSResultGroup, html, unsafeCSS} from 'lit';
|
|
3
|
-
import {HasSlotController} from "../../internal/slot";
|
|
4
|
-
import {property} from 'lit/decorators.js';
|
|
1
|
+
import { classMap } from "lit/directives/class-map.js";
|
|
2
|
+
import { type CSSResultGroup, html, unsafeCSS } from 'lit';
|
|
3
|
+
import { HasSlotController } from "../../internal/slot";
|
|
4
|
+
import { property } from 'lit/decorators.js';
|
|
5
5
|
import ZincElement from '../../internal/zinc-element';
|
|
6
6
|
|
|
7
7
|
import styles from './form-group.scss';
|
|
@@ -13,12 +13,13 @@ import styles from './form-group.scss';
|
|
|
13
13
|
* @since 1.0
|
|
14
14
|
*
|
|
15
15
|
* @slot - The default slot.
|
|
16
|
+
* @slot chip - A chip displayed under the form group's help text.
|
|
16
17
|
*
|
|
17
18
|
*/
|
|
18
19
|
export default class ZnFormGroup extends ZincElement {
|
|
19
20
|
static styles: CSSResultGroup = unsafeCSS(styles);
|
|
20
21
|
|
|
21
|
-
private readonly hasSlotController = new HasSlotController(this, 'help-text', 'label');
|
|
22
|
+
private readonly hasSlotController = new HasSlotController(this, 'help-text', 'label', 'chip');
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* The form group's label. Required for proper accessibility. If you need to display HTML, use the `label` slot
|
|
@@ -30,12 +31,14 @@ export default class ZnFormGroup extends ZincElement {
|
|
|
30
31
|
* Text that appears in a tooltip next to the label. If you need to display HTML in the tooltip, use the
|
|
31
32
|
* `label-tooltip` slot instead.
|
|
32
33
|
*/
|
|
33
|
-
@property({attribute: 'label-tooltip'}) labelTooltip = '';
|
|
34
|
+
@property({ attribute: 'label-tooltip' }) labelTooltip = '';
|
|
34
35
|
|
|
35
36
|
/** The form groups help text. If you need to display HTML, use the `help-text` slot instead. */
|
|
36
|
-
@property({attribute: 'help-text'}) helpText = '';
|
|
37
|
-
@property({attribute: 'cols', type: Boolean}) forceCols = false;
|
|
38
|
-
@property({attribute: 'layout', type: String}) layout = "1,2";
|
|
37
|
+
@property({ attribute: 'help-text' }) helpText = '';
|
|
38
|
+
@property({ attribute: 'cols', type: Boolean }) forceCols = false;
|
|
39
|
+
@property({ attribute: 'layout', type: String }) layout = "1,2";
|
|
40
|
+
|
|
41
|
+
@property({ attribute: 'pad', type: Boolean }) pad: boolean = false;
|
|
39
42
|
|
|
40
43
|
render() {
|
|
41
44
|
const hasLabelSlot = this.hasSlotController.test('label');
|
|
@@ -44,6 +47,7 @@ export default class ZnFormGroup extends ZincElement {
|
|
|
44
47
|
const hasLabel = this.label ? true : hasLabelSlot;
|
|
45
48
|
const hasLabelTooltip = this.labelTooltip ? true : hasLabelTooltipSlot;
|
|
46
49
|
const hasHelpText = this.helpText ? true : hasHelpTextSlot;
|
|
50
|
+
const hasChip = this.hasSlotController.test('chip');
|
|
47
51
|
|
|
48
52
|
return html`
|
|
49
53
|
<fieldset
|
|
@@ -52,14 +56,15 @@ export default class ZnFormGroup extends ZincElement {
|
|
|
52
56
|
'form-control': true,
|
|
53
57
|
'form-control--has-label': hasLabel,
|
|
54
58
|
'form-control--has-label-tooltip': hasLabelTooltip,
|
|
55
|
-
'form-control--has-help-text': hasHelpText
|
|
59
|
+
'form-control--has-help-text': hasHelpText,
|
|
60
|
+
'form-control--pad': this.pad
|
|
56
61
|
})}"
|
|
57
62
|
aria-labelledby="label"
|
|
58
63
|
aria-describedby="help-text">
|
|
59
64
|
|
|
60
65
|
<zn-cols layout="${this.layout}" part="form-control-container" class="form-control__container">
|
|
61
|
-
${hasLabel || hasHelpText || this.forceCols ? html`
|
|
62
|
-
<div>
|
|
66
|
+
${hasLabel || hasHelpText || hasChip || this.forceCols ? html`
|
|
67
|
+
<div class="form-control__text">
|
|
63
68
|
|
|
64
69
|
${hasLabel ? html`
|
|
65
70
|
<label
|
|
@@ -87,6 +92,13 @@ export default class ZnFormGroup extends ZincElement {
|
|
|
87
92
|
<slot name="help-text">${this.helpText}</slot>
|
|
88
93
|
</div>` : html``}
|
|
89
94
|
|
|
95
|
+
${hasChip ? html`
|
|
96
|
+
<div
|
|
97
|
+
part="form-control-chip"
|
|
98
|
+
class="form-control__chip">
|
|
99
|
+
<slot name="chip"></slot>
|
|
100
|
+
</div>` : html``}
|
|
101
|
+
|
|
90
102
|
</div>` : html``}
|
|
91
103
|
|
|
92
104
|
<div part="form-control-input" class="form-control-input">
|
|
@@ -28,6 +28,10 @@
|
|
|
28
28
|
--zn-col-basis: 200px;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
.form-control__chip {
|
|
32
|
+
margin-top: var(--zn-spacing-x-small);
|
|
33
|
+
}
|
|
34
|
+
|
|
31
35
|
.visually-hidden {
|
|
32
36
|
position: absolute;
|
|
33
37
|
width: 1px;
|
|
@@ -87,3 +91,9 @@
|
|
|
87
91
|
zn-cols::part(base) {
|
|
88
92
|
gap: 70px;
|
|
89
93
|
}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
.form-control--pad .form-control__text {
|
|
97
|
+
padding-top: var(--zn-base-gap);
|
|
98
|
+
padding-left: var(--zn-base-gap);
|
|
99
|
+
}
|
|
@@ -7,4 +7,23 @@ describe('<zn-form-group>', () => {
|
|
|
7
7
|
|
|
8
8
|
expect(el).to.exist;
|
|
9
9
|
});
|
|
10
|
+
|
|
11
|
+
it('should render the chip slot under the help text', async () => {
|
|
12
|
+
const el = await fixture<HTMLElement>(html`
|
|
13
|
+
<zn-form-group label="Settings" help-text="Configure your settings">
|
|
14
|
+
<zn-chip slot="chip">New</zn-chip>
|
|
15
|
+
</zn-form-group>`);
|
|
16
|
+
|
|
17
|
+
const helpText = el.shadowRoot!.querySelector('[part="form-control-help-text"]')!;
|
|
18
|
+
const chip = el.shadowRoot!.querySelector('[part="form-control-chip"]')!;
|
|
19
|
+
|
|
20
|
+
expect(chip).to.exist;
|
|
21
|
+
expect(helpText.nextElementSibling).to.equal(chip);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should not render the chip container without a chip slot', async () => {
|
|
25
|
+
const el = await fixture<HTMLElement>(html` <zn-form-group label="Settings"></zn-form-group> `);
|
|
26
|
+
|
|
27
|
+
expect(el.shadowRoot!.querySelector('[part="form-control-chip"]')).to.not.exist;
|
|
28
|
+
});
|
|
10
29
|
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import ZnPreviewFrame from './preview-frame.component';
|
|
2
|
+
|
|
3
|
+
export * from './preview-frame.component';
|
|
4
|
+
export default ZnPreviewFrame;
|
|
5
|
+
|
|
6
|
+
ZnPreviewFrame.define('zn-preview-frame');
|
|
7
|
+
|
|
8
|
+
declare global {
|
|
9
|
+
interface HTMLElementTagNameMap {
|
|
10
|
+
'zn-preview-frame': ZnPreviewFrame;
|
|
11
|
+
}
|
|
12
|
+
}
|