@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.
- package/CHANGELOG.md +50 -0
- package/LICENSE +22 -0
- package/README.md +160 -0
- package/SKILL.md +119 -0
- package/dist/charts/core.d.ts +273 -0
- package/dist/charts/index.cjs +1828 -0
- package/dist/charts/index.cjs.map +7 -0
- package/dist/charts/index.d.ts +65 -0
- package/dist/charts/index.js +1805 -0
- package/dist/charts/index.js.map +7 -0
- package/dist/charts/vd3-charts.css +51 -0
- package/dist/charts/vue.d.ts +86 -0
- package/dist/flowchart/core.d.ts +288 -0
- package/dist/flowchart/index.cjs +3447 -0
- package/dist/flowchart/index.cjs.map +7 -0
- package/dist/flowchart/index.d.ts +54 -0
- package/dist/flowchart/index.js +3424 -0
- package/dist/flowchart/index.js.map +7 -0
- package/dist/flowchart/vd3-flowchart.css +600 -0
- package/dist/flowchart/vue.d.ts +66 -0
- package/dist/hex-grid/core.d.ts +200 -0
- package/dist/hex-grid/hex-math.cjs +162 -0
- package/dist/hex-grid/hex-math.cjs.map +7 -0
- package/dist/hex-grid/hex-math.d.ts +119 -0
- package/dist/hex-grid/hex-math.js +141 -0
- package/dist/hex-grid/hex-math.js.map +7 -0
- package/dist/hex-grid/index.cjs +915 -0
- package/dist/hex-grid/index.cjs.map +7 -0
- package/dist/hex-grid/index.d.ts +15 -0
- package/dist/hex-grid/index.js +894 -0
- package/dist/hex-grid/index.js.map +7 -0
- package/dist/hex-grid/vue.d.ts +14 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +7 -0
- package/dist/meta.json +551 -0
- package/dist/music-player/core.d.ts +88 -0
- package/dist/music-player/index.cjs +1227 -0
- package/dist/music-player/index.cjs.map +7 -0
- package/dist/music-player/index.d.ts +12 -0
- package/dist/music-player/index.js +1204 -0
- package/dist/music-player/index.js.map +7 -0
- package/dist/music-player/vd3-music-player.css +829 -0
- package/dist/music-player/vue.d.ts +32 -0
- 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
|