@combos-fun/plugin-development-tool 0.0.33 → 0.0.35

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.
@@ -3,6 +3,7 @@ import { Component, System, OBSERVER_TYPE, GameObject, decorators } from '@combo
3
3
  import { RendererSystem } from '@combos-fun/plugin-renderer';
4
4
  import { Graphics } from '@combos-fun/plugin-renderer-graphics';
5
5
  import { Event, HIT_AREA_TYPE } from '@combos-fun/plugin-renderer-event';
6
+ import { IDE_PROPERTY_METADATA } from '@combos-fun/inspector-decorator';
6
7
 
7
8
  /** Toggle development-tool selection / parent postMessage. Payload: `{ enabled: boolean }`. */
8
9
  const COMBOS_DEVELOPMENT_TOOL_SET = 'combos-development-tool:set';
@@ -199,30 +200,17 @@ function isSceneSourceAnchor(v) {
199
200
  typeof v.file === 'string');
200
201
  }
201
202
 
202
- const SKIP_KEYS = new Set([
203
- 'gameObject',
204
- 'name',
205
- 'started',
206
- '__componentDefaultParams',
207
- 'destroyed',
208
- 'inScene',
209
- 'worldTransform',
210
- 'children',
211
- '_parent',
212
- ]);
213
- const COMPONENT_SKIP_KEYS = {
214
- Graphics: new Set(['graphics']),
215
- Physics: new Set([
216
- 'body',
217
- 'Body',
218
- 'PhysicsEngine',
219
- 'World',
220
- 'Constraint',
221
- 'mouseConstraint',
222
- 'bodyParams',
223
- ]),
224
- Event: new Set(['hitArea']),
225
- };
203
+ function resolveValueType(value) {
204
+ if (typeof value === 'string')
205
+ return 'string';
206
+ if (typeof value === 'number')
207
+ return 'number';
208
+ if (typeof value === 'boolean')
209
+ return 'boolean';
210
+ if (value !== null && typeof value === 'object')
211
+ return 'object';
212
+ return 'unsupported';
213
+ }
226
214
  function cloneJsonSafe(value, depth = 0) {
227
215
  if (value === null || value === undefined)
228
216
  return value;
@@ -247,20 +235,171 @@ function cloneJsonSafe(value, depth = 0) {
247
235
  }
248
236
  return undefined;
249
237
  }
238
+ function readFieldReflectSchema(ctor) {
239
+ const properties = Reflect.getMetadata(IDE_PROPERTY_METADATA, ctor) ||
240
+ {};
241
+ return Object.keys(properties).map(key => ({
242
+ key,
243
+ ...properties[key],
244
+ }));
245
+ }
246
+ /** Legacy `@type` / `@step` write `constructor.IDEProps` as an object keyed by property name. */
247
+ function readLegacyIdePropsSchema(ctor) {
248
+ const raw = ctor.IDEProps;
249
+ if (!raw || Array.isArray(raw) || typeof raw !== 'object') {
250
+ return [];
251
+ }
252
+ return Object.keys(raw).map(key => {
253
+ const entry = raw[key] || {};
254
+ return {
255
+ key,
256
+ type: typeof entry.type === 'string' ? entry.type : undefined,
257
+ step: typeof entry.step === 'number' ? entry.step : undefined,
258
+ min: typeof entry.min === 'number' ? entry.min : undefined,
259
+ max: typeof entry.max === 'number' ? entry.max : undefined,
260
+ label: typeof entry.label === 'string' ? entry.label : undefined,
261
+ description: typeof entry.description === 'string' ? entry.description : undefined,
262
+ group: typeof entry.group === 'string' ? entry.group : undefined,
263
+ editor: entry.editor,
264
+ enumOptions: Array.isArray(entry.enumOptions) ? entry.enumOptions : undefined,
265
+ unit: typeof entry.unit === 'string' ? entry.unit : undefined,
266
+ };
267
+ });
268
+ }
269
+ function collectSchemaProps(comp) {
270
+ const ctor = comp.constructor;
271
+ const fromField = readFieldReflectSchema(ctor);
272
+ if (fromField.length > 0) {
273
+ return fromField;
274
+ }
275
+ return readLegacyIdePropsSchema(ctor);
276
+ }
277
+ function mapEditorKind(options, valueType) {
278
+ if (options.editor) {
279
+ return options.editor;
280
+ }
281
+ if (valueType === 'boolean')
282
+ return 'toggle';
283
+ if (options.enumOptions?.length && valueType === 'string')
284
+ return 'enum';
285
+ if (valueType === 'number') {
286
+ if (options.min !== undefined && options.max !== undefined) {
287
+ return 'number-slider';
288
+ }
289
+ return 'number-stepper';
290
+ }
291
+ if (valueType === 'string')
292
+ return 'text';
293
+ return undefined;
294
+ }
295
+ function buildNumberMeta(options) {
296
+ if (options.min === undefined &&
297
+ options.max === undefined &&
298
+ options.step === undefined &&
299
+ options.unit === undefined) {
300
+ return undefined;
301
+ }
302
+ return {
303
+ min: options.min,
304
+ max: options.max,
305
+ step: options.step,
306
+ unit: options.unit,
307
+ };
308
+ }
309
+ function titleCaseLeaf(leaf) {
310
+ return leaf
311
+ .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
312
+ .replace(/_/g, ' ')
313
+ .replace(/^./, char => char.toUpperCase());
314
+ }
315
+ function resolveLabel(options, path) {
316
+ if (options.label?.trim()) {
317
+ return options.label.trim();
318
+ }
319
+ const leaf = path[path.length - 1];
320
+ return leaf ? titleCaseLeaf(leaf) : undefined;
321
+ }
322
+ function leafDescriptorsFromValue(componentName, options, path, value) {
323
+ const description = options.description?.trim();
324
+ if (!description) {
325
+ // Scene Edit only exposes fields that include a human description.
326
+ return [];
327
+ }
328
+ const valueType = resolveValueType(value);
329
+ if (valueType === 'unsupported') {
330
+ return [];
331
+ }
332
+ const typeHint = options.type;
333
+ const isVector = typeHint === 'vector2' ||
334
+ (valueType === 'object' &&
335
+ value !== null &&
336
+ 'x' in value &&
337
+ 'y' in value);
338
+ const isSize = typeHint === 'size' ||
339
+ (valueType === 'object' &&
340
+ value !== null &&
341
+ 'width' in value &&
342
+ 'height' in value);
343
+ if (isVector || isSize) {
344
+ const record = value;
345
+ const axes = isSize ? ['width', 'height'] : ['x', 'y'];
346
+ const baseLabel = resolveLabel(options, path) ?? componentName;
347
+ const out = [];
348
+ for (const axis of axes) {
349
+ const axisValue = record[axis];
350
+ const axisType = resolveValueType(axisValue);
351
+ if (axisType === 'unsupported' || axisType === 'object')
352
+ continue;
353
+ const axisPath = [...path, axis];
354
+ out.push({
355
+ path: axisPath,
356
+ value: axisValue,
357
+ valueType: axisType,
358
+ label: `${baseLabel} ${axis.toUpperCase()}`,
359
+ description,
360
+ group: options.group ?? componentName,
361
+ editorKind: mapEditorKind(options, axisType),
362
+ numberMeta: buildNumberMeta(options),
363
+ persistTarget: componentName === 'Transform' ? 'gameObjectConstructor' : 'component',
364
+ });
365
+ }
366
+ return out;
367
+ }
368
+ if (valueType === 'object') {
369
+ return [];
370
+ }
371
+ const label = resolveLabel(options, path);
372
+ if (!label) {
373
+ return [];
374
+ }
375
+ return [
376
+ {
377
+ path,
378
+ value: cloneJsonSafe(value),
379
+ valueType,
380
+ label,
381
+ description,
382
+ group: options.group ?? componentName,
383
+ editorKind: mapEditorKind(options, valueType),
384
+ enumOptions: options.enumOptions,
385
+ numberMeta: buildNumberMeta(options),
386
+ persistTarget: componentName === 'Transform' ? 'gameObjectConstructor' : 'component',
387
+ },
388
+ ];
389
+ }
250
390
  function serializeComponent(comp) {
251
- const out = {};
252
- const componentSkips = COMPONENT_SKIP_KEYS[comp.name];
253
- for (const key of Object.keys(comp)) {
254
- if (SKIP_KEYS.has(key) || key.startsWith('_'))
255
- continue;
256
- if (componentSkips?.has(key))
257
- continue;
258
- const value = comp[key];
391
+ const componentName = comp.name;
392
+ const schema = collectSchemaProps(comp);
393
+ if (schema.length === 0) {
394
+ return [];
395
+ }
396
+ const out = [];
397
+ const record = comp;
398
+ for (const prop of schema) {
399
+ const value = record[prop.key];
259
400
  if (typeof value === 'function')
260
401
  continue;
261
- const cloned = cloneJsonSafe(value);
262
- if (cloned !== undefined)
263
- out[key] = cloned;
402
+ out.push(...leafDescriptorsFromValue(componentName, prop, [prop.key], value));
264
403
  }
265
404
  return out;
266
405
  }
@@ -279,8 +418,9 @@ function buildGameObjectSnapshot(go) {
279
418
  .filter(c => c.name !== 'CombosDevelopmentToolTarget')
280
419
  .map(c => ({
281
420
  componentName: c.name,
282
- fields: serializeComponent(c),
283
- }));
421
+ fieldDescriptors: serializeComponent(c),
422
+ }))
423
+ .filter(c => c.fieldDescriptors.length > 0);
284
424
  return {
285
425
  id: go.id,
286
426
  name: go.name,
@@ -1233,7 +1373,7 @@ var CombosDevelopmentToolSystem$1 = CombosDevelopmentToolSystem;
1233
1373
  /** Auto-generated by scripts/build-package.mjs — do not edit. */
1234
1374
  Object.assign(CombosDevelopmentToolSystem$1, {
1235
1375
  packageName: "@combos-fun/plugin-development-tool",
1236
- packageVersion: "0.0.33",
1376
+ packageVersion: "0.0.35",
1237
1377
  });
1238
1378
 
1239
1379
  export { COMBOS_DEVELOPMENT_TOOL_APPLY_PROPERTY, COMBOS_DEVELOPMENT_TOOL_CLEAR_SELECTION, COMBOS_DEVELOPMENT_TOOL_GAMEOBJECT_DESELECTED, COMBOS_DEVELOPMENT_TOOL_GAMEOBJECT_SELECTED, COMBOS_DEVELOPMENT_TOOL_MARKER_OVERLAY_SUCCESS, COMBOS_DEVELOPMENT_TOOL_READY, COMBOS_DEVELOPMENT_TOOL_REFRESH, COMBOS_DEVELOPMENT_TOOL_SET, COMBOS_DEVELOPMENT_TOOL_SET_MARKER_OVERLAY, COMBOS_DEVELOPMENT_TOOL_SET_MUTED, COMBOS_DEVELOPMENT_TOOL_SET_SUCCESS, COMBOS_DEVELOPMENT_TOOL_STATE_CHANGED, CombosDevelopmentToolSystem$1 as CombosDevelopmentToolSystem, CombosDevelopmentToolTarget, DEFAULT_ALLOWED_MESSAGE_HOST_SUFFIXES, MARKER_COMPONENTS, MARKER_ICON_SIZE, applyPropertyValue, applyPropertyWithHooks, buildGameObjectDeselectedPayload, buildGameObjectSnapshot, drawMarkerIcon, isAllowedMessageOrigin, isClearSelectionMessage, mergeAllowedMessageOrigins, readPropertyValue, readSourceAnchor, resolveMarkerDef, shouldNotifyGameObjectDeselected };