@vanduo-oss/vd3-cbun 1.0.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.
Files changed (45) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/LICENSE +22 -0
  3. package/README.md +160 -0
  4. package/SKILL.md +119 -0
  5. package/dist/charts/core.d.ts +273 -0
  6. package/dist/charts/index.cjs +1828 -0
  7. package/dist/charts/index.cjs.map +7 -0
  8. package/dist/charts/index.d.ts +65 -0
  9. package/dist/charts/index.js +1805 -0
  10. package/dist/charts/index.js.map +7 -0
  11. package/dist/charts/vd3-charts.css +51 -0
  12. package/dist/charts/vue.d.ts +86 -0
  13. package/dist/flowchart/core.d.ts +288 -0
  14. package/dist/flowchart/index.cjs +3447 -0
  15. package/dist/flowchart/index.cjs.map +7 -0
  16. package/dist/flowchart/index.d.ts +54 -0
  17. package/dist/flowchart/index.js +3424 -0
  18. package/dist/flowchart/index.js.map +7 -0
  19. package/dist/flowchart/vd3-flowchart.css +600 -0
  20. package/dist/flowchart/vue.d.ts +66 -0
  21. package/dist/hex-grid/core.d.ts +200 -0
  22. package/dist/hex-grid/hex-math.cjs +162 -0
  23. package/dist/hex-grid/hex-math.cjs.map +7 -0
  24. package/dist/hex-grid/hex-math.d.ts +119 -0
  25. package/dist/hex-grid/hex-math.js +141 -0
  26. package/dist/hex-grid/hex-math.js.map +7 -0
  27. package/dist/hex-grid/index.cjs +915 -0
  28. package/dist/hex-grid/index.cjs.map +7 -0
  29. package/dist/hex-grid/index.d.ts +15 -0
  30. package/dist/hex-grid/index.js +894 -0
  31. package/dist/hex-grid/index.js.map +7 -0
  32. package/dist/hex-grid/vue.d.ts +14 -0
  33. package/dist/index.d.ts +13 -0
  34. package/dist/index.js +11 -0
  35. package/dist/index.js.map +7 -0
  36. package/dist/meta.json +551 -0
  37. package/dist/music-player/core.d.ts +88 -0
  38. package/dist/music-player/index.cjs +1227 -0
  39. package/dist/music-player/index.cjs.map +7 -0
  40. package/dist/music-player/index.d.ts +12 -0
  41. package/dist/music-player/index.js +1204 -0
  42. package/dist/music-player/index.js.map +7 -0
  43. package/dist/music-player/vd3-music-player.css +829 -0
  44. package/dist/music-player/vue.d.ts +32 -0
  45. package/package.json +105 -0
@@ -0,0 +1,894 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+
5
+ // src/hex-grid/vue.js
6
+ import { defineComponent, h, ref, onMounted, onBeforeUnmount, watch } from "vue";
7
+
8
+ // src/hex-grid/hex-math.js
9
+ function rotatePoint(x, y, rotation = 0) {
10
+ if (!rotation) {
11
+ return { x, y };
12
+ }
13
+ const cosRot = Math.cos(rotation);
14
+ const sinRot = Math.sin(rotation);
15
+ return {
16
+ x: x * cosRot - y * sinRot,
17
+ y: x * sinRot + y * cosRot
18
+ };
19
+ }
20
+ function unrotatePoint(x, y, rotation = 0) {
21
+ return rotatePoint(x, y, -rotation);
22
+ }
23
+ function hexToPixel(q, r, size, rotation = 0) {
24
+ const baseX = size * 1.5 * q;
25
+ const baseY = size * Math.sqrt(3) * (r + q * 0.5);
26
+ return rotatePoint(baseX, baseY, rotation);
27
+ }
28
+ function pixelToHex(px, py, size, rotation = 0) {
29
+ const point = unrotatePoint(px, py, rotation);
30
+ const q = 2 / 3 * point.x / size;
31
+ const r = (-1 / 3 * point.x + Math.sqrt(3) / 3 * point.y) / size;
32
+ return axialRound(q, r);
33
+ }
34
+ function axialRound(q, r) {
35
+ const s = -q - r;
36
+ let rq = Math.round(q);
37
+ let rr = Math.round(r);
38
+ const rs = Math.round(s);
39
+ const qDiff = Math.abs(rq - q);
40
+ const rDiff = Math.abs(rr - r);
41
+ const sDiff = Math.abs(rs - s);
42
+ if (qDiff > rDiff && qDiff > sDiff) {
43
+ rq = -rr - rs;
44
+ } else if (rDiff > sDiff) {
45
+ rr = -rq - rs;
46
+ }
47
+ return { q: rq, r: rr };
48
+ }
49
+ function getHexCorners(x, y, size, rotation = 0) {
50
+ const corners = [];
51
+ for (let i = 0; i < 6; i++) {
52
+ const angleRad = Math.PI / 180 * (60 * i) + rotation;
53
+ corners.push({
54
+ x: x + size * Math.cos(angleRad),
55
+ y: y + size * Math.sin(angleRad)
56
+ });
57
+ }
58
+ return corners;
59
+ }
60
+ function getAdjacentHexes(q, r) {
61
+ return [
62
+ { q: q + 1, r },
63
+ { q: q + 1, r: r - 1 },
64
+ { q, r: r - 1 },
65
+ { q: q - 1, r },
66
+ { q: q - 1, r: r + 1 },
67
+ { q, r: r + 1 }
68
+ ];
69
+ }
70
+ function hexDistance(q1, r1, q2, r2) {
71
+ return (Math.abs(q1 - q2) + Math.abs(q1 + r1 - q2 - r2) + Math.abs(r1 - r2)) / 2;
72
+ }
73
+ var TerrainType = Object.freeze({
74
+ GRASSLAND: "Grassland",
75
+ PLAINS: "Plains",
76
+ DESERT: "Desert",
77
+ TUNDRA: "Tundra",
78
+ SNOW: "Snow",
79
+ MOUNTAIN: "Mountain",
80
+ OCEAN: "Ocean",
81
+ COAST: "Coast"
82
+ });
83
+ var TERRAIN_COLORS = Object.freeze({
84
+ [TerrainType.GRASSLAND]: "#47602f",
85
+ [TerrainType.PLAINS]: "#6e6838",
86
+ [TerrainType.DESERT]: "#bd9a60",
87
+ [TerrainType.TUNDRA]: "#75787b",
88
+ [TerrainType.SNOW]: "#cfdce4",
89
+ [TerrainType.MOUNTAIN]: "#464543",
90
+ [TerrainType.OCEAN]: "#1d354c",
91
+ [TerrainType.COAST]: "#295170"
92
+ });
93
+ var DEFAULT_TERRAIN_COLOR = "#FF00FF";
94
+ var TERRAIN_YIELDS = Object.freeze({
95
+ [TerrainType.GRASSLAND]: { food: 2, production: 0, gold: 0 },
96
+ [TerrainType.PLAINS]: { food: 1, production: 1, gold: 0 },
97
+ [TerrainType.DESERT]: { food: 0, production: 1, gold: 0 },
98
+ [TerrainType.TUNDRA]: { food: 1, production: 0, gold: 0 },
99
+ [TerrainType.SNOW]: { food: 0, production: 0, gold: 0 },
100
+ [TerrainType.COAST]: { food: 1, production: 0, gold: 0 },
101
+ [TerrainType.OCEAN]: { food: 0, production: 0, gold: 0 },
102
+ [TerrainType.MOUNTAIN]: { food: 0, production: 0, gold: 0 }
103
+ });
104
+ var TERRAIN_MOVEMENT_COSTS = Object.freeze({
105
+ [TerrainType.GRASSLAND]: 1,
106
+ [TerrainType.PLAINS]: 1,
107
+ [TerrainType.DESERT]: 1,
108
+ [TerrainType.TUNDRA]: 1,
109
+ [TerrainType.SNOW]: 2,
110
+ [TerrainType.COAST]: 1,
111
+ [TerrainType.OCEAN]: 999,
112
+ // Impassable for land units
113
+ [TerrainType.MOUNTAIN]: 999
114
+ // Impassable
115
+ });
116
+ function isPassable(terrainType) {
117
+ const cost = TERRAIN_MOVEMENT_COSTS[terrainType];
118
+ return cost !== void 0 && cost < 999;
119
+ }
120
+ function getMovementCost(terrainType) {
121
+ return TERRAIN_MOVEMENT_COSTS[terrainType] ?? 999;
122
+ }
123
+ function getTerrainYields(terrainType) {
124
+ return TERRAIN_YIELDS[terrainType] || { food: 0, production: 0, gold: 0 };
125
+ }
126
+ function getTerrainColor(terrainType) {
127
+ return TERRAIN_COLORS[terrainType] || DEFAULT_TERRAIN_COLOR;
128
+ }
129
+
130
+ // src/hex-grid/core.js
131
+ var VD_HEX_VERSION = "1.0.0";
132
+ var ZOOM_MIN = 0.3;
133
+ var ZOOM_MAX = 3;
134
+ var ZOOM_FACTOR = 0.1;
135
+ var DRAG_THRESHOLD = 2;
136
+ var VdHexGrid = class {
137
+ constructor({ element, canvas, size = 30, width = 10, height = 10, rotation = 0 }) {
138
+ this.element = element;
139
+ this.canvas = canvas;
140
+ this.size = size;
141
+ this.width = width;
142
+ this.height = height;
143
+ this.rotation = rotation;
144
+ this.hexes = /* @__PURE__ */ new Map();
145
+ this.selectedHex = null;
146
+ this.listeners = {};
147
+ this.transform = { x: 0, y: 0, scale: 1 };
148
+ this.dragging = false;
149
+ this.lastPos = null;
150
+ this.hasMoved = false;
151
+ this.themeColors = this._getThemeColors();
152
+ this.customRenderCallback = null;
153
+ if (!this.canvas) {
154
+ this.canvas = element.querySelector("canvas") || document.createElement("canvas");
155
+ if (!element.contains(this.canvas)) {
156
+ element.appendChild(this.canvas);
157
+ }
158
+ }
159
+ this.ctx = this.canvas.getContext("2d");
160
+ this._generateGrid();
161
+ this._render();
162
+ this._setupEvents();
163
+ this._observeThemeChanges();
164
+ }
165
+ /**
166
+ * Get theme colors from CSS custom properties
167
+ */
168
+ _getThemeColors() {
169
+ const root = document.documentElement;
170
+ const style = getComputedStyle(root);
171
+ const read = (token, legacy, fallback) => style.getPropertyValue(token).trim() || style.getPropertyValue(legacy).trim() || fallback;
172
+ return {
173
+ bgPrimary: read("--vd-bg-primary", "--bg-primary", "#ffffff"),
174
+ bgSecondary: read("--vd-bg-secondary", "--bg-secondary", "#f5f5f5"),
175
+ borderColor: read("--vd-border-color", "--border-color", "#e0e0e0"),
176
+ colorPrimary: read("--vd-color-primary", "--color-primary", "#3b82f6"),
177
+ textColor: read("--vd-text-primary", "--text-primary", "#1f2937"),
178
+ textMuted: read("--vd-text-muted", "--text-muted", "#6b7280")
179
+ };
180
+ }
181
+ /**
182
+ * Observe theme changes and re-render when theme changes
183
+ */
184
+ _observeThemeChanges() {
185
+ const reTheme = () => {
186
+ this.themeColors = this._getThemeColors();
187
+ this._render();
188
+ };
189
+ this._themeObserver = new MutationObserver(reTheme);
190
+ this._themeObserver.observe(document.documentElement, {
191
+ attributes: true,
192
+ attributeFilter: ["data-theme"]
193
+ });
194
+ if (typeof window !== "undefined" && window.matchMedia) {
195
+ this._themeMedia = window.matchMedia("(prefers-color-scheme: dark)");
196
+ this._themeMediaHandler = reTheme;
197
+ this._themeMedia.addEventListener("change", this._themeMediaHandler);
198
+ }
199
+ }
200
+ /**
201
+ * Disconnect theme listeners. Call before discarding the instance (for example
202
+ * on SPA navigation) to avoid leaking observers and media-query listeners.
203
+ */
204
+ destroy() {
205
+ if (this._themeObserver) {
206
+ this._themeObserver.disconnect();
207
+ this._themeObserver = null;
208
+ }
209
+ if (this._themeMedia && this._themeMediaHandler) {
210
+ this._themeMedia.removeEventListener("change", this._themeMediaHandler);
211
+ this._themeMedia = null;
212
+ this._themeMediaHandler = null;
213
+ }
214
+ }
215
+ /**
216
+ * Convert screen coordinates to world coordinates
217
+ */
218
+ _screenToWorld(screenX, screenY) {
219
+ const rect = this.canvas.getBoundingClientRect();
220
+ const canvasX = screenX - rect.left;
221
+ const canvasY = screenY - rect.top;
222
+ return {
223
+ x: (canvasX - this.transform.x) / this.transform.scale,
224
+ y: (canvasY - this.transform.y) / this.transform.scale
225
+ };
226
+ }
227
+ /**
228
+ * Convert client coordinates to canvas-local coordinates
229
+ */
230
+ _clientToCanvas(clientX, clientY) {
231
+ const rect = this.canvas.getBoundingClientRect();
232
+ return {
233
+ x: clientX - rect.left,
234
+ y: clientY - rect.top
235
+ };
236
+ }
237
+ /**
238
+ * Generate hex grid data
239
+ */
240
+ _generateGrid() {
241
+ this.hexes.clear();
242
+ for (let r = 0; r < this.height; r++) {
243
+ const qOffset = Math.floor(r / 2);
244
+ for (let q = -qOffset; q < this.width - qOffset; q++) {
245
+ const pixel = hexToPixel(q, r, this.size, this.rotation);
246
+ const hex = {
247
+ q,
248
+ r,
249
+ x: pixel.x,
250
+ y: pixel.y,
251
+ fill: this.themeColors.bgSecondary,
252
+ stroke: this.themeColors.borderColor,
253
+ adjacent: getAdjacentHexes(q, r),
254
+ terrain: null,
255
+ data: {}
256
+ };
257
+ this.hexes.set(`${q},${r}`, hex);
258
+ }
259
+ }
260
+ }
261
+ /**
262
+ * Keep selected hex reference in sync after grid regeneration
263
+ */
264
+ _resyncSelectedHex() {
265
+ if (!this.selectedHex) return;
266
+ this.selectedHex = this.hexes.get(`${this.selectedHex.q},${this.selectedHex.r}`) ?? null;
267
+ }
268
+ /**
269
+ * Render the hex grid on canvas
270
+ */
271
+ _render() {
272
+ const rect = this.canvas.getBoundingClientRect();
273
+ const displayWidth = rect.width || 800;
274
+ const displayHeight = rect.height || 400;
275
+ this.canvas.width = displayWidth;
276
+ this.canvas.height = displayHeight;
277
+ this.ctx.fillStyle = this.themeColors.bgPrimary;
278
+ this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
279
+ this.ctx.save();
280
+ this.ctx.translate(this.transform.x, this.transform.y);
281
+ this.ctx.scale(this.transform.scale, this.transform.scale);
282
+ this.hexes.forEach((hex) => {
283
+ this._drawHex(hex);
284
+ if (this.customRenderCallback) {
285
+ this.customRenderCallback(this.ctx, hex, this.size);
286
+ }
287
+ });
288
+ if (this.selectedHex) {
289
+ this._drawHex(this.selectedHex, true);
290
+ }
291
+ this.ctx.restore();
292
+ }
293
+ /**
294
+ * Draw a single hex
295
+ */
296
+ _drawHex(hex, isSelected = false) {
297
+ const corners = getHexCorners(hex.x, hex.y, this.size, this.rotation);
298
+ this.ctx.beginPath();
299
+ this.ctx.moveTo(corners[0].x, corners[0].y);
300
+ for (let i = 1; i < corners.length; i++) {
301
+ this.ctx.lineTo(corners[i].x, corners[i].y);
302
+ }
303
+ this.ctx.closePath();
304
+ let fill;
305
+ if (isSelected) {
306
+ fill = this.themeColors.colorPrimary;
307
+ } else if (hex.terrain) {
308
+ fill = getTerrainColor(hex.terrain);
309
+ } else if (hex.fill) {
310
+ fill = hex.fill;
311
+ } else {
312
+ fill = this.themeColors.bgSecondary;
313
+ }
314
+ this.ctx.fillStyle = fill;
315
+ this.ctx.fill();
316
+ const stroke = isSelected ? this.themeColors.colorPrimary : hex.stroke || this.themeColors.borderColor;
317
+ this.ctx.strokeStyle = stroke;
318
+ this.ctx.lineWidth = isSelected ? 3 : 1;
319
+ this.ctx.stroke();
320
+ if (isSelected) {
321
+ this.ctx.fillStyle = "#ffffff";
322
+ this.ctx.font = "10px monospace";
323
+ this.ctx.textAlign = "center";
324
+ this.ctx.textBaseline = "middle";
325
+ this.ctx.fillText(`${hex.q},${hex.r}`, hex.x, hex.y);
326
+ }
327
+ }
328
+ /**
329
+ * Set up mouse/touch events for hex selection, pan, and zoom
330
+ */
331
+ _setupEvents() {
332
+ this.touchState = {
333
+ initialDistance: 0,
334
+ initialScale: 1,
335
+ touches: []
336
+ };
337
+ this.canvas.addEventListener("pointerdown", (e) => {
338
+ this.dragging = true;
339
+ this.hasMoved = false;
340
+ this.lastPos = { x: e.clientX, y: e.clientY };
341
+ this.canvas.style.cursor = "grabbing";
342
+ });
343
+ this.canvas.addEventListener("pointermove", (e) => {
344
+ if (!this.dragging) return;
345
+ const cur = { x: e.clientX, y: e.clientY };
346
+ const dx = cur.x - this.lastPos.x;
347
+ const dy = cur.y - this.lastPos.y;
348
+ if (Math.abs(dx) > DRAG_THRESHOLD || Math.abs(dy) > DRAG_THRESHOLD) {
349
+ this.hasMoved = true;
350
+ }
351
+ this.transform.x += dx;
352
+ this.transform.y += dy;
353
+ this.lastPos = cur;
354
+ this._render();
355
+ });
356
+ const stopDrag = () => {
357
+ this.dragging = false;
358
+ if (!this.hasMoved) {
359
+ this.canvas.style.cursor = "pointer";
360
+ }
361
+ };
362
+ this.canvas.addEventListener("pointerup", stopDrag);
363
+ this.canvas.addEventListener("pointerleave", stopDrag);
364
+ this.canvas.addEventListener("click", (e) => {
365
+ if (this.hasMoved) return;
366
+ const worldPos = this._screenToWorld(e.clientX, e.clientY);
367
+ const hexCoords = pixelToHex(worldPos.x, worldPos.y, this.size, this.rotation);
368
+ const hex = this.hexes.get(`${hexCoords.q},${hexCoords.r}`);
369
+ if (hex) {
370
+ this.selectedHex = hex;
371
+ this._render();
372
+ this._emit("select", hex);
373
+ }
374
+ });
375
+ this.canvas.addEventListener(
376
+ "wheel",
377
+ (e) => {
378
+ e.preventDefault();
379
+ const zoomFactor = e.deltaY > 0 ? 1 - ZOOM_FACTOR : 1 + ZOOM_FACTOR;
380
+ const newScale = Math.max(ZOOM_MIN, Math.min(this.transform.scale * zoomFactor, ZOOM_MAX));
381
+ const mouse = this._clientToCanvas(e.clientX, e.clientY);
382
+ const scaleDiff = newScale / this.transform.scale;
383
+ this.transform.x = mouse.x - (mouse.x - this.transform.x) * scaleDiff;
384
+ this.transform.y = mouse.y - (mouse.y - this.transform.y) * scaleDiff;
385
+ this.transform.scale = newScale;
386
+ this._render();
387
+ this._emit("zoom", { scale: this.transform.scale });
388
+ },
389
+ { passive: false }
390
+ );
391
+ this.canvas.addEventListener(
392
+ "touchstart",
393
+ (e) => {
394
+ if (e.touches.length === 2) {
395
+ e.preventDefault();
396
+ this.touchState.touches = Array.from(e.touches);
397
+ this.touchState.initialDistance = this._getTouchDistance(e.touches);
398
+ this.touchState.initialScale = this.transform.scale;
399
+ }
400
+ },
401
+ { passive: false }
402
+ );
403
+ this.canvas.addEventListener(
404
+ "touchmove",
405
+ (e) => {
406
+ if (e.touches.length === 2) {
407
+ e.preventDefault();
408
+ const currentDistance = this._getTouchDistance(e.touches);
409
+ const scale = currentDistance / this.touchState.initialDistance * this.touchState.initialScale;
410
+ const newScale = Math.max(ZOOM_MIN, Math.min(scale, ZOOM_MAX));
411
+ const centerClientX = (e.touches[0].clientX + e.touches[1].clientX) / 2;
412
+ const centerClientY = (e.touches[0].clientY + e.touches[1].clientY) / 2;
413
+ const center = this._clientToCanvas(centerClientX, centerClientY);
414
+ const scaleDiff = newScale / this.transform.scale;
415
+ this.transform.x = center.x - (center.x - this.transform.x) * scaleDiff;
416
+ this.transform.y = center.y - (center.y - this.transform.y) * scaleDiff;
417
+ this.transform.scale = newScale;
418
+ this._render();
419
+ this._emit("zoom", { scale: this.transform.scale });
420
+ }
421
+ },
422
+ { passive: false }
423
+ );
424
+ this.canvas.addEventListener("touchend", () => {
425
+ this.touchState.touches = [];
426
+ });
427
+ this.canvas.addEventListener("mouseenter", () => {
428
+ this.canvas.style.cursor = "grab";
429
+ });
430
+ this.canvas.addEventListener("mouseleave", () => {
431
+ this.canvas.style.cursor = "default";
432
+ });
433
+ }
434
+ /**
435
+ * Calculate distance between two touch points
436
+ * @param {TouchList} touches - Touch list
437
+ * @returns {number} Distance in pixels
438
+ */
439
+ _getTouchDistance(touches) {
440
+ if (touches.length < 2) return 0;
441
+ const dx = touches[0].clientX - touches[1].clientX;
442
+ const dy = touches[0].clientY - touches[1].clientY;
443
+ return Math.sqrt(dx * dx + dy * dy);
444
+ }
445
+ /**
446
+ * Set hex size
447
+ */
448
+ setSize(size) {
449
+ this.size = size;
450
+ this._generateGrid();
451
+ this._resyncSelectedHex();
452
+ this._render();
453
+ }
454
+ /**
455
+ * Set grid dimensions
456
+ */
457
+ setDimensions(width, height) {
458
+ this.width = width;
459
+ this.height = height;
460
+ this._generateGrid();
461
+ this._resyncSelectedHex();
462
+ this._render();
463
+ }
464
+ /**
465
+ * Reset grid to defaults
466
+ */
467
+ reset() {
468
+ this.size = 30;
469
+ this.width = 15;
470
+ this.height = 10;
471
+ this.rotation = 0;
472
+ this.selectedHex = null;
473
+ this.transform = { x: 0, y: 0, scale: 1 };
474
+ this._generateGrid();
475
+ this._render();
476
+ }
477
+ /**
478
+ * Fill hexes with random colors
479
+ */
480
+ fillRandom() {
481
+ const colors = [
482
+ "#f0f0f0",
483
+ "#d4e5d4",
484
+ "#e5d4d4",
485
+ "#d4d4e5",
486
+ "#e5e5d4",
487
+ "#d4e5e5",
488
+ "#e8e8e8",
489
+ "#d0d0d0"
490
+ ];
491
+ this.hexes.forEach((hex) => {
492
+ hex.fill = colors[Math.floor(Math.random() * colors.length)];
493
+ });
494
+ this._render();
495
+ }
496
+ /**
497
+ * Get hex by coordinates
498
+ */
499
+ getHex(q, r) {
500
+ return this.hexes.get(`${q},${r}`);
501
+ }
502
+ /**
503
+ * Get all hexes
504
+ */
505
+ getAllHexes() {
506
+ return Array.from(this.hexes.values());
507
+ }
508
+ /**
509
+ * Set hex fill color
510
+ */
511
+ setHexFill(q, r, color) {
512
+ const hex = this.hexes.get(`${q},${r}`);
513
+ if (hex) {
514
+ hex.fill = color;
515
+ this._render();
516
+ }
517
+ }
518
+ /**
519
+ * Reset view to default position
520
+ */
521
+ resetView() {
522
+ this.transform = { x: 0, y: 0, scale: 1 };
523
+ this._render();
524
+ this._emit("pan", { x: 0, y: 0 });
525
+ this._emit("zoom", { scale: 1 });
526
+ }
527
+ /**
528
+ * Zoom in
529
+ */
530
+ zoomIn() {
531
+ const newScale = Math.min(this.transform.scale * (1 + ZOOM_FACTOR), ZOOM_MAX);
532
+ this.transform.scale = newScale;
533
+ this._render();
534
+ this._emit("zoom", { scale: this.transform.scale });
535
+ }
536
+ /**
537
+ * Zoom out
538
+ */
539
+ zoomOut() {
540
+ const newScale = Math.max(this.transform.scale * (1 - ZOOM_FACTOR), ZOOM_MIN);
541
+ this.transform.scale = newScale;
542
+ this._render();
543
+ this._emit("zoom", { scale: this.transform.scale });
544
+ }
545
+ /**
546
+ * Get current transform state
547
+ */
548
+ getTransform() {
549
+ return { ...this.transform };
550
+ }
551
+ /**
552
+ * Subscribe to events
553
+ */
554
+ on(event, callback) {
555
+ if (!this.listeners[event]) {
556
+ this.listeners[event] = [];
557
+ }
558
+ this.listeners[event].push(callback);
559
+ }
560
+ /**
561
+ * Emit events
562
+ */
563
+ _emit(event, data) {
564
+ if (this.listeners[event]) {
565
+ this.listeners[event].forEach((callback) => callback(data));
566
+ }
567
+ }
568
+ // ═══════════════════════════════════════════════════════
569
+ // Terrain System
570
+ // ═══════════════════════════════════════════════════════
571
+ /**
572
+ * Set terrain type for a hex
573
+ * @param {number} q - Hex column
574
+ * @param {number} r - Hex row
575
+ * @param {string} terrainType - Terrain type (e.g., 'GRASSLAND', 'OCEAN')
576
+ */
577
+ setHexTerrain(q, r, terrainType) {
578
+ const hex = this.hexes.get(`${q},${r}`);
579
+ if (hex) {
580
+ hex.terrain = terrainType;
581
+ this._render();
582
+ }
583
+ }
584
+ /**
585
+ * Get terrain type for a hex
586
+ * @param {number} q - Hex column
587
+ * @param {number} r - Hex row
588
+ * @returns {string|null} Terrain type or null
589
+ */
590
+ getHexTerrain(q, r) {
591
+ const hex = this.hexes.get(`${q},${r}`);
592
+ return hex ? hex.terrain : null;
593
+ }
594
+ /**
595
+ * Generate random terrain for all hexes
596
+ */
597
+ generateRandomTerrain() {
598
+ const terrainTypes = Object.values(TerrainType);
599
+ this.hexes.forEach((hex) => {
600
+ hex.terrain = terrainTypes[Math.floor(Math.random() * terrainTypes.length)];
601
+ });
602
+ this._render();
603
+ }
604
+ /**
605
+ * Get terrain yields for a hex
606
+ * @param {number} q - Hex column
607
+ * @param {number} r - Hex row
608
+ * @returns {Object} Yields object {food, production, gold}
609
+ */
610
+ getHexYields(q, r) {
611
+ const terrain = this.getHexTerrain(q, r);
612
+ return terrain ? getTerrainYields(terrain) : { food: 0, production: 0, gold: 0 };
613
+ }
614
+ /**
615
+ * Get movement cost for a hex
616
+ * @param {number} q - Hex column
617
+ * @param {number} r - Hex row
618
+ * @returns {number} Movement cost
619
+ */
620
+ getHexMovementCost(q, r) {
621
+ const terrain = this.getHexTerrain(q, r);
622
+ return terrain ? getMovementCost(terrain) : 999;
623
+ }
624
+ /**
625
+ * Check if hex is passable
626
+ * @param {number} q - Hex column
627
+ * @param {number} r - Hex row
628
+ * @returns {boolean} True if passable
629
+ */
630
+ isHexPassable(q, r) {
631
+ const terrain = this.getHexTerrain(q, r);
632
+ return terrain ? isPassable(terrain) : false;
633
+ }
634
+ // ═══════════════════════════════════════════════════════
635
+ // Hex Data Attachment
636
+ // ═══════════════════════════════════════════════════════
637
+ /**
638
+ * Set custom data for a hex
639
+ * @param {number} q - Hex column
640
+ * @param {number} r - Hex row
641
+ * @param {Object} data - Custom data object
642
+ */
643
+ setHexData(q, r, data) {
644
+ const hex = this.hexes.get(`${q},${r}`);
645
+ if (hex) {
646
+ hex.data = { ...hex.data, ...data };
647
+ }
648
+ }
649
+ /**
650
+ * Get custom data for a hex
651
+ * @param {number} q - Hex column
652
+ * @param {number} r - Hex row
653
+ * @returns {Object} Custom data object
654
+ */
655
+ getHexData(q, r) {
656
+ const hex = this.hexes.get(`${q},${r}`);
657
+ return hex ? hex.data : {};
658
+ }
659
+ /**
660
+ * Clear custom data for a hex
661
+ * @param {number} q - Hex column
662
+ * @param {number} r - Hex row
663
+ */
664
+ clearHexData(q, r) {
665
+ const hex = this.hexes.get(`${q},${r}`);
666
+ if (hex) {
667
+ hex.data = {};
668
+ }
669
+ }
670
+ // ═══════════════════════════════════════════════════════
671
+ // Distance & Pathfinding
672
+ // ═══════════════════════════════════════════════════════
673
+ /**
674
+ * Calculate distance between two hexes
675
+ * @param {number} q1 - First hex q coordinate
676
+ * @param {number} r1 - First hex r coordinate
677
+ * @param {number} q2 - Second hex q coordinate
678
+ * @param {number} r2 - Second hex r coordinate
679
+ * @returns {number} Distance in hex steps
680
+ */
681
+ hexDistance(q1, r1, q2, r2) {
682
+ return hexDistance(q1, r1, q2, r2);
683
+ }
684
+ /**
685
+ * Get valid moves from a hex within movement points
686
+ * @param {number} q - Starting hex column
687
+ * @param {number} r - Starting hex row
688
+ * @param {number} movementPoints - Available movement points
689
+ * @returns {Array<{q: number, r: number}>} Array of valid hex coordinates
690
+ */
691
+ getValidMoves(q, r, movementPoints) {
692
+ const validHexes = [];
693
+ const adjacent = getAdjacentHexes(q, r);
694
+ for (const hex of adjacent) {
695
+ if (!this.hexes.has(`${hex.q},${hex.r}`)) continue;
696
+ const cost = this.getHexMovementCost(hex.q, hex.r);
697
+ if (cost < 999 && movementPoints >= cost) {
698
+ validHexes.push(hex);
699
+ }
700
+ }
701
+ return validHexes;
702
+ }
703
+ /**
704
+ * Get path between two hexes (simple BFS)
705
+ * @param {number} startQ - Starting hex column
706
+ * @param {number} startR - Starting hex row
707
+ * @param {number} endQ - Ending hex column
708
+ * @param {number} endR - Ending hex row
709
+ * @returns {Array<{q: number, r: number}>} Array of hex coordinates forming path
710
+ */
711
+ getPath(startQ, startR, endQ, endR) {
712
+ const startKey = `${startQ},${startR}`;
713
+ const endKey = `${endQ},${endR}`;
714
+ if (!this.hexes.has(startKey) || !this.hexes.has(endKey)) {
715
+ return [];
716
+ }
717
+ const queue = [[startQ, startR]];
718
+ const visited = /* @__PURE__ */ new Set([startKey]);
719
+ const parent = /* @__PURE__ */ new Map();
720
+ while (queue.length > 0) {
721
+ const [currentQ, currentR] = queue.shift();
722
+ const currentKey = `${currentQ},${currentR}`;
723
+ if (currentKey === endKey) {
724
+ const path = [];
725
+ let key = endKey;
726
+ while (key) {
727
+ const [q, r] = key.split(",").map(Number);
728
+ path.unshift({ q, r });
729
+ key = parent.get(key);
730
+ }
731
+ return path;
732
+ }
733
+ const adjacent = getAdjacentHexes(currentQ, currentR);
734
+ for (const neighbor of adjacent) {
735
+ const neighborKey = `${neighbor.q},${neighbor.r}`;
736
+ if (this.hexes.has(neighborKey) && !visited.has(neighborKey)) {
737
+ if (this.isHexPassable(neighbor.q, neighbor.r)) {
738
+ visited.add(neighborKey);
739
+ parent.set(neighborKey, currentKey);
740
+ queue.push([neighbor.q, neighbor.r]);
741
+ }
742
+ }
743
+ }
744
+ }
745
+ return [];
746
+ }
747
+ // ═══════════════════════════════════════════════════════
748
+ // Grid Rotation
749
+ // ═══════════════════════════════════════════════════════
750
+ /**
751
+ * Set grid rotation
752
+ * @param {number} rotation - Rotation in radians
753
+ */
754
+ setRotation(rotation) {
755
+ this.rotation = rotation;
756
+ this._generateGrid();
757
+ this._resyncSelectedHex();
758
+ this._render();
759
+ }
760
+ /**
761
+ * Get current grid rotation
762
+ * @returns {number} Rotation in radians
763
+ */
764
+ getRotation() {
765
+ return this.rotation;
766
+ }
767
+ // ═══════════════════════════════════════════════════════
768
+ // Custom Rendering
769
+ // ═══════════════════════════════════════════════════════
770
+ /**
771
+ * Set custom render callback for each hex
772
+ * @param {function} callback - Called with (ctx, hex, size) for each hex
773
+ */
774
+ setCustomRender(callback) {
775
+ this.customRenderCallback = callback;
776
+ this._render();
777
+ }
778
+ /**
779
+ * Clear custom render callback
780
+ */
781
+ clearCustomRender() {
782
+ this.customRenderCallback = null;
783
+ this._render();
784
+ }
785
+ // ═══════════════════════════════════════════════════════
786
+ // Utility Methods
787
+ // ═══════════════════════════════════════════════════════
788
+ /**
789
+ * Check if hex exists at coordinates
790
+ * @param {number} q - Hex column
791
+ * @param {number} r - Hex row
792
+ * @returns {boolean}
793
+ */
794
+ hasHex(q, r) {
795
+ return this.hexes.has(`${q},${r}`);
796
+ }
797
+ /**
798
+ * Get hex count
799
+ * @returns {number} Number of hexes in grid
800
+ */
801
+ getHexCount() {
802
+ return this.hexes.size;
803
+ }
804
+ /**
805
+ * Export terrain data as JSON
806
+ * @returns {Object} Terrain data object
807
+ */
808
+ exportTerrainData() {
809
+ const data = {};
810
+ this.hexes.forEach((hex, key) => {
811
+ if (hex.terrain) {
812
+ data[key] = hex.terrain;
813
+ }
814
+ });
815
+ return data;
816
+ }
817
+ /**
818
+ * Import terrain data from JSON
819
+ * @param {Object} data - Terrain data object
820
+ */
821
+ importTerrainData(data) {
822
+ Object.entries(data).forEach(([key, terrain]) => {
823
+ const [q, r] = key.split(",").map(Number);
824
+ this.setHexTerrain(q, r, terrain);
825
+ });
826
+ }
827
+ };
828
+ __publicField(VdHexGrid, "VERSION", VD_HEX_VERSION);
829
+
830
+ // src/hex-grid/vue.js
831
+ var FORWARDED_EVENTS = ["select", "zoom", "pan"];
832
+ var VdHexGrid2 = defineComponent({
833
+ name: "VdHexGrid",
834
+ props: {
835
+ /** Hexagon size (px). */
836
+ size: { type: Number, default: 30 },
837
+ /** Grid columns (number of hexes). */
838
+ width: { type: Number, default: 10 },
839
+ /** Grid rows (number of hexes). */
840
+ height: { type: Number, default: 10 },
841
+ /** Grid rotation (radians). */
842
+ rotation: { type: Number, default: 0 }
843
+ },
844
+ emits: ["select", "zoom", "pan", "ready"],
845
+ setup(props, { emit, expose }) {
846
+ const el = ref(null);
847
+ let instance = null;
848
+ const create = () => {
849
+ instance = new VdHexGrid({
850
+ element: el.value,
851
+ size: props.size,
852
+ width: props.width,
853
+ height: props.height,
854
+ rotation: props.rotation
855
+ });
856
+ FORWARDED_EVENTS.forEach((name) => {
857
+ instance.on(name, (data) => emit(name, data));
858
+ });
859
+ emit("ready", instance);
860
+ };
861
+ onMounted(() => {
862
+ if (typeof window === "undefined" || !el.value) return;
863
+ create();
864
+ });
865
+ watch(
866
+ () => props.size,
867
+ (v) => instance && instance.setSize(v)
868
+ );
869
+ watch(
870
+ () => [props.width, props.height],
871
+ ([w, hgt]) => instance && instance.setDimensions(w, hgt)
872
+ );
873
+ watch(
874
+ () => props.rotation,
875
+ (v) => instance && instance.setRotation(v)
876
+ );
877
+ onBeforeUnmount(() => {
878
+ if (instance) {
879
+ instance.destroy();
880
+ instance = null;
881
+ }
882
+ });
883
+ expose({ getInstance: () => instance });
884
+ return () => h("div", { ref: el, class: "vd-hex-grid", style: { width: "100%", height: "100%" } }, [
885
+ h("canvas", { style: { width: "100%", height: "100%", display: "block", cursor: "grab" } })
886
+ ]);
887
+ }
888
+ });
889
+ export {
890
+ VD_HEX_VERSION,
891
+ VdHexGrid2 as VdHexGrid,
892
+ VdHexGrid as VdHexGridCore
893
+ };
894
+ //# sourceMappingURL=index.js.map