@lightningjs/blits 1.48.0 → 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.
Binary file
package/index.d.ts CHANGED
@@ -18,6 +18,10 @@
18
18
  // blits file type reference
19
19
  /// <reference path="./blits.d.ts" />
20
20
 
21
+ import {type ShaderEffect as RendererShaderEffect, type RendererMainSettings} from '@lightningjs/renderer'
22
+ import { CanvasShaderType } from '@lightningjs/renderer/canvas';
23
+ import { WebGlShaderType } from '@lightningjs/renderer/webgl';
24
+
21
25
  declare module '@lightningjs/blits' {
22
26
  type RendererShaderEffect = import('@lightningjs/renderer').ShaderEffect
23
27
  type WebGlCoreShader = import('@lightningjs/renderer').WebGlCoreShader
@@ -389,7 +393,7 @@ declare module '@lightningjs/blits' {
389
393
  *
390
394
  * @returns Boolean
391
395
  */
392
- hasFocus: boolean,
396
+ readonly $hasFocus: boolean,
393
397
 
394
398
  /**
395
399
  * Listen to events emitted by other components
@@ -478,12 +482,6 @@ declare module '@lightningjs/blits' {
478
482
  * @returns Returns true if this component or a parent component handled the event, false otherwise
479
483
  */
480
484
  $input: (event: KeyboardEvent) => boolean
481
- /**
482
- * @deprecated
483
- * Deprecated: use `this.$focus()` instead
484
- */
485
- focus: (event?: KeyboardEvent) => void
486
-
487
485
  /**
488
486
  * Select a child Element or Component by ref
489
487
  *
@@ -501,12 +499,6 @@ declare module '@lightningjs/blits' {
501
499
  */
502
500
  $select: (ref: string) => ComponentBase
503
501
 
504
- /**
505
- * @deprecated
506
- * Deprecated: use `this.$select()` instead
507
- */
508
- select: (ref: string) => ComponentBase
509
-
510
502
  /**
511
503
  * Announcer methods for screen reader support
512
504
  */
@@ -516,13 +508,6 @@ declare module '@lightningjs/blits' {
516
508
  * Triggers a forced update on state variables.
517
509
  */
518
510
  $trigger: (key: string) => void
519
- /**
520
- * @deprecated
521
- *
522
- * Triggers a forced update on state variables.
523
- * Deprecated: use `this.$trigger()` instead
524
- */
525
- trigger: (key: string) => void
526
511
  /**
527
512
  * Router instance
528
513
  */
@@ -572,20 +557,22 @@ declare module '@lightningjs/blits' {
572
557
  cast?: () => any
573
558
  };
574
559
 
575
- // Props Array
576
- export type Props = (string | PropObject)[];
560
+ export type Props = Record<string, any>
577
561
 
578
- // Extract the prop names from the props array
579
- type ExtractPropNames<P extends Props> = {
580
- readonly [K in P[number] as K extends string ? K : K extends { key: infer Key } ? Key : never]: any;
581
- };
562
+ type InferProp<T> = T extends (...args: any[]) => any ? ReturnType<T> : T
582
563
 
583
- // Update the PropsDefinition to handle props as strings or objects
584
- export type PropsDefinition<P extends Props> = ExtractPropNames<P>;
564
+ type InferProps<T extends Record<string, any>> = {
565
+ [K in keyof T]: InferProp<T[K]>
566
+ }
585
567
 
586
- export type ComponentContext<P extends Props, S, M, C> = ThisType<PropsDefinition<P> & S & M & C & ComponentBase>
568
+ export type ComponentContext<
569
+ P extends Record<string, any>,
570
+ S,
571
+ M,
572
+ C
573
+ > = ThisType<Readonly<InferProps<P>> & S & M & Readonly<C> & ComponentBase>
587
574
 
588
- export interface ComponentConfig<P extends Props, S, M, C, W> {
575
+ export interface ComponentConfig<P extends Props = {}, S, M, C, W> {
589
576
  components?: {
590
577
  [key: string]: ComponentFactory,
591
578
  },
@@ -641,7 +628,7 @@ declare module '@lightningjs/blits' {
641
628
  * }
642
629
  * ```
643
630
  */
644
- state?: (this: PropsDefinition<P>) => S;
631
+ state?: (this: InferProps<P>) => S;
645
632
  /**
646
633
  * Methods for abstracting more complex business logic into separate function
647
634
  */
@@ -930,7 +917,7 @@ declare module '@lightningjs/blits' {
930
917
 
931
918
  type Shader = {
932
919
  name: string,
933
- type: WebGlCoreShader
920
+ type: WebGlShaderType | CanvasShaderType
934
921
  }
935
922
 
936
923
  type ScreenResolutions = 'hd' | '720p' | 720 | 'fhd' | 'fullhd' | '1080p' | 1080 | '4k' | '2160p' | 2160
@@ -966,10 +953,6 @@ declare module '@lightningjs/blits' {
966
953
  * Fonts to be used in the Application
967
954
  */
968
955
  fonts?: Font[],
969
- /**
970
- * Effects to be used by DynamicShader
971
- */
972
- effects?: ShaderEffect[],
973
956
  /**
974
957
  * Shaders to be used in the application
975
958
  */
@@ -1102,18 +1085,6 @@ declare module '@lightningjs/blits' {
1102
1085
  * Defaults to `0`
1103
1086
  */
1104
1087
  viewportMargin?: number | [number, number, number, number],
1105
- /**
1106
- * Threshold in `Megabytes` after which all the textures that are currently not visible
1107
- * within the configured viewport margin will be be freed and cleaned up
1108
- *
1109
- * When passed `0` the threshold is disabled and textures will not be actively freed
1110
- * and cleaned up
1111
- *
1112
- * Defaults to `200` (mb)
1113
- * @deprecated
1114
- * Deprecated: use `gpuMemory` launch setting instead
1115
- */
1116
- gpuMemoryLimit?: number,
1117
1088
  /**
1118
1089
  * Configures the gpu memory settings used by the renderer
1119
1090
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightningjs/blits",
3
- "version": "1.48.0",
3
+ "version": "2.0.0-beta.2",
4
4
  "description": "Blits: The Lightning 3 App Development Framework",
5
5
  "bin": "bin/index.js",
6
6
  "exports": {
@@ -35,6 +35,7 @@
35
35
  "test": "c8 node scripts/runTests.js",
36
36
  "test:ci": "c8 npm run test:run",
37
37
  "test:run": "node -r global-jsdom/register ./node_modules/.bin/tape '{,!(node_modules|packages)/**/}*.test.js' | tap-diff",
38
+ "test:run:failures": "node -r global-jsdom/register ./node_modules/.bin/tape '{,!(node_modules|packages)/**/}*.test.js' | tap-filter test-fail | tap-diff",
38
39
  "lint": "node scripts/lint.js",
39
40
  "lint:fix": "node scripts/lint.js fix",
40
41
  "prepublishOnly": "node scripts/prepublishOnly.js",
@@ -70,11 +71,11 @@
70
71
  "prettier": "^3.6.2",
71
72
  "sinon": "^21.0.0",
72
73
  "tap-diff": "^0.1.1",
74
+ "tap-filter": "^1.0.3",
73
75
  "tape": "^5.5.0"
74
76
  },
75
77
  "dependencies": {
76
- "@lightningjs/msdf-generator": "^1.2.0",
77
- "@lightningjs/renderer": "^2.21.0",
78
+ "@lightningjs/renderer": "3.0.0-beta21",
78
79
  "magic-string": "^0.30.21"
79
80
  },
80
81
  "repository": {
@@ -26,26 +26,14 @@ import { renderer } from '../../launch.js'
26
26
  import { keyMap } from '../../application.js'
27
27
 
28
28
  export default {
29
- focus: {
30
- /**
31
- * @this {import('../../component').BlitsComponent}
32
- */
33
- value: function (e) {
34
- Log.warn('this.focus is deprecated, use this.$focus instead')
35
- return this.$focus(e)
36
- },
37
- writable: false,
38
- enumerable: true,
39
- configurable: false,
40
- },
41
29
  $focus: {
42
30
  /**
43
31
  * @this {import('../../component').BlitsComponent}
44
32
  */
45
33
  value: function (e) {
46
34
  // force refocus when the component is already in focused state
47
- if (this.lifecycle.state === 'focus') {
48
- this.lifecycle.state = 'refocus'
35
+ if (this[symbols.lifecycle].state === 'focus') {
36
+ this[symbols.lifecycle].state = 'refocus'
49
37
  }
50
38
  Focus.set(this, e)
51
39
  },
@@ -93,8 +81,8 @@ export default {
93
81
  * @this {import('../../component').BlitsComponent}
94
82
  */
95
83
  value: function () {
96
- this[symbols.state].hasFocus = false
97
- this.lifecycle.state = 'unfocus'
84
+ this[symbols.state].$hasFocus = false
85
+ this[symbols.lifecycle].state = 'unfocus'
98
86
  },
99
87
  writable: false,
100
88
  enumerable: true,
@@ -106,11 +94,11 @@ export default {
106
94
  */
107
95
  value: function () {
108
96
  this.eol = true
109
- this.lifecycle.state = 'destroy'
97
+ this[symbols.lifecycle].state = 'destroy'
110
98
 
111
99
  // when destroying a component that currently has focus
112
100
  // pass focus to the parent so we don't get lost in focus limbo
113
- if (this.hasFocus === true) this.parent.$focus()
101
+ if (this.$hasFocus === true) this[symbols.parent].$focus()
114
102
 
115
103
  // @todo - is this really necessary?
116
104
  // This cause an issue with auto sizing of parent (and required an extra eol check there)
@@ -143,26 +131,25 @@ export default {
143
131
  this[symbols.state] = {}
144
132
 
145
133
  this[symbols.props] = {}
146
- this[symbols.computed] = null
147
- this.lifecycle = {}
134
+ this[symbols.computedKeys] = null
135
+ this[symbols.lifecycle] = {}
148
136
  this[symbols.effects].length = 0
149
- this.parent = null
150
- this.rootParent = null
137
+ this[symbols.parent] = null
138
+ this[symbols.rootParent] = null
151
139
  this[symbols.wrapper] = null
152
140
  this[symbols.originalState] = null
153
141
  this[symbols.slots].length = 0
154
142
 
155
- delete this[symbols.computed]
156
- delete this.parent
157
- delete this.rootParent
143
+ delete this[symbols.computedKeys]
144
+ delete this[symbols.parent]
145
+ delete this[symbols.rootParent]
158
146
  delete this[symbols.wrapper]
159
147
  delete this[symbols.originalState]
160
148
  delete this[symbols.children]
161
149
  delete this[symbols.slots]
162
- delete this.componentId
163
150
  delete this[symbols.id]
164
151
  delete this.ref
165
- delete this[symbols.state].hasFocus
152
+ delete this[symbols.state].$hasFocus
166
153
 
167
154
  this[symbols.holder].destroy()
168
155
  this[symbols.holder] = null
@@ -173,7 +160,8 @@ export default {
173
160
 
174
161
  delete this[symbols.effects]
175
162
 
176
- Log.debug(`Destroyed component ${this.componentId}`)
163
+ Log.debug(`Destroyed component ${this.$componentId}`)
164
+ delete this.$componentId
177
165
  },
178
166
  writable: false,
179
167
  enumerable: true,
@@ -190,18 +178,6 @@ export default {
190
178
  enumerable: false,
191
179
  configurable: false,
192
180
  },
193
- select: {
194
- /**
195
- * @this {import('../../component').BlitsComponent}
196
- */
197
- value: function (ref) {
198
- Log.warn('this.select is deprecated, use this.$select instead')
199
- return this.$select(ref)
200
- },
201
- writable: false,
202
- enumerable: true,
203
- configurable: false,
204
- },
205
181
  $select: {
206
182
  /**
207
183
  * @this {import('../../component').BlitsComponent}
@@ -230,18 +206,6 @@ export default {
230
206
  enumerable: true,
231
207
  configurable: false,
232
208
  },
233
- trigger: {
234
- /**
235
- * @this {import('../../component').BlitsComponent}
236
- */
237
- value: function (key) {
238
- Log.warn('this.trigger is deprecated, use this.$trigger instead')
239
- return this.$trigger(key)
240
- },
241
- writable: false,
242
- enumerable: true,
243
- configurable: false,
244
- },
245
209
  $trigger: {
246
210
  /**
247
211
  * @this {import('../../component').BlitsComponent}
@@ -263,30 +227,6 @@ export default {
263
227
  enumerable: true,
264
228
  configurable: false,
265
229
  },
266
- shader: {
267
- /**
268
- * @this {import('../../component').BlitsComponent}
269
- */
270
- value: function (type, args) {
271
- return {
272
- type: type,
273
- props: args,
274
- }
275
- // const shaders = renderer.driver.stage.shManager.getRegisteredEffects()
276
-
277
- // if (target in shaders) {
278
- // return {
279
- // type: target,
280
- // props: args,
281
- // }
282
- // } else {
283
- // Log.error(`Shader ${type} not found`)
284
- // }
285
- },
286
- writable: false,
287
- enumerable: true,
288
- configurable: false,
289
- },
290
230
  }
291
231
 
292
232
  /**
@@ -29,6 +29,20 @@ export default {
29
29
  enumerable: true,
30
30
  configurable: false,
31
31
  },
32
+ $parent: {
33
+ get: function () {
34
+ return this[symbols.parent]
35
+ },
36
+ enumerable: true,
37
+ configurable: false,
38
+ },
39
+ $id: {
40
+ get: function () {
41
+ return this[symbols.id]
42
+ },
43
+ enumerable: true,
44
+ configurable: false,
45
+ },
32
46
  [symbols.renderer]: {
33
47
  value: () => renderer,
34
48
  writable: false,
@@ -37,7 +51,7 @@ export default {
37
51
  },
38
52
  [symbols.getChildren]: {
39
53
  value() {
40
- const parent = this.rootParent || this.parent
54
+ const parent = this[symbols.rootParent] || this[symbols.parent]
41
55
  return (this[symbols.children] || []).concat(
42
56
  (parent &&
43
57
  parent[symbols.getChildren]()
@@ -46,7 +60,7 @@ export default {
46
60
  return Object.values(child).map((c) => {
47
61
  // ugly hack .. but the point is to reference the right component
48
62
  c.forComponent =
49
- c[Symbol.for('config')] && c[Symbol.for('config')].parent.component
63
+ c[Symbol.for('config')] && c[Symbol.for('config')][symbols.parent].component
50
64
  return c
51
65
  })
52
66
  }
@@ -57,8 +71,8 @@ export default {
57
71
  // problem is that component of a forloop in a slot has component of root component
58
72
  if (child && child.component) {
59
73
  return (
60
- (child.component && child.component.componentId === this.componentId) ||
61
- (child.forComponent && child.forComponent.componentId === this.componentId)
74
+ (child.component && child.component.$componentId === this.$componentId) ||
75
+ (child.forComponent && child.forComponent.$componentId === this.$componentId)
62
76
  )
63
77
  }
64
78
  })) ||
@@ -16,46 +16,55 @@
16
16
  */
17
17
 
18
18
  import { Log } from '../../lib/log.js'
19
-
20
19
  import symbols from '../../lib/symbols.js'
21
20
 
22
- const baseProp = {
23
- cast: (v) => v,
24
- required: false,
21
+ const normalizeProps = (props) => {
22
+ Log.warn(
23
+ 'Defining props as an Array has been deprecated and will stop working in future versions. Please use the new notation instead (an object with key values pairs).',
24
+ props
25
+ )
26
+ const out = {}
27
+ const propLength = props.length
28
+ for (let i = 0; i < propLength; i++) {
29
+ const prop = props[i]
30
+ if (typeof prop === 'string') {
31
+ out[prop] = undefined
32
+ } else {
33
+ Log.error(
34
+ 'Defining props as an array of objects is no longer supported. Please use the new format: an object with key-value pairs.'
35
+ )
36
+ break
37
+ }
38
+ }
39
+ return out
25
40
  }
26
41
 
27
- export default (component, props = []) => {
28
- if (props.indexOf('ref') === -1) {
29
- props.push('ref')
42
+ export default (component, props = {}) => {
43
+ if (Array.isArray(props) === true) {
44
+ props = normalizeProps(props)
45
+ }
46
+ if (!('ref' in props)) {
47
+ props.ref = undefined
30
48
  }
31
- component[symbols.propKeys] = []
49
+ const keys = Object.keys(props)
50
+ component[symbols.propKeys] = keys
32
51
 
33
- const propsLength = props.length
52
+ for (let i = 0; i < keys.length; i++) {
53
+ const key = keys[i]
54
+ const defaultPropValue = props[key]
34
55
 
35
- for (let i = 0; i < propsLength; i++) {
36
- const prop = { ...baseProp, ...(typeof props[i] === 'object' ? props[i] : { key: props[i] }) }
37
- component[symbols.propKeys].push(prop.key)
38
- Object.defineProperty(component, prop.key, {
56
+ Object.defineProperty(component, key, {
39
57
  get() {
40
- const value = prop.cast(
41
- this[symbols.props] !== undefined && prop.key in this[symbols.props]
42
- ? this[symbols.props][prop.key]
43
- : 'default' in prop
44
- ? prop.default
45
- : undefined
46
- )
47
-
48
- if (prop.required === true && value === undefined) {
49
- Log.warn(`${prop.key} is required`)
50
- }
51
-
52
- return value
58
+ if (this[symbols.props] === undefined) return undefined
59
+ // if the key is specified independent of the value (falsy, null, undefined) use that value
60
+ // otherwise return the default prop value
61
+ return key in this[symbols.props] ? this[symbols.props][key] : defaultPropValue
53
62
  },
54
63
  set(v) {
55
64
  Log.warn(
56
- `Warning! Avoid mutating props directly (prop "${prop.key}" in component "${this.componentId}")`
65
+ `Warning! Avoid mutating props directly (prop "${key}" in component "${this.componentId}")`
57
66
  )
58
- this[symbols.props][prop.key] = v
67
+ this[symbols.props][key] = v
59
68
  },
60
69
  })
61
70
  }
@@ -28,13 +28,13 @@ export default (component, state = () => {}) => {
28
28
  })
29
29
 
30
30
  const stateKeys = Object.keys(state.apply(component) || {})
31
- if (stateKeys.indexOf('hasFocus') > -1) {
31
+ if (stateKeys.indexOf('$hasFocus') > -1) {
32
32
  Log.warn(
33
- 'State `hasFocus` already exists as a built-in Component variable (to indicate whether the component currently has focus). Avoid using your own `hasFocus` key in the Component state'
33
+ 'State `$hasFocus` already exists as a built-in Component variable (to indicate whether the component currently has focus). Avoid using your own `$hasFocus` key in the Component state'
34
34
  )
35
35
  } else {
36
- // add built-in hasFocus key
37
- stateKeys.push(['hasFocus'])
36
+ // add built-in $hasFocus key
37
+ stateKeys.push('$hasFocus')
38
38
  }
39
39
  const stateKeysLength = stateKeys.length
40
40
 
package/src/component.js CHANGED
@@ -135,7 +135,7 @@ const required = (name) => {
135
135
  * @property {any[]} [stateKeys] - The keys of the state of the component instance
136
136
  * Single props:
137
137
  * @property {object} activeView - The active view of the component instance
138
- * @property {boolean} hasFocus - Indicates if the component has focus
138
+ * @property {boolean} $hasFocus - Indicates if the component has focus
139
139
  * @property {string} ref - The reference name of the component instance
140
140
  * @property {number|undefined} index - The index in a for loop
141
141
  * @property {number|undefined} activeRow - The active row in a for loop
@@ -194,22 +194,23 @@ const Component = (name = required('name'), config = required('config')) => {
194
194
 
195
195
  const component = function (opts, parentEl, parentComponent, rootComponent) {
196
196
  // generate a human readable ID for the component instance (i.e. Blits::ComponentName1)
197
- this.componentId = createHumanReadableId(name)
197
+ this.$componentId = createHumanReadableId(name)
198
+
199
+ this[symbols.effects] = []
198
200
 
199
201
  this[symbols.effects] = []
200
202
 
201
203
  // instantiate a lifecycle object for this instance
202
- this.lifecycle = Object.assign(Object.create(Lifecycle), {
204
+ this[symbols.lifecycle] = Object.assign(Object.create(Lifecycle), {
203
205
  component: this,
204
206
  previous: null,
205
207
  current: null,
206
208
  })
207
209
 
208
210
  // set a reference to the parent component
209
- this.parent = parentComponent
211
+ this[symbols.parent] = parentComponent
210
212
 
211
- //
212
- this.rootParent = rootComponent
213
+ this[symbols.rootParent] = rootComponent
213
214
 
214
215
  // set a reference to the holder / parentElement
215
216
  // Components are wrapped in a holder node (used to apply positioning and transforms
@@ -235,19 +236,19 @@ const Component = (name = required('name'), config = required('config')) => {
235
236
  // and store a reference to this original state
236
237
  this[symbols.originalState] =
237
238
  (config.state && typeof config.state === 'function' && config.state.apply(this)) || {}
238
- // add hasFocus key in
239
- this[symbols.originalState]['hasFocus'] = false
239
+ // add $hasFocus key in
240
+ this[symbols.originalState]['$hasFocus'] = false
240
241
 
241
242
  // generate a reactive state (using the result of previously execute state function)
242
243
  // and store it
243
244
  this[symbols.state] = reactive(this[symbols.originalState], Settings.get('reactivityMode'))
244
245
 
245
246
  // all basic setup has been done now, set the lifecycle to state 'init'
246
- this.lifecycle.state = 'init'
247
+ this[symbols.lifecycle].state = 'init'
247
248
 
248
249
  // execute the render code that constructs the initial state of the component
249
250
  // and store the children result (a flat map of elements and components)
250
- const { elms, cleanup } = config.code.render.apply(stage, [
251
+ const { elms, cleanup, skips } = config.code.render.apply(stage, [
251
252
  parentEl,
252
253
  this,
253
254
  config,
@@ -300,28 +301,28 @@ const Component = (name = required('name'), config = required('config')) => {
300
301
  // inBounds event emiting a lifecycle attach event
301
302
  if (config.hooks.attach) {
302
303
  this[symbols.wrapper].node.on('inBounds', () => {
303
- this.lifecycle.state = 'attach'
304
+ this[symbols.lifecycle].state = 'attach'
304
305
  })
305
306
  }
306
307
 
307
308
  // outOfBounds event emiting a lifeycle detach event
308
309
  if (config.hooks.detach) {
309
310
  this[symbols.wrapper].node.on('outOfBounds', (node, { previous }) => {
310
- if (previous > 0) this.lifecycle.state = 'detach'
311
+ if (previous > 0) this[symbols.lifecycle].state = 'detach'
311
312
  })
312
313
  }
313
314
 
314
315
  // inViewport event emiting a lifecycle enter event
315
316
  if (config.hooks.enter) {
316
317
  this[symbols.wrapper].node.on('inViewport', () => {
317
- this.lifecycle.state = 'enter'
318
+ this[symbols.lifecycle].state = 'enter'
318
319
  })
319
320
  }
320
321
 
321
322
  // outOfViewport event emitting a lifecycle exit event
322
323
  if (config.hooks.exit) {
323
324
  this[symbols.wrapper].node.on('outOfBounds', () => {
324
- this.lifecycle.state = 'exit'
325
+ this[symbols.lifecycle].state = 'exit'
325
326
  })
326
327
  }
327
328
  }
@@ -331,7 +332,15 @@ const Component = (name = required('name'), config = required('config')) => {
331
332
  const effects = config.code.effects
332
333
  for (let i = 0; i < effects.length; i++) {
333
334
  const eff = () => {
334
- effects[i](this, this[symbols.children], config, globalComponents, rootComponent, effect)
335
+ effects[i](
336
+ this,
337
+ this[symbols.children],
338
+ config,
339
+ globalComponents,
340
+ rootComponent,
341
+ skips,
342
+ effect
343
+ )
335
344
  }
336
345
  // store reference to the effect
337
346
  this[symbols.effects].push(eff)
@@ -355,7 +364,7 @@ const Component = (name = required('name'), config = required('config')) => {
355
364
  }
356
365
  }
357
366
 
358
- let old = this[key]
367
+ let old = target[key]
359
368
 
360
369
  const eff = (force = false) => {
361
370
  const newValue = target[key]
@@ -372,7 +381,7 @@ const Component = (name = required('name'), config = required('config')) => {
372
381
  }
373
382
 
374
383
  // finaly set the lifecycle state to ready (in the next tick)
375
- setTimeout(() => (this.lifecycle.state = 'ready'))
384
+ setTimeout(() => (this[symbols.lifecycle].state = 'ready'))
376
385
 
377
386
  // and return this
378
387
  return this