@shopify/klint 0.0.4 → 0.0.7

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.
package/README.md CHANGED
@@ -1,36 +1,89 @@
1
1
  # Klint
2
2
 
3
- A modern creative coding library
3
+ A modern creative coding library for React applications that provides an intuitive interface to HTML Canvas 2D rendering. Klint simplifies the process of creating interactive graphics, animations, and visualizations.
4
+
5
+ ## Features
6
+
7
+ - React integration with hooks and components
8
+ - Intuitive drawing API inspired by Processing and P5.js
9
+ - Responsive canvas that automatically resizes
10
+ - Support for animations, static renderings, and interactive content
11
+ - Built-in utilities for managing state, input, and resources
12
+
13
+ ## Example
14
+
15
+ ```jsx
16
+ import { Klint, useKlint, type KlintContext } from "klint";
17
+
18
+ function AnimatedCircle() {
19
+ const { context } = useKlint();
20
+
21
+ const draw = (K: KlintContext) => {
22
+ K.background("#222");
23
+ K.fillColor("red");
24
+
25
+ // Draw a pulsing circle at the center of the canvas
26
+ const size = 50 + Math.sin(K.frame * 0.05) * 25;
27
+ K.circle(K.width/2, K.height/2, size);
28
+ };
29
+
30
+ return <Klint context={context} draw={draw} />;
31
+ }
32
+ ```
4
33
 
5
34
  ## Release Process
6
35
 
7
36
  This package uses GitHub Actions for automated releases. Here's how to create a new release:
8
37
 
9
- 1. Make your changes and commit them to the repository
10
- 2. Update the version in package.json:
38
+ ## Development and tests
39
+
40
+ > **Important:** The Klint library isn't public yet, so you'll need to link it manually if you want to edit it.
41
+
42
+ 1. Clone the repository
43
+ ```bash
44
+ git clone https://github.com/Shopify/klint.git
45
+ cd klint
46
+ ```
47
+
48
+ 2. Go to the lib folder and link it locally
11
49
  ```bash
12
50
  cd lib
13
- yarn version --new-version [patch|minor|major]
51
+ npm link
14
52
  ```
15
- This will:
16
- - Update the version in package.json
17
- - Create a git commit with the new version
18
- - Create a git tag with the new version (e.g., v1.0.0)
19
53
 
20
- 3. Push the changes and the tag:
54
+ 3. In your working directory, link to the local Klint
21
55
  ```bash
22
- git push && git push --tags
56
+ cd your-project
57
+ npm link klint
23
58
  ```
24
59
 
25
- 4. The GitHub Action will automatically:
26
- - Run tests
27
- - Build the package
28
- - Publish to npm
29
- - Create a GitHub release with auto-generated release notes
60
+ 4. Run the dev server:
61
+ ```bash
62
+ npm run dev
63
+ ```
64
+
65
+ 5. When finished, unlink both in your project and the local repo
66
+ ```bash
67
+ # In your project
68
+ npm unlink klint
69
+
70
+ # In the Klint lib folder
71
+ npm unlink
72
+ ```
73
+
74
+ 6. If you change anything in the library, you will need to rebuild
75
+ ```bash
76
+ # In the Klint lib folder
77
+ npm build
78
+ npm link
79
+ ```
80
+
81
+ 7. I use Vitest for testing
82
+ ```bash
83
+ npm test
84
+ ```
30
85
 
31
- ## Required Secrets
86
+ 8. Push your commit
32
87
 
33
- The workflow requires an NPM_TOKEN secret to be set in the GitHub repository:
34
88
 
35
- 1. Create an npm access token with publish permissions
36
- 2. Add it as a secret named NPM_TOKEN in your GitHub repository settings
89
+ Made with love at Shopify, 2025
package/dist/index.cjs CHANGED
@@ -28,8 +28,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
28
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
29
 
30
30
  // src/index.tsx
31
- var src_exports = {};
32
- __export(src_exports, {
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
33
  CONFIG_PROPS: () => CONFIG_PROPS,
34
34
  EPSILON: () => EPSILON,
35
35
  Klint: () => Klint,
@@ -39,7 +39,7 @@ __export(src_exports, {
39
39
  useProps: () => useProps,
40
40
  useStorage: () => useStorage
41
41
  });
42
- module.exports = __toCommonJS(src_exports);
42
+ module.exports = __toCommonJS(index_exports);
43
43
 
44
44
  // src/Klint.tsx
45
45
  var import_react = __toESM(require("react"), 1);
@@ -86,10 +86,8 @@ function useAnimate(contextRef, draw, isVisible) {
86
86
  const animationFrameId = (0, import_react.useRef)(0);
87
87
  const animate = (0, import_react.useCallback)(
88
88
  (timestamp = 0) => {
89
- if (!contextRef.current || !isVisible)
90
- return;
91
- if (!contextRef.current.__isReadyToDraw)
92
- return;
89
+ if (!contextRef.current || !isVisible) return;
90
+ if (!contextRef.current.__isReadyToDraw) return;
93
91
  if (!contextRef.current.__isPlaying) {
94
92
  return;
95
93
  }
@@ -105,10 +103,8 @@ function useAnimate(contextRef, draw, isVisible) {
105
103
  if (sinceLast >= target - epsilon) {
106
104
  context.deltaTime = now - context.__lastRealTime;
107
105
  draw(context);
108
- if (context.time > 1e7)
109
- context.time = 0;
110
- if (context.frame > 1e7)
111
- context.frame = 0;
106
+ if (context.time > 1e7) context.time = 0;
107
+ if (context.frame > 1e7) context.frame = 0;
112
108
  context.time += context.deltaTime / DEFAULT_FPS;
113
109
  context.frame++;
114
110
  context.__lastTargetTime = now;
@@ -161,19 +157,16 @@ function Klint({
161
157
  if (__options.origin === "center") {
162
158
  context2.translate(canvas.width * 0.5, canvas.height * 0.5);
163
159
  }
164
- if (shouldRedraw)
165
- draw(context2);
160
+ if (shouldRedraw) draw(context2);
166
161
  };
167
162
  (0, import_react.useEffect)(() => {
168
- if (!canvasRef.current || !containerRef.current)
169
- return;
163
+ if (!canvasRef.current || !containerRef.current) return;
170
164
  const canvas = canvasRef.current;
171
165
  const container = containerRef.current;
172
166
  const dpr = window.devicePixelRatio || 3;
173
167
  contextRef.current = initContext ? initContext(canvas, __options) : null;
174
168
  const context2 = contextRef.current;
175
- if (!context2)
176
- return;
169
+ if (!context2) return;
177
170
  context2.__dpr = dpr;
178
171
  if (__options.fps && __options.fps !== context2.fps) {
179
172
  context2.fps = __options.fps;
@@ -196,8 +189,7 @@ function Klint({
196
189
  );
197
190
  intersectionObserverRef.current.observe(container);
198
191
  const initializeKlint = async () => {
199
- if (!context2)
200
- return;
192
+ if (!context2) return;
201
193
  const handleStaticMode = () => {
202
194
  try {
203
195
  const imageUrl = canvas.toDataURL("image/png");
@@ -211,8 +203,7 @@ function Klint({
211
203
  if (preload && (unsafeReset || !context2.__isPreloaded)) {
212
204
  try {
213
205
  await preload(context2);
214
- if (!unsafeReset)
215
- context2.__isPreloaded = true;
206
+ if (!unsafeReset) context2.__isPreloaded = true;
216
207
  } catch (error) {
217
208
  const message = error instanceof Error ? error.message : String(error);
218
209
  throw new Error(`Klint error in the preload: ${message}`);
@@ -221,8 +212,7 @@ function Klint({
221
212
  if (setup && (unsafeReset || !context2.__isSetup)) {
222
213
  try {
223
214
  setup(context2);
224
- if (!unsafeReset)
225
- context2.__isSetup = true;
215
+ if (!unsafeReset) context2.__isSetup = true;
226
216
  } catch (error) {
227
217
  const message = error instanceof Error ? error.message : String(error);
228
218
  throw new Error(`Klint error in the setup: ${message}`);
@@ -239,14 +229,12 @@ function Klint({
239
229
  }
240
230
  };
241
231
  const unsafeMode = __options.unsafemode === "true";
242
- if (!unsafeMode && context2.__isReadyToDraw)
243
- return;
232
+ if (!unsafeMode && context2.__isReadyToDraw) return;
244
233
  await initializeContext(unsafeMode);
245
234
  if (__options.static === "true") {
246
235
  handleStaticMode();
247
236
  } else {
248
- if (__options.noloop !== "true")
249
- animate();
237
+ if (__options.noloop !== "true") animate();
250
238
  }
251
239
  };
252
240
  initializeKlint();
@@ -299,19 +287,16 @@ var KlintCoreFunctions = {
299
287
  ctx.canvas.requestFullscreen?.();
300
288
  },
301
289
  play: (ctx) => () => {
302
- if (!ctx.__isPlaying)
303
- ctx.__isPlaying = true;
290
+ if (!ctx.__isPlaying) ctx.__isPlaying = true;
304
291
  },
305
292
  pause: (ctx) => () => {
306
- if (ctx.__isPlaying)
307
- ctx.__isPlaying = false;
293
+ if (ctx.__isPlaying) ctx.__isPlaying = false;
308
294
  },
309
295
  // to do
310
296
  redraw: () => () => {
311
297
  },
312
298
  extend: (ctx) => (name, data, enforceReplace = false) => {
313
- if (name in ctx && !enforceReplace)
314
- return;
299
+ if (name in ctx && !enforceReplace) return;
315
300
  ctx[name] = data;
316
301
  },
317
302
  passImage: () => (element) => {
@@ -352,8 +337,7 @@ var KlintCoreFunctions = {
352
337
  alpha: options?.alpha ?? true,
353
338
  willReadFrequently: options?.willreadfrequently ?? false
354
339
  });
355
- if (!context)
356
- throw new Error("Failed to create offscreen context");
340
+ if (!context) throw new Error("Failed to create offscreen context");
357
341
  context.__dpr = ctx.__dpr;
358
342
  context.width = width * ctx.__dpr;
359
343
  context.height = height * ctx.__dpr;
@@ -402,8 +386,7 @@ var KlintCoreFunctions = {
402
386
  };
403
387
  var KlintFunctions = {
404
388
  extend: (ctx) => (name, data, enforceReplace = false) => {
405
- if (name in ctx && !enforceReplace)
406
- return;
389
+ if (name in ctx && !enforceReplace) return;
407
390
  ctx[name] = data;
408
391
  },
409
392
  background: (ctx) => (color) => {
@@ -457,27 +440,21 @@ var KlintFunctions = {
457
440
  ctx.restore();
458
441
  },
459
442
  point: (ctx) => (x, y) => {
460
- if (!ctx.checkTransparency("stroke"))
461
- return;
443
+ if (!ctx.checkTransparency("stroke")) return;
462
444
  ctx.beginPath();
463
445
  ctx.strokeRect(x, y, 1, 1);
464
446
  },
465
447
  checkTransparency: (ctx) => (toCheck) => {
466
- if (toCheck === "stroke" && ctx.strokeStyle === "transparent")
467
- return false;
468
- if (toCheck === "fill" && ctx.fillStyle === "transparent")
469
- return false;
448
+ if (toCheck === "stroke" && ctx.strokeStyle === "transparent") return false;
449
+ if (toCheck === "fill" && ctx.fillStyle === "transparent") return false;
470
450
  return true;
471
451
  },
472
452
  drawIfVisible: (ctx) => () => {
473
- if (ctx.checkTransparency("fill"))
474
- ctx.fill();
475
- if (ctx.checkTransparency("stroke"))
476
- ctx.stroke();
453
+ if (ctx.checkTransparency("fill")) ctx.fill();
454
+ if (ctx.checkTransparency("stroke")) ctx.stroke();
477
455
  },
478
456
  line: (ctx) => (x1, y1, x2, y2) => {
479
- if (!ctx.checkTransparency("stroke"))
480
- return;
457
+ if (!ctx.checkTransparency("stroke")) return;
481
458
  ctx.beginPath();
482
459
  ctx.moveTo(x1, y1);
483
460
  ctx.lineTo(x2, y2);
@@ -523,25 +500,21 @@ var KlintFunctions = {
523
500
  const angle = i * 2 * Math.PI / sides + rotation;
524
501
  const pointX = x + radius * Math.cos(angle);
525
502
  const pointY = y + (radius2 ? radius2 : radius) * Math.sin(angle);
526
- if (i === 0)
527
- ctx.moveTo(pointX, pointY);
528
- else
529
- ctx.lineTo(pointX, pointY);
503
+ if (i === 0) ctx.moveTo(pointX, pointY);
504
+ else ctx.lineTo(pointX, pointY);
530
505
  }
531
506
  ctx.closePath();
532
507
  ctx.drawIfVisible();
533
508
  },
534
509
  beginShape: (ctx) => () => {
535
- if (ctx.__startedShape)
536
- return;
510
+ if (ctx.__startedShape) return;
537
511
  ctx.beginPath();
538
512
  ctx.__startedShape = true;
539
513
  ctx.__currentShape = [];
540
514
  ctx.__currentContours = [];
541
515
  },
542
516
  beginContour: (ctx) => () => {
543
- if (!ctx.__startedShape)
544
- return;
517
+ if (!ctx.__startedShape) return;
545
518
  if (ctx.__startedContour && ctx.__currentContour?.length) {
546
519
  ctx.__currentContours?.push([...ctx.__currentContour]);
547
520
  }
@@ -549,14 +522,12 @@ var KlintFunctions = {
549
522
  ctx.__currentContour = [];
550
523
  },
551
524
  vertex: (ctx) => (x, y) => {
552
- if (!ctx.__startedShape)
553
- return;
525
+ if (!ctx.__startedShape) return;
554
526
  const points = ctx.__startedContour ? ctx.__currentContour : ctx.__currentShape;
555
527
  points?.push([x, y]);
556
528
  },
557
529
  endContour: (ctx) => (forceRevert = true) => {
558
- if (!ctx.__startedContour || !ctx.__currentContour?.length)
559
- return;
530
+ if (!ctx.__startedContour || !ctx.__currentContour?.length) return;
560
531
  const contourPoints = [...ctx.__currentContour];
561
532
  if (forceRevert) {
562
533
  contourPoints.reverse();
@@ -566,13 +537,10 @@ var KlintFunctions = {
566
537
  ctx.__startedContour = false;
567
538
  },
568
539
  endShape: (ctx) => (close = false) => {
569
- if (!ctx.__startedShape)
570
- return;
571
- if (ctx.__startedContour)
572
- ctx.endContour();
540
+ if (!ctx.__startedShape) return;
541
+ if (ctx.__startedContour) ctx.endContour();
573
542
  const points = ctx.__currentShape;
574
- if (!points?.length)
575
- return;
543
+ if (!points?.length) return;
576
544
  const drawPath = (points2, close2 = false) => {
577
545
  ctx.moveTo(points2[0][0], points2[0][1]);
578
546
  for (let i = 1; i < points2.length; i++) {
@@ -622,8 +590,7 @@ var KlintFunctions = {
622
590
  if (mode === "fast") {
623
591
  return n - ~~(n / mod) * mod;
624
592
  }
625
- if (n >= 0)
626
- return n % mod;
593
+ if (n >= 0) return n % mod;
627
594
  return mod - -n % mod;
628
595
  },
629
596
  distance: (ctx) => (x1, y1, x2, y2, mode = "precise") => {
@@ -685,16 +652,14 @@ var KlintFunctions = {
685
652
  },
686
653
  computeFont: (ctx) => () => {
687
654
  ctx.computeTextStyle();
688
- if (ctx.font !== ctx.__computedTextFont)
689
- ctx.font = ctx.__computedTextFont;
655
+ if (ctx.font !== ctx.__computedTextFont) ctx.font = ctx.__computedTextFont;
690
656
  },
691
657
  textWidth: (ctx) => (text) => {
692
658
  ctx.computeFont();
693
659
  return ctx.measureText(text).width;
694
660
  },
695
661
  text: (ctx) => (text, x, y, maxWidth = void 0) => {
696
- if (text === void 0)
697
- return;
662
+ if (text === void 0) return;
698
663
  ctx.computeFont();
699
664
  if (ctx.textAlign !== ctx.__textAlignment.horizontal) {
700
665
  ctx.textAlign = ctx.__textAlignment.horizontal;
@@ -805,8 +770,7 @@ var KlintFunctions = {
805
770
  Object.assign(ctx, config);
806
771
  },
807
772
  resizeCanvas: (ctx) => (width, height) => {
808
- if (ctx.__isMainContext)
809
- return;
773
+ if (ctx.__isMainContext) return;
810
774
  const config = ctx.saveConfig();
811
775
  ctx.canvas.width = ctx.width = width;
812
776
  ctx.canvas.height = ctx.height = height;
@@ -906,8 +870,7 @@ function useKlint() {
906
870
  const mouseDownCallbackRef = (0, import_react2.useRef)(null);
907
871
  const mouseUpCallbackRef = (0, import_react2.useRef)(null);
908
872
  (0, import_react2.useEffect)(() => {
909
- if (!contextRef.current?.canvas)
910
- return;
873
+ if (!contextRef.current?.canvas) return;
911
874
  const canvas = contextRef.current.canvas;
912
875
  const ctx = contextRef.current;
913
876
  const updateMousePosition = (e) => {
@@ -930,32 +893,23 @@ function useKlint() {
930
893
  }
931
894
  };
932
895
  const handleMouseDown = (e) => {
933
- if (mouseRef.current)
934
- mouseRef.current.isPressed = true;
935
- if (mouseDownCallbackRef.current)
936
- mouseDownCallbackRef.current(ctx, e);
896
+ if (mouseRef.current) mouseRef.current.isPressed = true;
897
+ if (mouseDownCallbackRef.current) mouseDownCallbackRef.current(ctx, e);
937
898
  };
938
899
  const handleMouseUp = (e) => {
939
- if (mouseRef.current)
940
- mouseRef.current.isPressed = false;
941
- if (mouseUpCallbackRef.current)
942
- mouseUpCallbackRef.current(ctx, e);
900
+ if (mouseRef.current) mouseRef.current.isPressed = false;
901
+ if (mouseUpCallbackRef.current) mouseUpCallbackRef.current(ctx, e);
943
902
  };
944
903
  const handleMouseEnter = (e) => {
945
- if (mouseRef.current)
946
- mouseRef.current.isHover = true;
947
- if (mouseInCallbackRef.current)
948
- mouseInCallbackRef.current(ctx, e);
904
+ if (mouseRef.current) mouseRef.current.isHover = true;
905
+ if (mouseInCallbackRef.current) mouseInCallbackRef.current(ctx, e);
949
906
  };
950
907
  const handleMouseLeave = (e) => {
951
- if (mouseRef.current)
952
- mouseRef.current.isHover = false;
953
- if (mouseOutCallbackRef.current)
954
- mouseOutCallbackRef.current(ctx, e);
908
+ if (mouseRef.current) mouseRef.current.isHover = false;
909
+ if (mouseOutCallbackRef.current) mouseOutCallbackRef.current(ctx, e);
955
910
  };
956
911
  const handleClick = (e) => {
957
- if (clickCallbackRef.current)
958
- clickCallbackRef.current(ctx, e);
912
+ if (clickCallbackRef.current) clickCallbackRef.current(ctx, e);
959
913
  };
960
914
  canvas.addEventListener("mousemove", updateMousePosition);
961
915
  canvas.addEventListener("mousedown", handleMouseDown);
@@ -987,14 +941,12 @@ function useKlint() {
987
941
  }
988
942
  const scrollCallbackRef = (0, import_react2.useRef)(null);
989
943
  (0, import_react2.useEffect)(() => {
990
- if (!contextRef.current?.canvas)
991
- return;
944
+ if (!contextRef.current?.canvas) return;
992
945
  const canvas = contextRef.current.canvas;
993
946
  const ctx = contextRef.current;
994
947
  const handleScroll = (e) => {
995
948
  e.preventDefault();
996
- if (!scrollRef.current)
997
- return;
949
+ if (!scrollRef.current) return;
998
950
  const currentTime = performance.now();
999
951
  const deltaTime = currentTime - scrollRef.current.lastTime;
1000
952
  scrollRef.current.distance += e.deltaY;
@@ -1020,20 +972,16 @@ function useKlint() {
1020
972
  const focusCallbackRef = (0, import_react2.useRef)(null);
1021
973
  const visibilityChangeCallbackRef = (0, import_react2.useRef)(null);
1022
974
  (0, import_react2.useEffect)(() => {
1023
- if (!contextRef.current)
1024
- return;
975
+ if (!contextRef.current) return;
1025
976
  const ctx = contextRef.current;
1026
977
  const handleResize = () => {
1027
- if (resizeCallbackRef.current)
1028
- resizeCallbackRef.current(ctx);
978
+ if (resizeCallbackRef.current) resizeCallbackRef.current(ctx);
1029
979
  };
1030
980
  const handleBlur = () => {
1031
- if (blurCallbackRef.current)
1032
- blurCallbackRef.current(ctx);
981
+ if (blurCallbackRef.current) blurCallbackRef.current(ctx);
1033
982
  };
1034
983
  const handleFocus = () => {
1035
- if (focusCallbackRef.current)
1036
- focusCallbackRef.current(ctx);
984
+ if (focusCallbackRef.current) focusCallbackRef.current(ctx);
1037
985
  };
1038
986
  const handleVisibilityChange = () => {
1039
987
  const isVisible = document.visibilityState === "visible";
@@ -1098,8 +1046,7 @@ function useKlint() {
1098
1046
  alpha: options.alpha ?? true,
1099
1047
  willReadFrequently: options.willreadfrequently ?? true
1100
1048
  });
1101
- if (!ctx)
1102
- throw new Error("Failed to get canvas context");
1049
+ if (!ctx) throw new Error("Failed to get canvas context");
1103
1050
  contextRef.current = buildKlintContext(ctx, options);
1104
1051
  }
1105
1052
  return contextRef.current;
@@ -1107,8 +1054,7 @@ function useKlint() {
1107
1054
  []
1108
1055
  );
1109
1056
  const togglePlay = (0, import_react2.useCallback)((playing) => {
1110
- if (!contextRef.current)
1111
- return;
1057
+ if (!contextRef.current) return;
1112
1058
  if (playing !== void 0) {
1113
1059
  contextRef.current.__isPlaying = playing;
1114
1060
  } else {