From 23b288edc8463b592932d0a21f555612032e8eab Mon Sep 17 00:00:00 2001 From: Davide Tantillo Date: Tue, 20 May 2025 14:52:43 +0200 Subject: [PATCH 1/5] [ts][webcomponents] Removed mode attribute. Moved mode origin as fit type. --- .../spine-webcomponents/example/tutorial.html | 9 ++------- .../src/SpineWebComponentOverlay.ts | 19 +++++++++---------- .../src/SpineWebComponentSkeleton.ts | 15 ++------------- spine-ts/spine-webcomponents/src/wcUtils.ts | 15 ++++----------- 4 files changed, 17 insertions(+), 41 deletions(-) diff --git a/spine-ts/spine-webcomponents/example/tutorial.html b/spine-ts/spine-webcomponents/example/tutorial.html index bc9287393..20d721234 100644 --- a/spine-ts/spine-webcomponents/example/tutorial.html +++ b/spine-ts/spine-webcomponents/example/tutorial.html @@ -231,11 +231,11 @@ >
- If you want to preserve the original scale, you can use fit="none". + If you want to preserve the original scale, you can use fit="none" (center the bounds) or fit="origin" (center the skeleton origin). In combination with that, you can use the scale attribute to set your desired scale.

- Other fit modes are width, height, cover, and scaleDown. + Other fit modes are width, height, cover, scaleDown..
@@ -363,11 +363,6 @@
- The origin mode centers the animation's world origin with the center of the HTML element. -
- You are responsible for scaling the skeleton when using this mode. -
-
Move the origin by a percentage of the div's width and height using the x-axis and y-axis attributes, respectively.
diff --git a/spine-ts/spine-webcomponents/src/SpineWebComponentOverlay.ts b/spine-ts/spine-webcomponents/src/SpineWebComponentOverlay.ts index 56fe8d5a1..3fb7b38eb 100644 --- a/spine-ts/spine-webcomponents/src/SpineWebComponentOverlay.ts +++ b/spine-ts/spine-webcomponents/src/SpineWebComponentOverlay.ts @@ -483,7 +483,7 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr const tempVector = new Vector3(); for (const widget of this.widgets) { - const { skeleton, pma, bounds, mode, debug, offsetX, offsetY, xAxis, yAxis, dragX, dragY, fit, noSpinner, loading, clip, isDraggable } = widget; + const { skeleton, pma, bounds, debug, offsetX, offsetY, dragX, dragY, fit, noSpinner, loading, clip, isDraggable } = widget; if (widget.isOffScreenAndWasMoved()) continue; const elementRef = widget.getHostElement(); @@ -497,7 +497,7 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr divBounds.y -= offsetTopForOverlay; } - const { padLeft, padRight, padTop, padBottom } = widget + const { padLeft, padRight, padTop, padBottom, xAxis, yAxis } = widget const paddingShiftHorizontal = (padLeft - padRight) / 2; const paddingShiftVertical = (padTop - padBottom) / 2; @@ -525,7 +525,7 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr } if (skeleton) { - if (mode === "inside") { + if (fit !== "origin") { let { x: ax, y: ay, width: aw, height: ah } = bounds; if (aw <= 0 || ah <= 0) continue; @@ -591,8 +591,9 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr } } - const worldOffsetX = divOriginX + offsetX + dragX; - const worldOffsetY = divOriginY + offsetY + dragY; + // const worldOffsetX = divOriginX + offsetX + dragX; + const worldOffsetX = divOriginX + offsetX * window.devicePixelRatio + dragX; + const worldOffsetY = divOriginY + offsetY * window.devicePixelRatio + dragY; widget.worldX = worldOffsetX; widget.worldY = worldOffsetY; @@ -634,12 +635,10 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr renderer.circle(true, root.x + worldOffsetX, root.y + worldOffsetY, 10, red); // show shifted origin - const originX = worldOffsetX - dragX - offsetX; - const originY = worldOffsetY - dragY - offsetY; - renderer.circle(true, originX, originY, 10, green); + renderer.circle(true, divOriginX, divOriginY, 10, green); // show line from origin to bounds center - renderer.line(originX, originY, bbCenterX, bbCenterY, green); + renderer.line(divOriginX, divOriginY, bbCenterX, bbCenterY, green); } if (clip) endScissor(); @@ -951,7 +950,7 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr private updateWidgetScales () { for (const widget of this.widgets) { // inside mode scale automatically to fit the skeleton within its parent - if (widget.mode !== "origin" && widget.fit !== "none") continue; + if (widget.fit !== "origin" && widget.fit !== "none") continue; const skeleton = widget.skeleton; if (!skeleton) continue; diff --git a/spine-ts/spine-webcomponents/src/SpineWebComponentSkeleton.ts b/spine-ts/spine-webcomponents/src/SpineWebComponentSkeleton.ts index c9644e1ce..29a576fc2 100644 --- a/spine-ts/spine-webcomponents/src/SpineWebComponentSkeleton.ts +++ b/spine-ts/spine-webcomponents/src/SpineWebComponentSkeleton.ts @@ -56,8 +56,7 @@ import { SpineWebComponentOverlay } from "./SpineWebComponentOverlay.js"; type UpdateSpineWidgetFunction = (delta: number, skeleton: Skeleton, state: AnimationState) => void; export type OffScreenUpdateBehaviourType = "pause" | "update" | "pose"; -export type ModeType = "inside" | "origin"; -export type FitType = "fill" | "width" | "height" | "contain" | "cover" | "none" | "scaleDown"; +export type FitType = "fill" | "width" | "height" | "contain" | "cover" | "none" | "scaleDown" | "origin"; export type AnimationsInfo = Record Date: Tue, 20 May 2025 15:35:31 +0200 Subject: [PATCH 2/5] [ts][webcomponents] skin accepts a list of skins. --- .../spine-webcomponents/example/tutorial.html | 6 +++-- .../src/SpineWebComponentSkeleton.ts | 25 +++++++++++++------ 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/spine-ts/spine-webcomponents/example/tutorial.html b/spine-ts/spine-webcomponents/example/tutorial.html index 20d721234..35f76edc4 100644 --- a/spine-ts/spine-webcomponents/example/tutorial.html +++ b/spine-ts/spine-webcomponents/example/tutorial.html @@ -370,7 +370,7 @@ atlas="/assets/vine-pma.atlas" skeleton="/assets/vine-pro.skel" animation="grow" - mode="origin" + fit="origin" scale=".5" y-axis="-.5" > @@ -385,7 +385,7 @@ atlas="/assets/vine-pma.atlas" skeleton="/assets/vine-pro.skel" animation="grow" - mode="origin" + fit="origin" scale=".5" y-axis="-.5" > @@ -1422,6 +1422,8 @@ function toggleSpinner(element) {
It's very easy to display your different skins and animations. Simply create a table and use the skin and animation attributes. +
+ skin accepts an comma separated list of skin names. The skins will be combined in a new one, from the first to the last. If multiple skins set the same slot, the latest in the list will be used.
diff --git a/spine-ts/spine-webcomponents/src/SpineWebComponentSkeleton.ts b/spine-ts/spine-webcomponents/src/SpineWebComponentSkeleton.ts index 29a576fc2..7d41a35c8 100644 --- a/spine-ts/spine-webcomponents/src/SpineWebComponentSkeleton.ts +++ b/spine-ts/spine-webcomponents/src/SpineWebComponentSkeleton.ts @@ -49,6 +49,7 @@ import { RegionAttachment, MeshAttachment, Bone, + Skin, } from "@esotericsoftware/spine-webgl"; import { AttributeTypes, castValue, isBase64, Rectangle } from "./wcUtils.js"; import { SpineWebComponentOverlay } from "./SpineWebComponentOverlay.js"; @@ -76,7 +77,7 @@ interface WidgetAttributes { animation?: string animations?: AnimationsInfo defaultMix?: number - skin?: string + skin?: string[] fit: FitType xAxis: number yAxis: number @@ -219,14 +220,14 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable * Optional: The name of the skin to be set * Connected to `skin` attribute. */ - public get skin (): string | undefined { + public get skin (): string[] | undefined { return this._skin; } - public set skin (value: string | undefined) { + public set skin (value: string[] | undefined) { this._skin = value; this.initWidget(); } - private _skin?: string + private _skin?: string[] /** * Specify the way the skeleton is sized within the element automatically changing its `scaleX` and `scaleY`. @@ -695,7 +696,7 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable animations: { propertyName: "animations", type: "animationsInfo", defaultValue: undefined }, "animation-bounds": { propertyName: "animationsBound", type: "array-string", defaultValue: undefined }, "default-mix": { propertyName: "defaultMix", type: "number", defaultValue: 0 }, - skin: { propertyName: "skin", type: "string" }, + skin: { propertyName: "skin", type: "array-string" }, width: { propertyName: "width", type: "number", defaultValue: -1 }, height: { propertyName: "height", type: "number", defaultValue: -1 }, isdraggable: { propertyName: "isDraggable", type: "boolean" }, @@ -997,18 +998,28 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable // skeleton.scaleX = this.dprScale; // skeleton.scaleY = this.dprScale; + this.loading = false; + // the bounds are calculated the first time, if no custom bound is provided this.initWidget(this.bounds.width <= 0 || this.bounds.height <= 0); - this.loading = false; return this; } private initWidget (forceRecalculate = false) { + if (this.loading) return; + const { skeleton, state, animation, animations: animationsInfo, skin, defaultMix } = this; if (skin) { - skeleton?.setSkinByName(skin); + if (skin.length === 1) { + skeleton?.setSkinByName(skin[0]); + } else { + const customSkin = new Skin("custom"); + for (const s of skin) customSkin.addSkin(skeleton?.data.findSkin(s) as Skin); + skeleton?.setSkin(customSkin); + } + skeleton?.setSlotsToSetupPose(); } From b588b95d76dc3d6e5e50b00e473b8935fdc79d62 Mon Sep 17 00:00:00 2001 From: Davide Tantillo Date: Tue, 20 May 2025 16:11:47 +0200 Subject: [PATCH 3/5] [ts][webcomponents] followAttachmentAttach to followVisibility. --- .../spine-webcomponents/example/tutorial.html | 36 +++++++++---------- .../src/SpineWebComponentOverlay.ts | 4 +-- .../src/SpineWebComponentSkeleton.ts | 10 +++--- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/spine-ts/spine-webcomponents/example/tutorial.html b/spine-ts/spine-webcomponents/example/tutorial.html index 35f76edc4..90a3449d0 100644 --- a/spine-ts/spine-webcomponents/example/tutorial.html +++ b/spine-ts/spine-webcomponents/example/tutorial.html @@ -1423,7 +1423,7 @@ function toggleSpinner(element) {
It's very easy to display your different skins and animations. Simply create a table and use the skin and animation attributes.
- skin accepts an comma separated list of skin names. The skins will be combined in a new one, from the first to the last. If multiple skins set the same slot, the latest in the list will be used. + skin accepts a comma separated list of skin names. The skins will be combined in a new one, from the first to the last. If multiple skins set the same slot, the latest in the list will be used.
@@ -3276,7 +3276,7 @@ const darkPicker = document.getElementById("dark-picker");
  • followOpacity: the element opacity is connected to the slot alpha
  • followScale: the element scale is connected to the slot scale
  • followRotation: the element rotation is connected to the slot rotation
  • -
  • followAttachmentAttach: the element is shown/hidden depending if the slot contains an attachment or not
  • +
  • followVisibility: the element is shown/hidden depending if the slot contains an attachment or not
  • hideAttachment: the slot attachment is hidden as if the element replaced the attachment
  • @@ -3301,10 +3301,10 @@ const darkPicker = document.getElementById("dark-picker"); @@ -3328,10 +3328,10 @@ const darkPicker = document.getElementById("dark-picker"); (async () => { const widget = await spine.getSpineWidget("potty").whenReady; - widget.followSlot("rain/rain-color", document.getElementById("rain/rain-color"), { followAttachmentAttach: false, hideAttachment: true }); - widget.followSlot("rain/rain-white", document.getElementById("rain/rain-white"), { followAttachmentAttach: false, hideAttachment: true }); - widget.followSlot("rain/rain-blue", document.getElementById("rain/rain-blue"), { followAttachmentAttach: false, hideAttachment: true }); - widget.followSlot("rain/rain-green", document.getElementById("rain/rain-green"), { followAttachmentAttach: false, hideAttachment: true }); + widget.followSlot("rain/rain-color", document.getElementById("rain/rain-color"), { followVisibility: false, hideAttachment: true }); + widget.followSlot("rain/rain-white", document.getElementById("rain/rain-white"), { followVisibility: false, hideAttachment: true }); + widget.followSlot("rain/rain-blue", document.getElementById("rain/rain-blue"), { followVisibility: false, hideAttachment: true }); + widget.followSlot("rain/rain-green", document.getElementById("rain/rain-green"), { followVisibility: false, hideAttachment: true }); })();`);
    @@ -3375,10 +3375,10 @@ const darkPicker = document.getElementById("dark-picker"); @@ -3402,10 +3402,10 @@ const darkPicker = document.getElementById("dark-picker"); (async () => { const widget = await spine.getSpineWidget("potty2").whenReady; - widget.followSlot("rain/rain-color", spine.getSpineWidget("potty2-1"), { followAttachmentAttach: false, hideAttachment: true }); - widget.followSlot("rain/rain-white", spine.getSpineWidget("potty2-2"), { followAttachmentAttach: false, hideAttachment: true }); - widget.followSlot("rain/rain-blue", spine.getSpineWidget("potty2-3"), { followAttachmentAttach: false, hideAttachment: true }); - widget.followSlot("rain/rain-green", spine.getSpineWidget("potty2-4"), { followAttachmentAttach: false, hideAttachment: true }); + widget.followSlot("rain/rain-color", spine.getSpineWidget("potty2-1"), { followVisibility: false, hideAttachment: true }); + widget.followSlot("rain/rain-white", spine.getSpineWidget("potty2-2"), { followVisibility: false, hideAttachment: true }); + widget.followSlot("rain/rain-blue", spine.getSpineWidget("potty2-3"), { followVisibility: false, hideAttachment: true }); + widget.followSlot("rain/rain-green", spine.getSpineWidget("potty2-4"), { followVisibility: false, hideAttachment: true }); })();`);
    diff --git a/spine-ts/spine-webcomponents/src/SpineWebComponentOverlay.ts b/spine-ts/spine-webcomponents/src/SpineWebComponentOverlay.ts index 3fb7b38eb..8ef026d50 100644 --- a/spine-ts/spine-webcomponents/src/SpineWebComponentOverlay.ts +++ b/spine-ts/spine-webcomponents/src/SpineWebComponentOverlay.ts @@ -653,7 +653,7 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr if (widget.isOffScreenAndWasMoved() || !widget.skeleton) continue; for (const boneFollower of widget.boneFollowerList) { - const { slot, bone, element, followAttachmentAttach, followRotation, followOpacity, followScale } = boneFollower; + const { slot, bone, element, followVisibility, followRotation, followOpacity, followScale } = boneFollower; const { worldX, worldY } = widget; this.worldToScreen(this.tempFollowBoneVector, bone.worldX + worldX, bone.worldY + worldY); @@ -674,7 +674,7 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr element.style.display = "" - if (followAttachmentAttach && !slot.attachment) { + if (followVisibility && !slot.attachment) { element.style.opacity = "0"; } else if (followOpacity) { element.style.opacity = `${slot.color.a}`; diff --git a/spine-ts/spine-webcomponents/src/SpineWebComponentSkeleton.ts b/spine-ts/spine-webcomponents/src/SpineWebComponentSkeleton.ts index 7d41a35c8..e4dcab519 100644 --- a/spine-ts/spine-webcomponents/src/SpineWebComponentSkeleton.ts +++ b/spine-ts/spine-webcomponents/src/SpineWebComponentSkeleton.ts @@ -1016,7 +1016,7 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable skeleton?.setSkinByName(skin[0]); } else { const customSkin = new Skin("custom"); - for (const s of skin) customSkin.addSkin(skeleton?.data.findSkin(s) as Skin); + for (const s of skin) customSkin.addSkin(skeleton?.data.findSkin(s) as Skin); skeleton?.setSkin(customSkin); } @@ -1231,10 +1231,10 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable * Other utilities */ - public boneFollowerList: Array<{ slot: Slot, bone: Bone, element: HTMLElement, followAttachmentAttach: boolean, followRotation: boolean, followOpacity: boolean, followScale: boolean, hideAttachment: boolean }> = []; - public followSlot (slotName: string | Slot, element: HTMLElement, options: { followAttachmentAttach?: boolean, followRotation?: boolean, followOpacity?: boolean, followScale?: boolean, hideAttachment?: boolean } = {}) { + public boneFollowerList: Array<{ slot: Slot, bone: Bone, element: HTMLElement, followVisibility: boolean, followRotation: boolean, followOpacity: boolean, followScale: boolean, hideAttachment: boolean }> = []; + public followSlot (slotName: string | Slot, element: HTMLElement, options: { followVisibility?: boolean, followRotation?: boolean, followOpacity?: boolean, followScale?: boolean, hideAttachment?: boolean } = {}) { const { - followAttachmentAttach = false, + followVisibility = false, followRotation = true, followOpacity = true, followScale = true, @@ -1253,7 +1253,7 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable element.style.left = '0px'; element.style.display = 'none'; - this.boneFollowerList.push({ slot, bone: slot.bone, element, followAttachmentAttach, followRotation, followOpacity, followScale, hideAttachment }); + this.boneFollowerList.push({ slot, bone: slot.bone, element, followVisibility, followRotation, followOpacity, followScale, hideAttachment }); this.overlay.addSlotFollowerElement(element); } public unfollowSlot (element: HTMLElement): HTMLElement | undefined { From 335d0ed17fecd2880283d3f4b1bfc65c1ac99f56 Mon Sep 17 00:00:00 2001 From: Davide Tantillo Date: Tue, 20 May 2025 17:21:13 +0200 Subject: [PATCH 4/5] [ts][webcomponents] holdDurationLastAnimation to repeatDelay. --- spine-ts/spine-webcomponents/example/tutorial.html | 4 ++-- .../src/SpineWebComponentSkeleton.ts | 8 ++++---- spine-ts/spine-webcomponents/src/wcUtils.ts | 14 +++++++------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/spine-ts/spine-webcomponents/example/tutorial.html b/spine-ts/spine-webcomponents/example/tutorial.html index 90a3449d0..6fdbbf9d0 100644 --- a/spine-ts/spine-webcomponents/example/tutorial.html +++ b/spine-ts/spine-webcomponents/example/tutorial.html @@ -767,11 +767,11 @@
  • mixDuration: the mix duration between this animation and the previous one (not used for the first animation on a track)
  • -

    To loop a track once it reaches the end, add the special group [loop, trackNumber, holdDurationLastAnimation], where:

    +

    To loop a track once it reaches the end, add the special group [loop, trackNumber, repeatDelay], where:

    • loop: identifies this as a loop instruction
    • trackNumber: the number of the track to loop
    • -
    • holdDurationLastAnimation: the number of seconds to wait after the last animation is completed before repeating the loop
    • +
    • repeatDelay: the number of seconds to wait after the last animation is completed before repeating the loop

    The parameters of the first group on each track are passed to the setAnimation method, while the remaining groups use addAnimation.

    diff --git a/spine-ts/spine-webcomponents/src/SpineWebComponentSkeleton.ts b/spine-ts/spine-webcomponents/src/SpineWebComponentSkeleton.ts index e4dcab519..34d17dba0 100644 --- a/spine-ts/spine-webcomponents/src/SpineWebComponentSkeleton.ts +++ b/spine-ts/spine-webcomponents/src/SpineWebComponentSkeleton.ts @@ -60,7 +60,7 @@ export type OffScreenUpdateBehaviourType = "pause" | "update" | "pose"; export type FitType = "fill" | "width" | "height" | "contain" | "cover" | "none" | "scaleDown" | "origin"; export type AnimationsInfo = Record }>; export type AnimationsType = { animationName: string | "#EMPTY#", loop?: boolean, delay?: number, mixDuration?: number }; @@ -1027,7 +1027,7 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable state.data.defaultMix = defaultMix; if (animationsInfo) { - for (const [trackIndexString, { cycle, animations, holdDurationLastAnimation }] of Object.entries(animationsInfo)) { + for (const [trackIndexString, { cycle, animations, repeatDelay }] of Object.entries(animationsInfo)) { const cycleFn = () => { const trackIndex = Number(trackIndexString); for (const [index, { animationName, delay, loop, mixDuration }] of animations.entries()) { @@ -1051,8 +1051,8 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable if (cycle && index === animations.length - 1) { track.listener = { complete: () => { - if (holdDurationLastAnimation) - setTimeout(() => cycleFn(), 1000 * holdDurationLastAnimation); + if (repeatDelay) + setTimeout(() => cycleFn(), 1000 * repeatDelay); else cycleFn(); delete track.listener?.complete; diff --git a/spine-ts/spine-webcomponents/src/wcUtils.ts b/spine-ts/spine-webcomponents/src/wcUtils.ts index ebc46966e..69b05474f 100644 --- a/spine-ts/spine-webcomponents/src/wcUtils.ts +++ b/spine-ts/spine-webcomponents/src/wcUtils.ts @@ -102,7 +102,7 @@ function castToAnimationsInfo (value: string | null): AnimationsInfo | undefined if (!matches) return undefined; return matches.reduce((obj, group) => { - const [trackIndexStringOrLoopDefinition, animationNameOrTrackIndexStringCycle, loopOrHoldDurationLastAnimation, delayString, mixDurationString] = group.slice(1, -1).split(',').map(v => v.trim()); + const [trackIndexStringOrLoopDefinition, animationNameOrTrackIndexStringCycle, loopOrRepeatDelay, delayString, mixDurationString] = group.slice(1, -1).split(',').map(v => v.trim()); if (trackIndexStringOrLoopDefinition === "loop") { if (!Number.isInteger(Number(animationNameOrTrackIndexStringCycle))) { @@ -111,12 +111,12 @@ function castToAnimationsInfo (value: string | null): AnimationsInfo | undefined const animationInfoObject = obj[animationNameOrTrackIndexStringCycle] ||= { animations: [] }; animationInfoObject.cycle = true; - if (loopOrHoldDurationLastAnimation !== undefined) { - const holdDurationLastAnimation = Number(loopOrHoldDurationLastAnimation); - if (Number.isNaN(holdDurationLastAnimation)) { - throw new Error(`If present, duration of last animation of cycle in ${group} must be a positive integer number, instead it is ${loopOrHoldDurationLastAnimation}. Original value: ${value}`); + if (loopOrRepeatDelay !== undefined) { + const repeatDelay = Number(loopOrRepeatDelay); + if (Number.isNaN(repeatDelay)) { + throw new Error(`If present, duration of last animation of cycle in ${group} must be a positive integer number, instead it is ${loopOrRepeatDelay}. Original value: ${value}`); } - animationInfoObject.holdDurationLastAnimation = holdDurationLastAnimation; + animationInfoObject.repeatDelay = repeatDelay; } return obj; @@ -146,7 +146,7 @@ function castToAnimationsInfo (value: string | null): AnimationsInfo | undefined const animationInfoObject = obj[trackIndexStringOrLoopDefinition] ||= { animations: [] }; animationInfoObject.animations.push({ animationName: animationNameOrTrackIndexStringCycle, - loop: loopOrHoldDurationLastAnimation.trim().toLowerCase() === "true", + loop: loopOrRepeatDelay.trim().toLowerCase() === "true", delay, mixDuration, }); From 5e6f4cae8d187747f93fceba7f2a88308583115d Mon Sep 17 00:00:00 2001 From: Davide Tantillo Date: Tue, 20 May 2025 17:29:26 +0200 Subject: [PATCH 5/5] [ts][webcomponents] Fix empty loop param in animations. --- spine-ts/spine-webcomponents/src/wcUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spine-ts/spine-webcomponents/src/wcUtils.ts b/spine-ts/spine-webcomponents/src/wcUtils.ts index 69b05474f..3e8d0aaa8 100644 --- a/spine-ts/spine-webcomponents/src/wcUtils.ts +++ b/spine-ts/spine-webcomponents/src/wcUtils.ts @@ -146,7 +146,7 @@ function castToAnimationsInfo (value: string | null): AnimationsInfo | undefined const animationInfoObject = obj[trackIndexStringOrLoopDefinition] ||= { animations: [] }; animationInfoObject.animations.push({ animationName: animationNameOrTrackIndexStringCycle, - loop: loopOrRepeatDelay.trim().toLowerCase() === "true", + loop: (loopOrRepeatDelay || "").trim().toLowerCase() === "true", delay, mixDuration, });