@lightningjs/blits 1.36.0 → 1.38.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/CHANGELOG.md +14 -0
- package/eslint.config.cjs +85 -0
- package/index.d.ts +44 -9
- package/package.json +11 -7
- package/src/application.js +6 -3
- package/src/component/setup/props.js +2 -2
- package/src/engines/L3/element.js +2 -2
- package/src/lib/codegenerator/generator.js +6 -6
- package/src/plugins/language.js +11 -8
- package/src/router/router.js +36 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v1.38.0
|
|
4
|
+
|
|
5
|
+
_26 Aug 2025_
|
|
6
|
+
|
|
7
|
+
- Added `reuseComponent` to route options to enable navigating to the same page and reusing the same page component instance
|
|
8
|
+
|
|
9
|
+
## v1.37.0
|
|
10
|
+
|
|
11
|
+
_22 Aug 2025_
|
|
12
|
+
|
|
13
|
+
- Added Route options check before checking passFocus flag
|
|
14
|
+
- Added support for additional metadata in Route config (for associating arbitrary data to a route)
|
|
15
|
+
- Upgraded dev dependencies to latest versions
|
|
16
|
+
|
|
3
17
|
## v1.36.0
|
|
4
18
|
|
|
5
19
|
_11 Aug 2025_
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2023 Comcast Cable Communications Management, LLC
|
|
3
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License.
|
|
5
|
+
* You may obtain a copy of the License at
|
|
6
|
+
*
|
|
7
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
*
|
|
9
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
* See the License for the specific language governing permissions and
|
|
13
|
+
* limitations under the License.
|
|
14
|
+
*
|
|
15
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const {
|
|
19
|
+
defineConfig,
|
|
20
|
+
globalIgnores,
|
|
21
|
+
} = require("eslint/config");
|
|
22
|
+
|
|
23
|
+
const globals = require("globals");
|
|
24
|
+
const prettier = require("eslint-plugin-prettier");
|
|
25
|
+
const babelParser = require("@babel/eslint-parser");
|
|
26
|
+
const js = require("@eslint/js");
|
|
27
|
+
|
|
28
|
+
const {
|
|
29
|
+
FlatCompat,
|
|
30
|
+
} = require("@eslint/eslintrc");
|
|
31
|
+
|
|
32
|
+
const compat = new FlatCompat({
|
|
33
|
+
baseDirectory: __dirname,
|
|
34
|
+
recommendedConfig: js.configs.recommended,
|
|
35
|
+
allConfig: js.configs.all
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
module.exports = defineConfig([{
|
|
39
|
+
languageOptions: {
|
|
40
|
+
globals: {
|
|
41
|
+
...globals.browser,
|
|
42
|
+
...globals.node,
|
|
43
|
+
globalThis: false,
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
parser: babelParser,
|
|
47
|
+
ecmaVersion: 2018,
|
|
48
|
+
sourceType: "module",
|
|
49
|
+
|
|
50
|
+
parserOptions: {
|
|
51
|
+
requireConfigFile: false,
|
|
52
|
+
|
|
53
|
+
babelOptions: {
|
|
54
|
+
plugins: ["@babel/plugin-syntax-import-assertions"],
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
plugins: {
|
|
60
|
+
prettier,
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
extends: compat.extends("eslint:recommended", "plugin:prettier/recommended", "prettier"),
|
|
64
|
+
|
|
65
|
+
rules: {
|
|
66
|
+
"no-console": process.env.NODE_ENV === "production" ? "error" : "off",
|
|
67
|
+
"no-debugger": process.env.NODE_ENV === "production" ? "error" : "off",
|
|
68
|
+
quotes: [2, "single", "avoid-escape"],
|
|
69
|
+
semi: [2, "never"],
|
|
70
|
+
"no-extra-boolean-cast": "off",
|
|
71
|
+
|
|
72
|
+
"no-unused-vars": [1, {
|
|
73
|
+
argsIgnorePattern: "res|next|^err",
|
|
74
|
+
}],
|
|
75
|
+
|
|
76
|
+
"prettier/prettier": ["error", {
|
|
77
|
+
trailingComma: "es5",
|
|
78
|
+
singleQuote: true,
|
|
79
|
+
tabWidth: 2,
|
|
80
|
+
semi: false,
|
|
81
|
+
printWidth: 100,
|
|
82
|
+
endOfLine: "auto",
|
|
83
|
+
}],
|
|
84
|
+
},
|
|
85
|
+
}, globalIgnores(["**/node_modules"])]);
|
package/index.d.ts
CHANGED
|
@@ -185,15 +185,31 @@ declare module '@lightningjs/blits' {
|
|
|
185
185
|
[key: string]: any
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
-
|
|
189
|
-
|
|
188
|
+
/**
|
|
189
|
+
* Router Options that can be used at the same time.
|
|
190
|
+
*/
|
|
191
|
+
interface ConcurrentRouteOpts {
|
|
190
192
|
/**
|
|
191
193
|
* Whether the page navigation should be added to the history stack
|
|
192
194
|
* used when navigating back using `this.$router.back()`
|
|
193
195
|
*
|
|
194
196
|
* @default true
|
|
195
197
|
*/
|
|
196
|
-
inHistory?:
|
|
198
|
+
inHistory?: boolean,
|
|
199
|
+
passFocus?: boolean,
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Route Options that can't be true at the same time
|
|
204
|
+
*/
|
|
205
|
+
type MutualExclusiveRouteOpts = {
|
|
206
|
+
/**
|
|
207
|
+
* Whether the router should reuse the current page component instance (when matching with the Component
|
|
208
|
+
* specified for the route that we're routing to).
|
|
209
|
+
*
|
|
210
|
+
* @default true
|
|
211
|
+
*/
|
|
212
|
+
reuseComponent?: true,
|
|
197
213
|
/**
|
|
198
214
|
* Whether the page should be kept alive when navigating away. Can be useful
|
|
199
215
|
* for a homepage where the state should be fully retained when navigating back
|
|
@@ -201,17 +217,25 @@ declare module '@lightningjs/blits' {
|
|
|
201
217
|
*
|
|
202
218
|
* @default false
|
|
203
219
|
*/
|
|
204
|
-
keepAlive?:
|
|
220
|
+
keepAlive?: false
|
|
221
|
+
} | {
|
|
205
222
|
/**
|
|
206
|
-
* Whether the
|
|
207
|
-
*
|
|
208
|
-
* RouterView, where the widget should maintain the focus (instead of the new page, which
|
|
209
|
-
* is the default behaviour)
|
|
223
|
+
* Whether the router should reuse the current page component instance (when matching with the Component
|
|
224
|
+
* specified for the route that we're routing to).
|
|
210
225
|
*
|
|
211
226
|
* @default true
|
|
212
227
|
*/
|
|
213
|
-
|
|
228
|
+
reuseComponent?: false,
|
|
229
|
+
/**
|
|
230
|
+
* Whether the page should be kept alive when navigating away. Can be useful
|
|
231
|
+
* for a homepage where the state should be fully retained when navigating back
|
|
232
|
+
* from a details page
|
|
233
|
+
*
|
|
234
|
+
* @default false
|
|
235
|
+
*/
|
|
236
|
+
keepAlive?: true
|
|
214
237
|
}
|
|
238
|
+
export type RouteOptions = ConcurrentRouteOpts & MutualExclusiveRouteOpts;
|
|
215
239
|
|
|
216
240
|
export interface Router {
|
|
217
241
|
/**
|
|
@@ -679,6 +703,17 @@ declare module '@lightningjs/blits' {
|
|
|
679
703
|
data?: {
|
|
680
704
|
[key: string]: any
|
|
681
705
|
}
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* Metadata attached to the route, can be used to add any arbitrary
|
|
709
|
+
* data to a route such as `auth: true/false`, a route ID or route description
|
|
710
|
+
* Note that this data is not reactive and not passed as props to components.
|
|
711
|
+
* The metadata is available in the router `before` or `beforeEach` hooks
|
|
712
|
+
*
|
|
713
|
+
*/
|
|
714
|
+
meta?: {
|
|
715
|
+
[key: string]: any
|
|
716
|
+
}
|
|
682
717
|
}
|
|
683
718
|
|
|
684
719
|
type ComponentFactory = () => void
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightningjs/blits",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.38.0",
|
|
4
4
|
"description": "Blits: The Lightning 3 App Development Framework",
|
|
5
5
|
"bin": "bin/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
"prepublishOnly": "node scripts/prepublishOnly.js",
|
|
22
22
|
"postpublish": "node scripts/postpublish.js",
|
|
23
23
|
"prepare": "husky",
|
|
24
|
-
"pack-preview": "npm pack --dry-run"
|
|
24
|
+
"pack-preview": "npm pack --dry-run",
|
|
25
|
+
"release": "npm publish --access public"
|
|
25
26
|
},
|
|
26
27
|
"types": "./index.d.ts",
|
|
27
28
|
"lint-staged": {
|
|
@@ -35,16 +36,19 @@
|
|
|
35
36
|
"devDependencies": {
|
|
36
37
|
"@babel/eslint-parser": "^7.26.5",
|
|
37
38
|
"@babel/plugin-syntax-import-assertions": "^7.26.0",
|
|
38
|
-
"
|
|
39
|
-
"eslint": "^
|
|
40
|
-
"
|
|
41
|
-
"eslint
|
|
39
|
+
"@eslint/eslintrc": "^3.3.1",
|
|
40
|
+
"@eslint/js": "^9.33.0",
|
|
41
|
+
"c8": "^9.1.0",
|
|
42
|
+
"eslint": "^9.33.0",
|
|
43
|
+
"eslint-config-prettier": "^10.1.8",
|
|
44
|
+
"eslint-plugin-prettier": "^5.5.4",
|
|
42
45
|
"fast-glob": "^3.3.3",
|
|
43
46
|
"global-jsdom": "24.0.0",
|
|
47
|
+
"globals": "^16.3.0",
|
|
44
48
|
"husky": "^9.1.7",
|
|
45
49
|
"jsdom": "24.0.0",
|
|
46
50
|
"lint-staged": "^15.5.0",
|
|
47
|
-
"prettier": "^
|
|
51
|
+
"prettier": "^3.6.2",
|
|
48
52
|
"sinon": "^21.0.0",
|
|
49
53
|
"tap-diff": "^0.1.1",
|
|
50
54
|
"tape": "^5.5.0"
|
package/src/application.js
CHANGED
|
@@ -96,9 +96,12 @@ const Application = (config) => {
|
|
|
96
96
|
|
|
97
97
|
Focus.input(key, e)
|
|
98
98
|
clearTimeout(holdTimeout)
|
|
99
|
-
holdTimeout = setTimeout(
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
holdTimeout = setTimeout(
|
|
100
|
+
() => {
|
|
101
|
+
Focus.hold = true
|
|
102
|
+
},
|
|
103
|
+
Settings.get('holdTimeout', DEFAULT_HOLD_TIMEOUT_MS)
|
|
104
|
+
)
|
|
102
105
|
}
|
|
103
106
|
|
|
104
107
|
keyUpHandler = (e) => {
|
|
@@ -41,8 +41,8 @@ export default (component, props = []) => {
|
|
|
41
41
|
this[symbols.props] !== undefined && prop.key in this[symbols.props]
|
|
42
42
|
? this[symbols.props][prop.key]
|
|
43
43
|
: 'default' in prop
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
? prop.default
|
|
45
|
+
: undefined
|
|
46
46
|
)
|
|
47
47
|
|
|
48
48
|
if (prop.required === true && value === undefined) {
|
|
@@ -191,9 +191,9 @@ const generateElementCode = function (
|
|
|
191
191
|
} else {
|
|
192
192
|
this.effectsCode.push(`
|
|
193
193
|
${elm}.set('${key.substring(1)}', ${interpolate(
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
194
|
+
templateObject[key],
|
|
195
|
+
options.component
|
|
196
|
+
)})
|
|
197
197
|
`)
|
|
198
198
|
}
|
|
199
199
|
// value.includes('.') === false &&
|
|
@@ -310,9 +310,9 @@ const generateComponentCode = function (
|
|
|
310
310
|
if (isReactiveKey(key)) {
|
|
311
311
|
this.effectsCode.push(`
|
|
312
312
|
${elm}[Symbol.for('props')]['${key.substring(1)}'] = ${interpolate(
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
313
|
+
templateObject[key],
|
|
314
|
+
options.component
|
|
315
|
+
)}`)
|
|
316
316
|
renderCode.push(`
|
|
317
317
|
propData = ${interpolate(templateObject[key], options.component)}
|
|
318
318
|
if (Array.isArray(propData) === true) {
|
package/src/plugins/language.js
CHANGED
|
@@ -41,8 +41,8 @@ export default {
|
|
|
41
41
|
Object.keys(translations).length === 0
|
|
42
42
|
? 'No translations loaded. Please load a file with translations or specify a translations object manually'
|
|
43
43
|
: language in translations === false
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
? `Language ${language} not available in the loaded translations`
|
|
45
|
+
: false
|
|
46
46
|
if (warningMsg) {
|
|
47
47
|
Log.warn(warningMsg)
|
|
48
48
|
// set the current language in the reactive state
|
|
@@ -127,12 +127,15 @@ export default {
|
|
|
127
127
|
return Object.keys(
|
|
128
128
|
// maps array input to an object {0: 'item1', 1: 'item2'}
|
|
129
129
|
Array.isArray(replacements) ? Object.assign({}, replacements) : replacements
|
|
130
|
-
).reduce(
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
130
|
+
).reduce(
|
|
131
|
+
(text, replacementKey) => {
|
|
132
|
+
return text.replace(
|
|
133
|
+
new RegExp('{\\s?' + replacementKey + '\\s?}', 'g'),
|
|
134
|
+
replacements[replacementKey]
|
|
135
|
+
)
|
|
136
|
+
},
|
|
137
|
+
(dictionary && dictionary[key]) || key
|
|
138
|
+
)
|
|
136
139
|
}
|
|
137
140
|
},
|
|
138
141
|
get language() {
|
package/src/router/router.js
CHANGED
|
@@ -176,7 +176,7 @@ export const matchHash = (path, routes = []) => {
|
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
if (matchingRoute) {
|
|
179
|
-
matchingRoute.options = { ...matchingRoute.options, ...overrideOptions }
|
|
179
|
+
matchingRoute.options = { ...defaultOptions, ...matchingRoute.options, ...overrideOptions }
|
|
180
180
|
if (!matchingRoute.data) {
|
|
181
181
|
matchingRoute.data = {}
|
|
182
182
|
}
|
|
@@ -186,6 +186,17 @@ export const matchHash = (path, routes = []) => {
|
|
|
186
186
|
return matchingRoute
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
+
/**
|
|
190
|
+
* Default Route options
|
|
191
|
+
*
|
|
192
|
+
*/
|
|
193
|
+
const defaultOptions = {
|
|
194
|
+
inHistory: true,
|
|
195
|
+
keepAlive: false,
|
|
196
|
+
passFocus: true,
|
|
197
|
+
reuseComponent: false,
|
|
198
|
+
}
|
|
199
|
+
|
|
189
200
|
/**
|
|
190
201
|
* Navigate to a route
|
|
191
202
|
*
|
|
@@ -202,12 +213,14 @@ export const navigate = async function () {
|
|
|
202
213
|
Announcer.stop()
|
|
203
214
|
Announcer.clear()
|
|
204
215
|
state.navigating = true
|
|
216
|
+
let reuse = false
|
|
205
217
|
if (this.parent[symbols.routes]) {
|
|
206
218
|
let previousRoute = currentRoute //? Object.assign({}, currentRoute) : undefined
|
|
207
219
|
const { hash, path, queryParams } = getHash()
|
|
208
220
|
let route = matchHash(path, this.parent[symbols.routes])
|
|
209
221
|
|
|
210
222
|
currentRoute = route
|
|
223
|
+
|
|
211
224
|
if (route) {
|
|
212
225
|
const queryParamsData = {}
|
|
213
226
|
const queryParamsEntries = [...queryParams.entries()]
|
|
@@ -271,6 +284,26 @@ export const navigate = async function () {
|
|
|
271
284
|
|
|
272
285
|
let { view, focus } = cacheMap.get(route.hash) || {}
|
|
273
286
|
|
|
287
|
+
// merge props with potential route params, navigation data and route data to be injected into the component instance
|
|
288
|
+
const props = {
|
|
289
|
+
...this[symbols.props],
|
|
290
|
+
...route.params,
|
|
291
|
+
...route.data,
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (
|
|
295
|
+
previousRoute &&
|
|
296
|
+
route.component === previousRoute.component &&
|
|
297
|
+
route.options.reuseComponent === true &&
|
|
298
|
+
route.options.keepAlive !== true
|
|
299
|
+
) {
|
|
300
|
+
reuse = true
|
|
301
|
+
view = this[symbols.children][this[symbols.children].length - 1]
|
|
302
|
+
for (const prop in props) {
|
|
303
|
+
view[symbols.props][prop] = props[prop]
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
274
307
|
// Announce route change if a message has been specified for this route
|
|
275
308
|
if (route.announce) {
|
|
276
309
|
if (typeof route.announce === 'string') {
|
|
@@ -288,13 +321,6 @@ export const navigate = async function () {
|
|
|
288
321
|
holder.set('w', '100%')
|
|
289
322
|
holder.set('h', '100%')
|
|
290
323
|
|
|
291
|
-
// merge props with potential route params, navigation data and route data to be injected into the component instance
|
|
292
|
-
const props = {
|
|
293
|
-
...this[symbols.props],
|
|
294
|
-
...route.params,
|
|
295
|
-
...route.data,
|
|
296
|
-
}
|
|
297
|
-
|
|
298
324
|
view = await route.component({ props }, holder, this)
|
|
299
325
|
|
|
300
326
|
if (view[Symbol.toStringTag] === 'Module') {
|
|
@@ -341,7 +367,7 @@ export const navigate = async function () {
|
|
|
341
367
|
this.activeView = children[children.length - 1]
|
|
342
368
|
|
|
343
369
|
// set focus to the view that we're routing to (unless explicitly disabling passing focus)
|
|
344
|
-
if (route.options.passFocus !== false) {
|
|
370
|
+
if (route.options === undefined || route.options.passFocus !== false) {
|
|
345
371
|
focus ? focus.$focus() : /** @type {BlitsComponent} */ (view).$focus()
|
|
346
372
|
}
|
|
347
373
|
|
|
@@ -359,7 +385,7 @@ export const navigate = async function () {
|
|
|
359
385
|
let shouldAnimate = false
|
|
360
386
|
|
|
361
387
|
// apply out out transition on previous view
|
|
362
|
-
if (previousRoute) {
|
|
388
|
+
if (previousRoute && reuse === false) {
|
|
363
389
|
// only animate when there is a previous route
|
|
364
390
|
shouldAnimate = true
|
|
365
391
|
const oldView = this[symbols.children].splice(1, 1).pop()
|