@refinitiv-ui/elements 6.0.1 → 6.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/lib/accordion/index.js +2 -2
- package/lib/autosuggest/helpers/renderer.d.ts +1 -1
- package/lib/autosuggest/helpers/utils.d.ts +1 -1
- package/lib/autosuggest/index.d.ts +2 -1
- package/lib/autosuggest/themes/halo/dark/index.js +1 -1
- package/lib/autosuggest/themes/halo/light/index.js +1 -1
- package/lib/button/index.js +2 -1
- package/lib/button/themes/halo/dark/index.js +1 -1
- package/lib/button/themes/halo/light/index.js +1 -1
- package/lib/button/themes/solar/charcoal/index.js +1 -1
- package/lib/button/themes/solar/pearl/index.js +1 -1
- package/lib/calendar/index.d.ts +1 -1
- package/lib/calendar/themes/halo/dark/index.js +1 -1
- package/lib/calendar/themes/halo/light/index.js +1 -1
- package/lib/calendar/themes/solar/charcoal/index.js +1 -1
- package/lib/calendar/themes/solar/pearl/index.js +1 -1
- package/lib/checkbox/index.d.ts +8 -15
- package/lib/checkbox/index.js +19 -41
- package/lib/checkbox/themes/halo/dark/index.js +1 -1
- package/lib/checkbox/themes/halo/light/index.js +1 -1
- package/lib/checkbox/themes/solar/charcoal/index.js +1 -1
- package/lib/checkbox/themes/solar/pearl/index.js +1 -1
- package/lib/clock/themes/halo/dark/index.js +1 -1
- package/lib/clock/themes/halo/light/index.js +1 -1
- package/lib/clock/themes/solar/charcoal/index.js +1 -1
- package/lib/clock/themes/solar/pearl/index.js +1 -1
- package/lib/combo-box/index.d.ts +4 -3
- package/lib/combo-box/index.js +7 -3
- package/lib/combo-box/themes/halo/dark/index.js +1 -1
- package/lib/combo-box/themes/halo/light/index.js +1 -1
- package/lib/combo-box/themes/solar/charcoal/index.js +1 -1
- package/lib/combo-box/themes/solar/pearl/index.js +1 -1
- package/lib/datetime-field/index.d.ts +1 -1
- package/lib/datetime-field/utils.d.ts +1 -1
- package/lib/datetime-picker/themes/halo/dark/index.js +1 -1
- package/lib/datetime-picker/themes/halo/light/index.js +1 -1
- package/lib/datetime-picker/themes/solar/charcoal/index.js +1 -1
- package/lib/datetime-picker/themes/solar/pearl/index.js +1 -1
- package/lib/email-field/themes/halo/dark/index.js +1 -1
- package/lib/email-field/themes/halo/light/index.js +1 -1
- package/lib/email-field/themes/solar/charcoal/index.js +1 -1
- package/lib/email-field/themes/solar/pearl/index.js +1 -1
- package/lib/heatmap/index.d.ts +2 -2
- package/lib/heatmap/themes/halo/light/index.js +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/item/helpers/types.d.ts +6 -6
- package/lib/item/index.d.ts +2 -2
- package/lib/item/index.js +0 -1
- package/lib/item/themes/halo/dark/index.js +1 -1
- package/lib/item/themes/halo/light/index.js +1 -1
- package/lib/list/elements/list-item.d.ts +30 -0
- package/lib/list/elements/list-item.js +19 -0
- package/lib/list/elements/list.d.ts +307 -0
- package/lib/list/elements/list.js +632 -0
- package/lib/list/helpers/renderer.d.ts +0 -1
- package/lib/list/helpers/renderer.js +1 -3
- package/lib/list/index.d.ts +3 -317
- package/lib/list/index.js +3 -641
- package/lib/list/themes/halo/dark/index.js +4 -1
- package/lib/list/themes/halo/light/index.js +5 -2
- package/lib/list/themes/solar/charcoal/index.js +4 -1
- package/lib/list/themes/solar/pearl/index.js +4 -1
- package/lib/multi-input/index.d.ts +1 -5
- package/lib/multi-input/index.js +17 -18
- package/lib/notification/themes/halo/dark/index.js +1 -1
- package/lib/notification/themes/halo/light/index.js +1 -1
- package/lib/notification/themes/solar/charcoal/index.js +1 -1
- package/lib/notification/themes/solar/pearl/index.js +1 -1
- package/lib/number-field/themes/halo/dark/index.js +1 -1
- package/lib/number-field/themes/halo/light/index.js +1 -1
- package/lib/number-field/themes/solar/charcoal/index.js +1 -1
- package/lib/number-field/themes/solar/pearl/index.js +1 -1
- package/lib/overlay/index.d.ts +2 -1
- package/lib/overlay-menu/helpers/constants.d.ts +6 -0
- package/lib/overlay-menu/helpers/constants.js +7 -0
- package/lib/overlay-menu/helpers/types.d.ts +0 -6
- package/lib/overlay-menu/helpers/types.js +1 -7
- package/lib/overlay-menu/index.d.ts +1 -1
- package/lib/overlay-menu/index.js +1 -1
- package/lib/password-field/themes/halo/dark/index.js +1 -1
- package/lib/password-field/themes/halo/light/index.js +1 -1
- package/lib/password-field/themes/solar/charcoal/index.js +1 -1
- package/lib/password-field/themes/solar/pearl/index.js +1 -1
- package/lib/pill/index.d.ts +11 -3
- package/lib/pill/index.js +25 -11
- package/lib/radio-button/index.d.ts +7 -11
- package/lib/radio-button/index.js +14 -25
- package/lib/radio-button/themes/halo/dark/index.js +1 -1
- package/lib/radio-button/themes/halo/light/index.js +1 -1
- package/lib/radio-button/themes/solar/charcoal/index.js +1 -1
- package/lib/radio-button/themes/solar/pearl/index.js +1 -1
- package/lib/search-field/themes/halo/dark/index.js +1 -1
- package/lib/search-field/themes/halo/light/index.js +1 -1
- package/lib/search-field/themes/solar/charcoal/index.js +1 -1
- package/lib/search-field/themes/solar/pearl/index.js +1 -1
- package/lib/select/index.d.ts +13 -1
- package/lib/select/index.js +25 -7
- package/lib/select/themes/halo/dark/index.js +1 -1
- package/lib/select/themes/halo/light/index.js +1 -1
- package/lib/select/themes/solar/charcoal/index.js +1 -1
- package/lib/select/themes/solar/pearl/index.js +1 -1
- package/lib/slider/themes/halo/dark/index.js +1 -1
- package/lib/slider/themes/halo/light/index.js +1 -1
- package/lib/slider/themes/solar/charcoal/index.js +1 -1
- package/lib/slider/themes/solar/pearl/index.js +1 -1
- package/lib/tab/themes/halo/dark/index.js +1 -1
- package/lib/tab/themes/halo/light/index.js +1 -1
- package/lib/tab/themes/solar/charcoal/index.js +1 -1
- package/lib/tab/themes/solar/pearl/index.js +1 -1
- package/lib/tab-bar/index.d.ts +6 -7
- package/lib/tab-bar/index.js +12 -10
- package/lib/tab-bar/themes/halo/dark/index.js +1 -0
- package/lib/tab-bar/themes/halo/light/index.js +1 -0
- package/lib/tab-bar/themes/solar/charcoal/index.js +1 -0
- package/lib/tab-bar/themes/solar/pearl/index.js +1 -0
- package/lib/text-field/index.js +2 -3
- package/lib/text-field/themes/halo/dark/index.js +1 -1
- package/lib/text-field/themes/halo/light/index.js +1 -1
- package/lib/text-field/themes/solar/charcoal/index.js +1 -1
- package/lib/text-field/themes/solar/pearl/index.js +1 -1
- package/lib/toggle/index.d.ts +7 -10
- package/lib/toggle/index.js +14 -24
- package/lib/toggle/themes/halo/dark/index.js +1 -1
- package/lib/toggle/themes/halo/light/index.js +1 -1
- package/lib/toggle/themes/solar/charcoal/index.js +1 -1
- package/lib/toggle/themes/solar/pearl/index.js +1 -1
- package/lib/tooltip/helpers/overflow-tooltip.d.ts +11 -4
- package/lib/tooltip/helpers/overflow-tooltip.js +34 -6
- package/lib/tooltip/index.d.ts +2 -2
- package/lib/tooltip/index.js +2 -2
- package/lib/tree/elements/tree-item.d.ts +4 -0
- package/lib/tree/elements/tree-item.js +4 -0
- package/lib/tree/elements/tree.d.ts +1 -0
- package/lib/tree/elements/tree.js +1 -0
- package/lib/tree/helpers/renderer.d.ts +0 -1
- package/lib/tree/helpers/renderer.js +0 -2
- package/lib/tree/index.d.ts +2 -1
- package/lib/tree/themes/halo/dark/index.js +2 -2
- package/lib/tree/themes/halo/light/index.js +3 -3
- package/lib/tree/themes/solar/charcoal/index.js +2 -2
- package/lib/tree/themes/solar/pearl/index.js +2 -2
- package/lib/tree-select/themes/halo/dark/index.js +1 -1
- package/lib/tree-select/themes/halo/light/index.js +1 -1
- package/lib/tree-select/themes/solar/charcoal/index.js +1 -1
- package/lib/tree-select/themes/solar/pearl/index.js +1 -1
- package/lib/version.js +1 -1
- package/package.json +17 -17
|
@@ -0,0 +1,632 @@
|
|
|
1
|
+
import { __decorate } from "tslib";
|
|
2
|
+
import { ControlElement, css, html, WarningNotice } from '@refinitiv-ui/core';
|
|
3
|
+
import { customElement } from '@refinitiv-ui/core/decorators/custom-element.js';
|
|
4
|
+
import { property } from '@refinitiv-ui/core/decorators/property.js';
|
|
5
|
+
import { VERSION } from '../../version.js';
|
|
6
|
+
import { CollectionComposer } from '@refinitiv-ui/utils/collection.js';
|
|
7
|
+
import { getItemId } from '../helpers/item-id.js';
|
|
8
|
+
import { ListRenderer } from '../helpers/renderer.js';
|
|
9
|
+
import './list-item.js';
|
|
10
|
+
/**
|
|
11
|
+
* Key direction
|
|
12
|
+
*/
|
|
13
|
+
var Direction;
|
|
14
|
+
(function (Direction) {
|
|
15
|
+
Direction[Direction["UP"] = -1] = "UP";
|
|
16
|
+
Direction[Direction["DOWN"] = 1] = "DOWN";
|
|
17
|
+
})(Direction || (Direction = {}));
|
|
18
|
+
const valueFormatWarning = new WarningNotice('The specified \'values\' format does not conform to the required format.');
|
|
19
|
+
/**
|
|
20
|
+
* Provides listing and immutable selection
|
|
21
|
+
* @fires value-changed - Dispatched when value changes
|
|
22
|
+
*/
|
|
23
|
+
let List = class List extends ControlElement {
|
|
24
|
+
constructor() {
|
|
25
|
+
super(...arguments);
|
|
26
|
+
this.defaultRole = 'listbox';
|
|
27
|
+
/**
|
|
28
|
+
* Used to timestamp renders.
|
|
29
|
+
* This enables diff checking against item updates,
|
|
30
|
+
* rendering only items which have updated since the last render cycle.
|
|
31
|
+
*/
|
|
32
|
+
this.renderTimestamp = new Map();
|
|
33
|
+
/**
|
|
34
|
+
* Requests an update after a composer modification.
|
|
35
|
+
* @returns Update promise.
|
|
36
|
+
*/
|
|
37
|
+
this.modificationUpdate = () => {
|
|
38
|
+
this.requestUpdate();
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Item map; used to link element nodes to data items.
|
|
42
|
+
*/
|
|
43
|
+
this.itemMap = new Map();
|
|
44
|
+
/**
|
|
45
|
+
* Element map; used to link data items to element nodes.
|
|
46
|
+
*/
|
|
47
|
+
this.elementMap = new Map();
|
|
48
|
+
/**
|
|
49
|
+
* Composer used to query and modify item state.
|
|
50
|
+
*/
|
|
51
|
+
this.composer = new CollectionComposer([]);
|
|
52
|
+
/**
|
|
53
|
+
* Element focus delegation.
|
|
54
|
+
* Set to `false` and relies on native focusing.
|
|
55
|
+
*/
|
|
56
|
+
this.delegatesFocus = false;
|
|
57
|
+
/**
|
|
58
|
+
* Renderer used to render list item elements
|
|
59
|
+
* @type {ListRenderer}
|
|
60
|
+
*/
|
|
61
|
+
this.renderer = new ListRenderer(this);
|
|
62
|
+
/**
|
|
63
|
+
* Disable selections
|
|
64
|
+
*/
|
|
65
|
+
this.stateless = false;
|
|
66
|
+
/**
|
|
67
|
+
* Allow multiple selections
|
|
68
|
+
*/
|
|
69
|
+
this.multiple = false;
|
|
70
|
+
this._data = null;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Element version number
|
|
74
|
+
* @returns version number
|
|
75
|
+
*/
|
|
76
|
+
static get version() {
|
|
77
|
+
return VERSION;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The data object, used to render the list.
|
|
81
|
+
* @type {ListData}
|
|
82
|
+
* @default null
|
|
83
|
+
*/
|
|
84
|
+
get data() {
|
|
85
|
+
return this._data;
|
|
86
|
+
}
|
|
87
|
+
set data(value) {
|
|
88
|
+
const oldValue = this._data;
|
|
89
|
+
if (oldValue === value) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (value instanceof CollectionComposer) {
|
|
93
|
+
this.composer = value;
|
|
94
|
+
}
|
|
95
|
+
else if (Array.isArray(value)) {
|
|
96
|
+
this.composer = new CollectionComposer(value);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
this.composer = new CollectionComposer([]);
|
|
100
|
+
}
|
|
101
|
+
this.composer.on('modification', // Listen for modifications
|
|
102
|
+
this.modificationUpdate // Update the template
|
|
103
|
+
);
|
|
104
|
+
this.clearMaps();
|
|
105
|
+
this._data = value;
|
|
106
|
+
this.requestUpdate('data', oldValue);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Returns the first selected item value.
|
|
110
|
+
* Use `values` when multiple selection mode is enabled.
|
|
111
|
+
* @default -
|
|
112
|
+
*/
|
|
113
|
+
get value() {
|
|
114
|
+
return this.values[0] || '';
|
|
115
|
+
}
|
|
116
|
+
set value(value) {
|
|
117
|
+
const oldValue = this.value;
|
|
118
|
+
if (value !== oldValue || this.values.length > 1) {
|
|
119
|
+
this.clearSelection();
|
|
120
|
+
const item = this.queryItemsByPropertyValue('value', value)[0];
|
|
121
|
+
if (item) {
|
|
122
|
+
this.composer.setItemPropertyValue(item, 'selected', true);
|
|
123
|
+
}
|
|
124
|
+
this.requestUpdate('value', oldValue);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Returns a values collection of the currently
|
|
129
|
+
* selected item values
|
|
130
|
+
* @type {string[]}
|
|
131
|
+
* @default []
|
|
132
|
+
* @readonly
|
|
133
|
+
*/
|
|
134
|
+
get values() {
|
|
135
|
+
return this.queryItemsByPropertyValue('selected', true)
|
|
136
|
+
.map((item) => this.composer.getItemPropertyValue(item, 'value'));
|
|
137
|
+
}
|
|
138
|
+
set values(values) {
|
|
139
|
+
if (!Array.isArray(values)) {
|
|
140
|
+
valueFormatWarning.show();
|
|
141
|
+
this.values = [];
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
// Clone value arrays
|
|
145
|
+
const newValue = values.slice();
|
|
146
|
+
const oldValue = this.values.slice();
|
|
147
|
+
newValue.sort();
|
|
148
|
+
oldValue.sort();
|
|
149
|
+
// Create comparison strings to check for differences
|
|
150
|
+
const newComparison = newValue.toString();
|
|
151
|
+
const oldComparison = oldValue.toString();
|
|
152
|
+
// Should we update the selection state?
|
|
153
|
+
if (newComparison !== oldComparison) {
|
|
154
|
+
this.clearSelection();
|
|
155
|
+
values.some((value) => {
|
|
156
|
+
const matches = this.queryItemsByPropertyValue('value', value);
|
|
157
|
+
matches.forEach((match) => this.composer.setItemPropertyValue(match, 'selected', true));
|
|
158
|
+
return !this.multiple; // Only set the fist value if multiple is not enabled
|
|
159
|
+
});
|
|
160
|
+
this.requestUpdate('values', oldValue);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Selects an item in the list
|
|
166
|
+
* @param item Data Item or Item Element
|
|
167
|
+
* @returns If a selection has been made or not
|
|
168
|
+
*/
|
|
169
|
+
selectItem(item) {
|
|
170
|
+
if (!this.stateless) {
|
|
171
|
+
if (item instanceof HTMLElement) {
|
|
172
|
+
item = this.itemFromElement(item);
|
|
173
|
+
}
|
|
174
|
+
if (item && this.multiple) {
|
|
175
|
+
const value = this.composer.getItemPropertyValue(item, 'selected');
|
|
176
|
+
this.composer.setItemPropertyValue(item, 'selected', !value);
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
if (item && this.composer.getItemPropertyValue(item, 'selected') !== true) {
|
|
180
|
+
this.clearSelection();
|
|
181
|
+
this.composer.setItemPropertyValue(item, 'selected', true);
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Navigate up through the list items
|
|
189
|
+
* @returns {void}
|
|
190
|
+
*/
|
|
191
|
+
up() {
|
|
192
|
+
this.highlightItem(this.getNextHighlightItem(Direction.UP), true);
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Navigate down through the list items
|
|
196
|
+
* @returns {void}
|
|
197
|
+
*/
|
|
198
|
+
down() {
|
|
199
|
+
this.highlightItem(this.getNextHighlightItem(Direction.DOWN), true);
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Navigate to first focusable item of the list
|
|
203
|
+
* @returns {void}
|
|
204
|
+
*/
|
|
205
|
+
first() {
|
|
206
|
+
const firstItem = this.itemMap.get(this.tabbableItems[0]);
|
|
207
|
+
this.highlightItem(firstItem, true);
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Navigate to first focusable item of the list
|
|
211
|
+
* @returns {void}
|
|
212
|
+
*/
|
|
213
|
+
last() {
|
|
214
|
+
const lastItem = this.itemMap.get(this.tabbableItems[this.tabbableItems.length - 1]);
|
|
215
|
+
this.highlightItem(lastItem, true);
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Proxy for querying the composer
|
|
219
|
+
* @param engine composer querying engine
|
|
220
|
+
* @returns Collection of queried items
|
|
221
|
+
*/
|
|
222
|
+
queryItems(engine) {
|
|
223
|
+
return this.composer.queryItems(engine);
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Proxy for querying the composer by property and value
|
|
227
|
+
* @param name Property name
|
|
228
|
+
* @param value Property value
|
|
229
|
+
* @returns Collection of queried items
|
|
230
|
+
*/
|
|
231
|
+
queryItemsByPropertyValue(name, value) {
|
|
232
|
+
return this.composer.queryItemsByPropertyValue(name, value);
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Gets the associated element for the data item provided,
|
|
236
|
+
* if there is one available.
|
|
237
|
+
* @param item Item to map element to
|
|
238
|
+
* @returns Associated element
|
|
239
|
+
*/
|
|
240
|
+
elementFromItem(item) {
|
|
241
|
+
return this.elementMap.get(item);
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Gets the associated data item for the provided element,
|
|
245
|
+
* if there is one available.
|
|
246
|
+
* @param element Element to map item to
|
|
247
|
+
* @returns Associated date item
|
|
248
|
+
*/
|
|
249
|
+
itemFromElement(element) {
|
|
250
|
+
return this.itemMap.get(element);
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Tries to find the next focusable element.
|
|
254
|
+
* @param direction Direction to search
|
|
255
|
+
* @param element Starting element
|
|
256
|
+
* @returns Next logical element to focus
|
|
257
|
+
*/
|
|
258
|
+
getNextFocusableItem(direction, element) {
|
|
259
|
+
if (!element) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const children = this.tabbableItems;
|
|
263
|
+
if (children.length > 1) {
|
|
264
|
+
let index = children.indexOf(element) + direction;
|
|
265
|
+
if (index < 0) {
|
|
266
|
+
index = children.length - 1;
|
|
267
|
+
}
|
|
268
|
+
else if (index >= children.length) {
|
|
269
|
+
index = 0;
|
|
270
|
+
}
|
|
271
|
+
return children[index];
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Tries to find the next highlight item
|
|
276
|
+
* @param direction Direction to search
|
|
277
|
+
* @returns A data item, if found.
|
|
278
|
+
*/
|
|
279
|
+
getNextHighlightItem(direction) {
|
|
280
|
+
const highlightItem = this.queryItemsByPropertyValue('highlighted', true)[0];
|
|
281
|
+
const nextElement = this.getNextFocusableItem(direction) || this.getNextFocusableItem(direction, this.elementFromItem(highlightItem));
|
|
282
|
+
const backupElement = this.tabbableItems[0];
|
|
283
|
+
if (nextElement) {
|
|
284
|
+
return this.itemFromElement(nextElement);
|
|
285
|
+
}
|
|
286
|
+
else if (backupElement) {
|
|
287
|
+
return this.itemFromElement(backupElement);
|
|
288
|
+
}
|
|
289
|
+
return undefined;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Clears any highlighted item
|
|
293
|
+
* @returns {void}
|
|
294
|
+
*/
|
|
295
|
+
clearHighlighted() {
|
|
296
|
+
this.queryItemsByPropertyValue('highlighted', true)
|
|
297
|
+
.forEach(item => this.composer.setItemPropertyValue(item, 'highlighted', false));
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Highlights a single item.
|
|
301
|
+
* Used for navigation.
|
|
302
|
+
* @param item Item to highlight
|
|
303
|
+
* @param scrollToItem Scroll the item into view?
|
|
304
|
+
* @returns {void}
|
|
305
|
+
*/
|
|
306
|
+
highlightItem(item, scrollToItem = false) {
|
|
307
|
+
if (item) {
|
|
308
|
+
this.clearHighlighted();
|
|
309
|
+
this.composer.setItemPropertyValue(item, 'highlighted', true);
|
|
310
|
+
if (this.tabIndex >= 0) {
|
|
311
|
+
const id = getItemId(this.renderer.key, item.value);
|
|
312
|
+
this.setAttribute('aria-activedescendant', id);
|
|
313
|
+
}
|
|
314
|
+
scrollToItem && this.scrollToItem(item);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Gets the available tabbable elements
|
|
319
|
+
*/
|
|
320
|
+
get tabbableItems() {
|
|
321
|
+
return Array.from(this.children).filter((item) => {
|
|
322
|
+
if (item instanceof HTMLElement) {
|
|
323
|
+
const role = item.getAttribute('role');
|
|
324
|
+
const isEnabled = !item.hasAttribute('disabled');
|
|
325
|
+
const isOption = role ? ['option', 'treeitem'].includes(role) : false;
|
|
326
|
+
return isOption && isEnabled;
|
|
327
|
+
}
|
|
328
|
+
return false;
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Returns the current focused element
|
|
333
|
+
*/
|
|
334
|
+
get highlightElement() {
|
|
335
|
+
const item = this.queryItemsByPropertyValue('highlighted', true)[0];
|
|
336
|
+
return this.elementFromItem(item) || null;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Tries to select the current highlighted element
|
|
340
|
+
* @returns {void}
|
|
341
|
+
*/
|
|
342
|
+
triggerActiveItem() {
|
|
343
|
+
const element = this.highlightElement;
|
|
344
|
+
const item = element && this.itemFromElement(element);
|
|
345
|
+
item && this.selectItem(item) && this.fireSelectionUpdate();
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Scroll to list item element
|
|
349
|
+
* @param item Data item to scroll to
|
|
350
|
+
* @returns {void}
|
|
351
|
+
*/
|
|
352
|
+
scrollToItem(item) {
|
|
353
|
+
const element = this.elementFromItem(item);
|
|
354
|
+
if (element) {
|
|
355
|
+
const minPosition = this.scrollTop;
|
|
356
|
+
const maxPosition = this.scrollTop + this.clientHeight - element.offsetHeight;
|
|
357
|
+
const position = element.offsetTop;
|
|
358
|
+
let scrollPosition;
|
|
359
|
+
if (position > maxPosition) {
|
|
360
|
+
scrollPosition = element.offsetTop - this.clientHeight + element.offsetHeight;
|
|
361
|
+
}
|
|
362
|
+
else if (position < minPosition) {
|
|
363
|
+
scrollPosition = element.offsetTop;
|
|
364
|
+
}
|
|
365
|
+
if (scrollPosition) {
|
|
366
|
+
this.scrollTop = scrollPosition;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Handles key input
|
|
372
|
+
* @param event Key down event object
|
|
373
|
+
* @returns {void}
|
|
374
|
+
*/
|
|
375
|
+
onKeyDown(event) {
|
|
376
|
+
switch (event.key) {
|
|
377
|
+
case ' ':
|
|
378
|
+
case 'Spacebar':
|
|
379
|
+
case 'Enter':
|
|
380
|
+
this.triggerActiveItem();
|
|
381
|
+
break;
|
|
382
|
+
case 'Up':
|
|
383
|
+
case 'ArrowUp':
|
|
384
|
+
this.up();
|
|
385
|
+
break;
|
|
386
|
+
case 'Down':
|
|
387
|
+
case 'ArrowDown':
|
|
388
|
+
this.down();
|
|
389
|
+
break;
|
|
390
|
+
case 'Home':
|
|
391
|
+
this.first();
|
|
392
|
+
break;
|
|
393
|
+
case 'End':
|
|
394
|
+
this.last();
|
|
395
|
+
break;
|
|
396
|
+
default:
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
event.preventDefault();
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Handle list on tap
|
|
403
|
+
* Typically it will select an item
|
|
404
|
+
* @param event Event to handle
|
|
405
|
+
* @returns {void}
|
|
406
|
+
*/
|
|
407
|
+
onTap(event) {
|
|
408
|
+
const element = this.findItemElementFromTarget(event.target);
|
|
409
|
+
const item = element && this.itemFromElement(element);
|
|
410
|
+
if (item) {
|
|
411
|
+
this.highlightItem(item);
|
|
412
|
+
if (this.selectItem(item)) {
|
|
413
|
+
this.fireSelectionUpdate();
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Handles mouse move
|
|
419
|
+
* Typically it will highlight an item
|
|
420
|
+
* @param event Event to handle
|
|
421
|
+
* @returns {void}
|
|
422
|
+
*/
|
|
423
|
+
onMouse(event) {
|
|
424
|
+
const element = this.findItemElementFromTarget(event.target);
|
|
425
|
+
const item = element && this.itemFromElement(element);
|
|
426
|
+
if (item && element !== this.highlightElement) {
|
|
427
|
+
this.highlightItem(item);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Handles item focus out
|
|
432
|
+
* Typically it will remove highlighting
|
|
433
|
+
* @returns {void}
|
|
434
|
+
*/
|
|
435
|
+
onBlur() {
|
|
436
|
+
this.clearHighlighted();
|
|
437
|
+
this.removeAttribute('aria-activedescendant');
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Tries to find a known item element,
|
|
441
|
+
* from an event target
|
|
442
|
+
* @param target Event target
|
|
443
|
+
* @returns Found element, if available
|
|
444
|
+
*/
|
|
445
|
+
findItemElementFromTarget(target) {
|
|
446
|
+
let element = target;
|
|
447
|
+
while (element) {
|
|
448
|
+
if (this.itemMap.has(element)) {
|
|
449
|
+
break; // known rendered item
|
|
450
|
+
}
|
|
451
|
+
element = element.parentElement;
|
|
452
|
+
}
|
|
453
|
+
return element;
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Clears the current selected items
|
|
457
|
+
* @returns {void}
|
|
458
|
+
*/
|
|
459
|
+
clearSelection() {
|
|
460
|
+
this.queryItemsByPropertyValue('selected', true)
|
|
461
|
+
.forEach((item) => this.composer.setItemPropertyValue(item, 'selected', false));
|
|
462
|
+
this.requestUpdate();
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Queries and returns all renderable items.
|
|
466
|
+
* @returns Collection of renderable items
|
|
467
|
+
*/
|
|
468
|
+
get renderItems() {
|
|
469
|
+
return this.queryItems((item, composer) => {
|
|
470
|
+
return composer.getItemPropertyValue(item, 'hidden') !== true;
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Proxy for creating list item elements.
|
|
475
|
+
* Allows for a mapping to be created between
|
|
476
|
+
* Data Item and Item Element.
|
|
477
|
+
* @param item Data item context
|
|
478
|
+
* @param recyclableElements Child elements available for reuse
|
|
479
|
+
* @returns List item element
|
|
480
|
+
*/
|
|
481
|
+
createListItem(item, recyclableElements) {
|
|
482
|
+
const cachedElement = this.elementFromItem(item);
|
|
483
|
+
const previousTimestamp = this.renderTimestamp.get(item) || NaN;
|
|
484
|
+
if (cachedElement && previousTimestamp > this.composer.getItemTimestamp(item)) {
|
|
485
|
+
return cachedElement; // don't re-render if the item hasn't changed
|
|
486
|
+
}
|
|
487
|
+
if (!cachedElement && recyclableElements.length) {
|
|
488
|
+
// Remove any old ties with the reusable element.
|
|
489
|
+
const recycledElement = recyclableElements.pop();
|
|
490
|
+
const previousItem = this.itemFromElement(recycledElement);
|
|
491
|
+
this.itemMap.delete(recycledElement);
|
|
492
|
+
previousItem && this.elementMap.delete(previousItem);
|
|
493
|
+
this.elementMap.set(item, recycledElement);
|
|
494
|
+
}
|
|
495
|
+
const freshElement = this.renderer(item, this.composer, this.elementFromItem(item));
|
|
496
|
+
if (cachedElement && cachedElement !== freshElement) {
|
|
497
|
+
// Renderer returned a new element, so remove the old link.
|
|
498
|
+
this.itemMap.delete(cachedElement);
|
|
499
|
+
}
|
|
500
|
+
this.itemMap.set(freshElement, item); // Link element to item
|
|
501
|
+
this.elementMap.set(item, freshElement); // Link item to element
|
|
502
|
+
this.renderTimestamp.set(item, performance.now());
|
|
503
|
+
return freshElement;
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Clears all item-element and timestamp maps
|
|
507
|
+
* @returns {void}
|
|
508
|
+
*/
|
|
509
|
+
clearMaps() {
|
|
510
|
+
this.itemMap.clear();
|
|
511
|
+
this.elementMap.clear();
|
|
512
|
+
this.renderTimestamp.clear();
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Fire value changed event
|
|
516
|
+
* @returns {void}
|
|
517
|
+
*/
|
|
518
|
+
fireSelectionUpdate() {
|
|
519
|
+
/**
|
|
520
|
+
* @event List#value-changed
|
|
521
|
+
*/
|
|
522
|
+
this.notifyPropertyChange('value', this.value);
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Calculates what elements can be recycled safely
|
|
526
|
+
* @param renderItems Current items to render
|
|
527
|
+
* @returns Collection of elements to be recycled
|
|
528
|
+
*/
|
|
529
|
+
calculateRecyclableElements(renderItems) {
|
|
530
|
+
const result = [];
|
|
531
|
+
for (const element of this.children) {
|
|
532
|
+
const item = this.itemFromElement(element);
|
|
533
|
+
if (item && !renderItems.includes(item)) {
|
|
534
|
+
result.push(element);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
return result;
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Renders updates to light DOM
|
|
541
|
+
* @returns {void}
|
|
542
|
+
*/
|
|
543
|
+
renderLightDOM() {
|
|
544
|
+
const renderItems = this.renderItems;
|
|
545
|
+
const currentChildren = Array.from(this.children);
|
|
546
|
+
const recyclableElements = this.calculateRecyclableElements(renderItems);
|
|
547
|
+
const renderChildren = renderItems.map((item) => this.createListItem(item, recyclableElements));
|
|
548
|
+
const deletions = currentChildren.filter(element => !renderChildren.includes(element));
|
|
549
|
+
deletions.forEach(element => this.removeChild(element));
|
|
550
|
+
renderChildren.forEach((element, index) => {
|
|
551
|
+
if (this.children.length === index) {
|
|
552
|
+
this.appendChild(element);
|
|
553
|
+
}
|
|
554
|
+
else if (element !== this.children[index]) {
|
|
555
|
+
this.insertBefore(element, this.children[index]);
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Invoked when the element is first updated. Implement to perform one time work on the element after update.
|
|
561
|
+
* @param changeProperties changed properties
|
|
562
|
+
* @returns {void}
|
|
563
|
+
*/
|
|
564
|
+
firstUpdated(changeProperties) {
|
|
565
|
+
super.firstUpdated(changeProperties);
|
|
566
|
+
this.addEventListener('keydown', this.onKeyDown);
|
|
567
|
+
this.addEventListener('tap', this.onTap);
|
|
568
|
+
this.addEventListener('mousemove', this.onMouse);
|
|
569
|
+
this.addEventListener('mouseleave', this.clearHighlighted);
|
|
570
|
+
this.addEventListener('focusout', this.onBlur);
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Invoked before update() to compute values needed during the update.
|
|
574
|
+
* @param changeProperties changed properties
|
|
575
|
+
* @returns {void}
|
|
576
|
+
*/
|
|
577
|
+
willUpdate(changeProperties) {
|
|
578
|
+
if (changeProperties.has('multiple')) {
|
|
579
|
+
this.renderTimestamp.clear(); // force render of all items
|
|
580
|
+
this.setAttribute('aria-multiselectable', this.multiple ? 'true' : 'false');
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* A `CSSResultGroup` that will be used
|
|
585
|
+
* to style the host, slotted children
|
|
586
|
+
* and the internal template of the element.
|
|
587
|
+
* @return CSS template
|
|
588
|
+
*/
|
|
589
|
+
static get styles() {
|
|
590
|
+
return css `
|
|
591
|
+
:host {
|
|
592
|
+
display: block;
|
|
593
|
+
max-height: 600px;
|
|
594
|
+
overflow-y: auto;
|
|
595
|
+
position: relative; /* required for scrollToItem */
|
|
596
|
+
}
|
|
597
|
+
`;
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* A `TemplateResult` that will be used
|
|
601
|
+
* to render the updated internal template.
|
|
602
|
+
* @return Render template
|
|
603
|
+
*/
|
|
604
|
+
render() {
|
|
605
|
+
this.renderLightDOM();
|
|
606
|
+
return html `<slot></slot>`;
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
__decorate([
|
|
610
|
+
property({ type: Function, attribute: false })
|
|
611
|
+
], List.prototype, "renderer", void 0);
|
|
612
|
+
__decorate([
|
|
613
|
+
property({ type: Boolean })
|
|
614
|
+
], List.prototype, "stateless", void 0);
|
|
615
|
+
__decorate([
|
|
616
|
+
property({ type: Boolean })
|
|
617
|
+
], List.prototype, "multiple", void 0);
|
|
618
|
+
__decorate([
|
|
619
|
+
property({ attribute: false })
|
|
620
|
+
], List.prototype, "data", null);
|
|
621
|
+
__decorate([
|
|
622
|
+
property({ type: String })
|
|
623
|
+
], List.prototype, "value", null);
|
|
624
|
+
__decorate([
|
|
625
|
+
property({ type: Array, attribute: false })
|
|
626
|
+
], List.prototype, "values", null);
|
|
627
|
+
List = __decorate([
|
|
628
|
+
customElement('ef-list', {
|
|
629
|
+
alias: 'coral-list'
|
|
630
|
+
})
|
|
631
|
+
], List);
|
|
632
|
+
export { List };
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { uuid } from '@refinitiv-ui/utils/uuid.js';
|
|
2
|
-
import '../../item/index.js';
|
|
3
2
|
import { getItemId } from './item-id.js';
|
|
4
3
|
import { Renderer } from '../renderer.js';
|
|
5
4
|
/**
|
|
@@ -15,7 +14,7 @@ export class ListRenderer extends Renderer {
|
|
|
15
14
|
/**
|
|
16
15
|
* Element to render
|
|
17
16
|
*/
|
|
18
|
-
const el = (element || document.createElement('ef-item'));
|
|
17
|
+
const el = (element || document.createElement('ef-list-item'));
|
|
19
18
|
/**
|
|
20
19
|
* Tooltip value to be used, if any.
|
|
21
20
|
*/
|
|
@@ -32,7 +31,6 @@ export class ListRenderer extends Renderer {
|
|
|
32
31
|
el.type = composer.getItemPropertyValue(item, 'type');
|
|
33
32
|
el.multiple = !!context && context.multiple === true;
|
|
34
33
|
const itemRole = el.type === 'text' || !el.type ? 'option' : 'presentation';
|
|
35
|
-
el.tabIndex = -1;
|
|
36
34
|
el.setAttribute('role', itemRole);
|
|
37
35
|
tooltip ? el.setAttribute('title', tooltip) : el.removeAttribute('title');
|
|
38
36
|
return el;
|