@chestnutlabs/gcode-preview-element 0.2.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 ADDED
@@ -0,0 +1,74 @@
1
+ # @chestnutlabs/gcode-preview-element
2
+
3
+ A framework-free **`<gcode-preview>` Web Component** for the Chestnut Labs G-code viewer — a thin
4
+ custom-element shell over [`@chestnutlabs/gcode-preview-core`](../gcode-preview-core), the same engine
5
+ the Vue/React/Svelte adapters wrap. Use it from plain HTML, Angular, or any framework — **no peer
6
+ framework dependency**.
7
+
8
+ ## Install
9
+
10
+ ```sh
11
+ npm install @chestnutlabs/gcode-preview-element three
12
+ ```
13
+
14
+ `three` is a peer dependency of the underlying renderer.
15
+
16
+ ## Usage
17
+
18
+ ```html
19
+ <script type="module">
20
+ import '@chestnutlabs/gcode-preview-element/define'; // registers <gcode-preview>
21
+
22
+ const el = document.querySelector('gcode-preview');
23
+ el.addEventListener('ready', (e) => console.log('parsed', e.detail));
24
+ document.querySelector('input[type=file]').addEventListener('change', (ev) => {
25
+ el.source = ev.target.files[0]; // a File / Uint8Array / ArrayBuffer
26
+ });
27
+ </script>
28
+
29
+ <gcode-preview quality="tubes" show-travel="false" style="width: 100%; height: 70vh"></gcode-preview>
30
+ ```
31
+
32
+ Prefer explicit registration? Import the function instead of the side-effectful entry:
33
+
34
+ ```js
35
+ import { defineGcodePreview } from '@chestnutlabs/gcode-preview-element';
36
+ defineGcodePreview(); // idempotent; defaults to the <gcode-preview> tag
37
+ ```
38
+
39
+ ## Surface
40
+
41
+ Scalar options are **attributes** (also settable as properties); rich options are **properties**.
42
+
43
+ | Attribute | Property | Type | Default |
44
+ |---|---|---|---|
45
+ | `quality` | `quality` | `'auto' \| 'lines' \| 'tubes'` | `'auto'` |
46
+ | `camera-mode` | `cameraMode` | `'perspective' \| 'orthographic'` | `'perspective'` |
47
+ | `show-travel` | `showTravel` | `"true" \| "false"` | `true` |
48
+ | `show-retractions` | `showRetractions` | `"true" \| "false"` | `false` |
49
+ | `scrub` | `scrub` | integer | — (all) |
50
+ | `layer-range` | `layerRange` | `"start,end"` / `[number, number]` | — (all) |
51
+ | — | `source` | `Uint8Array \| ArrayBuffer \| File` | `null` |
52
+ | — | `colorMode` | `ColorMode` | — |
53
+ | — | `theme` | `Theme` | — |
54
+ | — | `buildVolume` | `BuildVolumeDef \| MachineGeometry` | — |
55
+ | — | `progress` | `ProgressObservation \| null` | `null` |
56
+ | — | `createWorker` | `() => WorkerLike` | batteries default |
57
+
58
+ > `source` is a **property only** (never a URL attribute) — the element does not fetch.
59
+
60
+ ### DOM events (`CustomEvent`, kebab-case `detail`)
61
+
62
+ `ready` · `parse-error` · `parse-cancelled` · `parse-progress` · `build-complete` · `disclosure` ·
63
+ `quality-fallback` · `progress-presentation-changed` · `machine-geometry-discovered` ·
64
+ `machine-geometry-mismatch` · `error`.
65
+
66
+ The underlying handle is also exposed as instance members (`state`, `controls`, `raw`, `onEvent`,
67
+ `parse`, `observeProgress`, `tickProgress`, `clearProgress`) for advanced use — the same neutral
68
+ controller surface the other adapters expose.
69
+
70
+ ## Workers
71
+
72
+ By default the batteries worker parses off-thread. To supply a custom/slim worker, set the
73
+ `createWorker` factory property (see the [Vue README](../gcode-preview-vue) for the shared worker
74
+ notes). The element passes the same behavioral parity suite as the Vue/React/Svelte adapters.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=define.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define.d.ts","sourceRoot":"","sources":["../src/define.ts"],"names":[],"mappings":""}
package/dist/define.js ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Side-effectful registration entry: `import '@chestnutlabs/gcode-preview-element/define'`
3
+ * auto-registers `<gcode-preview>`. Kept as a separate subpath so the main `.` entry stays
4
+ * `sideEffects: false`.
5
+ */
6
+ import { defineGcodePreview } from './index.js';
7
+ defineGcodePreview();
@@ -0,0 +1,94 @@
1
+ /**
2
+ * <gcode-preview> — the framework-free Web Component adapter (DD-007 D1 / DD-009 D5).
3
+ *
4
+ * Strictly a SHELL over `createPreviewController` (D1: never a separate implementation),
5
+ * exactly like the Vue/React/Svelte adapters: attributes and properties map to the same
6
+ * neutral controller options / `controls.*` calls, and controller events are re-emitted as
7
+ * DOM `CustomEvent`s (kebab-case). The underlying handle is also exposed as instance members
8
+ * (`state`/`controls`/`raw`/`onEvent`/`parse`/…) so the shared behavioral suite runs against
9
+ * it identically to the other three adapters.
10
+ *
11
+ * No framework peer dependency — this is the plain-DOM / Angular / vanilla path.
12
+ */
13
+ import { createPreviewController, type GcodePreviewControls, type GcodePreviewState, type ParseOutcome, type PreviewController, type PreviewEvent } from '@chestnutlabs/gcode-preview-core';
14
+ import type { BuildVolumeDef, ColorMode, Theme, TubeOptions } from '@chestnutlabs/gcode-renderer-three';
15
+ import type { MachineGeometry, MappedProgress, ProgressObservation } from '@chestnutlabs/toolpath-core';
16
+ import type { WireParseOptions, WorkerLike } from '@chestnutlabs/gcode-parser';
17
+ export type GcodePreviewSource = Uint8Array | ArrayBuffer | File | null;
18
+ /** Advanced/test renderer injectables (pass-throughs of the controller's renderer contract). */
19
+ export type ElementRendererOptions = Omit<NonNullable<PreviewControllerRenderer>, 'buildVolume' | 'quality' | 'cameraMode' | 'theme' | 'colorMode' | 'tube'>;
20
+ type PreviewControllerRenderer = NonNullable<Parameters<typeof createPreviewController>[0]>['renderer'];
21
+ /** The default tag name (DD-009 §4.5). */
22
+ export declare const DEFAULT_TAG = "gcode-preview";
23
+ export declare class GcodePreviewElement extends HTMLElement {
24
+ static get observedAttributes(): string[];
25
+ private controller;
26
+ private canvasEl;
27
+ private _source;
28
+ private _parseOptions;
29
+ private _buildVolume;
30
+ private _colorMode;
31
+ private _tube;
32
+ private _theme;
33
+ private _progress;
34
+ private _createWorker;
35
+ private _rendererOptions;
36
+ get source(): GcodePreviewSource;
37
+ set source(v: GcodePreviewSource);
38
+ get parseOptions(): WireParseOptions | undefined;
39
+ set parseOptions(v: WireParseOptions | undefined);
40
+ get buildVolume(): BuildVolumeDef | MachineGeometry | undefined;
41
+ set buildVolume(v: BuildVolumeDef | MachineGeometry | undefined);
42
+ get colorMode(): ColorMode | undefined;
43
+ set colorMode(v: ColorMode | undefined);
44
+ get tube(): TubeOptions | undefined;
45
+ set tube(v: TubeOptions | undefined);
46
+ get theme(): Theme | undefined;
47
+ set theme(v: Theme | undefined);
48
+ get progress(): ProgressObservation | null;
49
+ set progress(v: ProgressObservation | null);
50
+ get createWorker(): (() => WorkerLike) | undefined;
51
+ set createWorker(v: (() => WorkerLike) | undefined);
52
+ get rendererOptions(): ElementRendererOptions | undefined;
53
+ set rendererOptions(v: ElementRendererOptions | undefined);
54
+ get quality(): string | null;
55
+ set quality(v: string | null);
56
+ get cameraMode(): string | null;
57
+ set cameraMode(v: string | null);
58
+ /** Default true; only `show-travel="false"` hides travel. */
59
+ get showTravel(): boolean;
60
+ set showTravel(v: boolean);
61
+ /** Default false; only `show-retractions="true"` shows markers. */
62
+ get showRetractions(): boolean;
63
+ set showRetractions(v: boolean);
64
+ get scrub(): number | null;
65
+ set scrub(v: number | null);
66
+ get layerRange(): [number, number] | null;
67
+ set layerRange(v: [number, number] | null);
68
+ get state(): GcodePreviewState;
69
+ get controls(): GcodePreviewControls;
70
+ get raw(): PreviewController['raw'];
71
+ parse(input: Uint8Array | ArrayBuffer | File, opts?: WireParseOptions): Promise<ParseOutcome>;
72
+ observeProgress(obs: ProgressObservation): MappedProgress | null;
73
+ tickProgress(nowMs: number): MappedProgress | null;
74
+ clearProgress(): void;
75
+ onEvent(cb: (e: PreviewEvent) => void): () => void;
76
+ connectedCallback(): void;
77
+ disconnectedCallback(): void;
78
+ attributeChangedCallback(name: string, _old: string | null, value: string | null): void;
79
+ /** Apply the runtime-only controls (the construction options were set at controller creation). */
80
+ private applyRuntimeState;
81
+ private applyProgress;
82
+ private reflect;
83
+ private requireController;
84
+ /** Re-emit a controller event as a kebab-case DOM CustomEvent. */
85
+ private reemit;
86
+ }
87
+ /**
88
+ * Register `<gcode-preview>` (idempotent — safe to call repeatedly and from multiple
89
+ * bundles). Kept as a function (not a module side effect) so the package stays
90
+ * `sideEffects: false`; import `@chestnutlabs/gcode-preview-element/define` for auto-registration.
91
+ */
92
+ export declare function defineGcodePreview(tag?: string): void;
93
+ export {};
94
+ //# sourceMappingURL=gcode-preview-element.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gcode-preview-element.d.ts","sourceRoot":"","sources":["../src/gcode-preview-element.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,330 @@
1
+ /**
2
+ * <gcode-preview> — the framework-free Web Component adapter (DD-007 D1 / DD-009 D5).
3
+ *
4
+ * Strictly a SHELL over `createPreviewController` (D1: never a separate implementation),
5
+ * exactly like the Vue/React/Svelte adapters: attributes and properties map to the same
6
+ * neutral controller options / `controls.*` calls, and controller events are re-emitted as
7
+ * DOM `CustomEvent`s (kebab-case). The underlying handle is also exposed as instance members
8
+ * (`state`/`controls`/`raw`/`onEvent`/`parse`/…) so the shared behavioral suite runs against
9
+ * it identically to the other three adapters.
10
+ *
11
+ * No framework peer dependency — this is the plain-DOM / Angular / vanilla path.
12
+ */
13
+ import { createPreviewController } from '@chestnutlabs/gcode-preview-core';
14
+ /** Observed scalar/boolean/number attributes; rich options are property-only. */
15
+ const OBSERVED = ['quality', 'camera-mode', 'show-travel', 'show-retractions', 'scrub', 'layer-range'];
16
+ /** The default tag name (DD-009 §4.5). */
17
+ export const DEFAULT_TAG = 'gcode-preview';
18
+ export class GcodePreviewElement extends HTMLElement {
19
+ static get observedAttributes() {
20
+ return [...OBSERVED];
21
+ }
22
+ controller = null;
23
+ canvasEl = null;
24
+ // Rich options — objects/functions can't be attributes, so they are property-only.
25
+ _source = null;
26
+ _parseOptions;
27
+ _buildVolume;
28
+ _colorMode;
29
+ _tube;
30
+ _theme;
31
+ _progress = null;
32
+ _createWorker;
33
+ _rendererOptions;
34
+ // ---- rich-option property accessors ----
35
+ get source() {
36
+ return this._source;
37
+ }
38
+ set source(v) {
39
+ this._source = v;
40
+ if (this.controller !== null && v !== null)
41
+ void this.controller.parse(v, this._parseOptions);
42
+ }
43
+ get parseOptions() {
44
+ return this._parseOptions;
45
+ }
46
+ set parseOptions(v) {
47
+ this._parseOptions = v;
48
+ }
49
+ get buildVolume() {
50
+ return this._buildVolume;
51
+ }
52
+ set buildVolume(v) {
53
+ this._buildVolume = v;
54
+ if (this.controller !== null && v !== undefined && 'bed' in v)
55
+ this.controller.controls.setBuildVolume(v);
56
+ }
57
+ get colorMode() {
58
+ return this._colorMode;
59
+ }
60
+ set colorMode(v) {
61
+ this._colorMode = v;
62
+ if (this.controller !== null && v !== undefined)
63
+ this.controller.controls.setColorMode(v);
64
+ }
65
+ get tube() {
66
+ return this._tube;
67
+ }
68
+ set tube(v) {
69
+ this._tube = v; // construction-only (tube profile is fixed per renderer)
70
+ }
71
+ get theme() {
72
+ return this._theme;
73
+ }
74
+ set theme(v) {
75
+ this._theme = v;
76
+ if (this.controller !== null && v !== undefined)
77
+ this.controller.controls.setTheme(v);
78
+ }
79
+ get progress() {
80
+ return this._progress;
81
+ }
82
+ set progress(v) {
83
+ this._progress = v;
84
+ this.applyProgress();
85
+ }
86
+ get createWorker() {
87
+ return this._createWorker;
88
+ }
89
+ set createWorker(v) {
90
+ this._createWorker = v; // construction-only (a worker factory can't change mid-lifecycle)
91
+ }
92
+ get rendererOptions() {
93
+ return this._rendererOptions;
94
+ }
95
+ set rendererOptions(v) {
96
+ this._rendererOptions = v; // construction-only injectables
97
+ }
98
+ // ---- scalar-option accessors (reflect to attributes: one source of truth) ----
99
+ get quality() {
100
+ return this.getAttribute('quality');
101
+ }
102
+ set quality(v) {
103
+ this.reflect('quality', v);
104
+ }
105
+ get cameraMode() {
106
+ return this.getAttribute('camera-mode');
107
+ }
108
+ set cameraMode(v) {
109
+ this.reflect('camera-mode', v);
110
+ }
111
+ /** Default true; only `show-travel="false"` hides travel. */
112
+ get showTravel() {
113
+ return this.getAttribute('show-travel') !== 'false';
114
+ }
115
+ set showTravel(v) {
116
+ this.setAttribute('show-travel', v ? 'true' : 'false');
117
+ }
118
+ /** Default false; only `show-retractions="true"` shows markers. */
119
+ get showRetractions() {
120
+ return this.getAttribute('show-retractions') === 'true';
121
+ }
122
+ set showRetractions(v) {
123
+ this.setAttribute('show-retractions', v ? 'true' : 'false');
124
+ }
125
+ get scrub() {
126
+ const a = this.getAttribute('scrub');
127
+ return a === null ? null : Number(a);
128
+ }
129
+ set scrub(v) {
130
+ this.reflect('scrub', v);
131
+ }
132
+ get layerRange() {
133
+ return parseLayerRange(this.getAttribute('layer-range'));
134
+ }
135
+ set layerRange(v) {
136
+ this.reflect('layer-range', v === null ? null : `${v[0]},${v[1]}`);
137
+ }
138
+ // ---- handle-parity passthrough (advanced/parity surface; DOM events are the ergonomic one) ----
139
+ get state() {
140
+ return this.requireController().getState();
141
+ }
142
+ get controls() {
143
+ return this.requireController().controls;
144
+ }
145
+ get raw() {
146
+ return this.requireController().raw;
147
+ }
148
+ parse(input, opts) {
149
+ return this.requireController().parse(input, opts);
150
+ }
151
+ observeProgress(obs) {
152
+ return this.controller?.observeProgress(obs) ?? null;
153
+ }
154
+ tickProgress(nowMs) {
155
+ return this.controller?.tickProgress(nowMs) ?? null;
156
+ }
157
+ clearProgress() {
158
+ this.controller?.clearProgress();
159
+ }
160
+ onEvent(cb) {
161
+ return this.requireController().onEvent(cb);
162
+ }
163
+ // ---- lifecycle ----
164
+ connectedCallback() {
165
+ if (this.controller !== null)
166
+ return; // guard double-connect
167
+ const root = this.shadowRoot ?? this.attachShadow({ mode: 'open' });
168
+ root.replaceChildren();
169
+ const style = document.createElement('style');
170
+ style.textContent = ':host{display:block;width:100%;height:100%}canvas{display:block;width:100%;height:100%}';
171
+ const canvas = document.createElement('canvas');
172
+ canvas.setAttribute('aria-label', '3D G-code toolpath preview');
173
+ root.append(style, canvas);
174
+ this.canvasEl = canvas;
175
+ const isMachine = this._buildVolume !== undefined && 'bed' in this._buildVolume;
176
+ this.controller = createPreviewController({
177
+ createWorker: this._createWorker,
178
+ renderer: {
179
+ buildVolume: isMachine ? undefined : this._buildVolume,
180
+ quality: this.getAttribute('quality') ?? 'auto',
181
+ cameraMode: this.getAttribute('camera-mode') ?? undefined,
182
+ colorMode: this._colorMode,
183
+ tube: this._tube,
184
+ theme: this._theme,
185
+ ...this._rendererOptions
186
+ },
187
+ parseDefaults: this._parseOptions
188
+ });
189
+ this.controller.onEvent((e) => this.reemit(e));
190
+ this.controller.bindCanvas(canvas);
191
+ if (isMachine)
192
+ this.controller.controls.setBuildVolume(this._buildVolume);
193
+ this.applyRuntimeState();
194
+ if (this._source !== null)
195
+ void this.controller.parse(this._source, this._parseOptions);
196
+ }
197
+ disconnectedCallback() {
198
+ this.controller?.dispose();
199
+ this.controller = null;
200
+ this.canvasEl = null;
201
+ this.shadowRoot?.replaceChildren();
202
+ }
203
+ attributeChangedCallback(name, _old, value) {
204
+ if (this.controller === null)
205
+ return; // connectedCallback applies the initial state
206
+ const c = this.controller.controls;
207
+ switch (name) {
208
+ case 'quality':
209
+ c.setQuality(value ?? 'auto');
210
+ break;
211
+ case 'camera-mode':
212
+ if (value !== null)
213
+ c.setCameraMode(value);
214
+ break;
215
+ case 'show-travel':
216
+ c.setKindVisible('travel', value !== 'false');
217
+ break;
218
+ case 'show-retractions':
219
+ c.setShowRetractions(value === 'true');
220
+ break;
221
+ case 'scrub':
222
+ c.setScrubPosition(value === null ? null : Number(value));
223
+ break;
224
+ case 'layer-range': {
225
+ const r = parseLayerRange(value);
226
+ if (r === null)
227
+ c.setLayerRange(0, Number.POSITIVE_INFINITY);
228
+ else
229
+ c.setLayerRange(r[0], r[1]);
230
+ break;
231
+ }
232
+ default:
233
+ break;
234
+ }
235
+ }
236
+ // ---- internals ----
237
+ /** Apply the runtime-only controls (the construction options were set at controller creation). */
238
+ applyRuntimeState() {
239
+ const c = this.requireController().controls;
240
+ c.setKindVisible('travel', this.showTravel);
241
+ c.setShowRetractions(this.showRetractions);
242
+ const r = this.layerRange;
243
+ if (r === null)
244
+ c.setLayerRange(0, Number.POSITIVE_INFINITY);
245
+ else
246
+ c.setLayerRange(r[0], r[1]);
247
+ c.setScrubPosition(this.scrub);
248
+ this.applyProgress();
249
+ }
250
+ applyProgress() {
251
+ if (this.controller === null)
252
+ return;
253
+ if (this._progress === null)
254
+ this.controller.clearProgress();
255
+ else
256
+ this.controller.observeProgress(this._progress);
257
+ }
258
+ reflect(attr, v) {
259
+ if (v === null)
260
+ this.removeAttribute(attr);
261
+ else
262
+ this.setAttribute(attr, String(v));
263
+ }
264
+ requireController() {
265
+ if (this.controller === null) {
266
+ throw new Error('<gcode-preview>: not connected to the DOM yet (no controller). Append it before use.');
267
+ }
268
+ return this.controller;
269
+ }
270
+ /** Re-emit a controller event as a kebab-case DOM CustomEvent. */
271
+ reemit(e) {
272
+ const emit = (type, detail) => {
273
+ this.dispatchEvent(new CustomEvent(type, { detail }));
274
+ };
275
+ switch (e.type) {
276
+ case 'parse-complete':
277
+ emit('ready', { segments: e.segments, layers: e.layers, complete: e.complete });
278
+ break;
279
+ case 'parse-error':
280
+ emit('parse-error', { code: e.code, message: e.message });
281
+ break;
282
+ case 'parse-cancelled':
283
+ emit('parse-cancelled');
284
+ break;
285
+ case 'parse-progress':
286
+ emit('parse-progress', { bytesProcessed: e.progress.bytesProcessed, totalBytes: e.progress.totalBytes });
287
+ break;
288
+ case 'buildComplete':
289
+ emit('build-complete', { segments: e.segments, quality: e.quality });
290
+ emit('disclosure', { text: this.controller?.getState().disclosure ?? '' });
291
+ break;
292
+ case 'qualityFallback':
293
+ emit('quality-fallback', { from: e.from, to: e.to, reason: e.reason });
294
+ break;
295
+ case 'progress-presentation-changed':
296
+ emit('progress-presentation-changed', e.reason === undefined ? { mode: e.mode } : { mode: e.mode, reason: e.reason });
297
+ break;
298
+ case 'machine-geometry-discovered':
299
+ emit('machine-geometry-discovered', { machine: e.machine });
300
+ break;
301
+ case 'error':
302
+ if (e.code === 'machine-geometry-mismatch')
303
+ emit('machine-geometry-mismatch', { message: e.message });
304
+ else
305
+ emit('error', { code: e.code, message: e.message });
306
+ break;
307
+ default:
308
+ break;
309
+ }
310
+ }
311
+ }
312
+ function parseLayerRange(value) {
313
+ if (value === null)
314
+ return null;
315
+ const parts = value.split(',').map((s) => Number(s.trim()));
316
+ if (parts.length !== 2 || parts.some((n) => Number.isNaN(n)))
317
+ return null;
318
+ return [parts[0], parts[1]];
319
+ }
320
+ /**
321
+ * Register `<gcode-preview>` (idempotent — safe to call repeatedly and from multiple
322
+ * bundles). Kept as a function (not a module side effect) so the package stays
323
+ * `sideEffects: false`; import `@chestnutlabs/gcode-preview-element/define` for auto-registration.
324
+ */
325
+ export function defineGcodePreview(tag = DEFAULT_TAG) {
326
+ if (typeof customElements === 'undefined')
327
+ return;
328
+ if (customElements.get(tag) === undefined)
329
+ customElements.define(tag, GcodePreviewElement);
330
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @chestnutlabs/gcode-preview-element — the framework-free `<gcode-preview>` Web Component
3
+ * adapter (DD-009 D5), a thin shell over @chestnutlabs/gcode-preview-core. Registration is a
4
+ * function (`defineGcodePreview`) so the `.` entry stays side-effect-free; import
5
+ * `@chestnutlabs/gcode-preview-element/define` to auto-register.
6
+ */
7
+ export { GcodePreviewElement, defineGcodePreview, DEFAULT_TAG, type GcodePreviewSource, type ElementRendererOptions } from './gcode-preview-element.js';
8
+ export type { GcodePreviewControls, GcodePreviewState, ParseOutcome, PreviewEvent } from '@chestnutlabs/gcode-preview-core';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,WAAW,EACX,KAAK,kBAAkB,EACvB,KAAK,sBAAsB,EAC5B,MAAM,4BAA4B,CAAC;AAGpC,YAAY,EACV,oBAAoB,EACpB,iBAAiB,EACjB,YAAY,EACZ,YAAY,EACb,MAAM,kCAAkC,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @chestnutlabs/gcode-preview-element — the framework-free `<gcode-preview>` Web Component
3
+ * adapter (DD-009 D5), a thin shell over @chestnutlabs/gcode-preview-core. Registration is a
4
+ * function (`defineGcodePreview`) so the `.` entry stays side-effect-free; import
5
+ * `@chestnutlabs/gcode-preview-element/define` to auto-register.
6
+ */
7
+ export { GcodePreviewElement, defineGcodePreview, DEFAULT_TAG } from './gcode-preview-element.js';
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@chestnutlabs/gcode-preview-element",
3
+ "version": "0.2.0",
4
+ "description": "Framework-free <gcode-preview> Web Component for the Chestnut Labs G-code viewer (DD-007 D1 / DD-009 D5): a custom element bridging @chestnutlabs/gcode-preview-core, no framework peer dependency.",
5
+ "keywords": [
6
+ "gcode",
7
+ "3d-printing",
8
+ "toolpath",
9
+ "cnc",
10
+ "chestnutlabs",
11
+ "web-component",
12
+ "custom-element",
13
+ "viewer",
14
+ "preview"
15
+ ],
16
+ "author": "Chestnut Labs",
17
+ "homepage": "https://github.com/ChestnutLabs/gcode-preview#readme",
18
+ "bugs": "https://github.com/ChestnutLabs/gcode-preview/issues",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/ChestnutLabs/gcode-preview.git",
22
+ "directory": "packages/gcode-preview-element"
23
+ },
24
+ "license": "MIT",
25
+ "engines": {
26
+ "node": ">=22"
27
+ },
28
+ "type": "module",
29
+ "sideEffects": false,
30
+ "main": "./dist/index.js",
31
+ "module": "./dist/index.js",
32
+ "types": "./dist/index.d.ts",
33
+ "exports": {
34
+ ".": {
35
+ "types": "./dist/index.d.ts",
36
+ "import": "./dist/index.js"
37
+ },
38
+ "./define": {
39
+ "types": "./dist/define.d.ts",
40
+ "import": "./dist/define.js"
41
+ }
42
+ },
43
+ "files": [
44
+ "dist"
45
+ ],
46
+ "publishConfig": {
47
+ "access": "public"
48
+ },
49
+ "scripts": {
50
+ "build": "tsc -p tsconfig.json",
51
+ "typecheck": "tsc -p tsconfig.json --noEmit",
52
+ "test": "vitest run"
53
+ },
54
+ "dependencies": {
55
+ "@chestnutlabs/gcode-parser": "0.2.0",
56
+ "@chestnutlabs/gcode-preview-core": "0.2.0",
57
+ "@chestnutlabs/gcode-renderer-three": "0.2.0",
58
+ "@chestnutlabs/toolpath-core": "0.2.0"
59
+ }
60
+ }