neo.mjs 3.0.0 → 3.0.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.
@@ -166,9 +166,7 @@ class MainContainerController extends ComponentController {
166
166
  let me = this;
167
167
 
168
168
  Neo.Main.getWindowData().then(winData => {
169
- Neo.main.DomAccess.getBoundingClientRect({
170
- id: [me.getReference(containerReference).id]
171
- }).then(data => {
169
+ me.component.getDomRect(me.getReference(containerReference).id).then(data => {
172
170
  let {height, left, top, width} = data[0];
173
171
 
174
172
  height -= 50; // popup header in Chrome
@@ -178,7 +176,7 @@ class MainContainerController extends ComponentController {
178
176
  Neo.Main.windowOpen({
179
177
  url : `../${url}/index.html`,
180
178
  windowFeatures: `height=${height},left=${left},top=${top},width=${width}`,
181
- windowName : windowName
179
+ windowName
182
180
  });
183
181
  });
184
182
  });
@@ -371,13 +369,11 @@ class MainContainerController extends ComponentController {
371
369
  onComponentConstructed() {
372
370
  super.onComponentConstructed();
373
371
 
374
- if (!Neo.config.hash) {
375
- this.onHashChange({
376
- country : 'all',
377
- hash : {mainview: 'table'},
378
- hashString: 'mainview=table'
379
- }, null);
380
- }
372
+ !Neo.config.hash && this.onHashChange({
373
+ country : 'all',
374
+ hash : {mainview: 'table'},
375
+ hashString: 'mainview=table'
376
+ }, null);
381
377
  }
382
378
 
383
379
  /**
@@ -611,16 +607,16 @@ class MainContainerController extends ComponentController {
611
607
  } else {
612
608
  [component.appName, ...me.connectedApps].forEach(appName => {
613
609
  Neo.main.addon.Stylesheet.swapStyleSheet({
614
- appName: appName,
615
- href : href,
616
- id : 'neo-theme'
610
+ appName,
611
+ href,
612
+ id: 'neo-theme'
617
613
  });
618
614
  });
619
615
  }
620
616
 
621
617
  button.set({
622
- iconCls: iconCls,
623
- text : buttonText
618
+ iconCls,
619
+ text: buttonText
624
620
  });
625
621
 
626
622
  if (mapView) {
@@ -1,4 +1,17 @@
1
1
  [
2
+ {
3
+ "author" : "Tobias Uhlig",
4
+ "authorImage" : "author_TobiasUhlig.jpeg",
5
+ "date" : "Dec 15, 2021",
6
+ "id" : 47,
7
+ "image" : "resolving-pitfalls-of-the-ecmascript-class-system.png",
8
+ "name" : "Resolving pitfalls of the ECMAScript class system",
9
+ "provider" : "Medium",
10
+ "publisher" : "ITNEXT",
11
+ "selectedInto": [],
12
+ "type" : "Blog Post",
13
+ "url" : "https://itnext.io/resolving-pitfalls-of-the-ecmascript-class-system-856024218399?source=friends_link&sk=9733342654c50d8d618671b754089e1d"
14
+ },
2
15
  {
3
16
  "author" : "Tobias Uhlig",
4
17
  "authorImage" : "author_TobiasUhlig.jpeg",
@@ -32,13 +32,15 @@ class List extends BaseList {
32
32
  * @returns {Object|Object[]|String} Either a config object to assign to the item, a vdom cn array or a html string
33
33
  */
34
34
  createItemContent(record, index) {
35
+ let id = this.getItemId(record.id);
36
+
35
37
  return [
36
- {cls: ['neo-list-item-content'], cn: [
37
- {tag: 'img', src: `../../../resources/examples/${record.image}`},
38
- {cls: ['neo-list-item-text'], cn: [
39
- {html: record.firstname},
40
- {cls: ['neo-lastname'], html: record.lastname},
41
- {cls: ['neo-is-online'], removeDom: !record.isOnline}
38
+ {cls: ['neo-list-item-content'], id: `${id}__content`, cn: [
39
+ {tag: 'img', id: `${id}__image`, src: `../../../resources/examples/${record.image}`},
40
+ {cls: ['neo-list-item-text'], id: `${id}__content_wrapper`, cn: [
41
+ {html: record.firstname, id: `${id}__firstname`},
42
+ {cls: ['neo-lastname'], id: `${id}__lastname`, html: record.lastname},
43
+ {cls: ['neo-is-online'], id: `${id}__isonline`, removeDom: !record.isOnline}
42
44
  ]}
43
45
  ]}
44
46
  ];
@@ -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
@@ -12,7 +14,8 @@ class MainContainer extends Viewport {
12
14
  static getConfig() {return {
13
15
  className: 'Neo.examples.list.animate.MainContainer',
14
16
  autoMount: true,
15
- layout : {ntype: 'vbox', align: 'stretch'}
17
+ layout : {ntype: 'vbox', align: 'stretch'},
18
+ sortBy : 'firstname'
16
19
  }}
17
20
 
18
21
  /**
@@ -36,11 +39,16 @@ class MainContainer extends Viewport {
36
39
  ntype: 'label',
37
40
  text : 'Sort by'
38
41
  }, {
39
- handler: me.changeSorting.bind(me, 'firstname'),
40
- text : 'Firstname'
42
+ field : 'firstname',
43
+ handler : me.changeSorting.bind(me, 'firstname'),
44
+ iconCls : 'fas fa-arrow-circle-up',
45
+ iconPosition: 'right',
46
+ text : 'Firstname'
41
47
  }, {
42
- handler: me.changeSorting.bind(me, 'lastname'),
43
- text : 'Lastname'
48
+ field : 'lastname',
49
+ handler : me.changeSorting.bind(me, 'lastname'),
50
+ iconPosition: 'right',
51
+ text : 'Lastname'
44
52
  }, {
45
53
  module : CheckBox,
46
54
  labelText : 'Is online',
@@ -48,6 +56,27 @@ class MainContainer extends Viewport {
48
56
  listeners : {change: me.changeIsOnlineFilter.bind(me)},
49
57
  style : {marginLeft: '50px'}
50
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 : 3000,
79
+ width : 262
51
80
  }, {
52
81
  module: List,
53
82
  store : MainStore
@@ -64,13 +93,48 @@ class MainContainer extends Viewport {
64
93
  }
65
94
 
66
95
  /**
67
- * @param {String} field
68
96
  * @param {Object} data
69
97
  */
70
- changeSorting(field, data) {
98
+ changeNameFilter(data) {
71
99
  let store = this.down({module: List}).store;
72
100
 
73
- store.sorters[0].property = field;
101
+ store.getFilter('name').value = data.value;
102
+ }
103
+
104
+ /**
105
+ * @param {String} property
106
+ * @param {Object} data
107
+ */
108
+ changeSorting(property, data) {
109
+ let me = this,
110
+ buttonFirstName = me.down({field: 'firstname'}),
111
+ buttonLastName = me.down({field: 'lastname'}),
112
+ direction = 'ASC',
113
+ store = me.down({module: List}).store,
114
+ sorter = store.sorters[0],
115
+ button;
116
+
117
+ button = property === 'firstname' ? buttonFirstName : buttonLastName;
118
+
119
+ if (property === me.sortBy) {
120
+ direction = sorter.direction === 'ASC' ? 'DESC' : 'ASC';
121
+ }
122
+
123
+ button.iconCls = `fas fa-arrow-circle-${direction === 'ASC' ? 'up' : 'down'}`;
124
+
125
+ button = button === buttonFirstName ? buttonLastName : buttonFirstName;
126
+ button.iconCls = null;
127
+
128
+ sorter.set({direction, property});
129
+
130
+ me.sortBy = property;
131
+ }
132
+
133
+ /**
134
+ * @param {Object} data
135
+ */
136
+ changeTransitionDuration(data) {
137
+ this.down({module: List}).getPlugin('animate').transitionDuration = data.value;
74
138
  }
75
139
  }
76
140
 
@@ -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;
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.0",
3
+ "version": "3.0.4",
4
4
  "description": "The webworkers driven UI framework",
5
5
  "repository": {
6
6
  "type": "git",
@@ -36,22 +36,22 @@
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.0",
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.12",
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",
47
47
  "inquirer": "^8.1.5",
48
48
  "neo-jsdoc": "^1.0.1",
49
49
  "neo-jsdoc-x": "^1.0.4",
50
- "postcss": "^8.4.4",
51
- "sass": "^1.45.0",
50
+ "postcss": "^8.4.5",
51
+ "sass": "^1.47.0",
52
52
  "webpack": "^5.65.0",
53
53
  "webpack-cli": "^4.9.1",
54
- "webpack-dev-server": "4.6.0",
54
+ "webpack-dev-server": "4.7.2",
55
55
  "webpack-hook-plugin": "^1.0.7",
56
56
  "webpack-node-externals": "^3.0.0"
57
57
  },
@@ -17,7 +17,6 @@
17
17
  border : 1px solid #1c60a0;
18
18
  border-radius : 6px;
19
19
  opacity : .9;
20
- transition : transform .5s ease-in-out;
21
20
  }
22
21
 
23
22
  .neo-list-item-content {
@@ -488,9 +488,7 @@ class YearComponent extends Component {
488
488
  if (!me.isUpdating) {
489
489
  me.isUpdating = true;
490
490
 
491
- Neo.main.DomAccess.getBoundingClientRect({
492
- id: me.id
493
- }).then(data => {
491
+ me.getDomRect().then(data => {
494
492
  scrollFromTop = me.scrollNewYearFromTop && increment < 0 || !me.scrollNewYearFromTop && increment > 0;
495
493
  vdom = me.vdom;
496
494
  y = scrollFromTop ? 0 : -data.height;
@@ -316,9 +316,7 @@ class Component extends BaseComponent {
316
316
  }
317
317
 
318
318
  setTimeout(() => {
319
- Neo.main.DomAccess.getBoundingClientRect({
320
- id: [me.vdom.cn[1].id, me.vdom.cn[0].id]
321
- }).then(data => {
319
+ me.getDomRect([me.vdom.cn[1].id, me.vdom.cn[0].id]).then(data => {
322
320
  me.headerHeight = data[1].height;
323
321
 
324
322
  Neo.main.DomAccess.scrollTo({
@@ -458,10 +458,7 @@ class Component extends BaseComponent {
458
458
  }
459
459
 
460
460
  setTimeout(() => {
461
- Neo.main.DomAccess.getBoundingClientRect({
462
- appName: me.appName,
463
- id : me.getColumnContainer().id
464
- }).then(data => {
461
+ me.getDomRect(me.getColumnContainer().id).then(data => {
465
462
  Neo.main.DomAccess.scrollBy({
466
463
  appName : me.appName,
467
464
  direction: 'left',
@@ -501,9 +501,7 @@ class EventDragZone extends DragZone {
501
501
  let me = this,
502
502
  eventDuration, offsetX, offsetY;
503
503
 
504
- Neo.main.DomAccess.getBoundingClientRect({
505
- id: [me.getDragElementRoot().id, data.path[1].id]
506
- }).then(rects => {
504
+ me.owner.getDomRect([me.getDragElementRoot().id, data.path[1].id]).then(rects => {
507
505
  eventDuration = (me.eventRecord.endDate - me.eventRecord.startDate) / 60 / 1000;
508
506
  offsetX = data.clientX - rects[0].left;
509
507
  offsetY = data.clientY - rects[0].top;
@@ -469,11 +469,13 @@ class Base extends CoreBase {
469
469
  }
470
470
 
471
471
  /**
472
+ *
473
+ * @param {Object[]} items=this._items
474
+ * @param {Boolean} silent=false
472
475
  * @protected
473
476
  */
474
- doSort() {
477
+ doSort(items=this._items, silent=false) {
475
478
  let me = this,
476
- items = me._items,
477
479
  previousItems = [...items],
478
480
  sorters = me.sorters,
479
481
  sortDirections = me.sortDirections,
@@ -558,7 +560,7 @@ class Base extends CoreBase {
558
560
 
559
561
  me[isSorted] = countSorters > 0;
560
562
 
561
- if (me[updatingIndex] === 0) {
563
+ if (!silent && me[updatingIndex] === 0) {
562
564
  me.fire('sort', {
563
565
  items: me._items,
564
566
  previousItems,
@@ -607,6 +609,8 @@ class Base extends CoreBase {
607
609
  i = 0,
608
610
  countItems = items.length,
609
611
  filteredItems = [],
612
+ needsSorting = false,
613
+ oldItems = [...me._items],
610
614
  config, isIncluded, item, j, tmpItems;
611
615
 
612
616
  for (; i < countAllFilters; i++) {
@@ -616,6 +620,10 @@ class Base extends CoreBase {
616
620
  }
617
621
 
618
622
  if (countFilters === 0 && me.allItems) {
623
+ if (me.sorters.length > 0) {
624
+ needsSorting = true;
625
+ }
626
+
619
627
  me.clear();
620
628
 
621
629
  me.items = [...me.allItems._items];
@@ -682,7 +690,16 @@ class Base extends CoreBase {
682
690
 
683
691
  me[isFiltered] = countFilters !== 0;
684
692
 
685
- me.fire('filter', me);
693
+ if (needsSorting) {
694
+ me.doSort(me.items, true);
695
+ }
696
+
697
+ me.fire('filter', {
698
+ isFiltered: me[isFiltered],
699
+ items : me.items,
700
+ oldItems,
701
+ scope : me
702
+ });
686
703
  }
687
704
 
688
705
  /**
@@ -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))) {
@@ -959,6 +959,19 @@ class Base extends CoreBase {
959
959
  return this.getConfigInstanceByNtype('controller', ntype);
960
960
  }
961
961
 
962
+ /**
963
+ * Convenience shortcut
964
+ * @param {String[]|String} id=this.id
965
+ * @param {String} appName=this.appName
966
+ * @returns {Promise<*>}
967
+ */
968
+ getDomRect(id=this.id, appName=this.appName) {
969
+ return Neo.main.DomAccess.getBoundingClientRect({
970
+ appName,
971
+ id
972
+ });
973
+ }
974
+
962
975
  /**
963
976
  * Returns this.model or the closest parent model
964
977
  * @param {String} [ntype]
@@ -400,9 +400,7 @@ class DateSelector extends Component {
400
400
  if (!me.isUpdating) {
401
401
  me.isUpdating = true;
402
402
 
403
- Neo.main.DomAccess.getBoundingClientRect({
404
- id: [me.getCenterContentEl().id, me.getHeaderMonthEl().id]
405
- }).then(data => {
403
+ me.getDomRect([me.getCenterContentEl().id, me.getHeaderMonthEl().id]).then(data => {
406
404
  vdom = me.vdom;
407
405
  x = slideDirection === 'right' ? 0 : -data[0].width;
408
406
 
@@ -486,9 +484,7 @@ class DateSelector extends Component {
486
484
  if (!me.isUpdating) {
487
485
  me.isUpdating = true;
488
486
 
489
- Neo.main.DomAccess.getBoundingClientRect({
490
- id: me.getCenterContentEl().id
491
- }).then(data => {
487
+ me.getDomRect(me.getCenterContentEl().id).then(data => {
492
488
  scrollFromTop = me.scrollNewYearFromTop && increment < 0 || !me.scrollNewYearFromTop && increment > 0;
493
489
  vdom = me.vdom;
494
490
  y = scrollFromTop ? 0 : -data.height;
@@ -158,9 +158,7 @@ class Splitter extends Component {
158
158
 
159
159
  me.style = style;
160
160
 
161
- Neo.main.DomAccess.getBoundingClientRect({
162
- id: me.parentId
163
- }).then(parentRect => {
161
+ me.getDomRect(me.parentId).then(parentRect => {
164
162
  parent = Neo.getComponent(me.parentId);
165
163
  index = parent.indexOf(me);
166
164
  sibling = parent.items[resizeNext ? index + 1 :index - 1];
@@ -107,6 +107,10 @@ class Observable extends Base {
107
107
  for (i = 0; i < len; i++) {
108
108
  eventConfig = events[i];
109
109
 
110
+ if (!Neo.isFunction(eventConfig.fn)) {
111
+ eventConfig.fn = eventConfig.scope[eventConfig.fn];
112
+ }
113
+
110
114
  eventConfig.fn.apply(eventConfig.scope || me, eventConfig.data ? args.concat(eventConfig.data) : args);
111
115
  }
112
116
  }
@@ -286,10 +286,7 @@ class Base extends Panel {
286
286
  appName = me.appName,
287
287
  id = me.getAnimateTargetId();
288
288
 
289
- Neo.main.DomAccess.getBoundingClientRect({
290
- appName: appName,
291
- id : [me.id, me.animateTargetId]
292
- }).then(rects => {
289
+ me.getDomRect([me.id, me.animateTargetId]).then(rects => {
293
290
  Neo.currentWorker.promiseMessage('main', {
294
291
  action : 'mountDom',
295
292
  appName : appName,
@@ -336,10 +333,7 @@ class Base extends Panel {
336
333
  appName = me.appName,
337
334
  id = me.getAnimateTargetId();
338
335
 
339
- Neo.main.DomAccess.getBoundingClientRect({
340
- appName: appName,
341
- id : me.animateTargetId
342
- }).then(rect => {
336
+ me.getDomRect(me.animateTargetId).then(rect => {
343
337
  Neo.currentWorker.promiseMessage('main', {
344
338
  action : 'mountDom',
345
339
  appName : appName,
@@ -530,10 +524,7 @@ class Base extends Panel {
530
524
  initialTransitionProperty, wrapperStyle;
531
525
 
532
526
  if (!me.maximized) {
533
- Neo.main.DomAccess.getBoundingClientRect({
534
- appName: me.appName,
535
- id : me.dragZone.dragProxy.id
536
- }).then(rect => {
527
+ me.getDomRect(me.dragZone.dragProxy.id).then(rect => {
537
528
  wrapperStyle = me.wrapperStyle;
538
529
 
539
530
  Object.assign(wrapperStyle, {
@@ -204,9 +204,7 @@ class SortZone extends DragZone {
204
204
  });
205
205
  });
206
206
 
207
- Neo.main.DomAccess.getBoundingClientRect({
208
- id: [owner.id].concat(owner.items.map(e => e.id))
209
- }).then(itemRects => {
207
+ owner.getDomRect([owner.id].concat(owner.items.map(e => e.id))).then(itemRects => {
210
208
  me.ownerRect = itemRects[0];
211
209
 
212
210
  ownerStyle.height = `${itemRects[0].height}px`;
@@ -143,9 +143,7 @@ class Picker extends Text {
143
143
  getClientRectsThenShow(callback, callbackScope) {
144
144
  let me = this;
145
145
 
146
- Neo.main.DomAccess.getBoundingClientRect({
147
- id: [me.id, me.getInputWrapperId(), 'body']
148
- }).then(data => {
146
+ me.getDomRect([me.id, me.getInputWrapperId(), 'body']).then(data => {
149
147
  me.clientRects = data;
150
148
  me.showPicker(callback, callbackScope);
151
149
  });
@@ -921,9 +921,7 @@ class Text extends Base {
921
921
  updateCenterBorderElWidth(silent=false) {
922
922
  let me = this;
923
923
 
924
- me.mounted && Neo.main.DomAccess.getBoundingClientRect({
925
- id: me.getCenterBorderEl().id
926
- }).then(data => {
924
+ me.mounted && me.getDomRect(me.getCenterBorderEl().id).then(data => {
927
925
  me.centerBorderElWidth = Math.round(data.width * .7) + 8;
928
926
 
929
927
  if (!silent) {
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
 
@@ -191,9 +191,9 @@ class Base extends Component {
191
191
  let me = this;
192
192
 
193
193
  value?.on({
194
- filter : me.onStoreFilter,
195
- load : me.onStoreLoad,
196
- recordChange: me.onStoreRecordChange,
194
+ filter : 'onStoreFilter',
195
+ load : 'onStoreLoad',
196
+ recordChange: 'onStoreRecordChange',
197
197
  scope : me
198
198
  });
199
199
 
@@ -1,4 +1,6 @@
1
- import Base from '../../plugin/Base.mjs';
1
+ import Base from '../../plugin/Base.mjs';
2
+ import CssUtil from '../../util/Css.mjs';
3
+ import VdomUtil from '../../util/VDom.mjs';
2
4
 
3
5
  /**
4
6
  * @class Neo.list.plugin.Animate
@@ -39,7 +41,17 @@ class Animate extends Base {
39
41
  * Read only
40
42
  * @member {Number|null} rows=null
41
43
  */
42
- rows: null
44
+ rows: null,
45
+ /**
46
+ * Time in ms. Please ensure to match the CSS based value, in case you change the default.
47
+ * @member {Number} transitionDuration_=500
48
+ */
49
+ transitionDuration_: 2000,
50
+ /**
51
+ * The id of the setTimeout() call which gets triggered after a transition is done.
52
+ * @member {Number|null} transitionTimeoutId=null
53
+ */
54
+ transitionTimeoutId: null
43
55
  }}
44
56
 
45
57
  /**
@@ -48,14 +60,16 @@ class Animate extends Base {
48
60
  construct(config) {
49
61
  super.construct(config);
50
62
 
51
- let me = this;
63
+ let me = this,
64
+ owner = me.owner;
52
65
 
53
66
  me.adjustCreateItem();
54
67
 
55
- me.owner.store.on({
56
- filter: me.onFilter,
57
- sort : me.onSort,
58
- scope : me
68
+ owner.onStoreFilter = me.onStoreFilter.bind(me);
69
+
70
+ owner.store.on({
71
+ sort : me.onStoreSort,
72
+ scope: me
59
73
  });
60
74
  }
61
75
 
@@ -70,6 +84,25 @@ class Animate extends Base {
70
84
  owner.createItem = me.createItem.bind(owner, me);
71
85
  }
72
86
 
87
+ /**
88
+ * Triggered after the transitionDuration config got changed.
89
+ *
90
+ * We do not want to apply the style to each list item itself,
91
+ * so we are using Neo.util.Css
92
+ * @param {Boolean} value
93
+ * @param {Boolean} oldValue
94
+ * @protected
95
+ */
96
+ afterSetTransitionDuration(value, oldValue) {
97
+ Neo.isNumber(oldValue) && CssUtil.deleteRules(`#${this.owner.id} .neo-list-item`);
98
+
99
+ CssUtil.insertRules([
100
+ `#${this.owner.id} .neo-list-item {`,
101
+ `transition: opacity ${value}ms ease-in-out, transform ${value}ms ease-in-out`,
102
+ '}'
103
+ ].join(''));
104
+ }
105
+
73
106
  /**
74
107
  * @param {Neo.list.plugin.Animate} me
75
108
  * @param {Object} record
@@ -77,27 +110,19 @@ class Animate extends Base {
77
110
  * @returns {Object}
78
111
  */
79
112
  createItem(me, record, index) {
80
- let item = me.ownerCreateItem(record, index),
81
- itemHeight = me.itemHeight,
82
- itemWidth = me.itemWidth,
83
- margin = me.itemMargin,
84
- style = item.style || {},
85
- column, row, x, y;
113
+ let item = me.ownerCreateItem(record, index),
114
+ position = me.getItemPosition(record, index),
115
+ style = item.style || {};
86
116
 
87
117
  if (!me.ownerRect) {
88
118
  return null;
89
119
  }
90
120
 
91
- column = index % me.columns;
92
- row = Math.floor(index / me.columns);
93
- x = column * (margin + itemWidth) + margin;
94
- y = row * (margin + itemHeight) + margin;
95
-
96
121
  Object.assign(style, {
97
- height : `${itemHeight}px`,
122
+ height : `${me.itemHeight}px`,
98
123
  position : 'absolute',
99
- transform: `translate(${x}px, ${y}px)`,
100
- width : `${itemWidth}px`
124
+ transform: `translate(${position.x}px, ${position.y}px)`,
125
+ width : `${me.itemWidth}px`
101
126
  });
102
127
 
103
128
  item.style = style;
@@ -106,10 +131,38 @@ class Animate extends Base {
106
131
  }
107
132
 
108
133
  /**
109
- * @param {Object} data
134
+ *
135
+ * @param {Object} record
136
+ * @param {Number} index
137
+ * @returns {{x: Number, y: Number}}
110
138
  */
111
- onFilter(data) {
112
- console.log('onFilter', data);
139
+ getItemPosition(record, index) {
140
+ let me = this,
141
+ column = index % me.columns,
142
+ margin = me.itemMargin,
143
+ row = Math.floor(index / me.columns),
144
+ x = column * (margin + me.itemWidth) + margin,
145
+ y = row * (margin + me.itemHeight) + margin;
146
+
147
+ return {x, y};
148
+ }
149
+
150
+ /**
151
+ *
152
+ * @param {Object} obj
153
+ * @param {String[]} map
154
+ * @param {Boolean} intercept
155
+ * @returns {Number}
156
+ */
157
+ getItemIndex(obj, map, intercept) {
158
+ if (!intercept) {
159
+ return obj.index;
160
+ }
161
+
162
+ let owner = this.owner,
163
+ key = owner.store.keyProperty;
164
+
165
+ return map.indexOf(owner.getItemId(obj.record[key]));
113
166
  }
114
167
 
115
168
  /**
@@ -118,9 +171,7 @@ class Animate extends Base {
118
171
  onOwnerMounted() {
119
172
  let me = this;
120
173
 
121
- Neo.main.DomAccess.getBoundingClientRect({
122
- id: me.owner.id
123
- }).then(rect => {
174
+ me.owner.getDomRect().then(rect => {
124
175
  Object.assign(me, {
125
176
  columns : Math.floor(rect.width / me.itemWidth),
126
177
  ownerRect: rect,
@@ -129,13 +180,126 @@ class Animate extends Base {
129
180
  });
130
181
  }
131
182
 
183
+ /**
184
+ * @param {Object} data
185
+ * @param {Boolean} data.isFiltered
186
+ * @param {Object[]} data.items
187
+ * @param {Object[]} data.oldItems
188
+ * @param {Neo.data.Store} data.scope
189
+ */
190
+ onStoreFilter(data) {
191
+ let me = this,
192
+ owner = me.owner,
193
+ key = owner.store.keyProperty,
194
+ hasAddedItems = false,
195
+ addedItems = [],
196
+ movedItems = [],
197
+ removedItems = [],
198
+ transitionTimeoutId = me.transitionTimeoutId,
199
+ intercept = !!transitionTimeoutId,
200
+ vdom = owner.vdom,
201
+ index, item, map, position, vdomIndex;
202
+
203
+ if (transitionTimeoutId) {
204
+ clearTimeout(transitionTimeoutId);
205
+ me.transitionTimeoutId = null;
206
+ }
207
+
208
+ map = intercept ? vdom.cn.map(e => e.id) : [];
209
+
210
+ data.items.forEach((record, index) => {
211
+ item = {index, record};
212
+
213
+ if (!data.oldItems.includes(record)) {
214
+ // flag items which are still inside the DOM (running remove OP)
215
+ if (intercept && map.includes(owner.getItemId(record[key]))) {
216
+ item.reAdded = true;
217
+ }
218
+
219
+ addedItems.push(item);
220
+ } else {
221
+ movedItems.push(item);
222
+ }
223
+ });
224
+
225
+ data.oldItems.forEach((record, index) => {
226
+ if (!data.items.includes(record)) {
227
+ removedItems.push({index, record});
228
+ }
229
+ });
230
+
231
+ addedItems.forEach(obj => {
232
+ if (!obj.reAdded) {
233
+ index = me.getItemIndex(obj, map, intercept);
234
+
235
+ if (index > -1) {
236
+ hasAddedItems = true;
237
+
238
+ vdom.cn.splice(index, 0, me.createItem(me, obj.record, obj.index));
239
+
240
+ obj.item = vdom.cn[index];
241
+ obj.item.style.opacity = 0;
242
+ }
243
+ }
244
+ });
245
+
246
+ if (hasAddedItems) {
247
+ owner.vdom = vdom;
248
+ }
249
+
250
+ // ensure to get into the next animation frame
251
+ setTimeout(() => {
252
+ // get the latest version of the vdom, since this is a delayed callback
253
+ vdom = owner.vdom;
254
+
255
+ // new items are already added into the vdom, while old items are not yet removed
256
+ // => we need a map to ensure getting the correct index
257
+ map = vdom.cn.map(e => e.id);
258
+
259
+ addedItems.forEach(obj => {
260
+ index = me.getItemIndex(obj, map, intercept);
261
+
262
+ if (index > -1) {
263
+ // we can change the opacity for re-added items too => the vdom engine will ignore this
264
+ vdom.cn[index].style.opacity = 1;
265
+ }
266
+ });
267
+
268
+ movedItems.forEach(obj => {
269
+ index = me.getItemIndex(obj, map, true); // honor removed items, even without interceptions
270
+
271
+ if (index > -1) {
272
+ position = me.getItemPosition(obj.record, obj.index);
273
+
274
+ Object.assign(vdom.cn[index].style, {
275
+ opacity : 1,
276
+ transform: `translate(${position.x}px, ${position.y}px)`
277
+ });
278
+ }
279
+ });
280
+
281
+ removedItems.forEach(obj => {
282
+ index = me.getItemIndex(obj, map, intercept);
283
+
284
+ if (index > -1) {
285
+ obj.item = vdom.cn[index];
286
+ obj.item.style.opacity = 0;
287
+ }
288
+ });
289
+
290
+ owner.vdom = vdom;
291
+
292
+ me.triggerTransitionCallback();
293
+ }, 50);
294
+ }
295
+
132
296
  /**
133
297
  * @param {Object} data
134
298
  * @param {Object[]} data.items
135
299
  * @param {Object[]} data.previousItems
136
300
  * @param {Neo.data.Store} data.scope
137
301
  */
138
- onSort(data) {
302
+ onStoreSort(data) {
139
303
  let me = this,
140
304
  hasChange = false,
141
305
  keyProperty = data.scope.keyProperty,
@@ -169,6 +333,19 @@ class Animate extends Base {
169
333
  }
170
334
  }
171
335
  }
336
+
337
+ /**
338
+ *
339
+ */
340
+ triggerTransitionCallback() {
341
+ let me = this;
342
+
343
+ me.transitionTimeoutId = setTimeout(() => {
344
+ me.transitionTimeoutId = null;
345
+
346
+ me.owner.createItems();
347
+ }, me.transitionDuration);
348
+ }
172
349
  }
173
350
 
174
351
  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
 
package/src/menu/List.mjs CHANGED
@@ -193,9 +193,7 @@ class List extends BaseList {
193
193
 
194
194
  me.store.destroy();
195
195
 
196
- if (activeSubMenu) {
197
- activeSubMenu.unmount();
198
- }
196
+ activeSubMenu?.unmount();
199
197
 
200
198
  Object.entries(subMenuMap).forEach(([key, value]) => {
201
199
  value.destroy();
@@ -279,10 +277,7 @@ class List extends BaseList {
279
277
 
280
278
  if (me.activeSubMenu !== me.subMenuMap?.[me.getMenuMapId(recordId)]) {
281
279
  me.hideSubMenu();
282
-
283
- if (me.hasChildren(record)) {
284
- me.showSubMenu(nodeId, record);
285
- }
280
+ me.hasChildren(record) && me.showSubMenu(nodeId, record);
286
281
  }
287
282
  }
288
283
 
@@ -299,10 +294,7 @@ class List extends BaseList {
299
294
  subMenu = subMenuMap[subMenuMapId],
300
295
  menuStyle, style;
301
296
 
302
- Neo.main.DomAccess.getBoundingClientRect({
303
- appName: me.appName,
304
- id : nodeId
305
- }).then(rect => {
297
+ me.getDomRect(nodeId).then(rect => {
306
298
  style = {
307
299
  left: `${rect.right + me.subMenuGap}px`,
308
300
  top : `${rect.top - 1}px` // minus the border
@@ -324,7 +316,7 @@ class List extends BaseList {
324
316
  parentId : Neo.apps[me.appName].mainView.id,
325
317
  parentIndex: store.indexOf(record),
326
318
  parentMenu : me,
327
- style : style,
319
+ style,
328
320
  zIndex : me.zIndex + 1
329
321
  });
330
322
  }
@@ -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
  /**
@@ -396,7 +396,7 @@ class Resizable extends Base {
396
396
  me.isDragging = true;
397
397
 
398
398
  style.opacity = 0.3;
399
- me.owner.wrapperStyle = style;
399
+ owner.wrapperStyle = style;
400
400
 
401
401
  for (; i < len; i++) {
402
402
  target = data.path[i];
@@ -414,10 +414,7 @@ class Resizable extends Base {
414
414
  }
415
415
 
416
416
  if (!me.boundaryContainerRect) {
417
- Neo.main.DomAccess.getBoundingClientRect({
418
- appName: appName,
419
- id : me.boundaryContainerRect
420
- }).then(rect => {
417
+ owner.getDomRect(me.boundaryContainerRect).then(rect => {
421
418
  me.boundaryContainerRect = rect;
422
419
  });
423
420
  }
package/src/tab/Strip.mjs CHANGED
@@ -80,9 +80,7 @@ class Strip extends Component {
80
80
  ids = ids.map(e => e?.id).filter(Boolean);
81
81
 
82
82
  if (me.useActiveTabIndicator) {
83
- Neo.main.DomAccess.getBoundingClientRect({
84
- id: ids
85
- }).then(data => {
83
+ me.getDomRect(ids).then(data => {
86
84
  me.moveActiveIndicator(data);
87
85
  });
88
86
  }
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
  }