neo.mjs 6.18.0 → 6.18.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.
Files changed (44) hide show
  1. package/apps/ServiceWorker.mjs +2 -2
  2. package/apps/colors/view/HeaderToolbar.mjs +6 -6
  3. package/apps/portal/view/HeaderToolbar.mjs +1 -2
  4. package/apps/portal/view/home/MainContainer.mjs +39 -8
  5. package/apps/portal/view/home/parts/AfterMath.mjs +19 -16
  6. package/apps/portal/view/home/parts/BaseContainer.mjs +31 -0
  7. package/apps/portal/view/home/parts/Colors.mjs +43 -19
  8. package/apps/portal/view/home/parts/Features.mjs +6 -6
  9. package/apps/portal/view/home/parts/Helix.mjs +55 -19
  10. package/apps/portal/view/home/parts/HelloWorld.mjs +11 -10
  11. package/apps/portal/view/home/parts/How.mjs +66 -0
  12. package/apps/portal/view/home/parts/MainNeo.mjs +17 -14
  13. package/examples/ServiceWorker.mjs +2 -2
  14. package/examples/model/dialog/EditUserDialog.mjs +1 -8
  15. package/package.json +1 -1
  16. package/resources/data/deck/learnneo/pages/Events.md +15 -9
  17. package/resources/data/deck/learnneo/pages/GuideEvents.md +142 -19
  18. package/resources/data/deck/learnneo/pages/GuideViewModels.md +444 -0
  19. package/resources/data/deck/learnneo/tree.json +1 -0
  20. package/resources/scss/src/apps/portal/HeaderToolbar.scss +143 -125
  21. package/resources/scss/src/apps/portal/home/MainContainer.scss +0 -45
  22. package/resources/scss/src/apps/portal/home/parts/BaseContainer.scss +27 -0
  23. package/resources/scss/src/apps/portal/home/parts/MainNeo.scss +24 -0
  24. package/resources/scss/src/apps/portal/learn/ContentTreeList.scss +1 -1
  25. package/resources/scss/src/calendar/view/EditEventContainer.scss +1 -1
  26. package/resources/scss/src/calendar/view/calendars/List.scss +1 -1
  27. package/resources/scss/src/dialog/Base.scss +1 -6
  28. package/resources/scss/theme-dark/dialog/Base.scss +1 -0
  29. package/resources/scss/theme-light/dialog/Base.scss +1 -0
  30. package/resources/scss/theme-neo-light/Global.scss +13 -11
  31. package/resources/scss/theme-neo-light/dialog/Base.scss +1 -0
  32. package/src/DefaultConfig.mjs +2 -2
  33. package/src/button/Base.mjs +3 -2
  34. package/src/calendar/view/EditEventContainer.mjs +1 -1
  35. package/src/component/Base.mjs +49 -16
  36. package/src/controller/Base.mjs +5 -5
  37. package/src/core/Observable.mjs +23 -7
  38. package/src/dialog/Base.mjs +23 -45
  39. package/src/form/field/Color.mjs +5 -5
  40. package/src/main/addon/IntersectionObserver.mjs +20 -1
  41. package/src/model/Component.mjs +11 -9
  42. package/src/selection/DateSelectorModel.mjs +2 -2
  43. package/src/util/HashHistory.mjs +45 -12
  44. package/src/worker/Base.mjs +15 -8
@@ -13,8 +13,10 @@ import VDomUtil from '../util/VDom.mjs';
13
13
  import VNodeUtil from '../util/VNode.mjs';
14
14
 
15
15
  const
16
- lengthRE = /^\d+\w+$/,
17
- addUnits = value => value == null ? value : isNaN(value) ? value : `${value}px`;
16
+ addUnits = value => value == null ? value : isNaN(value) ? value : `${value}px`,
17
+ closestController = Symbol.for('closestController'),
18
+ closestModel = Symbol.for('closestModel'),
19
+ lengthRE = /^\d+\w+$/;
18
20
 
19
21
  /**
20
22
  * @class Neo.component.Base
@@ -577,9 +579,11 @@ class Base extends CoreBase {
577
579
  afterSetDomListeners(value, oldValue) {
578
580
  let me = this;
579
581
 
580
- me.getController()?.parseDomListeners(me);
582
+ if (value?.[0] || oldValue?.[0]) {
583
+ me.getController()?.parseDomListeners(me);
581
584
 
582
- DomEventManager.updateDomListeners(me, value, oldValue)
585
+ DomEventManager.updateDomListeners(me, value, oldValue)
586
+ }
583
587
  }
584
588
 
585
589
  /**
@@ -1240,7 +1244,7 @@ class Base extends CoreBase {
1240
1244
  * Creates a KeyNavigation instance if needed.
1241
1245
  * @param {Object} value
1242
1246
  * @param {Object} oldValue
1243
- * @returns {Neo.util.KeyNavigation}
1247
+ * @returns {Neo.util.KeyNavigation|null}
1244
1248
  * @protected
1245
1249
  */
1246
1250
  beforeSetKeys(value, oldValue) {
@@ -1487,12 +1491,7 @@ class Base extends CoreBase {
1487
1491
  * @param {String} id=this.id
1488
1492
  */
1489
1493
  focus(id=this.id) {
1490
- Neo.main.DomAccess.focus({
1491
- id,
1492
- windowId: this.id
1493
- }).catch(err => {
1494
- console.log('Error attempting to receive focus for component', err, this)
1495
- })
1494
+ Neo.main.DomAccess.focus({id, windowId: this.windowId})
1496
1495
  }
1497
1496
 
1498
1497
  /**
@@ -1503,10 +1502,10 @@ class Base extends CoreBase {
1503
1502
  const result = [];
1504
1503
 
1505
1504
  if (this.floating) {
1506
- result.push('neo-floating');
1505
+ result.push('neo-floating')
1507
1506
  }
1508
1507
 
1509
- return result;
1508
+ return result
1510
1509
  }
1511
1510
 
1512
1511
  /**
@@ -1543,7 +1542,24 @@ class Base extends CoreBase {
1543
1542
  * @returns {Neo.controller.Component|null}
1544
1543
  */
1545
1544
  getController(ntype) {
1546
- return this.getConfigInstanceByNtype('controller', ntype)
1545
+ let me = this,
1546
+ controller;
1547
+
1548
+ if (!ntype) {
1549
+ controller = me[closestController];
1550
+
1551
+ if (controller) {
1552
+ return controller
1553
+ }
1554
+ }
1555
+
1556
+ controller = me.getConfigInstanceByNtype('controller', ntype);
1557
+
1558
+ if (!ntype) {
1559
+ me[closestController] = controller;
1560
+ }
1561
+
1562
+ return controller
1547
1563
  }
1548
1564
 
1549
1565
  /**
@@ -1553,7 +1569,7 @@ class Base extends CoreBase {
1553
1569
  * @returns {Promise<Neo.util.Rectangle>}
1554
1570
  */
1555
1571
  async getDomRect(id=this.id, appName=this.appName) {
1556
- const result = await Neo.main.DomAccess.getBoundingClientRect({appName, id, windowId: this.windowId});
1572
+ let result = await Neo.main.DomAccess.getBoundingClientRect({appName, id, windowId: this.windowId});
1557
1573
 
1558
1574
  if (Array.isArray(result)) {
1559
1575
  return result.map(rect => Rectangle.clone(rect))
@@ -1572,7 +1588,24 @@ class Base extends CoreBase {
1572
1588
  return null
1573
1589
  }
1574
1590
 
1575
- return this.getConfigInstanceByNtype('model', ntype)
1591
+ let me = this,
1592
+ model;
1593
+
1594
+ if (!ntype) {
1595
+ model = me[closestModel];
1596
+
1597
+ if (model) {
1598
+ return model
1599
+ }
1600
+ }
1601
+
1602
+ model = me.getConfigInstanceByNtype('model', ntype);
1603
+
1604
+ if (!ntype) {
1605
+ me[closestModel] = model
1606
+ }
1607
+
1608
+ return model
1576
1609
  }
1577
1610
 
1578
1611
  /**
@@ -96,15 +96,15 @@ class Base extends CoreBase {
96
96
  *
97
97
  */
98
98
  async onConstructed() {
99
- let me = this,
100
- currentHash = HashHistory.first(),
101
- {defaultHash} = me;
99
+ let me = this,
100
+ {defaultHash, windowId} = me,
101
+ currentHash = HashHistory.first(windowId);
102
102
 
103
103
  // get outside the construction chain => a related cmp & vm has to be constructed too
104
104
  await me.timeout(1);
105
105
 
106
106
  if (currentHash) {
107
- if (currentHash.windowId === me.windowId) {
107
+ if (currentHash.windowId === windowId) {
108
108
  await me.onHashChange(currentHash, null)
109
109
  }
110
110
  } else {
@@ -113,7 +113,7 @@ class Base extends CoreBase {
113
113
  * We only want to set a default route, in case the HashHistory is empty and there is no initial
114
114
  * value that will get consumed.
115
115
  */
116
- !Neo.config.hash && defaultHash && Neo.Main.setRoute({value: defaultHash})
116
+ !Neo.config.hash && defaultHash && Neo.Main.setRoute({value: defaultHash, windowId})
117
117
  }
118
118
  }
119
119
 
@@ -33,12 +33,28 @@ class Observable extends Base {
33
33
  * @returns {String|null} eventId null in case an object gets passed as the name (multiple ids)
34
34
  */
35
35
  addListener(name, opts, scope, eventId, data, order) {
36
- let me = this,
37
- delay = 0,
38
- nameObject = typeof name === 'object',
39
- once = false,
36
+ let me = this,
37
+ delay = 0,
38
+ eventIdObject = typeof eventId === 'object',
39
+ nameObject = typeof name === 'object',
40
+ once = false,
41
+ optsType = typeof opts,
40
42
  listener, existing, eventConfig;
41
43
 
44
+ /*
45
+ * let us support the following format too:
46
+ *
47
+ * currentWorker.on('connected', () => {
48
+ * Base.sendRemotes(className, remote)
49
+ * }, me, {once: true})
50
+ */
51
+ if (eventIdObject && optsType === 'function') {
52
+ eventId.fn = opts;
53
+ opts = eventId;
54
+ optsType = 'object';
55
+ eventId = null;
56
+ }
57
+
42
58
  if (nameObject) {
43
59
  if (name.hasOwnProperty('delay')) {
44
60
  delay = name.delay;
@@ -62,16 +78,16 @@ class Observable extends Base {
62
78
  me.addListener(key, {delay, fn: value, once, scope})
63
79
  }
64
80
  })
65
- } else if (typeof opts === 'object') {
81
+ } else if (optsType === 'object') {
66
82
  delay = delay || opts.delay;
67
83
  eventId = eventId || opts.eventId;
68
84
  listener = opts.fn;
69
85
  once = once || opts.once;
70
86
  order = order || opts.order;
71
87
  scope = scope || opts.scope
72
- } else if (typeof opts === 'function') {
88
+ } else if (optsType === 'function') {
73
89
  listener = opts
74
- } else if (typeof opts === 'string') {
90
+ } else if (optsType === 'string') {
75
91
  listener = opts // VC hook, can get parsed after onConstructed in case the view uses the parent VC
76
92
  } else {
77
93
  throw new Error('Invalid addListener call: ' + name)
@@ -136,17 +136,10 @@ class Base extends Panel {
136
136
  title_: null,
137
137
  /**
138
138
  * Set to `true` to have tabbing wrap within this Dialog.
139
- *
140
139
  * Should be used with `modal`.
141
140
  * @member {Boolean} trapFocus_=false
142
141
  */
143
- trapFocus_: false,
144
- /**
145
- * Set to `true` to have this Dialog centered in the viewport.
146
- *
147
- * @member {Boolean} centered_=false
148
- */
149
- centered_: false
142
+ trapFocus_: false
150
143
  }
151
144
 
152
145
  /**
@@ -154,22 +147,7 @@ class Base extends Panel {
154
147
  */
155
148
  construct(config) {
156
149
  super.construct(config);
157
-
158
- let me = this,
159
- {style} = me;
160
-
161
- me.createHeader();
162
-
163
- if (!me.animateTargetId && !me.centered) {
164
- Neo.assignDefaults(style, {
165
- left : '50%',
166
- top : '50%',
167
- transform: 'translate(-50%, -50%)',
168
- width : '50%'
169
- });
170
-
171
- me.style = style
172
- }
150
+ this.createHeader()
173
151
  }
174
152
 
175
153
  /**
@@ -204,17 +182,6 @@ class Base extends Panel {
204
182
  super.afterSetAppName(value, oldValue)
205
183
  }
206
184
 
207
- /**
208
- * Triggered after the centered config got changed
209
- * @param {Boolean} value
210
- * @param {Boolean} oldValue
211
- * @protected
212
- */
213
- afterSetCentered(value, oldValue) {
214
- NeoArray.toggle(this.vdom.cls, 'neo-centered', value);
215
- this.update();
216
- }
217
-
218
185
  /**
219
186
  * Triggered after the draggable config got changed
220
187
  * @param {Boolean} value
@@ -392,8 +359,12 @@ class Base extends Panel {
392
359
  {appName, id, style} = me,
393
360
  rect = await me.getDomRect(me.animateTargetId);
394
361
 
362
+ // rendered outside the visible area
395
363
  await me.render(true);
396
364
 
365
+ let [dialogRect, bodyRect] = await me.getDomRect([me.id, 'document.body']);
366
+ console.log(dialogRect, bodyRect);
367
+
397
368
  // Move to cover the animation target
398
369
  await Neo.applyDeltas(appName, {
399
370
  id,
@@ -415,11 +386,10 @@ class Base extends Panel {
415
386
  add: ['animated-hiding-showing']
416
387
  },
417
388
  style: {
418
- height : style?.height || null, // height will point to the animation origin, so we need a reset
419
- left : style?.left || '50%',
420
- top : style?.top || '50%',
421
- transform: style?.transform || 'translate(-50%, -50%)',
422
- width : style?.width || '50%'
389
+ height: style?.height || `${dialogRect.height}px`,
390
+ left : style?.left || `${Math.round(bodyRect.width / 2 - dialogRect.width / 2)}px`,
391
+ top : style?.top || `${Math.round(bodyRect.height / 2 - dialogRect.height / 2)}px`,
392
+ width : style?.width || `${dialogRect.width}px`
423
393
  }
424
394
  });
425
395
 
@@ -699,8 +669,11 @@ class Base extends Panel {
699
669
  }
700
670
  }
701
671
 
672
+ /**
673
+ *
674
+ */
702
675
  onKeyDownEscape() {
703
- this.hidden = true;
676
+ this.hidden = true
704
677
  }
705
678
 
706
679
  /**
@@ -710,7 +683,7 @@ class Base extends Panel {
710
683
  let me = this;
711
684
 
712
685
  if (animate) {
713
- me.animateShow();
686
+ me.animateShow()
714
687
  } else {
715
688
  if (!me.rendered) {
716
689
  me.render(true)
@@ -726,16 +699,21 @@ class Base extends Panel {
726
699
  * @param {String} id=this.id
727
700
  */
728
701
  syncModalMask(id=this.id) {
702
+ let {modal, windowId} = this;
703
+
729
704
  // This should sync the visibility and position of the modal mask element.
730
- Neo.main.DomAccess.syncModalMask({id, modal: this.modal})
705
+ Neo.main.DomAccess.syncModalMask({id, modal, windowId})
731
706
  }
732
707
 
733
708
  /**
734
709
  *
735
710
  */
736
711
  syncTrapFocus() {
737
- if (this.mounted) {
738
- Neo.main.DomAccess.trapFocus({id: this.id, trap: this.trapFocus})
712
+ let me = this,
713
+ {id, windowId} = me;
714
+
715
+ if (me.mounted) {
716
+ Neo.main.DomAccess.trapFocus({id, trap: me.trapFocus, windowId})
739
717
  }
740
718
  }
741
719
  }
@@ -19,9 +19,9 @@ class Color extends ComboBox {
19
19
  */
20
20
  ntype: 'colorfield',
21
21
  /**
22
- * @member {String[]} baseCls=['neo-colorfield','neo-selectfield','neo-pickerfield','neo-textfield']
22
+ * @member {String[]} baseCls=['neo-colorfield','neo-combobox','neo-pickerfield','neo-textfield']
23
23
  */
24
- baseCls: ['neo-colorfield', 'neo-selectfield', 'neo-pickerfield', 'neo-textfield'],
24
+ baseCls: ['neo-colorfield', 'neo-combobox', 'neo-pickerfield', 'neo-textfield'],
25
25
  /**
26
26
  * The data.Model field which contains the color value
27
27
  * @member {String} colorField='name'
@@ -95,10 +95,10 @@ class Color extends ComboBox {
95
95
  * @returns {String}
96
96
  */
97
97
  getColor() {
98
- let me = this,
99
- {record, value} = me;
98
+ let me = this,
99
+ {inputValue, value} = me;
100
100
 
101
- return record ? me.colorFormatter(me, record) : me.forceSelection ? null : value
101
+ return value ? me.colorFormatter(me, value) : me.forceSelection ? null : inputValue
102
102
  }
103
103
 
104
104
  /**
@@ -77,6 +77,25 @@ class NeoIntersectionObserver extends Base {
77
77
  })
78
78
  }
79
79
 
80
+ /**
81
+ * @param {IntersectionObserverEntry[]} entries
82
+ * @param {IntersectionObserver} observer
83
+ */
84
+ isVisible(entries, observer) {
85
+ let me = this,
86
+ data, path, target;
87
+
88
+ entries.forEach(entry => {
89
+ target = entry.target;
90
+ data = target.dataset && {...target.dataset} || null;
91
+ path = DomEvents.getPathFromElement(entry.target).map(e => DomEvents.getTargetData(e));
92
+
93
+ if (entry.isIntersecting) {
94
+ me.sendMessage({data, id: observer.rootId, isIntersecting: true, path, targetId: target.id})
95
+ }
96
+ })
97
+ }
98
+
80
99
  /**
81
100
  * Add more or new items into an existing observer instance
82
101
  * @param {Object} data
@@ -128,7 +147,7 @@ class NeoIntersectionObserver extends Base {
128
147
  */
129
148
  register(data) {
130
149
  let me = this,
131
- cache = me.cache,
150
+ {cache} = me,
132
151
  {id, observe} = data,
133
152
  observer;
134
153
 
@@ -153,12 +153,14 @@ class Component extends Base {
153
153
  * @protected
154
154
  */
155
155
  beforeSetStores(value, oldValue) {
156
- let controller = this.component.getController();
156
+ if (value) {
157
+ let controller = this.component.getController();
157
158
 
158
- value && Object.entries(value).forEach(([key, storeValue]) => {
159
- controller?.parseConfig(storeValue);
160
- value[key] = ClassSystemUtil.beforeSetInstance(storeValue)
161
- });
159
+ Object.entries(value).forEach(([key, storeValue]) => {
160
+ controller?.parseConfig(storeValue);
161
+ value[key] = ClassSystemUtil.beforeSetInstance(storeValue)
162
+ })
163
+ }
162
164
 
163
165
  return value
164
166
  }
@@ -445,13 +447,13 @@ class Component extends Base {
445
447
  * @returns {Neo.model.Component|null}
446
448
  */
447
449
  getParent() {
448
- let me = this;
450
+ let {parent} = this;
449
451
 
450
- if (me.parent) {
451
- return me.parent
452
+ if (parent) {
453
+ return parent
452
454
  }
453
455
 
454
- return me.component.parent?.getModel() || null
456
+ return this.component.parent?.getModel() || null
455
457
  }
456
458
 
457
459
  /**
@@ -138,7 +138,7 @@ class DateSelectorModel extends Model {
138
138
  super.register(component);
139
139
 
140
140
  let scope = {scope: this.id},
141
- {view} = this.view;
141
+ {view} = this;
142
142
 
143
143
  view.keys?._keys.push(
144
144
  {fn: 'onKeyDownDown' ,key: 'Down' ,...scope},
@@ -153,7 +153,7 @@ class DateSelectorModel extends Model {
153
153
  */
154
154
  unregister() {
155
155
  let scope = {scope: this.id},
156
- {view} = this.view;
156
+ {view} = this;
157
157
 
158
158
  view.keys?.removeKeys([
159
159
  {fn: 'onKeyDownDown' ,key: 'Down' ,...scope},
@@ -8,7 +8,6 @@ import Observable from '../core/Observable.mjs';
8
8
  */
9
9
  class HashHistory extends Base {
10
10
  /**
11
- * True automatically applies the core.Observable mixin
12
11
  * @member {Boolean} observable=true
13
12
  * @static
14
13
  */
@@ -16,7 +15,7 @@ class HashHistory extends Base {
16
15
 
17
16
  static config = {
18
17
  /**
19
- * @member {String} className='Neo.util.ClassSystem'
18
+ * @member {String} className='Neo.util.HashHistory'
20
19
  * @protected
21
20
  */
22
21
  className: 'Neo.util.HashHistory',
@@ -31,24 +30,54 @@ class HashHistory extends Base {
31
30
  */
32
31
  maxItems: 50,
33
32
  /**
34
- * @member {Array} stack=[]
33
+ * Storing one stack per windowId
34
+ * @member {Object} stacks={}
35
35
  * @protected
36
36
  */
37
- stack: []
37
+ stacks: {}
38
38
  }
39
39
 
40
40
  /**
41
+ * Convenience shortcut
42
+ * @param {Number} [windowId]
41
43
  * @returns {Object}
42
44
  */
43
- first() {
44
- return this.stack[0] || null
45
+ first(windowId) {
46
+ return this.getAt(0, windowId)
45
47
  }
46
48
 
47
49
  /**
50
+ * @param {Number} index
51
+ * @param {Number} [windowId]
48
52
  * @returns {Number}
49
53
  */
50
- getCount() {
51
- return this.stack.length
54
+ getAt(index, windowId) {
55
+ return this.getStack(windowId)[index]
56
+ }
57
+
58
+ /**
59
+ * @param {Number} [windowId]
60
+ * @returns {Number}
61
+ */
62
+ getCount(windowId) {
63
+ return this.getStack(windowId).length
64
+ }
65
+
66
+ /**
67
+ * @param {Number} [windowId]
68
+ * @returns {Number}
69
+ */
70
+ getStack(windowId) {
71
+ let me = this,
72
+ {stacks} = me,
73
+ stackId = windowId || Object.keys(stacks)[0],
74
+ stack = stacks[stackId];
75
+
76
+ if (!stack) {
77
+ stacks[stackId] = stack = []
78
+ }
79
+
80
+ return stack
52
81
  }
53
82
 
54
83
  /**
@@ -59,10 +88,12 @@ class HashHistory extends Base {
59
88
  * @param {Number} data.windowId
60
89
  */
61
90
  push(data) {
62
- let me = this,
63
- {stack} = me;
91
+ let me = this,
92
+ {windowId} = data,
93
+ stack = me.getStack(windowId);
64
94
 
65
95
  if (stack[0]?.hashString !== data.hashString) {
96
+ delete data[windowId];
66
97
  stack.unshift(data);
67
98
 
68
99
  if (stack.length > me.maxItems) {
@@ -74,10 +105,12 @@ class HashHistory extends Base {
74
105
  }
75
106
 
76
107
  /**
108
+ * Convenience shortcut
109
+ * @param {Number} [windowId]
77
110
  * @returns {Object}
78
111
  */
79
- second() {
80
- return this.stack[1] || null
112
+ second(windowId) {
113
+ return this.getAt(0, windowId)
81
114
  }
82
115
  }
83
116
 
@@ -252,9 +252,14 @@ class Base extends CoreBase {
252
252
 
253
253
  return new Promise(function(resolve, reject) {
254
254
  let message = me.sendMessage(dest, opts, transfer),
255
- msgId = message.id;
255
+ msgId = message?.id;
256
256
 
257
- me.promises[msgId] = {reject, resolve}
257
+ if (!msgId) {
258
+ // a window got closed and the message port no longer exist (SharedWorkers)
259
+ reject()
260
+ } else {
261
+ me.promises[msgId] = {reject, resolve}
262
+ }
258
263
  })
259
264
  }
260
265
 
@@ -282,22 +287,24 @@ class Base extends CoreBase {
282
287
  port = me.getPort({id: opts.port}).port
283
288
  } else if (opts.windowId) {
284
289
  portObject = me.getPort({windowId: opts.windowId});
285
- port = portObject.port;
290
+ port = portObject?.port;
286
291
 
287
- opts.port = portObject.id
292
+ opts.port = portObject?.id
288
293
  } else if (opts.appName) {
289
294
  portObject = me.getPort({appName: opts.appName});
290
- port = portObject.port;
295
+ port = portObject?.port;
291
296
 
292
- opts.port = portObject.id
297
+ opts.port = portObject?.id
293
298
  } else {
294
299
  port = me.ports[0].port
295
300
  }
296
301
  }
297
302
 
298
- message = new Message(opts);
303
+ if (port) {
304
+ message = new Message(opts);
305
+ port.postMessage(message, transfer);
306
+ }
299
307
 
300
- port.postMessage(message, transfer);
301
308
  return message
302
309
  }
303
310
  }