etro 0.7.0 → 0.8.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.
Files changed (106) hide show
  1. package/.github/workflows/nodejs.yml +1 -1
  2. package/CHANGELOG.md +45 -1
  3. package/CONTRIBUTING.md +23 -19
  4. package/README.md +81 -26
  5. package/dist/effect/base.d.ts +38 -0
  6. package/dist/effect/brightness.d.ts +16 -0
  7. package/dist/effect/channels.d.ts +23 -0
  8. package/dist/effect/chroma-key.d.ts +23 -0
  9. package/dist/effect/contrast.d.ts +15 -0
  10. package/dist/effect/elliptical-mask.d.ts +31 -0
  11. package/dist/effect/gaussian-blur.d.ts +60 -0
  12. package/dist/effect/grayscale.d.ts +7 -0
  13. package/dist/effect/index.d.ts +15 -0
  14. package/dist/effect/pixelate.d.ts +18 -0
  15. package/dist/effect/shader.d.ts +99 -0
  16. package/dist/effect/stack.d.ts +23 -0
  17. package/dist/effect/transform.d.ts +73 -0
  18. package/dist/etro-cjs.js +9337 -3331
  19. package/dist/etro-iife.js +9279 -3273
  20. package/dist/etro.d.ts +7 -0
  21. package/dist/event.d.ts +35 -0
  22. package/dist/index.d.ts +6 -0
  23. package/dist/layer/audio-source.d.ts +24 -0
  24. package/dist/layer/audio.d.ts +14 -0
  25. package/dist/layer/base.d.ts +69 -0
  26. package/dist/layer/image.d.ts +6 -0
  27. package/dist/layer/index.d.ts +11 -0
  28. package/dist/layer/text.d.ts +60 -0
  29. package/dist/layer/video.d.ts +11 -0
  30. package/dist/layer/visual-source.d.ts +32 -0
  31. package/dist/layer/visual.d.ts +58 -0
  32. package/dist/movie.d.ts +192 -0
  33. package/dist/object.d.ts +12 -0
  34. package/dist/util.d.ts +125 -0
  35. package/eslint.conf.js +0 -8
  36. package/eslint.example-conf.js +9 -0
  37. package/eslint.typescript-conf.js +5 -0
  38. package/examples/application/readme-screenshot.html +12 -9
  39. package/examples/application/video-player.html +7 -7
  40. package/examples/application/webcam.html +6 -6
  41. package/examples/introduction/audio.html +30 -18
  42. package/examples/introduction/effects.html +14 -10
  43. package/examples/introduction/export.html +32 -25
  44. package/examples/introduction/functions.html +6 -4
  45. package/examples/introduction/hello-world1.html +9 -5
  46. package/examples/introduction/hello-world2.html +5 -5
  47. package/examples/introduction/keyframes.html +35 -23
  48. package/examples/introduction/media.html +26 -18
  49. package/examples/introduction/text.html +9 -5
  50. package/karma.conf.js +1 -1
  51. package/package.json +29 -13
  52. package/rollup.config.js +15 -4
  53. package/scripts/gen-effect-samples.html +29 -25
  54. package/scripts/save-effect-samples.js +14 -15
  55. package/spec/assets/effect/gaussian-blur-horizontal.png +0 -0
  56. package/spec/assets/effect/gaussian-blur-vertical.png +0 -0
  57. package/spec/assets/effect/grayscale.png +0 -0
  58. package/spec/assets/effect/original.png +0 -0
  59. package/spec/assets/effect/pixelate.png +0 -0
  60. package/spec/assets/effect/transform/multiply.png +0 -0
  61. package/spec/assets/effect/transform/rotate.png +0 -0
  62. package/spec/assets/effect/transform/scale-fraction.png +0 -0
  63. package/spec/assets/effect/transform/scale.png +0 -0
  64. package/spec/assets/effect/transform/translate-fraction.png +0 -0
  65. package/spec/assets/effect/transform/translate.png +0 -0
  66. package/spec/effect.spec.js +126 -57
  67. package/spec/event.spec.js +14 -0
  68. package/spec/layer.spec.js +175 -18
  69. package/spec/movie.spec.js +191 -7
  70. package/spec/util.spec.js +14 -5
  71. package/src/effect/base.ts +96 -0
  72. package/src/effect/brightness.ts +43 -0
  73. package/src/effect/channels.ts +50 -0
  74. package/src/effect/chroma-key.ts +82 -0
  75. package/src/effect/contrast.ts +42 -0
  76. package/src/effect/elliptical-mask.ts +75 -0
  77. package/src/effect/gaussian-blur.ts +232 -0
  78. package/src/effect/grayscale.ts +34 -0
  79. package/src/effect/index.ts +22 -0
  80. package/src/effect/pixelate.ts +59 -0
  81. package/src/effect/shader.ts +561 -0
  82. package/src/effect/stack.ts +74 -0
  83. package/src/effect/transform.ts +194 -0
  84. package/src/etro.ts +26 -0
  85. package/src/event.ts +118 -0
  86. package/src/index.ts +8 -0
  87. package/src/layer/audio-source.ts +217 -0
  88. package/src/layer/audio.ts +35 -0
  89. package/src/layer/base.ts +156 -0
  90. package/src/layer/image.ts +8 -0
  91. package/src/layer/index.ts +13 -0
  92. package/src/layer/text.ts +138 -0
  93. package/src/layer/video.ts +15 -0
  94. package/src/layer/visual-source.ts +150 -0
  95. package/src/layer/visual.ts +198 -0
  96. package/src/movie.ts +709 -0
  97. package/src/object.ts +14 -0
  98. package/src/util.ts +473 -0
  99. package/tsconfig.json +8 -0
  100. package/screenshots/2019-08-17_0.png +0 -0
  101. package/src/effect.js +0 -1268
  102. package/src/event.js +0 -78
  103. package/src/index.js +0 -23
  104. package/src/layer.js +0 -897
  105. package/src/movie.js +0 -637
  106. package/src/util.js +0 -505
package/src/util.js DELETED
@@ -1,505 +0,0 @@
1
- /**
2
- * @module util
3
- */
4
-
5
- import { publish } from './event.js'
6
-
7
- /**
8
- * Gets the first matching property descriptor in the prototype chain, or undefined.
9
- * @param {Object} obj
10
- * @param {string|Symbol} name
11
- */
12
- function getPropertyDescriptor (obj, name) {
13
- do {
14
- const propDesc = Object.getOwnPropertyDescriptor(obj, name)
15
- if (propDesc) {
16
- return propDesc
17
- }
18
- obj = Object.getPrototypeOf(obj)
19
- } while (obj)
20
- return undefined
21
- }
22
-
23
- /**
24
- * Merges `options` with `defaultOptions`, and then copies the properties with the keys in `defaultOptions`
25
- * from the merged object to `destObj`.
26
- *
27
- * @return {undefined}
28
- * @todo Make methods like getDefaultOptions private
29
- */
30
- export function applyOptions (options, destObj) {
31
- const defaultOptions = destObj.getDefaultOptions()
32
-
33
- // validate; make sure `keys` doesn't have any extraneous items
34
- for (const option in options) {
35
- // eslint-disable-next-line no-prototype-builtins
36
- if (!defaultOptions.hasOwnProperty(option)) {
37
- throw new Error("Invalid option: '" + option + "'")
38
- }
39
- }
40
-
41
- // merge options and defaultOptions
42
- options = { ...defaultOptions, ...options }
43
-
44
- // copy options
45
- for (const option in options) {
46
- const propDesc = getPropertyDescriptor(destObj, option)
47
- // Update the property as long as the property has not been set (unless if it has a setter)
48
- if (!propDesc || propDesc.set) {
49
- destObj[option] = options[option]
50
- }
51
- }
52
- }
53
-
54
- // https://stackoverflow.com/a/8024294/3783155
55
- /**
56
- * Get all inherited keys
57
- * @param {object} obj
58
- * @param {boolean} excludeObjectClass - don't add properties of the <code>Object</code> prototype
59
- * @private
60
- */
61
- function getAllPropertyNames (obj, excludeObjectClass) {
62
- let props = []
63
- do {
64
- props = props.concat(Object.getOwnPropertyNames(obj))
65
- } while ((obj = Object.getPrototypeOf(obj)) && (excludeObjectClass ? obj.constructor.name !== 'Object' : true))
66
- return props
67
- }
68
-
69
- /**
70
- * @return {boolean} <code>true</code> if <code>property</code> is a non-array object and all of its own
71
- * property keys are numbers or <code>"interpolate"</code> or <code>"interpolationKeys"</code>, and
72
- * <code>false</code> otherwise.
73
- */
74
- function isKeyFrames (property) {
75
- if ((typeof property !== 'object' || property === null) || Array.isArray(property)) {
76
- return false
77
- }
78
- // is reduce slow? I think it is
79
- // let keys = Object.keys(property); // own propeties
80
- const keys = getAllPropertyNames(property, true) // includes non-enumerable properties (except that of `Object`)
81
- for (let i = 0; i < keys.length; i++) {
82
- const key = keys[i]
83
- // convert key to number, because object keys are always converted to strings
84
- if (isNaN(key) && !(key === 'interpolate' || key === 'interpolationKeys')) {
85
- return false
86
- }
87
- }
88
- // If it's an empty object, don't treat is as keyframe set.
89
- // https://stackoverflow.com/a/32108184/3783155
90
- const isEmpty = property.constructor === Object && Object.entries(property).length === 0
91
- return !isEmpty
92
- }
93
-
94
- // must be cleared at the start of each frame
95
- const valCache = new WeakMap()
96
- function cacheValue (element, path, value) {
97
- if (!valCache.has(element.movie)) {
98
- valCache.set(element.movie, new WeakMap())
99
- }
100
- const movieCache = valCache.get(element.movie)
101
-
102
- if (!movieCache.has(element)) {
103
- movieCache.set(element, {})
104
- }
105
- const elementCache = movieCache.get(element)
106
-
107
- elementCache[path] = value
108
- return value
109
- }
110
- function hasCachedValue (element, path) {
111
- return valCache.has(element.movie) &&
112
- valCache.get(element.movie).has(element) &&
113
- path in valCache.get(element.movie).get(element)
114
- }
115
- function getCachedValue (element, path) {
116
- return valCache.get(element.movie).get(element)[path]
117
- }
118
- export function clearCachedValues (movie) {
119
- valCache.delete(movie)
120
- }
121
-
122
- /**
123
- * Calculates the value of keyframe set <code>property</code> at <code>time</code> if
124
- * <code>property</code> is an array, or returns <code>property</code>, assuming that it's a number.
125
- *
126
- * @param {(*|module:util.KeyFrames)} property - value or map of time-to-value pairs for keyframes
127
- * @param {object} element - the object to which the property belongs
128
- * @param {number} time - time to calculate keyframes for, if necessary
129
- *
130
- * Note that only values used in keyframes that numbers or objects (including arrays) are interpolated.
131
- * All other values are taken sequentially with no interpolation. JavaScript will convert parsed colors,
132
- * if created correctly, to their string representations when assigned to a CanvasRenderingContext2D property
133
- * (I'm pretty sure).
134
- *
135
- * @todo Is this function efficient?
136
- * @todo Update doc @params to allow for keyframes
137
- *
138
- * @typedef {Object} module:util.KeyFrames
139
- * @property {function} interpolate - the function to interpolate between keyframes, defaults to
140
- * {@link module:util.linearInterp}
141
- * @property {string[]} interpolationKeys - keys to interpolate for objects, defaults to all
142
- * own enumerable properties
143
- */
144
- export function val (element, path, time) {
145
- if (hasCachedValue(element, path)) {
146
- return getCachedValue(element, path)
147
- }
148
-
149
- // get property of element at path
150
- const pathParts = path.split('.')
151
- let property = element
152
- while (pathParts.length > 0) {
153
- property = property[pathParts.shift()]
154
- }
155
- const process = element.propertyFilters[path]
156
-
157
- let value
158
- if (isKeyFrames(property)) {
159
- value = valKeyFrame(property, time)
160
- } else if (typeof property === 'function') {
161
- value = property(element, time) // TODO? add more args
162
- } else {
163
- value = property // simple value
164
- }
165
- return cacheValue(element, path, process ? process(value) : value)
166
- }
167
-
168
- function valKeyFrame (property, time) {
169
- // if (Object.keys(property).length === 0) throw "Empty key frame set"; // this will never be executed
170
- if (time === undefined) {
171
- throw new Error('|time| is undefined or null')
172
- }
173
- // I think .reduce and such are slow to do per-frame (or more)?
174
- // lower is the max beneath time, upper is the min above time
175
- let lowerTime = 0; let upperTime = Infinity
176
- let lowerValue = null; let upperValue = null // default values for the inequalities
177
- for (let keyTime in property) {
178
- const keyValue = property[keyTime]
179
- keyTime = +keyTime // valueOf to convert to number
180
-
181
- if (lowerTime <= keyTime && keyTime <= time) {
182
- lowerValue = keyValue
183
- lowerTime = keyTime
184
- }
185
- if (time <= keyTime && keyTime <= upperTime) {
186
- upperValue = keyValue
187
- upperTime = keyTime
188
- }
189
- }
190
- // TODO: support custom interpolation for 'other' types
191
- if (lowerValue === null) {
192
- throw new Error(`No keyframes located before or at time ${time}.`)
193
- }
194
- // no need for upperValue if it is flat interpolation
195
- if (!(typeof lowerValue === 'number' || typeof lowerValue === 'object')) {
196
- return lowerValue
197
- }
198
- if (upperValue === null) {
199
- throw new Error(`No keyframes located after or at time ${time}.`)
200
- }
201
- if (typeof lowerValue !== typeof upperValue) {
202
- throw new Error('Type mismatch in keyframe values')
203
- }
204
- // interpolate
205
- // the following should mean that there is a key frame *at* |time|; prevents division by zero below
206
- if (upperTime === lowerTime) {
207
- return upperValue
208
- }
209
- const progress = time - lowerTime; const percentProgress = progress / (upperTime - lowerTime)
210
- const interpolate = property.interpolate || linearInterp
211
- return interpolate(lowerValue, upperValue, percentProgress, property.interpolationKeys)
212
- }
213
-
214
- /* export function floorInterp(x1, x2, t, objectKeys) {
215
- // https://stackoverflow.com/a/25835337/3783155 (TODO: preserve getters/setters, etc?)
216
- return !objectKeys ? x1 : objectKeys.reduce((a, x) => {
217
- if (x1.hasOwnProperty(x)) a[x] = o[x]; // ignore x2
218
- return a;
219
- }, Object.create(Object.getPrototypeOf(x1)));
220
- } */
221
-
222
- export function linearInterp (x1, x2, t, objectKeys) {
223
- if (typeof x1 !== typeof x2) {
224
- throw new Error('Type mismatch')
225
- }
226
- if (typeof x1 !== 'number' && typeof x1 !== 'object') {
227
- return x1
228
- } // flat interpolation (floor)
229
- if (typeof x1 === 'object') { // to work with objects (including arrays)
230
- // TODO: make this code DRY
231
- if (Object.getPrototypeOf(x1) !== Object.getPrototypeOf(x2)) {
232
- throw new Error('Prototype mismatch')
233
- }
234
- const int = Object.create(Object.getPrototypeOf(x1)) // preserve prototype of objects
235
- // only take the union of properties
236
- const keys = Object.keys(x1) || objectKeys
237
- for (let i = 0; i < keys.length; i++) {
238
- const key = keys[i]
239
- // (only take the union of properties)
240
- // eslint-disable-next-line no-prototype-builtins
241
- if (!x1.hasOwnProperty(key) || !x2.hasOwnProperty(key)) {
242
- continue
243
- }
244
- int[key] = linearInterp(x1[key], x2[key], t)
245
- }
246
- return int
247
- }
248
- return (1 - t) * x1 + t * x2
249
- }
250
-
251
- export function cosineInterp (x1, x2, t, objectKeys) {
252
- if (typeof x1 !== typeof x2) {
253
- throw new Error('Type mismatch')
254
- }
255
- if (typeof x1 !== 'number' && typeof x1 !== 'object') {
256
- return x1
257
- } // flat interpolation (floor)
258
- if (typeof x1 === 'object' && typeof x2 === 'object') { // to work with objects (including arrays)
259
- if (Object.getPrototypeOf(x1) !== Object.getPrototypeOf(x2)) {
260
- throw new Error('Prototype mismatch')
261
- }
262
- const int = Object.create(Object.getPrototypeOf(x1)) // preserve prototype of objects
263
- // only take the union of properties
264
- const keys = Object.keys(x1) || objectKeys
265
- for (let i = 0; i < keys.length; i++) {
266
- const key = keys[i]
267
- // (only take the union of properties)
268
- // eslint-disable-next-line no-prototype-builtins
269
- if (!x1.hasOwnProperty(key) || !x2.hasOwnProperty(key)) {
270
- continue
271
- }
272
- int[key] = cosineInterp(x1[key], x2[key], t)
273
- }
274
- return int
275
- }
276
- const cos = Math.cos(Math.PI / 2 * t)
277
- return cos * x1 + (1 - cos) * x2
278
- }
279
-
280
- /**
281
- * An rgba color, for proper interpolation and shader effects
282
- */
283
- export class Color {
284
- /**
285
- * @param {number} r
286
- * @param {number} g
287
- * @param {number} b
288
- * @param {number} a
289
- */
290
- constructor (r, g, b, a = 1.0) {
291
- /** @type number */
292
- this.r = r
293
- /** @type number */
294
- this.g = g
295
- /** @type number */
296
- this.b = b
297
- /** @type number */
298
- this.a = a
299
- }
300
-
301
- /**
302
- * Converts to css color
303
- */
304
- toString () {
305
- return `rgba(${this.r}, ${this.g}, ${this.b}, ${this.a})`
306
- }
307
- }
308
-
309
- const parseColorCanvas = document.createElement('canvas')
310
- parseColorCanvas.width = parseColorCanvas.height = 1
311
- const parseColorCtx = parseColorCanvas.getContext('2d')
312
- /**
313
- * Converts a css color string to a {@link module:util.Color} object representation.
314
- * @param {string} str
315
- * @return {module:util.Color} the parsed color
316
- */
317
- export function parseColor (str) {
318
- // TODO - find a better way to cope with the fact that invalid
319
- // values of "col" are ignored
320
- parseColorCtx.clearRect(0, 0, 1, 1)
321
- parseColorCtx.fillStyle = str
322
- parseColorCtx.fillRect(0, 0, 1, 1)
323
- const data = parseColorCtx.getImageData(0, 0, 1, 1).data
324
- return new Color(data[0], data[1], data[2], data[3] / 255)
325
- }
326
-
327
- /**
328
- * A font, for proper interpolation
329
- */
330
- export class Font {
331
- /**
332
- * @param {number} size
333
- * @param {string} family
334
- * @param {string} sizeUnit
335
- */
336
- constructor (size, sizeUnit, family, style = 'normal', variant = 'normal',
337
- weight = 'normal', stretch = 'normal', lineHeight = 'normal') {
338
- this.size = size
339
- this.sizeUnit = sizeUnit
340
- this.family = family
341
- this.style = style
342
- this.variant = variant
343
- this.weight = weight
344
- this.stretch = stretch
345
- this.lineHeight = lineHeight
346
- }
347
-
348
- /**
349
- * Converts to css font syntax
350
- * @see https://developer.mozilla.org/en-US/docs/Web/CSS/font
351
- */
352
- toString () {
353
- let s = ''
354
- if (this.style !== 'normal') s += this.style + ' '
355
- if (this.variant !== 'normal') s += this.variant + ' '
356
- if (this.weight !== 'normal') s += this.weight + ' '
357
- if (this.stretch !== 'normal') s += this.stretch + ' '
358
- s += `${this.size}${this.sizeUnit} `
359
- if (this.lineHeight !== 'normal') s += this.lineHeight + ' '
360
- s += this.family
361
-
362
- return s
363
- }
364
- }
365
-
366
- const parseFontEl = document.createElement('div')
367
- /**
368
- * Converts a css font string to a {@link module:util.Font} object representation.
369
- * @param {string} str
370
- * @return {module:util.Font} the parsed font
371
- */
372
- export function parseFont (str) {
373
- parseFontEl.setAttribute('style', `font: ${str}`) // assign css string to html element
374
- const {
375
- fontSize, fontFamily, fontStyle, fontVariant, fontWeight, lineHeight
376
- } = parseFontEl.style
377
- parseFontEl.removeAttribute('style')
378
-
379
- const size = parseFloat(fontSize)
380
- const sizeUnit = fontSize.substring(size.toString().length)
381
- return new Font(size, sizeUnit, fontFamily, fontStyle, fontVariant, fontWeight, lineHeight)
382
- }
383
-
384
- /*
385
- * Attempts to solve the diamond inheritance problem using mixins
386
- * See {@link http://javascriptweblog.wordpress.com/2011/05/31/a-fresh-look-at-javascript-mixins/}<br>
387
- *
388
- * <strong>Note that the caller has to explicitly update the class value and as well as the class's property
389
- * <code>constructor</code> to its prototype's constructor.</strong><br>
390
- *
391
- * This throws an error when composing functions with return values; unless if the composed function is a
392
- * constructor, which is handled specially.
393
- *
394
- * Note that all properties must be functions for this to work as expected.
395
- *
396
- * If the destination and source have the methods with the same name (key), assign a new function
397
- * that calls both with the given arguments. The arguments list passed to each subfunction will be the
398
- * argument list that was called to the composite function.
399
- *
400
- * This function only works with functions, getters and setters.
401
- *
402
- * TODO: make a lot more robust
403
- * TODO: rethink my ways... this is evil
404
- */
405
- /* export function extendProto(destination, source) {
406
- for (let name in source) {
407
- const extendMethod = (sourceDescriptor, which) => {
408
- let sourceFn = sourceDescriptor[which],
409
- origDestDescriptor = Object.getOwnPropertyDescriptor(destination, name),
410
- origDestFn = origDestDescriptor ? origDestDescriptor[which] : undefined;
411
- let destFn = !origDestFn ? sourceFn : function compositeMethod() { // `function` or `()` ?
412
- try {
413
- // |.apply()| because we're seperating the method from the object, so return the value
414
- // of |this| back to the function
415
- let r1 = origDestFn.apply(this, arguments),
416
- r2 = sourceFn.apply(this, arguments);
417
- if (r1 || r2) throw "Return value in composite method"; // null will slip by ig
418
- } catch (e) {
419
- if (e.toString() === "TypeError: class constructors must be invoked with |new|") {
420
- let inst = new origDestFn(...arguments);
421
- sourceFn.apply(inst, arguments);
422
- return inst;
423
- } else throw e;
424
- }
425
- };
426
-
427
- let destDescriptor = {...sourceDescriptor}; // shallow clone
428
- destDescriptor[which] = destFn;
429
- Object.defineProperty(destination, name, destDescriptor);
430
- };
431
-
432
- let descriptor = Object.getOwnPropertyDescriptor(source, name);
433
- if (descriptor) { // if hasOwnProperty
434
- if (descriptor.get) extendMethod(descriptor, 'get');
435
- if (descriptor.set) extendMethod(descriptor, 'set');
436
- if (descriptor.value) extendMethod(descriptor, 'value');
437
- }
438
- }
439
- } */
440
-
441
- // TODO: remove this function
442
- export function mapPixels (mapper, canvas, ctx, x, y, width, height, flush = true) {
443
- x = x || 0
444
- y = y || 0
445
- width = width || canvas.width
446
- height = height || canvas.height
447
- const frame = ctx.getImageData(x, y, width, height)
448
- for (let i = 0, l = frame.data.length; i < l; i += 4) {
449
- mapper(frame.data, i)
450
- }
451
- if (flush) {
452
- ctx.putImageData(frame, x, y)
453
- }
454
- }
455
-
456
- /**
457
- * <p>Emits "change" event when public properties updated, recursively
458
- * <p>Must be called before any watchable properties are set, and only once in the prototype chain
459
- *
460
- * @param {object} target - object to watch
461
- */
462
- export function watchPublic (target) {
463
- const getPath = (receiver, prop) =>
464
- (receiver === proxy ? '' : (paths.get(receiver) + '.')) + prop
465
- const callback = function (prop, val, receiver) {
466
- // Public API property updated, emit 'modify' event.
467
- publish(proxy, `${target.type}.change.modify`, { property: getPath(receiver, prop), newValue: val })
468
- }
469
- const check = prop => !(prop.startsWith('_') || target.publicExcludes.includes(prop))
470
-
471
- const paths = new WeakMap() // the path to each child property (each is a unique proxy)
472
-
473
- const handler = {
474
- set (obj, prop, val, receiver) {
475
- // Recurse
476
- if (typeof val === 'object' && val !== null && !paths.has(val) && check(prop)) {
477
- val = new Proxy(val, handler)
478
- paths.set(val, getPath(receiver, prop))
479
- }
480
-
481
- const was = prop in obj
482
- // set property or attribute
483
- // Search prototype chain for the closest setter
484
- let objProto = obj
485
- while ((objProto = Object.getPrototypeOf(objProto))) {
486
- const propDesc = Object.getOwnPropertyDescriptor(objProto, prop)
487
- if (propDesc && propDesc.set) {
488
- propDesc.set.call(receiver, val) // call setter, supplying proxy as this (fixes event bugs)
489
- break
490
- }
491
- }
492
- if (!objProto) { // couldn't find setter; set value on instance
493
- obj[prop] = val
494
- }
495
- // Check if it already existed and if it's a valid property to watch, if on root object
496
- if (obj !== target || (was && check(prop))) {
497
- callback(prop, val, receiver)
498
- }
499
- return true
500
- }
501
- }
502
-
503
- const proxy = new Proxy(target, handler)
504
- return proxy
505
- }