@kubex/zinc 1.1.67 → 1.1.69
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 +1288 -20
- package/dist/vscode.html-custom-data.json +167 -11
- package/dist/web-types.json +360 -17
- package/dist/zn.d.ts +368 -20
- package/dist/zn.min.js +764 -645
- package/docs/pages/components/collapsible.md +6 -2
- package/docs/pages/components/preview-frame-demo.njk +33 -4
- package/docs/pages/components/preview-frame.md +20 -0
- package/docs/pages/components/theme-editor.md +269 -0
- package/docs/superpowers/plans/2026-08-03-theme-editor.md +1536 -0
- package/docs/superpowers/specs/2026-08-03-theme-editor-design.md +327 -0
- package/package.json +1 -1
- package/src/components/collapsible/collapsible.component.ts +21 -23
- package/src/components/collapsible/collapsible.scss +10 -0
- package/src/components/data-table/data-table.component.ts +49 -44
- package/src/components/data-table/data-table.scss +4 -0
- package/src/components/data-table/data-table.test.ts +42 -0
- package/src/components/defined-label/defined-label.component.ts +118 -95
- package/src/components/defined-label/defined-label.scss +40 -28
- package/src/components/dropdown/dropdown.component.ts +9 -0
- package/src/components/editor/editor.component.ts +20 -21
- package/src/components/file/file.component.ts +70 -0
- package/src/components/page-builder/page-builder.scss +1 -1
- package/src/components/preview-frame/preview-frame.component.ts +126 -16
- package/src/components/preview-frame/preview-frame.scss +27 -0
- package/src/components/preview-frame/preview-frame.test.ts +253 -0
- package/src/components/theme-editor/index.ts +12 -0
- package/src/components/theme-editor/theme-editor.component.ts +761 -0
- package/src/components/theme-editor/theme-editor.scss +309 -0
- package/src/components/theme-editor/theme-editor.test.ts +1584 -0
- package/src/events/events.ts +2 -0
- package/src/events/zn-theme-change.ts +13 -0
- package/src/events/zn-theme-submit.ts +9 -0
- package/src/types/web-test-runner-commands.d.ts +6 -0
- package/src/zinc.ts +1 -0
- package/tsconfig.json +7 -1
- package/web-test-runner.config.js +3 -1
|
@@ -109,11 +109,15 @@ Use the `count-element` attribute to count specific elements instead of all chil
|
|
|
109
109
|
|
|
110
110
|
### Flush Content
|
|
111
111
|
|
|
112
|
-
Use the `flush` attribute to remove the
|
|
112
|
+
Use the `flush` attribute to remove the horizontal padding from both the content
|
|
113
|
+
area and the header, so the caption and the content align with the container's
|
|
114
|
+
edges. This matches `flush` on [`zn-pane`](/components/pane/),
|
|
115
|
+
[`zn-panel`](/components/panel/) and [`zn-tabs`](/components/tabs/), and suits a
|
|
116
|
+
narrow column where an indent wastes width.
|
|
113
117
|
|
|
114
118
|
```html:preview
|
|
115
119
|
<zn-collapsible caption="Flush Content"
|
|
116
|
-
description="No
|
|
120
|
+
description="No horizontal padding on the header or content"
|
|
117
121
|
flush>
|
|
118
122
|
<zn-button>This button aligns with the left edge</zn-button>
|
|
119
123
|
<p>Content starts at the container edge</p>
|
|
@@ -9,12 +9,12 @@ permalink: /components/preview-frame-demo/index.html
|
|
|
9
9
|
---
|
|
10
10
|
{% raw %}
|
|
11
11
|
<div class="shell">
|
|
12
|
-
<p id="waiting">Waiting for
|
|
12
|
+
<p id="waiting">Waiting for the host…</p>
|
|
13
13
|
|
|
14
14
|
<div class="card" id="card" hidden>
|
|
15
15
|
<div class="card__accent"></div>
|
|
16
|
-
<h2 id="merchant"
|
|
17
|
-
<p class="card__amount" id="amount"
|
|
16
|
+
<h2 id="merchant">Merchant</h2>
|
|
17
|
+
<p class="card__amount" id="amount">£24.99</p>
|
|
18
18
|
<label>Card number
|
|
19
19
|
<input type="text" placeholder="4242 4242 4242 4242" disabled>
|
|
20
20
|
</label>
|
|
@@ -26,7 +26,7 @@ permalink: /components/preview-frame-demo/index.html
|
|
|
26
26
|
<input type="text" placeholder="123" disabled>
|
|
27
27
|
</label>
|
|
28
28
|
</div>
|
|
29
|
-
<button id="pay" type="button"
|
|
29
|
+
<button id="pay" type="button">Pay now</button>
|
|
30
30
|
</div>
|
|
31
31
|
</div>
|
|
32
32
|
|
|
@@ -106,6 +106,16 @@ permalink: /components/preview-frame-demo/index.html
|
|
|
106
106
|
font-weight: 600;
|
|
107
107
|
cursor: pointer;
|
|
108
108
|
}
|
|
109
|
+
|
|
110
|
+
body.is-dark {
|
|
111
|
+
background: #101014;
|
|
112
|
+
color: #f4f4f5;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
body.is-dark .card {
|
|
116
|
+
background: #18181b;
|
|
117
|
+
border-color: #33333a;
|
|
118
|
+
}
|
|
109
119
|
</style>
|
|
110
120
|
|
|
111
121
|
<script>
|
|
@@ -133,6 +143,25 @@ permalink: /components/preview-frame-demo/index.html
|
|
|
133
143
|
post({type: 'hp-preview:rendered'});
|
|
134
144
|
});
|
|
135
145
|
|
|
146
|
+
// The theme half of the protocol: applied independently of the config, so
|
|
147
|
+
// the editor can push values before any config exists.
|
|
148
|
+
window.addEventListener('message', e => {
|
|
149
|
+
const data = e.data;
|
|
150
|
+
if (data?.type !== 'hp-preview:theme') return;
|
|
151
|
+
|
|
152
|
+
const values = data.values || {};
|
|
153
|
+
document.body.classList.toggle('is-dark', data.mode === 'dark');
|
|
154
|
+
const card = document.getElementById('card');
|
|
155
|
+
card.hidden = false;
|
|
156
|
+
if (values.background) card.style.background = values.background;
|
|
157
|
+
if (values.accent) card.style.setProperty('--accent', values.accent);
|
|
158
|
+
if (values.radius !== undefined && values.radius !== '') {
|
|
159
|
+
card.style.borderRadius = values.radius + 'px';
|
|
160
|
+
}
|
|
161
|
+
document.getElementById('waiting').hidden = true;
|
|
162
|
+
post({type: 'hp-preview:rendered'});
|
|
163
|
+
});
|
|
164
|
+
|
|
136
165
|
post({type: 'hp-preview:ready'});
|
|
137
166
|
</script>
|
|
138
167
|
{% endraw %}
|
|
@@ -27,6 +27,26 @@ The example below embeds the [demo embed page](/components/preview-frame-demo/),
|
|
|
27
27
|
|
|
28
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
29
|
|
|
30
|
+
Set `fill` to make the panel fill its container's height instead — `min-height` then becomes a floor rather than the height, for hosts (like [`zn-theme-editor`](/components/theme-editor/)) whose layout already stretches the panel to match a taller sibling. `zoom` is ignored when `fill` is set, since its oversize maths needs a known pixel height to scale against, which `fill` deliberately doesn't have.
|
|
31
|
+
|
|
32
|
+
`device` constrains and centres the preview to `desktop` (full width), `tablet`
|
|
33
|
+
(768px) or `mobile` (390px), resizing the iframe itself so the embedded page's
|
|
34
|
+
media queries fire. `setTheme({mode, values})` posts an `hp-preview:theme` message and
|
|
35
|
+
replays it after each ready handshake, which is how
|
|
36
|
+
[`zn-theme-editor`](/components/theme-editor/) drives a live preview.
|
|
37
|
+
|
|
38
|
+
The panel behind the preview is a dot grid, so the frame's bounds stay visible
|
|
39
|
+
instead of blending into the page — at `tablet` or `mobile` the dots fill the
|
|
40
|
+
gutters either side of the narrowed iframe. Tune it with
|
|
41
|
+
`--zn-preview-frame-dot-spacing` (default `20px`) and
|
|
42
|
+
`--zn-preview-frame-dot-opacity` (default `0.08`). The iframe itself is given an
|
|
43
|
+
opaque background, so the dots never show through the previewed page.
|
|
44
|
+
|
|
45
|
+
Set `backdrop="panel"` to swap the dot grid for a plain `rgb(var(--zn-panel))`
|
|
46
|
+
fill — used by [`zn-theme-editor`](/components/theme-editor/)'s `standalone`
|
|
47
|
+
mode, where the frame is already inside its own bordered panel. `backdrop="dots"`
|
|
48
|
+
is the default.
|
|
49
|
+
|
|
30
50
|
## Live Form Updates
|
|
31
51
|
|
|
32
52
|
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.
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
---
|
|
2
|
+
meta:
|
|
3
|
+
title: Theme Editor
|
|
4
|
+
description: Theme controls on the left, a live preview frame on the right, with light/dark and device switching.
|
|
5
|
+
layout: component
|
|
6
|
+
fullWidth: true
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
Put form controls in the default slot and give each a `name`. Every control is
|
|
10
|
+
per-mode: it holds a light value and a dark value, and the sidebar's mode
|
|
11
|
+
toggle swaps which one the control displays. Changing a control harvests every
|
|
12
|
+
named control's *currently displayed* value and pushes the active mode's set
|
|
13
|
+
into the embedded [preview frame](/components/preview-frame/) as an
|
|
14
|
+
`hp-preview:theme` message — no save, no fetch, no page reload.
|
|
15
|
+
|
|
16
|
+
The controls column runs the full height of the component, with its own
|
|
17
|
+
header row on top holding the light/dark mode toggle beside its caption. The
|
|
18
|
+
toolbar sits opposite it, above the preview only, and switches the width the
|
|
19
|
+
preview renders at: desktop (full width), tablet (768px) or mobile (390px).
|
|
20
|
+
Because the iframe itself is resized, the embedded page's own media queries
|
|
21
|
+
fire.
|
|
22
|
+
|
|
23
|
+
Set `controls-caption` and `preview-caption` to label each column's header
|
|
24
|
+
row — both are empty by default, rendering no text (the controls column's
|
|
25
|
+
header row still renders either way, so the two columns stay aligned).
|
|
26
|
+
|
|
27
|
+
The preview always fills its column, leaving no dead space beneath it.
|
|
28
|
+
`min-height` (default `480`) is a floor for that column, not a fixed height —
|
|
29
|
+
it's still forwarded to the [preview frame](/components/preview-frame/), which
|
|
30
|
+
uses it the same way.
|
|
31
|
+
|
|
32
|
+
Controls can be organized into tabs with collapsible groups inside each —
|
|
33
|
+
`sections` with `groups` below is the canonical arrangement:
|
|
34
|
+
|
|
35
|
+
```html:preview
|
|
36
|
+
<zn-theme-editor
|
|
37
|
+
id="theme-editor-demo"
|
|
38
|
+
src="/components/preview-frame-demo/"
|
|
39
|
+
min-height="420"
|
|
40
|
+
controls-caption="Theme Builder"
|
|
41
|
+
preview-caption="Live Preview"
|
|
42
|
+
sections='[
|
|
43
|
+
{"name":"colors","caption":"Colors","groups":[
|
|
44
|
+
{"name":"brand","caption":"Brand","open":true},
|
|
45
|
+
{"name":"background","caption":"Background"}
|
|
46
|
+
]},
|
|
47
|
+
{"name":"shapes","caption":"Shapes","groups":[
|
|
48
|
+
{"name":"radius","caption":"Radius"}
|
|
49
|
+
]}
|
|
50
|
+
]'>
|
|
51
|
+
<zn-input slot="brand" name="accent" label="Accent" value="#6936f5" dark-value="#f5c542" type="color"></zn-input>
|
|
52
|
+
<zn-input slot="background" name="background" label="Background" value="#ffffff" dark-value="#18181b" type="color"></zn-input>
|
|
53
|
+
<zn-input slot="radius" name="radius" label="Corner radius" type="number" value="4"></zn-input>
|
|
54
|
+
</zn-theme-editor>
|
|
55
|
+
|
|
56
|
+
<script>
|
|
57
|
+
document.getElementById('theme-editor-demo').frameOrigin = location.origin;
|
|
58
|
+
</script>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
:::tip
|
|
62
|
+
`frame-origin` must match the embed's origin exactly — messages from any other
|
|
63
|
+
origin are ignored. The example sets it at runtime because the docs site is
|
|
64
|
+
same-origin.
|
|
65
|
+
:::
|
|
66
|
+
|
|
67
|
+
## Dark values
|
|
68
|
+
|
|
69
|
+
Give a control a `dark-value` attribute alongside `value` to author its dark
|
|
70
|
+
variant, as `accent` and `background` do above. A control with no
|
|
71
|
+
`dark-value` falls back to its `value` in dark mode, so adding dark support to
|
|
72
|
+
an existing editor is additive:
|
|
73
|
+
|
|
74
|
+
```html
|
|
75
|
+
<zn-color-select name="accent" label="Accent" value="#6936f5" dark-value="#f5c542"></zn-color-select>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
For boolean controls (`zn-checkbox`, `zn-toggle`, `input[type=checkbox]`),
|
|
79
|
+
two separate rules apply depending on whether `dark-value` is present:
|
|
80
|
+
|
|
81
|
+
- **Present** — parsed as truthy: `dark-value="1"` or `dark-value="true"`
|
|
82
|
+
seeds the dark state checked, any other value seeds it unchecked.
|
|
83
|
+
- **Absent** — the dark state mirrors the control's own light `checked`
|
|
84
|
+
state, whatever that is (including `true`), the same fallback rule
|
|
85
|
+
non-boolean controls get.
|
|
86
|
+
|
|
87
|
+
## Reading and Persisting Values
|
|
88
|
+
|
|
89
|
+
Every change emits `zn-theme-change` with `{values, mode, device}`, where
|
|
90
|
+
`values` is `{light, dark}` — both full sets, regardless of which mode is
|
|
91
|
+
active:
|
|
92
|
+
|
|
93
|
+
```js
|
|
94
|
+
editor.addEventListener('zn-theme-change', event => {
|
|
95
|
+
console.log(event.detail.values.light, event.detail.values.dark);
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Set `action` to persist automatically instead — both sets are POSTed as
|
|
100
|
+
`FormData` on a longer debounce (`save-debounce`, default `1000`ms), with every
|
|
101
|
+
key bracketed by mode: `light[accent]`, `dark[accent]`, `light[radius]`,
|
|
102
|
+
`dark[radius]`. `mode` and `device` are view state and are never saved.
|
|
103
|
+
|
|
104
|
+
```html
|
|
105
|
+
<zn-theme-editor src="/embed?t=..." frame-origin="https://pay.example" action="/theme/save">
|
|
106
|
+
<zn-color-select name="accent" label="Accent" value="#6936f5" dark-value="#f5c542"></zn-color-select>
|
|
107
|
+
</zn-theme-editor>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Saves are serialized: if changes land while a POST is in flight, exactly one
|
|
111
|
+
further save runs afterwards with the latest values.
|
|
112
|
+
|
|
113
|
+
### Manual saving with a submit button
|
|
114
|
+
|
|
115
|
+
Set `submit-label` to render a built-in save button at the right of the toolbar,
|
|
116
|
+
opposite the device controls —
|
|
117
|
+
empty (the default) renders no button. Add `manual` to disable the debounced
|
|
118
|
+
auto-save entirely, so persistence only happens when the button is clicked;
|
|
119
|
+
the live preview keeps updating on every change either way, only saving
|
|
120
|
+
becomes explicit:
|
|
121
|
+
|
|
122
|
+
```html
|
|
123
|
+
<zn-theme-editor
|
|
124
|
+
src="/embed?t=..." frame-origin="https://pay.example"
|
|
125
|
+
manual
|
|
126
|
+
submit-label="Save theme">
|
|
127
|
+
<zn-color-select name="accent" label="Accent" value="#6936f5" dark-value="#f5c542"></zn-color-select>
|
|
128
|
+
</zn-theme-editor>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Clicking the button flushes any pending edit, then saves immediately through
|
|
132
|
+
the same single-slot save queue used for auto-save — it never opens a second
|
|
133
|
+
concurrent request. With `action` set, a successful save emits `zn-theme-submit`
|
|
134
|
+
carrying `{values}` (both `light` and `dark` sets); a failed save surfaces
|
|
135
|
+
through the same error strip and `zn-error` as auto-save. With no `action`,
|
|
136
|
+
nothing is POSTed but `zn-theme-submit` still fires, so a host can persist the
|
|
137
|
+
values itself:
|
|
138
|
+
|
|
139
|
+
```js
|
|
140
|
+
editor.addEventListener('zn-theme-submit', event => {
|
|
141
|
+
console.log(event.detail.values.light, event.detail.values.dark);
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The button shows a loading state while its save is in flight and ignores
|
|
146
|
+
further clicks until it resolves.
|
|
147
|
+
|
|
148
|
+
## Controls
|
|
149
|
+
|
|
150
|
+
Any Zinc form control works. Controls must carry `name` as an **attribute** —
|
|
151
|
+
`zn-checkbox` and `zn-toggle` contribute their `checked` state as a boolean,
|
|
152
|
+
everything else contributes `value`. Disabled and unnamed controls are skipped.
|
|
153
|
+
Every control is per-mode with no opt-in attribute, so mode-independent values
|
|
154
|
+
(like `radius` above) end up duplicated across `light` and `dark` — accepted
|
|
155
|
+
knowingly to keep the rule uniform.
|
|
156
|
+
|
|
157
|
+
The value store is the theme, not a mirror of the currently visible controls:
|
|
158
|
+
removing a control from the markup keeps its key in both value sets, and it
|
|
159
|
+
keeps being pushed to the preview and included in saves. Clear it server-side
|
|
160
|
+
or re-add the control under the same name to edit it again.
|
|
161
|
+
|
|
162
|
+
The `footer` slot holds actions beneath the controls:
|
|
163
|
+
|
|
164
|
+
```html
|
|
165
|
+
<zn-theme-editor src="/embed?t=..." frame-origin="https://pay.example">
|
|
166
|
+
<zn-color-select name="accent" label="Accent"></zn-color-select>
|
|
167
|
+
<zn-button slot="footer">Save</zn-button>
|
|
168
|
+
</zn-theme-editor>
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Set the controls column width with `--zn-theme-editor-controls-width`
|
|
172
|
+
(default `343px`, matching page-builder's palette). Below 768px the columns
|
|
173
|
+
stack.
|
|
174
|
+
|
|
175
|
+
## Grouping controls into tabs and collapsibles
|
|
176
|
+
|
|
177
|
+
Set `sections` to a JSON array of `{name, caption, groups}`. Each section
|
|
178
|
+
becomes a `zn-tabs` tab; each entry in its `groups` — `{name, caption,
|
|
179
|
+
description?, open?}` — becomes a collapsible inside that tab, and a control
|
|
180
|
+
is assigned to a group with `slot="<group-name>"`:
|
|
181
|
+
|
|
182
|
+
```html
|
|
183
|
+
<zn-theme-editor
|
|
184
|
+
src="/embed?t=..." frame-origin="https://pay.example"
|
|
185
|
+
sections='[
|
|
186
|
+
{"name":"colors","caption":"Colors","groups":[
|
|
187
|
+
{"name":"brand","caption":"Brand","open":true},
|
|
188
|
+
{"name":"semantic","caption":"Semantic"}
|
|
189
|
+
]},
|
|
190
|
+
{"name":"shapes","caption":"Shapes","groups":[{"name":"radius","caption":"Radius"}]}
|
|
191
|
+
]'>
|
|
192
|
+
<zn-color-select slot="brand" name="accent" label="Accent" value="#6936f5"></zn-color-select>
|
|
193
|
+
<zn-input slot="radius" name="radius" label="Corner radius" type="number" value="4"></zn-input>
|
|
194
|
+
</zn-theme-editor>
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
A group with no assigned controls renders no collapsible, and a section none
|
|
198
|
+
of whose groups are populated renders no tab — the same "no chrome for empty
|
|
199
|
+
config" rule flat sections already followed. Every tab's panel stays mounted
|
|
200
|
+
while hidden (`zn-tabs` toggles visibility, never removes a panel), so
|
|
201
|
+
switching tabs never drops a value out of the theme, a preview push or a save.
|
|
202
|
+
|
|
203
|
+
### Flat sections (no groups)
|
|
204
|
+
|
|
205
|
+
A section can omit `groups` and just take controls directly via
|
|
206
|
+
`slot="<section-name>"`, exactly as before nesting existed. `section-layout`
|
|
207
|
+
then decides the presentation — stacked `zn-collapsible`s (`"collapsible"`,
|
|
208
|
+
the default) or a `zn-tabs` strip (`"tabs"`) — and is otherwise ignored: once
|
|
209
|
+
*any* section has a populated `groups`, every section renders as a nested tab
|
|
210
|
+
regardless of `section-layout`.
|
|
211
|
+
|
|
212
|
+
```html
|
|
213
|
+
<zn-theme-editor
|
|
214
|
+
src="/embed?t=..." frame-origin="https://pay.example"
|
|
215
|
+
section-layout="tabs"
|
|
216
|
+
sections='[{"name":"colors","caption":"Colors"},{"name":"layout","caption":"Layout"}]'>
|
|
217
|
+
<zn-color-select slot="colors" name="accent" label="Accent" value="#6936f5"></zn-color-select>
|
|
218
|
+
<zn-input slot="layout" name="radius" label="Corner radius" type="number" value="4"></zn-input>
|
|
219
|
+
</zn-theme-editor>
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
An author can also slot their own `zn-collapsible` into any named slot (or the
|
|
223
|
+
default slot) instead of relying on `groups` — its presentation is then
|
|
224
|
+
entirely its own; add `flush` yourself if you want it to run the full width of
|
|
225
|
+
the column.
|
|
226
|
+
|
|
227
|
+
## Preview sources
|
|
228
|
+
|
|
229
|
+
Set `sources` to a JSON array of `{label, src}` to render a dropdown in the
|
|
230
|
+
toolbar, beside the device buttons, for switching which page the preview
|
|
231
|
+
loads:
|
|
232
|
+
|
|
233
|
+
```html
|
|
234
|
+
<zn-theme-editor
|
|
235
|
+
src="/embed?t=..." frame-origin="https://pay.example"
|
|
236
|
+
sources='[{"label":"Checkout","src":"/embed/checkout"},{"label":"Storefront","src":"/embed/storefront"}]'>
|
|
237
|
+
<zn-color-select name="accent" label="Accent" value="#6936f5"></zn-color-select>
|
|
238
|
+
</zn-theme-editor>
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
The first entry is the initial selection — it wins over an explicit `src` when
|
|
242
|
+
`sources` is non-empty. Selecting a different entry reloads the iframe; nothing
|
|
243
|
+
further is needed to keep the theme, since the frame retains the last pushed
|
|
244
|
+
payload and replays it once the reloaded page re-announces itself ready. Leave
|
|
245
|
+
`sources` unset (the default) and `src` behaves exactly as it always has, with
|
|
246
|
+
no dropdown rendered.
|
|
247
|
+
|
|
248
|
+
## Collapsing the controls column
|
|
249
|
+
|
|
250
|
+
Set `controls-collapsed` to hide the controls column, or click the chevron
|
|
251
|
+
toggle that sits on the seam between the columns. Collapsing is purely a
|
|
252
|
+
layout change — it never affects harvested values or pushes a new theme to
|
|
253
|
+
the preview. Below the 768px stacked breakpoint the toggle is hidden, since
|
|
254
|
+
there's no side-by-side seam to tuck into — the editor also un-collapses
|
|
255
|
+
itself if it's already showing `controls-collapsed` when the layout narrows
|
|
256
|
+
that far, so the controls are never stuck unreachable.
|
|
257
|
+
|
|
258
|
+
## Standalone panel
|
|
259
|
+
|
|
260
|
+
Set `standalone` to present the editor as its own bordered, rounded panel —
|
|
261
|
+
useful when it isn't already embedded in a page shell that provides that
|
|
262
|
+
chrome. It also switches the preview's [backdrop](/components/preview-frame/)
|
|
263
|
+
from the dot grid to a plain panel to match.
|
|
264
|
+
|
|
265
|
+
```html
|
|
266
|
+
<zn-theme-editor src="/embed?t=..." frame-origin="https://pay.example" standalone>
|
|
267
|
+
<zn-color-select name="accent" label="Accent" value="#6936f5" dark-value="#f5c542"></zn-color-select>
|
|
268
|
+
</zn-theme-editor>
|
|
269
|
+
```
|