wavesurfer.js 6.5.1 → 7.0.0-alpha.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 (132) hide show
  1. package/.eslintrc.json +24 -0
  2. package/.prettierrc +8 -0
  3. package/README.md +26 -117
  4. package/dist/base-plugin.d.ts +10 -0
  5. package/dist/base-plugin.js +13 -0
  6. package/dist/decoder.d.ts +10 -0
  7. package/dist/decoder.js +43 -0
  8. package/dist/event-emitter.d.ts +11 -0
  9. package/dist/event-emitter.js +21 -0
  10. package/dist/fetcher.d.ts +4 -0
  11. package/dist/fetcher.js +17 -0
  12. package/dist/index.d.ts +92 -0
  13. package/dist/index.js +166 -0
  14. package/dist/player-webaudio.d.ts +8 -0
  15. package/dist/player-webaudio.js +31 -0
  16. package/dist/player.d.ts +16 -0
  17. package/dist/player.js +40 -0
  18. package/dist/plugins/regions.d.ts +48 -0
  19. package/dist/plugins/regions.js +183 -0
  20. package/dist/renderer.d.ts +34 -0
  21. package/dist/renderer.js +193 -0
  22. package/dist/timer.d.ts +10 -0
  23. package/dist/timer.js +17 -0
  24. package/examples/audio.ogg +0 -0
  25. package/examples/bars.js +19 -0
  26. package/examples/basic.js +8 -0
  27. package/examples/gradient.js +28 -0
  28. package/examples/regions.js +63 -0
  29. package/examples/video.js +19 -0
  30. package/examples/webaudio.js +14 -0
  31. package/package.json +19 -99
  32. package/src/base-plugin.ts +20 -0
  33. package/src/decoder.ts +41 -0
  34. package/src/event-emitter.ts +35 -0
  35. package/src/fetcher.ts +7 -0
  36. package/src/index.ts +252 -0
  37. package/src/player-webaudio.ts +34 -0
  38. package/src/player.ts +50 -0
  39. package/src/plugins/regions.ts +240 -0
  40. package/src/renderer.ts +250 -0
  41. package/src/timer.ts +27 -0
  42. package/tsconfig.json +105 -0
  43. package/tutorial/index.html +47 -0
  44. package/tutorial/src/editor.js +70 -0
  45. package/tutorial/src/init.js +66 -0
  46. package/tutorial/src/url.js +25 -0
  47. package/tutorial/style.css +211 -0
  48. package/yarn-error.log +1049 -0
  49. package/LICENSE +0 -29
  50. package/dist/plugin/wavesurfer.cursor.js +0 -418
  51. package/dist/plugin/wavesurfer.cursor.js.map +0 -1
  52. package/dist/plugin/wavesurfer.cursor.min.js +0 -7
  53. package/dist/plugin/wavesurfer.cursor.min.js.map +0 -1
  54. package/dist/plugin/wavesurfer.elan.js +0 -381
  55. package/dist/plugin/wavesurfer.elan.js.map +0 -1
  56. package/dist/plugin/wavesurfer.elan.min.js +0 -7
  57. package/dist/plugin/wavesurfer.elan.min.js.map +0 -1
  58. package/dist/plugin/wavesurfer.markers.js +0 -520
  59. package/dist/plugin/wavesurfer.markers.js.map +0 -1
  60. package/dist/plugin/wavesurfer.markers.min.js +0 -7
  61. package/dist/plugin/wavesurfer.markers.min.js.map +0 -1
  62. package/dist/plugin/wavesurfer.mediasession.js +0 -181
  63. package/dist/plugin/wavesurfer.mediasession.js.map +0 -1
  64. package/dist/plugin/wavesurfer.mediasession.min.js +0 -7
  65. package/dist/plugin/wavesurfer.mediasession.min.js.map +0 -1
  66. package/dist/plugin/wavesurfer.microphone.js +0 -466
  67. package/dist/plugin/wavesurfer.microphone.js.map +0 -1
  68. package/dist/plugin/wavesurfer.microphone.min.js +0 -7
  69. package/dist/plugin/wavesurfer.microphone.min.js.map +0 -1
  70. package/dist/plugin/wavesurfer.minimap.js +0 -440
  71. package/dist/plugin/wavesurfer.minimap.js.map +0 -1
  72. package/dist/plugin/wavesurfer.minimap.min.js +0 -7
  73. package/dist/plugin/wavesurfer.minimap.min.js.map +0 -1
  74. package/dist/plugin/wavesurfer.playhead.js +0 -300
  75. package/dist/plugin/wavesurfer.playhead.js.map +0 -1
  76. package/dist/plugin/wavesurfer.playhead.min.js +0 -7
  77. package/dist/plugin/wavesurfer.playhead.min.js.map +0 -1
  78. package/dist/plugin/wavesurfer.regions.js +0 -1335
  79. package/dist/plugin/wavesurfer.regions.js.map +0 -1
  80. package/dist/plugin/wavesurfer.regions.min.js +0 -7
  81. package/dist/plugin/wavesurfer.regions.min.js.map +0 -1
  82. package/dist/plugin/wavesurfer.spectrogram.js +0 -736
  83. package/dist/plugin/wavesurfer.spectrogram.js.map +0 -1
  84. package/dist/plugin/wavesurfer.spectrogram.min.js +0 -7
  85. package/dist/plugin/wavesurfer.spectrogram.min.js.map +0 -1
  86. package/dist/plugin/wavesurfer.timeline.js +0 -649
  87. package/dist/plugin/wavesurfer.timeline.js.map +0 -1
  88. package/dist/plugin/wavesurfer.timeline.min.js +0 -7
  89. package/dist/plugin/wavesurfer.timeline.min.js.map +0 -1
  90. package/dist/wavesurfer-html-init.js +0 -381
  91. package/dist/wavesurfer-html-init.js.map +0 -1
  92. package/dist/wavesurfer-html-init.min.js +0 -7
  93. package/dist/wavesurfer-html-init.min.js.map +0 -1
  94. package/dist/wavesurfer.js +0 -6145
  95. package/dist/wavesurfer.js.map +0 -1
  96. package/dist/wavesurfer.min.js +0 -7
  97. package/dist/wavesurfer.min.js.map +0 -1
  98. package/src/drawer.canvasentry.js +0 -427
  99. package/src/drawer.js +0 -413
  100. package/src/drawer.multicanvas.js +0 -617
  101. package/src/html-init.js +0 -241
  102. package/src/mediaelement-webaudio.js +0 -77
  103. package/src/mediaelement.js +0 -443
  104. package/src/peakcache.js +0 -127
  105. package/src/plugin/cursor/index.js +0 -350
  106. package/src/plugin/elan/index.js +0 -307
  107. package/src/plugin/markers/index.js +0 -436
  108. package/src/plugin/mediasession/index.js +0 -94
  109. package/src/plugin/microphone/index.js +0 -415
  110. package/src/plugin/minimap/index.js +0 -403
  111. package/src/plugin/playhead/index.js +0 -226
  112. package/src/plugin/regions/index.js +0 -478
  113. package/src/plugin/regions/region.js +0 -875
  114. package/src/plugin/spectrogram/fft.js +0 -222
  115. package/src/plugin/spectrogram/index.js +0 -522
  116. package/src/plugin/timeline/index.js +0 -600
  117. package/src/util/absMax.js +0 -16
  118. package/src/util/clamp.js +0 -11
  119. package/src/util/fetch.js +0 -222
  120. package/src/util/frame.js +0 -13
  121. package/src/util/get-id.js +0 -22
  122. package/src/util/index.js +0 -14
  123. package/src/util/max.js +0 -16
  124. package/src/util/min.js +0 -16
  125. package/src/util/observer.js +0 -144
  126. package/src/util/orientation.js +0 -98
  127. package/src/util/prevent-click.js +0 -19
  128. package/src/util/request-animation-frame.js +0 -15
  129. package/src/util/silence-mode.js +0 -42
  130. package/src/util/style.js +0 -16
  131. package/src/wavesurfer.js +0 -1797
  132. package/src/webaudio.js +0 -779
@@ -1,478 +0,0 @@
1
- /**
2
- * @since 4.0.0 This class has been split
3
- *
4
- * @typedef {Object} RegionsPluginParams
5
- * @property {?boolean} dragSelection Enable creating regions by dragging with
6
- * the mouse
7
- * @property {?boolean} contentEditable=false Allow/disallow editing content of the region
8
- * @property {?boolean} removeButton=false adds remove region button
9
- * @property {?RegionParams[]} regions Regions that should be added upon
10
- * initialisation
11
- * @property {number} slop=2 The sensitivity of the mouse dragging
12
- * @property {?number} snapToGridInterval Snap the regions to a grid of the specified multiples in seconds
13
- * @property {?number} snapToGridOffset Shift the snap-to-grid by the specified seconds. May also be negative.
14
- * @property {?boolean} deferInit Set to true to manually call
15
- * @property {number} maxRegions Maximum number of regions that may be created by the user at one time.
16
- * `initPlugin('regions')`
17
- * @property {function} formatTimeCallback Allows custom formating for region tooltip.
18
- * @property {?number} edgeScrollWidth='5% from container edges' Optional width for edgeScroll to start
19
- */
20
-
21
- /**
22
- * @typedef {Object} RegionParams
23
- * @desc The parameters used to describe a region.
24
- * @example wavesurfer.addRegion(regionParams);
25
- * @property {string} id=→random The id of the region
26
- * @property {number} start=0 The start position of the region (in seconds).
27
- * @property {number} end=0 The end position of the region (in seconds).
28
- * @property {?boolean} loop Whether to loop the region when played back.
29
- * @property {boolean} drag=true Allow/disallow dragging the region.
30
- * @property {boolean} resize=true Allow/disallow resizing the region.
31
- * @property {string} [color='rgba(0, 0, 0, 0.1)'] HTML color code.
32
- * @property {?number} channelIdx Select channel to draw the region on (if there are multiple channel waveforms).
33
- * @property {?object} handleStyle A set of CSS properties used to style the left and right handle.
34
- * @property {?boolean} preventContextMenu=false Determines whether the context menu is prevented from being opened.
35
- * @property {boolean} showTooltip=true Enable/disable tooltip displaying start and end times when hovering over region.
36
- */
37
-
38
- import {Region} from "./region.js";
39
-
40
- /**
41
- * Regions are visual overlays on waveform that can be used to play and loop
42
- * portions of audio. Regions can be dragged and resized.
43
- *
44
- * Visual customization is possible via CSS (using the selectors
45
- * `.wavesurfer-region` and `.wavesurfer-handle`).
46
- *
47
- * @implements {PluginClass}
48
- * @extends {Observer}
49
- *
50
- * @example
51
- * // es6
52
- * import RegionsPlugin from 'wavesurfer.regions.js';
53
- *
54
- * // commonjs
55
- * var RegionsPlugin = require('wavesurfer.regions.js');
56
- *
57
- * // if you are using <script> tags
58
- * var RegionsPlugin = window.WaveSurfer.regions;
59
- *
60
- * // ... initialising wavesurfer with the plugin
61
- * var wavesurfer = WaveSurfer.create({
62
- * // wavesurfer options ...
63
- * plugins: [
64
- * RegionsPlugin.create({
65
- * // plugin options ...
66
- * })
67
- * ]
68
- * });
69
- */
70
- export default class RegionsPlugin {
71
- /**
72
- * Regions plugin definition factory
73
- *
74
- * This function must be used to create a plugin definition which can be
75
- * used by wavesurfer to correctly instantiate the plugin.
76
- *
77
- * @param {RegionsPluginParams} params parameters use to initialise the plugin
78
- * @return {PluginDefinition} an object representing the plugin
79
- */
80
- static create(params) {
81
- return {
82
- name: 'regions',
83
- deferInit: params && params.deferInit ? params.deferInit : false,
84
- params: params,
85
- staticProps: {
86
- addRegion(options) {
87
- if (!this.initialisedPluginList.regions) {
88
- this.initPlugin('regions');
89
- }
90
- return this.regions.add(options);
91
- },
92
-
93
- clearRegions() {
94
- this.regions && this.regions.clear();
95
- },
96
-
97
- enableDragSelection(options) {
98
- if (!this.initialisedPluginList.regions) {
99
- this.initPlugin('regions');
100
- }
101
- this.regions.enableDragSelection(options);
102
- },
103
-
104
- disableDragSelection() {
105
- this.regions.disableDragSelection();
106
- }
107
- },
108
- instance: RegionsPlugin
109
- };
110
- }
111
-
112
- constructor(params, ws) {
113
- this.params = params;
114
- this.wavesurfer = ws;
115
- this.util = {
116
- ...ws.util,
117
- getRegionSnapToGridValue: value => {
118
- return this.getRegionSnapToGridValue(value, params);
119
- }
120
- };
121
- this.maxRegions = params.maxRegions;
122
- this.regionsMinLength = params.regionsMinLength || null;
123
-
124
- // turn the plugin instance into an observer
125
- const observerPrototypeKeys = Object.getOwnPropertyNames(
126
- this.util.Observer.prototype
127
- );
128
- observerPrototypeKeys.forEach(key => {
129
- Region.prototype[key] = this.util.Observer.prototype[key];
130
- });
131
- this.wavesurfer.Region = Region;
132
-
133
- // By default, scroll the container if the user drags a region
134
- // within 5% (based on its initial size) of its edge
135
- const scrollWidthProportion = 0.05;
136
- this._onBackendCreated = () => {
137
- this.wrapper = this.wavesurfer.drawer.wrapper;
138
- this.orientation = this.wavesurfer.drawer.orientation;
139
- this.defaultEdgeScrollWidth = this.wrapper.clientWidth * scrollWidthProportion;
140
- if (this.params.regions) {
141
- this.params.regions.forEach(region => {
142
- this.add(region);
143
- });
144
- }
145
- };
146
-
147
- // Id-based hash of regions
148
- this.list = {};
149
- this._onReady = () => {
150
- this.wrapper = this.wavesurfer.drawer.wrapper;
151
- this.vertical = this.wavesurfer.drawer.params.vertical;
152
- if (this.params.dragSelection) {
153
- this.enableDragSelection(this.params);
154
- }
155
- Object.keys(this.list).forEach(id => {
156
- this.list[id].updateRender();
157
- });
158
- };
159
- }
160
-
161
- init() {
162
- // Check if ws is ready
163
- if (this.wavesurfer.isReady) {
164
- this._onBackendCreated();
165
- this._onReady();
166
- } else {
167
- this.wavesurfer.once('ready', this._onReady);
168
- this.wavesurfer.once('backend-created', this._onBackendCreated);
169
- }
170
- }
171
-
172
- destroy() {
173
- this.wavesurfer.un('ready', this._onReady);
174
- this.wavesurfer.un('backend-created', this._onBackendCreated);
175
- // Disabling `region-removed' because destroying the plugin calls
176
- // the Region.remove() method that is also used to remove regions based
177
- // on user input. This can cause confusion since teardown is not a
178
- // user event, but would emit `region-removed` as if it was.
179
- this.wavesurfer.setDisabledEventEmissions(['region-removed']);
180
- this.disableDragSelection();
181
- this.clear();
182
- }
183
-
184
- /**
185
- * check to see if adding a new region would exceed maxRegions
186
- * @return {boolean} whether we should proceed and create a region
187
- * @private
188
- */
189
- wouldExceedMaxRegions() {
190
- return (
191
- this.maxRegions && Object.keys(this.list).length >= this.maxRegions
192
- );
193
- }
194
-
195
- /**
196
- * Add a region
197
- *
198
- * @param {object} params Region parameters
199
- * @return {Region} The created region
200
- */
201
- add(params) {
202
- if (this.wouldExceedMaxRegions()) {
203
- return null;
204
- }
205
-
206
- params = {
207
- edgeScrollWidth: this.params.edgeScrollWidth || this.defaultEdgeScrollWidth,
208
- contentEditable: this.params.contentEditable,
209
- removeButton: this.params.removeButton,
210
- ...params
211
- };
212
-
213
- // Take formatTimeCallback from plugin params if not already set
214
- if (!params.formatTimeCallback && this.params.formatTimeCallback) {
215
- params = {...params, formatTimeCallback: this.params.formatTimeCallback};
216
- }
217
-
218
- if (!params.minLength && this.regionsMinLength) {
219
- params = {...params, minLength: this.regionsMinLength};
220
- }
221
-
222
- const region = new this.wavesurfer.Region(params, this.util, this.wavesurfer);
223
-
224
- this.list[region.id] = region;
225
-
226
- region.on('remove', () => {
227
- delete this.list[region.id];
228
- });
229
-
230
- return region;
231
- }
232
-
233
- /**
234
- * Remove all regions
235
- */
236
- clear() {
237
- Object.keys(this.list).forEach(id => {
238
- this.list[id].remove();
239
- });
240
- }
241
-
242
- enableDragSelection(params) {
243
- this.disableDragSelection();
244
-
245
- const slop = params.slop || 2;
246
- const container = this.wavesurfer.drawer.container;
247
- const scroll =
248
- params.scroll !== false && this.wavesurfer.params.scrollParent;
249
- const scrollSpeed = params.scrollSpeed || 1;
250
- const scrollThreshold = params.scrollThreshold || 10;
251
- let drag;
252
- let duration = this.wavesurfer.getDuration();
253
- let maxScroll;
254
- let start;
255
- let region;
256
- let touchId;
257
- let pxMove = 0;
258
- let scrollDirection;
259
- let wrapperRect;
260
-
261
- // Scroll when the user is dragging within the threshold
262
- const edgeScroll = e => {
263
- if (!region || !scrollDirection) {
264
- return;
265
- }
266
-
267
- // Update scroll position
268
- let scrollLeft =
269
- this.wrapper.scrollLeft + scrollSpeed * scrollDirection;
270
- this.wrapper.scrollLeft = scrollLeft = Math.min(
271
- maxScroll,
272
- Math.max(0, scrollLeft)
273
- );
274
-
275
- // Update range
276
- const end = this.wavesurfer.drawer.handleEvent(e);
277
- region.update({
278
- start: Math.min(end * duration, start * duration),
279
- end: Math.max(end * duration, start * duration)
280
- });
281
-
282
- // Check that there is more to scroll and repeat
283
- if (scrollLeft < maxScroll && scrollLeft > 0) {
284
- window.requestAnimationFrame(() => {
285
- edgeScroll(e);
286
- });
287
- }
288
- };
289
-
290
- const eventDown = e => {
291
- if (e.touches && e.touches.length > 1) {
292
- return;
293
- }
294
- duration = this.wavesurfer.getDuration();
295
- touchId = e.targetTouches ? e.targetTouches[0].identifier : null;
296
-
297
- // Store for scroll calculations
298
- maxScroll = this.wrapper.scrollWidth -
299
- this.wrapper.clientWidth;
300
- wrapperRect = this.util.withOrientation(
301
- this.wrapper.getBoundingClientRect(),
302
- this.vertical
303
- );
304
-
305
- // set the region channel index based on the clicked area
306
- if (this.wavesurfer.params.splitChannels && this.wavesurfer.params.splitChannelsOptions.splitDragSelection) {
307
- const y = (e.touches ? e.touches[0].clientY : e.clientY) - wrapperRect.top;
308
- const channelCount = this.wavesurfer.backend.buffer != null ? this.wavesurfer.backend.buffer.numberOfChannels : 1;
309
- const channelHeight = this.wrapper.clientHeight / channelCount;
310
- const channelIdx = Math.floor(y / channelHeight);
311
- params.channelIdx = channelIdx;
312
- const channelColors = this.wavesurfer.params.splitChannelsOptions.channelColors[channelIdx];
313
- if (channelColors && channelColors.dragColor) {
314
- params.color = channelColors.dragColor;
315
- }
316
- }
317
-
318
- drag = true;
319
- start = this.wavesurfer.drawer.handleEvent(e, true);
320
- region = null;
321
- scrollDirection = null;
322
- };
323
- this.wrapper.addEventListener('mousedown', eventDown);
324
- this.wrapper.addEventListener('touchstart', eventDown);
325
- this.on('disable-drag-selection', () => {
326
- this.wrapper.removeEventListener('touchstart', eventDown);
327
- this.wrapper.removeEventListener('mousedown', eventDown);
328
- });
329
-
330
- const eventUp = e => {
331
- if (e.touches && e.touches.length > 1) {
332
- return;
333
- }
334
-
335
- drag = false;
336
- pxMove = 0;
337
- scrollDirection = null;
338
-
339
- if (region) {
340
- this.util.preventClick();
341
- region.fireEvent('update-end', e);
342
- this.wavesurfer.fireEvent('region-update-end', region, e);
343
- }
344
-
345
- region = null;
346
- };
347
- this.wrapper.addEventListener('mouseleave', eventUp);
348
- this.wrapper.addEventListener('mouseup', eventUp);
349
- this.wrapper.addEventListener('touchend', eventUp);
350
-
351
- document.body.addEventListener('mouseup', eventUp);
352
- document.body.addEventListener('touchend', eventUp);
353
- this.on('disable-drag-selection', () => {
354
- document.body.removeEventListener('mouseup', eventUp);
355
- document.body.removeEventListener('touchend', eventUp);
356
- this.wrapper.removeEventListener('touchend', eventUp);
357
- this.wrapper.removeEventListener('mouseup', eventUp);
358
- this.wrapper.removeEventListener('mouseleave', eventUp);
359
- });
360
-
361
- const eventMove = event => {
362
- if (!drag) {
363
- return;
364
- }
365
- if (++pxMove <= slop) {
366
- return;
367
- }
368
-
369
- if (event.touches && event.touches.length > 1) {
370
- return;
371
- }
372
- if (event.targetTouches && event.targetTouches[0].identifier != touchId) {
373
- return;
374
- }
375
-
376
- // auto-create a region during mouse drag, unless region-count would exceed "maxRegions"
377
- if (!region) {
378
- region = this.add(params || {});
379
- if (!region) {
380
- return;
381
- }
382
- }
383
-
384
- const end = this.wavesurfer.drawer.handleEvent(event);
385
- const startUpdate = this.wavesurfer.regions.util.getRegionSnapToGridValue(
386
- start * duration
387
- );
388
- const endUpdate = this.wavesurfer.regions.util.getRegionSnapToGridValue(
389
- end * duration
390
- );
391
- region.update({
392
- start: Math.min(endUpdate, startUpdate),
393
- end: Math.max(endUpdate, startUpdate)
394
- });
395
-
396
- let orientedEvent = this.util.withOrientation(event, this.vertical);
397
-
398
- // If scrolling is enabled
399
- if (scroll && container.clientWidth < this.wrapper.scrollWidth) {
400
- // Check threshold based on mouse
401
- const x = orientedEvent.clientX - wrapperRect.left;
402
- if (x <= scrollThreshold) {
403
- scrollDirection = -1;
404
- } else if (x >= wrapperRect.right - scrollThreshold) {
405
- scrollDirection = 1;
406
- } else {
407
- scrollDirection = null;
408
- }
409
- scrollDirection && edgeScroll(event);
410
- }
411
- };
412
- this.wrapper.addEventListener('mousemove', eventMove);
413
- this.wrapper.addEventListener('touchmove', eventMove);
414
- this.on('disable-drag-selection', () => {
415
- this.wrapper.removeEventListener('touchmove', eventMove);
416
- this.wrapper.removeEventListener('mousemove', eventMove);
417
- });
418
-
419
- this.wavesurfer.on('region-created', region => {
420
- if (this.regionsMinLength) {
421
- region.minLength = this.regionsMinLength;
422
- }
423
- });
424
- }
425
-
426
- disableDragSelection() {
427
- this.fireEvent('disable-drag-selection');
428
- }
429
-
430
- /**
431
- * Get current region
432
- *
433
- * The smallest region that contains the current time. If several such
434
- * regions exist, take the first. Return `null` if none exist.
435
- *
436
- * @returns {Region} The current region
437
- */
438
- getCurrentRegion() {
439
- const time = this.wavesurfer.getCurrentTime();
440
- let min = null;
441
- Object.keys(this.list).forEach(id => {
442
- const cur = this.list[id];
443
- if (cur.start <= time && cur.end >= time) {
444
- if (!min || cur.end - cur.start < min.end - min.start) {
445
- min = cur;
446
- }
447
- }
448
- });
449
-
450
- return min;
451
- }
452
-
453
- /**
454
- * Match the value to the grid, if required
455
- *
456
- * If the regions plugin params have a snapToGridInterval set, return the
457
- * value matching the nearest grid interval. If no snapToGridInterval is set,
458
- * the passed value will be returned without modification.
459
- *
460
- * @param {number} value the value to snap to the grid, if needed
461
- * @param {Object} params the regions plugin params
462
- * @returns {number} value
463
- */
464
- getRegionSnapToGridValue(value, params) {
465
- if (params.snapToGridInterval) {
466
- // the regions should snap to a grid
467
- const offset = params.snapToGridOffset || 0;
468
- return (
469
- Math.round((value - offset) / params.snapToGridInterval) *
470
- params.snapToGridInterval +
471
- offset
472
- );
473
- }
474
-
475
- // no snap-to-grid
476
- return value;
477
- }
478
- }