@kubex/zinc 1.0.25 → 1.0.99

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.
Files changed (199) hide show
  1. package/.claude/project.md +556 -0
  2. package/.github/workflows/js_build_and_deploy.yaml +9 -10
  3. package/dist/custom-elements.json +13575 -8856
  4. package/dist/vscode.html-custom-data.json +583 -48
  5. package/dist/web-types.json +1441 -181
  6. package/dist/zn.d.ts +1645 -660
  7. package/dist/zn.min.css +1 -1
  8. package/dist/zn.min.js +1412 -893
  9. package/docs/_utilities/code-previews.cjs +10 -1
  10. package/docs/assets/scripts/docs.js +42 -18
  11. package/docs/data/data-table.json +229 -66
  12. package/docs/data/empty.json +6 -0
  13. package/docs/data/products-table.json +41 -193
  14. package/docs/pages/components/action-bar.md +121 -6
  15. package/docs/pages/components/alert.md +118 -15
  16. package/docs/pages/components/animated-button.md +299 -0
  17. package/docs/pages/components/audio-select.md +534 -0
  18. package/docs/pages/components/bulk-actions.md +79 -5
  19. package/docs/pages/components/button-group.md +235 -27
  20. package/docs/pages/components/button.md +139 -1
  21. package/docs/pages/components/chart.md +365 -9
  22. package/docs/pages/components/checkbox.md +387 -58
  23. package/docs/pages/components/chip.md +253 -33
  24. package/docs/pages/components/collapsible.md +393 -46
  25. package/docs/pages/components/cols.md +152 -30
  26. package/docs/pages/components/confirm.md +182 -16
  27. package/docs/pages/components/copy-button.md +169 -6
  28. package/docs/pages/components/data-select.md +194 -15
  29. package/docs/pages/components/data-table-filter.md +96 -5
  30. package/docs/pages/components/data-table-search.md +451 -0
  31. package/docs/pages/components/data-table-sort.md +66 -5
  32. package/docs/pages/components/data-table.md +727 -66
  33. package/docs/pages/components/datepicker.md +402 -7
  34. package/docs/pages/components/defined-label.md +120 -5
  35. package/docs/pages/components/dialog.md +339 -59
  36. package/docs/pages/components/dropdown.md +366 -11
  37. package/docs/pages/components/editor.md +604 -7
  38. package/docs/pages/components/empty-state.md +315 -16
  39. package/docs/pages/components/expanding-action.md +212 -28
  40. package/docs/pages/components/file.md +421 -12
  41. package/docs/pages/components/filter-container.md +158 -5
  42. package/docs/pages/components/filter-wrapper.md +167 -6
  43. package/docs/pages/components/form-group.md +383 -6
  44. package/docs/pages/components/hover-container.md +237 -6
  45. package/docs/pages/components/icon-picker.md +121 -0
  46. package/docs/pages/components/icon.md +454 -48
  47. package/docs/pages/components/inline-edit.md +787 -19
  48. package/docs/pages/components/input-group.md +207 -13
  49. package/docs/pages/components/input.md +194 -0
  50. package/docs/pages/components/item.md +18 -0
  51. package/docs/pages/components/key-container.md +115 -0
  52. package/docs/pages/components/key.md +101 -0
  53. package/docs/pages/components/menu.md +512 -6
  54. package/docs/pages/components/note.md +239 -36
  55. package/docs/pages/components/opt-group.md +107 -0
  56. package/docs/pages/components/pagination.md +332 -6
  57. package/docs/pages/components/pane.md +222 -6
  58. package/docs/pages/components/panel.md +213 -26
  59. package/docs/pages/components/popup.md +1354 -6
  60. package/docs/pages/components/priority-list.md +233 -0
  61. package/docs/pages/components/progress-bar.md +387 -6
  62. package/docs/pages/components/radio-group.md +242 -5
  63. package/docs/pages/components/radio.md +474 -6
  64. package/docs/pages/components/rating.md +331 -7
  65. package/docs/pages/components/reveal.md +524 -6
  66. package/docs/pages/components/scroll-container.md +552 -6
  67. package/docs/pages/components/select.md +762 -160
  68. package/docs/pages/components/skeleton.md +473 -30
  69. package/docs/pages/components/slideout.md +261 -5
  70. package/docs/pages/components/sp.md +99 -41
  71. package/docs/pages/components/split-button.md +347 -9
  72. package/docs/pages/components/split-pane.md +445 -6
  73. package/docs/pages/components/stat.md +549 -9
  74. package/docs/pages/components/status-indicator.md +230 -1
  75. package/docs/pages/components/stepper.md +563 -6
  76. package/docs/pages/components/tabs.md +439 -13
  77. package/docs/pages/components/textarea.md +415 -17
  78. package/docs/pages/components/tile.md +385 -26
  79. package/docs/pages/components/toggle.md +424 -6
  80. package/docs/pages/components/tooltip.md +367 -0
  81. package/docs/pages/components/translation-group.md +210 -0
  82. package/docs/pages/components/translations.md +319 -19
  83. package/docs/pages/components/vertical-stepper.md +401 -8
  84. package/docs/pages/components/well.md +300 -3
  85. package/docs/pages/getting-started/form-controls.md +162 -0
  86. package/docs/pages/index.md +116 -15
  87. package/package.json +5 -6
  88. package/scss/_global-spacing.scss +4 -0
  89. package/scss/_root.scss +2 -2
  90. package/scss/boot.scss +19 -0
  91. package/scss/shared/layout.scss +5 -0
  92. package/scss/themes/_light.scss +3 -3
  93. package/src/components/alert/alert.scss +1 -0
  94. package/src/components/animated-button/README.md +306 -0
  95. package/src/components/animated-button/animated-button.component.ts +229 -0
  96. package/src/components/animated-button/animated-button.scss +228 -0
  97. package/src/components/animated-button/animated-button.test.ts +143 -0
  98. package/src/components/animated-button/index.ts +12 -0
  99. package/src/components/button/button.component.ts +5 -0
  100. package/src/components/button-group/button-group.component.ts +25 -13
  101. package/src/components/button-group/button-group.scss +6 -0
  102. package/src/components/checkbox/checkbox.component.ts +4 -0
  103. package/src/components/checkbox/checkbox.scss +7 -4
  104. package/src/components/checkbox-group/checkbox-group.scss +7 -0
  105. package/src/components/chip/chip.component.ts +7 -2
  106. package/src/components/chip/chip.scss +16 -2
  107. package/src/components/collapsible/collapsible.component.ts +27 -20
  108. package/src/components/cols/cols.component.ts +14 -2
  109. package/src/components/cols/cols.scss +6 -12
  110. package/src/components/confirm/confirm.component.ts +7 -0
  111. package/src/components/copy-button/copy-button.component.ts +12 -11
  112. package/src/components/data-select/data-select.component.ts +215 -44
  113. package/src/components/data-select/data-select.scss +66 -6
  114. package/src/components/data-select/providers/currency-data-provider.ts +20 -6
  115. package/src/components/data-select/providers/provider.ts +1 -0
  116. package/src/components/data-select/providers/us-state-data-provider.ts +68 -0
  117. package/src/components/data-table/data-table.component.ts +81 -22
  118. package/src/components/data-table-search/data-table-search.component.ts +184 -0
  119. package/src/components/data-table-search/data-table-search.scss +17 -0
  120. package/src/components/data-table-search/data-table-search.test.ts +11 -0
  121. package/src/components/data-table-search/index.ts +12 -0
  122. package/src/components/datepicker/datepicker.component.ts +347 -13
  123. package/src/components/dialog/dialog.component.ts +1 -1
  124. package/src/components/empty-state/empty-state.scss +6 -1
  125. package/src/components/header/header.scss +3 -3
  126. package/src/components/icon/icon.component.ts +23 -1
  127. package/src/components/icon/icon.scss +23 -0
  128. package/src/components/icon-picker/brand-icons.ts +24 -0
  129. package/src/components/icon-picker/icon-picker.component.ts +407 -0
  130. package/src/components/icon-picker/icon-picker.scss +205 -0
  131. package/src/components/icon-picker/index.ts +12 -0
  132. package/src/components/icon-picker/line-icons.ts +612 -0
  133. package/src/components/icon-picker/material-icons.ts +14543 -0
  134. package/src/components/inline-edit/inline-edit.component.ts +190 -46
  135. package/src/components/inline-edit/inline-edit.scss +29 -0
  136. package/src/components/inline-edit/inline-edit.test.ts +314 -1
  137. package/src/components/input/input.component.ts +324 -5
  138. package/src/components/input/input.scss +87 -0
  139. package/src/components/input-group/input-group.component.ts +28 -7
  140. package/src/components/input-group/input-group.scss +48 -10
  141. package/src/components/item/item.component.ts +24 -3
  142. package/src/components/item/item.scss +18 -0
  143. package/src/components/key/index.ts +12 -0
  144. package/src/components/key/key.component.ts +47 -0
  145. package/src/components/key/key.scss +38 -0
  146. package/src/components/key-container/index.ts +12 -0
  147. package/src/components/key-container/key-container.component.ts +142 -0
  148. package/src/components/key-container/key-container.scss +33 -0
  149. package/src/components/menu-item/menu-item.component.ts +20 -0
  150. package/src/components/menu-item/menu-item.scss +69 -2
  151. package/src/components/navbar/navbar.component.ts +103 -11
  152. package/src/components/navbar/navbar.scss +6 -1
  153. package/src/components/opt-group/index.ts +12 -0
  154. package/src/components/opt-group/opt-group.component.ts +74 -0
  155. package/src/components/opt-group/opt-group.scss +37 -0
  156. package/src/components/opt-group/opt-group.test.ts +27 -0
  157. package/src/components/option/option.component.ts +1 -16
  158. package/src/components/option/option.scss +4 -0
  159. package/src/components/panel/panel.component.ts +10 -8
  160. package/src/components/panel/panel.scss +20 -0
  161. package/src/components/priority-list/index.ts +12 -0
  162. package/src/components/priority-list/priority-list.component.ts +611 -0
  163. package/src/components/priority-list/priority-list.scss +185 -0
  164. package/src/components/priority-list/priority-list.test.ts +139 -0
  165. package/src/components/progress-bar/progress-bar.component.ts +22 -16
  166. package/src/components/radio/radio.scss +10 -3
  167. package/src/components/select/select.component.ts +812 -72
  168. package/src/components/select/select.scss +108 -2
  169. package/src/components/select/select.test.ts +147 -2
  170. package/src/components/settings-container/settings-container.component.ts +44 -19
  171. package/src/components/settings-container/settings-container.scss +1 -1
  172. package/src/components/skeleton/skeleton.component.ts +6 -1
  173. package/src/components/slideout/slideout.scss +8 -3
  174. package/src/components/sp/sp.component.ts +3 -1
  175. package/src/components/sp/sp.scss +19 -0
  176. package/src/components/stepper/stepper.component.ts +16 -10
  177. package/src/components/textarea/textarea.scss +0 -4
  178. package/src/components/tile/tile.component.ts +12 -3
  179. package/src/components/tile/tile.scss +1 -1
  180. package/src/components/timer/timer.component.ts +15 -2
  181. package/src/components/toggle/toggle.component.ts +29 -26
  182. package/src/components/toggle/toggle.scss +13 -1
  183. package/src/components/translation-group/index.ts +12 -0
  184. package/src/components/translation-group/translation-group.component.ts +207 -0
  185. package/src/components/translation-group/translation-group.scss +8 -0
  186. package/src/components/translations/translations.component.ts +90 -44
  187. package/src/components/translations/translations.scss +4 -0
  188. package/src/components/vertical-stepper/vertical-stepper.component.ts +14 -9
  189. package/src/events/events.ts +12 -7
  190. package/src/events/zn-language-change.ts +7 -0
  191. package/src/events/zn-purchase.ts +11 -0
  192. package/src/events/zn-redirect.ts +7 -0
  193. package/src/events/zn-reorder.ts +7 -0
  194. package/src/events/zn-search-change.ts +11 -0
  195. package/src/events/zn-submit.ts +1 -1
  196. package/src/form-control.scss +4 -0
  197. package/src/internal/form-navigation.ts +510 -0
  198. package/src/internal/form.ts +6 -0
  199. package/src/zinc.ts +91 -83
@@ -53,12 +53,24 @@ export default class ZnCols extends ZincElement {
53
53
  this.maxColumns = layout.reduce((a, b) => a + b, 0);
54
54
 
55
55
  const prefix = 'zn-col-';
56
- this.querySelectorAll(':scope > *').forEach((element: HTMLElement, index) => {
56
+ // Only count children slotted into the default slot (no slot attribute)
57
+ const children = Array.from(this.querySelectorAll(':scope > *:not([slot])')) as HTMLElement[];
58
+ const colsPerRow = layout.length;
59
+ const lastRowStart = children.length - (children.length % colsPerRow || colsPerRow);
60
+
61
+ children.forEach((element, index) => {
57
62
  const classes = element.className.split(' ').filter((c) => !c.startsWith(prefix));
58
63
  element.className = classes.join(' ');
59
64
 
60
- const col = index % layout.length;
65
+ const col = index % colsPerRow;
61
66
  element.classList.add(prefix + layout[col]);
67
+
68
+ // Clip overflow on last-row elements to hide the ::before border pseudo
69
+ if(this.border && index >= lastRowStart) {
70
+ element.style.overflow = 'hidden';
71
+ } else {
72
+ element.style.overflow = '';
73
+ }
62
74
  });
63
75
 
64
76
  return html`
@@ -6,6 +6,10 @@
6
6
  --col-width: 23%;
7
7
  }
8
8
 
9
+ :host([contents]) {
10
+ display: contents;
11
+ }
12
+
9
13
  .cols {
10
14
  display: flex;
11
15
  flex-wrap: wrap;
@@ -72,7 +76,8 @@
72
76
  }
73
77
 
74
78
  // Adds full-width lines between bordered cols
75
- &--border ::slotted(*:not(:last-child)):before {
79
+ // Last row elements get overflow:hidden via JS to clip this pseudo
80
+ &--border ::slotted(*):before {
76
81
  content: "";
77
82
  position: absolute;
78
83
  bottom: -1px;
@@ -82,17 +87,6 @@
82
87
  border-bottom: 1px solid rgb(var(--zn-border-color)) !important;
83
88
  }
84
89
 
85
- // Stops the last child/row having a border
86
- &--border ::slotted(*:last-child):before {
87
- content: "";
88
- position: absolute;
89
- bottom: -1px;
90
- left: -100vw;
91
- right: -5px;
92
- z-index: 1111;
93
- border-bottom: 1px solid var(--zn-panel-background-color) !important;
94
- }
95
-
96
90
  &--pad {
97
91
  padding: var(--zn-spacing-2x-small);
98
92
  }
@@ -178,6 +178,13 @@ export default class ZnConfirm extends ZincElement {
178
178
  form.action = this.action;
179
179
  form.method = 'POST';
180
180
 
181
+ // copy data-attributes from the confirm element to the form
182
+ Array.from(this.attributes).forEach(attr => {
183
+ if (attr.name.startsWith('data-')) {
184
+ form?.setAttribute(attr.name, attr.value);
185
+ }
186
+ });
187
+
181
188
  this.querySelectorAll('input').forEach((el: HTMLInputElement) => {
182
189
  form?.appendChild(el.cloneNode() as Node);
183
190
  })
@@ -1,10 +1,10 @@
1
- import {property, query, state} from 'lit/decorators.js';
1
+ import {classMap} from "lit/directives/class-map.js";
2
2
  import {type CSSResultGroup, html, unsafeCSS} from 'lit';
3
+ import {property, query, state} from 'lit/decorators.js';
3
4
  import ZincElement from '../../internal/zinc-element';
5
+ import type ZnTooltip from "../tooltip";
4
6
 
5
7
  import styles from './copy-button.scss';
6
- import {classMap} from "lit/directives/class-map.js";
7
- import ZnTooltip from "../tooltip";
8
8
 
9
9
  /**
10
10
  * @summary Short summary of the component's intended use.
@@ -44,6 +44,7 @@ export default class ZnCopyButton extends ZincElement {
44
44
 
45
45
  @property() src = '';
46
46
 
47
+ @property({type: Number, reflect: true}) size: number = 24;
47
48
 
48
49
  /**
49
50
  * An id that references an element in the same document from which data will be copied. If both this and `value` are
@@ -67,20 +68,20 @@ export default class ZnCopyButton extends ZincElement {
67
68
  content=${copyLabel}>
68
69
  <button class="copy-button__button" part="button" @click=${this.handleCopy}>
69
70
  <slot part="copy-icon" name="copy-icon">
70
- <zn-icon src="${this.src ? this.src : 'content_copy'}"></zn-icon>
71
+ <zn-icon src="${this.src ? this.src : 'content_copy'}" size="${this.size}"></zn-icon>
71
72
  </slot>
72
73
  <slot part="success-icon" name="success-icon" hidden>
73
- <zn-icon src="check"></zn-icon>
74
+ <zn-icon src="check" size="${this.size}"></zn-icon>
74
75
  </slot>
75
76
  <slot part="error-icon" name="error-icon" hidden>
76
- <zn-icon src="priority_high"></zn-icon>
77
+ <zn-icon src="priority_high" size="${this.size}"></zn-icon>
77
78
  </slot>
78
79
  </button>
79
80
  </zn-tooltip>
80
81
  `;
81
82
  }
82
83
 
83
- private async showStatus(status: 'success' | 'error') {
84
+ private showStatus(status: 'success' | 'error') {
84
85
  const iconToShow = status === 'success' ? this.successIcon : this.errorIcon;
85
86
  this.tooltip.content = status === 'success' ? 'Copied!' : 'Error!';
86
87
 
@@ -103,7 +104,7 @@ export default class ZnCopyButton extends ZincElement {
103
104
  }
104
105
  this.isCopying = true;
105
106
 
106
- let valueToCopy = this.value||this.textContent;
107
+ let valueToCopy = this.value || this.textContent;
107
108
 
108
109
  if (this.from) {
109
110
  const root = this.getRootNode() as ShadowRoot | Document;
@@ -140,14 +141,14 @@ export default class ZnCopyButton extends ZincElement {
140
141
 
141
142
  if (!valueToCopy) {
142
143
  console.error('No value to copy. Please provide a value or a valid "from" attribute.');
143
- await this.showStatus('error');
144
+ this.showStatus('error');
144
145
  this.emit('zn-error');
145
146
  return;
146
147
  }
147
148
 
148
149
  try {
149
150
  await navigator.clipboard.writeText(valueToCopy);
150
- await this.showStatus('success');
151
+ this.showStatus('success');
151
152
  this.emit('zn-copy', {
152
153
  detail: {
153
154
  value: valueToCopy
@@ -155,7 +156,7 @@ export default class ZnCopyButton extends ZincElement {
155
156
  })
156
157
  } catch (error) {
157
158
  console.error('Failed to copy text: ', error);
158
- await this.showStatus('error');
159
+ this.showStatus('error');
159
160
  this.emit('zn-error');
160
161
  }
161
162
 
@@ -6,48 +6,73 @@ import {
6
6
  type DataProviderOption,
7
7
  emptyDataProvider,
8
8
  type LocalDataProvider,
9
+ usStateDataProvider,
9
10
  } from "./providers/provider";
10
11
  import {type CSSResultGroup, html, nothing, type PropertyValues, unsafeCSS} from 'lit';
11
12
  import {FormControlController} from "../../internal/form";
12
13
  import {property, query} from 'lit/decorators.js';
13
14
  import {watch} from "../../internal/watch";
14
15
  import ZincElement from '../../internal/zinc-element';
16
+ import ZnOption from "../option";
17
+ import ZnSelect from "../select";
15
18
  import type {ZincFormControl} from '../../internal/zinc-element';
16
- import type ZnSelect from "../select";
17
19
 
18
20
  import styles from './data-select.scss';
19
21
 
20
22
  /**
21
- * @summary Short summary of the component's intended use.
23
+ * @summary A select component with built-in data providers for common options like colors, currencies, and countries.
22
24
  * @documentation https://zinc.style/components/data-select
23
25
  * @status experimental
24
26
  * @since 1.0
25
27
  *
26
- * @dependency zn-example
28
+ * @dependency zn-select
29
+ * @dependency zn-option
27
30
  *
28
- * @event zn-event-name - Emitted as an example.
31
+ * @event zn-input - Emitted when the select's value changes.
32
+ * @event zn-clear - Emitted when the clear button is activated.
33
+ * @event blur - Emitted when the select loses focus.
29
34
  *
30
- * @slot - The default slot.
31
- * @slot example - An example slot.
35
+ * @slot label - The select's label. Alternatively, you can use the `label` attribute.
36
+ * @slot label-tooltip - Used to add text that is displayed in a tooltip next to the label. Alternatively, you can use the `label-tooltip` attribute.
37
+ * @slot context-note - Used to add contextual text that is displayed above the select, on the right. Alternatively, you can use the `context-note` attribute.
38
+ * @slot help-text - Text that describes how to use the select. Alternatively, you can use the `help-text` attribute.
32
39
  *
33
- * @csspart base - The component's base wrapper.
34
- *
35
- * @cssproperty --example - An example CSS custom property.
40
+ * @csspart combobox - The container that wraps the prefix, combobox, clear icon, and expand button (forwarded from zn-select).
41
+ * @csspart expand-icon - The container that wraps the expand icon (forwarded from zn-select).
42
+ * @csspart form-control-help-text - The help text's wrapper (forwarded from zn-select).
43
+ * @csspart form-control-input - The select's wrapper (forwarded from zn-select).
44
+ * @csspart display-input - The element that displays the selected option's label (forwarded from zn-select).
36
45
  */
37
46
  export default class ZnDataSelect extends ZincElement implements ZincFormControl {
38
47
  static styles: CSSResultGroup = unsafeCSS(styles);
48
+ static dependencies = {
49
+ 'zn-select': ZnSelect,
50
+ 'zn-option': ZnOption,
51
+ };
39
52
 
40
53
  @query('#select') select: ZnSelect;
41
- @query('#select__prefix') selectPrefix: HTMLElement;
42
54
 
43
55
  /** The name of the select. Used for form submission. */
44
56
  @property() name: string;
45
57
 
46
- /** The value of the select. Used for form submission. */
47
- @property() value: string;
58
+ /** The value of the select. Used for form submission. When `multiple` is enabled, this is an array of strings. */
59
+ @property({
60
+ converter: {
61
+ fromAttribute: (value: string | null) => {
62
+ if (!value) return '';
63
+ return value;
64
+ },
65
+ toAttribute: (value: string | string[]) => {
66
+ if (Array.isArray(value)) {
67
+ return value.join(',');
68
+ }
69
+ return value || '';
70
+ }
71
+ }
72
+ }) value: string | string[] = '';
48
73
 
49
74
  /** The provider of the select. */
50
- @property() provider: 'color' | 'currency' | 'country' | 'phone';
75
+ @property() provider: 'color' | 'currency' | 'country' | 'phone' | 'us-state';
51
76
 
52
77
  /** The position of the icon. */
53
78
  @property({attribute: 'icon-position'}) iconPosition: 'start' | 'end' | 'none' = 'none';
@@ -70,6 +95,9 @@ export default class ZnDataSelect extends ZincElement implements ZincFormControl
70
95
  /** Include an "All" option at the top. */
71
96
  @property({type: Boolean, attribute: 'allow-all'}) allowAll = false;
72
97
 
98
+ /** Include a "Common" option that selects multiple common currencies. */
99
+ @property({type: Boolean, attribute: 'allow-common'}) allowCommon = false;
100
+
73
101
  /** The selects label. If you need to display HTML, use the `label` slot instead. */
74
102
  @property() label = '';
75
103
 
@@ -91,12 +119,23 @@ export default class ZnDataSelect extends ZincElement implements ZincFormControl
91
119
  /** The selects required attribute. */
92
120
  @property({type: Boolean, reflect: true}) required = false;
93
121
 
122
+ /** Disables the select. */
123
+ @property({type: Boolean, reflect: true}) disabled = false;
124
+
94
125
  @property({attribute: "icon-only", type: Boolean, reflect: true}) iconOnly = false;
95
126
 
96
127
  @property({type: Boolean}) multiple = false;
97
128
 
129
+ @property({attribute: "select-first", type: Boolean}) selectFirst = false;
130
+
131
+ @property() distinct = "";
132
+
133
+ @property() conditional = "";
134
+
98
135
  protected readonly formControlController = new FormControlController(this);
99
136
 
137
+ private selectObserver?: MutationObserver;
138
+
100
139
  get validationMessage() {
101
140
  return this.select.validationMessage;
102
141
  }
@@ -120,13 +159,39 @@ export default class ZnDataSelect extends ZincElement implements ZincFormControl
120
159
  disconnectedCallback() {
121
160
  super.disconnectedCallback();
122
161
  window.removeEventListener('keydown', this.closeOnTab);
162
+ this.selectObserver?.disconnect();
123
163
  }
124
164
 
125
- protected firstUpdated(_changedProperties: PropertyValues) {
165
+ protected async firstUpdated(_changedProperties: PropertyValues) {
126
166
  super.firstUpdated(_changedProperties);
167
+
168
+ if (this.select) {
169
+ await this.select.updateComplete;
170
+ this.select.addEventListener('zn-change', () => {
171
+ this._updatePrefix();
172
+ });
173
+ this._observeSelectDisabled();
174
+ }
127
175
  this._updatePrefix();
128
176
  }
129
177
 
178
+ protected async updated(_changedProperties: PropertyValues) {
179
+ super.updated(_changedProperties);
180
+
181
+ if (_changedProperties.has('value') || _changedProperties.has('multiple')) {
182
+ this._normalizeValue();
183
+ }
184
+
185
+ if (_changedProperties.has('value') ||
186
+ _changedProperties.has('provider') ||
187
+ _changedProperties.has('iconPosition')) {
188
+ if (this.select) {
189
+ await this.select.updateComplete;
190
+ }
191
+ this._updatePrefix();
192
+ }
193
+ }
194
+
130
195
  checkValidity(): boolean {
131
196
  return this.select.checkValidity();
132
197
  }
@@ -145,7 +210,7 @@ export default class ZnDataSelect extends ZincElement implements ZincFormControl
145
210
 
146
211
  closeOnTab = (e: KeyboardEvent) => {
147
212
  if (this.select.open && e.key === 'Tab') {
148
- this.select.hide();
213
+ this.select.hide().then();
149
214
  }
150
215
  }
151
216
 
@@ -153,15 +218,20 @@ export default class ZnDataSelect extends ZincElement implements ZincFormControl
153
218
  async handleValueChange() {
154
219
  await this.updateComplete;
155
220
  this.formControlController.updateValidity();
221
+ if (this.select) {
222
+ await this.select.updateComplete;
223
+ await new Promise(resolve => setTimeout(resolve, 0));
224
+ }
156
225
  this._updatePrefix();
157
226
  }
158
227
 
159
228
  handleInput = (e: Event) => {
160
- this.value = (e.target as (HTMLInputElement | HTMLSelectElement)).value;
229
+ const target = e.target as ZnSelect;
230
+ this.value = target.value ?? (this.multiple ? [] : '');
161
231
  };
162
232
 
163
233
  handleClear = () => {
164
- this.value = '';
234
+ this.value = this.multiple ? [] : '';
165
235
  }
166
236
 
167
237
  getLocalProvider(name: string): LocalDataProvider<DataProviderOption> {
@@ -169,11 +239,13 @@ export default class ZnDataSelect extends ZincElement implements ZincFormControl
169
239
  case 'color':
170
240
  return colorDataProvider;
171
241
  case 'currency':
172
- return currencyDataProvider;
242
+ return currencyDataProvider(this.allowCommon);
173
243
  case 'country':
174
244
  return countryDataProvider;
175
245
  case 'phone':
176
246
  return countryDialPrefixDataProvider;
247
+ case 'us-state':
248
+ return usStateDataProvider;
177
249
  default:
178
250
  return emptyDataProvider;
179
251
  }
@@ -189,16 +261,35 @@ export default class ZnDataSelect extends ZincElement implements ZincFormControl
189
261
  let data = localProvider.getData;
190
262
 
191
263
  if (filterKeys.length) {
192
- data = localProvider.getData.filter(item => filterKeys.includes(item.key));
264
+ const normalizedFilterKeys = filterKeys.map(key => key.toUpperCase());
265
+ data = localProvider.getData.reduce<DataProviderOption[]>((acc, item) => {
266
+ const key = item.key.toUpperCase();
267
+ if (key.includes(',')) {
268
+ const parts = key.split(',').map(p => p.trim());
269
+ const matchingParts = parts.filter(part => normalizedFilterKeys.includes(part));
270
+ if (matchingParts.length > 1) {
271
+ acc.push({
272
+ ...item,
273
+ key: matchingParts.join(','),
274
+ value: `Common - ${matchingParts.join(', ')}`
275
+ });
276
+ }
277
+ } else if (normalizedFilterKeys.includes(key)) {
278
+ acc.push(item);
279
+ }
280
+ return acc;
281
+ }, []);
193
282
  }
194
283
 
195
284
  if (this.provider !== 'color' && this.allowAll) {
196
- const label = {
285
+ const label: Record<string, string> = {
197
286
  currency: 'All Currencies',
198
287
  country: 'All Countries',
199
- phone: 'All Phones'
200
- }[this.provider] || 'All';
201
- const allOption: DataProviderOption = {key: '', value: label, prefix: ''};
288
+ phone: 'All Phones',
289
+ 'us-state': 'All States'
290
+ };
291
+ const allLabel = label[this.provider] || 'All';
292
+ const allOption: DataProviderOption = {key: '', value: allLabel, prefix: ''};
202
293
  data = [allOption, ...data.filter(item => item.key !== allOption.key)];
203
294
  }
204
295
 
@@ -213,50 +304,130 @@ export default class ZnDataSelect extends ZincElement implements ZincFormControl
213
304
  required=${this.required || nothing}
214
305
  placement=${this.placement}
215
306
  name="${this.name}"
307
+ distinct="${this.distinct}"
308
+ ?disabled="${this.disabled}"
309
+ ?select-first="${this.selectFirst}"
310
+ conditional="${this.conditional}"
216
311
  @zn-input="${this.handleInput}"
217
312
  @zn-clear="${this.handleClear}"
218
313
  @blur=${this.blur}
219
- value="${this.value}"
314
+ .value="${this.value}"
220
315
  ?multiple=${this.multiple}
221
316
  placeholder="${this.getPlaceholder(localProvider)}"
222
317
  exportparts="combobox,expand-icon,form-control-help-text,form-control-input,display-input">
223
318
  ${(this.iconPosition !== 'none' || this.iconOnly) ? html`
224
- <div id="select__prefix" slot="prefix" class="select__prefix"></div>` : ''}
319
+ <div slot="prefix"
320
+ class="select__icon ${this.iconPosition === 'end' ? 'select__icon--end' : ''}"></div>` : ''}
225
321
  ${data.map((item: DataProviderOption) => html`
226
322
  <zn-option class="select__option" value="${item.key}">
227
323
  ${(this.iconPosition !== 'none' || this.iconOnly) ? html`<span
228
- slot="${this.iconPosition === 'end' ? 'suffix' : 'prefix'}">${item.prefix}</span>` : ''}
324
+ slot="prefix">${item.prefix}</span>` : ''}
229
325
  ${this.iconOnly ? undefined : item.value}
230
326
  </zn-option>`)}
231
327
  </zn-select>`;
232
328
  }
233
329
 
234
330
  private _updatePrefix() {
235
- // Set the prefix of the select to the selected values prefix
236
- const selectedOption = this.select.selectedOptions[0];
237
- if (selectedOption && (this.iconPosition !== 'none' || this.iconOnly) && this.selectPrefix) {
238
- if (this.iconPosition === 'none' && this.iconOnly) {
239
- this.iconPosition = 'start';
240
- }
241
- const slot = this.iconPosition === 'start'
242
- ? selectedOption.querySelector('[slot="prefix"]')
243
- : selectedOption.querySelector('[slot="suffix"]');
244
- if (slot) {
245
- this.selectPrefix.innerHTML = '';
246
- this.selectPrefix.appendChild(slot.cloneNode(true));
247
- } else {
248
- this.selectPrefix.innerHTML = '';
249
- }
250
- } else if (this.selectPrefix) {
251
- this.selectPrefix.innerHTML = '';
331
+ if (!this.select || !this.shadowRoot) {
332
+ return;
333
+ }
334
+
335
+ const selectIcon = this.shadowRoot.querySelector('.select__icon')!;
336
+ const shouldShowIcon = this.iconPosition !== 'none' || this.iconOnly;
337
+ if (!shouldShowIcon) {
338
+ if (selectIcon) selectIcon.innerHTML = '';
339
+ this._updateIconEmptyState();
340
+ return;
341
+ }
342
+
343
+ if (this.iconPosition === 'none' && this.iconOnly) {
344
+ this.iconPosition = 'start';
345
+ }
346
+
347
+ const selectedOptions = this.select.selectedOptions;
348
+ if (!selectedOptions || selectedOptions.length === 0) {
349
+ if (selectIcon) selectIcon.innerHTML = '';
350
+ this._updateIconEmptyState();
351
+ return;
352
+ }
353
+
354
+ if (!selectIcon) {
355
+ return;
356
+ }
357
+
358
+ const selectedOption = selectedOptions[0];
359
+ const slot = selectedOption.querySelector('[slot="prefix"]');
360
+
361
+ if (slot) {
362
+ selectIcon.innerHTML = '';
363
+ selectIcon.appendChild(slot.cloneNode(true));
364
+ } else {
365
+ selectIcon.innerHTML = '';
366
+ }
367
+
368
+ this._updateIconEmptyState();
369
+ }
370
+
371
+ private _updateIconEmptyState() {
372
+ if (!this.shadowRoot) return;
373
+
374
+ const selectIcon = this.shadowRoot.querySelector('.select__icon');
375
+ const isEmpty = !selectIcon || selectIcon.innerHTML.trim() === '';
376
+
377
+ if (isEmpty) {
378
+ this.setAttribute('icon-empty', '');
379
+ } else {
380
+ this.removeAttribute('icon-empty');
252
381
  }
253
382
  }
254
383
 
255
384
  private getPlaceholder(localProvider: LocalDataProvider<any>) {
256
- if (this.value) {
385
+ if (!this.value) {
386
+ return `Choose a ${localProvider.getName}`;
387
+ }
388
+
389
+ const hasValue = Array.isArray(this.value) ? this.value.length > 0 : this.value.length > 0;
390
+ if (hasValue) {
257
391
  return '';
258
392
  }
259
393
 
260
394
  return `Choose a ${localProvider.getName}`
261
395
  }
396
+
397
+ private _observeSelectDisabled() {
398
+ if (!this.select) return;
399
+
400
+ this._syncDisabledState();
401
+
402
+ this.selectObserver = new MutationObserver(() => {
403
+ this._syncDisabledState();
404
+ });
405
+
406
+ this.selectObserver.observe(this.select, {
407
+ attributes: true,
408
+ attributeFilter: ['disabled']
409
+ });
410
+ }
411
+
412
+ private _syncDisabledState() {
413
+ if (!this.select) return;
414
+
415
+ this.disabled = this.select.disabled;
416
+ }
417
+
418
+ private _normalizeValue() {
419
+ if (this.multiple) {
420
+ if (typeof this.value === 'string') {
421
+ if (this.value === '') {
422
+ this.value = [];
423
+ } else {
424
+ this.value = this.value.split(',').map(v => v.trim()).filter(v => v !== '');
425
+ }
426
+ }
427
+ } else {
428
+ if (Array.isArray(this.value)) {
429
+ this.value = this.value.length > 0 ? this.value[0] : '';
430
+ }
431
+ }
432
+ }
262
433
  }
@@ -1,5 +1,6 @@
1
1
  @use "../../wc";
2
2
 
3
+ // Color icon palette
3
4
  $colors: (
4
5
  red: var(--zn-color-red-500),
5
6
  blue: var(--zn-color-blue-500),
@@ -12,15 +13,47 @@ $colors: (
12
13
  gray: var(--zn-color-gray-500)
13
14
  );
14
15
 
16
+ // Size spacing configuration
17
+ $sizes: (
18
+ small: var(--zn-input-spacing-small),
19
+ medium: var(--zn-input-spacing-medium),
20
+ large: var(--zn-input-spacing-large)
21
+ );
22
+
23
+ // Mixin for icon spacing based on size
24
+ @mixin icon-spacing($size, $spacing) {
25
+ :host([size="#{$size}"]) .select__icon {
26
+ padding-inline-start: $spacing;
27
+ margin-inline-end: $spacing;
28
+ }
29
+ }
30
+
31
+ // Mixin for end-positioned icon spacing
32
+ @mixin icon-end-spacing($size, $spacing) {
33
+ :host([size="#{$size}"]) .select__icon--end {
34
+ padding-inline-start: 0;
35
+ padding-inline-end: $spacing;
36
+ margin-inline-end: 0;
37
+ margin-inline-start: auto;
38
+ }
39
+ }
40
+
41
+ // Mixin for combobox padding restoration
42
+ @mixin combobox-padding($condition, $size, $spacing) {
43
+ :host([#{$condition}][size="#{$size}"]) zn-select::part(combobox) {
44
+ padding-inline-start: $spacing !important;
45
+ }
46
+ }
47
+
15
48
  :host {
16
49
  display: block;
17
50
  }
18
51
 
19
52
  .color-icon {
20
- width: 15px;
21
- height: 15px;
53
+ width: var(--zn-spacing-medium);
54
+ height: var(--zn-spacing-medium);
22
55
  display: block;
23
- border-radius: 100%;
56
+ border-radius: var(--zn-border-radius-circle);
24
57
  flex-shrink: 0;
25
58
 
26
59
  @each $name, $color in $colors {
@@ -34,10 +67,37 @@ zn-select::part(listbox) {
34
67
  max-height: 300px;
35
68
  }
36
69
 
37
- .select__prefix {
38
- font-size: 20px;
70
+ .select__icon {
71
+ font-size: var(--zn-font-size-x-large);
72
+ line-height: 1;
73
+ display: inline-flex;
74
+ align-items: center;
39
75
 
40
76
  &:empty {
41
- display: contents;
77
+ display: none;
42
78
  }
43
79
  }
80
+
81
+ @each $size, $spacing in $sizes {
82
+ @include icon-spacing($size, $spacing);
83
+ @include icon-end-spacing($size, $spacing);
84
+ @include combobox-padding('icon-position="end"', $size, $spacing);
85
+ @include combobox-padding('icon-position="start"][icon-empty', $size, $spacing);
86
+ }
87
+
88
+ :host(:not([size])) .select__icon {
89
+ padding-inline-start: var(--zn-input-spacing-medium);
90
+ margin-inline-end: var(--zn-input-spacing-medium);
91
+ }
92
+
93
+ :host(:not([size])) .select__icon--end {
94
+ padding-inline-start: 0;
95
+ padding-inline-end: var(--zn-input-spacing-medium);
96
+ margin-inline-end: 0;
97
+ margin-inline-start: auto;
98
+ }
99
+
100
+ :host([icon-position="end"]:not([size])) zn-select::part(combobox),
101
+ :host([icon-position="start"][icon-empty]:not([size])) zn-select::part(combobox) {
102
+ padding-inline-start: var(--zn-input-spacing-medium) !important;
103
+ }