@kubex/zinc 1.1.72 → 1.1.74

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,7 @@
1
+ {
2
+ "merchant": "Acme Donuts",
3
+ "amount": "£24.99",
4
+ "accent": "#6936f5",
5
+ "buttonLabel": "Pay £24.99",
6
+ "lines": 16
7
+ }
@@ -26,6 +26,7 @@ permalink: /components/preview-frame-demo/index.html
26
26
  <input type="text" placeholder="123" disabled>
27
27
  </label>
28
28
  </div>
29
+ <div class="card__lines" id="lines"></div>
29
30
  <button id="pay" type="button">Pay now</button>
30
31
  </div>
31
32
  </div>
@@ -38,6 +39,13 @@ permalink: /components/preview-frame-demo/index.html
38
39
  justify-content: center;
39
40
  }
40
41
 
42
+ /* A page taller than the frame: top-aligned, or the overflow clips the top. */
43
+ .shell--tall {
44
+ height: auto;
45
+ align-items: flex-start;
46
+ padding: 24px 0;
47
+ }
48
+
41
49
  #waiting {
42
50
  color: rgb(var(--zn-color-muted-text));
43
51
  }
@@ -91,6 +99,20 @@ permalink: /components/preview-frame-demo/index.html
91
99
  gap: 12px;
92
100
  }
93
101
 
102
+ .card__lines:not(:empty) {
103
+ display: flex;
104
+ flex-direction: column;
105
+ gap: 8px;
106
+ font-size: 0.8125rem;
107
+ }
108
+
109
+ .card__line {
110
+ display: flex;
111
+ justify-content: space-between;
112
+ padding-bottom: 8px;
113
+ border-bottom: 1px solid rgb(var(--zn-border-color));
114
+ }
115
+
94
116
  .card__row label {
95
117
  flex: 1;
96
118
  }
@@ -124,6 +146,26 @@ permalink: /components/preview-frame-demo/index.html
124
146
  // should use the host's origin instead of '*'.
125
147
  const post = message => window.parent.postMessage(message, '*');
126
148
 
149
+ // Reporting a height lets the host grow the frame to fit, so an overflowing
150
+ // page is scrolled by the panel instead of inside the frame. Measured after a
151
+ // frame so the layout has settled.
152
+ const postRendered = () => requestAnimationFrame(() => post({
153
+ type: 'hp-preview:rendered',
154
+ height: document.documentElement.scrollHeight
155
+ }));
156
+
157
+ const renderLines = count => {
158
+ const lines = document.getElementById('lines');
159
+ lines.innerHTML = '';
160
+ document.querySelector('.shell').classList.toggle('shell--tall', count > 0);
161
+ for (let i = 1; i <= count; i++) {
162
+ const line = document.createElement('div');
163
+ line.className = 'card__line';
164
+ line.innerHTML = `<span>Line item ${i}</span><span>£${(i * 1.5).toFixed(2)}</span>`;
165
+ lines.append(line);
166
+ }
167
+ };
168
+
127
169
  window.addEventListener('message', e => {
128
170
  const data = e.data;
129
171
  if (data?.type !== 'hp-preview:config') return;
@@ -140,7 +182,8 @@ permalink: /components/preview-frame-demo/index.html
140
182
  document.getElementById('merchant').textContent = data.merchant || 'Merchant';
141
183
  document.getElementById('amount').textContent = data.amount || '';
142
184
  document.getElementById('pay').textContent = data.buttonLabel || 'Pay now';
143
- post({type: 'hp-preview:rendered'});
185
+ renderLines(Number(data.lines) || 0);
186
+ postRendered();
144
187
  });
145
188
 
146
189
  // The theme half of the protocol: applied independently of the config, so
@@ -159,7 +202,7 @@ permalink: /components/preview-frame-demo/index.html
159
202
  card.style.borderRadius = values.radius + 'px';
160
203
  }
161
204
  document.getElementById('waiting').hidden = true;
162
- post({type: 'hp-preview:rendered'});
205
+ postRendered();
163
206
  });
164
207
 
165
208
  post({type: 'hp-preview:ready'});
@@ -47,6 +47,85 @@ fill — used by [`zn-theme-editor`](/components/theme-editor/)'s `standalone`
47
47
  mode, where the frame is already inside its own bordered panel. `backdrop="dots"`
48
48
  is the default.
49
49
 
50
+ ## Interactivity
51
+
52
+ The preview is display-only: the iframe takes `pointer-events: none`, so clicks
53
+ never reach the embedded page and the previewed form can't be submitted or
54
+ navigated away from inside the frame. The embed is cross-origin, so its own
55
+ handlers can't be cancelled from out here — blocking pointer input is the only
56
+ way to stop them, and hover goes with it. Scrolling doesn't: an overflowing page
57
+ is scrolled by the panel instead, as below.
58
+
59
+ Set `interactive` when the embed is meant to be used rather than looked at.
60
+
61
+ ```html:preview
62
+ <zn-preview-frame
63
+ id="preview-frame-interactive"
64
+ src="/components/preview-frame-demo/"
65
+ data-uri="/data/preview-frame-payload.json"
66
+ watch="#preview-frame-interactive-none"
67
+ interactive></zn-preview-frame>
68
+
69
+ <script>
70
+ document.getElementById('preview-frame-interactive').frameOrigin = location.origin;
71
+ </script>
72
+ ```
73
+
74
+ ## Overflowing Content
75
+
76
+ A page taller than the panel is scrolled by the panel, not inside the frame. The
77
+ frame can't do it itself: a cross-origin document can't be scrolled from the host
78
+ (`contentWindow.scrollTo` is blocked), and with pointer input off the wheel never
79
+ reaches it anyway. So the frame is instead laid out at its full content height —
80
+ nothing scrolls inside it — and the panel scrolls that.
81
+
82
+ For the frame to be sized that way, the embed reports its height alongside
83
+ `hp-preview:rendered`:
84
+
85
+ ```js
86
+ post({
87
+ type: 'hp-preview:rendered',
88
+ height: document.documentElement.scrollHeight
89
+ });
90
+ ```
91
+
92
+ A page that grows after its first render — a revealed section, a lazy-loaded
93
+ image — reports the new height on its own:
94
+
95
+ ```js
96
+ post({type: 'hp-preview:height', height: document.documentElement.scrollHeight});
97
+ ```
98
+
99
+ Heights that aren't a positive number are ignored, as is one reported while the
100
+ error overlay is up. A height under the panel's own is kept but changes nothing:
101
+ the frame still fills the panel, so the backdrop never shows under a short page.
102
+ The height is dropped whenever `src` changes, since the next page has its own.
103
+
104
+ The example below previews a long itemised page, so the panel scrolls. Scrolling
105
+ works with the frame inert — clicking `Pay` still does nothing.
106
+
107
+ ```html:preview
108
+ <zn-preview-frame
109
+ id="preview-frame-tall"
110
+ src="/components/preview-frame-demo/"
111
+ data-uri="/data/preview-frame-payload-tall.json"
112
+ watch="#preview-frame-tall-none"></zn-preview-frame>
113
+
114
+ <script>
115
+ document.getElementById('preview-frame-tall').frameOrigin = location.origin;
116
+ </script>
117
+ ```
118
+
119
+ :::tip
120
+ A **same-origin** embed doesn't need to report anything — its document is
121
+ measured directly. The measurement only ever grows the frame: the frame's own
122
+ height feeds back into it, so a value at or under the current height is ignored
123
+ rather than flipping the frame between two sizes forever. An embed that shrinks
124
+ has to report its height to be followed back down, and one whose root is sized
125
+ to the viewport (`html {height: 100%}`) can't be measured at all — it reports or
126
+ it clips.
127
+ :::
128
+
50
129
  ## Live Form Updates
51
130
 
52
131
  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.
@@ -200,8 +200,15 @@ Use the `active` attribute to specify which tab should be active by default.
200
200
 
201
201
  ### Storage Persistence
202
202
 
203
- Use `store-key` to persist the active tab selection across page reloads. By default, uses session storage with a
204
- 5-minute TTL. Use `local-storage` for persistent storage beyond the session.
203
+ Use `store-key` to persist the active tab selection.
204
+
205
+ Each tab you open becomes its own history entry, so Back and Forward step through the tabs you visited before leaving the
206
+ page, and the entry the page was first rendered on returns you to the default tab. Reloading restores the tab you were
207
+ on. Navigating to the page — including a client-side navigation back to somewhere visited earlier — starts from the
208
+ default tab.
209
+
210
+ Selections are held in session storage, keyed by location. Use `local-storage` when a selection should survive fresh
211
+ navigation and browser sessions; it is then keyed by `store-key` alone and takes no part in history.
205
212
 
206
213
  ```html:preview
207
214
  <zn-tabs flush store-key="my-tabs">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubex/zinc",
3
- "version": "1.1.72",
3
+ "version": "1.1.74",
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",
@@ -82,7 +82,6 @@
82
82
  "custom-element-vuejs-integration": "^1.4.0",
83
83
  "del": "^8.0.0",
84
84
  "esbuild": "^0.27.7",
85
- "esbuild-plugin-replace": "^1.4.0",
86
85
  "esbuild-sass-plugin": "^3.7.0",
87
86
  "eslint": "^8.57.1",
88
87
  "eslint-plugin-chai-expect": "^3.1.0",
package/scripts/build.js CHANGED
@@ -9,7 +9,6 @@ import * as path from 'path';
9
9
  import chalk from 'chalk';
10
10
  import fs from 'fs/promises';
11
11
  import {readFileSync} from 'fs';
12
- import {replace} from 'esbuild-plugin-replace';
13
12
  import {sassPlugin} from 'esbuild-sass-plugin';
14
13
  import getPort, {portNumbers} from 'get-port';
15
14
  import postCSS from 'postcss';
@@ -144,7 +143,11 @@ async function buildTheSource()
144
143
  entryPoints,
145
144
  define: {
146
145
  // Floating UI requires this to be set
147
- 'process.env.NODE_ENV': '"production"'
146
+ 'process.env.NODE_ENV': '"production"',
147
+ // Substituted natively by esbuild. Don't move this to an onLoad-based replace plugin: those
148
+ // read every module in the graph from Node with no concurrency cap, which exhausts the file
149
+ // table (ENFILE) once deps with thousands of modules (echarts, lucide) are bundled.
150
+ __ZINC_VERSION__: zincVersion
148
151
  },
149
152
  bundle: true,
150
153
  splitting: true,
@@ -165,9 +168,6 @@ async function buildTheSource()
165
168
  console.error(err);
166
169
  }
167
170
  }
168
- }),
169
- replace({
170
- __ZINC_VERSION__: zincVersion
171
171
  })
172
172
  ]
173
173
  };
@@ -149,21 +149,15 @@ export default class ZnDefinedLabel extends ZincElement implements ZincFormContr
149
149
  private handleInputValueChange(e: Event) {
150
150
  const target = e.target as HTMLInputElement | HTMLSelectElement;
151
151
  this.inputValue = target.value.toLowerCase();
152
-
153
- if (target.hasAttribute('data-label')) this.value = target.getAttribute('data-label') ?? "";
154
152
  }
155
153
 
156
- private handleFormSubmit(e: Event, label?: string) {
157
- // Sync from the submitted row so a partially typed filter never wins over
158
- // the row's actual label key, and stale values from other rows are discarded
154
+ private handleFormSubmit(e: Event, label: string) {
155
+ // Submit the key shown on the clicked row a predefined name, or the typed
156
+ // key for the custom row and discard values left behind on other rows
159
157
  const row = (e.currentTarget as HTMLElement).closest('.defined-label__row');
160
158
  const control = row?.querySelector<ZnInput | ZnSelect>('.defined-label__value');
161
- if (label) {
162
- this.value = label;
163
- }
164
- if (control) {
165
- this.inputValue = String(control.value ?? '').toLowerCase();
166
- }
159
+ this.value = label;
160
+ this.inputValue = control ? String(control.value ?? '').toLowerCase() : '';
167
161
 
168
162
  const form = this.formControlController.getForm();
169
163
 
@@ -181,7 +175,6 @@ export default class ZnDefinedLabel extends ZincElement implements ZincFormContr
181
175
  <zn-select
182
176
  part="input-value"
183
177
  class="defined-label__value"
184
- data-label="${label.name}"
185
178
  size="small"
186
179
  @zn-change="${this.handleInputValueChange}"
187
180
  @zn-input="${this.handleInputValueChange}">
@@ -197,16 +190,15 @@ export default class ZnDefinedLabel extends ZincElement implements ZincFormContr
197
190
  class="defined-label__value"
198
191
  type="text"
199
192
  placeholder="Label Value"
200
- data-label="${label.name}"
201
193
  size="small"
202
194
  @zn-change="${this.handleInputValueChange}"
203
195
  @zn-input="${this.handleInputValueChange}"></zn-input>`;
204
196
  }
205
197
 
206
- private renderRow(name: string, control: TemplateResult, label?: string): TemplateResult {
198
+ private renderRow(label: string, control: TemplateResult): TemplateResult {
207
199
  return html`
208
200
  <div class="defined-label__row">
209
- <small class="defined-label__row-label">${name}</small>
201
+ <small class="defined-label__row-label">${label}</small>
210
202
  <div class="defined-label__row-controls">
211
203
  ${control}
212
204
  <zn-button
@@ -219,6 +211,7 @@ export default class ZnDefinedLabel extends ZincElement implements ZincFormContr
219
211
 
220
212
  render() {
221
213
  const labels = this.getFilteredLabels();
214
+ const showCustom = this.allowCustom && this.value !== '' && !labels.some(label => label.name === this.value);
222
215
 
223
216
  return html`
224
217
  <zn-dropdown class="defined-label__dropdown" sync="width">
@@ -243,11 +236,11 @@ export default class ZnDefinedLabel extends ZincElement implements ZincFormContr
243
236
 
244
237
  <div class="defined-label__panel">
245
238
  ${labels.length > 0
246
- ? labels.map(label => this.renderRow(label.name, this.renderValueControl(label), label.name))
239
+ ? labels.map(label => this.renderRow(label.name, this.renderValueControl(label)))
247
240
  : html`
248
241
  <div class="defined-label__empty">Cannot find any predefined labels</div>`}
249
242
 
250
- ${this.allowCustom && this.value !== '' ? this.renderRow(this.value, html`
243
+ ${showCustom ? this.renderRow(this.value, html`
251
244
  <zn-input
252
245
  part="input-value"
253
246
  class="defined-label__value"
@@ -1,5 +1,55 @@
1
1
  import '../../../dist/zn.min.js';
2
- import { expect, fixture, html } from '@open-wc/testing';
2
+ import { expect, fixture, html, waitUntil } from '@open-wc/testing';
3
+ import type ZnDefinedLabel from './defined-label.component';
4
+
5
+ const predefined = [
6
+ { name: 'outbound', options: [] },
7
+ { name: 'another', options: ['one', 'two'] }
8
+ ];
9
+
10
+ async function fixtureWithForm() {
11
+ const form: HTMLFormElement = await fixture(html`
12
+ <form>
13
+ <zn-defined-label allow-custom name="label" .predefinedLabels="${predefined}"></zn-defined-label>
14
+ </form>`);
15
+
16
+ const el = form.querySelector<ZnDefinedLabel>('zn-defined-label')!;
17
+ await el.updateComplete;
18
+
19
+ const submissions: string[] = [];
20
+ form.addEventListener('submit', event => {
21
+ event.preventDefault();
22
+ submissions.push([...new FormData(form).entries()].map(([key, value]) => `${key}=${String(value)}`).join('&'));
23
+ });
24
+
25
+ return { el, submissions };
26
+ }
27
+
28
+ async function type(el: ZnDefinedLabel, control: Element, text: string) {
29
+ const native = control.shadowRoot!.querySelector('input')!;
30
+ native.focus();
31
+
32
+ for (const character of text) {
33
+ native.value += character;
34
+ native.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
35
+ await el.updateComplete;
36
+ }
37
+ }
38
+
39
+ function rows(el: ZnDefinedLabel) {
40
+ return [...(el.shadowRoot?.querySelectorAll('.defined-label__row') ?? [])];
41
+ }
42
+
43
+ function rowLabels(el: ZnDefinedLabel) {
44
+ return rows(el).map(row => row.querySelector('.defined-label__row-label')?.textContent);
45
+ }
46
+
47
+ async function clickAdd(row: Element, submissions: string[]) {
48
+ const button = row.querySelector('zn-button')!.shadowRoot!.querySelector('button')!;
49
+ button.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, composed: true }));
50
+ button.dispatchEvent(new MouseEvent('click', { bubbles: true, composed: true }));
51
+ await waitUntil(() => submissions.length > 0, 'the form never submitted');
52
+ }
3
53
 
4
54
  describe('<zn-defined-label>', () => {
5
55
  it('should render a component', async () => {
@@ -7,4 +57,60 @@ describe('<zn-defined-label>', () => {
7
57
 
8
58
  expect(el).to.exist;
9
59
  });
60
+
61
+ it('should offer a custom row alongside partially matching predefined labels', async () => {
62
+ const { el } = await fixtureWithForm();
63
+
64
+ await type(el, el.input, 'out');
65
+
66
+ expect(rowLabels(el)).to.eql(['outbound', 'out']);
67
+ });
68
+
69
+ it('should keep the typed key when a value is entered on a partially matching predefined row', async () => {
70
+ const { el } = await fixtureWithForm();
71
+
72
+ await type(el, el.input, 'out');
73
+ await type(el, rows(el)[0].querySelector('.defined-label__value')!, 'x');
74
+
75
+ expect(el.value).to.equal('out');
76
+ expect(rowLabels(el)).to.eql(['outbound', 'out']);
77
+ });
78
+
79
+ it('should submit the typed key from the custom row, not a partially matching predefined label', async () => {
80
+ const { el, submissions } = await fixtureWithForm();
81
+
82
+ await type(el, el.input, 'out');
83
+ await type(el, rows(el)[1].querySelector('.defined-label__value')!, 'bob');
84
+ await clickAdd(rows(el)[1], submissions);
85
+
86
+ expect(submissions[0]).to.equal('label=out:bob');
87
+ });
88
+
89
+ it('should submit the predefined key when its own row is added', async () => {
90
+ const { el, submissions } = await fixtureWithForm();
91
+
92
+ await type(el, el.input, 'out');
93
+ await type(el, rows(el)[0].querySelector('.defined-label__value')!, 'x');
94
+ await clickAdd(rows(el)[0], submissions);
95
+
96
+ expect(submissions[0]).to.equal('label=outbound:x');
97
+ });
98
+
99
+ it('should discard a value left on another row', async () => {
100
+ const { el, submissions } = await fixtureWithForm();
101
+
102
+ await type(el, el.input, 'out');
103
+ await type(el, rows(el)[0].querySelector('.defined-label__value')!, 'x');
104
+ await clickAdd(rows(el)[1], submissions);
105
+
106
+ expect(submissions[0]).to.equal('label=out');
107
+ });
108
+
109
+ it('should not duplicate a predefined label as a custom row', async () => {
110
+ const { el } = await fixtureWithForm();
111
+
112
+ await type(el, el.input, 'outbound');
113
+
114
+ expect(rowLabels(el)).to.eql(['outbound']);
115
+ });
10
116
  });
@@ -300,6 +300,13 @@ export default class ZnIconPicker extends ZincElement implements ZincFormControl
300
300
  this.openDialog();
301
301
  }
302
302
 
303
+ private _handleTriggerKeyDown(e: KeyboardEvent) {
304
+ if (e.key === 'Enter' || e.key === ' ') {
305
+ e.preventDefault();
306
+ this._handleTriggerClick();
307
+ }
308
+ }
309
+
303
310
  render() {
304
311
  const hasLabel = !!this.label;
305
312
  const hasHelpText = !!this.helpText;
@@ -320,27 +327,51 @@ export default class ZnIconPicker extends ZincElement implements ZincFormControl
320
327
  </label>
321
328
 
322
329
  <div part="form-control-input" class="form-control-input">
323
- <zn-button
324
- part="trigger"
325
- class="icon-picker__trigger"
326
- panel-bg
327
- icon=${hasValue ? triggerIcon : nothing}
328
- icon-library=${triggerLibrary}
329
- icon-color=${(hasValue && !this.isImageValue && this.color) || nothing}
330
- icon-size="24"
331
- ?disabled=${this.disabled}
332
- @click=${this._handleTriggerClick}>
333
- ${hasValue ? 'Click to edit' : 'Set an icon'}
334
- </zn-button>
335
- ${hasValue ? html`
330
+ ${hasValue && this.isImageValue ? html`
331
+ <div
332
+ part="trigger"
333
+ class="icon-picker__image-trigger"
334
+ role="button"
335
+ aria-label="Click to edit"
336
+ tabindex=${this.disabled ? '-1' : '0'}
337
+ @click=${this._handleTriggerClick}
338
+ @keydown=${this._handleTriggerKeyDown}>
339
+ <zn-button
340
+ class="icon-picker__image-clear"
341
+ color="default"
342
+ outline
343
+ icon="close"
344
+ icon-size="18"
345
+ ?disabled=${this.disabled}
346
+ @click="${this.handleClear}"
347
+ ></zn-button>
348
+ <div class="icon-picker__image-trigger-background">
349
+ <img class="icon-picker__image-trigger-preview" src=${triggerIcon} alt="">
350
+ </div>
351
+ </div>
352
+ ` : html`
336
353
  <zn-button
337
- class="icon-picker__clear"
338
- color="transparent"
339
- icon="close"
340
- icon-size="16"
341
- @click="${this.handleClear}"
342
- ></zn-button>
343
- ` : nothing}
354
+ part="trigger"
355
+ class="icon-picker__trigger"
356
+ panel-bg
357
+ icon=${hasValue ? triggerIcon : nothing}
358
+ icon-library=${triggerLibrary}
359
+ icon-color=${(hasValue && this.color) || nothing}
360
+ icon-size="24"
361
+ ?disabled=${this.disabled}
362
+ @click=${this._handleTriggerClick}>
363
+ ${hasValue ? 'Click to edit' : 'Set an icon'}
364
+ </zn-button>
365
+ ${hasValue ? html`
366
+ <zn-button
367
+ class="icon-picker__clear"
368
+ color="transparent"
369
+ icon="close"
370
+ icon-size="16"
371
+ @click="${this.handleClear}"
372
+ ></zn-button>
373
+ ` : nothing}
374
+ `}
344
375
  </div>
345
376
 
346
377
  ${this.name ? html`
@@ -16,6 +16,77 @@ zn-dialog {
16
16
  --icon-color: rgb(var(--zn-text));
17
17
  }
18
18
 
19
+ // Mirrors zn-file's droparea preview so a chosen image reads the same as an
20
+ // uploaded file.
21
+ .icon-picker__image-trigger {
22
+ position: relative;
23
+ flex: 1;
24
+ min-width: 0;
25
+ min-height: 180px;
26
+ border: 1px solid var(--zn-input-border-color);
27
+ border-radius: var(--zn-border-radius);
28
+ cursor: pointer;
29
+ transition: var(--zn-transition-medium) background,
30
+ var(--zn-transition-medium) border-color;
31
+
32
+ &:focus-visible {
33
+ border-color: var(--zn-color-primary-600);
34
+ outline: var(--zn-focus-ring);
35
+ outline-offset: var(--zn-focus-ring-offset);
36
+ }
37
+
38
+ &:not(:focus-visible):hover {
39
+ border-color: var(--zn-color-primary-700);
40
+ }
41
+ }
42
+
43
+ :host([disabled]) .icon-picker__image-trigger {
44
+ cursor: not-allowed;
45
+ opacity: 0.5;
46
+ }
47
+
48
+ .icon-picker__image-trigger-background {
49
+ --zn-droparea-checker-size: 12px;
50
+ --zn-droparea-checker-a: var(--zn-color-neutral-50);
51
+ --zn-droparea-checker-b: var(--zn-color-neutral-100);
52
+
53
+ display: flex;
54
+ align-items: center;
55
+ justify-content: center;
56
+ width: 100%;
57
+ height: 100%;
58
+ min-height: inherit;
59
+ padding: var(--zn-spacing-large) var(--zn-spacing-medium);
60
+ box-sizing: border-box;
61
+ background-color: var(--zn-droparea-checker-a);
62
+ background-image:
63
+ linear-gradient(45deg, var(--zn-droparea-checker-b) 25%, transparent 25%),
64
+ linear-gradient(-45deg, var(--zn-droparea-checker-b) 25%, transparent 25%),
65
+ linear-gradient(45deg, transparent 75%, var(--zn-droparea-checker-b) 75%),
66
+ linear-gradient(-45deg, transparent 75%, var(--zn-droparea-checker-b) 75%);
67
+ background-size: var(--zn-droparea-checker-size) var(--zn-droparea-checker-size);
68
+ background-position:
69
+ 0 0,
70
+ 0 calc(var(--zn-droparea-checker-size) / 2),
71
+ calc(var(--zn-droparea-checker-size) / 2) calc(var(--zn-droparea-checker-size) / -2),
72
+ calc(var(--zn-droparea-checker-size) / -2) 0;
73
+ border-radius: calc(var(--zn-border-radius) - 1px);
74
+ }
75
+
76
+ .icon-picker__image-trigger-preview {
77
+ display: block;
78
+ width: 100%;
79
+ height: 150px;
80
+ object-fit: contain;
81
+ }
82
+
83
+ .icon-picker__image-clear {
84
+ position: absolute;
85
+ top: var(--zn-spacing-small);
86
+ right: var(--zn-spacing-small);
87
+ z-index: 1;
88
+ }
89
+
19
90
  .icon-picker__dialog-layout {
20
91
  display: flex;
21
92
  gap: var(--zn-spacing-large);