neo.mjs 3.0.2 → 3.0.6

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.
@@ -36,7 +36,7 @@ class List extends BaseList {
36
36
 
37
37
  return [
38
38
  {cls: ['neo-list-item-content'], id: `${id}__content`, cn: [
39
- {tag: 'img', id: `${id}__image`, src: `../../../resources/examples/${record.image}`},
39
+ {tag: 'img', id: `${id}__image`, src: `${Neo.config.resourcesPath}examples/${record.image}`},
40
40
  {cls: ['neo-list-item-text'], id: `${id}__content_wrapper`, cn: [
41
41
  {html: record.firstname, id: `${id}__firstname`},
42
42
  {cls: ['neo-lastname'], id: `${id}__lastname`, html: record.lastname},
@@ -1,8 +1,10 @@
1
- import CheckBox from '../../../src/form/field/CheckBox.mjs';
2
- import List from './List.mjs';
3
- import MainStore from './MainStore.mjs';
4
- import Toolbar from '../../../src/container/Toolbar.mjs';
5
- import Viewport from '../../../src/container/Viewport.mjs';
1
+ import CheckBox from '../../../src/form/field/CheckBox.mjs';
2
+ import List from './List.mjs';
3
+ import MainStore from './MainStore.mjs';
4
+ import NumberField from '../../../src/form/field/Number.mjs';
5
+ import TextField from '../../../src/form/field/Text.mjs';
6
+ import Toolbar from '../../../src/container/Toolbar.mjs';
7
+ import Viewport from '../../../src/container/Viewport.mjs';
6
8
 
7
9
  /**
8
10
  * @class Neo.examples.list.animate.MainContainer
@@ -54,6 +56,27 @@ class MainContainer extends Viewport {
54
56
  listeners : {change: me.changeIsOnlineFilter.bind(me)},
55
57
  style : {marginLeft: '50px'}
56
58
  }]
59
+ }, {
60
+ module : TextField,
61
+ flex : 'none',
62
+ labelText : 'Search',
63
+ labelWidth: 60,
64
+ listeners : {change: me.changeNameFilter.bind(me)},
65
+ style : {marginLeft: '10px'},
66
+ width : 262
67
+ }, {
68
+ module : NumberField,
69
+ clearToOriginalValue: true,
70
+ flex : 'none',
71
+ labelText : 'Transition Duration',
72
+ labelWidth : 150,
73
+ listeners : {change: me.changeTransitionDuration.bind(me)},
74
+ maxValue : 5000,
75
+ minValue : 100,
76
+ stepSize : 100,
77
+ style : {marginLeft: '10px'},
78
+ value : 500,
79
+ width : 262
57
80
  }, {
58
81
  module: List,
59
82
  store : MainStore
@@ -69,6 +92,15 @@ class MainContainer extends Viewport {
69
92
  store.getFilter('isOnline').disabled = !data.value;
70
93
  }
71
94
 
95
+ /**
96
+ * @param {Object} data
97
+ */
98
+ changeNameFilter(data) {
99
+ let store = this.down({module: List}).store;
100
+
101
+ store.getFilter('name').value = data.value;
102
+ }
103
+
72
104
  /**
73
105
  * @param {String} property
74
106
  * @param {Object} data
@@ -97,6 +129,13 @@ class MainContainer extends Viewport {
97
129
 
98
130
  me.sortBy = property;
99
131
  }
132
+
133
+ /**
134
+ * @param {Object} data
135
+ */
136
+ changeTransitionDuration(data) {
137
+ this.down({module: List}).getPlugin('animate').transitionDuration = data.value;
138
+ }
100
139
  }
101
140
 
102
141
  Neo.applyClassConfig(MainContainer);
@@ -17,6 +17,23 @@ class MainStore extends Store {
17
17
  disabled : true,
18
18
  property : 'isOnline',
19
19
  value : true
20
+ }, {
21
+ property : 'name',
22
+ value : null,
23
+
24
+ filterBy: opts => {
25
+ let record = opts.item,
26
+ value = opts.value?.toLowerCase();
27
+
28
+ if (value) {
29
+ return !(
30
+ record.firstname.toLowerCase().includes(value) ||
31
+ record.lastname .toLowerCase().includes(value)
32
+ );
33
+ }
34
+
35
+ return false;
36
+ }
20
37
  }],
21
38
 
22
39
  sorters: [{
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neo.mjs",
3
- "version": "3.0.2",
3
+ "version": "3.0.6",
4
4
  "description": "The webworkers driven UI framework",
5
5
  "repository": {
6
6
  "type": "git",
@@ -36,11 +36,11 @@
36
36
  "@fortawesome/fontawesome-free": "^5.15.4",
37
37
  "@material/mwc-button": "^0.25.3",
38
38
  "@material/mwc-textfield": "^0.25.3",
39
- "autoprefixer": "^10.4.1",
39
+ "autoprefixer": "^10.4.2",
40
40
  "chalk": "^4.1.2",
41
41
  "clean-webpack-plugin": "^4.0.0",
42
42
  "commander": "^8.3.0",
43
- "cssnano": "^5.0.14",
43
+ "cssnano": "^5.0.15",
44
44
  "envinfo": "^7.8.1",
45
45
  "fs-extra": "^10.0.0",
46
46
  "highlightjs-line-numbers.js": "^2.8.0",
@@ -48,7 +48,7 @@
48
48
  "neo-jsdoc": "^1.0.1",
49
49
  "neo-jsdoc-x": "^1.0.4",
50
50
  "postcss": "^8.4.5",
51
- "sass": "^1.45.2",
51
+ "sass": "^1.47.0",
52
52
  "webpack": "^5.65.0",
53
53
  "webpack-cli": "^4.9.1",
54
54
  "webpack-dev-server": "4.7.2",
@@ -17,7 +17,6 @@
17
17
  border : 1px solid #1c60a0;
18
18
  border-radius : 6px;
19
19
  opacity : .9;
20
- transition : opacity .5s ease-in-out, transform .5s ease-in-out;
21
20
  }
22
21
 
23
22
  .neo-list-item-content {
@@ -40,7 +40,7 @@ class Filter extends Base {
40
40
  */
41
41
  disabled_: false,
42
42
  /**
43
- * Provide a custom filtering function, has a higher priority than property, operator & value
43
+ * Provide a custom filtering function which has a higher priority than property, operator & value
44
44
  * @member {Function|null} filterBy_=null
45
45
  */
46
46
  filterBy_: null,
@@ -173,7 +173,12 @@ class Filter extends Base {
173
173
  }
174
174
 
175
175
  if (me._filterBy) {
176
- return me.filterBy.call(me.scope || me, item, filteredItems, allItems);
176
+ return me.filterBy.call(me.scope || me, {
177
+ allItems,
178
+ filteredItems,
179
+ item,
180
+ value: me._value
181
+ });
177
182
  }
178
183
 
179
184
  if (me.includeEmptyValues && (me._value === null || Neo.isEmpty(me._value))) {
package/src/list/Base.mjs CHANGED
@@ -132,7 +132,7 @@ class Base extends Component {
132
132
  plugins.push({
133
133
  module : module.default,
134
134
  appName: me.appName,
135
- flag : 'animate',
135
+ id : 'animate',
136
136
  ...me.pluginAnimateConfig
137
137
  });
138
138
 
@@ -1,10 +1,21 @@
1
- import Base from '../../plugin/Base.mjs';
1
+ import Base from '../../plugin/Base.mjs';
2
+ import CssUtil from '../../util/Css.mjs';
2
3
 
3
4
  /**
4
5
  * @class Neo.list.plugin.Animate
5
6
  * @extends Neo.plugin.Base
6
7
  */
7
8
  class Animate extends Base {
9
+ static getStaticConfig() {return {
10
+ /**
11
+ * Valid values for transitionEasing
12
+ * @member {String[]} transitionEasings=['ease','ease-in','ease-out','ease-in-out','linear']
13
+ * @protected
14
+ * @static
15
+ */
16
+ transitionEasings: ['ease', 'ease-in', 'ease-out', 'ease-in-out', 'linear']
17
+ }}
18
+
8
19
  static getConfig() {return {
9
20
  /**
10
21
  * @member {String} className='Neo.list.plugin.Animate'
@@ -42,9 +53,20 @@ class Animate extends Base {
42
53
  rows: null,
43
54
  /**
44
55
  * Time in ms. Please ensure to match the CSS based value, in case you change the default.
45
- * @member {Number} transitionDuration=500
56
+ * @member {Number} transitionDuration_=500
57
+ */
58
+ transitionDuration_: 500,
59
+ /**
60
+ * The easing used for fadeIn, fadeOut and position changes.
61
+ * Valid values: 'ease','ease-in','ease-out','ease-in-out','linear'
62
+ * @member {String} transitionEasing_='ease-in-out'
63
+ */
64
+ transitionEasing_: 'ease-in-out',
65
+ /**
66
+ * The id of the setTimeout() call which gets triggered after a transition is done.
67
+ * @member {Number|null} transitionTimeoutId=null
46
68
  */
47
- transitionDuration: 500
69
+ transitionTimeoutId: null
48
70
  }}
49
71
 
50
72
  /**
@@ -61,9 +83,11 @@ class Animate extends Base {
61
83
  owner.onStoreFilter = me.onStoreFilter.bind(me);
62
84
 
63
85
  owner.store.on({
64
- sort : me.onSort,
86
+ sort : me.onStoreSort,
65
87
  scope: me
66
88
  });
89
+
90
+ this.updateTransitionDetails(false);
67
91
  }
68
92
 
69
93
  /**
@@ -77,6 +101,36 @@ class Animate extends Base {
77
101
  owner.createItem = me.createItem.bind(owner, me);
78
102
  }
79
103
 
104
+ /**
105
+ * Triggered after the transitionDuration config got changed.
106
+ * @param {Boolean} value
107
+ * @param {Boolean} oldValue
108
+ * @protected
109
+ */
110
+ afterSetTransitionDuration(value, oldValue) {
111
+ this.isConstructed && this.updateTransitionDetails(Neo.isNumber(oldValue));
112
+ }
113
+
114
+ /**
115
+ * Triggered after the transitionEasing config got changed.
116
+ * @param {Number} value
117
+ * @param {Number} oldValue
118
+ * @protected
119
+ */
120
+ afterSetTransitionEasing(value, oldValue) {
121
+ this.isConstructed && this.updateTransitionDetails(!!oldValue);
122
+ }
123
+
124
+ /**
125
+ * Triggered before the transitionEasing config gets changed
126
+ * @param {String} value
127
+ * @param {String} oldValue
128
+ * @protected
129
+ */
130
+ beforeSetTransitionEasing(value, oldValue) {
131
+ return this.beforeSetEnumValue(value, oldValue, 'transitionEasing');
132
+ }
133
+
80
134
  /**
81
135
  * @param {Neo.list.plugin.Animate} me
82
136
  * @param {Object} record
@@ -105,7 +159,6 @@ class Animate extends Base {
105
159
  }
106
160
 
107
161
  /**
108
- *
109
162
  * @param {Object} record
110
163
  * @param {Number} index
111
164
  * @returns {{x: Number, y: Number}}
@@ -121,6 +174,23 @@ class Animate extends Base {
121
174
  return {x, y};
122
175
  }
123
176
 
177
+ /**
178
+ * @param {Object} obj
179
+ * @param {String[]} map
180
+ * @param {Boolean} intercept
181
+ * @returns {Number}
182
+ */
183
+ getItemIndex(obj, map, intercept) {
184
+ if (!intercept) {
185
+ return obj.index;
186
+ }
187
+
188
+ let owner = this.owner,
189
+ key = owner.store.keyProperty;
190
+
191
+ return map.indexOf(owner.getItemId(obj.record[key]));
192
+ }
193
+
124
194
  /**
125
195
  *
126
196
  */
@@ -136,47 +206,6 @@ class Animate extends Base {
136
206
  });
137
207
  }
138
208
 
139
- /**
140
- * @param {Object} data
141
- * @param {Object[]} data.items
142
- * @param {Object[]} data.previousItems
143
- * @param {Neo.data.Store} data.scope
144
- */
145
- onSort(data) {
146
- let me = this,
147
- hasChange = false,
148
- keyProperty = data.scope.keyProperty,
149
- owner = me.owner,
150
- newVdomCn = [],
151
- vdom = owner.vdom,
152
- vdomMap = vdom.cn.map(e => e.id),
153
- fromIndex, itemId;
154
-
155
- if (vdomMap.length > 0) {
156
- data.items.forEach((item, index) => {
157
- itemId = owner.getItemId(item[keyProperty]);
158
- fromIndex = vdomMap.indexOf(itemId);
159
-
160
- newVdomCn.push(vdom.cn[fromIndex]);
161
-
162
- if (fromIndex !== index) {
163
- hasChange = true;
164
- }
165
- });
166
-
167
- if (hasChange) {
168
- owner.vdom.cn = newVdomCn;
169
-
170
- owner.promiseVdomUpdate().then(() => {
171
- // we need to ensure to get this call into the next animation frame
172
- setTimeout(() => {
173
- owner.createItems();
174
- }, 50);
175
- });
176
- }
177
- }
178
- }
179
-
180
209
  /**
181
210
  * @param {Object} data
182
211
  * @param {Boolean} data.isFiltered
@@ -185,19 +214,37 @@ class Animate extends Base {
185
214
  * @param {Neo.data.Store} data.scope
186
215
  */
187
216
  onStoreFilter(data) {
188
- let me = this,
189
- owner = me.owner,
190
- addedItems = [],
191
- movedItems = [],
192
- removedItems = [],
193
- vdom = owner.vdom,
194
- index, map, position;
217
+ let me = this,
218
+ owner = me.owner,
219
+ key = owner.store.keyProperty,
220
+ hasAddedItems = false,
221
+ addedItems = [],
222
+ movedItems = [],
223
+ removedItems = [],
224
+ transitionTimeoutId = me.transitionTimeoutId,
225
+ intercept = !!transitionTimeoutId,
226
+ vdom = owner.vdom,
227
+ index, item, map, position;
228
+
229
+ if (transitionTimeoutId) {
230
+ clearTimeout(transitionTimeoutId);
231
+ me.transitionTimeoutId = null;
232
+ }
233
+
234
+ map = intercept ? vdom.cn.map(e => e.id) : [];
195
235
 
196
236
  data.items.forEach((record, index) => {
237
+ item = {index, record};
238
+
197
239
  if (!data.oldItems.includes(record)) {
198
- addedItems.push({index, record});
240
+ // flag items which are still inside the DOM (running remove OP)
241
+ if (intercept && map.includes(owner.getItemId(record[key]))) {
242
+ item.reAdded = true;
243
+ }
244
+
245
+ addedItems.push(item);
199
246
  } else {
200
- movedItems.push({index, record});
247
+ movedItems.push(item);
201
248
  }
202
249
  });
203
250
 
@@ -208,44 +255,144 @@ class Animate extends Base {
208
255
  });
209
256
 
210
257
  addedItems.forEach(obj => {
211
- vdom.cn.splice(obj.index, 0, me.createItem(me, obj.record, obj.index));
258
+ if (!obj.reAdded) {
259
+ index = me.getItemIndex(obj, map, intercept);
260
+
261
+ if (index > -1) {
262
+ hasAddedItems = true;
263
+
264
+ vdom.cn.splice(index, 0, me.createItem(me, obj.record, obj.index));
212
265
 
213
- obj.item = vdom.cn[obj.index];
214
- obj.item.style.opacity = 0;
266
+ obj.item = vdom.cn[index];
267
+ obj.item.style.opacity = 0;
268
+ }
269
+ }
215
270
  });
216
271
 
217
- owner.vdom = vdom;
272
+ if (hasAddedItems) {
273
+ owner.vdom = vdom;
274
+ }
218
275
 
219
276
  // ensure to get into the next animation frame
220
277
  setTimeout(() => {
278
+ // get the latest version of the vdom, since this is a delayed callback
221
279
  vdom = owner.vdom;
222
280
 
223
- addedItems.forEach(obj => {
224
- vdom.cn[obj.index].style.opacity = 1;
225
- });
226
-
227
281
  // new items are already added into the vdom, while old items are not yet removed
228
282
  // => we need a map to ensure getting the correct index
229
283
  map = vdom.cn.map(e => e.id);
230
284
 
285
+ addedItems.forEach(obj => {
286
+ index = me.getItemIndex(obj, map, intercept);
287
+
288
+ if (index > -1) {
289
+ // we can change the opacity for re-added items too => the vdom engine will ignore this
290
+ vdom.cn[index].style.opacity = 1;
291
+ }
292
+ });
293
+
231
294
  movedItems.forEach(obj => {
232
- index = map.indexOf(owner.getItemId(obj.record[owner.store.keyProperty]));
233
- position = me.getItemPosition(obj.record, obj.index);
234
- vdom.cn[index].style.transform = `translate(${position.x}px, ${position.y}px)`;
295
+ index = me.getItemIndex(obj, map, true); // honor removed items, even without interceptions
296
+
297
+ if (index > -1) {
298
+ position = me.getItemPosition(obj.record, obj.index);
299
+
300
+ Object.assign(vdom.cn[index].style, {
301
+ opacity : 1,
302
+ transform: `translate(${position.x}px, ${position.y}px)`
303
+ });
304
+ }
235
305
  });
236
306
 
237
307
  removedItems.forEach(obj => {
238
- obj.item = vdom.cn[obj.index];
239
- obj.item.style.opacity = 0;
308
+ index = me.getItemIndex(obj, map, intercept);
309
+
310
+ if (index > -1) {
311
+ obj.item = vdom.cn[index];
312
+ obj.item.style.opacity = 0;
313
+ }
240
314
  });
241
315
 
242
316
  owner.vdom = vdom;
243
317
 
244
- setTimeout(() => {
245
- owner.createItems();
246
- }, me.transitionDuration);
318
+ me.triggerTransitionCallback();
247
319
  }, 50);
248
320
  }
321
+
322
+ /**
323
+ * @param {Object} data
324
+ * @param {Object[]} data.items
325
+ * @param {Object[]} data.previousItems
326
+ * @param {Neo.data.Store} data.scope
327
+ */
328
+ onStoreSort(data) {
329
+ let me = this,
330
+ hasChange = false,
331
+ keyProperty = data.scope.keyProperty,
332
+ owner = me.owner,
333
+ newVdomCn = [],
334
+ vdom = owner.vdom,
335
+ vdomMap = vdom.cn.map(e => e.id),
336
+ fromIndex, itemId;
337
+
338
+ if (vdomMap.length > 0) {
339
+ data.items.forEach((item, index) => {
340
+ itemId = owner.getItemId(item[keyProperty]);
341
+ fromIndex = vdomMap.indexOf(itemId);
342
+
343
+ newVdomCn.push(vdom.cn[fromIndex]);
344
+
345
+ if (fromIndex !== index) {
346
+ hasChange = true;
347
+ }
348
+ });
349
+
350
+ if (hasChange) {
351
+ owner.vdom.cn = newVdomCn;
352
+
353
+ owner.vdom = vdom;
354
+
355
+ // we need to ensure to get this call into the next animation frame
356
+ setTimeout(() => {
357
+ owner.createItems();
358
+ }, 50);
359
+ }
360
+ }
361
+ }
362
+
363
+ /**
364
+ *
365
+ */
366
+ triggerTransitionCallback() {
367
+ let me = this;
368
+
369
+ me.transitionTimeoutId = setTimeout(() => {
370
+ me.transitionTimeoutId = null;
371
+
372
+ me.owner.createItems();
373
+ }, me.transitionDuration);
374
+ }
375
+
376
+ /**
377
+ * We do not want to apply the style to each list item itself,
378
+ * so we are using Neo.util.Css
379
+ * @param {Boolean} deleteRule
380
+ * @protected
381
+ */
382
+ updateTransitionDetails(deleteRule) {
383
+ let me = this,
384
+ duration = me.transitionDuration,
385
+ easing = me.transitionEasing,
386
+ id = me.owner.id;
387
+
388
+ deleteRule && CssUtil.deleteRules(`#${id} .neo-list-item`);
389
+
390
+ CssUtil.insertRules([
391
+ `#${id} .neo-list-item {`,
392
+ `transition: opacity ${duration}ms ${easing}, transform ${duration}ms ${easing}`,
393
+ '}'
394
+ ].join(''));
395
+ }
249
396
  }
250
397
 
251
398
  Neo.applyClassConfig(Animate);
@@ -8,6 +8,12 @@ import Base from '../../core/Base.mjs';
8
8
  * @singleton
9
9
  */
10
10
  class Stylesheet extends Base {
11
+ /**
12
+ * @member {String} dynamicStyleSheetId='neo-dynamic-stylesheet'
13
+ * @protected
14
+ */
15
+ dynamicStyleSheetId = 'neo-dynamic-stylesheet';
16
+
11
17
  static getConfig() {return {
12
18
  /**
13
19
  * @member {String} className='Neo.main.addon.Stylesheet'
@@ -23,6 +29,7 @@ class Stylesheet extends Base {
23
29
  app: [
24
30
  'addThemeFiles',
25
31
  'createStyleSheet',
32
+ 'deleteCssRules',
26
33
  'insertCssRules',
27
34
  'swapStyleSheet'
28
35
  ]
@@ -137,6 +144,32 @@ class Stylesheet extends Base {
137
144
  document.head.appendChild(link);
138
145
  }
139
146
 
147
+ /**
148
+ * @param {Object} data
149
+ * @param {Array} data.rules
150
+ * @protected
151
+ */
152
+ deleteCssRules(data) {
153
+ let styleEl = document.getElementById(this.dynamicStyleSheetId),
154
+ styleSheet = styleEl.sheet,
155
+ cssRules = styleSheet.cssRules,
156
+ i = 0,
157
+ len = data.rules.length,
158
+ j, rulesLen;
159
+
160
+ for (; i < len; i++) {
161
+ j = 0;
162
+ rulesLen = cssRules.length;
163
+
164
+ for (; j < rulesLen; j++) {
165
+ if (cssRules[j].selectorText === data.rules[i]) {
166
+ styleSheet.deleteRule(j);
167
+ break;
168
+ }
169
+ }
170
+ }
171
+ }
172
+
140
173
  /**
141
174
  * @param {String} token
142
175
  * @returns {Boolean}
@@ -162,7 +195,7 @@ class Stylesheet extends Base {
162
195
  * @protected
163
196
  */
164
197
  insertCssRules(data) {
165
- let styleEl = document.getElementById('neoDynamicStyleSheet'),
198
+ let styleEl = document.getElementById(this.dynamicStyleSheetId),
166
199
  i = 0,
167
200
  len = data.rules.length,
168
201
  styleSheet;
@@ -170,7 +203,7 @@ class Stylesheet extends Base {
170
203
  if (!styleEl) {
171
204
  styleEl = document.createElement('style');
172
205
 
173
- styleEl.id = 'neoDynamicStyleSheet';
206
+ styleEl.id = this.dynamicStyleSheetId;
174
207
  document.head.appendChild(styleEl);
175
208
  }
176
209
 
@@ -33,7 +33,11 @@ class Base extends CoreBase {
33
33
 
34
34
  let me = this;
35
35
 
36
- me.owner.on('mounted', me.onOwnerMounted, me);
36
+ if (me.owner.mounted) {
37
+ me.onOwnerMounted();
38
+ } else {
39
+ me.owner.on('mounted', me.onOwnerMounted, me);
40
+ }
37
41
  }
38
42
 
39
43
  /**
package/src/util/Css.mjs CHANGED
@@ -14,15 +14,29 @@ class Css extends Base {
14
14
  }}
15
15
 
16
16
  /**
17
- * @param {Array} rules
17
+ * Pass the selectorText of the rules which you want to remove
18
+ * @param {String[]|String} rules
19
+ */
20
+ static deleteRules(rules) {
21
+ if (!Array.isArray(rules)) {
22
+ rules = [rules];
23
+ }
24
+
25
+ Neo.main.addon.Stylesheet.deleteCssRules({
26
+ rules: rules
27
+ });
28
+ }
29
+
30
+ /**
31
+ * @param {String[]|String} rules
18
32
  */
19
33
  static insertRules(rules) {
34
+ if (!Array.isArray(rules)) {
35
+ rules = [rules];
36
+ }
37
+
20
38
  Neo.main.addon.Stylesheet.insertCssRules({
21
39
  rules: rules
22
- }).then(function(data) {
23
- // console.log('inserted CSS rules', data);
24
- }).catch(function(err) {
25
- console.log('App: Got error attempting to insert CSS rules', err, rules);
26
40
  });
27
41
  }
28
42
  }