neo.mjs 10.1.0 → 10.2.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.
- package/.github/RELEASE_NOTES/v10.1.1.md +13 -0
- package/.github/RELEASE_NOTES/v10.2.0.md +34 -0
- package/ServiceWorker.mjs +2 -2
- package/apps/covid/view/country/Gallery.mjs +1 -1
- package/apps/covid/view/country/Helix.mjs +1 -1
- package/apps/covid/view/country/Table.mjs +27 -29
- package/apps/portal/index.html +1 -1
- package/apps/portal/resources/data/blog.json +12 -0
- package/apps/portal/view/home/FooterContainer.mjs +1 -1
- package/apps/sharedcovid/view/country/Gallery.mjs +1 -1
- package/apps/sharedcovid/view/country/Helix.mjs +1 -1
- package/apps/sharedcovid/view/country/Table.mjs +22 -22
- package/examples/grid/bigData/ControlsContainer.mjs +14 -0
- package/examples/stateProvider/inline/MainContainer.mjs +1 -1
- package/examples/stateProvider/twoWay/MainContainer.mjs +2 -2
- package/examples/treeAccordion/MainContainer.mjs +1 -1
- package/learn/blog/v10-deep-dive-functional-components.md +107 -97
- package/learn/blog/v10-deep-dive-reactivity.md +3 -3
- package/learn/blog/v10-deep-dive-state-provider.md +42 -137
- package/learn/blog/v10-deep-dive-vdom-revolution.md +35 -61
- package/learn/blog/v10-post1-love-story.md +3 -3
- package/learn/gettingstarted/DescribingTheUI.md +108 -33
- package/learn/guides/fundamentals/ConfigSystemDeepDive.md +118 -18
- package/learn/guides/fundamentals/InstanceLifecycle.md +121 -84
- package/learn/tree.json +1 -0
- package/learn/tutorials/CreatingAFunctionalButton.md +179 -0
- package/package.json +3 -3
- package/src/DefaultConfig.mjs +2 -2
- package/src/collection/Base.mjs +8 -3
- package/src/data/Store.mjs +8 -3
- package/src/date/SelectorContainer.mjs +2 -2
- package/src/form/field/Base.mjs +15 -1
- package/src/form/field/ComboBox.mjs +5 -15
- package/src/functional/component/Base.mjs +26 -0
- package/src/functional/util/html.mjs +75 -0
- package/src/state/Provider.mjs +7 -4
- package/src/tree/Accordion.mjs +1 -1
- package/test/siesta/siesta.js +8 -1
- package/test/siesta/tests/CollectionBase.mjs +46 -0
- package/test/siesta/tests/form/field/AfterSetValueSequence.mjs +106 -0
- package/test/siesta/tests/functional/HtmlTemplateComponent.mjs +92 -0
- package/test/siesta/tests/state/FeedbackLoop.mjs +159 -0
- package/test/siesta/tests/state/Provider.mjs +56 -0
@@ -0,0 +1,13 @@
|
|
1
|
+
# Neo.mjs v10.1.1 Release Notes
|
2
|
+
|
3
|
+
This is a patch release that addresses a critical regression bug affecting drag-and-drop operations in grids.
|
4
|
+
|
5
|
+
## Bug Fixes
|
6
|
+
|
7
|
+
### Grid Column Drag & Drop
|
8
|
+
|
9
|
+
- Fixed a bug in `Neo.collection.Base` where the `move()` method would fail to correctly swap adjacent items. This was caused by an unsafe, nested `splice()` operation that could lead to unpredictable behavior.
|
10
|
+
- The direct impact of this bug was the failure of drag-and-drop for grid column reordering, which is a significant regression.
|
11
|
+
- The `move()` method has been refactored to use a safer, two-step approach, ensuring the stability of all collection-based move operations.
|
12
|
+
|
13
|
+
This fix restores the expected drag-and-drop functionality for grid columns.
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# Neo.mjs v10.2.0 Release Notes
|
2
|
+
|
3
|
+
## Title
|
4
|
+
v10.2.0: Stabilization, DX Improvements & Template PoC
|
5
|
+
|
6
|
+
## Introduction
|
7
|
+
|
8
|
+
Following the major architectural changes in v10.0.0 with the new core and state provider, v10.2.0 is focused on stabilization, refinement, and developer experience. This release adjusts our demonstration applications to the new architecture, fixes key regressions in the single and multi-window Covid apps, and polishes the Big Data Grid example.
|
9
|
+
|
10
|
+
A key highlight is the introduction of an early-stage proof-of-concept for string-based VDOM templates, offering a more intuitive way to build functional components.
|
11
|
+
|
12
|
+
## Key Changes
|
13
|
+
|
14
|
+
### New Features & Enhancements
|
15
|
+
|
16
|
+
- **PoC: String-Based VDOM Templates**: An initial proof-of-concept for using tagged template literals to define VDOM in functional components has been introduced. This feature can be enabled via the `enableHtmlTemplates` config and provides a more familiar, HTML-like syntax for building component views. See commit `a3bbca76653ffc822a102e9bde76dea3d8e7bb8b`.
|
17
|
+
|
18
|
+
- **Synchronous `fireChangeEvent`**: The `fireChangeEvent` method in `ComboBox` and other form fields is now fully synchronous. This resolves critical timing issues in applications like the Covid tracker, where dependent logic needs to execute immediately after a value changes to ensure state consistency. See issue [#7129](https://github.com/neomjs/neo/issues/7129).
|
19
|
+
|
20
|
+
- **Optimized ComboBox Selection**: The `afterSetValue` logic for single-select `ComboBox` fields has been optimized to prevent unnecessary processing when the selection has not changed, improving performance. See issue [#7125](https://github.com/neomjs/neo/issues/7125).
|
21
|
+
|
22
|
+
- **Refined StateProvider Logic**: The `StateProvider` change notification logic has been improved to be more precise, preventing unnecessary component updates and ensuring a more efficient data flow. See issue [#7124](https://github.com/neomjs/neo/issues/7124).
|
23
|
+
|
24
|
+
### Bug Fixes
|
25
|
+
|
26
|
+
- **Form Field Value Sequencing**: Addressed a bug related to the sequencing of `afterSetValue` calls in form fields, ensuring that values are processed in the correct order. See issue [#7127](https://github.com/neomjs/neo/issues/7127).
|
27
|
+
|
28
|
+
- **Big Data Grid Example**: Fixed an issue in the Big Data Grid example where dropdowns in the controls container were not showing their initial values correctly. See issue [#7126](https://github.com/neomjs/neo/issues/7126).
|
29
|
+
|
30
|
+
- **Covid App Regression**: Fixed a regression bug in the Covid app that caused an infinite selection loop when choosing a country from the header dropdown. See issue [#7123](https://github.com/neomjs/neo/issues/7123).
|
31
|
+
|
32
|
+
- **SharedCovid App Regression**: Resolved a regression bug in the SharedCovid app where clicking on a table row would throw an error. See issue [#7122](https://github.com/neomjs/neo/issues/7122).
|
33
|
+
|
34
|
+
- **State Provider Feedback Loop**: Added a new test case to prevent feedback loops within the `StateProvider`, enhancing its reliability. See issue [#7128](https://github.com/neomjs/neo/issues/7128).
|
package/ServiceWorker.mjs
CHANGED
@@ -21,8 +21,18 @@ class Table extends Container {
|
|
21
21
|
* @member {Object} bind
|
22
22
|
*/
|
23
23
|
bind: {
|
24
|
-
country: {
|
24
|
+
country: {key: 'country', twoWay: true}
|
25
25
|
},
|
26
|
+
/**
|
27
|
+
* @member {String|null} country_=null
|
28
|
+
* @reactive
|
29
|
+
*/
|
30
|
+
country_: null,
|
31
|
+
/**
|
32
|
+
* @member {Neo.data.Store} store=CountryStore
|
33
|
+
* @reactive
|
34
|
+
*/
|
35
|
+
store: CountryStore,
|
26
36
|
/**
|
27
37
|
* Default configs for each column
|
28
38
|
* @member {Object} columnDefaults
|
@@ -52,15 +62,15 @@ class Table extends Container {
|
|
52
62
|
text : 'Country',
|
53
63
|
width : 200,
|
54
64
|
|
55
|
-
renderer
|
65
|
+
renderer(data) {
|
56
66
|
return {
|
57
67
|
cls : ['neo-country-column', 'neo-table-cell'],
|
58
68
|
html: [
|
59
69
|
'<div style="display: flex; align-items: center">',
|
60
|
-
|
70
|
+
'<img style="height:20px; margin-right:10px; width:20px;" src="' + Util.getCountryFlagUrl(data.value) + '">' + data.value,
|
61
71
|
'</div>'
|
62
72
|
].join('')
|
63
|
-
}
|
73
|
+
}
|
64
74
|
}
|
65
75
|
}, {
|
66
76
|
dataField: 'cases',
|
@@ -101,17 +111,7 @@ class Table extends Container {
|
|
101
111
|
}, {
|
102
112
|
dataField: 'testsPerOneMillion',
|
103
113
|
text : 'Tests / 1M'
|
104
|
-
}]
|
105
|
-
/**
|
106
|
-
* @member {String|null} country_=null
|
107
|
-
* @reactive
|
108
|
-
*/
|
109
|
-
country_: null,
|
110
|
-
/**
|
111
|
-
* @member {Neo.data.Store} store=CountryStore
|
112
|
-
* @reactive
|
113
|
-
*/
|
114
|
-
store: CountryStore
|
114
|
+
}]
|
115
115
|
}
|
116
116
|
|
117
117
|
/**
|
@@ -123,22 +123,20 @@ class Table extends Container {
|
|
123
123
|
afterSetCountry(value, oldValue) {
|
124
124
|
if (oldValue !== undefined) {
|
125
125
|
let me = this,
|
126
|
-
{
|
127
|
-
{selectionModel} =
|
126
|
+
{body} = me,
|
127
|
+
{selectionModel} = body,
|
128
128
|
id;
|
129
129
|
|
130
|
-
if (
|
131
|
-
|
132
|
-
id = `${view.id}__tr__${value}`; // the store can not be loaded on the first selection
|
130
|
+
if (value) {
|
131
|
+
id = `${body.id}__tr__${value}`; // The store might not be loaded on the first selection
|
133
132
|
|
134
|
-
|
135
|
-
|
133
|
+
if (!selectionModel.isSelected(id)) {
|
134
|
+
selectionModel.select(id);
|
136
135
|
|
137
|
-
|
138
|
-
}
|
139
|
-
} else {
|
140
|
-
selectionModel.deselectAll();
|
136
|
+
me.mounted && Neo.main.DomAccess.scrollToTableRow({id: id})
|
141
137
|
}
|
138
|
+
} else {
|
139
|
+
selectionModel.deselectAll()
|
142
140
|
}
|
143
141
|
}
|
144
142
|
}
|
@@ -148,7 +146,7 @@ class Table extends Container {
|
|
148
146
|
* @param {String[]} items
|
149
147
|
*/
|
150
148
|
onDeselect(items) {
|
151
|
-
this.country = null
|
149
|
+
this.country = null
|
152
150
|
}
|
153
151
|
|
154
152
|
/**
|
@@ -161,12 +159,12 @@ class Table extends Container {
|
|
161
159
|
|
162
160
|
if (me.store.getCount() > 0) {
|
163
161
|
if (item) {
|
164
|
-
item = me.
|
162
|
+
item = me.body.getRecordByRowId(item)?.country
|
165
163
|
}
|
166
164
|
|
167
165
|
// in case getRecordByRowId() has no match, the initial row creation will include the selection
|
168
166
|
if (item) {
|
169
|
-
me.country = item
|
167
|
+
me.country = item
|
170
168
|
}
|
171
169
|
}
|
172
170
|
}
|
package/apps/portal/index.html
CHANGED
@@ -1,4 +1,16 @@
|
|
1
1
|
[{
|
2
|
+
"author" : "Tobias Uhlig",
|
3
|
+
"authorImage" : "author_TobiasUhlig.jpeg",
|
4
|
+
"date" : "Jul 28, 2025",
|
5
|
+
"id" : 67,
|
6
|
+
"image" : "DesigningFunctionalComponents.png",
|
7
|
+
"name" : "Designing Functional Components for a Multi-Threaded WorldDesigning Functional Components for a Multi-Threaded World",
|
8
|
+
"provider" : "Medium",
|
9
|
+
"publisher" : "ITNEXT",
|
10
|
+
"selectedInto": [],
|
11
|
+
"type" : "Blog Post",
|
12
|
+
"url" : "https://itnext.io/designing-functional-components-for-a-multi-threaded-world-22f27119509a?source=friends_link&sk=5434dd074a70c23a9ce0e8c78a98fed8"
|
13
|
+
}, {
|
2
14
|
"author" : "Tobias Uhlig",
|
3
15
|
"authorImage" : "author_TobiasUhlig.jpeg",
|
4
16
|
"date" : "Jul 24, 2025",
|
@@ -21,8 +21,18 @@ class Table extends Container {
|
|
21
21
|
* @member {Object} bind
|
22
22
|
*/
|
23
23
|
bind: {
|
24
|
-
country: {
|
24
|
+
country: {key: 'country', twoWay: true}
|
25
25
|
},
|
26
|
+
/**
|
27
|
+
* @member {String|null} country_=null
|
28
|
+
* @reactive
|
29
|
+
*/
|
30
|
+
country_: null,
|
31
|
+
/**
|
32
|
+
* @member {Neo.data.Store} store=CountryStore
|
33
|
+
* @reactive
|
34
|
+
*/
|
35
|
+
store: CountryStore,
|
26
36
|
/**
|
27
37
|
* Default configs for each column
|
28
38
|
* @member {Object} columnDefaults
|
@@ -52,7 +62,7 @@ class Table extends Container {
|
|
52
62
|
text : 'Country',
|
53
63
|
width : 200,
|
54
64
|
|
55
|
-
renderer
|
65
|
+
renderer(data) {
|
56
66
|
return {
|
57
67
|
cls : ['neo-country-column', 'neo-table-cell'],
|
58
68
|
html: [
|
@@ -60,7 +70,7 @@ class Table extends Container {
|
|
60
70
|
'<img style="height:20px; margin-right:10px; width:20px;" src="' + Util.getCountryFlagUrl(data.value) + '">' + data.value,
|
61
71
|
'</div>'
|
62
72
|
].join('')
|
63
|
-
}
|
73
|
+
}
|
64
74
|
}
|
65
75
|
}, {
|
66
76
|
dataField: 'cases',
|
@@ -101,17 +111,7 @@ class Table extends Container {
|
|
101
111
|
}, {
|
102
112
|
dataField: 'testsPerOneMillion',
|
103
113
|
text : 'Tests / 1M'
|
104
|
-
}]
|
105
|
-
/**
|
106
|
-
* @member {String|null} country_=null
|
107
|
-
* @reactive
|
108
|
-
*/
|
109
|
-
country_: null,
|
110
|
-
/**
|
111
|
-
* @member {Neo.data.Store} store=CountryStore
|
112
|
-
* @reactive
|
113
|
-
*/
|
114
|
-
store: CountryStore
|
114
|
+
}]
|
115
115
|
}
|
116
116
|
|
117
117
|
/**
|
@@ -123,20 +123,20 @@ class Table extends Container {
|
|
123
123
|
afterSetCountry(value, oldValue) {
|
124
124
|
if (oldValue !== undefined) {
|
125
125
|
let me = this,
|
126
|
-
{
|
127
|
-
{selectionModel} =
|
126
|
+
{body} = me,
|
127
|
+
{selectionModel} = body,
|
128
128
|
id;
|
129
129
|
|
130
130
|
if (value) {
|
131
|
-
id = `${
|
131
|
+
id = `${body.id}__tr__${value}`; // The store might not be loaded on the first selection
|
132
132
|
|
133
133
|
if (!selectionModel.isSelected(id)) {
|
134
134
|
selectionModel.select(id);
|
135
135
|
|
136
|
-
me.mounted && Neo.main.DomAccess.scrollToTableRow({id: id})
|
136
|
+
me.mounted && Neo.main.DomAccess.scrollToTableRow({id: id})
|
137
137
|
}
|
138
138
|
} else {
|
139
|
-
selectionModel.deselectAll()
|
139
|
+
selectionModel.deselectAll()
|
140
140
|
}
|
141
141
|
}
|
142
142
|
}
|
@@ -146,7 +146,7 @@ class Table extends Container {
|
|
146
146
|
* @param {String[]} items
|
147
147
|
*/
|
148
148
|
onDeselect(items) {
|
149
|
-
this.country = null
|
149
|
+
this.country = null
|
150
150
|
}
|
151
151
|
|
152
152
|
/**
|
@@ -159,12 +159,12 @@ class Table extends Container {
|
|
159
159
|
|
160
160
|
if (me.store.getCount() > 0) {
|
161
161
|
if (item) {
|
162
|
-
item = me.
|
162
|
+
item = me.body.getRecordByRowId(item)?.country
|
163
163
|
}
|
164
164
|
|
165
165
|
// in case getRecordByRowId() has no match, the initial row creation will include the selection
|
166
166
|
if (item) {
|
167
|
-
me.country = item
|
167
|
+
me.country = item
|
168
168
|
}
|
169
169
|
}
|
170
170
|
}
|
@@ -9,6 +9,20 @@ import TabContainer from '../../../src/tab/Container.mjs';
|
|
9
9
|
* @extends Neo.container.Base
|
10
10
|
*/
|
11
11
|
class ControlsContainer extends Container {
|
12
|
+
/**
|
13
|
+
* We need to add a 1-frame delay here. Rationale: the change events trigger huge computational logic.
|
14
|
+
* We need to ensure that a ComboBox list selection first updates the field input value node,
|
15
|
+
* and then continues with the grid update, to maintain a great UX.
|
16
|
+
* @member {Object} delayable
|
17
|
+
* @static
|
18
|
+
*/
|
19
|
+
static delayable = {
|
20
|
+
onAmountColumnsChange : {type: 'buffer', timer: 30},
|
21
|
+
onAmountRowsChange : {type: 'buffer', timer: 30},
|
22
|
+
onBufferColumnRangeChange: {type: 'buffer', timer: 30},
|
23
|
+
onBufferRowRangeChange : {type: 'buffer', timer: 30}
|
24
|
+
}
|
25
|
+
|
12
26
|
static config = {
|
13
27
|
/**
|
14
28
|
* @member {String} className='Neo.examples.grid.bigData.ControlsContainer'
|
@@ -49,7 +49,7 @@ class MainContainer extends Viewport {
|
|
49
49
|
width : 300,
|
50
50
|
|
51
51
|
bind: {
|
52
|
-
value: {
|
52
|
+
value: {key: 'user.details.firstname', twoWay: true}
|
53
53
|
},
|
54
54
|
}, {
|
55
55
|
module : TextField,
|
@@ -59,7 +59,7 @@ class MainContainer extends Viewport {
|
|
59
59
|
width : 300,
|
60
60
|
|
61
61
|
bind: {
|
62
|
-
value: {
|
62
|
+
value: {key: 'user.details.lastname', twoWay: true}
|
63
63
|
},
|
64
64
|
}],
|
65
65
|
/**
|