@glowbox/vue 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,97 @@
1
+ # @glowbox/vue
2
+
3
+ glowbox components for **Vue 3**: `<LedGrid>` — the 3D WebGL LED-grid display (over
4
+ **[@glowbox/led-grid](../led-grid)**) — and `<NixieTube>` — a glowing nixie-tube numeral (over
5
+ **[@glowbox/nixie](../nixie)**).
6
+
7
+ ```sh
8
+ yarn add @glowbox/vue
9
+ # peer: vue ^3 (@glowbox/led-grid + @glowbox/nixie come along as dependencies)
10
+ ```
11
+
12
+ ## `<LedGrid>`
13
+
14
+ ```vue
15
+ <script setup lang="ts">
16
+ import { LedGrid, type LedDisplay } from '@glowbox/vue';
17
+
18
+ const draw = (d: LedDisplay, dt: number) => {
19
+ d.clear();
20
+ d.sphere([4, 4, 4], 3, '#00aaff');
21
+ };
22
+ </script>
23
+
24
+ <template>
25
+ <div style="width: 480px; height: 480px">
26
+ <LedGrid
27
+ :size="[8, 8, 8]"
28
+ :draw="draw"
29
+ :led="{ glow: 3, offColor: '#0a0a12' }"
30
+ :camera="{ autoOrbit: true, projection: 'perspective' }"
31
+ :color="{ background: '#000', gain: 1.1 }"
32
+ :interaction="{ zoom: true }"
33
+ />
34
+ </div>
35
+ </template>
36
+ ```
37
+
38
+ | prop | type | notes |
39
+ | ------------- | ----------------------------------- | -------------------------------------------------------------------------------------- |
40
+ | `size` | `[number, number, number]` | grid dims `[nx, ny, nz]` (changing it resizes in place — no remount) |
41
+ | `draw` | `(d: LedDisplay, dt: number)=>void` | called every frame; write voxels here |
42
+ | `led` | `LedOptions` | `style` `shape` `stagger` `rgb` `rgbLayout` `vivid` `outline` `size` `glow` `offColor` |
43
+ | `color` | `ColorOptions` | `background` `gain` `tint` |
44
+ | `camera` | `CameraOptions` | `yaw` `pitch` `distance` `fov` `projection` `autoOrbit` `orbitSpeed` `pitchLimits` |
45
+ | `interaction` | `InteractionOptions` | `drag` `dragSpeed` `zoom` `zoomLimits` |
46
+ | `quality` | `QualityOptions` | `pixelRatio` `antialias` `paused` `fps` (frame-rate cap) |
47
+
48
+ The component `expose()`s the imperative `LedDisplay` handle as `display` on the component
49
+ ref (`snapshot()`, `stats`, `setCamera`, …):
50
+
51
+ ```vue
52
+ <script setup lang="ts">
53
+ import { ref } from 'vue';
54
+ const grid = ref();
55
+ // grid.value?.display?.snapshot()
56
+ </script>
57
+
58
+ <template><LedGrid ref="grid" :size="[8, 8, 8]" /></template>
59
+ ```
60
+
61
+ The grouped props mirror `@glowbox/led-grid`'s options 1:1 and update **live**. See
62
+ **@glowbox/led-grid** for defaults, the voxel API, and colour semantics.
63
+
64
+ ## `<NixieTube>`
65
+
66
+ ```vue
67
+ <script setup lang="ts">
68
+ import { NixieTube } from '@glowbox/vue';
69
+ </script>
70
+
71
+ <template>
72
+ <div style="width: 80px; height: 150px">
73
+ <NixieTube value="7" tube-style="classic" color="#ff6a12" />
74
+ </div>
75
+ </template>
76
+ ```
77
+
78
+ | prop | type | notes |
79
+ | ------------ | ------------------------------- | ---------------------------------------------------------- |
80
+ | `value` | `string \| number \| null` | the lit symbol: `0`–`9`, `:`, `-`, or `null`/`''` for dark |
81
+ | `tubeStyle` | `'classic' \| 'slim' \| 'tall'` | physical tube style (maps to the core `style` option) |
82
+ | `color` | `Color` | glow colour (default warm nixie orange) |
83
+ | `glow` | `number` | glow strength 0..1 |
84
+ | `background` | `Color` | tube glass colour |
85
+ | `mesh` | `boolean` | draw the honeycomb anode mesh (default `true`) |
86
+ | `ghost` | `boolean` | show the unlit cathode stack (default `true`) |
87
+ | `pixelRatio` | `number` | cap on `devicePixelRatio` |
88
+
89
+ Props update **live**; the tube handle is `expose()`d as `tube` (`setValue`, `setOptions`,
90
+ `resize`, `snapshot`). See **@glowbox/nixie** for defaults + the size-adaptive rendering.
91
+
92
+ ---
93
+
94
+ Sibling packages with the same components: **[@glowbox/svelte](../svelte)** and
95
+ **[@glowbox/react](../react)**; content helpers in **[@glowbox/extras](../extras)**. Each
96
+ component fills its parent; give the parent a size. Live demos:
97
+ <https://eetu.github.io/glowbox/>.
@@ -0,0 +1,79 @@
1
+ import { CameraOptions, ColorOptions, InteractionOptions, LedDisplay, LedOptions, QualityOptions } from '@glowbox/led-grid';
2
+ import { PropType } from 'vue';
3
+ /**
4
+ * `<LedGrid>` mounts a 3D WebGL LED-grid display and runs your per-frame draw
5
+ * callback. `expose()`s the imperative `LedDisplay` handle as `display`
6
+ * (`snapshot()`, `stats`, `setCamera`, …) on the component ref.
7
+ */
8
+ export declare const LedGrid: import('vue').DefineComponent<import('vue').ExtractPropTypes<{
9
+ /** Grid size [nx, ny, nz]. Changing it resizes in place (no remount). */
10
+ size: {
11
+ type: PropType<[number, number, number]>;
12
+ required: true;
13
+ };
14
+ /** Called every frame; write voxels here. */
15
+ draw: {
16
+ type: PropType<(d: LedDisplay, dt: number) => void>;
17
+ default: undefined;
18
+ };
19
+ led: {
20
+ type: PropType<LedOptions>;
21
+ default: undefined;
22
+ };
23
+ color: {
24
+ type: PropType<ColorOptions>;
25
+ default: undefined;
26
+ };
27
+ camera: {
28
+ type: PropType<CameraOptions>;
29
+ default: undefined;
30
+ };
31
+ interaction: {
32
+ type: PropType<InteractionOptions>;
33
+ default: undefined;
34
+ };
35
+ quality: {
36
+ type: PropType<QualityOptions>;
37
+ default: undefined;
38
+ };
39
+ }>, () => import('vue').VNode<import('vue').RendererNode, import('vue').RendererElement, {
40
+ [key: string]: any;
41
+ }>, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<import('vue').ExtractPropTypes<{
42
+ /** Grid size [nx, ny, nz]. Changing it resizes in place (no remount). */
43
+ size: {
44
+ type: PropType<[number, number, number]>;
45
+ required: true;
46
+ };
47
+ /** Called every frame; write voxels here. */
48
+ draw: {
49
+ type: PropType<(d: LedDisplay, dt: number) => void>;
50
+ default: undefined;
51
+ };
52
+ led: {
53
+ type: PropType<LedOptions>;
54
+ default: undefined;
55
+ };
56
+ color: {
57
+ type: PropType<ColorOptions>;
58
+ default: undefined;
59
+ };
60
+ camera: {
61
+ type: PropType<CameraOptions>;
62
+ default: undefined;
63
+ };
64
+ interaction: {
65
+ type: PropType<InteractionOptions>;
66
+ default: undefined;
67
+ };
68
+ quality: {
69
+ type: PropType<QualityOptions>;
70
+ default: undefined;
71
+ };
72
+ }>> & Readonly<{}>, {
73
+ draw: (d: LedDisplay, dt: number) => void;
74
+ led: LedOptions;
75
+ color: ColorOptions;
76
+ camera: CameraOptions;
77
+ interaction: InteractionOptions;
78
+ quality: QualityOptions;
79
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, any>;
@@ -0,0 +1,88 @@
1
+ import { NixieOptions, NixieStyle } from '@glowbox/nixie';
2
+ import { PropType } from 'vue';
3
+ /**
4
+ * `<NixieTube>` mounts a single glowing nixie-tube numeral. `expose()`s the imperative
5
+ * `NixieTube` handle as `tube` (`setValue`, `setOptions`, `resize`, `snapshot`, …).
6
+ */
7
+ export declare const NixieTube: import('vue').DefineComponent<import('vue').ExtractPropTypes<{
8
+ /** The lit symbol: a char `0`–`9`, `:`, `-`, or null/'' for all-cathodes-dark. */
9
+ value: {
10
+ type: PropType<string | number | null>;
11
+ default: null;
12
+ };
13
+ /** Physical tube style — maps to the core `style` option (renamed to avoid Vue's `style`). */
14
+ tubeStyle: {
15
+ type: PropType<NixieStyle>;
16
+ default: string;
17
+ };
18
+ color: {
19
+ type: PropType<NixieOptions["color"]>;
20
+ default: undefined;
21
+ };
22
+ glow: {
23
+ type: NumberConstructor;
24
+ default: undefined;
25
+ };
26
+ background: {
27
+ type: PropType<NixieOptions["background"]>;
28
+ default: undefined;
29
+ };
30
+ mesh: {
31
+ type: BooleanConstructor;
32
+ default: undefined;
33
+ };
34
+ ghost: {
35
+ type: BooleanConstructor;
36
+ default: undefined;
37
+ };
38
+ pixelRatio: {
39
+ type: NumberConstructor;
40
+ default: undefined;
41
+ };
42
+ }>, () => import('vue').VNode<import('vue').RendererNode, import('vue').RendererElement, {
43
+ [key: string]: any;
44
+ }>, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<import('vue').ExtractPropTypes<{
45
+ /** The lit symbol: a char `0`–`9`, `:`, `-`, or null/'' for all-cathodes-dark. */
46
+ value: {
47
+ type: PropType<string | number | null>;
48
+ default: null;
49
+ };
50
+ /** Physical tube style — maps to the core `style` option (renamed to avoid Vue's `style`). */
51
+ tubeStyle: {
52
+ type: PropType<NixieStyle>;
53
+ default: string;
54
+ };
55
+ color: {
56
+ type: PropType<NixieOptions["color"]>;
57
+ default: undefined;
58
+ };
59
+ glow: {
60
+ type: NumberConstructor;
61
+ default: undefined;
62
+ };
63
+ background: {
64
+ type: PropType<NixieOptions["background"]>;
65
+ default: undefined;
66
+ };
67
+ mesh: {
68
+ type: BooleanConstructor;
69
+ default: undefined;
70
+ };
71
+ ghost: {
72
+ type: BooleanConstructor;
73
+ default: undefined;
74
+ };
75
+ pixelRatio: {
76
+ type: NumberConstructor;
77
+ default: undefined;
78
+ };
79
+ }>> & Readonly<{}>, {
80
+ color: import('@glowbox/led-grid').Color | undefined;
81
+ value: string | number | null;
82
+ background: import('@glowbox/led-grid').Color | undefined;
83
+ tubeStyle: NixieStyle;
84
+ glow: number;
85
+ mesh: boolean;
86
+ ghost: boolean;
87
+ pixelRatio: number;
88
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, any>;
@@ -0,0 +1,4 @@
1
+ export { LedGrid } from './LedGrid';
2
+ export { NixieTube } from './NixieTube';
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,150 @@
1
+ import { createLedDisplay as e } from "@glowbox/led-grid";
2
+ import { defineComponent as t, h as n, onMounted as r, onUnmounted as i, ref as a, watch as o } from "vue";
3
+ import { createNixieTube as s } from "@glowbox/nixie";
4
+ //#region src/LedGrid.ts
5
+ var c = {
6
+ display: "block",
7
+ width: "100%",
8
+ height: "100%",
9
+ touchAction: "none"
10
+ }, l = t({
11
+ name: "LedGrid",
12
+ props: {
13
+ size: {
14
+ type: Array,
15
+ required: !0
16
+ },
17
+ draw: {
18
+ type: Function,
19
+ default: void 0
20
+ },
21
+ led: {
22
+ type: Object,
23
+ default: void 0
24
+ },
25
+ color: {
26
+ type: Object,
27
+ default: void 0
28
+ },
29
+ camera: {
30
+ type: Object,
31
+ default: void 0
32
+ },
33
+ interaction: {
34
+ type: Object,
35
+ default: void 0
36
+ },
37
+ quality: {
38
+ type: Object,
39
+ default: void 0
40
+ }
41
+ },
42
+ setup(t, { expose: s }) {
43
+ let l = a(null), u = null, d = null, f = () => {
44
+ d?.(), d = null, u && t.draw && (d = u.onFrame(t.draw));
45
+ };
46
+ return r(() => {
47
+ if (l.value) {
48
+ if (u = e(l.value, {
49
+ size: t.size,
50
+ led: t.led,
51
+ color: t.color,
52
+ camera: t.camera,
53
+ interaction: t.interaction,
54
+ quality: t.quality
55
+ }), !u) {
56
+ console.warn("LedGrid: WebGL unavailable");
57
+ return;
58
+ }
59
+ f();
60
+ }
61
+ }), o(() => [
62
+ t.size[0],
63
+ t.size[1],
64
+ t.size[2]
65
+ ], (e) => u?.resize(e)), o(() => t.led, () => u?.setOptions({ led: t.led }), { deep: !0 }), o(() => t.color, () => u?.setOptions({ color: t.color }), { deep: !0 }), o(() => t.camera, () => u?.setOptions({ camera: t.camera }), { deep: !0 }), o(() => t.interaction, () => u?.setOptions({ interaction: t.interaction }), { deep: !0 }), o(() => t.quality, () => u?.setOptions({ quality: t.quality }), { deep: !0 }), o(() => t.draw, () => f()), i(() => {
66
+ d?.(), u?.dispose(), u = null;
67
+ }), s({ get display() {
68
+ return u;
69
+ } }), () => n("canvas", {
70
+ ref: l,
71
+ style: c
72
+ });
73
+ }
74
+ }), u = {
75
+ display: "block",
76
+ width: "100%",
77
+ height: "100%"
78
+ }, d = t({
79
+ name: "NixieTube",
80
+ props: {
81
+ value: {
82
+ type: [String, Number],
83
+ default: null
84
+ },
85
+ tubeStyle: {
86
+ type: String,
87
+ default: "classic"
88
+ },
89
+ color: {
90
+ type: [String, Array],
91
+ default: void 0
92
+ },
93
+ glow: {
94
+ type: Number,
95
+ default: void 0
96
+ },
97
+ background: {
98
+ type: [String, Array],
99
+ default: void 0
100
+ },
101
+ mesh: {
102
+ type: Boolean,
103
+ default: void 0
104
+ },
105
+ ghost: {
106
+ type: Boolean,
107
+ default: void 0
108
+ },
109
+ pixelRatio: {
110
+ type: Number,
111
+ default: void 0
112
+ }
113
+ },
114
+ setup(e, { expose: t }) {
115
+ let c = a(null), l = null, d = () => ({
116
+ style: e.tubeStyle,
117
+ color: e.color,
118
+ glow: e.glow,
119
+ background: e.background,
120
+ mesh: e.mesh,
121
+ ghost: e.ghost,
122
+ pixelRatio: e.pixelRatio
123
+ });
124
+ return r(() => {
125
+ c.value && (l = s(c.value, {
126
+ value: e.value,
127
+ ...d()
128
+ }), l || console.warn("NixieTube: 2D canvas unavailable"));
129
+ }), o(() => e.value, (e) => l?.setValue(e)), o(() => [
130
+ e.tubeStyle,
131
+ e.color,
132
+ e.glow,
133
+ e.background,
134
+ e.mesh,
135
+ e.ghost,
136
+ e.pixelRatio
137
+ ], () => l?.setOptions(d()), { deep: !0 }), i(() => {
138
+ l?.dispose(), l = null;
139
+ }), t({ get tube() {
140
+ return l;
141
+ } }), () => n("canvas", {
142
+ ref: c,
143
+ style: u
144
+ });
145
+ }
146
+ });
147
+ //#endregion
148
+ export { l as LedGrid, d as NixieTube };
149
+
150
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/LedGrid.ts","../src/NixieTube.ts"],"sourcesContent":["// Vue 3 wrapper around the plain-JS LED display. Give it a `size` and an optional\n// `draw(d, dt)` callback; the grouped option props (led/color/camera/interaction/\n// quality) mirror @glowbox/led-grid's options 1:1 and update live. All content is the\n// client's — this ships no programs. A render-function component (no SFC), so it\n// mirrors @glowbox/svelte over the same core with no template compiler.\nimport {\n\ttype CameraOptions,\n\ttype ColorOptions,\n\tcreateLedDisplay,\n\ttype InteractionOptions,\n\ttype LedDisplay,\n\ttype LedOptions,\n\ttype QualityOptions\n} from '@glowbox/led-grid';\nimport {\n\tdefineComponent,\n\th,\n\tonMounted,\n\tonUnmounted,\n\ttype PropType,\n\tref,\n\ttype StyleValue,\n\twatch\n} from 'vue';\n\n// The canvas fills its parent by default; give the parent a size.\nconst baseStyle: StyleValue = {\n\tdisplay: 'block',\n\twidth: '100%',\n\theight: '100%',\n\ttouchAction: 'none' // let drag-orbit work without the page panning\n};\n\n/**\n * `<LedGrid>` mounts a 3D WebGL LED-grid display and runs your per-frame draw\n * callback. `expose()`s the imperative `LedDisplay` handle as `display`\n * (`snapshot()`, `stats`, `setCamera`, …) on the component ref.\n */\nexport const LedGrid = defineComponent({\n\tname: 'LedGrid',\n\tprops: {\n\t\t/** Grid size [nx, ny, nz]. Changing it resizes in place (no remount). */\n\t\tsize: { type: Array as unknown as PropType<[number, number, number]>, required: true },\n\t\t/** Called every frame; write voxels here. */\n\t\tdraw: { type: Function as PropType<(d: LedDisplay, dt: number) => void>, default: undefined },\n\t\tled: { type: Object as PropType<LedOptions>, default: undefined },\n\t\tcolor: { type: Object as PropType<ColorOptions>, default: undefined },\n\t\tcamera: { type: Object as PropType<CameraOptions>, default: undefined },\n\t\tinteraction: { type: Object as PropType<InteractionOptions>, default: undefined },\n\t\tquality: { type: Object as PropType<QualityOptions>, default: undefined }\n\t},\n\tsetup(props, { expose }) {\n\t\tconst canvas = ref<HTMLCanvasElement | null>(null);\n\t\tlet display: LedDisplay | null = null;\n\t\tlet stopFrame: (() => void) | null = null;\n\n\t\tconst bindDraw = () => {\n\t\t\tstopFrame?.();\n\t\t\tstopFrame = null;\n\t\t\tif (display && props.draw) stopFrame = display.onFrame(props.draw);\n\t\t};\n\n\t\tonMounted(() => {\n\t\t\tif (!canvas.value) return;\n\t\t\tdisplay = createLedDisplay(canvas.value, {\n\t\t\t\tsize: props.size,\n\t\t\t\tled: props.led,\n\t\t\t\tcolor: props.color,\n\t\t\t\tcamera: props.camera,\n\t\t\t\tinteraction: props.interaction,\n\t\t\t\tquality: props.quality\n\t\t\t});\n\t\t\tif (!display) {\n\t\t\t\tconsole.warn('LedGrid: WebGL unavailable');\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tbindDraw();\n\t\t});\n\n\t\t// Resize the grid in place when the dimensions change (no remount / context loss).\n\t\twatch(\n\t\t\t() => [props.size[0], props.size[1], props.size[2]] as [number, number, number],\n\t\t\t(s) => display?.resize(s)\n\t\t);\n\n\t\t// Live-update each option group *independently*. Patching all groups on any one\n\t\t// change would re-send `camera` (yaw/pitch/distance) on, say, a colour tweak —\n\t\t// snapping the view back and fighting drag / auto-orbit. One watch per group.\n\t\twatch(\n\t\t\t() => props.led,\n\t\t\t() => display?.setOptions({ led: props.led }),\n\t\t\t{ deep: true }\n\t\t);\n\t\twatch(\n\t\t\t() => props.color,\n\t\t\t() => display?.setOptions({ color: props.color }),\n\t\t\t{ deep: true }\n\t\t);\n\t\twatch(\n\t\t\t() => props.camera,\n\t\t\t() => display?.setOptions({ camera: props.camera }),\n\t\t\t{ deep: true }\n\t\t);\n\t\twatch(\n\t\t\t() => props.interaction,\n\t\t\t() => display?.setOptions({ interaction: props.interaction }),\n\t\t\t{\n\t\t\t\tdeep: true\n\t\t\t}\n\t\t);\n\t\twatch(\n\t\t\t() => props.quality,\n\t\t\t() => display?.setOptions({ quality: props.quality }),\n\t\t\t{\n\t\t\t\tdeep: true\n\t\t\t}\n\t\t);\n\n\t\t// (Re)bind the per-frame draw callback.\n\t\twatch(\n\t\t\t() => props.draw,\n\t\t\t() => bindDraw()\n\t\t);\n\n\t\tonUnmounted(() => {\n\t\t\tstopFrame?.();\n\t\t\tdisplay?.dispose();\n\t\t\tdisplay = null;\n\t\t});\n\n\t\t// Expose the live display handle for imperative access via the component ref.\n\t\texpose({\n\t\t\tget display() {\n\t\t\t\treturn display;\n\t\t\t}\n\t\t});\n\n\t\treturn () => h('canvas', { ref: canvas, style: baseStyle });\n\t}\n});\n","// Vue 3 wrapper around @glowbox/nixie's canvas tube. Give it a `value` (the lit symbol)\n// plus optional appearance props that mirror the core NixieOptions and update live. The\n// canvas fills its parent — size the parent to size the tube. A render-function component\n// (no SFC). Ships in @glowbox/vue alongside <LedGrid>, over the sibling @glowbox/nixie core.\nimport {\n\tcreateNixieTube,\n\ttype NixieOptions,\n\ttype NixieStyle,\n\ttype NixieTube as NixieTubeHandle\n} from '@glowbox/nixie';\nimport {\n\tdefineComponent,\n\th,\n\tonMounted,\n\tonUnmounted,\n\ttype PropType,\n\tref,\n\ttype StyleValue,\n\twatch\n} from 'vue';\n\n// The canvas fills its parent by default; give the parent a size.\nconst baseStyle: StyleValue = { display: 'block', width: '100%', height: '100%' };\n\n/**\n * `<NixieTube>` mounts a single glowing nixie-tube numeral. `expose()`s the imperative\n * `NixieTube` handle as `tube` (`setValue`, `setOptions`, `resize`, `snapshot`, …).\n */\nexport const NixieTube = defineComponent({\n\tname: 'NixieTube',\n\tprops: {\n\t\t/** The lit symbol: a char `0`–`9`, `:`, `-`, or null/'' for all-cathodes-dark. */\n\t\tvalue: { type: [String, Number] as PropType<string | number | null>, default: null },\n\t\t/** Physical tube style — maps to the core `style` option (renamed to avoid Vue's `style`). */\n\t\ttubeStyle: { type: String as PropType<NixieStyle>, default: 'classic' },\n\t\tcolor: { type: [String, Array] as PropType<NixieOptions['color']>, default: undefined },\n\t\tglow: { type: Number, default: undefined },\n\t\tbackground: {\n\t\t\ttype: [String, Array] as PropType<NixieOptions['background']>,\n\t\t\tdefault: undefined\n\t\t},\n\t\t// default: undefined (not the Boolean-absent → false cast) so the core's own\n\t\t// defaults (mesh/ghost = true) apply when the prop is omitted.\n\t\tmesh: { type: Boolean, default: undefined },\n\t\tghost: { type: Boolean, default: undefined },\n\t\tpixelRatio: { type: Number, default: undefined }\n\t},\n\tsetup(props, { expose }) {\n\t\tconst canvas = ref<HTMLCanvasElement | null>(null);\n\t\tlet tube: NixieTubeHandle | null = null;\n\n\t\tconst options = () => ({\n\t\t\tstyle: props.tubeStyle,\n\t\t\tcolor: props.color,\n\t\t\tglow: props.glow,\n\t\t\tbackground: props.background,\n\t\t\tmesh: props.mesh,\n\t\t\tghost: props.ghost,\n\t\t\tpixelRatio: props.pixelRatio\n\t\t});\n\n\t\tonMounted(() => {\n\t\t\tif (!canvas.value) return;\n\t\t\ttube = createNixieTube(canvas.value, { value: props.value, ...options() });\n\t\t\tif (!tube) console.warn('NixieTube: 2D canvas unavailable');\n\t\t});\n\n\t\t// Live-update the lit symbol.\n\t\twatch(\n\t\t\t() => props.value,\n\t\t\t(v) => tube?.setValue(v)\n\t\t);\n\n\t\t// Live-update appearance when any option changes.\n\t\twatch(\n\t\t\t() => [\n\t\t\t\tprops.tubeStyle,\n\t\t\t\tprops.color,\n\t\t\t\tprops.glow,\n\t\t\t\tprops.background,\n\t\t\t\tprops.mesh,\n\t\t\t\tprops.ghost,\n\t\t\t\tprops.pixelRatio\n\t\t\t],\n\t\t\t() => tube?.setOptions(options()),\n\t\t\t{ deep: true }\n\t\t);\n\n\t\tonUnmounted(() => {\n\t\t\ttube?.dispose();\n\t\t\ttube = null;\n\t\t});\n\n\t\t// Expose the live tube handle for imperative access via the component ref.\n\t\texpose({\n\t\t\tget tube() {\n\t\t\t\treturn tube;\n\t\t\t}\n\t\t});\n\n\t\treturn () => h('canvas', { ref: canvas, style: baseStyle });\n\t}\n});\n"],"mappings":";;;;AA0BA,IAAM,IAAwB;CAC7B,SAAS;CACT,OAAO;CACP,QAAQ;CACR,aAAa;AACd,GAOa,IAAU,EAAgB;CACtC,MAAM;CACN,OAAO;EAEN,MAAM;GAAE,MAAM;GAAwD,UAAU;EAAK;EAErF,MAAM;GAAE,MAAM;GAA2D,SAAS,KAAA;EAAU;EAC5F,KAAK;GAAE,MAAM;GAAgC,SAAS,KAAA;EAAU;EAChE,OAAO;GAAE,MAAM;GAAkC,SAAS,KAAA;EAAU;EACpE,QAAQ;GAAE,MAAM;GAAmC,SAAS,KAAA;EAAU;EACtE,aAAa;GAAE,MAAM;GAAwC,SAAS,KAAA;EAAU;EAChF,SAAS;GAAE,MAAM;GAAoC,SAAS,KAAA;EAAU;CACzE;CACA,MAAM,GAAO,EAAE,aAAU;EACxB,IAAM,IAAS,EAA8B,IAAI,GAC7C,IAA6B,MAC7B,IAAiC,MAE/B,UAAiB;GAGtB,AAFA,IAAY,GACZ,IAAY,MACR,KAAW,EAAM,SAAM,IAAY,EAAQ,QAAQ,EAAM,IAAI;EAClE;EA6EA,OA3EA,QAAgB;GACV,MAAO,OASZ;QARA,IAAU,EAAiB,EAAO,OAAO;KACxC,MAAM,EAAM;KACZ,KAAK,EAAM;KACX,OAAO,EAAM;KACb,QAAQ,EAAM;KACd,aAAa,EAAM;KACnB,SAAS,EAAM;IAChB,CAAC,GACG,CAAC,GAAS;KACb,QAAQ,KAAK,4BAA4B;KACzC;IACD;IACA,EAAS;GADT;EAED,CAAC,GAGD,QACO;GAAC,EAAM,KAAK;GAAI,EAAM,KAAK;GAAI,EAAM,KAAK;EAAE,IACjD,MAAM,GAAS,OAAO,CAAC,CACzB,GAKA,QACO,EAAM,WACN,GAAS,WAAW,EAAE,KAAK,EAAM,IAAI,CAAC,GAC5C,EAAE,MAAM,GAAK,CACd,GACA,QACO,EAAM,aACN,GAAS,WAAW,EAAE,OAAO,EAAM,MAAM,CAAC,GAChD,EAAE,MAAM,GAAK,CACd,GACA,QACO,EAAM,cACN,GAAS,WAAW,EAAE,QAAQ,EAAM,OAAO,CAAC,GAClD,EAAE,MAAM,GAAK,CACd,GACA,QACO,EAAM,mBACN,GAAS,WAAW,EAAE,aAAa,EAAM,YAAY,CAAC,GAC5D,EACC,MAAM,GACP,CACD,GACA,QACO,EAAM,eACN,GAAS,WAAW,EAAE,SAAS,EAAM,QAAQ,CAAC,GACpD,EACC,MAAM,GACP,CACD,GAGA,QACO,EAAM,YACN,EAAS,CAChB,GAEA,QAAkB;GAGjB,AAFA,IAAY,GACZ,GAAS,QAAQ,GACjB,IAAU;EACX,CAAC,GAGD,EAAO,EACN,IAAI,UAAU;GACb,OAAO;EACR,EACD,CAAC,SAEY,EAAE,UAAU;GAAE,KAAK;GAAQ,OAAO;EAAU,CAAC;CAC3D;AACD,CAAC,GCrHK,IAAwB;CAAE,SAAS;CAAS,OAAO;CAAQ,QAAQ;AAAO,GAMnE,IAAY,EAAgB;CACxC,MAAM;CACN,OAAO;EAEN,OAAO;GAAE,MAAM,CAAC,QAAQ,MAAM;GAAuC,SAAS;EAAK;EAEnF,WAAW;GAAE,MAAM;GAAgC,SAAS;EAAU;EACtE,OAAO;GAAE,MAAM,CAAC,QAAQ,KAAK;GAAsC,SAAS,KAAA;EAAU;EACtF,MAAM;GAAE,MAAM;GAAQ,SAAS,KAAA;EAAU;EACzC,YAAY;GACX,MAAM,CAAC,QAAQ,KAAK;GACpB,SAAS,KAAA;EACV;EAGA,MAAM;GAAE,MAAM;GAAS,SAAS,KAAA;EAAU;EAC1C,OAAO;GAAE,MAAM;GAAS,SAAS,KAAA;EAAU;EAC3C,YAAY;GAAE,MAAM;GAAQ,SAAS,KAAA;EAAU;CAChD;CACA,MAAM,GAAO,EAAE,aAAU;EACxB,IAAM,IAAS,EAA8B,IAAI,GAC7C,IAA+B,MAE7B,WAAiB;GACtB,OAAO,EAAM;GACb,OAAO,EAAM;GACb,MAAM,EAAM;GACZ,YAAY,EAAM;GAClB,MAAM,EAAM;GACZ,OAAO,EAAM;GACb,YAAY,EAAM;EACnB;EAyCA,OAvCA,QAAgB;GACV,EAAO,UACZ,IAAO,EAAgB,EAAO,OAAO;IAAE,OAAO,EAAM;IAAO,GAAG,EAAQ;GAAE,CAAC,GACpE,KAAM,QAAQ,KAAK,kCAAkC;EAC3D,CAAC,GAGD,QACO,EAAM,QACX,MAAM,GAAM,SAAS,CAAC,CACxB,GAGA,QACO;GACL,EAAM;GACN,EAAM;GACN,EAAM;GACN,EAAM;GACN,EAAM;GACN,EAAM;GACN,EAAM;EACP,SACM,GAAM,WAAW,EAAQ,CAAC,GAChC,EAAE,MAAM,GAAK,CACd,GAEA,QAAkB;GAEjB,AADA,GAAM,QAAQ,GACd,IAAO;EACR,CAAC,GAGD,EAAO,EACN,IAAI,OAAO;GACV,OAAO;EACR,EACD,CAAC,SAEY,EAAE,UAAU;GAAE,KAAK;GAAQ,OAAO;EAAU,CAAC;CAC3D;AACD,CAAC"}
package/package.json ADDED
@@ -0,0 +1,80 @@
1
+ {
2
+ "name": "@glowbox/vue",
3
+ "version": "1.0.0-rc.2",
4
+ "description": "glowbox components for Vue 3 — <LedGrid> (3D WebGL LED grid) + <NixieTube> (nixie-tube display).",
5
+ "keywords": [
6
+ "vue",
7
+ "led",
8
+ "led-cube",
9
+ "webgl",
10
+ "voxel",
11
+ "3d",
12
+ "nixie",
13
+ "component"
14
+ ],
15
+ "license": "MIT",
16
+ "type": "module",
17
+ "sideEffects": false,
18
+ "files": [
19
+ "dist"
20
+ ],
21
+ "types": "./dist/index.d.ts",
22
+ "module": "./dist/index.js",
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/index.d.ts",
26
+ "import": "./dist/index.js"
27
+ }
28
+ },
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/eetu/glowbox.git",
35
+ "directory": "packages/vue"
36
+ },
37
+ "homepage": "https://eetu.github.io/glowbox/",
38
+ "scripts": {
39
+ "build": "vite build",
40
+ "prepack": "vite build",
41
+ "test": "vitest run",
42
+ "lint": "eslint .",
43
+ "lint:fix": "eslint . --fix",
44
+ "typecheck": "tsc --noEmit",
45
+ "size": "size-limit"
46
+ },
47
+ "size-limit": [
48
+ {
49
+ "name": "esm (own code; vue + core external)",
50
+ "path": "dist/index.js",
51
+ "ignore": [
52
+ "vue",
53
+ "@glowbox/led-grid",
54
+ "@glowbox/nixie"
55
+ ],
56
+ "limit": "1.5 kB"
57
+ }
58
+ ],
59
+ "peerDependencies": {
60
+ "vue": "^3"
61
+ },
62
+ "dependencies": {
63
+ "@glowbox/led-grid": "^1.0.0-rc.2",
64
+ "@glowbox/nixie": "^1.0.0-rc.2"
65
+ },
66
+ "devDependencies": {
67
+ "@anarkisti/eslint-config": "^1",
68
+ "@vitest/browser": "^4.1",
69
+ "@vitest/browser-playwright": "^4.1",
70
+ "@vue/compiler-dom": "^3.5",
71
+ "@vue/test-utils": "^2.4",
72
+ "eslint": "^10",
73
+ "playwright": "^1.61",
74
+ "typescript": "^6",
75
+ "vite": "^8.1",
76
+ "vite-plugin-dts": "^5",
77
+ "vitest": "^4.1",
78
+ "vue": "^3.5"
79
+ }
80
+ }