@almadar/ui 2.16.0 → 2.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
- import * as React113 from 'react';
2
- import React113__default, { createContext, useCallback, useState, useRef, useLayoutEffect, useEffect, forwardRef, useImperativeHandle, lazy, useMemo, useContext, Component } from 'react';
1
+ import * as React110 from 'react';
2
+ import React110__default, { createContext, useCallback, useState, useRef, useLayoutEffect, useEffect, lazy, useContext, useMemo } from 'react';
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
4
  import '@tanstack/react-query';
5
5
  import 'react-dom';
@@ -20,12 +20,6 @@ import remarkMath from 'remark-math';
20
20
  import rehypeKatex from 'rehype-katex';
21
21
  import SyntaxHighlighter from 'react-syntax-highlighter/dist/esm/prism';
22
22
  import dark from 'react-syntax-highlighter/dist/esm/styles/prism/vsc-dark-plus';
23
- import { useThree, useFrame, Canvas } from '@react-three/fiber';
24
- import { OrbitControls, Grid } from '@react-three/drei';
25
- import * as THREE from 'three';
26
- import { GLTFLoader as GLTFLoader$1 } from 'three/examples/jsm/loaders/GLTFLoader';
27
- import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
28
- import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
29
23
  import { isCircuitEvent, schemaToIR, getPage, clearSchemaCache as clearSchemaCache$1 } from '@almadar/core';
30
24
  import { StateMachineManager, createContextFromBindings, interpolateValue, EffectExecutor } from '@almadar/runtime';
31
25
 
@@ -103,15 +97,6 @@ function useEventBus() {
103
97
  const context = useContext(EventBusContext);
104
98
  return context ?? getGlobalEventBus() ?? fallbackEventBus;
105
99
  }
106
- function useEmitEvent() {
107
- const eventBus = useEventBus();
108
- return useCallback(
109
- (type, payload) => {
110
- eventBus.emit(type, payload);
111
- },
112
- [eventBus]
113
- );
114
- }
115
100
  createContext(null);
116
101
  createContext(null);
117
102
 
@@ -384,7 +369,7 @@ var positionStyles = {
384
369
  fixed: "fixed",
385
370
  sticky: "sticky"
386
371
  };
387
- var Box = React113__default.forwardRef(
372
+ var Box = React110__default.forwardRef(
388
373
  ({
389
374
  padding,
390
375
  paddingX,
@@ -403,7 +388,7 @@ var Box = React113__default.forwardRef(
403
388
  position,
404
389
  className,
405
390
  children,
406
- as: Component2 = "div",
391
+ as: Component = "div",
407
392
  action,
408
393
  actionPayload,
409
394
  hoverEvent,
@@ -433,7 +418,7 @@ var Box = React113__default.forwardRef(
433
418
  onMouseLeave?.(e);
434
419
  }, [hoverEvent, eventBus, onMouseLeave]);
435
420
  const isClickable = action || onClick;
436
- const Comp = Component2;
421
+ const Comp = Component;
437
422
  return /* @__PURE__ */ jsx(
438
423
  Comp,
439
424
  {
@@ -563,8 +548,8 @@ var Typography = ({
563
548
  children
564
549
  }) => {
565
550
  const variant = variantProp ?? (level ? `h${level}` : "body1");
566
- const Component2 = as || defaultElements[variant];
567
- const Comp = Component2;
551
+ const Component = as || defaultElements[variant];
552
+ const Comp = Component;
568
553
  return /* @__PURE__ */ jsx(
569
554
  Comp,
570
555
  {
@@ -654,7 +639,7 @@ function resolveIconProp(value, sizeClass) {
654
639
  const IconComp = value;
655
640
  return /* @__PURE__ */ jsx(IconComp, { className: sizeClass });
656
641
  }
657
- if (React113__default.isValidElement(value)) {
642
+ if (React110__default.isValidElement(value)) {
658
643
  return value;
659
644
  }
660
645
  if (typeof value === "object" && value !== null && "render" in value) {
@@ -663,7 +648,7 @@ function resolveIconProp(value, sizeClass) {
663
648
  }
664
649
  return value;
665
650
  }
666
- var Button = React113__default.forwardRef(
651
+ var Button = React110__default.forwardRef(
667
652
  ({
668
653
  className,
669
654
  variant = "primary",
@@ -758,7 +743,7 @@ var sizeStyles3 = {
758
743
  md: "px-2.5 py-1 text-sm",
759
744
  lg: "px-3 py-1.5 text-base"
760
745
  };
761
- var Badge = React113__default.forwardRef(
746
+ var Badge = React110__default.forwardRef(
762
747
  ({ className, variant = "default", size = "sm", amount, label, icon, children, ...props }, ref) => {
763
748
  const iconSizes2 = { sm: "w-3 h-3", md: "w-3.5 h-3.5", lg: "w-4 h-4" };
764
749
  const resolvedIcon = typeof icon === "string" ? (() => {
@@ -785,7 +770,7 @@ var Badge = React113__default.forwardRef(
785
770
  }
786
771
  );
787
772
  Badge.displayName = "Badge";
788
- var Input = React113__default.forwardRef(
773
+ var Input = React110__default.forwardRef(
789
774
  ({
790
775
  className,
791
776
  inputType,
@@ -897,7 +882,7 @@ var Input = React113__default.forwardRef(
897
882
  }
898
883
  );
899
884
  Input.displayName = "Input";
900
- var Label = React113__default.forwardRef(
885
+ var Label = React110__default.forwardRef(
901
886
  ({ className, required, children, ...props }, ref) => {
902
887
  return /* @__PURE__ */ jsxs(
903
888
  "label",
@@ -917,7 +902,7 @@ var Label = React113__default.forwardRef(
917
902
  }
918
903
  );
919
904
  Label.displayName = "Label";
920
- var Textarea = React113__default.forwardRef(
905
+ var Textarea = React110__default.forwardRef(
921
906
  ({ className, error, ...props }, ref) => {
922
907
  return /* @__PURE__ */ jsx(
923
908
  "textarea",
@@ -940,7 +925,7 @@ var Textarea = React113__default.forwardRef(
940
925
  }
941
926
  );
942
927
  Textarea.displayName = "Textarea";
943
- var Select = React113__default.forwardRef(
928
+ var Select = React110__default.forwardRef(
944
929
  ({ className, options, placeholder, error, ...props }, ref) => {
945
930
  return /* @__PURE__ */ jsxs("div", { className: "relative", children: [
946
931
  /* @__PURE__ */ jsxs(
@@ -976,7 +961,7 @@ var Select = React113__default.forwardRef(
976
961
  }
977
962
  );
978
963
  Select.displayName = "Select";
979
- var Checkbox = React113__default.forwardRef(
964
+ var Checkbox = React110__default.forwardRef(
980
965
  ({ className, label, id, ...props }, ref) => {
981
966
  const inputId = id || `checkbox-${Math.random().toString(36).substr(2, 9)}`;
982
967
  return /* @__PURE__ */ jsxs("div", { className: "flex items-center", children: [
@@ -1050,7 +1035,7 @@ var shadowStyles2 = {
1050
1035
  md: "shadow-[var(--shadow-main)]",
1051
1036
  lg: "shadow-[var(--shadow-lg)]"
1052
1037
  };
1053
- var Card = React113__default.forwardRef(
1038
+ var Card = React110__default.forwardRef(
1054
1039
  ({
1055
1040
  className,
1056
1041
  variant = "bordered",
@@ -1086,9 +1071,9 @@ var Card = React113__default.forwardRef(
1086
1071
  }
1087
1072
  );
1088
1073
  Card.displayName = "Card";
1089
- var CardHeader = React113__default.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, className: cn("mb-4", className), ...props }));
1074
+ var CardHeader = React110__default.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, className: cn("mb-4", className), ...props }));
1090
1075
  CardHeader.displayName = "CardHeader";
1091
- var CardTitle = React113__default.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
1076
+ var CardTitle = React110__default.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
1092
1077
  "h3",
1093
1078
  {
1094
1079
  ref,
@@ -1101,11 +1086,11 @@ var CardTitle = React113__default.forwardRef(({ className, ...props }, ref) => /
1101
1086
  }
1102
1087
  ));
1103
1088
  CardTitle.displayName = "CardTitle";
1104
- var CardContent = React113__default.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, className: cn("", className), ...props }));
1089
+ var CardContent = React110__default.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, className: cn("", className), ...props }));
1105
1090
  CardContent.displayName = "CardContent";
1106
1091
  var CardBody = CardContent;
1107
1092
  CardBody.displayName = "CardBody";
1108
- var CardFooter = React113__default.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
1093
+ var CardFooter = React110__default.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
1109
1094
  "div",
1110
1095
  {
1111
1096
  ref,
@@ -1120,7 +1105,7 @@ var sizeStyles4 = {
1120
1105
  md: "h-6 w-6",
1121
1106
  lg: "h-8 w-8"
1122
1107
  };
1123
- var Spinner = React113__default.forwardRef(
1108
+ var Spinner = React110__default.forwardRef(
1124
1109
  ({ className, size = "md", ...props }, ref) => {
1125
1110
  return /* @__PURE__ */ jsx(
1126
1111
  "div",
@@ -1134,7 +1119,7 @@ var Spinner = React113__default.forwardRef(
1134
1119
  }
1135
1120
  );
1136
1121
  Spinner.displayName = "Spinner";
1137
- var Radio = React113__default.forwardRef(
1122
+ var Radio = React110__default.forwardRef(
1138
1123
  ({
1139
1124
  label,
1140
1125
  helperText,
@@ -1238,7 +1223,7 @@ var Radio = React113__default.forwardRef(
1238
1223
  }
1239
1224
  );
1240
1225
  Radio.displayName = "Radio";
1241
- var Switch = React113.forwardRef(
1226
+ var Switch = React110.forwardRef(
1242
1227
  ({
1243
1228
  checked,
1244
1229
  defaultChecked = false,
@@ -1249,10 +1234,10 @@ var Switch = React113.forwardRef(
1249
1234
  name,
1250
1235
  className
1251
1236
  }, ref) => {
1252
- const [isChecked, setIsChecked] = React113.useState(
1237
+ const [isChecked, setIsChecked] = React110.useState(
1253
1238
  checked !== void 0 ? checked : defaultChecked
1254
1239
  );
1255
- React113.useEffect(() => {
1240
+ React110.useEffect(() => {
1256
1241
  if (checked !== void 0) {
1257
1242
  setIsChecked(checked);
1258
1243
  }
@@ -1346,7 +1331,7 @@ var Stack = ({
1346
1331
  className,
1347
1332
  style,
1348
1333
  children,
1349
- as: Component2 = "div",
1334
+ as: Component = "div",
1350
1335
  onClick,
1351
1336
  onKeyDown,
1352
1337
  role,
@@ -1364,7 +1349,7 @@ var Stack = ({
1364
1349
  };
1365
1350
  const isHorizontal = direction === "horizontal";
1366
1351
  const directionClass = responsive && isHorizontal ? reverse ? "flex-col-reverse md:flex-row-reverse" : "flex-col md:flex-row" : isHorizontal ? reverse ? "flex-row-reverse" : "flex-row" : reverse ? "flex-col-reverse" : "flex-col";
1367
- const Comp = Component2;
1352
+ const Comp = Component;
1368
1353
  return /* @__PURE__ */ jsx(
1369
1354
  Comp,
1370
1355
  {
@@ -1410,7 +1395,7 @@ var sizeStyles5 = {
1410
1395
  md: "w-2.5 h-2.5",
1411
1396
  lg: "w-3 h-3"
1412
1397
  };
1413
- var StatusDot = React113__default.forwardRef(
1398
+ var StatusDot = React110__default.forwardRef(
1414
1399
  ({ className, status = "offline", pulse = false, size = "md", label, ...props }, ref) => {
1415
1400
  return /* @__PURE__ */ jsx(
1416
1401
  "span",
@@ -1457,7 +1442,7 @@ var iconMap2 = {
1457
1442
  down: TrendingDown,
1458
1443
  flat: ArrowRight
1459
1444
  };
1460
- var TrendIndicator = React113__default.forwardRef(
1445
+ var TrendIndicator = React110__default.forwardRef(
1461
1446
  ({
1462
1447
  className,
1463
1448
  value,
@@ -1516,7 +1501,7 @@ var thumbSizes = {
1516
1501
  md: "w-4 h-4",
1517
1502
  lg: "w-5 h-5"
1518
1503
  };
1519
- var RangeSlider = React113__default.forwardRef(
1504
+ var RangeSlider = React110__default.forwardRef(
1520
1505
  ({
1521
1506
  className,
1522
1507
  min = 0,
@@ -1719,7 +1704,7 @@ var paddingClasses = {
1719
1704
  md: "py-16",
1720
1705
  lg: "py-24"
1721
1706
  };
1722
- var ContentSection = React113__default.forwardRef(
1707
+ var ContentSection = React110__default.forwardRef(
1723
1708
  ({ children, background = "default", padding = "lg", id, className }, ref) => {
1724
1709
  return /* @__PURE__ */ jsx(
1725
1710
  Box,
@@ -1772,7 +1757,7 @@ var ErrorState = ({
1772
1757
  );
1773
1758
  };
1774
1759
  ErrorState.displayName = "ErrorState";
1775
- var ErrorBoundary = class extends React113__default.Component {
1760
+ var ErrorBoundary = class extends React110__default.Component {
1776
1761
  constructor(props) {
1777
1762
  super(props);
1778
1763
  __publicField(this, "reset", () => {
@@ -1947,7 +1932,7 @@ function waitForTransition(event, timeoutMs = 1e4) {
1947
1932
  });
1948
1933
  }
1949
1934
  exposeOnWindow();
1950
- var MarkdownContent = React113__default.memo(
1935
+ var MarkdownContent = React110__default.memo(
1951
1936
  ({ content, direction, className }) => {
1952
1937
  const { t: _t } = useTranslate();
1953
1938
  return /* @__PURE__ */ jsx(
@@ -2048,7 +2033,7 @@ var MarkdownContent = React113__default.memo(
2048
2033
  (prev, next) => prev.content === next.content && prev.className === next.className && prev.direction === next.direction
2049
2034
  );
2050
2035
  MarkdownContent.displayName = "MarkdownContent";
2051
- var CodeBlock = React113__default.memo(
2036
+ var CodeBlock = React110__default.memo(
2052
2037
  ({
2053
2038
  code,
2054
2039
  language = "text",
@@ -2155,1394 +2140,12 @@ var CodeBlock = React113__default.memo(
2155
2140
  (prev, next) => prev.language === next.language && prev.code === next.code && prev.showCopyButton === next.showCopyButton && prev.maxHeight === next.maxHeight
2156
2141
  );
2157
2142
  CodeBlock.displayName = "CodeBlock";
2158
- var Camera3D = forwardRef(
2159
- ({
2160
- mode = "isometric",
2161
- position = [10, 10, 10],
2162
- target = [0, 0, 0],
2163
- zoom = 1,
2164
- fov = 45,
2165
- enableOrbit = true,
2166
- minDistance = 2,
2167
- maxDistance = 100,
2168
- onChange
2169
- }, ref) => {
2170
- const { camera, set, viewport } = useThree();
2171
- const controlsRef = useRef(null);
2172
- const initialPosition = useRef(new THREE.Vector3(...position));
2173
- const initialTarget = useRef(new THREE.Vector3(...target));
2174
- useEffect(() => {
2175
- let newCamera;
2176
- if (mode === "isometric") {
2177
- const aspect = viewport.aspect;
2178
- const size = 10 / zoom;
2179
- newCamera = new THREE.OrthographicCamera(
2180
- -size * aspect,
2181
- size * aspect,
2182
- size,
2183
- -size,
2184
- 0.1,
2185
- 1e3
2186
- );
2187
- } else {
2188
- newCamera = new THREE.PerspectiveCamera(fov, viewport.aspect, 0.1, 1e3);
2189
- }
2190
- newCamera.position.copy(initialPosition.current);
2191
- newCamera.lookAt(initialTarget.current);
2192
- set({ camera: newCamera });
2193
- if (mode === "top-down") {
2194
- newCamera.position.set(0, 20 / zoom, 0);
2195
- newCamera.lookAt(0, 0, 0);
2196
- }
2197
- return () => {
2198
- };
2199
- }, [mode, fov, zoom, viewport.aspect, set]);
2200
- useFrame(() => {
2201
- if (onChange) {
2202
- onChange(camera);
2203
- }
2204
- });
2205
- useImperativeHandle(ref, () => ({
2206
- getCamera: () => camera,
2207
- setPosition: (x, y, z) => {
2208
- camera.position.set(x, y, z);
2209
- if (controlsRef.current) {
2210
- controlsRef.current.update();
2211
- }
2212
- },
2213
- lookAt: (x, y, z) => {
2214
- camera.lookAt(x, y, z);
2215
- if (controlsRef.current) {
2216
- controlsRef.current.target.set(x, y, z);
2217
- controlsRef.current.update();
2218
- }
2219
- },
2220
- reset: () => {
2221
- camera.position.copy(initialPosition.current);
2222
- camera.lookAt(initialTarget.current);
2223
- if (controlsRef.current) {
2224
- controlsRef.current.target.copy(initialTarget.current);
2225
- controlsRef.current.update();
2226
- }
2227
- },
2228
- getViewBounds: () => {
2229
- const min = new THREE.Vector3(-10, -10, -10);
2230
- const max = new THREE.Vector3(10, 10, 10);
2231
- return { min, max };
2232
- }
2233
- }));
2234
- const maxPolarAngle = mode === "top-down" ? 0.1 : Math.PI / 2 - 0.1;
2235
- return /* @__PURE__ */ jsx(
2236
- OrbitControls,
2237
- {
2238
- ref: controlsRef,
2239
- camera,
2240
- enabled: enableOrbit,
2241
- target: initialTarget.current,
2242
- minDistance,
2243
- maxDistance,
2244
- maxPolarAngle,
2245
- enableDamping: true,
2246
- dampingFactor: 0.05
2247
- }
2248
- );
2249
- }
2250
- );
2251
- Camera3D.displayName = "Camera3D";
2252
- var Canvas3DErrorBoundary = class extends Component {
2253
- constructor(props) {
2254
- super(props);
2255
- __publicField(this, "handleReset", () => {
2256
- this.setState({
2257
- hasError: false,
2258
- error: null,
2259
- errorInfo: null
2260
- });
2261
- this.props.onReset?.();
2262
- });
2263
- this.state = {
2264
- hasError: false,
2265
- error: null,
2266
- errorInfo: null
2267
- };
2268
- }
2269
- static getDerivedStateFromError(error) {
2270
- return {
2271
- hasError: true,
2272
- error,
2273
- errorInfo: null
2274
- };
2275
- }
2276
- componentDidCatch(error, errorInfo) {
2277
- this.setState({ errorInfo });
2278
- this.props.onError?.(error, errorInfo);
2279
- console.error("[Canvas3DErrorBoundary] Error caught:", error);
2280
- console.error("[Canvas3DErrorBoundary] Component stack:", errorInfo.componentStack);
2281
- }
2282
- render() {
2283
- if (this.state.hasError) {
2284
- if (this.props.fallback) {
2285
- return this.props.fallback;
2286
- }
2287
- return /* @__PURE__ */ jsx("div", { className: "canvas-3d-error", children: /* @__PURE__ */ jsxs("div", { className: "canvas-3d-error__content", children: [
2288
- /* @__PURE__ */ jsx("div", { className: "canvas-3d-error__icon", children: "\u26A0\uFE0F" }),
2289
- /* @__PURE__ */ jsx("h2", { className: "canvas-3d-error__title", children: "3D Scene Error" }),
2290
- /* @__PURE__ */ jsx("p", { className: "canvas-3d-error__message", children: "Something went wrong while rendering the 3D scene." }),
2291
- this.state.error && /* @__PURE__ */ jsxs("details", { className: "canvas-3d-error__details", children: [
2292
- /* @__PURE__ */ jsx("summary", { children: "Error Details" }),
2293
- /* @__PURE__ */ jsxs("pre", { className: "error__stack", children: [
2294
- this.state.error.message,
2295
- "\n",
2296
- this.state.error.stack
2297
- ] }),
2298
- this.state.errorInfo && /* @__PURE__ */ jsx("pre", { className: "error__component-stack", children: this.state.errorInfo.componentStack })
2299
- ] }),
2300
- /* @__PURE__ */ jsxs("div", { className: "canvas-3d-error__actions", children: [
2301
- /* @__PURE__ */ jsx(
2302
- "button",
2303
- {
2304
- className: "error__button error__button--primary",
2305
- onClick: this.handleReset,
2306
- children: "Try Again"
2307
- }
2308
- ),
2309
- /* @__PURE__ */ jsx(
2310
- "button",
2311
- {
2312
- className: "error__button error__button--secondary",
2313
- onClick: () => window.location.reload(),
2314
- children: "Reload Page"
2315
- }
2316
- )
2317
- ] })
2318
- ] }) });
2319
- }
2320
- return this.props.children;
2321
- }
2322
- };
2323
- function Canvas3DLoadingState({
2324
- progress = 0,
2325
- loaded = 0,
2326
- total = 0,
2327
- message = "Loading 3D Scene...",
2328
- details,
2329
- showSpinner = true,
2330
- className
2331
- }) {
2332
- const clampedProgress = Math.max(0, Math.min(100, progress));
2333
- const hasProgress = total > 0;
2334
- return /* @__PURE__ */ jsxs("div", { className: `canvas-3d-loading ${className || ""}`, children: [
2335
- /* @__PURE__ */ jsxs("div", { className: "canvas-3d-loading__content", children: [
2336
- showSpinner && /* @__PURE__ */ jsxs("div", { className: "canvas-3d-loading__spinner", children: [
2337
- /* @__PURE__ */ jsx("div", { className: "spinner__ring" }),
2338
- /* @__PURE__ */ jsx("div", { className: "spinner__ring spinner__ring--secondary" })
2339
- ] }),
2340
- /* @__PURE__ */ jsx("div", { className: "canvas-3d-loading__message", children: message }),
2341
- details && /* @__PURE__ */ jsx("div", { className: "canvas-3d-loading__details", children: details }),
2342
- hasProgress && /* @__PURE__ */ jsxs("div", { className: "canvas-3d-loading__progress", children: [
2343
- /* @__PURE__ */ jsx("div", { className: "progress__bar", children: /* @__PURE__ */ jsx(
2344
- "div",
2345
- {
2346
- className: "progress__fill",
2347
- style: { width: `${clampedProgress}%` }
2348
- }
2349
- ) }),
2350
- /* @__PURE__ */ jsxs("div", { className: "progress__text", children: [
2351
- /* @__PURE__ */ jsxs("span", { className: "progress__percentage", children: [
2352
- clampedProgress,
2353
- "%"
2354
- ] }),
2355
- /* @__PURE__ */ jsxs("span", { className: "progress__count", children: [
2356
- "(",
2357
- loaded,
2358
- "/",
2359
- total,
2360
- ")"
2361
- ] })
2362
- ] })
2363
- ] })
2364
- ] }),
2365
- /* @__PURE__ */ jsx("div", { className: "canvas-3d-loading__background", children: /* @__PURE__ */ jsx("div", { className: "bg__grid" }) })
2366
- ] });
2367
- }
2368
2143
 
2369
2144
  // lib/debug.ts
2370
2145
  typeof window !== "undefined" && (localStorage.getItem("debug") === "true" || process.env.NODE_ENV === "development");
2371
2146
  lazy(() => import('react-markdown'));
2372
2147
  var GameAudioContext = createContext(null);
2373
2148
  GameAudioContext.displayName = "GameAudioContext";
2374
- function detectAssetRoot2(modelUrl) {
2375
- const idx = modelUrl.indexOf("/3d/");
2376
- if (idx !== -1) {
2377
- return modelUrl.substring(0, idx + 4);
2378
- }
2379
- return modelUrl.substring(0, modelUrl.lastIndexOf("/") + 1);
2380
- }
2381
- function createGLTFLoaderForUrl(url) {
2382
- const loader = new GLTFLoader();
2383
- loader.setResourcePath(detectAssetRoot2(url));
2384
- return loader;
2385
- }
2386
- var AssetLoader = class {
2387
- constructor() {
2388
- __publicField(this, "objLoader");
2389
- __publicField(this, "textureLoader");
2390
- __publicField(this, "modelCache");
2391
- __publicField(this, "textureCache");
2392
- __publicField(this, "loadingPromises");
2393
- this.objLoader = new OBJLoader();
2394
- this.textureLoader = new THREE.TextureLoader();
2395
- this.modelCache = /* @__PURE__ */ new Map();
2396
- this.textureCache = /* @__PURE__ */ new Map();
2397
- this.loadingPromises = /* @__PURE__ */ new Map();
2398
- }
2399
- /**
2400
- * Load a GLB/GLTF model
2401
- * @param url - URL to the .glb or .gltf file
2402
- * @returns Promise with loaded model scene and animations
2403
- */
2404
- async loadModel(url) {
2405
- if (this.modelCache.has(url)) {
2406
- return this.modelCache.get(url);
2407
- }
2408
- if (this.loadingPromises.has(url)) {
2409
- return this.loadingPromises.get(url);
2410
- }
2411
- const loader = createGLTFLoaderForUrl(url);
2412
- const loadPromise = loader.loadAsync(url).then((gltf) => {
2413
- const result = {
2414
- scene: gltf.scene,
2415
- animations: gltf.animations || []
2416
- };
2417
- this.modelCache.set(url, result);
2418
- this.loadingPromises.delete(url);
2419
- return result;
2420
- }).catch((error) => {
2421
- this.loadingPromises.delete(url);
2422
- throw new Error(`Failed to load model ${url}: ${error.message}`);
2423
- });
2424
- this.loadingPromises.set(url, loadPromise);
2425
- return loadPromise;
2426
- }
2427
- /**
2428
- * Load an OBJ model (fallback for non-GLB assets)
2429
- * @param url - URL to the .obj file
2430
- * @returns Promise with loaded object group
2431
- */
2432
- async loadOBJ(url) {
2433
- if (this.modelCache.has(url)) {
2434
- return this.modelCache.get(url).scene;
2435
- }
2436
- if (this.loadingPromises.has(url)) {
2437
- const result = await this.loadingPromises.get(url);
2438
- return result.scene;
2439
- }
2440
- const loadPromise = this.objLoader.loadAsync(url).then((group) => {
2441
- const result = {
2442
- scene: group,
2443
- animations: []
2444
- };
2445
- this.modelCache.set(url, result);
2446
- this.loadingPromises.delete(url);
2447
- return result;
2448
- }).catch((error) => {
2449
- this.loadingPromises.delete(url);
2450
- throw new Error(`Failed to load OBJ ${url}: ${error.message}`);
2451
- });
2452
- this.loadingPromises.set(url, loadPromise);
2453
- return (await loadPromise).scene;
2454
- }
2455
- /**
2456
- * Load a texture
2457
- * @param url - URL to the texture image
2458
- * @returns Promise with loaded texture
2459
- */
2460
- async loadTexture(url) {
2461
- if (this.textureCache.has(url)) {
2462
- return this.textureCache.get(url);
2463
- }
2464
- if (this.loadingPromises.has(`texture:${url}`)) {
2465
- return this.loadingPromises.get(`texture:${url}`);
2466
- }
2467
- const loadPromise = this.textureLoader.loadAsync(url).then((texture) => {
2468
- texture.colorSpace = THREE.SRGBColorSpace;
2469
- this.textureCache.set(url, texture);
2470
- this.loadingPromises.delete(`texture:${url}`);
2471
- return texture;
2472
- }).catch((error) => {
2473
- this.loadingPromises.delete(`texture:${url}`);
2474
- throw new Error(`Failed to load texture ${url}: ${error.message}`);
2475
- });
2476
- this.loadingPromises.set(`texture:${url}`, loadPromise);
2477
- return loadPromise;
2478
- }
2479
- /**
2480
- * Preload multiple assets
2481
- * @param urls - Array of asset URLs to preload
2482
- * @returns Promise that resolves when all assets are loaded
2483
- */
2484
- async preload(urls) {
2485
- const promises = urls.map((url) => {
2486
- if (url.endsWith(".glb") || url.endsWith(".gltf")) {
2487
- return this.loadModel(url).catch(() => null);
2488
- } else if (url.endsWith(".obj")) {
2489
- return this.loadOBJ(url).catch(() => null);
2490
- } else if (/\.(png|jpg|jpeg|webp)$/i.test(url)) {
2491
- return this.loadTexture(url).catch(() => null);
2492
- }
2493
- return Promise.resolve(null);
2494
- });
2495
- await Promise.all(promises);
2496
- }
2497
- /**
2498
- * Check if a model is cached
2499
- * @param url - Model URL
2500
- */
2501
- hasModel(url) {
2502
- return this.modelCache.has(url);
2503
- }
2504
- /**
2505
- * Check if a texture is cached
2506
- * @param url - Texture URL
2507
- */
2508
- hasTexture(url) {
2509
- return this.textureCache.has(url);
2510
- }
2511
- /**
2512
- * Get cached model (throws if not cached)
2513
- * @param url - Model URL
2514
- */
2515
- getModel(url) {
2516
- const model = this.modelCache.get(url);
2517
- if (!model) {
2518
- throw new Error(`Model ${url} not in cache`);
2519
- }
2520
- return model;
2521
- }
2522
- /**
2523
- * Get cached texture (throws if not cached)
2524
- * @param url - Texture URL
2525
- */
2526
- getTexture(url) {
2527
- const texture = this.textureCache.get(url);
2528
- if (!texture) {
2529
- throw new Error(`Texture ${url} not in cache`);
2530
- }
2531
- return texture;
2532
- }
2533
- /**
2534
- * Clear all caches
2535
- */
2536
- clearCache() {
2537
- this.textureCache.forEach((texture) => {
2538
- texture.dispose();
2539
- });
2540
- this.modelCache.forEach((model) => {
2541
- model.scene.traverse((child) => {
2542
- if (child instanceof THREE.Mesh) {
2543
- child.geometry.dispose();
2544
- if (Array.isArray(child.material)) {
2545
- child.material.forEach((m) => m.dispose());
2546
- } else {
2547
- child.material.dispose();
2548
- }
2549
- }
2550
- });
2551
- });
2552
- this.modelCache.clear();
2553
- this.textureCache.clear();
2554
- this.loadingPromises.clear();
2555
- }
2556
- /**
2557
- * Get cache statistics
2558
- */
2559
- getStats() {
2560
- return {
2561
- models: this.modelCache.size,
2562
- textures: this.textureCache.size,
2563
- loading: this.loadingPromises.size
2564
- };
2565
- }
2566
- };
2567
- new AssetLoader();
2568
- function useAssetLoader(options = {}) {
2569
- const { preloadUrls = [], loader: customLoader } = options;
2570
- const loaderRef = useRef(customLoader || new AssetLoader());
2571
- const [state, setState] = useState({
2572
- isLoading: false,
2573
- progress: 0,
2574
- loaded: 0,
2575
- total: 0,
2576
- errors: []
2577
- });
2578
- useEffect(() => {
2579
- if (preloadUrls.length > 0) {
2580
- preload(preloadUrls);
2581
- }
2582
- }, []);
2583
- const updateProgress = useCallback((loaded, total) => {
2584
- setState((prev) => ({
2585
- ...prev,
2586
- loaded,
2587
- total,
2588
- progress: total > 0 ? Math.round(loaded / total * 100) : 0
2589
- }));
2590
- }, []);
2591
- const loadModel = useCallback(
2592
- async (url) => {
2593
- setState((prev) => ({ ...prev, isLoading: true }));
2594
- try {
2595
- const model = await loaderRef.current.loadModel(url);
2596
- setState((prev) => ({
2597
- ...prev,
2598
- isLoading: false,
2599
- loaded: prev.loaded + 1
2600
- }));
2601
- return model;
2602
- } catch (error) {
2603
- const errorMsg = error instanceof Error ? error.message : String(error);
2604
- setState((prev) => ({
2605
- ...prev,
2606
- isLoading: false,
2607
- errors: [...prev.errors, errorMsg]
2608
- }));
2609
- throw error;
2610
- }
2611
- },
2612
- []
2613
- );
2614
- const loadOBJ = useCallback(
2615
- async (url) => {
2616
- setState((prev) => ({ ...prev, isLoading: true }));
2617
- try {
2618
- const model = await loaderRef.current.loadOBJ(url);
2619
- setState((prev) => ({
2620
- ...prev,
2621
- isLoading: false,
2622
- loaded: prev.loaded + 1
2623
- }));
2624
- return model;
2625
- } catch (error) {
2626
- const errorMsg = error instanceof Error ? error.message : String(error);
2627
- setState((prev) => ({
2628
- ...prev,
2629
- isLoading: false,
2630
- errors: [...prev.errors, errorMsg]
2631
- }));
2632
- throw error;
2633
- }
2634
- },
2635
- []
2636
- );
2637
- const loadTexture = useCallback(
2638
- async (url) => {
2639
- setState((prev) => ({ ...prev, isLoading: true }));
2640
- try {
2641
- const texture = await loaderRef.current.loadTexture(url);
2642
- setState((prev) => ({
2643
- ...prev,
2644
- isLoading: false,
2645
- loaded: prev.loaded + 1
2646
- }));
2647
- return texture;
2648
- } catch (error) {
2649
- const errorMsg = error instanceof Error ? error.message : String(error);
2650
- setState((prev) => ({
2651
- ...prev,
2652
- isLoading: false,
2653
- errors: [...prev.errors, errorMsg]
2654
- }));
2655
- throw error;
2656
- }
2657
- },
2658
- []
2659
- );
2660
- const preload = useCallback(
2661
- async (urls) => {
2662
- setState((prev) => ({
2663
- ...prev,
2664
- isLoading: true,
2665
- total: urls.length,
2666
- loaded: 0,
2667
- errors: []
2668
- }));
2669
- let completed = 0;
2670
- const errors = [];
2671
- await Promise.all(
2672
- urls.map(async (url) => {
2673
- try {
2674
- if (url.endsWith(".glb") || url.endsWith(".gltf")) {
2675
- await loaderRef.current.loadModel(url);
2676
- } else if (url.endsWith(".obj")) {
2677
- await loaderRef.current.loadOBJ(url);
2678
- } else if (/\.(png|jpg|jpeg|webp)$/i.test(url)) {
2679
- await loaderRef.current.loadTexture(url);
2680
- }
2681
- completed++;
2682
- updateProgress(completed, urls.length);
2683
- } catch (error) {
2684
- const errorMsg = error instanceof Error ? error.message : String(error);
2685
- errors.push(`${url}: ${errorMsg}`);
2686
- completed++;
2687
- updateProgress(completed, urls.length);
2688
- }
2689
- })
2690
- );
2691
- setState((prev) => ({
2692
- ...prev,
2693
- isLoading: false,
2694
- errors
2695
- }));
2696
- },
2697
- [updateProgress]
2698
- );
2699
- const hasModel = useCallback((url) => {
2700
- return loaderRef.current.hasModel(url);
2701
- }, []);
2702
- const hasTexture = useCallback((url) => {
2703
- return loaderRef.current.hasTexture(url);
2704
- }, []);
2705
- const getModel = useCallback((url) => {
2706
- try {
2707
- return loaderRef.current.getModel(url);
2708
- } catch {
2709
- return void 0;
2710
- }
2711
- }, []);
2712
- const getTexture = useCallback((url) => {
2713
- try {
2714
- return loaderRef.current.getTexture(url);
2715
- } catch {
2716
- return void 0;
2717
- }
2718
- }, []);
2719
- const clearCache = useCallback(() => {
2720
- loaderRef.current.clearCache();
2721
- setState({
2722
- isLoading: false,
2723
- progress: 0,
2724
- loaded: 0,
2725
- total: 0,
2726
- errors: []
2727
- });
2728
- }, []);
2729
- return {
2730
- ...state,
2731
- loadModel,
2732
- loadOBJ,
2733
- loadTexture,
2734
- preload,
2735
- hasModel,
2736
- hasTexture,
2737
- getModel,
2738
- getTexture,
2739
- clearCache
2740
- };
2741
- }
2742
- function useGameCanvas3DEvents(options) {
2743
- const {
2744
- tileClickEvent,
2745
- unitClickEvent,
2746
- featureClickEvent,
2747
- canvasClickEvent,
2748
- tileHoverEvent,
2749
- tileLeaveEvent,
2750
- unitAnimationEvent,
2751
- cameraChangeEvent,
2752
- onTileClick,
2753
- onUnitClick,
2754
- onFeatureClick,
2755
- onCanvasClick,
2756
- onTileHover,
2757
- onUnitAnimation
2758
- } = options;
2759
- const emit = useEmitEvent();
2760
- const optionsRef = useRef(options);
2761
- optionsRef.current = options;
2762
- const handleTileClick = useCallback(
2763
- (tile, event) => {
2764
- if (tileClickEvent) {
2765
- emit(tileClickEvent, {
2766
- tileId: tile.id,
2767
- x: tile.x,
2768
- z: tile.z ?? tile.y ?? 0,
2769
- type: tile.type,
2770
- terrain: tile.terrain,
2771
- elevation: tile.elevation
2772
- });
2773
- }
2774
- optionsRef.current.onTileClick?.(tile, event);
2775
- },
2776
- [tileClickEvent, emit]
2777
- );
2778
- const handleUnitClick = useCallback(
2779
- (unit, event) => {
2780
- if (unitClickEvent) {
2781
- emit(unitClickEvent, {
2782
- unitId: unit.id,
2783
- x: unit.x,
2784
- z: unit.z ?? unit.y ?? 0,
2785
- unitType: unit.unitType,
2786
- name: unit.name,
2787
- team: unit.team,
2788
- faction: unit.faction,
2789
- health: unit.health,
2790
- maxHealth: unit.maxHealth
2791
- });
2792
- }
2793
- optionsRef.current.onUnitClick?.(unit, event);
2794
- },
2795
- [unitClickEvent, emit]
2796
- );
2797
- const handleFeatureClick = useCallback(
2798
- (feature, event) => {
2799
- if (featureClickEvent) {
2800
- emit(featureClickEvent, {
2801
- featureId: feature.id,
2802
- x: feature.x,
2803
- z: feature.z ?? feature.y ?? 0,
2804
- type: feature.type,
2805
- elevation: feature.elevation
2806
- });
2807
- }
2808
- optionsRef.current.onFeatureClick?.(feature, event);
2809
- },
2810
- [featureClickEvent, emit]
2811
- );
2812
- const handleCanvasClick = useCallback(
2813
- (event) => {
2814
- if (canvasClickEvent) {
2815
- emit(canvasClickEvent, {
2816
- clientX: event.clientX,
2817
- clientY: event.clientY,
2818
- button: event.button
2819
- });
2820
- }
2821
- optionsRef.current.onCanvasClick?.(event);
2822
- },
2823
- [canvasClickEvent, emit]
2824
- );
2825
- const handleTileHover = useCallback(
2826
- (tile, event) => {
2827
- if (tile) {
2828
- if (tileHoverEvent) {
2829
- emit(tileHoverEvent, {
2830
- tileId: tile.id,
2831
- x: tile.x,
2832
- z: tile.z ?? tile.y ?? 0,
2833
- type: tile.type
2834
- });
2835
- }
2836
- } else {
2837
- if (tileLeaveEvent) {
2838
- emit(tileLeaveEvent, {});
2839
- }
2840
- }
2841
- optionsRef.current.onTileHover?.(tile, event);
2842
- },
2843
- [tileHoverEvent, tileLeaveEvent, emit]
2844
- );
2845
- const handleUnitAnimation = useCallback(
2846
- (unitId, state) => {
2847
- if (unitAnimationEvent) {
2848
- emit(unitAnimationEvent, {
2849
- unitId,
2850
- state,
2851
- timestamp: Date.now()
2852
- });
2853
- }
2854
- optionsRef.current.onUnitAnimation?.(unitId, state);
2855
- },
2856
- [unitAnimationEvent, emit]
2857
- );
2858
- const handleCameraChange = useCallback(
2859
- (position) => {
2860
- if (cameraChangeEvent) {
2861
- emit(cameraChangeEvent, {
2862
- position,
2863
- timestamp: Date.now()
2864
- });
2865
- }
2866
- },
2867
- [cameraChangeEvent, emit]
2868
- );
2869
- return {
2870
- handleTileClick,
2871
- handleUnitClick,
2872
- handleFeatureClick,
2873
- handleCanvasClick,
2874
- handleTileHover,
2875
- handleUnitAnimation,
2876
- handleCameraChange
2877
- };
2878
- }
2879
- function detectAssetRoot3(modelUrl) {
2880
- const idx = modelUrl.indexOf("/3d/");
2881
- if (idx !== -1) {
2882
- return modelUrl.substring(0, idx + 4);
2883
- }
2884
- return modelUrl.substring(0, modelUrl.lastIndexOf("/") + 1);
2885
- }
2886
- function useGLTFModel2(url, resourceBasePath) {
2887
- const [state, setState] = useState({
2888
- model: null,
2889
- isLoading: false,
2890
- error: null
2891
- });
2892
- useEffect(() => {
2893
- if (!url) {
2894
- setState({ model: null, isLoading: false, error: null });
2895
- return;
2896
- }
2897
- console.log("[ModelLoader] Loading:", url);
2898
- setState((prev) => ({ ...prev, isLoading: true, error: null }));
2899
- const assetRoot = resourceBasePath || detectAssetRoot3(url);
2900
- const loader = new GLTFLoader$1();
2901
- loader.setResourcePath(assetRoot);
2902
- loader.load(
2903
- url,
2904
- (gltf) => {
2905
- console.log("[ModelLoader] Loaded:", url);
2906
- setState({
2907
- model: gltf.scene,
2908
- isLoading: false,
2909
- error: null
2910
- });
2911
- },
2912
- void 0,
2913
- (err) => {
2914
- const errorMsg = err instanceof Error ? err.message : String(err);
2915
- console.warn("[ModelLoader] Failed:", url, errorMsg);
2916
- setState({
2917
- model: null,
2918
- isLoading: false,
2919
- error: err instanceof Error ? err : new Error(String(err))
2920
- });
2921
- }
2922
- );
2923
- }, [url, resourceBasePath]);
2924
- return state;
2925
- }
2926
- function ModelLoader({
2927
- url,
2928
- position = [0, 0, 0],
2929
- scale = 1,
2930
- rotation = [0, 0, 0],
2931
- isSelected = false,
2932
- isHovered = false,
2933
- onClick,
2934
- onHover,
2935
- fallbackGeometry = "box",
2936
- castShadow = true,
2937
- receiveShadow = true,
2938
- resourceBasePath
2939
- }) {
2940
- const { model: loadedModel, isLoading, error } = useGLTFModel2(url, resourceBasePath);
2941
- const model = useMemo(() => {
2942
- if (!loadedModel) return null;
2943
- const cloned = loadedModel.clone();
2944
- cloned.traverse((child) => {
2945
- if (child instanceof THREE.Mesh) {
2946
- child.castShadow = castShadow;
2947
- child.receiveShadow = receiveShadow;
2948
- }
2949
- });
2950
- return cloned;
2951
- }, [loadedModel, castShadow, receiveShadow]);
2952
- const scaleArray = useMemo(() => {
2953
- if (typeof scale === "number") {
2954
- return [scale, scale, scale];
2955
- }
2956
- return scale;
2957
- }, [scale]);
2958
- const rotationRad = useMemo(() => {
2959
- return [
2960
- rotation[0] * Math.PI / 180,
2961
- rotation[1] * Math.PI / 180,
2962
- rotation[2] * Math.PI / 180
2963
- ];
2964
- }, [rotation]);
2965
- if (isLoading) {
2966
- return /* @__PURE__ */ jsx("group", { position, children: /* @__PURE__ */ jsxs("mesh", { rotation: [Math.PI / 2, 0, 0], children: [
2967
- /* @__PURE__ */ jsx("ringGeometry", { args: [0.3, 0.35, 16] }),
2968
- /* @__PURE__ */ jsx("meshBasicMaterial", { color: "#4a90d9", transparent: true, opacity: 0.8 })
2969
- ] }) });
2970
- }
2971
- if (error || !model) {
2972
- if (fallbackGeometry === "none") {
2973
- return /* @__PURE__ */ jsx("group", { position });
2974
- }
2975
- const fallbackProps = {
2976
- onClick,
2977
- onPointerOver: () => onHover?.(true),
2978
- onPointerOut: () => onHover?.(false)
2979
- };
2980
- return /* @__PURE__ */ jsxs("group", { position, children: [
2981
- (isSelected || isHovered) && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.02, 0], rotation: [-Math.PI / 2, 0, 0], children: [
2982
- /* @__PURE__ */ jsx("ringGeometry", { args: [0.6, 0.7, 32] }),
2983
- /* @__PURE__ */ jsx(
2984
- "meshBasicMaterial",
2985
- {
2986
- color: isSelected ? 16755200 : 16777215,
2987
- transparent: true,
2988
- opacity: 0.5
2989
- }
2990
- )
2991
- ] }),
2992
- fallbackGeometry === "box" && /* @__PURE__ */ jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
2993
- /* @__PURE__ */ jsx("boxGeometry", { args: [0.8, 0.8, 0.8] }),
2994
- /* @__PURE__ */ jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
2995
- ] }),
2996
- fallbackGeometry === "sphere" && /* @__PURE__ */ jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
2997
- /* @__PURE__ */ jsx("sphereGeometry", { args: [0.4, 16, 16] }),
2998
- /* @__PURE__ */ jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
2999
- ] }),
3000
- fallbackGeometry === "cylinder" && /* @__PURE__ */ jsxs("mesh", { ...fallbackProps, position: [0, 0.5, 0], children: [
3001
- /* @__PURE__ */ jsx("cylinderGeometry", { args: [0.3, 0.3, 0.8, 16] }),
3002
- /* @__PURE__ */ jsx("meshStandardMaterial", { color: error ? 16729156 : 8947848 })
3003
- ] })
3004
- ] });
3005
- }
3006
- return /* @__PURE__ */ jsxs(
3007
- "group",
3008
- {
3009
- position,
3010
- rotation: rotationRad,
3011
- onClick,
3012
- onPointerOver: () => onHover?.(true),
3013
- onPointerOut: () => onHover?.(false),
3014
- children: [
3015
- (isSelected || isHovered) && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.02, 0], rotation: [-Math.PI / 2, 0, 0], children: [
3016
- /* @__PURE__ */ jsx("ringGeometry", { args: [0.6, 0.7, 32] }),
3017
- /* @__PURE__ */ jsx(
3018
- "meshBasicMaterial",
3019
- {
3020
- color: isSelected ? 16755200 : 16777215,
3021
- transparent: true,
3022
- opacity: 0.5
3023
- }
3024
- )
3025
- ] }),
3026
- /* @__PURE__ */ jsx("primitive", { object: model, scale: scaleArray })
3027
- ]
3028
- }
3029
- );
3030
- }
3031
- var DEFAULT_GRID_CONFIG = {
3032
- cellSize: 1,
3033
- offsetX: 0,
3034
- offsetZ: 0
3035
- };
3036
- function CameraController({
3037
- onCameraChange
3038
- }) {
3039
- const { camera } = useThree();
3040
- useEffect(() => {
3041
- if (onCameraChange) {
3042
- onCameraChange({
3043
- x: camera.position.x,
3044
- y: camera.position.y,
3045
- z: camera.position.z
3046
- });
3047
- }
3048
- }, [camera.position, onCameraChange]);
3049
- return null;
3050
- }
3051
- var GameCanvas3D = forwardRef(
3052
- ({
3053
- tiles = [],
3054
- units = [],
3055
- features = [],
3056
- events: events2 = [],
3057
- orientation = "standard",
3058
- cameraMode = "isometric",
3059
- showGrid = true,
3060
- showCoordinates = false,
3061
- showTileInfo = false,
3062
- overlay = "default",
3063
- shadows = true,
3064
- backgroundColor = "#1a1a2e",
3065
- onTileClick,
3066
- onUnitClick,
3067
- onFeatureClick,
3068
- onCanvasClick,
3069
- onTileHover,
3070
- onUnitAnimation,
3071
- assetLoader: customAssetLoader,
3072
- tileRenderer: CustomTileRenderer,
3073
- unitRenderer: CustomUnitRenderer,
3074
- featureRenderer: CustomFeatureRenderer,
3075
- className,
3076
- isLoading: externalLoading,
3077
- error: externalError,
3078
- entity,
3079
- preloadAssets = [],
3080
- tileClickEvent,
3081
- unitClickEvent,
3082
- featureClickEvent,
3083
- canvasClickEvent,
3084
- tileHoverEvent,
3085
- tileLeaveEvent,
3086
- unitAnimationEvent,
3087
- cameraChangeEvent,
3088
- loadingMessage = "Loading 3D Scene...",
3089
- useInstancing = true,
3090
- validMoves = [],
3091
- attackTargets = [],
3092
- selectedTileIds = [],
3093
- selectedUnitId = null,
3094
- children
3095
- }, ref) => {
3096
- const containerRef = useRef(null);
3097
- const controlsRef = useRef(null);
3098
- const [hoveredTile, setHoveredTile] = useState(null);
3099
- const [internalError, setInternalError] = useState(null);
3100
- const { isLoading: assetsLoading, progress, loaded, total } = useAssetLoader({
3101
- preloadUrls: preloadAssets,
3102
- loader: customAssetLoader
3103
- });
3104
- const eventHandlers = useGameCanvas3DEvents({
3105
- tileClickEvent,
3106
- unitClickEvent,
3107
- featureClickEvent,
3108
- canvasClickEvent,
3109
- tileHoverEvent,
3110
- tileLeaveEvent,
3111
- unitAnimationEvent,
3112
- cameraChangeEvent,
3113
- onTileClick,
3114
- onUnitClick,
3115
- onFeatureClick,
3116
- onCanvasClick,
3117
- onTileHover,
3118
- onUnitAnimation
3119
- });
3120
- const gridBounds = useMemo(() => {
3121
- if (tiles.length === 0) {
3122
- return { minX: 0, maxX: 10, minZ: 0, maxZ: 10 };
3123
- }
3124
- const xs = tiles.map((t) => t.x);
3125
- const zs = tiles.map((t) => t.z || t.y || 0);
3126
- return {
3127
- minX: Math.min(...xs),
3128
- maxX: Math.max(...xs),
3129
- minZ: Math.min(...zs),
3130
- maxZ: Math.max(...zs)
3131
- };
3132
- }, [tiles]);
3133
- const cameraTarget = useMemo(() => {
3134
- return [
3135
- (gridBounds.minX + gridBounds.maxX) / 2,
3136
- 0,
3137
- (gridBounds.minZ + gridBounds.maxZ) / 2
3138
- ];
3139
- }, [gridBounds]);
3140
- const gridConfig = useMemo(
3141
- () => ({
3142
- ...DEFAULT_GRID_CONFIG,
3143
- offsetX: -(gridBounds.maxX - gridBounds.minX) / 2,
3144
- offsetZ: -(gridBounds.maxZ - gridBounds.minZ) / 2
3145
- }),
3146
- [gridBounds]
3147
- );
3148
- const gridToWorld = useCallback(
3149
- (x, z, y = 0) => {
3150
- const worldX = (x - gridBounds.minX) * gridConfig.cellSize;
3151
- const worldZ = (z - gridBounds.minZ) * gridConfig.cellSize;
3152
- return [worldX, y * gridConfig.cellSize, worldZ];
3153
- },
3154
- [gridBounds, gridConfig]
3155
- );
3156
- useImperativeHandle(ref, () => ({
3157
- getCameraPosition: () => {
3158
- if (controlsRef.current) {
3159
- const pos = controlsRef.current.object.position;
3160
- return new THREE.Vector3(pos.x, pos.y, pos.z);
3161
- }
3162
- return null;
3163
- },
3164
- setCameraPosition: (x, y, z) => {
3165
- if (controlsRef.current) {
3166
- controlsRef.current.object.position.set(x, y, z);
3167
- controlsRef.current.update();
3168
- }
3169
- },
3170
- lookAt: (x, y, z) => {
3171
- if (controlsRef.current) {
3172
- controlsRef.current.target.set(x, y, z);
3173
- controlsRef.current.update();
3174
- }
3175
- },
3176
- resetCamera: () => {
3177
- if (controlsRef.current) {
3178
- controlsRef.current.reset();
3179
- }
3180
- },
3181
- screenshot: () => {
3182
- const canvas = containerRef.current?.querySelector("canvas");
3183
- if (canvas) {
3184
- return canvas.toDataURL("image/png");
3185
- }
3186
- return null;
3187
- },
3188
- export: () => ({
3189
- tiles,
3190
- units,
3191
- features
3192
- })
3193
- }));
3194
- const handleTileClick = useCallback(
3195
- (tile, event) => {
3196
- eventHandlers.handleTileClick(tile, event);
3197
- },
3198
- [eventHandlers]
3199
- );
3200
- const handleUnitClick = useCallback(
3201
- (unit, event) => {
3202
- eventHandlers.handleUnitClick(unit, event);
3203
- },
3204
- [eventHandlers]
3205
- );
3206
- const handleFeatureClick = useCallback(
3207
- (feature, event) => {
3208
- if (event) {
3209
- eventHandlers.handleFeatureClick(feature, event);
3210
- }
3211
- },
3212
- [eventHandlers]
3213
- );
3214
- const handleTileHover = useCallback(
3215
- (tile, event) => {
3216
- setHoveredTile(tile);
3217
- if (event) {
3218
- eventHandlers.handleTileHover(tile, event);
3219
- }
3220
- },
3221
- [eventHandlers]
3222
- );
3223
- const cameraConfig = useMemo(() => {
3224
- const size = Math.max(
3225
- gridBounds.maxX - gridBounds.minX,
3226
- gridBounds.maxZ - gridBounds.minZ
3227
- );
3228
- const distance = size * 1.5;
3229
- switch (cameraMode) {
3230
- case "isometric":
3231
- return {
3232
- position: [distance, distance * 0.8, distance],
3233
- fov: 45
3234
- };
3235
- case "top-down":
3236
- return {
3237
- position: [0, distance * 2, 0],
3238
- fov: 45
3239
- };
3240
- case "perspective":
3241
- default:
3242
- return {
3243
- position: [distance, distance, distance],
3244
- fov: 45
3245
- };
3246
- }
3247
- }, [cameraMode, gridBounds]);
3248
- const DefaultTileRenderer = useCallback(
3249
- ({ tile, position }) => {
3250
- const isSelected = tile.id ? selectedTileIds.includes(tile.id) : false;
3251
- const isHovered = hoveredTile?.id === tile.id;
3252
- const isValidMove = validMoves.some(
3253
- (m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)
3254
- );
3255
- const isAttackTarget = attackTargets.some(
3256
- (m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)
3257
- );
3258
- let color = 8421504;
3259
- if (tile.type === "water") color = 4491468;
3260
- else if (tile.type === "grass") color = 4500036;
3261
- else if (tile.type === "sand") color = 14535816;
3262
- else if (tile.type === "rock") color = 8947848;
3263
- else if (tile.type === "snow") color = 15658734;
3264
- let emissive = 0;
3265
- if (isSelected) emissive = 4473924;
3266
- else if (isAttackTarget) emissive = 4456448;
3267
- else if (isValidMove) emissive = 17408;
3268
- else if (isHovered) emissive = 2236962;
3269
- return /* @__PURE__ */ jsxs(
3270
- "mesh",
3271
- {
3272
- position,
3273
- onClick: (e) => handleTileClick(tile, e),
3274
- onPointerEnter: (e) => handleTileHover(tile, e),
3275
- onPointerLeave: (e) => handleTileHover(null, e),
3276
- userData: { type: "tile", tileId: tile.id, gridX: tile.x, gridZ: tile.z ?? tile.y },
3277
- children: [
3278
- /* @__PURE__ */ jsx("boxGeometry", { args: [0.95, 0.2, 0.95] }),
3279
- /* @__PURE__ */ jsx("meshStandardMaterial", { color, emissive })
3280
- ]
3281
- }
3282
- );
3283
- },
3284
- [selectedTileIds, hoveredTile, validMoves, attackTargets, handleTileClick, handleTileHover]
3285
- );
3286
- const DefaultUnitRenderer = useCallback(
3287
- ({ unit, position }) => {
3288
- const isSelected = selectedUnitId === unit.id;
3289
- const color = unit.faction === "player" ? 4491519 : unit.faction === "enemy" ? 16729156 : 16777028;
3290
- return /* @__PURE__ */ jsxs(
3291
- "group",
3292
- {
3293
- position,
3294
- onClick: (e) => handleUnitClick(unit, e),
3295
- userData: { type: "unit", unitId: unit.id },
3296
- children: [
3297
- isSelected && /* @__PURE__ */ jsxs("mesh", { position: [0, 0.05, 0], rotation: [-Math.PI / 2, 0, 0], children: [
3298
- /* @__PURE__ */ jsx("ringGeometry", { args: [0.4, 0.5, 32] }),
3299
- /* @__PURE__ */ jsx("meshBasicMaterial", { color: "#ffff00", transparent: true, opacity: 0.8 })
3300
- ] }),
3301
- /* @__PURE__ */ jsxs("mesh", { position: [0, 0.3, 0], children: [
3302
- /* @__PURE__ */ jsx("cylinderGeometry", { args: [0.3, 0.3, 0.1, 8] }),
3303
- /* @__PURE__ */ jsx("meshStandardMaterial", { color })
3304
- ] }),
3305
- /* @__PURE__ */ jsxs("mesh", { position: [0, 0.6, 0], children: [
3306
- /* @__PURE__ */ jsx("capsuleGeometry", { args: [0.2, 0.4, 4, 8] }),
3307
- /* @__PURE__ */ jsx("meshStandardMaterial", { color })
3308
- ] }),
3309
- /* @__PURE__ */ jsxs("mesh", { position: [0, 0.9, 0], children: [
3310
- /* @__PURE__ */ jsx("sphereGeometry", { args: [0.12, 8, 8] }),
3311
- /* @__PURE__ */ jsx("meshStandardMaterial", { color })
3312
- ] }),
3313
- unit.health !== void 0 && unit.maxHealth !== void 0 && /* @__PURE__ */ jsxs("group", { position: [0, 1.2, 0], children: [
3314
- /* @__PURE__ */ jsxs("mesh", { position: [-0.25, 0, 0], children: [
3315
- /* @__PURE__ */ jsx("planeGeometry", { args: [0.5, 0.05] }),
3316
- /* @__PURE__ */ jsx("meshBasicMaterial", { color: 3355443 })
3317
- ] }),
3318
- /* @__PURE__ */ jsxs(
3319
- "mesh",
3320
- {
3321
- position: [
3322
- -0.25 + 0.5 * (unit.health / unit.maxHealth) / 2,
3323
- 0,
3324
- 0.01
3325
- ],
3326
- children: [
3327
- /* @__PURE__ */ jsx("planeGeometry", { args: [0.5 * (unit.health / unit.maxHealth), 0.05] }),
3328
- /* @__PURE__ */ jsx(
3329
- "meshBasicMaterial",
3330
- {
3331
- color: unit.health / unit.maxHealth > 0.5 ? 4500036 : unit.health / unit.maxHealth > 0.25 ? 11184708 : 16729156
3332
- }
3333
- )
3334
- ]
3335
- }
3336
- )
3337
- ] })
3338
- ]
3339
- }
3340
- );
3341
- },
3342
- [selectedUnitId, handleUnitClick]
3343
- );
3344
- const DefaultFeatureRenderer = useCallback(
3345
- ({
3346
- feature,
3347
- position
3348
- }) => {
3349
- if (feature.assetUrl) {
3350
- return /* @__PURE__ */ jsx(
3351
- ModelLoader,
3352
- {
3353
- url: feature.assetUrl,
3354
- position,
3355
- scale: 0.5,
3356
- rotation: [0, feature.rotation ?? 0, 0],
3357
- onClick: () => handleFeatureClick(feature, null),
3358
- fallbackGeometry: "box"
3359
- },
3360
- feature.id
3361
- );
3362
- }
3363
- if (feature.type === "tree") {
3364
- return /* @__PURE__ */ jsxs(
3365
- "group",
3366
- {
3367
- position,
3368
- onClick: (e) => handleFeatureClick(feature, e),
3369
- userData: { type: "feature", featureId: feature.id },
3370
- children: [
3371
- /* @__PURE__ */ jsxs("mesh", { position: [0, 0.4, 0], children: [
3372
- /* @__PURE__ */ jsx("cylinderGeometry", { args: [0.1, 0.15, 0.8, 6] }),
3373
- /* @__PURE__ */ jsx("meshStandardMaterial", { color: 9127187 })
3374
- ] }),
3375
- /* @__PURE__ */ jsxs("mesh", { position: [0, 0.9, 0], children: [
3376
- /* @__PURE__ */ jsx("coneGeometry", { args: [0.5, 0.8, 8] }),
3377
- /* @__PURE__ */ jsx("meshStandardMaterial", { color: 2263842 })
3378
- ] })
3379
- ]
3380
- }
3381
- );
3382
- }
3383
- if (feature.type === "rock") {
3384
- return /* @__PURE__ */ jsxs(
3385
- "mesh",
3386
- {
3387
- position: [position[0], position[1] + 0.3, position[2]],
3388
- onClick: (e) => handleFeatureClick(feature, e),
3389
- userData: { type: "feature", featureId: feature.id },
3390
- children: [
3391
- /* @__PURE__ */ jsx("dodecahedronGeometry", { args: [0.3, 0] }),
3392
- /* @__PURE__ */ jsx("meshStandardMaterial", { color: 8421504 })
3393
- ]
3394
- }
3395
- );
3396
- }
3397
- return null;
3398
- },
3399
- [handleFeatureClick]
3400
- );
3401
- if (externalLoading || assetsLoading && preloadAssets.length > 0) {
3402
- return /* @__PURE__ */ jsx(
3403
- Canvas3DLoadingState,
3404
- {
3405
- progress,
3406
- loaded,
3407
- total,
3408
- message: loadingMessage,
3409
- className
3410
- }
3411
- );
3412
- }
3413
- const displayError = externalError || internalError;
3414
- if (displayError) {
3415
- return /* @__PURE__ */ jsx(Canvas3DErrorBoundary, { children: /* @__PURE__ */ jsx("div", { className: "game-canvas-3d game-canvas-3d--error", children: /* @__PURE__ */ jsxs("div", { className: "game-canvas-3d__error", children: [
3416
- "Error: ",
3417
- displayError
3418
- ] }) }) });
3419
- }
3420
- return /* @__PURE__ */ jsx(
3421
- Canvas3DErrorBoundary,
3422
- {
3423
- onError: (err) => setInternalError(err.message),
3424
- onReset: () => setInternalError(null),
3425
- children: /* @__PURE__ */ jsxs(
3426
- "div",
3427
- {
3428
- ref: containerRef,
3429
- className: `game-canvas-3d ${className || ""}`,
3430
- "data-orientation": orientation,
3431
- "data-camera-mode": cameraMode,
3432
- "data-overlay": overlay,
3433
- children: [
3434
- /* @__PURE__ */ jsxs(
3435
- Canvas,
3436
- {
3437
- shadows,
3438
- camera: {
3439
- position: cameraConfig.position,
3440
- fov: cameraConfig.fov,
3441
- near: 0.1,
3442
- far: 1e3
3443
- },
3444
- style: { background: backgroundColor },
3445
- onClick: (e) => {
3446
- if (e.target === e.currentTarget) {
3447
- eventHandlers.handleCanvasClick(e);
3448
- }
3449
- },
3450
- children: [
3451
- /* @__PURE__ */ jsx(CameraController, { onCameraChange: eventHandlers.handleCameraChange }),
3452
- /* @__PURE__ */ jsx("ambientLight", { intensity: 0.6 }),
3453
- /* @__PURE__ */ jsx(
3454
- "directionalLight",
3455
- {
3456
- position: [10, 20, 10],
3457
- intensity: 0.8,
3458
- castShadow: shadows,
3459
- "shadow-mapSize": [2048, 2048]
3460
- }
3461
- ),
3462
- /* @__PURE__ */ jsx("hemisphereLight", { intensity: 0.3, color: "#87ceeb", groundColor: "#362d1d" }),
3463
- showGrid && /* @__PURE__ */ jsx(
3464
- Grid,
3465
- {
3466
- args: [
3467
- Math.max(gridBounds.maxX - gridBounds.minX + 2, 10),
3468
- Math.max(gridBounds.maxZ - gridBounds.minZ + 2, 10)
3469
- ],
3470
- position: [
3471
- (gridBounds.maxX - gridBounds.minX) / 2 - 0.5,
3472
- 0,
3473
- (gridBounds.maxZ - gridBounds.minZ) / 2 - 0.5
3474
- ],
3475
- cellSize: 1,
3476
- cellThickness: 1,
3477
- cellColor: "#444444",
3478
- sectionSize: 5,
3479
- sectionThickness: 1.5,
3480
- sectionColor: "#666666",
3481
- fadeDistance: 50,
3482
- fadeStrength: 1
3483
- }
3484
- ),
3485
- tiles.map((tile, index) => {
3486
- const position = gridToWorld(
3487
- tile.x,
3488
- tile.z ?? tile.y ?? 0,
3489
- tile.elevation ?? 0
3490
- );
3491
- const Renderer = CustomTileRenderer || DefaultTileRenderer;
3492
- return /* @__PURE__ */ jsx(Renderer, { tile, position }, tile.id ?? `tile-${index}`);
3493
- }),
3494
- features.map((feature, index) => {
3495
- const position = gridToWorld(
3496
- feature.x,
3497
- feature.z ?? feature.y ?? 0,
3498
- (feature.elevation ?? 0) + 0.5
3499
- );
3500
- const Renderer = CustomFeatureRenderer || DefaultFeatureRenderer;
3501
- return /* @__PURE__ */ jsx(Renderer, { feature, position }, feature.id ?? `feature-${index}`);
3502
- }),
3503
- units.map((unit) => {
3504
- const position = gridToWorld(
3505
- unit.x ?? 0,
3506
- unit.z ?? unit.y ?? 0,
3507
- (unit.elevation ?? 0) + 0.5
3508
- );
3509
- const Renderer = CustomUnitRenderer || DefaultUnitRenderer;
3510
- return /* @__PURE__ */ jsx(Renderer, { unit, position }, unit.id);
3511
- }),
3512
- children,
3513
- /* @__PURE__ */ jsx(
3514
- OrbitControls,
3515
- {
3516
- ref: controlsRef,
3517
- target: cameraTarget,
3518
- enableDamping: true,
3519
- dampingFactor: 0.05,
3520
- minDistance: 2,
3521
- maxDistance: 100,
3522
- maxPolarAngle: Math.PI / 2 - 0.1
3523
- }
3524
- )
3525
- ]
3526
- }
3527
- ),
3528
- showCoordinates && hoveredTile && /* @__PURE__ */ jsxs("div", { className: "game-canvas-3d__coordinates", children: [
3529
- "X: ",
3530
- hoveredTile.x,
3531
- ", Z: ",
3532
- hoveredTile.z ?? hoveredTile.y ?? 0
3533
- ] }),
3534
- showTileInfo && hoveredTile && /* @__PURE__ */ jsxs("div", { className: "game-canvas-3d__tile-info", children: [
3535
- /* @__PURE__ */ jsx("div", { className: "tile-info__type", children: hoveredTile.type }),
3536
- hoveredTile.terrain && /* @__PURE__ */ jsx("div", { className: "tile-info__terrain", children: hoveredTile.terrain })
3537
- ] })
3538
- ]
3539
- }
3540
- )
3541
- }
3542
- );
3543
- }
3544
- );
3545
- GameCanvas3D.displayName = "GameCanvas3D";
3546
2149
 
3547
2150
  // lib/traitRegistry.ts
3548
2151
  var traits = /* @__PURE__ */ new Map();
@@ -3566,6 +2169,35 @@ function unregisterTrait(id) {
3566
2169
  traits.delete(id);
3567
2170
  notifyListeners2();
3568
2171
  }
2172
+
2173
+ // components/organisms/component-registry.generated.ts
2174
+ function lazyThree(name, loader) {
2175
+ const Lazy = React110__default.lazy(() => loader().then((m) => ({ default: m[name] })));
2176
+ function ThreeWrapper(props) {
2177
+ return React110__default.createElement(
2178
+ React110__default.Suspense,
2179
+ { fallback: null },
2180
+ React110__default.createElement(Lazy, props)
2181
+ );
2182
+ }
2183
+ ThreeWrapper.displayName = `Lazy(${name})`;
2184
+ return ThreeWrapper;
2185
+ }
2186
+ lazyThree("Camera3D", () => import('@almadar/ui/components/organisms/game/three'));
2187
+ lazyThree("Canvas3DErrorBoundary", () => import('@almadar/ui/components/organisms/game/three'));
2188
+ lazyThree("Canvas3DLoadingState", () => import('@almadar/ui/components/organisms/game/three'));
2189
+ lazyThree("FeatureRenderer", () => import('@almadar/ui/components/organisms/game/three'));
2190
+ lazyThree("FeatureRenderer3D", () => import('@almadar/ui/components/organisms/game/three'));
2191
+ lazyThree("GameCanvas3D", () => import('@almadar/ui/components/organisms/game/three'));
2192
+ lazyThree("GameCanvas3DBattleTemplate", () => import('@almadar/ui/components/organisms/game/three'));
2193
+ lazyThree("GameCanvas3DCastleTemplate", () => import('@almadar/ui/components/organisms/game/three'));
2194
+ lazyThree("GameCanvas3DWorldMapTemplate", () => import('@almadar/ui/components/organisms/game/three'));
2195
+ lazyThree("Lighting3D", () => import('@almadar/ui/components/organisms/game/three'));
2196
+ lazyThree("ModelLoader", () => import('@almadar/ui/components/organisms/game/three'));
2197
+ lazyThree("PhysicsObject3D", () => import('@almadar/ui/components/organisms/game/three'));
2198
+ lazyThree("Scene3D", () => import('@almadar/ui/components/organisms/game/three'));
2199
+ lazyThree("TileRenderer", () => import('@almadar/ui/components/organisms/game/three'));
2200
+ lazyThree("UnitRenderer", () => import('@almadar/ui/components/organisms/game/three'));
3569
2201
  createContext({ enabled: false });
3570
2202
  createContext(false);
3571
2203
  createContext(null);