svelte-p5-components 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/dist/Sketch.svelte +20 -18
- package/dist/Sketch.svelte.d.ts +23 -12
- package/dist/Sketch.test.svelte.d.ts +1 -0
- package/dist/Sketch.test.svelte.js +102 -0
- package/dist/index.d.ts +1 -0
- package/dist/sketch-types.d.ts +28 -0
- package/dist/sketch-types.js +1 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -41,6 +41,16 @@ Pin to a SHA (`@<commit-sha>`) instead of `@main` for reproducible installs.
|
|
|
41
41
|
</div>
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
+
Props:
|
|
45
|
+
|
|
46
|
+
| Prop | Type | Default | Notes |
|
|
47
|
+
| ---------- | ------------------------------------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
48
|
+
| `sketch` | `SketchFn<Ext>` | — | Required. Generic over custom instance members (see the core README). |
|
|
49
|
+
| `hidpi` | `boolean \| number` | `true` | `true` → `devicePixelRatio`, `false` → `pixelDensity(1)`, number → that exact density (cap for large WEBGL canvases: `Math.min(devicePixelRatio, 2)`). |
|
|
50
|
+
| `instance` | `ExtendedP5<Ext> \| null` (bindable) | `null` | The p5 instance once mounted. |
|
|
51
|
+
| `onReady` | `(instance) => void` | — | Fires once after creation, density, and initial sizing. |
|
|
52
|
+
| `onResize` | `(instance, width, height) => void` | — | Fires after each `ResizeObserver`-driven `resizeCanvas` (not the initial sizing). Rebuild size-dependent state here; call `instance.loop()` if you're noLoop-gated. |
|
|
53
|
+
|
|
44
54
|
## `<FPSMonitor>`
|
|
45
55
|
|
|
46
56
|
Absolute-positioned FPS readout. Takes the p5 instance and samples `p.frameRate()` every 30 frames by default.
|
package/dist/Sketch.svelte
CHANGED
|
@@ -1,16 +1,6 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { P5Canvas, type
|
|
3
|
-
import type
|
|
4
|
-
|
|
5
|
-
interface Props {
|
|
6
|
-
sketch: SketchFn;
|
|
7
|
-
/** Apply `pixelDensity(devicePixelRatio)` on ready. Default: true */
|
|
8
|
-
hidpi?: boolean;
|
|
9
|
-
class?: string;
|
|
10
|
-
style?: string;
|
|
11
|
-
instance?: p5 | null;
|
|
12
|
-
onReady?: (instance: p5) => void;
|
|
13
|
-
}
|
|
1
|
+
<script lang="ts" generics="Ext = unknown">
|
|
2
|
+
import { P5Canvas, type ExtendedP5 } from 'svelte-p5';
|
|
3
|
+
import type { SketchProps } from './sketch-types.js';
|
|
14
4
|
|
|
15
5
|
let {
|
|
16
6
|
sketch,
|
|
@@ -18,14 +8,22 @@
|
|
|
18
8
|
class: className = '',
|
|
19
9
|
style = 'display: block; width: 100%; height: 100%; overflow: hidden;',
|
|
20
10
|
instance = $bindable(null),
|
|
21
|
-
onReady
|
|
22
|
-
|
|
11
|
+
onReady,
|
|
12
|
+
onResize
|
|
13
|
+
}: SketchProps<Ext> = $props();
|
|
23
14
|
|
|
24
15
|
let container: HTMLDivElement | null = $state(null);
|
|
25
16
|
|
|
26
|
-
function handleReady(p:
|
|
27
|
-
if (
|
|
28
|
-
|
|
17
|
+
function handleReady(p: ExtendedP5<Ext>) {
|
|
18
|
+
if (typeof window !== 'undefined') {
|
|
19
|
+
if (typeof hidpi === 'number') {
|
|
20
|
+
p.pixelDensity(hidpi);
|
|
21
|
+
} else if (hidpi) {
|
|
22
|
+
p.pixelDensity(window.devicePixelRatio);
|
|
23
|
+
} else {
|
|
24
|
+
// p5 defaults to devicePixelRatio, so opting out must be explicit.
|
|
25
|
+
p.pixelDensity(1);
|
|
26
|
+
}
|
|
29
27
|
}
|
|
30
28
|
// Size immediately to the current container dimensions.
|
|
31
29
|
if (container) {
|
|
@@ -43,7 +41,11 @@
|
|
|
43
41
|
if (!entry || !instance) return;
|
|
44
42
|
const w = Math.max(1, Math.floor(entry.contentRect.width));
|
|
45
43
|
const h = Math.max(1, Math.floor(entry.contentRect.height));
|
|
44
|
+
// Skip no-op resizes; this also swallows the observer's initial
|
|
45
|
+
// fire, which would otherwise double-report the handleReady sizing.
|
|
46
|
+
if (w === instance.width && h === instance.height) return;
|
|
46
47
|
instance.resizeCanvas(w, h);
|
|
48
|
+
onResize?.(instance, w, h);
|
|
47
49
|
});
|
|
48
50
|
ro.observe(el);
|
|
49
51
|
return () => ro.disconnect();
|
package/dist/Sketch.svelte.d.ts
CHANGED
|
@@ -1,14 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
import type { SketchProps } from './sketch-types.js';
|
|
2
|
+
declare function $$render<Ext = unknown>(): {
|
|
3
|
+
props: SketchProps<Ext>;
|
|
4
|
+
exports: {};
|
|
5
|
+
bindings: "instance";
|
|
6
|
+
slots: {};
|
|
7
|
+
events: {};
|
|
8
|
+
};
|
|
9
|
+
declare class __sveltets_Render<Ext = unknown> {
|
|
10
|
+
props(): ReturnType<typeof $$render<Ext>>['props'];
|
|
11
|
+
events(): ReturnType<typeof $$render<Ext>>['events'];
|
|
12
|
+
slots(): ReturnType<typeof $$render<Ext>>['slots'];
|
|
13
|
+
bindings(): "instance";
|
|
14
|
+
exports(): {};
|
|
11
15
|
}
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
interface $$IsomorphicComponent {
|
|
17
|
+
new <Ext = unknown>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<Ext>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<Ext>['props']>, ReturnType<__sveltets_Render<Ext>['events']>, ReturnType<__sveltets_Render<Ext>['slots']>> & {
|
|
18
|
+
$$bindings?: ReturnType<__sveltets_Render<Ext>['bindings']>;
|
|
19
|
+
} & ReturnType<__sveltets_Render<Ext>['exports']>;
|
|
20
|
+
<Ext = unknown>(internal: unknown, props: ReturnType<__sveltets_Render<Ext>['props']> & {}): ReturnType<__sveltets_Render<Ext>['exports']>;
|
|
21
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
22
|
+
}
|
|
23
|
+
declare const Sketch: $$IsomorphicComponent;
|
|
24
|
+
type Sketch<Ext = unknown> = InstanceType<typeof Sketch<Ext>>;
|
|
14
25
|
export default Sketch;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { render } from '@testing-library/svelte';
|
|
3
|
+
import Sketch from './Sketch.svelte';
|
|
4
|
+
// P5Canvas does `await import('p5')`; give it a fake constructor that mirrors
|
|
5
|
+
// the bits Sketch touches. p5 calls the sketch function synchronously inside
|
|
6
|
+
// the constructor, and so does the fake.
|
|
7
|
+
vi.mock('p5', () => {
|
|
8
|
+
class FakeP5 {
|
|
9
|
+
width = 0;
|
|
10
|
+
height = 0;
|
|
11
|
+
resizeCanvas = vi.fn((w, h) => {
|
|
12
|
+
this.width = w;
|
|
13
|
+
this.height = h;
|
|
14
|
+
});
|
|
15
|
+
pixelDensity = vi.fn();
|
|
16
|
+
remove = vi.fn();
|
|
17
|
+
// P5Canvas passes (sketchFn, containerNode); the node is irrelevant here.
|
|
18
|
+
constructor(sketch) {
|
|
19
|
+
sketch(this);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return { default: FakeP5 };
|
|
23
|
+
});
|
|
24
|
+
// Controllable ResizeObserver stub: tests fire the captured callback directly.
|
|
25
|
+
let roCallback = null;
|
|
26
|
+
const savedRO = globalThis.ResizeObserver;
|
|
27
|
+
function fireResize(width, height) {
|
|
28
|
+
roCallback?.([{ contentRect: { width, height } }], {});
|
|
29
|
+
}
|
|
30
|
+
async function renderSketch(props = {}) {
|
|
31
|
+
let ready = null;
|
|
32
|
+
const onReady = (p) => {
|
|
33
|
+
ready = p;
|
|
34
|
+
};
|
|
35
|
+
const result = render(Sketch, { sketch: () => { }, onReady, ...props });
|
|
36
|
+
// P5Canvas awaits import('p5') before constructing; wait for onReady.
|
|
37
|
+
await vi.waitFor(() => {
|
|
38
|
+
if (!ready)
|
|
39
|
+
throw new Error('instance not ready');
|
|
40
|
+
});
|
|
41
|
+
return { ...result, instance: ready };
|
|
42
|
+
}
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
roCallback = null;
|
|
45
|
+
globalThis.ResizeObserver = class {
|
|
46
|
+
constructor(cb) {
|
|
47
|
+
roCallback = cb;
|
|
48
|
+
}
|
|
49
|
+
observe() { }
|
|
50
|
+
unobserve() { }
|
|
51
|
+
disconnect() { }
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
afterEach(() => {
|
|
55
|
+
globalThis.ResizeObserver = savedRO;
|
|
56
|
+
});
|
|
57
|
+
describe('<Sketch> resize', () => {
|
|
58
|
+
it('calls resizeCanvas then onResize, in that order, when the container resizes', async () => {
|
|
59
|
+
const calls = [];
|
|
60
|
+
const onResize = vi.fn((_p, w, h) => {
|
|
61
|
+
calls.push(`onResize:${w}x${h}`);
|
|
62
|
+
});
|
|
63
|
+
const { instance } = await renderSketch({ onResize });
|
|
64
|
+
instance.resizeCanvas.mockImplementation((w, h) => {
|
|
65
|
+
instance.width = w;
|
|
66
|
+
instance.height = h;
|
|
67
|
+
calls.push(`resizeCanvas:${w}x${h}`);
|
|
68
|
+
});
|
|
69
|
+
fireResize(640, 480);
|
|
70
|
+
expect(calls).toEqual(['resizeCanvas:640x480', 'onResize:640x480']);
|
|
71
|
+
expect(onResize).toHaveBeenCalledWith(instance, 640, 480);
|
|
72
|
+
});
|
|
73
|
+
it('skips resizeCanvas and onResize when the size is unchanged', async () => {
|
|
74
|
+
const onResize = vi.fn();
|
|
75
|
+
const { instance } = await renderSketch({ onResize });
|
|
76
|
+
instance.width = 300;
|
|
77
|
+
instance.height = 200;
|
|
78
|
+
instance.resizeCanvas.mockClear();
|
|
79
|
+
fireResize(300, 200);
|
|
80
|
+
expect(instance.resizeCanvas).not.toHaveBeenCalled();
|
|
81
|
+
expect(onResize).not.toHaveBeenCalled();
|
|
82
|
+
});
|
|
83
|
+
it('does not fire onResize for the initial handleReady sizing', async () => {
|
|
84
|
+
const onResize = vi.fn();
|
|
85
|
+
await renderSketch({ onResize });
|
|
86
|
+
expect(onResize).not.toHaveBeenCalled();
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
describe('<Sketch> pixel density', () => {
|
|
90
|
+
it('hidpi=true applies devicePixelRatio', async () => {
|
|
91
|
+
const { instance } = await renderSketch({ hidpi: true });
|
|
92
|
+
expect(instance.pixelDensity).toHaveBeenCalledWith(window.devicePixelRatio);
|
|
93
|
+
});
|
|
94
|
+
it('hidpi=false pins density to 1', async () => {
|
|
95
|
+
const { instance } = await renderSketch({ hidpi: false });
|
|
96
|
+
expect(instance.pixelDensity).toHaveBeenCalledWith(1);
|
|
97
|
+
});
|
|
98
|
+
it('numeric hidpi applies that exact density', async () => {
|
|
99
|
+
const { instance } = await renderSketch({ hidpi: 1.5 });
|
|
100
|
+
expect(instance.pixelDensity).toHaveBeenCalledWith(1.5);
|
|
101
|
+
});
|
|
102
|
+
});
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { default as Sketch } from './Sketch.svelte';
|
|
2
|
+
export type { SketchProps } from './sketch-types.js';
|
|
2
3
|
export { default as FPSMonitor } from './FPSMonitor.svelte';
|
|
3
4
|
export { default as SketchDebug } from './SketchDebug.svelte';
|
|
4
5
|
export { default as DraggableWindow } from './DraggableWindow.svelte';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ExtendedP5, SketchFn } from 'svelte-p5';
|
|
2
|
+
/**
|
|
3
|
+
* Props accepted by the `<Sketch>` component.
|
|
4
|
+
*/
|
|
5
|
+
export interface SketchProps<Ext = unknown> {
|
|
6
|
+
sketch: SketchFn<Ext>;
|
|
7
|
+
/**
|
|
8
|
+
* Pixel density applied on ready. `true` → `devicePixelRatio`,
|
|
9
|
+
* `false` → 1 (p5's own default is already devicePixelRatio, so this
|
|
10
|
+
* is the only way to genuinely opt out), a number → that exact
|
|
11
|
+
* density. Cap it for large WEBGL canvases, e.g.
|
|
12
|
+
* `Math.min(devicePixelRatio, 2)`. Default: true.
|
|
13
|
+
*/
|
|
14
|
+
hidpi?: boolean | number;
|
|
15
|
+
class?: string;
|
|
16
|
+
style?: string;
|
|
17
|
+
/** Bindable: the p5 instance, available after mount. `null` before mount or after unmount. */
|
|
18
|
+
instance?: ExtendedP5<Ext> | null;
|
|
19
|
+
/** Called once after creation, pixel density, and initial container sizing. */
|
|
20
|
+
onReady?: (instance: ExtendedP5<Ext>) => void;
|
|
21
|
+
/**
|
|
22
|
+
* Called after the canvas has been resized to track its container
|
|
23
|
+
* (ResizeObserver path). Not called for the initial sizing — use
|
|
24
|
+
* `onReady` for that. Rebuild size-dependent state here and request
|
|
25
|
+
* a redraw if your sketch is noLoop-gated.
|
|
26
|
+
*/
|
|
27
|
+
onResize?: (instance: ExtendedP5<Ext>, width: number, height: number) => void;
|
|
28
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svelte-p5-components",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Higher-level Svelte 5 components built on svelte-p5: responsive canvas, FPS monitor, sketch debug overlay, draggable windows",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"svelte",
|
|
@@ -51,10 +51,11 @@
|
|
|
51
51
|
"test:coverage": "vitest run --coverage"
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
|
+
"@neodrag/core": "3.0.0-next.8",
|
|
54
55
|
"@neodrag/svelte": "3.0.0-next.8"
|
|
55
56
|
},
|
|
56
57
|
"peerDependencies": {
|
|
57
|
-
"svelte-p5": "
|
|
58
|
+
"svelte-p5": ">=0.4.0",
|
|
58
59
|
"p5": ">=1.11.0 <3",
|
|
59
60
|
"svelte": "^5.0.0"
|
|
60
61
|
},
|