neo.mjs 6.11.0 → 6.12.1

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.
@@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase {
20
20
  */
21
21
  singleton: true,
22
22
  /**
23
- * @member {String} version='6.11.0'
23
+ * @member {String} version='6.12.1'
24
24
  */
25
- version: '6.11.0'
25
+ version: '6.12.1'
26
26
  }
27
27
 
28
28
  /**
@@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase {
20
20
  */
21
21
  singleton: true,
22
22
  /**
23
- * @member {String} version='6.11.0'
23
+ * @member {String} version='6.12.1'
24
24
  */
25
- version: '6.11.0'
25
+ version: '6.12.1'
26
26
  }
27
27
 
28
28
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neo.mjs",
3
- "version": "6.11.0",
3
+ "version": "6.12.1",
4
4
  "description": "The webworkers driven UI framework",
5
5
  "type": "module",
6
6
  "repository": {
@@ -62,7 +62,7 @@
62
62
  "url": "^0.11.3",
63
63
  "webpack": "^5.90.1",
64
64
  "webpack-cli": "^5.1.4",
65
- "webpack-dev-server": "4.15.1",
65
+ "webpack-dev-server": "5.0.1",
66
66
  "webpack-hook-plugin": "^1.0.7",
67
67
  "webpack-node-externals": "^3.0.0"
68
68
  },
@@ -236,12 +236,12 @@ const DefaultConfig = {
236
236
  useVdomWorker: true,
237
237
  /**
238
238
  * buildScripts/injectPackageVersion.mjs will update this value
239
- * @default '6.11.0'
239
+ * @default '6.12.1'
240
240
  * @memberOf! module:Neo
241
241
  * @name config.version
242
242
  * @type String
243
243
  */
244
- version: '6.11.0'
244
+ version: '6.12.1'
245
245
  };
246
246
 
247
247
  Object.assign(DefaultConfig, {
package/src/Main.mjs CHANGED
@@ -209,7 +209,7 @@ class Main extends core.Base {
209
209
  module = await import(`./main/addon/${name}.mjs`)
210
210
  }
211
211
 
212
- this.addon[module.default.constructor.name] = module.default;
212
+ this.registerAddon(module.default);
213
213
 
214
214
  return true
215
215
  }
@@ -254,7 +254,7 @@ class Main extends core.Base {
254
254
  me.addon = {};
255
255
 
256
256
  modules.forEach(module => {
257
- me.addon[module.default.constructor.name] = module.default;
257
+ me.registerAddon(module.default)
258
258
  });
259
259
 
260
260
  WorkerManager.onWorkerConstructed({
@@ -370,6 +370,24 @@ class Main extends core.Base {
370
370
  window.location.href = data.url;
371
371
  }
372
372
 
373
+ /**
374
+ * Helper method to register main thread addons
375
+ * @param {Neo.core.Base} addon Can either be a neo class or instance
376
+ */
377
+ registerAddon(addon) {
378
+ if (Neo.typeOf(addon) === 'NeoClass') {
379
+ // Addons could get imported multiple times. Ensure to only create an instance once.
380
+ if (Neo.typeOf(Neo.ns(addon.prototype.className)) !== 'NeoInstance') {
381
+ addon = Neo.create(addon)
382
+ }
383
+
384
+ // Main thread addons need to get registered as singletons inside the neo namespace
385
+ Neo.applyToGlobalNs(addon)
386
+ }
387
+
388
+ this.addon[addon.constructor.name] = addon;
389
+ }
390
+
373
391
  /**
374
392
  * Triggers the different DOM operation queues
375
393
  * @protected
@@ -801,12 +801,6 @@ class Base extends CoreBase {
801
801
  }
802
802
  }
803
803
 
804
- revertFocus() {
805
- if (this.containsFocus && this.focusEnterData?.relatedTarget) {
806
- Neo.getComponent(this.focusEnterData.relatedTarget.id)?.focus();
807
- }
808
- }
809
-
810
804
  /**
811
805
  * Triggered after the reference config got changed
812
806
  * @param {String|null} value
@@ -1838,13 +1832,19 @@ class Base extends CoreBase {
1838
1832
  this.keys?.register(this)
1839
1833
  }
1840
1834
 
1835
+ /**
1836
+ * @param {Object} data
1837
+ */
1841
1838
  onFocusEnter(data) {
1842
1839
  // If we are hidden, or unmounted while we still contain focus, we have to revert
1843
1840
  // focus to where it came from if possible
1844
1841
  this.focusEnterData = data;
1845
1842
  }
1846
1843
 
1847
- onFocusLeave() {
1844
+ /**
1845
+ * @param {Object} data
1846
+ */
1847
+ onFocusLeave(data) {
1848
1848
  this.focusEnterData = null;
1849
1849
  }
1850
1850
 
@@ -2067,6 +2067,17 @@ class Base extends CoreBase {
2067
2067
  }
2068
2068
  }
2069
2069
 
2070
+ /**
2071
+ *
2072
+ */
2073
+ revertFocus() {
2074
+ let relatedTarget = this.focusEnterData?.relatedTarget;
2075
+
2076
+ if (this.containsFocus && relatedTarget) {
2077
+ Neo.getComponent(relatedTarget.id)?.focus()
2078
+ }
2079
+ }
2080
+
2070
2081
  /**
2071
2082
  * Change multiple configs at once, ensuring that all afterSet methods get all new assigned values
2072
2083
  * @param {Object} values={}
package/src/core/Base.mjs CHANGED
@@ -370,7 +370,7 @@ class Base {
370
370
  currentWorker = Neo.currentWorker,
371
371
  listenerId;
372
372
 
373
- if (!me.singleton) {
373
+ if (!me.singleton && !me.isMainThreadAddon) {
374
374
  throw new Error('Remote method access is only functional for Singleton classes ' + className)
375
375
  }
376
376
 
@@ -32,6 +32,10 @@ class Select extends Picker {
32
32
  * @protected
33
33
  */
34
34
  ntype: 'selectfield',
35
+ /**
36
+ * @member {String|Number|null} activeRecordId=null
37
+ */
38
+ activeRecordId: null,
35
39
  /**
36
40
  * @member {String[]} baseCls=['neo-selectfield','neo-pickerfield','neo-textfield']
37
41
  */
@@ -40,6 +44,12 @@ class Select extends Picker {
40
44
  * @member {String} displayField='name'
41
45
  */
42
46
  displayField: 'name',
47
+ /**
48
+ * The millisecond time to delay between input field mutation and applying the input field's
49
+ * new value to the filter
50
+ * @member {Number} filterDelay=300
51
+ */
52
+ filterDelay : 300,
43
53
  /**
44
54
  * @member {String} filterOperator_='like'
45
55
  */
@@ -50,10 +60,6 @@ class Select extends Picker {
50
60
  * @member {Boolean} forceSelection=false
51
61
  */
52
62
  forceSelection: false,
53
- /**
54
- * @member {String|Number|null} activeRecordId=null
55
- */
56
- activeRecordId: null,
57
63
  /**
58
64
  * Additional used keys for the selection model
59
65
  * @member {Object} keys
@@ -116,13 +122,7 @@ class Select extends Picker {
116
122
  * which you want to submit instead
117
123
  * @member {Number|String} valueField='id'
118
124
  */
119
- valueField: 'id',
120
- /**
121
- * The millisecond time to delay between input field mutation and applying the input field's
122
- * new value to the filter
123
- * @member {Number} filterDelay=300
124
- */
125
- filterDelay : 300
125
+ valueField: 'id'
126
126
  }
127
127
 
128
128
  /**
@@ -143,7 +143,7 @@ class Select extends Picker {
143
143
  * @param {Object} oldValue
144
144
  * @protected
145
145
  */
146
- afterSetRecord(value) {
146
+ afterSetRecord(value, oldValue) {
147
147
  if (this._picker?.isVisible) {
148
148
  let me = this,
149
149
  selectionModel = me.list?.selectionModel;
@@ -252,7 +252,7 @@ class Select extends Picker {
252
252
  }
253
253
  }
254
254
 
255
- return ClassSystemUtil.beforeSetInstance(value, Store);
255
+ return ClassSystemUtil.beforeSetInstance(value, Store)
256
256
  }
257
257
 
258
258
  /**
@@ -305,16 +305,15 @@ class Select extends Picker {
305
305
  module : List,
306
306
  appName : me.appName,
307
307
  displayField : me.displayField,
308
- role : 'listbox',
309
308
  itemRole : 'option',
309
+ navigator : {eventSource : me.getInputElId()},
310
310
  parentId : me.id,
311
- navigator : {
312
- eventSource : me.getInputElId()
313
- },
311
+ role : 'listbox',
314
312
  selectionModel : {stayInList: false},
315
313
  store : me.store,
316
314
  ...me.listConfig
317
315
  });
316
+
318
317
  me.getInputEl()['aria-controls'] = me.list.id;
319
318
 
320
319
  me.list.addDomListeners({
@@ -323,15 +322,76 @@ class Select extends Picker {
323
322
  scope : me
324
323
  }
325
324
  });
325
+
326
326
  me.list.selectionModel.on({
327
- selectionChange : me.onListItemSelectionChange,
328
327
  noChange : me.onListItemSelectionNoChange,
328
+ selectionChange : me.onListItemSelectionChange,
329
329
  scope : me
330
330
  })
331
331
 
332
332
  return me.list;
333
333
  }
334
334
 
335
+ /**
336
+ * All routes which expect to open the picker route through here. This updates the
337
+ * filter and ensures that the picker is visible and reflecting the state of the filter.
338
+ *
339
+ * Input event processing passes the current input field value in as the filter value.
340
+ *
341
+ * Invocation of the expand trigger passes `null` so as to clear filtering.
342
+ * @private
343
+ * @param {String|null} value The value to filter the picker by
344
+ */
345
+ doFilter(value) {
346
+ let me = this,
347
+ filter = me.store.getFilter(me.displayField),
348
+ {
349
+ record,
350
+ picker
351
+ } = me;
352
+
353
+ if (filter) {
354
+ filter.value = value;
355
+ }
356
+
357
+ // Filter resulting in something to show
358
+ if (me.store.getCount()) {
359
+ me.getPicker().hidden = false;
360
+
361
+ // List might not exist until the picker is created
362
+ const
363
+ { list } = me,
364
+ { selectionModel } = list;
365
+
366
+ // On show, set the active item to be the current selected record or the first
367
+ if (record) {
368
+ // We do not want to hear back about our own selection
369
+ selectionModel.suspendEvents = true;
370
+ selectionModel.select(record);
371
+ selectionModel.suspendEvents = false;
372
+ }
373
+ setTimeout(() => {
374
+ list.activeIndex = me.record || 0;
375
+ }, 100)
376
+ }
377
+ // Filtered down to nothing - hide picker if it has been created.
378
+ else if (picker) {
379
+ picker._hidden = true;
380
+ }
381
+ }
382
+
383
+ /**
384
+ * @param {Object} data
385
+ */
386
+ filterOnInput(data) {
387
+ if (data.value) {
388
+ this.doFilter(data.value);
389
+ }
390
+ else if (this.picker) {
391
+ this.picker?.hide();
392
+ }
393
+ }
394
+
335
395
  /**
336
396
  * Overrides form.field.Base
337
397
  * @param {*} value
@@ -383,7 +443,7 @@ class Select extends Picker {
383
443
 
384
444
  /**
385
445
  * Returns the first selected record or null
386
- * returns {Object}
446
+ * @returns {Object}
387
447
  */
388
448
  getRecord() {
389
449
  let list = this.list,
@@ -401,13 +461,17 @@ class Select extends Picker {
401
461
  return me.record?.[me.valueField] || me.value
402
462
  }
403
463
 
464
+ /**
465
+ *
466
+ */
404
467
  onConstructed() {
405
468
  const inputEl = this.getInputEl();
406
469
 
407
- inputEl['aria-expanded'] = false
408
- inputEl['aria-haspopup'] = 'listbox'
409
- inputEl['aria-activedescendant'] = ''
410
- super.onConstructed(...arguments);
470
+ inputEl['aria-activedescendant'] = '';
471
+ inputEl['aria-expanded'] = false;
472
+ inputEl['aria-haspopup'] = 'listbox';
473
+
474
+ super.onConstructed(...arguments)
411
475
  }
412
476
 
413
477
  /**
@@ -439,15 +503,6 @@ class Select extends Picker {
439
503
  super.onFocusLeave(data)
440
504
  }
441
505
 
442
- filterOnInput(data) {
443
- if (data.value) {
444
- this.doFilter(data.value);
445
- }
446
- else if (this.picker) {
447
- this.picker?.hide();
448
- }
449
- }
450
-
451
506
  /**
452
507
  * @param {Object} data
453
508
  * @protected
@@ -469,17 +524,6 @@ class Select extends Picker {
469
524
  }
470
525
  }
471
526
 
472
- onPickerHiddenChange({ value }) {
473
- const inputEl = this.getInputEl();
474
-
475
- super.onPickerHiddenChange(...arguments);
476
- if (value) {
477
- inputEl['aria-activedescendant'] = '';
478
- }
479
- inputEl['aria-expanded'] = !value;
480
- this.update();
481
- }
482
-
483
527
  // TODO:
484
528
  // When we are using a `Collection` as our `valueCollection`, and that `Collection` is the
485
529
  // `items` of the List's `selectionModel`, then this will be `onValueCollectionChange`,
@@ -541,6 +585,34 @@ class Select extends Picker {
541
585
  }
542
586
  }
543
587
 
588
+ /**
589
+ * @param {Object} data
590
+ */
591
+ onPickerHiddenChange({ value }) {
592
+ const inputEl = this.getInputEl();
593
+
594
+ super.onPickerHiddenChange(...arguments);
595
+ if (value) {
596
+ inputEl['aria-activedescendant'] = '';
597
+ }
598
+ inputEl['aria-expanded'] = !value;
599
+ this.update();
600
+ }
601
+
602
+ /**
603
+ *
604
+ */
605
+ onPickerTriggerClick() {
606
+ let me = this;
607
+
608
+ if (me.picker?.isVisible) {
609
+ me.picker.hidden = true;
610
+ }
611
+ else if (!me.readOnly && !me.disabled) {
612
+ me.doFilter(null)
613
+ }
614
+ }
615
+
544
616
  /**
545
617
  * Selecting a record, if required
546
618
  * @param {Object[]} items
@@ -587,65 +659,6 @@ class Select extends Picker {
587
659
  me.list.selectItem(index);
588
660
  }
589
661
 
590
- onPickerTriggerClick() {
591
- let me = this;
592
-
593
- if (me.picker?.isVisible) {
594
- me.picker.hidden = true;
595
- }
596
- else if (!me.readOnly && !me.disabled) {
597
- me.doFilter(null)
598
- }
599
- }
600
-
601
- /**
602
- * All routes which expect to open the picker route through here. This updates the
603
- * filter and ensures that the picker is visible and reflecting the state of the filter.
604
- *
605
- * Input event processing passes the current input field value in as the filter value.
606
- *
607
- * Invocation of the expand trigger passes `null` so as to clear filtering.
608
- * @private
609
- * @param {String}null} value The value to filter the picker by
610
- */
611
- doFilter(value) {
612
- let me = this,
613
- filter = me.store.getFilter(me.displayField),
614
- {
615
- record,
616
- picker
617
- } = me;
618
-
619
- if (filter) {
620
- filter.value = value;
621
- }
622
-
623
- // Filter resulting in something to show
624
- if (me.store.getCount()) {
625
- me.getPicker().hidden = false;
626
-
627
- // List might not exist until the picker is created
628
- const
629
- { list } = me,
630
- { selectionModel } = list;
631
-
632
- // On show, set the active item to be the current selected record or the first
633
- if (record) {
634
- // We do not want to hear back about our own selection
635
- selectionModel.suspendEvents = true;
636
- selectionModel.select(record);
637
- selectionModel.suspendEvents = false;
638
- }
639
- setTimeout(() => {
640
- list.activeIndex = me.record || 0;
641
- }, 100)
642
- }
643
- // Filtered down to nothing - hide picker if it has been created.
644
- else if (picker) {
645
- picker._hidden = true;
646
- }
647
- }
648
-
649
662
  /**
650
663
  * @param {Boolean} [silent=false]
651
664
  * @protected
@@ -661,7 +674,7 @@ class Select extends Picker {
661
674
  cls: ['neo-input-field-wrapper'],
662
675
  cn : [{
663
676
  tag : 'input',
664
- autocomplete: 'off',
677
+ autocomplete: 'no', // while "off" is the correct value, browser vendors ignore it. Arbitrary strings do the trick.
665
678
  autocorrect : 'off',
666
679
  cls : ['neo-textfield-input', 'neo-typeahead-input'],
667
680
  disabled : true,
@@ -318,7 +318,8 @@ class Text extends Base {
318
318
  * @protected
319
319
  */
320
320
  afterSetAutoComplete(value, oldValue) {
321
- this.changeInputElKey('autocomplete', value ? null : 'off')
321
+ // while "off" is the correct value, browser vendors ignore it. Arbitrary strings do the trick.
322
+ this.changeInputElKey('autocomplete', value ? null : 'no')
322
323
  }
323
324
 
324
325
  /**
package/src/list/Base.mjs CHANGED
@@ -21,6 +21,7 @@ class Base extends Component {
21
21
  */
22
22
  ntype: 'list',
23
23
  /**
24
+ * Keeps track of the selected item index and allows bindings and programmatic changes
24
25
  * @member {Number|null} activeIndex_=null
25
26
  */
26
27
  activeIndex_: null,
@@ -63,6 +64,12 @@ class Base extends Component {
63
64
  * @member {Object} dragZoneConfig=null
64
65
  */
65
66
  dragZoneConfig: null,
67
+ /**
68
+ * Keeps track of the focussed item index and allows bindings and programmatic changes.
69
+ * You can either pass the index or the related record
70
+ * @member {Number|Object|null} focusIndex_=null
71
+ */
72
+ focusIndex_: null,
66
73
  /**
67
74
  * In case we are using list item headers and want to bind list item indexes to e.g. a card layout
68
75
  * for e.g. a sidenav, this config comes in handy.
@@ -178,15 +185,16 @@ class Base extends Component {
178
185
  * @param {Number|null} oldValue
179
186
  * @protected
180
187
  */
181
- afterSetActiveIndex(value) {
182
- let me = this;
188
+ afterSetActiveIndex(value, oldValue) {
189
+ let me = this,
190
+ selectionModel = me.selectionModel;
183
191
 
184
192
  if (Neo.isNumber(value)) {
193
+ selectionModel?.selectAt(value);
185
194
  me.headerlessActiveIndex = me.getHeaderlessIndex(value)
186
- Neo.main.addon.Navigator.navigateTo([value, this.navigator])
187
- }
188
- else if (value) {
189
- Neo.main.addon.Navigator.navigateTo([me.getItemId(value[me.getKeyProperty()]), this.navigator])
195
+ } else if (Neo.isNumber(oldValue)) {
196
+ selectionModel.deselectAll();
197
+ me.headerlessActiveIndex = null
190
198
  }
191
199
  }
192
200
 
@@ -243,6 +251,22 @@ class Base extends Component {
243
251
  }
244
252
  }
245
253
 
254
+ /**
255
+ * Triggered after the focusIndex config got changed
256
+ * @param {Number|Object|null} value
257
+ * @param {Number|Object|null} oldValue
258
+ * @protected
259
+ */
260
+ afterSetFocusIndex(value, oldValue) {
261
+ let me = this;
262
+
263
+ if (Neo.isNumber(value)) {
264
+ Neo.main.addon.Navigator.navigateTo([me.getHeaderlessIndex(value), me.navigator])
265
+ } else if (value) {
266
+ Neo.main.addon.Navigator.navigateTo([me.getItemId(value[me.getKeyProperty()]), me.navigator])
267
+ }
268
+ }
269
+
246
270
  /**
247
271
  * Triggered after the headerlessActiveIndex config got changed
248
272
  * @param {Number} value
@@ -253,7 +277,7 @@ class Base extends Component {
253
277
  let me = this;
254
278
 
255
279
  if (Neo.isNumber(value)) {
256
- me.activeIndex = me.store.getCount() ? value: null
280
+ me.activeIndex = me.store.getCount() ? me.getActiveIndex(value) : null
257
281
  } else if (Neo.isNumber(oldValue)) {
258
282
  me.activeIndex = null
259
283
  }
@@ -755,14 +779,16 @@ class Base extends Component {
755
779
  * @param {Number|String} item
756
780
  */
757
781
  selectItem(item) {
758
- if (!this.disableSelection) {
782
+ let me = this;
783
+
784
+ if (!me.disableSelection) {
759
785
  // Selecting index
760
786
  if (Neo.isNumber(item)) {
761
- this.selectionModel?.selectAt(item)
787
+ me.selectionModel?.selectAt(item)
762
788
  }
763
789
  // Selecting record
764
790
  else if (item) {
765
- this.selectionModel?.selectAt(this.store.indexOf(item));
791
+ me.selectionModel?.selectAt(me.store.indexOf(item))
766
792
  }
767
793
  }
768
794
  }
@@ -1,12 +1,11 @@
1
- import Base from '../../core/Base.mjs';
1
+ import Base from './Base.mjs';
2
2
  import DomAccess from '../DomAccess.mjs';
3
3
 
4
4
  /**
5
5
  * Helper class to include amCharts into your neo.mjs app
6
6
  * https://www.amcharts.com/docs/v4/
7
7
  * @class Neo.main.addon.AmCharts
8
- * @extends Neo.core.Base
9
- * @singleton
8
+ * @extends Neo.main.addon.Base
10
9
  */
11
10
  class AmCharts extends Base {
12
11
  static config = {
@@ -44,16 +43,6 @@ class AmCharts extends Base {
44
43
  * @protected
45
44
  */
46
45
  fallbackPath: 'https://neomjs.github.io/pages/resources/amCharts/',
47
- /**
48
- * @member {Boolean} scriptsLoaded_=true
49
- * @protected
50
- */
51
- scriptsLoaded_: false,
52
- /**
53
- * @member {Boolean} singleton=true
54
- * @protected
55
- */
56
- singleton: true,
57
46
  /**
58
47
  * Remote method access for other workers
59
48
  * @member {Object} remote={app: [//...]}
@@ -68,7 +57,12 @@ class AmCharts extends Base {
68
57
  'setProperty',
69
58
  'updateData'
70
59
  ]
71
- }
60
+ },
61
+ /**
62
+ * @member {Boolean} scriptsLoaded_=true
63
+ * @protected
64
+ */
65
+ scriptsLoaded_: false
72
66
  }
73
67
 
74
68
  /**
@@ -278,6 +272,6 @@ class AmCharts extends Base {
278
272
  }
279
273
  }
280
274
 
281
- let instance = Neo.applyClassConfig(AmCharts);
275
+ Neo.applyClassConfig(AmCharts);
282
276
 
283
- export default instance;
277
+ export default AmCharts;
@@ -1,12 +1,11 @@
1
- import Base from '../../core/Base.mjs';
1
+ import Base from './Base.mjs';
2
2
 
3
3
  /**
4
4
  * Required for the online version of the examples & docs app
5
5
  * We can not name the file GoogleAnalytics, since it does break when using uBlock origin for dist versions.
6
6
  * See: https://github.com/neomjs/neo/issues/651
7
7
  * @class Neo.main.addon.AnalyticsByGoogle
8
- * @extends Neo.core.Base
9
- * @singleton
8
+ * @extends Neo.main.addon.Base
10
9
  */
11
10
  class AnalyticsByGoogle extends Base {
12
11
  static config = {
@@ -14,12 +13,7 @@ class AnalyticsByGoogle extends Base {
14
13
  * @member {String} className='Neo.main.addon.AnalyticsByGoogle'
15
14
  * @protected
16
15
  */
17
- className: 'Neo.main.addon.AnalyticsByGoogle',
18
- /**
19
- * @member {Boolean} singleton=true
20
- * @protected
21
- */
22
- singleton: true
16
+ className: 'Neo.main.addon.AnalyticsByGoogle'
23
17
  }
24
18
 
25
19
  /**
@@ -54,6 +48,6 @@ class AnalyticsByGoogle extends Base {
54
48
  }
55
49
  }
56
50
 
57
- let instance = Neo.applyClassConfig(AnalyticsByGoogle);
51
+ Neo.applyClassConfig(AnalyticsByGoogle);
58
52
 
59
- export default instance;
53
+ export default AnalyticsByGoogle;