@lightningjs/blits 1.27.1 → 1.29.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.
@@ -8,144 +8,118 @@ on:
8
8
 
9
9
  jobs:
10
10
  comment-lint-results:
11
- # Only run this job if the previous workflow failed (indicating lint errors)
12
11
  if: github.event.workflow_run.conclusion == 'failure'
13
12
  runs-on: ubuntu-latest
14
13
  permissions:
15
14
  pull-requests: write
16
15
 
17
16
  steps:
18
- - name: Download Lint Results
19
- id: download
20
- uses: actions/github-script@v6
21
- with:
22
- script: |
23
- try {
24
- core.info('Downloading lint results artifact');
25
-
26
- const artifacts = await github.rest.actions.listWorkflowRunArtifacts({
27
- owner: context.repo.owner,
28
- repo: context.repo.repo,
29
- run_id: ${{ github.event.workflow_run.id }}
30
- });
31
-
32
- const matchArtifact = artifacts.data.artifacts.find(artifact => artifact.name === "lint-results");
33
- if (!matchArtifact) {
34
- core.setFailed('No lint results artifact found');
35
- return false;
36
- }
37
-
38
- core.info(`Found artifact id: ${matchArtifact.id}`);
39
-
40
- const download = await github.rest.actions.downloadArtifact({
41
- owner: context.repo.owner,
42
- repo: context.repo.repo,
43
- artifact_id: matchArtifact.id,
44
- archive_format: 'zip'
45
- });
17
+ - name: Download Lint Results
18
+ id: download
19
+ uses: actions/github-script@v6
20
+ with:
21
+ result-encoding: string
22
+ script: |
46
23
 
47
- const fs = require('fs');
48
24
  try {
49
- fs.writeFileSync('lint-results.zip', Buffer.from(download.data));
50
- core.info('Successfully downloaded and saved artifact');
51
- return true;
52
- } catch (error) {
53
- core.setFailed(`Failed to write artifact: ${error.message}`);
54
- return false;
55
- }
56
- } catch (error) {
57
- core.setFailed(`Failed to download artifact: ${error.message}`);
58
- return false;
59
- }
60
- result-encoding: string
61
-
62
- - name: Extract Lint Results
63
- if: steps.download.outputs.result == 'true'
64
- id: extract
65
- run: |
66
- mkdir -p lint-results
67
- unzip -o lint-results.zip -d lint-results
68
-
69
- if [ -f lint-results/errors.md ]; then
70
- # Use GitHub's EOF syntax for multiline content
71
- echo "LINT_ERRORS<<EOF" >> $GITHUB_ENV
72
- cat lint-results/errors.md >> $GITHUB_ENV
73
- echo "EOF" >> $GITHUB_ENV
74
- echo "HAS_ERRORS=true" >> $GITHUB_OUTPUT
75
- else
76
- echo "HAS_ERRORS=false" >> $GITHUB_OUTPUT
77
- fi
78
-
79
- - name: Find PR Number
80
- if: steps.extract.outputs.HAS_ERRORS == 'true'
81
- id: find-pr
82
- uses: actions/github-script@v6
83
- with:
84
- script: |
85
- try {
86
- core.info('Finding associated PR');
87
-
88
- const { owner, repo } = context.repo;
89
- const run_id = ${{ github.event.workflow_run.id }};
90
-
91
- // Get the triggering workflow run
92
- const run = await github.rest.actions.getWorkflowRun({
93
- owner,
94
- repo,
95
- run_id
96
- });
97
-
98
- // Find associated PR - first try the event payload
99
- if (run.data.pull_requests && run.data.pull_requests.length > 0) {
100
- core.info(`Found PR directly from run data: #${run.data.pull_requests[0].number}`);
101
- return run.data.pull_requests[0].number;
102
- }
103
-
104
- // Fallback to searching by head SHA
105
- core.info(`Searching for PR using head SHA: ${run.data.head_sha}`);
106
- const pulls = await github.rest.pulls.list({
107
- owner,
108
- repo,
109
- state: 'open',
110
- head: `${owner}:${run.data.head_branch}`
111
- });
112
-
113
- if (pulls.data.length > 0) {
114
- core.info(`Found PR by branch: #${pulls.data[0].number}`);
115
- return pulls.data[0].number;
25
+ core.info('Downloading lint-results artifact');
26
+ const runId = context.payload.workflow_run.id;
27
+
28
+ const artifacts = await github.rest.actions.listWorkflowRunArtifacts({
29
+ owner: context.repo.owner,
30
+ repo: context.repo.repo,
31
+ run_id: runId
32
+ });
33
+
34
+ const art = artifacts.data.artifacts.find(a => a.name === "lint-results");
35
+ if (!art) {
36
+ core.setFailed('No lint-results artifact found');
37
+ return 'false';
38
+ }
39
+
40
+ core.info(`Found artifact id ${art.id}, downloading…`);
41
+ const dl = await github.rest.actions.downloadArtifact({
42
+ owner: context.repo.owner,
43
+ repo: context.repo.repo,
44
+ artifact_id: art.id,
45
+ archive_format: 'zip'
46
+ });
47
+
48
+ require('fs').writeFileSync('lint-results.zip', Buffer.from(dl.data));
49
+ core.info('Downloaded and saved lint-results.zip');
50
+ return 'true';
51
+
52
+ } catch (err) {
53
+ core.setFailed(`Download failed: ${err.message}`);
54
+ return 'false';
116
55
  }
117
56
 
118
- core.info('No PR found for this workflow run');
119
- return null;
120
- } catch (error) {
121
- core.setFailed(`Failed to find PR: ${error.message}`);
122
- return null;
123
- }
124
- result-encoding: string
125
-
126
- - name: Post Lint Results as PR Comment
127
- if: steps.find-pr.outputs.result != 'null' && steps.find-pr.outputs.result != ''
128
- uses: actions/github-script@v6
129
- with:
130
- script: |
131
- try {
132
- const pr_number = parseInt(${{ steps.find-pr.outputs.result }});
133
-
134
- if (isNaN(pr_number)) {
135
- core.warning('Invalid PR number');
136
- return;
137
- }
138
-
139
- core.info(`Posting comment to PR #${pr_number}`);
140
-
141
- await github.rest.issues.createComment({
142
- owner: context.repo.owner,
143
- repo: context.repo.repo,
144
- issue_number: pr_number,
145
- body: process.env.LINT_ERRORS
146
- });
147
-
148
- core.info(`Successfully posted lint errors comment to PR #${pr_number}`);
149
- } catch (error) {
150
- core.setFailed(`Failed to post comment: ${error.message}`);
151
- }
57
+ - name: Extract Lint Results
58
+ if: steps.download.outputs.result == 'true'
59
+ id: extract
60
+ run: |
61
+ mkdir -p extracted
62
+ unzip -o lint-results.zip -d extracted
63
+
64
+ # Read PR information
65
+ if [ -f extracted/pr_number.txt ]; then
66
+ echo "PR_NUMBER=$(cat extracted/pr_number.txt)" >> $GITHUB_ENV
67
+ else
68
+ echo "PR_NUMBER=0" >> $GITHUB_ENV
69
+ fi
70
+
71
+ if [ -f extracted/errors.md ] || [ -f extracted/lint-results/errors.md ]; then
72
+ if [ -f extracted/errors.md ]; then
73
+ # Handle flat structure
74
+ echo "LINT_ERRORS<<EOF" >> $GITHUB_ENV
75
+ cat extracted/errors.md >> $GITHUB_ENV
76
+ echo "EOF" >> $GITHUB_ENV
77
+ else
78
+ # Handle nested structure
79
+ echo "LINT_ERRORS<<EOF" >> $GITHUB_ENV
80
+ cat extracted/lint-results/errors.md >> $GITHUB_ENV
81
+ echo "EOF" >> $GITHUB_ENV
82
+ fi
83
+ echo "HAS_ERRORS=true" >> $GITHUB_OUTPUT
84
+ else
85
+ echo "HAS_ERRORS=false" >> $GITHUB_OUTPUT
86
+ fi
87
+
88
+ - name: Post Lint Results as PR Comment
89
+ if: steps.extract.outputs.HAS_ERRORS == 'true'
90
+ uses: actions/github-script@v6
91
+ with:
92
+ script: |
93
+ try {
94
+ // First check if PR number was passed from the lint workflow
95
+ let prNumber = parseInt(process.env.PR_NUMBER);
96
+
97
+ // Fallback to the workflow_run PR data if available
98
+ if (!prNumber || isNaN(prNumber) || prNumber === 0) {
99
+ const pulls = context.payload.workflow_run.pull_requests;
100
+ core.info(`Looking for PR information in workflow_run: Found ${pulls.length} PR(s)`);
101
+
102
+ if (pulls.length === 0) {
103
+ core.warning('No pull request information found. Skipping comment.');
104
+ return;
105
+ }
106
+
107
+ prNumber = pulls[0].number;
108
+ }
109
+
110
+ core.info(`Posting lint errors comment to PR #${prNumber}`);
111
+
112
+ const body = process.env.LINT_ERRORS;
113
+
114
+ await github.rest.issues.createComment({
115
+ owner: context.repo.owner,
116
+ repo: context.repo.repo,
117
+ issue_number: prNumber,
118
+ body
119
+ });
120
+
121
+ core.info('Lint errors comment posted successfully');
122
+ } catch (err) {
123
+ core.error('Failed to post lint comment:', err);
124
+ core.setFailed(err.message);
125
+ }
@@ -3,8 +3,7 @@ name: Comment Test Results
3
3
  on:
4
4
  workflow_run:
5
5
  workflows: ["Run Blits Tests"]
6
- types:
7
- - completed
6
+ types: [completed]
8
7
 
9
8
  jobs:
10
9
  comment-test-results:
@@ -13,102 +12,98 @@ jobs:
13
12
  pull-requests: write
14
13
 
15
14
  steps:
16
- - name: Download Test Results
17
- uses: actions/github-script@v6
18
- with:
19
- script: |
20
- const artifacts = await github.rest.actions.listWorkflowRunArtifacts({
21
- owner: context.repo.owner,
22
- repo: context.repo.repo,
23
- run_id: ${{ github.event.workflow_run.id }}
24
- });
25
-
26
- const matchArtifact = artifacts.data.artifacts.find(artifact => artifact.name === "test-results");
27
- if (!matchArtifact) {
28
- core.setFailed('No test results artifact found');
29
- return;
30
- }
31
-
32
- const download = await github.rest.actions.downloadArtifact({
33
- owner: context.repo.owner,
34
- repo: context.repo.repo,
35
- artifact_id: matchArtifact.id,
36
- archive_format: 'zip'
37
- });
38
-
39
- const fs = require('fs');
40
- fs.writeFileSync('test-results.zip', Buffer.from(download.data));
41
-
42
- - name: Extract Test Results
43
- run: |
44
- mkdir -p test-results
45
- unzip -o test-results.zip -d test-results
46
-
47
- TIMESTAMP=$(cat test-results/timestamp.txt)
48
- SUMMARY=$(cat test-results/summary.txt)
49
- FAILED=$(cat test-results/failed.txt)
50
-
51
- echo "TIMESTAMP=$TIMESTAMP" >> $GITHUB_ENV
52
- echo "SUMMARY=$SUMMARY" >> $GITHUB_ENV
53
- echo "FAILED=$FAILED" >> $GITHUB_ENV
54
-
55
- # Check if error.txt exists and read it if it does
56
- if [ -f test-results/error.txt ]; then
57
- # Store raw error output in a file for the next step
58
- cat test-results/error.txt > raw_error.txt
59
- fi
60
-
61
- - name: Find PR Number
62
- id: find-pr
63
- uses: actions/github-script@v6
64
- with:
65
- script: |
66
- const { owner, repo } = context.repo;
67
- const run_id = ${{ github.event.workflow_run.id }};
68
-
69
- // Get the triggering workflow run
70
- const run = await github.rest.actions.getWorkflowRun({
71
- owner,
72
- repo,
73
- run_id
74
- });
75
-
76
- // Find associated PR
77
- const pulls = await github.rest.pulls.list({
78
- owner,
79
- repo,
80
- state: 'open',
81
- head: run.data.head_sha
82
- });
83
-
84
- if (pulls.data.length > 0) {
85
- return pulls.data[0].number;
86
- }
87
-
88
- console.log('No PR found');
89
- return null;
90
- result-encoding: string
91
-
92
- - name: Post Test Results as PR Comment
93
- if: steps.find-pr.outputs.result != 'null'
94
- uses: actions/github-script@v6
95
- with:
96
- script: |
97
- const fs = require('fs');
98
- const pr_number = parseInt(${{ steps.find-pr.outputs.result }});
99
- const status = process.env.FAILED === 'true' ? '❌ FAILED' : '✅ PASSED';
100
-
101
- let commentBody = `#### Test Results: ${status}\n**Run at:** ${process.env.TIMESTAMP}\n\n**Summary:**\n${process.env.SUMMARY}`;
102
-
103
- // Add error output if it exists
104
- if (fs.existsSync('raw_error.txt')) {
105
- const errorOutput = fs.readFileSync('raw_error.txt', 'utf8');
106
- commentBody += '\n\n**Error Output:**\n```\n' + errorOutput + '\n```';
107
- }
108
-
109
- github.rest.issues.createComment({
110
- owner: context.repo.owner,
111
- repo: context.repo.repo,
112
- issue_number: pr_number,
113
- body: commentBody
114
- });
15
+ - name: Download Test Results
16
+ uses: actions/github-script@v6
17
+ with:
18
+ script: |
19
+ const artifacts = await github.rest.actions.listWorkflowRunArtifacts({
20
+ owner: context.repo.owner,
21
+ repo: context.repo.repo,
22
+ run_id: ${{ github.event.workflow_run.id }}
23
+ });
24
+ const art = artifacts.data.artifacts.find(a => a.name === "test-results");
25
+ if (!art) {
26
+ core.setFailed('No test-results artifact found');
27
+ return;
28
+ }
29
+ const dl = await github.rest.actions.downloadArtifact({
30
+ owner: context.repo.owner,
31
+ repo: context.repo.repo,
32
+ artifact_id: art.id,
33
+ archive_format: 'zip'
34
+ });
35
+ require('fs').writeFileSync('test-results.zip', Buffer.from(dl.data));
36
+
37
+ - name: Extract Test Results
38
+ run: |
39
+
40
+ mkdir -p extracted
41
+ unzip -o test-results.zip -d extracted
42
+
43
+ # Set environment variables from the extracted files
44
+ echo "TIMESTAMP=$(cat extracted/timestamp.txt)" >> $GITHUB_ENV
45
+ echo "SUMMARY=$(cat extracted/summary.txt)" >> $GITHUB_ENV
46
+ echo "FAILED=$(cat extracted/failed.txt)" >> $GITHUB_ENV
47
+
48
+ # Read PR information
49
+ if [ -f extracted/pr_number.txt ]; then
50
+ echo "PR_NUMBER=$(cat extracted/pr_number.txt)" >> $GITHUB_ENV
51
+ else
52
+ echo "PR_NUMBER=0" >> $GITHUB_ENV
53
+ fi
54
+
55
+
56
+ # Copy error file if it exists
57
+ if [ -f extracted/error.txt ]; then
58
+ cp extracted/error.txt raw_error.txt
59
+ fi
60
+
61
+ - name: Post Test Results as PR Comment
62
+ uses: actions/github-script@v6
63
+ with:
64
+ script: |
65
+ const fs = require('fs');
66
+
67
+ try {
68
+ // First check if PR number was passed from the test workflow
69
+ let prNumber = parseInt(process.env.PR_NUMBER);
70
+
71
+ // Fallback to the workflow_run PR data if available
72
+ if (!prNumber || isNaN(prNumber) || prNumber === 0) {
73
+ const pulls = context.payload.workflow_run.pull_requests;
74
+ console.log(`Looking for PR information in workflow_run: Found ${pulls.length} PR(s).`);
75
+
76
+ if (pulls.length === 0) {
77
+ console.warn('No pull request information found. Skipping comment.');
78
+ return;
79
+ }
80
+
81
+ prNumber = pulls[0].number;
82
+ }
83
+
84
+ console.log(`Posting comment to PR #${prNumber}`);
85
+
86
+ const status = process.env.FAILED === 'true' ? '❌ FAILED' : '✅ PASSED';
87
+ let commentBody = `#### Test Results: ${status}\n`
88
+ + `**Run at:** ${process.env.TIMESTAMP}\n\n`
89
+ + `**Summary:**\n${process.env.SUMMARY}`;
90
+
91
+ if (fs.existsSync('raw_error.txt')) {
92
+ commentBody += '\n\n**Error Output:**\n```\n'
93
+ + fs.readFileSync('raw_error.txt', 'utf8')
94
+ + '\n```';
95
+ }
96
+
97
+ await github.rest.issues.createComment({
98
+ owner: context.repo.owner,
99
+ repo: context.repo.repo,
100
+ issue_number: prNumber,
101
+ body: commentBody
102
+ });
103
+
104
+ console.log(`Successfully posted comment to PR #${prNumber}`);
105
+
106
+ } catch (err) {
107
+ console.error('🛑 Failed to post test results comment:', err);
108
+ core.setFailed(`Comment step failed: ${err.message}`);
109
+ }
@@ -21,6 +21,12 @@ jobs:
21
21
  with:
22
22
  node-version: 20.16.0
23
23
 
24
+ - name: Save PR Information
25
+ run: |
26
+ echo '${{ github.event.pull_request.number }}' > ./pr_number.txt
27
+ echo '${{ github.event.pull_request.head.repo.full_name }}' > ./pr_repo.txt
28
+ echo '${{ github.event.pull_request.head.ref }}' > ./pr_branch.txt
29
+
24
30
  - name: Install Dependencies
25
31
  run: npm install
26
32
 
@@ -91,18 +97,14 @@ jobs:
91
97
  * Outputs results and saves result files
92
98
  */
93
99
  function outputResults(results) {
94
- // Create test results directory and files
95
100
  try {
96
- fs.mkdirSync('./test-results', { recursive: true });
97
-
98
- // Write results files
99
- fs.writeFileSync('./test-results/timestamp.txt', results.timestamp);
100
- fs.writeFileSync('./test-results/summary.txt', results.summary);
101
- fs.writeFileSync('./test-results/failed.txt', results.testsFailed.toString());
101
+ fs.writeFileSync('./timestamp.txt', results.timestamp);
102
+ fs.writeFileSync('./summary.txt', results.summary);
103
+ fs.writeFileSync('./failed.txt', results.testsFailed.toString());
102
104
 
103
105
  // Save error output if it exists
104
106
  if (results.error) {
105
- fs.writeFileSync('./test-results/error.txt', results.error);
107
+ fs.writeFileSync('./error.txt', results.error);
106
108
  }
107
109
  } catch (err) {
108
110
  console.error('Error saving test results:', err);
@@ -124,7 +126,15 @@ jobs:
124
126
  uses: actions/upload-artifact@v4
125
127
  with:
126
128
  name: test-results
127
- path: ./test-results/
129
+ path: |
130
+
131
+ ./timestamp.txt
132
+ ./summary.txt
133
+ ./failed.txt
134
+ ./error.txt
135
+ ./pr_number.txt
136
+ ./pr_repo.txt
137
+ ./pr_branch.txt
128
138
 
129
139
  # Fail the workflow if tests failed
130
140
  - name: Check Test Status
@@ -19,6 +19,12 @@ jobs:
19
19
  # Needed to compare changes against base branch
20
20
  fetch-depth: 0
21
21
 
22
+ - name: Save PR Information
23
+ run: |
24
+ echo '${{ github.event.pull_request.number }}' > ./pr_number.txt
25
+ echo '${{ github.event.pull_request.head.repo.full_name }}' > ./pr_repo.txt
26
+ echo '${{ github.event.pull_request.head.ref }}' > ./pr_branch.txt
27
+
22
28
  - name: Set up Node.js
23
29
  uses: actions/setup-node@v3
24
30
  with:
@@ -110,7 +116,11 @@ jobs:
110
116
  uses: actions/upload-artifact@v4
111
117
  with:
112
118
  name: lint-results
113
- path: ./lint-results/
119
+ path: |
120
+ ./lint-results/
121
+ ./pr_number.txt
122
+ ./pr_repo.txt
123
+ ./pr_branch.txt
114
124
 
115
125
  # Fail the workflow if linting errors found
116
126
  - name: Check Lint Status
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ _22 may 2025_
4
+
5
+ ## v1.29.0
6
+
7
+ - Fixed memory leaks
8
+ - Added componentId in warning when modifying props directly for easier debugging
9
+
10
+ _15 apr 2025_
11
+
12
+ ## v1.28.0
13
+
14
+ - Added graceful handling of announcer when speechSynthesis API is not available
15
+ - Renamed `beforeAll` method on router to `beforeEach` (as specified in the type definitions)
16
+ - Fixed detection of correct PR to comment for github workflows
17
+
3
18
  _15 apr 2025_
4
19
 
5
20
  ## v1.27.1
@@ -168,3 +168,37 @@ Generally it's not recommended to access and interact directly with Elements in
168
168
  The `ref`-attribute can be used in combination with the `for`-attribute as well.
169
169
 
170
170
  Setting the _ref_ in a for loop context, can be done by either passing a string (i.e. `myitem`) and as Elements get generated in the for loop, the `index` value will be appended to it (i.e. `myitem0`, `myitem1`, etc.). Alternatively you can make the attribute interpolated and define the value for each item yourself (i.e. `:ref="'item' + $item.id"`).
171
+
172
+ ## Using the range attribute
173
+
174
+ Sometimes you may only need to render a portion of an array rather than all the items.
175
+
176
+ To do this you can use the `range` variable to specify a subset of items to render. You should specify a `from` and `to` value (`from` is inclusive, and `to` is exclusive) inside the `range` variable (i.e. `range="{from: 0, to: 2}"`). You can also make the variable reactive, and use moving pointers to dynamically render your items.
177
+
178
+ This works differently to using the `show` directive on the items, as the items will be destroyed once they are outside the range, and created again when they fall back in it.
179
+
180
+ ```js
181
+ export default Blits.Component('ForLoop', {
182
+ template: `
183
+ <Element>
184
+ <Element :for="(item, index) in $items" :range="{from: $selected - 1, to: $selected + 2}" w="100" h="100" color="lime" :x="$index * 150" />
185
+ </Element>
186
+ `,
187
+ state() {
188
+ return {
189
+ selected: 0,
190
+ items: ['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5', 'Item 6', 'Item 7']
191
+ }
192
+ },
193
+ input: {
194
+ left() {
195
+ this.selected = Math.max(0, this.selected - 1)
196
+ },
197
+ right() {
198
+ this.selected = Math.min(this.items.length, this.selected + 1)
199
+ }
200
+ }
201
+ })
202
+ ```
203
+
204
+ In the example above, there are 7 items in the array, but we only render the items immediately before and after the selected item at any time.
package/index.d.ts CHANGED
@@ -23,7 +23,7 @@ import {type ShaderEffect as RendererShaderEffect, type WebGlCoreShader, type Re
23
23
  declare module '@lightningjs/blits' {
24
24
 
25
25
 
26
- export interface AnnouncerUtterance extends Promise {
26
+ export interface AnnouncerUtterance extends Promise<T> {
27
27
  /**
28
28
  * Removes a specific message from the announcement queue,
29
29
  * to make sure it isn't spoke out.
@@ -481,7 +481,7 @@ declare module '@lightningjs/blits' {
481
481
  }
482
482
 
483
483
  export interface RouterHooks {
484
- beforeEach?: (to: Route, from: Route) => string | Route | Promise<string | Route>;
484
+ beforeEach?: (to: Route, from: Route) => string | Route | Promise<string | Route> | void;
485
485
  }
486
486
 
487
487
  export interface RouterConfig {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightningjs/blits",
3
- "version": "1.27.1",
3
+ "version": "1.29.0",
4
4
  "description": "Blits: The Lightning 3 App Development Framework",
5
5
  "bin": "bin/index.js",
6
6
  "exports": {
@@ -15,6 +15,8 @@
15
15
  * SPDX-License-Identifier: Apache-2.0
16
16
  */
17
17
 
18
+ import { Log } from '../lib/log.js'
19
+
18
20
  const syn = window.speechSynthesis
19
21
 
20
22
  const isAndroid = /android/i.test((window.navigator || {}).userAgent || '')
@@ -82,14 +84,21 @@ const speak = (options) => {
82
84
 
83
85
  export default {
84
86
  speak(options) {
85
- if (!initialized) {
86
- initialize()
87
+ if (syn !== undefined) {
88
+ if (initialized === false) {
89
+ initialize()
90
+ }
91
+ return speak(options)
92
+ } else {
93
+ Log.error('speechSynthesis web API not available')
94
+ return Promise.reject({ error: 'unavailable' })
87
95
  }
88
- return speak(options)
89
96
  },
90
97
  cancel() {
91
- syn.cancel()
92
- clear()
98
+ if (syn !== undefined) {
99
+ syn.cancel()
100
+ clear()
101
+ }
93
102
  },
94
103
  // @todo
95
104
  // getVoices() {
@@ -57,10 +57,36 @@ export default {
57
57
  this.$clearIntervals()
58
58
  eventListeners.removeListeners(this)
59
59
  deleteChildren(this[symbols.children])
60
+ this[symbols.children].length = 0
60
61
  removeGlobalEffects(this[symbols.effects])
61
62
  this[symbols.state] = {}
62
63
  this[symbols.props] = {}
64
+ this[symbols.computed] = null
65
+ this.lifecycle = null
63
66
  this[symbols.effects].length = 0
67
+ this.parent = null
68
+ this.rootParent = null
69
+ this[symbols.wrapper] = null
70
+ this[symbols.originalState] = null
71
+ this[symbols.slots].length = 0
72
+
73
+ delete this[symbols.computed]
74
+ delete this.lifecycle
75
+ delete this[symbols.effects]
76
+ delete this.parent
77
+ delete this.rootParent
78
+ delete this[symbols.wrapper]
79
+ delete this[symbols.originalState]
80
+ delete this[symbols.children]
81
+ delete this[symbols.slots]
82
+ delete this.componentId
83
+ delete this[symbols.id]
84
+ delete this.ref
85
+
86
+ this[symbols.holder].destroy()
87
+ this[symbols.holder] = null
88
+ delete this[symbols.holder]
89
+
64
90
  Log.debug(`Destroyed component ${this.componentId}`)
65
91
  },
66
92
  writable: false,
@@ -52,7 +52,9 @@ export default (component, props = []) => {
52
52
  return value
53
53
  },
54
54
  set(v) {
55
- Log.warn(`Warning! Avoid mutating props directly (${prop.key})`)
55
+ Log.warn(
56
+ `Warning! Avoid mutating props directly (prop "${prop.key}" in component "${this.componentId}")`
57
+ )
56
58
  this[symbols.props][prop.key] = v
57
59
  },
58
60
  })
@@ -223,7 +223,7 @@ test('Setting prop value directly', (assert) => {
223
223
  assert.equal(componentInstance[symbols.props].property, 'bar', 'Should be possible to mutate the property')
224
224
  let logs = capture()
225
225
  assert.equal(logs.length, 1)
226
- assert.equal(logs[0].args.pop(), 'Warning! Avoid mutating props directly (property)', 'Should log warning message')
226
+ assert.equal(logs[0].args.pop(), `Warning! Avoid mutating props directly (prop "${props[0].key}" in component "${componentInstance.componentId}")`, 'Should log warning message')
227
227
 
228
228
  assert.end()
229
229
  })
@@ -1492,18 +1492,19 @@ export default () =>
1492
1492
  let totalFps = 0
1493
1493
  let fpsUpdateCounter = 0
1494
1494
 
1495
- renderer.on('fpsUpdate', (rM, { fps }) => {
1496
- minFps = Math.min(fps, minFps)
1497
- maxFps = Math.max(fps, maxFps)
1498
- totalFps += fps
1499
- fpsUpdateCounter++
1500
- avgFps = Math.round(totalFps / fpsUpdateCounter)
1501
-
1502
- this.fps = fps.toString().padStart(3, '0')
1503
- this.avgFps = avgFps.toString().padStart(3, '0')
1504
- this.minFps = minFps.toString().padStart(3, '0')
1505
- this.maxFps = maxFps.toString().padStart(3, '0')
1506
- })
1495
+ // This leaks - might be L3 SubTexture issue
1496
+ // renderer.on('fpsUpdate', (rM, { fps }) => {
1497
+ // minFps = Math.min(fps, minFps)
1498
+ // maxFps = Math.max(fps, maxFps)
1499
+ // totalFps += fps
1500
+ // fpsUpdateCounter++
1501
+ // avgFps = Math.round(totalFps / fpsUpdateCounter)
1502
+
1503
+ // this.fps = fps.toString().padStart(3, '0')
1504
+ // this.avgFps = avgFps.toString().padStart(3, '0')
1505
+ // this.minFps = minFps.toString().padStart(3, '0')
1506
+ // this.maxFps = maxFps.toString().padStart(3, '0')
1507
+ // })
1507
1508
  },
1508
1509
  },
1509
1510
  })
@@ -103,18 +103,19 @@ export default () =>
103
103
  let totalFps = 0
104
104
  let fpsUpdateCounter = 0
105
105
 
106
- renderer.on('fpsUpdate', (rM, { fps }) => {
107
- minFps = Math.min(fps, minFps)
108
- maxFps = Math.max(fps, maxFps)
109
- totalFps += fps
110
- fpsUpdateCounter++
111
- avgFps = Math.round(totalFps / fpsUpdateCounter)
106
+ // This leaks - might be L3 SubTexture issue
107
+ // renderer.on('fpsUpdate', (rM, { fps }) => {
108
+ // minFps = Math.min(fps, minFps)
109
+ // maxFps = Math.max(fps, maxFps)
110
+ // totalFps += fps
111
+ // fpsUpdateCounter++
112
+ // avgFps = Math.round(totalFps / fpsUpdateCounter)
112
113
 
113
- this.fps = fps.toString().padStart(3, '0')
114
- this.avgFps = avgFps.toString().padStart(3, '0')
115
- this.minFps = minFps.toString().padStart(3, '0')
116
- this.maxFps = maxFps.toString().padStart(3, '0')
117
- })
114
+ // this.fps = fps.toString().padStart(3, '0')
115
+ // this.avgFps = avgFps.toString().padStart(3, '0')
116
+ // this.minFps = minFps.toString().padStart(3, '0')
117
+ // this.maxFps = maxFps.toString().padStart(3, '0')
118
+ // })
118
119
  },
119
120
  },
120
121
  })
@@ -620,6 +620,8 @@ const Element = {
620
620
  f.start()
621
621
  },
622
622
  destroy() {
623
+ if (this.node === null) return
624
+
623
625
  Log.debug('Deleting Node', this.nodeId)
624
626
  this.node.destroy()
625
627
 
@@ -632,6 +634,9 @@ const Element = {
632
634
  if (transition.f !== undefined) transition.f.stop()
633
635
  }
634
636
  }
637
+
638
+ // remove node reference
639
+ this.node = null
635
640
  },
636
641
  get nodeId() {
637
642
  return this.node && this.node.id
@@ -134,7 +134,7 @@ export const navigate = async function () {
134
134
  Announcer.clear()
135
135
  state.navigating = true
136
136
  if (this.parent[symbols.routes]) {
137
- let previousRoute = currentRoute ? Object.assign({}, currentRoute) : undefined
137
+ let previousRoute = currentRoute //? Object.assign({}, currentRoute) : undefined
138
138
  const { hash, path, queryParams } = getHash()
139
139
  let route = matchHash(path, this.parent[symbols.routes])
140
140
 
@@ -148,8 +148,8 @@ export const navigate = async function () {
148
148
  let beforeEachResult
149
149
  if (this.parent[symbols.routerHooks]) {
150
150
  const hooks = this.parent[symbols.routerHooks]
151
- if (hooks.beforeAll) {
152
- beforeEachResult = await hooks.beforeAll(route, previousRoute)
151
+ if (hooks.beforeEach) {
152
+ beforeEachResult = await hooks.beforeEach(route, previousRoute)
153
153
  if (isString(beforeEachResult)) {
154
154
  to(beforeEachResult)
155
155
  return
@@ -290,8 +290,6 @@ export const navigate = async function () {
290
290
  if (oldView) {
291
291
  removeView(previousRoute, oldView, route.transition.out)
292
292
  }
293
-
294
- previousRoute = undefined
295
293
  }
296
294
 
297
295
  state.path = route.path
@@ -358,16 +356,16 @@ const removeView = async (route, view, transition) => {
358
356
 
359
357
  const setOrAnimate = (node, transition, shouldAnimate = true) => {
360
358
  return new Promise((resolve) => {
361
- if (shouldAnimate) {
359
+ if (shouldAnimate === true) {
362
360
  // resolve the promise in the transition end-callback
363
361
  // ("extending" end callback when one is already specified)
364
- const existingEndCallback = transition.end
365
- transition.end = existingEndCallback
366
- ? (...args) => {
367
- existingEndCallback(...args)
368
- resolve()
369
- }
370
- : resolve
362
+ let existingEndCallback = transition.end
363
+ transition.end = (...args) => {
364
+ existingEndCallback && existingEndCallback(args)
365
+ // null the callback to enable memory cleanup
366
+ existingEndCallback = null
367
+ resolve()
368
+ }
371
369
  node.set(transition.prop, { transition })
372
370
  } else {
373
371
  node.set(transition.prop, transition.value)