@lightningjs/blits 1.9.2 → 1.10.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## v1.10.1
4
+
5
+ _15 nov 2024_
6
+
7
+ - Upgraded to renderer 2.7.1
8
+ - Added documentation on Router
9
+
10
+ ## v1.10.0
11
+
12
+ _8 nov 2024_
13
+
14
+ - Fixed issue with back button not bubbling to App component after last page in router history is popped
15
+ - Added `align-items` attribute to Layout component
16
+ - Fixed issue with watching nested state variables and global state
17
+ - Upgraded to renderer 2.6.2
18
+ - Fixed issue with white background for Elements with falsy src attribute
19
+ - Fixed issue with calling focus on component that already is focussed
20
+
21
+
3
22
  ## v1.9.2
4
23
 
5
24
  _5 nov 2024_
package/docs/_sidebar.md CHANGED
@@ -24,6 +24,9 @@
24
24
  - [Directives](/built-in/directives.md)
25
25
  - [For loop](/built-in/for-loop.md)
26
26
  - [Layout](/built-in/layout.md)
27
+ - Router
28
+ - [Basics](/router/basics.md)
29
+ - [Hooks](/router/hooks.md)
27
30
  - Plugins
28
31
  - [Text-to-Speech / Announcer](/plugins/text-to-speech-announcer.md)
29
32
  - [Language](/plugins/language.md)
@@ -37,6 +37,14 @@ In order to align vertically use `<Layout direction="vertical"></Layout>`. And u
37
37
 
38
38
  By default the Layout-component places each Element directly besides (or below) the previous one. By adding the `gap`-attribute you can control how much space will be added between each Element or Component. The `gap`-attribute accepts a number in pixels.
39
39
 
40
+ ### Aligning items
41
+
42
+ The layout component positions its children based on the provided direction (`horizontal` or `vertical`). With the `align-items`-attribute you can specify how to align the children on the opposite axis:
43
+
44
+ - `start` (the default value) - aligns the children in the _top_ for horizontal layouts, and on the _left_ for vertical layouts
45
+ - `center` - align the children in the center
46
+ - `end` - aligns the children in the _bottom_ for horizontal layouts, and on the _right_ for vertical layouts
47
+
40
48
  ### Dynamic dimensions
41
49
 
42
50
  For the Layout component to work properly, the direct children all need to have dimensions (i.e `w` and `h` attributes). The exception here being Text elements.
@@ -0,0 +1,98 @@
1
+ # Router Basics
2
+
3
+ The Blits framework comes with a built-in, performant router that eliminates the need to pull in any additional dependencies into your App.
4
+
5
+ Before we dive into the details, let's first touch base on some key concepts.
6
+
7
+ ## Key concepts
8
+
9
+ ### Pages are regular Blits Components
10
+
11
+ The router in Blits is primarily used to navigate between Pages in your App. In the context of a Blits App, a Page is just a regular Blits Component. Usually it will be a Component that fills the entire screen. But it is also possible to have only a smaller portion of the screen contain dynamic routed content.
12
+
13
+ ### URL hash
14
+
15
+ When navigating to a page, the hash in the URL of the browser will automatically be updated (i.e. `http://localhost:8080/#my/page`) to match the current page. The hash can also be used to _deep-link_ directly into your application. Upon first load, the router functionality will evaluate the URL hash and try to match that to a defined route. If a matching route is found, it will load that page as deep linked content.
16
+
17
+ ## Defining Routes
18
+
19
+ Routes are defined in the main Application component, as an Array under the `routes` key of the configuration object.
20
+
21
+ Each route in this array, is an Object literal that includes the following key properties:
22
+
23
+ - `path` - the url hash to the route (i.e. `/my/page`)
24
+ - `component` - the Blits component to render for the route. This can be a direct reference, but also a promise or a dynamic import that resolves to a Blits component
25
+ - `hooks` (optional) - hooks such as `before` can be defined to execute code before navigating to the route
26
+ - `options` (optional) - additional options defining route behaviour, such as `keepAlive` and `inHistory`
27
+ - `transitions` (optional) - used to define custom transitions between pages
28
+
29
+ ### Dynamic routes with params
30
+
31
+ Besides static routes such as `/account` and `/settings/wifi/advanced`, the Blits router also supports dynamic routes where URI parts can contain params.
32
+
33
+ To define a param in a route path prefix a string with a colon (`:`), followed by the name of the param, e.g. `/movies/:genre/:id`. This route path will match a URI hash such as `#/movies/sci-fi/65281918`. The router will take the params and inject them as a `prop` into the Page that's being navigated to.
34
+
35
+ To access these props in the Page component, they must first be defined in the Component first (i.e. `props: ['genre', 'id']`). Once defined, the values (`sci-fi` and `65281918`) in this case, will be available on the `this` scope, as with any regular Component.
36
+
37
+
38
+ ## Router view
39
+
40
+ The routes are loaded into a `<RouterView />` component, which acts as a placeholder for the routed Page components.
41
+
42
+ The `<RouterView />` component can be positioned and sized as you would expect from a normal Blits Component.
43
+
44
+
45
+ ```js
46
+ export default Blits.Application({
47
+ template: `
48
+ <Element>
49
+ <RouterView x="300" y="200" w="1520" h="680" />
50
+ </Element>
51
+ `,
52
+ routes: [
53
+ { path: '/', component: Home },
54
+ { path: '/details', component: () => import('./pages/Details.js'), },
55
+ { path: '/account',
56
+ component: () => {
57
+ // imagine this is an API call or some other async action
58
+ return new Promise((resolve) => {
59
+ resolve(Account)
60
+ })
61
+ }
62
+ }
63
+ ],
64
+ // ...
65
+ })
66
+ ```
67
+
68
+ ## Navigation
69
+
70
+ Each component in a routed Blits app has a `this.$router` object that provides access to the Router instance. It can be used to programmatically navigate to pages, by calling the `to()` method on it.
71
+
72
+ The `this.$router.to()`-methods accepts 3 arguments:
73
+
74
+ - `path` - the path of the route to navigate to
75
+ - `data` (optional) - an object with data to pass into the page as `props`
76
+ - `options` (optional) - additional options defining route behaviour, such as `keepAlive` and `inHistory` (these will overwrite any options specified for a route in the routes array)
77
+
78
+ ```js
79
+ export default Blits.Component('Poster', {
80
+ input: {
81
+ enter() {
82
+ this.$router.to('/movie/details', {id: '1', img: 'details.png'}, {inHistory: false})
83
+ }
84
+ }
85
+ })
86
+ ```
87
+
88
+ Whenever you navigate to a new page, the URL hash will automatically be updated. Unless specified otherwise, navigating to a new page, will add that route to the history stack. The `back` input action is automatically wired up to navigate back down the history stack.
89
+
90
+ ## Router API
91
+
92
+ The Router API provides several useful methods and properties for managing routes and navigation:
93
+
94
+ - `this.$router.to()` - navigating to a different location (as discussed above)
95
+ - `this.$router.back()` - programmatically navigate back down the history stack
96
+ - `this.$router.currentRoute` - retrieve the current route
97
+ - `this.$router.routes` - retrieve the list of all routes
98
+ - `this.$router.navigating` - retrieve the current navigating state
@@ -0,0 +1,70 @@
1
+ # Router Hooks
2
+
3
+ The Blits router supports hooks that allow you to execute code at specific points during the navigation process.
4
+
5
+ Hooks are defined per route in the optional `hooks` key within the Route configuration object.
6
+
7
+ > Currently, the Blits router only supports the before hook, with additional hooks planned for future releases.
8
+
9
+ ## Before hook
10
+
11
+ The before hook is executed before the navigation occurs. It can be used for checks, such as verifying user authentication for a specific route, for pre-fetching data, or for executing logic like sending stats beacons.
12
+
13
+ The before hook is defined as a function that receives 2 arguments: the Route object that is being navigated `to`. And the Route object that is being navigated away `from`.
14
+
15
+ ```js
16
+ export default Blits.Application({
17
+ // ...
18
+ routes: [
19
+ {
20
+ path: '/',
21
+ component: Home,
22
+ hooks: {
23
+ before(to, from) {
24
+ // custom logic
25
+ }
26
+ }
27
+ }
28
+ ],
29
+ // ...
30
+ })
31
+ ```
32
+
33
+ The before hook can optionally return a value, which will influence the behaviour of the route change:
34
+
35
+ - when `false` is returned, the router will fail to finish the routing, and act as if _no matching route was found_
36
+ - when a `string` is returned, the router will interpret this a _new route path to redirect to_
37
+ - when an `object` is returned, the router will interpret this as a route object, allowing to _modify (or completely replace) the route object_ being navigated to
38
+
39
+ Returning an `object` is a simple, yet powerful, mechanism to add advanced runtime configuration to your routes. A common scenario is to overwrite certain parts of the route object provided in the `to`-argument, such as the route options (such as `inHistory` or `stayAlive`) or define conditional page transitions.
40
+
41
+ It's also possible to add custom data to the route object in the `key` object. Custom data should be an object literal. When the new page loads, the keys in the `data` object are injected into the Component as `props`.
42
+
43
+ ```js
44
+ export default Blits.Application({
45
+ // ...
46
+ routes: [
47
+ {
48
+ path: '/video/details/:id',
49
+ component: Details,
50
+ hooks: {
51
+ before(to, from) {
52
+ // redirect to an error page when id is not a number
53
+ if(isNaN(to.params.id)) {
54
+ return '/not-valid'
55
+ }
56
+ // set a different transition when navigating from home page
57
+ if(from && from.path === '/home') {
58
+ to.transition = slideInTransition
59
+ }
60
+ // add custom data
61
+ to.data.title = 'Hello World'
62
+ // finally return the modified route object
63
+ return to
64
+ }
65
+ }
66
+ }
67
+ ],
68
+ // ...
69
+ })
70
+ ```
@@ -0,0 +1,146 @@
1
+ # Router Transitions
2
+
3
+ Page transitions add a layer of visual polish to your application by animating the change between routes.
4
+
5
+ When the router in Blits navigates to a new page, it automatically applies a subtle _fade-in / fade-out_ transition. Blits also allows to specify custom transitions on a per-route basis.
6
+
7
+ ## Defining Route Transitions
8
+
9
+ In the Route object, the `transition` key can be used to specify which transition should be applied when navigating _to_ that route.
10
+
11
+ It accepts a _Transition_ object, with 3 optional keys: `before`, `in`, `out`.
12
+
13
+ ### Before
14
+
15
+ The `before` key is used to set a property to a certain value _before_ the page transition starts. The properties are set on the page that is being navigated _to_. For example, it can be used to position the page out of screen by setting the `x` value to a negative value, in order to create a _slide in from the right_ effect. Or the `alpha` property can be set to `0` if you want to create a _fade-in_ effect.
16
+
17
+ ```js
18
+ const pageTransition = {
19
+ // position the new page outside the screen on the left side
20
+ before: {
21
+ prop: 'x',
22
+ value: -1920
23
+ }
24
+ }
25
+ ```
26
+
27
+ In order to set multiple properties to an initial state, an Array of objects can be assigned to the `before` key.
28
+
29
+ ```js
30
+ const pageTransition = {
31
+ // position the new page outside the screen on the left side
32
+ // and set the alpha to 0
33
+ before: [{
34
+ prop: 'x',
35
+ value: -1920
36
+ },{
37
+ prop: 'alpha',
38
+ value: 0
39
+ }]
40
+ }
41
+ ```
42
+
43
+ ### In
44
+
45
+ The `in`-key is used to define how the new page should transition _into_ the screen, given the initial defaults, in combination with any properties set in the optional `before` key.
46
+
47
+ The transition is defined by a _Transition_ object, consisting of:
48
+
49
+ - `prop` - the property to apply the transition on
50
+ - `value` - the value to transition to
51
+ - `duration` (optional) - the duration of the transition in milliseconds (defaults to `300ms`)
52
+ - `easing` (optional) - the easing function applied to the transition (defaults to `ease-in`)
53
+
54
+ ```js
55
+ const pageTransition = {
56
+ // position the new page outside the screen on the left side
57
+ before: {
58
+ prop: 'x',
59
+ value: -1920
60
+ },
61
+ // transition the new page from outside, into the screen
62
+ in: {
63
+ prop: 'x',
64
+ value: 0,
65
+ duration: 800,
66
+ easing: 'cubic-bezier(0.20, 1.00, 0.80, 1.00)'
67
+ }
68
+ }
69
+ ```
70
+
71
+ In order to transition multiple properties, an Array of objects can be assigned to the `in` key.
72
+
73
+ ```js
74
+ const pageTransition = {
75
+ // position the new page outside the screen on the left side
76
+ // and set the alpha to 0
77
+ before: [{
78
+ prop: 'x',
79
+ value: -1920
80
+ },{
81
+ prop: 'alpha',
82
+ value: 0
83
+ }],
84
+ // transition the new page from outside, into the screen
85
+ // and transition the alpha from 0 to 1
86
+ in: [{
87
+ prop: 'x',
88
+ value: 0,
89
+ duration: 800,
90
+ easing: 'cubic-bezier(0.20, 1.00, 0.80, 1.00)'
91
+ },{
92
+ prop: 'alpha',
93
+ value: 1,
94
+ duration: 500,
95
+ }]
96
+ }
97
+ ```
98
+
99
+ ### Out
100
+
101
+ Finally the `out`-key is used to define how the old page should transition _out of_ the screen.
102
+
103
+ Similar to the `in`-transition, the `out`-transition is defined by a _Transition object_, consisting of:
104
+
105
+ - `prop` - the property to apply the transition on
106
+ - `value` - the value to transition to
107
+ - `duration` (optional) - the duration of the transition in milliseconds (defaults to `300ms`)
108
+ - `easing` (optional) - the easing function applied to the transition (defaults to `ease-in`)
109
+
110
+ And in order to transition multiple properties at the same time during the _out_ transition, an Array of transition objects can be supplied.
111
+
112
+
113
+ ```js
114
+ const pageTransition = {
115
+ // position the new page outside the screen on the left side
116
+ // and set the alpha to 0
117
+ before: [{
118
+ prop: 'x',
119
+ value: -1920
120
+ },{
121
+ prop: 'alpha',
122
+ value: 0
123
+ }],
124
+ // transition the new page from outside, into the screen
125
+ // and transition the alpha from 0 to 1
126
+ in: [{
127
+ prop: 'x',
128
+ value: 0,
129
+ duration: 800,
130
+ easing: 'cubic-bezier(0.20, 1.00, 0.80, 1.00)'
131
+ },{
132
+ prop: 'alpha',
133
+ value: 1,
134
+ duration: 500,
135
+ }],
136
+ // slide up the old page
137
+ // while rotating
138
+ out: [{
139
+ prop: 'y',
140
+ value: -1080
141
+ },{
142
+ prop: 'rotate',
143
+ value: 720
144
+ }]
145
+ }
146
+ ```
package/docs/sidebar.json CHANGED
@@ -108,6 +108,19 @@
108
108
  }
109
109
  ]
110
110
  },
111
+ {
112
+ "text": "Router",
113
+ "items": [
114
+ {
115
+ "text": "Basics",
116
+ "link": "/router/basics"
117
+ },
118
+ {
119
+ "text": "Hooks",
120
+ "link": "/router/hooks"
121
+ }
122
+ ]
123
+ },
111
124
  {
112
125
  "text": "Plugins",
113
126
  "items": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightningjs/blits",
3
- "version": "1.9.2",
3
+ "version": "1.10.1",
4
4
  "description": "Blits: The Lightning 3 App Development Framework",
5
5
  "bin": "bin/index.js",
6
6
  "exports": {
@@ -50,7 +50,7 @@
50
50
  },
51
51
  "dependencies": {
52
52
  "@lightningjs/msdf-generator": "^1.1.0",
53
- "@lightningjs/renderer": "^2.6.0"
53
+ "@lightningjs/renderer": "^2.7.1"
54
54
  },
55
55
  "repository": {
56
56
  "type": "git",
package/src/component.js CHANGED
@@ -176,6 +176,7 @@ const Component = (name = required('name'), config = required('config')) => {
176
176
  for (let i = 0; i < watcherkeysLength; i++) {
177
177
  let target = this
178
178
  let key = watcherkeys[i]
179
+ const watchKey = key
179
180
  // when dot notation used, find the nested target
180
181
  if (key.indexOf('.') > -1) {
181
182
  const keys = key.split('.')
@@ -190,7 +191,7 @@ const Component = (name = required('name'), config = required('config')) => {
190
191
  effect((force = false) => {
191
192
  const newValue = target[key]
192
193
  if (old !== newValue || force === true) {
193
- this[symbols.watchers][key].apply(this, [newValue, old])
194
+ this[symbols.watchers][watchKey].apply(this, [newValue, old])
194
195
  old = newValue
195
196
  }
196
197
  })
@@ -60,8 +60,11 @@ export default () =>
60
60
  },
61
61
  },
62
62
  input: {
63
- back() {
64
- Router.back()
63
+ back(e) {
64
+ const navigating = Router.back()
65
+ if (navigating === false) {
66
+ this.parent.$focus(e)
67
+ }
65
68
  },
66
69
  },
67
70
  })
@@ -44,8 +44,11 @@ export default () =>
44
44
  },
45
45
  },
46
46
  input: {
47
- back() {
48
- Router.back()
47
+ back(e) {
48
+ const navigating = Router.back()
49
+ if (navigating === false) {
50
+ this.parent.$focus(e)
51
+ }
49
52
  },
50
53
  },
51
54
  })
@@ -24,9 +24,11 @@ import Settings from '../../settings.js'
24
24
 
25
25
  const layoutFn = function (config) {
26
26
  let offset = 0
27
- const xy = config.direction === 'vertical' ? 'y' : 'x'
28
- const wh = config.direction === 'vertical' ? 'height' : 'width'
29
- const opositeWh = config.direction === 'vertical' ? 'width' : 'height'
27
+ const position = config.direction === 'vertical' ? 'y' : 'x'
28
+ const oppositePosition = config.direction === 'vertical' ? 'x' : 'y'
29
+ const oppositeMount = config.direction === 'vertical' ? 'mountX' : 'mountY'
30
+ const dimension = config.direction === 'vertical' ? 'height' : 'width'
31
+ const oppositeDimension = config.direction === 'vertical' ? 'width' : 'height'
30
32
 
31
33
  const children = this.children
32
34
  const childrenLength = children.length
@@ -34,11 +36,11 @@ const layoutFn = function (config) {
34
36
  const gap = config.gap || 0
35
37
  for (let i = 0; i < childrenLength; i++) {
36
38
  const node = children[i]
37
- node[xy] = offset
39
+ node[position] = offset
38
40
  // todo: temporary text check, due to 1px width of empty text node
39
- if (wh === 'width') {
41
+ if (dimension === 'width') {
40
42
  offset += node.width + (node.width !== ('text' in node ? 1 : 0) ? gap : 0)
41
- } else if (wh === 'height') {
43
+ } else if (dimension === 'height') {
42
44
  offset +=
43
45
  'text' in node
44
46
  ? node.width > 1
@@ -48,11 +50,25 @@ const layoutFn = function (config) {
48
50
  ? node.height + gap
49
51
  : 0
50
52
  }
51
- otherDimension = Math.max(otherDimension, node[opositeWh])
53
+ otherDimension = Math.max(otherDimension, node[oppositeDimension])
52
54
  }
53
55
  // adjust the size of the layout container
54
- this[wh] = offset - gap
55
- this[opositeWh] = otherDimension
56
+ this[dimension] = offset - gap
57
+ this[oppositeDimension] = otherDimension
58
+
59
+ const align = {
60
+ start: 0,
61
+ end: 1,
62
+ center: 0.5,
63
+ }[config['align-items'] || 'start']
64
+
65
+ if (align !== 0) {
66
+ for (let i = 0; i < childrenLength; i++) {
67
+ const node = children[i]
68
+ node[oppositePosition] = otherDimension
69
+ node[oppositeMount] = align
70
+ }
71
+ }
56
72
  }
57
73
 
58
74
  const isTransition = (value) => {
@@ -146,7 +162,7 @@ const propsTransformer = {
146
162
  set src(v) {
147
163
  this.props['src'] = v
148
164
  if (this.raw['color'] === undefined) {
149
- this.props['color'] = 0xffffffff
165
+ this.props['color'] = this.props['src'] ? 0xffffffff : 0x00000000
150
166
  }
151
167
  },
152
168
  set texture(v) {
@@ -225,7 +241,15 @@ const propsTransformer = {
225
241
  }
226
242
  },
227
243
  set show(v) {
228
- this.props['alpha'] = v ? 1 : 0
244
+ if (v) {
245
+ this.props['alpha'] = 1
246
+ this.props['width'] = this.raw['w'] || this.raw['width']
247
+ this.props['height'] = this.raw['h'] || this.raw['height']
248
+ } else {
249
+ this.props['alpha'] = 0
250
+ this.props['width'] = 0
251
+ this.props['height'] = 0
252
+ }
229
253
  },
230
254
  set alpha(v) {
231
255
  this.props['alpha'] = v
package/src/focus.js CHANGED
@@ -38,6 +38,7 @@ export default {
38
38
  return focusedComponent
39
39
  },
40
40
  set(component, event) {
41
+ if (component === focusedComponent) return
41
42
  clearTimeout(setFocusTimeout)
42
43
  focusedComponent && focusedComponent !== component.parent && focusedComponent.unfocus()
43
44
  focusChain.reverse().forEach((cmp) => cmp.unfocus())
@@ -442,10 +442,18 @@ const generateForLoopCode = function (templateObject, parent) {
442
442
 
443
443
  // inject the destroy code in the correct spot
444
444
  ctx.renderCode.splice(indexToInjectDestroyCode, 0, ...destroyCode)
445
+
446
+ let effectKey = `${interpolate(result[2], '')}`
447
+
448
+ // Get the last property from nested path
449
+ if (effectKey && effectKey.includes('.')) {
450
+ effectKey = effectKey.match(/[^.]+$/)[0]
451
+ }
452
+
445
453
  ctx.renderCode.push(`
446
454
  effect(() => {
447
455
  forloop${forStartCounter}(${cast(result[2], ':for')}, elms, created${forStartCounter})
448
- }, '${interpolate(result[2], '')}')
456
+ }, '${effectKey}' )
449
457
  `)
450
458
 
451
459
  outerScopeEffects.forEach((effect) => {
@@ -2055,6 +2055,182 @@ test('Generate code for a template with a simple for-loop on an Element', (asser
2055
2055
  assert.end()
2056
2056
  })
2057
2057
 
2058
+ test('Generate code for a template with a simple for-loop on an Element, Using data from appState plugin', (assert) => {
2059
+ const templateObject = {
2060
+ children: [
2061
+ {
2062
+ [Symbol.for('componentType')]: 'Element',
2063
+ children: [
2064
+ {
2065
+ [Symbol.for('componentType')]: 'Element',
2066
+ ':for': 'item in $$appState.list',
2067
+ },
2068
+ ],
2069
+ },
2070
+ ],
2071
+ }
2072
+
2073
+ const expectedRender = `
2074
+ function anonymous(parent, component, context, components, effect, getRaw) {
2075
+ const elms = []
2076
+ let componentType
2077
+ const rootComponent = component
2078
+ let propData
2079
+ const elementConfig0 = {}
2080
+ elms[0] = this.element({ parent: parent || 'root' }, component)
2081
+ elms[0].populate(elementConfig0)
2082
+ parent = elms[0]
2083
+ const created1 = []
2084
+ const forloop1 = (collection = [], elms, created) => {
2085
+ const rawCollection = getRaw(collection)
2086
+ const keys = new Set()
2087
+ let l = rawCollection.length
2088
+ while (l--) {
2089
+ const item = rawCollection[l]
2090
+ const index = l
2091
+ keys.add('' + l)
2092
+ }
2093
+ let i = created.length
2094
+ while (i--) {
2095
+ if (keys.has(created[i]) === false) {
2096
+ const key = created[i]
2097
+ elms[1][key] && elms[1][key].destroy()
2098
+ delete elms[1][key]
2099
+ }
2100
+ }
2101
+ created.length = 0
2102
+ const length = rawCollection.length
2103
+ for (let __index = 0; __index < length; __index++) {
2104
+ const scope = Object.create(component)
2105
+ parent = elms[0]
2106
+ scope['index'] = __index
2107
+ scope['item'] = rawCollection[__index]
2108
+ scope['key'] = '' + __index
2109
+ created.push(scope.key)
2110
+ parent = elms[0]
2111
+ if (elms[1] === undefined) {
2112
+ elms[1] = {}
2113
+ }
2114
+ const elementConfig1 = {}
2115
+ if (elms[1][scope.key] === undefined) {
2116
+ elms[1][scope.key] = this.element({ parent: parent || 'root' }, component)
2117
+ }
2118
+ if (elms[1][scope.key].nodeId === undefined) {
2119
+ elms[1][scope.key].populate(elementConfig1)
2120
+ }
2121
+ }
2122
+ }
2123
+ effect(() => {
2124
+ forloop1(component.$appState.list, elms, created1)
2125
+ }, 'list')
2126
+ return elms
2127
+ }
2128
+ `
2129
+
2130
+ const actual = generator.call(scope, templateObject)
2131
+
2132
+ assert.equal(
2133
+ normalize(actual.render.toString()),
2134
+ normalize(expectedRender),
2135
+ 'Generator should return a render function with the correct code using data from appState plugin'
2136
+ )
2137
+
2138
+ assert.ok(
2139
+ Array.isArray(actual.effects) && actual.effects.length === 0,
2140
+ 'Generator should return an empty effect array'
2141
+ )
2142
+
2143
+ assert.end()
2144
+ })
2145
+
2146
+ test('Generate code for a template with a simple for-loop on an Element, Using data from nested state property', (assert) => {
2147
+ const templateObject = {
2148
+ children: [
2149
+ {
2150
+ [Symbol.for('componentType')]: 'Element',
2151
+ children: [
2152
+ {
2153
+ [Symbol.for('componentType')]: 'Element',
2154
+ ':for': 'item in $content.data',
2155
+ },
2156
+ ],
2157
+ },
2158
+ ],
2159
+ }
2160
+
2161
+ const expectedRender = `
2162
+ function anonymous(parent, component, context, components, effect, getRaw) {
2163
+ const elms = []
2164
+ let componentType
2165
+ const rootComponent = component
2166
+ let propData
2167
+ const elementConfig0 = {}
2168
+ elms[0] = this.element({ parent: parent || 'root' }, component)
2169
+ elms[0].populate(elementConfig0)
2170
+ parent = elms[0]
2171
+ const created1 = []
2172
+ const forloop1 = (collection = [], elms, created) => {
2173
+ const rawCollection = getRaw(collection)
2174
+ const keys = new Set()
2175
+ let l = rawCollection.length
2176
+ while (l--) {
2177
+ const item = rawCollection[l]
2178
+ const index = l
2179
+ keys.add('' + l)
2180
+ }
2181
+ let i = created.length
2182
+ while (i--) {
2183
+ if (keys.has(created[i]) === false) {
2184
+ const key = created[i]
2185
+ elms[1][key] && elms[1][key].destroy()
2186
+ delete elms[1][key]
2187
+ }
2188
+ }
2189
+ created.length = 0
2190
+ const length = rawCollection.length
2191
+ for (let __index = 0; __index < length; __index++) {
2192
+ const scope = Object.create(component)
2193
+ parent = elms[0]
2194
+ scope['index'] = __index
2195
+ scope['item'] = rawCollection[__index]
2196
+ scope['key'] = '' + __index
2197
+ created.push(scope.key)
2198
+ parent = elms[0]
2199
+ if (elms[1] === undefined) {
2200
+ elms[1] = {}
2201
+ }
2202
+ const elementConfig1 = {}
2203
+ if (elms[1][scope.key] === undefined) {
2204
+ elms[1][scope.key] = this.element({ parent: parent || 'root' }, component)
2205
+ }
2206
+ if (elms[1][scope.key].nodeId === undefined) {
2207
+ elms[1][scope.key].populate(elementConfig1)
2208
+ }
2209
+ }
2210
+ }
2211
+ effect(() => {
2212
+ forloop1(component.content.data, elms, created1)
2213
+ }, 'data')
2214
+ return elms
2215
+ }
2216
+ `
2217
+
2218
+ const actual = generator.call(scope, templateObject)
2219
+
2220
+ assert.equal(
2221
+ normalize(actual.render.toString()),
2222
+ normalize(expectedRender),
2223
+ 'Generator should return a render function with the correct code using data from nested state property'
2224
+ )
2225
+
2226
+ assert.ok(
2227
+ Array.isArray(actual.effects) && actual.effects.length === 0,
2228
+ 'Generator should return an empty effect array'
2229
+ )
2230
+
2231
+ assert.end()
2232
+ })
2233
+
2058
2234
  test('Generate code for a template with a simple for-loop on an Element with a custom index key', (assert) => {
2059
2235
  const templateObject = {
2060
2236
  children: [