neo.mjs 5.16.3 → 5.16.5

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='5.16.3'
23
+ * @member {String} version='5.16.5'
24
24
  */
25
- version: '5.16.3'
25
+ version: '5.16.5'
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='5.16.3'
23
+ * @member {String} version='5.16.5'
24
24
  */
25
- version: '5.16.3'
25
+ version: '5.16.5'
26
26
  }
27
27
 
28
28
  /**
@@ -0,0 +1,80 @@
1
+ import CheckBox from '../../../src/form/field/CheckBox.mjs';
2
+ import ConfigurationViewport from '../../ConfigurationViewport.mjs';
3
+ import NumberField from '../../../src/form/field/Number.mjs';
4
+ import Radio from '../../../src/form/field/Radio.mjs';
5
+ import StatusBadge from '../../../src/component/StatusBadge.mjs';
6
+ import TextField from '../../../src/form/field/Text.mjs';
7
+
8
+ /**
9
+ * @class Neo.examples.component.statusbadge.MainContainer
10
+ * @extends Neo.examples.ConfigurationViewport
11
+ */
12
+ class MainContainer extends ConfigurationViewport {
13
+ static config = {
14
+ className : 'Neo.examples.component.statusbadge.MainContainer',
15
+ autoMount : true,
16
+ configItemLabelWidth: 110,
17
+ configItemWidth : 230,
18
+ layout : {ntype: 'hbox', align: 'stretch'}
19
+ }
20
+
21
+ createConfigurationComponents() {
22
+ let me = this;
23
+
24
+ return [{
25
+ module : CheckBox,
26
+ checked : me.exampleComponent.closable,
27
+ labelText: 'closable',
28
+ listeners: {change: me.onConfigChange.bind(me, 'closable')}
29
+ }, {
30
+ module : NumberField,
31
+ clearable : true,
32
+ labelText : 'height',
33
+ listeners : {change: me.onConfigChange.bind(me, 'height')},
34
+ maxValue : 100,
35
+ minValue : 20,
36
+ stepSize : 2,
37
+ style : {marginTop: '10px'},
38
+ value : me.exampleComponent.height
39
+ }, {
40
+ module : Radio,
41
+ checked : me.exampleComponent.state === 'error',
42
+ hideValueLabel: false,
43
+ labelText : 'state',
44
+ listeners : {change: me.onRadioChange.bind(me, 'state', 'error')},
45
+ name : 'state',
46
+ style : {marginTop: '10px'},
47
+ valueLabelText: 'error'
48
+ }, {
49
+ module : Radio,
50
+ checked : me.exampleComponent.badgePosition === 'success',
51
+ hideValueLabel: false,
52
+ labelText : '',
53
+ listeners : {change: me.onRadioChange.bind(me, 'state', 'success')},
54
+ name : 'state',
55
+ valueLabelText: 'success'
56
+ }, {
57
+ module : NumberField,
58
+ clearable : true,
59
+ labelText : 'width',
60
+ listeners : {change: me.onConfigChange.bind(me, 'width')},
61
+ maxValue : 300,
62
+ minValue : 100,
63
+ stepSize : 5,
64
+ style : {marginTop: '10px'},
65
+ value : me.exampleComponent.width
66
+ }]
67
+ }
68
+
69
+ createExampleComponent() {
70
+ return Neo.create({
71
+ module: StatusBadge,
72
+ height: 30,
73
+ state : 'error'
74
+ })
75
+ }
76
+ }
77
+
78
+ Neo.applyClassConfig(MainContainer);
79
+
80
+ export default MainContainer;
@@ -0,0 +1,6 @@
1
+ import MainContainer from './MainContainer.mjs';
2
+
3
+ export const onStart = () => Neo.app({
4
+ mainView: MainContainer,
5
+ name : 'Neo.examples.component.statusbadge'
6
+ });
@@ -0,0 +1,11 @@
1
+ <!DOCTYPE HTML>
2
+ <html>
3
+ <head>
4
+ <meta name="viewport" content="width=device-width, initial-scale=1">
5
+ <meta charset="UTF-8">
6
+ <title>Neo StatusBadge</title>
7
+ </head>
8
+ <body>
9
+ <script src="../../../src/MicroLoader.mjs" type="module"></script>
10
+ </body>
11
+ </html>
@@ -0,0 +1,6 @@
1
+ {
2
+ "appPath" : "examples/component/statusbadge/app.mjs",
3
+ "basePath" : "../../../",
4
+ "environment": "development",
5
+ "mainPath" : "./Main.mjs"
6
+ }
@@ -37,9 +37,9 @@ class MainContainer extends ConfigurationViewport {
37
37
  module : FileUploadField,
38
38
  id : 'my-downloadable-test',
39
39
  uploadUrl : 'http://127.0.0.1:3000/file-upload-test',
40
- documentStatusUrl : 'http://127.0.0.1:3000/document-status-downloadable',
41
- documentDeleteUrl : 'http://127.0.0.1:3000/document-delete',
42
- downloadUrl : 'http://127.0.0.1:3000/getDocument',
40
+ documentStatusUrl : 'http://127.0.0.1:3000/document-status-downloadable?documentId={documentId}',
41
+ documentDeleteUrl : 'http://127.0.0.1:3000/document-delete?documentId={documentId}',
42
+ downloadUrl : 'http://127.0.0.1:3000/getDocument?documentId={documentId}',
43
43
  width : 350,
44
44
  maxSize : '10mb',
45
45
  types : {
@@ -47,14 +47,19 @@ class MainContainer extends ConfigurationViewport {
47
47
  jpg : 1,
48
48
  xls : 1,
49
49
  pdf : 1
50
+ },
51
+ listeners : {
52
+ beforeRequest({ headers }) {
53
+ headers['X-XSRF-TOKEN'] = 'my-xsrf-token'
54
+ }
50
55
  }
51
56
  }, {
52
57
  module : FileUploadField,
53
58
  id : 'my-not-downloadable-test',
54
59
  uploadUrl : 'http://127.0.0.1:3000/file-upload-test',
55
- documentStatusUrl : 'http://127.0.0.1:3000/document-status-not-downloadable',
56
- documentDeleteUrl : 'http://127.0.0.1:3000/document-delete',
57
- downloadUrl : 'http://127.0.0.1:3000/getDocument',
60
+ documentStatusUrl : 'http://127.0.0.1:3000/document-status-not-downloadable?documentId={documentId}',
61
+ documentDeleteUrl : 'http://127.0.0.1:3000/document-delete?documentId={documentId}',
62
+ downloadUrl : 'http://127.0.0.1:3000/getDocument?documentId={documentId}',
58
63
  width : 350,
59
64
  maxSize : '10mb',
60
65
  types : {
@@ -67,9 +72,9 @@ class MainContainer extends ConfigurationViewport {
67
72
  module : FileUploadField,
68
73
  id : 'my-upload-fail-test',
69
74
  uploadUrl : 'http://127.0.0.1:3000/file-upload-test-fail',
70
- documentStatusUrl : 'http://127.0.0.1:3000/document-status',
71
- documentDeleteUrl : 'http://127.0.0.1:3000/document-delete',
72
- downloadUrl : 'http://127.0.0.1:3000/getDocument',
75
+ documentStatusUrl : 'http://127.0.0.1:3000/document-status?documentId={documentId}',
76
+ documentDeleteUrl : 'http://127.0.0.1:3000/document-delete?documentId={documentId}',
77
+ downloadUrl : 'http://127.0.0.1:3000/getDocument?documentId={documentId}',
73
78
  width : 350,
74
79
  maxSize : '10mb',
75
80
  types : {
@@ -82,9 +87,41 @@ class MainContainer extends ConfigurationViewport {
82
87
  module : FileUploadField,
83
88
  id : 'my-scan-fail-test',
84
89
  uploadUrl : 'http://127.0.0.1:3000/file-upload-test',
85
- documentStatusUrl : 'http://127.0.0.1:3000/document-status-fail',
86
- documentDeleteUrl : 'http://127.0.0.1:3000/document-delete',
87
- downloadUrl : 'http://127.0.0.1:3000/getDocument',
90
+ documentStatusUrl : 'http://127.0.0.1:3000/document-status-fail?documentId={documentId}',
91
+ documentDeleteUrl : 'http://127.0.0.1:3000/document-delete?documentId={documentId}',
92
+ downloadUrl : 'http://127.0.0.1:3000/getDocument?documentId={documentId}',
93
+ width : 350,
94
+ maxSize : '10mb',
95
+ types : {
96
+ png : 1,
97
+ jpg : 1,
98
+ xls : 1,
99
+ pdf : 1
100
+ }
101
+ }, {
102
+ module : FileUploadField,
103
+ id : 'my-existing-document-test',
104
+ documentId : 2,
105
+ uploadUrl : 'http://127.0.0.1:3000/file-upload-test',
106
+ documentStatusUrl : 'http://127.0.0.1:3000/document-status-not-downloadable?documentId={documentId}',
107
+ documentDeleteUrl : 'http://127.0.0.1:3000/document-delete?documentId={documentId}',
108
+ downloadUrl : 'http://127.0.0.1:3000/getDocument?documentId={documentId}',
109
+ width : 350,
110
+ maxSize : '10mb',
111
+ types : {
112
+ png : 1,
113
+ jpg : 1,
114
+ xls : 1,
115
+ pdf : 1
116
+ }
117
+ }, {
118
+ module : FileUploadField,
119
+ id : 'my-non-existing-document-test',
120
+ documentId : 2,
121
+ uploadUrl : 'http://127.0.0.1:3000/file-upload-test',
122
+ documentStatusUrl : 'http://127.0.0.1:3000/document-status-non-existent?documentId={documentId}',
123
+ documentDeleteUrl : 'http://127.0.0.1:3000/document-delete?documentId={documentId}',
124
+ downloadUrl : 'http://127.0.0.1:3000/getDocument?documentId={documentId}',
88
125
  width : 350,
89
126
  maxSize : '10mb',
90
127
  types : {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neo.mjs",
3
- "version": "5.16.3",
3
+ "version": "5.16.5",
4
4
  "description": "The webworkers driven UI framework",
5
5
  "type": "module",
6
6
  "repository": {
@@ -0,0 +1,9 @@
1
+ .neo-status-badge {
2
+ &.neo-state-error {
3
+ background-color: red;
4
+ }
5
+
6
+ &.neo-state-success {
7
+ background-color: darkgreen;
8
+ }
9
+ }
@@ -125,8 +125,9 @@
125
125
  animation: spinner-rotation 3s linear infinite;
126
126
 
127
127
  &::after {
128
- content : "";
129
- flex : 0 0 calc(100% - 6px);
128
+ content : "";
129
+ flex : 0 0 calc(100% - 6px);
130
+ background-color : var(--fileuploadfield-background-color);
130
131
  }
131
132
  }
132
133
  }
@@ -245,12 +245,12 @@ const DefaultConfig = {
245
245
  useVdomWorker: true,
246
246
  /**
247
247
  * buildScripts/injectPackageVersion.mjs will update this value
248
- * @default '5.16.3'
248
+ * @default '5.16.5'
249
249
  * @memberOf! module:Neo
250
250
  * @name config.version
251
251
  * @type String
252
252
  */
253
- version: '5.16.3'
253
+ version: '5.16.5'
254
254
  };
255
255
 
256
256
  Object.assign(DefaultConfig, {
@@ -0,0 +1,73 @@
1
+ import Base from '../component/Base.mjs';
2
+ import NeoArray from '../util/Array.mjs';
3
+
4
+ /**
5
+ * @class Neo.component.StatusBadge
6
+ * @extends Neo.component.Base
7
+ */
8
+ class StatusBadge extends Base {
9
+ /**
10
+ * Valid values for state
11
+ * @member {String[]} states=['error','neutral','success']
12
+ * @protected
13
+ * @static
14
+ */
15
+ static states = ['error', 'neutral', 'success']
16
+
17
+ static config = {
18
+ /**
19
+ * @member {String} className='Neo.component.StatusBadge'
20
+ * @protected
21
+ */
22
+ className: 'Neo.component.StatusBadge',
23
+ /**
24
+ * @member {String} ntype='status-badge'
25
+ * @protected
26
+ */
27
+ ntype: 'status-badge',
28
+ /**
29
+ * @member {String[]} baseCls=['neo-status-badge']
30
+ * @protected
31
+ */
32
+ baseCls: ['neo-status-badge'],
33
+ /**
34
+ * @member {String} state_='neutral'
35
+ */
36
+ state_: 'neutral',
37
+ /**
38
+ * @member {Object} vdom
39
+ */
40
+ vdom:
41
+ {}
42
+ }
43
+
44
+ /**
45
+ * Triggered after the state config got changed
46
+ * @param {String} value
47
+ * @param {String} oldValue
48
+ * @protected
49
+ */
50
+ afterSetState(value, oldValue) {
51
+ let cls = this.cls;
52
+
53
+ NeoArray.remove(cls, 'neo-state-' + oldValue);
54
+ NeoArray.add(cls, 'neo-state-' + value);
55
+
56
+ this.cls = cls
57
+ }
58
+
59
+ /**
60
+ * Triggered before the state config gets changed
61
+ * @param {String} value
62
+ * @param {String} oldValue
63
+ * @returns {String}
64
+ * @protected
65
+ */
66
+ beforeSetState(value, oldValue) {
67
+ return this.beforeSetEnumValue(value, oldValue, 'state')
68
+ }
69
+ }
70
+
71
+ Neo.applyClassConfig(StatusBadge);
72
+
73
+ export default StatusBadge;
@@ -14,7 +14,7 @@ const
14
14
  * An accessible file uploading widget which automatically commences an upload as soon as
15
15
  * a file is selected using the UI.
16
16
  *
17
- * The URL to which the file must be uploaded is specified in the {@link #member-uploadUrl} property.
17
+ * The URL to which the file must be uploaded is specified in the {@link config#uploadUrl} property.
18
18
  * This service must return a JSON status response in the following form for successful uploads:
19
19
  *
20
20
  * ```json
@@ -130,6 +130,35 @@ class FileUpload extends Base {
130
130
 
131
131
  cls : [],
132
132
 
133
+ /**
134
+ * An Object containing a default set of headers to be passed to the server on every HTTP request.
135
+ * @member {Object} headers
136
+ */
137
+ headers_ : {},
138
+
139
+ /**
140
+ * An Object which allows the status text returned from the {@link #property-documentStatusUrl} to be
141
+ * mapped to the corresponding next widget state.
142
+ * @member {Object} documentStatusMap
143
+ */
144
+ documentStatusMap : {
145
+ SCANNING : 'scanning',
146
+ MALWARE_DETECTED : 'scan-failed',
147
+ UN_DOWNLOADABLE : 'not-downloadable',
148
+ DOWNLOADABLE : 'downloadable',
149
+ DELETED : 'deleted'
150
+ },
151
+
152
+ /**
153
+ * If this widget should reference an existing document, configure the widget with a documentId
154
+ * so that it can initialize in the correct "uploaded" state.
155
+ *
156
+ * If this is *not* configured, then this property will be set after a successful upload to
157
+ * the id returned from the {@link #property-uploadUrl}.
158
+ * @member {String|Number} documentId
159
+ */
160
+ documentId : null,
161
+
133
162
  /**
134
163
  * The URL of the file upload service to which the selected file is sent.
135
164
  *
@@ -153,8 +182,10 @@ class FileUpload extends Base {
153
182
 
154
183
  /**
155
184
  * The name of the JSON property in which the document id is returned in the upload response
156
- * JSON packet and the HTTP parameter which is used when requesting a malware scan and a document
157
- * deletion.
185
+ * JSON packet and the token string which is substituted for the document id when requesting
186
+ * a malware scan and a document deletion.
187
+ *
188
+ * Defaults fro `documentId`
158
189
  *
159
190
  * @member {String} downloadUrl
160
191
  */
@@ -163,6 +194,17 @@ class FileUpload extends Base {
163
194
  /**
164
195
  * The URL from which the file may be downloaded after it has finished its scan.
165
196
  *
197
+ * This must contain a substitution token named the same as the {@link #property-documentIdParameter}
198
+ * which is used when creating a URL
199
+ *
200
+ * for example:
201
+ *
202
+ * ```json
203
+ * {
204
+ * downloadUrl : '/getDocument/${documentId}'
205
+ * }
206
+ * ```
207
+ *
166
208
  * The document id returned from the {@link #member-uploadUrl upload} is passed in the parameter named
167
209
  * by the {@link #member-documentIdParameter}. It defaults to `'documentId'`.
168
210
  *
@@ -173,6 +215,17 @@ class FileUpload extends Base {
173
215
  /**
174
216
  * The URL of the file status reporting service.
175
217
  *
218
+ * This must contain a substitution token named the same as the {@link #property-documentIdParameter}
219
+ * which is used when creating a URL
220
+ *
221
+ * for example:
222
+ *
223
+ * ```json
224
+ * {
225
+ * documentStatusUrl : '/getDocumentStatus/${documentId}'
226
+ * }
227
+ * ```
228
+ *
176
229
  * This widget will use this service after a successful upload to determine its next
177
230
  * state.
178
231
  *
@@ -188,9 +241,30 @@ class FileUpload extends Base {
188
241
  */
189
242
  documentStatusUrl : null,
190
243
 
244
+ /**
245
+ * The polling interval *in milliseconds* to wait between asking the server how the document scan
246
+ * is proceeding.
247
+ *
248
+ * Defaults to 2000ms
249
+ *
250
+ * @member {String} documentDeleteUrl
251
+ */
252
+ statusScanInterval : 2000,
253
+
191
254
  /**
192
255
  * The URL of the file deletion service.
193
256
  *
257
+ * This must contain a substitution token named the same as the {@link #property-documentIdParameter}
258
+ * which is used when creating a URL
259
+ *
260
+ * for example:
261
+ *
262
+ * ```json
263
+ * {
264
+ * documentDeleteUrl : '/deleteDocument/${documentId}'
265
+ * }
266
+ * ```
267
+ *
194
268
  * This widget will use this service after a successful upload to determine its next
195
269
  * state.
196
270
  *
@@ -230,6 +304,13 @@ class FileUpload extends Base {
230
304
  { input : me.onInputValueChange, scope: me},
231
305
  { click : me.onActionButtonClick, delegate : '.neo-file-upload-action-button', scope : me}
232
306
  ]);
307
+
308
+ // If we are to reference an existing document, start by asking the server about its
309
+ // state. Widget state will proceed from there.
310
+ if (me.documentId) {
311
+ me.state = 'processing';
312
+ me.checkDocumentStatus();
313
+ }
233
314
  }
234
315
 
235
316
  async clear() {
@@ -288,7 +369,8 @@ class FileUpload extends Base {
288
369
  me = this,
289
370
  xhr = me.xhr = new XMLHttpRequest(),
290
371
  { upload } = xhr,
291
- fileData = new FormData();
372
+ fileData = new FormData(),
373
+ headers = { ...me.headers };
292
374
 
293
375
  // Show the action button
294
376
  me.state = 'starting';
@@ -311,6 +393,23 @@ class FileUpload extends Base {
311
393
 
312
394
  xhr.open("POST", me.uploadUrl, true);
313
395
 
396
+ /**
397
+ * This event fires before every HTTP request is sent to the server via any of the configured URLs.
398
+ *
399
+ *
400
+ * @event beforeRequest
401
+ * @param {Object} event The event
402
+ * @param {Object} event.headers An object containing the configured {@link #property-headers}
403
+ * for this widget, into which new headers may be injected.
404
+ * @returns {Object}
405
+ */
406
+ me.fire('beforeRequest', {
407
+ headers
408
+ });
409
+ for (const header in headers) {
410
+ xhr.setRequestHeader(header, headers[header]);
411
+ }
412
+
314
413
  xhr.send(fileData);
315
414
  }
316
415
 
@@ -404,39 +503,77 @@ class FileUpload extends Base {
404
503
  }
405
504
 
406
505
  async deleteDocument() {
506
+ const
507
+ me = this,
508
+ headers = { ...me.headers },
509
+ url = me.createUrl(me.documentDeleteUrl, {
510
+ [me.documentIdParameter] : me.documentId
511
+ });
512
+
513
+ me.fire('beforeRequest', {
514
+ headers
515
+ });
516
+
407
517
  // We ask the server to delete using our this.documentId
408
- const statusResponse = await fetch(`${this.documentDeleteUrl}?${this.documentIdParameter}=${this.documentId}`);
518
+ const statusResponse = await fetch(url, {
519
+ headers
520
+ });
409
521
 
410
522
  // Success
411
523
  if (String(statusResponse.status).slice(0, 1) === '2') {
412
- this.clear();
413
- this.state = 'ready';
524
+ me.clear();
525
+ me.state = 'ready';
414
526
  }
415
527
  else {
416
- this.error = `Document delete service error: ${statusResponse.statusText}`;
528
+ me.error = `Document delete service error: ${statusResponse.statusText}`;
417
529
  }
418
530
  }
419
531
 
420
532
  async checkDocumentStatus() {
421
- const me = this;
533
+ const
534
+ me = this,
535
+ headers = { ...me.headers },
536
+ url = me.createUrl(me.documentStatusUrl, {
537
+ [me.documentIdParameter] : me.documentId
538
+ });
539
+
540
+ if (me.state === 'processing') {
541
+ me.fire('beforeRequest', {
542
+ headers
543
+ });
422
544
 
423
- if (this.state === 'processing') {
424
- const statusResponse = await fetch(`${this.documentStatusUrl}?${me.documentIdParameter}=${this.documentId}`);
545
+ const statusResponse = await fetch(url, {
546
+ headers
547
+ });
425
548
 
426
549
  // Success
427
550
  if (String(statusResponse.status).slice(0, 1) === '2') {
428
- const status = (await statusResponse.json()).status;
551
+ const
552
+ serverJson = await statusResponse.json(),
553
+ serverStatus = serverJson.status,
554
+ // Map the server's states codes to our own status codes
555
+ status = me.documentStatusMap[serverStatus] || serverStatus;
429
556
 
430
557
  switch (status) {
431
558
  case 'scanning':
432
- setTimeout(() => me.checkDocumentStatus(), 2000);
559
+ setTimeout(() => me.checkDocumentStatus(), me.statusScanInterval);
560
+ break;
561
+ case 'deleted':
562
+ me.error = `Document ${me.documentId} is no longer available`;
563
+ me.state = 'ready';
433
564
  break;
434
565
  default:
566
+ const { fileName, size } = serverJson;
567
+
568
+ if (fileName) {
569
+ me.vdom.cn[1].cn[0].innerHTML = fileName;
570
+ me.fileSize = me.formatSize(size);
571
+ }
435
572
  me.state = status;
436
573
  }
437
574
  }
438
575
  else {
439
- this.error = `Document status service error: ${statusResponse.statusText}`;
576
+ me.error = `Document status service error: ${statusResponse.statusText}`;
440
577
  }
441
578
  }
442
579
  }
@@ -473,7 +610,9 @@ class FileUpload extends Base {
473
610
  break;
474
611
  case 'downloadable':
475
612
  anchor.tag = 'a';
476
- anchor.href = `${me.downloadUrl}?${me.documentIdParameter}=${me.documentId}`;
613
+ anchor.href = me.createUrl(me.downloadUrl, {
614
+ [me.documentIdParameter] : me.documentId
615
+ });
477
616
  status.innerHTML = me.fileSize;
478
617
  break;
479
618
  case 'not-downloadable':
@@ -490,6 +629,19 @@ class FileUpload extends Base {
490
629
  me.cls = cls;
491
630
  }
492
631
 
632
+ /**
633
+ * Creates a URL substituting the passed parameter names in at the places where the name
634
+ * occurs within `{}` in the pattern.
635
+ * @param {String} urlPattern
636
+ * @param {Object} params
637
+ */
638
+ createUrl(urlPattern, params) {
639
+ for (const paramName in params) {
640
+ urlPattern = urlPattern.replace(`{${paramName}}`, params[paramName]);
641
+ }
642
+ return urlPattern;
643
+ }
644
+
493
645
  beforeGetMaxSize(maxSize) {
494
646
  // Not configured means no limit
495
647
  if (maxSize == null) {
@@ -665,7 +665,7 @@ class Text extends Base {
665
665
 
666
666
  me.silentVdomUpdate = true;
667
667
 
668
- me.validate(false);
668
+ !me.clean && me.validate(false);
669
669
  me.changeInputElKey('required', value ? value : null);
670
670
  me.labelText = me.labelText; // apply the optional text if needed
671
671
 
@@ -49,21 +49,46 @@ class NeoResizeObserver extends Base {
49
49
  }
50
50
 
51
51
  /**
52
- * Internal callback for the ResizeObserver instance
52
+ * Internal callback for the ResizeObserver instance.
53
+ * See: https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserverEntry
53
54
  * @param {HTMLElement[]} entries
54
55
  * @param {ResizeObserver} observer
55
56
  * @protected
56
57
  */
57
58
  onResize(entries, observer) {
58
59
  entries.forEach(entry => {
60
+ // the content of entry is not spreadable, so we need to manually convert it
61
+ // structuredClone(entry) throws a JS error => ResizeObserverEntry object could not be cloned.
62
+
63
+ let borderBoxSize = entry.borderBoxSize[0],
64
+ contentBoxSize = entry.contentBoxSize[0],
65
+ devicePixelContentBoxSize = entry.devicePixelContentBoxSize?.[0] || {}, // Not supported in Safari yet
66
+ path = DomEvents.getPathFromElement(entry.target).map(e => DomEvents.getTargetData(e));
67
+
59
68
  Neo.worker.Manager.sendMessage('app', {
60
69
  action : 'domEvent',
61
70
  eventName: 'resize',
62
71
 
63
72
  data: {
64
- id : entry.target.id,
65
- path: DomEvents.getPathFromElement(entry.target).map(e => DomEvents.getTargetData(e)),
66
- rect: DomEvents.parseDomRect(entry.contentRect)
73
+ contentRect: DomEvents.parseDomRect(entry.contentRect),
74
+ id : entry.target.id,
75
+ path,
76
+ rect : path[0].rect,
77
+
78
+ borderBoxSize: {
79
+ blockSize : borderBoxSize.blockSize,
80
+ inlineSize: borderBoxSize.inlineSize
81
+ },
82
+
83
+ contentBoxSize: {
84
+ blockSize : contentBoxSize.blockSize,
85
+ inlineSize: contentBoxSize.inlineSize
86
+ },
87
+
88
+ devicePixelContentBoxSize: {
89
+ blockSize : devicePixelContentBoxSize.blockSize,
90
+ inlineSize: devicePixelContentBoxSize.inlineSize
91
+ }
67
92
  }
68
93
  })
69
94
  })