spoint 0.1.661 → 0.1.662

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.
@@ -10,180 +10,12 @@ import { createEditorEventLog } from './EditorEventLog.js'
10
10
  import { createWorldValidator } from './WorldValidator.js'
11
11
  import { createWaypointTimeline } from './WaypointTimeline.js'
12
12
  import { showToast, setSceneEntityIds } from './EditPanelDOM.js'
13
- import { fetchAssetManifest, ASSET_HOST } from './AssetManifest.js'
13
+ import { ASSET_HOST } from './AssetManifest.js'
14
14
  import { createWindowController } from './wm/WindowController.js'
15
- import { setSharedWM, promptText } from './wm/ui.js'
15
+ import { setSharedWM } from './wm/ui.js'
16
+ import { ADD_PRIMITIVES, buildAddMenuItems, buildPropCategoryItems, buildCategoryMenuItems, loadRecent, recordRecent, filterMenuItems, promptName, _ensureWmCSS, _ensureEditorResponsiveCSS, TABS, EDITOR_SHORTCUTS } from './EditorShellMenus.js'
16
17
  import { MSG } from '/src/protocol/MessageTypes.js'
17
18
 
18
- const ADD_PRIMITIVES = [
19
- { id: 'box-static', label: 'Box' },
20
- { id: 'sphere-static', label: 'Sphere' },
21
- { id: 'capsule-static', label: 'Capsule' },
22
- { id: 'cylinder-static', label: 'Cylinder' }
23
- ]
24
-
25
- function buildAddMenuItems(place, openPropSubmenu, scatterState) {
26
- const scatterLabel = scatterState && scatterState.on
27
- ? '✓ Scatter mode (drag to place many)'
28
- : 'Scatter mode (drag to place many)'
29
- return [
30
- ...(scatterState ? [{ label: scatterLabel, onSelect: () => scatterState.toggle() }] : []),
31
- { label: 'Prop...', onSelect: () => openPropSubmenu() },
32
- ...ADD_PRIMITIVES.map(p => ({ label: p.label, onSelect: () => place(p.id) }))
33
- ]
34
- }
35
-
36
- async function buildPropCategoryItems(onOpenCategory) {
37
- try {
38
- const manifest = await fetchAssetManifest()
39
- const cats = Object.keys(manifest).sort()
40
- // editor-place-menu-thumbnails: category glyph is a graceful fallback differentiator for the
41
- // category-list level (no per-category thumbnail exists in the manifest -- categories are just
42
- // string keys grouping models, see AssetManifest.js/manifest.json shape). Real per-MODEL thumb
43
- // images (manifest[cat][i].thumb, a live gh-pages-hosted PNG, confirmed present on every entry)
44
- // are wired at the model-row level in buildCategoryMenuItems below.
45
- return cats.length
46
- ? cats.map(cat => ({ label: `${_categoryGlyph(cat)} ${cat} (${(manifest[cat] || []).length})`, onSelect: () => onOpenCategory(cat, manifest[cat] || []) }))
47
- : [{ label: '(no props in catalog)', disabled: true }]
48
- } catch (e) {
49
- return [{ label: 'Catalog error: ' + e.message, disabled: true }]
50
- }
51
- }
52
-
53
- // Coarse category->glyph map (text-only fallback differentiator; the manifest has no per-category
54
- // icon/image field, only per-model `thumb`). Deliberately small and approximate -- any unmatched
55
- // category still gets the neutral default glyph rather than nothing.
56
- const _CATEGORY_GLYPHS = [
57
- [/kitchen|appliance|fridge|oven|stove|dish/i, '\u{1F373}'],
58
- [/bath|shower|toilet|sink/i, '\u{1F6BF}'],
59
- [/car|vehicle|truck|van|bus/i, '\u{1F697}'],
60
- [/tree|plant|foliage|flower|grass/i, '\u{1F333}'],
61
- [/rock|stone|boulder/i, '\u{1FAA8}'],
62
- [/chair|couch|sofa|table|desk|furniture|cabinet/i, '\u{1FA91}'],
63
- [/light|lamp/i, '\u{1F4A1}'],
64
- [/weapon|gun/i, '\u{1F52B}'],
65
- [/airport|container|industrial|barrel|dumpster/i, '\u{1F3ED}'],
66
- [/office/i, '\u{1F5C4}️']
67
- ]
68
- function _categoryGlyph(cat) {
69
- for (const [re, glyph] of _CATEGORY_GLYPHS) if (re.test(cat)) return glyph
70
- return '\u{1F4E6}' // generic package/prop glyph default
71
- }
72
-
73
- function buildCategoryMenuItems(models, onPlaceModel, onBack) {
74
- // _thumb carries the real manifest thumbnail URL (or null) through to the post-render DOM
75
- // decoration pass in openAddMenu -- ContextMenu's item shape ({label,onSelect,disabled}) has no
76
- // documented custom-render/icon hook (see openAddMenu's own comment), so the extra _thumb key
77
- // rides along unused by the kit and is read back out by label-text matching after applyDiff.
78
- const items = models.map(m => ({ label: m.name, onSelect: () => onPlaceModel(ASSET_HOST + m.path), _thumb: m.thumb ? ASSET_HOST + m.thumb : null }))
79
- return [{ label: '< Back', onSelect: onBack }, ...items]
80
- }
81
-
82
- // --- Add-menu recent-items tracking (editor-add-menu-recent) ---------------------------------
83
- // localStorage-persisted, keyed by asset url (props) or primitive kind ('box-static' etc).
84
- // Pure functions (recordRecent/loadRecent) so the list/dedupe/cap logic is exec_js-testable
85
- // independent of any DOM/menu wiring.
86
- const RECENT_KEY = 'ds-editor-add-menu-recent'
87
- const RECENT_MAX = 8
88
- function loadRecent() {
89
- try {
90
- const raw = localStorage.getItem(RECENT_KEY)
91
- const arr = raw ? JSON.parse(raw) : []
92
- return Array.isArray(arr) ? arr.filter(r => r && r.key && r.label) : []
93
- } catch (_) { return [] }
94
- }
95
- function recordRecent(entry, existing) {
96
- // entry: {key, label, kind:'primitive'|'prop', value}. Most-recent-first, deduped by key, capped at RECENT_MAX.
97
- const list = (existing || loadRecent()).filter(r => r.key !== entry.key)
98
- list.unshift(entry)
99
- const capped = list.slice(0, RECENT_MAX)
100
- try { localStorage.setItem(RECENT_KEY, JSON.stringify(capped)) } catch (_) {}
101
- return capped
102
- }
103
-
104
- // --- Add-menu substring filter (editor-add-menu-search) --------------------------------------
105
- // Pure: filters a flat item list by substring match on label, case-insensitive.
106
- function filterMenuItems(items, query) {
107
- const q = (query || '').trim().toLowerCase()
108
- if (!q) return items
109
- return items.filter(it => !it.disabled && (it.label || '').toLowerCase().includes(q))
110
- }
111
-
112
- function promptName(wm, { title, label, placeholder, initial = '' } = {}) {
113
- return promptText(wm, {
114
- title, label, placeholder, initial,
115
- validate: (raw) => {
116
- const name = raw.toLowerCase().replace(/[^a-z0-9-]+/g, '-').replace(/^-+|-+$/g, '')
117
- return name ? { ok: true, value: name } : { ok: false, error: (label || 'Name') + ' required' }
118
- }
119
- })
120
- }
121
-
122
- const TABS = ['Inspector', 'Apps', 'HookFlow', 'Events']
123
-
124
- const EDITOR_SHORTCUTS = [
125
- { combo: 'G / W', scope: 'gizmo', label: 'Translate (move) gizmo' },
126
- { combo: 'R / E', scope: 'gizmo', label: 'Rotate gizmo' },
127
- // Alt+S not bare S: bare WASDC drives the fly-camera, would collide with a plain letter shortcut.
128
- { combo: 'Alt+S', scope: 'gizmo', label: 'Scale gizmo' },
129
- { combo: 'F', scope: 'gizmo', label: 'Frame / focus selected entity' },
130
- { combo: 'Delete', scope: 'edit', label: 'Delete selected entity' },
131
- { combo: 'mod+Z', scope: 'history', label: 'Undo' },
132
- { combo: 'mod+Y', scope: 'history', label: 'Redo' },
133
- { combo: 'P', scope: 'editor', label: 'Toggle editor' },
134
- { combo: 'Alt+C', scope: 'debug', label: 'Toggle collider debug wireframe' },
135
- { combo: 'M', scope: 'nav', label: 'Open lobby' },
136
- { combo: 'X', scope: 'gizmo', label: 'Toggle snap-to-grid' },
137
- { combo: 'Y', scope: 'gizmo', label: 'Toggle gizmo space (world / local)' },
138
- { combo: 'Alt+P', scope: 'gizmo', label: 'Cycle multi-select pivot mode (active / centroid / individual)' },
139
- { combo: 'Alt+1..9', scope: 'camera', label: 'Recall camera bookmark N' },
140
- { combo: 'Ctrl+Alt+1..9', scope: 'camera', label: 'Save camera bookmark N' },
141
- { combo: 'Shift/Ctrl+click', scope: 'select', label: 'Add or remove an entity from multi-select' },
142
- { combo: 'Shift/Ctrl+drag (empty space)', scope: 'select', label: 'Marquee box-select entities in view' },
143
- { combo: 'Ctrl+drag Y-axis', scope: 'gizmo', label: 'Snap-to-surface while moving (raycasts down, grid-snap off)' },
144
- { combo: 'mod+C', scope: 'edit', label: 'Copy selected entity (transform + custom props)' },
145
- { combo: 'mod+V', scope: 'edit', label: 'Paste onto the currently-selected entity' },
146
- { combo: 'Arrow keys', scope: 'gizmo', label: 'Nudge selected entity on X/Z (grid step or 0.25)' },
147
- { combo: 'PageUp / PageDown', scope: 'gizmo', label: 'Nudge selected entity on Y' },
148
- { combo: '?', scope: 'editor', label: 'Toggle this shortcuts cheat-sheet' }
149
- ]
150
-
151
- let _wmCssInjected = false
152
- function _ensureWmCSS() {
153
- if (_wmCssInjected) return
154
- _wmCssInjected = true
155
- // Absolute server path, not import.meta.url-relative: import.meta.url of a bundled
156
- // app.js resolves to the bundle's own URL (not this source file's real location),
157
- // which would silently mis-resolve these hrefs to /wm/*.css instead of
158
- // /editor/wm/*.css once client/app.js is bundled by scripts/bundle-client.mjs. The
159
- // editor/ directory is a fixed, server-mounted path (client/editor/wm/*.css), so an
160
- // absolute reference is both bundling-safe and simpler than a relative one.
161
- for (const href of ['/editor/wm/os-token-bridge.css', '/editor/wm/wm.css']) {
162
- const l = document.createElement('link')
163
- l.rel = 'stylesheet'
164
- l.href = href
165
- document.head.appendChild(l)
166
- }
167
- }
168
-
169
- let _editorRespInjected = false
170
- function _ensureEditorResponsiveCSS() {
171
- if (_editorRespInjected) return
172
- _editorRespInjected = true
173
- const style = document.createElement('style')
174
- style.id = 'ds-editor-responsive'
175
- style.textContent = [
176
- '.ep-overlay .app-main{padding:0!important}',
177
- '.ep-overlay .app,.ep-overlay .app-shell{height:100%}',
178
- '.ep-overlay .app-main>*{flex:1;min-height:0}',
179
- '.ep-overlay .ds-ep-toolbar{flex-wrap:wrap;row-gap:4px;column-gap:6px}',
180
- '@media (pointer:coarse){.ep-overlay .ds-ep-tab,.ep-overlay .ds-ep-toolbar button,.ep-overlay .wm-btn,.ep-overlay .ds-ep-tree-row{min-height:44px}}',
181
- '.ds-ep-history-row:hover{background:var(--panel-2,rgba(255,255,255,0.06))}',
182
- '.ds-ep-history-row.current:hover{background:var(--accent-bg,rgba(80,160,255,0.24))}'
183
- ].join('\n')
184
- document.head.appendChild(style)
185
- }
186
-
187
19
  export function createEditPanel({ onPlace, onPlaceModel, onSave, onSaveWorld, onListWorlds, onGizmoModeChange, onGizmoSpaceChange, onPivotModeChange, onEntitySelect, onGetSource, onGetAppFiles, onDestroyEntity, onCreateApp, onSnapChange, onEventLogQuery, onReparent, onRename, onDuplicate, onLockChange, onHiddenChange, onScatterArm, onAlign, onDistribute, onGroup, isSingleplayer, onFsListTree, onFsGetSource, onFsSave, onFsMkdir, onFsDelete, onFsRename, onJumpToHistory, onAddWaypoint, onReorderWaypoints, onToggleMinimapOverlay, onWireCreate, floatingOrigin, onEdgeRemove, onPlaceBatch, onPlaytestStart, onPlaytestStop, onCommandPalette, onDebugModeChange, onOpenP2PRoom, onOpenFreddieChat } = {}) {
188
20
  const overlay = document.createElement('div')
189
21
  overlay.className = 'ds-247420 ep-overlay'
@@ -0,0 +1,183 @@
1
+ // Add-menu / prop-category / recent-items / shortcuts-cheatsheet helpers for EditorShell.js's
2
+ // createEditPanel: stateless (module-level RECENT_KEY/RECENT_MAX localStorage cache aside) menu-item
3
+ // builders, name-prompt validation, and one-time CSS injection. Split out as EditorShell.js's largest
4
+ // self-contained block -- none of these touch createEditPanel's own closure state, only their own
5
+ // params/module-level caches/constants.
6
+
7
+ import { ASSET_HOST, fetchAssetManifest } from './AssetManifest.js'
8
+ import { promptText } from './wm/ui.js'
9
+
10
+ const ADD_PRIMITIVES = [
11
+ { id: 'box-static', label: 'Box' },
12
+ { id: 'sphere-static', label: 'Sphere' },
13
+ { id: 'capsule-static', label: 'Capsule' },
14
+ { id: 'cylinder-static', label: 'Cylinder' }
15
+ ]
16
+
17
+ function buildAddMenuItems(place, openPropSubmenu, scatterState) {
18
+ const scatterLabel = scatterState && scatterState.on
19
+ ? '✓ Scatter mode (drag to place many)'
20
+ : 'Scatter mode (drag to place many)'
21
+ return [
22
+ ...(scatterState ? [{ label: scatterLabel, onSelect: () => scatterState.toggle() }] : []),
23
+ { label: 'Prop...', onSelect: () => openPropSubmenu() },
24
+ ...ADD_PRIMITIVES.map(p => ({ label: p.label, onSelect: () => place(p.id) }))
25
+ ]
26
+ }
27
+
28
+ async function buildPropCategoryItems(onOpenCategory) {
29
+ try {
30
+ const manifest = await fetchAssetManifest()
31
+ const cats = Object.keys(manifest).sort()
32
+ // editor-place-menu-thumbnails: category glyph is a graceful fallback differentiator for the
33
+ // category-list level (no per-category thumbnail exists in the manifest -- categories are just
34
+ // string keys grouping models, see AssetManifest.js/manifest.json shape). Real per-MODEL thumb
35
+ // images (manifest[cat][i].thumb, a live gh-pages-hosted PNG, confirmed present on every entry)
36
+ // are wired at the model-row level in buildCategoryMenuItems below.
37
+ return cats.length
38
+ ? cats.map(cat => ({ label: `${_categoryGlyph(cat)} ${cat} (${(manifest[cat] || []).length})`, onSelect: () => onOpenCategory(cat, manifest[cat] || []) }))
39
+ : [{ label: '(no props in catalog)', disabled: true }]
40
+ } catch (e) {
41
+ return [{ label: 'Catalog error: ' + e.message, disabled: true }]
42
+ }
43
+ }
44
+
45
+ // Coarse category->glyph map (text-only fallback differentiator; the manifest has no per-category
46
+ // icon/image field, only per-model `thumb`). Deliberately small and approximate -- any unmatched
47
+ // category still gets the neutral default glyph rather than nothing.
48
+ const _CATEGORY_GLYPHS = [
49
+ [/kitchen|appliance|fridge|oven|stove|dish/i, '\u{1F373}'],
50
+ [/bath|shower|toilet|sink/i, '\u{1F6BF}'],
51
+ [/car|vehicle|truck|van|bus/i, '\u{1F697}'],
52
+ [/tree|plant|foliage|flower|grass/i, '\u{1F333}'],
53
+ [/rock|stone|boulder/i, '\u{1FAA8}'],
54
+ [/chair|couch|sofa|table|desk|furniture|cabinet/i, '\u{1FA91}'],
55
+ [/light|lamp/i, '\u{1F4A1}'],
56
+ [/weapon|gun/i, '\u{1F52B}'],
57
+ [/airport|container|industrial|barrel|dumpster/i, '\u{1F3ED}'],
58
+ [/office/i, '\u{1F5C4}️']
59
+ ]
60
+ function _categoryGlyph(cat) {
61
+ for (const [re, glyph] of _CATEGORY_GLYPHS) if (re.test(cat)) return glyph
62
+ return '\u{1F4E6}' // generic package/prop glyph default
63
+ }
64
+
65
+ function buildCategoryMenuItems(models, onPlaceModel, onBack) {
66
+ // _thumb carries the real manifest thumbnail URL (or null) through to the post-render DOM
67
+ // decoration pass in openAddMenu -- ContextMenu's item shape ({label,onSelect,disabled}) has no
68
+ // documented custom-render/icon hook (see openAddMenu's own comment), so the extra _thumb key
69
+ // rides along unused by the kit and is read back out by label-text matching after applyDiff.
70
+ const items = models.map(m => ({ label: m.name, onSelect: () => onPlaceModel(ASSET_HOST + m.path), _thumb: m.thumb ? ASSET_HOST + m.thumb : null }))
71
+ return [{ label: '< Back', onSelect: onBack }, ...items]
72
+ }
73
+
74
+ // --- Add-menu recent-items tracking (editor-add-menu-recent) ---------------------------------
75
+ // localStorage-persisted, keyed by asset url (props) or primitive kind ('box-static' etc).
76
+ // Pure functions (recordRecent/loadRecent) so the list/dedupe/cap logic is exec_js-testable
77
+ // independent of any DOM/menu wiring.
78
+ const RECENT_KEY = 'ds-editor-add-menu-recent'
79
+ const RECENT_MAX = 8
80
+ function loadRecent() {
81
+ try {
82
+ const raw = localStorage.getItem(RECENT_KEY)
83
+ const arr = raw ? JSON.parse(raw) : []
84
+ return Array.isArray(arr) ? arr.filter(r => r && r.key && r.label) : []
85
+ } catch (_) { return [] }
86
+ }
87
+ function recordRecent(entry, existing) {
88
+ // entry: {key, label, kind:'primitive'|'prop', value}. Most-recent-first, deduped by key, capped at RECENT_MAX.
89
+ const list = (existing || loadRecent()).filter(r => r.key !== entry.key)
90
+ list.unshift(entry)
91
+ const capped = list.slice(0, RECENT_MAX)
92
+ try { localStorage.setItem(RECENT_KEY, JSON.stringify(capped)) } catch (_) {}
93
+ return capped
94
+ }
95
+
96
+ // --- Add-menu substring filter (editor-add-menu-search) --------------------------------------
97
+ // Pure: filters a flat item list by substring match on label, case-insensitive.
98
+ function filterMenuItems(items, query) {
99
+ const q = (query || '').trim().toLowerCase()
100
+ if (!q) return items
101
+ return items.filter(it => !it.disabled && (it.label || '').toLowerCase().includes(q))
102
+ }
103
+
104
+ function promptName(wm, { title, label, placeholder, initial = '' } = {}) {
105
+ return promptText(wm, {
106
+ title, label, placeholder, initial,
107
+ validate: (raw) => {
108
+ const name = raw.toLowerCase().replace(/[^a-z0-9-]+/g, '-').replace(/^-+|-+$/g, '')
109
+ return name ? { ok: true, value: name } : { ok: false, error: (label || 'Name') + ' required' }
110
+ }
111
+ })
112
+ }
113
+
114
+ const TABS = ['Inspector', 'Apps', 'HookFlow', 'Events']
115
+
116
+ const EDITOR_SHORTCUTS = [
117
+ { combo: 'G / W', scope: 'gizmo', label: 'Translate (move) gizmo' },
118
+ { combo: 'R / E', scope: 'gizmo', label: 'Rotate gizmo' },
119
+ // Alt+S not bare S: bare WASDC drives the fly-camera, would collide with a plain letter shortcut.
120
+ { combo: 'Alt+S', scope: 'gizmo', label: 'Scale gizmo' },
121
+ { combo: 'F', scope: 'gizmo', label: 'Frame / focus selected entity' },
122
+ { combo: 'Delete', scope: 'edit', label: 'Delete selected entity' },
123
+ { combo: 'mod+Z', scope: 'history', label: 'Undo' },
124
+ { combo: 'mod+Y', scope: 'history', label: 'Redo' },
125
+ { combo: 'P', scope: 'editor', label: 'Toggle editor' },
126
+ { combo: 'Alt+C', scope: 'debug', label: 'Toggle collider debug wireframe' },
127
+ { combo: 'M', scope: 'nav', label: 'Open lobby' },
128
+ { combo: 'X', scope: 'gizmo', label: 'Toggle snap-to-grid' },
129
+ { combo: 'Y', scope: 'gizmo', label: 'Toggle gizmo space (world / local)' },
130
+ { combo: 'Alt+P', scope: 'gizmo', label: 'Cycle multi-select pivot mode (active / centroid / individual)' },
131
+ { combo: 'Alt+1..9', scope: 'camera', label: 'Recall camera bookmark N' },
132
+ { combo: 'Ctrl+Alt+1..9', scope: 'camera', label: 'Save camera bookmark N' },
133
+ { combo: 'Shift/Ctrl+click', scope: 'select', label: 'Add or remove an entity from multi-select' },
134
+ { combo: 'Shift/Ctrl+drag (empty space)', scope: 'select', label: 'Marquee box-select entities in view' },
135
+ { combo: 'Ctrl+drag Y-axis', scope: 'gizmo', label: 'Snap-to-surface while moving (raycasts down, grid-snap off)' },
136
+ { combo: 'mod+C', scope: 'edit', label: 'Copy selected entity (transform + custom props)' },
137
+ { combo: 'mod+V', scope: 'edit', label: 'Paste onto the currently-selected entity' },
138
+ { combo: 'Arrow keys', scope: 'gizmo', label: 'Nudge selected entity on X/Z (grid step or 0.25)' },
139
+ { combo: 'PageUp / PageDown', scope: 'gizmo', label: 'Nudge selected entity on Y' },
140
+ { combo: '?', scope: 'editor', label: 'Toggle this shortcuts cheat-sheet' }
141
+ ]
142
+
143
+ let _wmCssInjected = false
144
+ function _ensureWmCSS() {
145
+ if (_wmCssInjected) return
146
+ _wmCssInjected = true
147
+ // Absolute server path, not import.meta.url-relative: import.meta.url of a bundled
148
+ // app.js resolves to the bundle's own URL (not this source file's real location),
149
+ // which would silently mis-resolve these hrefs to /wm/*.css instead of
150
+ // /editor/wm/*.css once client/app.js is bundled by scripts/bundle-client.mjs. The
151
+ // editor/ directory is a fixed, server-mounted path (client/editor/wm/*.css), so an
152
+ // absolute reference is both bundling-safe and simpler than a relative one.
153
+ for (const href of ['/editor/wm/os-token-bridge.css', '/editor/wm/wm.css']) {
154
+ const l = document.createElement('link')
155
+ l.rel = 'stylesheet'
156
+ l.href = href
157
+ document.head.appendChild(l)
158
+ }
159
+ }
160
+
161
+ let _editorRespInjected = false
162
+ function _ensureEditorResponsiveCSS() {
163
+ if (_editorRespInjected) return
164
+ _editorRespInjected = true
165
+ const style = document.createElement('style')
166
+ style.id = 'ds-editor-responsive'
167
+ style.textContent = [
168
+ '.ep-overlay .app-main{padding:0!important}',
169
+ '.ep-overlay .app,.ep-overlay .app-shell{height:100%}',
170
+ '.ep-overlay .app-main>*{flex:1;min-height:0}',
171
+ '.ep-overlay .ds-ep-toolbar{flex-wrap:wrap;row-gap:4px;column-gap:6px}',
172
+ '@media (pointer:coarse){.ep-overlay .ds-ep-tab,.ep-overlay .ds-ep-toolbar button,.ep-overlay .wm-btn,.ep-overlay .ds-ep-tree-row{min-height:44px}}',
173
+ '.ds-ep-history-row:hover{background:var(--panel-2,rgba(255,255,255,0.06))}',
174
+ '.ds-ep-history-row.current:hover{background:var(--accent-bg,rgba(80,160,255,0.24))}'
175
+ ].join('\n')
176
+ document.head.appendChild(style)
177
+ }
178
+
179
+ export {
180
+ ADD_PRIMITIVES, buildAddMenuItems, buildPropCategoryItems, buildCategoryMenuItems,
181
+ loadRecent, recordRecent, filterMenuItems, promptName,
182
+ _ensureWmCSS, _ensureEditorResponsiveCSS, TABS, EDITOR_SHORTCUTS
183
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spoint",
3
- "version": "0.1.661",
3
+ "version": "0.1.662",
4
4
  "description": "Physics and netcode SDK for multiplayer game servers",
5
5
  "type": "module",
6
6
  "workspaces": [