cubeforge 0.7.0 → 0.8.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.
package/dist/index.d.ts CHANGED
@@ -17,6 +17,7 @@ export { AudioAnalyserControls, AudioAnalyserOptions, AudioGroup, AudioScheduler
17
17
  export { DevToolsHandle } from '@cubeforge/devtools';
18
18
  import { Room } from '@cubeforge/net';
19
19
  export { BinaryNetTransport, ClientPrediction, InterpolationBuffer, InterpolationBufferConfig, InterpolationState, NetMessage, NetTransport, NetworkInputConfig, PredictionConfig, Room, RoomConfig, SyncConfig, WebRTCTransport, WebRTCTransportConfig, WebSocketTransportOptions, createWebRTCTransport, createWebSocketTransport, isBinaryTransport, syncEntity, useNetworkInput } from '@cubeforge/net';
20
+ export { BoolField, ColorField, EditorShell, EditorShellProps, EditorState, EntityInfo, EntityInspector, EntityInspectorProps, NumberField, SceneHierarchy, SceneHierarchyProps, TextField, Vec2Field, useEditorState } from '@cubeforge/editor';
20
21
 
21
22
  interface GameControls {
22
23
  pause(): void;
package/dist/index.js CHANGED
@@ -83,6 +83,20 @@ function applyDeltaSnapshot(baseline, delta) {
83
83
  }
84
84
  return { nextId: delta.nextId, rngState: delta.rngState, entities };
85
85
  }
86
+ function _componentsChanged(a, b) {
87
+ if (a.length !== b.length) return true;
88
+ for (let i = 0; i < a.length; i++) {
89
+ const ca = a[i];
90
+ const cb = b[i];
91
+ if (ca["type"] !== cb["type"]) return true;
92
+ const keysA = Object.keys(ca);
93
+ if (keysA.length !== Object.keys(cb).length) return true;
94
+ for (const k of keysA) {
95
+ if (ca[k] !== cb[k]) return true;
96
+ }
97
+ }
98
+ return false;
99
+ }
86
100
  var ECSWorld = class {
87
101
  nextId = 0;
88
102
  // Secondary index: O(1) single-entity component lookup
@@ -201,6 +215,23 @@ var ECSWorld = class {
201
215
  hasComponent(id, type) {
202
216
  return this.componentIndex.get(id)?.has(type) ?? false;
203
217
  }
218
+ /**
219
+ * Returns live references to all components on an entity.
220
+ * Useful for editor / inspector tooling. Returns an empty array if the
221
+ * entity does not exist.
222
+ *
223
+ * **Do not mutate structural fields** (e.g. `type`) — doing so will
224
+ * desync the archetype index. Mutating value fields (x, y, color …) is safe.
225
+ */
226
+ getEntityComponents(id) {
227
+ const map = this.componentIndex.get(id);
228
+ if (!map) return [];
229
+ return [...map.values()];
230
+ }
231
+ /** Returns all live entity IDs currently in the world. */
232
+ getAllEntityIds() {
233
+ return [...this.componentIndex.keys()];
234
+ }
204
235
  // Flush pending dirty flags into the query cache immediately.
205
236
  // Called inline at the top of query() so any mid-frame mutation
206
237
  // (destroyEntity, addComponent, removeComponent) is reflected before
@@ -437,7 +468,7 @@ var ECSWorld = class {
437
468
  const removed = [];
438
469
  for (const entity of current.entities) {
439
470
  const base = baseMap.get(entity.id);
440
- if (!base || JSON.stringify(entity.components) !== JSON.stringify(base.components)) {
471
+ if (!base || _componentsChanged(entity.components, base.components)) {
441
472
  changed.push(entity);
442
473
  }
443
474
  }
@@ -8597,7 +8628,6 @@ function Checkpoint({
8597
8628
  // src/components/Tilemap.tsx
8598
8629
  import { useEffect as useEffect30, useState as useState6, useContext as useContext20 } from "react";
8599
8630
  import { Fragment as Fragment5, jsx as jsx10 } from "react/jsx-runtime";
8600
- var animatedTiles = /* @__PURE__ */ new Map();
8601
8631
  function getProperty(props, name) {
8602
8632
  return props?.find((p) => p.name === name)?.value;
8603
8633
  }
@@ -8629,6 +8659,7 @@ function Tilemap({
8629
8659
  useEffect30(() => {
8630
8660
  if (!engine) return;
8631
8661
  const createdEntities = [];
8662
+ const animatedTiles = /* @__PURE__ */ new Map();
8632
8663
  async function load() {
8633
8664
  let mapData;
8634
8665
  try {
@@ -8764,6 +8795,7 @@ function Tilemap({
8764
8795
  if (frame) {
8765
8796
  sprite.frame = { sx: frame.sx, sy: frame.sy, sw: frame.sw, sh: frame.sh };
8766
8797
  engine.assets.loadImage(frame.imageSrc).then((img) => {
8798
+ if (!engine.ecs.hasEntity(eid)) return;
8767
8799
  const s2 = engine.ecs.getComponent(eid, "Sprite");
8768
8800
  if (s2) s2.image = img;
8769
8801
  }).catch(() => {
@@ -8779,6 +8811,7 @@ function Tilemap({
8779
8811
  animatedTiles.set(eid, state);
8780
8812
  const firstFrameRegion = getFrameForLocalId(resolved.tileset, frames[0]);
8781
8813
  engine.assets.loadImage(firstFrameRegion.imageSrc).then((img) => {
8814
+ if (!engine.ecs.hasEntity(eid)) return;
8782
8815
  const s2 = engine.ecs.getComponent(eid, "Sprite");
8783
8816
  if (s2) {
8784
8817
  s2.image = img;
@@ -14610,7 +14643,7 @@ function HUDBar({
14610
14643
  display: "flex",
14611
14644
  flexDirection: rtl ? "row-reverse" : "row"
14612
14645
  };
14613
- const labelStyle = {
14646
+ const labelStyle2 = {
14614
14647
  fontFamily: "system-ui, sans-serif",
14615
14648
  fontSize: 11,
14616
14649
  letterSpacing: 0.5,
@@ -14622,7 +14655,7 @@ function HUDBar({
14622
14655
  marginBottom: 4
14623
14656
  };
14624
14657
  return /* @__PURE__ */ jsxs11("div", { style: { display: "flex", flexDirection: "column", ...style }, "aria-label": label, children: [
14625
- (label || showValue) && /* @__PURE__ */ jsxs11("div", { style: labelStyle, children: [
14658
+ (label || showValue) && /* @__PURE__ */ jsxs11("div", { style: labelStyle2, children: [
14626
14659
  label && /* @__PURE__ */ jsx22("span", { children: label }),
14627
14660
  showValue && /* @__PURE__ */ jsxs11("span", { style: { fontVariantNumeric: "tabular-nums" }, children: [
14628
14661
  Math.round(value),
@@ -15330,12 +15363,558 @@ function useRemotePlayer(config) {
15330
15363
  }, [room, world]);
15331
15364
  return { players };
15332
15365
  }
15366
+
15367
+ // ../editor/src/components/SceneHierarchy.tsx
15368
+ import { jsx as jsx23, jsxs as jsxs12 } from "react/jsx-runtime";
15369
+ var panelStyle = {
15370
+ background: "#0d1520",
15371
+ borderRight: "1px solid #1a2a3a",
15372
+ color: "#c0d0e0",
15373
+ fontFamily: "system-ui, sans-serif",
15374
+ fontSize: 12,
15375
+ display: "flex",
15376
+ flexDirection: "column",
15377
+ overflow: "hidden",
15378
+ userSelect: "none"
15379
+ };
15380
+ var headerStyle = {
15381
+ padding: "6px 10px",
15382
+ fontSize: 10,
15383
+ fontWeight: 700,
15384
+ textTransform: "uppercase",
15385
+ letterSpacing: 1,
15386
+ color: "#4fc3f7",
15387
+ borderBottom: "1px solid #1a2a3a",
15388
+ flexShrink: 0
15389
+ };
15390
+ var listStyle = {
15391
+ flex: 1,
15392
+ overflowY: "auto",
15393
+ padding: "4px 0"
15394
+ };
15395
+ function entityRowStyle(selected) {
15396
+ return {
15397
+ display: "flex",
15398
+ alignItems: "center",
15399
+ gap: 6,
15400
+ padding: "3px 10px",
15401
+ cursor: "pointer",
15402
+ background: selected ? "#1a3050" : "transparent",
15403
+ borderLeft: selected ? "2px solid #4fc3f7" : "2px solid transparent",
15404
+ color: selected ? "#e0f0ff" : "#a0b4c8",
15405
+ transition: "background 80ms"
15406
+ };
15407
+ }
15408
+ var componentBadgeStyle = {
15409
+ fontSize: 9,
15410
+ background: "#1a2a3a",
15411
+ color: "#607080",
15412
+ borderRadius: 3,
15413
+ padding: "1px 4px",
15414
+ maxWidth: 60,
15415
+ overflow: "hidden",
15416
+ textOverflow: "ellipsis",
15417
+ whiteSpace: "nowrap"
15418
+ };
15419
+ function SceneHierarchy({ entities, selectedId, onSelect, width = 220, style }) {
15420
+ return /* @__PURE__ */ jsxs12("div", { style: { ...panelStyle, width, ...style }, children: [
15421
+ /* @__PURE__ */ jsx23("div", { style: headerStyle, children: "Scene Hierarchy" }),
15422
+ /* @__PURE__ */ jsxs12("div", { style: { padding: "4px 10px", borderBottom: "1px solid #1a2a3a", color: "#4a5a6a", fontSize: 10 }, children: [
15423
+ entities.length,
15424
+ " ",
15425
+ entities.length === 1 ? "entity" : "entities"
15426
+ ] }),
15427
+ /* @__PURE__ */ jsxs12("div", { style: listStyle, children: [
15428
+ entities.map((e) => /* @__PURE__ */ jsx23(
15429
+ "div",
15430
+ {
15431
+ style: entityRowStyle(e.id === selectedId),
15432
+ onClick: () => onSelect(e.id === selectedId ? null : e.id),
15433
+ children: /* @__PURE__ */ jsxs12("div", { style: { flex: 1, overflow: "hidden" }, children: [
15434
+ /* @__PURE__ */ jsx23("div", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: e.name }),
15435
+ /* @__PURE__ */ jsxs12("div", { style: { display: "flex", gap: 3, marginTop: 2, flexWrap: "wrap" }, children: [
15436
+ e.componentTypes.slice(0, 3).map((t) => /* @__PURE__ */ jsx23("span", { style: componentBadgeStyle, children: t }, t)),
15437
+ e.componentTypes.length > 3 && /* @__PURE__ */ jsxs12("span", { style: componentBadgeStyle, children: [
15438
+ "+",
15439
+ e.componentTypes.length - 3
15440
+ ] })
15441
+ ] })
15442
+ ] })
15443
+ },
15444
+ e.id
15445
+ )),
15446
+ entities.length === 0 && /* @__PURE__ */ jsx23("div", { style: { padding: "10px", color: "#3a5060", fontSize: 11, textAlign: "center" }, children: "No entities" })
15447
+ ] })
15448
+ ] });
15449
+ }
15450
+
15451
+ // ../editor/src/components/PropertyField.tsx
15452
+ import { jsx as jsx24, jsxs as jsxs13 } from "react/jsx-runtime";
15453
+ var fieldRow = {
15454
+ display: "flex",
15455
+ alignItems: "center",
15456
+ gap: 6,
15457
+ marginBottom: 4,
15458
+ minHeight: 24
15459
+ };
15460
+ var labelStyle = {
15461
+ flex: "0 0 90px",
15462
+ fontSize: 11,
15463
+ color: "#8899aa",
15464
+ overflow: "hidden",
15465
+ textOverflow: "ellipsis",
15466
+ whiteSpace: "nowrap",
15467
+ fontFamily: "system-ui, sans-serif",
15468
+ textTransform: "uppercase",
15469
+ letterSpacing: 0.5
15470
+ };
15471
+ var inputBase = {
15472
+ flex: 1,
15473
+ background: "#1a2030",
15474
+ border: "1px solid #2a3a50",
15475
+ borderRadius: 3,
15476
+ color: "#d0e0f0",
15477
+ padding: "2px 6px",
15478
+ fontSize: 11,
15479
+ fontFamily: "monospace",
15480
+ outline: "none",
15481
+ minWidth: 0
15482
+ };
15483
+ function NumberField({ label, value, onChange, step = 1 }) {
15484
+ return /* @__PURE__ */ jsxs13("div", { style: fieldRow, children: [
15485
+ /* @__PURE__ */ jsx24("span", { style: labelStyle, title: label, children: label }),
15486
+ /* @__PURE__ */ jsx24(
15487
+ "input",
15488
+ {
15489
+ type: "number",
15490
+ style: inputBase,
15491
+ value: isNaN(value) ? "" : value,
15492
+ step,
15493
+ onChange: (e) => {
15494
+ const v = parseFloat(e.target.value);
15495
+ if (!isNaN(v)) onChange(v);
15496
+ }
15497
+ }
15498
+ )
15499
+ ] });
15500
+ }
15501
+ function TextField({ label, value, onChange }) {
15502
+ return /* @__PURE__ */ jsxs13("div", { style: fieldRow, children: [
15503
+ /* @__PURE__ */ jsx24("span", { style: labelStyle, title: label, children: label }),
15504
+ /* @__PURE__ */ jsx24("input", { type: "text", style: inputBase, value, onChange: (e) => onChange(e.target.value) })
15505
+ ] });
15506
+ }
15507
+ function BoolField({ label, value, onChange }) {
15508
+ return /* @__PURE__ */ jsxs13("div", { style: fieldRow, children: [
15509
+ /* @__PURE__ */ jsx24("span", { style: labelStyle, title: label, children: label }),
15510
+ /* @__PURE__ */ jsx24(
15511
+ "input",
15512
+ {
15513
+ type: "checkbox",
15514
+ checked: value,
15515
+ style: { accentColor: "#4fc3f7", cursor: "pointer" },
15516
+ onChange: (e) => onChange(e.target.checked)
15517
+ }
15518
+ )
15519
+ ] });
15520
+ }
15521
+ function ColorField({ label, value, onChange }) {
15522
+ return /* @__PURE__ */ jsxs13("div", { style: fieldRow, children: [
15523
+ /* @__PURE__ */ jsx24("span", { style: labelStyle, title: label, children: label }),
15524
+ /* @__PURE__ */ jsxs13("div", { style: { display: "flex", gap: 4, flex: 1 }, children: [
15525
+ /* @__PURE__ */ jsx24(
15526
+ "input",
15527
+ {
15528
+ type: "color",
15529
+ value: value.startsWith("#") ? value.slice(0, 7) : "#ffffff",
15530
+ style: { width: 28, height: 22, padding: 1, border: "1px solid #2a3a50", borderRadius: 3, cursor: "pointer" },
15531
+ onChange: (e) => onChange(e.target.value)
15532
+ }
15533
+ ),
15534
+ /* @__PURE__ */ jsx24("input", { type: "text", style: { ...inputBase, flex: 1 }, value, onChange: (e) => onChange(e.target.value) })
15535
+ ] })
15536
+ ] });
15537
+ }
15538
+ function Vec2Field({ label, x, y, onChangeX, onChangeY, step = 1 }) {
15539
+ return /* @__PURE__ */ jsxs13("div", { style: fieldRow, children: [
15540
+ /* @__PURE__ */ jsx24("span", { style: labelStyle, title: label, children: label }),
15541
+ /* @__PURE__ */ jsxs13("div", { style: { display: "flex", gap: 4, flex: 1 }, children: [
15542
+ /* @__PURE__ */ jsx24(
15543
+ "input",
15544
+ {
15545
+ type: "number",
15546
+ style: { ...inputBase, flex: 1 },
15547
+ value: isNaN(x) ? "" : x,
15548
+ step,
15549
+ onChange: (e) => {
15550
+ const v = parseFloat(e.target.value);
15551
+ if (!isNaN(v)) onChangeX(v);
15552
+ }
15553
+ }
15554
+ ),
15555
+ /* @__PURE__ */ jsx24(
15556
+ "input",
15557
+ {
15558
+ type: "number",
15559
+ style: { ...inputBase, flex: 1 },
15560
+ value: isNaN(y) ? "" : y,
15561
+ step,
15562
+ onChange: (e) => {
15563
+ const v = parseFloat(e.target.value);
15564
+ if (!isNaN(v)) onChangeY(v);
15565
+ }
15566
+ }
15567
+ )
15568
+ ] })
15569
+ ] });
15570
+ }
15571
+
15572
+ // ../editor/src/components/EntityInspector.tsx
15573
+ import { Fragment as Fragment10, jsx as jsx25, jsxs as jsxs14 } from "react/jsx-runtime";
15574
+ var panelStyle2 = {
15575
+ background: "#0d1520",
15576
+ borderLeft: "1px solid #1a2a3a",
15577
+ color: "#c0d0e0",
15578
+ fontFamily: "system-ui, sans-serif",
15579
+ fontSize: 12,
15580
+ display: "flex",
15581
+ flexDirection: "column",
15582
+ overflow: "hidden",
15583
+ userSelect: "none"
15584
+ };
15585
+ var headerStyle2 = {
15586
+ padding: "6px 10px",
15587
+ fontSize: 10,
15588
+ fontWeight: 700,
15589
+ textTransform: "uppercase",
15590
+ letterSpacing: 1,
15591
+ color: "#4fc3f7",
15592
+ borderBottom: "1px solid #1a2a3a",
15593
+ flexShrink: 0
15594
+ };
15595
+ var scrollStyle = {
15596
+ flex: 1,
15597
+ overflowY: "auto",
15598
+ padding: "6px 0"
15599
+ };
15600
+ var componentSectionStyle = {
15601
+ marginBottom: 2,
15602
+ borderBottom: "1px solid #131d2a"
15603
+ };
15604
+ var componentHeaderStyle = {
15605
+ display: "flex",
15606
+ alignItems: "center",
15607
+ padding: "4px 10px",
15608
+ fontSize: 10,
15609
+ fontWeight: 700,
15610
+ textTransform: "uppercase",
15611
+ letterSpacing: 0.8,
15612
+ color: "#80a8c0",
15613
+ cursor: "default",
15614
+ background: "#0f1a28"
15615
+ };
15616
+ var fieldsStyle = {
15617
+ padding: "4px 10px 6px"
15618
+ };
15619
+ function isColorKey(key) {
15620
+ const lower = key.toLowerCase();
15621
+ return lower === "color" || lower === "background" || lower === "fill" || lower === "stroke" || lower.includes("color");
15622
+ }
15623
+ function ComponentSection({ comp }) {
15624
+ const compRecord = comp;
15625
+ const entries = Object.entries(comp).filter(([k]) => k !== "type");
15626
+ const rendered = /* @__PURE__ */ new Set();
15627
+ return /* @__PURE__ */ jsxs14("div", { style: componentSectionStyle, children: [
15628
+ /* @__PURE__ */ jsx25("div", { style: componentHeaderStyle, children: comp.type }),
15629
+ /* @__PURE__ */ jsxs14("div", { style: fieldsStyle, children: [
15630
+ entries.map(([key, val]) => {
15631
+ if (rendered.has(key)) return null;
15632
+ if (key === "x" && typeof val === "number" && typeof compRecord["y"] === "number") {
15633
+ rendered.add("x");
15634
+ rendered.add("y");
15635
+ return /* @__PURE__ */ jsx25(
15636
+ Vec2Field,
15637
+ {
15638
+ label: "pos",
15639
+ x: val,
15640
+ y: compRecord["y"],
15641
+ step: 0.5,
15642
+ onChangeX: (v) => {
15643
+ compRecord["x"] = v;
15644
+ },
15645
+ onChangeY: (v) => {
15646
+ compRecord["y"] = v;
15647
+ }
15648
+ },
15649
+ "xy"
15650
+ );
15651
+ }
15652
+ if (key === "followOffsetX" && typeof val === "number") {
15653
+ const oy = compRecord["followOffsetY"];
15654
+ if (typeof oy === "number") {
15655
+ rendered.add("followOffsetX");
15656
+ rendered.add("followOffsetY");
15657
+ return /* @__PURE__ */ jsx25(
15658
+ Vec2Field,
15659
+ {
15660
+ label: "offset",
15661
+ x: val,
15662
+ y: oy,
15663
+ step: 1,
15664
+ onChangeX: (v) => {
15665
+ compRecord["followOffsetX"] = v;
15666
+ },
15667
+ onChangeY: (v) => {
15668
+ compRecord["followOffsetY"] = v;
15669
+ }
15670
+ },
15671
+ "followOffset"
15672
+ );
15673
+ }
15674
+ }
15675
+ if (typeof val === "number") {
15676
+ rendered.add(key);
15677
+ return /* @__PURE__ */ jsx25(
15678
+ NumberField,
15679
+ {
15680
+ label: key,
15681
+ value: val,
15682
+ step: Math.abs(val) < 2 ? 0.01 : 1,
15683
+ onChange: (v) => {
15684
+ compRecord[key] = v;
15685
+ }
15686
+ },
15687
+ key
15688
+ );
15689
+ }
15690
+ if (typeof val === "string" && isColorKey(key)) {
15691
+ rendered.add(key);
15692
+ return /* @__PURE__ */ jsx25(
15693
+ ColorField,
15694
+ {
15695
+ label: key,
15696
+ value: val,
15697
+ onChange: (v) => {
15698
+ compRecord[key] = v;
15699
+ }
15700
+ },
15701
+ key
15702
+ );
15703
+ }
15704
+ if (typeof val === "string") {
15705
+ rendered.add(key);
15706
+ return /* @__PURE__ */ jsx25(
15707
+ TextField,
15708
+ {
15709
+ label: key,
15710
+ value: val,
15711
+ onChange: (v) => {
15712
+ compRecord[key] = v;
15713
+ }
15714
+ },
15715
+ key
15716
+ );
15717
+ }
15718
+ if (typeof val === "boolean") {
15719
+ rendered.add(key);
15720
+ return /* @__PURE__ */ jsx25(
15721
+ BoolField,
15722
+ {
15723
+ label: key,
15724
+ value: val,
15725
+ onChange: (v) => {
15726
+ compRecord[key] = v;
15727
+ }
15728
+ },
15729
+ key
15730
+ );
15731
+ }
15732
+ if (val !== null && val !== void 0 && typeof val !== "function") {
15733
+ rendered.add(key);
15734
+ const preview = typeof val === "object" ? JSON.stringify(val).slice(0, 60) : String(val);
15735
+ return /* @__PURE__ */ jsxs14(
15736
+ "div",
15737
+ {
15738
+ style: {
15739
+ display: "flex",
15740
+ gap: 6,
15741
+ marginBottom: 4,
15742
+ fontSize: 11,
15743
+ alignItems: "flex-start"
15744
+ },
15745
+ children: [
15746
+ /* @__PURE__ */ jsx25(
15747
+ "span",
15748
+ {
15749
+ style: {
15750
+ flex: "0 0 90px",
15751
+ color: "#6a7a8a",
15752
+ textTransform: "uppercase",
15753
+ fontSize: 10,
15754
+ letterSpacing: 0.5,
15755
+ paddingTop: 2
15756
+ },
15757
+ children: key
15758
+ }
15759
+ ),
15760
+ /* @__PURE__ */ jsx25(
15761
+ "span",
15762
+ {
15763
+ style: { flex: 1, color: "#506070", fontFamily: "monospace", wordBreak: "break-all", fontSize: 10 },
15764
+ children: preview
15765
+ }
15766
+ )
15767
+ ]
15768
+ },
15769
+ key
15770
+ );
15771
+ }
15772
+ return null;
15773
+ }),
15774
+ entries.length === 0 && /* @__PURE__ */ jsx25("div", { style: { color: "#3a5060", fontSize: 10 }, children: "No fields" })
15775
+ ] })
15776
+ ] });
15777
+ }
15778
+ function EntityInspector({ entity, components, width = 260, style }) {
15779
+ return /* @__PURE__ */ jsxs14("div", { style: { ...panelStyle2, width, ...style }, children: [
15780
+ /* @__PURE__ */ jsx25("div", { style: headerStyle2, children: "Inspector" }),
15781
+ entity ? /* @__PURE__ */ jsxs14(Fragment10, { children: [
15782
+ /* @__PURE__ */ jsxs14(
15783
+ "div",
15784
+ {
15785
+ style: {
15786
+ padding: "6px 10px",
15787
+ borderBottom: "1px solid #1a2a3a",
15788
+ color: "#d0e0f0",
15789
+ fontSize: 12,
15790
+ fontWeight: 600
15791
+ },
15792
+ children: [
15793
+ entity.name,
15794
+ /* @__PURE__ */ jsxs14("span", { style: { color: "#4a6070", fontSize: 10, marginLeft: 6 }, children: [
15795
+ "#",
15796
+ entity.id
15797
+ ] })
15798
+ ]
15799
+ }
15800
+ ),
15801
+ /* @__PURE__ */ jsxs14("div", { style: scrollStyle, children: [
15802
+ components.map((comp) => /* @__PURE__ */ jsx25(ComponentSection, { comp }, comp.type)),
15803
+ components.length === 0 && /* @__PURE__ */ jsx25("div", { style: { padding: "10px", color: "#3a5060", fontSize: 11, textAlign: "center" }, children: "No components" })
15804
+ ] })
15805
+ ] }) : /* @__PURE__ */ jsx25(
15806
+ "div",
15807
+ {
15808
+ style: {
15809
+ flex: 1,
15810
+ display: "flex",
15811
+ alignItems: "center",
15812
+ justifyContent: "center",
15813
+ color: "#2a4050",
15814
+ fontSize: 11
15815
+ },
15816
+ children: "Select an entity"
15817
+ }
15818
+ )
15819
+ ] });
15820
+ }
15821
+
15822
+ // ../editor/src/hooks/useEditorState.ts
15823
+ import { useState as useState34, useCallback as useCallback37, useContext as useContext81, useEffect as useEffect86, useRef as useRef54 } from "react";
15824
+ function buildEntityInfo(engine) {
15825
+ const nameMap = /* @__PURE__ */ new Map();
15826
+ engine.entityIds.forEach((eid, name) => nameMap.set(eid, name));
15827
+ const ids = engine.ecs.getAllEntityIds();
15828
+ return ids.map((id) => {
15829
+ const comps = engine.ecs.getEntityComponents(id);
15830
+ return {
15831
+ id,
15832
+ name: nameMap.get(id) ?? `Entity #${id}`,
15833
+ componentTypes: comps.map((c) => c.type)
15834
+ };
15835
+ });
15836
+ }
15837
+ function useEditorState(refreshHz = 4) {
15838
+ const engine = useContext81(EngineContext);
15839
+ const [entities, setEntities] = useState34([]);
15840
+ const [selectedId, setSelectedId] = useState34(null);
15841
+ const intervalRef = useRef54(null);
15842
+ const refresh = useCallback37(() => {
15843
+ setEntities(buildEntityInfo(engine));
15844
+ }, [engine]);
15845
+ useEffect86(() => {
15846
+ refresh();
15847
+ intervalRef.current = setInterval(refresh, 1e3 / refreshHz);
15848
+ return () => {
15849
+ if (intervalRef.current !== null) clearInterval(intervalRef.current);
15850
+ };
15851
+ }, [refresh, refreshHz]);
15852
+ const select = useCallback37((id) => {
15853
+ setSelectedId(id);
15854
+ }, []);
15855
+ const selectedComponents = selectedId !== null ? engine.ecs.getEntityComponents(selectedId) : [];
15856
+ return { entities, selectedId, selectedComponents, select, refresh };
15857
+ }
15858
+
15859
+ // ../editor/src/components/EditorShell.tsx
15860
+ import { Fragment as Fragment11, jsx as jsx26, jsxs as jsxs15 } from "react/jsx-runtime";
15861
+ function EditorShell({
15862
+ children,
15863
+ active = true,
15864
+ hierarchyWidth = 220,
15865
+ inspectorWidth = 260,
15866
+ style
15867
+ }) {
15868
+ const state = useEditorState();
15869
+ const selectedEntity = state.entities.find((e) => e.id === state.selectedId) ?? null;
15870
+ if (!active) {
15871
+ return /* @__PURE__ */ jsx26(Fragment11, { children });
15872
+ }
15873
+ const shellStyle = {
15874
+ position: "absolute",
15875
+ inset: 0,
15876
+ display: "flex",
15877
+ flexDirection: "row",
15878
+ pointerEvents: "none",
15879
+ zIndex: 200,
15880
+ ...style
15881
+ };
15882
+ const sideStyle = {
15883
+ pointerEvents: "auto",
15884
+ flexShrink: 0,
15885
+ height: "100%",
15886
+ overflow: "hidden"
15887
+ };
15888
+ return /* @__PURE__ */ jsxs15("div", { style: shellStyle, children: [
15889
+ /* @__PURE__ */ jsx26("div", { style: sideStyle, children: /* @__PURE__ */ jsx26(
15890
+ SceneHierarchy,
15891
+ {
15892
+ entities: state.entities,
15893
+ selectedId: state.selectedId,
15894
+ onSelect: state.select,
15895
+ width: hierarchyWidth,
15896
+ style: { height: "100%" }
15897
+ }
15898
+ ) }),
15899
+ /* @__PURE__ */ jsx26("div", { style: { flex: 1, pointerEvents: "none" }, children }),
15900
+ /* @__PURE__ */ jsx26("div", { style: sideStyle, children: /* @__PURE__ */ jsx26(
15901
+ EntityInspector,
15902
+ {
15903
+ entity: selectedEntity,
15904
+ components: state.selectedComponents,
15905
+ width: inspectorWidth,
15906
+ style: { height: "100%" }
15907
+ }
15908
+ ) })
15909
+ ] });
15910
+ }
15333
15911
  export {
15334
15912
  A11yNode,
15335
15913
  AnimatedSprite,
15336
15914
  Animation,
15337
15915
  Animator,
15338
15916
  AssetLoader,
15917
+ BoolField,
15339
15918
  BoxCollider,
15340
15919
  COLLISION_DYNAMIC_DYNAMIC,
15341
15920
  COLLISION_DYNAMIC_KINEMATIC,
@@ -15351,6 +15930,7 @@ export {
15351
15930
  CircleCollider,
15352
15931
  ClientPrediction,
15353
15932
  CollisionPipeline,
15933
+ ColorField,
15354
15934
  ComboDetector,
15355
15935
  CompoundCollider,
15356
15936
  ConvexCollider,
@@ -15359,7 +15939,9 @@ export {
15359
15939
  DialogueBox,
15360
15940
  Ease,
15361
15941
  EditableText,
15942
+ EditorShell,
15362
15943
  Entity,
15944
+ EntityInspector,
15363
15945
  FocusRing,
15364
15946
  Game,
15365
15947
  Gradient,
@@ -15377,6 +15959,7 @@ export {
15377
15959
  Mask,
15378
15960
  MovingPlatform,
15379
15961
  NineSlice,
15962
+ NumberField,
15380
15963
  PARTICLE_PRESETS,
15381
15964
  ParallaxLayer,
15382
15965
  ParticleEmitter,
@@ -15384,6 +15967,7 @@ export {
15384
15967
  RenderSystem,
15385
15968
  RigidBody,
15386
15969
  Room,
15970
+ SceneHierarchy,
15387
15971
  SceneTransitionOverlay,
15388
15972
  ScreenFlash,
15389
15973
  Script,
@@ -15394,6 +15978,7 @@ export {
15394
15978
  SquashStretch,
15395
15979
  Stage,
15396
15980
  Text,
15981
+ TextField,
15397
15982
  TextureFilter,
15398
15983
  Tilemap,
15399
15984
  Trail,
@@ -15401,6 +15986,7 @@ export {
15401
15986
  TransformHandles,
15402
15987
  TriMeshCollider,
15403
15988
  TriangleCollider,
15989
+ Vec2Field,
15404
15990
  VectorPath,
15405
15991
  VirtualCamera,
15406
15992
  VirtualJoystick,
@@ -15578,6 +16164,7 @@ export {
15578
16164
  useDraggable,
15579
16165
  useDropThrough,
15580
16166
  useDroppable,
16167
+ useEditorState,
15581
16168
  useEntity,
15582
16169
  useEvent,
15583
16170
  useEvents,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cubeforge",
3
- "version": "0.7.0",
3
+ "version": "0.8.1",
4
4
  "description": "React-first 2D browser game engine",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",