@kubex/zinc 1.1.62 → 1.1.63

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.
@@ -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
+ ```
@@ -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.63",
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",
@@ -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
+ }