diff --git a/packages/@lightningjs/ui-components/src/components/Base/Base.d.ts b/packages/@lightningjs/ui-components/src/components/Base/Base.d.ts index 63ec32264..aab204ba5 100644 --- a/packages/@lightningjs/ui-components/src/components/Base/Base.d.ts +++ b/packages/@lightningjs/ui-components/src/components/Base/Base.d.ts @@ -103,7 +103,7 @@ declare class Base< /** * returns true if this component is fully within the stage and boundsMargin */ - isFullyOnScreen(): boolean; + isFullyOnScreen(offsets: { offsetX: number; offsetY: number }): boolean; // TODO: for future reference these accessors should technically be public /** diff --git a/packages/@lightningjs/ui-components/src/components/Base/Base.js b/packages/@lightningjs/ui-components/src/components/Base/Base.js index 7583e70da..de649f4f8 100644 --- a/packages/@lightningjs/ui-components/src/components/Base/Base.js +++ b/packages/@lightningjs/ui-components/src/components/Base/Base.js @@ -121,8 +121,8 @@ class Base extends lng.Component { return this.mode === 'focused'; } - isFullyOnScreen() { - return isComponentOnScreen(this); + isFullyOnScreen(offsets) { + return isComponentOnScreen(this, offsets); } getFocusScale() { diff --git a/packages/@lightningjs/ui-components/src/components/NavigationManager/NavigationManager.js b/packages/@lightningjs/ui-components/src/components/NavigationManager/NavigationManager.js index 0e7d9b3f2..c599ca00c 100644 --- a/packages/@lightningjs/ui-components/src/components/NavigationManager/NavigationManager.js +++ b/packages/@lightningjs/ui-components/src/components/NavigationManager/NavigationManager.js @@ -499,4 +499,17 @@ export default class NavigationManager extends FocusManager { ? this._scrollIndex : this.style.scrollIndex; } + + isFullyOnScreen({ offsetX = 0, offsetY = 0 } = {}) { + // if the NavigationManager is nested in another Focus Manager + // (like a Row inside of a Column), + // the `isComponentOnScreen` method needs to account for + // how much the Items container is moving as it scrolls + const focusmanager = this.parent?.parent; + if (focusmanager instanceof FocusManager) { + offsetX += focusmanager.Items.transition('x').targetValue || 0; + offsetY += focusmanager.Items.transition('y').targetValue || 0; + } + return super.isFullyOnScreen({ offsetX, offsetY }); + } } diff --git a/packages/@lightningjs/ui-components/src/docs/Base.mdx b/packages/@lightningjs/ui-components/src/docs/Base.mdx index b29e42eec..8da38118c 100644 --- a/packages/@lightningjs/ui-components/src/docs/Base.mdx +++ b/packages/@lightningjs/ui-components/src/docs/Base.mdx @@ -80,7 +80,7 @@ Any component which extends the Base component and uses the `withThemeStyles` mi This method accepts a target component, patch object, and optional smooth object. If the component is visible, it will smooth in the smooth object, or fall back to the patch object, if not it will apply the patch. -#### isFullyOnScreen(): bool +#### isFullyOnScreen(\{ offsetX: number, offsetY: number \}): bool Returns a boolean for whether or not the entirety of the component is rendered within the bounds of the screen size. diff --git a/packages/@lightningjs/ui-components/src/utils/index.js b/packages/@lightningjs/ui-components/src/utils/index.js index ff31b165c..94d8f72a2 100644 --- a/packages/@lightningjs/ui-components/src/utils/index.js +++ b/packages/@lightningjs/ui-components/src/utils/index.js @@ -132,7 +132,7 @@ export function getShortestDistance(coordinate, element) { return Math.min(distanceToStart, distanceToMiddle, distanceToEnd); } -export function isComponentOnScreen(component) { +export function isComponentOnScreen(component, offsets = {}) { if (!component) return false; const { @@ -143,8 +143,29 @@ export function isComponentOnScreen(component) { const stageH = component.stage.h / component.stage.getRenderPrecision(); const stageW = component.stage.w / component.stage.getRenderPrecision(); - const wVis = px >= 0 && px + w <= stageW; - const hVis = py >= 0 && py + h <= stageH; + let finalX = px; + let finalY = py; + // keep track of the different between the the absolute world position and relative position + const relativeOffsetX = px - component.x; + const relativeOffsetY = py - component.y; + const offsetX = offsets.offsetX - relativeOffsetX || 0; + const offsetY = offsets.offsetY - relativeOffsetY || 0; + // if the current component is animating, apply the relative offset to the transition value + if (component.transition('x')) { + finalX = px - component.x + component.transition('x').targetValue; + } + if (component.transition('y')) { + finalY = py - component.y + component.transition('y').targetValue; + } + // apply any offset passed into the function + // this is mainly used to parent components that are transitioning, + // like in the case of Rows nested inside of Columns where the Rows themselves do not animate, + // but their parent container does + finalX += offsetX; + finalY += offsetY; + + const wVis = finalX >= 0 && finalX + w <= stageW; + const hVis = finalY >= 0 && finalY + h <= stageH; if (!wVis || !hVis) return false; @@ -157,12 +178,13 @@ export function isComponentOnScreen(component) { ] = scissor; const withinLeftClippingBounds = - Math.round(px + w) >= Math.round(leftBounds); + Math.round(finalX + w) >= Math.round(leftBounds); const withinRightClippingBounds = - Math.round(px) <= Math.round(leftBounds + clipWidth); - const withinTopClippingBounds = Math.round(py + h) >= Math.round(topBounds); + Math.round(finalX) <= Math.round(leftBounds + clipWidth); + const withinTopClippingBounds = + Math.round(finalY + h) >= Math.round(topBounds); const withinBottomClippingBounds = - Math.round(py + h) <= Math.round(topBounds + clipHeight); + Math.round(finalY + h) <= Math.round(topBounds + clipHeight); return ( withinLeftClippingBounds &&