@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.
- package/.github/workflows/comment-lint-results.yml +105 -131
- package/.github/workflows/comment-test-results.yml +96 -101
- package/.github/workflows/run-blits-tests.yml +19 -9
- package/.github/workflows/run-lint.yml +11 -1
- package/CHANGELOG.md +15 -0
- package/docs/built-in/for-loop.md +34 -0
- package/index.d.ts +2 -2
- package/package.json +1 -1
- package/src/announcer/speechSynthesis.js +14 -5
- package/src/component/base/methods.js +26 -0
- package/src/component/setup/props.js +3 -1
- package/src/component/setup/props.test.js +1 -1
- package/src/components/FPScounter.js +13 -12
- package/src/components/FPScounter.orig.js +12 -11
- package/src/engines/L3/element.js +5 -0
- package/src/router/router.js +11 -13
|
@@ -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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
#
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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.
|
|
97
|
-
|
|
98
|
-
|
|
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('./
|
|
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:
|
|
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:
|
|
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
|
@@ -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 (
|
|
86
|
-
|
|
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
|
|
92
|
-
|
|
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(
|
|
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(),
|
|
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
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
package/src/router/router.js
CHANGED
|
@@ -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
|
|
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.
|
|
152
|
-
beforeEachResult = await hooks.
|
|
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
|
-
|
|
365
|
-
transition.end =
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
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)
|