@vanduo-oss/vd3-cbun 1.3.2 → 1.4.1

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.
Files changed (40) hide show
  1. package/CHANGELOG.md +83 -0
  2. package/README.md +24 -9
  3. package/SKILL.md +42 -15
  4. package/dist/charts/core.d.ts +4 -0
  5. package/dist/charts/index.cjs +197 -10
  6. package/dist/charts/index.cjs.map +3 -3
  7. package/dist/charts/index.js +197 -10
  8. package/dist/charts/index.js.map +3 -3
  9. package/dist/charts/vd3-charts.css +82 -0
  10. package/dist/charts/vue.d.ts +4 -0
  11. package/dist/code-editor/core.d.ts +19 -2
  12. package/dist/code-editor/highlight.cjs +1242 -0
  13. package/dist/code-editor/highlight.cjs.map +7 -0
  14. package/dist/code-editor/highlight.d.ts +6 -0
  15. package/dist/code-editor/highlight.js +1219 -0
  16. package/dist/code-editor/highlight.js.map +7 -0
  17. package/dist/code-editor/index.cjs +694 -70
  18. package/dist/code-editor/index.cjs.map +4 -4
  19. package/dist/code-editor/index.d.ts +2 -0
  20. package/dist/code-editor/index.js +694 -70
  21. package/dist/code-editor/index.js.map +4 -4
  22. package/dist/code-editor/vd3-code-editor.css +5 -0
  23. package/dist/draw/core.d.ts +7 -1
  24. package/dist/draw/index.cjs +470 -52
  25. package/dist/draw/index.cjs.map +2 -2
  26. package/dist/draw/index.js +470 -52
  27. package/dist/draw/index.js.map +2 -2
  28. package/dist/draw/vd3-draw.css +45 -0
  29. package/dist/draw/vue.d.ts +4 -0
  30. package/dist/hex-grid/core.d.ts +41 -0
  31. package/dist/hex-grid/index.cjs +308 -13
  32. package/dist/hex-grid/index.cjs.map +3 -3
  33. package/dist/hex-grid/index.d.ts +1 -0
  34. package/dist/hex-grid/index.js +308 -13
  35. package/dist/hex-grid/index.js.map +3 -3
  36. package/dist/hex-grid/vue.d.ts +4 -0
  37. package/dist/index.js +2 -2
  38. package/dist/index.js.map +2 -2
  39. package/dist/meta.json +177 -66
  40. package/package.json +15 -10
@@ -211,6 +211,31 @@
211
211
  cursor: crosshair;
212
212
  }
213
213
 
214
+ .vd-draw-canvas[data-tool="select"] {
215
+ cursor: default;
216
+ }
217
+
218
+ .vd-draw-canvas[data-tool="select"] .vd-draw-shape-layer > * {
219
+ cursor: move;
220
+ }
221
+
222
+ .vd-draw-canvas[data-tool="hand"] {
223
+ cursor: grab;
224
+ }
225
+
226
+ .vd-draw-canvas[data-tool="hand"].vd-draw-panning,
227
+ .vd-draw-canvas.vd-draw-panning {
228
+ cursor: grabbing;
229
+ }
230
+
231
+ .vd-draw-canvas[data-tool="text"] {
232
+ cursor: text;
233
+ }
234
+
235
+ .vd-draw-canvas[data-tool="eraser"] {
236
+ cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Ccircle cx='12' cy='12' r='10' fill='rgba(230,74,25,0.08)' stroke='%23e64a19' stroke-width='1.5' stroke-dasharray='2 2'/%3E%3Ccircle cx='12' cy='12' r='1' fill='%23e64a19'/%3E%3C/svg%3E") 12 12, crosshair;
237
+ }
238
+
214
239
  .vd-draw-canvas:focus {
215
240
  outline: none;
216
241
  }
@@ -264,6 +289,26 @@
264
289
  cursor: nwse-resize;
265
290
  }
266
291
 
292
+ .vd-draw-handle[data-handle="n"],
293
+ .vd-draw-handle[data-handle="s"] {
294
+ cursor: ns-resize;
295
+ }
296
+
297
+ .vd-draw-handle[data-handle="e"],
298
+ .vd-draw-handle[data-handle="w"] {
299
+ cursor: ew-resize;
300
+ }
301
+
302
+ .vd-draw-handle[data-handle="nw"],
303
+ .vd-draw-handle[data-handle="se"] {
304
+ cursor: nwse-resize;
305
+ }
306
+
307
+ .vd-draw-handle[data-handle="ne"],
308
+ .vd-draw-handle[data-handle="sw"] {
309
+ cursor: nesw-resize;
310
+ }
311
+
267
312
  .vd-draw-marquee-rect {
268
313
  fill: color-mix(in srgb, var(--vd-draw-selection) 12%, transparent);
269
314
  stroke: var(--vd-draw-selection);
@@ -46,6 +46,10 @@ export interface VdDrawEmits {
46
46
  export interface VdDrawExposed {
47
47
  getInstance(): VdDrawCore | null;
48
48
  setTool(tool: DrawTool): void;
49
+ setReadonly(readonly: boolean): void;
50
+ setSnap(snap: boolean): void;
51
+ setHistoryEnabled(enabled: boolean): void;
52
+ setHistoryLimit(limit: number): void;
49
53
  undo(): void;
50
54
  redo(): void;
51
55
  canUndo(): boolean;
@@ -20,6 +20,13 @@ export interface VdHexGridOptions {
20
20
  height?: number;
21
21
  /** Grid rotation in radians. Default 0. */
22
22
  rotation?: number;
23
+ /**
24
+ * Canvas backing-store multiplier. `'auto'` (default) resolves to
25
+ * `min(devicePixelRatio || 1, 2)`. Pass `1` for a 1:1 CSS-pixel buffer.
26
+ */
27
+ pixelRatio?: number | 'auto';
28
+ /** Viewport culling. Default true. */
29
+ cull?: boolean;
23
30
  }
24
31
 
25
32
  /** A single hex cell stored in the grid. */
@@ -47,6 +54,24 @@ export interface HexGridTransform {
47
54
  scale: number;
48
55
  }
49
56
 
57
+ /** Last-frame render metrics from {@link VdHexGrid.getRenderStats}. */
58
+ export interface HexRenderStats {
59
+ /** Total hexes in the grid. */
60
+ total: number;
61
+ /** Hexes intersecting the current viewport. */
62
+ visible: number;
63
+ /** Hexes actually drawn in the last frame. */
64
+ drawn: number;
65
+ /** Whether the last frame was a sharp culled render or a fast blit. */
66
+ mode: 'sharp' | 'fast';
67
+ /** Duration of the last sharp render in milliseconds. */
68
+ lastRenderMs: number;
69
+ /** Effective device-pixel-ratio multiplier. */
70
+ pixelRatio: number;
71
+ /** Current zoom scale. */
72
+ scale: number;
73
+ }
74
+
50
75
  /** Event payloads for {@link VdHexGrid.on}. */
51
76
  export interface VdHexGridEventMap {
52
77
  /** A hex was selected by click/tap. */
@@ -83,6 +108,10 @@ export declare class VdHexGrid {
83
108
  height: number;
84
109
  /** Grid rotation in radians. */
85
110
  rotation: number;
111
+ /** Canvas backing-store multiplier (`'auto'` or a number). */
112
+ pixelRatio: number | 'auto';
113
+ /** Viewport culling flag. */
114
+ cull: boolean;
86
115
  /** All hexes, keyed by `"q,r"`. */
87
116
  hexes: Map<string, HexCell>;
88
117
  /** Currently selected hex, or null. */
@@ -117,6 +146,18 @@ export declare class VdHexGrid {
117
146
  /** Get all hexes. */
118
147
  getAllHexes(): HexCell[];
119
148
 
149
+ /** Get the hexes intersecting the current viewport, sorted by row then column. */
150
+ getVisibleHexes(): HexCell[];
151
+
152
+ /** Get last-frame render metrics. */
153
+ getRenderStats(): HexRenderStats;
154
+
155
+ /** Set the device-pixel-ratio multiplier and re-render (no grid regeneration). */
156
+ setPixelRatio(ratio: number | 'auto'): void;
157
+
158
+ /** Toggle viewport culling and re-render (no grid regeneration). */
159
+ setCull(cull: boolean): void;
160
+
120
161
  /** Set hex fill color. */
121
162
  setHexFill(q: number, r: number, color: string): void;
122
163
 
@@ -154,22 +154,54 @@ function getTerrainColor(terrainType) {
154
154
  }
155
155
 
156
156
  // src/hex-grid/core.js
157
- var VD_HEX_VERSION = "1.0.1";
157
+ var VD_HEX_VERSION = "1.1.0";
158
158
  var ZOOM_MIN = 0.3;
159
159
  var ZOOM_MAX = 3;
160
160
  var ZOOM_FACTOR = 0.1;
161
161
  var DRAG_THRESHOLD = 2;
162
+ var FAST_RENDER_LIMIT = 8e3;
163
+ var SHARP_IDLE_MS = 120;
164
+ var MAX_PIXEL_RATIO = 2;
165
+ var MIN_BUCKET_SIZE = 64;
166
+ var now = () => typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
162
167
  var VdHexGrid = class {
163
- constructor({ element, canvas, size = 30, width = 10, height = 10, rotation = 0 }) {
168
+ constructor({
169
+ element,
170
+ canvas,
171
+ size = 30,
172
+ width = 10,
173
+ height = 10,
174
+ rotation = 0,
175
+ pixelRatio = "auto",
176
+ cull = true
177
+ }) {
164
178
  this.element = element;
165
179
  this.canvas = canvas;
166
180
  this.size = size;
167
181
  this.width = width;
168
182
  this.height = height;
169
183
  this.rotation = rotation;
184
+ this.pixelRatio = pixelRatio;
185
+ this.cull = cull;
170
186
  this.hexes = /* @__PURE__ */ new Map();
171
187
  this.selectedHex = null;
172
188
  this.listeners = {};
189
+ this._bucketSize = Math.max(size * 2, MIN_BUCKET_SIZE);
190
+ this._index = /* @__PURE__ */ new Map();
191
+ this._gestureCancel = null;
192
+ this._sharpTimer = null;
193
+ this._frameCanvas = null;
194
+ this._frameCtx = null;
195
+ this._frameTransform = null;
196
+ this._stats = {
197
+ total: 0,
198
+ visible: 0,
199
+ drawn: 0,
200
+ mode: "sharp",
201
+ lastRenderMs: 0,
202
+ pixelRatio: 1,
203
+ scale: 1
204
+ };
173
205
  this.transform = { x: 0, y: 0, scale: 1 };
174
206
  this.dragging = false;
175
207
  this.lastPos = null;
@@ -229,6 +261,14 @@ var VdHexGrid = class {
229
261
  */
230
262
  destroy() {
231
263
  this._teardownEvents();
264
+ this._cancelGestureRender();
265
+ if (this._sharpTimer) {
266
+ clearTimeout(this._sharpTimer);
267
+ this._sharpTimer = null;
268
+ }
269
+ this._frameCanvas = null;
270
+ this._frameCtx = null;
271
+ this._frameTransform = null;
232
272
  if (this._themeObserver) {
233
273
  this._themeObserver.disconnect();
234
274
  this._themeObserver = null;
@@ -239,6 +279,195 @@ var VdHexGrid = class {
239
279
  this._themeMediaHandler = null;
240
280
  }
241
281
  }
282
+ /**
283
+ * Resolve the effective device-pixel-ratio multiplier.
284
+ * @returns {number}
285
+ */
286
+ _resolvePixelRatio() {
287
+ const requested = this.pixelRatio;
288
+ if (requested === "auto" || requested == null) {
289
+ const dpr = typeof window !== "undefined" && typeof window.devicePixelRatio === "number" ? window.devicePixelRatio : 1;
290
+ return Math.max(1, Math.min(dpr || 1, MAX_PIXEL_RATIO));
291
+ }
292
+ const n = Number(requested);
293
+ return Number.isFinite(n) && n > 0 ? n : 1;
294
+ }
295
+ /** Request an animation frame, returning a cancel function. */
296
+ _requestFrame(callback) {
297
+ if (typeof requestAnimationFrame === "function") {
298
+ const id2 = requestAnimationFrame(callback);
299
+ return () => cancelAnimationFrame(id2);
300
+ }
301
+ const id = setTimeout(callback, 16);
302
+ return () => clearTimeout(id);
303
+ }
304
+ /** Cancel a pending gesture frame, if any. */
305
+ _cancelGestureRender() {
306
+ if (this._gestureCancel) {
307
+ this._gestureCancel();
308
+ this._gestureCancel = null;
309
+ }
310
+ }
311
+ /**
312
+ * Coalesce gesture-driven transform changes into at most one render/frame.
313
+ */
314
+ _scheduleGestureRender() {
315
+ if (this._gestureCancel) return;
316
+ this._gestureCancel = this._requestFrame(() => {
317
+ this._gestureCancel = null;
318
+ this._renderGesture();
319
+ });
320
+ }
321
+ /**
322
+ * Schedule a sharp culled re-render after the gesture goes idle.
323
+ */
324
+ _scheduleSharpRender() {
325
+ if (this._sharpTimer) clearTimeout(this._sharpTimer);
326
+ this._sharpTimer = setTimeout(() => {
327
+ this._sharpTimer = null;
328
+ this._render();
329
+ }, SHARP_IDLE_MS);
330
+ }
331
+ /** Render the current gesture frame (fast blit or sharp, adaptively). */
332
+ _renderGesture() {
333
+ const visible = this.cull ? this._computeVisibleHexes() : null;
334
+ const count = visible ? visible.length : this.hexes.size;
335
+ if (count > FAST_RENDER_LIMIT && this._frameCanvas) {
336
+ this._blitFrame();
337
+ } else {
338
+ this._render(visible);
339
+ }
340
+ this._scheduleSharpRender();
341
+ }
342
+ /**
343
+ * Blit the last sharp frame snapshot offset/scaled for the current transform.
344
+ * The snapshot is in screen (CSS) space at `_frameTransform`; this maps the
345
+ * pixel under the old transform to its position under the current transform.
346
+ */
347
+ _blitFrame() {
348
+ const frame = this._frameCanvas;
349
+ const from = this._frameTransform;
350
+ if (!frame || !from || !frame.width || !frame.height) {
351
+ this._render();
352
+ return;
353
+ }
354
+ const rect = this.canvas.getBoundingClientRect();
355
+ const displayWidth = rect.width || 800;
356
+ const displayHeight = rect.height || 400;
357
+ const ratio = this._resolvePixelRatio();
358
+ const { x, y, scale } = this.transform;
359
+ const k = scale / (from.scale || 1);
360
+ const bx = x - k * from.x;
361
+ const by = y - k * from.y;
362
+ this.ctx.setTransform(1, 0, 0, 1, 0, 0);
363
+ this.ctx.fillStyle = this.themeColors.bgPrimary;
364
+ this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
365
+ this.ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
366
+ this.ctx.drawImage(
367
+ frame,
368
+ 0,
369
+ 0,
370
+ frame.width,
371
+ frame.height,
372
+ bx,
373
+ by,
374
+ displayWidth * k,
375
+ displayHeight * k
376
+ );
377
+ this.ctx.setTransform(1, 0, 0, 1, 0, 0);
378
+ this._stats.mode = "fast";
379
+ this._stats.pixelRatio = ratio;
380
+ this._stats.scale = scale;
381
+ this._stats.lastRenderMs = 0;
382
+ }
383
+ /**
384
+ * Capture the current canvas into the offscreen frame snapshot.
385
+ */
386
+ _captureFrame() {
387
+ const w = this.canvas.width;
388
+ const h2 = this.canvas.height;
389
+ if (!w || !h2) return;
390
+ if (!this._frameCanvas) {
391
+ this._frameCanvas = typeof document !== "undefined" ? document.createElement("canvas") : null;
392
+ if (!this._frameCanvas) return;
393
+ }
394
+ const frame = this._frameCanvas;
395
+ if (frame.width !== w) frame.width = w;
396
+ if (frame.height !== h2) frame.height = h2;
397
+ const fctx = frame.getContext("2d");
398
+ if (!fctx) return;
399
+ this._frameCtx = fctx;
400
+ fctx.setTransform(1, 0, 0, 1, 0, 0);
401
+ fctx.clearRect(0, 0, w, h2);
402
+ fctx.drawImage(this.canvas, 0, 0);
403
+ this._frameTransform = { ...this.transform };
404
+ }
405
+ /**
406
+ * Compute the cells intersecting the current viewport (always culled by the
407
+ * viewport, independent of the `cull` render switch). Uses the spatial bucket
408
+ * index when present.
409
+ * @returns {HexCell[]}
410
+ */
411
+ _computeVisibleHexes() {
412
+ const rect = this.canvas.getBoundingClientRect();
413
+ const displayWidth = rect.width || 800;
414
+ const displayHeight = rect.height || 400;
415
+ const { x, y, scale } = this.transform;
416
+ const minX = -x / scale - this.size;
417
+ const maxX = (displayWidth - x) / scale + this.size;
418
+ const minY = -y / scale - this.size;
419
+ const maxY = (displayHeight - y) / scale + this.size;
420
+ if (!this._index || this._index.size === 0) return this.getAllHexes();
421
+ const bs = this._bucketSize;
422
+ const bx0 = Math.floor(minX / bs);
423
+ const bx1 = Math.floor(maxX / bs);
424
+ const by0 = Math.floor(minY / bs);
425
+ const by1 = Math.floor(maxY / bs);
426
+ const out = [];
427
+ for (let by = by0; by <= by1; by++) {
428
+ for (let bx = bx0; bx <= bx1; bx++) {
429
+ const bucket = this._index.get(`${bx},${by}`);
430
+ if (!bucket) continue;
431
+ for (let i = 0; i < bucket.length; i++) {
432
+ const hex = bucket[i];
433
+ if (hex.x >= minX && hex.x <= maxX && hex.y >= minY && hex.y <= maxY) {
434
+ out.push(hex);
435
+ }
436
+ }
437
+ }
438
+ }
439
+ return out;
440
+ }
441
+ /**
442
+ * Get the cells intersecting the current viewport, sorted by row then column.
443
+ * @returns {HexCell[]}
444
+ */
445
+ getVisibleHexes() {
446
+ return this._computeVisibleHexes().sort((a, b) => a.r - b.r || a.q - b.q);
447
+ }
448
+ /**
449
+ * Last-frame render metrics.
450
+ * @returns {{total: number, visible: number, drawn: number, mode: string, lastRenderMs: number, pixelRatio: number, scale: number}}
451
+ */
452
+ getRenderStats() {
453
+ return { ...this._stats };
454
+ }
455
+ /**
456
+ * Set the device-pixel-ratio multiplier and re-render (no grid regeneration).
457
+ * @param {number|'auto'} ratio
458
+ */
459
+ setPixelRatio(ratio) {
460
+ this.pixelRatio = ratio;
461
+ this._render();
462
+ }
463
+ /**
464
+ * Toggle viewport culling and re-render (no grid regeneration).
465
+ * @param {boolean} cull
466
+ */
467
+ setCull(cull) {
468
+ this.cull = !!cull;
469
+ this._render();
470
+ }
242
471
  /**
243
472
  * Convert screen coordinates to world coordinates
244
473
  */
@@ -266,6 +495,8 @@ var VdHexGrid = class {
266
495
  */
267
496
  _generateGrid() {
268
497
  this.hexes.clear();
498
+ this._bucketSize = Math.max(this.size * 2, MIN_BUCKET_SIZE);
499
+ this._index = /* @__PURE__ */ new Map();
269
500
  for (let r = 0; r < this.height; r++) {
270
501
  const qOffset = Math.floor(r / 2);
271
502
  for (let q = -qOffset; q < this.width - qOffset; q++) {
@@ -282,6 +513,15 @@ var VdHexGrid = class {
282
513
  data: {}
283
514
  };
284
515
  this.hexes.set(`${q},${r}`, hex);
516
+ const bx = Math.floor(hex.x / this._bucketSize);
517
+ const by = Math.floor(hex.y / this._bucketSize);
518
+ const key = `${bx},${by}`;
519
+ let bucket = this._index.get(key);
520
+ if (!bucket) {
521
+ bucket = [];
522
+ this._index.set(key, bucket);
523
+ }
524
+ bucket.push(hex);
285
525
  }
286
526
  }
287
527
  }
@@ -293,29 +533,55 @@ var VdHexGrid = class {
293
533
  this.selectedHex = this.hexes.get(`${this.selectedHex.q},${this.selectedHex.r}`) ?? null;
294
534
  }
295
535
  /**
296
- * Render the hex grid on canvas
536
+ * Render the hex grid on canvas (synchronous sharp render).
537
+ *
538
+ * @param {HexCell[]|null} [precomputedVisible] - Visible cells to draw when
539
+ * culling is on; computed from the viewport when omitted.
297
540
  */
298
- _render() {
541
+ _render(precomputedVisible) {
542
+ const started = now();
299
543
  const rect = this.canvas.getBoundingClientRect();
300
544
  const displayWidth = rect.width || 800;
301
545
  const displayHeight = rect.height || 400;
302
- this.canvas.width = displayWidth;
303
- this.canvas.height = displayHeight;
546
+ const ratio = this._resolvePixelRatio();
547
+ const bufferWidth = Math.max(1, Math.round(displayWidth * ratio));
548
+ const bufferHeight = Math.max(1, Math.round(displayHeight * ratio));
549
+ if (this.canvas.width !== bufferWidth) this.canvas.width = bufferWidth;
550
+ if (this.canvas.height !== bufferHeight) this.canvas.height = bufferHeight;
551
+ this.ctx.setTransform(1, 0, 0, 1, 0, 0);
304
552
  this.ctx.fillStyle = this.themeColors.bgPrimary;
305
553
  this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
554
+ this.ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
306
555
  this.ctx.save();
307
556
  this.ctx.translate(this.transform.x, this.transform.y);
308
557
  this.ctx.scale(this.transform.scale, this.transform.scale);
309
- this.hexes.forEach((hex) => {
558
+ const visible = this.cull && this._index && this._index.size > 0 ? precomputedVisible || this._computeVisibleHexes() : null;
559
+ let drawn = 0;
560
+ const drawHex = (hex) => {
310
561
  this._drawHex(hex);
562
+ drawn += 1;
311
563
  if (this.customRenderCallback) {
312
564
  this.customRenderCallback(this.ctx, hex, this.size);
313
565
  }
314
- });
566
+ };
567
+ if (visible) {
568
+ for (let i = 0; i < visible.length; i++) drawHex(visible[i]);
569
+ } else {
570
+ this.hexes.forEach(drawHex);
571
+ }
315
572
  if (this.selectedHex) {
316
573
  this._drawHex(this.selectedHex, true);
317
574
  }
318
575
  this.ctx.restore();
576
+ this.ctx.setTransform(1, 0, 0, 1, 0, 0);
577
+ this._stats.total = this.hexes.size;
578
+ this._stats.visible = visible ? visible.length : this.hexes.size;
579
+ this._stats.drawn = drawn;
580
+ this._stats.mode = "sharp";
581
+ this._stats.pixelRatio = ratio;
582
+ this._stats.scale = this.transform.scale;
583
+ this._stats.lastRenderMs = now() - started;
584
+ this._captureFrame();
319
585
  }
320
586
  /**
321
587
  * Draw a single hex
@@ -381,11 +647,20 @@ var VdHexGrid = class {
381
647
  this.transform.x += dx;
382
648
  this.transform.y += dy;
383
649
  this.lastPos = cur;
384
- this._render();
650
+ this._scheduleGestureRender();
385
651
  },
386
652
  // Pan - pointer up / leave (shared stopDrag)
387
653
  pointerup: () => {
654
+ const wasDragging = this.dragging;
388
655
  this.dragging = false;
656
+ if (wasDragging) {
657
+ this._cancelGestureRender();
658
+ if (this._sharpTimer) {
659
+ clearTimeout(this._sharpTimer);
660
+ this._sharpTimer = null;
661
+ }
662
+ this._render();
663
+ }
389
664
  if (!this.hasMoved) {
390
665
  this.canvas.style.cursor = "pointer";
391
666
  }
@@ -412,7 +687,7 @@ var VdHexGrid = class {
412
687
  this.transform.x = mouse.x - (mouse.x - this.transform.x) * scaleDiff;
413
688
  this.transform.y = mouse.y - (mouse.y - this.transform.y) * scaleDiff;
414
689
  this.transform.scale = newScale;
415
- this._render();
690
+ this._scheduleGestureRender();
416
691
  this._emit("zoom", { scale: this.transform.scale });
417
692
  },
418
693
  // Touch events for pinch-to-zoom
@@ -437,12 +712,18 @@ var VdHexGrid = class {
437
712
  this.transform.x = center.x - (center.x - this.transform.x) * scaleDiff;
438
713
  this.transform.y = center.y - (center.y - this.transform.y) * scaleDiff;
439
714
  this.transform.scale = newScale;
440
- this._render();
715
+ this._scheduleGestureRender();
441
716
  this._emit("zoom", { scale: this.transform.scale });
442
717
  }
443
718
  },
444
719
  touchend: () => {
445
720
  this.touchState.touches = [];
721
+ this._cancelGestureRender();
722
+ if (this._sharpTimer) {
723
+ clearTimeout(this._sharpTimer);
724
+ this._sharpTimer = null;
725
+ }
726
+ this._render();
446
727
  },
447
728
  // Cursor style
448
729
  mouseenter: () => {
@@ -893,7 +1174,11 @@ var VdHexGrid2 = (0, import_vue.defineComponent)({
893
1174
  /** Grid rows (number of hexes). */
894
1175
  height: { type: Number, default: 10 },
895
1176
  /** Grid rotation (radians). */
896
- rotation: { type: Number, default: 0 }
1177
+ rotation: { type: Number, default: 0 },
1178
+ /** Canvas backing-store multiplier: a number or `'auto'` (default). */
1179
+ pixelRatio: { type: [Number, String], default: "auto" },
1180
+ /** Viewport culling (default true). */
1181
+ cull: { type: Boolean, default: true }
897
1182
  },
898
1183
  emits: ["select", "zoom", "pan", "ready"],
899
1184
  setup(props, { emit, expose }) {
@@ -905,7 +1190,9 @@ var VdHexGrid2 = (0, import_vue.defineComponent)({
905
1190
  size: props.size,
906
1191
  width: props.width,
907
1192
  height: props.height,
908
- rotation: props.rotation
1193
+ rotation: props.rotation,
1194
+ pixelRatio: props.pixelRatio,
1195
+ cull: props.cull
909
1196
  });
910
1197
  FORWARDED_EVENTS.forEach((name) => {
911
1198
  instance.on(name, (data) => emit(name, data));
@@ -928,6 +1215,14 @@ var VdHexGrid2 = (0, import_vue.defineComponent)({
928
1215
  () => props.rotation,
929
1216
  (v) => instance && instance.setRotation(v)
930
1217
  );
1218
+ (0, import_vue.watch)(
1219
+ () => props.pixelRatio,
1220
+ (v) => instance && instance.setPixelRatio(v)
1221
+ );
1222
+ (0, import_vue.watch)(
1223
+ () => props.cull,
1224
+ (v) => instance && instance.setCull(v)
1225
+ );
931
1226
  (0, import_vue.onBeforeUnmount)(() => {
932
1227
  if (instance) {
933
1228
  instance.destroy();