neo.mjs 8.21.2 → 8.22.0

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.
@@ -50,4 +50,3 @@ Build with :heart: in Germany
50
50
  <br>
51
51
  <br>
52
52
  Copyright (c) 2015 - today, <a href="https://www.linkedin.com/in/tobiasuhlig/">Tobias Uhlig</a>
53
- & <a href="https://www.linkedin.com/in/richwaters/">Rich Waters</a>
@@ -85,4 +85,3 @@ https://www.contributor-covenant.org/faq. Translations are available at https://
85
85
 
86
86
  <br><br>
87
87
  Copyright (c) 2015 - today, <a href="https://www.linkedin.com/in/tobiasuhlig/">Tobias Uhlig</a>
88
- & <a href="https://www.linkedin.com/in/richwaters/">Rich Waters</a>
@@ -461,4 +461,3 @@ items: [{
461
461
 
462
462
  <br><br>
463
463
  Copyright (c) 2015 - today, <a href="https://www.linkedin.com/in/tobiasuhlig/">Tobias Uhlig</a>
464
- & <a href="https://www.linkedin.com/in/richwaters/">Rich Waters</a>
@@ -53,4 +53,3 @@ These versions also work in Firefox & Safari
53
53
 
54
54
  <br><br>
55
55
  Copyright (c) 2015 - today, <a href="https://www.linkedin.com/in/tobiasuhlig/">Tobias Uhlig</a>
56
- & <a href="https://www.linkedin.com/in/richwaters/">Rich Waters</a>
@@ -26,4 +26,3 @@ Tobi
26
26
 
27
27
  <br><br>
28
28
  Copyright (c) 2015 - today, <a href="https://www.linkedin.com/in/tobiasuhlig/">Tobias Uhlig</a>
29
- & <a href="https://www.linkedin.com/in/richwaters/">Rich Waters</a>
package/.github/STORY.md CHANGED
@@ -73,4 +73,3 @@ For more input on what you can do using neo.mjs, please take a look at the guide
73
73
 
74
74
  <br><br>
75
75
  Copyright (c) 2015 - today, <a href="https://www.linkedin.com/in/tobiasuhlig/">Tobias Uhlig</a>
76
- & <a href="https://www.linkedin.com/in/richwaters/">Rich Waters</a>
package/.github/VISION.md CHANGED
@@ -1,142 +1,18 @@
1
1
  # The neo.mjs vision
2
2
 
3
- At this point I (Tobias) can not commit to adding dates to the planned items.
4
- After investing 1.5 years of my full and unpaid working time I am in need to make up on the financial side of things.
3
+ At this point I (Tobias) can not commit to adding dates to planned items.
4
+ After investing 5 years of my full and unpaid working time I am in need to make up on the financial side of things.
5
5
  So as long as the project is not properly funded, I can only afford to continue working on it in my spare time.
6
6
 
7
7
  To speed up the current development there are two options:
8
8
  1. Help promoting neo.mjs or jump in as a contributor (see <a href="../CONTRIBUTING.md">Contributing</a>)
9
9
  2. Jump in as a sponsor to ensure I can spend more time on the neo.mjs coding side (see <a href="../BACKERS.md">Sponsors & Backers</a>)
10
10
 
11
- The following items are ***not*** ordered by priority. In case certain topics are important to you, please use the issues
12
- tracker to create an awareness (like / comment on current tickets or create new ones as needed).
13
11
 
14
12
  Thanks for your support!
15
13
 
16
- * Real World app version 2
17
- 1. Version 1 is definitely worth a look to see how to craft custom components and connect to an API,
18
- but it is not the best starting point to see how to craft an neo.mjs app. Since the requirement was to use a given
19
- Bootstrap theme, only component.Base is in use (since more advanced components require a neo.mjs CSS theme).
20
- At this point I recommend to take a look at the Docs app to learn how to craft an neo.mjs app.
21
- 2. Version 2 is intended to fill this gap and will not use the Bootstrap based theme.
22
- 3. This allows us to use the full range of neo.mjs components (Toolbar, Button, List, TabContainer, Gallery, Helix, etc.)
23
- 4. Once this app is finished, it will be the perfect starting point to learn how to use neo.mjs,
24
- so right now this item has the highest priority for me.
25
- 5. The RW2 app requires a 3rd neo.mjs theme => based on the conduit styles
26
- 6. Add the ability to switch themes inside the app
27
- * Drag & Drop (see <a href="https://github.com/neomjs/neo/issues/16">#16</a>)
28
- 1. This ticket is definitely an epic, since DD operations happen inside the main thread, while the handlers will
29
- live inside the app thread.
30
- 2. Once DD is in place, we can create a real slider component (see <a href="https://github.com/neomjs/neo/issues/18">#18</a>)
31
- 3. We can create Dialogs (Windows), which can get moved and resized (see <a href="https://github.com/neomjs/neo/issues/15">#15</a>)
32
- 4. We can create sortable tabs (see <a href="https://github.com/neomjs/neo/issues/23">#23</a>)
33
- * Docs App version 2
34
- 1. I am not planning to re-create the existing app, but enhance it with more features:
35
- 2. Support for showing mixins inside the class views (<a href="https://github.com/neomjs/neo/issues/99">#99</a>)
36
- 3. Add tooltips (especially for the Configs, Methods & Events buttons => navigation shortcut)
37
- 4. Expandable class member items (add the ability to expand or collapse items to make the list shorter)
38
- 5. Writing more guides
39
- 6. Enhance the example views:
40
- 1. The example components should get shown inside a tabContainer: first tab containing the component,
41
- second tab a source code view of the example
42
- 2. It should be possible to export the current configs (e.g. 3rd tab, configs as JSON)
43
- 3. The configuration container should be collapsible (using a sliding animation)
44
- 4. Add a second tab to the config area to show theme css vars and make them changeable
45
- 7. class member inheritance: when overriding a config or assigning a value to a parent one, the parent config
46
- should get listed (including its initial value)
47
- * Build Scripts
48
- 1. The current scripts work fine inside the neo repository. Since neo.mjs can now get used as a node module,
49
- enhancements feel necessary.
50
- 1. An npx create-app script (see <a href="https://github.com/neomjs/neo/issues/90">#90</a>)
51
- 2. The build-my-apps scripts should work as well, when used outside of the repo (manually hacked this into the
52
- Real World app version 1)
53
- * Mobile Support
54
- 1. Add touch based events (swipe, long-tap, etc.) to the global domListeners
55
- 2. Split the current events into desktop & mobile and add a Neo.config to choose which ones to use
56
- 3. Use the events based on the browser feature detection
57
- 4. Create new components and adjust current ones to better work on mobile
58
- * Mobile Docs App
59
- 1. Create a new UI or make the current one responsive
60
- * Make the Data worker more meaningful
61
- 1. Right now, the data worker only executes the XHR requests
62
- 2. Rich originally planned to let stores live inside this thread, but this would create a lot more async logic inside
63
- the app thread, plus it does not make sense for stores with little data
64
- 3. What should be possible: for remote stores which need to parse the data getting back from an API, these transformations
65
- should happen inside the app thread (like a data reader)
66
- 4. Remote stores with local sorting: this sorting could happen inside the data worker as well
67
- * Data Package version 2
68
- 1. The collection class is already very powerful (needs some polishing though). For the first version of the data
69
- package, stores were extending collection.Base. Afterwards records were introduced (not instances of data.Model,
70
- but a super lightweight extension of a JS Object). At this point, stores should no longer extend a collection,
71
- but use one instance instead (e.g. a collection config).
72
- 2. More polishing of Sorters & Filters & add the ability for stores to sort & filter per remote (adding params to
73
- each request).
74
- 3. Enhance the API for stores: when using a collection, several methods need to get bound to the collection, but
75
- ensuring that data objects get transformed into records.
76
- 4. use data fields: each store should exactly use one instance of data.Model. Inside a model, you can define fields.
77
- Fields should either be a singleton or a class with static methods. We need to provide parsing methods, e.g. toString()
78
- for a field type "String".
79
- * Finish the implementation for Tooltips; Rectangle utility class (see <a href="https://github.com/neomjs/neo/issues/51">#51</a>)
80
- * Finish the implementation for form.field.Chip (see <a href="https://github.com/neomjs/neo/issues/31">#31</a>)
81
- * Create a coding style guide (see <a href="https://github.com/neomjs/neo/issues/93">#93</a>)
82
- * Virtual Dom Engine enhancements
83
- 1. Add a 2nd mode where ids do get ignored (e.g. for comparing content on fixed positions like grid rows)
84
- 2. Add an option to specify the the tree depth to compare (e.g. only the first level for containers)
85
- 3. Refactor vdom.Helper: createDeltas
86
- * Create a buffered Grid
87
- 1. So far I mostly focused on table.Container. Since tables are not good for buffering (too many layout reflows),
88
- it is time to pick up the grid.Container implementation again.
89
- 2. The grid.Container will use divs only. grids need a rowHeight config, since it has to be fixed for buffering.
90
- 3. Add 1-2 more rows as the visible area can show and adjust the content when scrolling (move the top row div to
91
- the bottom or vice versa)
92
- 4. when clicking on the scrollbar, adjust the full grid cell content
93
- 5. add column reordering via DD (relies on the DD implementation)
94
- 6. add cell-editing
95
- 7. add action columns
96
- 8. add buffering for horizontal scrolling as well
97
- * Create a Router
98
- * Enable neo.mjs to run in node
99
- 1. There are some uses of "self", which work fine inside the main thread & inside the worker scope, but not in node
100
- * neo.mjs based middle-ware (see <a href="https://github.com/neomjs/neo/issues/19">#19</a>)
101
- * Enhance the Siesta tests to run inside a node env
102
- * Write more tests (Epic!)
103
- * main.mixins.FeatureDetection
104
- 1. Add meaningful checks for relevant features
105
- * Create a Website for the neo.mjs project
106
- 1. Right now, only Online Examples are in place
107
- 2. The examples need to support mobile
108
- 3. Intro Texts are needed (see <a href="https://github.com/neomjs/neo/issues/7">#7</a>)
109
- 4. A Logo for neo.mjs is needed
110
- * Create a MarketPlace for User Extensions / Custom Components
111
- 1. for UX which don't fit into the framework repo
112
- 2. for custom themes
113
- 3. add the ability for developers / companies to sell their extensions vs a fee (similar to the apple app store)
114
- * Create a Chrome extension to make the debugging / working with neo.mjs more easy
115
- 1. see all created stores & their data
116
- 2. Access to the component manager
117
- 3. Ability to click on the screen and receive the closest neo.mjs component
118
- 4. Ability to see the full component tree of your app
119
- * Use SharedWorker(s)
120
- 1. Optional
121
- 2. Once Chrome supports using JS modules inside shared workers
122
- 3. This will allow us to create ***multi screen apps***
123
- 4. Imagine dragging a dialog outside of your browser tab and it appears in another one
124
-
125
- ***TL-BR***: This list is most likely not even complete.
126
- You don't need to be a genius in math to figure out that this is a massive amount of work.
127
- If I would create neo.mjs just for myself, there would be no Docs app, no Real World 1 or 2 app.
128
- I already put in a massive amount of time to enable you to use neo.mjs as well and I don't expect anyone to say thank
129
- you for doing this (although it is nice to hear :)).
130
-
131
- I would even be willing to continue this project on my own, but I would run bankrupt rather sooner than later without
132
- funding. So, assuming you know how the romantic idea of Open Source works, let me repeat the first part again:
133
-
134
- To speed up the current development there are two options:
135
- 1. Help promoting neo.mjs or jump in as a contributor (see <a href="../CONTRIBUTING.md">Contributing</a>)
136
- 2. Jump in as a sponsor to ensure I can spend more time on the neo.mjs coding side (see <a href="../BACKERS.md">Sponsors & Backers</a>)
137
14
 
138
15
  Best regards, Tobias
139
16
 
140
17
  <br><br>
141
18
  Copyright (c) 2015 - today, <a href="https://www.linkedin.com/in/tobiasuhlig/">Tobias Uhlig</a>
142
- & <a href="https://www.linkedin.com/in/richwaters/">Rich Waters</a>
package/BACKERS.md CHANGED
@@ -62,4 +62,3 @@ after signing a sponsor program to provide your company logo!
62
62
 
63
63
  <br><br>
64
64
  Copyright (c) 2015 - today, <a href="https://www.linkedin.com/in/tobiasuhlig/">Tobias Uhlig</a>
65
- & <a href="https://www.linkedin.com/in/richwaters/">Rich Waters</a>
package/CONTRIBUTING.md CHANGED
@@ -37,4 +37,3 @@ No worries, you don't need to be a guru, ninja or rockstar to support the projec
37
37
 
38
38
  <br><br>
39
39
  Copyright (c) 2015 - today, <a href="https://www.linkedin.com/in/tobiasuhlig/">Tobias Uhlig</a>
40
- & <a href="https://www.linkedin.com/in/richwaters/">Rich Waters</a>
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2015 - today, Tobias Uhlig & Rich Waters
3
+ Copyright (c) 2015 - today, Tobias Uhlig
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -90,4 +90,3 @@ All Blog Posts are listed here: <a href="https://neomjs.com/dist/production/apps
90
90
 
91
91
  </br></br>
92
92
  Copyright (c) 2015 - today, <a href="https://www.linkedin.com/in/tobiasuhlig/">Tobias Uhlig</a>
93
- & <a href="https://www.linkedin.com/in/richwaters/">Rich Waters</a>
@@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase {
20
20
  */
21
21
  singleton: true,
22
22
  /**
23
- * @member {String} version='8.21.2'
23
+ * @member {String} version='8.22.0'
24
24
  */
25
- version: '8.21.2'
25
+ version: '8.22.0'
26
26
  }
27
27
 
28
28
  /**
@@ -16,7 +16,7 @@
16
16
  "@type": "Organization",
17
17
  "name": "Neo.mjs"
18
18
  },
19
- "datePublished": "2025-02-12",
19
+ "datePublished": "2025-02-18",
20
20
  "publisher": {
21
21
  "@type": "Organization",
22
22
  "name": "Neo.mjs"
@@ -37,7 +37,7 @@ class Container extends Base {
37
37
  picture : 'rwaters.png',
38
38
  profileGitHub : 'https://github.com/rwaters',
39
39
  profileLinkedIn: 'https://www.linkedin.com/in/rwaters/',
40
- teamRole : 'Co-Founder & Core Team Member'
40
+ teamRole : 'Contributor'
41
41
  }, {
42
42
  module : MemberContainer,
43
43
  location : 'Germany',
@@ -107,7 +107,7 @@ class FooterContainer extends Container {
107
107
  }, {
108
108
  module: Component,
109
109
  cls : ['neo-version'],
110
- html : 'v8.21.2'
110
+ html : 'v8.22.0'
111
111
  }]
112
112
  }],
113
113
  /**
@@ -21,15 +21,6 @@ class Component extends BaseComponent {
21
21
  vdom:
22
22
  {cn: [
23
23
  {tag: 'h1', cls: ['neo-h1'], html: 'Services'},
24
- {cls: ['info-block'], cn: [
25
- {tag: 'h2', cls: ['neo-h2'], html: 'Weekly Workshops'},
26
- {tag: 'p', html: [
27
- 'We are doing weekly workshops on Thursdays 18:30 CEST (12:30am EST) for 60m free of charge.</br>',
28
- 'Ping us inside our ',
29
- '<a href="https://join.slack.com/t/neomjs/shared_invite/zt-6c50ueeu-3E1~M4T9xkNnb~M_prEEOA">Slack Channel</a>, ',
30
- 'in case you would like to join us.'
31
- ].join('')},
32
- ]},
33
24
  {cls: ['info-block'], cn: [
34
25
  {tag: 'h2', cls: ['neo-h2'], html: 'Professional Trainings'},
35
26
  {tag: 'p', html: [
@@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase {
20
20
  */
21
21
  singleton: true,
22
22
  /**
23
- * @member {String} version='8.21.2'
23
+ * @member {String} version='8.22.0'
24
24
  */
25
- version: '8.21.2'
25
+ version: '8.22.0'
26
26
  }
27
27
 
28
28
  /**
@@ -1,6 +1,8 @@
1
- import ComboBox from '../../../src/form/field/ComboBox.mjs';
2
- import Container from '../../../src/container/Base.mjs';
3
- import Radio from '../../../src/form/field/Radio.mjs';
1
+ import * as selection from '../../../src/selection/grid/_export.mjs';
2
+ import ComboBox from '../../../src/form/field/ComboBox.mjs';
3
+ import Container from '../../../src/container/Base.mjs';
4
+ import Radio from '../../../src/form/field/Radio.mjs';
5
+ import TabContainer from '../../../src/tab/Container.mjs';
4
6
 
5
7
  /**
6
8
  * @class Neo.examples.grid.bigData.ControlsContainer
@@ -26,91 +28,137 @@ class ControlsContainer extends Container {
26
28
  handler: 'up.onControlsToggleButtonClick',
27
29
  iconCls: 'fas fa-bars'
28
30
  }, {
29
- module: Container,
31
+ module: TabContainer,
30
32
  cls : ['neo-examples-bigdata-controls-container-content'],
31
- layout: 'vbox',
32
-
33
- itemDefaults: {
34
- module : ComboBox,
35
- clearable : false,
36
- displayField: 'id',
37
- editable : false
38
- },
39
33
 
40
34
  items: [{
41
- labelText : 'Amount Rows',
42
- labelWidth: 120,
43
- listeners : {change: 'up.onAmountRowsChange'},
44
- store : ['1000', '5000', '10000', '20000', '50000'],
45
- value : '1000',
46
- width : 200
47
- }, {
48
- labelText : 'Amount Columns',
49
- labelWidth: 145,
50
- listeners : {change: 'up.onAmountColumnsChange'},
51
- store : ['10', '25', '50', '75', '100'],
52
- value : '50',
53
- width : 200
54
- }, {
55
- labelText : 'Buffer Rows',
56
- labelWidth: 145,
57
- listeners : {change: 'up.onBufferRowRangeChange'},
58
- store : ['0', '3', '5', '10', '25', '50'],
59
- value : '5',
60
- width : 200
61
- }, {
62
- labelText : 'Buffer Columns',
63
- labelWidth: 145,
64
- listeners : {change: 'up.onBufferColumnRangeChange'},
65
- store : ['0', '3', '5', '10', '20'],
66
- value : '3',
67
- width : 200
68
- }, {
69
- module : Radio,
70
- checked : true,
71
- labelText : 'Theme',
72
- labelWidth : 70,
73
- listeners : {change: 'up.onThemeRadioChange'},
74
- name : 'theme',
75
- style : {marginTop: '2em'},
76
- value : 'neo-theme-dark',
77
- valueLabelText: 'Dark'
78
- }, {
79
- module : Radio,
80
- labelText : '',
81
- labelWidth : 70,
82
- listeners : {change: 'up.onThemeRadioChange'},
83
- name : 'theme',
84
- style : {marginTop: '.3em'},
85
- value : 'neo-theme-light',
86
- valueLabelText: 'Light'
87
- }, {
88
- ntype: 'label',
89
- style: {marginTop: '2em'},
90
- text : 'Filters'
91
- }, {
92
- ntype : 'textfield',
93
- clearable : true,
94
- editable : true,
95
- labelText : 'Firstname',
96
- labelWidth: 90,
97
- listeners : {change: 'up.onFilterFieldChange'},
98
- name : 'firstname',
99
- style : {marginTop: '.3em'},
100
- width : 200
101
- }, {
102
- ntype : 'textfield',
103
- clearable : true,
104
- editable : true,
105
- labelText : 'Lastname',
106
- labelWidth: 90,
107
- listeners : {change: 'up.onFilterFieldChange'},
108
- name : 'lastname',
109
- width : 200
35
+ module: Container,
36
+ header: {text: 'Settings'},
37
+ layout: 'vbox',
38
+
39
+ itemDefaults: {
40
+ module : ComboBox,
41
+ clearable : false,
42
+ displayField: 'id',
43
+ editable : false
44
+ },
45
+
46
+ items: [{
47
+ labelText : 'Amount Rows',
48
+ labelWidth: 120,
49
+ listeners : {change: 'up.onAmountRowsChange'},
50
+ store : ['1000', '5000', '10000', '20000', '50000'],
51
+ value : '1000',
52
+ width : 200
53
+ }, {
54
+ labelText : 'Amount Columns',
55
+ labelWidth: 145,
56
+ listeners : {change: 'up.onAmountColumnsChange'},
57
+ store : ['10', '25', '50', '75', '100'],
58
+ value : '50',
59
+ width : 200
60
+ }, {
61
+ labelText : 'Buffer Rows',
62
+ labelWidth: 145,
63
+ listeners : {change: 'up.onBufferRowRangeChange'},
64
+ store : ['0', '3', '5', '10', '25', '50'],
65
+ value : '5',
66
+ width : 200
67
+ }, {
68
+ labelText : 'Buffer Columns',
69
+ labelWidth: 145,
70
+ listeners : {change: 'up.onBufferColumnRangeChange'},
71
+ store : ['0', '3', '5', '10', '20'],
72
+ value : '3',
73
+ width : 200
74
+ }, {
75
+ module : Radio,
76
+ checked : true,
77
+ labelText : 'Theme',
78
+ labelWidth : 70,
79
+ listeners : {change: 'up.onThemeRadioChange'},
80
+ name : 'theme',
81
+ style : {marginTop: '2em'},
82
+ value : 'neo-theme-dark',
83
+ valueLabelText: 'Dark'
84
+ }, {
85
+ module : Radio,
86
+ labelText : '',
87
+ labelWidth : 70,
88
+ listeners : {change: 'up.onThemeRadioChange'},
89
+ name : 'theme',
90
+ style : {marginTop: '.3em'},
91
+ value : 'neo-theme-light',
92
+ valueLabelText: 'Light'
93
+ }, {
94
+ ntype: 'label',
95
+ style: {marginTop: '2em'},
96
+ text : 'Filters'
97
+ }, {
98
+ ntype : 'textfield',
99
+ clearable : true,
100
+ editable : true,
101
+ labelText : 'Firstname',
102
+ labelWidth: 90,
103
+ listeners : {change: 'up.onFilterFieldChange'},
104
+ name : 'firstname',
105
+ style : {marginTop: '.3em'},
106
+ width : 200
107
+ }, {
108
+ ntype : 'textfield',
109
+ clearable : true,
110
+ editable : true,
111
+ labelText : 'Lastname',
112
+ labelWidth: 90,
113
+ listeners : {change: 'up.onFilterFieldChange'},
114
+ name : 'lastname',
115
+ width : 200
116
+ }, {
117
+ ntype : 'label',
118
+ reference: 'count-rows-label',
119
+ style : {marginTop: '1em'}
120
+ }]
110
121
  }, {
111
- ntype : 'label',
112
- reference: 'count-rows-label',
113
- style : {marginTop: '1em'}
122
+ module: Container,
123
+ header: {text: 'Selection'},
124
+ layout: 'vbox',
125
+
126
+ itemDefaults: {
127
+ module : Radio,
128
+ hideLabel : true,
129
+ hideValueLabel: false,
130
+ labelText : '',
131
+ listeners : {change: 'up.onSelectionModelChange'},
132
+ name : 'selectionModel',
133
+ style : {marginTop: '.3em'},
134
+ width : 200
135
+ },
136
+
137
+ items: [{
138
+ ntype: 'label',
139
+ style: {marginTop: 0},
140
+ text : 'Pick the Selection Model'
141
+ }, {
142
+ style : {marginTop: '1em'},
143
+ selectionModel: selection.CellModel,
144
+ valueLabelText: 'Cell'
145
+ }, {
146
+ selectionModel: selection.ColumnModel,
147
+ valueLabelText: 'Column'
148
+ }, {
149
+ checked : true,
150
+ selectionModel: selection.RowModel,
151
+ valueLabelText: 'Row'
152
+ }, {
153
+ selectionModel: selection.CellColumnModel,
154
+ valueLabelText: 'Cell & Column'
155
+ }, {
156
+ selectionModel: selection.CellRowModel,
157
+ valueLabelText: 'Cell & Row'
158
+ }, {
159
+ selectionModel: selection.CellColumnRowModel,
160
+ valueLabelText: 'Cell & Column & Row'
161
+ }]
114
162
  }]
115
163
  }],
116
164
  /**
@@ -204,6 +252,16 @@ class ControlsContainer extends Container {
204
252
  this.grid.store.getFilter(data.component.name).value = data.value
205
253
  }
206
254
 
255
+ /**
256
+ * @param {Object} data
257
+ */
258
+ onSelectionModelChange(data) {
259
+ this.grid.view.selectionModel = data.component.selectionModel
260
+ }
261
+
262
+ /**
263
+ *
264
+ */
207
265
  updateRowsLabel() {
208
266
  let {store} = this.grid;
209
267
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neo.mjs",
3
- "version": "8.21.2",
3
+ "version": "8.22.0",
4
4
  "description": "The webworkers driven UI framework",
5
5
  "type": "module",
6
6
  "repository": {
@@ -36,9 +36,11 @@
36
36
  "webworker",
37
37
  "ecmascript",
38
38
  "css",
39
- "json"
39
+ "json",
40
+ "react-alternative",
41
+ "angular-alternative"
40
42
  ],
41
- "author": "Tobias Uhlig, Rich Waters",
43
+ "author": "Tobias Uhlig",
42
44
  "license": "MIT",
43
45
  "bugs": {
44
46
  "url": "https://github.com/neomjs/neo/issues"
@@ -54,16 +56,16 @@
54
56
  "envinfo": "^7.14.0",
55
57
  "fs-extra": "^11.3.0",
56
58
  "highlightjs-line-numbers.js": "^2.9.0",
57
- "inquirer": "^12.4.1",
59
+ "inquirer": "^12.4.2",
58
60
  "marked": "^15.0.7",
59
61
  "monaco-editor": "0.50.0",
60
62
  "neo-jsdoc": "1.0.1",
61
63
  "neo-jsdoc-x": "1.0.5",
62
64
  "postcss": "^8.5.2",
63
- "sass": "^1.84.0",
65
+ "sass": "^1.85.0",
64
66
  "siesta-lite": "5.5.2",
65
67
  "url": "^0.11.4",
66
- "webpack": "^5.97.1",
68
+ "webpack": "^5.98.0",
67
69
  "webpack-cli": "^6.0.1",
68
70
  "webpack-dev-server": "^5.2.0",
69
71
  "webpack-hook-plugin": "^1.0.7",
@@ -15,8 +15,17 @@
15
15
  .neo-examples-bigdata-controls-container-content {
16
16
  box-shadow: 0 5px 10px rgba(75,75,75,.3);
17
17
  opacity : 0;
18
- padding : .5em 1em;
19
18
  transition: opacity 250ms ease-out;
19
+
20
+ > .neo-container {
21
+ overflow-y: auto;
22
+ padding : .5em 1em;
23
+
24
+ > .neo-container {
25
+ flex : none;
26
+ height: fit-content;
27
+ }
28
+ }
20
29
  }
21
30
 
22
31
  &.neo-expanded {
@@ -263,12 +263,12 @@ const DefaultConfig = {
263
263
  useVdomWorker: true,
264
264
  /**
265
265
  * buildScripts/injectPackageVersion.mjs will update this value
266
- * @default '8.21.2'
266
+ * @default '8.22.0'
267
267
  * @memberOf! module:Neo
268
268
  * @name config.version
269
269
  * @type String
270
270
  */
271
- version: '8.21.2'
271
+ version: '8.22.0'
272
272
  };
273
273
 
274
274
  Object.assign(DefaultConfig, {
package/src/grid/View.mjs CHANGED
@@ -81,6 +81,12 @@ class GridView extends Component {
81
81
  * @member {Object} keys
82
82
  */
83
83
  keys: {},
84
+ /**
85
+ * Stores the indexes of the first & last mounted rows, including bufferRowRange
86
+ * @member {Number[]} mountedRows=[0,0]
87
+ * @protected
88
+ */
89
+ mountedRows: [0, 0],
84
90
  /**
85
91
  * @member {String} role='rowgroup'
86
92
  */
@@ -116,6 +122,12 @@ class GridView extends Component {
116
122
  * @protected
117
123
  */
118
124
  visibleColumns_: [0, 0],
125
+ /**
126
+ * Stores the indexes of the first & last visible rows, excluding bufferRowRange
127
+ * @member {Number[]} visibleRows=[0,0]
128
+ * @protected
129
+ */
130
+ visibleRows: [0, 0],
119
131
  /**
120
132
  * @member {String[]} wrapperCls=[]
121
133
  */
@@ -146,6 +158,19 @@ class GridView extends Component {
146
158
  */
147
159
  scrollTimeoutId = null
148
160
 
161
+ /**
162
+ * @member {String[]} selectedRows
163
+ */
164
+ get selectedCells() {
165
+ let {selectionModel} = this;
166
+
167
+ if (selectionModel.ntype.includes('cell')) {
168
+ return selectionModel.items
169
+ }
170
+
171
+ return []
172
+ }
173
+
149
174
  /**
150
175
  * @member {String[]} selectedRows
151
176
  */
@@ -193,7 +218,7 @@ class GridView extends Component {
193
218
  */
194
219
  afterSetAvailableHeight(value, oldValue) {
195
220
  if (value > 0) {
196
- this.availableRows = Math.ceil(value / this.rowHeight) + 1
221
+ this.availableRows = Math.ceil(value / this.rowHeight) - 1
197
222
  }
198
223
  }
199
224
 
@@ -308,10 +333,11 @@ class GridView extends Component {
308
333
  if (value.y !== oldValue?.y) {
309
334
  newStartIndex = Math.floor(value.y / me.rowHeight);
310
335
 
311
- if (newStartIndex < bufferRowRange) {
312
- me.startIndex = 0
313
- } else if (Math.abs(me.startIndex - newStartIndex) >= bufferRowRange) {
336
+ if (Math.abs(me.startIndex - newStartIndex) >= bufferRowRange) {
314
337
  me.startIndex = newStartIndex
338
+ } else {
339
+ me.visibleRows[0] = newStartIndex;
340
+ me.visibleRows[1] = newStartIndex + me.availableRows
315
341
  }
316
342
  }
317
343
  }
@@ -359,13 +385,13 @@ class GridView extends Component {
359
385
  */
360
386
  applyRendererOutput(data) {
361
387
  let {cellId, column, columnIndex, record, rowIndex} = data,
362
- me = this,
363
- gridContainer = me.parent,
364
- {store} = me,
365
- cellCls = ['neo-grid-cell'],
366
- colspan = record[me.colspanField],
367
- {dataField} = column,
368
- fieldValue = record[dataField],
388
+ me = this,
389
+ gridContainer = me.parent,
390
+ {selectedCells, store} = me,
391
+ cellCls = ['neo-grid-cell'],
392
+ colspan = record[me.colspanField],
393
+ {dataField} = column,
394
+ fieldValue = record[dataField],
369
395
  cellConfig, rendererOutput;
370
396
 
371
397
  if (fieldValue === null || fieldValue === undefined) {
@@ -421,6 +447,10 @@ class GridView extends Component {
421
447
  cellId = me.getCellId(record, column.dataField)
422
448
  }
423
449
 
450
+ if (selectedCells.includes(cellId)) {
451
+ cellCls.push('neo-selected')
452
+ }
453
+
424
454
  cellConfig = {
425
455
  'aria-colindex': columnIndex + 1, // 1 based
426
456
  id : cellId,
@@ -553,11 +583,10 @@ class GridView extends Component {
553
583
  *
554
584
  */
555
585
  createViewData() {
556
- let me = this,
557
- {bufferRowRange, startIndex, store} = me,
558
- countRecords = store.getCount(),
559
- rows = [],
560
- endIndex, i;
586
+ let me = this,
587
+ {mountedRows, store} = me,
588
+ rows = [],
589
+ i;
561
590
 
562
591
  if (
563
592
  store.isLoading ||
@@ -569,10 +598,10 @@ class GridView extends Component {
569
598
  return
570
599
  }
571
600
 
572
- endIndex = Math.min(countRecords, me.availableRows + startIndex + bufferRowRange);
573
- startIndex = Math.max(0, startIndex - bufferRowRange);
601
+ // Creates the new start & end indexes
602
+ me.updateMountedAndVisibleRows();
574
603
 
575
- for (i=startIndex; i < endIndex; i++) {
604
+ for (i=mountedRows[0]; i < mountedRows[1]; i++) {
576
605
  rows.push(me.createRow({record: store.items[i], rowIndex: i}))
577
606
  }
578
607
 
@@ -823,6 +852,7 @@ class GridView extends Component {
823
852
  /**
824
853
  * Only triggers for vertical scrolling
825
854
  * @param {Object} data
855
+ * @protected
826
856
  */
827
857
  onScroll({scrollTop, touches}) {
828
858
  let me = this,
@@ -940,6 +970,55 @@ class GridView extends Component {
940
970
  parent.lastTouchY = 0
941
971
  }
942
972
 
973
+ /**
974
+ * Used for keyboard navigation (selection models)
975
+ * @param {Number} index
976
+ * @param {Number} step
977
+ */
978
+ scrollByRows(index, step) {
979
+ let me = this,
980
+ {mountedRows, visibleRows} = me,
981
+ countRecords = me.store.getCount(),
982
+ newIndex = index + step,
983
+ lastRowGap, mounted, scrollPosition, visible;
984
+
985
+ if (newIndex >= countRecords) {
986
+ newIndex %= countRecords;
987
+ step = newIndex - index
988
+ }
989
+
990
+ while (newIndex < 0) {
991
+ newIndex += countRecords;
992
+ step += countRecords
993
+ }
994
+
995
+ mounted = newIndex >= mountedRows[0] && newIndex <= mountedRows[1];
996
+
997
+ // Not using >= or <=, since the first / last row might not be fully visible
998
+ visible = newIndex > visibleRows[0] && newIndex < visibleRows[1];
999
+
1000
+ if (!visible) {
1001
+ // Leaving the mounted area will re-calculate the visibleRows for us
1002
+ if (mounted) {
1003
+ visibleRows[0] += step;
1004
+ visibleRows[1] += step
1005
+ }
1006
+
1007
+ if (step < 0) {
1008
+ scrollPosition = newIndex * me.rowHeight
1009
+ } else {
1010
+ lastRowGap = me.rowHeight - (me.availableHeight % me.rowHeight);
1011
+ scrollPosition = (newIndex - me.availableRows) * me.rowHeight + lastRowGap
1012
+ }
1013
+
1014
+ Neo.main.DomAccess.scrollTo({
1015
+ id : me.vdom.id,
1016
+ value : scrollPosition,
1017
+ windowId: me.windowId
1018
+ })
1019
+ }
1020
+ }
1021
+
943
1022
  /**
944
1023
  * @param {Boolean} silent=false
945
1024
  */
@@ -954,6 +1033,25 @@ class GridView extends Component {
954
1033
  }
955
1034
  }
956
1035
 
1036
+ /**
1037
+ *
1038
+ */
1039
+ updateMountedAndVisibleRows() {
1040
+ let me = this,
1041
+ {bufferRowRange, startIndex, store} = me,
1042
+ countRecords = store.getCount(),
1043
+ endIndex = Math.min(countRecords, startIndex + me.availableRows);
1044
+
1045
+ me.visibleRows[0] = startIndex; // update the array inline
1046
+ me.visibleRows[1] = endIndex;
1047
+
1048
+ startIndex = Math.max(0, startIndex - bufferRowRange);
1049
+ endIndex = Math.min(countRecords, endIndex + bufferRowRange);
1050
+
1051
+ me.mountedRows[0] = startIndex; // update the array inline
1052
+ me.mountedRows[1] = endIndex;
1053
+ }
1054
+
957
1055
  /**
958
1056
  *
959
1057
  */
@@ -888,20 +888,17 @@ class DomAccess extends Base {
888
888
  }
889
889
 
890
890
  /**
891
+ * See: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollBy
891
892
  * @param {Object} data
892
- * @param {String} data.direction left, top
893
+ * @param {String} data.behavior='auto' auto, instant, smooth
894
+ * @param {String} data.direction='top' left, top
893
895
  * @param {String} data.id
894
896
  * @param {Number} data.value
895
897
  * @returns {Object} obj.id => the passed id
896
898
  */
897
- scrollBy(data) {
898
- let node = this.getElement(data.id);
899
-
900
- if (node) {
901
- node[`scroll${Neo.capitalize(data.direction)}`] += data.value
902
- }
903
-
904
- return {id: data.id}
899
+ scrollBy({behavior='auto', direction='top', id, value}) {
900
+ this.getElement(id)?.scrollBy({behavior, [direction]: value});
901
+ return {id}
905
902
  }
906
903
 
907
904
  /**
@@ -928,7 +925,7 @@ class DomAccess extends Base {
928
925
  if (node) {
929
926
  let hasListener = 'scrollend' in window;
930
927
 
931
- hasListener && document.addEventListener('scrollend', () =>resolve(), {capture: true, once: true});
928
+ hasListener && document.addEventListener('scrollend', () => resolve(), {capture: true, once: true});
932
929
 
933
930
  node.scrollIntoView(opts);
934
931
 
@@ -941,31 +938,28 @@ class DomAccess extends Base {
941
938
  }
942
939
 
943
940
  /**
941
+ * See: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollTo
944
942
  * @param {Object} data
943
+ * @param {String} data.behavior='auto' auto, instant, smooth
945
944
  * @param {String} data.direction='top' left, top
946
945
  * @param {String} data.id
947
946
  * @param {Number} data.value
948
947
  * @returns {Object} obj.id => the passed id
949
948
  */
950
- scrollTo({direction='top', id, value}) {
951
- let node = this.getElement(id);
952
-
953
- if (node) {
954
- node[`scroll${Neo.capitalize(direction)}`] = value
955
- }
956
-
949
+ scrollTo({behavior='auto', direction='top', id, value}) {
950
+ this.getElement(id)?.scrollTo({behavior, [direction]: value});
957
951
  return {id}
958
952
  }
959
953
 
960
954
  /**
961
955
  * @param {Object} data
962
956
  * @param {String} data.id
963
- * @param {String} [data.behavior='smooth']
964
- * @param {String} [data.offset=34]
957
+ * @param {String} data.behavior='smooth'
958
+ * @param {Number} data.offset=34
965
959
  * @returns {Object} obj.id => the passed id
966
960
  */
967
- scrollToTableRow(data) {
968
- let node = this.getElement(data.id); // tr tag
961
+ scrollToTableRow({id, behavior='smooth', offset=34}) {
962
+ let node = this.getElement(id); // tr tag
969
963
 
970
964
  if (node) {
971
965
  let tableNode = node.parentNode.parentNode,
@@ -974,12 +968,12 @@ class DomAccess extends Base {
974
968
  top = node.getBoundingClientRect().top;
975
969
 
976
970
  wrapperNode.scrollTo({
977
- behavior: data.behavior || 'smooth',
978
- top : top - tableTop - (data.hasOwnProperty('offset') ? data.offset : 34)
971
+ behavior,
972
+ top: top - tableTop - offset
979
973
  })
980
974
  }
981
975
 
982
- return {id: data.id}
976
+ return {id}
983
977
  }
984
978
 
985
979
  /**
@@ -94,6 +94,8 @@ class RowModel extends BaseModel {
94
94
  rowId = view.getRowId(record);
95
95
 
96
96
  if (rowId) {
97
+ view.scrollByRows(currentIndex, step);
98
+
97
99
  me.select(rowId);
98
100
  view.fire('select', {record})
99
101
  }
@@ -0,0 +1,8 @@
1
+ import CellColumnModel from './CellColumnModel.mjs';
2
+ import CellColumnRowModel from './CellColumnRowModel.mjs';
3
+ import CellModel from './CellModel.mjs';
4
+ import CellRowModel from './CellRowModel.mjs';
5
+ import ColumnModel from './ColumnModel.mjs';
6
+ import RowModel from './RowModel.mjs';
7
+
8
+ export {CellColumnModel, CellColumnRowModel, CellModel, CellRowModel, ColumnModel, RowModel};
@@ -21,7 +21,7 @@ class ClassSystem extends Base {
21
21
  * @returns {Neo.core.Base} instance
22
22
  */
23
23
  static beforeSetInstance(config, DefaultClass=null, defaultValues={}) {
24
- let isInstance = config instanceof Neo.core.Base;
24
+ let configType = Neo.typeOf(config);
25
25
 
26
26
  if (Neo.isString(DefaultClass)) {
27
27
  DefaultClass = Neo.ns(DefaultClass)
@@ -29,9 +29,9 @@ class ClassSystem extends Base {
29
29
 
30
30
  if (!config && DefaultClass) {
31
31
  config = Neo.create(DefaultClass, defaultValues)
32
- } else if (config?.isClass) {
32
+ } else if (configType === 'NeoClass') {
33
33
  config = Neo.create(config, defaultValues)
34
- } else if (Neo.isObject(config) && !isInstance) {
34
+ } else if (configType === 'Object') {
35
35
  if (config.ntype) {
36
36
  config = Neo.ntype({
37
37
  ...defaultValues,
@@ -51,7 +51,7 @@ class ClassSystem extends Base {
51
51
 
52
52
  config = Neo.create(newConfig)
53
53
  }
54
- } else if (isInstance) {
54
+ } else if (configType === 'NeoInstance') {
55
55
  if (defaultValues?.listeners) {
56
56
  config.on(defaultValues.listeners)
57
57
  }