@shopify/klint 0.0.93 → 0.0.97

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.
@@ -352,6 +352,7 @@ interface KlintOffscreenContext extends CanvasRenderingContext2D, KlintFunctions
352
352
  horizontal: CanvasTextAlign;
353
353
  vertical: CanvasTextBaseline;
354
354
  };
355
+ createVector: (x: number, y: number) => Vector;
355
356
  [key: string]: any;
356
357
  }
357
358
  interface KlintContext extends KlintOffscreenContext, KlintCoreFunctions {
@@ -375,6 +376,7 @@ interface KlintCanvasOptions {
375
376
  nocanvas?: string;
376
377
  fps?: number;
377
378
  unsafemode?: string;
379
+ dpr?: number | "default";
378
380
  origin?: "corner" | "center";
379
381
  }
380
382
  type KlintConfig = Partial<Pick<KlintContext, (typeof CONFIG_PROPS)[number]>>;
@@ -352,6 +352,7 @@ interface KlintOffscreenContext extends CanvasRenderingContext2D, KlintFunctions
352
352
  horizontal: CanvasTextAlign;
353
353
  vertical: CanvasTextBaseline;
354
354
  };
355
+ createVector: (x: number, y: number) => Vector;
355
356
  [key: string]: any;
356
357
  }
357
358
  interface KlintContext extends KlintOffscreenContext, KlintCoreFunctions {
@@ -375,6 +376,7 @@ interface KlintCanvasOptions {
375
376
  nocanvas?: string;
376
377
  fps?: number;
377
378
  unsafemode?: string;
379
+ dpr?: number | "default";
378
380
  origin?: "corner" | "center";
379
381
  }
380
382
  type KlintConfig = Partial<Pick<KlintContext, (typeof CONFIG_PROPS)[number]>>;
package/dist/index.cjs CHANGED
@@ -55,6 +55,7 @@ var DEFAULT_OPTIONS = {
55
55
  unsafemode: "false",
56
56
  willreadfrequently: "false",
57
57
  fps: DEFAULT_FPS,
58
+ dpr: "default",
58
59
  origin: "corner"
59
60
  };
60
61
  var CONFIG_PROPS = [
@@ -165,7 +166,8 @@ function Klint({
165
166
  if (!canvasRef.current || !containerRef.current) return;
166
167
  const canvas = canvasRef.current;
167
168
  const container = containerRef.current;
168
- const dpr = window.devicePixelRatio || 3;
169
+ const defaultDPR = window.devicePixelRatio || 3;
170
+ const dpr = __options.dpr ? __options.dpr === "default" ? defaultDPR : __options.dpr : defaultDPR;
169
171
  contextRef.current = initContext ? initContext(canvas, __options) : null;
170
172
  const context2 = contextRef.current;
171
173
  if (!context2) return;
@@ -1711,17 +1713,35 @@ var DEFAULT_SCROLL_STATE = {
1711
1713
  velocity: 0,
1712
1714
  lastTime: 0
1713
1715
  };
1716
+ var DEFAULT_GESTURE_STATE = {
1717
+ active: false,
1718
+ touches: null,
1719
+ startTouches: null,
1720
+ startDistance: 0,
1721
+ currentDistance: 0,
1722
+ scale: 1,
1723
+ rotation: 0,
1724
+ startTime: 0,
1725
+ deltaX: 0,
1726
+ deltaY: 0,
1727
+ velocityX: 0,
1728
+ velocityY: 0,
1729
+ lastTime: 0,
1730
+ lastX: 0,
1731
+ lastY: 0
1732
+ };
1714
1733
  function useKlint() {
1715
1734
  const contextRef = (0, import_react2.useRef)(null);
1716
1735
  const mouseRef = (0, import_react2.useRef)(null);
1717
1736
  const scrollRef = (0, import_react2.useRef)(null);
1737
+ const gestureRef = (0, import_react2.useRef)(null);
1718
1738
  const useDev = () => {
1719
1739
  return;
1720
1740
  };
1721
1741
  const KlintImage = () => {
1722
1742
  const imagesRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
1723
1743
  const loadImage = (0, import_react2.useCallback)(
1724
- async (key, url) => {
1744
+ async (key, url, options) => {
1725
1745
  return new Promise((resolve, reject) => {
1726
1746
  const img = new Image();
1727
1747
  img.onload = () => {
@@ -1731,15 +1751,16 @@ function useKlint() {
1731
1751
  resolve(img);
1732
1752
  };
1733
1753
  img.onerror = reject;
1754
+ img.crossOrigin = options?.crossOrigin || "anonymous";
1734
1755
  img.src = url;
1735
1756
  });
1736
1757
  },
1737
1758
  []
1738
1759
  );
1739
1760
  const loadImages = (0, import_react2.useCallback)(
1740
- async (imageMap) => {
1761
+ async (imageMap, options) => {
1741
1762
  const promises = Object.entries(imageMap).map(
1742
- ([key, url]) => loadImage(key, url).then(
1763
+ ([key, url]) => loadImage(key, url, options).then(
1743
1764
  (img) => [key, img]
1744
1765
  )
1745
1766
  );
@@ -1880,6 +1901,169 @@ function useKlint() {
1880
1901
  onScroll: (callback) => scrollCallbackRef.current = callback
1881
1902
  };
1882
1903
  };
1904
+ const KlintGesture = () => {
1905
+ if (!gestureRef.current) {
1906
+ gestureRef.current = { ...DEFAULT_GESTURE_STATE };
1907
+ }
1908
+ const tapCallbackRef = (0, import_react2.useRef)(null);
1909
+ const swipeCallbackRef = (0, import_react2.useRef)(null);
1910
+ const pinchCallbackRef = (0, import_react2.useRef)(null);
1911
+ const rotateCallbackRef = (0, import_react2.useRef)(null);
1912
+ const touchStartCallbackRef = (0, import_react2.useRef)(null);
1913
+ const touchMoveCallbackRef = (0, import_react2.useRef)(null);
1914
+ const touchEndCallbackRef = (0, import_react2.useRef)(null);
1915
+ (0, import_react2.useEffect)(() => {
1916
+ if (!contextRef.current?.canvas) return;
1917
+ const canvas = contextRef.current.canvas;
1918
+ const ctx = contextRef.current;
1919
+ const getDistance = (t1, t2) => {
1920
+ const dx = t1.clientX - t2.clientX;
1921
+ const dy = t1.clientY - t2.clientY;
1922
+ return Math.sqrt(dx * dx + dy * dy);
1923
+ };
1924
+ const getAngle = (t1, t2) => {
1925
+ return Math.atan2(t2.clientY - t1.clientY, t2.clientX - t1.clientX) * 180 / Math.PI;
1926
+ };
1927
+ const getTouchCenter = (touches) => {
1928
+ if (touches.length === 1) {
1929
+ return {
1930
+ x: touches[0].clientX,
1931
+ y: touches[0].clientY
1932
+ };
1933
+ }
1934
+ let sumX = 0;
1935
+ let sumY = 0;
1936
+ for (let i = 0; i < touches.length; i++) {
1937
+ sumX += touches[i].clientX;
1938
+ sumY += touches[i].clientY;
1939
+ }
1940
+ return {
1941
+ x: sumX / touches.length,
1942
+ y: sumY / touches.length
1943
+ };
1944
+ };
1945
+ const handleTouchStart = (e) => {
1946
+ if (!gestureRef.current) return;
1947
+ const now = performance.now();
1948
+ const touchCenter = getTouchCenter(e.touches);
1949
+ gestureRef.current.active = true;
1950
+ gestureRef.current.touches = e.touches;
1951
+ gestureRef.current.startTouches = e.touches;
1952
+ gestureRef.current.startTime = now;
1953
+ gestureRef.current.lastTime = now;
1954
+ gestureRef.current.lastX = touchCenter.x;
1955
+ gestureRef.current.lastY = touchCenter.y;
1956
+ gestureRef.current.deltaX = 0;
1957
+ gestureRef.current.deltaY = 0;
1958
+ gestureRef.current.velocityX = 0;
1959
+ gestureRef.current.velocityY = 0;
1960
+ if (e.touches.length >= 2) {
1961
+ gestureRef.current.startDistance = getDistance(
1962
+ e.touches[0],
1963
+ e.touches[1]
1964
+ );
1965
+ gestureRef.current.currentDistance = gestureRef.current.startDistance;
1966
+ gestureRef.current.scale = 1;
1967
+ gestureRef.current.rotation = getAngle(e.touches[0], e.touches[1]);
1968
+ }
1969
+ if (touchStartCallbackRef.current) {
1970
+ touchStartCallbackRef.current(ctx, e, gestureRef.current);
1971
+ }
1972
+ };
1973
+ const handleTouchMove = (e) => {
1974
+ if (!gestureRef.current || !gestureRef.current.active) return;
1975
+ const now = performance.now();
1976
+ const deltaTime = now - gestureRef.current.lastTime;
1977
+ const touchCenter = getTouchCenter(e.touches);
1978
+ gestureRef.current.touches = e.touches;
1979
+ gestureRef.current.deltaX = touchCenter.x - gestureRef.current.lastX;
1980
+ gestureRef.current.deltaY = touchCenter.y - gestureRef.current.lastY;
1981
+ if (deltaTime > 0) {
1982
+ gestureRef.current.velocityX = gestureRef.current.deltaX / deltaTime;
1983
+ gestureRef.current.velocityY = gestureRef.current.deltaY / deltaTime;
1984
+ }
1985
+ gestureRef.current.lastTime = now;
1986
+ gestureRef.current.lastX = touchCenter.x;
1987
+ gestureRef.current.lastY = touchCenter.y;
1988
+ if (e.touches.length >= 2) {
1989
+ const currentDistance = getDistance(e.touches[0], e.touches[1]);
1990
+ gestureRef.current.currentDistance = currentDistance;
1991
+ if (gestureRef.current.startDistance > 0) {
1992
+ gestureRef.current.scale = currentDistance / gestureRef.current.startDistance;
1993
+ }
1994
+ const currentAngle = getAngle(e.touches[0], e.touches[1]);
1995
+ const startAngle = gestureRef.current.rotation;
1996
+ gestureRef.current.rotation = currentAngle - startAngle;
1997
+ if (pinchCallbackRef.current) {
1998
+ pinchCallbackRef.current(ctx, e, gestureRef.current);
1999
+ }
2000
+ if (rotateCallbackRef.current && Math.abs(gestureRef.current.rotation) > 5) {
2001
+ rotateCallbackRef.current(ctx, e, gestureRef.current);
2002
+ }
2003
+ }
2004
+ if (touchMoveCallbackRef.current) {
2005
+ touchMoveCallbackRef.current(ctx, e, gestureRef.current);
2006
+ }
2007
+ };
2008
+ const handleTouchEnd = (e) => {
2009
+ if (!gestureRef.current || !gestureRef.current.active || !gestureRef.current.startTouches)
2010
+ return;
2011
+ const now = performance.now();
2012
+ const touchDuration = now - gestureRef.current.startTime;
2013
+ if (touchDuration < 300 && Math.abs(gestureRef.current.deltaX) < 10 && Math.abs(gestureRef.current.deltaY) < 10) {
2014
+ if (tapCallbackRef.current) {
2015
+ tapCallbackRef.current(ctx, e, gestureRef.current);
2016
+ }
2017
+ }
2018
+ const swipeThreshold = 50;
2019
+ const isHorizontalSwipe = Math.abs(gestureRef.current.deltaX) > Math.abs(gestureRef.current.deltaY);
2020
+ if (swipeCallbackRef.current && touchDuration < 300) {
2021
+ if (isHorizontalSwipe && Math.abs(gestureRef.current.deltaX) > swipeThreshold) {
2022
+ const direction = gestureRef.current.deltaX > 0 ? "right" : "left";
2023
+ swipeCallbackRef.current(ctx, e, gestureRef.current, direction);
2024
+ } else if (!isHorizontalSwipe && Math.abs(gestureRef.current.deltaY) > swipeThreshold) {
2025
+ const direction = gestureRef.current.deltaY > 0 ? "down" : "up";
2026
+ swipeCallbackRef.current(ctx, e, gestureRef.current, direction);
2027
+ }
2028
+ }
2029
+ if (touchEndCallbackRef.current) {
2030
+ touchEndCallbackRef.current(ctx, e, gestureRef.current);
2031
+ }
2032
+ if (e.touches.length === 0) {
2033
+ gestureRef.current.active = false;
2034
+ }
2035
+ };
2036
+ const handleTouchCancel = (e) => {
2037
+ if (!gestureRef.current) return;
2038
+ gestureRef.current.active = false;
2039
+ if (touchEndCallbackRef.current) {
2040
+ touchEndCallbackRef.current(ctx, e, gestureRef.current);
2041
+ }
2042
+ };
2043
+ canvas.addEventListener("touchstart", handleTouchStart, {
2044
+ passive: false
2045
+ });
2046
+ canvas.addEventListener("touchmove", handleTouchMove, { passive: false });
2047
+ canvas.addEventListener("touchend", handleTouchEnd);
2048
+ canvas.addEventListener("touchcancel", handleTouchCancel);
2049
+ return () => {
2050
+ canvas.removeEventListener("touchstart", handleTouchStart);
2051
+ canvas.removeEventListener("touchmove", handleTouchMove);
2052
+ canvas.removeEventListener("touchend", handleTouchEnd);
2053
+ canvas.removeEventListener("touchcancel", handleTouchCancel);
2054
+ };
2055
+ }, []);
2056
+ return {
2057
+ gesture: gestureRef.current,
2058
+ onTap: (callback) => tapCallbackRef.current = callback,
2059
+ onSwipe: (callback) => swipeCallbackRef.current = callback,
2060
+ onPinch: (callback) => pinchCallbackRef.current = callback,
2061
+ onRotate: (callback) => rotateCallbackRef.current = callback,
2062
+ onTouchStart: (callback) => touchStartCallbackRef.current = callback,
2063
+ onTouchMove: (callback) => touchMoveCallbackRef.current = callback,
2064
+ onTouchEnd: (callback) => touchEndCallbackRef.current = callback
2065
+ };
2066
+ };
1883
2067
  const KlintWindow = () => {
1884
2068
  const resizeCallbackRef = (0, import_react2.useRef)(
1885
2069
  null
@@ -1991,6 +2175,7 @@ function useKlint() {
1991
2175
  },
1992
2176
  KlintMouse,
1993
2177
  KlintScroll,
2178
+ KlintGesture,
1994
2179
  KlintWindow,
1995
2180
  KlintImage,
1996
2181
  togglePlay,
package/dist/index.d.cts CHANGED
@@ -628,6 +628,7 @@ interface KlintOffscreenContext extends CanvasRenderingContext2D, KlintFunctions
628
628
  horizontal: CanvasTextAlign;
629
629
  vertical: CanvasTextBaseline;
630
630
  };
631
+ createVector: (x: number, y: number) => Vector;
631
632
  [key: string]: any;
632
633
  }
633
634
  interface KlintContext extends KlintOffscreenContext, KlintCoreFunctions {
@@ -651,6 +652,7 @@ interface KlintCanvasOptions {
651
652
  nocanvas?: string;
652
653
  fps?: number;
653
654
  unsafemode?: string;
655
+ dpr?: number | "default";
654
656
  origin?: "corner" | "center";
655
657
  }
656
658
  type KlintConfig = Partial<Pick<KlintContext, (typeof CONFIG_PROPS)[number]>>;
@@ -686,6 +688,23 @@ interface KlintScroll {
686
688
  velocity: number;
687
689
  lastTime: number;
688
690
  }
691
+ interface KlintGesture {
692
+ active: boolean;
693
+ touches: TouchList | null;
694
+ startTouches: TouchList | null;
695
+ startDistance: number;
696
+ currentDistance: number;
697
+ scale: number;
698
+ rotation: number;
699
+ startTime: number;
700
+ deltaX: number;
701
+ deltaY: number;
702
+ velocityX: number;
703
+ velocityY: number;
704
+ lastTime: number;
705
+ lastX: number;
706
+ lastY: number;
707
+ }
689
708
  declare function useKlint(): {
690
709
  context: {
691
710
  context: KlintContext | null;
@@ -703,6 +722,16 @@ declare function useKlint(): {
703
722
  scroll: KlintScroll;
704
723
  onScroll: (callback: (ctx: KlintContext, scroll: KlintScroll, e: WheelEvent) => void) => (ctx: KlintContext, scroll: KlintScroll, e: WheelEvent) => void;
705
724
  };
725
+ KlintGesture: () => {
726
+ gesture: KlintGesture;
727
+ onTap: (callback: (ctx: KlintContext, e: TouchEvent, gesture: KlintGesture) => void) => (ctx: KlintContext, e: TouchEvent, gesture: KlintGesture) => void;
728
+ onSwipe: (callback: (ctx: KlintContext, e: TouchEvent, gesture: KlintGesture, direction: "left" | "right" | "up" | "down") => void) => (ctx: KlintContext, e: TouchEvent, gesture: KlintGesture, direction: "left" | "right" | "up" | "down") => void;
729
+ onPinch: (callback: (ctx: KlintContext, e: TouchEvent, gesture: KlintGesture) => void) => (ctx: KlintContext, e: TouchEvent, gesture: KlintGesture) => void;
730
+ onRotate: (callback: (ctx: KlintContext, e: TouchEvent, gesture: KlintGesture) => void) => (ctx: KlintContext, e: TouchEvent, gesture: KlintGesture) => void;
731
+ onTouchStart: (callback: (ctx: KlintContext, e: TouchEvent, gesture: KlintGesture) => void) => (ctx: KlintContext, e: TouchEvent, gesture: KlintGesture) => void;
732
+ onTouchMove: (callback: (ctx: KlintContext, e: TouchEvent, gesture: KlintGesture) => void) => (ctx: KlintContext, e: TouchEvent, gesture: KlintGesture) => void;
733
+ onTouchEnd: (callback: (ctx: KlintContext, e: TouchEvent, gesture: KlintGesture) => void) => (ctx: KlintContext, e: TouchEvent, gesture: KlintGesture) => void;
734
+ };
706
735
  KlintWindow: () => {
707
736
  onResize: (callback: (ctx: KlintContext) => void) => (ctx: KlintContext) => void;
708
737
  onBlur: (callback: (ctx: KlintContext) => void) => (ctx: KlintContext) => void;
@@ -711,8 +740,12 @@ declare function useKlint(): {
711
740
  };
712
741
  KlintImage: () => {
713
742
  images: Record<string, HTMLImageElement>;
714
- loadImage: (key: string, url: string) => Promise<HTMLImageElement>;
715
- loadImages: (imageMap: Record<string, string>) => Promise<Map<string, HTMLImageElement>>;
743
+ loadImage: (key: string, url: string, options?: {
744
+ crossOrigin?: string;
745
+ }) => Promise<HTMLImageElement>;
746
+ loadImages: (imageMap: Record<string, string>, options?: {
747
+ crossOrigin?: string;
748
+ }) => Promise<Map<string, HTMLImageElement>>;
716
749
  getImage: (key: string) => HTMLImageElement | undefined;
717
750
  hasImage: (key: string) => boolean;
718
751
  clearImages: () => void;
package/dist/index.d.ts CHANGED
@@ -628,6 +628,7 @@ interface KlintOffscreenContext extends CanvasRenderingContext2D, KlintFunctions
628
628
  horizontal: CanvasTextAlign;
629
629
  vertical: CanvasTextBaseline;
630
630
  };
631
+ createVector: (x: number, y: number) => Vector;
631
632
  [key: string]: any;
632
633
  }
633
634
  interface KlintContext extends KlintOffscreenContext, KlintCoreFunctions {
@@ -651,6 +652,7 @@ interface KlintCanvasOptions {
651
652
  nocanvas?: string;
652
653
  fps?: number;
653
654
  unsafemode?: string;
655
+ dpr?: number | "default";
654
656
  origin?: "corner" | "center";
655
657
  }
656
658
  type KlintConfig = Partial<Pick<KlintContext, (typeof CONFIG_PROPS)[number]>>;
@@ -686,6 +688,23 @@ interface KlintScroll {
686
688
  velocity: number;
687
689
  lastTime: number;
688
690
  }
691
+ interface KlintGesture {
692
+ active: boolean;
693
+ touches: TouchList | null;
694
+ startTouches: TouchList | null;
695
+ startDistance: number;
696
+ currentDistance: number;
697
+ scale: number;
698
+ rotation: number;
699
+ startTime: number;
700
+ deltaX: number;
701
+ deltaY: number;
702
+ velocityX: number;
703
+ velocityY: number;
704
+ lastTime: number;
705
+ lastX: number;
706
+ lastY: number;
707
+ }
689
708
  declare function useKlint(): {
690
709
  context: {
691
710
  context: KlintContext | null;
@@ -703,6 +722,16 @@ declare function useKlint(): {
703
722
  scroll: KlintScroll;
704
723
  onScroll: (callback: (ctx: KlintContext, scroll: KlintScroll, e: WheelEvent) => void) => (ctx: KlintContext, scroll: KlintScroll, e: WheelEvent) => void;
705
724
  };
725
+ KlintGesture: () => {
726
+ gesture: KlintGesture;
727
+ onTap: (callback: (ctx: KlintContext, e: TouchEvent, gesture: KlintGesture) => void) => (ctx: KlintContext, e: TouchEvent, gesture: KlintGesture) => void;
728
+ onSwipe: (callback: (ctx: KlintContext, e: TouchEvent, gesture: KlintGesture, direction: "left" | "right" | "up" | "down") => void) => (ctx: KlintContext, e: TouchEvent, gesture: KlintGesture, direction: "left" | "right" | "up" | "down") => void;
729
+ onPinch: (callback: (ctx: KlintContext, e: TouchEvent, gesture: KlintGesture) => void) => (ctx: KlintContext, e: TouchEvent, gesture: KlintGesture) => void;
730
+ onRotate: (callback: (ctx: KlintContext, e: TouchEvent, gesture: KlintGesture) => void) => (ctx: KlintContext, e: TouchEvent, gesture: KlintGesture) => void;
731
+ onTouchStart: (callback: (ctx: KlintContext, e: TouchEvent, gesture: KlintGesture) => void) => (ctx: KlintContext, e: TouchEvent, gesture: KlintGesture) => void;
732
+ onTouchMove: (callback: (ctx: KlintContext, e: TouchEvent, gesture: KlintGesture) => void) => (ctx: KlintContext, e: TouchEvent, gesture: KlintGesture) => void;
733
+ onTouchEnd: (callback: (ctx: KlintContext, e: TouchEvent, gesture: KlintGesture) => void) => (ctx: KlintContext, e: TouchEvent, gesture: KlintGesture) => void;
734
+ };
706
735
  KlintWindow: () => {
707
736
  onResize: (callback: (ctx: KlintContext) => void) => (ctx: KlintContext) => void;
708
737
  onBlur: (callback: (ctx: KlintContext) => void) => (ctx: KlintContext) => void;
@@ -711,8 +740,12 @@ declare function useKlint(): {
711
740
  };
712
741
  KlintImage: () => {
713
742
  images: Record<string, HTMLImageElement>;
714
- loadImage: (key: string, url: string) => Promise<HTMLImageElement>;
715
- loadImages: (imageMap: Record<string, string>) => Promise<Map<string, HTMLImageElement>>;
743
+ loadImage: (key: string, url: string, options?: {
744
+ crossOrigin?: string;
745
+ }) => Promise<HTMLImageElement>;
746
+ loadImages: (imageMap: Record<string, string>, options?: {
747
+ crossOrigin?: string;
748
+ }) => Promise<Map<string, HTMLImageElement>>;
716
749
  getImage: (key: string) => HTMLImageElement | undefined;
717
750
  hasImage: (key: string) => boolean;
718
751
  clearImages: () => void;
package/dist/index.js CHANGED
@@ -12,6 +12,7 @@ var DEFAULT_OPTIONS = {
12
12
  unsafemode: "false",
13
13
  willreadfrequently: "false",
14
14
  fps: DEFAULT_FPS,
15
+ dpr: "default",
15
16
  origin: "corner"
16
17
  };
17
18
  var CONFIG_PROPS = [
@@ -122,7 +123,8 @@ function Klint({
122
123
  if (!canvasRef.current || !containerRef.current) return;
123
124
  const canvas = canvasRef.current;
124
125
  const container = containerRef.current;
125
- const dpr = window.devicePixelRatio || 3;
126
+ const defaultDPR = window.devicePixelRatio || 3;
127
+ const dpr = __options.dpr ? __options.dpr === "default" ? defaultDPR : __options.dpr : defaultDPR;
126
128
  contextRef.current = initContext ? initContext(canvas, __options) : null;
127
129
  const context2 = contextRef.current;
128
130
  if (!context2) return;
@@ -1668,17 +1670,35 @@ var DEFAULT_SCROLL_STATE = {
1668
1670
  velocity: 0,
1669
1671
  lastTime: 0
1670
1672
  };
1673
+ var DEFAULT_GESTURE_STATE = {
1674
+ active: false,
1675
+ touches: null,
1676
+ startTouches: null,
1677
+ startDistance: 0,
1678
+ currentDistance: 0,
1679
+ scale: 1,
1680
+ rotation: 0,
1681
+ startTime: 0,
1682
+ deltaX: 0,
1683
+ deltaY: 0,
1684
+ velocityX: 0,
1685
+ velocityY: 0,
1686
+ lastTime: 0,
1687
+ lastX: 0,
1688
+ lastY: 0
1689
+ };
1671
1690
  function useKlint() {
1672
1691
  const contextRef = useRef2(null);
1673
1692
  const mouseRef = useRef2(null);
1674
1693
  const scrollRef = useRef2(null);
1694
+ const gestureRef = useRef2(null);
1675
1695
  const useDev = () => {
1676
1696
  return;
1677
1697
  };
1678
1698
  const KlintImage = () => {
1679
1699
  const imagesRef = useRef2(/* @__PURE__ */ new Map());
1680
1700
  const loadImage = useCallback2(
1681
- async (key, url) => {
1701
+ async (key, url, options) => {
1682
1702
  return new Promise((resolve, reject) => {
1683
1703
  const img = new Image();
1684
1704
  img.onload = () => {
@@ -1688,15 +1708,16 @@ function useKlint() {
1688
1708
  resolve(img);
1689
1709
  };
1690
1710
  img.onerror = reject;
1711
+ img.crossOrigin = options?.crossOrigin || "anonymous";
1691
1712
  img.src = url;
1692
1713
  });
1693
1714
  },
1694
1715
  []
1695
1716
  );
1696
1717
  const loadImages = useCallback2(
1697
- async (imageMap) => {
1718
+ async (imageMap, options) => {
1698
1719
  const promises = Object.entries(imageMap).map(
1699
- ([key, url]) => loadImage(key, url).then(
1720
+ ([key, url]) => loadImage(key, url, options).then(
1700
1721
  (img) => [key, img]
1701
1722
  )
1702
1723
  );
@@ -1837,6 +1858,169 @@ function useKlint() {
1837
1858
  onScroll: (callback) => scrollCallbackRef.current = callback
1838
1859
  };
1839
1860
  };
1861
+ const KlintGesture = () => {
1862
+ if (!gestureRef.current) {
1863
+ gestureRef.current = { ...DEFAULT_GESTURE_STATE };
1864
+ }
1865
+ const tapCallbackRef = useRef2(null);
1866
+ const swipeCallbackRef = useRef2(null);
1867
+ const pinchCallbackRef = useRef2(null);
1868
+ const rotateCallbackRef = useRef2(null);
1869
+ const touchStartCallbackRef = useRef2(null);
1870
+ const touchMoveCallbackRef = useRef2(null);
1871
+ const touchEndCallbackRef = useRef2(null);
1872
+ useEffect2(() => {
1873
+ if (!contextRef.current?.canvas) return;
1874
+ const canvas = contextRef.current.canvas;
1875
+ const ctx = contextRef.current;
1876
+ const getDistance = (t1, t2) => {
1877
+ const dx = t1.clientX - t2.clientX;
1878
+ const dy = t1.clientY - t2.clientY;
1879
+ return Math.sqrt(dx * dx + dy * dy);
1880
+ };
1881
+ const getAngle = (t1, t2) => {
1882
+ return Math.atan2(t2.clientY - t1.clientY, t2.clientX - t1.clientX) * 180 / Math.PI;
1883
+ };
1884
+ const getTouchCenter = (touches) => {
1885
+ if (touches.length === 1) {
1886
+ return {
1887
+ x: touches[0].clientX,
1888
+ y: touches[0].clientY
1889
+ };
1890
+ }
1891
+ let sumX = 0;
1892
+ let sumY = 0;
1893
+ for (let i = 0; i < touches.length; i++) {
1894
+ sumX += touches[i].clientX;
1895
+ sumY += touches[i].clientY;
1896
+ }
1897
+ return {
1898
+ x: sumX / touches.length,
1899
+ y: sumY / touches.length
1900
+ };
1901
+ };
1902
+ const handleTouchStart = (e) => {
1903
+ if (!gestureRef.current) return;
1904
+ const now = performance.now();
1905
+ const touchCenter = getTouchCenter(e.touches);
1906
+ gestureRef.current.active = true;
1907
+ gestureRef.current.touches = e.touches;
1908
+ gestureRef.current.startTouches = e.touches;
1909
+ gestureRef.current.startTime = now;
1910
+ gestureRef.current.lastTime = now;
1911
+ gestureRef.current.lastX = touchCenter.x;
1912
+ gestureRef.current.lastY = touchCenter.y;
1913
+ gestureRef.current.deltaX = 0;
1914
+ gestureRef.current.deltaY = 0;
1915
+ gestureRef.current.velocityX = 0;
1916
+ gestureRef.current.velocityY = 0;
1917
+ if (e.touches.length >= 2) {
1918
+ gestureRef.current.startDistance = getDistance(
1919
+ e.touches[0],
1920
+ e.touches[1]
1921
+ );
1922
+ gestureRef.current.currentDistance = gestureRef.current.startDistance;
1923
+ gestureRef.current.scale = 1;
1924
+ gestureRef.current.rotation = getAngle(e.touches[0], e.touches[1]);
1925
+ }
1926
+ if (touchStartCallbackRef.current) {
1927
+ touchStartCallbackRef.current(ctx, e, gestureRef.current);
1928
+ }
1929
+ };
1930
+ const handleTouchMove = (e) => {
1931
+ if (!gestureRef.current || !gestureRef.current.active) return;
1932
+ const now = performance.now();
1933
+ const deltaTime = now - gestureRef.current.lastTime;
1934
+ const touchCenter = getTouchCenter(e.touches);
1935
+ gestureRef.current.touches = e.touches;
1936
+ gestureRef.current.deltaX = touchCenter.x - gestureRef.current.lastX;
1937
+ gestureRef.current.deltaY = touchCenter.y - gestureRef.current.lastY;
1938
+ if (deltaTime > 0) {
1939
+ gestureRef.current.velocityX = gestureRef.current.deltaX / deltaTime;
1940
+ gestureRef.current.velocityY = gestureRef.current.deltaY / deltaTime;
1941
+ }
1942
+ gestureRef.current.lastTime = now;
1943
+ gestureRef.current.lastX = touchCenter.x;
1944
+ gestureRef.current.lastY = touchCenter.y;
1945
+ if (e.touches.length >= 2) {
1946
+ const currentDistance = getDistance(e.touches[0], e.touches[1]);
1947
+ gestureRef.current.currentDistance = currentDistance;
1948
+ if (gestureRef.current.startDistance > 0) {
1949
+ gestureRef.current.scale = currentDistance / gestureRef.current.startDistance;
1950
+ }
1951
+ const currentAngle = getAngle(e.touches[0], e.touches[1]);
1952
+ const startAngle = gestureRef.current.rotation;
1953
+ gestureRef.current.rotation = currentAngle - startAngle;
1954
+ if (pinchCallbackRef.current) {
1955
+ pinchCallbackRef.current(ctx, e, gestureRef.current);
1956
+ }
1957
+ if (rotateCallbackRef.current && Math.abs(gestureRef.current.rotation) > 5) {
1958
+ rotateCallbackRef.current(ctx, e, gestureRef.current);
1959
+ }
1960
+ }
1961
+ if (touchMoveCallbackRef.current) {
1962
+ touchMoveCallbackRef.current(ctx, e, gestureRef.current);
1963
+ }
1964
+ };
1965
+ const handleTouchEnd = (e) => {
1966
+ if (!gestureRef.current || !gestureRef.current.active || !gestureRef.current.startTouches)
1967
+ return;
1968
+ const now = performance.now();
1969
+ const touchDuration = now - gestureRef.current.startTime;
1970
+ if (touchDuration < 300 && Math.abs(gestureRef.current.deltaX) < 10 && Math.abs(gestureRef.current.deltaY) < 10) {
1971
+ if (tapCallbackRef.current) {
1972
+ tapCallbackRef.current(ctx, e, gestureRef.current);
1973
+ }
1974
+ }
1975
+ const swipeThreshold = 50;
1976
+ const isHorizontalSwipe = Math.abs(gestureRef.current.deltaX) > Math.abs(gestureRef.current.deltaY);
1977
+ if (swipeCallbackRef.current && touchDuration < 300) {
1978
+ if (isHorizontalSwipe && Math.abs(gestureRef.current.deltaX) > swipeThreshold) {
1979
+ const direction = gestureRef.current.deltaX > 0 ? "right" : "left";
1980
+ swipeCallbackRef.current(ctx, e, gestureRef.current, direction);
1981
+ } else if (!isHorizontalSwipe && Math.abs(gestureRef.current.deltaY) > swipeThreshold) {
1982
+ const direction = gestureRef.current.deltaY > 0 ? "down" : "up";
1983
+ swipeCallbackRef.current(ctx, e, gestureRef.current, direction);
1984
+ }
1985
+ }
1986
+ if (touchEndCallbackRef.current) {
1987
+ touchEndCallbackRef.current(ctx, e, gestureRef.current);
1988
+ }
1989
+ if (e.touches.length === 0) {
1990
+ gestureRef.current.active = false;
1991
+ }
1992
+ };
1993
+ const handleTouchCancel = (e) => {
1994
+ if (!gestureRef.current) return;
1995
+ gestureRef.current.active = false;
1996
+ if (touchEndCallbackRef.current) {
1997
+ touchEndCallbackRef.current(ctx, e, gestureRef.current);
1998
+ }
1999
+ };
2000
+ canvas.addEventListener("touchstart", handleTouchStart, {
2001
+ passive: false
2002
+ });
2003
+ canvas.addEventListener("touchmove", handleTouchMove, { passive: false });
2004
+ canvas.addEventListener("touchend", handleTouchEnd);
2005
+ canvas.addEventListener("touchcancel", handleTouchCancel);
2006
+ return () => {
2007
+ canvas.removeEventListener("touchstart", handleTouchStart);
2008
+ canvas.removeEventListener("touchmove", handleTouchMove);
2009
+ canvas.removeEventListener("touchend", handleTouchEnd);
2010
+ canvas.removeEventListener("touchcancel", handleTouchCancel);
2011
+ };
2012
+ }, []);
2013
+ return {
2014
+ gesture: gestureRef.current,
2015
+ onTap: (callback) => tapCallbackRef.current = callback,
2016
+ onSwipe: (callback) => swipeCallbackRef.current = callback,
2017
+ onPinch: (callback) => pinchCallbackRef.current = callback,
2018
+ onRotate: (callback) => rotateCallbackRef.current = callback,
2019
+ onTouchStart: (callback) => touchStartCallbackRef.current = callback,
2020
+ onTouchMove: (callback) => touchMoveCallbackRef.current = callback,
2021
+ onTouchEnd: (callback) => touchEndCallbackRef.current = callback
2022
+ };
2023
+ };
1840
2024
  const KlintWindow = () => {
1841
2025
  const resizeCallbackRef = useRef2(
1842
2026
  null
@@ -1948,6 +2132,7 @@ function useKlint() {
1948
2132
  },
1949
2133
  KlintMouse,
1950
2134
  KlintScroll,
2135
+ KlintGesture,
1951
2136
  KlintWindow,
1952
2137
  KlintImage,
1953
2138
  togglePlay,
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@shopify/klint",
3
3
  "author": "arthurcloche",
4
4
  "license": "MIT",
5
- "version": "0.0.93",
5
+ "version": "0.0.97",
6
6
  "description": "A modern creative coding library",
7
7
  "type": "module",
8
8
  "main": "dist/index.cjs",
@@ -66,12 +66,12 @@
66
66
  "version": "yarn version"
67
67
  },
68
68
  "peerDependencies": {
69
- "react": "^19.1.0",
69
+ "react": ">=16.8.0",
70
70
  "react-dom": ">=16.8.0"
71
71
  },
72
72
  "devDependencies": {
73
- "@types/react": "^19.1.1",
74
- "@types/react-dom": "^19.1.2",
73
+ "@types/react": "^16.0.0",
74
+ "@types/react-dom": "^16.0.0",
75
75
  "concurrently": "^8.2.0",
76
76
  "jsdom": "^26.0.0",
77
77
  "rimraf": "^5.0.0",