neo.mjs 6.10.12 → 6.10.14
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/apps/ServiceWorker.mjs +2 -2
- package/apps/portal/view/learn/LivePreview.mjs +4 -2
- package/buildScripts/createAppMinimal.mjs +0 -30
- package/examples/ServiceWorker.mjs +2 -2
- package/examples/button/base/neo-config.json +1 -2
- package/examples/dialog/MainContainer.mjs +2 -0
- package/package.json +4 -4
- package/resources/data/deck/learnneo/p/2023-10-14T19-25-08-153Z.md +1 -0
- package/resources/data/deck/learnneo/p/ComponentModels.md +27 -13
- package/resources/data/deck/learnneo/p/DescribingTheUI.md +1 -1
- package/resources/data/deck/learnneo/p/Earthquakes.md +1226 -31
- package/resources/data/deck/learnneo/p/Events.md +11 -0
- package/resources/data/deck/learnneo/p/GuideEvents.md +153 -0
- package/resources/data/deck/learnneo/t.json +3 -1
- package/resources/scss/src/apps/portal/learn/ContentView.scss +1 -0
- package/resources/scss/src/list/Base.scss +1 -45
- package/src/DefaultConfig.mjs +2 -2
- package/src/component/Base.mjs +32 -0
- package/src/data/RecordFactory.mjs +0 -3
- package/src/dialog/Base.mjs +14 -7
- package/src/form/field/Picker.mjs +9 -1
- package/src/form/field/Select.mjs +23 -4
- package/src/form/field/Text.mjs +4 -2
- package/src/layout/Flexbox.mjs +31 -23
- package/src/layout/HBox.mjs +1 -1
- package/src/layout/VBox.mjs +1 -1
- package/src/main/DomAccess.mjs +11 -3
- package/src/main/DomUtils.mjs +26 -3
- package/src/main/addon/Navigator.mjs +139 -27
- package/src/manager/DomEvent.mjs +9 -3
- package/src/manager/Focus.mjs +3 -1
- package/src/util/String.mjs +3 -2
- package/test/components/files/form/field/Select.mjs +25 -4
- package/resources/data/deck/learnneo/p/AddingProperties.md +0 -1
- package/resources/data/deck/learnneo/p/ComponentState.md +0 -1
- package/resources/scss/src/apps/newwebsite/Viewport.scss +0 -32
- package/resources/scss/theme-neo-light/design-tokens/Components.scss +0 -3
package/src/layout/HBox.mjs
CHANGED
package/src/layout/VBox.mjs
CHANGED
package/src/main/DomAccess.mjs
CHANGED
@@ -360,10 +360,18 @@ class DomAccess extends Base {
|
|
360
360
|
let node = this.getElement(data.id);
|
361
361
|
|
362
362
|
if (node) {
|
363
|
-
|
363
|
+
// The children property means focus inner elements if possible.
|
364
|
+
if (!DomUtils.isFocusable(node) && data.children) {
|
365
|
+
// Prefer to focus input fields over buttons.
|
366
|
+
// querySelector('input,textarea,button') returns buttons first, so use multiple calls.
|
367
|
+
node = node.querySelector('input:not(:disabled)') || node.querySelector('textarea:not(:disabled)') || node.querySelector('button:not(:disabled)') || [...node.querySelectorAll('*')].find(DomUtils.isFocusable);
|
368
|
+
}
|
369
|
+
if (node) {
|
370
|
+
node.focus();
|
364
371
|
|
365
|
-
|
366
|
-
|
372
|
+
if (Neo.isNumber(node.selectionStart)) {
|
373
|
+
node.selectionStart = node.selectionEnd = node.value.length;
|
374
|
+
}
|
367
375
|
}
|
368
376
|
}
|
369
377
|
|
package/src/main/DomUtils.mjs
CHANGED
@@ -42,9 +42,31 @@ export default class DomUtils extends Base {
|
|
42
42
|
}
|
43
43
|
}
|
44
44
|
|
45
|
+
/**
|
46
|
+
* Analogous to the `HTMLElement` `querySelectorAll` method. Searches the passed element
|
47
|
+
* and all descendants for all elements for which the passed `filterFn` returns `true`.
|
48
|
+
* @param {HTMLElement} el The element to start from.
|
49
|
+
* @param {Function} filterFn A function which returns `true` when a desired element is reached.
|
50
|
+
* @returns {HTMLElement[]} An array of matching elements
|
51
|
+
*/
|
52
|
+
static queryAll(el, filterFn) {
|
53
|
+
return [el, ...el.querySelectorAll('*')].filter(filterFn);
|
54
|
+
}
|
55
|
+
|
56
|
+
/**
|
57
|
+
* Analogous to the `HTMLElement` `querySelector` method. Searches the passed element
|
58
|
+
* and all descendants for the first element for which the passed `filterFn` returns `true`.
|
59
|
+
* @param {HTMLElement} el The element to start from.
|
60
|
+
* @param {Function} filterFn A function which returns `true` when the desired element is reached.
|
61
|
+
* @returns {HTMLElement} The first matching element
|
62
|
+
*/
|
63
|
+
static query(el, filterFn) {
|
64
|
+
return [el, ...el.querySelectorAll('*')].find(filterFn);
|
65
|
+
}
|
66
|
+
|
45
67
|
static isFocusable(e) {
|
46
68
|
// May be used as a scopeless callback, so use "DomUtils", not "this"
|
47
|
-
return DomUtils.isTabbable(e) || e.getAttribute('tabIndex')
|
69
|
+
return DomUtils.isTabbable(e) || Number(e.getAttribute('tabIndex')) < 0;
|
48
70
|
}
|
49
71
|
|
50
72
|
static isTabbable(e) {
|
@@ -53,8 +75,9 @@ export default class DomUtils extends Base {
|
|
53
75
|
style = getComputedStyle(e),
|
54
76
|
tabIndex = e.getAttribute('tabIndex');
|
55
77
|
|
56
|
-
// Hidden elements not tabbable
|
57
|
-
|
78
|
+
// Hidden elements are not tabbable.
|
79
|
+
// Negative tabIndex also means not tabbable (Though still focusable)
|
80
|
+
if (!e.isConnected || !e.offsetParent || style.getPropertyValue('visibility') === 'hidden' || Number(tabIndex) < 0) {
|
58
81
|
return false
|
59
82
|
}
|
60
83
|
|
@@ -3,6 +3,13 @@ import DomAccess from '../DomAccess.mjs';
|
|
3
3
|
import DomUtils from '../DomUtils.mjs';
|
4
4
|
import DomEvents from '../DomEvents.mjs';
|
5
5
|
|
6
|
+
// We do not need to inject a synthesized "click" event when we detect an ENTER
|
7
|
+
// keypress on these element types.
|
8
|
+
const enterActivatedTags= {
|
9
|
+
A : 1,
|
10
|
+
BUTTON : 1
|
11
|
+
};
|
12
|
+
|
6
13
|
/**
|
7
14
|
* Addon for Navigator
|
8
15
|
* @class Neo.main.addon.Navigator
|
@@ -40,13 +47,27 @@ class Navigator extends Base {
|
|
40
47
|
*
|
41
48
|
* When navigation occurs from one navigable element to another, the `navigate` event
|
42
49
|
* will be fired.
|
50
|
+
*
|
51
|
+
* Note that if focus is expected to enter the subject, the navigable elements
|
52
|
+
* designated by the `selector` must be focusable in some way. So if not using natively
|
53
|
+
* focusable elements, they must have `tabIndex="-1"`.
|
54
|
+
*
|
55
|
+
* Upon navigation, the `aria-activedescendant` property is automatically updated
|
56
|
+
* on the `eventSource` element (which defaults to the subject element, but may be external)
|
57
|
+
*
|
58
|
+
* Pressing `Enter` when an item is active clicks that item.
|
59
|
+
*
|
60
|
+
* if `autoClick` is set to `true` in the data, simply navigating to an element will click it.
|
43
61
|
* @param {*} data
|
44
62
|
* @param {String} data.id The element id to navigate in.
|
45
63
|
* @param {String} [data.eventSource] Optional - the element id to read keystrokes from.
|
46
|
-
* defaults to the main element id.
|
64
|
+
* defaults to the main element id. Select field uses this. Focus remains in the field's
|
65
|
+
* `<input>` element while navigating its dropdown.
|
47
66
|
* @param {String} data.selector A CSS selector which identifies the navigable elements.
|
48
67
|
* @param {String} data.activeCls A CSS class to add to the currently active navigable element.
|
49
|
-
* @param {Boolean} wrap Pass as `true` to have navigation wrap from first to last and vice versa.
|
68
|
+
* @param {Boolean} data.wrap Pass as `true` to have navigation wrap from first to last and vice versa.
|
69
|
+
* @param {Boolean} [data.autoClick=false] Pass as `true` to have navigation click the target navigated to.
|
70
|
+
* TabPanels will use this on their tab toolbar.
|
50
71
|
*/
|
51
72
|
subscribe(data) {
|
52
73
|
const
|
@@ -60,12 +81,16 @@ class Navigator extends Base {
|
|
60
81
|
data.activeCls = 'neo-navigator-active-item'
|
61
82
|
}
|
62
83
|
|
84
|
+
// Ensure that only *one* of the child focusables is actually tabbable.
|
85
|
+
// We use arrow keys for internal navigation. TAB must move out.
|
86
|
+
me.fixItemFocusability(data);
|
87
|
+
|
63
88
|
// Finds a focusable item starting from a descendant el within one of our selector items
|
64
89
|
data.findFocusable = el => DomUtils.closest(el, el =>
|
65
90
|
// We're looking for an element that is focusable
|
66
91
|
DomUtils.isFocusable(el) &&
|
67
92
|
// And within our subject element
|
68
|
-
(subject.
|
93
|
+
(subject.compareDocumentPosition(el) & Node.DOCUMENT_POSITION_CONTAINED_BY) &&
|
69
94
|
// And within an element that matches our selector
|
70
95
|
el.closest(data.selector)
|
71
96
|
);
|
@@ -80,9 +105,39 @@ class Navigator extends Base {
|
|
80
105
|
});
|
81
106
|
|
82
107
|
eventSource.addEventListener('keydown', data.l1 = e => me.navigateKeyDownHandler(e, data));
|
83
|
-
subject.addEventListener('mousedown',
|
84
|
-
subject.addEventListener('click',
|
85
|
-
subject.addEventListener('focusin',
|
108
|
+
subject.addEventListener('mousedown', data.l2 = e => me.navigateMouseDownHandler(e, data));
|
109
|
+
subject.addEventListener('click', data.l3 = e => me.navigateClickHandler(e, data));
|
110
|
+
subject.addEventListener('focusin', data.l4 = e => me.navigateFocusInHandler(e, data));
|
111
|
+
subject.addEventListener('focusout', data.l5 = e => me.navigateFocusOutHandler(e, data));
|
112
|
+
}
|
113
|
+
|
114
|
+
// The navigables we are dealing with, if they are focusable must *not* be tabbable.
|
115
|
+
// Only *one* must be tabbable, so that tabbing into the subject element goes to the
|
116
|
+
// one active element.
|
117
|
+
//
|
118
|
+
// Tabbing *from* that must exit the subject element.
|
119
|
+
//
|
120
|
+
// So we must ensure that all the focusable elements except the first are not tabbable.
|
121
|
+
fixItemFocusability(data) {
|
122
|
+
// If the key events are being read from an external element, then that will always contain
|
123
|
+
// focus, so we have nothing to do here. The navigable items wil be inert and not
|
124
|
+
// focusable. Navigation will be "virtual". Select field navigates its dropdowns like this.
|
125
|
+
if (!data.subject.contains(data.eventSource)) {
|
126
|
+
return;
|
127
|
+
}
|
128
|
+
|
129
|
+
const
|
130
|
+
focusables = DomUtils.queryAll(data.subject, DomUtils.isFocusable),
|
131
|
+
defaultActiveItem = focusables[0] || data.subject.querySelector(data.selector);
|
132
|
+
|
133
|
+
// Ensure the items are not tabbable.
|
134
|
+
// TAB navigates out of the subject.
|
135
|
+
focusables.forEach(e => e !== defaultActiveItem && (e.tabIndex = -1));
|
136
|
+
|
137
|
+
// Make at least one thing tabbable so focus can move into the subject element
|
138
|
+
if (defaultActiveItem) {
|
139
|
+
defaultActiveItem.tabIndex = 0;
|
140
|
+
}
|
86
141
|
}
|
87
142
|
|
88
143
|
unsubscribe(data) {
|
@@ -96,10 +151,15 @@ class Navigator extends Base {
|
|
96
151
|
target.removeEventListener('mousedown', data.l2);
|
97
152
|
target.removeEventListener('click', data.l3);
|
98
153
|
target.removeEventListener('focusin', data.l4);
|
154
|
+
target.removeEventListener('focusout', data.l5);
|
99
155
|
}
|
100
156
|
}
|
101
157
|
|
158
|
+
// This is called if mutations take place within the subject element.
|
159
|
+
// We have to keep things in order if the list items change.
|
102
160
|
navigateTargetChildListChange(mutations, data) {
|
161
|
+
this.fixItemFocusability(data);
|
162
|
+
|
103
163
|
// Active item gone.
|
104
164
|
// Try to activate the item at the same index;
|
105
165
|
if (data.activeItem && !data.subject.contains(data.activeItem)) {
|
@@ -110,11 +170,33 @@ class Navigator extends Base {
|
|
110
170
|
}
|
111
171
|
|
112
172
|
navigateFocusInHandler(e, data) {
|
113
|
-
const
|
173
|
+
const
|
174
|
+
target = e.target.closest(data.selector),
|
175
|
+
{ relatedTarget } = e,
|
176
|
+
{ subject } = data;
|
114
177
|
|
115
178
|
// If our targets are focusable and recieve focus, that is a navigation.
|
116
179
|
if (target) {
|
117
180
|
this.setActiveItem(target, data);
|
181
|
+
|
182
|
+
// This was internal navigation.
|
183
|
+
// The items must be focusable, but *not* tabbable.
|
184
|
+
// So remove tabbability on the last active item
|
185
|
+
if (subject.contains(relatedTarget)) {
|
186
|
+
relatedTarget.tabIndex = -1;
|
187
|
+
}
|
188
|
+
}
|
189
|
+
}
|
190
|
+
|
191
|
+
navigateFocusOutHandler(e, data) {
|
192
|
+
const { target } = e;
|
193
|
+
|
194
|
+
// Clear active class from the item we are leaving from.
|
195
|
+
target.closest(data.selector).classList.remove(data.activeCls);
|
196
|
+
|
197
|
+
// On focusout, leave the last active item as tabbable so user can TAB back in here
|
198
|
+
if (!DomUtils.isTabbable(target)) {
|
199
|
+
target.tabIndex = 0;
|
118
200
|
}
|
119
201
|
}
|
120
202
|
|
@@ -168,44 +250,36 @@ class Navigator extends Base {
|
|
168
250
|
}
|
169
251
|
}
|
170
252
|
|
171
|
-
let { key } = keyEvent,
|
253
|
+
let { key, target } = keyEvent,
|
172
254
|
newActiveElement;
|
173
255
|
|
174
256
|
switch(key) {
|
257
|
+
// Move to the previous navigable item
|
175
258
|
case data.previousKey:
|
176
259
|
newActiveElement = me.navigateGetAdjacent(-1, data);
|
177
260
|
if (!newActiveElement && wrap) {
|
178
261
|
newActiveElement = subject.querySelector(`${data.selector}:last-of-type`);
|
179
262
|
}
|
180
263
|
break;
|
264
|
+
// Move to the next navigable item
|
181
265
|
case data.nextKey:
|
182
266
|
newActiveElement = me.navigateGetAdjacent(1, data);
|
183
267
|
if (!newActiveElement && wrap) {
|
184
268
|
newActiveElement = subject.querySelector(data.selector);
|
185
269
|
}
|
186
270
|
break;
|
271
|
+
// Move to the first navigable item
|
187
272
|
case 'Home':
|
188
273
|
newActiveElement = subject.querySelector(data.selector);
|
189
274
|
break;
|
275
|
+
// Move to the last navigable item
|
190
276
|
case 'End':
|
191
277
|
newActiveElement = subject.querySelector(`${data.selector}:last-of-type`);
|
192
278
|
break;
|
279
|
+
// Click the currently active item if necessary
|
193
280
|
case 'Enter':
|
194
|
-
if (data.activeItem) {
|
195
|
-
|
196
|
-
rect = data.activeItem.getBoundingClientRect(),
|
197
|
-
clientX = rect.x + (rect.width / 2),
|
198
|
-
clientY = rect.y + (rect.height / 2);
|
199
|
-
|
200
|
-
data.activeItem.dispatchEvent(new MouseEvent('click', {
|
201
|
-
bubbles : true,
|
202
|
-
altKey : Neo.altKeyDown,
|
203
|
-
ctrlKey : Neo.controlKeyDown,
|
204
|
-
metaKey : Neo.metaKeyDown,
|
205
|
-
shiftKey : Neo.shiftKeyDown,
|
206
|
-
clientX,
|
207
|
-
clientY
|
208
|
-
}))
|
281
|
+
if (data.activeItem && !enterActivatedTags[target.tagName]) {
|
282
|
+
this.clickItem(data.activeItem);
|
209
283
|
}
|
210
284
|
}
|
211
285
|
|
@@ -215,6 +289,30 @@ class Navigator extends Base {
|
|
215
289
|
}
|
216
290
|
}
|
217
291
|
|
292
|
+
clickItem(el) {
|
293
|
+
// The element knows how to click itself.
|
294
|
+
if (typeof el.click === 'function') {
|
295
|
+
el.click();
|
296
|
+
}
|
297
|
+
// It operates through a listenert, so needs an event firing into it.
|
298
|
+
else {
|
299
|
+
const
|
300
|
+
rect = el.getBoundingClientRect(),
|
301
|
+
clientX = rect.x + (rect.width / 2),
|
302
|
+
clientY = rect.y + (rect.height / 2);
|
303
|
+
|
304
|
+
el.dispatchEvent(new MouseEvent('click', {
|
305
|
+
bubbles : true,
|
306
|
+
altKey : Neo.altKeyDown,
|
307
|
+
ctrlKey : Neo.controlKeyDown,
|
308
|
+
metaKey : Neo.metaKeyDown,
|
309
|
+
shiftKey : Neo.shiftKeyDown,
|
310
|
+
clientX,
|
311
|
+
clientY
|
312
|
+
}));
|
313
|
+
}
|
314
|
+
}
|
315
|
+
|
218
316
|
/**
|
219
317
|
* Navigates to the passed
|
220
318
|
* @param {String|Number} newActiveElement The id of the new active element in the subject
|
@@ -232,15 +330,20 @@ class Navigator extends Base {
|
|
232
330
|
// Can navigate by index. This is useful if the active item is deleted.
|
233
331
|
// We can navigate to the same index and preserve UI stability.
|
234
332
|
if (typeof newActiveElement === 'number') {
|
235
|
-
newActiveElement = data.subject.querySelectorAll(data.selector)[newActiveElement];
|
333
|
+
newActiveElement = data.subject.querySelectorAll(data.selector)?.[newActiveElement];
|
236
334
|
}
|
237
335
|
else if (typeof newActiveElement === 'string') {
|
238
336
|
newActiveElement = DomAccess.getElement(newActiveElement);
|
239
337
|
}
|
240
338
|
|
339
|
+
// Could not do what was asked because we could not find the requested item
|
340
|
+
if (!newActiveElement) {
|
341
|
+
return;
|
342
|
+
}
|
343
|
+
|
241
344
|
// Find a focusable element which may be the item, or inside the item to draw focus to.
|
242
345
|
// For example a Chip list in which .neo-list-items contain focusable Chips.
|
243
|
-
const focusTarget =
|
346
|
+
const focusTarget = DomUtils.query(newActiveElement, DomUtils.isFocusable);
|
244
347
|
|
245
348
|
// If the item contains a focusable, we focus it and then react in navigateFocusInHandler
|
246
349
|
if (focusTarget) {
|
@@ -270,7 +373,10 @@ class Navigator extends Base {
|
|
270
373
|
block : 'nearest',
|
271
374
|
inline : 'nearest',
|
272
375
|
nehavior : 'smooth'
|
273
|
-
})
|
376
|
+
});
|
377
|
+
|
378
|
+
// Link the event source or the encapsulating element to the active item for A11Y
|
379
|
+
(data.eventSource || data.subject).setAttribute('aria-activedescendant', data.activeItem.id);
|
274
380
|
|
275
381
|
DomEvents.sendMessageToApp({
|
276
382
|
type : 'neonavigate',
|
@@ -286,7 +392,13 @@ class Navigator extends Base {
|
|
286
392
|
ctrlKey : Neo.controlKeyDown,
|
287
393
|
metaKey : Neo.metaKeyDown,
|
288
394
|
shiftKey : Neo.shiftKeyDown
|
289
|
-
})
|
395
|
+
});
|
396
|
+
|
397
|
+
// Navigation causes click if autoClick set.
|
398
|
+
// TabPanels work like this.
|
399
|
+
if (data.autoClick) {
|
400
|
+
this.clickItem(newActiveElement);
|
401
|
+
}
|
290
402
|
}
|
291
403
|
|
292
404
|
navigateGetAdjacent(direction = 1, data) {
|
package/src/manager/DomEvent.mjs
CHANGED
@@ -111,7 +111,10 @@ class DomEvent extends Base {
|
|
111
111
|
// console.log('fire', eventName, data, listeners, path);
|
112
112
|
|
113
113
|
if (Array.isArray(listeners)) {
|
114
|
-
|
114
|
+
// Stop iteration if a handler returns false
|
115
|
+
listeners.every(listener => {
|
116
|
+
let result;
|
117
|
+
|
115
118
|
if (listener && listener.fn) {
|
116
119
|
delegationTargetId = me.verifyDelegationPath(listener, data.path);
|
117
120
|
|
@@ -131,7 +134,7 @@ class DomEvent extends Base {
|
|
131
134
|
|
132
135
|
// Handler needs to know which actual target matched the delegate
|
133
136
|
data.currentTarget = delegationTargetId;
|
134
|
-
listener.fn.apply(listener.scope || globalThis, [data]);
|
137
|
+
result = listener.fn.apply(listener.scope || globalThis, [data]);
|
135
138
|
|
136
139
|
if (!listener.bubble) {
|
137
140
|
bubble = false;
|
@@ -139,6 +142,8 @@ class DomEvent extends Base {
|
|
139
142
|
}
|
140
143
|
}
|
141
144
|
}
|
145
|
+
// If a listener returns false, we stop iterating the listeners
|
146
|
+
return result !== false
|
142
147
|
});
|
143
148
|
}
|
144
149
|
}
|
@@ -153,7 +158,8 @@ class DomEvent extends Base {
|
|
153
158
|
break;
|
154
159
|
}
|
155
160
|
|
156
|
-
|
161
|
+
// Honour the Event cancelBubble property
|
162
|
+
if (!bubble || data.cancelBubble) {
|
157
163
|
break;
|
158
164
|
}
|
159
165
|
}
|
package/src/manager/Focus.mjs
CHANGED
@@ -168,7 +168,9 @@ class Focus extends CoreBase {
|
|
168
168
|
* @protected
|
169
169
|
*/
|
170
170
|
setComponentFocus(opts, containsFocus) {
|
171
|
-
let data = {
|
171
|
+
let data = {
|
172
|
+
relatedTarget : opts.data.relatedTarget
|
173
|
+
},
|
172
174
|
components = opts.componentPath.map(id => Neo.getComponent(id)),
|
173
175
|
handler;
|
174
176
|
|
package/src/util/String.mjs
CHANGED
@@ -16,7 +16,8 @@ class StringUtil extends Base {
|
|
16
16
|
'"' : '"',
|
17
17
|
'\'': ''',
|
18
18
|
'$' : '$',
|
19
|
-
'\\': '\'
|
19
|
+
'\\': '\',
|
20
|
+
'/' : '/'
|
20
21
|
}
|
21
22
|
/**
|
22
23
|
* @member {RegExp} charPattern
|
@@ -27,7 +28,7 @@ class StringUtil extends Base {
|
|
27
28
|
* @member {RegExp} entityPattern
|
28
29
|
* @static
|
29
30
|
*/
|
30
|
-
static entityPattern = /(&)|(<)|(>)|(")|(')|($)|(\)/g
|
31
|
+
static entityPattern = /(&)|(<)|(>)|(")|(')|($)|(\)|(/)/g
|
31
32
|
|
32
33
|
static config = {
|
33
34
|
/**
|
@@ -63,7 +63,7 @@ StartTest(t => {
|
|
63
63
|
|
64
64
|
await t.waitForSelector('.neo-picker-container');
|
65
65
|
|
66
|
-
t.
|
66
|
+
await t.waitFor(() => inputField.getAttribute('aria-expanded') === 'true');
|
67
67
|
|
68
68
|
// Roles correct
|
69
69
|
t.hasAttributeValue('.neo-picker-container .neo-list', 'role', 'listbox');
|
@@ -80,6 +80,8 @@ StartTest(t => {
|
|
80
80
|
|
81
81
|
t.hasAttributeValue(inputField, 'aria-activedescendant', 'neo-list-1__AL');
|
82
82
|
|
83
|
+
await t.waitFor(100);
|
84
|
+
|
83
85
|
// Select that first item.
|
84
86
|
await t.type(null, '[ENTER]');
|
85
87
|
|
@@ -100,7 +102,7 @@ StartTest(t => {
|
|
100
102
|
t.is(blurCount, 1);
|
101
103
|
});
|
102
104
|
|
103
|
-
t.
|
105
|
+
t.it('Keyboard navigation', async t => {
|
104
106
|
await setup();
|
105
107
|
const blurEl = document.createElement('input');
|
106
108
|
document.body.appendChild(blurEl);
|
@@ -128,6 +130,8 @@ StartTest(t => {
|
|
128
130
|
|
129
131
|
await t.waitForSelectorNotFound('.neo-picker-container:visible');
|
130
132
|
|
133
|
+
await t.waitFor(100);
|
134
|
+
|
131
135
|
t.is(inputField.value, 'Wyoming');
|
132
136
|
|
133
137
|
await t.type(null, '[DOWN]');
|
@@ -139,11 +143,13 @@ StartTest(t => {
|
|
139
143
|
|
140
144
|
await t.waitForSelector('.neo-list-item.neo-navigator-active-item:contains("Wisconsin")');
|
141
145
|
|
146
|
+
await t.waitFor(100);
|
147
|
+
|
142
148
|
await t.type(null, '[ENTER]');
|
143
149
|
|
144
150
|
await t.waitForSelectorNotFound('.neo-picker-container:visible');
|
145
151
|
|
146
|
-
t.
|
152
|
+
await t.waitFor(() => inputField.value === 'Wisconsin');
|
147
153
|
|
148
154
|
await t.type(null, '[DOWN]');
|
149
155
|
|
@@ -183,6 +189,8 @@ StartTest(t => {
|
|
183
189
|
// Picker Must show with Maryland activated
|
184
190
|
await t.waitForSelector('.neo-list-item.neo-navigator-active-item:contains("Maryland")');
|
185
191
|
|
192
|
+
await t.waitFor(100);
|
193
|
+
|
186
194
|
// Matches three states
|
187
195
|
t.selectorCountIs('.neo-picker-container .neo-list-item', 3);
|
188
196
|
|
@@ -192,11 +200,24 @@ StartTest(t => {
|
|
192
200
|
// Blur without selecting a value
|
193
201
|
await t.type(null, '[TAB]');
|
194
202
|
|
195
|
-
await t.waitFor(100)
|
203
|
+
await t.waitFor(100);
|
196
204
|
|
197
205
|
// Inputs must have been cleared. Both typeahead and filter.
|
198
206
|
t.isDeeply(t.query(`#${testId} input`).map(i => i.value), ['', '']);
|
199
207
|
|
200
208
|
blurEl.remove();
|
201
209
|
});
|
210
|
+
|
211
|
+
t.it('With store as data', async t => {
|
212
|
+
await setup({
|
213
|
+
labelText : 'Foo',
|
214
|
+
store : ['Foo', 'Bar', 'Bletch']
|
215
|
+
});
|
216
|
+
await t.click('.neo-field-trigger.fa-caret-down');
|
217
|
+
|
218
|
+
await t.waitForSelector('.neo-list-item:contains(Foo)');
|
219
|
+
|
220
|
+
// All data ityems represented
|
221
|
+
t.selectorCountIs('.neo-list-item', 3);
|
222
|
+
});
|
202
223
|
});
|
@@ -1 +0,0 @@
|
|
1
|
-
### todo: Adding Properties
|
@@ -1 +0,0 @@
|
|
1
|
-
### todo: Updating View State
|
@@ -1,32 +0,0 @@
|
|
1
|
-
.newwebsite-viewport {
|
2
|
-
align-items : center !important;
|
3
|
-
display : flex !important;
|
4
|
-
gap : 48px;
|
5
|
-
justify-content: center !important;
|
6
|
-
padding : 10% 15% 15% 15%;
|
7
|
-
}
|
8
|
-
|
9
|
-
.button-group {
|
10
|
-
display : flex !important;
|
11
|
-
flex-direction: row-reverse !important;
|
12
|
-
gap : 8px !important;
|
13
|
-
}
|
14
|
-
|
15
|
-
.neo-h1 {
|
16
|
-
font-size : 48px;
|
17
|
-
font-weight: 600;
|
18
|
-
text-align : center;
|
19
|
-
}
|
20
|
-
|
21
|
-
.vector {
|
22
|
-
background-image : url("../../../../../../resources/images/Neo_Vector.svg");
|
23
|
-
background-position: center center;
|
24
|
-
background-repeat : no-repeat;
|
25
|
-
background-size : contain;
|
26
|
-
height : 150px;
|
27
|
-
width : 100%;
|
28
|
-
}
|
29
|
-
|
30
|
-
.get-started-button {
|
31
|
-
cursor: not-allowed !important;
|
32
|
-
}
|