@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.
Files changed (36) hide show
  1. package/dist/custom-elements.json +3141 -1885
  2. package/dist/vscode.html-custom-data.json +102 -3
  3. package/dist/web-types.json +224 -6
  4. package/dist/zn.d.ts +235 -0
  5. package/dist/zn.min.js +795 -679
  6. package/docs/data/preview-frame-payload-error.json +4 -0
  7. package/docs/data/preview-frame-payload.json +6 -0
  8. package/docs/pages/components/form-group.md +12 -0
  9. package/docs/pages/components/preview-frame-demo.njk +138 -0
  10. package/docs/pages/components/preview-frame.md +105 -0
  11. package/docs/pages/components/remarkd-editor.md +103 -0
  12. package/docs/pages/components/toggle.md +2 -2
  13. package/package.json +3 -2
  14. package/src/components/file/file.component.ts +16 -1
  15. package/src/components/form-group/form-group.component.ts +24 -12
  16. package/src/components/form-group/form-group.scss +10 -0
  17. package/src/components/form-group/form-group.test.ts +19 -0
  18. package/src/components/preview-frame/index.ts +12 -0
  19. package/src/components/preview-frame/preview-frame.component.ts +246 -0
  20. package/src/components/preview-frame/preview-frame.scss +27 -0
  21. package/src/components/preview-frame/preview-frame.test.ts +268 -0
  22. package/src/components/query-builder/query-builder.component.ts +14 -6
  23. package/src/components/remarkd-editor/index.ts +12 -0
  24. package/src/components/remarkd-editor/remarkd-editor.component.ts +752 -0
  25. package/src/components/remarkd-editor/remarkd-editor.scss +241 -0
  26. package/src/components/remarkd-editor/remarkd-editor.test.ts +147 -0
  27. package/src/components/select/select.component.ts +4 -0
  28. package/src/components/select/select.test.ts +29 -0
  29. package/src/components/sp/sp.scss +5 -1
  30. package/src/components/tabs/tabs.component.ts +9 -4
  31. package/src/components/tabs/tabs.test.ts +42 -1
  32. package/src/components/tile/tile.scss +5 -1
  33. package/src/components/toggle/toggle.component.ts +3 -12
  34. package/src/components/toggle/toggle.test.ts +29 -0
  35. package/src/events/zn-error.ts +1 -1
  36. package/src/zinc.ts +2 -0
@@ -0,0 +1,4 @@
1
+ {
2
+ "fail": true,
3
+ "message": "Example error: the configured theme colour is invalid."
4
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "merchant": "Acme Donuts",
3
+ "amount": "£24.99",
4
+ "accent": "#6936f5",
5
+ "buttonLabel": "Pay £24.99"
6
+ }
@@ -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 will be submitted with form data.
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
- Use the `fallback` attribute to submit a specific value when the toggle is unchecked. This is useful when you need to ensure a value is always submitted for the field.
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.62",
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: (el: ZnFile) => el.files
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
+ }