neo.mjs 6.8.3 → 6.9.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 (50) hide show
  1. package/apps/ServiceWorker.mjs +2 -2
  2. package/apps/learnneo/view/home/MainContainer.mjs +4 -3
  3. package/apps/learnneo/view/home/MainContainerController.mjs +24 -2
  4. package/apps/route/app.mjs +6 -0
  5. package/apps/route/index.html +11 -0
  6. package/apps/route/neo-config.json +6 -0
  7. package/apps/route/view/ButtonBar.mjs +57 -0
  8. package/apps/route/view/CenterContainer.mjs +37 -0
  9. package/apps/route/view/FooterContainer.mjs +47 -0
  10. package/apps/route/view/HeaderContainer.mjs +47 -0
  11. package/apps/route/view/MainView.mjs +66 -0
  12. package/apps/route/view/MainViewController.mjs +210 -0
  13. package/apps/route/view/MetaContainer.mjs +52 -0
  14. package/apps/route/view/Viewport.mjs +15 -0
  15. package/apps/route/view/center/CardAdministration.mjs +36 -0
  16. package/apps/route/view/center/CardAdministrationDenied.mjs +26 -0
  17. package/apps/route/view/center/CardContact.mjs +29 -0
  18. package/apps/route/view/center/CardHome.mjs +26 -0
  19. package/apps/route/view/center/CardSection1.mjs +26 -0
  20. package/apps/route/view/center/CardSection2.mjs +27 -0
  21. package/examples/ConfigurationViewport.mjs +1 -1
  22. package/examples/ServiceWorker.mjs +2 -2
  23. package/examples/form/field/select/MainContainer.mjs +1 -2
  24. package/examples/table/container/MainContainer.mjs +4 -2
  25. package/examples/table/container/MainModel.mjs +3 -0
  26. package/examples/table/container/MainStore.mjs +10 -10
  27. package/examples/toolbar/paging/view/MainContainer.mjs +31 -3
  28. package/package.json +1 -1
  29. package/resources/data/learnneo/content.json +9 -9
  30. package/resources/data/learnneo/pages/whyneo.md +76 -0
  31. package/resources/scss/src/apps/route/CenterContainer.scss +29 -0
  32. package/resources/scss/src/apps/route/HeaderContainer.scss +122 -0
  33. package/resources/scss/src/apps/route/MainView.scss +3 -0
  34. package/resources/scss/src/apps/route/MetaContainer.scss +44 -0
  35. package/resources/scss/src/apps/route/_all.scss +1 -0
  36. package/src/DefaultConfig.mjs +2 -2
  37. package/src/Neo.mjs +15 -14
  38. package/src/button/Base.mjs +2 -2
  39. package/src/component/Base.mjs +41 -50
  40. package/src/container/Base.mjs +59 -2
  41. package/src/controller/Base.mjs +84 -4
  42. package/src/controller/Component.mjs +22 -7
  43. package/src/core/Observable.mjs +50 -9
  44. package/src/form/field/Range.mjs +8 -0
  45. package/src/main/DomEvents.mjs +9 -3
  46. package/src/manager/DomEvent.mjs +3 -0
  47. package/src/menu/List.mjs +1 -1
  48. package/src/table/View.mjs +78 -53
  49. package/src/toolbar/Paging.mjs +68 -76
  50. package/src/tooltip/Base.mjs +111 -11
@@ -51,7 +51,7 @@ class Base extends Component {
51
51
  editRoute: true,
52
52
  /**
53
53
  * Shortcut for domListeners={click:handler}
54
- * A string based value assumes that the handlerFn lives inside a ComponentController
54
+ * A string based value assumes that the handlerFn lives inside a controller.Component
55
55
  * @member {Function|String|null} handler_=null
56
56
  */
57
57
  handler_: null,
@@ -487,7 +487,7 @@ class Base extends Component {
487
487
  onClick(data) {
488
488
  let me = this;
489
489
 
490
- me.handler?.call(me.handlerScope || me, data);
490
+ me.callback(me.handler, me.handlerScope || me, [data]);
491
491
 
492
492
  me.menu && me.toggleMenu();
493
493
  me.route && me.changeRoute();
@@ -302,11 +302,17 @@ class Base extends CoreBase {
302
302
  */
303
303
  theme_: null,
304
304
  /**
305
- * Add tooltip config objects
305
+ * Add tooltip config object or a string containing the display text
306
306
  * See tooltip/Base.mjs
307
- * @member {Array|Object} tooltips_=null
307
+ *
308
+ * By default, a single, shared Tooltip instance is used for all widgets which request
309
+ * a tooltip. It reconfigures itself from the widget's definition just before showing.
310
+ *
311
+ * If a widget needs its own instance for any reason, inslude the property `ownInstance : true`
312
+ * in the tooltip config object.
313
+ * @member {Object|String} tooltip_=null
308
314
  */
309
- tooltips_: null,
315
+ tooltip_: null,
310
316
  /**
311
317
  * Add 'primary' and other attributes to make it an outstanding design
312
318
  * @member {String|null} ui_=null
@@ -771,21 +777,21 @@ class Base extends CoreBase {
771
777
  }
772
778
 
773
779
  /**
774
- * Triggered after the tooltips config got changed
775
- * @param {Boolean} value
776
- * @param {Boolean} oldValue
780
+ * Triggered after the tooltip config got changed
781
+ * @param {Object|String} value
782
+ * @param {Object|String} oldValue
777
783
  * @protected
778
784
  */
779
- afterSetTooltips(value, oldValue) {
780
- if (value) {
781
- let me = this;
785
+ afterSetTooltip(value, oldValue) {
786
+ oldValue?.destroy();
782
787
 
788
+ if (value) {
783
789
  if (Neo.ns('Neo.tooltip.Base')) {
784
- me.createTooltips(value)
790
+ this.createTooltip(value);
785
791
  } else {
786
- import('../tooltip/Base.mjs').then((module) => {
787
- me.createTooltips(value)
788
- })
792
+ import('../tooltip/Base.mjs').then(() => {
793
+ this.createTooltip(value);
794
+ });
789
795
  }
790
796
  }
791
797
  }
@@ -1129,24 +1135,6 @@ class Base extends CoreBase {
1129
1135
  return (Neo.isNumber(oldValue) && oldValue > 0) ? (oldValue - 1) : 0
1130
1136
  }
1131
1137
 
1132
- /**
1133
- * Triggered before the style config gets changed.
1134
- * @param {Object} value
1135
- * @param {Object} oldValue
1136
- * @returns {Object}
1137
- * @protected
1138
- */
1139
- beforeSetStyle(value, oldValue) {
1140
- let me = this;
1141
-
1142
- if (typeof value === 'object') {
1143
- // merge the incoming style specification into the configured default
1144
- value = Neo.merge(Neo.merge({}, me.constructor.config.style), value)
1145
- }
1146
-
1147
- return value
1148
- }
1149
-
1150
1138
  /**
1151
1139
  * Changes the value of a vdom object attribute or removes it in case it has no value
1152
1140
  * @param {String} key
@@ -1167,31 +1155,31 @@ class Base extends CoreBase {
1167
1155
 
1168
1156
  /**
1169
1157
  * Creates the tooltip instances
1170
- * @param {Array|Object} value
1158
+ * @param {Object|String} value
1171
1159
  * @protected
1172
1160
  */
1173
- createTooltips(value) {
1174
- if (!Array.isArray(value)) {
1175
- value = [value];
1161
+ createTooltip(value) {
1162
+ if (typeof value === 'string') {
1163
+ value = {
1164
+ text : value
1165
+ };
1176
1166
  }
1177
1167
 
1178
- let me = this,
1179
- tooltips = [],
1180
- tip;
1181
-
1182
- value.forEach(item => {
1183
- // todo: check for existing tooltips
1168
+ let me = this;
1184
1169
 
1185
- tip = Neo.create('Neo.tooltip.Base', {
1170
+ if (value.ownInstance) {
1171
+ me._tooltip = Neo.create('Neo.tooltip.Base', {
1172
+ ...value,
1186
1173
  appName : me.appName,
1187
- componentId: me.id,
1188
- ...item
1174
+ componentId: me.id
1189
1175
  });
1190
-
1191
- tooltips.push(tip)
1192
- });
1193
-
1194
- me._tooltips = tooltips // silent update
1176
+ }
1177
+ else {
1178
+ me._tooltip = value;
1179
+ Neo.tooltip.Base.createSingleton(me.app);
1180
+ me.addCls('neo-uses-shared-tooltip');
1181
+ me.update();
1182
+ }
1195
1183
  }
1196
1184
 
1197
1185
  /**
@@ -1665,7 +1653,10 @@ class Base extends CoreBase {
1665
1653
  // does not clone existing Neo instances
1666
1654
  me._vdom = Neo.clone(vdom, true, true);
1667
1655
 
1668
- me[Neo.isEmpty(config.style) ? '_style' : 'style'] = config.style;
1656
+ if (config.style) {
1657
+ // If we are passed an object, merge it with the class's own style
1658
+ me.style = Neo.typeOf(config.style) === 'Object' ? { ...config.style, ...me.constructor.config.style } : config.style;
1659
+ }
1669
1660
 
1670
1661
  me.wrapperStyle = Neo.clone(config.wrapperStyle, false);
1671
1662
 
@@ -8,6 +8,8 @@ import LayoutVBox from '../layout/VBox.mjs';
8
8
  import Logger from '../util/Logger.mjs';
9
9
  import NeoArray from '../util/Array.mjs';
10
10
 
11
+ const byWeight = ({ weight : lhs = 0 }, { weight : rhs = 0 }) => lhs - rhs;
12
+
11
13
  /**
12
14
  * @class Neo.container.Base
13
15
  * @extends Neo.component.Base
@@ -33,10 +35,33 @@ class Base extends Component {
33
35
  */
34
36
  itemDefaults_: null,
35
37
  /**
36
- * An array of config objects|instances|modules for each child component
38
+ * An array or an object of config objects|instances|modules for each child component
37
39
  * @member {Object[]} items_=[]
38
40
  * @example
39
41
  * import Button from '../button/Base.mjs';
42
+ * import Toolbar from '../toolbar/Base.mjs';
43
+ *
44
+ * let myButton = Neo.create(Button, {
45
+ * text: 'Button1'
46
+ * });
47
+ *
48
+ * Neo.create(Toolbar, {
49
+ * //...
50
+ * items: {
51
+ * buttonRef : {
52
+ * ntype: 'button', // by ntype
53
+ * text : 'Button 2'
54
+ * },
55
+ * secondRef : {
56
+ * module: Button, // by imported module
57
+ * text : 'Button 3'
58
+ * }
59
+ * }
60
+ * });
61
+ *
62
+ * or
63
+ * @example
64
+ * import Button from '../button/Base.mjs';
40
65
  * import MyRedButton from 'myapp/MyRedButton.mjs';
41
66
  * import Toolbar from '../toolbar/Base.mjs';
42
67
  *
@@ -192,6 +217,36 @@ class Base extends Component {
192
217
  }
193
218
  }
194
219
 
220
+ /**
221
+ * Convert items object to an array for onward storage as _items
222
+ * @param {Object|Object[]} value
223
+ * @param {Object|Object[]} oldValue
224
+ * @returns {Object[]}
225
+ * @protected
226
+ */
227
+ beforeSetItems(value, oldValue) {
228
+ if (Neo.typeOf(value) === 'Object') {
229
+ const result = [];
230
+
231
+ let hasWeight;
232
+
233
+ for (const ref in value) {
234
+ const item = value[ref]
235
+
236
+ item.reference = ref;
237
+ result.push(item);
238
+ hasWeight ||= ('weight' in item);
239
+ }
240
+
241
+ if (hasWeight) {
242
+ result.sort(byWeight);
243
+ }
244
+ value = result;
245
+ }
246
+
247
+ return value;
248
+ }
249
+
195
250
  /**
196
251
  * @param {Object|String} value
197
252
  * @param {Object|String|Neo.layout.Base} oldValue
@@ -446,7 +501,9 @@ class Base extends Component {
446
501
  }
447
502
 
448
503
  if (config.items) {
449
- me._items = Neo.clone(config.items, true, true);
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;
450
507
  delete config.items
451
508
  }
452
509
 
@@ -16,7 +16,23 @@ class Base extends CoreBase {
16
16
  * @member {String} ntype='controller'
17
17
  * @protected
18
18
  */
19
- ntype: 'controller'
19
+ ntype: 'controller',
20
+
21
+ /**
22
+ * @member {Object} routes={}
23
+ */
24
+ routes: {},
25
+
26
+ /**
27
+ * @member {Object} handleRoutes={}
28
+ */
29
+ handleRoutes: {},
30
+
31
+ /**
32
+ * @member {String} defaultRoute=undefined
33
+ */
34
+ defaultRoute: null
35
+
20
36
  }
21
37
 
22
38
  /**
@@ -25,7 +41,21 @@ class Base extends CoreBase {
25
41
  construct(config) {
26
42
  super.construct(config);
27
43
 
28
- HashHistory.on('change', this.onHashChange, this);
44
+ const me = this;
45
+
46
+ me.handleRoutes = {};
47
+ if (Object.keys(me.routes).length > 0) {
48
+ Object.keys(me.routes).forEach(key => {
49
+ if (key.toLowerCase() === 'default'){
50
+ me.defaultRoute = me.routes[key];
51
+ } else {
52
+ me.handleRoutes[key] = new RegExp(key.replace(/{[^\s/]+}/g, '([\\w-]+)')+'$');
53
+ }
54
+
55
+ });
56
+ }
57
+
58
+ HashHistory.on('change', me.onHashChange, me);
29
59
  }
30
60
 
31
61
  /**
@@ -37,6 +67,15 @@ class Base extends CoreBase {
37
67
  super.destroy(...args);
38
68
  }
39
69
 
70
+ /**
71
+ * Placeholder method which gets triggered when an invalid route is called
72
+ * @param {Object} value
73
+ * @param {Object} oldValue
74
+ */
75
+ onNoRouteFound(value, oldValue) {
76
+
77
+ }
78
+
40
79
  /**
41
80
  * Placeholder method which gets triggered when the hash inside the browser url changes
42
81
  * @param {Object} value
@@ -44,6 +83,46 @@ class Base extends CoreBase {
44
83
  */
45
84
  onHashChange(value, oldValue) {
46
85
 
86
+ const me = this;
87
+ let hasRouteBeenFound = false;
88
+ Object.keys(me.handleRoutes).every( key => {
89
+ let preHandler = undefined;
90
+ let executeHandler = undefined;
91
+ let responsePreHandler = undefined;
92
+
93
+ const result = value.hashString.match(me.handleRoutes[key]);
94
+ if (result){
95
+ const target = me.routes[key];
96
+ if (Neo.isString(target)){
97
+ executeHandler = this.routes[key];
98
+ responsePreHandler = true;
99
+ }
100
+ if (Neo.isObject(target)){
101
+ executeHandler = this.routes[key].handler;
102
+ preHandler = this.routes[key].preHandler;
103
+ responsePreHandler = preHandler ? me[preHandler]?.call(this, value, oldValue, result.splice(1,result.length - 1)) : true;
104
+ }
105
+
106
+ hasRouteBeenFound = true;
107
+
108
+ 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');
112
+ }
113
+ return false;
114
+
115
+ }
116
+ return true;
117
+ });
118
+
119
+ if (Object.keys(me.handleRoutes).length > 0 && !hasRouteBeenFound) {
120
+ if (me.defaultRoute) {
121
+ this[me.defaultRoute]?.call(this, value, oldValue);
122
+ } else {
123
+ this.onNoRouteFound(value, oldValue);
124
+ }
125
+ }
47
126
  }
48
127
 
49
128
  /**
@@ -53,9 +132,10 @@ class Base extends CoreBase {
53
132
  let currentHash = HashHistory.first();
54
133
 
55
134
  currentHash && this.onHashChange(currentHash, null);
56
-
57
- super.onConstructed();
58
135
  }
136
+
137
+
138
+
59
139
  }
60
140
 
61
141
  Neo.applyClassConfig(Base);
@@ -69,10 +69,22 @@ class Component extends Base {
69
69
  * @param {String} handlerName
70
70
  * @returns {Neo.controller.Component|null}
71
71
  */
72
- getHandlerScope(handlerName) {
72
+ getHandlerScope(handlerName, component) {
73
73
  let me = this,
74
74
  parent = me.parent;
75
75
 
76
+ if (component) {
77
+ // Look for ths function *name* first in the Component itself.
78
+ // If we find it, return true so calling code knows not to continue to search.
79
+ const handlerCb = component.resolveCallback(handlerName, component);
80
+
81
+ // Handler fn is resolved in the Component or its own parent chain.
82
+ // Return a status indicating that we do not need an erly binding
83
+ if (handlerCb.fn) {
84
+ return true;
85
+ }
86
+ }
87
+
76
88
  return Neo.isFunction(me[handlerName]) ?
77
89
  me : parent ?
78
90
  parent.getHandlerScope(handlerName) : null;
@@ -140,9 +152,12 @@ class Component extends Base {
140
152
  eventHandler, handlerScope;
141
153
 
142
154
  if (handler && typeof handler === 'string') {
143
- handlerScope = me.getHandlerScope(handler);
155
+ handlerScope = me.getHandlerScope(handler, component);
144
156
 
145
- component.handler = handlerScope[handler].bind(component.handlerScope || handlerScope)
157
+ // If the handler name was not resolved in the Component itself, bind it
158
+ if (handlerScope !== true) {
159
+ component.handler = handlerScope[handler].bind(component.handlerScope || handlerScope);
160
+ }
146
161
  }
147
162
 
148
163
  if (listeners) {
@@ -150,11 +165,11 @@ class Component extends Base {
150
165
  if (key !== 'scope' && key !== 'delegate') {
151
166
  if (Neo.isString(value)) {
152
167
  eventHandler = value;
153
- handlerScope = me.getHandlerScope(eventHandler);
168
+ handlerScope = me.getHandlerScope(eventHandler, component);
154
169
 
155
170
  if (!handlerScope) {
156
171
  Logger.logError('Unknown event handler for', eventHandler, component)
157
- } else {
172
+ } else if (handlerScope !== true) {
158
173
  listeners[key] = {};
159
174
  listeners[key].fn = handlerScope[eventHandler].bind(handlerScope)
160
175
  }
@@ -162,11 +177,11 @@ class Component extends Base {
162
177
  value.forEach(listener => {
163
178
  if (Neo.isObject(listener) && listener.hasOwnProperty('fn') && Neo.isString(listener.fn)) {
164
179
  eventHandler = listener.fn;
165
- handlerScope = me.getHandlerScope(eventHandler);
180
+ handlerScope = me.getHandlerScope(eventHandler, component);
166
181
 
167
182
  if (!handlerScope) {
168
183
  Logger.logError('Unknown event handler for', eventHandler, component)
169
- } else {
184
+ } else if (handlerScope !== true) {
170
185
  listener.fn = handlerScope[eventHandler].bind(handlerScope)
171
186
  }
172
187
  }
@@ -98,31 +98,50 @@ class Observable extends Base {
98
98
  let me = this,
99
99
  args = [].slice.call(arguments, 1),
100
100
  listeners = me.listeners,
101
- eventConfig, events, i, len;
101
+ handler, handlers, i, len;
102
102
 
103
103
  if (listeners && listeners[name]) {
104
- events = [...listeners[name]];
105
- len = events.length;
104
+ handlers = [...listeners[name]];
105
+ len = handlers.length;
106
106
 
107
107
  for (i = 0; i < len; i++) {
108
- eventConfig = events[i];
108
+ handler = handlers[i];
109
109
 
110
- if (!Neo.isFunction(eventConfig.fn)) {
111
- eventConfig.fn = eventConfig.scope[eventConfig.fn];
112
- }
110
+ // Resolve function name on the scope (oe me), or, if it starts with 'up.'
111
+ // look in the ownership hierarchy from me.
112
+ const cb = me.resolveCallback(handler.fn, handler.scope || me);
113
113
 
114
114
  // remove the listener, in case the scope no longer exists
115
- if (eventConfig.scope && !eventConfig.scope.id) {
115
+ if (cb.scope && !cb.scope.id) {
116
116
  listeners[name].splice(i, 1);
117
117
  } else {
118
118
  if (!me.suspendEvents) {
119
- eventConfig.fn.apply(eventConfig.scope || me, eventConfig.data ? args.concat(eventConfig.data) : args);
119
+ // Object event format. Inject firer reference in as 'source'
120
+ if (args.length === 1 && typeof(args[0]) === 'object') {
121
+ args[0].source = me.id;
122
+ }
123
+ cb.fn.apply(cb.scope, handler.data ? args.concat(handler.data) : args);
120
124
  }
121
125
  }
122
126
  }
123
127
  }
124
128
  }
125
129
 
130
+ /**
131
+ * Call the passed function, or a function by *name* which exists in the passed scope's
132
+ * or this component's ownership chain.
133
+ * @param {Function|String} fn A function, or the name of a function to find in the passed scope object/
134
+ * @param {Object} scope The scope to find the function in if it is specified as a string.
135
+ * @param {Array} args Arguments to pass to the callback.
136
+ */
137
+ callback(fn, scope=this, args) {
138
+ if (fn) {
139
+ const handler = this.resolveCallback(fn, scope);
140
+
141
+ handler.fn.apply(handler.scope, args);
142
+ }
143
+ }
144
+
126
145
  /**
127
146
  * @param {Object} config
128
147
  */
@@ -240,6 +259,28 @@ class Observable extends Base {
240
259
 
241
260
  // }
242
261
 
262
+ /**
263
+ * Locate a callable function by name in the passed scope.
264
+ *
265
+ * If the name starts with 'up.', the parent Component chain is searched.
266
+ *
267
+ * This is used by Observable.fire and by 'handler' function calls to resolve
268
+ * string function names in the Component's own hierarchy.
269
+ * @param {Function|String} fn A function, or the name of a function to find in the passed scope object/
270
+ * @param {Object} scope The scope to find the function in if it is specified as a string.
271
+ * @returns {Object}
272
+ */
273
+ resolveCallback(fn, scope=this) {
274
+ if (typeof fn === 'string') {
275
+ if (!scope[fn] && fn.startsWith('up.')) {
276
+ fn = fn.slice(3);
277
+ while (!scope[fn] && (scope = scope.parent));
278
+ }
279
+ fn = scope[fn];
280
+ }
281
+ return { scope, fn };
282
+ }
283
+
243
284
  /**
244
285
  * Alias for removeListener
245
286
  * @param {Object|String} name
@@ -5,6 +5,14 @@ import Number from './Number.mjs';
5
5
  * @extends Neo.form.field.Number
6
6
  */
7
7
  class Range extends Number {
8
+ /**
9
+ * Removing the debounce for range fields
10
+ * @member {Object} delayable
11
+ * @protected
12
+ * @static
13
+ */
14
+ static delayable = {}
15
+
8
16
  static config = {
9
17
  /**
10
18
  * @member {String} className='Neo.form.field.Range'
@@ -262,12 +262,18 @@ class DomEvents extends Base {
262
262
  path = event.path;
263
263
  }
264
264
 
265
- return {
265
+ const result = {
266
266
  path : path.map(e => this.getTargetData(e)),
267
267
  target : this.getTargetData(event.target),
268
268
  timeStamp: event.timeStamp,
269
269
  type : event.type
270
+ };
271
+
272
+ if (event.relatedTarget) {
273
+ result.relatedTarget = this.getTargetData(event.relatedTarget);
270
274
  }
275
+
276
+ return result;
271
277
  }
272
278
 
273
279
  /**
@@ -552,7 +558,7 @@ class DomEvents extends Base {
552
558
  */
553
559
  onMouseEnter(event) {
554
560
  let me = this,
555
- appEvent = {...me.getMouseEventData(event), fromElementId: event.fromElement?.id || null};
561
+ appEvent = {...me.getMouseEventData(event), fromElementId: event.fromElement?.id || null, toElementId: event.toElement?.id || null};
556
562
 
557
563
  me.sendMessageToApp(appEvent);
558
564
  me.fire('mouseEnter', appEvent)
@@ -563,7 +569,7 @@ class DomEvents extends Base {
563
569
  */
564
570
  onMouseLeave(event) {
565
571
  let me = this,
566
- appEvent = {...me.getMouseEventData(event), toElementId: event.toElement?.id || null};
572
+ appEvent = {...me.getMouseEventData(event), fromElementId: event.fromElement?.id || null, toElementId: event.toElement?.id || null};
567
573
 
568
574
  me.sendMessageToApp(appEvent);
569
575
  me.fire('mouseLeave', appEvent)
@@ -127,6 +127,9 @@ class DomEvent extends Base {
127
127
  data = Neo.clone(data, true, true);
128
128
 
129
129
  data.component = component;
130
+
131
+ // Handler needs to know which actual target matched the delegate
132
+ data.currentTarget = delegationTargetId;
130
133
  listener.fn.apply(listener.scope || globalThis, [data]);
131
134
 
132
135
  if (!listener.bubble) {
package/src/menu/List.mjs CHANGED
@@ -311,7 +311,7 @@ class List extends BaseList {
311
311
  record = me.store.get(recordId),
312
312
  submenu;
313
313
 
314
- record.handler?.call(me, record);
314
+ me.callback(record.handler, me, [record]);
315
315
 
316
316
  record.route && Neo.Main.setRoute({
317
317
  appName: me.appName,