neo.mjs 6.9.0 → 6.9.2

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.9.0'
23
+ * @member {String} version='6.9.2'
24
24
  */
25
- version: '6.9.0'
25
+ version: '6.9.2'
26
26
  }
27
27
 
28
28
  /**
@@ -20,7 +20,7 @@ class MainContainerController extends Component {
20
20
  '/contact': 'handleContactRoute',
21
21
  '/users/{userId}': { handler: 'handleUserRoute', preHandler: 'doPrehandling' },
22
22
  // '/users/{userId}' : {handler: 'handleUserRoute', preHandler: 'doPrehandling'}, //example
23
- // '/users/{userId}/posts/{postId}' : {handler:'handleUserPostsRoute', preHandler: 'doPrehandlingFalse'}, //example
23
+ //'/users/{userId}/posts/{postId}' : {handler:'handleUserPostsRoute', preHandler: 'doPrehandlingFalse'}, //example
24
24
  // default: 'doDefaultHandling' //optional - example
25
25
  },
26
26
 
@@ -46,7 +46,7 @@ class MainContainerController extends Component {
46
46
 
47
47
 
48
48
  doPrehandling(value, oldValue, params = null) {
49
- const userId = parseInt(params);
49
+ const userId = parseInt(params.userId);
50
50
  if (userId > 0 && userId === this.data.activeUser) {
51
51
  return true;
52
52
  }
@@ -132,7 +132,6 @@ class MainContainerController extends Component {
132
132
  * @param {Object} data
133
133
  */
134
134
  onSwitchButtonMetaReset(data) {
135
- const currentUser = this.data.activeUser;
136
135
  this.data.activeUser = 0;
137
136
  this.#setUsername();
138
137
  this.#removeMetaButtonSelection();
@@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase {
20
20
  */
21
21
  singleton: true,
22
22
  /**
23
- * @member {String} version='6.9.0'
23
+ * @member {String} version='6.9.2'
24
24
  */
25
- version: '6.9.0'
25
+ version: '6.9.2'
26
26
  }
27
27
 
28
28
  /**
@@ -66,9 +66,17 @@ class MainContainer extends Viewport {
66
66
  // These two have been moved to the start of the Toolbar by their weights
67
67
  label: {
68
68
  style : {marginLeft: 0},
69
- weight: -10000
69
+ weight: -10000,
70
+
71
+ // Embed a tooltip request into the DOM
72
+ vdom: {
73
+ data: {
74
+ neoTooltip: 'The Label'
75
+ }
76
+ }
70
77
  },
71
78
  rowsPerPage: {
79
+ style : {margin: '0 .5em'},
72
80
  tooltip: 'Set the number of rows to display in a page',
73
81
  weight : -999
74
82
  }
@@ -23,10 +23,6 @@ class MainContainer extends ConfigurationViewport {
23
23
  cls : ['examples-container-accordion']
24
24
  }
25
25
 
26
- onConfigChange(config, opts) {
27
- this.exampleComponent.items[0][config] = opts.value;
28
- }
29
-
30
26
  createConfigurationComponents() {
31
27
  let me = this,
32
28
  treeList = me.exampleComponent.items[0];
@@ -74,6 +70,12 @@ class MainContainer extends ConfigurationViewport {
74
70
  stepSize : 3,
75
71
  style : {marginTop: '10px'},
76
72
  value : 400
73
+ }, {
74
+ ntype : 'button',
75
+ handler: me.onRemoveDomButtonClick.bind(me),
76
+ style : {marginTop: '20px'},
77
+ text : 'Remove DOM',
78
+ width : 100
77
79
  }];
78
80
  }
79
81
 
@@ -169,6 +171,23 @@ class MainContainer extends ConfigurationViewport {
169
171
  }]
170
172
  });
171
173
  }
174
+
175
+ /**
176
+ * @param {String} config
177
+ * @param {Object} opts
178
+ */
179
+ onConfigChange(config, opts) {
180
+ this.exampleComponent.items[0][config] = opts.value;
181
+ }
182
+
183
+ /**
184
+ *
185
+ * @param data
186
+ */
187
+ onRemoveDomButtonClick(data) {
188
+ let accordion = this.exampleComponent.items[0];
189
+ accordion.hidden = !accordion.hidden
190
+ }
172
191
  }
173
192
 
174
193
  Neo.applyClassConfig(MainContainer);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neo.mjs",
3
- "version": "6.9.0",
3
+ "version": "6.9.2",
4
4
  "description": "The webworkers driven UI framework",
5
5
  "type": "module",
6
6
  "repository": {
@@ -47,7 +47,7 @@
47
47
  "autoprefixer": "^10.4.16",
48
48
  "chalk": "^5.3.0",
49
49
  "clean-webpack-plugin": "^4.0.0",
50
- "commander": "^11.0.0",
50
+ "commander": "^11.1.0",
51
51
  "cssnano": "^6.0.1",
52
52
  "envinfo": "^7.10.0",
53
53
  "fs-extra": "^11.1.1",
@@ -56,9 +56,9 @@
56
56
  "neo-jsdoc": "1.0.1",
57
57
  "neo-jsdoc-x": "1.0.5",
58
58
  "postcss": "^8.4.31",
59
- "sass": "^1.69.1",
59
+ "sass": "^1.69.4",
60
60
  "showdown": "^2.1.0",
61
- "webpack": "^5.88.2",
61
+ "webpack": "^5.89.0",
62
62
  "webpack-cli": "^5.1.4",
63
63
  "webpack-dev-server": "4.15.1",
64
64
  "webpack-hook-plugin": "^1.0.7",
@@ -1,12 +1,10 @@
1
1
  .neo-toast {
2
- z-index : 20; // ensure to be on top of table headers
3
2
  align-items : center;
4
3
  background-color: var(--toast-background-color);
5
4
  border-radius : 0.5rem;
6
5
  box-shadow : 0 0.25rem 0.5rem var(--toast-shadow-color);
7
6
  color : var(--toast-color);
8
7
  display : flex;
9
- position : fixed;
10
8
  transition : bottom .3s ease-out, top .3s ease-out;
11
9
 
12
10
  .neo-toast-inner {
@@ -90,6 +88,7 @@
90
88
  /* Toast positions */
91
89
  .neo-toast-tr {
92
90
  right: 1rem;
91
+ left : auto;
93
92
  top : 1rem;
94
93
  }
95
94
 
@@ -107,6 +106,7 @@
107
106
  .neo-toast-br {
108
107
  bottom: 1rem;
109
108
  right : 1rem;
109
+ left : auto;
110
110
  }
111
111
 
112
112
  .neo-toast-bc {
@@ -236,12 +236,12 @@ const DefaultConfig = {
236
236
  useVdomWorker: true,
237
237
  /**
238
238
  * buildScripts/injectPackageVersion.mjs will update this value
239
- * @default '6.9.0'
239
+ * @default '6.9.2'
240
240
  * @memberOf! module:Neo
241
241
  * @name config.version
242
242
  * @type String
243
243
  */
244
- version: '6.9.0'
244
+ version: '6.9.2'
245
245
  };
246
246
 
247
247
  Object.assign(DefaultConfig, {
@@ -14,12 +14,15 @@ class Filter extends Base {
14
14
  static observable = true
15
15
  /**
16
16
  * Valid values for the operator config:<br>
17
- * ['==', '===', '!=', '!==', '<', '<=', '>', '>=', 'excluded', 'included', 'isDefined', 'isUndefined', 'like']
17
+ * ['==','===','!=','!==','<','<=','>','>=','endsWith','excluded','included','isDefined','isUndefined','like','startsWith']
18
18
  * @member {String[]} operators
19
19
  * @protected
20
20
  * @static
21
21
  */
22
- static operators = ['==', '===', '!=', '!==', '<', '<=', '>', '>=', 'excluded', 'included', 'isDefined', 'isUndefined', 'like']
22
+ static operators = [
23
+ '==', '===', '!=', '!==', '<', '<=', '>', '>=', 'endsWith', 'excluded', 'included',
24
+ 'isDefined', 'isUndefined', 'like', 'startsWith'
25
+ ]
23
26
 
24
27
  static config = {
25
28
  /**
@@ -95,7 +98,7 @@ class Filter extends Base {
95
98
  }
96
99
 
97
100
  afterSetDisabled(...args) {
98
- this.fireChangeEvent(...args);
101
+ this.fireChangeEvent(...args)
99
102
  }
100
103
 
101
104
  afterSetFilterBy(value, oldValue) {
@@ -103,28 +106,28 @@ class Filter extends Base {
103
106
  }
104
107
 
105
108
  afterSetIsUpdating(value, oldValue) {
106
- value === false && this.fireChangeEvent(value, oldValue);
109
+ value === false && this.fireChangeEvent(value, oldValue)
107
110
  }
108
111
 
109
112
  afterSetOperator(...args) {
110
- this.fireChangeEvent(...args);
113
+ this.fireChangeEvent(...args)
111
114
  }
112
115
 
113
116
  afterSetProperty(...args) {
114
- this.fireChangeEvent(...args);
117
+ this.fireChangeEvent(...args)
115
118
  }
116
119
 
117
120
  afterSetValue(...args) {
118
- this.fireChangeEvent(...args);
121
+ this.fireChangeEvent(...args)
119
122
  }
120
123
 
121
124
  beforeSetFilterBy(value, oldValue) {
122
125
  if (value && typeof value !== 'function') {
123
126
  Neo.logError('filterBy has to be a function', this);
124
- return oldValue;
127
+ return oldValue
125
128
  }
126
129
 
127
- return value;
130
+ return value
128
131
  }
129
132
 
130
133
  /**
@@ -135,7 +138,7 @@ class Filter extends Base {
135
138
  * @protected
136
139
  */
137
140
  beforeSetOperator(value, oldValue) {
138
- return this.beforeSetEnumValue(value, oldValue, 'operator');
141
+ return this.beforeSetEnumValue(value, oldValue, 'operator')
139
142
  }
140
143
 
141
144
  /**
@@ -149,10 +152,10 @@ class Filter extends Base {
149
152
  value = me.value;
150
153
 
151
154
  if (!me.filterBy) {
152
- return {operator, property, value};
155
+ return {operator, property, value}
153
156
  }
154
157
 
155
- return null;
158
+ return null
156
159
  }
157
160
 
158
161
  /**
@@ -167,7 +170,7 @@ class Filter extends Base {
167
170
  operator: me.operator,
168
171
  property: me.property,
169
172
  value : me.value
170
- });
173
+ })
171
174
  }
172
175
  }
173
176
 
@@ -184,7 +187,7 @@ class Filter extends Base {
184
187
  filterValue, recordValue;
185
188
 
186
189
  if (me._disabled) {
187
- return false;
190
+ return false
188
191
  }
189
192
 
190
193
  if (me._filterBy) {
@@ -193,11 +196,11 @@ class Filter extends Base {
193
196
  filteredItems,
194
197
  item,
195
198
  value: me._value
196
- });
199
+ })
197
200
  }
198
201
 
199
202
  if (me.includeEmptyValues && (me._value === null || Neo.isEmpty(me._value))) {
200
- return false;
203
+ return false
201
204
  }
202
205
 
203
206
  filterValue = me._value;
@@ -205,39 +208,56 @@ class Filter extends Base {
205
208
 
206
209
  if (filterValue instanceof Date && recordValue instanceof Date) {
207
210
  filterValue = filterValue.valueOf();
208
- recordValue = recordValue.valueOf();
211
+ recordValue = recordValue.valueOf()
209
212
  }
210
213
 
211
- return !Filter[me._operator](recordValue, filterValue);
214
+ return !Filter[me._operator](recordValue, filterValue)
212
215
  }
213
216
 
214
- static ['=='] (a, b) {return a == b;}
215
- static ['==='](a, b) {return a === b;}
216
- static ['!='] (a, b) {return a != b;}
217
- static ['!=='](a, b) {return a !== b;}
218
- static ['<'] (a, b) {return a < b;}
219
- static ['<='] (a, b) {return a <= b;}
220
- static ['>'] (a, b) {return a > b;}
221
- static ['>='] (a, b) {return a >= b;}
217
+ static ['=='] (a, b) {return a == b}
218
+ static ['==='](a, b) {return a === b}
219
+ static ['!='] (a, b) {return a != b}
220
+ static ['!=='](a, b) {return a !== b}
221
+ static ['<'] (a, b) {return a < b}
222
+ static ['<='] (a, b) {return a <= b}
223
+ static ['>'] (a, b) {return a > b}
224
+ static ['>='] (a, b) {return a >= b}
225
+
226
+ static ['endsWith'](a, b) {
227
+ if (!Neo.isString(a)) {a = String(a)}
228
+ if (!Neo.isString(b)) {b = String(b)}
229
+
230
+ return a?.toLowerCase().endsWith(b?.toLowerCase()) || false
231
+ }
222
232
 
223
233
  static ['excluded'](a, b) {
224
- return b.indexOf(a) < 0;
234
+ return b.indexOf(a) < 0
225
235
  }
226
236
 
227
237
  static ['included'](a, b) {
228
- return b.indexOf(a) > -1;
238
+ return b.indexOf(a) > -1
229
239
  }
230
240
 
231
241
  static ['isDefined'](a, b) {
232
- return a !== undefined;
242
+ return a !== undefined
233
243
  }
234
244
 
235
245
  static ['isUndefined'](a, b) {
236
- return a === undefined;
246
+ return a === undefined
237
247
  }
238
248
 
239
249
  static ['like'](a, b) {
240
- return a?.toLowerCase().includes(b?.toLowerCase()) || false;
250
+ if (!Neo.isString(a)) {a = String(a)}
251
+ if (!Neo.isString(b)) {b = String(b)}
252
+
253
+ return a?.toLowerCase().includes(b?.toLowerCase()) || false
254
+ }
255
+
256
+ static ['startsWith'](a, b) {
257
+ if (!Neo.isString(a)) {a = String(a)}
258
+ if (!Neo.isString(b)) {b = String(b)}
259
+
260
+ return a?.toLowerCase().startsWith(b?.toLowerCase()) || false
241
261
  }
242
262
  }
243
263
 
@@ -107,7 +107,8 @@ class Toast extends Base {
107
107
  ]},
108
108
  {cls: ['neo-toast-close', 'fa', 'fa-close'], removeDom: true}
109
109
  ]
110
- }]}
110
+ }]},
111
+ floating : true
111
112
  }
112
113
 
113
114
  /**
@@ -401,7 +401,7 @@ class Base extends Component {
401
401
  * @param {Boolean} [silent=false] true to update the vdom silently (useful for destroying multiple child items in a row)
402
402
  */
403
403
  destroy(updateParentVdom=false, silent=false) {
404
- this.items.forEach(item => {
404
+ this.items?.forEach(item => {
405
405
  item.destroy?.(false, true)
406
406
  });
407
407
 
@@ -502,8 +502,11 @@ class Base extends Component {
502
502
 
503
503
  if (config.items) {
504
504
  // If we are passed an object, merge the class's own items object into it
505
- me.items = Neo.typeOf(config.items) === 'Object' ?
506
- Neo.merge(Neo.clone(me.constructor.config.items), config.items) : config.items;
505
+ if (Neo.typeOf(config.items) === 'Object') {
506
+ me.items = Neo.merge(Neo.clone(me.constructor.config.items), config.items)
507
+ } else {
508
+ me._items = Neo.clone(config.items, true, true)
509
+ }
507
510
  delete config.items
508
511
  }
509
512
 
@@ -21,7 +21,7 @@ class Base extends CoreBase {
21
21
  /**
22
22
  * @member {Object} routes={}
23
23
  */
24
- routes: {},
24
+ routes_: {},
25
25
 
26
26
  /**
27
27
  * @member {Object} handleRoutes={}
@@ -42,20 +42,42 @@ class Base extends CoreBase {
42
42
  super.construct(config);
43
43
 
44
44
  const me = this;
45
+ HashHistory.on('change', me.onHashChange, me);
46
+ }
47
+
48
+ /**
49
+ * Triggered after the badgePosition config got changed
50
+ * @param {String} value
51
+ * @param {String} oldValue
52
+ * @protected
53
+ */
54
+ afterSetRoutes(value, oldValue){
55
+ const me = this;
56
+
57
+ const functionSort = (a,b) => {
58
+ const usedRegex = new RegExp("/", "g");
59
+ return a.match(usedRegex).length - b.match(usedRegex).length;
60
+ }
61
+ me.routes = Object.keys(value).sort(functionSort).reduce(
62
+ (obj, key) => {
63
+ obj[key] = value[key];
64
+ return obj;
65
+ },
66
+ {}
67
+ );
68
+
45
69
 
46
70
  me.handleRoutes = {};
47
71
  if (Object.keys(me.routes).length > 0) {
48
72
  Object.keys(me.routes).forEach(key => {
49
73
  if (key.toLowerCase() === 'default'){
50
- me.defaultRoute = me.routes[key];
74
+ me.defaultRoute = value[key];
51
75
  } else {
52
76
  me.handleRoutes[key] = new RegExp(key.replace(/{[^\s/]+}/g, '([\\w-]+)')+'$');
53
77
  }
54
78
 
55
79
  });
56
80
  }
57
-
58
- HashHistory.on('change', me.onHashChange, me);
59
81
  }
60
82
 
61
83
  /**
@@ -85,13 +107,26 @@ class Base extends CoreBase {
85
107
 
86
108
  const me = this;
87
109
  let hasRouteBeenFound = false;
88
- Object.keys(me.handleRoutes).every( key => {
110
+ Object.keys(me.handleRoutes).forEach( key => {
89
111
  let preHandler = undefined;
90
112
  let executeHandler = undefined;
91
113
  let responsePreHandler = undefined;
92
114
 
93
115
  const result = value.hashString.match(me.handleRoutes[key]);
94
116
  if (result){
117
+
118
+ const paramsRegex = /{[^\s/]+}/g;
119
+ const arrayParamIds = key.match(paramsRegex);
120
+ const arrayParamValues = result.splice(1,result.length - 1);
121
+ if (arrayParamIds && arrayParamIds.length !== arrayParamValues.length){
122
+ throw "Number of IDs and number of Values do not match";
123
+ }
124
+
125
+ const paramObject = {};
126
+ for(let i=0; arrayParamIds && i< arrayParamIds.length; i++){
127
+ paramObject[ arrayParamIds[i].substring(1,arrayParamIds[i].length -1)] = arrayParamValues[i];
128
+ }
129
+
95
130
  const target = me.routes[key];
96
131
  if (Neo.isString(target)){
97
132
  executeHandler = this.routes[key];
@@ -100,20 +135,20 @@ class Base extends CoreBase {
100
135
  if (Neo.isObject(target)){
101
136
  executeHandler = this.routes[key].handler;
102
137
  preHandler = this.routes[key].preHandler;
103
- responsePreHandler = preHandler ? me[preHandler]?.call(this, value, oldValue, result.splice(1,result.length - 1)) : true;
138
+ if (preHandler) {
139
+ responsePreHandler = me[preHandler]?.call(this, value, oldValue, paramObject);
140
+ } else {
141
+ responsePreHandler = true;
142
+ console.warn('No preHandler defined for routes -> todo it better');
143
+ }
104
144
  }
105
145
 
106
146
  hasRouteBeenFound = true;
107
147
 
108
148
  if (responsePreHandler) {
109
- this[executeHandler]?.call(this, value, oldValue, result.splice(1,result.length - 1));
110
- } else {
111
- console.warn('No preHandler defined for routes -> todo it better');
149
+ this[executeHandler]?.call(this, value, oldValue, paramObject);
112
150
  }
113
- return false;
114
-
115
151
  }
116
- return true;
117
152
  });
118
153
 
119
154
  if (Object.keys(me.handleRoutes).length > 0 && !hasRouteBeenFound) {
@@ -285,7 +285,7 @@ class FileUpload extends Base {
285
285
  documentDeleteMethod: 'DELETE',
286
286
 
287
287
  /**
288
- * @member {String} state_=null
288
+ * @member {String} state_=ready
289
289
  */
290
290
  state_: 'ready',
291
291
 
@@ -729,7 +729,7 @@ class FileUpload extends Base {
729
729
  isChangeEventNeeded = true;
730
730
  }
731
731
 
732
- if (isChangeEventNeeded) {
732
+ if (isChangeEventNeeded && oldValue !== undefined) {
733
733
  me.fireChangeEvent(me.file)
734
734
  }
735
735
  me.validate();
@@ -38,6 +38,10 @@ class Select extends Picker {
38
38
  * @member {String} displayField='name'
39
39
  */
40
40
  displayField: 'name',
41
+ /**
42
+ * @member {String} filterOperator_='like'
43
+ */
44
+ filterOperator_: 'like',
41
45
  /**
42
46
  * True will only fire a change event, in case the TextField input value matches a record.
43
47
  * onFocusLeave() will try to select a hint record, if needed and possible.
@@ -103,6 +107,11 @@ class Select extends Picker {
103
107
  * @member {Boolean} typeAhead_=true
104
108
  */
105
109
  typeAhead_: true,
110
+ /**
111
+ * Set this config to false, in case typing into the input field should not filter list items
112
+ * @member {Boolean} useFilter_=true
113
+ */
114
+ useFilter_: true,
106
115
  /**
107
116
  * This config should point to the store keyProperty or a different model field,
108
117
  * which you want to submit instead
@@ -111,16 +120,6 @@ class Select extends Picker {
111
120
  valueField: 'id'
112
121
  }
113
122
 
114
- /**
115
- * @member {String} filterOperator='like'
116
- */
117
- filterOperator = 'like'
118
- /**
119
- * Set this config to false, in case typing into the input field should not filter list items
120
- * @member {Boolean} useFilter=true
121
- */
122
- useFilter = true
123
-
124
123
  /**
125
124
  * @param {Object} config
126
125
  */
@@ -367,8 +367,9 @@ class Text extends Base {
367
367
  { cls } = me;
368
368
 
369
369
  NeoArray.toggle(cls, 'neo-not-editable', !value);
370
- me.cls = cls
371
- me.changeInputElKey('readonly', value ? false : true);
370
+ me.cls = cls;
371
+
372
+ me.updateReadOnlyState()
372
373
  }
373
374
 
374
375
  /**
@@ -675,7 +676,7 @@ class Text extends Base {
675
676
  NeoArray[value ? 'add' : 'remove'](cls, 'neo-readonly');
676
677
  me.cls = cls;
677
678
 
678
- me.changeInputElKey('readonly', value ? value : null);
679
+ me.updateReadOnlyState();
679
680
 
680
681
  me.triggers?.forEach(trigger => {
681
682
  trigger.hidden = value ? true : trigger.getHiddenState?.() || false
@@ -1440,6 +1441,15 @@ class Text extends Base {
1440
1441
  me.update()
1441
1442
  }
1442
1443
 
1444
+ /**
1445
+ * The DOM based readonly attribute needs to honor the editable & readOnly configs
1446
+ */
1447
+ updateReadOnlyState() {
1448
+ let me = this;
1449
+
1450
+ me.changeInputElKey('readonly', !me.editable || me.readOnly || null);
1451
+ }
1452
+
1443
1453
  /**
1444
1454
  * Since triggers do not get rendered, assign the relevant props
1445
1455
  * todo: this could be handled by component.Base
@@ -34,14 +34,7 @@ class Picker extends Base {
34
34
  */
35
35
  onTriggerClick(data) {
36
36
  this.field.onPickerTriggerClick();
37
- }
38
-
39
- /**
40
- * @returns {Boolean} true in case the trigger should be hidden
41
- */
42
- getHiddenState() {
43
- return !this.field.editable;
44
- }
37
+ }
45
38
  }
46
39
 
47
40
  Neo.applyClassConfig(Picker);
@@ -420,13 +420,11 @@ class DomEvents extends Base {
420
420
  onChange(event) {
421
421
  let me = this,
422
422
  target = event.target,
423
- tagName = target.tagName,
424
- value = target.value,
425
423
 
426
424
  data = {
427
425
  ...me.getEventData(event),
428
426
  valid: target.checkValidity(),
429
- value: tagName === 'INPUT' ? StringUtil.escapeHtml(value) : tagName === 'TEXTAREA' ? me.stripHtml(value) : value
427
+ value: target.value
430
428
  };
431
429
 
432
430
  // input and change events can pass a FileList for input type file
@@ -196,7 +196,9 @@ class DeltaUpdates extends Base {
196
196
  node = me.getElementOrBody(delta.id);
197
197
 
198
198
  if (!node) {
199
- console.warn('du_updateNode: node not found for id', delta.id)
199
+ if (Neo.config.environment === 'development') {
200
+ console.warn('du_updateNode: node not found for id', delta.id)
201
+ }
200
202
  } else {
201
203
  Object.entries(delta).forEach(([prop, value]) => {
202
204
  switch(prop) {
@@ -432,34 +432,44 @@ class DomEvent extends Base {
432
432
  * @returns {Boolean|String} true in case the delegation string matches the event path
433
433
  */
434
434
  verifyDelegationPath(listener, path) {
435
- let delegationArray = listener.delegate.split(' '),
436
- j = 0,
437
- len = delegationArray.length,
438
- pathLen = path.length,
439
- hasMatch, i, item, isId, targetId;
440
-
441
- for (i=len-1; i >= 0; i--) {
442
- hasMatch = false;
443
- item = delegationArray[i];
444
- isId = item.startsWith('#');
445
-
446
- if (isId || item.startsWith('.')) {
447
- item = item.substr(1);
435
+ const { delegate } = listener;
436
+
437
+ let j = 0, pathLen = path.length, targetId;
438
+
439
+ if (typeof delegate === 'function') {
440
+ j = delegate(path);
441
+ if (j != null) {
442
+ targetId = path[j].id;
448
443
  }
444
+ }
445
+ else {
446
+ let delegationArray = delegate.split(' '),
447
+ len = delegationArray.length,
448
+ hasMatch, i, item, isId;
449
+
450
+ for (i=len-1; i >= 0; i--) {
451
+ hasMatch = false;
452
+ item = delegationArray[i];
453
+ isId = item.startsWith('#');
454
+
455
+ if (isId || item.startsWith('.')) {
456
+ item = item.substr(1);
457
+ }
449
458
 
450
- for (; j < pathLen; j++) {
451
- if (
452
- (isId && path[j].id === item) ||
453
- path[j].cls.includes(item)
454
- ) {
455
- hasMatch = true;
456
- targetId = path[j].id;
457
- break
459
+ for (; j < pathLen; j++) {
460
+ if (
461
+ (isId && path[j].id === item) ||
462
+ path[j].cls.includes(item)
463
+ ) {
464
+ hasMatch = true;
465
+ targetId = path[j].id;
466
+ break
467
+ }
458
468
  }
459
- }
460
469
 
461
- if (!hasMatch) {
462
- return false
470
+ if (!hasMatch) {
471
+ return false
472
+ }
463
473
  }
464
474
  }
465
475
 
@@ -237,7 +237,7 @@ class View extends Component {
237
237
  me.promiseUpdate().then(() => {
238
238
  if (selectedRows?.length > 0) {
239
239
  // this logic only works for selection.table.RowModel
240
- Neo.main.DomAccess.scrollToTableRow({id: selectedRows[0]});
240
+ Neo.main.DomAccess.scrollToTableRow({appName: me.appName, id: selectedRows[0]})
241
241
  }
242
242
  })
243
243
  }
@@ -196,17 +196,18 @@ class Base extends Container {
196
196
  componentId : app.mainView.id,
197
197
  resetCfg : {},
198
198
  isShared : true,
199
- delegate : '.neo-uses-shared-tooltip',
199
+ delegate : this.delegateFilter,
200
200
  listeners : {
201
201
  // Reconfigure on over a target
202
- async targetOver({ target }) {
202
+ async targetOver({ target, data }) {
203
203
  // Revert last pointerOver config set to initial setting.
204
204
  this.set(this.resetCfg);
205
205
  this.resetCfg = {};
206
206
 
207
207
  // Use the tooltip config block that the target was configured with
208
- // to reconfogure tis instance
209
- const config = target?._tooltip;
208
+ // to reconfigure this instance, or if there was none, check the
209
+ // data-neo-tooltip property for a text string.
210
+ const config = target?._tooltip || { text : data.target.data.neoTooltip };
210
211
 
211
212
  // Cache things we have to reset
212
213
  for (const key in config) {
@@ -264,7 +265,8 @@ class Base extends Container {
264
265
  me.align.targetMargin = 10;
265
266
 
266
267
  me.fire('targetOver', {
267
- target : me.activeTarget
268
+ target : me.activeTarget,
269
+ data
268
270
  });
269
271
 
270
272
  // Still visible, just realign
@@ -278,6 +280,15 @@ class Base extends Container {
278
280
  }
279
281
  }
280
282
 
283
+ // Used as a delegate filter to activate on targets which have a tooltip configuration
284
+ static delegateFilter(path) {
285
+ for (let i = 0, { length } = path; i < length; i++) {
286
+ if (path[i].cls.includes('neo-uses-shared-tooltip') || path[i].data['neoTooltip']) {
287
+ return i;
288
+ }
289
+ }
290
+ }
291
+
281
292
  /**
282
293
  * @param {Object} data
283
294
  */
@@ -287,7 +298,8 @@ class Base extends Container {
287
298
  // If it's an internal move within the delegate, do nothing
288
299
  if (data.currentTarget === me.activeTarget?.id) {
289
300
  me.fire('targetOut', {
290
- target : this.activeTarget
301
+ target : me.activeTarget,
302
+ data
291
303
  });
292
304
  me.activeTarget = null;
293
305
  me.hideDelayed(data);
@@ -750,6 +750,13 @@ class Helper extends Base {
750
750
  node.className = value;
751
751
  }
752
752
  break;
753
+ case 'data':
754
+ if (value && Neo.typeOf(value) === 'Object') {
755
+ Object.entries(value).forEach(([key, val]) => {
756
+ node.attributes[`data-${Neo.decamel(key)}`] = val
757
+ })
758
+ }
759
+ break;
753
760
  case 'height':
754
761
  case 'maxHeight':
755
762
  case 'maxWidth':
@@ -134,6 +134,10 @@ class App extends Base {
134
134
  }
135
135
  }
136
136
  } else {
137
+ // default parentId='document.body' => we want it to get shown
138
+ config.autoMount = true;
139
+ config.autoRender = true;
140
+
137
141
  instance = Neo[config.ntype ? 'ntype' : 'create'](config)
138
142
  }
139
143
 
@@ -0,0 +1,8 @@
1
+ // Important: You need to import all classes which you want to use inside tests here
2
+ // (excluding base classes (prototypes) of already imported classes)
3
+
4
+ import Button from '../../src/button/Base.mjs';
5
+ import DateSelector from '../../src/component/DateSelector.mjs';
6
+ import SelectField from '../../src/form/field/Select.mjs';
7
+
8
+ export const onStart = () => Neo.app({name: 'AppEmpty'})
@@ -0,0 +1,17 @@
1
+ StartTest(t => {
2
+ t.it('Checking if neo.mjs got started', async t => {
3
+ if (!globalThis.Neo?.Main) {
4
+ console.log('Starting the neo.mjs workers setup');
5
+
6
+ await import('../../../../src/MicroLoader.mjs');
7
+ }
8
+
9
+ setTimeout(() => {
10
+ Neo.worker.App.createNeoInstance({
11
+ ntype : 'button',
12
+ iconCls: 'fa fa-home',
13
+ text : 'Hello Siesta'
14
+ })
15
+ }, 300)
16
+ });
17
+ });
@@ -0,0 +1,18 @@
1
+ StartTest(t => {
2
+ t.it('Checking if neo.mjs got started', async t => {
3
+ if (!globalThis.Neo?.Main) {
4
+ console.log('Starting the neo.mjs workers setup');
5
+
6
+ await import('../../../../src/MicroLoader.mjs');
7
+ }
8
+
9
+ setTimeout(() => {
10
+ Neo.worker.App.createNeoInstance({
11
+ ntype : 'dateselector',
12
+ height: 250,
13
+ value : '2023-10-15',
14
+ width : 300
15
+ })
16
+ }, 300)
17
+ });
18
+ });
@@ -0,0 +1,35 @@
1
+ StartTest(t => {
2
+ t.it('Checking if neo.mjs got started', async t => {
3
+ if (!globalThis.Neo?.Main) {
4
+ console.log('Starting the neo.mjs workers setup');
5
+
6
+ await import('../../../../../src/MicroLoader.mjs');
7
+ }
8
+
9
+ setTimeout(() => {
10
+ Neo.worker.App.createNeoInstance({
11
+ ntype : 'selectfield',
12
+ labelPosition: 'inline',
13
+ labelText : 'US States',
14
+ labelWidth : 80,
15
+ width : 300,
16
+
17
+ store : {
18
+ autoLoad : true,
19
+ keyProperty: 'abbreviation',
20
+ url : '../../resources/examples/data/us_states.json',
21
+
22
+ model: {
23
+ fields: [{
24
+ name: 'abbreviation',
25
+ type: 'string'
26
+ }, {
27
+ name: 'name',
28
+ type: 'string'
29
+ }]
30
+ }
31
+ }
32
+ })
33
+ }, 300)
34
+ });
35
+ });
@@ -0,0 +1,17 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
6
+
7
+ <!-- Web interface -->
8
+ <link rel="stylesheet" type="text/css" href="../../node_modules/siesta-lite/resources/css/siesta-all.css">
9
+ <script type="text/javascript" src="../../node_modules/siesta-lite/siesta-all.js"></script>
10
+
11
+ <!-- Project file -->
12
+ <script type="text/javascript" src="siesta.js"></script>
13
+ </head>
14
+
15
+ <body>
16
+ </body>
17
+ </html>
@@ -0,0 +1,6 @@
1
+ {
2
+ "appPath" : "test/components/app.mjs",
3
+ "basePath" : "../../",
4
+ "environment": "development",
5
+ "mainPath" : "./Main.mjs"
6
+ }
@@ -0,0 +1,34 @@
1
+ const project = new Siesta.Project.Browser();
2
+
3
+ project.configure({
4
+ title : 'Neo Component Tests',
5
+ isEcmaModule: true
6
+ });
7
+
8
+ project.plan(
9
+ {
10
+ group: 'button',
11
+ items: [
12
+ 'files/button/Base.mjs'
13
+ ]
14
+ },
15
+ {
16
+ group: 'component',
17
+ items: [
18
+ 'files/component/DateSelector.mjs'
19
+ ]
20
+ },
21
+ {
22
+ group: 'form',
23
+ items: [
24
+ {
25
+ group: 'field',
26
+ items: [
27
+ 'files/form/field/Select.mjs'
28
+ ]
29
+ }
30
+ ]
31
+ }
32
+ );
33
+
34
+ project.start();