|
1 | 1 | import { css, html, LitElement } from 'lit'; |
2 | | -import { customElement, queryAssignedElements, state } from 'lit/decorators.js'; |
| 2 | +import { customElement, property, queryAssignedElements, state } from 'lit/decorators.js'; |
3 | 3 | import '../../shared/components/button'; |
4 | 4 | import '../../shared/components/code-icon'; |
5 | 5 |
|
@@ -250,6 +250,42 @@ export class GlFeatureCard extends LitElement { |
250 | 250 | `, |
251 | 251 | ]; |
252 | 252 |
|
| 253 | + private hasBeenVisible: boolean = false; |
| 254 | + |
| 255 | + override updated(changedProperties: Map<PropertyKey, unknown>): void { |
| 256 | + super.updated(changedProperties); |
| 257 | + |
| 258 | + const isVisible = isElementVisible(this); |
| 259 | + if (!isVisible || this.hasBeenVisible) return; |
| 260 | + |
| 261 | + const isInViewport = isElementInViewport(this); |
| 262 | + const isPartiallyInViewport = isElementPartiallyInViewport(this); |
| 263 | + const visible = isVisible && (isInViewport || isPartiallyInViewport); |
| 264 | + if (visible && !this.hasBeenVisible) { |
| 265 | + this.hasBeenVisible = true; |
| 266 | + |
| 267 | + // Dispatch a custom event when any property changes |
| 268 | + this.dispatchEvent( |
| 269 | + new CustomEvent('gl-feature-appeared', { |
| 270 | + detail: { |
| 271 | + changedProperties: Array.from(changedProperties.keys()), |
| 272 | + visibility: { |
| 273 | + isVisible: isVisible, |
| 274 | + isInViewport: isInViewport, |
| 275 | + isPartiallyInViewport: isPartiallyInViewport, |
| 276 | + }, |
| 277 | + }, |
| 278 | + bubbles: true, |
| 279 | + composed: true, |
| 280 | + }), |
| 281 | + ); |
| 282 | + } |
| 283 | + } |
| 284 | + |
| 285 | + /** is used to make the component reactive to 'data-active' attribute changes */ |
| 286 | + @property({ type: Boolean, reflect: true, attribute: 'data-active' }) |
| 287 | + private _dataActive: boolean = false; |
| 288 | + |
253 | 289 | override render(): unknown { |
254 | 290 | return html` |
255 | 291 | <div class="image"> |
@@ -370,3 +406,37 @@ export class GlScrollableFeatures extends LitElement { |
370 | 406 | return html`<div class="content"><slot></slot></div>`; |
371 | 407 | } |
372 | 408 | } |
| 409 | + |
| 410 | +function isElementVisible(element: HTMLElement): boolean { |
| 411 | + // Check if element is hidden by display: none or visibility: hidden |
| 412 | + const style = window.getComputedStyle(element); |
| 413 | + if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') { |
| 414 | + return false; |
| 415 | + } |
| 416 | + |
| 417 | + // Check if element has zero dimensions |
| 418 | + const rect = element.getBoundingClientRect(); |
| 419 | + if (rect.width === 0 || rect.height === 0) { |
| 420 | + return false; |
| 421 | + } |
| 422 | + |
| 423 | + return true; |
| 424 | +} |
| 425 | + |
| 426 | +function isElementInViewport(element: HTMLElement): boolean { |
| 427 | + const rect = element.getBoundingClientRect(); |
| 428 | + return ( |
| 429 | + rect.top >= 0 && |
| 430 | + rect.left >= 0 && |
| 431 | + rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && |
| 432 | + rect.right <= (window.innerWidth || document.documentElement.clientWidth) |
| 433 | + ); |
| 434 | +} |
| 435 | + |
| 436 | +function isElementPartiallyInViewport(element: HTMLElement): boolean { |
| 437 | + const rect = element.getBoundingClientRect(); |
| 438 | + const windowHeight = window.innerHeight || document.documentElement.clientHeight; |
| 439 | + const windowWidth = window.innerWidth || document.documentElement.clientWidth; |
| 440 | + |
| 441 | + return rect.bottom > 0 && rect.right > 0 && rect.top < windowHeight && rect.left < windowWidth; |
| 442 | +} |
0 commit comments