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
@@ -81,6 +81,17 @@ class Base extends Component {
81
81
  * @member {String|null} formGroupString=null
82
82
  */
83
83
  formGroupString = null
84
+ /**
85
+ * Base implementation to check if the fields value has changed.
86
+ * Can get overridden in superclasses.
87
+ * @returns {Boolean}
88
+ */
89
+ get isDirty() {
90
+ let originalValue = this.originalConfig.value,
91
+ value = this.value;
92
+
93
+ return value !== originalValue && Neo.isEmpty(value) !== Neo.isEmpty(originalValue)
94
+ }
84
95
  /**
85
96
  * An internal cache for formGroup(s) and the field name
86
97
  * @member {String|null} path=null
@@ -96,7 +107,7 @@ class Base extends Component {
96
107
  let cls = this.cls;
97
108
 
98
109
  NeoArray.toggle(cls, 'neo-is-touched', value);
99
- this.cls = cls;
110
+ this.cls = cls
100
111
  }
101
112
 
102
113
  /**
@@ -118,7 +129,7 @@ class Base extends Component {
118
129
  */
119
130
  afterSetRole(value, oldValue) {
120
131
  this.getInputEl().role = value;
121
- this.update();
132
+ this.update()
122
133
  }
123
134
 
124
135
  /**
@@ -142,7 +153,7 @@ class Base extends Component {
142
153
  returnValue;
143
154
 
144
155
  if (me.formGroupString) {
145
- return me.formGroupString;
156
+ return me.formGroupString
146
157
  }
147
158
 
148
159
  value && group.push(value);
@@ -178,9 +189,9 @@ class Base extends Component {
178
189
  let me = this;
179
190
 
180
191
  if (value || Neo.isBoolean(value) || value === 0) {
181
- me.getInputEl()[key] = value;
192
+ me.getInputEl()[key] = value
182
193
  } else {
183
- delete me.getInputEl()[key];
194
+ delete me.getInputEl()[key]
184
195
  }
185
196
 
186
197
  !silent && me.update()
@@ -295,14 +306,14 @@ class Base extends Component {
295
306
  * @returns {*}
296
307
  */
297
308
  getValue() {
298
- return this.value;
309
+ return this.value
299
310
  }
300
311
 
301
312
  /**
302
313
  * @returns {Boolean}
303
314
  */
304
315
  isValid() {
305
- return true;
316
+ return true
306
317
  }
307
318
 
308
319
  /**
@@ -350,7 +361,8 @@ class Base extends Component {
350
361
  * @param {*} value=null
351
362
  */
352
363
  reset(value=null) {
353
- this.value = value;
364
+ this.originalConfig.value = value;
365
+ this.value = value
354
366
  }
355
367
 
356
368
  /**
@@ -359,7 +371,7 @@ class Base extends Component {
359
371
  * @returns {Boolean} Returns true in case there are no client-side errors
360
372
  */
361
373
  validate(silent=true) {
362
- return true;
374
+ return true
363
375
  }
364
376
  }
365
377
 
@@ -88,11 +88,6 @@ class Select extends Picker {
88
88
  * @member {Number|null} pickerHeight=null
89
89
  */
90
90
  pickerHeight: null,
91
- /**
92
- * @member {Object} record_=null
93
- * @protected
94
- */
95
- record_: null,
96
91
  /**
97
92
  * @member {String|null} role='combobox'
98
93
  */
@@ -126,6 +121,17 @@ class Select extends Picker {
126
121
  valueField: 'id'
127
122
  }
128
123
 
124
+ /**
125
+ * Internal flag to store the value, in case it was set before the store was loaded
126
+ * @member {Number|String} preStoreLoadValue=null
127
+ */
128
+ preStoreLoadValue = null
129
+ /**
130
+ * Internal flag to not show a picker when non user-based input value changes happen
131
+ * @member {Boolean} programmaticValueChange=false
132
+ */
133
+ programmaticValueChange = false
134
+
129
135
  /**
130
136
  * @param {Object} config
131
137
  */
@@ -135,28 +141,20 @@ class Select extends Picker {
135
141
  let me = this;
136
142
 
137
143
  // Create buffered function to respond to input field mutation
138
- me.filterOnInput = buffer(me.filterOnInput, me, me.filterDelay);
144
+ //me.filterOnInput = buffer(me.filterOnInput, me, me.filterDelay);
139
145
 
140
146
  me.typeAhead && me.updateTypeAhead()
141
147
  }
142
148
 
143
149
  /**
144
- * Triggered after the record config got changed
145
- * @param {Object} value
146
- * @param {Object} oldValue
150
+ * Triggered after the inputValue config got changed
151
+ * @param {String|null} value
152
+ * @param {String|null} oldValue
147
153
  * @protected
148
154
  */
149
- afterSetRecord(value, oldValue) {
150
- if (this._picker?.isVisible) {
151
- let selectionModel = this.list?.selectionModel;
152
-
153
- if (value) {
154
- oldValue && selectionModel?.deselect(oldValue);
155
- selectionModel?.select(value)
156
- } else {
157
- selectionModel.deselectAll()
158
- }
159
- }
155
+ afterSetInputValue(value, oldValue) {
156
+ super.afterSetInputValue(value, oldValue);
157
+ this.updateTypeAheadValue(value);
160
158
  }
161
159
 
162
160
  /**
@@ -177,7 +175,7 @@ class Select extends Picker {
177
175
  includeEmptyValues: true,
178
176
  operator : me.filterOperator,
179
177
  property : me.displayField,
180
- value : value.get(me.value)?.[me.displayField] || me.value
178
+ value : value?.[me.displayField] || null
181
179
  });
182
180
 
183
181
  value.filters = filters
@@ -201,6 +199,31 @@ class Select extends Picker {
201
199
  this.rendered && this.updateTypeAhead()
202
200
  }
203
201
 
202
+ /**
203
+ * Triggered after the value config got changed
204
+ * @param {Object} value
205
+ * @param {Object} oldValue
206
+ * @protected
207
+ */
208
+ afterSetValue(value, oldValue) {
209
+ super.afterSetValue(value, oldValue);
210
+
211
+ let me = this;
212
+
213
+ me.programmaticValueChange = false;
214
+
215
+ if (me._picker?.isVisible) {
216
+ let selectionModel = me.list?.selectionModel;
217
+
218
+ if (value) {
219
+ oldValue && selectionModel?.deselect(oldValue);
220
+ selectionModel?.select(value)
221
+ } else {
222
+ selectionModel.deselectAll()
223
+ }
224
+ }
225
+ }
226
+
204
227
  /**
205
228
  * Triggered before the listConfig config gets changed.
206
229
  * @param {Object} value
@@ -236,11 +259,12 @@ class Select extends Picker {
236
259
  v = {
237
260
  [valueField] : v,
238
261
  [displayField] : v
239
- };
262
+ }
240
263
  }
241
- return v;
264
+
265
+ return v
242
266
  })
243
- };
267
+ }
244
268
  }
245
269
 
246
270
  // to reduce boilerplate code, a store config object without a defined model should default
@@ -269,9 +293,9 @@ class Select extends Picker {
269
293
 
270
294
  /**
271
295
  * Triggered before the value config gets changed.
272
- * @param {Number|String|null} value
273
- * @param {Number|String|null} oldValue
274
- * @returns {Number|String|null}
296
+ * @param {Number|Object|String} value
297
+ * @param {Number|Object|String} oldValue
298
+ * @returns {Number|Object|String}
275
299
  * @protected
276
300
  */
277
301
  beforeSetValue(value, oldValue) {
@@ -280,21 +304,31 @@ class Select extends Picker {
280
304
  store = me.store,
281
305
  record;
282
306
 
307
+ me.programmaticValueChange = true;
308
+
309
+ // getting a record, nothing to do
283
310
  if (Neo.isObject(value)) {
284
- me.record = value;
285
- return value[displayField];
286
- } else {
311
+ return value
312
+ }
313
+
314
+ if (value === null) {
315
+ return null
316
+ }
317
+
318
+ // we can only match record ids or display values in case the store is loaded
319
+ if (store.getCount() > 0) {
287
320
  record = store.isFiltered() ? store.allItems.get(value) : store.get(value);
288
321
 
289
322
  if (record) {
290
- me.record = record;
291
- return record[displayField];
323
+ return record
292
324
  }
293
- }
294
-
295
- me.record = store.find(displayField, value)[0] || null;
296
325
 
297
- return value
326
+ return store.find(displayField, value)[0] || null
327
+ } else {
328
+ // store not loaded yet
329
+ me.preStoreLoadValue = value;
330
+ return null;
331
+ }
298
332
  }
299
333
 
300
334
  /**
@@ -349,10 +383,8 @@ class Select extends Picker {
349
383
  let me = this,
350
384
  store = me.store,
351
385
  filter = store.getFilter(me.displayField),
352
- {
353
- picker,
354
- record
355
- } = me;
386
+ picker = me.picker,
387
+ record = me.value;
356
388
 
357
389
  if (filter) {
358
390
  filter.value = value
@@ -387,11 +419,11 @@ class Select extends Picker {
387
419
  }
388
420
 
389
421
  /**
390
- * @param {Object} data
422
+ * @param {String} value
391
423
  */
392
- filterOnInput(data) {
393
- if (data.value) {
394
- this.doFilter(data.value)
424
+ filterOnInput(value) {
425
+ if (value) {
426
+ this.doFilter(value)
395
427
  } else {
396
428
  this.picker?.hide()
397
429
  }
@@ -406,19 +438,10 @@ class Select extends Picker {
406
438
  fireChangeEvent(value, oldValue) {
407
439
  let me = this,
408
440
  FormContainer = Neo.form?.Container,
409
- record = me.record,
410
- oldRecord, params;
441
+ params;
411
442
 
412
- if (!(me.forceSelection && !record)) {
413
- oldRecord = me.store.get(oldValue) || null;
414
-
415
- params = {
416
- component: me,
417
- oldRecord,
418
- oldValue,
419
- record,
420
- value
421
- };
443
+ if (!(me.forceSelection && !value)) {
444
+ params = {component: me, oldValue, value};
422
445
 
423
446
  me.fire('change', params);
424
447
 
@@ -463,7 +486,7 @@ class Select extends Picker {
463
486
  getValue() {
464
487
  let me = this;
465
488
 
466
- return me.record?.[me.valueField] || me.value
489
+ return me.value?.[me.valueField] || me.emptyValue
467
490
  }
468
491
 
469
492
  /**
@@ -487,24 +510,15 @@ class Select extends Picker {
487
510
  onFocusLeave(data) {
488
511
  let me = this;
489
512
 
490
- console.log(me.forceSelection, me.record, me.activeRecordId);
491
-
492
- if (me.forceSelection && !me.record) {
493
- me.value = me.store.get(me.activeRecordId)
513
+ if (me.forceSelection && !me.value) {
514
+ me.programmaticValueChange = true;
515
+ me.value = me.store.get(me.activeRecordId);
516
+ me.programmaticValueChange = false;
494
517
  }
495
518
 
496
- super.onFocusLeave(data)
497
- }
519
+ me.updateTypeAheadValue(null);
498
520
 
499
- /**
500
- * @param {Object} data
501
- * @protected
502
- */
503
- onInputValueChange(data) {
504
- // We do not call super here. The value of the Select is *not* connected to the value
505
- // typed into the input area. The input area is just a filter value to filter the list.
506
- this.lastManualInput = data.value
507
- this.filterOnInput(data);
521
+ super.onFocusLeave(data)
508
522
  }
509
523
 
510
524
  /**
@@ -530,27 +544,30 @@ class Select extends Picker {
530
544
  * @param {Object[]} selectionChangeEvent.selection
531
545
  * @protected
532
546
  */
533
- onListItemSelectionChange({ selection }) {
547
+ async onListItemSelectionChange({ selection }) {
534
548
  if (selection?.length) {
535
549
  const
536
- me = this,
537
- oldValue = me.value,
538
- selected = selection[0],
539
- record = typeof selected === 'string' ? me.store.get(me.list.getItemRecordId(selected)) : selected,
540
- value = record[me.displayField];
550
+ me = this,
551
+ selected = selection[0],
552
+ record = typeof selected === 'string' ? me.store.get(me.list.getItemRecordId(selected)) : selected;
541
553
 
542
- me.hidePicker();
543
554
  me.hintRecordId = null;
544
- me.record = record;
545
- me._value = value;
546
- me.getInputHintEl().value = null;
547
555
 
548
- me.afterSetValue(value, oldValue, true); // prevent the list from getting filtered
556
+ me.updateTypeAheadValue(null, true);
557
+
558
+ me.preventFiltering = true;
559
+ me.value = record;
560
+ me.preventFiltering = false;
549
561
 
550
562
  me.fire('select', {
551
- record,
552
- value
553
- })
563
+ value: record
564
+ });
565
+
566
+ // Short delay to let selection DOM updates get applied.
567
+ // Alternatively, we could hide the picker before the selection happen and limit updates to the vdom.
568
+ //await me.timeout(20);
569
+
570
+ await me.hidePicker()
554
571
  }
555
572
  }
556
573
 
@@ -559,14 +576,16 @@ class Select extends Picker {
559
576
  * For example clicking on already selected list item.
560
577
  */
561
578
  onListItemSelectionNoChange() {
562
- this.hidePicker();
579
+ this.hidePicker()
563
580
  }
564
581
 
565
582
  /**
566
583
  * @param {Object} record
567
584
  * @protected
568
585
  */
569
- onListItemNavigate({ activeItem, activeIndex }) {
586
+ onListItemNavigate(record) {
587
+ let {activeItem, activeIndex} = record;
588
+
570
589
  if (activeIndex >= 0) {
571
590
  const
572
591
  me = this,
@@ -587,11 +606,13 @@ class Select extends Picker {
587
606
  const inputEl = this.getInputEl();
588
607
 
589
608
  super.onPickerHiddenChange(...arguments);
609
+
590
610
  if (value) {
591
- inputEl['aria-activedescendant'] = '';
611
+ inputEl['aria-activedescendant'] = ''
592
612
  }
613
+
593
614
  inputEl['aria-expanded'] = !value;
594
- this.update();
615
+ this.update()
595
616
  }
596
617
 
597
618
  /**
@@ -602,8 +623,7 @@ class Select extends Picker {
602
623
 
603
624
  if (me.picker?.isVisible) {
604
625
  me.picker.hidden = true
605
- }
606
- else if (!me.readOnly && !me.disabled) {
626
+ } else if (!me.disabled && !me.readOnly) {
607
627
  me.doFilter(null)
608
628
  }
609
629
  }
@@ -614,10 +634,10 @@ class Select extends Picker {
614
634
  */
615
635
  onStoreLoad(items) {
616
636
  let me = this,
617
- value = me.value;
637
+ value = me.preStoreLoadValue;
618
638
 
619
- if (value) {
620
- me._value = null; // silent update
639
+ if (value !== null) {
640
+ me._value = undefined; // silent update
621
641
  me.value = value
622
642
  }
623
643
  }
@@ -645,13 +665,28 @@ class Select extends Picker {
645
665
 
646
666
  if (!Neo.isNumber(index)) {
647
667
  if (me.activeRecordId) {
648
- index = me.store.indexOfKey(me.activeRecordId);
668
+ index = me.store.indexOfKey(me.activeRecordId)
649
669
  } else {
650
- index = 0;
670
+ index = 0
651
671
  }
652
672
  }
653
673
 
654
- me.list.selectItem(index);
674
+ me.list.selectItem(index)
675
+ }
676
+
677
+ /**
678
+ * Override this method as needed inside class extensions.
679
+ * @param {*} value
680
+ * @protected
681
+ */
682
+ updateInputValueFromValue(value) {
683
+ let inputValue = null;
684
+
685
+ if (Neo.isObject(value)) {
686
+ inputValue = value[this.displayField]
687
+ }
688
+
689
+ this.inputValue = inputValue
655
690
  }
656
691
 
657
692
  /**
@@ -690,31 +725,45 @@ class Select extends Picker {
690
725
  * @protected
691
726
  */
692
727
  updateTypeAheadValue(value=this.lastManualInput, silent=false) {
693
- let me = this,
694
- match = false,
695
- {
696
- store,
697
- displayField
698
- }
699
- = me,
700
- inputHintEl = me.getInputHintEl();
728
+ let me = this,
729
+ match = false,
730
+ inputHintEl = me.getInputHintEl(),
731
+ {displayField, store} = me;
701
732
 
702
- if (!me.record && value?.length > 0) {
703
- const search = value.toLocaleLowerCase();
704
- match = store.items.find(r => r[displayField]?.toLowerCase?.()?.startsWith(search));
733
+ if (me.typeAhead) {
734
+ if (!me.value && value?.length > 0) {
735
+ const search = value.toLocaleLowerCase();
736
+ match = store.items.find(r => r[displayField]?.toLowerCase?.()?.startsWith(search));
737
+
738
+ if (match && inputHintEl) {
739
+ inputHintEl.value = value + match[displayField].substr(value.length);
740
+ me.activeRecord = match;
741
+ me.activeRecordId = match[store.keyProperty || store.model.keyProperty]
742
+ }
743
+ }
705
744
 
706
- if (match && inputHintEl) {
707
- inputHintEl.value = value + match[displayField].substr(value.length);
708
- me.activeRecord = match;
709
- me.activeRecordId = match[store.keyProperty || store.model.keyProperty]
745
+ if (!match && inputHintEl) {
746
+ inputHintEl.value = me.activeRecord = me.activeRecordId = null;
710
747
  }
711
- }
712
748
 
713
- if (!match && inputHintEl) {
714
- inputHintEl.value = me.activeRecord = me.activeRecordId = null;
749
+ !silent && me.update()
715
750
  }
751
+ }
752
+ /**
753
+ * @param {String} inputValue
754
+ * @protected
755
+ */
756
+ updateValueFromInputValue(inputValue) {
757
+ let me = this;
716
758
 
717
- !silent && me.update()
759
+ me.lastManualInput = inputValue;
760
+
761
+ if (!me.programmaticValueChange) {
762
+ // changing the input => silent record reset
763
+ me._value = null;
764
+
765
+ me.filterOnInput(inputValue)
766
+ }
718
767
  }
719
768
  }
720
769
 
@@ -496,7 +496,7 @@ class Text extends Base {
496
496
  NeoArray.toggle(cls, 'neo-has-content', me.hasContent());
497
497
  me.cls = cls;
498
498
 
499
- me.value = me.updateValueFromInputValue(value)
499
+ me.updateValueFromInputValue(value)
500
500
  }
501
501
 
502
502
  /**
@@ -866,18 +866,17 @@ class Text extends Base {
866
866
  * @protected
867
867
  */
868
868
  afterSetValue(value, oldValue) {
869
- let me = this,
870
- originalValue = me.originalConfig.value,
871
- isDirty = value !== originalValue && Neo.isEmpty(value) !== Neo.isEmpty(originalValue),
869
+ let me = this,
872
870
  cls;
873
871
 
874
872
  me.silentVdomUpdate = true;
875
- me.inputValue = me.updateInputValueFromValue(value);
873
+
874
+ me.updateInputValueFromValue(value);
876
875
 
877
876
  me.validate(); // silent
878
877
 
879
878
  cls = me.cls;
880
- NeoArray.toggle(cls, 'neo-is-dirty', isDirty);
879
+ NeoArray.toggle(cls, 'neo-is-dirty', me.isDirty);
881
880
  me.cls = cls;
882
881
 
883
882
  me.silentVdomUpdate = false;
@@ -1505,11 +1504,10 @@ class Text extends Base {
1505
1504
  /**
1506
1505
  * Override this method as needed inside class extensions.
1507
1506
  * @param {*} value
1508
- * @returns {String}
1509
1507
  * @protected
1510
1508
  */
1511
1509
  updateInputValueFromValue(value) {
1512
- return value
1510
+ this.inputValue = value
1513
1511
  }
1514
1512
 
1515
1513
  /**
@@ -1562,11 +1560,10 @@ class Text extends Base {
1562
1560
  /**
1563
1561
  * Override this method as needed inside class extensions.
1564
1562
  * @param {String} inputValue
1565
- * @returns {*}
1566
1563
  * @protected
1567
1564
  */
1568
1565
  updateValueFromInputValue(inputValue) {
1569
- return inputValue
1566
+ this.value = inputValue
1570
1567
  }
1571
1568
 
1572
1569
  /**
@@ -513,7 +513,8 @@ class DomEvents extends Base {
513
513
  data : {
514
514
  appNames: manager.appNames,
515
515
  hash : this.parseHash(hashString),
516
- hashString
516
+ hashString,
517
+ windowId: manager.windowId
517
518
  }
518
519
  })
519
520
  }
@@ -17,9 +17,9 @@ class MonacoEditor extends Base {
17
17
  */
18
18
  className: 'Neo.main.addon.MonacoEditor',
19
19
  /**
20
- * @member {String} libraryBasePath='../../../../node_modules/monaco-editor/min/vs'
20
+ * @member {String} libraryBasePath='../../node_modules/monaco-editor/min/vs'
21
21
  */
22
- libraryBasePath: '../../../../node_modules/monaco-editor/min/vs',
22
+ libraryBasePath: '../../node_modules/monaco-editor/min/vs',
23
23
  /**
24
24
  * Remote method access for other workers
25
25
  * @member {Object} remote