@pixi-ui-editor/schema 0.17.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ import { collectLocalizationIssues, createDefaultLocalization, LocalizationSchem
7
7
  export { collectBindingKeys, collectBindingKeyScopes, OWNER_BINDING_KEY_SCOPE } from "./bindingKeys.js";
8
8
  export { rewriteNodeReferences, rewritePrefabInstanceReferencePatches } from "./nodeReferences.js";
9
9
  export { clearNodeAssetReferences } from "./assetReferences.js";
10
- export const CURRENT_SCHEMA_VERSION = 40;
10
+ export const CURRENT_SCHEMA_VERSION = 44;
11
11
  const Id = Type.String({ format: "uuid" });
12
12
  const Name = Type.String({ minLength: 1 });
13
13
  /**
@@ -53,9 +53,11 @@ const GridLayout = Type.Composite([NodeBase, Type.Object({ type: Type.Literal("g
53
53
  // assetId отсутствует — нода без источника: рантайм и редактор рисуют белый прямоугольник её размера.
54
54
  // `nineSlice` отсутствует — обычный Sprite, растянутый по transform (старое поведение, без миграции).
55
55
  // При наличии — рамки в пикселях источника (как @pixi/ui fillPadding), не сериализованные проценты:
56
- // нода `NineSliceSprite`, углы/края не искажаются при растяжении.
56
+ // нода `NineSliceSprite`, углы/края не искажаются при растяжении. `cornerRadius` — третий режим
57
+ // отображения: тот же растянутый Sprite, но с GPU-маской скруглённого прямоугольника в logical pixels.
58
+ // Inspector делает `nineSlice` и `cornerRadius` взаимоисключающими.
57
59
  const ImageTint = Type.String({ pattern: "^#[0-9A-Fa-f]{6}$" });
58
- const Image = Type.Composite([NodeBase, Type.Object({ type: Type.Literal("image"), assetId: Type.Optional(Id), opacity: Type.Optional(Type.Number({ minimum: 0, maximum: 1 })), tint: Type.Optional(ImageTint), nineSlice: Type.Optional(Padding) })]);
60
+ const Image = Type.Composite([NodeBase, Type.Object({ type: Type.Literal("image"), assetId: Type.Optional(Id), opacity: Type.Optional(Type.Number({ minimum: 0, maximum: 1 })), tint: Type.Optional(ImageTint), nineSlice: Type.Optional(Padding), cornerRadius: Type.Optional(Type.Number({ minimum: 0 })) })]);
59
61
  const TextStroke = Type.Object({ color: Type.String({ pattern: "^#[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?$" }), width: Type.Number({ minimum: 0 }) });
60
62
  // Числовые начертания соответствуют CSS/PIXI `font-weight` напрямую (без адаптера): у каждого
61
63
  // значения должен быть загружен свой файл шрифта с тем же `family` (см. FontAsset.weight), иначе
@@ -105,7 +107,7 @@ export function resolveSpineConnectorPositionMultiplier(connector, profile) {
105
107
  const override = profile === "desktop" ? undefined : connector.positionMultiplierOverrides?.mobile;
106
108
  return { x: override?.x ?? base.x, y: override?.y ?? base.y };
107
109
  }
108
- const Spine = Type.Composite([NodeBase, Type.Object({ type: Type.Literal("spine"), assetId: Id, animation: Type.Optional(Type.String({ minLength: 1 })), autoplay: Type.Optional(Type.Boolean()), loop: Type.Optional(Type.Boolean()), animationSpeed: Type.Optional(Type.Number({ exclusiveMinimum: 0 })), spineOverrides: Type.Optional(SpineOverrides), connectors: Type.Optional(Type.Array(SpineConnectorSchema)) })]);
110
+ const Spine = Type.Composite([NodeBase, Type.Object({ type: Type.Literal("spine"), assetId: Id, opacity: Type.Optional(Type.Number({ minimum: 0, maximum: 1 })), animation: Type.Optional(Type.String({ minLength: 1 })), autoplay: Type.Optional(Type.Boolean()), loop: Type.Optional(Type.Boolean()), animationSpeed: Type.Optional(Type.Number({ exclusiveMinimum: 0 })), spineOverrides: Type.Optional(SpineOverrides), connectors: Type.Optional(Type.Array(SpineConnectorSchema)) })]);
109
111
  const BitmapTextAlign = Type.Union([Type.Literal("left"), Type.Literal("center"), Type.Literal("right")]);
110
112
  // A bitmap font's glyphs come pre-rendered from its texture, so unlike `Text` there is no system
111
113
  // fallback: `assetId` is required (like Spine's), and the "Bitmap Text" add-node entry stays disabled
@@ -299,12 +301,6 @@ const Pagination = Type.Composite([NodeBase, Type.Object({
299
301
  inactiveAssetId: Type.Optional(Id),
300
302
  numberStyle: Type.Optional(TextStyleDefinitionSchema),
301
303
  numberActiveColor: Type.Optional(Type.String({ pattern: "^#[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?$" })),
302
- // Optional "pill" background behind the current page's digit only (a plain Sprite, or a
303
- // NineSliceSprite when `numberActiveNineSlice` is set) — sized to `dotSize`, the same field a
304
- // `dots`-style item already uses for its own box. Absent means no background, same as before this
305
- // field existed: just the `numberActiveColor` text highlight.
306
- numberActiveBackgroundAssetId: Type.Optional(Id),
307
- numberActiveNineSlice: Type.Optional(Padding),
308
304
  pageCount: Type.Integer({ minimum: 1 }),
309
305
  defaultPage: Type.Integer({ minimum: 0 }),
310
306
  direction: PaginationDirection,
@@ -318,6 +314,12 @@ const Pagination = Type.Composite([NodeBase, Type.Object({
318
314
  // `NodeAddress`, а не `Id`: стрелка часто лежит внутри инстанса пресета и адресуется путём.
319
315
  prevButtonNodeId: Type.Optional(NodeAddress),
320
316
  nextButtonNodeId: Type.Optional(NodeAddress),
317
+ // An existing authored node wired as the "current page" highlight, the same reference-not-owned
318
+ // contract as the prev/next arrows. The widget renders nothing itself: while `style: "numbers"` it
319
+ // only sets this node's `visible` flag on, so the node's own asset / nine-slice / rounded corners /
320
+ // size / position — authored on the node — are the entire visual. Absent means no highlight beyond
321
+ // `numberActiveColor`. Ignored while `style: "dots"` (the node is kept hidden).
322
+ activeBackgroundNodeId: Type.Optional(NodeAddress),
321
323
  // How many indicator slots are rendered at once. Must be odd, because the active page always sits
322
324
  // in the middle slot: 4 pages with page 3 active render 2-3-4. A slot whose page falls outside
323
325
  // 0..pageCount-1 keeps its `dotSize` footprint and renders nothing, so the active page never moves
@@ -484,6 +486,13 @@ function checkNodeReferences(document, owner, path, issues) {
484
486
  else if (target.type !== "button" && target.type !== "staged-button")
485
487
  add(issues, "INVALID_PAGINATION_ARROW", arrowPath, "A pagination arrow must reference a button or staged-button node.");
486
488
  }
489
+ if (node.activeBackgroundNodeId !== undefined) {
490
+ const backgroundPath = `${path}/paginationActiveBackground/${node.id}`;
491
+ if (!nodes.has(node.activeBackgroundNodeId))
492
+ add(issues, "MISSING_PAGINATION_ACTIVE_BACKGROUND", backgroundPath, `Pagination active background '${node.activeBackgroundNodeId}' does not exist in this owner.`);
493
+ else if (node.activeBackgroundNodeId === node.id)
494
+ add(issues, "INVALID_PAGINATION_ACTIVE_BACKGROUND", backgroundPath, "A pagination cannot use itself as its active background.");
495
+ }
487
496
  continue;
488
497
  }
489
498
  if (node.type === "page-group") {
@@ -755,14 +764,6 @@ function hierarchy(owner, path, assets, prefabs, effects, issues) {
755
764
  else if (asset.type !== "font")
756
765
  add(issues, "INCOMPATIBLE_ASSET_REFERENCE", fontPath, "A pagination numberStyle fontAssetId requires a font asset.");
757
766
  }
758
- if (node.numberActiveBackgroundAssetId !== undefined) {
759
- const asset = assets.get(node.numberActiveBackgroundAssetId);
760
- const bgPath = `${nodePath}/numberActiveBackgroundAssetId`;
761
- if (!asset)
762
- addWarning(issues, "MISSING_ASSET_REFERENCE", bgPath, `Asset '${node.numberActiveBackgroundAssetId}' does not exist.`);
763
- else if (asset.type !== "image")
764
- add(issues, "INCOMPATIBLE_ASSET_REFERENCE", bgPath, "A pagination numberActiveBackgroundAssetId requires an image asset.");
765
- }
766
767
  }
767
768
  if (node.defaultPage > node.pageCount - 1)
768
769
  add(issues, "VALUE_OUT_OF_RANGE", `${nodePath}/defaultPage`, "Pagination defaultPage must be inside the inclusive 0..pageCount-1 range.");
@@ -1296,6 +1297,42 @@ function migrateV32ToV33(document) {
1296
1297
  }
1297
1298
  /** v40 adds sparse bitmap-text presentation overrides; existing values remain the shared base. */
1298
1299
  function migrateV39ToV40(_document) { }
1300
+ /** v41 adds an optional image corner radius; older plain and nine-slice images already retain their meaning. */
1301
+ function migrateV40ToV41(_document) { }
1302
+ /** v42 adds an optional rounded mode to pagination's active number background. */
1303
+ function migrateV41ToV42(_document) { }
1304
+ /**
1305
+ * v43 replaces pagination's inline active-background asset (`numberActiveBackgroundAssetId` plus its
1306
+ * `numberActiveNineSlice`/`numberActiveCornerRadius` rendering fields) with a single
1307
+ * `activeBackgroundNodeId` reference to an authored node. There is no authored node to point the
1308
+ * reference at, so the obsolete fields are simply dropped from every pagination node and sparse patch.
1309
+ */
1310
+ function migrateV42ToV43(document) {
1311
+ const obsolete = ["numberActiveBackgroundAssetId", "numberActiveNineSlice", "numberActiveCornerRadius"];
1312
+ const strip = (record) => { for (const key of obsolete)
1313
+ delete record[key]; };
1314
+ const visit = (node) => {
1315
+ if (node.type === "pagination")
1316
+ strip(node);
1317
+ if (Array.isArray(node.propertyOverrides))
1318
+ for (const override of node.propertyOverrides) {
1319
+ const patch = override.patch;
1320
+ if (typeof patch === "object" && patch !== null)
1321
+ strip(patch);
1322
+ }
1323
+ if (Array.isArray(node.locallyAddedNodes))
1324
+ for (const local of node.locallyAddedNodes)
1325
+ if (typeof local === "object" && local !== null)
1326
+ visit(local);
1327
+ };
1328
+ const owners = [...(document.scenes ?? []), ...(document.prefabs ?? [])];
1329
+ for (const owner of owners)
1330
+ for (const node of owner.nodes ?? [])
1331
+ if (typeof node === "object" && node !== null)
1332
+ visit(node);
1333
+ }
1334
+ /** v44 adds an optional Spine opacity; existing Spine nodes remain fully opaque. */
1335
+ function migrateV43ToV44(_document) { }
1299
1336
  export function migrateProjectDocument(input) {
1300
1337
  if (typeof input !== "object" || input === null || !Object.hasOwn(input, "schemaVersion"))
1301
1338
  throw new ProjectDocumentMigrationError("A schemaVersion is required for migration.");
@@ -1353,6 +1390,14 @@ export function migrateProjectDocument(input) {
1353
1390
  migrateV32ToV33(migrated);
1354
1391
  if (version <= 39)
1355
1392
  migrateV39ToV40(migrated);
1393
+ if (version <= 40)
1394
+ migrateV40ToV41(migrated);
1395
+ if (version <= 41)
1396
+ migrateV41ToV42(migrated);
1397
+ if (version <= 42)
1398
+ migrateV42ToV43(migrated);
1399
+ if (version <= 43)
1400
+ migrateV43ToV44(migrated);
1356
1401
  // v36 → v37 (position multiplier у spine-коннекторов) не требует переписывания данных:
1357
1402
  // добавлены только опциональные поля коннектора, и любой документ v36 уже валиден как v37.
1358
1403
  // v34 → v35 (`states.normalAssetId` стал опциональным у button/staged-button) не требует
@@ -1368,6 +1413,11 @@ export function migrateProjectDocument(input) {
1368
1413
  // v38 → v39 (per-state button tint/opacity) не требует переписывания данных: новые appearance
1369
1414
  // fields optional and therefore resolve to white / 1 for every existing button.
1370
1415
  // v39 → v40 (bitmap-text presentation overrides) likewise adds only optional sparse fields.
1416
+ // v40 → v41 (`image.cornerRadius`) is an optional field; existing plain and nine-slice images
1417
+ // already have their intended rendering mode.
1418
+ // v41 → v42 (`pagination.numberActiveCornerRadius`) is optional; existing active backgrounds
1419
+ // remain plain or nine-slice.
1420
+ // v43 → v44 (`spine.opacity`) is optional; existing Spine nodes remain fully opaque.
1371
1421
  // v37 → v38 (page-group carousel arrows) не требует переписывания данных: добавлены только
1372
1422
  // опциональные поля `pageUpButtonNodeId`/`pageDownButtonNodeId`, любой документ v37 уже валиден как v38.
1373
1423
  migrated.schemaVersion = CURRENT_SCHEMA_VERSION;