@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
@@ -128,22 +128,54 @@ function getTerrainColor(terrainType) {
128
128
  }
129
129
 
130
130
  // src/hex-grid/core.js
131
- var VD_HEX_VERSION = "1.0.1";
131
+ var VD_HEX_VERSION = "1.1.0";
132
132
  var ZOOM_MIN = 0.3;
133
133
  var ZOOM_MAX = 3;
134
134
  var ZOOM_FACTOR = 0.1;
135
135
  var DRAG_THRESHOLD = 2;
136
+ var FAST_RENDER_LIMIT = 8e3;
137
+ var SHARP_IDLE_MS = 120;
138
+ var MAX_PIXEL_RATIO = 2;
139
+ var MIN_BUCKET_SIZE = 64;
140
+ var now = () => typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
136
141
  var VdHexGrid = class {
137
- constructor({ element, canvas, size = 30, width = 10, height = 10, rotation = 0 }) {
142
+ constructor({
143
+ element,
144
+ canvas,
145
+ size = 30,
146
+ width = 10,
147
+ height = 10,
148
+ rotation = 0,
149
+ pixelRatio = "auto",
150
+ cull = true
151
+ }) {
138
152
  this.element = element;
139
153
  this.canvas = canvas;
140
154
  this.size = size;
141
155
  this.width = width;
142
156
  this.height = height;
143
157
  this.rotation = rotation;
158
+ this.pixelRatio = pixelRatio;
159
+ this.cull = cull;
144
160
  this.hexes = /* @__PURE__ */ new Map();
145
161
  this.selectedHex = null;
146
162
  this.listeners = {};
163
+ this._bucketSize = Math.max(size * 2, MIN_BUCKET_SIZE);
164
+ this._index = /* @__PURE__ */ new Map();
165
+ this._gestureCancel = null;
166
+ this._sharpTimer = null;
167
+ this._frameCanvas = null;
168
+ this._frameCtx = null;
169
+ this._frameTransform = null;
170
+ this._stats = {
171
+ total: 0,
172
+ visible: 0,
173
+ drawn: 0,
174
+ mode: "sharp",
175
+ lastRenderMs: 0,
176
+ pixelRatio: 1,
177
+ scale: 1
178
+ };
147
179
  this.transform = { x: 0, y: 0, scale: 1 };
148
180
  this.dragging = false;
149
181
  this.lastPos = null;
@@ -203,6 +235,14 @@ var VdHexGrid = class {
203
235
  */
204
236
  destroy() {
205
237
  this._teardownEvents();
238
+ this._cancelGestureRender();
239
+ if (this._sharpTimer) {
240
+ clearTimeout(this._sharpTimer);
241
+ this._sharpTimer = null;
242
+ }
243
+ this._frameCanvas = null;
244
+ this._frameCtx = null;
245
+ this._frameTransform = null;
206
246
  if (this._themeObserver) {
207
247
  this._themeObserver.disconnect();
208
248
  this._themeObserver = null;
@@ -213,6 +253,195 @@ var VdHexGrid = class {
213
253
  this._themeMediaHandler = null;
214
254
  }
215
255
  }
256
+ /**
257
+ * Resolve the effective device-pixel-ratio multiplier.
258
+ * @returns {number}
259
+ */
260
+ _resolvePixelRatio() {
261
+ const requested = this.pixelRatio;
262
+ if (requested === "auto" || requested == null) {
263
+ const dpr = typeof window !== "undefined" && typeof window.devicePixelRatio === "number" ? window.devicePixelRatio : 1;
264
+ return Math.max(1, Math.min(dpr || 1, MAX_PIXEL_RATIO));
265
+ }
266
+ const n = Number(requested);
267
+ return Number.isFinite(n) && n > 0 ? n : 1;
268
+ }
269
+ /** Request an animation frame, returning a cancel function. */
270
+ _requestFrame(callback) {
271
+ if (typeof requestAnimationFrame === "function") {
272
+ const id2 = requestAnimationFrame(callback);
273
+ return () => cancelAnimationFrame(id2);
274
+ }
275
+ const id = setTimeout(callback, 16);
276
+ return () => clearTimeout(id);
277
+ }
278
+ /** Cancel a pending gesture frame, if any. */
279
+ _cancelGestureRender() {
280
+ if (this._gestureCancel) {
281
+ this._gestureCancel();
282
+ this._gestureCancel = null;
283
+ }
284
+ }
285
+ /**
286
+ * Coalesce gesture-driven transform changes into at most one render/frame.
287
+ */
288
+ _scheduleGestureRender() {
289
+ if (this._gestureCancel) return;
290
+ this._gestureCancel = this._requestFrame(() => {
291
+ this._gestureCancel = null;
292
+ this._renderGesture();
293
+ });
294
+ }
295
+ /**
296
+ * Schedule a sharp culled re-render after the gesture goes idle.
297
+ */
298
+ _scheduleSharpRender() {
299
+ if (this._sharpTimer) clearTimeout(this._sharpTimer);
300
+ this._sharpTimer = setTimeout(() => {
301
+ this._sharpTimer = null;
302
+ this._render();
303
+ }, SHARP_IDLE_MS);
304
+ }
305
+ /** Render the current gesture frame (fast blit or sharp, adaptively). */
306
+ _renderGesture() {
307
+ const visible = this.cull ? this._computeVisibleHexes() : null;
308
+ const count = visible ? visible.length : this.hexes.size;
309
+ if (count > FAST_RENDER_LIMIT && this._frameCanvas) {
310
+ this._blitFrame();
311
+ } else {
312
+ this._render(visible);
313
+ }
314
+ this._scheduleSharpRender();
315
+ }
316
+ /**
317
+ * Blit the last sharp frame snapshot offset/scaled for the current transform.
318
+ * The snapshot is in screen (CSS) space at `_frameTransform`; this maps the
319
+ * pixel under the old transform to its position under the current transform.
320
+ */
321
+ _blitFrame() {
322
+ const frame = this._frameCanvas;
323
+ const from = this._frameTransform;
324
+ if (!frame || !from || !frame.width || !frame.height) {
325
+ this._render();
326
+ return;
327
+ }
328
+ const rect = this.canvas.getBoundingClientRect();
329
+ const displayWidth = rect.width || 800;
330
+ const displayHeight = rect.height || 400;
331
+ const ratio = this._resolvePixelRatio();
332
+ const { x, y, scale } = this.transform;
333
+ const k = scale / (from.scale || 1);
334
+ const bx = x - k * from.x;
335
+ const by = y - k * from.y;
336
+ this.ctx.setTransform(1, 0, 0, 1, 0, 0);
337
+ this.ctx.fillStyle = this.themeColors.bgPrimary;
338
+ this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
339
+ this.ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
340
+ this.ctx.drawImage(
341
+ frame,
342
+ 0,
343
+ 0,
344
+ frame.width,
345
+ frame.height,
346
+ bx,
347
+ by,
348
+ displayWidth * k,
349
+ displayHeight * k
350
+ );
351
+ this.ctx.setTransform(1, 0, 0, 1, 0, 0);
352
+ this._stats.mode = "fast";
353
+ this._stats.pixelRatio = ratio;
354
+ this._stats.scale = scale;
355
+ this._stats.lastRenderMs = 0;
356
+ }
357
+ /**
358
+ * Capture the current canvas into the offscreen frame snapshot.
359
+ */
360
+ _captureFrame() {
361
+ const w = this.canvas.width;
362
+ const h2 = this.canvas.height;
363
+ if (!w || !h2) return;
364
+ if (!this._frameCanvas) {
365
+ this._frameCanvas = typeof document !== "undefined" ? document.createElement("canvas") : null;
366
+ if (!this._frameCanvas) return;
367
+ }
368
+ const frame = this._frameCanvas;
369
+ if (frame.width !== w) frame.width = w;
370
+ if (frame.height !== h2) frame.height = h2;
371
+ const fctx = frame.getContext("2d");
372
+ if (!fctx) return;
373
+ this._frameCtx = fctx;
374
+ fctx.setTransform(1, 0, 0, 1, 0, 0);
375
+ fctx.clearRect(0, 0, w, h2);
376
+ fctx.drawImage(this.canvas, 0, 0);
377
+ this._frameTransform = { ...this.transform };
378
+ }
379
+ /**
380
+ * Compute the cells intersecting the current viewport (always culled by the
381
+ * viewport, independent of the `cull` render switch). Uses the spatial bucket
382
+ * index when present.
383
+ * @returns {HexCell[]}
384
+ */
385
+ _computeVisibleHexes() {
386
+ const rect = this.canvas.getBoundingClientRect();
387
+ const displayWidth = rect.width || 800;
388
+ const displayHeight = rect.height || 400;
389
+ const { x, y, scale } = this.transform;
390
+ const minX = -x / scale - this.size;
391
+ const maxX = (displayWidth - x) / scale + this.size;
392
+ const minY = -y / scale - this.size;
393
+ const maxY = (displayHeight - y) / scale + this.size;
394
+ if (!this._index || this._index.size === 0) return this.getAllHexes();
395
+ const bs = this._bucketSize;
396
+ const bx0 = Math.floor(minX / bs);
397
+ const bx1 = Math.floor(maxX / bs);
398
+ const by0 = Math.floor(minY / bs);
399
+ const by1 = Math.floor(maxY / bs);
400
+ const out = [];
401
+ for (let by = by0; by <= by1; by++) {
402
+ for (let bx = bx0; bx <= bx1; bx++) {
403
+ const bucket = this._index.get(`${bx},${by}`);
404
+ if (!bucket) continue;
405
+ for (let i = 0; i < bucket.length; i++) {
406
+ const hex = bucket[i];
407
+ if (hex.x >= minX && hex.x <= maxX && hex.y >= minY && hex.y <= maxY) {
408
+ out.push(hex);
409
+ }
410
+ }
411
+ }
412
+ }
413
+ return out;
414
+ }
415
+ /**
416
+ * Get the cells intersecting the current viewport, sorted by row then column.
417
+ * @returns {HexCell[]}
418
+ */
419
+ getVisibleHexes() {
420
+ return this._computeVisibleHexes().sort((a, b) => a.r - b.r || a.q - b.q);
421
+ }
422
+ /**
423
+ * Last-frame render metrics.
424
+ * @returns {{total: number, visible: number, drawn: number, mode: string, lastRenderMs: number, pixelRatio: number, scale: number}}
425
+ */
426
+ getRenderStats() {
427
+ return { ...this._stats };
428
+ }
429
+ /**
430
+ * Set the device-pixel-ratio multiplier and re-render (no grid regeneration).
431
+ * @param {number|'auto'} ratio
432
+ */
433
+ setPixelRatio(ratio) {
434
+ this.pixelRatio = ratio;
435
+ this._render();
436
+ }
437
+ /**
438
+ * Toggle viewport culling and re-render (no grid regeneration).
439
+ * @param {boolean} cull
440
+ */
441
+ setCull(cull) {
442
+ this.cull = !!cull;
443
+ this._render();
444
+ }
216
445
  /**
217
446
  * Convert screen coordinates to world coordinates
218
447
  */
@@ -240,6 +469,8 @@ var VdHexGrid = class {
240
469
  */
241
470
  _generateGrid() {
242
471
  this.hexes.clear();
472
+ this._bucketSize = Math.max(this.size * 2, MIN_BUCKET_SIZE);
473
+ this._index = /* @__PURE__ */ new Map();
243
474
  for (let r = 0; r < this.height; r++) {
244
475
  const qOffset = Math.floor(r / 2);
245
476
  for (let q = -qOffset; q < this.width - qOffset; q++) {
@@ -256,6 +487,15 @@ var VdHexGrid = class {
256
487
  data: {}
257
488
  };
258
489
  this.hexes.set(`${q},${r}`, hex);
490
+ const bx = Math.floor(hex.x / this._bucketSize);
491
+ const by = Math.floor(hex.y / this._bucketSize);
492
+ const key = `${bx},${by}`;
493
+ let bucket = this._index.get(key);
494
+ if (!bucket) {
495
+ bucket = [];
496
+ this._index.set(key, bucket);
497
+ }
498
+ bucket.push(hex);
259
499
  }
260
500
  }
261
501
  }
@@ -267,29 +507,55 @@ var VdHexGrid = class {
267
507
  this.selectedHex = this.hexes.get(`${this.selectedHex.q},${this.selectedHex.r}`) ?? null;
268
508
  }
269
509
  /**
270
- * Render the hex grid on canvas
510
+ * Render the hex grid on canvas (synchronous sharp render).
511
+ *
512
+ * @param {HexCell[]|null} [precomputedVisible] - Visible cells to draw when
513
+ * culling is on; computed from the viewport when omitted.
271
514
  */
272
- _render() {
515
+ _render(precomputedVisible) {
516
+ const started = now();
273
517
  const rect = this.canvas.getBoundingClientRect();
274
518
  const displayWidth = rect.width || 800;
275
519
  const displayHeight = rect.height || 400;
276
- this.canvas.width = displayWidth;
277
- this.canvas.height = displayHeight;
520
+ const ratio = this._resolvePixelRatio();
521
+ const bufferWidth = Math.max(1, Math.round(displayWidth * ratio));
522
+ const bufferHeight = Math.max(1, Math.round(displayHeight * ratio));
523
+ if (this.canvas.width !== bufferWidth) this.canvas.width = bufferWidth;
524
+ if (this.canvas.height !== bufferHeight) this.canvas.height = bufferHeight;
525
+ this.ctx.setTransform(1, 0, 0, 1, 0, 0);
278
526
  this.ctx.fillStyle = this.themeColors.bgPrimary;
279
527
  this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
528
+ this.ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
280
529
  this.ctx.save();
281
530
  this.ctx.translate(this.transform.x, this.transform.y);
282
531
  this.ctx.scale(this.transform.scale, this.transform.scale);
283
- this.hexes.forEach((hex) => {
532
+ const visible = this.cull && this._index && this._index.size > 0 ? precomputedVisible || this._computeVisibleHexes() : null;
533
+ let drawn = 0;
534
+ const drawHex = (hex) => {
284
535
  this._drawHex(hex);
536
+ drawn += 1;
285
537
  if (this.customRenderCallback) {
286
538
  this.customRenderCallback(this.ctx, hex, this.size);
287
539
  }
288
- });
540
+ };
541
+ if (visible) {
542
+ for (let i = 0; i < visible.length; i++) drawHex(visible[i]);
543
+ } else {
544
+ this.hexes.forEach(drawHex);
545
+ }
289
546
  if (this.selectedHex) {
290
547
  this._drawHex(this.selectedHex, true);
291
548
  }
292
549
  this.ctx.restore();
550
+ this.ctx.setTransform(1, 0, 0, 1, 0, 0);
551
+ this._stats.total = this.hexes.size;
552
+ this._stats.visible = visible ? visible.length : this.hexes.size;
553
+ this._stats.drawn = drawn;
554
+ this._stats.mode = "sharp";
555
+ this._stats.pixelRatio = ratio;
556
+ this._stats.scale = this.transform.scale;
557
+ this._stats.lastRenderMs = now() - started;
558
+ this._captureFrame();
293
559
  }
294
560
  /**
295
561
  * Draw a single hex
@@ -355,11 +621,20 @@ var VdHexGrid = class {
355
621
  this.transform.x += dx;
356
622
  this.transform.y += dy;
357
623
  this.lastPos = cur;
358
- this._render();
624
+ this._scheduleGestureRender();
359
625
  },
360
626
  // Pan - pointer up / leave (shared stopDrag)
361
627
  pointerup: () => {
628
+ const wasDragging = this.dragging;
362
629
  this.dragging = false;
630
+ if (wasDragging) {
631
+ this._cancelGestureRender();
632
+ if (this._sharpTimer) {
633
+ clearTimeout(this._sharpTimer);
634
+ this._sharpTimer = null;
635
+ }
636
+ this._render();
637
+ }
363
638
  if (!this.hasMoved) {
364
639
  this.canvas.style.cursor = "pointer";
365
640
  }
@@ -386,7 +661,7 @@ var VdHexGrid = class {
386
661
  this.transform.x = mouse.x - (mouse.x - this.transform.x) * scaleDiff;
387
662
  this.transform.y = mouse.y - (mouse.y - this.transform.y) * scaleDiff;
388
663
  this.transform.scale = newScale;
389
- this._render();
664
+ this._scheduleGestureRender();
390
665
  this._emit("zoom", { scale: this.transform.scale });
391
666
  },
392
667
  // Touch events for pinch-to-zoom
@@ -411,12 +686,18 @@ var VdHexGrid = class {
411
686
  this.transform.x = center.x - (center.x - this.transform.x) * scaleDiff;
412
687
  this.transform.y = center.y - (center.y - this.transform.y) * scaleDiff;
413
688
  this.transform.scale = newScale;
414
- this._render();
689
+ this._scheduleGestureRender();
415
690
  this._emit("zoom", { scale: this.transform.scale });
416
691
  }
417
692
  },
418
693
  touchend: () => {
419
694
  this.touchState.touches = [];
695
+ this._cancelGestureRender();
696
+ if (this._sharpTimer) {
697
+ clearTimeout(this._sharpTimer);
698
+ this._sharpTimer = null;
699
+ }
700
+ this._render();
420
701
  },
421
702
  // Cursor style
422
703
  mouseenter: () => {
@@ -867,7 +1148,11 @@ var VdHexGrid2 = defineComponent({
867
1148
  /** Grid rows (number of hexes). */
868
1149
  height: { type: Number, default: 10 },
869
1150
  /** Grid rotation (radians). */
870
- rotation: { type: Number, default: 0 }
1151
+ rotation: { type: Number, default: 0 },
1152
+ /** Canvas backing-store multiplier: a number or `'auto'` (default). */
1153
+ pixelRatio: { type: [Number, String], default: "auto" },
1154
+ /** Viewport culling (default true). */
1155
+ cull: { type: Boolean, default: true }
871
1156
  },
872
1157
  emits: ["select", "zoom", "pan", "ready"],
873
1158
  setup(props, { emit, expose }) {
@@ -879,7 +1164,9 @@ var VdHexGrid2 = defineComponent({
879
1164
  size: props.size,
880
1165
  width: props.width,
881
1166
  height: props.height,
882
- rotation: props.rotation
1167
+ rotation: props.rotation,
1168
+ pixelRatio: props.pixelRatio,
1169
+ cull: props.cull
883
1170
  });
884
1171
  FORWARDED_EVENTS.forEach((name) => {
885
1172
  instance.on(name, (data) => emit(name, data));
@@ -902,6 +1189,14 @@ var VdHexGrid2 = defineComponent({
902
1189
  () => props.rotation,
903
1190
  (v) => instance && instance.setRotation(v)
904
1191
  );
1192
+ watch(
1193
+ () => props.pixelRatio,
1194
+ (v) => instance && instance.setPixelRatio(v)
1195
+ );
1196
+ watch(
1197
+ () => props.cull,
1198
+ (v) => instance && instance.setCull(v)
1199
+ );
905
1200
  onBeforeUnmount(() => {
906
1201
  if (instance) {
907
1202
  instance.destroy();