@duetds/components 8.5.5 → 8.6.0

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 (212) hide show
  1. package/hydrate/index.js +289 -113
  2. package/lib/cjs/duet-alert.cjs.entry.js +1 -1
  3. package/lib/cjs/duet-banner.cjs.entry.js +1 -1
  4. package/lib/cjs/duet-callout.cjs.entry.js +1 -1
  5. package/lib/cjs/duet-chip.cjs.entry.js +9 -2
  6. package/lib/cjs/duet-choice_2.cjs.entry.js +1 -1
  7. package/lib/cjs/duet-collapsible.cjs.entry.js +1 -1
  8. package/lib/cjs/duet-combobox.cjs.entry.js +260 -127
  9. package/lib/cjs/duet-date-picker.cjs.entry.js +1 -1
  10. package/lib/cjs/duet-fieldset.cjs.entry.js +1 -1
  11. package/lib/cjs/duet-hero.cjs.entry.js +1 -1
  12. package/lib/cjs/duet-input_2.cjs.entry.js +39 -3
  13. package/lib/cjs/duet-modal.cjs.entry.js +1 -1
  14. package/lib/cjs/duet-multiselect.cjs.entry.js +1 -1
  15. package/lib/cjs/duet-number-input.cjs.entry.js +1 -1
  16. package/lib/cjs/duet-promo-card.cjs.entry.js +1 -1
  17. package/lib/cjs/duet-radio_2.cjs.entry.js +1 -1
  18. package/lib/cjs/duet-scrollable_3.cjs.entry.js +1 -1
  19. package/lib/cjs/duet-select.cjs.entry.js +1 -1
  20. package/lib/cjs/duet-slideout-panel.cjs.entry.js +1 -1
  21. package/lib/cjs/duet-slideout.cjs.entry.js +1 -1
  22. package/lib/cjs/duet-textarea.cjs.entry.js +1 -1
  23. package/lib/cjs/duet.cjs.js +1 -1
  24. package/lib/cjs/loader.cjs.js +1 -1
  25. package/lib/cjs/{slot-utils-f5073417.js → slot-utils-03a40c78.js} +3 -1
  26. package/lib/collection/components/duet-chip/duet-chip.css +7 -0
  27. package/lib/collection/components/duet-chip/duet-chip.js +29 -2
  28. package/lib/collection/components/duet-combobox/duet-combobox-select-single.js +2 -2
  29. package/lib/collection/components/duet-combobox/duet-combobox.css +10 -2
  30. package/lib/collection/components/duet-combobox/duet-combobox.e2e.js +81 -51
  31. package/lib/collection/components/duet-combobox/duet-combobox.js +321 -142
  32. package/lib/collection/components/duet-input/duet-input.css +29 -0
  33. package/lib/collection/components/duet-input/duet-input.js +159 -3
  34. package/lib/collection/utils/slot-utils.js +3 -1
  35. package/lib/dist-custom-elements/duet-action-button.js +1 -1
  36. package/lib/dist-custom-elements/duet-alert.js +1 -1
  37. package/lib/dist-custom-elements/duet-banner.js +1 -1
  38. package/lib/dist-custom-elements/duet-breadcrumb.js +1 -1
  39. package/lib/dist-custom-elements/duet-callout.js +1 -1
  40. package/lib/dist-custom-elements/duet-card.js +1 -1
  41. package/lib/dist-custom-elements/duet-chip.js +1 -273
  42. package/lib/dist-custom-elements/duet-choice-group.js +5 -5
  43. package/lib/dist-custom-elements/duet-choice.js +2 -2
  44. package/lib/dist-custom-elements/duet-collapsible.js +2 -2
  45. package/lib/dist-custom-elements/duet-combobox.js +326 -132
  46. package/lib/dist-custom-elements/duet-cookie-consent.js +1 -1
  47. package/lib/dist-custom-elements/duet-date-picker.js +6 -6
  48. package/lib/dist-custom-elements/duet-editable-table.js +2 -2
  49. package/lib/dist-custom-elements/duet-fieldset.js +1 -1
  50. package/lib/dist-custom-elements/duet-footer.js +1 -1
  51. package/lib/dist-custom-elements/duet-header.js +1 -1
  52. package/lib/dist-custom-elements/duet-hero.js +1 -1
  53. package/lib/dist-custom-elements/duet-input.js +1 -1
  54. package/lib/dist-custom-elements/duet-menu-bar-dropdown-link.js +1 -1
  55. package/lib/dist-custom-elements/duet-menu-bar-dropdown.js +1 -1
  56. package/lib/dist-custom-elements/duet-modal.js +2 -2
  57. package/lib/dist-custom-elements/duet-multiselect.js +5 -5
  58. package/lib/dist-custom-elements/duet-notification-drawer.js +1 -1
  59. package/lib/dist-custom-elements/duet-notification.js +1 -1
  60. package/lib/dist-custom-elements/duet-number-input.js +6 -6
  61. package/lib/dist-custom-elements/duet-pagination.js +6 -6
  62. package/lib/dist-custom-elements/duet-popup-menu.js +1 -1
  63. package/lib/dist-custom-elements/duet-promo-card.js +1 -1
  64. package/lib/dist-custom-elements/duet-radio-group.js +5 -5
  65. package/lib/dist-custom-elements/duet-range-slider.js +1 -1
  66. package/lib/dist-custom-elements/duet-range-stepper.js +2 -2
  67. package/lib/dist-custom-elements/duet-scrollable.js +1 -1
  68. package/lib/dist-custom-elements/duet-select.js +1 -1
  69. package/lib/dist-custom-elements/duet-show-more.js +1 -1
  70. package/lib/dist-custom-elements/duet-slideout-panel.js +1 -1
  71. package/lib/dist-custom-elements/duet-slideout.js +2 -2
  72. package/lib/dist-custom-elements/duet-step.js +1 -1
  73. package/lib/dist-custom-elements/duet-submenu-bar-dropdown-link.js +1 -1
  74. package/lib/dist-custom-elements/duet-submenu-bar-dropdown.js +1 -1
  75. package/lib/dist-custom-elements/duet-submenu-bar-item.js +1 -1
  76. package/lib/dist-custom-elements/duet-submenu-bar-link.js +1 -1
  77. package/lib/dist-custom-elements/duet-tab-group.js +6 -6
  78. package/lib/dist-custom-elements/duet-tab.js +1 -1
  79. package/lib/dist-custom-elements/duet-textarea.js +4 -4
  80. package/lib/dist-custom-elements/duet-toggle.js +1 -1
  81. package/lib/dist-custom-elements/duet-toolbar-dropdown-link.js +1 -1
  82. package/lib/dist-custom-elements/duet-toolbar-dropdown.js +1 -1
  83. package/lib/dist-custom-elements/duet-tooltip-button.js +1 -1
  84. package/lib/dist-custom-elements/duet-tooltip-popup.js +1 -1
  85. package/lib/dist-custom-elements/duet-tooltip.js +1 -1
  86. package/lib/dist-custom-elements/duet-tray.js +1 -1
  87. package/lib/dist-custom-elements/duet-upload-item.js +1 -1
  88. package/lib/dist-custom-elements/duet-upload.js +5 -5
  89. package/lib/dist-custom-elements/{p-cff358b0.js → p-014c1177.js} +49 -8
  90. package/lib/dist-custom-elements/{p-c3d02eb2.js → p-03152b20.js} +1 -1
  91. package/lib/dist-custom-elements/{p-d87f6dd5.js → p-06e608ae.js} +3 -1
  92. package/lib/dist-custom-elements/{p-9cdbc360.js → p-0cee8a12.js} +1 -1
  93. package/lib/dist-custom-elements/{p-12a0876b.js → p-24693e9a.js} +1 -1
  94. package/lib/dist-custom-elements/{p-9a833e56.js → p-28ec3461.js} +1 -1
  95. package/lib/dist-custom-elements/{p-d191ba9e.js → p-45fd4d84.js} +1 -1
  96. package/lib/dist-custom-elements/{p-89d58b60.js → p-6d366100.js} +3 -3
  97. package/lib/dist-custom-elements/p-755dd68f.js +285 -0
  98. package/lib/dist-custom-elements/{p-e44c0eff.js → p-785686e3.js} +1 -1
  99. package/lib/dist-custom-elements/{p-e00d43f3.js → p-92a16064.js} +1 -1
  100. package/lib/dist-custom-elements/{p-f9f9e33d.js → p-a02e62f3.js} +3 -3
  101. package/lib/dist-custom-elements/{p-0d5c0a22.js → p-c8415e2f.js} +1 -1
  102. package/lib/dist-custom-elements/{p-d848b48d.js → p-e702eb52.js} +4 -4
  103. package/lib/duet/duet.esm.js +1 -1
  104. package/lib/duet/{p-88a46585.system.entry.js → p-061f4be0.system.entry.js} +1 -1
  105. package/lib/duet/p-06e608ae.js +4 -0
  106. package/lib/duet/{p-8ca813cb.system.entry.js → p-0778ccde.system.entry.js} +1 -1
  107. package/lib/duet/p-07ac7f3e.system.entry.js +4 -0
  108. package/lib/duet/p-15e24bf0.entry.js +4 -0
  109. package/lib/duet/{p-0dac34a4.system.entry.js → p-1ef0e5ca.system.entry.js} +1 -1
  110. package/lib/duet/{p-c9781f22.entry.js → p-2084a65f.entry.js} +1 -1
  111. package/lib/duet/{p-fd0bb0bf.entry.js → p-23cecf9f.entry.js} +1 -1
  112. package/lib/duet/{p-09e4195f.system.entry.js → p-257800b7.system.entry.js} +1 -1
  113. package/lib/duet/{p-1afd901a.entry.js → p-29491f9f.entry.js} +1 -1
  114. package/lib/duet/{p-3b5ff822.entry.js → p-3302d75d.entry.js} +1 -1
  115. package/lib/duet/{p-7629cd0a.entry.js → p-40375e30.entry.js} +1 -1
  116. package/lib/duet/{p-ba4445cf.system.entry.js → p-45bb3cd0.system.entry.js} +1 -1
  117. package/lib/duet/p-4781cd58.entry.js +4 -0
  118. package/lib/duet/p-4cb5943c.entry.js +4 -0
  119. package/lib/duet/{p-b2f2992f.system.entry.js → p-4de910bd.system.entry.js} +1 -1
  120. package/lib/duet/p-6151635f.system.js +1 -1
  121. package/lib/duet/{p-d349dd93.entry.js → p-67382632.entry.js} +1 -1
  122. package/lib/duet/{p-699a98b4.entry.js → p-76e54ff5.entry.js} +1 -1
  123. package/lib/duet/{p-4d2ef0e2.entry.js → p-7a161455.entry.js} +1 -1
  124. package/lib/duet/{p-3d38079c.entry.js → p-7ca15c93.entry.js} +1 -1
  125. package/lib/duet/{p-bb09fff0.system.entry.js → p-7de614df.system.entry.js} +1 -1
  126. package/lib/duet/{p-480f37b6.entry.js → p-83238fe7.entry.js} +1 -1
  127. package/lib/duet/{p-7c1db591.system.entry.js → p-8a4ba903.system.entry.js} +1 -1
  128. package/lib/duet/{p-4f32a7ee.entry.js → p-8ba91848.entry.js} +1 -1
  129. package/lib/duet/{p-708359ec.system.entry.js → p-999fff8d.system.entry.js} +1 -1
  130. package/lib/duet/{p-9d91a1dd.system.entry.js → p-9e75e5d8.system.entry.js} +1 -1
  131. package/lib/duet/{p-343d037e.entry.js → p-a3afb1af.entry.js} +1 -1
  132. package/lib/duet/{p-2ca6a8a0.system.entry.js → p-a3e90064.system.entry.js} +1 -1
  133. package/lib/duet/{p-892a5644.system.entry.js → p-aba91113.system.entry.js} +1 -1
  134. package/lib/duet/p-ae21ad57.system.js +4 -0
  135. package/lib/duet/{p-ce05fe6d.system.entry.js → p-b2fc4b72.system.entry.js} +1 -1
  136. package/lib/duet/{p-84778369.entry.js → p-b5595969.entry.js} +1 -1
  137. package/lib/duet/{p-bf51f8a5.system.entry.js → p-c68602c9.system.entry.js} +1 -1
  138. package/lib/duet/{p-f6530332.system.entry.js → p-d5721f0d.system.entry.js} +1 -1
  139. package/lib/duet/{p-c00fe4ed.entry.js → p-dc133655.entry.js} +1 -1
  140. package/lib/duet/p-de5054b6.system.entry.js +4 -0
  141. package/lib/duet/{p-61feb701.entry.js → p-df345202.entry.js} +1 -1
  142. package/lib/duet/{p-2628a322.entry.js → p-e0907f29.entry.js} +1 -1
  143. package/lib/duet/p-e333b9fc.system.entry.js +4 -0
  144. package/lib/duet/p-e411627f.entry.js +4 -0
  145. package/lib/duet/{p-da51c71f.system.entry.js → p-eafed149.system.entry.js} +1 -1
  146. package/lib/duet/p-f2279e1e.entry.js +4 -0
  147. package/lib/duet/{p-b1ab1664.system.entry.js → p-fa311641.system.entry.js} +1 -1
  148. package/lib/duet/{p-320318de.system.entry.js → p-fa72c79b.system.entry.js} +1 -1
  149. package/lib/esm/duet-alert.entry.js +1 -1
  150. package/lib/esm/duet-banner.entry.js +1 -1
  151. package/lib/esm/duet-callout.entry.js +1 -1
  152. package/lib/esm/duet-chip.entry.js +9 -2
  153. package/lib/esm/duet-choice_2.entry.js +1 -1
  154. package/lib/esm/duet-collapsible.entry.js +1 -1
  155. package/lib/esm/duet-combobox.entry.js +261 -128
  156. package/lib/esm/duet-date-picker.entry.js +1 -1
  157. package/lib/esm/duet-fieldset.entry.js +1 -1
  158. package/lib/esm/duet-hero.entry.js +1 -1
  159. package/lib/esm/duet-input_2.entry.js +39 -3
  160. package/lib/esm/duet-modal.entry.js +1 -1
  161. package/lib/esm/duet-multiselect.entry.js +1 -1
  162. package/lib/esm/duet-number-input.entry.js +1 -1
  163. package/lib/esm/duet-promo-card.entry.js +1 -1
  164. package/lib/esm/duet-radio_2.entry.js +1 -1
  165. package/lib/esm/duet-scrollable_3.entry.js +1 -1
  166. package/lib/esm/duet-select.entry.js +1 -1
  167. package/lib/esm/duet-slideout-panel.entry.js +1 -1
  168. package/lib/esm/duet-slideout.entry.js +1 -1
  169. package/lib/esm/duet-textarea.entry.js +1 -1
  170. package/lib/esm/duet.js +1 -1
  171. package/lib/esm/loader.js +1 -1
  172. package/lib/esm/{slot-utils-1115a819.js → slot-utils-b50aaef5.js} +3 -1
  173. package/lib/esm-es5/duet-alert.entry.js +1 -1
  174. package/lib/esm-es5/duet-banner.entry.js +1 -1
  175. package/lib/esm-es5/duet-callout.entry.js +1 -1
  176. package/lib/esm-es5/duet-chip.entry.js +1 -1
  177. package/lib/esm-es5/duet-choice_2.entry.js +1 -1
  178. package/lib/esm-es5/duet-collapsible.entry.js +1 -1
  179. package/lib/esm-es5/duet-combobox.entry.js +2 -2
  180. package/lib/esm-es5/duet-date-picker.entry.js +1 -1
  181. package/lib/esm-es5/duet-fieldset.entry.js +1 -1
  182. package/lib/esm-es5/duet-hero.entry.js +1 -1
  183. package/lib/esm-es5/duet-input_2.entry.js +2 -2
  184. package/lib/esm-es5/duet-modal.entry.js +1 -1
  185. package/lib/esm-es5/duet-multiselect.entry.js +1 -1
  186. package/lib/esm-es5/duet-number-input.entry.js +2 -2
  187. package/lib/esm-es5/duet-promo-card.entry.js +2 -2
  188. package/lib/esm-es5/duet-radio_2.entry.js +1 -1
  189. package/lib/esm-es5/duet-scrollable_3.entry.js +1 -1
  190. package/lib/esm-es5/duet-select.entry.js +1 -1
  191. package/lib/esm-es5/duet-slideout-panel.entry.js +1 -1
  192. package/lib/esm-es5/duet-slideout.entry.js +1 -1
  193. package/lib/esm-es5/duet-textarea.entry.js +1 -1
  194. package/lib/esm-es5/duet.js +1 -1
  195. package/lib/esm-es5/loader.js +1 -1
  196. package/lib/esm-es5/slot-utils-b50aaef5.js +4 -0
  197. package/lib/types/components/duet-chip/duet-chip.d.ts +4 -0
  198. package/lib/types/components/duet-combobox/duet-combobox.d.ts +49 -35
  199. package/lib/types/components/duet-input/duet-input.d.ts +30 -0
  200. package/lib/types/components.d.ts +44 -21
  201. package/package.json +2 -2
  202. package/lib/duet/p-7cf03aa8.system.js +0 -4
  203. package/lib/duet/p-92e1181c.entry.js +0 -4
  204. package/lib/duet/p-985c0c67.system.entry.js +0 -4
  205. package/lib/duet/p-9d27bc65.entry.js +0 -4
  206. package/lib/duet/p-afe29a3e.entry.js +0 -4
  207. package/lib/duet/p-cc4a8964.entry.js +0 -4
  208. package/lib/duet/p-d50ad3ed.entry.js +0 -4
  209. package/lib/duet/p-d87f6dd5.js +0 -4
  210. package/lib/duet/p-f1b01db9.system.entry.js +0 -4
  211. package/lib/duet/p-fa21a3b6.system.entry.js +0 -4
  212. package/lib/esm-es5/slot-utils-1115a819.js +0 -4
@@ -5,8 +5,9 @@ import { Build, h, Host, } from "@stencil/core";
5
5
  import { inheritGlobalTheme } from "../../common";
6
6
  import { createID } from "../../utils/create-id";
7
7
  import { debounce } from "../../utils/js-utils";
8
- import { isArrowDownKey, isArrowUpKey, isEnterKey, isEscapeKey, isTabKey } from "../../utils/keyboard-utils";
8
+ import { isArrowDownKey, isArrowUpKey, isBackspaceKey, isEnterKey, isEscapeKey, isTabKey, } from "../../utils/keyboard-utils";
9
9
  import { connectLanguageChangeObserver, disconnectLanguageChangeObserver, getLanguage, getLocaleString, } from "../../utils/language-utils";
10
+ import { getElementsFromDefaultSlot } from "../../utils/slot-utils";
10
11
  import { parsePossibleJSON } from "../../utils/string-utils";
11
12
  import { DuetComboBoxSelect } from "./duet-combobox-select";
12
13
  export class DuetCombobox {
@@ -21,24 +22,63 @@ export class DuetCombobox {
21
22
  // this.listElement.style.width = `${currentWidth + 1}px`
22
23
  }
23
24
  }, 100));
24
- this.handleInputKeyDownEvent = (e) => {
25
+ this.onInputChange = async (e) => {
26
+ const newValue = e.detail.value;
27
+ this.inputValue = newValue;
28
+ };
29
+ this.onInputClick = () => {
30
+ var _a, _b;
31
+ if (this.openListOnClick) {
32
+ this.listOpen = !this.listOpen;
33
+ }
34
+ else {
35
+ this.listOpen = ((_a = this.inputValue) === null || _a === void 0 ? void 0 : _a.length) >= this.minCharacters;
36
+ }
37
+ if (!((_b = this.getFilteredItems()) === null || _b === void 0 ? void 0 : _b.length)) {
38
+ this.listOpen = false;
39
+ }
40
+ };
41
+ this.onInputTyping = async (e) => {
42
+ var _a;
43
+ const newValue = e.detail.value;
44
+ if (!this.force && !this.multiple && this.selectedItems.size > 0 && newValue !== this.getSelectedItemLabel()) {
45
+ this.selectedItems.clear();
46
+ }
47
+ this.listOpen = String(newValue).length >= this.minCharacters && ((_a = this.getFilteredItems()) === null || _a === void 0 ? void 0 : _a.length) > 0;
48
+ };
49
+ this.onKeyDown = (e) => {
25
50
  //if selection is made, close the list and update internal and external values
51
+ const listItems = this.returnFilteredOrNonFiltered();
26
52
  // this should be set to undefined on everything BUT up/down arrow according to the aria 1.1 specs
27
53
  // https://www.w3.org/TR/wai-aria-practices-1.1/#combobox
28
54
  this.input.accessibleActiveDescendant = undefined;
29
55
  if (isEnterKey(e)) {
30
56
  e.preventDefault();
31
- this.selectActiveItem();
32
- this.updateInputText(false);
57
+ if (this.listOpen && this.activeItem !== undefined && listItems[this.activeItem]) {
58
+ if (this.multiple) {
59
+ this.addSelectedItem(listItems[this.activeItem].id, true);
60
+ }
61
+ else {
62
+ this.updateSelectedItem(listItems[this.activeItem].id, true);
63
+ }
64
+ }
65
+ this.activeItem = undefined;
33
66
  this.listOpen = false;
34
67
  return;
35
68
  }
36
- //tabbing out or escaping, restore initial state
37
69
  if (isEscapeKey(e) || isTabKey(e)) {
38
70
  this.updateInputText(true);
71
+ this.activeItem = undefined;
39
72
  this.listOpen = false;
40
73
  return;
41
74
  }
75
+ if (isBackspaceKey(e) && this.inputValue === "") {
76
+ if (this.multiple && this.selectedItems.size > 0) {
77
+ const lastItem = Array.from(this.selectedItems).pop();
78
+ this.removeSelectedItem(lastItem, true);
79
+ }
80
+ return;
81
+ }
42
82
  //handle arrow up/down navigation
43
83
  let nextActiveItem = this.activeItem;
44
84
  if (isArrowUpKey(e)) {
@@ -51,7 +91,6 @@ export class DuetCombobox {
51
91
  this.listOpen = true;
52
92
  }
53
93
  if (isArrowDownKey(e) || isArrowUpKey(e)) {
54
- const listItems = this.shouldListBeFiltered() ? this.getFilteredItems() : this.processedItems;
55
94
  if (nextActiveItem < 0 || !nextActiveItem) {
56
95
  nextActiveItem = 0;
57
96
  }
@@ -64,13 +103,10 @@ export class DuetCombobox {
64
103
  this.activeItem = nextActiveItem;
65
104
  this.scrollToActive();
66
105
  }
67
- this.announceActive();
68
106
  return;
69
107
  };
70
108
  this.processedItems = null;
71
- this.inputWidth = 0;
72
109
  this.inputValue = "";
73
- this.selectionMsg = "";
74
110
  this.listOpen = false;
75
111
  this.selectedItems = new Set();
76
112
  this.activeItem = undefined;
@@ -92,8 +128,11 @@ export class DuetCombobox {
92
128
  },
93
129
  };
94
130
  this.accessibleLabels = getLocaleString(this.accessibleLabelDefaults, getLanguage());
131
+ this.label = "";
132
+ this.caption = "";
95
133
  this.theme = "";
96
134
  this.force = false;
135
+ this.multiple = false;
97
136
  this.items = undefined;
98
137
  this.formatter = (item) => item && item.name ? item.name : "";
99
138
  this.value = undefined;
@@ -109,7 +148,7 @@ export class DuetCombobox {
109
148
  const path = e.composedPath();
110
149
  const isClickOutside = path.every(el => el !== this.element);
111
150
  if (isClickOutside) {
112
- this.updateInputText(true, true);
151
+ this.updateInputText(true);
113
152
  this.listOpen = false;
114
153
  }
115
154
  }
@@ -120,17 +159,67 @@ export class DuetCombobox {
120
159
  async formatItem(item) {
121
160
  return this.formatter(item);
122
161
  }
123
- async processItems() {
124
- this.items = this.processedItems = parsePossibleJSON(this.items);
125
- await this.updateInputText();
162
+ processItems() {
163
+ const ids = [];
164
+ this.processedItems = parsePossibleJSON(this.items);
165
+ if (!Array.isArray(this.processedItems)) {
166
+ console.error("DuetCombobox: Items should be an array");
167
+ return;
168
+ }
169
+ if (this.processedItems.length !== new Set([...this.processedItems]).size) {
170
+ console.warn("DuetCombobox: Duplicate item values found in the list");
171
+ }
172
+ this.processedItems = this.processedItems.map((item, index) => {
173
+ if (typeof item === "string") {
174
+ item = { name: item, value: item };
175
+ }
176
+ if (item.id !== undefined) {
177
+ item.id = String(item.id);
178
+ }
179
+ else {
180
+ item.id = `${JSON.stringify(item.value)}-${item.name}`;
181
+ }
182
+ if (ids.includes(item.id)) {
183
+ console.warn("DuetCombobox: Duplicate item id found in the list, postfixed with index");
184
+ item.id = `${item.id}-${index}`;
185
+ }
186
+ ids.push(item.id);
187
+ return item;
188
+ });
189
+ // remove any item selections that are no longer in the items list
190
+ const selectionsMissing = Array.from(this.selectedItems).filter(id => !this.processedItems.find(item => item.id === id));
191
+ if (selectionsMissing.length) {
192
+ if (this.multiple) {
193
+ selectionsMissing.forEach(id => this.removeSelectedItem(id));
194
+ }
195
+ else {
196
+ this.updateSelectedItem(selectionsMissing[0], true);
197
+ }
198
+ }
126
199
  }
127
- async processValue() {
128
- if (this.value) {
129
- const selectedItem = this.items.find(item => item.value === this.value);
130
- this.updateSelectedItems(selectedItem.id);
200
+ processValue() {
201
+ // empty string may be valid item value
202
+ if (this.value !== "" && !this.value) {
203
+ this.selectedItems.clear();
204
+ return;
205
+ }
206
+ const values = Array.isArray(this.value) ? this.value : [this.value];
207
+ const ids = this.processedItems.filter(item => values.includes(item.value)).map(item => item.id);
208
+ // if value corresponds to the selectedItems, do nothing
209
+ if (ids.length === this.selectedItems.size && ids.every(id => this.selectedItems.has(id))) {
210
+ return;
211
+ }
212
+ if (this.multiple) {
213
+ this.selectedItems.clear();
214
+ this.input.clearChips();
215
+ ids.forEach(id => this.addSelectedItem(id));
216
+ }
217
+ else {
218
+ const selectedItem = this.processedItems.find(item => item.value === this.value);
219
+ this.updateSelectedItem(selectedItem.id);
131
220
  }
132
221
  }
133
- async processListOpenChange() {
222
+ processListOpenChange() {
134
223
  if (this.openListOnClick) {
135
224
  this.input.icon = this.listOpen ? "action-arrow-up" : "action-arrow-down";
136
225
  }
@@ -146,66 +235,54 @@ export class DuetCombobox {
146
235
  if (Build.isServer) {
147
236
  return;
148
237
  }
149
- this.items = this.processedItems = parsePossibleJSON(this.items);
150
- this.input = this.element.querySelector("duet-input");
151
- this.input.addEventListener("duetChange", this.updateInputValue.bind(this));
152
- this.input.addEventListener("click", () => {
153
- if (!this.openListOnClick) {
154
- this.inputValue.length >= this.minCharacters ? (this.listOpen = true) : (this.listOpen = false);
155
- }
156
- else {
157
- this.listOpen = !this.listOpen;
158
- }
159
- });
160
- this.element.addEventListener("keydown", this.handleInputKeyDownEvent);
161
- connectLanguageChangeObserver(this, { prop: "accessibleLabels", defaults: "accessibleLabelDefaults" });
238
+ if (this.items) {
239
+ this.processItems();
240
+ }
241
+ const slottedElements = getElementsFromDefaultSlot(this.element);
242
+ if (slottedElements.length) {
243
+ this.input = slottedElements[0];
244
+ }
245
+ else {
246
+ this.input = document.createElement("duet-input");
247
+ this.input.label = this.label;
248
+ this.input.caption = this.caption;
249
+ this.input.expand = true;
250
+ this.element.prepend(this.input);
251
+ }
252
+ this.input.chips = this.multiple;
253
+ this.input.addEventListener("duetChange", this.onInputChange);
254
+ this.input.addEventListener("duetInput", this.onInputTyping);
255
+ this.input.addEventListener("click", this.onInputClick);
162
256
  //add correct aria attributes to the input element
163
257
  this.input.role = "combobox";
164
258
  this.input.accessibleExpanded = this.listOpen ? "true" : "false";
165
259
  this.input.accessibleAutocomplete = "list";
166
260
  this.input.accessibleControls = this.listBoxId;
261
+ this.element.addEventListener("keydown", this.onKeyDown);
262
+ connectLanguageChangeObserver(this, { prop: "accessibleLabels", defaults: "accessibleLabelDefaults" });
167
263
  this.processListOpenChange();
168
264
  }
169
265
  disconnectedCallback() {
170
266
  if (Build.isServer) {
171
267
  return;
172
268
  }
173
- this.input.removeEventListener("duetChange", this.updateInputValue.bind(this));
174
- this.input.removeEventListener("click", () => this.inputValue.length ? (this.listOpen = true) : (this.listOpen = false));
175
- this.element.removeEventListener("keydown", this.handleInputKeyDownEvent);
176
- this.listElement.removeEventListener("click", e => {
177
- e.stopPropagation();
178
- });
269
+ this.input.removeEventListener("duetChange", this.onInputChange);
270
+ this.input.removeEventListener("click", this.onInputClick);
271
+ this.element.removeEventListener("keydown", this.onKeyDown);
179
272
  this.resizeObserver.disconnect();
180
273
  disconnectLanguageChangeObserver(this);
181
274
  }
182
275
  componentDidLoad() {
183
276
  // observe resize events to dynamically adjust size of dropdown area
184
277
  this.resizeObserver.observe(this.input);
185
- this.listElement.addEventListener("click", e => {
186
- e.stopPropagation();
187
- });
188
278
  this.processValue();
189
279
  }
190
- /**
191
- * Updates the input text based on the selected/clicked items.
192
- * @param event - event
193
- *'param item: DuetComboboxItem - the item that was selected
194
- */
195
- onListClick(e, item) {
196
- e.preventDefault();
197
- // update selected item with id
198
- this.updateSelectedItems(item.id, true);
199
- this.listOpen = false;
200
- return this.listOpen;
201
- }
202
280
  /**
203
281
  * Helper function that checks inputs in the field, compares it with the item list and returns true when inputvalue matches selectedItem id
204
- * TODO: this should be disabled in multiple mode
205
282
  */
206
283
  shouldListBeFiltered() {
207
- if (this.selectedItems.size && this.input.value === this.inputValue) {
208
- const item = this.items.filter(item => this.selectedItems.has(item.id));
284
+ if (!this.multiple && this.selectedItems.size && this.input.value === this.inputValue) {
285
+ const item = this.processedItems.find(item => this.selectedItems.has(item.id));
209
286
  return !(this.formatter(item).toLowerCase() === this.input.value.toLowerCase());
210
287
  }
211
288
  return true;
@@ -220,91 +297,135 @@ export class DuetCombobox {
220
297
  : this.sortFilteredItems(this.processedItems)
221
298
  : [];
222
299
  }
300
+ emitChangeEvent(id) {
301
+ const item = this.processedItems.find(item => item.id === id);
302
+ this.duetChange.emit({
303
+ item,
304
+ value: this.value,
305
+ component: "duet-combobox",
306
+ });
307
+ }
223
308
  /**
224
- * Updates the selected items based on the item id.
225
- * @param id
226
- * @private
309
+ * Updates the value after selected items have changed for multiple selection.
227
310
  */
228
- async updateSelectedItems(id, emitEvent) {
229
- // for a multiple scenario wrap this in an if/else and just don't clear here
230
- this.selectedItems.clear();
231
- if (this.selectedItems) {
232
- if (this.selectedItems.has(id)) {
233
- this.selectedItems.delete(id);
234
- }
235
- else {
236
- this.selectedItems.add(id);
237
- }
311
+ updateMultipleValue() {
312
+ this.value = this.processedItems.filter(item => this.selectedItems.has(item.id)).map(item => item.value);
313
+ this.input.value = "";
314
+ this.updateChips();
315
+ }
316
+ /**
317
+ * Add item with id to the selectedItems set. Used only when multiple is true.
318
+ */
319
+ addSelectedItem(id, emitEvent) {
320
+ if (this.selectedItems.has(id)) {
321
+ return;
238
322
  }
323
+ this.selectedItems.add(id);
324
+ this.updateMultipleValue();
239
325
  if (emitEvent) {
240
- const item = this.selectedItems.size === 0 ? [] : this.items.filter(item => this.selectedItems.has(item.id));
241
- this.duetChange.emit({
242
- value: item[0].value,
243
- item: item[0],
244
- component: "duet-combobox",
245
- });
326
+ this.emitChangeEvent(id);
246
327
  }
247
- await this.updateInputText();
248
328
  }
249
329
  /**
250
- * Updates the input text based on the selected/clicked items.
251
- * @param bool -override to clear if invoked by "click outside"
330
+ * Remove item with id from the selectedItems set. Used only when multiple is true.
252
331
  */
253
- async updateInputText(isBlurred = false, isClickOutside = false) {
254
- const item = this.selectedItems.size === 0 ? [] : this.items.filter(item => this.selectedItems.has(item.id));
255
- //if items changed and given id no longer exists
256
- if (item.length === 0 && this.selectedItems.size > 0) {
257
- this.selectedItems.clear();
258
- this.input.value = "";
332
+ removeSelectedItem(id, emitEvent) {
333
+ if (!this.selectedItems.has(id)) {
334
+ return;
335
+ }
336
+ this.selectedItems.delete(id);
337
+ this.updateMultipleValue();
338
+ if (emitEvent) {
339
+ this.emitChangeEvent(id);
340
+ }
341
+ }
342
+ /**
343
+ * Updates the selected item based on the item id. Used only when multiple is false.
344
+ */
345
+ async updateSelectedItem(id, emitEvent) {
346
+ if (this.selectedItems.has(id)) {
347
+ this.selectedItems.delete(id);
259
348
  this.value = undefined;
260
- this.activeItem = undefined;
261
349
  }
262
- if (!isClickOutside && this.selectedItems.size === 1) {
263
- this.input.value = ""; // set value to empty so that cursor position follows accordingly
264
- this.input.value = await this.formatItem(item[0]);
265
- this.input.scrollLeft = this.input.scrollWidth;
266
- this.activeItem = undefined;
267
- this.value = item[0].value;
350
+ else {
351
+ this.selectedItems.clear();
352
+ this.selectedItems.add(id);
353
+ this.value = this.processedItems.find(item => item.id === id).value;
268
354
  }
269
- if (isBlurred) {
270
- if (this.selectedItems.size === 0) {
271
- this.activeItem = undefined;
272
- if (this.force) {
273
- this.inputValue = "";
274
- this.input.value = "";
275
- }
355
+ if (emitEvent) {
356
+ this.emitChangeEvent(id);
357
+ }
358
+ await this.updateInputText();
359
+ }
360
+ /**
361
+ * Add or remove chips of the input based on the selectedItems Set.
362
+ */
363
+ async updateChips() {
364
+ const ids = Array.from(this.selectedItems);
365
+ const chips = await this.input.getChips();
366
+ ids.forEach(async (id) => {
367
+ const item = this.processedItems.find(i => i.id === id);
368
+ const text = await this.formatItem(item);
369
+ const chipExists = await this.input.hasChip({ value: `${id}`, text });
370
+ if (!chipExists) {
371
+ const chip = document.createElement("duet-chip");
372
+ chip.variation = "input";
373
+ chip.value = `${id}`;
374
+ chip.textContent = text;
375
+ chip.addEventListener("duetRemove", evt => {
376
+ const id = evt.detail.value;
377
+ this.removeSelectedItem(id, true);
378
+ });
379
+ this.input.addChip(chip);
276
380
  }
277
- else if (this.force) {
278
- const selectedValueIndex = [...this.selectedItems][0];
279
- const selectedValueLabel = await this.formatItem(this.items[selectedValueIndex]);
280
- this.inputValue = selectedValueLabel;
281
- this.input.value = selectedValueLabel;
381
+ });
382
+ chips.forEach(chip => {
383
+ if (!ids.includes(chip.value)) {
384
+ chip.remove();
282
385
  }
283
- }
284
- this.listOpen = false;
386
+ });
285
387
  }
286
- updateInputValue(e) {
287
- var _a;
288
- this.inputValue = e.detail.value;
289
- this.listOpen = String(this.inputValue).length >= this.minCharacters && ((_a = this.getFilteredItems()) === null || _a === void 0 ? void 0 : _a.length) > 0;
388
+ async getSelectedItemLabel() {
389
+ const item = this.processedItems.find(item => this.selectedItems.has(item.id));
390
+ const label = item ? await this.formatItem(item) : "";
391
+ return label;
290
392
  }
291
- selectActiveItem() {
292
- if (this.activeItem === undefined) {
393
+ /**
394
+ * Updates the input text when the combobox loses focus or when a selection is made.
395
+ */
396
+ async updateInputText(isBlurred = false) {
397
+ if (isBlurred && this.multiple) {
398
+ this.input.value = "";
293
399
  return;
294
400
  }
295
- const filteredItem = this.returnFilteredOrNonFiltered();
296
- const item = filteredItem[this.activeItem];
297
- this.updateSelectedItems(item.id, true);
401
+ if (isBlurred && this.force) {
402
+ if (this.selectedItems.size === 0) {
403
+ this.input.value = "";
404
+ }
405
+ else {
406
+ this.input.value = await this.getSelectedItemLabel();
407
+ }
408
+ }
409
+ if (!isBlurred && this.selectedItems.size > 0) {
410
+ this.input.value = ""; // set value to empty so that cursor position follows accordingly
411
+ this.input.value = await this.getSelectedItemLabel();
412
+ this.input.scrollLeft = this.input.scrollWidth;
413
+ }
298
414
  }
299
- announceActive(items) {
300
- if (!items) {
301
- items = this.returnFilteredOrNonFiltered();
415
+ // Event handlers
416
+ onListClick(e, item) {
417
+ e.preventDefault();
418
+ e.stopPropagation();
419
+ // update selected item with id
420
+ if (this.multiple) {
421
+ this.addSelectedItem(item.id, true);
302
422
  }
303
- if (this.activeItem === undefined || this.activeItem === -1) {
304
- return;
423
+ else {
424
+ this.updateSelectedItem(item.id, true);
305
425
  }
306
- const item = items[this.activeItem];
307
- this.selectionMsg = structuredClone(this.formatLabel(item, items.length, this.items.length));
426
+ this.activeItem = undefined;
427
+ this.listOpen = false;
428
+ return this.listOpen;
308
429
  }
309
430
  //function that scrolls to the li element with the class "active"
310
431
  scrollToActive() {
@@ -332,16 +453,27 @@ export class DuetCombobox {
332
453
  */
333
454
  getFilteredItems() {
334
455
  // filter items based on user inputs
335
- const filteredItems = this.processedItems.filter(item => {
336
- var _a, _b, _c, _d;
456
+ const filteredItems = this.processedItems
457
+ .filter(item => {
458
+ var _a, _b, _c, _d, _e;
337
459
  // filter by name and value
338
460
  // if value contains inputvalue
339
461
  return ((_a = String(item.value)) === null || _a === void 0 ? void 0 : _a.toLowerCase()[this.filterType]((_b = this.inputValue) === null || _b === void 0 ? void 0 : _b.toLowerCase())) ||
340
462
  (
341
463
  // if name contains input value
342
464
  (_c = String(item.name)) === null || _c === void 0 ? void 0 : _c.toLowerCase()[this.filterType]((_d = this.inputValue) === null || _d === void 0 ? void 0 : _d.toLowerCase())) ||
465
+ // if name contains input value
466
+ this.formatter(item).toLowerCase()[this.filterType]((_e = this.inputValue) === null || _e === void 0 ? void 0 : _e.toLowerCase()) ||
343
467
  // if the item is selected
344
468
  this.selectedItems.has(item.id);
469
+ })
470
+ // if multiple, filter out the selected items
471
+ .filter(item => {
472
+ let include = true;
473
+ if (this.multiple && this.selectedItems.has(item.id)) {
474
+ include = false;
475
+ }
476
+ return include;
345
477
  });
346
478
  return this.sortFilteredItems(filteredItems);
347
479
  }
@@ -378,9 +510,10 @@ export class DuetCombobox {
378
510
  }, ref: el => (this.listContainer = el) }, h("ul", { class: {
379
511
  "duet-combobox-listbox-open": this.listOpen,
380
512
  "duet-combobox-listbox": true,
381
- }, role: "listbox", ref: el => (this.listElement = el), id: this.listBoxId }, selectElements.map((item, index) => {
382
- return (h(DuetComboBoxSelect, { item: item, active: index === this.activeItem, selected: this.selectedItems.has(item.id), search: this.inputValue, total: selectElements.length, clickHandler: e => this.onListClick(e, item), label: this.formatLabel(item, selectElements.length, this.items.length) }));
383
- })))));
513
+ }, role: "listbox", ref: el => (this.listElement = el), id: this.listBoxId }, this.listOpen &&
514
+ selectElements.map((item, index) => {
515
+ return (h(DuetComboBoxSelect, { item: item, active: index === this.activeItem, selected: this.selectedItems.has(item.id), search: this.inputValue, total: selectElements.length, clickHandler: e => this.onListClick(e, item), label: this.formatLabel(item, selectElements.length, this.processedItems.length) }));
516
+ })))));
384
517
  }
385
518
  static get is() { return "duet-combobox"; }
386
519
  static get encapsulation() { return "scoped"; }
@@ -413,11 +546,8 @@ export class DuetCombobox {
413
546
  "required": false,
414
547
  "optional": false,
415
548
  "docs": {
416
- "tags": [{
417
- "name": "default",
418
- "text": "{ fi: \"pp.kk.vvvv\", en: \"dd.mm.yyyy\", sv: \"dd.mm.\u00E5\u00E5\u00E5\u00E5\" }"
419
- }],
420
- "text": "Placeholder defaults"
549
+ "tags": [],
550
+ "text": "Defaults for the accessible labels for the select items"
421
551
  },
422
552
  "attribute": "accessible-label-defaults",
423
553
  "reflect": false,
@@ -439,14 +569,47 @@ export class DuetCombobox {
439
569
  "required": false,
440
570
  "optional": false,
441
571
  "docs": {
442
- "tags": [{
443
- "name": "default",
444
- "text": "{\nheading: \"Valitse:\",\nitem: \"{0}, ({1}/{2})\",\nitemFiltered: \"{0}, ({1}/{2} - {3} suodatettu\"\n}"
445
- }],
446
- "text": "Hint text to display before the user types into the date picker input."
572
+ "tags": [],
573
+ "text": "Accessible labels for the select items"
447
574
  },
448
575
  "defaultValue": "getLocaleString(\n this.accessibleLabelDefaults,\n getLanguage()\n )"
449
576
  },
577
+ "label": {
578
+ "type": "string",
579
+ "mutable": false,
580
+ "complexType": {
581
+ "original": "string",
582
+ "resolved": "string",
583
+ "references": {}
584
+ },
585
+ "required": false,
586
+ "optional": false,
587
+ "docs": {
588
+ "tags": [],
589
+ "text": "Label for the input if input is not provided as a slotted element."
590
+ },
591
+ "attribute": "label",
592
+ "reflect": false,
593
+ "defaultValue": "\"\""
594
+ },
595
+ "caption": {
596
+ "type": "string",
597
+ "mutable": false,
598
+ "complexType": {
599
+ "original": "string",
600
+ "resolved": "string",
601
+ "references": {}
602
+ },
603
+ "required": false,
604
+ "optional": false,
605
+ "docs": {
606
+ "tags": [],
607
+ "text": "Caption for the input if input is not provided as a slotted element."
608
+ },
609
+ "attribute": "caption",
610
+ "reflect": false,
611
+ "defaultValue": "\"\""
612
+ },
450
613
  "theme": {
451
614
  "type": "string",
452
615
  "mutable": true,
@@ -489,6 +652,24 @@ export class DuetCombobox {
489
652
  "reflect": false,
490
653
  "defaultValue": "false"
491
654
  },
655
+ "multiple": {
656
+ "type": "boolean",
657
+ "mutable": false,
658
+ "complexType": {
659
+ "original": "boolean",
660
+ "resolved": "boolean",
661
+ "references": {}
662
+ },
663
+ "required": false,
664
+ "optional": false,
665
+ "docs": {
666
+ "tags": [],
667
+ "text": "Allow multiple selections. Selections are displayed as DuetChips."
668
+ },
669
+ "attribute": "multiple",
670
+ "reflect": false,
671
+ "defaultValue": "false"
672
+ },
492
673
  "items": {
493
674
  "type": "any",
494
675
  "mutable": true,
@@ -501,7 +682,7 @@ export class DuetCombobox {
501
682
  "optional": false,
502
683
  "docs": {
503
684
  "tags": [],
504
- "text": "Array of item objects."
685
+ "text": "Array of item objects. Each item should have properties name, value and optionally id. If id is not provided, it will be generated.\nAlternatively, an array of strings can be provided, in which case the strings will be used for name, value and id."
505
686
  },
506
687
  "attribute": "items",
507
688
  "reflect": false
@@ -538,15 +719,15 @@ export class DuetCombobox {
538
719
  "type": "string",
539
720
  "mutable": true,
540
721
  "complexType": {
541
- "original": "string",
542
- "resolved": "string",
722
+ "original": "string | string[]",
723
+ "resolved": "string | string[]",
543
724
  "references": {}
544
725
  },
545
726
  "required": false,
546
727
  "optional": false,
547
728
  "docs": {
548
729
  "tags": [],
549
- "text": "Value of selected item/s"
730
+ "text": "Value of selected item/s. If multiple is true, value is an array of selected items, else it's a string."
550
731
  },
551
732
  "attribute": "value",
552
733
  "reflect": true
@@ -610,9 +791,7 @@ export class DuetCombobox {
610
791
  static get states() {
611
792
  return {
612
793
  "processedItems": {},
613
- "inputWidth": {},
614
794
  "inputValue": {},
615
- "selectionMsg": {},
616
795
  "listOpen": {},
617
796
  "selectedItems": {},
618
797
  "activeItem": {}
@@ -649,7 +828,7 @@ export class DuetCombobox {
649
828
  "signature": "(item: DuetComboboxItem) => Promise<string>",
650
829
  "parameters": [{
651
830
  "name": "item",
652
- "type": "{ id?: number; value: any; name: string; html?: string; tags?: string[]; }",
831
+ "type": "{ id?: string; value: any; name: string; html?: string; tags?: string[]; }",
653
832
  "docs": ": DuetComboboxItem"
654
833
  }],
655
834
  "references": {