@kubex/zinc 1.0.16 → 1.0.18

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.
@@ -41,7 +41,6 @@ on [form controls](/getting-started/form-controls) to learn more about form subm
41
41
 
42
42
  ### Distinct Selects
43
43
 
44
-
45
44
  ```html:preview
46
45
 
47
46
  <div class="form-spacing">
@@ -60,7 +59,6 @@ on [form controls](/getting-started/form-controls) to learn more about form subm
60
59
 
61
60
  ### Conditional Selects
62
61
 
63
-
64
62
  ```html:preview
65
63
 
66
64
  <div class="form-spacing">
@@ -423,6 +421,29 @@ so that we can consider whether the pattern needs to be updated.
423
421
  </zn-select>
424
422
  ```
425
423
 
424
+ ### Checkbox Prefix
425
+
426
+ You can place a checkbox in the `prefix` slot to visually join it to the select. The checkbox will match the height of
427
+ the control and can be interacted with independently (clicking or toggling it won’t open the select).
428
+
429
+ ```html:preview
430
+ <zn-select label="Notify me about" help-text="Use the checkbox to enable/disable notifications; choose a channel with the select">
431
+ <zn-checkbox slot="prefix" aria-label="Enable notifications"></zn-checkbox>
432
+ <zn-option value="email">Email</zn-option>
433
+ <zn-option value="sms">SMS</zn-option>
434
+ <zn-option value="push">Push</zn-option>
435
+ </zn-select>
436
+
437
+ <br />
438
+
439
+ <zn-select label="Large with checkbox" size="large" value="email">
440
+ <zn-checkbox slot="prefix" size="large" aria-label="Enable notifications"></zn-checkbox>
441
+ <zn-option value="email">Email</zn-option>
442
+ <zn-option value="sms">SMS</zn-option>
443
+ <zn-option value="push">Push</zn-option>
444
+ </zn-select>
445
+ ```
446
+
426
447
  ### Custom Tags
427
448
 
428
449
  When multiple options can be selected, you can provide custom tags by passing a function to the `getTag` property. Your
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubex/zinc",
3
- "version": "1.0.16",
3
+ "version": "1.0.18",
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",
@@ -21,6 +21,7 @@
21
21
 
22
22
  border-width: 1px;
23
23
  border-color: transparent;
24
+ height: 100%;
24
25
  display: inline-flex;
25
26
  position: relative;
26
27
 
@@ -7,9 +7,9 @@ import {ifDefined} from "lit/directives/if-defined.js";
7
7
  import {live} from "lit/directives/live.js";
8
8
  import {property, query, state} from 'lit/decorators.js';
9
9
  import {watch} from '../../internal/watch';
10
- import type {ZincFormControl} from '../../internal/zinc-element';
11
10
  import ZincElement from '../../internal/zinc-element';
12
11
  import ZnIcon from "../icon";
12
+ import type {ZincFormControl} from '../../internal/zinc-element';
13
13
 
14
14
  import styles from './checkbox.scss';
15
15
 
@@ -289,10 +289,15 @@ export default class ZnCheckbox extends ZincElement implements ZincFormControl {
289
289
  ? ' control--indeterminate'
290
290
  : ''}"
291
291
  class="checkbox__control">
292
- ${this.checked
293
- ? html`
294
- <zn-icon part="checked-icon" class="checkbox__checked-icon" size="16" src="check"></zn-icon>`
295
- : ''}
292
+ <svg viewBox="0 0 24 24"
293
+ fill="none"
294
+ stroke="currentColor"
295
+ stroke-width="4"
296
+ stroke-linecap="round"
297
+ stroke-linejoin="round"
298
+ class="svg-icon">
299
+ <polyline class="checkbox__checked-icon" points="20 6 9 17 4 12"></polyline>
300
+ </svg>
296
301
  ${!this.checked && this.indeterminate
297
302
  ? html`
298
303
  <zn-icon
@@ -8,6 +8,7 @@
8
8
  .form-control {
9
9
  display: flex;
10
10
  flex-direction: column;
11
+ height: 100%;
11
12
  }
12
13
 
13
14
  .form-control .form-control__label {
@@ -23,6 +24,7 @@
23
24
  color: var(--zn-input-label-color);
24
25
  vertical-align: middle;
25
26
  cursor: pointer;
27
+ height: 100%;
26
28
  }
27
29
 
28
30
  .checkbox--small {
@@ -76,7 +78,6 @@
76
78
  pointer-events: none;
77
79
  }
78
80
 
79
- .checkbox__checked-icon,
80
81
  .checkbox__indeterminate-icon {
81
82
  --icon-size: var(--toggle-size);
82
83
  --icon-color: var(--zn-color-neutral-0);
@@ -86,6 +87,32 @@
86
87
  height: var(--toggle-size);
87
88
  }
88
89
 
90
+ .svg-icon {
91
+ width: 70%;
92
+ height: 70%;
93
+ --icon-color: var(--zn-color-neutral-0);
94
+
95
+ .checkbox__checked-icon {
96
+ stroke-dasharray: 24;
97
+ stroke-dashoffset: 24;
98
+ transition: stroke-dashoffset 0.4s cubic-bezier(0.4, 0, 0.2, 1);
99
+ }
100
+ }
101
+
102
+ .checkbox--indeterminate .svg-icon {
103
+ color: transparent;
104
+ }
105
+
106
+ .checkbox--checked {
107
+ .svg-icon {
108
+ color: var(--zn-color-neutral-0);
109
+ }
110
+
111
+ .checkbox__checked-icon {
112
+ stroke-dashoffset: 0;
113
+ }
114
+ }
115
+
89
116
  /* Hover */
90
117
  .checkbox:not(.checkbox--checked):not(.checkbox--disabled) .checkbox__control:hover {
91
118
  border-color: var(--zn-input-border-color-hover);
@@ -200,3 +227,23 @@
200
227
  outline: 1px solid var(--zn-color-purple-600);
201
228
  transition: var(--zn-transition-medium) all;
202
229
  }
230
+
231
+ /** Select prefix slot */
232
+ :host([slot="prefix"]) {
233
+ height: 100%;
234
+ aspect-ratio: 1;
235
+
236
+ .checkbox__control {
237
+ height: 100%;
238
+ width: 100%;
239
+ border-left: unset;
240
+ border-top: unset;
241
+ border-bottom: unset;
242
+ box-shadow: unset;
243
+ background-color: var(--zn-color-purple-50);
244
+ }
245
+
246
+ .checkbox--checked .checkbox__control {
247
+ background-color: var(--zn-color-primary-600);
248
+ }
249
+ }
@@ -417,21 +417,47 @@ export default class ZnDataTable extends ZincElement {
417
417
 
418
418
  // add groups to the current rows
419
419
  const toAdd = this.groups.split(',').map(g => g.trim()).filter(g => g.length > 0);
420
- toAdd.forEach((group) => {
421
- if (!groupedRows[this.humanize(group)]) {
422
- groupedRows[this.humanize(group)] = [];
420
+
421
+ const hasWildcard = toAdd.includes('*');
422
+ const explicitGroups = new Set(
423
+ toAdd
424
+ .filter(g => g !== '*')
425
+ .map(g => this.humanize(g))
426
+ );
427
+
428
+ // pre-create explicit groups
429
+ explicitGroups.forEach((group) => {
430
+ if (!groupedRows[group]) {
431
+ groupedRows[group] = [];
423
432
  }
424
433
  });
425
434
 
435
+ // pre-create wildcard grouping if requested
436
+ if (hasWildcard) {
437
+ groupedRows['*'] = [];
438
+ }
439
+
426
440
  this._rows.forEach((row: Row) => {
427
441
  const groupCell = row.cells.find((cell: Cell) => cell.column === this.groupBy);
428
- const groupValue = groupCell ? groupCell.text : 'Ungrouped';
429
- if (!groupedRows[this.humanize(groupValue)] && (!this.groups || groupValue === 'Ungrouped')) {
430
- groupedRows[this.humanize(groupValue)] = [];
442
+ const rawGroupValue = groupCell ? groupCell.text : 'Ungrouped';
443
+ const groupValue = this.humanize(rawGroupValue);
444
+
445
+ if (explicitGroups.has(groupValue)) {
446
+ groupedRows[groupValue].push(row);
447
+ return;
431
448
  }
432
449
 
433
- if (groupedRows[this.humanize(groupValue)]) {
434
- groupedRows[this.humanize(groupValue)].push(row);
450
+ if (hasWildcard) {
451
+ groupedRows['*'].push(row);
452
+ return;
453
+ }
454
+
455
+ // If no groups specified, or handling ungrouped fallback, create groups dynamically
456
+ if (!this.groups || groupValue === 'Ungrouped') {
457
+ if (!groupedRows[groupValue]) {
458
+ groupedRows[groupValue] = [];
459
+ }
460
+ groupedRows[groupValue].push(row);
435
461
  }
436
462
  });
437
463
 
@@ -447,7 +473,7 @@ export default class ZnDataTable extends ZincElement {
447
473
  <zn-sp flush>
448
474
  ${Object.keys(groupedRows).map((groupKey) => html`
449
475
  <div class="table-group">
450
- <h3 class="table-group__title">${groupKey === "Ungrouped" ? "" : groupKey}</h3>
476
+ <h3 class="table-group__title">${(groupKey === "Ungrouped" || groupKey === "*") ? "" : groupKey}</h3>
451
477
  ${this.renderTableData(groupedRows[groupKey])}
452
478
  </div>`
453
479
  )}
@@ -1,11 +1,11 @@
1
1
  import {classMap} from "lit/directives/class-map.js";
2
2
  import {type colors} from "../data-select/providers/color-data-provider";
3
3
  import {type CSSResultGroup, html, unsafeCSS} from 'lit';
4
+ import {HasSlotController} from "../../internal/slot";
4
5
  import {property, state} from "lit/decorators.js";
5
6
  import ZincElement from '../../internal/zinc-element';
6
7
 
7
8
  import styles from './note.scss';
8
- import {HasSlotController} from "../../internal/slot";
9
9
 
10
10
  /**
11
11
  * @summary Short summary of the component's intended use.
@@ -61,9 +61,12 @@ export default class ZnNote extends ZincElement {
61
61
  })}">
62
62
  <div class="note__header">
63
63
  <slot name="caption" class="note__header__caption">${this.caption}</slot>
64
- <slot name="date" class="note__header__date">
65
- <small>${this.date}</small>
66
- </slot>
64
+ <div class="note__header__end">
65
+ <slot name="date" class="note__header__date">
66
+ <small>${this.date}</small>
67
+ </slot>
68
+ <slot name="action"></slot>
69
+ </div>
67
70
  </div>
68
71
 
69
72
  ${this.expanded && showExpandableButton ?
@@ -40,6 +40,11 @@ $colors: (
40
40
  &__date {
41
41
  font-size: var(--zn-font-size-small);
42
42
  }
43
+
44
+ &__end {
45
+ display: flex;
46
+ gap: var(--zn-spacing-small);
47
+ }
43
48
  }
44
49
 
45
50
  &__body-container {
@@ -1,22 +1,23 @@
1
- import { animateTo, stopAnimations } from '../../internal/animate.js';
2
- import { classMap } from "lit/directives/class-map.js";
3
- import { type CSSResultGroup, html, nothing, PropertyValues, type TemplateResult, unsafeCSS } from 'lit';
1
+ import {animateTo, stopAnimations} from '../../internal/animate.js';
2
+ import {classMap} from "lit/directives/class-map.js";
4
3
  import {deepQuerySelectorAll} from "../../utilities/query";
5
- import { FormControlController } from "../../internal/form";
6
- import { getAnimation, setDefaultAnimation } from "../../utilities/animation-registry";
7
- import { HasSlotController } from "../../internal/slot";
8
- import { LocalizeController } from "../../utilities/localize";
9
- import { property, query, state } from 'lit/decorators.js';
10
- import { scrollIntoView } from "../../internal/scroll";
11
- import { unsafeHTML } from "lit/directives/unsafe-html.js";
12
- import { waitForEvent } from "../../internal/event";
13
- import { watch } from '../../internal/watch';
4
+ import {FormControlController} from "../../internal/form";
5
+ import {getAnimation, setDefaultAnimation} from "../../utilities/animation-registry";
6
+ import {HasSlotController} from "../../internal/slot";
7
+ import {html, nothing, unsafeCSS} from 'lit';
8
+ import {LocalizeController} from "../../utilities/localize";
9
+ import {property, query, state} from 'lit/decorators.js';
10
+ import {scrollIntoView} from "../../internal/scroll";
11
+ import {unsafeHTML} from "lit/directives/unsafe-html.js";
12
+ import {waitForEvent} from "../../internal/event";
13
+ import {watch} from '../../internal/watch';
14
14
  import ZincElement from '../../internal/zinc-element';
15
15
  import ZnChip from "../chip";
16
16
  import ZnIcon from "../icon";
17
17
  import ZnPopup from "../popup";
18
- import type { ZincFormControl } from '../../internal/zinc-element';
19
- import type { ZnRemoveEvent } from "../../events/zn-remove";
18
+ import type {CSSResultGroup, PropertyValues, TemplateResult} from 'lit';
19
+ import type {ZincFormControl} from '../../internal/zinc-element';
20
+ import type {ZnRemoveEvent} from "../../events/zn-remove";
20
21
  import type ZnOption from "../option";
21
22
 
22
23
  import styles from './select.scss';
@@ -90,12 +91,14 @@ export default class ZnSelect extends ZincElement implements ZincFormControl {
90
91
  @query('.select__display-input') displayInput: HTMLInputElement;
91
92
  @query('.select__value-input') valueInput: HTMLInputElement;
92
93
  @query('.select__listbox') listbox: HTMLSlotElement;
94
+ @query('slot[name="prefix"]') prefixSlot: HTMLSlotElement;
93
95
 
94
96
  @state() private hasFocus = false;
95
97
  @state() displayLabel = '';
96
98
  @state() currentOption: ZnOption;
97
99
  @state() selectedOptions: ZnOption[] = [];
98
100
  @state() private valueHasChanged: boolean = false;
101
+ @state() private hasCheckboxPrefix: boolean = false;
99
102
 
100
103
  /** The name of the select, submitted as a name/value pair with form data. */
101
104
  @property() name = '';
@@ -142,73 +145,75 @@ export default class ZnSelect extends ZincElement implements ZincFormControl {
142
145
  }) defaultValue: string | string[] = '';
143
146
 
144
147
  /** The select's size. */
145
- @property({ reflect: true }) size: 'small' | 'medium' | 'large' = 'medium';
148
+ @property({reflect: true}) size: 'small' | 'medium' | 'large' = 'medium';
146
149
 
147
150
  /** Placeholder text to show as a hint when the select is empty. */
148
151
  @property() placeholder = '';
149
152
 
150
153
  /** Allows more than one option to be selected. */
151
- @property({ type: Boolean, reflect: true }) multiple = false;
154
+ @property({type: Boolean, reflect: true}) multiple = false;
152
155
 
153
156
  /** Max number of options that can be selected when `multiple` is true. Set to 0 to allow unlimited selections. */
154
- @property({ attribute: 'max-options', type: Number }) maxOptions = 0;
157
+ @property({attribute: 'max-options', type: Number}) maxOptions = 0;
155
158
 
156
159
  /**
157
160
  * The maximum number of selected options to show when `multiple` is true. After the maximum, "+n" will be shown to
158
161
  * indicate the number of additional items that are selected. Set to 0 to remove the limit.
159
162
  */
160
- @property({ attribute: 'max-options-visible', type: Number }) maxOptionsVisible = 3;
163
+ @property({attribute: 'max-options-visible', type: Number}) maxOptionsVisible = 3;
161
164
 
162
165
  /** Disables the select control. */
163
- @property({ type: Boolean, reflect: true }) disabled = false;
166
+ @property({type: Boolean, reflect: true}) disabled = false;
164
167
 
165
168
  /** Adds a clear button when the select is not empty. */
166
- @property({ type: Boolean }) clearable = false;
169
+ @property({type: Boolean}) clearable = false;
167
170
 
168
171
  /**
169
172
  * Indicates whether or not the select is open. You can toggle this attribute to show and hide the menu, or you can
170
173
  * use the `show()` and `hide()` methods and this attribute will reflect the select's open state.
171
174
  */
172
- @property({ type: Boolean, reflect: true }) open = false;
175
+ @property({type: Boolean, reflect: true}) open = false;
173
176
 
174
177
  /**
175
178
  * Enable this option to prevent the listbox from being clipped when the component is placed inside a container with
176
179
  * `overflow: auto|scroll`. Hoisting uses a fixed positioning strategy that works in many, but not all, scenarios.
177
180
  */
178
- @property({ type: Boolean }) hoist = false;
181
+ @property({type: Boolean}) hoist = false;
179
182
 
180
183
  /** Draws a pill-style select with rounded edges. */
181
- @property({ type: Boolean, reflect: true }) pill = false;
184
+ @property({type: Boolean, reflect: true}) pill = false;
182
185
 
183
186
  /** The select's label. If you need to display HTML, use the `label` slot instead. */
184
187
  @property() label = '';
185
188
 
186
189
  /** Text that appears in a tooltip next to the label. If you need to display HTML in the tooltip, use the `label-tooltip` slot instead. */
187
- @property({ attribute: 'label-tooltip' }) labelTooltip = '';
190
+ @property({attribute: 'label-tooltip'}) labelTooltip = '';
188
191
 
189
192
  /** Text that appears above the input, on the right, to add additional context. If you need to display HTML in this text, use the `context-note` slot instead. */
190
- @property({ attribute: 'context-note' }) contextNote = '';
193
+ @property({attribute: 'context-note'}) contextNote = '';
191
194
 
192
195
  /**
193
196
  * The preferred placement of the selects menu. Note that the actual placement may vary as needed to keep the listbox
194
197
  * inside the viewport.
195
198
  */
196
- @property({ reflect: true }) placement: 'top' | 'bottom' = 'bottom';
199
+ @property({reflect: true}) placement: 'top' | 'bottom' = 'bottom';
197
200
 
198
201
  /** The select's help text. If you need to display HTML, use the `help-text` slot instead. */
199
- @property({ attribute: 'help-text' }) helpText = '';
202
+ @property({attribute: 'help-text'}) helpText = '';
200
203
 
201
204
  /**
202
205
  * By default, form controls are associated with the nearest containing `<form>` element. This attribute allows you
203
206
  * to place the form control outside of a form and associate it with the form that has this `id`. The form must be in
204
207
  * the same document or shadow root for this to work.
205
208
  */
206
- @property({ reflect: true }) form: string;
209
+ @property({reflect: true}) form: string;
207
210
 
208
211
  /** The select's required attribute. */
209
- @property({ type: Boolean, reflect: true }) required = false;
212
+ @property({type: Boolean, reflect: true}) required = false;
210
213
 
211
- @property({ attribute: 'cache-key' }) cacheKey: string = "";
214
+ @property({attribute: 'cache-key'}) cacheKey: string = "";
215
+
216
+ @property({type: Boolean, attribute: 'trigger-submit'}) triggerSubmit = false;
212
217
 
213
218
  /**
214
219
  * A function that customizes the tags to be rendered when multiple=true. The first argument is the option, the second
@@ -266,6 +271,15 @@ export default class ZnSelect extends ZincElement implements ZincFormControl {
266
271
  }
267
272
  }
268
273
 
274
+ private updateHasCheckboxPrefix() {
275
+ try {
276
+ const assigned = this.prefixSlot?.assignedElements({flatten: true}) || [];
277
+ this.hasCheckboxPrefix = assigned.some(el => el.tagName === 'ZN-CHECKBOX');
278
+ } catch {
279
+ this.hasCheckboxPrefix = false;
280
+ }
281
+ }
282
+
269
283
  private addOpenListeners() {
270
284
  const root = this.getRootNode();
271
285
  root.addEventListener('focusin', this.handleDocumentFocusIn);
@@ -278,7 +292,7 @@ export default class ZnSelect extends ZincElement implements ZincFormControl {
278
292
  this.closeWatcher.onclose = () => {
279
293
  if (this.open) {
280
294
  this.hide();
281
- this.displayInput.focus({ preventScroll: true });
295
+ this.displayInput.focus({preventScroll: true});
282
296
  }
283
297
  };
284
298
  }
@@ -330,7 +344,7 @@ export default class ZnSelect extends ZincElement implements ZincFormControl {
330
344
  event.preventDefault();
331
345
  event.stopPropagation();
332
346
  this.hide();
333
- this.displayInput.focus({ preventScroll: true });
347
+ this.displayInput.focus({preventScroll: true});
334
348
  }
335
349
 
336
350
  // Handle enter and space. When pressing space, we allow for type to select behaviors so if there's anything in the
@@ -362,7 +376,7 @@ export default class ZnSelect extends ZincElement implements ZincFormControl {
362
376
 
363
377
  if (!this.multiple) {
364
378
  this.hide();
365
- this.displayInput.focus({ preventScroll: true });
379
+ this.displayInput.focus({preventScroll: true});
366
380
  }
367
381
  }
368
382
 
@@ -463,13 +477,15 @@ export default class ZnSelect extends ZincElement implements ZincFormControl {
463
477
 
464
478
  // not the keyboard_arrow_down icon
465
479
  const isExpandIcon = path.some(el => el instanceof Element && el.classList.contains('select__expand-icon'));
480
+ // if click is inside the prefix area (e.g. checkbox), don't toggle the select
481
+ const inPrefix = path.some(el => el instanceof Element && el.classList.contains('select__prefix'));
466
482
  // Ignore disabled controls and clicks on tags (remove buttons)
467
- if ((this.disabled || isIcon) && !isExpandIcon) {
483
+ if (((this.disabled || isIcon) && !isExpandIcon) || inPrefix) {
468
484
  return;
469
485
  }
470
486
 
471
487
  event.preventDefault();
472
- this.displayInput.focus({ preventScroll: true });
488
+ this.displayInput.focus({preventScroll: true});
473
489
  this.open = !this.open;
474
490
  }
475
491
 
@@ -478,8 +494,14 @@ export default class ZnSelect extends ZincElement implements ZincFormControl {
478
494
  return;
479
495
  }
480
496
 
481
- event.stopPropagation();
482
- this.handleDocumentKeyDown(event);
497
+ // If keydown originated from within the prefix (e.g. checkbox)
498
+ const path = event.composedPath();
499
+ const inPrefix = path.some(el => el instanceof Element && (el as Element).classList?.contains('select__prefix'));
500
+
501
+ if (!inPrefix) {
502
+ event.stopPropagation();
503
+ this.handleDocumentKeyDown(event);
504
+ }
483
505
  }
484
506
 
485
507
  private handleClearClick(event: MouseEvent) {
@@ -487,7 +509,7 @@ export default class ZnSelect extends ZincElement implements ZincFormControl {
487
509
 
488
510
  if (this.value !== '') {
489
511
  this.setSelectedOptions([]);
490
- this.displayInput.focus({ preventScroll: true });
512
+ this.displayInput.focus({preventScroll: true});
491
513
 
492
514
  // Emit after update
493
515
  this.updateComplete.then(() => {
@@ -526,7 +548,7 @@ export default class ZnSelect extends ZincElement implements ZincFormControl {
526
548
  }
527
549
 
528
550
  // Set focus after updating so the value is announced by screen readers
529
- this.updateComplete.then(() => this.displayInput.focus({ preventScroll: true }));
551
+ this.updateComplete.then(() => this.displayInput.focus({preventScroll: true}));
530
552
 
531
553
  if (this.value !== oldValue) {
532
554
  // Emit after updating
@@ -538,7 +560,11 @@ export default class ZnSelect extends ZincElement implements ZincFormControl {
538
560
 
539
561
  if (!this.multiple) {
540
562
  this.hide();
541
- this.displayInput.focus({ preventScroll: true });
563
+ this.displayInput.focus({preventScroll: true});
564
+ }
565
+
566
+ if (this.triggerSubmit) {
567
+ this.formControlController.submit();
542
568
  }
543
569
  }
544
570
  }
@@ -745,9 +771,11 @@ export default class ZnSelect extends ZincElement implements ZincFormControl {
745
771
  conditionalSelect.dispatchEvent(new Event('zn-input'));
746
772
  });
747
773
  }
774
+
775
+ this.updateHasCheckboxPrefix();
748
776
  }
749
777
 
750
- @watch('disabled', { waitUntilFirstUpdate: true })
778
+ @watch('disabled', {waitUntilFirstUpdate: true})
751
779
  handleDisabledChange() {
752
780
  // Close the listbox when the control is disabled
753
781
  if (this.disabled) {
@@ -768,7 +796,7 @@ export default class ZnSelect extends ZincElement implements ZincFormControl {
768
796
  }
769
797
  }
770
798
 
771
- @watch(['defaultValue', 'value'], { waitUntilFirstUpdate: true })
799
+ @watch(['defaultValue', 'value'], {waitUntilFirstUpdate: true})
772
800
  handleValueChange() {
773
801
  if (!this.valueHasChanged) {
774
802
  const cachedValueHasChanged = this.valueHasChanged;
@@ -784,7 +812,7 @@ export default class ZnSelect extends ZincElement implements ZincFormControl {
784
812
  this.setSelectedOptions(allOptions.filter(el => value.includes(el.value)));
785
813
  }
786
814
 
787
- @watch('open', { waitUntilFirstUpdate: true })
815
+ @watch('open', {waitUntilFirstUpdate: true})
788
816
  async handleOpenChange() {
789
817
  if (this.open && !this.disabled) {
790
818
  // Reset the current option
@@ -803,7 +831,7 @@ export default class ZnSelect extends ZincElement implements ZincFormControl {
803
831
  this.setCurrentOption(this.currentOption);
804
832
  });
805
833
 
806
- const { keyframes, options } = getAnimation(this, 'select.show', { dir: this.localize.dir() });
834
+ const {keyframes, options} = getAnimation(this, 'select.show', {dir: this.localize.dir()});
807
835
  await animateTo(this.popup.popup, keyframes, options);
808
836
 
809
837
  // Make sure the current option is scrolled into view (required for Safari)
@@ -818,7 +846,7 @@ export default class ZnSelect extends ZincElement implements ZincFormControl {
818
846
  this.removeOpenListeners();
819
847
 
820
848
  await stopAnimations(this);
821
- const { keyframes, options } = getAnimation(this, 'select.hide', { dir: this.localize.dir() });
849
+ const {keyframes, options} = getAnimation(this, 'select.hide', {dir: this.localize.dir()});
822
850
  await animateTo(this.popup.popup, keyframes, options);
823
851
  this.listbox.hidden = true;
824
852
  this.popup.active = false;
@@ -942,7 +970,8 @@ export default class ZnSelect extends ZincElement implements ZincFormControl {
942
970
  'select--bottom': this.placement === 'bottom',
943
971
  'select--small': this.size === 'small',
944
972
  'select--medium': this.size === 'medium',
945
- 'select--large': this.size === 'large'
973
+ 'select--large': this.size === 'large',
974
+ 'select--has-checkbox-prefix': this.hasCheckboxPrefix
946
975
  })}
947
976
  placement=${this.placement}
948
977
  strategy=${this.hoist ? 'fixed' : 'absolute'}
@@ -957,7 +986,10 @@ export default class ZnSelect extends ZincElement implements ZincFormControl {
957
986
  slot="anchor"
958
987
  @keydown=${this.handleComboboxKeyDown}
959
988
  @mousedown=${this.handleComboboxMouseDown}>
960
- <slot part="prefix" name="prefix" class="select__prefix"></slot>
989
+ <slot part="prefix"
990
+ name="prefix"
991
+ class="select__prefix"
992
+ @slotchange=${() => this.updateHasCheckboxPrefix()}></slot>
961
993
 
962
994
  <input
963
995
  part="display-input"
@@ -1049,16 +1081,16 @@ export default class ZnSelect extends ZincElement implements ZincFormControl {
1049
1081
 
1050
1082
  setDefaultAnimation('select.show', {
1051
1083
  keyframes: [
1052
- { opacity: 0, scale: 0.9 },
1053
- { opacity: 1, scale: 1 }
1084
+ {opacity: 0, scale: 0.9},
1085
+ {opacity: 1, scale: 1}
1054
1086
  ],
1055
- options: { duration: 100, easing: 'ease' }
1087
+ options: {duration: 100, easing: 'ease'}
1056
1088
  });
1057
1089
 
1058
1090
  setDefaultAnimation('select.hide', {
1059
1091
  keyframes: [
1060
- { opacity: 1, scale: 1 },
1061
- { opacity: 0, scale: 0.9 }
1092
+ {opacity: 1, scale: 1},
1093
+ {opacity: 0, scale: 0.9}
1062
1094
  ],
1063
- options: { duration: 100, easing: 'ease' }
1095
+ options: {duration: 100, easing: 'ease'}
1064
1096
  });
@@ -48,6 +48,14 @@
48
48
  var(--zn-transition-fast) background-color;
49
49
  }
50
50
 
51
+ /* Prefix area: allow a leading control (e.g., checkbox) full-height */
52
+ .select__prefix {
53
+ display: flex;
54
+ align-items: center;
55
+ align-self: stretch;
56
+ height: 100%;
57
+ }
58
+
51
59
  .select__display-input {
52
60
  position: relative;
53
61
  width: 100%;
@@ -175,6 +183,10 @@
175
183
  padding-inline: var(--zn-input-spacing-medium);
176
184
  }
177
185
 
186
+ .select--has-checkbox-prefix.select--medium .select__combobox {
187
+ padding-inline-start: 0;
188
+ }
189
+
178
190
  .select--medium .select__clear {
179
191
  margin-inline-start: var(--zn-input-spacing-medium);
180
192
  }
@@ -200,6 +212,10 @@
200
212
  padding-inline: var(--zn-input-spacing-large);
201
213
  }
202
214
 
215
+ .select--has-checkbox-prefix.select--large .select__combobox {
216
+ padding-inline-start: 0;
217
+ }
218
+
203
219
  .select--large .select__clear {
204
220
  margin-inline-start: var(--zn-input-spacing-large);
205
221
  }
@@ -241,6 +257,11 @@
241
257
  color: var(--zn-input-icon-color);
242
258
  }
243
259
 
260
+ /* When a checkbox is used in the prefix, remove left padding so the prefix joins flush */
261
+ .select--has-checkbox-prefix.select--standard .select__combobox {
262
+ padding-inline-start: 0;
263
+ }
264
+
244
265
  /* Clear button */
245
266
  .select__clear {
246
267
  display: inline-flex;