fragment-tools 0.2.11 → 0.2.13

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 (67) hide show
  1. package/package.json +12 -11
  2. package/src/cli/build.js +1 -0
  3. package/src/cli/create.js +22 -4
  4. package/src/cli/createConfig.js +2 -2
  5. package/src/cli/getEntries.js +10 -1
  6. package/src/cli/plugins/hot-shader-replacement.js +54 -16
  7. package/src/cli/plugins/save.js +97 -38
  8. package/src/cli/prompts.js +89 -36
  9. package/src/cli/run.js +1 -1
  10. package/src/cli/templates/blank/index.ts +1 -1
  11. package/src/cli/templates/default/index.js +10 -2
  12. package/src/cli/templates/default/index.ts +5 -2
  13. package/src/cli/templates/fragment-gl/index.ts +1 -1
  14. package/src/cli/templates/p5/index.ts +1 -1
  15. package/src/cli/templates/p5-webgl/index.ts +1 -1
  16. package/src/cli/templates/three-fragment/index.js +5 -3
  17. package/src/cli/templates/three-fragment/index.ts +5 -4
  18. package/src/cli/templates/three-orthographic/index.js +6 -1
  19. package/src/cli/templates/three-orthographic/index.ts +6 -2
  20. package/src/cli/templates/three-perspective/index.js +6 -1
  21. package/src/cli/templates/three-perspective/index.ts +6 -2
  22. package/src/client/app/actions/resize.js +8 -1
  23. package/src/client/app/attachments/draggable.js +93 -0
  24. package/src/client/app/client.js +90 -18
  25. package/src/client/app/components/IconFlip.svelte +46 -0
  26. package/src/client/app/hooks.js +25 -1
  27. package/src/client/app/lib/canvas-recorder/CanvasRecorder.js +95 -3
  28. package/src/client/app/lib/canvas-recorder/FrameRecorder.js +45 -3
  29. package/src/client/app/lib/canvas-recorder/GIFRecorder.js +72 -13
  30. package/src/client/app/lib/canvas-recorder/MediaBunnyRecorder.js +43 -9
  31. package/src/client/app/lib/canvas-recorder/utils.js +18 -9
  32. package/src/client/app/modules/Params.svelte +1 -0
  33. package/src/client/app/renderers/2DRenderer.js +20 -16
  34. package/src/client/app/renderers/P5GLRenderer.js +13 -5
  35. package/src/client/app/renderers/P5Renderer.js +9 -1
  36. package/src/client/app/renderers/THREERenderer.js +63 -48
  37. package/src/client/app/state/Sketch.svelte.js +150 -10
  38. package/src/client/app/state/errors.svelte.js +19 -0
  39. package/src/client/app/state/exports.svelte.js +14 -1
  40. package/src/client/app/state/rendering.svelte.js +90 -13
  41. package/src/client/app/state/sketches.svelte.js +43 -7
  42. package/src/client/app/state/utils.svelte.js +49 -0
  43. package/src/client/app/ui/Field.svelte +63 -16
  44. package/src/client/app/ui/FieldSection.svelte +4 -4
  45. package/src/client/app/ui/ParamsOutput.svelte +7 -5
  46. package/src/client/app/ui/SketchRenderer.svelte +21 -0
  47. package/src/client/app/ui/fields/ButtonInput.svelte +2 -0
  48. package/src/client/app/ui/fields/CheckboxInput.svelte +13 -11
  49. package/src/client/app/ui/fields/ColorInput.svelte +16 -11
  50. package/src/client/app/ui/fields/GradientInput.svelte +607 -0
  51. package/src/client/app/ui/fields/Input.svelte +10 -6
  52. package/src/client/app/ui/fields/IntervalInput.svelte +27 -35
  53. package/src/client/app/ui/fields/NumberInput.svelte +51 -13
  54. package/src/client/app/ui/fields/PaletteInput.svelte +181 -0
  55. package/src/client/app/ui/fields/ProgressInput.svelte +44 -16
  56. package/src/client/app/ui/fields/TextareaInput.svelte +93 -0
  57. package/src/client/app/utils/canvas.utils.js +105 -28
  58. package/src/client/app/utils/color.utils.js +74 -17
  59. package/src/client/app/utils/fields.utils.js +70 -17
  60. package/src/client/app/utils/file.utils.js +86 -31
  61. package/src/client/app/utils/glsl.utils.js +11 -2
  62. package/src/client/app/utils/glslErrors.js +31 -21
  63. package/src/client/app/utils/index.js +28 -12
  64. package/src/client/main.js +7 -1
  65. package/src/types/global.d.ts +143 -0
  66. package/src/types/props.d.ts +41 -15
  67. package/tsconfig.json +1 -1
@@ -1,3 +1,4 @@
1
+ import { styleText } from 'node:util';
1
2
  import { ConfirmPrompt, SelectPrompt, TextPrompt } from '@clack/core';
2
3
  import isUnicodeSupported from 'is-unicode-supported';
3
4
  import * as color from 'kleur/colors';
@@ -23,6 +24,7 @@ const S_BAR = s('│', '|');
23
24
 
24
25
  /**
25
26
  * @param {object} opts
27
+ * @param {string} opts.message
26
28
  * @param {string} opts.active
27
29
  * @param {string} opts.inactive
28
30
  * @param {boolean} opts.initialValue
@@ -67,9 +69,9 @@ export const confirm = (opts) => {
67
69
  * @param {string} [opts.placeholder]
68
70
  * @param {string} [opts.defaultValue]
69
71
  * @param {string} [opts.initialValue]
70
- * @param {string} [opts.initialValue]
71
- * @param {function} [opts.validate]
72
- * @returns {Promise<string|symbol>}
72
+ * @param {string} [opts.hint]
73
+ * @param {(value: string | undefined) => any} [opts.validate]
74
+ * @returns {Promise<string | symbol | undefined>}
73
75
  */
74
76
  export const text = (opts) => {
75
77
  const { hint = '' } = opts;
@@ -82,24 +84,36 @@ export const text = (opts) => {
82
84
  render() {
83
85
  const title = `${opts.message}\n`;
84
86
  const placeholder = opts.placeholder
85
- ? color.inverse(opts.placeholder[0]) +
86
- color.dim(opts.placeholder.slice(1))
87
- : color.inverse(color.hidden('_'));
88
- const value = !this.value
89
- ? `${placeholder} ${color.dim(hint)}`.trim()
90
- : this.valueWithCursor;
87
+ ? styleText('inverse', opts.placeholder[0]) +
88
+ styleText('dim', opts.placeholder.slice(1))
89
+ : styleText(['inverse', 'hidden'], '_');
90
+ const userInput = !this.userInput
91
+ ? `${placeholder} ${styleText('dim', hint)}`.trim()
92
+ : this.userInputWithCursor;
93
+ const value = this.value ?? '';
91
94
 
92
95
  switch (this.state) {
93
- case 'error':
94
- return `${title.trim()}\n${value}\n ${color.yellow(this.error)}\n`;
95
- case 'submit':
96
- return `${color.dim(`${title}`)} ${color.green(this.value || opts.placeholder)}\n`;
97
- case 'cancel':
98
- return `${title} ${color.strikethrough(
99
- color.dim(this.value ?? placeholder),
100
- )}\n`;
101
- default:
102
- return `${title} ${value}\n`;
96
+ case 'error': {
97
+ const errorText = this.error
98
+ ? ` ${styleText('yellow', this.error)}`
99
+ : '';
100
+ return `${title.trim()}\n${userInput}\n${errorText}\n`;
101
+ }
102
+ case 'submit': {
103
+ const valueText = value
104
+ ? `${styleText('green', value)}`
105
+ : '';
106
+ return `${styleText('dim', `${title}`)} ${valueText}\n`;
107
+ }
108
+ case 'cancel': {
109
+ const valueText = value
110
+ ? ` ${styleText(['strikethrough', 'dim'], value)}`
111
+ : '';
112
+ return `${title}${valueText}${value.trim() ? `\n` : ''}`;
113
+ }
114
+ default: {
115
+ return `${title}${userInput}\n\n`;
116
+ }
103
117
  }
104
118
  },
105
119
  }).prompt();
@@ -140,6 +154,22 @@ const limitOptions = (params) => {
140
154
  });
141
155
  };
142
156
 
157
+ /**
158
+ *
159
+ * @param {string} label
160
+ * @param {(text: string) => string} format
161
+ * @returns {string}
162
+ */
163
+ const computeLabel = (label, format) => {
164
+ if (!label.includes('\n')) {
165
+ return format(label);
166
+ }
167
+ return label
168
+ .split('\n')
169
+ .map((line) => format(line))
170
+ .join('\n');
171
+ };
172
+
143
173
  /**
144
174
  * @typedef {string|boolean|number} Value
145
175
  */
@@ -149,12 +179,13 @@ const limitOptions = (params) => {
149
179
  * @property {string} value
150
180
  * @property {string} [label]
151
181
  * @property {string} [hint]
182
+ * @property {boolean} [disabled]
152
183
  */
153
184
 
154
185
  /**
155
186
  * @param {object} opts
156
187
  * @param {string} opts.message
157
- * @param {Option<Value>[]} opts.options
188
+ * @param {import('@clack/core').SelectOptions<Value>} opts.options
158
189
  * @param {Value} [opts.initialValue]
159
190
  * @param {number} [opts.maxItems]
160
191
  * @returns {Promise<boolean|symbol>}
@@ -162,47 +193,69 @@ const limitOptions = (params) => {
162
193
  export const select = (opts) => {
163
194
  /**
164
195
  *
165
- * @param {Option<Value>} option
166
- * @param {'inactive' | 'active' | 'selected' | 'cancelled'} state
196
+ * @param {Option} option
197
+ * @param {'inactive' | 'active' | 'selected' | 'cancelled' | 'disabled'} state
167
198
  * @returns {string}
168
199
  */
169
200
  const opt = (option, state) => {
170
201
  const label = option.label ?? String(option.value);
171
202
  switch (state) {
203
+ case 'disabled':
204
+ return `${styleText('gray', S_RADIO_INACTIVE)} ${computeLabel(label, (text) => styleText('gray', text))}${
205
+ option.hint
206
+ ? ` ${styleText('dim', `(${option.hint ?? 'disabled'})`)}`
207
+ : ''
208
+ }`;
172
209
  case 'selected':
173
- return `${label}`;
210
+ return `${computeLabel(label, (text) => text)}`;
174
211
  case 'active':
175
- return `${color.green(S_RADIO_ACTIVE)} ${label} ${
176
- option.hint ? color.dim(`(${option.hint})`) : ''
212
+ return `${styleText('green', S_RADIO_ACTIVE)} ${label}${
213
+ option.hint
214
+ ? ` ${styleText('dim', `(${option.hint})`)}`
215
+ : ''
177
216
  }`;
178
217
  case 'cancelled':
179
- return `${color.strikethrough(color.dim(label))}`;
218
+ return `${computeLabel(label, (str) => styleText(['strikethrough', 'dim'], str))}`;
180
219
  default:
181
- return `${color.dim(S_RADIO_INACTIVE)} ${color.dim(label)}`;
220
+ return `${styleText('dim', S_RADIO_INACTIVE)} ${computeLabel(label, (text) => styleText('dim', text))}`;
182
221
  }
183
222
  };
184
223
 
185
224
  return new SelectPrompt({
186
225
  options: opts.options,
226
+ signal: opts.signal,
227
+ input: opts.input,
228
+ output: opts.output,
187
229
  initialValue: opts.initialValue,
188
230
  render() {
189
231
  const title = `${opts.message}\n`;
190
232
 
191
233
  switch (this.state) {
192
- case 'submit':
193
- return `${color.dim(title)} ${color.green(opt(this.options[this.cursor], 'selected'))}\n`;
194
- case 'cancel':
195
- return `${title} ${opt(
196
- this.options[this.cursor],
197
- 'cancelled',
198
- )}\n`;
234
+ case 'submit': {
235
+ return `${styleText('dim', title)} ${styleText('green', opt(this.options[this.cursor], 'selected'))}\n`;
236
+ }
237
+ case 'cancel': {
238
+ return `${title}${opt(this.options[this.cursor], 'cancelled')}`;
239
+ }
199
240
  default: {
200
- return `${title} ${limitOptions({
241
+ const titleLineCount = title.split('\n').length;
242
+ const footerLineCount = 1;
243
+ return `${title}${limitOptions({
244
+ output: opts.output,
201
245
  cursor: this.cursor,
202
246
  options: this.options,
203
247
  maxItems: opts.maxItems,
248
+ columnPadding: 0,
249
+ rowPadding: titleLineCount + footerLineCount,
204
250
  style: (item, active) =>
205
- opt(item, active ? 'active' : 'inactive'),
251
+ opt(
252
+ item,
253
+ item.disabled
254
+ ? 'disabled'
255
+ : active
256
+ ? 'active'
257
+ : 'inactive',
258
+ ),
206
259
  }).join(`\n`)}\n`;
207
260
  }
208
261
  }
package/src/cli/run.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  FRAGMENT_DIRECTORY,
8
8
  } from './createFragmentFile.js';
9
9
  import { getEntries } from './getEntries.js';
10
- import { log, magenta, bold, cyan, red } from './log.js';
10
+ import { log, magenta, bold, cyan } from './log.js';
11
11
  import save from './plugins/save.js';
12
12
  import * as p from './prompts.js';
13
13
  import { prettifyTime } from './utils.js';
@@ -1,4 +1,4 @@
1
- import { Init, Resize, Update } from '@fragment/types';
1
+ import type { Init, Resize, Update } from '@fragment/types';
2
2
  import { defineProps } from '@fragment/types/utils';
3
3
 
4
4
  export const props = defineProps({});
@@ -1,3 +1,5 @@
1
+ let resolution = { x: 0, y: 0 };
2
+
1
3
  export const props = {};
2
4
 
3
5
  /**
@@ -24,8 +26,11 @@ export const init = ({ canvas, context, width, height }) => {};
24
26
  * @param {number} params.playcount
25
27
  */
26
28
  export const update = ({ context, width, height, pixelRatio }) => {
29
+ const w = width * pixelRatio;
30
+ const h = height * pixelRatio;
31
+
27
32
  context.fillStyle = 'rgb(0, 255, 0)';
28
- context.fillRect(0, 0, width * pixelRatio, height * pixelRatio);
33
+ context.fillRect(0, 0, w, h);
29
34
  };
30
35
 
31
36
  /**
@@ -35,6 +40,9 @@ export const update = ({ context, width, height, pixelRatio }) => {
35
40
  * @param {number} params.height
36
41
  * @param {number} params.pixelRatio
37
42
  */
38
- export const resize = ({ width, height }) => {};
43
+ export const resize = ({ width, height, pixelRatio }) => {
44
+ resolution.x = width * pixelRatio;
45
+ resolution.y = height * pixelRatio;
46
+ };
39
47
 
40
48
  export const rendering = '2d';
@@ -1,4 +1,4 @@
1
- import { Init, Rendering, Resize, Update } from '@fragment/types';
1
+ import type { Init, Rendering, Resize, Update } from '@fragment/types';
2
2
  import { defineProps } from '@fragment/types/utils';
3
3
 
4
4
  export const props = defineProps({});
@@ -11,8 +11,11 @@ export const update: Update<'2d'> = ({
11
11
  height,
12
12
  pixelRatio,
13
13
  }) => {
14
+ const w = width * pixelRatio;
15
+ const h = height * pixelRatio;
16
+
14
17
  context.fillStyle = 'rgb(0, 255, 0)';
15
- context.fillRect(0, 0, width * pixelRatio, height * pixelRatio);
18
+ context.fillRect(0, 0, w, h);
16
19
  };
17
20
 
18
21
  export const resize: Resize<'2d'> = ({}) => {};
@@ -1,4 +1,4 @@
1
- import { Init, Rendering, Update } from '@fragment/types';
1
+ import type { Init, Rendering, Update } from '@fragment/types';
2
2
 
3
3
  import fragmentShader from './fragment.fs';
4
4
 
@@ -1,6 +1,6 @@
1
1
  import p5 from 'p5';
2
2
 
3
- import { Init, Rendering, Update } from '@fragment/types';
3
+ import type { Init, Rendering, Update } from '@fragment/types';
4
4
  import { defineProps } from '@fragment/types/utils';
5
5
 
6
6
  export const props = defineProps({});
@@ -1,6 +1,6 @@
1
1
  import p5, { type Shader } from 'p5';
2
2
 
3
- import { Init, Rendering, Update } from '@fragment/types';
3
+ import type { Init, Rendering, Update } from '@fragment/types';
4
4
  import { defineProps } from '@fragment/types/utils';
5
5
 
6
6
  import fragmentShader from './fragment.fs';
@@ -6,9 +6,11 @@ import fragmentShader from './fragment.fs';
6
6
  let scene;
7
7
  /** @type {THREE.OrthographicCamera} */
8
8
  let camera;
9
+ /** @type {THREE.Vector2} */
10
+ let resolution = new THREE.Vector2();
9
11
 
10
12
  let uniforms = {
11
- uResolution: { value: new THREE.Vector2() },
13
+ uResolution: { value: resolution },
12
14
  uTime: { value: 0 },
13
15
  };
14
16
 
@@ -87,8 +89,8 @@ export const update = ({ renderer, time, deltaTime }) => {
87
89
  * @param {number} params.pixelRatio
88
90
  */
89
91
  export const resize = ({ width, height, pixelRatio }) => {
90
- uniforms.uResolution.value.x = width * pixelRatio;
91
- uniforms.uResolution.value.y = height * pixelRatio;
92
+ resolution.x = width * pixelRatio;
93
+ resolution.y = height * pixelRatio;
92
94
 
93
95
  camera.left = -width * 0.5;
94
96
  camera.right = width * 0.5;
@@ -1,13 +1,14 @@
1
1
  import * as THREE from 'three';
2
2
 
3
- import { Init, Rendering, Resize, Update } from '@fragment/types';
3
+ import type { Init, Rendering, Resize, Update } from '@fragment/types';
4
4
 
5
5
  import fragmentShader from './fragment.fs';
6
6
 
7
7
  let scene: THREE.Scene;
8
8
  let camera: THREE.OrthographicCamera;
9
+ let resolution = new THREE.Vector2();
9
10
  let uniforms = {
10
- uResolution: { value: new THREE.Vector2() },
11
+ uResolution: { value: resolution },
11
12
  uTime: { value: 0 },
12
13
  };
13
14
 
@@ -54,8 +55,8 @@ export const update: Update<'three'> = ({ renderer, time }) => {
54
55
  };
55
56
 
56
57
  export const resize: Resize<'three'> = ({ width, height, pixelRatio }) => {
57
- uniforms.uResolution.value.x = width * pixelRatio;
58
- uniforms.uResolution.value.y = height * pixelRatio;
58
+ resolution.x = width * pixelRatio;
59
+ resolution.y = height * pixelRatio;
59
60
 
60
61
  camera.left = -width * 0.5;
61
62
  camera.right = width * 0.5;
@@ -4,6 +4,8 @@ import * as THREE from 'three';
4
4
  let scene;
5
5
  /** @type {THREE.OrthographicCamera} */
6
6
  let camera;
7
+ /** @type {THREE.Vector2} */
8
+ let resolution = new THREE.Vector2();
7
9
 
8
10
  /**
9
11
  * @param {object} params
@@ -49,7 +51,10 @@ export const update = ({ renderer, time, deltaTime }) => {
49
51
  * @param {number} params.height
50
52
  * @param {number} params.pixelRatio
51
53
  */
52
- export const resize = ({ width, height }) => {
54
+ export const resize = ({ width, height, pixelRatio }) => {
55
+ resolution.x = width * pixelRatio;
56
+ resolution.y = height * pixelRatio;
57
+
53
58
  camera.left = -width * 0.5;
54
59
  camera.right = width * 0.5;
55
60
  camera.top = height * 0.5;
@@ -1,9 +1,10 @@
1
1
  import * as THREE from 'three';
2
2
 
3
- import { Init, Rendering, Resize, Update } from '@fragment/types';
3
+ import type { Init, Rendering, Resize, Update } from '@fragment/types';
4
4
 
5
5
  let scene: THREE.Scene;
6
6
  let camera: THREE.OrthographicCamera;
7
+ let resolution = new THREE.Vector2();
7
8
 
8
9
  export const init: Init<'three'> = ({}) => {
9
10
  scene = new THREE.Scene();
@@ -17,7 +18,10 @@ export const update: Update<'three'> = ({ renderer }) => {
17
18
  renderer.render(scene, camera);
18
19
  };
19
20
 
20
- export const resize: Resize<'three'> = ({ width, height }) => {
21
+ export const resize: Resize<'three'> = ({ width, height, pixelRatio }) => {
22
+ resolution.x = width * pixelRatio;
23
+ resolution.y = height * pixelRatio;
24
+
21
25
  camera.left = -width * 0.5;
22
26
  camera.right = width * 0.5;
23
27
  camera.top = height * 0.5;
@@ -4,6 +4,8 @@ import * as THREE from 'three';
4
4
  let scene;
5
5
  /** @type {THREE.OrthographicCamera} */
6
6
  let camera;
7
+ /** @type {THREE.Vector2} */
8
+ let resolution = new THREE.Vector2();
7
9
 
8
10
  /**
9
11
  * @param {object} params
@@ -50,7 +52,10 @@ export const update = ({ renderer, time, deltaTime }) => {
50
52
  * @param {number} params.height
51
53
  * @param {number} params.pixelRatio
52
54
  */
53
- export const resize = ({ width, height }) => {
55
+ export const resize = ({ width, height, pixelRatio }) => {
56
+ resolution.x = width * pixelRatio;
57
+ resolution.y = height * pixelRatio;
58
+
54
59
  camera.aspect = width / height;
55
60
  camera.updateProjectionMatrix();
56
61
  };
@@ -1,9 +1,10 @@
1
1
  import * as THREE from 'three';
2
2
 
3
- import { Init, Rendering, Resize, Update } from '@fragment/types';
3
+ import type { Init, Rendering, Resize, Update } from '@fragment/types';
4
4
 
5
5
  let scene: THREE.Scene;
6
6
  let camera: THREE.PerspectiveCamera;
7
+ let resolution = new THREE.Vector2();
7
8
 
8
9
  export const init: Init<'three'> = ({}) => {
9
10
  scene = new THREE.Scene();
@@ -18,7 +19,10 @@ export const update: Update<'three'> = ({ renderer }) => {
18
19
  renderer.render(scene, camera);
19
20
  };
20
21
 
21
- export const resize: Resize<'three'> = ({ width, height }) => {
22
+ export const resize: Resize<'three'> = ({ width, height, pixelRatio }) => {
23
+ resolution.x = width * pixelRatio;
24
+ resolution.y = height * pixelRatio;
25
+
22
26
  camera.aspect = width / height;
23
27
  camera.updateProjectionMatrix();
24
28
  };
@@ -1,13 +1,20 @@
1
+ /**
2
+ *
3
+ * @param {HTMLElement} node
4
+ * @param {() => void} callback
5
+ * @returns
6
+ */
1
7
  export function resize(node, callback) {
2
8
  if (typeof callback !== 'function') return;
3
9
 
10
+ /** @type {ResizeObserver | null} */
4
11
  let observer = new ResizeObserver(callback);
5
12
 
6
13
  observer.observe(node);
7
14
 
8
15
  return {
9
16
  destroy: () => {
10
- observer.disconnect();
17
+ observer?.disconnect();
11
18
  observer = null;
12
19
  },
13
20
  };
@@ -0,0 +1,93 @@
1
+ /**
2
+ *
3
+ * @param {object} options
4
+ * @param {Function} options.onDragStart
5
+ * @param {Function} options.onDrag
6
+ * @param {Function} options.onDragEnd
7
+ * @returns {import('svelte/attachments').Attachment} */
8
+ export function draggable({ onDragStart, onDrag, onDragEnd } = {}) {
9
+ return (node) => {
10
+ let isDragging = false;
11
+ /** @type {MouseEvent | undefined} */
12
+ let eventStart;
13
+ /** @type {DOMRect | undefined} */
14
+ let rect;
15
+ let classNameDragging = 'fragment-dragging';
16
+
17
+ /**
18
+ *
19
+ * @param {MouseEvent} event
20
+ */
21
+ function handleMouseDown(event) {
22
+ isDragging = true;
23
+ eventStart = event;
24
+
25
+ document.addEventListener('mousemove', handleMouseMove);
26
+ document.addEventListener('mouseup', handleMouseUp);
27
+
28
+ document.body.classList.add(classNameDragging);
29
+
30
+ rect = node.getBoundingClientRect();
31
+
32
+ const params = computeDrag(event);
33
+
34
+ onDragStart?.(event, params);
35
+ onDrag?.(event, params);
36
+ }
37
+
38
+ /**
39
+ *
40
+ * @param {MouseEvent} event
41
+ */
42
+ function handleMouseMove(event) {
43
+ onDrag?.(event, computeDrag(event));
44
+ }
45
+
46
+ /**
47
+ *
48
+ * @param {MouseEvent} event
49
+ */
50
+ function handleMouseUp(event) {
51
+ document.body.classList.remove(classNameDragging);
52
+ document.removeEventListener('mousemove', handleMouseMove);
53
+ document.removeEventListener('mouseup', handleMouseUp);
54
+
55
+ isDragging = false;
56
+
57
+ const params = computeDrag(event);
58
+ onDrag?.(event, params);
59
+ onDragEnd?.(event, params);
60
+ }
61
+
62
+ /**
63
+ *
64
+ * @param {MouseEvent} event
65
+ */
66
+ function computeDrag(event) {
67
+ let distanceX = event.clientX - eventStart.clientX;
68
+ let distanceY = event.clientY - eventStart.clientY;
69
+
70
+ let distance = Math.sqrt(
71
+ distanceX * distanceX + distanceY * distanceY,
72
+ );
73
+
74
+ return {
75
+ distanceX,
76
+ distanceY,
77
+ distance,
78
+ isDragging,
79
+ rect,
80
+ node,
81
+ };
82
+ }
83
+
84
+ node.addEventListener('mousedown', handleMouseDown);
85
+
86
+ return () => {
87
+ node.removeEventListener('mousedown', handleMouseDown);
88
+
89
+ rect = undefined;
90
+ eventStart = undefined;
91
+ };
92
+ };
93
+ }