@aurelia-ui-toolkits/headless 1.0.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 (284) hide show
  1. package/dist/alert/ui-alert.html +19 -0
  2. package/dist/alert/ui-alert.js +67 -0
  3. package/dist/alert/ui-alert.js.map +1 -0
  4. package/dist/alert-service/alert-configuration.js +11 -0
  5. package/dist/alert-service/alert-configuration.js.map +1 -0
  6. package/dist/alert-service/alert-modal/alert-modal.html +12 -0
  7. package/dist/alert-service/alert-modal/alert-modal.js +31 -0
  8. package/dist/alert-service/alert-modal/alert-modal.js.map +1 -0
  9. package/dist/alert-service/alert-modal/i-alert-modal-payload.js +2 -0
  10. package/dist/alert-service/alert-modal/i-alert-modal-payload.js.map +1 -0
  11. package/dist/alert-service/alert-service.js +108 -0
  12. package/dist/alert-service/alert-service.js.map +1 -0
  13. package/dist/alert-service/decorators/confirm-action.js +14 -0
  14. package/dist/alert-service/decorators/confirm-action.js.map +1 -0
  15. package/dist/alert-service/decorators/using-progress.js +23 -0
  16. package/dist/alert-service/decorators/using-progress.js.map +1 -0
  17. package/dist/alert-service/exceptions-tracker.js +4 -0
  18. package/dist/alert-service/exceptions-tracker.js.map +1 -0
  19. package/dist/alert-service/prompt-dialog/prompt-dialog.html +13 -0
  20. package/dist/alert-service/prompt-dialog/prompt-dialog.js +39 -0
  21. package/dist/alert-service/prompt-dialog/prompt-dialog.js.map +1 -0
  22. package/dist/badge/ui-badge.html +6 -0
  23. package/dist/badge/ui-badge.js +44 -0
  24. package/dist/badge/ui-badge.js.map +1 -0
  25. package/dist/base/boolean-attr.js +4 -0
  26. package/dist/base/boolean-attr.js.map +1 -0
  27. package/dist/base/i-validated-element.js +2 -0
  28. package/dist/base/i-validated-element.js.map +1 -0
  29. package/dist/base/keys.js +14 -0
  30. package/dist/base/keys.js.map +1 -0
  31. package/dist/breadcrumbs/ui-breadcrumbs.html +10 -0
  32. package/dist/breadcrumbs/ui-breadcrumbs.js +31 -0
  33. package/dist/breadcrumbs/ui-breadcrumbs.js.map +1 -0
  34. package/dist/button/enhance-ui-button.js +25 -0
  35. package/dist/button/enhance-ui-button.js.map +1 -0
  36. package/dist/button/ui-button.html +20 -0
  37. package/dist/button/ui-button.js +77 -0
  38. package/dist/button/ui-button.js.map +1 -0
  39. package/dist/button/ui-icon-button.html +20 -0
  40. package/dist/button/ui-icon-button.js +77 -0
  41. package/dist/button/ui-icon-button.js.map +1 -0
  42. package/dist/checkbox/ui-checkbox.html +32 -0
  43. package/dist/checkbox/ui-checkbox.js +186 -0
  44. package/dist/checkbox/ui-checkbox.js.map +1 -0
  45. package/dist/chip/ui-chip.html +32 -0
  46. package/dist/chip/ui-chip.js +129 -0
  47. package/dist/chip/ui-chip.js.map +1 -0
  48. package/dist/combobox/enhance-ui-combobox.js +26 -0
  49. package/dist/combobox/enhance-ui-combobox.js.map +1 -0
  50. package/dist/combobox/ui-combobox.html +76 -0
  51. package/dist/combobox/ui-combobox.js +478 -0
  52. package/dist/combobox/ui-combobox.js.map +1 -0
  53. package/dist/disclosure/ui-disclosure.html +28 -0
  54. package/dist/disclosure/ui-disclosure.js +75 -0
  55. package/dist/disclosure/ui-disclosure.js.map +1 -0
  56. package/dist/drawer/ui-drawer.html +35 -0
  57. package/dist/drawer/ui-drawer.js +246 -0
  58. package/dist/drawer/ui-drawer.js.map +1 -0
  59. package/dist/index.js +135 -0
  60. package/dist/index.js.map +1 -0
  61. package/dist/input/enhance-ui-input.js +26 -0
  62. package/dist/input/enhance-ui-input.js.map +1 -0
  63. package/dist/input/ui-input.html +56 -0
  64. package/dist/input/ui-input.js +259 -0
  65. package/dist/input/ui-input.js.map +1 -0
  66. package/dist/list/ui-list-item.html +22 -0
  67. package/dist/list/ui-list-item.js +59 -0
  68. package/dist/list/ui-list-item.js.map +1 -0
  69. package/dist/list/ui-list.html +10 -0
  70. package/dist/list/ui-list.js +316 -0
  71. package/dist/list/ui-list.js.map +1 -0
  72. package/dist/menu/ui-menu.html +20 -0
  73. package/dist/menu/ui-menu.js +154 -0
  74. package/dist/menu/ui-menu.js.map +1 -0
  75. package/dist/popup/ui-popup.html +16 -0
  76. package/dist/popup/ui-popup.js +397 -0
  77. package/dist/popup/ui-popup.js.map +1 -0
  78. package/dist/progress/ui-progress.html +17 -0
  79. package/dist/progress/ui-progress.js +76 -0
  80. package/dist/progress/ui-progress.js.map +1 -0
  81. package/dist/radio/ui-radio-group.html +27 -0
  82. package/dist/radio/ui-radio-group.js +259 -0
  83. package/dist/radio/ui-radio-group.js.map +1 -0
  84. package/dist/radio/ui-radio.html +11 -0
  85. package/dist/radio/ui-radio.js +55 -0
  86. package/dist/radio/ui-radio.js.map +1 -0
  87. package/dist/segmented-control/ui-segment.html +10 -0
  88. package/dist/segmented-control/ui-segment.js +44 -0
  89. package/dist/segmented-control/ui-segment.js.map +1 -0
  90. package/dist/segmented-control/ui-segmented-control.html +9 -0
  91. package/dist/segmented-control/ui-segmented-control.js +134 -0
  92. package/dist/segmented-control/ui-segmented-control.js.map +1 -0
  93. package/dist/select/enhance-ui-select.js +26 -0
  94. package/dist/select/enhance-ui-select.js.map +1 -0
  95. package/dist/select/ui-select.html +70 -0
  96. package/dist/select/ui-select.js +323 -0
  97. package/dist/select/ui-select.js.map +1 -0
  98. package/dist/slider/ui-slider.html +69 -0
  99. package/dist/slider/ui-slider.js +362 -0
  100. package/dist/slider/ui-slider.js.map +1 -0
  101. package/dist/splitter/ui-splitter.html +14 -0
  102. package/dist/splitter/ui-splitter.js +257 -0
  103. package/dist/splitter/ui-splitter.js.map +1 -0
  104. package/dist/switch/ui-switch.html +23 -0
  105. package/dist/switch/ui-switch.js +121 -0
  106. package/dist/switch/ui-switch.js.map +1 -0
  107. package/dist/table/enhance-ui-table.js +25 -0
  108. package/dist/table/enhance-ui-table.js.map +1 -0
  109. package/dist/table/ui-table-column.html +17 -0
  110. package/dist/table/ui-table-column.js +127 -0
  111. package/dist/table/ui-table-column.js.map +1 -0
  112. package/dist/table/ui-table.html +56 -0
  113. package/dist/table/ui-table.js +225 -0
  114. package/dist/table/ui-table.js.map +1 -0
  115. package/dist/tabs/ui-tab.html +10 -0
  116. package/dist/tabs/ui-tab.js +52 -0
  117. package/dist/tabs/ui-tab.js.map +1 -0
  118. package/dist/tabs/ui-tabs.html +3 -0
  119. package/dist/tabs/ui-tabs.js +112 -0
  120. package/dist/tabs/ui-tabs.js.map +1 -0
  121. package/dist/toast/ui-toast-region.html +11 -0
  122. package/dist/toast/ui-toast-region.js +38 -0
  123. package/dist/toast/ui-toast-region.js.map +1 -0
  124. package/dist/toast/ui-toast-service.js +70 -0
  125. package/dist/toast/ui-toast-service.js.map +1 -0
  126. package/dist/tooltip/ui-tooltip-service.js +63 -0
  127. package/dist/tooltip/ui-tooltip-service.js.map +1 -0
  128. package/dist/tooltip/ui-tooltip.js +221 -0
  129. package/dist/tooltip/ui-tooltip.js.map +1 -0
  130. package/dist/top-app-bar/ui-top-app-bar.html +24 -0
  131. package/dist/top-app-bar/ui-top-app-bar.js +68 -0
  132. package/dist/top-app-bar/ui-top-app-bar.js.map +1 -0
  133. package/dist/tree/ui-tree.html +38 -0
  134. package/dist/tree/ui-tree.js +340 -0
  135. package/dist/tree/ui-tree.js.map +1 -0
  136. package/dist/types/alert/ui-alert.d.ts +11 -0
  137. package/dist/types/alert-service/alert-configuration.d.ts +8 -0
  138. package/dist/types/alert-service/alert-modal/alert-modal.d.ts +7 -0
  139. package/dist/types/alert-service/alert-modal/i-alert-modal-payload.d.ts +14 -0
  140. package/dist/types/alert-service/alert-service.d.ts +17 -0
  141. package/dist/types/alert-service/decorators/confirm-action.d.ts +3 -0
  142. package/dist/types/alert-service/decorators/using-progress.d.ts +6 -0
  143. package/dist/types/alert-service/exceptions-tracker.d.ts +3 -0
  144. package/dist/types/alert-service/prompt-dialog/prompt-dialog.d.ts +17 -0
  145. package/dist/types/badge/ui-badge.d.ts +7 -0
  146. package/dist/types/base/boolean-attr.d.ts +1 -0
  147. package/dist/types/base/i-validated-element.d.ts +10 -0
  148. package/dist/types/base/keys.d.ts +12 -0
  149. package/dist/types/breadcrumbs/ui-breadcrumbs.d.ts +8 -0
  150. package/dist/types/button/enhance-ui-button.d.ts +3 -0
  151. package/dist/types/button/ui-button.d.ts +16 -0
  152. package/dist/types/button/ui-icon-button.d.ts +18 -0
  153. package/dist/types/checkbox/ui-checkbox.d.ts +32 -0
  154. package/dist/types/chip/ui-chip.d.ts +26 -0
  155. package/dist/types/combobox/enhance-ui-combobox.d.ts +3 -0
  156. package/dist/types/combobox/ui-combobox.d.ts +79 -0
  157. package/dist/types/disclosure/ui-disclosure.d.ts +18 -0
  158. package/dist/types/drawer/ui-drawer.d.ts +38 -0
  159. package/dist/types/index.d.ts +91 -0
  160. package/dist/types/input/enhance-ui-input.d.ts +3 -0
  161. package/dist/types/input/ui-input.d.ts +44 -0
  162. package/dist/types/list/ui-list-item.d.ts +10 -0
  163. package/dist/types/list/ui-list.d.ts +45 -0
  164. package/dist/types/menu/ui-menu.d.ts +29 -0
  165. package/dist/types/popup/ui-popup.d.ts +61 -0
  166. package/dist/types/progress/ui-progress.d.ts +15 -0
  167. package/dist/types/radio/ui-radio-group.d.ts +37 -0
  168. package/dist/types/radio/ui-radio.d.ts +11 -0
  169. package/dist/types/segmented-control/ui-segment.d.ts +8 -0
  170. package/dist/types/segmented-control/ui-segmented-control.d.ts +17 -0
  171. package/dist/types/select/enhance-ui-select.d.ts +3 -0
  172. package/dist/types/select/ui-select.d.ts +50 -0
  173. package/dist/types/slider/ui-slider.d.ts +58 -0
  174. package/dist/types/splitter/ui-splitter.d.ts +39 -0
  175. package/dist/types/switch/ui-switch.d.ts +23 -0
  176. package/dist/types/table/enhance-ui-table.d.ts +3 -0
  177. package/dist/types/table/ui-table-column.d.ts +22 -0
  178. package/dist/types/table/ui-table.d.ts +51 -0
  179. package/dist/types/tabs/ui-tab.d.ts +10 -0
  180. package/dist/types/tabs/ui-tabs.d.ts +16 -0
  181. package/dist/types/toast/ui-toast-region.d.ts +6 -0
  182. package/dist/types/toast/ui-toast-service.d.ts +28 -0
  183. package/dist/types/tooltip/ui-tooltip-service.d.ts +23 -0
  184. package/dist/types/tooltip/ui-tooltip.d.ts +35 -0
  185. package/dist/types/top-app-bar/ui-top-app-bar.d.ts +9 -0
  186. package/dist/types/tree/ui-tree.d.ts +70 -0
  187. package/dist/types/validation/ui-validation-controller-factory.d.ts +7 -0
  188. package/dist/types/validation/ui-validation-result-presenter.d.ts +4 -0
  189. package/dist/types/validation/validate.d.ts +13 -0
  190. package/dist/validation/ui-validation-controller-factory.js +17 -0
  191. package/dist/validation/ui-validation-controller-factory.js.map +1 -0
  192. package/dist/validation/ui-validation-result-presenter.js +23 -0
  193. package/dist/validation/ui-validation-result-presenter.js.map +1 -0
  194. package/dist/validation/validate.js +24 -0
  195. package/dist/validation/validate.js.map +1 -0
  196. package/package.json +50 -0
  197. package/src/alert/ui-alert.html +19 -0
  198. package/src/alert/ui-alert.ts +33 -0
  199. package/src/alert-service/alert-configuration.ts +11 -0
  200. package/src/alert-service/alert-modal/alert-modal.html +12 -0
  201. package/src/alert-service/alert-modal/alert-modal.ts +19 -0
  202. package/src/alert-service/alert-modal/i-alert-modal-payload.ts +14 -0
  203. package/src/alert-service/alert-service.ts +116 -0
  204. package/src/alert-service/decorators/confirm-action.ts +18 -0
  205. package/src/alert-service/decorators/using-progress.ts +32 -0
  206. package/src/alert-service/exceptions-tracker.ts +3 -0
  207. package/src/alert-service/prompt-dialog/prompt-dialog.html +13 -0
  208. package/src/alert-service/prompt-dialog/prompt-dialog.ts +37 -0
  209. package/src/badge/ui-badge.html +6 -0
  210. package/src/badge/ui-badge.ts +17 -0
  211. package/src/base/boolean-attr.ts +3 -0
  212. package/src/base/i-validated-element.ts +11 -0
  213. package/src/base/keys.ts +12 -0
  214. package/src/breadcrumbs/ui-breadcrumbs.html +10 -0
  215. package/src/breadcrumbs/ui-breadcrumbs.ts +14 -0
  216. package/src/button/enhance-ui-button.ts +9 -0
  217. package/src/button/ui-button.html +20 -0
  218. package/src/button/ui-button.ts +60 -0
  219. package/src/button/ui-icon-button.html +20 -0
  220. package/src/button/ui-icon-button.ts +62 -0
  221. package/src/checkbox/ui-checkbox.html +32 -0
  222. package/src/checkbox/ui-checkbox.ts +188 -0
  223. package/src/chip/ui-chip.html +32 -0
  224. package/src/chip/ui-chip.ts +118 -0
  225. package/src/combobox/enhance-ui-combobox.ts +10 -0
  226. package/src/combobox/ui-combobox.html +76 -0
  227. package/src/combobox/ui-combobox.ts +450 -0
  228. package/src/disclosure/ui-disclosure.html +28 -0
  229. package/src/disclosure/ui-disclosure.ts +68 -0
  230. package/src/drawer/ui-drawer.html +35 -0
  231. package/src/drawer/ui-drawer.ts +219 -0
  232. package/src/index.ts +152 -0
  233. package/src/input/enhance-ui-input.ts +10 -0
  234. package/src/input/ui-input.html +56 -0
  235. package/src/input/ui-input.ts +225 -0
  236. package/src/list/ui-list-item.html +22 -0
  237. package/src/list/ui-list-item.ts +25 -0
  238. package/src/list/ui-list.html +10 -0
  239. package/src/list/ui-list.ts +330 -0
  240. package/src/menu/ui-menu.html +20 -0
  241. package/src/menu/ui-menu.ts +103 -0
  242. package/src/popup/ui-popup.html +16 -0
  243. package/src/popup/ui-popup.ts +391 -0
  244. package/src/progress/ui-progress.html +17 -0
  245. package/src/progress/ui-progress.ts +51 -0
  246. package/src/radio/ui-radio-group.html +27 -0
  247. package/src/radio/ui-radio-group.ts +250 -0
  248. package/src/radio/ui-radio.html +11 -0
  249. package/src/radio/ui-radio.ts +35 -0
  250. package/src/resource.d.ts +4 -0
  251. package/src/segmented-control/ui-segment.html +10 -0
  252. package/src/segmented-control/ui-segment.ts +20 -0
  253. package/src/segmented-control/ui-segmented-control.html +9 -0
  254. package/src/segmented-control/ui-segmented-control.ts +119 -0
  255. package/src/select/enhance-ui-select.ts +10 -0
  256. package/src/select/ui-select.html +70 -0
  257. package/src/select/ui-select.ts +294 -0
  258. package/src/slider/ui-slider.html +69 -0
  259. package/src/slider/ui-slider.ts +329 -0
  260. package/src/splitter/ui-splitter.html +14 -0
  261. package/src/splitter/ui-splitter.ts +249 -0
  262. package/src/switch/ui-switch.html +23 -0
  263. package/src/switch/ui-switch.ts +121 -0
  264. package/src/table/enhance-ui-table.ts +9 -0
  265. package/src/table/ui-table-column.html +17 -0
  266. package/src/table/ui-table-column.ts +108 -0
  267. package/src/table/ui-table.html +56 -0
  268. package/src/table/ui-table.ts +209 -0
  269. package/src/tabs/ui-tab.html +10 -0
  270. package/src/tabs/ui-tab.ts +30 -0
  271. package/src/tabs/ui-tabs.html +3 -0
  272. package/src/tabs/ui-tabs.ts +105 -0
  273. package/src/toast/ui-toast-region.html +11 -0
  274. package/src/toast/ui-toast-region.ts +18 -0
  275. package/src/toast/ui-toast-service.ts +100 -0
  276. package/src/tooltip/ui-tooltip-service.ts +84 -0
  277. package/src/tooltip/ui-tooltip.ts +200 -0
  278. package/src/top-app-bar/ui-top-app-bar.html +24 -0
  279. package/src/top-app-bar/ui-top-app-bar.ts +27 -0
  280. package/src/tree/ui-tree.html +38 -0
  281. package/src/tree/ui-tree.ts +363 -0
  282. package/src/validation/ui-validation-controller-factory.ts +20 -0
  283. package/src/validation/ui-validation-result-presenter.ts +26 -0
  284. package/src/validation/validate.ts +35 -0
@@ -0,0 +1,25 @@
1
+ import { bindable, customElement, INode, resolve, slotted } from 'aurelia';
2
+ import { booleanAttr } from '../base/boolean-attr';
3
+ import template from './ui-list-item.html?raw';
4
+ import { UiList } from './ui-list';
5
+
6
+ @customElement({ name: 'ui-list-item', template })
7
+ export class UiListItem {
8
+ readonly element = resolve(INode) as HTMLElement;
9
+ readonly parentList = resolve(UiList);
10
+
11
+ @slotted({ slotName: 'leading' })
12
+ leadingNodes: readonly Node[] = [];
13
+
14
+ @slotted({ slotName: 'secondary' })
15
+ secondaryNodes: readonly Node[] = [];
16
+
17
+ @slotted({ slotName: 'trailing' })
18
+ trailingNodes: readonly Node[] = [];
19
+
20
+ @bindable
21
+ value: object = this;
22
+
23
+ @bindable({ set: booleanAttr })
24
+ disabled: boolean = false;
25
+ }
@@ -0,0 +1,10 @@
1
+ <template class="ui-list"
2
+ role="listbox"
3
+ tabindex="0"
4
+ data-orientation.bind="orientation"
5
+ keydown.trigger="onKeyDown($event)"
6
+ click.trigger="onClick($event)"
7
+ mousemove.trigger="onMouseMove()"
8
+ mouseover.trigger="onMouseOver($event)">
9
+ <au-slot></au-slot>
10
+ </template>
@@ -0,0 +1,330 @@
1
+ import { bindable, children, customElement, INode, resolve } from 'aurelia';
2
+ import { booleanAttr } from '../base/boolean-attr';
3
+ import { Keys } from '../base/keys';
4
+ import { UiListItem } from './ui-list-item';
5
+ import template from './ui-list.html?raw';
6
+
7
+ type ListOrientation = 'vertical' | 'horizontal';
8
+
9
+ @customElement({ name: 'ui-list', template })
10
+ export class UiList {
11
+ private readonly host = resolve(INode) as HTMLElement;
12
+
13
+ @bindable
14
+ items: any[] = [];
15
+
16
+ @bindable({ set: booleanAttr })
17
+ loop: boolean = true;
18
+
19
+ @bindable
20
+ orientation: ListOrientation = 'vertical';
21
+
22
+ @bindable
23
+ typeaheadField: string | undefined;
24
+
25
+ @bindable
26
+ disabledField: string | ((item: unknown) => boolean) = 'disabled';
27
+
28
+ @bindable({ mode: 'twoWay' })
29
+ selected: unknown;
30
+
31
+ @children({
32
+ query: 'ui-list-item',
33
+ map: (_node, viewModel) => viewModel
34
+ })
35
+ listItems: UiListItem[] = [];
36
+ listItemsChanged() {
37
+ if (!this.items.length) {
38
+ this.items = this.listItems.map(x => x.value);
39
+ }
40
+ if (this.listItemsChangedCallback) {
41
+ this.listItemsChangedCallback();
42
+ this.listItemsChangedCallback = undefined;
43
+ }
44
+ }
45
+
46
+ private listItemsChangedCallback: (() => void) | undefined;
47
+ activeItem: unknown;
48
+ private typeaheadBuffer = '';
49
+ private typeaheadTimer: ReturnType<typeof setTimeout> | undefined;
50
+
51
+ detaching(): void {
52
+ this.clearTypeahead();
53
+ }
54
+
55
+ onKeyDown(event: KeyboardEvent): void {
56
+ const values = this.getEffectiveItems();
57
+ if (values.length === 0) {
58
+ return;
59
+ }
60
+
61
+ if (this.isNextKey(event.key)) {
62
+ event.preventDefault();
63
+ this.move(1);
64
+ return;
65
+ }
66
+
67
+ if (this.isPreviousKey(event.key)) {
68
+ event.preventDefault();
69
+ this.move(-1);
70
+ return;
71
+ }
72
+
73
+ if (event.key === Keys.Home) {
74
+ event.preventDefault();
75
+ this.setFirstActive();
76
+ return;
77
+ }
78
+
79
+ if (event.key === Keys.End) {
80
+ event.preventDefault();
81
+ this.setLastActive();
82
+ return;
83
+ }
84
+
85
+ if (event.key === Keys.Enter) {
86
+ event.preventDefault();
87
+ this.selectActive();
88
+ return;
89
+ }
90
+
91
+ if (this.typeaheadField && this.isTypeaheadKey(event)) {
92
+ event.preventDefault();
93
+ this.handleTypeahead(event.key.toLowerCase());
94
+ }
95
+ }
96
+
97
+ isItemDisabled(item: unknown): boolean {
98
+ const listItem = this.listItems.find(x => x.value === item);
99
+ if (listItem) {
100
+ return listItem.disabled;
101
+ }
102
+
103
+ if (typeof this.disabledField === 'string') {
104
+ return !!(item as any)[this.disabledField];
105
+ } else if (typeof this.disabledField === 'function') {
106
+ return this.disabledField(item);
107
+ }
108
+
109
+ return false;
110
+ }
111
+
112
+ onClick(event: MouseEvent): void {
113
+ const item = this.resolveItemFromEvent(event.target);
114
+ if (item === undefined || item === null || this.isItemDisabled(item)) {
115
+ return;
116
+ }
117
+
118
+ this.selectItem(item);
119
+ }
120
+
121
+ private suppressMouseOver = false;
122
+
123
+ onMouseMove(): void {
124
+ if (this.suppressMouseOver) {
125
+ this.suppressMouseOver = false;
126
+ }
127
+ }
128
+
129
+ onMouseOver(event: MouseEvent): void {
130
+ if (this.suppressMouseOver) {
131
+ return;
132
+ }
133
+ const item = this.resolveItemFromEvent(event.target);
134
+ if (item === undefined || item === null || this.isItemDisabled(item)) {
135
+ return;
136
+ }
137
+
138
+ this.activateItem(item);
139
+ }
140
+
141
+ focusFirst(): void {
142
+ this.host.focus();
143
+ this.setFirstActive();
144
+ }
145
+
146
+ focusLast(): void {
147
+ this.host.focus();
148
+ this.setLastActive();
149
+ }
150
+
151
+ private getEffectiveItems(): unknown[] {
152
+ if (this.items?.length) {
153
+ return this.items;
154
+ }
155
+
156
+ return this.listItems.map(item => item.value);
157
+ }
158
+
159
+ private isNextKey(key: string): boolean {
160
+ if (this.orientation === 'horizontal') {
161
+ return key === Keys.ArrowRight;
162
+ }
163
+
164
+ return key === Keys.ArrowDown;
165
+ }
166
+
167
+ private isPreviousKey(key: string): boolean {
168
+ if (this.orientation === 'horizontal') {
169
+ return key === Keys.ArrowLeft;
170
+ }
171
+
172
+ return key === Keys.ArrowUp;
173
+ }
174
+
175
+ private move(direction: 1 | -1): void {
176
+ const items = this.getEffectiveItems();
177
+ const currentIndex = this.activeItem ? items.indexOf(this.activeItem) : -1;
178
+ let nextIndex = currentIndex + direction;
179
+
180
+ if (this.loop) {
181
+ nextIndex = (nextIndex + items.length) % items.length;
182
+ } else {
183
+ nextIndex = Math.max(0, Math.min(items.length - 1, nextIndex));
184
+ }
185
+
186
+ const next = this.getNonDisabled(items[nextIndex], direction, items);
187
+ if (next !== undefined) {
188
+ this.scrollItemIntoView(next);
189
+ this.activateItem(next);
190
+ }
191
+ }
192
+
193
+ private getNonDisabled(item: unknown, direction: 1 | -1, items = this.getEffectiveItems()): unknown {
194
+ if (!this.isItemDisabled(item)) {
195
+ return item;
196
+ }
197
+ const startIndex = items.indexOf(item);
198
+ for (let step = 1; step < items.length; step++) {
199
+ const index = (startIndex + direction * step + items.length) % items.length;
200
+ const next = items[index];
201
+ if (!this.isItemDisabled(next)) {
202
+ return next;
203
+ }
204
+ }
205
+
206
+ return undefined;
207
+ }
208
+
209
+ private async setFirstActive() {
210
+ const items = this.getEffectiveItems();
211
+ const firstItem = this.getNonDisabled(items[0], 1, items);
212
+ if (firstItem === undefined) {
213
+ return;
214
+ }
215
+
216
+ this.scrollItemIntoView(firstItem);
217
+ this.activateItem(firstItem);
218
+ }
219
+
220
+ private setLastActive(): void {
221
+ const items = this.getEffectiveItems();
222
+ const lastItem = this.getNonDisabled(items[items.length - 1], -1, items);
223
+ if (lastItem === undefined) {
224
+ return;
225
+ }
226
+
227
+ this.scrollItemIntoView(lastItem);
228
+ this.activateItem(lastItem);
229
+ }
230
+
231
+ private activateItem(item: unknown): void {
232
+ this.activeItem = item;
233
+ this.emitActivate(item);
234
+ }
235
+
236
+ private selectActive(): void {
237
+ if (this.activeItem !== undefined && !this.isItemDisabled(this.activeItem)) {
238
+ this.selectItem(this.activeItem);
239
+ }
240
+ }
241
+
242
+ private selectItem(item: unknown): void {
243
+ this.activateItem(item);
244
+ this.selected = item;
245
+ this.emitSelection(item);
246
+ }
247
+
248
+ private emitActivate(item: unknown): void {
249
+ this.host.dispatchEvent(new CustomEvent('list-activate', {
250
+ bubbles: true,
251
+ detail: item
252
+ }));
253
+ }
254
+
255
+ private emitSelection(item: unknown): void {
256
+ this.host.dispatchEvent(new CustomEvent('list-select', {
257
+ bubbles: true,
258
+ detail: item
259
+ }));
260
+ }
261
+
262
+ private resolveItemFromEvent(target: EventTarget | null) {
263
+ const element = target instanceof HTMLElement ? target.closest('ui-list-item') : null;
264
+ if (!element) {
265
+ return undefined;
266
+ }
267
+
268
+ return this.listItems.find((item) => item.element === element)?.value;
269
+ }
270
+
271
+ private isTypeaheadKey(event: KeyboardEvent): boolean {
272
+ return (event.key.length === 1 || event.key === ' ') && !event.altKey && !event.ctrlKey && !event.metaKey;
273
+ }
274
+
275
+ private handleTypeahead(character: string): void {
276
+ this.typeaheadBuffer += character;
277
+ this.clearTypeahead();
278
+ this.typeaheadTimer = setTimeout(() => {
279
+ this.typeaheadBuffer = '';
280
+ this.typeaheadTimer = undefined;
281
+ }, 800);
282
+
283
+ const items = this.getEffectiveItems();
284
+ const startIndex = this.activeItem ? items.indexOf(this.activeItem) : 0;
285
+
286
+ for (let step = 0; step < items.length; step++) {
287
+ const index = (startIndex + step) % items.length;
288
+ const item = items[index];
289
+ const label = this.getItemText(item).toLowerCase();
290
+ if (label.startsWith(this.typeaheadBuffer) && !this.isItemDisabled(item)) {
291
+ this.scrollItemIntoView(item);
292
+ this.activateItem(item);
293
+ return;
294
+ }
295
+ }
296
+ }
297
+
298
+ private getItemText(item: unknown): string {
299
+ if (this.typeaheadField && item && typeof item === 'object') {
300
+ const value = (item as Record<string, unknown>)[this.typeaheadField];
301
+ return value === undefined || value === null ? '' : String(value);
302
+ }
303
+
304
+ return item === undefined || item === null ? '' : String(item);
305
+ }
306
+
307
+ private clearTypeahead(): void {
308
+ if (this.typeaheadTimer) {
309
+ clearTimeout(this.typeaheadTimer);
310
+ this.typeaheadTimer = undefined;
311
+ }
312
+ }
313
+
314
+ private scrollItemIntoView(item: unknown): void {
315
+ const listItem = this.listItems.find(x => x.value === item);
316
+ this.suppressMouseOver = true;
317
+
318
+ if (listItem) {
319
+ listItem.element.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' });
320
+ } else {
321
+ const items = this.getEffectiveItems();
322
+ const index = items.indexOf(item);
323
+ this.host.scrollTo({ top: this.host.scrollHeight / items.length * index });
324
+ // this is a workaround to ensure the active item is scrolled into view after the items are rendered,
325
+ // if CSS defines a gap between items the virtual-repeat spacer height might be incorrect
326
+ // so we need to adjust the scroll after the list item gets rendered
327
+ this.listItemsChangedCallback = () => this.scrollItemIntoView(item);
328
+ }
329
+ }
330
+ }
@@ -0,0 +1,20 @@
1
+ <template class="ui-menu" role="menu" data-open.attr="open ? '' : null">
2
+ <ui-popup component.ref="popup"
3
+ open.bind="open"
4
+ anchor.bind="anchor"
5
+ tab-reference.bind="tabReference"
6
+ placement.bind="placement"
7
+ offset.bind="offset"
8
+ portal-target.bind="portalTarget"
9
+ portal-position.bind="portalPosition"
10
+ exposed-host.bind="effectiveSlotHost"
11
+ close-on-outside.bind="closeOnOutside"
12
+ close-on-escape.bind="closeOnEscape"
13
+ match-anchor-width.bind="matchAnchorWidth"
14
+ focus-on-open.bind="focusOnOpen"
15
+ restore-focus.bind="restoreFocus"
16
+ popup-tab-away.trigger="onPopupTabAway($event)"
17
+ list-select.trigger="onListSelect($event)">
18
+ <au-slot expose.bind="effectiveSlotHost"></au-slot>
19
+ </ui-popup>
20
+ </template>
@@ -0,0 +1,103 @@
1
+ import { bindable, BindingMode, CustomElement, customElement, INode, resolve } from 'aurelia';
2
+ import { booleanAttr } from '../base/boolean-attr';
3
+ import { Keys } from '../base/keys';
4
+ import { UiList } from '../list/ui-list';
5
+ import { UiPopup } from '../popup/ui-popup';
6
+ import template from './ui-menu.html?raw';
7
+
8
+ type MenuPlacement = 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end';
9
+
10
+ @customElement({ name: 'ui-menu', template })
11
+ export class UiMenu {
12
+ readonly element = resolve(INode) as HTMLElement;
13
+ readonly slotHost = this;
14
+ popup!: UiPopup;
15
+
16
+ @bindable({ mode: BindingMode.twoWay, set: booleanAttr })
17
+ open: boolean = false;
18
+ openChanged(newValue: boolean): void {
19
+ if (newValue && this.focusOnOpen) {
20
+ this.focus();
21
+ }
22
+ }
23
+
24
+ @bindable
25
+ anchor: Element | undefined;
26
+
27
+ @bindable
28
+ tabReference: HTMLElement | undefined;
29
+
30
+ @bindable
31
+ placement: MenuPlacement = 'bottom-start';
32
+
33
+ @bindable
34
+ offset: number = 6;
35
+
36
+ @bindable
37
+ portalTarget: string | Element | null | undefined;
38
+
39
+ @bindable
40
+ portalPosition: InsertPosition = 'beforeend';
41
+
42
+ @bindable
43
+ exposedHost: unknown;
44
+
45
+ @bindable({ set: booleanAttr })
46
+ closeOnOutside: boolean = true;
47
+
48
+ @bindable({ set: booleanAttr })
49
+ closeOnEscape: boolean = true;
50
+
51
+ @bindable({ set: booleanAttr })
52
+ matchAnchorWidth: boolean = false;
53
+
54
+ @bindable({ set: booleanAttr })
55
+ focusOnOpen: boolean = true;
56
+
57
+ @bindable({ set: booleanAttr })
58
+ restoreFocus: boolean = true;
59
+
60
+ @bindable({ set: booleanAttr })
61
+ closeOnSelect: boolean = true;
62
+
63
+ get effectiveSlotHost(): unknown {
64
+ return this.exposedHost ?? this.slotHost;
65
+ }
66
+
67
+ onListSelect(event: Event): void {
68
+ this.element.dispatchEvent(new CustomEvent('menu-select', {
69
+ bubbles: true,
70
+ detail: (event as CustomEvent).detail
71
+ }));
72
+
73
+ if (this.closeOnSelect) {
74
+ this.open = false;
75
+ }
76
+ }
77
+
78
+ onPopupTabAway(event: CustomEvent): void {
79
+ this.element.dispatchEvent(new CustomEvent('menu-tab-away', {
80
+ bubbles: true,
81
+ detail: event.detail
82
+ }));
83
+ }
84
+
85
+ focus(key: Keys.Home | Keys.End = Keys.Home): void {
86
+ const list = this.popup.panelElement?.querySelector('ui-list') as HTMLElement | null;
87
+ if (!list) {
88
+ this.popup.focus();
89
+ return;
90
+ }
91
+
92
+ const listViewModel = CustomElement.for<UiList>(list).viewModel;
93
+ if (key === Keys.End) {
94
+ listViewModel.focusLast();
95
+ } else {
96
+ listViewModel.focusFirst();
97
+ }
98
+ }
99
+
100
+ contains(target: Node): boolean {
101
+ return this.popup.contains(target);
102
+ }
103
+ }
@@ -0,0 +1,16 @@
1
+ <template class="ui-popup" data-open.attr="open ? '' : null">
2
+ <div portal="target.bind: portalTarget; position.bind: portalPosition">
3
+ <div class="ui-popup__panel"
4
+ ref="panelElement"
5
+ id.bind="panelId"
6
+ role.bind="panelRole"
7
+ tabindex="-1"
8
+ list-select.trigger="onListSelect($event)"
9
+ show.bind="open"
10
+ style.bind="panelStyle"
11
+ data-role.attr="panelRole"
12
+ data-open.attr="open ? '' : null">
13
+ <au-slot expose.bind="effectiveSlotHost"></au-slot>
14
+ </div>
15
+ </div>
16
+ </template>