neo.mjs 6.13.0 → 6.14.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 (44) hide show
  1. package/apps/covid/neo-config.json +1 -1
  2. package/apps/portal/view/ViewportController.mjs +5 -4
  3. package/apps/portal/view/home/ContentBox.mjs +80 -0
  4. package/apps/portal/view/home/MainContainer.mjs +51 -15
  5. package/apps/portal/view/learn/ContentTreeList.mjs +10 -3
  6. package/apps/portal/view/learn/MainContainerController.mjs +37 -5
  7. package/apps/portal/view/learn/MainContainerModel.mjs +51 -7
  8. package/apps/portal/view/learn/PageContainer.mjs +21 -9
  9. package/apps/sharedcovid/neo-config.json +1 -1
  10. package/examples/form/field/select/MainContainer.mjs +1 -1
  11. package/package.json +3 -3
  12. package/resources/data/deck/learnneo/pages/2023-10-14T19-25-08-153Z.md +16 -1
  13. package/resources/data/deck/learnneo/pages/ComponentsAndContainers.md +180 -0
  14. package/resources/data/deck/learnneo/pages/Config.md +11 -4
  15. package/resources/data/deck/learnneo/pages/DescribingTheUI.md +6 -0
  16. package/resources/data/deck/learnneo/pages/Earthquakes.md +36 -9
  17. package/resources/data/deck/learnneo/pages/Events.md +55 -43
  18. package/resources/data/deck/learnneo/pages/GuideEvents.md +9 -8
  19. package/resources/data/deck/learnneo/pages/References.md +14 -7
  20. package/resources/data/deck/learnneo/pages/TodoList.md +241 -0
  21. package/resources/data/deck/learnneo/pages/WhyNeo-Quick.md +6 -11
  22. package/resources/data/deck/learnneo/tree.json +2 -0
  23. package/resources/scss/src/apps/portal/home/ContentBox.scss +26 -0
  24. package/resources/scss/src/apps/portal/home/MainContainer.scss +4 -12
  25. package/resources/scss/src/apps/portal/learn/MainContainer.scss +0 -7
  26. package/resources/scss/src/apps/portal/learn/PageContainer.scss +35 -0
  27. package/resources/scss/src/apps/portal/learn/PageSectionsPanel.scss +8 -0
  28. package/src/component/Base.mjs +16 -1
  29. package/src/controller/Application.mjs +12 -1
  30. package/src/controller/Base.mjs +15 -4
  31. package/src/controller/Component.mjs +5 -1
  32. package/src/core/Observable.mjs +62 -22
  33. package/src/form/field/Base.mjs +21 -9
  34. package/src/form/field/Select.mjs +166 -117
  35. package/src/form/field/Text.mjs +7 -10
  36. package/src/main/DomEvents.mjs +2 -1
  37. package/src/main/addon/MonacoEditor.mjs +2 -2
  38. package/src/main/addon/Navigator.mjs +27 -24
  39. package/src/selection/ListModel.mjs +13 -14
  40. package/src/selection/Model.mjs +10 -10
  41. package/src/util/HashHistory.mjs +1 -0
  42. package/src/worker/App.mjs +5 -1
  43. package/src/worker/Manager.mjs +14 -7
  44. package/test/components/files/form/field/Select.mjs +6 -4
@@ -295,7 +295,7 @@ class Navigator extends Base {
295
295
  if (typeof el.click === 'function') {
296
296
  el.click();
297
297
  }
298
- // It operates through a listenert, so needs an event firing into it.
298
+ // It operates through a listener, so needs an event firing into it.
299
299
  else {
300
300
  const
301
301
  rect = el.getBoundingClientRect(),
@@ -324,7 +324,7 @@ class Navigator extends Base {
324
324
  if (!data.subject) {
325
325
  // If subject has been unmounted, we cannot navigate
326
326
  if (!(data = DomAccess.getElement(data.id)?.$navigator)) {
327
- return;
327
+ return
328
328
  }
329
329
  }
330
330
 
@@ -344,8 +344,8 @@ class Navigator extends Base {
344
344
 
345
345
  // Scroll the target into view smoothly before we focus it without triggering a scroll
346
346
  newActiveElement.scrollIntoView({
347
- block : 'nearest',
348
- behavior : 'smooth'
347
+ behavior : 'smooth',
348
+ block : 'nearest'
349
349
  });
350
350
 
351
351
  // Find a focusable element which may be the item, or inside the item to draw focus to.
@@ -356,7 +356,7 @@ class Navigator extends Base {
356
356
  if (focusTarget) {
357
357
  focusTarget.focus({ preventScroll : true });
358
358
  }
359
- // If not, we programatically navigate there
359
+ // If not, we programmatically navigate there
360
360
  else {
361
361
  this.setActiveItem(newActiveElement, data);
362
362
  }
@@ -377,34 +377,37 @@ class Navigator extends Base {
377
377
  data.activeIndex = newActiveElement ? allItems.indexOf(newActiveElement) : -1;
378
378
 
379
379
  newActiveElement.scrollIntoView({
380
- block : 'nearest',
381
- inline : 'nearest',
382
- nehavior : 'smooth'
380
+ behavior: 'smooth',
381
+ block : 'nearest',
382
+ inline : 'nearest'
383
383
  });
384
384
 
385
385
  // Link the event source or the encapsulating element to the active item for A11Y
386
386
  (data.eventSource || data.subject).setAttribute('aria-activedescendant', data.activeItem.id);
387
387
 
388
- DomEvents.sendMessageToApp({
389
- type : 'neonavigate',
390
- target : data.id,
391
- path : [{
392
- id : data.id
393
- }],
394
- activeItem : data.activeItem.id,
395
- previousActiveItem : data.previousActiveItem?.id,
396
- activeIndex : data.activeIndex,
397
- previousActiveIndex : data.previousActiveIndex,
398
- altKey : Neo.altKeyDown,
399
- ctrlKey : Neo.controlKeyDown,
400
- metaKey : Neo.metaKeyDown,
401
- shiftKey : Neo.shiftKeyDown
402
- });
388
+ // navigating to the same element should get ignored
389
+ if (data.activeItem !== data.previousActiveItem) {
390
+ DomEvents.sendMessageToApp({
391
+ type : 'neonavigate',
392
+ target : data.id,
393
+ path : [{
394
+ id : data.id
395
+ }],
396
+ activeItem : data.activeItem.id,
397
+ previousActiveItem : data.previousActiveItem?.id,
398
+ activeIndex : data.activeIndex,
399
+ previousActiveIndex : data.previousActiveIndex,
400
+ altKey : Neo.altKeyDown,
401
+ ctrlKey : Neo.controlKeyDown,
402
+ metaKey : Neo.metaKeyDown,
403
+ shiftKey : Neo.shiftKeyDown
404
+ })
405
+ }
403
406
 
404
407
  // Navigation causes click if autoClick set.
405
408
  // TabPanels work like this.
406
409
  if (data.autoClick) {
407
- this.clickItem(newActiveElement);
410
+ this.clickItem(newActiveElement)
408
411
  }
409
412
  }
410
413
 
@@ -58,13 +58,13 @@ class ListModel extends Model {
58
58
  * @param {Object} data
59
59
  */
60
60
  onListClick({ currentTarget }) {
61
- const { view } = this;
61
+ const {view} = this;
62
62
 
63
63
  if (!view.disableSelection) {
64
64
  const record = view.store.get(view.getItemRecordId(currentTarget));
65
65
 
66
66
  if (record) {
67
- this.select(record);
67
+ this.select(record)
68
68
  }
69
69
  }
70
70
  }
@@ -74,8 +74,8 @@ class ListModel extends Model {
74
74
  */
75
75
  onListNavigate(data) {
76
76
  const
77
- { view } = this,
78
- { store } = view;
77
+ {view} = this,
78
+ {store} = view;
79
79
 
80
80
  data.record = store.getAt(Math.min(data.activeIndex, store.getCount()));
81
81
  view._focusIndex = store.indexOf(data.record); // silent update, no need to refocus
@@ -89,16 +89,16 @@ class ListModel extends Model {
89
89
  register(component) {
90
90
  super.register(component);
91
91
 
92
- let me = this,
93
- id = me.id,
94
- view = me.view;
92
+ let me = this,
93
+ {id, view} = me;
95
94
 
96
95
  view.addDomListeners([{
97
- click : me.onListClick,
96
+ click: me.onListClick,
97
+ scope: me,
98
98
 
99
99
  // Should be `.${view.itemCls}:not(.neo-disabled,.neo-list-header)`
100
100
  // TODO parse delegate selectors
101
- delegate : path => {
101
+ delegate: path => {
102
102
  for (let i = 0, { length } = path; i < length; i++) {
103
103
  const { cls } = path[i];
104
104
 
@@ -106,12 +106,11 @@ class ListModel extends Model {
106
106
  return i;
107
107
  }
108
108
  }
109
- },
110
- scope : me
109
+ }
111
110
  }, {
112
111
  neonavigate : me.onListNavigate,
113
- scope : me
114
- }])
112
+ scope : me
113
+ }]);
115
114
 
116
115
  view.keys?._keys.push(
117
116
  {fn: 'onKeyDownDown' ,key: 'Down' ,scope: id},
@@ -132,7 +131,7 @@ class ListModel extends Model {
132
131
  itemId = recordKey && view.getItemId(recordKey);
133
132
 
134
133
  if (itemId) {
135
- this.select(itemId);
134
+ this.select(itemId)
136
135
  }
137
136
  }
138
137
 
@@ -217,16 +217,16 @@ class Model extends Base {
217
217
  * @param {String} [selectedCls]
218
218
  */
219
219
  select(items, itemCollection=this.items, selectedCls) {
220
- let me = this,
221
- view = me.view,
222
- vdom = view.vdom;
220
+ let me = this,
221
+ {view} = me;
223
222
 
224
223
  // We hold vdom ids for now, so all incoming selections must be converted.
225
- items = (items = Array.isArray(items) ? items : [items]).map(item => item.isRecord ? view.getItemId(item) : Neo.isObject(item) ? item.id : item)
224
+ items = (items = Array.isArray(items) ?
225
+ items : [items]).map(item => item.isRecord ? view.getItemId(item) : Neo.isObject(item) ? item.id : item);
226
226
 
227
227
  if (!Neo.isEqual(itemCollection, items)) {
228
228
  if (me.singleSelect) {
229
- me.deselectAll(true);
229
+ me.deselectAll(true)
230
230
  }
231
231
 
232
232
  items.forEach((node, i) => {
@@ -234,22 +234,22 @@ class Model extends Base {
234
234
 
235
235
  if (node) {
236
236
  node.cls = NeoArray.add(node.cls || [], selectedCls || me.selectedCls);
237
- node['aria-selected'] = true;
237
+ node['aria-selected'] = true
238
238
  }
239
239
  });
240
240
 
241
241
  NeoArray.add(itemCollection, items);
242
242
 
243
- view[view.silentSelect ? '_vdom' : 'vdom'] = vdom;
243
+ !view.silentSelect && view.update();
244
244
 
245
245
  view.onSelect?.(items);
246
246
 
247
247
  me.fire('selectionChange', {
248
- selection : itemCollection
249
- });
248
+ selection: itemCollection
249
+ })
250
250
  }
251
251
  else {
252
- me.fire('noChange');
252
+ me.fire('noChange')
253
253
  }
254
254
  }
255
255
 
@@ -56,6 +56,7 @@ class HashHistory extends Base {
56
56
  * @param {String} data.appName
57
57
  * @param {Object} data.hash
58
58
  * @param {String} data.hashString
59
+ * @param {Number} data.windowId
59
60
  */
60
61
  push(data) {
61
62
  let me = this,
@@ -370,7 +370,11 @@ class App extends Base {
370
370
  app = module.onStart();
371
371
 
372
372
  // short delay to ensure Component Controllers are ready
373
- config.hash && setTimeout(() => HashHistory.push(config.hash), 5)
373
+ config.hash && setTimeout(() => {
374
+ HashHistory.push(config.hash);
375
+ // apps which will get created later must not use outdated hash values
376
+ delete config.hash
377
+ }, 5)
374
378
  })
375
379
  }
376
380
 
@@ -6,12 +6,11 @@ import Observable from '../core/Observable.mjs';
6
6
  import RemoteMethodAccess from './mixin/RemoteMethodAccess.mjs';
7
7
 
8
8
  const NeoConfig = Neo.config,
9
- devMode = NeoConfig.environment === 'development',
10
- windowId = new Date().getTime();
9
+ devMode = NeoConfig.environment === 'development';
11
10
 
12
11
  /**
13
12
  * The worker manager lives inside the main thread and creates the App, Data & VDom worker.
14
- * Also responsible for sending messages from the main thread to the different workers.
13
+ * Also, responsible for sending messages from the main thread to the different workers.
15
14
  * @class Neo.worker.Manager
16
15
  * @extends Neo.core.Base
17
16
  * @singleton
@@ -72,6 +71,12 @@ class Manager extends Base {
72
71
  * @protected
73
72
  */
74
73
  webWorkersEnabled: false,
74
+ /**
75
+ * Using the current timestamp as an unique window identifier
76
+ * @member {Number} windowId=new Date().getTime()
77
+ * @protected
78
+ */
79
+ windowId: new Date().getTime(),
75
80
  /**
76
81
  * Contains the fileNames for the App, Data & Vdom workers
77
82
  * @member {Object} workers
@@ -174,9 +179,10 @@ class Manager extends Base {
174
179
  * Calls createWorker for each worker inside the this.workers config.
175
180
  */
176
181
  createWorkers() {
177
- let me = this,
178
- config = Neo.clone(NeoConfig, true),
179
- hash = location.hash,
182
+ let me = this,
183
+ config = Neo.clone(NeoConfig, true),
184
+ hash = location.hash,
185
+ windowId = me.windowId,
180
186
  key, value;
181
187
 
182
188
  // remove configs which are not relevant for the workers scope
@@ -186,7 +192,8 @@ class Manager extends Base {
186
192
  if (hash) {
187
193
  config.hash = {
188
194
  hash : DomEvents.parseHash(hash.substring(1)),
189
- hashString: hash.substring(1)
195
+ hashString: hash.substring(1),
196
+ windowId : me.windowId
190
197
  }
191
198
  }
192
199
 
@@ -202,17 +202,19 @@ StartTest(t => {
202
202
 
203
203
  await t.waitFor(100);
204
204
 
205
- // Inputs must have been cleared. Both typeahead and filter.
206
- t.isDeeply(t.query(`#${testId} input`).map(i => i.value), ['', '']);
205
+ // typeahead input must be cleared, forceSelection must pick the closest value onFocusLeave
206
+ // todo: add another test without forceSelection => Inputs must have been cleared. Both typeahead and filter.
207
+ t.isDeeply(t.query(`#${testId} input`).map(i => i.value), ['', 'Marshall Islands']);
207
208
 
208
209
  blurEl.remove();
209
210
  });
210
211
 
211
212
  t.it('With store as data', async t => {
212
213
  await setup({
213
- labelText : 'Foo',
214
- store : ['Foo', 'Bar', 'Bletch']
214
+ labelText: 'Foo',
215
+ store : ['Foo', 'Bar', 'Bletch']
215
216
  });
217
+
216
218
  await t.click('.neo-field-trigger.fa-caret-down');
217
219
 
218
220
  await t.waitForSelector('.neo-list-item:contains(Foo)');