neo.mjs 4.6.2 → 4.6.4

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.
@@ -0,0 +1,282 @@
1
+ import Base from '../component/Base.mjs';
2
+ import ToastManager from '../manager/Toast.mjs';
3
+ import NeoArray from "../util/Array.mjs";
4
+
5
+ /**
6
+ * @class Neo.component.Toast
7
+ * @extends Neo.component.Base
8
+ *
9
+ * @example
10
+ Neo.toast({
11
+ // mandatory
12
+ appName : this.component.appName,
13
+ msg : 'Alarm was set to 11:30 for journey into Neo development',
14
+ // optional defaults
15
+ closable : true, // false
16
+ iconCls : 'fa fa-bell', // null
17
+ maxWidth : 300, // 250
18
+ position : 'br', // 'tr'
19
+ slideDirection : 'right', // 'right'
20
+ title : 'Alarm Clock' // null
21
+ })
22
+ */
23
+ class Toast extends Base {
24
+ /**
25
+ * Used by the ToastManager
26
+ * @member {Boolean} running=false
27
+ * @private
28
+ */
29
+ running = false
30
+ /**
31
+ * Timeout in ms after which the toast is removed
32
+ * @member {Number} timeout=3000
33
+ */
34
+ timeout = 3000
35
+
36
+ static getStaticConfig() {return {
37
+ /**
38
+ * Valid values for positions
39
+ * @member {String[]} positions = ['tl', 'tc', 'tr', 'bl', 'bc', 'br']
40
+ * @protected
41
+ * @static
42
+ */
43
+ positions: ['tl', 'tc', 'tr', 'bl', 'bc', 'br'],
44
+ /**
45
+ * True automatically applies the core/Observable.mjs mixin
46
+ * @member {String[]} slideDirections = ['down', 'up', 'left', 'right']
47
+ * @static
48
+ */
49
+ slideDirections: ['down', 'up', 'left', 'right']
50
+ }}
51
+
52
+ static getConfig() {return {
53
+ /**
54
+ * @member {String} className='Neo.component.Toast'
55
+ * @protected
56
+ */
57
+ className: 'Neo.component.Toast',
58
+ /**
59
+ * @member {String} ntype='toast'
60
+ * @protected
61
+ */
62
+ ntype: 'toast',
63
+ /**
64
+ * @member {String[]} baseCls=['neo-toast']
65
+ * @protected
66
+ */
67
+ baseCls: ['neo-toast'],
68
+ /**
69
+ * If true makes the toast sticky and show a close icon
70
+ * @member {Boolean} closable=false
71
+ */
72
+ closable_: false,
73
+ /**
74
+ * If set, it shows this icon in front of the text
75
+ * e.g. 'fa fa-cog'
76
+ * @member {String|null} iconCls=null
77
+ */
78
+ iconCls_: null,
79
+ /**
80
+ * Limits the width of the Toast
81
+ * @member {Number} maxWidth=250
82
+ */
83
+ maxWidth: 250,
84
+ /**
85
+ * Sets the minimum height of the Toast
86
+ * @member {Number} minHeight=50
87
+ */
88
+ minHeight: 50,
89
+ /**
90
+ * Your message. You can also pass in an iconCls
91
+ * @member {String|null} msg_=null
92
+ */
93
+ msg_: null,
94
+ /**
95
+ * Describes the position of the toast, e.g. bl=bottom-left
96
+ * This creates a cls `noe-toast-position`
97
+ * @member {'tl'|'tc'|'tr'|'bl'|'bc'|'br'} position='tr'
98
+ */
99
+ position_: 'tr',
100
+ /**
101
+ * Describes which direction from which side the toasts slides-in
102
+ * This creates a cls `neo-toast-slide-${direction}-in`
103
+ * @member {'down'|'up'|'left'|'right'} slideDirection_='right'
104
+ */
105
+ slideDirection_: 'right',
106
+ /**
107
+ * Adds a title to the toast
108
+ * @member {Number} title_=null
109
+ */
110
+ title_: null,
111
+ /**
112
+ * @member {String|null} title=null
113
+ */
114
+ vdom: {cn: [{
115
+ cls: 'neo-toast-inner', cn: [
116
+ {cls: ['neo-toast-icon'], removeDom: true},
117
+ {cls: 'neo-toast-text', cn: [
118
+ {cls: ['neo-toast-title'], removeDom: true},
119
+ {cls: 'neo-toast-msg'}
120
+ ]},
121
+ {cls: ['neo-toast-close', 'fa', 'fa-close'], removeDom: true}
122
+ ]
123
+ }]}
124
+ }}
125
+
126
+ /**
127
+ * @param {Object} config
128
+ */
129
+ construct(config) {
130
+ super.construct(config);
131
+
132
+ let me = this;
133
+
134
+ // click listener for close
135
+ me.addDomListeners([
136
+ {click: {fn: me.unregister, delegate: '.neo-toast-close', scope: me}}
137
+ ]);
138
+
139
+ ToastManager.register(me);
140
+ }
141
+
142
+ /**
143
+ * @param {String|null} value
144
+ * @param {String|null} oldValue
145
+ */
146
+ afterSetClosable(value, oldValue) {
147
+ let vdom = this.getVdomInner().cn[2];
148
+
149
+ vdom.removeDom = !value;
150
+ }
151
+
152
+ /**
153
+ * @param {String|null} value
154
+ * @param {String|null} oldValue
155
+ */
156
+ afterSetIconCls(value, oldValue) {
157
+ let vdom = this.getVdomInner().cn[0],
158
+ cls = vdom.cls,
159
+ clsFn = !!value ? 'add' : 'remove';
160
+
161
+ vdom.removeDom = Neo.isEmpty(value);
162
+ NeoArray[clsFn](cls, value);
163
+ }
164
+
165
+ /**
166
+ * Using the afterSetMsg to trigger the setup of the dom
167
+ * A new container is added as an item.
168
+ * We cannot use the vdom here.
169
+ * @param {String|null} value
170
+ * @param {String|null} oldValue
171
+ */
172
+ afterSetMsg(value, oldValue) {
173
+ let vdom = this.getTextRootVdom().cn[1];
174
+
175
+ vdom.innerHTML = value;
176
+ }
177
+
178
+ /**
179
+ * Apply a cls, based on the position
180
+ * @param {String} value
181
+ * @param {String} oldValue
182
+ */
183
+ afterSetPosition(value, oldValue) {
184
+ value && this.addCls(`neo-toast-${value}`)
185
+ }
186
+
187
+ /**
188
+ * Apply a cls, based on the slideDirection
189
+ * @param {String} value
190
+ * @param {String} oldValue
191
+ */
192
+ afterSetSlideDirection(value, oldValue) {
193
+ value && this.addCls(`neo-toast-slide-${value}-in`)
194
+ }
195
+
196
+ /**
197
+ * Close the toast after the mounted if not closable
198
+ * @param {Boolean} value
199
+ * @param {Boolean} oldValue
200
+ */
201
+ afterSetMounted(value, oldValue) {
202
+ super.afterSetMounted(value, oldValue);
203
+
204
+ let me = this;
205
+
206
+ if (!me.closable && value) {
207
+ setTimeout(() => {
208
+ this.destroy(true);
209
+ }, me.timeout)
210
+ }
211
+ }
212
+
213
+ /**
214
+ * @param {String|null} value
215
+ * @param {String|null} oldValue
216
+ */
217
+ afterSetTitle(value, oldValue) {
218
+ let vdom = this.getTextRootVdom().cn[0],
219
+ clsFn = !!value ? 'add' : 'remove';
220
+
221
+ vdom.removeDom = Neo.isEmpty(value);
222
+ vdom.innerHTML = value;
223
+ NeoArray[clsFn](vdom.cls, 'neo-toast-has-title');
224
+ }
225
+
226
+ /**
227
+ * Triggered before the position config gets changed
228
+ * @param {String} value
229
+ * @param {String} oldValue
230
+ * @protected
231
+ */
232
+ beforeSetPosition(value, oldValue) {
233
+ return this.beforeSetEnumValue(value, oldValue, 'position');
234
+ }
235
+
236
+ /**
237
+ * Triggered before the slideDirection config gets changed
238
+ * @param {String} value
239
+ * @param {String} oldValue
240
+ * @protected
241
+ */
242
+ beforeSetSlideDirection(value, oldValue) {
243
+ return this.beforeSetEnumValue(value, oldValue, 'slideDirection');
244
+ }
245
+
246
+ /**
247
+ *
248
+ */
249
+ async destroy(...args) {
250
+ let me = this;
251
+
252
+ me.addDomListeners({
253
+ animationend: function () {
254
+ ToastManager.removeToast(me.id);
255
+ ToastManager.unregister(me);
256
+ me.destroy(true);
257
+ }
258
+ });
259
+
260
+ me.addCls('neo-toast-fade-out')
261
+ }
262
+
263
+ /**
264
+ * This is a dialog, so we have to add an item to be able to
265
+ * @returns {Object} vdom
266
+ */
267
+ getTextRootVdom() {
268
+ return this.getVdomInner().cn[1];
269
+ }
270
+
271
+ /**
272
+ * This is a dialog, so we have to add an item to be able to
273
+ * @returns {Object} vdom
274
+ */
275
+ getVdomInner() {
276
+ return this.vdom.cn[0];
277
+ }
278
+ }
279
+
280
+ Neo.applyClassConfig(Toast);
281
+
282
+ export default Toast;
@@ -908,15 +908,17 @@ class Text extends Base {
908
908
  let me = this,
909
909
  cls = me.cls;
910
910
 
911
- NeoArray.add(cls, 'neo-focus');
912
- me.cls = cls;
911
+ if (!me.readOnly) {
912
+ NeoArray.add(cls, 'neo-focus');
913
+ me.cls = cls;
913
914
 
914
- if (me.labelPosition === 'inline') {
915
- if (me.centerBorderElWidth) {
916
- me.getCenterBorderEl().width = me.centerBorderElWidth;
917
- me.update();
918
- } else {
919
- me.updateCenterBorderElWidth(false);
915
+ if (me.labelPosition === 'inline') {
916
+ if (me.centerBorderElWidth) {
917
+ me.getCenterBorderEl().width = me.centerBorderElWidth;
918
+ me.update();
919
+ } else {
920
+ me.updateCenterBorderElWidth(false);
921
+ }
920
922
  }
921
923
  }
922
924
  }
@@ -931,16 +933,18 @@ class Text extends Base {
931
933
  centerBorderEl = me.getCenterBorderEl(), // labelPosition: 'inline'
932
934
  cls = me.cls;
933
935
 
934
- me.validate(); // silent
936
+ if (!me.readOnly) {
937
+ me.validate(); // silent
935
938
 
936
- NeoArray.remove(cls, 'neo-focus');
937
- me.cls = cls;
939
+ NeoArray.remove(cls, 'neo-focus');
940
+ me.cls = cls;
938
941
 
939
- if (centerBorderEl && me.isEmpty()) {
940
- delete centerBorderEl.width;
941
- }
942
+ if (centerBorderEl && me.isEmpty()) {
943
+ delete centerBorderEl.width;
944
+ }
942
945
 
943
- me.update();
946
+ me.update();
947
+ }
944
948
  }
945
949
 
946
950
  /**
@@ -969,20 +973,26 @@ class Text extends Base {
969
973
  * @param {Object} data
970
974
  */
971
975
  onMouseEnter(data) {
972
- let cls = this.cls;
976
+ let me = this,
977
+ cls = me.cls;
973
978
 
974
- NeoArray.add(cls, 'neo-hovered');
975
- this.cls = cls;
979
+ if (!me.readOnly) {
980
+ NeoArray.add(cls, 'neo-hovered');
981
+ me.cls = cls;
982
+ }
976
983
  }
977
984
 
978
985
  /**
979
986
  * @param {Object} data
980
987
  */
981
988
  onMouseLeave(data) {
982
- let cls = this.cls;
989
+ let me = this,
990
+ cls = me.cls;
983
991
 
984
- NeoArray.remove(cls, 'neo-hovered');
985
- this.cls = cls;
992
+ if (!me.readOnly) {
993
+ NeoArray.remove(cls, 'neo-hovered');
994
+ me.cls = cls;
995
+ }
986
996
  }
987
997
 
988
998
  /**
@@ -1,4 +1,5 @@
1
- import Base from './Base.mjs';
1
+ import Base from './Base.mjs';
2
+ import NeoArray from "../util/Array.mjs";
2
3
 
3
4
  /**
4
5
  * See Neo.dialog.Toast for examples
@@ -8,37 +9,29 @@ import Base from './Base.mjs';
8
9
  */
9
10
  class Toast extends Base {
10
11
  /**
11
- * This is the default config for the Neo.dialog.Toast
12
- * @member {Object}
12
+ * Using a default margin between the item
13
+ * If you switch the distance to the top or bottom you have to change this value accordingly
14
+ * @member {Number} defaultMargin=16
13
15
  */
14
- defaultToastConfig = {
15
- closable : false,
16
- cls : ['neo-toast'],
17
- maxWidth : 250,
18
- position : 'tr',
19
- running : false,
20
- slideDirection: 'down',
21
- timeout : 3000,
22
- title : null
23
- }
16
+ defaultMargin = 16
24
17
  /**
25
18
  * Currently only 1 is supported, because they would overlap
26
- * @member {1} maxToasts=1
19
+ * @member {Number} maxToasts=3
27
20
  */
28
- maxToasts = 1
21
+ maxToasts = 3
29
22
  /**
30
23
  * Counts the currently running Toasts per area
31
24
  * @member {Object} running
32
25
  */
33
26
  running = {
34
- bc: 0, bl: 0, br: 0,
35
- tc: 0, tl: 0, tr: 0
27
+ bc: [], bl: [], br: [],
28
+ tc: [], tl: [], tr: []
36
29
  }
37
30
  /**
38
31
  * If you prefer your own class to open, override here
39
- * @member {String} toastClass='Neo.dialog.Toast'
32
+ * @member {String} toastClass='Neo.component.Toast'
40
33
  */
41
- toastClass = 'Neo.dialog.Toast'
34
+ toastClass = 'Neo.component.Toast'
42
35
 
43
36
  static getConfig() {return {
44
37
  /**
@@ -53,37 +46,26 @@ class Toast extends Base {
53
46
  singleton: true
54
47
  }}
55
48
 
49
+ /**
50
+ * @param {Object} config
51
+ */
56
52
  construct(config) {
57
53
  super.construct(config);
58
54
  Neo.toast = this.createToast.bind(this);
59
55
  }
60
56
 
61
- /**
62
- * @param {Object} item
63
- */
64
- register(item) {
65
- super.register(item);
66
- this.runQueue();
67
- }
68
-
69
- /**
70
- * Removes a collection item passed by reference or key
71
- * @param {Object|String} item
72
- */
73
- unregister(item) {
74
- super.unregister(item);
75
- this.runQueue();
76
- }
77
-
78
57
  /**
79
58
  * Create the Toast definition and pass it to the Collection
80
- *
81
59
  * @param {Object} toast
82
- * @returns {Object}
60
+ * @returns {String|null}
83
61
  */
84
62
  createToast(toast) {
85
- let me = this,
86
- id;
63
+ let me = this;
64
+
65
+ if (toast.position && !me.running[toast.position]) {
66
+ Neo.logError('[Neo.manager.Toast] Supported values for slideDirection are: tl, tc, tr, bl, bc, br');
67
+ return null;
68
+ }
87
69
 
88
70
  if (!toast.msg || !toast.appName) {
89
71
  !toast.msg && Neo.logError('[Neo.manager.Toast] Toast has to define a msg');
@@ -91,18 +73,49 @@ class Toast extends Base {
91
73
  return null;
92
74
  }
93
75
 
94
- id = Neo.core.IdGenerator.getId('toastmanager-toast');
95
-
96
- toast = {
97
- id,
98
- toastManagerId: id,
99
- ...me.defaultToastConfig,
76
+ toast = Neo.create({
77
+ className: this.toastClass,
100
78
  ...toast
101
- };
79
+ });
102
80
 
103
- me.register(toast);
81
+ toast.on({
82
+ mounted: me.updateItemsInPosition,
83
+ scope : me
84
+ })
104
85
 
105
- return toast.toastManagerId;
86
+ return toast.id;
87
+ }
88
+
89
+ /**
90
+ * Find the first toast based on the maximum allowed toasts
91
+ * @returns {*}
92
+ * @private
93
+ */
94
+ findFirstToast() {
95
+ let me = this,
96
+ firstToast, item;
97
+
98
+ me.filters = [{property: 'running', value: false}];
99
+
100
+ for (item of me.map.values()) {
101
+ if (me.running[item.position].length < me.maxToasts) {
102
+ firstToast = item;
103
+ firstToast.running = true;
104
+ break;
105
+ }
106
+ }
107
+
108
+ me.clearFilters();
109
+
110
+ return firstToast;
111
+ }
112
+
113
+ /**
114
+ * @param {Object} item
115
+ */
116
+ register(item) {
117
+ super.register(item);
118
+ this.runQueue();
106
119
  }
107
120
 
108
121
  /**
@@ -110,11 +123,20 @@ class Toast extends Base {
110
123
  * @param {String} toastId
111
124
  */
112
125
  removeToast(toastId) {
113
- let me = this;
126
+ let me = this,
127
+ toast = me.get(toastId),
128
+ position;
129
+
130
+ if (!toast) {
131
+ return;
132
+ }
133
+
134
+ position = toast.position;
114
135
 
115
136
  // decrease total of displayed toasts for a position
116
- me.running[me.map.get(toastId).position]--;
117
- me.unregister(toastId);
137
+ NeoArray.remove(me.running[position], toastId);
138
+
139
+ me.updateItemsInPosition(toastId);
118
140
  }
119
141
 
120
142
  /**
@@ -131,36 +153,59 @@ class Toast extends Base {
131
153
  }
132
154
  }
133
155
 
156
+ /**
157
+ * @param {Neo.component.Toast} toast
158
+ */
134
159
  showToast(toast) {
135
- let toastConfig = Neo.clone(toast);
160
+ toast.render(true);
161
+
136
162
  // increase total of displayed toasts for a position
137
- this.running[toastConfig.position]++
138
- // Neo.create does not allow to pass an id
139
- delete toastConfig.id;
140
- Neo.create(this.toastClass, toastConfig);
163
+ this.running[toast.position].unshift(toast.id);
164
+
165
+ // todo: we could use a mounted listener
166
+ setTimeout(() => {
167
+ this.updateItemsInPosition(toast.id);
168
+ }, 50);
141
169
  }
142
170
 
143
171
  /**
144
- * Find the first toast based on the maximum allowed toasts
145
- * @returns {*}
172
+ * Removes a collection item passed by reference or key
173
+ * @param {Object|String} item
146
174
  */
147
- findFirstToast() {
148
- let me = this,
149
- firstToast, item;
150
-
151
- me.filters = [{property: 'running', value: false}];
152
- me.filter();
175
+ unregister(item) {
176
+ super.unregister(item);
177
+ this.runQueue();
178
+ }
153
179
 
154
- for (item of me.map.values()) {
155
- if (me.running[item.position] < me.maxToasts) {
156
- firstToast = item;
157
- break;
158
- }
180
+ /**
181
+ * To handle multiple toasts we handle the exact position
182
+ * from the top or bottom
183
+ * @param {String} id
184
+ * @returns {Promise<void>}
185
+ */
186
+ async updateItemsInPosition(id) {
187
+ let me = this,
188
+ toast = me.get(id),
189
+ position = toast.position,
190
+ positionArray = me.running[position],
191
+ acc = 0,
192
+ margin = me.defaultMargin,
193
+ moveTo = position.substring(0, 1) === 't' ? 'top' : 'bottom',
194
+ component, componentId, index, moveObj, rects;
195
+
196
+ rects = await toast.getDomRect(positionArray);
197
+
198
+ for ([index, componentId] of positionArray.entries()) {
199
+ component = Neo.getComponent(componentId);
200
+ moveObj = {};
201
+
202
+ acc = acc + margin;
203
+ moveObj[moveTo] = acc + 'px';
204
+ component.style = moveObj;
205
+ component.update();
206
+
207
+ acc = acc + rects[index].height;
159
208
  }
160
-
161
- me.clearFilters();
162
-
163
- return firstToast;
164
209
  }
165
210
  }
166
211
 
@@ -1,13 +0,0 @@
1
- $neoMap: map-merge($neoMap, (
2
- 'toast-background-color' : #083741,
3
- 'toast-color' : lightblue,
4
- 'toast-shadow-color' : rgba(255, 255, 255, 0.15)
5
- ));
6
-
7
- @if $useCssVars == true {
8
- :root .neo-theme-dark { // .neo-button
9
- --toast-background-color : #{neo(toast-background-color)};
10
- --toast-color : #{neo(toast-color)};
11
- --toast-shadow-color : #{neo(toast-shadow-color)};
12
- }
13
- }
@@ -1,25 +0,0 @@
1
- $neoMap: map-merge($neoMap, (
2
- 'toast-background-color' : lightblue,
3
- 'toast-color' : #083741,
4
- 'toast-shadow-color' : rgba(0, 0, 0, 0.15),
5
- // ui: danger
6
- 'toast-danger-background-color' : #e6adad,
7
- 'toast-danger-color' : #410808,
8
- // ui: success
9
- 'toast-success-background-color' : #b7e6ad,
10
- 'toast-success-color' : #124108,
11
- ));
12
-
13
- @if $useCssVars == true {
14
- :root .neo-theme-light { // .neo-button
15
- --toast-background-color : #{neo(toast-background-color)};
16
- --toast-color : #{neo(toast-color)};
17
- --toast-shadow-color : #{neo(toast-shadow-color)};
18
- // ui: danger
19
- --toast-danger-background-color : #{neo(toast-danger-background-color)};
20
- --toast-danger-color : #{neo(toast-danger-color)};
21
- // ui: success
22
- --toast-success-background-color : #{neo(toast-success-background-color)};
23
- --toast-success-color : #{neo(toast-success-color)};
24
- }
25
- }