@shopify/klint 0.0.4

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/dist/index.cjs ADDED
@@ -0,0 +1,1179 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.tsx
31
+ var src_exports = {};
32
+ __export(src_exports, {
33
+ CONFIG_PROPS: () => CONFIG_PROPS,
34
+ EPSILON: () => EPSILON,
35
+ Klint: () => Klint,
36
+ KlintCoreFunctions: () => KlintCoreFunctions,
37
+ KlintFunctions: () => KlintFunctions,
38
+ useKlint: () => useKlint,
39
+ useProps: () => useProps,
40
+ useStorage: () => useStorage
41
+ });
42
+ module.exports = __toCommonJS(src_exports);
43
+
44
+ // src/Klint.tsx
45
+ var import_react = __toESM(require("react"), 1);
46
+ var DEFAULT_FPS = 60;
47
+ var DEFAULT_ALT = "A beautiful artwork made with Klint Canvas";
48
+ var EPSILON = 1e-4;
49
+ var DEFAULT_OPTIONS = {
50
+ alpha: "true",
51
+ ignoreResize: "false",
52
+ noloop: "false",
53
+ static: "false",
54
+ nocanvas: "false",
55
+ unsafemode: "false",
56
+ willreadfrequently: "false",
57
+ fps: DEFAULT_FPS,
58
+ origin: "corner"
59
+ };
60
+ var CONFIG_PROPS = [
61
+ "lineWidth",
62
+ "strokeStyle",
63
+ "lineJoin",
64
+ "lineCap",
65
+ "fillStyle",
66
+ "font",
67
+ "textAlign",
68
+ "textBaseline",
69
+ "textRendering",
70
+ "wordSpacing",
71
+ "letterSpacing",
72
+ "globalAlpha",
73
+ "globalCompositeOperation",
74
+ "origin",
75
+ "transform",
76
+ "__imageOrigin",
77
+ "__rectangleOrigin",
78
+ "__textFont",
79
+ "__textWeight",
80
+ "__textStyle",
81
+ "__textSize",
82
+ "__textAlignment",
83
+ "__isPlaying"
84
+ ];
85
+ function useAnimate(contextRef, draw, isVisible) {
86
+ const animationFrameId = (0, import_react.useRef)(0);
87
+ const animate = (0, import_react.useCallback)(
88
+ (timestamp = 0) => {
89
+ if (!contextRef.current || !isVisible)
90
+ return;
91
+ if (!contextRef.current.__isReadyToDraw)
92
+ return;
93
+ if (!contextRef.current.__isPlaying) {
94
+ return;
95
+ }
96
+ const context = contextRef.current;
97
+ const now = timestamp;
98
+ const target = 1e3 / context.fps;
99
+ if (!context.__lastTargetTime) {
100
+ context.__lastTargetTime = now;
101
+ context.__lastRealTime = now;
102
+ }
103
+ const sinceLast = now - context.__lastTargetTime;
104
+ const epsilon = 5;
105
+ if (sinceLast >= target - epsilon) {
106
+ context.deltaTime = now - context.__lastRealTime;
107
+ draw(context);
108
+ if (context.time > 1e7)
109
+ context.time = 0;
110
+ if (context.frame > 1e7)
111
+ context.frame = 0;
112
+ context.time += context.deltaTime / DEFAULT_FPS;
113
+ context.frame++;
114
+ context.__lastTargetTime = now;
115
+ context.__lastRealTime = now;
116
+ }
117
+ animationFrameId.current = requestAnimationFrame(animate);
118
+ },
119
+ [draw, isVisible, contextRef]
120
+ );
121
+ return {
122
+ animate,
123
+ animationFrameId
124
+ };
125
+ }
126
+ function Klint({
127
+ context,
128
+ setup,
129
+ draw,
130
+ options = {},
131
+ preload,
132
+ onVisible
133
+ }) {
134
+ const canvasRef = (0, import_react.useRef)(null);
135
+ const containerRef = (0, import_react.useRef)(null);
136
+ const contextRef = (0, import_react.useRef)(null);
137
+ const intersectionObserverRef = (0, import_react.useRef)(null);
138
+ const resizeObserverRef = (0, import_react.useRef)(null);
139
+ const [isVisible, setIsVisible] = (0, import_react.useState)(true);
140
+ const __options = {
141
+ ...DEFAULT_OPTIONS,
142
+ ...options
143
+ };
144
+ const [toStaticImage, setStaticImage] = (0, import_react.useState)(null);
145
+ const initContext = context?.initCoreContext;
146
+ const { animate, animationFrameId } = useAnimate(contextRef, draw, isVisible);
147
+ const updateCanvasSize = (shouldRedraw = false) => {
148
+ if (!containerRef.current || !contextRef.current || !canvasRef.current)
149
+ return;
150
+ const container = containerRef.current;
151
+ const context2 = contextRef.current;
152
+ const canvas = canvasRef.current;
153
+ const { width, height } = container.getBoundingClientRect();
154
+ const config = context2.saveConfig();
155
+ context2.dpr = context2.__dpr;
156
+ canvas.width = context2.width = ~~(width * context2.__dpr);
157
+ canvas.height = context2.height = ~~(height * context2.__dpr);
158
+ canvas.style.width = `${width}px`;
159
+ canvas.style.height = `${height}px`;
160
+ context2.restoreConfig(config);
161
+ if (__options.origin === "center") {
162
+ context2.translate(canvas.width * 0.5, canvas.height * 0.5);
163
+ }
164
+ if (shouldRedraw)
165
+ draw(context2);
166
+ };
167
+ (0, import_react.useEffect)(() => {
168
+ if (!canvasRef.current || !containerRef.current)
169
+ return;
170
+ const canvas = canvasRef.current;
171
+ const container = containerRef.current;
172
+ const dpr = window.devicePixelRatio || 3;
173
+ contextRef.current = initContext ? initContext(canvas, __options) : null;
174
+ const context2 = contextRef.current;
175
+ if (!context2)
176
+ return;
177
+ context2.__dpr = dpr;
178
+ if (__options.fps && __options.fps !== context2.fps) {
179
+ context2.fps = __options.fps;
180
+ }
181
+ updateCanvasSize();
182
+ if (__options.ignoreResize !== "true") {
183
+ resizeObserverRef.current = new ResizeObserver(() => {
184
+ updateCanvasSize(context2.__isReadyToDraw);
185
+ });
186
+ resizeObserverRef.current.observe(container);
187
+ }
188
+ intersectionObserverRef.current = new IntersectionObserver(
189
+ (entries) => {
190
+ entries.forEach((entry) => {
191
+ setIsVisible(entry.isIntersecting);
192
+ onVisible?.(context2);
193
+ });
194
+ },
195
+ { threshold: 0.1, root: null, rootMargin: "50px" }
196
+ );
197
+ intersectionObserverRef.current.observe(container);
198
+ const initializeKlint = async () => {
199
+ if (!context2)
200
+ return;
201
+ const handleStaticMode = () => {
202
+ try {
203
+ const imageUrl = canvas.toDataURL("image/png");
204
+ setStaticImage(imageUrl);
205
+ } catch (error) {
206
+ const message = error instanceof Error ? error.message : String(error);
207
+ throw new Error(`Klint draw error in static mode: ${message}`);
208
+ }
209
+ };
210
+ const initializeContext = async (unsafeReset = false) => {
211
+ if (preload && (unsafeReset || !context2.__isPreloaded)) {
212
+ try {
213
+ await preload(context2);
214
+ if (!unsafeReset)
215
+ context2.__isPreloaded = true;
216
+ } catch (error) {
217
+ const message = error instanceof Error ? error.message : String(error);
218
+ throw new Error(`Klint error in the preload: ${message}`);
219
+ }
220
+ }
221
+ if (setup && (unsafeReset || !context2.__isSetup)) {
222
+ try {
223
+ setup(context2);
224
+ if (!unsafeReset)
225
+ context2.__isSetup = true;
226
+ } catch (error) {
227
+ const message = error instanceof Error ? error.message : String(error);
228
+ throw new Error(`Klint error in the setup: ${message}`);
229
+ }
230
+ }
231
+ if (draw && !context2.__isReadyToDraw) {
232
+ try {
233
+ draw(context2);
234
+ context2.__isReadyToDraw = true;
235
+ } catch (error) {
236
+ const message = error instanceof Error ? error.message : String(error);
237
+ throw new Error(`Klint error in the draw: ${message}`);
238
+ }
239
+ }
240
+ };
241
+ const unsafeMode = __options.unsafemode === "true";
242
+ if (!unsafeMode && context2.__isReadyToDraw)
243
+ return;
244
+ await initializeContext(unsafeMode);
245
+ if (__options.static === "true") {
246
+ handleStaticMode();
247
+ } else {
248
+ if (__options.noloop !== "true")
249
+ animate();
250
+ }
251
+ };
252
+ initializeKlint();
253
+ const frameId = animationFrameId.current;
254
+ return () => {
255
+ resizeObserverRef.current?.disconnect();
256
+ intersectionObserverRef.current?.disconnect();
257
+ if (frameId) {
258
+ cancelAnimationFrame(frameId);
259
+ }
260
+ };
261
+ }, []);
262
+ return /* @__PURE__ */ import_react.default.createElement("div", { ref: containerRef, style: { width: "100%", height: "100%" } }, toStaticImage ? /* @__PURE__ */ import_react.default.createElement(
263
+ "img",
264
+ {
265
+ src: toStaticImage,
266
+ alt: contextRef.current?.__description || DEFAULT_ALT,
267
+ style: {
268
+ display: "block",
269
+ width: "100%",
270
+ height: "100%",
271
+ objectFit: "contain"
272
+ }
273
+ }
274
+ ) : /* @__PURE__ */ import_react.default.createElement(
275
+ "canvas",
276
+ {
277
+ ref: canvasRef,
278
+ style: {
279
+ display: __options.nocanvas === "true" ? "none" : "block"
280
+ },
281
+ "aria-label": contextRef.current?.__description || DEFAULT_ALT,
282
+ role: "img"
283
+ }
284
+ ));
285
+ }
286
+
287
+ // src/useKlint.tsx
288
+ var import_react2 = require("react");
289
+
290
+ // src/KlintFunctions.tsx
291
+ var KlintCoreFunctions = {
292
+ saveCanvas: (ctx) => () => {
293
+ const link = document.createElement("a");
294
+ link.download = "canvas.png";
295
+ link.href = ctx.canvas.toDataURL();
296
+ link.click();
297
+ },
298
+ fullscreen: (ctx) => () => {
299
+ ctx.canvas.requestFullscreen?.();
300
+ },
301
+ play: (ctx) => () => {
302
+ if (!ctx.__isPlaying)
303
+ ctx.__isPlaying = true;
304
+ },
305
+ pause: (ctx) => () => {
306
+ if (ctx.__isPlaying)
307
+ ctx.__isPlaying = false;
308
+ },
309
+ // to do
310
+ redraw: () => () => {
311
+ },
312
+ extend: (ctx) => (name, data, enforceReplace = false) => {
313
+ if (name in ctx && !enforceReplace)
314
+ return;
315
+ ctx[name] = data;
316
+ },
317
+ passImage: () => (element) => {
318
+ if (!element.complete) {
319
+ console.warn("Image passed to passImage() is not fully loaded");
320
+ return null;
321
+ }
322
+ return element;
323
+ },
324
+ passImages: () => (elements) => {
325
+ return elements.map((element) => {
326
+ if (!element.complete) {
327
+ console.warn("Image passed to passImages() is not fully loaded");
328
+ return null;
329
+ }
330
+ return element;
331
+ });
332
+ },
333
+ saveConfig: (ctx) => (from) => {
334
+ return Object.fromEntries(
335
+ CONFIG_PROPS.map((key) => [
336
+ key,
337
+ from?.[key] ?? ctx[key]
338
+ ])
339
+ );
340
+ },
341
+ restoreConfig: (ctx) => (config) => {
342
+ Object.assign(ctx, config);
343
+ },
344
+ describe: (ctx) => (description) => {
345
+ ctx.__description = description;
346
+ },
347
+ createOffscreen: (ctx) => (id, width, height, options, callback) => {
348
+ const offscreen = document.createElement("canvas");
349
+ offscreen.width = width * ctx.__dpr;
350
+ offscreen.height = height * ctx.__dpr;
351
+ const context = offscreen.getContext("2d", {
352
+ alpha: options?.alpha ?? true,
353
+ willReadFrequently: options?.willreadfrequently ?? false
354
+ });
355
+ if (!context)
356
+ throw new Error("Failed to create offscreen context");
357
+ context.__dpr = ctx.__dpr;
358
+ context.width = width * ctx.__dpr;
359
+ context.height = height * ctx.__dpr;
360
+ context.__isMainContext = false;
361
+ context.__imageOrigin = "corner";
362
+ context.__rectangleOrigin = "corner";
363
+ context.__canvasOrigin = "corner";
364
+ context.__textFont = "sans-serif";
365
+ context.__textWeight = "normal";
366
+ context.__textStyle = "normal";
367
+ context.__textSize = 120;
368
+ context.__textAlignment = {
369
+ horizontal: "left",
370
+ vertical: "top"
371
+ };
372
+ if (!options?.ignoreFunctions) {
373
+ Object.entries(KlintFunctions).forEach(([name, fn]) => {
374
+ context[name] = fn(context);
375
+ });
376
+ }
377
+ if (options?.origin) {
378
+ context.__canvasOrigin = options.origin;
379
+ if (options.origin === "center") {
380
+ context.translate(context.width * 0.5, context.height * 0.5);
381
+ }
382
+ }
383
+ if (callback) {
384
+ callback(context);
385
+ }
386
+ if (options?.static === "true") {
387
+ const base64 = offscreen.toDataURL();
388
+ const img = new Image();
389
+ img.src = base64;
390
+ ctx.__offscreens.set(id, img);
391
+ return img;
392
+ }
393
+ ctx.__offscreens.set(id, context);
394
+ return context;
395
+ },
396
+ getOffscreen: (ctx) => (id) => {
397
+ const offscreen = ctx.__offscreens.get(id);
398
+ if (!offscreen)
399
+ throw new Error(`No offscreen context found with id: ${id}`);
400
+ return offscreen;
401
+ }
402
+ };
403
+ var KlintFunctions = {
404
+ extend: (ctx) => (name, data, enforceReplace = false) => {
405
+ if (name in ctx && !enforceReplace)
406
+ return;
407
+ ctx[name] = data;
408
+ },
409
+ background: (ctx) => (color) => {
410
+ ctx.resetTransform();
411
+ ctx.push();
412
+ if (color && color !== "transparent") {
413
+ ctx.fillStyle = color;
414
+ ctx.fillRect(0, 0, ctx.width, ctx.height);
415
+ } else {
416
+ ctx.clearRect(0, 0, ctx.width, ctx.height);
417
+ }
418
+ ctx.pop();
419
+ if (ctx.__canvasOrigin === "center")
420
+ ctx.translate(ctx.width * 0.5, ctx.height * 0.5);
421
+ },
422
+ reset: (ctx) => () => {
423
+ ctx.clearRect(0, 0, ctx.width, ctx.height);
424
+ ctx.resetTransform();
425
+ },
426
+ clear: (ctx) => () => {
427
+ ctx.clearRect(0, 0, ctx.width, ctx.height);
428
+ },
429
+ fillColor: (ctx) => (color) => {
430
+ ctx.fillStyle = color;
431
+ },
432
+ strokeColor: (ctx) => (color) => {
433
+ ctx.strokeStyle = color;
434
+ },
435
+ noFill: (ctx) => () => {
436
+ ctx.fillStyle = "transparent";
437
+ },
438
+ noStroke: (ctx) => () => {
439
+ ctx.strokeStyle = "transparent";
440
+ },
441
+ strokeWidth: (ctx) => (width) => {
442
+ if (width <= 0) {
443
+ ctx.lineWidth = EPSILON;
444
+ }
445
+ ctx.lineWidth = width;
446
+ },
447
+ strokeJoin: (ctx) => (join) => {
448
+ ctx.lineJoin = join;
449
+ },
450
+ strokeCap: (ctx) => (cap) => {
451
+ ctx.lineCap = cap;
452
+ },
453
+ push: (ctx) => () => {
454
+ ctx.save();
455
+ },
456
+ pop: (ctx) => () => {
457
+ ctx.restore();
458
+ },
459
+ point: (ctx) => (x, y) => {
460
+ if (!ctx.checkTransparency("stroke"))
461
+ return;
462
+ ctx.beginPath();
463
+ ctx.strokeRect(x, y, 1, 1);
464
+ },
465
+ checkTransparency: (ctx) => (toCheck) => {
466
+ if (toCheck === "stroke" && ctx.strokeStyle === "transparent")
467
+ return false;
468
+ if (toCheck === "fill" && ctx.fillStyle === "transparent")
469
+ return false;
470
+ return true;
471
+ },
472
+ drawIfVisible: (ctx) => () => {
473
+ if (ctx.checkTransparency("fill"))
474
+ ctx.fill();
475
+ if (ctx.checkTransparency("stroke"))
476
+ ctx.stroke();
477
+ },
478
+ line: (ctx) => (x1, y1, x2, y2) => {
479
+ if (!ctx.checkTransparency("stroke"))
480
+ return;
481
+ ctx.beginPath();
482
+ ctx.moveTo(x1, y1);
483
+ ctx.lineTo(x2, y2);
484
+ ctx.stroke();
485
+ },
486
+ circle: (ctx) => (x, y, radius, radius2) => {
487
+ ctx.beginPath();
488
+ ctx.ellipse(x, y, radius, radius2 || radius, 0, 0, Math.PI * 2);
489
+ ctx.drawIfVisible();
490
+ },
491
+ disk: (ctx) => (x, y, radius, startAngle = 0, endAngle = Math.PI * 2, closed = true) => {
492
+ ctx.beginPath();
493
+ if (closed) {
494
+ ctx.moveTo(x, y);
495
+ ctx.arc(x, y, radius, startAngle, endAngle);
496
+ ctx.lineTo(x, y);
497
+ } else {
498
+ ctx.arc(x, y, radius, startAngle, endAngle);
499
+ }
500
+ ctx.drawIfVisible();
501
+ },
502
+ rectangle: (ctx) => (x, y, width, height) => {
503
+ const originType = ctx.__rectangleOrigin || ctx.origin;
504
+ const h = height ?? width;
505
+ const drawX = originType === "center" ? x - width / 2 : x;
506
+ const drawY = originType === "center" ? y - h / 2 : y;
507
+ ctx.beginPath();
508
+ ctx.rect(drawX, drawY, width, h);
509
+ ctx.drawIfVisible();
510
+ },
511
+ roundedRectangle: (ctx) => (x, y, width, radius, height) => {
512
+ const originType = ctx.__rectangleOrigin || ctx.origin;
513
+ const h = height ?? width;
514
+ const drawX = originType === "center" ? x - width / 2 : x;
515
+ const drawY = originType === "center" ? y - h / 2 : y;
516
+ ctx.beginPath();
517
+ ctx.roundRect(drawX, drawY, width, h, radius);
518
+ ctx.drawIfVisible();
519
+ },
520
+ polygon: (ctx) => (x, y, radius, sides, radius2, rotation = 0) => {
521
+ ctx.beginPath();
522
+ for (let i = 0; i < sides; i++) {
523
+ const angle = i * 2 * Math.PI / sides + rotation;
524
+ const pointX = x + radius * Math.cos(angle);
525
+ 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);
530
+ }
531
+ ctx.closePath();
532
+ ctx.drawIfVisible();
533
+ },
534
+ beginShape: (ctx) => () => {
535
+ if (ctx.__startedShape)
536
+ return;
537
+ ctx.beginPath();
538
+ ctx.__startedShape = true;
539
+ ctx.__currentShape = [];
540
+ ctx.__currentContours = [];
541
+ },
542
+ beginContour: (ctx) => () => {
543
+ if (!ctx.__startedShape)
544
+ return;
545
+ if (ctx.__startedContour && ctx.__currentContour?.length) {
546
+ ctx.__currentContours?.push([...ctx.__currentContour]);
547
+ }
548
+ ctx.__startedContour = true;
549
+ ctx.__currentContour = [];
550
+ },
551
+ vertex: (ctx) => (x, y) => {
552
+ if (!ctx.__startedShape)
553
+ return;
554
+ const points = ctx.__startedContour ? ctx.__currentContour : ctx.__currentShape;
555
+ points?.push([x, y]);
556
+ },
557
+ endContour: (ctx) => (forceRevert = true) => {
558
+ if (!ctx.__startedContour || !ctx.__currentContour?.length)
559
+ return;
560
+ const contourPoints = [...ctx.__currentContour];
561
+ if (forceRevert) {
562
+ contourPoints.reverse();
563
+ }
564
+ ctx.__currentContours?.push(contourPoints);
565
+ ctx.__currentContour = null;
566
+ ctx.__startedContour = false;
567
+ },
568
+ endShape: (ctx) => (close = false) => {
569
+ if (!ctx.__startedShape)
570
+ return;
571
+ if (ctx.__startedContour)
572
+ ctx.endContour();
573
+ const points = ctx.__currentShape;
574
+ if (!points?.length)
575
+ return;
576
+ const drawPath = (points2, close2 = false) => {
577
+ ctx.moveTo(points2[0][0], points2[0][1]);
578
+ for (let i = 1; i < points2.length; i++) {
579
+ ctx.lineTo(points2[i][0], points2[i][1]);
580
+ }
581
+ if (close2) {
582
+ const [firstX, firstY] = points2[0];
583
+ const lastPoint = points2[points2.length - 1];
584
+ if (lastPoint[0] !== firstX || lastPoint[1] !== firstY) {
585
+ ctx.lineTo(firstX, firstY);
586
+ }
587
+ }
588
+ };
589
+ ctx.beginPath();
590
+ drawPath(points, close);
591
+ ctx.__currentContours?.forEach(
592
+ (contour) => drawPath(contour, true)
593
+ );
594
+ ctx.drawIfVisible();
595
+ ctx.__currentShape = null;
596
+ ctx.__currentContours = null;
597
+ ctx.__startedShape = false;
598
+ },
599
+ gradient: (ctx) => (x1 = 0, y1 = 0, x2 = ctx.width, y2 = ctx.width) => {
600
+ return ctx.createLinearGradient(x1, y1, x2, y2);
601
+ },
602
+ radialGradient: (ctx) => (x1 = ctx.width / 2, y1 = ctx.height / 2, r1 = 0, x2 = ctx.width / 2, y2 = ctx.height / 2, r2 = Math.min(ctx.width, ctx.height)) => {
603
+ return ctx.createRadialGradient(x1, y1, r1, x2, y2, r2);
604
+ },
605
+ conicGradient: (ctx) => (angle = 0, x1 = ctx.width / 2, y1 = ctx.height / 2) => {
606
+ return ctx.createConicGradient(angle, x1, y1);
607
+ },
608
+ addColorStop: () => (gradient, offset = 0, color = "#000") => {
609
+ return gradient.addColorStop(offset, color);
610
+ },
611
+ constrain: () => (val, floor, ceil) => {
612
+ return Math.max(floor, Math.min(val, ceil));
613
+ },
614
+ lerp: (ctx) => (A, B, mix, bounded = true) => {
615
+ return A + (B - A) * (bounded ? ctx.constrain(mix, 0, 1) : mix);
616
+ },
617
+ fract: () => (n, mod, mode = "precise") => {
618
+ if (mode === "faster") {
619
+ const floor = (x) => x >> 0;
620
+ return n - floor(n / mod) * mod;
621
+ }
622
+ if (mode === "fast") {
623
+ return n - ~~(n / mod) * mod;
624
+ }
625
+ if (n >= 0)
626
+ return n % mod;
627
+ return mod - -n % mod;
628
+ },
629
+ distance: (ctx) => (x1, y1, x2, y2, mode = "precise") => {
630
+ if (mode === "faster") {
631
+ const dx = Math.abs(x2 - x1);
632
+ const dy = Math.abs(y2 - y1);
633
+ return dx + dy - Math.min(dx, dy) * 0.3;
634
+ }
635
+ if (mode === "fast")
636
+ return ctx.squareDistance(x1, y1, x2, y2) * Math.SQRT1_2;
637
+ return Math.hypot(x2 - x1, y2 - y1);
638
+ },
639
+ squareDistance: () => (x1, y1, x2, y2) => {
640
+ return (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1);
641
+ },
642
+ dot: () => (x1, y1, x2, y2) => {
643
+ return x1 * x2 + y1 * y2;
644
+ },
645
+ remap: (ctx) => (n, A, B, C, D, bounded = true) => {
646
+ const t = (n - A) / (B - A);
647
+ return ctx.lerp(C, D, t, bounded);
648
+ },
649
+ textFont: (ctx) => (font) => {
650
+ ctx.__textFont = font;
651
+ },
652
+ textSize: (ctx) => (size) => {
653
+ ctx.__textSize = size * ctx.__dpr || ctx.__textSize;
654
+ },
655
+ textStyle: (ctx) => (style) => {
656
+ ctx.__textStyle = style || "normal";
657
+ },
658
+ textWeight: (ctx) => (weight) => {
659
+ ctx.__textWeight = weight || "normal";
660
+ },
661
+ textQuality: (ctx) => (quality = "auto") => {
662
+ if (quality === "speed") {
663
+ ctx.textRendering = "optimizeSpeed";
664
+ } else if (quality === "auto") {
665
+ ctx.textRendering = "auto";
666
+ } else if (quality === "legibility") {
667
+ ctx.textRendering = "optimizeLegibility";
668
+ } else if (quality === "precision") {
669
+ ctx.textRendering = "geometricPrecision";
670
+ }
671
+ },
672
+ textSpacing: (ctx) => (kind, value) => {
673
+ ctx[`${kind}Spacing`] = `${value}px`;
674
+ },
675
+ // TO DO : add variable axis handling
676
+ computeTextStyle: (ctx) => () => {
677
+ ctx.__computedTextFont = `${ctx.__textWeight} ${ctx.__textStyle} ${ctx.__textSize}px ${ctx.__textFont}`;
678
+ },
679
+ alignText: (ctx) => (horizontal, vertical) => {
680
+ ctx.__textAlignment.horizontal = horizontal;
681
+ ctx.__textAlignment.vertical = vertical ?? ctx.__textAlignment.vertical;
682
+ },
683
+ textLeading: (ctx) => (spacing) => {
684
+ ctx.lineHeight = `${spacing}px`;
685
+ },
686
+ computeFont: (ctx) => () => {
687
+ ctx.computeTextStyle();
688
+ if (ctx.font !== ctx.__computedTextFont)
689
+ ctx.font = ctx.__computedTextFont;
690
+ },
691
+ textWidth: (ctx) => (text) => {
692
+ ctx.computeFont();
693
+ return ctx.measureText(text).width;
694
+ },
695
+ text: (ctx) => (text, x, y, maxWidth = void 0) => {
696
+ if (text === void 0)
697
+ return;
698
+ ctx.computeFont();
699
+ if (ctx.textAlign !== ctx.__textAlignment.horizontal) {
700
+ ctx.textAlign = ctx.__textAlignment.horizontal;
701
+ }
702
+ if (ctx.textBaseline !== ctx.__textAlignment.vertical) {
703
+ ctx.textBaseline = ctx.__textAlignment.vertical;
704
+ }
705
+ if (ctx.checkTransparency("fill"))
706
+ ctx.fillText(String(text), x, y, maxWidth);
707
+ if (ctx.checkTransparency("stroke"))
708
+ ctx.strokeText(String(text), x, y, maxWidth);
709
+ },
710
+ // DO NOT use putImageData for images you can draw : https://www.measurethat.net/Benchmarks/Show/9510/0/putimagedata-vs-drawimage
711
+ image: (ctx) => (image, x, y, arg3, arg4, arg5, arg6, arg7, arg8) => {
712
+ const sourceImage = "canvas" in image ? image.canvas : image;
713
+ if (arg5 !== void 0) {
714
+ const [sx, sy, sWidth, sHeight, dx2, dy2, dWidth, dHeight] = [
715
+ x,
716
+ y,
717
+ arg3,
718
+ arg4,
719
+ arg5,
720
+ arg6,
721
+ arg7,
722
+ arg8
723
+ ];
724
+ const adjustedX2 = ctx.__imageOrigin === "center" ? dx2 - dWidth / 2 : dx2;
725
+ const adjustedY2 = ctx.__imageOrigin === "center" ? dy2 - dHeight / 2 : dy2;
726
+ ctx.drawImage(
727
+ sourceImage,
728
+ sx,
729
+ sy,
730
+ sWidth,
731
+ sHeight,
732
+ adjustedX2,
733
+ adjustedY2,
734
+ dWidth,
735
+ dHeight
736
+ );
737
+ return;
738
+ }
739
+ if (arg3 !== void 0) {
740
+ const [dx2, dy2, dWidth, dHeight] = [x, y, arg3, arg4];
741
+ const adjustedX2 = ctx.__imageOrigin === "center" ? dx2 - dWidth / 2 : dx2;
742
+ const adjustedY2 = ctx.__imageOrigin === "center" ? dy2 - dHeight / 2 : dy2;
743
+ ctx.drawImage(sourceImage, adjustedX2, adjustedY2, dWidth, dHeight);
744
+ return;
745
+ }
746
+ const [dx, dy] = [x, y];
747
+ const width = sourceImage instanceof HTMLImageElement ? sourceImage.naturalWidth : sourceImage.width;
748
+ const height = sourceImage instanceof HTMLImageElement ? sourceImage.naturalHeight : sourceImage.height;
749
+ const adjustedX = ctx.__imageOrigin === "center" ? dx - width / 2 : dx;
750
+ const adjustedY = ctx.__imageOrigin === "center" ? dy - height / 2 : dy;
751
+ ctx.drawImage(sourceImage, adjustedX, adjustedY);
752
+ },
753
+ // unsure about keeping those next two, maybe a shader plugin would be better
754
+ loadPixels: (ctx) => () => {
755
+ return ctx.getImageData(0, 0, ctx.width, ctx.height);
756
+ },
757
+ updatePixels: (ctx) => (pixels) => {
758
+ const imageData = new ImageData(
759
+ pixels instanceof Uint8ClampedArray ? pixels : new Uint8ClampedArray(pixels),
760
+ ctx.width,
761
+ ctx.height
762
+ );
763
+ ctx.putImageData(imageData, 0, 0);
764
+ },
765
+ readPixels: (ctx) => (x, y, w = 1, h = 1) => {
766
+ const imageData = ctx.getImageData(x, y, w, h);
767
+ return Array.from(imageData.data);
768
+ },
769
+ scaleTo: () => (originWidth, originHeight, destinationWidth, destinationHeight, cover = false) => {
770
+ const widthRatio = destinationWidth / originWidth;
771
+ const heightRatio = destinationHeight / originHeight;
772
+ return cover ? Math.max(widthRatio, heightRatio) : Math.min(widthRatio, heightRatio);
773
+ },
774
+ opacity: (ctx) => (value) => {
775
+ ctx.globalAlpha = ctx.constrain(value, 0, 1);
776
+ },
777
+ blend: (ctx) => (blend) => {
778
+ ctx.globalCompositeOperation = blend;
779
+ },
780
+ setCanvasOrigin: (ctx) => (type) => {
781
+ ctx.__canvasOrigin = type;
782
+ },
783
+ setImageOrigin: (ctx) => (type) => {
784
+ ctx.__imageOrigin = type;
785
+ },
786
+ setRectOrigin: (ctx) => (type) => {
787
+ ctx.__rectangleOrigin = type;
788
+ },
789
+ withConfig: (ctx) => (config) => {
790
+ Object.assign(ctx, config);
791
+ },
792
+ toBase64: (ctx) => (type = "image/png", quality) => {
793
+ const canvas = ctx.canvas;
794
+ return canvas.toDataURL(type, quality);
795
+ },
796
+ saveConfig: (ctx) => (from) => {
797
+ return Object.fromEntries(
798
+ CONFIG_PROPS.map((key) => [
799
+ key,
800
+ from?.[key] ?? ctx[key]
801
+ ])
802
+ );
803
+ },
804
+ restoreConfig: (ctx) => (config) => {
805
+ Object.assign(ctx, config);
806
+ },
807
+ resizeCanvas: (ctx) => (width, height) => {
808
+ if (ctx.__isMainContext)
809
+ return;
810
+ const config = ctx.saveConfig();
811
+ ctx.canvas.width = ctx.width = width;
812
+ ctx.canvas.height = ctx.height = height;
813
+ ctx.restoreConfig(config);
814
+ if (ctx.__canvasOrigin === "center") {
815
+ ctx.translate(ctx.width * 0.5, ctx.height * 0.5);
816
+ }
817
+ }
818
+ };
819
+
820
+ // src/useKlint.tsx
821
+ var DEFAULT_MOUSE_STATE = {
822
+ x: 0,
823
+ y: 0,
824
+ px: 0,
825
+ py: 0,
826
+ vx: 0,
827
+ vy: 0,
828
+ angle: 0,
829
+ isPressed: false,
830
+ isHover: false
831
+ };
832
+ var DEFAULT_SCROLL_STATE = {
833
+ distance: 0,
834
+ velocity: 0,
835
+ lastTime: 0
836
+ };
837
+ function useKlint() {
838
+ const contextRef = (0, import_react2.useRef)(null);
839
+ const mouseRef = (0, import_react2.useRef)(null);
840
+ const scrollRef = (0, import_react2.useRef)(null);
841
+ const useImage = () => {
842
+ const imagesRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
843
+ const loadImage = (0, import_react2.useCallback)(
844
+ async (key, url) => {
845
+ return new Promise((resolve, reject) => {
846
+ const img = new Image();
847
+ img.onload = () => {
848
+ img.width = img.naturalWidth;
849
+ img.height = img.naturalHeight;
850
+ imagesRef.current.set(key, img);
851
+ resolve(img);
852
+ };
853
+ img.onerror = reject;
854
+ img.src = url;
855
+ });
856
+ },
857
+ []
858
+ );
859
+ const loadImages = (0, import_react2.useCallback)(
860
+ async (imageMap) => {
861
+ const promises = Object.entries(imageMap).map(
862
+ ([key, url]) => loadImage(key, url).then(
863
+ (img) => [key, img]
864
+ )
865
+ );
866
+ const results = await Promise.all(promises);
867
+ return new Map(results);
868
+ },
869
+ [loadImage]
870
+ );
871
+ const imagesProxy = (0, import_react2.useMemo)(() => {
872
+ return new Proxy({}, {
873
+ get: (_, prop) => {
874
+ if (prop === "get") {
875
+ return (key) => imagesRef.current.get(key);
876
+ }
877
+ if (typeof prop === "string") {
878
+ return imagesRef.current.get(prop);
879
+ }
880
+ return void 0;
881
+ },
882
+ has: (_, prop) => {
883
+ if (typeof prop === "string") {
884
+ return imagesRef.current.has(prop);
885
+ }
886
+ return false;
887
+ }
888
+ });
889
+ }, []);
890
+ return {
891
+ images: imagesProxy,
892
+ loadImage,
893
+ loadImages,
894
+ getImage: (0, import_react2.useCallback)((key) => imagesRef.current.get(key), []),
895
+ hasImage: (0, import_react2.useCallback)((key) => imagesRef.current.has(key), []),
896
+ clearImages: (0, import_react2.useCallback)(() => imagesRef.current.clear(), [])
897
+ };
898
+ };
899
+ const useMouse = () => {
900
+ if (!mouseRef.current) {
901
+ mouseRef.current = { ...DEFAULT_MOUSE_STATE };
902
+ }
903
+ const clickCallbackRef = (0, import_react2.useRef)(null);
904
+ const mouseInCallbackRef = (0, import_react2.useRef)(null);
905
+ const mouseOutCallbackRef = (0, import_react2.useRef)(null);
906
+ const mouseDownCallbackRef = (0, import_react2.useRef)(null);
907
+ const mouseUpCallbackRef = (0, import_react2.useRef)(null);
908
+ (0, import_react2.useEffect)(() => {
909
+ if (!contextRef.current?.canvas)
910
+ return;
911
+ const canvas = contextRef.current.canvas;
912
+ const ctx = contextRef.current;
913
+ const updateMousePosition = (e) => {
914
+ const rect = canvas.getBoundingClientRect();
915
+ const dpr = window.devicePixelRatio || 1;
916
+ const origin = contextRef.current?.__canvasOrigin || "corner";
917
+ const x = origin === "center" ? (e.clientX - rect.left) * dpr - canvas.width / 2 : (e.clientX - rect.left) * dpr;
918
+ const y = origin === "center" ? (e.clientY - rect.top) * dpr - canvas.height / 2 : (e.clientY - rect.top) * dpr;
919
+ if (mouseRef.current) {
920
+ mouseRef.current.px = mouseRef.current.x;
921
+ mouseRef.current.py = mouseRef.current.y;
922
+ mouseRef.current.x = x;
923
+ mouseRef.current.y = y;
924
+ mouseRef.current.vx = x - mouseRef.current.px;
925
+ mouseRef.current.vy = y - mouseRef.current.py;
926
+ mouseRef.current.angle = Math.atan2(
927
+ mouseRef.current.vy,
928
+ mouseRef.current.vx
929
+ );
930
+ }
931
+ };
932
+ const handleMouseDown = (e) => {
933
+ if (mouseRef.current)
934
+ mouseRef.current.isPressed = true;
935
+ if (mouseDownCallbackRef.current)
936
+ mouseDownCallbackRef.current(ctx, e);
937
+ };
938
+ const handleMouseUp = (e) => {
939
+ if (mouseRef.current)
940
+ mouseRef.current.isPressed = false;
941
+ if (mouseUpCallbackRef.current)
942
+ mouseUpCallbackRef.current(ctx, e);
943
+ };
944
+ const handleMouseEnter = (e) => {
945
+ if (mouseRef.current)
946
+ mouseRef.current.isHover = true;
947
+ if (mouseInCallbackRef.current)
948
+ mouseInCallbackRef.current(ctx, e);
949
+ };
950
+ const handleMouseLeave = (e) => {
951
+ if (mouseRef.current)
952
+ mouseRef.current.isHover = false;
953
+ if (mouseOutCallbackRef.current)
954
+ mouseOutCallbackRef.current(ctx, e);
955
+ };
956
+ const handleClick = (e) => {
957
+ if (clickCallbackRef.current)
958
+ clickCallbackRef.current(ctx, e);
959
+ };
960
+ canvas.addEventListener("mousemove", updateMousePosition);
961
+ canvas.addEventListener("mousedown", handleMouseDown);
962
+ canvas.addEventListener("mouseup", handleMouseUp);
963
+ canvas.addEventListener("mouseenter", handleMouseEnter);
964
+ canvas.addEventListener("mouseleave", handleMouseLeave);
965
+ canvas.addEventListener("click", handleClick);
966
+ return () => {
967
+ canvas.removeEventListener("mousemove", updateMousePosition);
968
+ canvas.removeEventListener("mousedown", handleMouseDown);
969
+ canvas.removeEventListener("mouseup", handleMouseUp);
970
+ canvas.removeEventListener("mouseenter", handleMouseEnter);
971
+ canvas.removeEventListener("mouseleave", handleMouseLeave);
972
+ canvas.removeEventListener("click", handleClick);
973
+ };
974
+ });
975
+ return {
976
+ mouse: mouseRef.current,
977
+ onClick: (callback) => clickCallbackRef.current = callback,
978
+ onMouseIn: (callback) => mouseInCallbackRef.current = callback,
979
+ onMouseOut: (callback) => mouseOutCallbackRef.current = callback,
980
+ onMouseDown: (callback) => mouseDownCallbackRef.current = callback,
981
+ onMouseUp: (callback) => mouseUpCallbackRef.current = callback
982
+ };
983
+ };
984
+ const useScroll = () => {
985
+ if (!scrollRef.current) {
986
+ scrollRef.current = { ...DEFAULT_SCROLL_STATE };
987
+ }
988
+ const scrollCallbackRef = (0, import_react2.useRef)(null);
989
+ (0, import_react2.useEffect)(() => {
990
+ if (!contextRef.current?.canvas)
991
+ return;
992
+ const canvas = contextRef.current.canvas;
993
+ const ctx = contextRef.current;
994
+ const handleScroll = (e) => {
995
+ e.preventDefault();
996
+ if (!scrollRef.current)
997
+ return;
998
+ const currentTime = performance.now();
999
+ const deltaTime = currentTime - scrollRef.current.lastTime;
1000
+ scrollRef.current.distance += e.deltaY;
1001
+ scrollRef.current.velocity = deltaTime > 0 ? e.deltaY / deltaTime : 0;
1002
+ scrollRef.current.lastTime = currentTime;
1003
+ if (scrollCallbackRef.current) {
1004
+ scrollCallbackRef.current(ctx, scrollRef.current, e);
1005
+ }
1006
+ };
1007
+ canvas.addEventListener("wheel", handleScroll);
1008
+ return () => canvas.removeEventListener("wheel", handleScroll);
1009
+ });
1010
+ return {
1011
+ scroll: scrollRef.current,
1012
+ onScroll: (callback) => scrollCallbackRef.current = callback
1013
+ };
1014
+ };
1015
+ const useWindow = () => {
1016
+ const resizeCallbackRef = (0, import_react2.useRef)(
1017
+ null
1018
+ );
1019
+ const blurCallbackRef = (0, import_react2.useRef)(null);
1020
+ const focusCallbackRef = (0, import_react2.useRef)(null);
1021
+ const visibilityChangeCallbackRef = (0, import_react2.useRef)(null);
1022
+ (0, import_react2.useEffect)(() => {
1023
+ if (!contextRef.current)
1024
+ return;
1025
+ const ctx = contextRef.current;
1026
+ const handleResize = () => {
1027
+ if (resizeCallbackRef.current)
1028
+ resizeCallbackRef.current(ctx);
1029
+ };
1030
+ const handleBlur = () => {
1031
+ if (blurCallbackRef.current)
1032
+ blurCallbackRef.current(ctx);
1033
+ };
1034
+ const handleFocus = () => {
1035
+ if (focusCallbackRef.current)
1036
+ focusCallbackRef.current(ctx);
1037
+ };
1038
+ const handleVisibilityChange = () => {
1039
+ const isVisible = document.visibilityState === "visible";
1040
+ if (visibilityChangeCallbackRef.current) {
1041
+ visibilityChangeCallbackRef.current(ctx, isVisible);
1042
+ }
1043
+ };
1044
+ window.addEventListener("resize", handleResize);
1045
+ window.addEventListener("blur", handleBlur);
1046
+ window.addEventListener("focus", handleFocus);
1047
+ document.addEventListener("visibilitychange", handleVisibilityChange);
1048
+ return () => {
1049
+ window.removeEventListener("resize", handleResize);
1050
+ window.removeEventListener("blur", handleBlur);
1051
+ window.removeEventListener("focus", handleFocus);
1052
+ document.removeEventListener(
1053
+ "visibilitychange",
1054
+ handleVisibilityChange
1055
+ );
1056
+ };
1057
+ }, []);
1058
+ return {
1059
+ onResize: (callback) => resizeCallbackRef.current = callback,
1060
+ onBlur: (callback) => blurCallbackRef.current = callback,
1061
+ onFocus: (callback) => focusCallbackRef.current = callback,
1062
+ onVisibilityChange: (callback) => visibilityChangeCallbackRef.current = callback
1063
+ };
1064
+ };
1065
+ const buildKlintContext = (ctx, options) => {
1066
+ const context = ctx;
1067
+ context.__isMainContext = true;
1068
+ context.fps = 60;
1069
+ context.frame = 0;
1070
+ context.time = 0;
1071
+ context.deltaTime = 0;
1072
+ context.__imageOrigin = options.origin === "center" ? "center" : "corner";
1073
+ context.__rectangleOrigin = options.origin === "center" ? "center" : "corner";
1074
+ context.__canvasOrigin = options.origin === "center" ? "center" : "corner";
1075
+ context.__textFont = "sans-serif";
1076
+ context.__textWeight = "normal";
1077
+ context.__textStyle = "normal";
1078
+ context.__textSize = 72;
1079
+ context.__textAlignment = {
1080
+ horizontal: "left",
1081
+ vertical: "top"
1082
+ };
1083
+ context.__offscreens = /* @__PURE__ */ new Map();
1084
+ context.__isPlaying = true;
1085
+ context.__currentContext = context;
1086
+ Object.entries(KlintCoreFunctions).forEach(([name, fn]) => {
1087
+ context[name] = fn(context);
1088
+ });
1089
+ Object.entries(KlintFunctions).forEach(([name, fn]) => {
1090
+ context[name] = fn(context);
1091
+ });
1092
+ return context;
1093
+ };
1094
+ const initCoreContext = (0, import_react2.useCallback)(
1095
+ (canvas, options) => {
1096
+ if (!contextRef.current) {
1097
+ const ctx = canvas.getContext("2d", {
1098
+ alpha: options.alpha ?? true,
1099
+ willReadFrequently: options.willreadfrequently ?? true
1100
+ });
1101
+ if (!ctx)
1102
+ throw new Error("Failed to get canvas context");
1103
+ contextRef.current = buildKlintContext(ctx, options);
1104
+ }
1105
+ return contextRef.current;
1106
+ },
1107
+ []
1108
+ );
1109
+ const togglePlay = (0, import_react2.useCallback)((playing) => {
1110
+ if (!contextRef.current)
1111
+ return;
1112
+ if (playing !== void 0) {
1113
+ contextRef.current.__isPlaying = playing;
1114
+ } else {
1115
+ contextRef.current.__isPlaying = !contextRef.current.__isPlaying;
1116
+ }
1117
+ }, []);
1118
+ return {
1119
+ context: {
1120
+ context: contextRef.current,
1121
+ initCoreContext
1122
+ },
1123
+ useMouse,
1124
+ useScroll,
1125
+ useWindow,
1126
+ useImage,
1127
+ togglePlay
1128
+ };
1129
+ }
1130
+ var useProps = (props) => {
1131
+ const propsRef = (0, import_react2.useRef)(props);
1132
+ (0, import_react2.useEffect)(() => {
1133
+ propsRef.current = props;
1134
+ }, [props]);
1135
+ const get = (0, import_react2.useCallback)((key) => {
1136
+ return propsRef.current[key];
1137
+ }, []);
1138
+ const has = (0, import_react2.useCallback)((key) => {
1139
+ return key in propsRef.current;
1140
+ }, []);
1141
+ return {
1142
+ get,
1143
+ has,
1144
+ props: propsRef.current
1145
+ };
1146
+ };
1147
+ var useStorage = (initialProps = {}) => {
1148
+ const storeRef = (0, import_react2.useRef)(initialProps);
1149
+ const get = (0, import_react2.useCallback)((key) => {
1150
+ return storeRef.current[key];
1151
+ }, []);
1152
+ const set = (0, import_react2.useCallback)((key, value) => {
1153
+ storeRef.current[key] = value;
1154
+ }, []);
1155
+ const has = (0, import_react2.useCallback)((key) => {
1156
+ return key in storeRef.current;
1157
+ }, []);
1158
+ const remove = (0, import_react2.useCallback)((key) => {
1159
+ delete storeRef.current[key];
1160
+ }, []);
1161
+ return {
1162
+ get,
1163
+ set,
1164
+ has,
1165
+ remove,
1166
+ store: storeRef.current
1167
+ };
1168
+ };
1169
+ // Annotate the CommonJS export names for ESM import in node:
1170
+ 0 && (module.exports = {
1171
+ CONFIG_PROPS,
1172
+ EPSILON,
1173
+ Klint,
1174
+ KlintCoreFunctions,
1175
+ KlintFunctions,
1176
+ useKlint,
1177
+ useProps,
1178
+ useStorage
1179
+ });