@glowbox/svelte 1.0.0-rc.2

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Eetu Sutinen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # @glowbox/svelte
2
+
3
+ glowbox components for **Svelte 5**: `<LedGrid>` — the 3D WebGL LED-grid display
4
+ (over **[@glowbox/led-grid](../led-grid)**) — and `<NixieTube>` — a glowing nixie-tube numeral
5
+ (over **[@glowbox/nixie](../nixie)**).
6
+
7
+ ```sh
8
+ yarn add @glowbox/svelte
9
+ # peer: svelte ^5 (@glowbox/led-grid + @glowbox/nixie come along as dependencies)
10
+ ```
11
+
12
+ ## `<LedGrid>`
13
+
14
+ ```svelte
15
+ <script lang="ts">
16
+ import { LedGrid, type LedDisplay } from '@glowbox/svelte';
17
+
18
+ const draw = (d: LedDisplay, dt: number) => {
19
+ d.clear();
20
+ d.sphere([4, 4, 4], 3, '#00aaff');
21
+ };
22
+ </script>
23
+
24
+ <LedGrid
25
+ size={[8, 8, 8]}
26
+ {draw}
27
+ led={{ glow: 3, offColor: '#0a0a12' }}
28
+ camera={{ autoOrbit: true, projection: 'perspective' }}
29
+ color={{ background: '#000', gain: 1.1 }}
30
+ interaction={{ zoom: true }}
31
+ />
32
+ ```
33
+
34
+ | prop | type | notes |
35
+ | ------------- | ----------------------------------- | -------------------------------------------------------------------------------------- |
36
+ | `size` | `[number, number, number]` | grid dims `[nx, ny, nz]` (changing it resizes in place — no remount) |
37
+ | `draw` | `(d: LedDisplay, dt: number)=>void` | called every frame; write voxels here |
38
+ | `led` | `LedOptions` | `style` `shape` `stagger` `rgb` `rgbLayout` `vivid` `outline` `size` `glow` `offColor` |
39
+ | `color` | `ColorOptions` | `background` `gain` `tint` |
40
+ | `camera` | `CameraOptions` | `yaw` `pitch` `distance` `fov` `projection` `autoOrbit` `orbitSpeed` `pitchLimits` |
41
+ | `interaction` | `InteractionOptions` | `drag` `dragSpeed` `zoom` `zoomLimits` |
42
+ | `quality` | `QualityOptions` | `pixelRatio` `antialias` `paused` `fps` (frame-rate cap) |
43
+ | `oncreate` | `(d: LedDisplay \| null)=>void` | imperative handle — called with the display on create, `null` on teardown |
44
+
45
+ The grouped props mirror `@glowbox/led-grid`'s options 1:1 and update **live** — even `size`
46
+ resizes the grid in place (no remount / context loss). Colours accept a `Color` (`[r,g,b]`
47
+ 0..1, `>1` blooms, or any CSS string). See **@glowbox/led-grid** for every field's default,
48
+ the full voxel API, and colour semantics.
49
+
50
+ ## `<NixieTube>`
51
+
52
+ ```svelte
53
+ <script lang="ts">
54
+ import { NixieTube } from '@glowbox/svelte';
55
+ </script>
56
+
57
+ <!-- the tube fills its parent — size the parent -->
58
+ <div style="width: 80px; height: 150px">
59
+ <NixieTube value="7" tubeStyle="classic" color="#ff6a12" />
60
+ </div>
61
+ ```
62
+
63
+ | prop | type | notes |
64
+ | ------------ | --------------------------------- | ---------------------------------------------------------- |
65
+ | `value` | `string \| number \| null` | the lit symbol: `0`–`9`, `:`, `-`, or `null`/`''` for dark |
66
+ | `tubeStyle` | `'classic' \| 'slim' \| 'tall'` | physical tube style (maps to the core `style` option) |
67
+ | `color` | `Color` | glow colour (default warm nixie orange) |
68
+ | `glow` | `number` | glow strength 0..1 |
69
+ | `background` | `Color` | tube glass colour |
70
+ | `mesh` | `boolean` | draw the honeycomb anode mesh (default `true`) |
71
+ | `ghost` | `boolean` | show the unlit cathode stack (default `true`) |
72
+ | `pixelRatio` | `number` | cap on `devicePixelRatio` |
73
+ | `oncreate` | `(tube: NixieTube \| null)=>void` | imperative handle — the tube on create, `null` on teardown |
74
+
75
+ Props update **live** (`value` → `setValue`, the rest → `setOptions`). A clock is just a
76
+ row of `<NixieTube>`s. See **@glowbox/nixie** for defaults + the size-adaptive rendering.
77
+
78
+ ---
79
+
80
+ Sibling packages with the same components: **[@glowbox/react](../react)** and
81
+ **[@glowbox/vue](../vue)**; content helpers in **[@glowbox/extras](../extras)**. Each
82
+ component fills its parent (`width/height: 100%`); give the parent a size.
83
+ Live demos: <https://eetu.github.io/glowbox/>.
@@ -0,0 +1,110 @@
1
+ <script lang="ts">
2
+ // Svelte wrapper around the plain-JS LED display. Give it a `size` and an
3
+ // optional `draw(d, dt)` callback; the grouped option props (led/color/camera/
4
+ // interaction/quality) mirror @glowbox/led-grid's options 1:1 and update live. All
5
+ // content is the client's — this ships no programs. (React/Vue wrappers can
6
+ // mirror this over the same core.)
7
+ import {
8
+ type CameraOptions,
9
+ type ColorOptions,
10
+ createLedDisplay,
11
+ type InteractionOptions,
12
+ type LedDisplay,
13
+ type LedOptions,
14
+ type QualityOptions
15
+ } from '@glowbox/led-grid';
16
+ import { untrack } from 'svelte';
17
+
18
+ let {
19
+ size,
20
+ draw,
21
+ led,
22
+ color,
23
+ camera,
24
+ interaction,
25
+ quality,
26
+ oncreate
27
+ }: {
28
+ size: [number, number, number];
29
+ draw?: (d: LedDisplay, dt: number) => void;
30
+ led?: LedOptions;
31
+ color?: ColorOptions;
32
+ camera?: CameraOptions;
33
+ interaction?: InteractionOptions;
34
+ quality?: QualityOptions;
35
+ /** Called with the display when (re)created, and with null on teardown —
36
+ * an escape hatch for imperative access (snapshot(), stats, setCamera…). */
37
+ oncreate?: (display: LedDisplay | null) => void;
38
+ } = $props();
39
+
40
+ let canvas = $state<HTMLCanvasElement | null>(null);
41
+ // $state.raw: the display is an opaque handle (owns a Float32Array + methods),
42
+ // not reactive data — deep-proxying it would break identity checks and mutate
43
+ // its buffer through a proxy.
44
+ let display = $state.raw<LedDisplay | null>(null);
45
+
46
+ // Create the display once for the canvas. Depends only on `canvas` — the option
47
+ // groups and size are read untracked so changing them never re-creates (size
48
+ // changes go through display.resize() below, which keeps the same canvas/context).
49
+ $effect(() => {
50
+ const el = canvas;
51
+ if (!el) return;
52
+ const d = untrack(() =>
53
+ createLedDisplay(el, { size, led, color, camera, interaction, quality })
54
+ );
55
+ if (!d) {
56
+ console.warn('LedGrid: WebGL unavailable');
57
+ return;
58
+ }
59
+ display = d;
60
+ untrack(() => oncreate?.(d));
61
+ return () => {
62
+ d.dispose();
63
+ if (display === d) display = null;
64
+ untrack(() => oncreate?.(null));
65
+ };
66
+ });
67
+
68
+ // Resize the grid in place when the dimensions change (no remount / context loss).
69
+ $effect(() => {
70
+ const [x, y, z] = size;
71
+ display?.resize([x, y, z]);
72
+ });
73
+
74
+ // Live-update each option group *independently*. Patching all groups on any one change
75
+ // would re-send `camera` (yaw/pitch/distance) on, say, a colour tweak — snapping the
76
+ // view back and fighting drag / auto-orbit. One effect per group patches only what changed.
77
+ $effect(() => {
78
+ display?.setOptions({ led });
79
+ });
80
+ $effect(() => {
81
+ display?.setOptions({ color });
82
+ });
83
+ $effect(() => {
84
+ display?.setOptions({ camera });
85
+ });
86
+ $effect(() => {
87
+ display?.setOptions({ interaction });
88
+ });
89
+ $effect(() => {
90
+ display?.setOptions({ quality });
91
+ });
92
+
93
+ // (Re)bind the per-frame draw callback.
94
+ $effect(() => {
95
+ const d = display;
96
+ if (!d || !draw) return;
97
+ return d.onFrame(draw);
98
+ });
99
+ </script>
100
+
101
+ <canvas bind:this={canvas}></canvas>
102
+
103
+ <style>
104
+ canvas {
105
+ display: block;
106
+ width: 100%;
107
+ height: 100%;
108
+ touch-action: none; /* let drag-orbit work without the page panning */
109
+ }
110
+ </style>
@@ -0,0 +1,16 @@
1
+ import { type CameraOptions, type ColorOptions, type InteractionOptions, type LedDisplay, type LedOptions, type QualityOptions } from '@glowbox/led-grid';
2
+ type $$ComponentProps = {
3
+ size: [number, number, number];
4
+ draw?: (d: LedDisplay, dt: number) => void;
5
+ led?: LedOptions;
6
+ color?: ColorOptions;
7
+ camera?: CameraOptions;
8
+ interaction?: InteractionOptions;
9
+ quality?: QualityOptions;
10
+ /** Called with the display when (re)created, and with null on teardown —
11
+ * an escape hatch for imperative access (snapshot(), stats, setCamera…). */
12
+ oncreate?: (display: LedDisplay | null) => void;
13
+ };
14
+ declare const LedGrid: import("svelte").Component<$$ComponentProps, {}, "">;
15
+ type LedGrid = ReturnType<typeof LedGrid>;
16
+ export default LedGrid;
@@ -0,0 +1,92 @@
1
+ <script lang="ts">
2
+ // Svelte wrapper around @glowbox/nixie's canvas tube. Give it a `value` (the lit
3
+ // symbol) plus optional appearance props that mirror the core NixieOptions and update
4
+ // live. The canvas fills its parent — size the parent to size the tube. Ships in
5
+ // @glowbox/svelte alongside <LedGrid>, over the sibling @glowbox/nixie core.
6
+ import {
7
+ createNixieTube,
8
+ type NixieOptions,
9
+ type NixieStyle,
10
+ type NixieTube
11
+ } from '@glowbox/nixie';
12
+ import { untrack } from 'svelte';
13
+
14
+ let {
15
+ value = null,
16
+ tubeStyle = 'classic',
17
+ color,
18
+ glow,
19
+ background,
20
+ mesh,
21
+ ghost,
22
+ pixelRatio,
23
+ oncreate
24
+ }: {
25
+ /** The lit symbol: a char `0`–`9`, `:`, `-`, or null/'' for all-cathodes-dark. */
26
+ value?: string | number | null;
27
+ /** Physical tube style — maps to the core `style` option (renamed to avoid the DOM `style`). */
28
+ tubeStyle?: NixieStyle;
29
+ color?: NixieOptions['color'];
30
+ glow?: number;
31
+ background?: NixieOptions['background'];
32
+ mesh?: boolean;
33
+ ghost?: boolean;
34
+ pixelRatio?: number;
35
+ /** Called with the tube when created, and null on teardown — imperative escape hatch. */
36
+ oncreate?: (tube: NixieTube | null) => void;
37
+ } = $props();
38
+
39
+ let canvas = $state<HTMLCanvasElement | null>(null);
40
+ // $state.raw: the tube is an opaque handle (owns a 2D context + methods), not reactive data.
41
+ let tube = $state.raw<NixieTube | null>(null);
42
+
43
+ // Create the tube once for the canvas — options are read untracked so changing them
44
+ // never re-creates (value goes through setValue, appearance through setOptions below).
45
+ $effect(() => {
46
+ const el = canvas;
47
+ if (!el) return;
48
+ const t = untrack(() =>
49
+ createNixieTube(el, {
50
+ value,
51
+ style: tubeStyle,
52
+ color,
53
+ glow,
54
+ background,
55
+ mesh,
56
+ ghost,
57
+ pixelRatio
58
+ })
59
+ );
60
+ if (!t) {
61
+ console.warn('NixieTube: 2D canvas unavailable');
62
+ return;
63
+ }
64
+ tube = t;
65
+ untrack(() => oncreate?.(t));
66
+ return () => {
67
+ t.dispose();
68
+ if (tube === t) tube = null;
69
+ untrack(() => oncreate?.(null));
70
+ };
71
+ });
72
+
73
+ // Live-update the lit symbol.
74
+ $effect(() => {
75
+ tube?.setValue(value);
76
+ });
77
+
78
+ // Live-update appearance when any option changes.
79
+ $effect(() => {
80
+ tube?.setOptions({ style: tubeStyle, color, glow, background, mesh, ghost, pixelRatio });
81
+ });
82
+ </script>
83
+
84
+ <canvas bind:this={canvas}></canvas>
85
+
86
+ <style>
87
+ canvas {
88
+ display: block;
89
+ width: 100%;
90
+ height: 100%;
91
+ }
92
+ </style>
@@ -0,0 +1,18 @@
1
+ import { type NixieOptions, type NixieStyle, type NixieTube } from '@glowbox/nixie';
2
+ type $$ComponentProps = {
3
+ /** The lit symbol: a char `0`–`9`, `:`, `-`, or null/'' for all-cathodes-dark. */
4
+ value?: string | number | null;
5
+ /** Physical tube style — maps to the core `style` option (renamed to avoid the DOM `style`). */
6
+ tubeStyle?: NixieStyle;
7
+ color?: NixieOptions['color'];
8
+ glow?: number;
9
+ background?: NixieOptions['background'];
10
+ mesh?: boolean;
11
+ ghost?: boolean;
12
+ pixelRatio?: number;
13
+ /** Called with the tube when created, and null on teardown — imperative escape hatch. */
14
+ oncreate?: (tube: NixieTube | null) => void;
15
+ };
16
+ declare const NixieTube: import("svelte").Component<$$ComponentProps, {}, "">;
17
+ type NixieTube = ReturnType<typeof NixieTube>;
18
+ export default NixieTube;
@@ -0,0 +1,4 @@
1
+ export { default as LedGrid } from './LedGrid.svelte';
2
+ export { default as NixieTube } from './NixieTube.svelte';
3
+ export type * from '@glowbox/led-grid';
4
+ export type { NixieOptions, NixieStyle, NixieTube as NixieTubeHandle } from '@glowbox/nixie';
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ // @glowbox/svelte — glowbox components for Svelte 5:
2
+ // import { LedGrid, NixieTube } from "@glowbox/svelte";
3
+ export { default as LedGrid } from './LedGrid.svelte';
4
+ export { default as NixieTube } from './NixieTube.svelte';
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "@glowbox/svelte",
3
+ "version": "1.0.0-rc.2",
4
+ "description": "glowbox components for Svelte 5 — <LedGrid> (3D WebGL LED grid) + <NixieTube> (nixie-tube display).",
5
+ "keywords": [
6
+ "svelte",
7
+ "led",
8
+ "led-cube",
9
+ "webgl",
10
+ "voxel",
11
+ "3d",
12
+ "nixie",
13
+ "component"
14
+ ],
15
+ "license": "MIT",
16
+ "type": "module",
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "sideEffects": false,
21
+ "svelte": "./dist/index.js",
22
+ "types": "./dist/index.d.ts",
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/index.d.ts",
26
+ "svelte": "./dist/index.js",
27
+ "default": "./dist/index.js"
28
+ }
29
+ },
30
+ "publishConfig": {
31
+ "access": "public"
32
+ },
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/eetu/glowbox.git",
36
+ "directory": "packages/svelte"
37
+ },
38
+ "homepage": "https://eetu.github.io/glowbox/",
39
+ "scripts": {
40
+ "build": "svelte-package",
41
+ "prepack": "svelte-package",
42
+ "test": "vitest run",
43
+ "lint": "eslint .",
44
+ "lint:fix": "eslint . --fix",
45
+ "typecheck": "svelte-check --tsconfig ./tsconfig.json"
46
+ },
47
+ "peerDependencies": {
48
+ "svelte": "^5"
49
+ },
50
+ "dependencies": {
51
+ "@glowbox/led-grid": "^1.0.0-rc.2",
52
+ "@glowbox/nixie": "^1.0.0-rc.2"
53
+ },
54
+ "devDependencies": {
55
+ "@anarkisti/eslint-config": "^1",
56
+ "@sveltejs/package": "^2.5",
57
+ "@sveltejs/vite-plugin-svelte": "^7",
58
+ "@vitest/browser": "^4.1",
59
+ "@vitest/browser-playwright": "^4.1",
60
+ "eslint": "^10",
61
+ "playwright": "^1.61",
62
+ "svelte": "^5.56",
63
+ "svelte-check": "^4.7",
64
+ "typescript": "^6",
65
+ "vite": "^8.1",
66
+ "vitest": "^4.1",
67
+ "vitest-browser-svelte": "^2.2"
68
+ }
69
+ }