neo.mjs 8.43.1 → 9.0.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.
Files changed (48) hide show
  1. package/apps/ServiceWorker.mjs +2 -2
  2. package/apps/colors/view/ViewportController.mjs +2 -6
  3. package/apps/portal/index.html +1 -1
  4. package/apps/portal/view/ViewportController.mjs +5 -6
  5. package/apps/portal/view/home/FooterContainer.mjs +1 -1
  6. package/apps/portal/view/learn/ContentComponent.mjs +5 -5
  7. package/apps/portal/view/learn/MainContainerController.mjs +0 -23
  8. package/apps/sharedcovid/view/MainContainerController.mjs +4 -13
  9. package/examples/ServiceWorker.mjs +2 -2
  10. package/examples/button/base/MainContainer.mjs +9 -1
  11. package/examples/component/multiWindowCoronaGallery/ViewportController.mjs +4 -11
  12. package/examples/component/multiWindowHelix/ViewportController.mjs +1 -7
  13. package/package.json +5 -5
  14. package/src/DefaultConfig.mjs +2 -2
  15. package/src/button/Base.mjs +28 -7
  16. package/src/component/Base.mjs +43 -86
  17. package/src/component/wrapper/AmChart.mjs +4 -3
  18. package/src/container/Base.mjs +15 -3
  19. package/src/controller/Component.mjs +4 -106
  20. package/src/core/Base.mjs +15 -2
  21. package/src/core/Observable.mjs +9 -11
  22. package/src/data/Model.mjs +9 -0
  23. package/src/data/RecordFactory.mjs +1 -1
  24. package/src/form/field/Text.mjs +3 -1
  25. package/src/grid/Container.mjs +2 -4
  26. package/src/grid/View.mjs +37 -45
  27. package/src/grid/column/AnimatedChange.mjs +1 -1
  28. package/src/grid/column/Component.mjs +14 -5
  29. package/src/grid/header/Button.mjs +6 -17
  30. package/src/layout/Base.mjs +3 -2
  31. package/src/layout/Cube.mjs +41 -37
  32. package/src/main/DomEvents.mjs +26 -1
  33. package/src/manager/DomEvent.mjs +21 -22
  34. package/src/selection/Model.mjs +14 -11
  35. package/src/selection/grid/BaseModel.mjs +155 -2
  36. package/src/selection/grid/CellColumnModel.mjs +7 -11
  37. package/src/selection/grid/CellColumnRowModel.mjs +7 -11
  38. package/src/selection/grid/CellModel.mjs +5 -4
  39. package/src/selection/grid/CellRowModel.mjs +22 -30
  40. package/src/selection/grid/ColumnModel.mjs +7 -11
  41. package/src/selection/grid/RowModel.mjs +21 -35
  42. package/src/state/Provider.mjs +3 -8
  43. package/src/tab/Container.mjs +17 -1
  44. package/src/table/Container.mjs +38 -91
  45. package/src/table/View.mjs +51 -3
  46. package/src/table/header/Button.mjs +7 -18
  47. package/src/util/Function.mjs +3 -1
  48. package/src/util/VDom.mjs +4 -4
@@ -223,6 +223,29 @@ class DomEvents extends Base {
223
223
  Neo.worker.Manager.sendMessage('app', config)
224
224
  }
225
225
 
226
+ /**
227
+ *
228
+ * @param {HTMLElement} node
229
+ * @returns {Object}
230
+ */
231
+ geAriaAttributes(node) {
232
+ let ariaAttributes = {},
233
+ {attributes} = node,
234
+ i = 0,
235
+ len = attributes?.length || 0,
236
+ attribute;
237
+
238
+ for (; i < len; i++) {
239
+ attribute = attributes[i];
240
+
241
+ if (attribute.name.startsWith('aria-')) {
242
+ ariaAttributes[attribute.name.substring(5)] = attribute.value
243
+ }
244
+ }
245
+
246
+ return ariaAttributes
247
+ }
248
+
226
249
  /**
227
250
  * Returns the distance between two points
228
251
  * @param {Number} x1 The X position of the first point
@@ -349,6 +372,7 @@ class DomEvents extends Base {
349
372
  rect = r && this.parseDomRect(r) || {};
350
373
 
351
374
  return {
375
+ aria : this.geAriaAttributes(node),
352
376
  checked : node.checked,
353
377
  childElementCount: node.childElementCount,
354
378
  clientHeight : node.clientHeight,
@@ -369,12 +393,13 @@ class DomEvents extends Base {
369
393
  offsetTop : node.offsetTop,
370
394
  offsetWidth : node.offsetWidth,
371
395
  rect,
396
+ role : node.role,
372
397
  scrollHeight : node.scrollHeight,
373
398
  scrollLeft : node.scrollLeft,
374
399
  scrollTop : node.scrollTop,
375
400
  scrollWidth : node.scrollWidth,
376
401
  style : node.style?.cssText,
377
- tabIndex : node.tabIndex,
402
+ tabIndex : node.getAttribute?.('tabindex') ? node.tabIndex : null,
378
403
  tagName : node.tagName?.toLowerCase()
379
404
  }
380
405
  }
@@ -1,10 +1,9 @@
1
- import Base from '../core/Base.mjs';
2
- import ComponentManager from './Component.mjs';
3
- import FocusManager from './Focus.mjs';
4
- import Logger from '../util/Logger.mjs';
5
- import NeoArray from '../util/Array.mjs';
6
- import {resolveCallback} from '../util/Function.mjs';
7
- import VDomUtil from '../util/VDom.mjs';
1
+ import Base from '../core/Base.mjs';
2
+ import ComponentManager from './Component.mjs';
3
+ import FocusManager from './Focus.mjs';
4
+ import Logger from '../util/Logger.mjs';
5
+ import NeoArray from '../util/Array.mjs';
6
+ import VDomUtil from '../util/VDom.mjs';
8
7
 
9
8
  const eventConfigKeys = [
10
9
  'bubble',
@@ -110,8 +109,6 @@ class DomEvent extends Base {
110
109
  listeners = me.items[id]?.[eventName];
111
110
 
112
111
  if (listeners) {
113
- // console.log('fire', eventName, data, listeners, path);
114
-
115
112
  if (Array.isArray(listeners)) {
116
113
  // Stop iteration if a handler returns false
117
114
  listeners.every(listener => {
@@ -142,6 +139,10 @@ class DomEvent extends Base {
142
139
  // Handler needs to know which actual target matched the delegate
143
140
  data.currentTarget = delegationTargetId;
144
141
 
142
+ if (Neo.isString(listener.fn)) {
143
+ me.bindCallback(listener.fn, 'fn', listener.scope, listener)
144
+ }
145
+
145
146
  result = listener.fn.apply(listener.scope || globalThis, [data]);
146
147
 
147
148
  if (!listener.bubble) {
@@ -284,18 +285,18 @@ class DomEvent extends Base {
284
285
  }
285
286
 
286
287
  /**
287
- * @param {Object} config
288
+ * @param {Object} config
288
289
  * @param {Boolean} config.bubble
289
- * @param {String} config.delegate
290
- * @param {String} config.eventName
291
- * @param {String} config.id
290
+ * @param {String} config.delegate
291
+ * @param {String} config.eventName
292
+ * @param {String} config.id
292
293
  * @param {Boolean} config.local
293
- * @param {Number} config.opts
294
- * @param {Number} config.originalConfig
295
- * @param {String} config.ownerId
296
- * @param {Number} config.priority=1
297
- * @param {Object} config.scope
298
- * @param {String} config.vnodeId
294
+ * @param {Number} config.opts
295
+ * @param {Number} config.originalConfig
296
+ * @param {String} config.ownerId
297
+ * @param {Number} config.priority=1
298
+ * @param {Object} config.scope
299
+ * @param {String} config.vnodeId
299
300
  * @returns {Boolean} true if the listener got registered successfully (false in case it was already there)
300
301
  */
301
302
  register(config) {
@@ -306,10 +307,8 @@ class DomEvent extends Base {
306
307
  fnType = typeof opts,
307
308
  fn, listener, listenerConfig, listenerId;
308
309
 
309
- if (fnType === 'function') {
310
+ if (fnType === 'function' || fnType === 'string') {
310
311
  fn = opts
311
- } else if (fnType === 'string') {
312
- fn = resolveCallback(opts, scope).fn
313
312
  } else {
314
313
  fn = opts.fn;
315
314
  scope = opts.scope || scope
@@ -94,17 +94,19 @@ class Model extends Base {
94
94
  * @param {String} [selectedCls]
95
95
  */
96
96
  deselect(item, silent, itemCollection=this.items, selectedCls) {
97
+ let me = this,
98
+ {view} = me,
99
+ node;
100
+
97
101
  // We hold vdom ids for now, so all incoming selections must be converted.
98
102
  item = item.isRecord ? view.getItemId(item) : Neo.isObject(item) ? item.id : item;
99
103
 
100
104
  if (itemCollection.includes(item)) {
101
- let me = this,
102
- {view} = me,
103
- node = view.getVdomChild(item);
105
+ node = view.getVdomChild(item);
104
106
 
105
107
  if (node) {
106
108
  node.cls = NeoArray.remove(node.cls || [], selectedCls || me.selectedCls);
107
- node['aria-selected'] = false
109
+ delete node['aria-selected']
108
110
  }
109
111
 
110
112
  NeoArray.remove(itemCollection, item);
@@ -119,9 +121,8 @@ class Model extends Base {
119
121
  selection: itemCollection
120
122
  })
121
123
  }
122
- }
123
- else if (!silent) {
124
- this.fire('noChange')
124
+ } else if (!silent) {
125
+ me.fire('noChange')
125
126
  }
126
127
  }
127
128
 
@@ -146,8 +147,7 @@ class Model extends Base {
146
147
  me.fire('selectionChange', {
147
148
  selection: this.items
148
149
  })
149
- }
150
- else if (!silent) {
150
+ } else if (!silent) {
151
151
  me.fire('noChange')
152
152
  }
153
153
  }
@@ -225,12 +225,15 @@ class Model extends Base {
225
225
  * @param {String} [selectedCls]
226
226
  */
227
227
  select(items, itemCollection=this.items, selectedCls) {
228
+ if (!Array.isArray(items)) {
229
+ items = [items]
230
+ }
231
+
228
232
  let me = this,
229
233
  {view} = me;
230
234
 
231
235
  // We hold vdom ids for now, so all incoming selections must be converted.
232
- items = (items = Array.isArray(items) ?
233
- items: [items]).map(item => item.isRecord ? view.getItemId(item) : Neo.isObject(item) ? item.id : item);
236
+ items = items.map(item => item.isRecord ? view.getItemId(item) : Neo.isObject(item) ? item.id : item);
234
237
 
235
238
  if (!Neo.isEqual(itemCollection, items)) {
236
239
  if (me.singleSelect && itemCollection === me.items) {
@@ -1,4 +1,5 @@
1
- import Model from '../Model.mjs';
1
+ import Model from '../Model.mjs';
2
+ import NeoArray from '../../util/Array.mjs';
2
3
 
3
4
  /**
4
5
  * Abstract base class for all grid related selection models
@@ -12,7 +13,18 @@ class BaseModel extends Model {
12
13
  * @member {String} className='Neo.selection.grid.BaseModel'
13
14
  * @protected
14
15
  */
15
- className: 'Neo.selection.grid.BaseModel'
16
+ className: 'Neo.selection.grid.BaseModel',
17
+ /**
18
+ * Storing the column dataFields
19
+ * @member {String[]} selectedColumns=[]
20
+ */
21
+ selectedColumns: [],
22
+ /**
23
+ * Storing the record ids
24
+ * @member {Number[]|String[]} selectedRows=[]
25
+ * @protected
26
+ */
27
+ selectedRows: []
16
28
  }
17
29
 
18
30
  /**
@@ -23,6 +35,83 @@ class BaseModel extends Model {
23
35
  return this.view.parent.columns.items.map(column => column.dataField)
24
36
  }
25
37
 
38
+ /**
39
+ * @param {Boolean} [silent=false] true to prevent a vdom update
40
+ */
41
+ deselectAllRows(silent=false) {
42
+ let me = this,
43
+ items = [...me.selectedRows],
44
+ {view} = me;
45
+
46
+ if (items.length) {
47
+ items.forEach(item => {
48
+ me.deselectRow(item, true)
49
+ });
50
+
51
+ if (!silent && items.length > 0) {
52
+ view.update()
53
+ }
54
+
55
+ me.fire('selectionChange', {
56
+ selection: me.selectedRows
57
+ })
58
+ } else if (!silent) {
59
+ me.fire('noChange')
60
+ }
61
+ }
62
+
63
+ /**
64
+ * @param {Number|String} recordId
65
+ * @param {Boolean} [silent=false]
66
+ */
67
+ deselectRow(recordId, silent=false) {
68
+ let me = this,
69
+ {view} = me,
70
+ {store} = view,
71
+ record = store.get(recordId),
72
+ rowId = view.getRowId(store.indexOf(record)),
73
+ node = view.getVdomChild(rowId);
74
+
75
+ if (node) {
76
+ node.cls = NeoArray.remove(node.cls || [], me.selectedCls);
77
+ delete node['aria-selected']
78
+ }
79
+
80
+ me.selectedRows = [recordId];
81
+
82
+ !silent && view.update()
83
+ }
84
+
85
+ /**
86
+ * Get the record for a given event path
87
+ * @param {Object[]} path
88
+ * @returns {Number|String|null}
89
+ */
90
+ getRecord(path) {
91
+ let node, rowIndex;
92
+
93
+ for (node of path) {
94
+ if (node.aria.rowindex) {
95
+ rowIndex = parseInt(node.aria.rowindex);
96
+
97
+ // aria-rowindex is 1 based & also includes the header
98
+ rowIndex -= 2;
99
+
100
+ return this.view.store.getAt(rowIndex)
101
+ }
102
+ }
103
+
104
+ return null
105
+ }
106
+
107
+ /**
108
+ * @param {Record} record
109
+ * @returns {Boolean}
110
+ */
111
+ hasAnnotations(record) {
112
+ return !!Object.getOwnPropertyDescriptor(record.__proto__, this.view.selectedRecordField)
113
+ }
114
+
26
115
  /**
27
116
  * Checks if an event path contains a grid cell editor
28
117
  * @param {Object} data
@@ -38,6 +127,70 @@ class BaseModel extends Model {
38
127
 
39
128
  return false
40
129
  }
130
+
131
+ /**
132
+ * @param {String} dataField
133
+ * @returns {Boolean} true in case the column is selected
134
+ */
135
+ isSelectedColumn(dataField) {
136
+ return this.selectedColumns.includes(dataField)
137
+ }
138
+
139
+ /**
140
+ * @param {Number|String} recordId
141
+ * @returns {Boolean} true in case the row is selected
142
+ */
143
+ isSelectedRow(recordId) {
144
+ return this.selectedRows.includes(recordId)
145
+ }
146
+
147
+ /**
148
+ * @param {Number|String} recordId
149
+ * @param {Boolean} [silent=false]
150
+ */
151
+ selectRow(recordId, silent=false) {
152
+ let me = this,
153
+ {view} = me,
154
+ {store} = view,
155
+ record = store.get(recordId),
156
+ rowId = view.getRowId(store.indexOf(record)),
157
+ node = view.getVdomChild(rowId);
158
+
159
+ if (me.singleSelect) {
160
+ me.deselectAllRows(true)
161
+ }
162
+
163
+ if (node) {
164
+ node.cls = NeoArray.add(node.cls || [], me.selectedCls);
165
+ node['aria-selected'] = true
166
+ }
167
+
168
+ me.selectedRows = [recordId];
169
+
170
+ !silent && view.update()
171
+ }
172
+
173
+ /**
174
+ * @param {Number|String} recordId
175
+ * @param {Boolean} [silent=false]
176
+ */
177
+ toggleRowSelection(recordId, silent=false) {
178
+ this[this.isSelectedRow(recordId) ? 'deselectRow' : 'selectRow'](recordId, silent)
179
+ }
180
+
181
+ /**
182
+ *
183
+ */
184
+ unregister() {
185
+ let me = this,
186
+ countRows = me.selectedRows.length;
187
+
188
+ me.selectedRows = [];
189
+
190
+ countRows > 0 && me.view.createViewData();
191
+
192
+ super.unregister()
193
+ }
41
194
  }
42
195
 
43
196
  export default Neo.setupClass(BaseModel);
@@ -25,24 +25,20 @@ class CellColumnModel extends CellModel {
25
25
  * @member {String} selectedColumnCellCls='selected-column-cell'
26
26
  * @protected
27
27
  */
28
- selectedColumnCellCls: 'selected-column-cell',
29
- /**
30
- * Storing the column dataFields
31
- * @member {String[]} selectedColumns=[]
32
- */
33
- selectedColumns: []
28
+ selectedColumnCellCls: 'selected-column-cell'
34
29
  }
35
30
 
36
31
  /**
37
32
  * @param {Object} data
38
33
  */
39
34
  onCellClick(data) {
40
- let me = this,
41
- {view} = me,
42
- cellId = data.data.currentTarget;
35
+ let me = this,
36
+ {view} = me,
37
+ cellId = data.data.currentTarget,
38
+ dataField = cellId && view.getDataField(cellId);
43
39
 
44
- if (cellId) {
45
- me.selectedColumns = [view.getDataField(cellId)];
40
+ if (dataField) {
41
+ me.selectedColumns = me.isSelected(cellId) ? [] : [dataField];
46
42
  view.createViewData(true)
47
43
  }
48
44
 
@@ -25,24 +25,20 @@ class CellColumnRowModel extends CellRowModel {
25
25
  * @member {String} selectedColumnCellCls='selected-column-cell'
26
26
  * @protected
27
27
  */
28
- selectedColumnCellCls: 'selected-column-cell',
29
- /**
30
- * Storing the column dataFields
31
- * @member {String[]} selectedColumns=[]
32
- */
33
- selectedColumns: []
28
+ selectedColumnCellCls: 'selected-column-cell'
34
29
  }
35
30
 
36
31
  /**
37
32
  * @param {Object} data
38
33
  */
39
34
  onCellClick(data) {
40
- let me = this,
41
- {view} = me,
42
- cellId = data.data.currentTarget;
35
+ let me = this,
36
+ {view} = me,
37
+ cellId = data.data.currentTarget,
38
+ dataField = cellId && view.getDataField(cellId);
43
39
 
44
- if (cellId) {
45
- me.selectedColumns = [view.getDataField(cellId)];
40
+ if (dataField) {
41
+ me.selectedColumns = me.isSelected(cellId) ? [] : [dataField];
46
42
  view.createViewData(true)
47
43
  }
48
44
 
@@ -102,7 +102,7 @@ class CellModel extends BaseModel {
102
102
  newIndex += dataFields.length
103
103
  }
104
104
 
105
- me.select(view.getCellId(record, dataFields[newIndex]));
105
+ me.select(view.getCellId(store.indexOf(record), dataFields[newIndex]));
106
106
 
107
107
  view.parent.scrollByColumns(currentIndex, step)
108
108
  }
@@ -114,6 +114,7 @@ class CellModel extends BaseModel {
114
114
  let me = this,
115
115
  {view} = me,
116
116
  {store} = view,
117
+ countRecords = store.getCount(),
117
118
  currentIndex = 0,
118
119
  dataField, newIndex;
119
120
 
@@ -124,13 +125,13 @@ class CellModel extends BaseModel {
124
125
  dataField = me.dataFields[0]
125
126
  }
126
127
 
127
- newIndex = (currentIndex + step) % store.getCount();
128
+ newIndex = (currentIndex + step) % countRecords;
128
129
 
129
130
  while (newIndex < 0) {
130
- newIndex += store.getCount()
131
+ newIndex += countRecords
131
132
  }
132
133
 
133
- me.select(view.getCellId(store.getAt(newIndex), dataField));
134
+ me.select(view.getCellId(newIndex, dataField));
134
135
  view.scrollByRows(currentIndex, step)
135
136
  }
136
137
 
@@ -20,13 +20,7 @@ class CellRowModel extends CellModel {
20
20
  * @member {String} cls='neo-selection-cellrowmodel'
21
21
  * @protected
22
22
  */
23
- cls: 'neo-selection-cellrowmodel',
24
- /**
25
- * Storing the node ids
26
- * @member {String[]} selectedRows=[]
27
- * @protected
28
- */
29
- selectedRows: []
23
+ cls: 'neo-selection-cellrowmodel'
30
24
  }
31
25
 
32
26
  /**
@@ -35,12 +29,15 @@ class CellRowModel extends CellModel {
35
29
  onCellClick(data) {
36
30
  let me = this,
37
31
  {view} = me,
38
- record = view.getRecord(data.data.currentTarget),
39
- rowId = view.getRowId(record);
32
+ cellId = data.data.currentTarget,
33
+ record = me.getRecord(data.data.path);
40
34
 
41
- if (rowId) {
42
- me.selectedRows = [rowId];
43
- view.createViewData(true)
35
+ if (record) {
36
+ if (me.hasAnnotations(record)) {
37
+ me.updateAnnotations(record)
38
+ } else {
39
+ me[me.isSelected(cellId) ? 'deselectRow' : 'selectRow'](record[view.store.getKeyProperty()], true)
40
+ }
44
41
  }
45
42
 
46
43
  super.onCellClick(data)
@@ -54,35 +51,30 @@ class CellRowModel extends CellModel {
54
51
  {view} = me,
55
52
  {store} = view,
56
53
  countRecords = store.getCount(),
57
- rowId = me.selectedRows[0] || view.getRowId(store.getAt(0)),
58
- record = view.getRecord(rowId),
54
+ keyProperty = store.getKeyProperty(),
55
+ recordId = me.selectedRows[0] || store.getAt(0)[keyProperty],
56
+ record = store.get(recordId),
59
57
  index = store.indexOf(record),
60
- newIndex = (index + step) % countRecords,
61
- id;
58
+ newIndex = (index + step) % countRecords;
62
59
 
63
60
  while (newIndex < 0) {
64
61
  newIndex += countRecords
65
62
  }
66
63
 
67
- id = view.getRowId(store.getAt(newIndex));
64
+ record = store.getAt(newIndex);
68
65
 
69
- if (id) {
70
- me.selectedRows = [id];
71
- view.createViewData(true)
66
+ if (me.hasAnnotations(record)) {
67
+ me.updateAnnotations(record)
68
+ } else {
69
+ recordId = record[keyProperty];
70
+
71
+ if (recordId) {
72
+ me.selectRow(recordId, true) // silent
73
+ }
72
74
  }
73
75
 
74
76
  super.onNavKeyRow(step)
75
77
  }
76
-
77
- /**
78
- *
79
- */
80
- unregister() {
81
- this.selectedRows = [];
82
- this.view.createViewData();
83
-
84
- super.unregister()
85
- }
86
78
  }
87
79
 
88
80
  export default Neo.setupClass(CellRowModel);
@@ -20,12 +20,7 @@ class ColumnModel extends BaseModel {
20
20
  * @member {String} cls='neo-selection-columnmodel'
21
21
  * @protected
22
22
  */
23
- cls: 'neo-selection-columnmodel',
24
- /**
25
- * Storing the column dataFields
26
- * @member {String[]} selectedColumns=[]
27
- */
28
- selectedColumns: []
23
+ cls: 'neo-selection-columnmodel'
29
24
  }
30
25
 
31
26
  /**
@@ -59,12 +54,13 @@ class ColumnModel extends BaseModel {
59
54
  * @param {Object} data
60
55
  */
61
56
  onCellClick(data) {
62
- let me = this,
63
- {view} = me,
64
- cellId = data.data.currentTarget;
57
+ let me = this,
58
+ {view} = me,
59
+ cellId = data.data.currentTarget,
60
+ dataField = cellId && view.getDataField(cellId);
65
61
 
66
- if (cellId) {
67
- me.selectedColumns = [view.getDataField(cellId)];
62
+ if (dataField) {
63
+ me.selectedColumns = me.isSelectedColumn(dataField) ? [] : [dataField];
68
64
  view.createViewData()
69
65
  }
70
66
  }