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

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.
@@ -80,6 +80,7 @@ export default () =>
80
80
  let slotChildCounter = 0
81
81
  let cmps = []
82
82
  elementConfigs[0] = {}
83
+ elementConfigs[0]['___wrapper'] = true
83
84
 
84
85
  elms[0] = this.element(
85
86
  { parent: parent || 'root' },
@@ -167,15 +168,20 @@ export default () =>
167
168
  // Resolve frame data from sprite map
168
169
  let options = null
169
170
  if (
170
- this.map !== undefined &&
171
171
  this.map !== null &&
172
- this.frame !== undefined &&
173
- this.frame !== null
172
+ this.map !== undefined &&
173
+ this.frame !== null &&
174
+ this.frame !== undefined
174
175
  ) {
175
- options =
176
- 'frames' in this.map
177
- ? Object.assign({}, this.map.defaults || {}, this.map.frames[this.frame])
178
- : this.map[this.frame]
176
+ if (
177
+ this.map.frames !== null &&
178
+ this.map.frames !== undefined &&
179
+ this.frame in this.map.frames
180
+ ) {
181
+ options = Object.assign({}, this.map.defaults || {}, this.map.frames[this.frame])
182
+ } else if (this.frame in this.map) {
183
+ options = this.map[this.frame]
184
+ }
179
185
  }
180
186
 
181
187
  // If no map but frame is object (manual subtexture)
@@ -210,15 +210,29 @@ const propsTransformer = {
210
210
  this.props['rotation'] = v * (Math.PI / 180)
211
211
  },
212
212
  set w(v) {
213
- if (v < 0) v = 0
214
- this.props['w'] = parsePercentage.call(this, v, 'w')
213
+ const parsed = parsePercentage.call(this, v, 'w')
214
+ this.props['w'] = parsed
215
+ if (
216
+ this.___wrapper === true &&
217
+ this.element.component.eol !== true &&
218
+ this.element.component[symbols.holder] !== undefined
219
+ ) {
220
+ this.element.component[symbols.holder].set('w', parsed)
221
+ }
215
222
  },
216
223
  set width(v) {
217
224
  this.w = v
218
225
  },
219
226
  set h(v) {
220
- if (v < 0) v = 0
221
- this.props['h'] = parsePercentage.call(this, v, 'h')
227
+ const parsed = parsePercentage.call(this, v, 'h')
228
+ this.props['h'] = parsed
229
+ if (
230
+ this.___wrapper === true &&
231
+ this.element.component.eol !== true &&
232
+ this.element.component[symbols.holder] !== undefined
233
+ ) {
234
+ this.element.component[symbols.holder].set('h', parsed)
235
+ }
222
236
  },
223
237
  set height(v) {
224
238
  this.h = v
@@ -587,6 +601,39 @@ const Element = {
587
601
  })
588
602
  }
589
603
  },
604
+ /**
605
+ * Sets framework-provided inspector metadata
606
+ * Only sets if inspector is enabled and in dev mode
607
+ * @param {Object} data - Framework inspector metadata to merge
608
+ */
609
+ setInspectorMetadata(data) {
610
+ // Early return if inspector not enabled (performance optimization)
611
+ if (inspectorEnabled !== true) {
612
+ return
613
+ }
614
+
615
+ // Early return if element is destroyed (props.props is null)
616
+ if (this.props.props === undefined || this.props.props === null) {
617
+ return
618
+ }
619
+
620
+ // Initialize data object if it doesn't exist
621
+ if (this.props['data'] === undefined) {
622
+ this.props['data'] = {}
623
+ }
624
+ if (this.props.props['data'] === undefined) {
625
+ this.props.props['data'] = {}
626
+ }
627
+
628
+ // Merge framework data (blits-* keys data attributes)
629
+ Object.assign(this.props['data'], data)
630
+ Object.assign(this.props.props['data'], data)
631
+
632
+ // Sync to renderer node so inspector can see it
633
+ if (this.node !== undefined && this.node !== null) {
634
+ this.node.data = { ...this.props.props['data'] }
635
+ }
636
+ },
590
637
  /**
591
638
  * Set an individual property on the node
592
639
  *
@@ -632,13 +679,31 @@ const Element = {
632
679
  }
633
680
  },
634
681
  animate(prop, value, transition) {
682
+ // Clear any existing debounce timeout for this property
683
+ if (this.debounceTimeouts[prop] !== undefined) {
684
+ clearTimeout(this.debounceTimeouts[prop])
685
+ Log.debug(`Cleared debounce timeout for property "${prop}"`)
686
+ }
687
+
688
+ // Debounce the animation execution
689
+ this.debounceTimeouts[prop] = setTimeout(() => {
690
+ delete this.debounceTimeouts[prop]
691
+ this._executeAnimation(prop, value, transition)
692
+ }, 0)
693
+ },
694
+ _executeAnimation(prop, value, transition) {
635
695
  // check if a transition is already scheduled to run on the same prop
636
696
  // and cancels it if it does
697
+ const stateOfAnimation =
698
+ this.scheduledTransitions[prop] !== undefined
699
+ ? this.scheduledTransitions[prop].f.state
700
+ : undefined
701
+
637
702
  if (
638
- this.scheduledTransitions[prop] !== undefined &&
639
- this.scheduledTransitions[prop].f.state === 'scheduled'
703
+ stateOfAnimation !== undefined &&
704
+ (stateOfAnimation === 'scheduled' || stateOfAnimation === 'running')
640
705
  ) {
641
- this.scheduledTransitions[prop].f.stop()
706
+ this.scheduledTransitions[prop].f.stop(stateOfAnimation === 'running' ? false : true)
642
707
  }
643
708
 
644
709
  // if current value is the same as the value to animate to, instantly resolve
@@ -674,6 +739,11 @@ const Element = {
674
739
  f,
675
740
  }
676
741
 
742
+ // Update inspector metadata when transition starts
743
+ if (inspectorEnabled === true) {
744
+ this.setInspectorMetadata({ 'blits-isTransitioning': true })
745
+ }
746
+
677
747
  if (transition.start !== undefined && typeof transition.start === 'function') {
678
748
  // fire transition start callback when animation really starts (depending on specified delay)
679
749
  f.once('animating', () => {
@@ -708,6 +778,12 @@ const Element = {
708
778
  }
709
779
  // remove the prop from scheduled transitions
710
780
  delete this.scheduledTransitions[prop]
781
+ // Update inspector metadata when transition ends
782
+ if (inspectorEnabled === true) {
783
+ this.setInspectorMetadata({
784
+ 'blits-isTransitioning': Object.keys(this.scheduledTransitions).length > 0,
785
+ })
786
+ }
711
787
  })
712
788
 
713
789
  // start animation
@@ -717,6 +793,14 @@ const Element = {
717
793
  if (this.node === null) return
718
794
 
719
795
  Log.debug('Deleting Node', this.nodeId)
796
+
797
+ // Clear all pending debounce timeouts
798
+ const debounceProps = Object.keys(this.debounceTimeouts)
799
+ for (let i = 0; i < debounceProps.length; i++) {
800
+ clearTimeout(this.debounceTimeouts[debounceProps[i]])
801
+ }
802
+ this.debounceTimeouts = null
803
+
720
804
  // Clearing transition end callback functions
721
805
  const transitionProps = Object.keys(this.scheduledTransitions)
722
806
  for (let i = 0; i < transitionProps.length; i++) {
@@ -799,6 +883,7 @@ export default (config, component) => {
799
883
  return Object.assign(Object.create(Element), {
800
884
  props: Object.assign(Object.create(propsTransformer), { props: {} }),
801
885
  scheduledTransitions: {},
886
+ debounceTimeouts: {},
802
887
  config,
803
888
  component,
804
889
  })
@@ -163,6 +163,10 @@ const generateElementCode = function (
163
163
 
164
164
  renderCode.push(`elementConfigs[${counter}] = {}`)
165
165
 
166
+ if (counter === 0) {
167
+ renderCode.push(`elementConfigs[${counter}]['___wrapper'] = true `)
168
+ }
169
+
166
170
  if (options.forloop) {
167
171
  renderCode.push(`if(${elm} === undefined) {`)
168
172
  }
@@ -325,9 +329,11 @@ const generateComponentCode = function (
325
329
 
326
330
  const children = templateObject.children
327
331
  delete templateObject.children
332
+ // Capture holder counter before generating element code (which may process children and increment counter)
333
+ const holderCounter = counter
328
334
  generateElementCode.call(this, templateObject, parent, { ...options, ...{ holder: true } })
329
335
 
330
- parent = options.key ? `elms[${counter}][${options.key}]` : `elms[${counter}]`
336
+ parent = options.key ? `elms[${holderCounter}][${options.key}]` : `elms[${holderCounter}]`
331
337
 
332
338
  counter++
333
339
 
@@ -396,6 +402,21 @@ const generateComponentCode = function (
396
402
  }
397
403
  `)
398
404
 
405
+ if (isDev === true) {
406
+ const templateTagName = templateObject[Symbol.for('componentType')]
407
+ const holderElm = options.key
408
+ ? `elms[${holderCounter}][${options.key}]`
409
+ : `elms[${holderCounter}]`
410
+ const componentDisplayName = `typeof componentType === 'string'
411
+ ? componentType
412
+ : (componentType?.[Symbol.for('componentType')] || '${templateTagName}')`
413
+ renderCode.push(`
414
+ if (${holderElm} !== undefined && typeof ${holderElm}.setInspectorMetadata === 'function') {
415
+ ${holderElm}.setInspectorMetadata({ 'blits-componentType': ${componentDisplayName} })
416
+ }
417
+ `)
418
+ }
419
+
399
420
  this.cleanupCode.push(`components[${counter}] = null`)
400
421
 
401
422
  if (options.forloop) {
@@ -451,7 +472,7 @@ const generateForLoopCode = function (templateObject, parent) {
451
472
  .replace(')', '')
452
473
  .split(/\s*,\s*/)
453
474
 
454
- const scopeRegex = new RegExp(`(scope\\.(?!${item}\\.|${index}|key)(\\$?\\w+))`, 'gi')
475
+ const scopeRegex = new RegExp(`(scope\\.(?!${item}\\.|${index}|key)([\\w$]+))`, 'gi')
455
476
 
456
477
  // local context
457
478
  const ctx = {
@@ -524,10 +545,16 @@ const generateForLoopCode = function (templateObject, parent) {
524
545
 
525
546
  component !== null && component[Symbol.for('removeGlobalEffects')](effects[${forStartCounter}])
526
547
 
527
- for(let i = 0; i < effects[${forStartCounter}].length; i++) {
528
- const value = effects[${forStartCounter}][i]
529
- const index = component[Symbol.for('effects')].indexOf(value)
530
- if (index > -1) component[Symbol.for('effects')].splice(index, 1)
548
+ const effectsToRemove = new Set(effects[${forStartCounter}].slice(0))
549
+ if (effectsToRemove.size > 0) {
550
+ const componentEffects = component?.[Symbol.for('effects')] || []
551
+ let writeIndex = 0
552
+ for (let readIndex = 0; readIndex < componentEffects.length; readIndex++) {
553
+ if (!effectsToRemove.has(componentEffects[readIndex])) {
554
+ componentEffects[writeIndex++] = componentEffects[readIndex]
555
+ }
556
+ }
557
+ componentEffects.length = writeIndex
531
558
  }
532
559
 
533
560
  effects[${forStartCounter}].length = 0
@@ -825,8 +852,13 @@ const cast = (val = '', key = false, component = 'component.') => {
825
852
  }
826
853
  // @-listener
827
854
  else if (key.startsWith('@') && val) {
828
- const c = component.slice(0, -1)
829
- castedValue = `${c}['${val.replace('$', '')}'] && ${c}['${val.replace('$', '')}'].bind(${c})`
855
+ const trimmed = val.trim()
856
+ if (/^\$?\w+$/.test(trimmed)) {
857
+ const c = component.slice(0, -1)
858
+ castedValue = `${c}['${trimmed.replace('$', '')}'] && ${c}['${trimmed.replace('$', '')}'].bind(${c})`
859
+ } else {
860
+ castedValue = interpolate(trimmed, component)
861
+ }
830
862
  }
831
863
  // dynamic value
832
864
  else if (val.startsWith('$')) {
@@ -18,6 +18,9 @@
18
18
  import { Log } from './log.js'
19
19
  import { emit, privateEmit } from './hooks.js'
20
20
  import symbols from './symbols.js'
21
+ import Settings from '../settings.js'
22
+
23
+ let inspectorEnabled = null
21
24
 
22
25
  /**
23
26
  * List of valid lifecycle states for a component.
@@ -81,9 +84,32 @@ export default {
81
84
  privateEmit(v, this.component[symbols.identifier], this.component)
82
85
  // emit 'public' hook
83
86
  emit(v, this.component[symbols.identifier], this.component)
84
- // update the built-in hasFocus state variable
85
- if (v === 'focus') this.component[symbols.state].$hasFocus = true
86
- if (v === 'unfocus') this.component[symbols.state].$hasFocus = false
87
+ // update the built-in $hasFocus state variable
88
+ if (v === 'focus' || v === 'unfocus') {
89
+ if (inspectorEnabled === null) {
90
+ inspectorEnabled = Settings.get('inspector', false)
91
+ }
92
+ }
93
+ if (v === 'focus') {
94
+ this.component[symbols.state].$hasFocus = true
95
+ if (
96
+ inspectorEnabled === true &&
97
+ this.component[symbols.holder] &&
98
+ typeof this.component[symbols.holder].setInspectorMetadata === 'function'
99
+ ) {
100
+ this.component[symbols.holder].setInspectorMetadata({ 'blits-hasFocus': true })
101
+ }
102
+ }
103
+ if (v === 'unfocus') {
104
+ this.component[symbols.state].$hasFocus = false
105
+ if (
106
+ inspectorEnabled === true &&
107
+ this.component[symbols.holder] &&
108
+ typeof this.component[symbols.holder].setInspectorMetadata === 'function'
109
+ ) {
110
+ this.component[symbols.holder].setInspectorMetadata({ 'blits-hasFocus': false })
111
+ }
112
+ }
87
113
  }
88
114
  },
89
115
  }
@@ -17,6 +17,7 @@
17
17
 
18
18
  import parser from '../templateparser/parser.js'
19
19
  import generator from '../codegenerator/generator.js'
20
+ import MagicString from 'magic-string'
20
21
 
21
22
  export default (source, filePath, mode) => {
22
23
  if (
@@ -26,12 +27,9 @@ export default (source, filePath, mode) => {
26
27
  /\{.*?template\s*:\s*(['"`])((?:\\?.)*?)\1.*?\}/s.test(source) // object with template key
27
28
  ) {
28
29
  const templates = source.matchAll(/(?<!\/\/\s*)template\s*:\s*(['"`])((?:\\?.)*?)\1/gs)
29
- let newSource = source
30
- /*
31
- if there are multiple templates in the file, we need to keep
32
- track of the offset caused by the previous replacements
33
- */
34
- let offset = 0
30
+
31
+ // Use MagicString to track changes
32
+ const s = new MagicString(source)
35
33
 
36
34
  for (const template of templates) {
37
35
  if (template[2]) {
@@ -39,7 +37,8 @@ export default (source, filePath, mode) => {
39
37
 
40
38
  // Only process if it looks like a Blits template
41
39
  if (templateContent.match(/^\s*(<!--[\s\S]*?-->|<[A-Za-z][^>]*>)/s)) {
42
- const templateStartIndex = template.index + offset
40
+ // Use original indices - MagicString handles position tracking automatically
41
+ const templateStartIndex = template.index
43
42
  const templateEndIndex = templateStartIndex + template[0].length
44
43
 
45
44
  // Parse the template
@@ -58,16 +57,26 @@ export default (source, filePath, mode) => {
58
57
  (fn) => fn.toString()
59
58
  )}], context: {}}`
60
59
 
61
- offset += replacement.length - template[0].length
62
-
63
- newSource =
64
- newSource.substring(0, templateStartIndex) +
65
- replacement +
66
- newSource.substring(templateEndIndex)
60
+ // MagicString tracks all changes automatically, no offset needed
61
+ s.overwrite(templateStartIndex, templateEndIndex, replacement)
67
62
  }
68
63
  }
69
64
  }
70
- return newSource
65
+
66
+ const newSource = s.toString()
67
+
68
+ if (newSource !== source) {
69
+ const fileName = filePath.split(/[\\/]/).pop()
70
+ return {
71
+ code: newSource,
72
+ map: s.generateMap({
73
+ hires: true,
74
+ source: fileName,
75
+ includeContent: true,
76
+ file: fileName,
77
+ }),
78
+ }
79
+ }
71
80
  }
72
81
  return source
73
82
  }
@@ -32,9 +32,10 @@ const objectMap = new WeakMap()
32
32
  const globalEffectsMap = new Map()
33
33
 
34
34
  export const removeGlobalEffects = (effectsToRemove) => {
35
- if (globalEffectsMap.size === 0) return
35
+ if (globalEffectsMap.size === 0 || effectsToRemove.length === 0) return
36
+ const effectsToRemoveSet = new Set(effectsToRemove)
36
37
  for (const [effect, target] of globalEffectsMap) {
37
- if (effectsToRemove.indexOf(effect) === -1) continue
38
+ if (!effectsToRemoveSet.has(effect)) continue
38
39
  const effectsSet = objectMap.get(target)
39
40
  if (effectsSet === undefined) continue
40
41
  for (const set of effectsSet.values()) {
@@ -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,14 +180,15 @@ 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
 
@@ -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
@@ -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') {
@@ -17,6 +17,7 @@
17
17
  * @property {symbol} inputEvents
18
18
  * @property {symbol} internalEvent
19
19
  * @property {symbol} intervals
20
+ * @property {symbol} debounces
20
21
  * @property {symbol} isProxy
21
22
  * @property {symbol} launched
22
23
  * @property {symbol} level
@@ -73,6 +74,7 @@ export default {
73
74
  inputEvents: Symbol('inputEvents'),
74
75
  internalEvent: Symbol('internalEvent'),
75
76
  intervals: Symbol('intervals'),
77
+ debounces: Symbol('debounces'),
76
78
  isProxy: Symbol('isProxy'),
77
79
  launched: Symbol('launched'),
78
80
  level: Symbol('level'),
@@ -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
+