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