Skip to content

selmetwa/web-component-utils

Repository files navigation

Web Component Utils

Deploy Live Demos

A small collection of reusable, event driven web components. Currently includes an Intersection Observer component, and a Get Request component.

πŸš€ View Live Demos

πŸ“‘ Table of Contents

πŸš€ Quick Start

# Clone the repository
git clone https://github.com/selmetwa/web-component-utils.git
cd web-component-utils

# Install dependencies for examples
cd vanilla-example && npm install && cd ..
cd react-example && npm install && cd ..
cd vue-example && npm install && cd ..

# Run an example
npm run dev:vanilla
npm run dev:react
npm run dev:vue

πŸ“¦ Components

Intersection Observer

A web component that detects when content becomes visible in the viewport. Perfect for lazy loading, animations, analytics, and more.

Features:

  • βœ… Framework-agnostic (works with React, Vue, Angular, Svelte, vanilla JS)
  • βœ… Event-based API
  • βœ… Custom event names via event-key and ready-event-key attributes
  • βœ… Configurable threshold and root margin
  • βœ… Shadow DOM with slot support
  • βœ… Fully documented with JSDoc

Basic Usage:

<intersection-observer 
  threshold="0.5" 
  event-key="my-visibility-event"
  ready-event-key="my-ready-event">
  <div>Your content here</div>
</intersection-observer>

<script type="module">
  import './components/intersection-observer/index.js'
  
  const observer = document.querySelector('intersection-observer')
  
  // Listen for visibility changes
  observer.addEventListener('my-visibility-event', (e) => {
    console.log('Visible:', e.detail.isIntersecting)
    console.log('Ratio:', e.detail.intersectionRatio)
  })
  
  // Listen for ready event
  observer.addEventListener('my-ready-event', () => {
    console.log('Observer is ready!')
  })
</script>

Get Request

A web component that handles GET requests with built-in caching, loading states, and error handling. Perfect for fetching data from APIs without managing fetch logic yourself.

Features:

  • βœ… Framework-agnostic (works with React, Vue, Angular, Svelte, vanilla JS)
  • βœ… Built-in LRU cache with configurable size
  • βœ… Automatic fetching when URL changes
  • βœ… Custom event names for loading, success, and error states
  • βœ… Shadow DOM with slot support
  • βœ… Manual and automatic fetch modes
  • βœ… Fully documented with JSDoc

Basic Usage:

<get-request 
  url="https://api.example.com/data"
  loading-event-key="my-loading"
  success-event-key="my-success"
  error-event-key="my-error">
  <div>Your content here</div>
</get-request>

<script type="module">
  import './components/get-request/index.js'
  
  const request = document.querySelector('get-request')
  
  // Listen for loading state
  request.addEventListener('my-loading', (e) => {
    console.log('Loading:', e.detail.url)
  })
  
  // Listen for success
  request.addEventListener('my-success', (e) => {
    console.log('Data:', e.detail.data)
    console.log('From cache:', e.detail.fromCache)
  })
  
  // Listen for errors
  request.addEventListener('my-error', (e) => {
    console.error('Error:', e.detail.error)
  })
</script>

🎯 API Reference

Intersection Observer API

Attributes:

Attribute Type Default Description
threshold number 0.1 Percentage of visibility required to trigger (0-1)
root-margin string '0px' Margin around the root element
event-key string 'intersection-change' Custom event name for visibility changes
ready-event-key string 'observer-ready' Custom event name for ready state

Events:

  • {event-key} (default: intersection-change) - Fired when intersection state changes

    {
      isIntersecting: boolean,      // Whether element is intersecting
      intersectionRatio: number,     // Ratio of element visible (0-1)
      entry: IntersectionObserverEntry  // Full entry object
    }
  • {ready-event-key} (default: observer-ready) - Fired when component is fully initialized

Properties (Getters):

  • isIntersecting - Get current intersection state (boolean)
  • intersectionRatio - Get current intersection ratio (number)

Methods:

  • startObserving() - Start observing the target
  • stopObserving() - Stop observing the target

Get Request API

Attributes:

Attribute Type Default Description
url string '' The URL to fetch from
cache-size number 50 Maximum number of cached responses
enable-cache boolean true Whether to enable caching
auto-fetch boolean true Whether to automatically fetch when URL changes
loading-event-key string 'request-loading' Custom event name for loading state
success-event-key string 'request-success' Custom event name for success state
error-event-key string 'request-error' Custom event name for error state

Events:

  • {loading-event-key} (default: request-loading) - Fired when request starts

    {
      url: string  // The URL being fetched
    }
  • {success-event-key} (default: request-success) - Fired when request succeeds

    {
      data: any,          // The response data
      fromCache: boolean  // Whether data came from cache
    }
  • {error-event-key} (default: request-error) - Fired when request fails

    {
      error: Error,  // The error object
      message: string  // Error message
    }

Properties (Getters):

  • data - Get the current data (any)
  • loading - Get loading state (boolean)
  • error - Get error state (Error | null)

Methods:

  • fetch() - Manually trigger a fetch request
  • clearCache() - Clear the request cache

πŸ”§ Framework Integration

Vanilla JavaScript

Intersection Observer:

import './components/intersection-observer/index.js'

const observer = document.getElementById('observer')
observer.addEventListener('visibility-change', (event) => {
  const { isIntersecting, intersectionRatio } = event.detail
  // Update your UI
})

observer.addEventListener('observer-ready', () => {
  console.log('Ready!')
})

Get Request:

import './components/get-request/index.js'

const request = document.getElementById('request')
request.addEventListener('request-success', (event) => {
  const { data, fromCache } = event.detail
  console.log('Data:', data)
})

request.addEventListener('request-error', (event) => {
  console.error('Error:', event.detail.error)
})

React

Intersection Observer:

import { useState, useEffect, useRef } from 'react'
import './components/intersection-observer/index.js'

function MyComponent() {
  const [isVisible, setIsVisible] = useState(false)
  const ref = useRef(null)

  useEffect(() => {
    const handleChange = (e) => setIsVisible(e.detail.isIntersecting)
    const handleReady = () => console.log('Ready!')
    
    ref.current?.addEventListener('visibility-change', handleChange)
    ref.current?.addEventListener('observer-ready', handleReady)
    
    return () => {
      ref.current?.removeEventListener('visibility-change', handleChange)
      ref.current?.removeEventListener('observer-ready', handleReady)
    }
  }, [])

  return (
    <intersection-observer 
      ref={ref} 
      threshold="0.5" 
      event-key="visibility-change"
      ready-event-key="observer-ready">
      <MyContent isVisible={isVisible} />
    </intersection-observer>
  )
}

Get Request:

import { useState, useEffect, useRef } from 'react'
import './components/get-request/index.js'

function MyComponent() {
  const [data, setData] = useState(null)
  const [loading, setLoading] = useState(false)
  const ref = useRef(null)

  useEffect(() => {
    const handleLoading = () => setLoading(true)
    const handleSuccess = (e) => {
      setData(e.detail.data)
      setLoading(false)
    }
    
    ref.current?.addEventListener('request-loading', handleLoading)
    ref.current?.addEventListener('request-success', handleSuccess)
    
    return () => {
      ref.current?.removeEventListener('request-loading', handleLoading)
      ref.current?.removeEventListener('request-success', handleSuccess)
    }
  }, [])

  return (
    <get-request 
      ref={ref}
      url="https://api.example.com/data"
      loading-event-key="request-loading"
      success-event-key="request-success">
      {loading ? <div>Loading...</div> : <pre>{JSON.stringify(data, null, 2)}</pre>}
    </get-request>
  )
}

Vue

Intersection Observer:

<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import './components/intersection-observer/index.js'

const isVisible = ref(false)
const observerRef = ref(null)

onMounted(() => {
  const handleChange = (e) => {
    isVisible.value = e.detail.isIntersecting
  }
  const handleReady = () => console.log('Ready!')
  
  observerRef.value?.addEventListener('visibility-change', handleChange)
  observerRef.value?.addEventListener('observer-ready', handleReady)
  
  onUnmounted(() => {
    observerRef.value?.removeEventListener('visibility-change', handleChange)
    observerRef.value?.removeEventListener('observer-ready', handleReady)
  })
})
</script>

<template>
  <intersection-observer 
    ref="observerRef" 
    threshold="0.5"
    event-key="visibility-change"
    ready-event-key="observer-ready">
    <MyComponent :is-visible="isVisible" />
  </intersection-observer>
</template>

Get Request:

<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import './components/get-request/index.js'

const data = ref(null)
const loading = ref(false)
const requestRef = ref(null)

onMounted(() => {
  const handleLoading = () => {
    loading.value = true
  }
  const handleSuccess = (e) => {
    data.value = e.detail.data
    loading.value = false
  }
  
  requestRef.value?.addEventListener('request-loading', handleLoading)
  requestRef.value?.addEventListener('request-success', handleSuccess)
  
  onUnmounted(() => {
    requestRef.value?.removeEventListener('request-loading', handleLoading)
    requestRef.value?.removeEventListener('request-success', handleSuccess)
  })
})
</script>

<template>
  <get-request 
    ref="requestRef"
    url="https://api.example.com/data"
    loading-event-key="request-loading"
    success-event-key="request-success">
    <div v-if="loading">Loading...</div>
    <pre v-else>{{ JSON.stringify(data, null, 2) }}</pre>
  </get-request>
</template>

πŸ“ Project Structure

web-component-utils/
β”œβ”€β”€ components/
β”‚   β”œβ”€β”€ intersection-observer/
β”‚   β”‚   └── index.js                # Intersection Observer component
β”‚   └── get-request/
β”‚       └── index.js                # Get Request component
β”œβ”€β”€ vanilla-example/                # Vanilla JS examples
β”‚   └── src/
β”‚       β”œβ”€β”€ main.js                 # Router
β”‚       └── pages/
β”‚           β”œβ”€β”€ intersection-observer.js
β”‚           └── get-request.js
β”œβ”€β”€ react-example/                  # React examples
β”‚   └── src/
β”‚       β”œβ”€β”€ App.jsx                 # Router
β”‚       └── pages/
β”‚           β”œβ”€β”€ IntersectionObserverPage.jsx
β”‚           └── GetRequestPage.jsx
β”œβ”€β”€ vue-example/                    # Vue examples
β”‚   └── src/
β”‚       β”œβ”€β”€ App.vue                 # Main layout
β”‚       β”œβ”€β”€ router/
β”‚       β”‚   └── index.js
β”‚       └── pages/
β”‚           β”œβ”€β”€ IntersectionObserverPage.vue
β”‚           └── GetRequestPage.vue
β”œβ”€β”€ .github/
β”‚   └── workflows/
β”‚       └── deploy.yml              # GitHub Pages deployment
β”œβ”€β”€ SETUP.md                        # Installation instructions
└── package.json                    # Root scripts

About

A small collection of reusable, event driven web components.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published