@kubex/zinc 1.0.114 → 1.0.115

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubex/zinc",
3
- "version": "1.0.114",
3
+ "version": "1.0.115",
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",
package/scss/_root.scss CHANGED
@@ -52,21 +52,21 @@
52
52
 
53
53
  @media (min-width: 1024px) {
54
54
  :root {
55
- --zn-base-gap: 14px;
55
+ --zn-base-gap: 16px;
56
56
  --zn-gutter: 6px;
57
57
  }
58
58
  }
59
59
 
60
60
  @media (min-width: 1600px) {
61
61
  :root {
62
- --zn-base-gap: 16px;
62
+ --zn-base-gap: 20px;
63
63
  --zn-gutter: 8px;
64
64
  }
65
65
  }
66
66
 
67
67
  @media (min-width: 2000px) {
68
68
  :root {
69
- --zn-base-gap: 18px;
69
+ --zn-base-gap: 24px;
70
70
  --zn-gutter: 10px;
71
71
  }
72
72
  }
@@ -43,7 +43,7 @@
43
43
  }
44
44
 
45
45
  @mixin padding {
46
- padding: var(--zn-spacing-medium, 0);
46
+ padding: var(--zn-base-gap, 0);
47
47
  }
48
48
 
49
49
  @function spacing($size, $default: sm) {
@@ -18,7 +18,7 @@
18
18
  width: 100%;
19
19
  container-type: inline-size;
20
20
  overflow: hidden;
21
- gap: var(--zn-base-gap, var(--zn-divide-gap, var(--zn-spacing-medium)));
21
+ gap: var(--zn-spacing-medium);
22
22
 
23
23
  max-width: 100%;
24
24
 
@@ -65,6 +65,7 @@ $sizes: (
65
65
 
66
66
  zn-select::part(listbox) {
67
67
  max-height: 300px;
68
+ padding-block: 0;
68
69
  }
69
70
 
70
71
  .select__icon {
@@ -19,6 +19,10 @@
19
19
  overflow: auto;
20
20
  }
21
21
 
22
+ .dialog__header + .dialog__body {
23
+ padding-top: 0;
24
+ }
25
+
22
26
  .dialog {
23
27
  flex-direction: column;
24
28
  z-index: 2;
@@ -35,6 +35,7 @@ export default class ZnFormGroup extends ZincElement {
35
35
  /** The form groups help text. If you need to display HTML, use the `help-text` slot instead. */
36
36
  @property({attribute: 'help-text'}) helpText = '';
37
37
  @property({attribute: 'cols', type: Boolean}) forceCols = false;
38
+ @property({attribute: 'layout', type: String}) layout = "1,2";
38
39
 
39
40
  render() {
40
41
  const hasLabelSlot = this.hasSlotController.test('label');
@@ -56,38 +57,37 @@ export default class ZnFormGroup extends ZincElement {
56
57
  aria-labelledby="label"
57
58
  aria-describedby="help-text">
58
59
 
59
- <zn-cols layout="1,2" mc="2" part="form-control-container" class="form-control__container">
60
+ <zn-cols layout="${this.layout}" part="form-control-container" class="form-control__container">
60
61
  ${hasLabel || hasHelpText || this.forceCols ? html`
61
- <div>` : html``}
62
- ${hasLabel ? html`
63
62
  <div>
64
- <label
65
- part="form-control-label"
66
- id="label"
67
- class="form-control__label"
68
- aria-hidden="${hasLabel ? 'false' : 'true'}">
69
- <slot name="label">${this.label}</slot>
70
- ${hasLabelTooltip
71
- ? html`
72
- <zn-tooltip class="form-control--label-tooltip">
73
- <div slot="content">
74
- <slot name="label-tooltip">${this.labelTooltip}</slot>
75
- </div>
76
- <zn-icon src="info"></zn-icon>
77
- </zn-tooltip>`
78
- : ''}
79
- </label>
80
- </div>` : html``}
81
63
 
82
- ${hasHelpText ? html`
83
- <div
84
- part="form-control-help-text"
85
- id="help-text"
86
- class="form-control__help-text">
87
- <slot name="help-text">${this.helpText}</slot>
88
- </div>` : html``}
64
+ ${hasLabel ? html`
65
+ <label
66
+ part="form-control-label"
67
+ id="label"
68
+ class="form-control__label"
69
+ aria-hidden="${hasLabel ? 'false' : 'true'}">
70
+ <slot name="label">${this.label}</slot>
71
+ ${hasLabelTooltip
72
+ ? html`
73
+ <zn-tooltip class="form-control--label-tooltip">
74
+ <div slot="content">
75
+ <slot name="label-tooltip">${this.labelTooltip}</slot>
76
+ </div>
77
+ <zn-icon src="info"></zn-icon>
78
+ </zn-tooltip>`
79
+ : ''}
80
+ </label>` : html``}
89
81
 
90
- ${hasLabel || hasHelpText || this.forceCols ? html`</div>` : html``}
82
+ ${hasHelpText ? html`
83
+ <div
84
+ part="form-control-help-text"
85
+ id="help-text"
86
+ class="form-control__help-text">
87
+ <slot name="help-text">${this.helpText}</slot>
88
+ </div>` : html``}
89
+
90
+ </div>` : html``}
91
91
 
92
92
  <div part="form-control-input" class="form-control-input">
93
93
  <slot></slot>
@@ -5,6 +5,7 @@
5
5
  display: block;
6
6
  line-height: var(--zn-line-height-dense);
7
7
  max-width: var(--zn-container-lg);
8
+ --zn-col-gap: calc(var(--zn-spacing-medium, 20px) * 3)
8
9
  }
9
10
 
10
11
  .form-control {
@@ -109,10 +109,6 @@
109
109
  &__caption {
110
110
 
111
111
  }
112
-
113
- .caption {
114
- min-height: initial;
115
- }
116
112
  }
117
113
  }
118
114
 
@@ -139,7 +135,7 @@
139
135
  justify-content: center;
140
136
  flex-direction: column;
141
137
  gap: 1px;
142
- min-height: 43px;
138
+ min-height: 24px;
143
139
  flex-shrink: 1;
144
140
  max-width: 100%;
145
141
  }
@@ -43,7 +43,7 @@
43
43
 
44
44
 
45
45
  &__header {
46
- padding: var(--zn-spacing-x-small) var(--zn-spacing-medium);
46
+ padding: var(--zn-base-gap);
47
47
 
48
48
  // background-color: rgba(var(--zn-panel-highlight), 0.6);
49
49
  // border-bottom: 1px solid rgb(var(--zn-border-color));
@@ -74,7 +74,7 @@
74
74
  flex-direction: column;
75
75
  overflow-y: auto;
76
76
  flex-grow: 1;
77
- padding: 0 var(--zn-spacing-medium) var(--zn-spacing-medium);
77
+ padding: 0 var(--zn-base-gap) var(--zn-base-gap) var(--zn-base-gap);
78
78
 
79
79
  > :not([hidden]) ~ :not([hidden]) {
80
80
  border: 1px solid rgb(var(--zn-border-color));
@@ -84,7 +84,7 @@
84
84
  &__footer {
85
85
  font-size: var(--zn-font-size-small);
86
86
  display: block;
87
- padding: var(--zn-spacing-small) var(--zn-spacing-medium);
87
+ padding: var(--zn-spacing-small) var(--zn-base-gap);
88
88
  background: rgb(var(--zn-panel-secondary));
89
89
  border-bottom-left-radius: var(--zn-border-radius-large);
90
90
  border-bottom-right-radius: var(--zn-border-radius-large);
@@ -109,21 +109,9 @@
109
109
  }
110
110
  }
111
111
 
112
- &:before {
113
- position: absolute;
114
- content: '';
115
- bottom: -3px;
116
- left: 0;
117
- right: 0;
118
- height: 20px;
119
- background-color: rgb(var(--zn-shadow));
120
- border-radius: var(--zn-border-radius-large);
121
- z-index: -1;
122
- }
123
-
124
112
 
125
113
  &:not(.panel--has-header):not(.panel--flush, .panel--flush-y) &__body {
126
- padding-top: var(--zn-spacing-medium);
114
+ padding-top: var(--zn-base-gap);
127
115
  }
128
116
 
129
117
  &--flush {
@@ -722,7 +722,9 @@ export default class ZnSelect extends ZincElement implements ZincFormControl {
722
722
  private handleClearClick(event: MouseEvent) {
723
723
  event.stopPropagation();
724
724
 
725
- if (this.value !== '') {
725
+ if (this.value.length > 0) {
726
+ this.valueHasChanged = true;
727
+
726
728
  if (this._isTypeable) {
727
729
  this.clearSearch();
728
730
  this.displayInput.value = '';
@@ -127,6 +127,15 @@
127
127
  flex-wrap: wrap;
128
128
  gap: var(--zn-spacing-2x-small);
129
129
  padding-inline-start: 0;
130
+ /* Allow the control to grow with the wrapped tag rows instead of clipping them, and pack the rows
131
+ from the top with a little vertical padding. align-self stops the popup from stretching the
132
+ combobox to a fixed height, so it sizes to its (wrapped) content. */
133
+ overflow: visible;
134
+ align-content: flex-start;
135
+ align-items: center;
136
+ align-self: flex-start;
137
+ height: auto;
138
+ padding-block: var(--zn-spacing-2x-small);
130
139
  }
131
140
 
132
141
  .select__value-input {
@@ -149,13 +158,21 @@
149
158
  margin-inline-start: var(--zn-spacing-2x-small);
150
159
  }
151
160
 
152
- .select__tags::slotted(zn-chip) {
153
- cursor: pointer !important;
161
+ /* The tags are rendered into the shadow (not slotted), so target them as descendants. Only the remove
162
+ "X" is clickable (pointer); the chip body isn't, so give it a default cursor rather than letting it
163
+ inherit the combobox's text cursor in search/free-text mode. */
164
+ .select__tags zn-chip {
165
+ cursor: default;
166
+ }
167
+
168
+ .select__tags zn-icon[slot="action"] {
169
+ cursor: pointer;
154
170
  }
155
171
 
156
172
  .select--disabled .select__tags,
157
- .select--disabled .select__tags::slotted(zn-chip) {
158
- cursor: not-allowed !important;
173
+ .select--disabled .select__tags zn-chip,
174
+ .select--disabled .select__tags zn-icon[slot="action"] {
175
+ cursor: not-allowed;
159
176
  }
160
177
 
161
178
  /* Standard selects */
@@ -374,7 +391,6 @@
374
391
  border: solid var(--zn-panel-border-width) var(--zn-panel-border-color);
375
392
  border-radius: var(--zn-border-radius-medium);
376
393
  margin: var(--zn-spacing-x-small) 0;
377
- padding-block: var(--zn-spacing-x-small);
378
394
  padding-inline: 0;
379
395
  overflow: auto;
380
396
  overscroll-behavior: none;
@@ -3,6 +3,16 @@ import {expect, fixture, html} from '@open-wc/testing';
3
3
  import type ZnOption from '../option/option.component';
4
4
  import type ZnSelect from './select.component';
5
5
 
6
+ // The popup's ResizeObserver can emit a benign "loop completed with undelivered notifications"
7
+ // warning when the dropdown opens during layout-measuring tests. It's not a real error — ignore it
8
+ // so the test runner doesn't treat it as an uncaught exception (capture phase runs before the runner's).
9
+ window.addEventListener('error', (e: ErrorEvent) => {
10
+ if (typeof e.message === 'string' && e.message.includes('ResizeObserver loop')) {
11
+ e.stopImmediatePropagation();
12
+ e.preventDefault();
13
+ }
14
+ }, true);
15
+
6
16
  describe('<zn-select>', () => {
7
17
  it('should render a component', async () => {
8
18
  const el = await fixture(html`
@@ -177,6 +187,52 @@ describe('<zn-select>', () => {
177
187
 
178
188
  });
179
189
 
190
+ describe('multiple + search layout', () => {
191
+ const openWithTags = async (width: number) => {
192
+ const el = await fixture<ZnSelect>(html`
193
+ <zn-select search multiple max-options-visible="0" style="width: ${width}px;">
194
+ <zn-option value="a" selected>Urgent</zn-option>
195
+ <zn-option value="b" selected>Backend</zn-option>
196
+ <zn-option value="c" selected>Frontend</zn-option>
197
+ <zn-option value="d" selected>stetset</zn-option>
198
+ <zn-option value="e" selected>another</zn-option>
199
+ </zn-select>
200
+ `);
201
+ await el.updateComplete;
202
+ await el.show();
203
+ await el.updateComplete;
204
+ return el.shadowRoot!.querySelector<HTMLElement>('.select__combobox')!;
205
+ };
206
+
207
+ it('grows to show all tags instead of clipping them when they wrap', async () => {
208
+ const cb = await openWithTags(360);
209
+ // scrollHeight > clientHeight would mean wrapped rows are clipped by the box.
210
+ expect(cb.scrollHeight).to.equal(cb.clientHeight);
211
+ });
212
+
213
+ it('does not clip tags at very narrow widths', async () => {
214
+ const cb = await openWithTags(180);
215
+ expect(cb.scrollHeight).to.equal(cb.clientHeight);
216
+ });
217
+
218
+ it('shows a pointer cursor on a tag remove icon (even in search mode)', async () => {
219
+ // In search/free-text mode the combobox uses cursor:text, which the chips would otherwise inherit.
220
+ const el = await fixture<ZnSelect>(html`
221
+ <zn-select multiple search>
222
+ <zn-option value="a" selected>Apple</zn-option>
223
+ </zn-select>
224
+ `);
225
+ await el.updateComplete;
226
+ const removeIcon = el.shadowRoot!.querySelector<HTMLElement>('.select__tags zn-icon[slot="action"]');
227
+ expect(removeIcon, 'remove icon should exist').to.exist;
228
+ // Only the remove X is clickable, so only it gets a pointer cursor...
229
+ expect(getComputedStyle(removeIcon!).cursor).to.equal('pointer');
230
+ // ...the chip body is not clickable, so it should not show a pointer.
231
+ const chip = el.shadowRoot!.querySelector<HTMLElement>('.select__tags zn-chip')!;
232
+ expect(getComputedStyle(chip).cursor).to.equal('default');
233
+ });
234
+ });
235
+
180
236
  describe('free-text', () => {
181
237
  const type = (el: ZnSelect, text: string) => {
182
238
  const displayInput = el.shadowRoot!.querySelector<HTMLInputElement>('.select__display-input')!;
@@ -95,6 +95,7 @@ td.capcol {
95
95
  }
96
96
 
97
97
  .actions button {
98
+ display: flex;
98
99
  padding: 0;
99
100
  margin: 0;
100
101
  background: transparent;
@@ -12,7 +12,7 @@
12
12
  &--has-properties,
13
13
  &--has-actions,
14
14
  &--has-image {
15
- padding: var(--zn-spacing-large);
15
+ padding: var(--zn-spacing-medium);
16
16
  }
17
17
 
18
18
 
@@ -62,7 +62,7 @@
62
62
  justify-content: flex-end;
63
63
  width: 50%;
64
64
  flex-grow: 1;
65
- column-gap: var(--zn-spacing-large);
65
+ column-gap: var(--zn-spacing-medium);
66
66
  }
67
67
 
68
68
  .tile__properties {