neo.mjs 8.32.0 → 8.34.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.
@@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase {
20
20
  */
21
21
  singleton: true,
22
22
  /**
23
- * @member {String} version='8.32.0'
23
+ * @member {String} version='8.34.0'
24
24
  */
25
- version: '8.32.0'
25
+ version: '8.34.0'
26
26
  }
27
27
 
28
28
  /**
@@ -0,0 +1,6 @@
1
+ import Viewport from './view/Viewport.mjs';
2
+
3
+ export const onStart = () => Neo.app({
4
+ mainView: Viewport,
5
+ name : 'Finance'
6
+ });
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE HTML>
2
+ <html>
3
+ <head>
4
+ <meta name="viewport" content="width=device-width, initial-scale=1">
5
+ <meta charset="UTF-8">
6
+ <title>Neo.mjs Finance Dashboard</title>
7
+ <link rel="icon" href="./resources/images/neo_logo_favicon.svg">
8
+ </head>
9
+ <body>
10
+ <script src="../../src/MicroLoader.mjs" type="module"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,37 @@
1
+ import Model from '../../../src/data/Model.mjs';
2
+
3
+ /**
4
+ * @class Finance.model.Company
5
+ * @extends Neo.data.Model
6
+ */
7
+ class Company extends Model {
8
+ static config = {
9
+ /**
10
+ * @member {String} className='Finance.model.Company'
11
+ * @protected
12
+ */
13
+ className: 'Finance.model.Company',
14
+ /**
15
+ * @member {Object[]} fields
16
+ */
17
+ fields: [{
18
+ name : 'change',
19
+ defaultValue: null,
20
+ type : 'Float'
21
+ }, {
22
+ name: 'name',
23
+ type: 'String'
24
+ }, {
25
+ name: 'sector',
26
+ type: 'String'
27
+ }, {
28
+ name: 'symbol',
29
+ type: 'String'
30
+ }, {
31
+ name: 'value',
32
+ type: 'Float'
33
+ }]
34
+ }
35
+ }
36
+
37
+ export default Neo.setupClass(Company);
@@ -0,0 +1,7 @@
1
+ {
2
+ "appPath" : "apps/finance/app.mjs",
3
+ "basePath" : "../../",
4
+ "environment": "development",
5
+ "mainPath" : "./Main.mjs",
6
+ "themes" : ["neo-theme-dark"]
7
+ }
@@ -0,0 +1,103 @@
1
+ [
2
+ {"symbol": "ADBE", "name": "Adobe Inc.", "sector": "Information Technology"},
3
+ {"symbol": "AMD", "name": "Advanced Micro Devices", "sector": "Information Technology"},
4
+ {"symbol": "ABNB", "name": "Airbnb", "sector": "Consumer Discretionary"},
5
+ {"symbol": "GOOGL", "name": "Alphabet Inc. (Class A)", "sector": "Communication Services"},
6
+ {"symbol": "GOOG", "name": "Alphabet Inc. (Class C)", "sector": "Communication Services"},
7
+ {"symbol": "AMZN", "name": "Amazon", "sector": "Consumer Discretionary"},
8
+ {"symbol": "AEP", "name": "American Electric Power", "sector": "Utilities"},
9
+ {"symbol": "AMGN", "name": "Amgen", "sector": "Health Care"},
10
+ {"symbol": "ADI", "name": "Analog Devices", "sector": "Information Technology"},
11
+ {"symbol": "ANSS", "name": "Ansys", "sector": "Information Technology"},
12
+ {"symbol": "AAPL", "name": "Apple Inc.", "sector": "Information Technology"},
13
+ {"symbol": "AMAT", "name": "Applied Materials", "sector": "Information Technology"},
14
+ {"symbol": "APP", "name": "AppLovin", "sector": "Information Technology"},
15
+ {"symbol": "ARM", "name": "Arm Holdings", "sector": "Information Technology"},
16
+ {"symbol": "ASML", "name": "ASML Holding", "sector": "Information Technology"},
17
+ {"symbol": "AZN", "name": "AstraZeneca", "sector": "Health Care"},
18
+ {"symbol": "TEAM", "name": "Atlassian", "sector": "Information Technology"},
19
+ {"symbol": "ADSK", "name": "Autodesk", "sector": "Information Technology"},
20
+ {"symbol": "ADP", "name": "Automatic Data Processing", "sector": "Industrials"},
21
+ {"symbol": "AXON", "name": "Axon Enterprise", "sector": "Industrials"},
22
+ {"symbol": "BKR", "name": "Baker Hughes", "sector": "Energy"},
23
+ {"symbol": "BIIB", "name": "Biogen", "sector": "Health Care"},
24
+ {"symbol": "BKNG", "name": "Booking Holdings", "sector": "Consumer Discretionary"},
25
+ {"symbol": "AVGO", "name": "Broadcom", "sector": "Information Technology"},
26
+ {"symbol": "CDNS", "name": "Cadence Design Systems", "sector": "Information Technology"},
27
+ {"symbol": "CDW", "name": "CDW Corporation", "sector": "Information Technology"},
28
+ {"symbol": "CHTR", "name": "Charter Communications", "sector": "Communication Services"},
29
+ {"symbol": "CTAS", "name": "Cintas", "sector": "Industrials"},
30
+ {"symbol": "CSCO", "name": "Cisco", "sector": "Information Technology"},
31
+ {"symbol": "CCEP", "name": "Coca-Cola Europacific Partners", "sector": "Consumer Staples"},
32
+ {"symbol": "CTSH", "name": "Cognizant", "sector": "Information Technology"},
33
+ {"symbol": "CMCSA", "name": "Comcast", "sector": "Communication Services"},
34
+ {"symbol": "CEG", "name": "Constellation Energy", "sector": "Utilities"},
35
+ {"symbol": "CPRT", "name": "Copart", "sector": "Industrials"},
36
+ {"symbol": "CSGP", "name": "CoStar Group", "sector": "Real Estate"},
37
+ {"symbol": "COST", "name": "Costco", "sector": "Consumer Staples"},
38
+ {"symbol": "CRWD", "name": "CrowdStrike", "sector": "Information Technology"},
39
+ {"symbol": "CSX", "name": "CSX Corporation", "sector": "Industrials"},
40
+ {"symbol": "DDOG", "name": "Datadog", "sector": "Information Technology"},
41
+ {"symbol": "DXCM", "name": "DexCom", "sector": "Health Care"},
42
+ {"symbol": "FANG", "name": "Diamondback Energy", "sector": "Energy"},
43
+ {"symbol": "DASH", "name": "DoorDash", "sector": "Consumer Discretionary"},
44
+ {"symbol": "EA", "name": "Electronic Arts", "sector": "Communication Services"},
45
+ {"symbol": "EXC", "name": "Exelon", "sector": "Utilities"},
46
+ {"symbol": "FAST", "name": "Fastenal", "sector": "Industrials"},
47
+ {"symbol": "FTNT", "name": "Fortinet", "sector": "Information Technology"},
48
+ {"symbol": "GEHC", "name": "GE HealthCare", "sector": "Health Care"},
49
+ {"symbol": "GILD", "name": "Gilead Sciences", "sector": "Health Care"},
50
+ {"symbol": "GFS", "name": "GlobalFoundries", "sector": "Information Technology"},
51
+ {"symbol": "HON", "name": "Honeywell", "sector": "Industrials"},
52
+ {"symbol": "IDXX", "name": "Idexx Laboratories", "sector": "Health Care"},
53
+ {"symbol": "INTC", "name": "Intel", "sector": "Information Technology"},
54
+ {"symbol": "INTU", "name": "Intuit", "sector": "Information Technology"},
55
+ {"symbol": "ISRG", "name": "Intuitive Surgical", "sector": "Health Care"},
56
+ {"symbol": "KDP", "name": "Keurig Dr Pepper", "sector": "Keurig Dr Pepper"},
57
+ {"symbol": "KLAC", "name": "KLA Corporation", "sector": "Information Technology"},
58
+ {"symbol": "KHC", "name": "Kraft Heinz", "sector": "Consumer Staples"},
59
+ {"symbol": "LRCX", "name": "Lam Research", "sector": "Information Technology"},
60
+ {"symbol": "LIN", "name": "Linde plc", "sector": "Materials"},
61
+ {"symbol": "LULU", "name": "Lululemon Athletica", "sector": "Consumer Discretionary"},
62
+ {"symbol": "MAR", "name": "Marriott International", "sector": "Consumer Discretionary"},
63
+ {"symbol": "MRVL", "name": "Marvell Technology", "sector": "Information Technology"},
64
+ {"symbol": "MELI", "name": "MercadoLibre", "sector": "Consumer Discretionary"},
65
+ {"symbol": "META", "name": "Meta Platforms", "sector": "Communication Services"},
66
+ {"symbol": "MCHP", "name": "Microchip Technology", "sector": "Information Technology"},
67
+ {"symbol": "MU", "name": "Micron Technology", "sector": "Information Technology"},
68
+ {"symbol": "MSFT", "name": "Microsoft", "sector": "Information Technology"},
69
+ {"symbol": "MSTR", "name": "MicroStrategy", "sector": "Information Technology"},
70
+ {"symbol": "MDLZ", "name": "Mondelez International", "sector": "Consumer Staples"},
71
+ {"symbol": "MDB", "name": "MongoDB Inc.", "sector": "Information Technology"},
72
+ {"symbol": "MNST", "name": "Monster Beverage", "sector": "Consumer Staples"},
73
+ {"symbol": "NFLX", "name": "Netflix, Inc.", "sector": "Communication Services"},
74
+ {"symbol": "NVDA", "name": "Nvidia", "sector": "Information Technology"},
75
+ {"symbol": "NXPI", "name": "NXP Semiconductors", "sector": "Information Technology"},
76
+ {"symbol": "ORLY", "name": "O'Reilly Automotive", "sector": "Consumer Discretionary"},
77
+ {"symbol": "ODFL", "name": "Old Dominion Freight Line", "sector": "Industrials"},
78
+ {"symbol": "ON", "name": "Onsemi", "sector": "Information Technology"},
79
+ {"symbol": "PCAR", "name": "Paccar", "sector": "Industrials"},
80
+ {"symbol": "PLTR", "name": "Palantir Technologies", "sector": "Information Technology"},
81
+ {"symbol": "PANW", "name": "Palo Alto Networks", "sector": "Information Technology"},
82
+ {"symbol": "PAYX", "name": "Paychex", "sector": "Industrials"},
83
+ {"symbol": "PYPL", "name": "PayPal", "sector": "Financials"},
84
+ {"symbol": "PDD", "name": "PDD Holdings", "sector": "Consumer Discretionary"},
85
+ {"symbol": "PEP", "name": "PepsiCo", "sector": "Consumer Staples"},
86
+ {"symbol": "QCOM", "name": "Qualcomm", "sector": "Information Technology"},
87
+ {"symbol": "REGN", "name": "Regeneron Pharmaceuticals", "sector": "Health Care"},
88
+ {"symbol": "ROP", "name": "Roper Technologies", "sector": "Information Technology"},
89
+ {"symbol": "ROST", "name": "Ross Stores", "sector": "Consumer Discretionary"},
90
+ {"symbol": "SBUX", "name": "Starbucks", "sector": "Consumer Discretionary"},
91
+ {"symbol": "SNPS", "name": "Synopsys", "sector": "Information Technology"},
92
+ {"symbol": "TTWO", "name": "Take-Two Interactive", "sector": "Communication Services"},
93
+ {"symbol": "TMUS", "name": "T-Mobile US", "sector": "Communication Services"},
94
+ {"symbol": "TSLA", "name": "Tesla, Inc.", "sector": "Consumer Discretionary"},
95
+ {"symbol": "TXN", "name": "Texas Instruments", "sector": "Information Technology"},
96
+ {"symbol": "TTD", "name": "Trade Desk (The)", "sector": "Communication Services"},
97
+ {"symbol": "VRSK", "name": "Verisk Analytics", "sector": "Industrials"},
98
+ {"symbol": "VRTX", "name": "Vertex Pharmaceuticals", "sector": "Health Care"},
99
+ {"symbol": "WBD", "name": "Warner Bros. Discovery", "sector": "Communication Services"},
100
+ {"symbol": "WDAY", "name": "Workday, Inc.", "sector": "Information Technology"},
101
+ {"symbol": "XEL", "name": "Xcel Energy", "sector": "Utilities"},
102
+ {"symbol": "ZS", "name": "Zscaler", "sector": "Information Technology"}
103
+ ]
@@ -0,0 +1,16 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
3
+ <svg width="100%" height="100%" viewBox="0 0 157 157" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;">
4
+ <style>
5
+ path {
6
+ stroke: #3E63DD;
7
+ }
8
+ @media (prefers-color-scheme: dark) {
9
+ path {
10
+ stroke: #fff;
11
+ }
12
+ }
13
+ </style>
14
+ <path d="M8.841,127.508L33.676,144.928L33.675,105.744L33.675,94.343L43.023,100.871L112.335,149.272L135.026,135.569L8.841,46.576L8.841,127.508Z" style="fill:white;fill-opacity:0;fill-rule:nonzero;stroke-width:11.89px;"/>
15
+ <path d="M144.867,30.661L120.032,13.241L120.033,52.425L120.034,63.826L110.686,57.298L41.374,8.897L18.682,22.6L144.867,111.593L144.867,30.661Z" style="fill:white;fill-opacity:0;fill-rule:nonzero;stroke-width:11.89px;"/>
16
+ </svg>
@@ -0,0 +1,37 @@
1
+ import CompanyModel from '../model/Company.mjs';
2
+ import Store from '../../../src/data/Store.mjs';
3
+
4
+ /**
5
+ * @class Finance.store.Companies
6
+ * @extends Neo.data.Store
7
+ */
8
+ class Companies extends Store {
9
+ static config = {
10
+ /**
11
+ * @member {String} className='Finance.store.Companies'
12
+ * @protected
13
+ */
14
+ className: 'Finance.store.Companies',
15
+ /**
16
+ * @member {String} keyProperty='symbol'
17
+ */
18
+ keyProperty: 'symbol',
19
+ /**
20
+ * @member {Neo.data.model} model=CompanyModel
21
+ */
22
+ model: CompanyModel,
23
+ /**
24
+ * @member {Object[]} sorters=[{property:'name',direction:'ASC'}]
25
+ */
26
+ sorters: [{
27
+ property : 'name',
28
+ direction: 'ASC'
29
+ }],
30
+ /**
31
+ * @member {String} url='../../apps/finance/resources/data/companies.json'
32
+ */
33
+ url: '../../apps/finance/resources/data/companies.json'
34
+ }
35
+ }
36
+
37
+ export default Neo.setupClass(Companies);
@@ -0,0 +1,59 @@
1
+ import BaseGridContainer from '../../../src/grid/Container.mjs';
2
+
3
+ /**
4
+ * @class Finance.view.GridContainer
5
+ * @extends Neo.table.Container
6
+ */
7
+ class GridContainer extends BaseGridContainer {
8
+ static config = {
9
+ /**
10
+ * @member {String} className='Finance.view.GridContainer'
11
+ * @protected
12
+ */
13
+ className: 'Finance.view.GridContainer',
14
+ /**
15
+ * @member {Object} bind
16
+ */
17
+ bind: {
18
+ store: 'stores.companies'
19
+ },
20
+ /**
21
+ * @member {Object[]} columns
22
+ */
23
+ columns: [{
24
+ dataField: 'id',
25
+ text : '#',
26
+ type : 'index',
27
+ width : 40
28
+ }, {
29
+ dataField: 'symbol',
30
+ text : 'Symbol',
31
+ width : 100
32
+ }, {
33
+ dataField: 'name',
34
+ text : 'Name',
35
+ width : 250
36
+ }, {
37
+ dataField: 'sector',
38
+ text : 'Sector',
39
+ width : 200
40
+ }, {
41
+ cellAlign: 'right',
42
+ dataField: 'change',
43
+ locale : 'en-US',
44
+ text : 'Change',
45
+ type : 'animatedCurrency',
46
+ width : 120
47
+ }, {
48
+ cellAlign : 'right',
49
+ compareField: 'change',
50
+ dataField : 'value',
51
+ locale : 'en-US',
52
+ text : 'Value',
53
+ type : 'animatedCurrency',
54
+ width : 120
55
+ }]
56
+ }
57
+ }
58
+
59
+ export default Neo.setupClass(GridContainer);
@@ -0,0 +1,43 @@
1
+ import BaseViewport from '../../../src/container/Viewport.mjs';
2
+ import GridContainer from './GridContainer.mjs';
3
+ import ViewportController from './ViewportController.mjs';
4
+ import ViewportStateProvider from './ViewportStateProvider.mjs';
5
+
6
+ /**
7
+ * @class Finance.view.Viewport
8
+ * @extends Neo.container.Viewport
9
+ */
10
+ class Viewport extends BaseViewport {
11
+ static config = {
12
+ /**
13
+ * @member {String} className='Finance.view.Viewport'
14
+ * @protected
15
+ */
16
+ className: 'Finance.view.Viewport',
17
+ /**
18
+ * @member {Neo.controller.Component} controller=ViewportController
19
+ */
20
+ controller: ViewportController,
21
+ /**
22
+ * @member {Object[]} items
23
+ */
24
+ items: [{
25
+ module : GridContainer,
26
+ reference: 'grid'
27
+ }],
28
+ /*
29
+ * @member {Object} layout={ntype:'fit'}
30
+ */
31
+ layout: {ntype: 'fit'},
32
+ /**
33
+ * @member {Neo.state.Provider} stateProvider=ViewportStateProvider
34
+ */
35
+ stateProvider: ViewportStateProvider,
36
+ /**
37
+ * @member {Object} style
38
+ */
39
+ style: {padding: '2em'}
40
+ }
41
+ }
42
+
43
+ export default Neo.setupClass(Viewport);
@@ -0,0 +1,50 @@
1
+ import Controller from '../../../src/controller/Component.mjs';
2
+
3
+ /**
4
+ * @class Finance.view.ViewportController
5
+ * @extends Neo.controller.Component
6
+ */
7
+ class ViewportController extends Controller {
8
+ static config = {
9
+ /**
10
+ * @member {String} className='Finance.view.ViewportController'
11
+ * @protected
12
+ */
13
+ className: 'Finance.view.ViewportController'
14
+ }
15
+
16
+ generateData() {
17
+ let me = this,
18
+ store = me.getStore('companies'),
19
+ change, index, record;
20
+
21
+ setInterval(() => {
22
+ index = Math.round(Math.random() * 100); // 0 - 100
23
+ change = Math.random() * 10 - 5;
24
+ record = store.getAt(index);
25
+
26
+ record.set({change, value: record.value + change})
27
+ }, 1)
28
+ }
29
+
30
+ /**
31
+ *
32
+ */
33
+ onCompaniesStoreLoad() {
34
+ let me = this,
35
+ companiesStore = me.getStore('companies'),
36
+ items = [];
37
+
38
+ companiesStore.items.forEach(record => {
39
+ items.push({
40
+ symbol: record.symbol,
41
+ value : Math.random() * 1000
42
+ })
43
+ });
44
+
45
+ me.getReference('grid').bulkUpdateRecords(items);
46
+ me.generateData()
47
+ }
48
+ }
49
+
50
+ export default Neo.setupClass(ViewportController);
@@ -0,0 +1,32 @@
1
+ import CompanyStore from '../store/Companies.mjs';
2
+ import StateProvider from '../../../src/state/Provider.mjs';
3
+
4
+ /**
5
+ * @class Finance.view.ViewportStateProvider
6
+ * @extends Neo.state.Provider
7
+ */
8
+ class ViewportStateProvider extends StateProvider {
9
+ static config = {
10
+ /**
11
+ * @member {String} className='Finance.view.ViewportStateProvider'
12
+ * @protected
13
+ */
14
+ className: 'Finance.view.ViewportStateProvider',
15
+ /**
16
+ * @member {Object} data
17
+ */
18
+ data: {},
19
+ /**
20
+ * @member {Object} stores
21
+ */
22
+ stores: {
23
+ companies: {
24
+ module : CompanyStore,
25
+ autoLoad : true,
26
+ listeners: {load: 'onCompaniesStoreLoad'}
27
+ }
28
+ }
29
+ }
30
+ }
31
+
32
+ export default Neo.setupClass(ViewportStateProvider);
@@ -16,7 +16,7 @@
16
16
  "@type": "Organization",
17
17
  "name": "Neo.mjs"
18
18
  },
19
- "datePublished": "2025-03-21",
19
+ "datePublished": "2025-03-26",
20
20
  "publisher": {
21
21
  "@type": "Organization",
22
22
  "name": "Neo.mjs"
@@ -107,7 +107,7 @@ class FooterContainer extends Container {
107
107
  }, {
108
108
  module: Component,
109
109
  cls : ['neo-version'],
110
- html : 'v8.32.0'
110
+ html : 'v8.34.0'
111
111
  }]
112
112
  }],
113
113
  /**
@@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase {
20
20
  */
21
21
  singleton: true,
22
22
  /**
23
- * @member {String} version='8.32.0'
23
+ * @member {String} version='8.34.0'
24
24
  */
25
- version: '8.32.0'
25
+ version: '8.34.0'
26
26
  }
27
27
 
28
28
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neo.mjs",
3
- "version": "8.32.0",
3
+ "version": "8.34.0",
4
4
  "description": "The webworkers driven UI framework",
5
5
  "type": "module",
6
6
  "repository": {
@@ -1,5 +1,5 @@
1
1
  @keyframes grid-animated-cell {
2
- 50% {background-color:var(--button-ripple-background-color);}
2
+ 50% {background-color: var(--button-ripple-background-color);}
3
3
  }
4
4
 
5
5
  .neo-grid-container {
@@ -0,0 +1,25 @@
1
+ @keyframes grid-animated-cell-negative {
2
+ 50% {background-color: darkred;}
3
+ }
4
+
5
+ @keyframes grid-animated-cell-positive {
6
+ 50% {background-color: darkgreen}
7
+ }
8
+
9
+ .neo-grid-container {
10
+ .neo-grid-row, .neo-grid-row.neo-even {
11
+ .neo-grid-cell {
12
+ &.neo-animated-negative {
13
+ animation-name : grid-animated-cell-negative;
14
+ animation-duration : .4s;
15
+ animation-timing-function: ease-in-out;
16
+ }
17
+
18
+ &.neo-animated-positive {
19
+ animation-name : grid-animated-cell-positive;
20
+ animation-duration : .4s;
21
+ animation-timing-function: ease-in-out;
22
+ }
23
+ }
24
+ }
25
+ }
@@ -263,12 +263,12 @@ const DefaultConfig = {
263
263
  useVdomWorker: true,
264
264
  /**
265
265
  * buildScripts/injectPackageVersion.mjs will update this value
266
- * @default '8.32.0'
266
+ * @default '8.34.0'
267
267
  * @memberOf! module:Neo
268
268
  * @name config.version
269
269
  * @type String
270
270
  */
271
- version: '8.32.0'
271
+ version: '8.34.0'
272
272
  };
273
273
 
274
274
  Object.assign(DefaultConfig, {
@@ -219,14 +219,21 @@ class MagicMoveText extends Component {
219
219
 
220
220
  let me = this;
221
221
 
222
- value && me.getDomRect().then(rect => {
223
- me.contentHeight = rect.height;
224
- me.contentWidth = rect.width;
222
+ if (value) {
223
+ me.getDomRect().then(rect => {
224
+ me.contentHeight = rect.height;
225
+ me.contentWidth = rect.width;
226
+ })
227
+ } else {
228
+ me.measureCache = {};
229
+ me.previousChars = []
230
+ }
225
231
 
226
- me.autoCycle && me.startAutoCycle(value)
227
- });
232
+ if(oldValue !== undefined) {
233
+ me.addResizeObserver(value);
228
234
 
229
- oldValue !== undefined && me.addResizeObserver(value)
235
+ me.autoCycle && me.startAutoCycle(value)
236
+ }
230
237
  }
231
238
 
232
239
  /**
@@ -19,11 +19,13 @@ class GridContainer extends BaseContainer {
19
19
  * @static
20
20
  */
21
21
  static columnTypes = {
22
- animatedChange: column.AnimatedChange,
23
- column : column.Base,
24
- component : column.Component,
25
- index : column.Index,
26
- progress : column.Progress
22
+ animatedChange : column.AnimatedChange,
23
+ animatedCurrency: column.AnimatedCurrency,
24
+ column : column.Base,
25
+ component : column.Component,
26
+ currency : column.Currency,
27
+ index : column.Index,
28
+ progress : column.Progress
27
29
  }
28
30
  /**
29
31
  * @member {Object} delayable
package/src/grid/View.mjs CHANGED
@@ -434,10 +434,6 @@ class GridView extends Component {
434
434
  fieldValue = record[dataField],
435
435
  cellConfig, rendererOutput;
436
436
 
437
- if (fieldValue === null || fieldValue === undefined) {
438
- fieldValue = ''
439
- }
440
-
441
437
  rendererOutput = column.renderer.call(column.rendererScope || column, {
442
438
  column,
443
439
  columnIndex,
@@ -0,0 +1,87 @@
1
+ import AnimatedChange from './AnimatedChange.mjs';
2
+
3
+ /**
4
+ * @class Neo.grid.column.AnimatedCurrency
5
+ * @extends Neo.grid.column.AnimatedChange
6
+ */
7
+ class AnimatedCurrency extends AnimatedChange {
8
+ static config = {
9
+ /**
10
+ * @member {String} className='Neo.grid.column.AnimatedCurrency'
11
+ * @protected
12
+ */
13
+ className: 'Neo.grid.column.AnimatedCurrency',
14
+ /**
15
+ * @member {String} type='animatedCurrency'
16
+ * @protected
17
+ */
18
+ type: 'animatedCurrency',
19
+ /**
20
+ * Set a different record field to base the change on.
21
+ * Defaults this.dataField
22
+ * @member {String|null} compareField=null
23
+ */
24
+ compareField: null,
25
+ /**
26
+ * @member {String} currency='USD'
27
+ */
28
+ currency: 'USD',
29
+ /**
30
+ * @member {String} locale='default'
31
+ */
32
+ locale: 'default'
33
+ }
34
+
35
+ /**
36
+ * @member {Intl.NumberFormat|null} formatter=null
37
+ */
38
+ formatter = null
39
+
40
+ /**
41
+ * @param {Object} config
42
+ */
43
+ construct(config) {
44
+ super.construct(config);
45
+ this.createFormatter()
46
+ }
47
+
48
+ /**
49
+ * @param {Object} data
50
+ * @param {Neo.button.Base} data.column
51
+ * @param {Number} data.columnIndex
52
+ * @param {String} data.dataField
53
+ * @param {Neo.grid.Container} data.gridContainer
54
+ * @param {Object} data.record
55
+ * @param {Number} data.rowIndex
56
+ * @param {Neo.data.Store} data.store
57
+ * @param {Number|String} data.value
58
+ * @returns {*}
59
+ */
60
+ cellRenderer({value}) {
61
+ if (value === null || value === undefined) {
62
+ return ''
63
+ }
64
+
65
+ return this.formatter.format(value)
66
+ }
67
+
68
+ /**
69
+ *
70
+ */
71
+ createFormatter() {
72
+ let me = this;
73
+
74
+ me.formatter = new Intl.NumberFormat(me.locale, {style: 'currency', currency: me.currency})
75
+ }
76
+
77
+ /**
78
+ * Override as needed for dynamic record-based animation classes
79
+ * @param {Record} record
80
+ * @returns {String}
81
+ */
82
+ getAnimationCls(record) {
83
+ return record[this.compareField || this.dataField] < 0 ? 'neo-animated-negative' : 'neo-animated-positive'
84
+ }
85
+ }
86
+
87
+ export default Neo.setupClass(AnimatedCurrency);
@@ -73,8 +73,12 @@ class Column extends Base {
73
73
  * @param {Number|String} data.value
74
74
  * @returns {*}
75
75
  */
76
- cellRenderer(data) {
77
- return data.value
76
+ cellRenderer({value}) {
77
+ if (value === null || value === undefined) {
78
+ return ''
79
+ }
80
+
81
+ return value
78
82
  }
79
83
  }
80
84
 
@@ -0,0 +1,72 @@
1
+ import Column from './Base.mjs';
2
+
3
+ /**
4
+ * @class Neo.grid.column.Currency
5
+ * @extends Neo.grid.column.Base
6
+ */
7
+ class Currency extends Column {
8
+ static config = {
9
+ /**
10
+ * @member {String} className='Neo.grid.column.Currency'
11
+ * @protected
12
+ */
13
+ className: 'Neo.grid.column.Currency',
14
+ /**
15
+ * @member {String} type='currency'
16
+ * @protected
17
+ */
18
+ type: 'currency',
19
+ /**
20
+ * @member {String} currency='USD'
21
+ */
22
+ currency: 'USD',
23
+ /**
24
+ * @member {String} locale='default'
25
+ */
26
+ locale: 'default'
27
+ }
28
+
29
+ /**
30
+ * @member {Intl.NumberFormat|null} formatter=null
31
+ */
32
+ formatter = null
33
+
34
+ /**
35
+ * @param {Object} config
36
+ */
37
+ construct(config) {
38
+ super.construct(config);
39
+ this.createFormatter()
40
+ }
41
+
42
+ /**
43
+ * @param {Object} data
44
+ * @param {Neo.button.Base} data.column
45
+ * @param {Number} data.columnIndex
46
+ * @param {String} data.dataField
47
+ * @param {Neo.grid.Container} data.gridContainer
48
+ * @param {Object} data.record
49
+ * @param {Number} data.rowIndex
50
+ * @param {Neo.data.Store} data.store
51
+ * @param {Number|String} data.value
52
+ * @returns {*}
53
+ */
54
+ cellRenderer({value}) {
55
+ if (value === null || value === undefined) {
56
+ return ''
57
+ }
58
+
59
+ return this.formatter.format(value)
60
+ }
61
+
62
+ /**
63
+ *
64
+ */
65
+ createFormatter() {
66
+ let me = this;
67
+
68
+ me.formatter = new Intl.NumberFormat(me.locale, {style: 'currency', currency: me.currency})
69
+ }
70
+ }
71
+
72
+ export default Neo.setupClass(Currency);
@@ -1,7 +1,9 @@
1
- import AnimatedChange from './AnimatedChange.mjs';
2
- import Base from './Base.mjs';
3
- import Component from './Component.mjs';
4
- import Index from './Index.mjs';
5
- import Progress from './Progress.mjs';
1
+ import AnimatedChange from './AnimatedChange.mjs';
2
+ import AnimatedCurrency from './AnimatedCurrency.mjs';
3
+ import Base from './Base.mjs';
4
+ import Component from './Component.mjs';
5
+ import Currency from './Currency.mjs';
6
+ import Index from './Index.mjs';
7
+ import Progress from './Progress.mjs';
6
8
 
7
- export {AnimatedChange, Base, Component, Index, Progress};
9
+ export {AnimatedChange, AnimatedCurrency, Base, Component, Currency, Index, Progress};
@@ -19,6 +19,14 @@ class ServiceWorker extends Base {
19
19
  * @param {Object} config
20
20
  */
21
21
  construct(config) {
22
+ super.construct(config);
23
+ this.registerServiceWorker()
24
+ }
25
+
26
+ /**
27
+ * @returns {Promise<void>}
28
+ */
29
+ async registerServiceWorker() {
22
30
  if ('serviceWorker' in navigator) {
23
31
  let me = this,
24
32
  {config} = Neo,
@@ -27,30 +35,32 @@ class ServiceWorker extends Base {
27
35
  folder = window.location.pathname.includes('/examples/') ? 'examples/' : 'apps/',
28
36
  opts = devMode ? {type: 'module'} : {},
29
37
  path = (devMode ? config.basePath : config.workerBasePath) + (devMode ? folder : '') + fileName,
30
- {serviceWorker} = navigator;
38
+ {serviceWorker} = navigator,
39
+ registration = await serviceWorker.register(path, opts);
31
40
 
32
41
  window.addEventListener('beforeunload', me.onBeforeUnload.bind(me));
33
42
 
34
- serviceWorker.register(path, opts)
35
- .then(registration => {
36
- serviceWorker.ready.then(() => {
37
- serviceWorker.onmessage = WorkerManager.onWorkerMessage.bind(WorkerManager);
43
+ registration.addEventListener('updatefound', () => {
44
+ window.location.reload()
45
+ })
46
+
47
+ await serviceWorker.ready;
48
+
49
+ serviceWorker.onmessage = WorkerManager.onWorkerMessage.bind(WorkerManager);
38
50
 
39
- if (!WorkerManager.getWorker('service')) {
40
- /*
41
- * navigator.serviceWorker.controller can be null in case we load a page for the first time
42
- * or in case of a force refresh.
43
- * See: https://www.w3.org/TR/service-workers/#navigator-service-worker-controller
44
- */
45
- WorkerManager.serviceWorker = registration.active
46
- }
51
+ if (!WorkerManager.getWorker('service')) {
52
+ /*
53
+ * navigator.serviceWorker.controller can be null in case we load a page for the first time
54
+ * or in case of a force refresh.
55
+ * See: https://www.w3.org/TR/service-workers/#navigator-service-worker-controller
56
+ */
57
+ WorkerManager.serviceWorker = registration.active
58
+ }
47
59
 
48
- WorkerManager.sendMessage('service', {
49
- action: 'registerNeoConfig',
50
- data : config
51
- })
52
- })
53
- })
60
+ WorkerManager.sendMessage('service', {
61
+ action: 'registerNeoConfig',
62
+ data : config
63
+ })
54
64
  }
55
65
  }
56
66
 
@@ -400,8 +400,6 @@ class Component extends Manager {
400
400
  if (vdom.cn) {
401
401
  output.cn = [];
402
402
 
403
- childDepth = depth === -1 ? -1 : depth > 1 ? depth-1 : 1;
404
-
405
403
  vdom.cn.forEach(item => {
406
404
  childDepth = depth;
407
405
 
@@ -413,17 +413,6 @@ class App extends Base {
413
413
  })
414
414
  })
415
415
  }
416
- /**
417
- * Triggered in case a connected ServiceWorker receives a new version.
418
- * Especially inside dist envs, a reload of the connecting window is required,
419
- * since the SW will clear its caches and the app can receive conflicting bundle versions.
420
- * @param {Object} data
421
- * @param {String} data.newVersion
422
- * @param {String} data.oldVersion
423
- */
424
- onNewVersion(data) {
425
- Neo.Main.reloadWindow({});
426
- }
427
416
 
428
417
  /**
429
418
  * Fire event on all apps
@@ -8,6 +8,10 @@ import RemoteMethodAccess from './mixin/RemoteMethodAccess.mjs';
8
8
  const NeoConfig = Neo.config,
9
9
  devMode = NeoConfig.environment === 'development';
10
10
 
11
+ navigator.serviceWorker.addEventListener('controllerchange', function() {
12
+ window.location.reload()
13
+ }, {once: true});
14
+
11
15
  /**
12
16
  * The worker manager lives inside the main thread and creates the App, Data & VDom worker.
13
17
  * Also, responsible for sending messages from the main thread to the different workers.
@@ -14,6 +14,10 @@ class ServiceBase extends Base {
14
14
  * @protected
15
15
  */
16
16
  className: 'Neo.worker.ServiceBase',
17
+ /**
18
+ * @member {String} cacheName_='neo-runtime'
19
+ */
20
+ cacheName_: 'neo-runtime',
17
21
  /**
18
22
  * @member {String[]|Neo.core.Base[]|null} mixins=[RemoteMethodAccess]
19
23
  */
@@ -33,10 +37,6 @@ class ServiceBase extends Base {
33
37
  }
34
38
  }
35
39
 
36
- /**
37
- * @member {String} cacheName='neo-runtime'
38
- */
39
- cacheName = 'neo-runtime'
40
40
  /**
41
41
  * @member {String[]} cachePaths
42
42
  */
@@ -94,6 +94,15 @@ class ServiceBase extends Base {
94
94
  Neo.workerId = me.workerId
95
95
  }
96
96
 
97
+ /**
98
+ * Triggered when accessing the cacheName config
99
+ * @param {String} value
100
+ * @protected
101
+ */
102
+ beforeGetCacheName(value) {
103
+ return value + '-' + this.version
104
+ }
105
+
97
106
  /**
98
107
  * @param {String} name=this.cacheName
99
108
  * @returns {Promise<Object>}
@@ -163,14 +172,26 @@ class ServiceBase extends Base {
163
172
  /**
164
173
  * @param {ExtendableMessageEvent} event
165
174
  */
166
- onActivate(event) {
167
- console.log('onActivate', event)
175
+ async onActivate(event) {
176
+ console.log('onActivate', event);
177
+
178
+ let me = this,
179
+ keys = await caches.keys(),
180
+ key;
181
+
182
+ for (key of keys) {
183
+ // Clear caches for prior SW versions, without touching non-related caches
184
+ if (key.startsWith(me._cacheName) && key !== me.cacheName) {
185
+ // No need to await the method execution
186
+ me.clearCache(key)
187
+ }
188
+ }
168
189
  }
169
190
 
170
191
  /**
171
192
  * @param {Client} source
172
193
  */
173
- onConnect(source) {
194
+ async onConnect(source) {
174
195
  console.log('onConnect', source);
175
196
 
176
197
  this.createMessageChannel(source);
@@ -181,8 +202,8 @@ class ServiceBase extends Base {
181
202
  * @param {ExtendableMessageEvent} event
182
203
  */
183
204
  onFetch(event) {
184
- let hasMatch = false,
185
- {request} = event,
205
+ let hasMatch = false,
206
+ {request} = event,
186
207
  key;
187
208
 
188
209
  for (key of this.cachePaths) {
@@ -192,7 +213,7 @@ class ServiceBase extends Base {
192
213
  }
193
214
  }
194
215
 
195
- hasMatch && event.respondWith(
216
+ hasMatch && request.method === 'GET' && event.respondWith(
196
217
  caches.match(request)
197
218
  .then(cachedResponse => cachedResponse || caches.open(this.cacheName)
198
219
  .then(cache => fetch(request)
@@ -206,8 +227,7 @@ class ServiceBase extends Base {
206
227
  * @param {ExtendableMessageEvent} event
207
228
  */
208
229
  onInstall(event) {
209
- console.log('onInstall', event);
210
- globalThis.skipWaiting()
230
+ console.log('onInstall', event)
211
231
  }
212
232
 
213
233
  /**
@@ -250,25 +270,15 @@ class ServiceBase extends Base {
250
270
  * @param {ExtendableMessageEvent} event
251
271
  */
252
272
  async onRegisterNeoConfig(msg, event) {
253
- let me = this,
254
- {version} = me;
255
-
256
- Neo.config = Neo.config || {};
257
- Object.assign(Neo.config, msg.data);
258
-
259
- if (version !== Neo.config.version) {
260
- await me.clearCaches();
261
-
262
- me.version = Neo.config.version;
273
+ this.onConnect(event.source)
274
+ }
263
275
 
264
- me.sendMessage('app', {
265
- action : 'newVersion',
266
- newVersion: Neo.config.version,
267
- oldVersion: version
268
- })
269
- } else {
270
- me.onConnect(event.source)
271
- }
276
+ /**
277
+ * @param {Object} msg
278
+ * @param {ExtendableMessageEvent} event
279
+ */
280
+ async onSkipWaiting(msg, event) {
281
+ await globalThis.skipWaiting()
272
282
  }
273
283
 
274
284
  /**