@lightningjs/blits 2.0.0-beta.1 → 2.0.0-beta.10

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.
Files changed (57) hide show
  1. package/CHANGELOG.md +83 -0
  2. package/README.md +2 -0
  3. package/element-test-full.txt +0 -0
  4. package/index.d.ts +114 -14
  5. package/package.json +28 -8
  6. package/scripts/lint.js +17 -0
  7. package/scripts/prepublishOnly.js +15 -6
  8. package/scripts/prepush.js +25 -7
  9. package/src/announcer/announcer.js +54 -6
  10. package/src/announcer/speechSynthesis.js +156 -37
  11. package/src/application.js +98 -3
  12. package/src/component/base/methods.js +5 -2
  13. package/src/component/base/router.js +6 -0
  14. package/src/component/base/timeouts_intervals.js +56 -0
  15. package/src/component/setup/routes.js +5 -0
  16. package/src/component/setup/state.js +2 -0
  17. package/src/component.js +64 -24
  18. package/src/components/Circle.js +5 -3
  19. package/src/components/FPScounter.js +1591 -1577
  20. package/src/components/RouterView.js +141 -124
  21. package/src/components/Sprite.js +65 -8
  22. package/src/components/index.js +0 -2
  23. package/src/constants.js +17 -0
  24. package/src/engine.js +17 -0
  25. package/src/engines/L3/element.js +101 -7
  26. package/src/engines/L3/fontLoader.js +17 -0
  27. package/src/engines/L3/index.js +17 -0
  28. package/src/engines/L3/launch.js +4 -1
  29. package/src/engines/L3/shaderLoader.js +17 -0
  30. package/src/engines/index.js +17 -0
  31. package/src/{focus.js → focus/focus.js} +6 -18
  32. package/src/focus/helpers.js +31 -0
  33. package/src/focus/hover.js +94 -0
  34. package/src/helpers/fetchJson.js +4 -6
  35. package/src/launch.js +1 -0
  36. package/src/lib/codegenerator/generator.js +55 -9
  37. package/src/lib/lifecycle.js +34 -3
  38. package/src/lib/log.js +4 -4
  39. package/src/lib/precompiler/precompiler.js +23 -14
  40. package/src/lib/reactivity/effect.js +3 -2
  41. package/src/lib/reactivityguard/computedprops.js +113 -28
  42. package/src/lib/shaders/shaders.js +2 -1
  43. package/src/lib/symbols.js +19 -0
  44. package/src/lib/utils.js +17 -0
  45. package/src/plugin.js +17 -0
  46. package/src/plugins/appstate.d.ts +26 -0
  47. package/src/plugins/index.d.ts +28 -0
  48. package/src/plugins/language.d.ts +37 -0
  49. package/src/plugins/storage/localCookie.js +4 -6
  50. package/src/plugins/storage/storage.d.ts +31 -0
  51. package/src/plugins/theme.d.ts +43 -0
  52. package/src/plugins/theme.js +30 -3
  53. package/src/router/router.js +78 -14
  54. package/test-output.txt +0 -0
  55. package/vite/inject-dev-config.js +17 -0
  56. package/vite/preCompiler.js +15 -5
  57. package/vscode/data/template-attributes.json +335 -86
@@ -35,10 +35,12 @@ const findMatchingBrace = (text, startPos) => {
35
35
  // Skip comments when not in strings
36
36
  else if (!inString && char === '/' && text[position + 1] === '/') {
37
37
  // Skip line comment
38
- position = text.indexOf('\n', position) || text.length - 1
38
+ const nextNewline = text.indexOf('\n', position + 2)
39
+ position = nextNewline === -1 ? text.length - 1 : nextNewline
39
40
  } else if (!inString && char === '/' && text[position + 1] === '*') {
40
41
  // Skip block comment
41
- position = text.indexOf('*/', position + 2) || text.length - 1
42
+ const end = text.indexOf('*/', position + 2)
43
+ position = end === -1 ? text.length - 1 : end + 1
42
44
  }
43
45
  // Count braces only outside strings and comments
44
46
  else if (!inString) {
@@ -60,6 +62,44 @@ const findComputedFunctionEnd = (computedObj, openingBracePos) => {
60
62
  return findMatchingBrace(computedObj, openingBracePos)
61
63
  }
62
64
 
65
+ // Skip whitespace + commas + comments (line and block) starting from `pos`, return next code position.
66
+ const skipNonCode = (text, pos) => {
67
+ let p = pos
68
+ while (p < text.length) {
69
+ const c = text[p]
70
+
71
+ // whitespace
72
+ if (c === ' ' || c === '\t' || c === '\n' || c === '\r') {
73
+ p++
74
+ continue
75
+ }
76
+
77
+ // separators between object members
78
+ if (c === ',') {
79
+ p++
80
+ continue
81
+ }
82
+
83
+ // line comment
84
+ if (c === '/' && text[p + 1] === '/') {
85
+ const nextNewline = text.indexOf('\n', p + 2)
86
+ p = nextNewline === -1 ? text.length : nextNewline + 1
87
+ continue
88
+ }
89
+
90
+ // block comment
91
+ if (c === '/' && text[p + 1] === '*') {
92
+ const end = text.indexOf('*/', p + 2)
93
+ p = end === -1 ? text.length : end + 2
94
+ continue
95
+ }
96
+
97
+ return p
98
+ }
99
+
100
+ return p
101
+ }
102
+
63
103
  export default (code) => {
64
104
  // Find component declarations
65
105
  const componentRegex =
@@ -83,6 +123,8 @@ export default (code) => {
83
123
 
84
124
  if (computedEnd !== -1) {
85
125
  const computedObj = configObject.substring(computedStart, computedEnd)
126
+ const computedBlockStart = computedKeywordMatch.index
127
+ const computedBlockEnd = computedEnd
86
128
 
87
129
  // Process each computed property
88
130
  const computedProps = []
@@ -93,15 +135,20 @@ export default (code) => {
93
135
  // using cursor-based parsing gives better accuracy
94
136
  while (currentPos < computedObj.length - 1) {
95
137
  // -1 to exclude the closing brace
96
- // Find the next function name
97
- const functionNameMatch = /\s*(\w+)\s*\(\)/.exec(computedObj.substring(currentPos))
138
+ // Skip whitespace/comments so we don't match commented-out computed functions
139
+ currentPos = skipNonCode(computedObj, currentPos)
140
+ if (currentPos >= computedObj.length - 1) break
141
+
142
+ // Find the next function name (computed properties are `name() { ... }`)
143
+ const functionNameMatch = /^(\w+)\s*\(\)\s*/.exec(computedObj.substring(currentPos))
98
144
  if (!functionNameMatch) break
99
145
 
100
146
  const funcName = functionNameMatch[1]
101
- const funcNamePos = currentPos + functionNameMatch.index
147
+ const funcNamePos = currentPos
102
148
 
103
149
  // Find opening brace for this function
104
- const openingBracePos = computedObj.indexOf('{', funcNamePos)
150
+ const afterSignaturePos = funcNamePos + functionNameMatch[0].length
151
+ const openingBracePos = computedObj.indexOf('{', afterSignaturePos)
105
152
  if (openingBracePos === -1) break
106
153
 
107
154
  // Find closing brace for individual computed function
@@ -133,21 +180,22 @@ export default (code) => {
133
180
  }
134
181
 
135
182
  // Store the original computed object for later replacement
136
- const originalComputedObj = configObject.substring(
137
- computedStart - 'computed: '.length,
138
- computedEnd
139
- )
183
+ const originalComputedObj = configObject.substring(computedBlockStart, computedBlockEnd)
184
+ const computedObjOffsetInOriginal = computedStart - computedBlockStart
185
+ const computedObjEndInOriginal = computedObjOffsetInOriginal + computedObj.length
140
186
 
141
187
  let modifiedComputedObj = originalComputedObj
142
188
  let hasChanges = false
143
189
 
190
+ const replacements = []
191
+
144
192
  for (const prop of computedProps) {
145
193
  const thisRefs = extractThisReferences(prop.body)
146
194
 
147
195
  if (thisRefs.size > 0) {
148
196
  // Skip if already modified
149
197
  if (
150
- prop.body.includes(commentText) ||
198
+ prop.body.includes(commentText.trim()) ||
151
199
  Array.from(thisRefs).some((ref) => prop.body.startsWith(ref))
152
200
  ) {
153
201
  continue
@@ -181,13 +229,31 @@ export default (code) => {
181
229
  prop.fullText.substring(0, openBraceIndex).match(/^(\s*)/)[0] +
182
230
  '}'
183
231
 
184
- // Replace this property in the computed object text
185
- modifiedComputedObj = modifiedComputedObj.replace(prop.fullText, modifiedPropText)
186
- hasChanges = true
232
+ replacements.push({
233
+ start: prop.startPos,
234
+ end: prop.endPos,
235
+ text: modifiedPropText,
236
+ })
187
237
  }
188
238
  }
189
239
  }
190
240
 
241
+ if (replacements.length > 0) {
242
+ let modifiedComputedInner = computedObj
243
+
244
+ replacements.sort((a, b) => b.start - a.start)
245
+ for (const r of replacements) {
246
+ modifiedComputedInner =
247
+ modifiedComputedInner.slice(0, r.start) + r.text + modifiedComputedInner.slice(r.end)
248
+ }
249
+
250
+ modifiedComputedObj =
251
+ originalComputedObj.slice(0, computedObjOffsetInOriginal) +
252
+ modifiedComputedInner +
253
+ originalComputedObj.slice(computedObjEndInOriginal)
254
+ hasChanges = true
255
+ }
256
+
191
257
  if (hasChanges) {
192
258
  // Store the modification of the entire computed object
193
259
  modifications.push({
@@ -213,7 +279,12 @@ export default (code) => {
213
279
  modifiedCode = modifiedCode.replace(mod.original, mod.replacement)
214
280
  }
215
281
 
216
- return { code: modifiedCode }
282
+ // Returning code without a source map
283
+ // Vite will automatically chain this with the next plugin's source map (preCompiler)
284
+ return {
285
+ code: modifiedCode,
286
+ map: { mappings: '' },
287
+ }
217
288
  }
218
289
 
219
290
  return null
@@ -222,24 +293,38 @@ export default (code) => {
222
293
  const extractThisReferences = (funcBody) => {
223
294
  const thisRefs = new Set()
224
295
 
225
- // Find all this.X references, but exclude method calls
226
- const refRegex = /this\.\w+(?!\s*\()/g // matches this.X but not this.X() or this.X.Y()
296
+ // Match full property chains, including optional chaining
297
+ const refRegex = /this(?:\.[$\w]+|\?\.[$\w]+)+/g
227
298
  let refMatch
228
299
 
229
300
  while ((refMatch = refRegex.exec(funcBody)) !== null) {
230
301
  const fullRef = refMatch[0]
231
302
 
232
- // Extract just the variable part (this.X)
233
- // We need to avoid method calls or chained methods
234
- const varRef = fullRef.split('.').slice(0, 2).join('.')
235
-
236
- // Don't add if it's part of a method call
237
- if (
238
- !funcBody
239
- .substring(refMatch.index)
240
- .match(new RegExp(`^${varRef.replace(/\./g, '\\.')}\\s*\\(`))
241
- ) {
242
- thisRefs.add(varRef)
303
+ const segmentRegex = /(\??\.)([$\w]+)/g
304
+ let segMatch
305
+ let lastValidRef = 'this'
306
+
307
+ while ((segMatch = segmentRegex.exec(fullRef.substring(4))) !== null) {
308
+ const candidate = 'this' + fullRef.substring(4, 4 + segMatch.index + segMatch[0].length)
309
+ const afterCandidate = funcBody.substring(refMatch.index + candidate.length)
310
+ if (afterCandidate.match(/^\s*\(/)) {
311
+ break
312
+ }
313
+ lastValidRef = candidate
314
+ }
315
+
316
+ if (lastValidRef !== 'this') {
317
+ thisRefs.add(lastValidRef)
318
+ }
319
+ }
320
+
321
+ // Remove shorter prefixes when a longer chain already covers them
322
+ for (const ref of [...thisRefs]) {
323
+ for (const other of thisRefs) {
324
+ if (other !== ref && other.startsWith(ref + '.')) {
325
+ thisRefs.delete(ref)
326
+ break
327
+ }
243
328
  }
244
329
  }
245
330
 
@@ -139,11 +139,12 @@ export default {
139
139
  return v
140
140
  }
141
141
  if (typeof v === 'string') {
142
- return colors.normalize(v)
142
+ return colors.normalize(v, v)
143
143
  }
144
144
  if (Array.isArray(v) === true) {
145
145
  return v.map((i) => this.parseProp(i))
146
146
  }
147
+ return v
147
148
  },
148
149
  parseProps(v) {
149
150
  if (typeof v === 'string') {
@@ -1,3 +1,20 @@
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
+
1
18
  /**
2
19
  * Symbol registry for internal framework properties and generated code.
3
20
  *
@@ -17,6 +34,7 @@
17
34
  * @property {symbol} inputEvents
18
35
  * @property {symbol} internalEvent
19
36
  * @property {symbol} intervals
37
+ * @property {symbol} debounces
20
38
  * @property {symbol} isProxy
21
39
  * @property {symbol} launched
22
40
  * @property {symbol} level
@@ -73,6 +91,7 @@ export default {
73
91
  inputEvents: Symbol('inputEvents'),
74
92
  internalEvent: Symbol('internalEvent'),
75
93
  intervals: Symbol('intervals'),
94
+ debounces: Symbol('debounces'),
76
95
  isProxy: Symbol('isProxy'),
77
96
  launched: Symbol('launched'),
78
97
  level: Symbol('level'),
package/src/lib/utils.js CHANGED
@@ -1,3 +1,20 @@
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
+
1
18
  /**
2
19
  * A mapping of common screen resolution names to their scale factors relative to 1080p (Full HD).
3
20
  *
package/src/plugin.js CHANGED
@@ -1,3 +1,20 @@
1
+ /*
2
+ * Copyright 2024 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
+
1
18
  import log from './plugins/log.js'
2
19
 
3
20
  export const plugins = {
@@ -0,0 +1,26 @@
1
+ /*
2
+ * Copyright 2024 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
+ export type AppStatePlugin<TState extends Record<string, unknown> = Record<string, unknown>> = TState
19
+
20
+ declare const appState: {
21
+ readonly name: 'appState'
22
+ plugin: <TState extends Record<string, unknown>>(state?: TState) => AppStatePlugin<TState>
23
+ }
24
+
25
+ export default appState
26
+
@@ -0,0 +1,28 @@
1
+ /*
2
+ * Copyright 2024 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
+ export { default as language } from './language.js'
19
+ export { default as theme } from './theme.js'
20
+ export { default as appState } from './appstate.js'
21
+ export { default as storage } from './storage/storage.js'
22
+
23
+ // Re-export plugin interfaces for convenience
24
+ export type { LanguagePlugin, LanguagePluginOptions } from './language.js'
25
+ export type { ThemePlugin, ThemePluginConfig } from './theme.js'
26
+ export type { StoragePlugin } from './storage/storage.js'
27
+ export type { AppStatePlugin } from './appstate.js'
28
+
@@ -0,0 +1,37 @@
1
+ /*
2
+ * Copyright 2024 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
+ export interface LanguagePlugin {
19
+ translate(key: string, ...replacements: any[]): string
20
+ readonly language: string
21
+ set(language: string): void
22
+ translations(translationsObject: Record<string, unknown>): void
23
+ load(file: string): Promise<void>
24
+ }
25
+
26
+ export interface LanguagePluginOptions {
27
+ file?: string
28
+ language?: string
29
+ }
30
+
31
+ declare const language: {
32
+ readonly name: 'language'
33
+ plugin: (options?: LanguagePluginOptions) => LanguagePlugin
34
+ }
35
+
36
+ export default language
37
+
@@ -1,10 +1,6 @@
1
1
  /*
2
- * If not stated otherwise in this file or this component's LICENSE file the
3
- * following copyright and licenses apply:
4
- *
5
- * Copyright 2019 Metrological
6
- *
7
- * Licensed under the Apache License, Version 2.0 (the License);
2
+ * Copyright 2025 Comcast Cable Communications Management, LLC
3
+ * Licensed under the Apache License, Version 2.0 (the "License");
8
4
  * you may not use this file except in compliance with the License.
9
5
  * You may obtain a copy of the License at
10
6
  *
@@ -15,6 +11,8 @@
15
11
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
12
  * See the License for the specific language governing permissions and
17
13
  * limitations under the License.
14
+ *
15
+ * SPDX-License-Identifier: Apache-2.0
18
16
  */
19
17
 
20
18
  // LocalStorage and Cookie storage library
@@ -0,0 +1,31 @@
1
+ /*
2
+ * Copyright 2024 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
+ export interface StoragePlugin {
19
+ get<T = unknown>(key: string): T | null
20
+ set(key: string, value: unknown): boolean
21
+ remove(key: string): void
22
+ clear(): void
23
+ }
24
+
25
+ declare const storage: {
26
+ readonly name: 'storage'
27
+ plugin: () => StoragePlugin
28
+ }
29
+
30
+ export default storage
31
+
@@ -0,0 +1,43 @@
1
+ /*
2
+ * Copyright 2024 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
+ export interface ThemePlugin {
19
+ get<T = unknown>(key: string): T | undefined
20
+ get<T>(key: string, fallback: T): T
21
+ /**
22
+ * @deprecated
23
+ * Deprecated: use `$theme.current()` instead
24
+ */
25
+ set(theme: string): void
26
+ current(theme: string): void
27
+ variant(theme: string): void
28
+ }
29
+
30
+ export interface ThemePluginConfig {
31
+ themes?: Record<string, Record<string, unknown>>
32
+ current?: string
33
+ base?: string
34
+ variant?: string
35
+ }
36
+
37
+ declare const theme: {
38
+ readonly name: 'theme'
39
+ plugin: (config?: ThemePluginConfig | Record<string, unknown>) => ThemePlugin
40
+ }
41
+
42
+ export default theme
43
+
@@ -20,13 +20,17 @@ import { reactive } from '../lib/reactivity/reactive.js'
20
20
 
21
21
  import Settings from '../settings.js'
22
22
 
23
- const getValueByDotNotation = (obj, base, path) => {
23
+ const getValueByDotNotation = (obj, base, variant, path) => {
24
24
  const keys = path.split('.')
25
25
 
26
26
  for (let i = 0; i < keys.length; i++) {
27
27
  const key = keys[i]
28
28
 
29
- if (obj !== null && key in obj === true) {
29
+ if (variant !== null && key in variant === true) {
30
+ obj = variant[key]
31
+ variant = variant !== null && key in variant === true ? variant[key] : null
32
+ base = base !== null && key in base === true ? base[key] : null
33
+ } else if (obj !== null && key in obj === true) {
30
34
  obj = obj[key]
31
35
  base = base !== null && key in base === true ? base[key] : null
32
36
  }
@@ -51,6 +55,7 @@ export default {
51
55
  const state = reactive(
52
56
  {
53
57
  current: 'default',
58
+ variant: null,
54
59
  },
55
60
  Settings.get('reactivityMode'),
56
61
  true
@@ -62,6 +67,7 @@ export default {
62
67
  themes = config.themes
63
68
  state.current = config.current || 'default'
64
69
  base = config.base || 'default'
70
+ state.variant = config.variant || null
65
71
  } else {
66
72
  themes = { default: config }
67
73
  }
@@ -71,8 +77,14 @@ export default {
71
77
  // reference state.current, to trigger reactive effects for
72
78
  // theme get function when current theme is modified
73
79
  state.current
80
+ state.variant
74
81
  if (themeMap[key] !== undefined) return themeMap[key]
75
- const value = getValueByDotNotation(themes[state.current], themes[base] || null, key)
82
+ const value = getValueByDotNotation(
83
+ themes[state.current],
84
+ themes[base] || null,
85
+ themes[state.variant] || null,
86
+ key
87
+ )
76
88
  if (value !== undefined) {
77
89
  // store the value for next time
78
90
  themeMap[key] = value
@@ -82,6 +94,11 @@ export default {
82
94
  },
83
95
 
84
96
  set(theme) {
97
+ Log.warn('$theme.set() is deprecated, use $theme.current() instead')
98
+ this.current(theme)
99
+ },
100
+
101
+ current(theme) {
85
102
  if (theme in themes) {
86
103
  themeMap = {}
87
104
  state.current = theme
@@ -89,6 +106,16 @@ export default {
89
106
  Log.warn(`Theme ${theme} not found`)
90
107
  }
91
108
  },
109
+
110
+ variant(theme) {
111
+ if (theme in themes) {
112
+ themeMap = {}
113
+ state.variant = theme
114
+ console.log('change variant', theme)
115
+ } else {
116
+ Log.warn(`Variant ${theme} not found`)
117
+ }
118
+ },
92
119
  }
93
120
  },
94
121
  }