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.
Files changed (37) hide show
  1. package/apps/ServiceWorker.mjs +2 -2
  2. package/apps/portal/view/learn/LivePreview.mjs +4 -2
  3. package/buildScripts/createAppMinimal.mjs +0 -30
  4. package/examples/ServiceWorker.mjs +2 -2
  5. package/examples/button/base/neo-config.json +1 -2
  6. package/examples/dialog/MainContainer.mjs +2 -0
  7. package/package.json +4 -4
  8. package/resources/data/deck/learnneo/p/2023-10-14T19-25-08-153Z.md +1 -0
  9. package/resources/data/deck/learnneo/p/ComponentModels.md +27 -13
  10. package/resources/data/deck/learnneo/p/DescribingTheUI.md +1 -1
  11. package/resources/data/deck/learnneo/p/Earthquakes.md +1226 -31
  12. package/resources/data/deck/learnneo/p/Events.md +11 -0
  13. package/resources/data/deck/learnneo/p/GuideEvents.md +153 -0
  14. package/resources/data/deck/learnneo/t.json +3 -1
  15. package/resources/scss/src/apps/portal/learn/ContentView.scss +1 -0
  16. package/resources/scss/src/list/Base.scss +1 -45
  17. package/src/DefaultConfig.mjs +2 -2
  18. package/src/component/Base.mjs +32 -0
  19. package/src/data/RecordFactory.mjs +0 -3
  20. package/src/dialog/Base.mjs +14 -7
  21. package/src/form/field/Picker.mjs +9 -1
  22. package/src/form/field/Select.mjs +23 -4
  23. package/src/form/field/Text.mjs +4 -2
  24. package/src/layout/Flexbox.mjs +31 -23
  25. package/src/layout/HBox.mjs +1 -1
  26. package/src/layout/VBox.mjs +1 -1
  27. package/src/main/DomAccess.mjs +11 -3
  28. package/src/main/DomUtils.mjs +26 -3
  29. package/src/main/addon/Navigator.mjs +139 -27
  30. package/src/manager/DomEvent.mjs +9 -3
  31. package/src/manager/Focus.mjs +3 -1
  32. package/src/util/String.mjs +3 -2
  33. package/test/components/files/form/field/Select.mjs +25 -4
  34. package/resources/data/deck/learnneo/p/AddingProperties.md +0 -1
  35. package/resources/data/deck/learnneo/p/ComponentState.md +0 -1
  36. package/resources/scss/src/apps/newwebsite/Viewport.scss +0 -32
  37. package/resources/scss/theme-neo-light/design-tokens/Components.scss +0 -3
@@ -29,7 +29,7 @@ class HBox extends Flexbox {
29
29
  */
30
30
  applyChildAttributes(item) {
31
31
  // Do not apply flex if fixed width
32
- !item.width && super.applyChildAttributes(item)
32
+ !item.width && super.applyChildAttributes(item);
33
33
  }
34
34
  }
35
35
 
@@ -29,7 +29,7 @@ class VBox extends Flexbox {
29
29
  */
30
30
  applyChildAttributes(item) {
31
31
  // Do not apply flex if fixed height
32
- !item.height && super.applyChildAttributes(item)
32
+ !item.height && super.applyChildAttributes(item);
33
33
  }
34
34
  }
35
35
 
@@ -360,10 +360,18 @@ class DomAccess extends Base {
360
360
  let node = this.getElement(data.id);
361
361
 
362
362
  if (node) {
363
- node.focus();
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
- if (Neo.isNumber(node.selectionStart)) {
366
- node.selectionStart = node.selectionEnd = node.value.length;
372
+ if (Neo.isNumber(node.selectionStart)) {
373
+ node.selectionStart = node.selectionEnd = node.value.length;
374
+ }
367
375
  }
368
376
  }
369
377
 
@@ -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') == -1;
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
- if (!e.offsetParent || style.getPropertyValue('visibility') === 'hidden') {
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.compcompareDocumentPosition(el) & Node.DOCUMENT_POSITION_CONTAINED_BY) &&
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', data.l2 = e => me.navigateMouseDownHandler(e, data));
84
- subject.addEventListener('click', data.l3 = e => me.navigateClickHandler(e, data));
85
- subject.addEventListener('focusin', data.l4 = e => me.navigateFocusInHandler(e, data));
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 target = e.target.closest(data.selector);
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
- const
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 = [newActiveElement, ...newActiveElement.querySelectorAll('*')].find(DomUtils.isFocusable);
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) {
@@ -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
- listeners.forEach(listener => {
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
- if (!bubble) {
161
+ // Honour the Event cancelBubble property
162
+ if (!bubble || data.cancelBubble) {
157
163
  break;
158
164
  }
159
165
  }
@@ -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
 
@@ -16,7 +16,8 @@ class StringUtil extends Base {
16
16
  '"' : '&quot;',
17
17
  '\'': '&apos;',
18
18
  '$' : '&dollar;',
19
- '\\': '&bsol;'
19
+ '\\': '&bsol;',
20
+ '/' : '&sol;'
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 = /(&amp;)|(&lt;)|(&gt;)|(&quot;)|(&apos;)|(&dollar;)|(&bsol;)/g
31
+ static entityPattern = /(&amp;)|(&lt;)|(&gt;)|(&quot;)|(&apos;)|(&dollar;)|(&bsol;)|(&sol;)/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.hasAttributeValue(inputField, 'aria-expanded', 'true');
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.iit('Keyboard navigation', async 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.is(inputField.value, 'Wisconsin');
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
- }
@@ -1,3 +0,0 @@
1
- :root .neo-theme-neo-light {
2
- --cmp-button-bg : blue;
3
- }