neo.mjs 6.5.1 → 6.5.3

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.5.1'
23
+ * @member {String} version='6.5.3'
24
24
  */
25
- version: '6.5.1'
25
+ version: '6.5.3'
26
26
  }
27
27
 
28
28
  /**
@@ -19,12 +19,10 @@ class FormContainerController extends Component {
19
19
  onComponentConstructed() {
20
20
  super.onComponentConstructed();
21
21
 
22
- this.component.on('fieldFocusLeave', data => {
23
- console.log('fieldFocusLeave', data);
24
- })
25
-
26
- this.component.on('fieldChange', data => {
27
- console.log('fieldChange', data);
22
+ this.component.on({
23
+ fieldChange : data => console.log('fieldChange' , data),
24
+ fieldFocusLeave: data => console.log('fieldFocusLeave', data),
25
+ fieldUserChange: data => console.log('fieldUserChange', data)
28
26
  })
29
27
  }
30
28
 
@@ -1,3 +1,4 @@
1
+ import Button from '../../../../src/button/Base.mjs';
1
2
  import FormPageContainer from '../FormPageContainer.mjs';
2
3
  import TextField from '../../../../src/form/field/Text.mjs';
3
4
 
@@ -31,8 +32,21 @@ class Page1 extends FormPageContainer {
31
32
  name : 'status',
32
33
  readOnly : true,
33
34
  value : 'Active'
35
+ }, {
36
+ module : Button,
37
+ handler: Page1.buttonHandler,
38
+ style : {marginTop: '2em', maxWidth: '300px'},
39
+ text : 'Change values'
34
40
  }]
35
41
  }
42
+
43
+ static buttonHandler(data) {
44
+ let container = data.component.up();
45
+
46
+ container.items[0].value = Math.random();
47
+ container.items[1].value = Math.random();
48
+ container.items[2].value = Math.random()
49
+ }
36
50
  }
37
51
 
38
52
  Neo.applyClassConfig(Page1);
@@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase {
20
20
  */
21
21
  singleton: true,
22
22
  /**
23
- * @member {String} version='6.5.1'
23
+ * @member {String} version='6.5.3'
24
24
  */
25
- version: '6.5.1'
25
+ version: '6.5.3'
26
26
  }
27
27
 
28
28
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neo.mjs",
3
- "version": "6.5.1",
3
+ "version": "6.5.3",
4
4
  "description": "The webworkers driven UI framework",
5
5
  "type": "module",
6
6
  "repository": {
@@ -53,11 +53,11 @@
53
53
  "envinfo": "^7.10.0",
54
54
  "fs-extra": "^11.1.1",
55
55
  "highlightjs-line-numbers.js": "^2.8.0",
56
- "inquirer": "^9.2.10",
56
+ "inquirer": "^9.2.11",
57
57
  "neo-jsdoc": "1.0.1",
58
58
  "neo-jsdoc-x": "1.0.5",
59
59
  "postcss": "^8.4.29",
60
- "sass": "^1.66.1",
60
+ "sass": "^1.67.0",
61
61
  "showdown": "^2.1.0",
62
62
  "webpack": "^5.88.2",
63
63
  "webpack-cli": "^5.1.4",
@@ -67,7 +67,7 @@
67
67
  },
68
68
  "devDependencies": {
69
69
  "siesta-lite": "5.5.2",
70
- "url": "^0.11.1"
70
+ "url": "^0.11.2"
71
71
  },
72
72
  "funding": {
73
73
  "type": "GitHub Sponsors",
@@ -236,12 +236,12 @@ const DefaultConfig = {
236
236
  useVdomWorker: true,
237
237
  /**
238
238
  * buildScripts/injectPackageVersion.mjs will update this value
239
- * @default '6.5.1'
239
+ * @default '6.5.3'
240
240
  * @memberOf! module:Neo
241
241
  * @name config.version
242
242
  * @type String
243
243
  */
244
- version: '6.5.1'
244
+ version: '6.5.3'
245
245
  };
246
246
 
247
247
  Object.assign(DefaultConfig, {
package/src/core/Base.mjs CHANGED
@@ -1,3 +1,4 @@
1
+ import {debounce} from '../util/Function.mjs';
1
2
  import IdGenerator from './IdGenerator.mjs'
2
3
 
3
4
  const configSymbol = Symbol.for('configSymbol'),
@@ -17,6 +18,20 @@ class Base {
17
18
  * @static
18
19
  */
19
20
  static methodNameRegex = /\n.*\n\s+at\s+.*\.(\w+)\s+.*/
21
+ /**
22
+ * You can define methods which should get delayed
23
+ * @example
24
+ * delayable: {
25
+ * fireChangeEvent: {
26
+ * type : 'debounce',
27
+ * timer: 300
28
+ * }
29
+ * }
30
+ * @member {Object} delayable={}
31
+ * @protected
32
+ * @static
33
+ */
34
+ static delayable = {}
20
35
  /**
21
36
  * True automatically applies the core.Observable mixin
22
37
  * @member {Boolean} observable=false
@@ -126,6 +141,8 @@ class Base {
126
141
  value : true
127
142
  });
128
143
 
144
+ me.applyDelayable();
145
+
129
146
  me.remote && setTimeout(me.initRemote.bind(me), 1)
130
147
  }
131
148
 
@@ -157,6 +174,23 @@ class Base {
157
174
  }
158
175
  }
159
176
 
177
+ /**
178
+ * Adjusts all methods inside static delayable
179
+ */
180
+ applyDelayable() {
181
+ let me = this;
182
+
183
+ Object.entries(me.constructor.delayable).forEach(([key, value]) => {
184
+ let map = {
185
+ debounce() {
186
+ me[key] = new debounce(me[key], me, value.timer)
187
+ }
188
+ };
189
+
190
+ map[value.type]?.()
191
+ })
192
+ }
193
+
160
194
  /**
161
195
  * Applying overwrites and adding overwrittenMethods to the class constructors
162
196
  * @param {Object} cfg
@@ -1,6 +1,6 @@
1
- import Base from '../../core/Base.mjs';
2
- import NeoFunction from '../../util/Function.mjs';
3
- import Observable from '../../core/Observable.mjs';
1
+ import Base from '../../core/Base.mjs';
2
+ import {createInterceptor} from '../../util/Function.mjs';
3
+ import Observable from '../../core/Observable.mjs';
4
4
 
5
5
  /**
6
6
  * @class Neo.data.connection.WebSocket
@@ -120,7 +120,7 @@ class Socket extends Base {
120
120
  onopen : me.onOpen .bind(me)
121
121
  });
122
122
 
123
- NeoFunction.createInterceptor(value, 'send', me.beforeSend, me);
123
+ createInterceptor(value, 'send', me.beforeSend, me);
124
124
  }
125
125
 
126
126
  return value;
@@ -33,7 +33,6 @@ class Container extends BaseContainer {
33
33
 
34
34
  /**
35
35
  * Helper function used by setValues() which wraps the leaves of a tree structure into a new property.
36
- * The logic assumes that field config values must not be objects (separation between the key & value realm).
37
36
  * @param {Object} values
38
37
  * @param {String} configName
39
38
  * @param {String[]} fieldPaths
@@ -7,6 +7,16 @@ import ComponentManager from '../../manager/Component.mjs';
7
7
  * @extends Neo.component.Base
8
8
  */
9
9
  class Base extends Component {
10
+ /**
11
+ * @member {Object} delayable
12
+ * @protected
13
+ * @static
14
+ */
15
+ static delayable = {
16
+ fireChangeEvent : {type: 'debounce', timer: 300},
17
+ fireUserChangeEvent: {type: 'debounce', timer: 300}
18
+ }
19
+
10
20
  static config = {
11
21
  /**
12
22
  * @member {String} className='Neo.form.field.Base'
@@ -127,29 +137,49 @@ class Base extends Component {
127
137
 
128
138
  /**
129
139
  * Override this method as needed
130
- * @param {*} value
131
- * @param {*} oldValue
140
+ * @param {*} value
141
+ * @param {*} oldValue
142
+ * @param {String} eventName
132
143
  */
133
- fireChangeEvent(value, oldValue) {
144
+ doFireChangeEvent(value, oldValue, eventName) {
134
145
  let me = this,
135
146
  FormContainer = Neo.form?.Container,
147
+ formEvent = 'field' + Neo.capitalize(eventName),
136
148
  opts = {component: me, oldValue, value};
137
149
 
138
150
  if (Neo.isFunction(me.getGroupValue)) {
139
151
  opts.groupValue = me.getGroupValue()
140
152
  }
141
153
 
142
- me.fire('change', opts);
154
+ me.fire(eventName, opts);
143
155
 
144
156
  if (!me.suspendEvents) {
145
157
  ComponentManager.getParents(me).forEach(parent => {
146
158
  if (FormContainer && parent instanceof FormContainer) {
147
- parent.fire('fieldChange', opts)
159
+ parent.fire(formEvent, opts)
148
160
  }
149
161
  })
150
162
  }
151
163
  }
152
164
 
165
+ /**
166
+ * Override this method as needed
167
+ * @param {*} value
168
+ * @param {*} oldValue
169
+ */
170
+ fireChangeEvent(value, oldValue) {
171
+ this.doFireChangeEvent(value, oldValue, 'change')
172
+ }
173
+
174
+ /**
175
+ * Override this method as needed
176
+ * @param {*} value
177
+ * @param {*} oldValue
178
+ */
179
+ fireUserChangeEvent(value, oldValue) {
180
+ this.doFireChangeEvent(value, oldValue, 'userChange')
181
+ }
182
+
153
183
  /**
154
184
  * Forms in neo can be nested. This method will return the closest parent which is a form.Container or null.
155
185
  * @returns {Neo.form.Container|null}
@@ -525,7 +525,9 @@ class CheckBox extends Base {
525
525
  // keep the vdom & vnode in sync for future updates
526
526
  me.vnode.childNodes[0].childNodes[me.hideLabel ? 0 : 1].attributes.checked = `${checked}`;
527
527
 
528
- me.checked = checked
528
+ me.checked = checked;
529
+
530
+ me.fireUserChangeEvent(me.getValue(), me.getOldValue())
529
531
  }
530
532
 
531
533
  /**
@@ -17,6 +17,17 @@ class Text extends Base {
17
17
  * @static
18
18
  */
19
19
  static autoCapitalizeValues = ['characters', 'none', 'on', 'off', 'sentences', 'words']
20
+ /**
21
+ * @member {Object} delayable
22
+ * @protected
23
+ * @static
24
+ */
25
+ static delayable = {
26
+ fireChangeEvent: {
27
+ type : 'debounce',
28
+ timer: 300
29
+ }
30
+ }
20
31
  /**
21
32
  * Valid values for labelPosition
22
33
  * @member {String[]} labelPositions=['bottom','inline','left','right','top']
@@ -1231,16 +1242,19 @@ class Text extends Base {
1231
1242
  * @protected
1232
1243
  */
1233
1244
  onInputValueChange(data) {
1234
- let me = this,
1235
- value = data.value,
1236
- vnode = VNodeUtil.findChildVnode(me.vnode, {nodeName: 'input'});
1245
+ let me = this,
1246
+ oldValue = me.value,
1247
+ value = data.value,
1248
+ vnode = VNodeUtil.findChildVnode(me.vnode, {nodeName: 'input'});
1237
1249
 
1238
1250
  if (vnode) {
1239
1251
  // required for validation -> revert a wrong user input
1240
1252
  vnode.vnode.attributes.value = value;
1241
1253
  }
1242
1254
 
1243
- me.value = me.inputValueAdjustor(value)
1255
+ me.value = me.inputValueAdjustor(value);
1256
+
1257
+ me.fireUserChangeEvent(value, oldValue)
1244
1258
  }
1245
1259
 
1246
1260
  /**
@@ -1,84 +1,93 @@
1
- import Base from '../core/Base.mjs';
2
-
3
1
  /**
4
- * @class Neo.util.Function
5
- * @extends Neo.core.Base
2
+ * Append args instead of prepending them
3
+ * @param {Object} scope
4
+ * @returns {Function}
6
5
  */
7
- class NeoFunction extends Base {
8
- static config = {
9
- /**
10
- * @member {String} className='Neo.util.Function'
11
- * @protected
12
- */
13
- className: 'Neo.util.Function'
6
+ export function bindAppend(scope) {
7
+ const fn = this,
8
+ args = [].slice.call(arguments).slice(1);
9
+
10
+ return function() {
11
+ return fn.apply(scope, [].slice.call(arguments).concat(args))
14
12
  }
13
+ }
15
14
 
16
- /**
17
- * Append args instead of prepending them
18
- * @param {Object} scope
19
- * @returns {Function}
20
- */
21
- static bindAppend(scope) {
22
- const fn = this,
23
- args = [].slice.call(arguments).slice(1);
15
+ /**
16
+ * Intended for functions with 1 param where the interceptor can change the value
17
+ * @param {Object} target
18
+ * @param {String} targetMethodName
19
+ * @param {Function} interceptFunction
20
+ * @param {Object} scope=target
21
+ * @returns {Function}
22
+ */
23
+ export function createInterceptor(target, targetMethodName, interceptFunction, scope) {
24
+ let targetMethod = target[targetMethodName];
24
25
 
25
- return function() {
26
- return fn.apply(scope, [].slice.call(arguments).concat(args));
27
- }
28
- }
26
+ return (target[targetMethodName] = function(value) {
27
+ return targetMethod.call(target, interceptFunction.call(scope || target, value))
28
+ })
29
+ }
29
30
 
30
- /**
31
- * Intended for functions with 1 param where the interceptor can change the value
32
- * @param {Object} target
33
- * @param {String} targetMethodName
34
- * @param {Function} interceptFunction
35
- * @param {Object} scope=target
36
- * @returns {Function}
37
- */
38
- static createInterceptor(target, targetMethodName, interceptFunction, scope) {
39
- let targetMethod = target[targetMethodName];
31
+ /**
32
+ * @param {Neo.core.Base} target
33
+ * @param {String} methodName
34
+ * @param {Function} fn
35
+ * @param {Object} scope
36
+ * @returns {Function}
37
+ */
38
+ export function createSequence(target, methodName, fn, scope) {
39
+ let method = target[methodName] || Neo.emptyFn;
40
40
 
41
- return (target[targetMethodName] = function(value) {
42
- return targetMethod.call(target, interceptFunction.call(scope || target, value));
43
- });
44
- }
41
+ return (target[methodName] = function() {
42
+ method.apply(this, arguments);
43
+ return fn.apply(scope || this, arguments);
44
+ })
45
+ }
45
46
 
46
- /**
47
- * @param {Neo.core.Base} target
48
- * @param {String} methodName
49
- * @param {Function} fn
50
- * @param {Object} scope
51
- * @returns {Function}
52
- */
53
- static createSequence(target, methodName, fn, scope) {
54
- let method = target[methodName] || Neo.emptyFn;
47
+ /**
48
+ * @param {Function} func
49
+ * @param {Neo.core.Base} scope
50
+ * @param {Number} timeout=300
51
+ * @returns {Function}
52
+ */
53
+ export function debounce(func, scope, timeout=300) {
54
+ let debounceTimer;
55
55
 
56
- return (target[methodName] = function() {
57
- method.apply(this, arguments);
58
- return fn.apply(scope || this, arguments);
59
- });
60
- }
56
+ return function(...args) {
57
+ // leading edge => trigger the first call right away
58
+ if (!Neo.isNumber(debounceTimer)) {
59
+ // we need to check if the scope (instance) did not get destroyed yet
60
+ scope?.id && func.apply(scope, args);
61
61
 
62
- /**
63
- * The interceptor can prevent the targetMethod from getting executed in case it returns false.
64
- * @param {Object} target
65
- * @param {String} targetMethodName
66
- * @param {Function} interceptFunction
67
- * @param {Object} scope=target
68
- * @param {*} preventedReturnValue=null The value to return in case the interceptFunction returns false
69
- * @returns {Function}
70
- */
71
- static intercept(target, targetMethodName, interceptFunction, scope, preventedReturnValue=null) {
72
- let targetMethod = target[targetMethodName];
62
+ // we still want to start a timer, do delay the 2nd+ update
63
+ debounceTimer = setTimeout(() => {debounceTimer = null}, timeout)
64
+ } else {
65
+ clearTimeout(debounceTimer);
73
66
 
74
- return (target[targetMethodName] = function() {
75
- return (interceptFunction.apply(scope || target, arguments) === false)
76
- ? preventedReturnValue
77
- : targetMethod.apply(target, arguments);
78
- });
67
+ debounceTimer = setTimeout(() => {
68
+ // we need to check if the scope (instance) did not get destroyed yet
69
+ scope?.id && func.apply(scope, args);
70
+ debounceTimer = setTimeout(() => {debounceTimer = null}, timeout)
71
+ }, timeout)
72
+ }
79
73
  }
80
74
  }
81
75
 
82
- Neo.applyClassConfig(NeoFunction);
76
+ /**
77
+ * The interceptor can prevent the targetMethod from getting executed in case it returns false.
78
+ * @param {Object} target
79
+ * @param {String} targetMethodName
80
+ * @param {Function} interceptFunction
81
+ * @param {Object} scope=target
82
+ * @param {*} preventedReturnValue=null The value to return in case the interceptFunction returns false
83
+ * @returns {Function}
84
+ */
85
+ export function intercept(target, targetMethodName, interceptFunction, scope, preventedReturnValue=null) {
86
+ let targetMethod = target[targetMethodName];
83
87
 
84
- export default NeoFunction;
88
+ return (target[targetMethodName] = function() {
89
+ return (interceptFunction.apply(scope || target, arguments) === false)
90
+ ? preventedReturnValue
91
+ : targetMethod.apply(target, arguments)
92
+ })
93
+ }
@@ -451,11 +451,11 @@ export default class Rectangle extends DOMRect {
451
451
 
452
452
  // Fall back to the other two zones.
453
453
  zonesToTry.push({
454
- zone : zone = (alignSpec.startZone + 1) % 4,
454
+ zone : zone = (edges.theirEdgeZone + 1) % 4,
455
455
  edgeAlign : `${oppositeEdge[zoneEdges[zone]]}-${zoneEdges[zone]}`
456
456
  });
457
457
  zonesToTry.push({
458
- zone : zone = (zone + 2) % 4,
458
+ zone : zone = (edges.theirEdgeZone + 3) % 4,
459
459
  edgeAlign : `${oppositeEdge[zoneEdges[zone]]}-${zoneEdges[zone]}`
460
460
  });
461
461
  }