pluton-2d 0.1.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/dist/index.js ADDED
@@ -0,0 +1,1119 @@
1
+ const c = "http://www.w3.org/2000/svg";
2
+ class w {
3
+ listeners = /* @__PURE__ */ new Map();
4
+ on(t, e) {
5
+ return this.listeners.has(t) || this.listeners.set(t, /* @__PURE__ */ new Set()), this.listeners.get(t).add(e), () => this.off(t, e);
6
+ }
7
+ off(t, e) {
8
+ this.listeners.get(t)?.delete(e);
9
+ }
10
+ emit(t, e) {
11
+ this.listeners.get(t)?.forEach((s) => {
12
+ try {
13
+ s(e);
14
+ } catch (i) {
15
+ console.error("EventBus listener error:", i);
16
+ }
17
+ });
18
+ }
19
+ clear() {
20
+ this.listeners.clear();
21
+ }
22
+ }
23
+ class S {
24
+ svg;
25
+ defs;
26
+ cameraRef;
27
+ cachedViewport = null;
28
+ resizeObserver;
29
+ onResize;
30
+ constructor(t, e, s, i) {
31
+ this.svg = t, this.defs = e, this.cameraRef = s, this.onResize = i, this.resizeObserver = new ResizeObserver(() => {
32
+ this.invalidateViewport(), this.onResize?.();
33
+ }), this.resizeObserver.observe(t);
34
+ }
35
+ dispose() {
36
+ this.resizeObserver.disconnect();
37
+ }
38
+ invalidateViewport() {
39
+ this.cachedViewport = null;
40
+ }
41
+ viewport = () => {
42
+ if (this.cachedViewport) return this.cachedViewport;
43
+ const t = this.svg.viewBox?.baseVal;
44
+ if (t && t.width && t.height)
45
+ return this.cachedViewport = { x: t.x, y: t.y, width: t.width, height: t.height }, this.cachedViewport;
46
+ const e = Number(this.svg.getAttribute("width")) || 0, s = Number(this.svg.getAttribute("height")) || 0;
47
+ if (e && s)
48
+ return this.cachedViewport = { x: 0, y: 0, width: e, height: s }, this.cachedViewport;
49
+ const i = this.svg.getBoundingClientRect();
50
+ return this.cachedViewport = { x: 0, y: 0, width: i.width, height: i.height }, this.cachedViewport;
51
+ };
52
+ camera = () => this.cameraRef?.state() ?? null;
53
+ }
54
+ const A = /* @__PURE__ */ new WeakMap();
55
+ function $(l) {
56
+ let t = A.get(l);
57
+ return t || (t = /* @__PURE__ */ new Map(), A.set(l, t)), t;
58
+ }
59
+ function m(l, t) {
60
+ const e = t.getAttribute("id");
61
+ if (!e) {
62
+ l.appendChild(t);
63
+ return;
64
+ }
65
+ const s = $(l), i = s.get(e);
66
+ i ? i.replaceWith(t) : l.appendChild(t), s.set(e, t);
67
+ }
68
+ class v {
69
+ hatchFill45Id = "pluton-pattern-fill-hatch-45";
70
+ graphPaperPatternId = "pluton-pattern-graph-paper";
71
+ defsEl;
72
+ constructor(t) {
73
+ this.defsEl = t;
74
+ }
75
+ sync() {
76
+ m(this.defsEl, this.createHatchFill45Pattern()), m(this.defsEl, this.createGraphPaperPattern());
77
+ }
78
+ createHatchFill45Pattern() {
79
+ const t = document.createElementNS(c, "pattern");
80
+ t.setAttribute("id", this.hatchFill45Id), t.setAttribute("patternUnits", "userSpaceOnUse"), t.setAttribute("width", "8"), t.setAttribute("height", "8"), t.setAttribute("patternTransform", "rotate(-45)");
81
+ const e = document.createElementNS(c, "line");
82
+ return e.setAttribute("x1", "0"), e.setAttribute("y1", "0"), e.setAttribute("x2", "0"), e.setAttribute("y2", "8"), e.setAttribute("stroke-width", "12.5"), e.classList.add("pluton-pattern-hatch"), t.appendChild(e), t;
83
+ }
84
+ createGraphPaperPattern() {
85
+ const s = document.createElementNS(c, "pattern");
86
+ s.setAttribute("id", this.graphPaperPatternId), s.setAttribute("patternUnits", "userSpaceOnUse"), s.setAttribute("x", "0"), s.setAttribute("y", "0"), s.setAttribute("width", String(50)), s.setAttribute("height", String(50));
87
+ const i = document.createElementNS(c, "path");
88
+ i.classList.add("pluton-pattern-graph-paper-minor");
89
+ const n = Math.max(1, Math.floor(50 / 10)), r = [];
90
+ for (let a = 1; a < n; a++) {
91
+ const h = a * 10;
92
+ r.push(`M ${h} 0 L ${h} 50`);
93
+ }
94
+ for (let a = 1; a < n; a++) {
95
+ const h = a * 10;
96
+ r.push(`M 0 ${h} L 50 ${h}`);
97
+ }
98
+ i.setAttribute("d", r.join(" ")), s.appendChild(i);
99
+ const o = document.createElementNS(c, "path");
100
+ return o.classList.add("pluton-pattern-graph-paper-major"), o.setAttribute(
101
+ "d",
102
+ "M 50 0 L 50 50 M 0 50 L 50 50"
103
+ ), s.appendChild(o), s;
104
+ }
105
+ }
106
+ class P {
107
+ graphPaperGradientId = "pluton-gradient-graph-paper";
108
+ graphPaperMaskId = "pluton-mask-graph-paper";
109
+ defsEl;
110
+ constructor(t) {
111
+ this.defsEl = t;
112
+ }
113
+ syncForViewport(t) {
114
+ m(this.defsEl, this.createGraphPaperFadeGradient(t)), m(this.defsEl, this.createGraphPaperFadeMask(t));
115
+ }
116
+ createGraphPaperFadeGradient(t) {
117
+ const i = t.width / 2, n = t.height / 2, r = Math.sqrt(i * i + n * n), o = r * 0.65, a = r * 1, h = document.createElementNS(c, "radialGradient");
118
+ h.setAttribute("id", this.graphPaperGradientId), h.setAttribute("gradientUnits", "userSpaceOnUse"), h.setAttribute("cx", String(i)), h.setAttribute("cy", String(n)), h.setAttribute("r", String(a));
119
+ const d = document.createElementNS(c, "stop");
120
+ d.setAttribute("offset", "0"), d.setAttribute("stop-color", "white");
121
+ const u = document.createElementNS(c, "stop");
122
+ u.setAttribute(
123
+ "offset",
124
+ a === 0 ? "0" : String(o / a)
125
+ ), u.setAttribute("stop-color", "white");
126
+ const p = document.createElementNS(c, "stop");
127
+ return p.setAttribute("offset", "1"), p.setAttribute("stop-color", "black"), h.append(d, u, p), h;
128
+ }
129
+ createGraphPaperFadeMask(t) {
130
+ const e = document.createElementNS(c, "mask");
131
+ e.setAttribute("id", this.graphPaperMaskId), e.setAttribute("maskUnits", "userSpaceOnUse"), e.setAttribute("x", "0"), e.setAttribute("y", "0"), e.setAttribute("width", String(t.width)), e.setAttribute("height", String(t.height));
132
+ const s = document.createElementNS(c, "rect");
133
+ return s.setAttribute("x", "0"), s.setAttribute("y", "0"), s.setAttribute("width", String(t.width)), s.setAttribute("height", String(t.height)), s.setAttribute("fill", `url(#${this.graphPaperGradientId})`), e.appendChild(s), e;
134
+ }
135
+ }
136
+ class E {
137
+ pencilFilterId = "pluton-filter-pencil";
138
+ defsEl;
139
+ intensity;
140
+ constructor(t, e = 1) {
141
+ this.defsEl = t, this.intensity = e;
142
+ }
143
+ sync() {
144
+ m(this.defsEl, this.createPencilFilter());
145
+ }
146
+ createPencilFilter() {
147
+ const s = document.createElementNS(c, "filter");
148
+ s.setAttribute("id", this.pencilFilterId), s.setAttribute("x", "-50%"), s.setAttribute("y", "-50%"), s.setAttribute("width", "200%"), s.setAttribute("height", "200%");
149
+ const i = document.createElementNS(c, "feTurbulence");
150
+ i.setAttribute("type", "fractalNoise"), i.setAttribute("baseFrequency", String(0.22)), i.setAttribute("numOctaves", String(3)), i.setAttribute("seed", "1"), i.setAttribute("result", "turbulence");
151
+ const n = document.createElementNS(
152
+ c,
153
+ "feDisplacementMap"
154
+ );
155
+ return n.setAttribute("in", "SourceGraphic"), n.setAttribute("in2", "turbulence"), n.setAttribute("scale", String(this.intensity)), n.setAttribute("xChannelSelector", "R"), n.setAttribute("yChannelSelector", "G"), s.appendChild(i), s.appendChild(n), s;
156
+ }
157
+ }
158
+ class L {
159
+ patterns;
160
+ gradients;
161
+ filters;
162
+ lastWidth = 0;
163
+ lastHeight = 0;
164
+ constructor(t, e = 1.25) {
165
+ this.patterns = new v(t), this.gradients = new P(t), this.filters = new E(t, e), this.patterns.sync(), this.filters.sync();
166
+ }
167
+ syncForViewport(t) {
168
+ this.lastWidth === t.width && this.lastHeight === t.height || (this.lastWidth = t.width, this.lastHeight = t.height, this.gradients.syncForViewport(t));
169
+ }
170
+ get hatchFill45Id() {
171
+ return this.patterns.hatchFill45Id;
172
+ }
173
+ get graphPaperPatternId() {
174
+ return this.patterns.graphPaperPatternId;
175
+ }
176
+ get graphPaperGradientId() {
177
+ return this.gradients.graphPaperGradientId;
178
+ }
179
+ get graphPaperMaskId() {
180
+ return this.gradients.graphPaperMaskId;
181
+ }
182
+ get pencilFilterId() {
183
+ return this.filters.pencilFilterId;
184
+ }
185
+ }
186
+ class C {
187
+ svg;
188
+ events;
189
+ requestFrame;
190
+ panX = 0;
191
+ panY = 0;
192
+ scale = 1;
193
+ targetPanX = 0;
194
+ targetPanY = 0;
195
+ targetScale = 1;
196
+ minScale = 1;
197
+ maxScale = 20;
198
+ dampingFactor = 0.2;
199
+ isPanning = !1;
200
+ isResetting = !1;
201
+ lastX = 0;
202
+ lastY = 0;
203
+ cachedRect = null;
204
+ panEnabled = !1;
205
+ zoomEnabled = !1;
206
+ panListeners = [];
207
+ zoomListeners = [];
208
+ constructor(t, e, s) {
209
+ this.svg = t, this.events = e, this.requestFrame = s;
210
+ }
211
+ dispose() {
212
+ this.enablePan(!1), this.enableZoom(!1);
213
+ }
214
+ state() {
215
+ return { panX: this.panX, panY: this.panY, scale: this.scale };
216
+ }
217
+ reset() {
218
+ this.isResetting || (this.isResetting = !0, this.targetPanX = 0, this.targetPanY = 0, this.targetScale = 1, this.requestFrame());
219
+ }
220
+ /**
221
+ * Interpolate current values toward targets.
222
+ * Called every frame by Engine's loop. Returns true if still animating.
223
+ */
224
+ tick() {
225
+ const e = this.panX, s = this.panY, i = this.scale;
226
+ this.panX = this.smoothStep(this.panX, this.targetPanX), this.panY = this.smoothStep(this.panY, this.targetPanY), this.scale = this.smoothStep(this.scale, this.targetScale);
227
+ const n = Math.abs(this.targetPanX - this.panX) < 0.01 && Math.abs(this.targetPanY - this.panY) < 0.01 && Math.abs(this.targetScale - this.scale) < 0.01;
228
+ return n && (this.panX = this.targetPanX, this.panY = this.targetPanY, this.scale = this.targetScale, this.isResetting && this.panX === 0 && this.panY === 0 && this.scale === 1 && (this.isResetting = !1)), (e !== this.panX || s !== this.panY || i !== this.scale) && this.events.emit("camera:changed", this.state()), !n;
229
+ }
230
+ smoothStep(t, e) {
231
+ return t + (e - t) * this.dampingFactor;
232
+ }
233
+ updateCachedRect() {
234
+ this.cachedRect = this.svg.getBoundingClientRect();
235
+ }
236
+ getRect() {
237
+ return this.cachedRect || this.updateCachedRect(), this.cachedRect;
238
+ }
239
+ enablePan(t) {
240
+ t !== this.panEnabled && (this.panEnabled = t, t ? this.addPanListeners() : this.removePanListeners());
241
+ }
242
+ enableZoom(t) {
243
+ t !== this.zoomEnabled && (this.zoomEnabled = t, t ? this.addZoomListeners() : this.removeZoomListeners());
244
+ }
245
+ addPanListeners() {
246
+ if (this.panListeners.length > 0) return;
247
+ const t = (i) => {
248
+ (i.button === 1 || i.button === 0 && i.shiftKey) && (i.preventDefault(), this.isPanning = !0, this.updateCachedRect(), this.lastX = i.clientX, this.lastY = i.clientY);
249
+ }, e = (i) => {
250
+ if (!this.isPanning || this.isResetting) return;
251
+ const n = i.clientX - this.lastX, r = i.clientY - this.lastY;
252
+ this.targetPanX += n, this.targetPanY += r;
253
+ const o = this.getRect(), a = o.width * 0.5 * this.targetScale, h = o.height * 0.5 * this.targetScale;
254
+ this.targetPanX = Math.max(-a, Math.min(a, this.targetPanX)), this.targetPanY = Math.max(-h, Math.min(h, this.targetPanY)), this.lastX = i.clientX, this.lastY = i.clientY, this.requestFrame();
255
+ }, s = (i) => {
256
+ (i.button === 1 || i.button === 0) && (this.isPanning = !1, this.cachedRect = null);
257
+ };
258
+ this.svg.addEventListener("mousedown", t), window.addEventListener("mousemove", e), window.addEventListener("mouseup", s), this.panListeners = [
259
+ {
260
+ target: this.svg,
261
+ type: "mousedown",
262
+ handler: t
263
+ },
264
+ {
265
+ target: window,
266
+ type: "mousemove",
267
+ handler: e
268
+ },
269
+ { target: window, type: "mouseup", handler: s }
270
+ ];
271
+ }
272
+ addZoomListeners() {
273
+ if (this.zoomListeners.length > 0) return;
274
+ const t = (e) => {
275
+ if (this.isResetting) return;
276
+ e.preventDefault(), this.updateCachedRect();
277
+ const s = this.cachedRect, i = e.clientX - s.left - s.width * 0.5, n = e.clientY - s.top - s.height * 0.5, r = e.deltaY < 0 ? 1.1 : 1 / 1.1, o = Math.max(
278
+ this.minScale,
279
+ Math.min(this.maxScale, this.targetScale * r)
280
+ ), a = o / this.targetScale;
281
+ this.targetPanX = i + (this.targetPanX - i) * a, this.targetPanY = n + (this.targetPanY - n) * a, this.targetScale = o, this.requestFrame();
282
+ };
283
+ this.svg.addEventListener("wheel", t, {
284
+ passive: !1
285
+ }), this.zoomListeners = [
286
+ { target: this.svg, type: "wheel", handler: t }
287
+ ];
288
+ }
289
+ removePanListeners() {
290
+ for (const { target: t, type: e, handler: s } of this.panListeners)
291
+ t.removeEventListener(e, s);
292
+ this.panListeners = [], this.isPanning = !1, this.cachedRect = null;
293
+ }
294
+ removeZoomListeners() {
295
+ for (const { target: t, type: e, handler: s } of this.zoomListeners)
296
+ t.removeEventListener(e, s);
297
+ this.zoomListeners = [];
298
+ }
299
+ }
300
+ class R {
301
+ root;
302
+ patternRect;
303
+ axesGroup;
304
+ xAxis;
305
+ yAxis;
306
+ constructor(t, e) {
307
+ this.root = document.createElementNS(c, "g"), this.root.classList.add("pluton-background"), t.appendChild(this.root);
308
+ const s = e.viewport(), i = this.computeExtent(s);
309
+ this.patternRect = document.createElementNS(c, "rect"), this.patternRect.classList.add("pluton-graph-paper"), this.patternRect.setAttribute("fill", `url(#${e.defs.graphPaperPatternId})`), this.root.appendChild(this.patternRect), this.updatePatternRect(i);
310
+ const { axes: n, xAxis: r, yAxis: o } = this.createAxes();
311
+ this.axesGroup = n, this.xAxis = r, this.yAxis = o, this.updateAxes(i);
312
+ }
313
+ updateForViewport(t) {
314
+ const e = this.computeExtent(t);
315
+ this.updatePatternRect(e), this.updateAxes(e);
316
+ }
317
+ enableGrid(t) {
318
+ this.patternRect.style.display = t ? "" : "none";
319
+ }
320
+ enableAxes(t) {
321
+ this.axesGroup.style.display = t ? "" : "none";
322
+ }
323
+ computeExtent(t) {
324
+ return Math.sqrt(t.width ** 2 + t.height ** 2) * 3;
325
+ }
326
+ updatePatternRect(t) {
327
+ this.patternRect.setAttribute("x", String(-t / 2)), this.patternRect.setAttribute("y", String(-t / 2)), this.patternRect.setAttribute("width", String(t)), this.patternRect.setAttribute("height", String(t));
328
+ }
329
+ updateAxes(t) {
330
+ this.xAxis.setAttribute("x1", String(-t)), this.xAxis.setAttribute("x2", String(t)), this.xAxis.setAttribute("y1", "0"), this.xAxis.setAttribute("y2", "0"), this.yAxis.setAttribute("x1", "0"), this.yAxis.setAttribute("x2", "0"), this.yAxis.setAttribute("y1", String(-t)), this.yAxis.setAttribute("y2", String(t));
331
+ }
332
+ createAxes() {
333
+ const t = document.createElementNS(c, "g");
334
+ t.classList.add("pluton-axes");
335
+ const e = document.createElementNS(c, "line");
336
+ e.classList.add("pluton-axis", "pluton-axis-x");
337
+ const s = document.createElementNS(c, "line");
338
+ return s.classList.add("pluton-axis", "pluton-axis-y"), t.append(e, s), this.root.appendChild(t), { axes: t, xAxis: e, yAxis: s };
339
+ }
340
+ }
341
+ class y {
342
+ root;
343
+ groups = [];
344
+ constructor(t, e) {
345
+ this.root = document.createElementNS(c, "g"), this.root.classList.add("pluton-layer", e), t.appendChild(this.root);
346
+ }
347
+ group() {
348
+ const t = this.createGroup(this.root);
349
+ return this.groups.push(t), t;
350
+ }
351
+ beginRecord() {
352
+ for (const t of this.groups) t.beginRecord();
353
+ }
354
+ commit() {
355
+ for (const t of this.groups) t.commit();
356
+ }
357
+ }
358
+ class M {
359
+ commands = [];
360
+ /**
361
+ * Move to a position relative to current position
362
+ * @param dx - horizontal offset
363
+ * @param dy - vertical offset
364
+ * @returns this builder for chaining
365
+ */
366
+ moveTo(t, e) {
367
+ return this.commands.push(`m ${t} ${e}`), this;
368
+ }
369
+ /**
370
+ * Move to an absolute position
371
+ * @param x - absolute x coordinate
372
+ * @param y - absolute y coordinate
373
+ * @returns this builder for chaining
374
+ */
375
+ moveToAbs(t, e) {
376
+ return this.commands.push(`M ${t} ${e}`), this;
377
+ }
378
+ /**
379
+ * Draw a line relative to current position
380
+ * @param dx - horizontal offset
381
+ * @param dy - vertical offset
382
+ * @returns this builder for chaining
383
+ */
384
+ lineTo(t, e) {
385
+ return this.commands.push(`l ${t} ${e}`), this;
386
+ }
387
+ /**
388
+ * Draw a line to an absolute position
389
+ * @param x - absolute x coordinate
390
+ * @param y - absolute y coordinate
391
+ * @returns this builder for chaining
392
+ */
393
+ lineToAbs(t, e) {
394
+ return this.commands.push(`L ${t} ${e}`), this;
395
+ }
396
+ /**
397
+ * Draw an arc relative to current position
398
+ * @param dx - horizontal offset to end point
399
+ * @param dy - vertical offset to end point
400
+ * @param r - arc radius
401
+ * @param clockwise - whether the arc sweeps clockwise
402
+ * @defaultValue false
403
+ * @param largeArc - whether to take the arc > 180°
404
+ * @defaultValue false
405
+ * @returns this builder for chaining
406
+ */
407
+ arcTo(t, e, s, i = !1, n = !1) {
408
+ if (s <= 0)
409
+ return this.commands.push(`l ${t} ${e}`), this;
410
+ const r = i ? 0 : 1, o = n ? 1 : 0;
411
+ return this.commands.push(`a ${s} ${s} 0 ${o} ${r} ${t} ${e}`), this;
412
+ }
413
+ /**
414
+ * Draw an arc to an absolute position
415
+ * @param x - absolute x coordinate of end point
416
+ * @param y - absolute y coordinate of end point
417
+ * @param r - arc radius
418
+ * @param clockwise - whether the arc sweeps clockwise
419
+ * @defaultValue false
420
+ * @param largeArc - whether to take the arc > 180°
421
+ * @defaultValue false
422
+ * @returns this builder for chaining
423
+ */
424
+ arcToAbs(t, e, s, i = !1, n = !1) {
425
+ if (s <= 0)
426
+ return this.commands.push(`L ${t} ${e}`), this;
427
+ const r = i ? 0 : 1, o = n ? 1 : 0;
428
+ return this.commands.push(`A ${s} ${s} 0 ${o} ${r} ${t} ${e}`), this;
429
+ }
430
+ /**
431
+ * Close the current path
432
+ * @returns this builder for chaining
433
+ */
434
+ close() {
435
+ return this.commands.push("z"), this;
436
+ }
437
+ /**
438
+ * Reset the builder
439
+ */
440
+ reset() {
441
+ this.commands.length = 0;
442
+ }
443
+ /**
444
+ * Get the SVG path data string
445
+ * @returns SVG path data
446
+ */
447
+ toString() {
448
+ return this.commands.join(" ");
449
+ }
450
+ }
451
+ class Y {
452
+ g;
453
+ paths = [];
454
+ // tracks current write position during record cycle
455
+ activeIndex = 0;
456
+ translateX = 0;
457
+ translateY = 0;
458
+ lastTransform = "";
459
+ drawUsage = "dynamic";
460
+ hasCommitted = !1;
461
+ constructor(t) {
462
+ this.g = document.createElementNS(c, "g"), t.appendChild(this.g);
463
+ }
464
+ /**
465
+ * Begin recording phase
466
+ */
467
+ beginRecord() {
468
+ this.activeIndex = 0;
469
+ }
470
+ /**
471
+ * Commit recorded paths to the DOM
472
+ */
473
+ commit() {
474
+ if (!(this.drawUsage === "static" && this.hasCommitted)) {
475
+ for (let t = 0; t < this.activeIndex; t++) {
476
+ const e = this.paths[t], s = e.builder.toString();
477
+ s !== e.lastD && (e.path.setAttribute("d", s), e.lastD = s);
478
+ }
479
+ if (this.paths.length > this.activeIndex)
480
+ for (let t = this.paths.length - 1; t >= this.activeIndex; t--)
481
+ this.paths[t].path.remove(), this.paths.pop();
482
+ this.drawUsage === "static" && (this.hasCommitted = !0);
483
+ }
484
+ }
485
+ translate(t, e) {
486
+ this.translateX = t, this.translateY = e, this.applyTransform();
487
+ }
488
+ setDrawUsage(t) {
489
+ this.drawUsage = t, t === "dynamic" && (this.hasCommitted = !1);
490
+ }
491
+ path(t) {
492
+ const e = this.activeIndex++, s = t?.className ?? "";
493
+ if (e < this.paths.length) {
494
+ const r = this.paths[e];
495
+ return r.builder.reset(), s !== r.lastClass && (s ? r.path.setAttribute("class", s) : r.path.removeAttribute("class"), r.lastClass = s), r.builder;
496
+ }
497
+ const i = new M(), n = document.createElementNS(c, "path");
498
+ return s && n.setAttribute("class", s), this.g.appendChild(n), this.paths.push({ builder: i, path: n, lastD: "", lastClass: s }), i;
499
+ }
500
+ clear() {
501
+ this.paths.length = 0, this.activeIndex = 0, this.g.replaceChildren(), this.hasCommitted = !1, this.translateX = 0, this.translateY = 0, this.applyTransform();
502
+ }
503
+ applyTransform() {
504
+ const t = `translate(${this.translateX}, ${this.translateY})`;
505
+ t !== this.lastTransform && (this.g.setAttribute("transform", t), this.lastTransform = t);
506
+ }
507
+ }
508
+ class X extends y {
509
+ unsubscribeBegin;
510
+ unsubscribeEnd;
511
+ constructor(t, e) {
512
+ super(t, "pluton-geometry"), this.unsubscribeBegin = e.on(
513
+ "engine:commit-start",
514
+ () => this.beginRecord()
515
+ ), this.unsubscribeEnd = e.on("engine:commit-end", () => this.commit());
516
+ }
517
+ createGroup(t) {
518
+ return new Y(t);
519
+ }
520
+ dispose() {
521
+ this.unsubscribeBegin(), this.unsubscribeEnd();
522
+ }
523
+ }
524
+ class F {
525
+ commands = [];
526
+ filledCommands = [];
527
+ textRecords = [];
528
+ currentX = 0;
529
+ currentY = 0;
530
+ /**
531
+ * Reset builder state
532
+ */
533
+ reset() {
534
+ this.commands.length = 0, this.textRecords.length = 0, this.filledCommands.length = 0, this.currentX = 0, this.currentY = 0;
535
+ }
536
+ /**
537
+ * Get SVG path data for dimension lines
538
+ * @returns SVG path data string
539
+ */
540
+ toPathData() {
541
+ return this.commands.join(" ");
542
+ }
543
+ /**
544
+ * Get SVG path data for filled shapes
545
+ * @returns SVG path data string
546
+ */
547
+ toFilledPathData() {
548
+ return this.filledCommands.join(" ");
549
+ }
550
+ /**
551
+ * Get all text records
552
+ * @returns array of text records with positions and alignment
553
+ */
554
+ getTexts() {
555
+ return this.textRecords;
556
+ }
557
+ // ------------------------------------
558
+ // positioning
559
+ // ------------------------------------
560
+ /**
561
+ * Move to a position relative to current position
562
+ * @param dx - horizontal offset
563
+ * @param dy - vertical offset
564
+ * @returns this builder for chaining
565
+ */
566
+ moveTo(t, e) {
567
+ return this.currentX += t, this.currentY += e, this.commands.push(`M ${this.currentX} ${this.currentY}`), this;
568
+ }
569
+ /**
570
+ * Move to an absolute position
571
+ * @param x - absolute x coordinate
572
+ * @param y - absolute y coordinate
573
+ * @returns this builder for chaining
574
+ */
575
+ moveToAbs(t, e) {
576
+ return this.currentX = t, this.currentY = e, this.commands.push(`M ${t} ${e}`), this;
577
+ }
578
+ /**
579
+ * Draw a line to a position relative to current position
580
+ * @param dx - horizontal offset
581
+ * @param dy - vertical offset
582
+ * @returns this builder for chaining
583
+ */
584
+ lineTo(t, e) {
585
+ return this.currentX += t, this.currentY += e, this.commands.push(`l ${t} ${e}`), this;
586
+ }
587
+ /**
588
+ * Draw a line to an absolute position
589
+ * @param x - absolute x coordinate
590
+ * @param y - absolute y coordinate
591
+ * @returns this builder for chaining
592
+ */
593
+ lineToAbs(t, e) {
594
+ return this.currentX = t, this.currentY = e, this.commands.push(`L ${t} ${e}`), this;
595
+ }
596
+ /**
597
+ * Draw an arc centered at current position
598
+ * @param r - arc radius
599
+ * @param startAngle - start angle in radians (0 = right, π/2 = up)
600
+ * @param endAngle - end angle in radians
601
+ * @returns this builder for chaining
602
+ */
603
+ arc(t, e, s) {
604
+ const i = this.currentX, n = this.currentY, r = i + t * Math.cos(e), o = n + t * Math.sin(e), a = i + t * Math.cos(s), h = n + t * Math.sin(s), d = s - e, u = d > 0 ? 1 : 0, g = Math.abs(d) > Math.PI ? 1 : 0;
605
+ this.commands.push(`M ${r} ${o}`);
606
+ const f = a - r, b = h - o;
607
+ return this.commands.push(`a ${t} ${t} 0 ${g} ${u} ${f} ${b}`), this.currentX = a, this.currentY = h, this;
608
+ }
609
+ // ------------------------------------
610
+ // primitives
611
+ // ------------------------------------
612
+ /**
613
+ * Draw an open arrow at current position
614
+ * @param angleRad - direction the arrow points in radians (0 = right, π/2 = up)
615
+ * @returns this builder for chaining
616
+ */
617
+ arrow(t, e = 8) {
618
+ const s = Math.PI / 4, i = this.currentX, n = this.currentY, r = i - Math.cos(t) * e, o = n - Math.sin(t) * e, a = this.rotateAround(r, o, i, n, +s), h = this.rotateAround(r, o, i, n, -s);
619
+ return this.commands.push(
620
+ `M ${a.x} ${a.y} L ${i} ${n} L ${h.x} ${h.y}`,
621
+ `M ${i} ${n}`
622
+ ), this;
623
+ }
624
+ /**
625
+ * Draw a filled arrow at current position
626
+ * @param angleRad - direction the arrow points in radians (0 = right, π/2 = up)
627
+ * @returns this builder for chaining
628
+ */
629
+ arrowFilled(t, e = 8) {
630
+ const s = Math.PI / 4, i = this.currentX, n = this.currentY, r = i - Math.cos(t) * e, o = n - Math.sin(t) * e, a = this.rotateAround(r, o, i, n, +s), h = this.rotateAround(r, o, i, n, -s);
631
+ return this.filledCommands.push(
632
+ `M ${a.x} ${a.y} L ${i} ${n} L ${h.x} ${h.y} Z`,
633
+ `M ${i} ${n}`
634
+ ), this;
635
+ }
636
+ /**
637
+ * Draw a tick mark at current position
638
+ * @param angleRad - orientation angle in radians (dimension line direction)
639
+ * @returns this builder for chaining
640
+ */
641
+ tick(t, e = 15) {
642
+ const s = this.currentX, i = this.currentY, n = e / 2, r = s, o = i - n, a = s, h = i + n, d = -Math.PI / 4, u = this.rotateAround(
643
+ r,
644
+ o,
645
+ s,
646
+ i,
647
+ d
648
+ ), p = this.rotateAround(
649
+ a,
650
+ h,
651
+ s,
652
+ i,
653
+ d
654
+ ), g = this.rotateAround(
655
+ r,
656
+ o,
657
+ s,
658
+ i,
659
+ t
660
+ ), f = this.rotateAround(
661
+ a,
662
+ h,
663
+ s,
664
+ i,
665
+ t
666
+ ), b = this.rotateAround(
667
+ u.x,
668
+ u.y,
669
+ s,
670
+ i,
671
+ t
672
+ ), x = this.rotateAround(
673
+ p.x,
674
+ p.y,
675
+ s,
676
+ i,
677
+ t
678
+ );
679
+ return this.commands.push(
680
+ `M ${g.x} ${g.y} L ${f.x} ${f.y}`,
681
+ `M ${b.x} ${b.y} L ${x.x} ${x.y}`,
682
+ `M ${s} ${i}`
683
+ ), this;
684
+ }
685
+ /**
686
+ * Draw a center mark (crosshair with center dot) at current position
687
+ * @param size - total size of the crosshair
688
+ * @defaultValue 10
689
+ * @returns this builder for chaining
690
+ */
691
+ centerMark(t = 10) {
692
+ const e = t / 2, s = this.currentX, i = this.currentY;
693
+ this.commands.push(
694
+ `M ${s - e} ${i} L ${s + e} ${i}`,
695
+ `M ${s} ${i - e} L ${s} ${i + e}`,
696
+ `M ${s} ${i}`
697
+ );
698
+ const n = t / 10;
699
+ return this.filledCommands.push(
700
+ `M ${s - n} ${i} a ${n} ${n} 0 1 0 ${n * 2} 0 a ${n} ${n} 0 1 0 ${-(n * 2)} 0`
701
+ ), this;
702
+ }
703
+ /**
704
+ * Rotate a point around a center point by a given angle
705
+ * @param x - point x coordinate
706
+ * @param y - point y coordinate
707
+ * @param centerX - rotation center x
708
+ * @param centerY - rotation center y
709
+ * @param radians - rotation angle in radians
710
+ * @returns rotated point coordinates
711
+ */
712
+ rotateAround = (t, e, s, i, n) => {
713
+ const r = t - s, o = e - i, a = Math.cos(n), h = Math.sin(n);
714
+ return {
715
+ x: s + r * a - o * h,
716
+ y: i + r * h + o * a
717
+ };
718
+ };
719
+ /**
720
+ * Place text at position relative to current position
721
+ * @param dx - horizontal offset from current position
722
+ * @param dy - vertical offset from current position
723
+ * @param text - text content to display
724
+ * @param align - text alignment (maps to SVG text-anchor: start, middle, end)
725
+ * @defaultValue "middle"
726
+ * @param className - optional class name for the text element
727
+ * @returns this builder for chaining
728
+ */
729
+ textAt(t, e, s, i = "middle", n) {
730
+ return this.textRecords.push({
731
+ x: this.currentX + t,
732
+ y: this.currentY + e,
733
+ text: s,
734
+ align: i,
735
+ className: n
736
+ }), this;
737
+ }
738
+ /**
739
+ * Place text at absolute position
740
+ * @param x - absolute x coordinate
741
+ * @param y - absolute y coordinate
742
+ * @param text - text content to display
743
+ * @param align - text alignment (maps to SVG text-anchor: start, middle, end)
744
+ * @defaultValue "middle"
745
+ * @param className - optional class name for the text element
746
+ * @returns this builder for chaining
747
+ */
748
+ textAtAbs(t, e, s, i = "middle", n) {
749
+ return this.textRecords.push({ x: t, y: e, text: s, align: i, className: n }), this;
750
+ }
751
+ /**
752
+ * Close the current path
753
+ * @returns this builder for chaining
754
+ */
755
+ close() {
756
+ return this.commands.push("z"), this;
757
+ }
758
+ }
759
+ class I {
760
+ g;
761
+ entries = [];
762
+ // tracks current dimension write position during record cycle
763
+ activeIndex = 0;
764
+ translateX = 0;
765
+ translateY = 0;
766
+ lastTransform = "";
767
+ drawUsage = "dynamic";
768
+ hasCommitted = !1;
769
+ constructor(t) {
770
+ this.g = document.createElementNS(c, "g"), t.appendChild(this.g);
771
+ }
772
+ /**
773
+ * Begin recording phase
774
+ */
775
+ beginRecord() {
776
+ this.activeIndex = 0;
777
+ for (const t of this.entries) t.activeTextCursor = 0;
778
+ }
779
+ /**
780
+ * Commit recorded dimensions to the DOM
781
+ */
782
+ commit() {
783
+ if (!(this.drawUsage === "static" && this.hasCommitted)) {
784
+ for (let t = 0; t < this.activeIndex; t++) {
785
+ const e = this.entries[t], s = e.builder.toPathData() ?? "";
786
+ s !== e.lastD && (e.path.setAttribute("d", s), e.lastD = s);
787
+ const i = e.builder.toFilledPathData() ?? "";
788
+ i !== e.lastFd && (e.filledPath.setAttribute("d", i), e.lastFd = i);
789
+ const n = e.builder.getTexts();
790
+ for (const r of n) {
791
+ const o = e.activeTextCursor++, a = o < e.texts.length ? e.texts[o] : this.createText(e), h = `translate(${r.x} ${r.y}) scale(1,-1)`;
792
+ r.align !== a.lastAnchor && (a.el.setAttribute("text-anchor", r.align), a.lastAnchor = r.align);
793
+ const d = r.className ?? "";
794
+ d !== a.lastClass && (d ? a.el.setAttribute("class", d) : a.el.removeAttribute("class"), a.lastClass = d), h !== a.lastTransform && (a.el.setAttribute("transform", h), a.lastTransform = h), r.text !== a.lastText && (a.el.textContent = r.text, a.lastText = r.text);
795
+ }
796
+ for (; e.texts.length > e.activeTextCursor; )
797
+ e.texts.pop()?.el.remove();
798
+ }
799
+ if (this.entries.length > this.activeIndex)
800
+ for (let t = this.entries.length - 1; t >= this.activeIndex; t--)
801
+ this.entries[t].root.remove(), this.entries.pop();
802
+ this.drawUsage === "static" && (this.hasCommitted = !0);
803
+ }
804
+ }
805
+ clear() {
806
+ this.entries.length = 0, this.activeIndex = 0, this.g.replaceChildren(), this.hasCommitted = !1, this.translateX = 0, this.translateY = 0, this.applyTransform();
807
+ }
808
+ translate(t, e) {
809
+ this.translateX = t, this.translateY = e, this.applyTransform();
810
+ }
811
+ setDrawUsage(t) {
812
+ this.drawUsage = t, t === "dynamic" && (this.hasCommitted = !1);
813
+ }
814
+ dimension(t) {
815
+ const { className: e = "" } = t ?? {}, s = this.activeIndex++;
816
+ if (s < this.entries.length) {
817
+ const h = this.entries[s];
818
+ return h.builder.reset(), e !== h.lastClass && (h.path.setAttribute("class", `pluton-dim-stroke ${e}`), h.filledPath.setAttribute("class", `pluton-dim-filled ${e}`), h.lastClass = e), h.builder;
819
+ }
820
+ const i = document.createElementNS(c, "g");
821
+ this.g.appendChild(i);
822
+ const n = document.createElementNS(c, "path");
823
+ n.setAttribute("class", `pluton-dim-stroke ${e}`), i.appendChild(n);
824
+ const r = document.createElementNS(c, "path");
825
+ r.setAttribute("class", `pluton-dim-filled ${e}`), i.appendChild(r);
826
+ const o = new F(), a = {
827
+ root: i,
828
+ path: n,
829
+ filledPath: r,
830
+ builder: o,
831
+ lastD: "",
832
+ lastFd: "",
833
+ lastClass: e,
834
+ texts: [],
835
+ activeTextCursor: 0
836
+ };
837
+ return this.entries.push(a), o;
838
+ }
839
+ applyTransform() {
840
+ const t = `translate(${this.translateX}, ${this.translateY})`;
841
+ t !== this.lastTransform && (this.g.setAttribute("transform", t), this.lastTransform = t);
842
+ }
843
+ /**
844
+ * Create a new text element for a dimension entry
845
+ * @param entry - dimension entry to add text to
846
+ * @returns newly created text cache
847
+ */
848
+ createText(t) {
849
+ const e = document.createElementNS(c, "text");
850
+ e.setAttribute("x", "0"), e.setAttribute("y", "0"), e.setAttribute("dominant-baseline", "middle"), t.root.appendChild(e);
851
+ const s = {
852
+ el: e,
853
+ lastTransform: "",
854
+ lastAnchor: "",
855
+ lastText: "",
856
+ lastClass: ""
857
+ };
858
+ return t.texts.push(s), s;
859
+ }
860
+ }
861
+ class k extends y {
862
+ unsubscribeBegin;
863
+ unsubscribeEnd;
864
+ constructor(t, e) {
865
+ super(t, "pluton-dimensions"), this.unsubscribeBegin = e.on(
866
+ "engine:commit-start",
867
+ () => this.beginRecord()
868
+ ), this.unsubscribeEnd = e.on("engine:commit-end", () => this.commit());
869
+ }
870
+ createGroup(t) {
871
+ return new I(t);
872
+ }
873
+ dispose() {
874
+ this.unsubscribeBegin(), this.unsubscribeEnd();
875
+ }
876
+ }
877
+ class T {
878
+ context;
879
+ backgroundLayer;
880
+ worldLayer;
881
+ geometryLayer;
882
+ dimensionsLayer;
883
+ pencilFilterEnabled = !1;
884
+ background;
885
+ lastCameraTransform = "";
886
+ constructor(t, e) {
887
+ this.context = t;
888
+ const s = t.svg, i = document.createElementNS(c, "g");
889
+ i.classList.add("pluton-background-container"), i.setAttribute(
890
+ "mask",
891
+ `url(#${t.defs.graphPaperMaskId})`
892
+ ), s.appendChild(i), this.backgroundLayer = document.createElementNS(c, "g"), this.backgroundLayer.classList.add("pluton-background-layer"), i.appendChild(this.backgroundLayer), this.background = new R(this.backgroundLayer, t);
893
+ const n = document.createElementNS(c, "g");
894
+ n.classList.add("pluton-content-container"), s.appendChild(n), this.worldLayer = document.createElementNS(c, "g"), this.worldLayer.classList.add("pluton-world-layer"), n.appendChild(this.worldLayer), this.geometryLayer = new X(this.worldLayer, e), this.dimensionsLayer = new k(this.worldLayer, e), this.applyFilter(), this.updateTransforms();
895
+ }
896
+ get geometry() {
897
+ return this.geometryLayer;
898
+ }
899
+ get dimensions() {
900
+ return this.dimensionsLayer;
901
+ }
902
+ dispose() {
903
+ this.geometryLayer.dispose(), this.dimensionsLayer.dispose(), this.backgroundLayer.parentElement?.remove(), this.worldLayer.parentElement?.remove();
904
+ }
905
+ updateTransforms() {
906
+ const t = this.context.viewport(), e = this.context.camera(), s = t.x + t.width * 0.5, i = t.y + t.height * 0.5;
907
+ if (e) {
908
+ const n = s + e.panX, r = i + e.panY, o = e.scale, a = `translate(${n}px, ${r}px) scale(${o}, ${-o})`;
909
+ a !== this.lastCameraTransform && (this.backgroundLayer.style.transform = a, this.worldLayer.style.transform = a, this.lastCameraTransform = a);
910
+ }
911
+ }
912
+ onViewportChanged(t) {
913
+ this.background.updateForViewport(t);
914
+ }
915
+ enableGrid(t) {
916
+ this.background.enableGrid(t);
917
+ }
918
+ enableAxes(t) {
919
+ this.background.enableAxes(t);
920
+ }
921
+ enableFilter(t) {
922
+ this.pencilFilterEnabled = t, this.applyFilter();
923
+ }
924
+ applyFilter() {
925
+ const t = this.context.defs.pencilFilterId, e = this.pencilFilterEnabled ? `url(#${t})` : "none";
926
+ this.geometryLayer.root.style.filter = e, this.dimensionsLayer.root.style.filter = e;
927
+ }
928
+ }
929
+ class N {
930
+ drawCallbacks = [];
931
+ paramsState;
932
+ autoRenderEnabled = !1;
933
+ events;
934
+ frameBudget = 1e3 / 60;
935
+ // 60 FPS cap (~16.67ms)
936
+ lastFrameTime = 0;
937
+ renderPending = !1;
938
+ rafId;
939
+ tickFn = null;
940
+ constructor(t, e) {
941
+ this.events = t;
942
+ for (const s of Object.keys(e)) {
943
+ const i = e[s];
944
+ if (i !== null && typeof i == "object")
945
+ throw new Error(
946
+ `Pluton2D params must be flat. "${s}" is an object — nested mutations won't trigger redraws.`
947
+ );
948
+ }
949
+ this.paramsState = new Proxy(e, {
950
+ set: (s, i, n) => {
951
+ if (n !== null && typeof n == "object")
952
+ throw new Error(
953
+ `Pluton2D params must be flat. "${String(i)}" cannot be an object.`
954
+ );
955
+ return s[i] = n, this.autoRenderEnabled && this.scheduleRender(), !0;
956
+ }
957
+ });
958
+ }
959
+ getParams() {
960
+ return this.paramsState;
961
+ }
962
+ /**
963
+ * Set a per-frame tick function (e.g. camera interpolation).
964
+ * Called every loop iteration. Return true to keep the loop alive.
965
+ */
966
+ setTickFn(t) {
967
+ this.tickFn = t;
968
+ }
969
+ draw(t) {
970
+ return this.drawCallbacks.push(t), this.autoRenderEnabled || (this.autoRenderEnabled = !0, this.scheduleRender()), () => {
971
+ const e = this.drawCallbacks.indexOf(t);
972
+ e >= 0 && this.drawCallbacks.splice(e, 1);
973
+ };
974
+ }
975
+ dispose() {
976
+ this.drawCallbacks.length = 0, this.autoRenderEnabled = !1, this.renderPending = !1, this.rafId !== void 0 && (cancelAnimationFrame(this.rafId), this.rafId = void 0);
977
+ }
978
+ /** Mark dirty + keep loop alive. Called on params mutation or resize. */
979
+ scheduleRender() {
980
+ this.renderPending = !0, this.ensureLoop();
981
+ }
982
+ /** Keep loop alive without marking dirty. Called by camera when targets change. */
983
+ requestFrame() {
984
+ this.ensureLoop();
985
+ }
986
+ ensureLoop() {
987
+ this.rafId === void 0 && (this.rafId = requestAnimationFrame((t) => this.loop(t)));
988
+ }
989
+ loop(t) {
990
+ this.rafId = void 0;
991
+ let s = this.tickFn?.() ?? !1;
992
+ if (this.renderPending) {
993
+ const i = t - this.lastFrameTime;
994
+ i >= this.frameBudget ? (this.lastFrameTime = t - i % this.frameBudget, this.renderPending = !1, this.commit()) : s = !0;
995
+ }
996
+ s && this.ensureLoop();
997
+ }
998
+ commit() {
999
+ this.events.emit("engine:commit-start", void 0);
1000
+ for (const t of this.drawCallbacks)
1001
+ t(this.paramsState);
1002
+ this.events.emit("engine:commit-end", void 0);
1003
+ }
1004
+ }
1005
+ class z {
1006
+ context;
1007
+ events;
1008
+ scene;
1009
+ engine;
1010
+ camera;
1011
+ defsEl;
1012
+ /**
1013
+ * Reactive parameters that trigger redraw when modified or reassigned
1014
+ */
1015
+ params;
1016
+ /**
1017
+ * Create a new Pluton2D instance
1018
+ * @param svg - SVG element to render into
1019
+ * @param initialParams - initial reactive parameters
1020
+ */
1021
+ constructor(t, e, s) {
1022
+ this.events = new w(), t.classList.add("pluton-root"), this.defsEl = document.createElementNS(c, "defs"), t.insertBefore(this.defsEl, t.firstChild);
1023
+ const i = new L(this.defsEl, s?.filterIntensity);
1024
+ this.engine = new N(this.events, e), this.params = this.engine.getParams(), this.camera = new C(t, this.events, () => this.engine.requestFrame()), this.engine.setTickFn(() => this.camera.tick());
1025
+ let n = null;
1026
+ this.context = new S(t, i, this.camera, () => {
1027
+ n?.();
1028
+ }), i.syncForViewport(this.context.viewport()), this.scene = new T(this.context, this.events), n = () => {
1029
+ this.context.invalidateViewport();
1030
+ const r = this.context.viewport();
1031
+ i.syncForViewport(r), this.scene.onViewportChanged(r), this.scene.updateTransforms(), this.engine.scheduleRender();
1032
+ }, this.events.on("camera:changed", () => {
1033
+ this.scene.updateTransforms();
1034
+ });
1035
+ }
1036
+ /**
1037
+ * Access the geometry layer for drawing shapes
1038
+ */
1039
+ get geometry() {
1040
+ return this.scene.geometry;
1041
+ }
1042
+ /**
1043
+ * Access the dimensions layer for annotations
1044
+ */
1045
+ get dimensions() {
1046
+ return this.scene.dimensions;
1047
+ }
1048
+ /**
1049
+ * Register a reactive drawing callback
1050
+ * @param callback - drawing function receiving current params
1051
+ * @returns unsubscribe function to remove the callback
1052
+ */
1053
+ draw(t) {
1054
+ return this.engine.draw(t);
1055
+ }
1056
+ /**
1057
+ * Enable or disable the hand-drawn pencil filter effect
1058
+ * @param enabled - whether the filter is active
1059
+ * @defaultValue false
1060
+ */
1061
+ enableFilter(t) {
1062
+ this.scene.enableFilter(t);
1063
+ }
1064
+ /**
1065
+ * Enable or disable the built-in graph-paper background
1066
+ * @param enabled - whether the graph-paper is visible
1067
+ * @defaultValue true
1068
+ */
1069
+ enableGrid(t) {
1070
+ this.scene.enableGrid(t);
1071
+ }
1072
+ /**
1073
+ * Enable or disable the built-in axes
1074
+ * @param enabled - whether the axes are visible
1075
+ * @defaultValue true
1076
+ */
1077
+ enableAxes(t) {
1078
+ this.scene.enableAxes(t);
1079
+ }
1080
+ /**
1081
+ * Enable or disable camera panning
1082
+ * @param enabled - whether pan input is active
1083
+ * @defaultValue false
1084
+ */
1085
+ enablePan(t) {
1086
+ this.camera.enablePan(t);
1087
+ }
1088
+ /**
1089
+ * Enable or disable camera zooming
1090
+ * @param enabled - whether zoom input is active
1091
+ * @defaultValue false
1092
+ */
1093
+ enableZoom(t) {
1094
+ this.camera.enableZoom(t);
1095
+ }
1096
+ /**
1097
+ * Enable or disable the built‑in hatch fill on geometry
1098
+ * @param enabled - whether hatch fill is active
1099
+ * @defaultValue false
1100
+ */
1101
+ enableHatchFill(t) {
1102
+ t ? this.context.svg.classList.add("pluton-fill-hatch") : this.context.svg.classList.remove("pluton-fill-hatch");
1103
+ }
1104
+ /**
1105
+ * Reset camera to initial position and zoom
1106
+ */
1107
+ resetCamera() {
1108
+ this.camera.reset();
1109
+ }
1110
+ /**
1111
+ * Clean up resources and remove event listeners
1112
+ */
1113
+ dispose() {
1114
+ this.camera.dispose(), this.scene.dispose(), this.engine.dispose(), this.context.dispose(), this.events.clear(), this.defsEl.remove(), this.context.svg.classList.remove("pluton-root");
1115
+ }
1116
+ }
1117
+ export {
1118
+ z as Pluton2D
1119
+ };