CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-vueuse--components

Renderless Vue.js components that expose VueUse composable functionality through declarative template-based interfaces

Pending
Overview
Eval results
Files

browser-apis.mddocs/

Browser APIs

Components for interacting with various browser APIs like clipboard, geolocation, fullscreen, and media features.

Capabilities

UseClipboard Component

Provides clipboard read and write functionality with reactive state.

/**
 * Component that provides clipboard read/write functionality
 * @example
 * <UseClipboard v-slot="{ text, copy, isSupported }">
 *   <div>
 *     <p>Clipboard: {{ text }}</p>
 *     <button @click="copy('Hello!')">Copy Text</button>
 *   </div>
 * </UseClipboard>
 */
interface UseClipboardProps {
  /** Text source to read from @default undefined */
  source?: MaybeRefOrGetter<string>;
  /** Whether to read clipboard content immediately @default false */
  read?: boolean;
  /** Legacy mode using document.execCommand @default false */
  legacy?: boolean;
  /** Copy options */
  copiedDuring?: number;
}

/** Slot data exposed by UseClipboard component */
interface UseClipboardReturn {
  /** Whether clipboard API is supported */
  isSupported: Ref<boolean>;
  /** Current clipboard text content */
  text: Ref<string>;
  /** Whether clipboard was recently copied */
  copied: Ref<boolean>;
  /** Copy text to clipboard */
  copy: (text?: string) => Promise<void>;
}

Usage Examples:

<template>
  <!-- Basic clipboard usage -->
  <UseClipboard v-slot="{ text, copy, copied, isSupported }">
    <div v-if="isSupported" class="clipboard-demo">
      <h3>Clipboard Demo</h3>
      <p>Current clipboard: {{ text || 'Empty' }}</p>
      <div class="clipboard-actions">
        <button @click="copy('Hello World!')" :disabled="copied">
          {{ copied ? 'Copied!' : 'Copy Hello World' }}
        </button>
        <button @click="copy(customText)">
          Copy Custom Text
        </button>
      </div>
      <input v-model="customText" placeholder="Enter text to copy" />
    </div>
    <div v-else>Clipboard API not supported</div>
  </UseClipboard>

  <!-- Auto-read clipboard -->
  <UseClipboard :read="true" v-slot="{ text, copy }">
    <div class="auto-clipboard">
      <p>Auto-synced clipboard: {{ text }}</p>
      <button @click="copy(`Timestamp: ${Date.now()}`)">
        Copy Timestamp
      </button>
    </div>
  </UseClipboard>

  <!-- Copy from source -->
  <UseClipboard :source="sourceText" v-slot="{ copy, copied }">
    <button @click="copy()" :class="{ copied }">
      {{ copied ? 'Copied!' : 'Copy Source Text' }}
    </button>
  </UseClipboard>
</template>

<script setup>
import { ref } from 'vue';
import { UseClipboard } from '@vueuse/components';

const customText = ref('');
const sourceText = ref('This is source text to copy');
</script>

<style>
.clipboard-actions {
  margin: 10px 0;
}

.clipboard-actions button {
  margin-right: 10px;
  padding: 8px 16px;
}

.copied {
  background-color: #4caf50;
  color: white;
}
</style>

UseGeolocation Component

Provides geolocation data from the browser's Geolocation API.

/**
 * Component that provides geolocation data
 * @example
 * <UseGeolocation v-slot="{ coords, error, resume, pause }">
 *   <div>Location: {{ coords.latitude }}, {{ coords.longitude }}</div>
 * </UseGeolocation>
 */
interface UseGeolocationProps {
  /** High accuracy mode @default true */
  enableHighAccuracy?: boolean;
  /** Maximum age of cached position (ms) @default 30000 */
  maximumAge?: number;
  /** Timeout for position request (ms) @default 27000 */
  timeout?: number;
  /** Start watching immediately @default true */
  immediate?: boolean;
}

/** Slot data exposed by UseGeolocation component */
interface UseGeolocationReturn {
  /** Whether geolocation is supported */
  isSupported: Ref<boolean>;
  /** Current position coordinates */
  coords: Ref<GeolocationCoordinates>;
  /** Location timestamp */
  locatedAt: Ref<number | null>;
  /** Geolocation error */
  error: Ref<GeolocationPositionError | null>;
  /** Start/resume location tracking */
  resume: () => void;
  /** Pause location tracking */
  pause: () => void;
}

interface GeolocationCoordinates {
  /** Latitude in decimal degrees */
  latitude: number;
  /** Longitude in decimal degrees */
  longitude: number;
  /** Altitude in meters above sea level */
  altitude: number | null;
  /** Accuracy of lat/lng in meters */
  accuracy: number;
  /** Accuracy of altitude in meters */
  altitudeAccuracy: number | null;
  /** Direction of travel in degrees */
  heading: number | null;
  /** Speed in meters per second */
  speed: number | null;
}

interface GeolocationPositionError {
  code: number;
  message: string;
}

Usage Examples:

<template>
  <!-- Basic geolocation -->
  <UseGeolocation v-slot="{ coords, error, isSupported, locatedAt }">
    <div v-if="isSupported" class="geolocation-info">
      <h3>Your Location</h3>
      <div v-if="error" class="error">
        Error: {{ error.message }}
      </div>
      <div v-else-if="coords.latitude">
        <p>📍 {{ coords.latitude.toFixed(6) }}, {{ coords.longitude.toFixed(6) }}</p>
        <p>📏 Accuracy: ±{{ Math.round(coords.accuracy) }}m</p>
        <p v-if="coords.altitude">🏔️ Altitude: {{ Math.round(coords.altitude) }}m</p>
        <p v-if="coords.speed">🏃 Speed: {{ (coords.speed * 3.6).toFixed(1) }} km/h</p>
        <p v-if="coords.heading">🧭 Heading: {{ Math.round(coords.heading) }}°</p>
        <p>⏰ Updated: {{ new Date(locatedAt).toLocaleTimeString() }}</p>
      </div>
      <div v-else class="loading">
        📡 Getting your location...
      </div>
    </div>
    <div v-else>Geolocation not supported</div>
  </UseGeolocation>

  <!-- Controlled geolocation -->
  <UseGeolocation 
    :immediate="false"
    :enable-high-accuracy="false"
    v-slot="{ coords, resume, pause, error }"
  >
    <div class="controlled-geo">
      <div class="geo-controls">
        <button @click="resume">Start Tracking</button>
        <button @click="pause">Stop Tracking</button>
      </div>
      <div v-if="coords.latitude" class="coordinates">
        Low accuracy location: {{ coords.latitude.toFixed(4) }}, {{ coords.longitude.toFixed(4) }}
      </div>
      <div v-if="error" class="error">{{ error.message }}</div>
    </div>
  </UseGeolocation>
</template>

<script setup>
import { UseGeolocation } from '@vueuse/components';
</script>

<style>
.error {
  color: #f44336;
  padding: 10px;
  background: #ffebee;
  border-radius: 4px;
}

.loading {
  color: #2196f3;
  font-style: italic;
}

.geo-controls button {
  margin-right: 10px;
  padding: 8px 16px;
}
</style>

UseFullscreen Component

Manages fullscreen mode for elements with the Fullscreen API.

/**
 * Component that manages fullscreen mode
 * @example
 * <UseFullscreen v-slot="{ isFullscreen, enter, exit, toggle }">
 *   <div>
 *     <button @click="toggle">{{ isFullscreen ? 'Exit' : 'Enter' }} Fullscreen</button>
 *   </div>
 * </UseFullscreen>
 */
interface UseFullscreenProps extends RenderableComponent {
  /** Auto-exit fullscreen when component unmounts @default false */
  autoExit?: boolean;
}

/** Slot data exposed by UseFullscreen component */
interface UseFullscreenReturn {
  /** Whether fullscreen API is supported */
  isSupported: Ref<boolean>;
  /** Whether element is in fullscreen */
  isFullscreen: Ref<boolean>;
  /** Enter fullscreen mode */
  enter: () => Promise<void>;
  /** Exit fullscreen mode */
  exit: () => Promise<void>;
  /** Toggle fullscreen mode */
  toggle: () => Promise<void>;
}

Usage Examples:

<template>
  <!-- Basic fullscreen -->
  <UseFullscreen v-slot="{ isFullscreen, toggle, isSupported }">
    <div v-if="isSupported" class="fullscreen-demo">
      <div class="content" :class="{ 'fullscreen-content': isFullscreen }">
        <h3>{{ isFullscreen ? '🎯 Fullscreen Mode!' : '📱 Normal Mode' }}</h3>
        <p>This content can go fullscreen</p>
        <button @click="toggle" class="fullscreen-btn">
          {{ isFullscreen ? 'Exit Fullscreen' : 'Go Fullscreen' }}
        </button>
      </div>
    </div>
    <div v-else>Fullscreen API not supported</div>
  </UseFullscreen>

  <!-- Video fullscreen -->
  <UseFullscreen v-slot="{ isFullscreen, enter, exit }">
    <div class="video-container">
      <video 
        ref="videoRef"
        controls 
        width="400"
        :style="{ width: isFullscreen ? '100vw' : '400px' }"
      >
        <source src="/sample-video.mp4" type="video/mp4">
      </video>
      <div class="video-controls">
        <button @click="enter">📺 Fullscreen Video</button>
        <button @click="exit" :disabled="!isFullscreen">↩️ Exit</button>
      </div>
    </div>
  </UseFullscreen>

  <!-- Auto-exit fullscreen -->
  <UseFullscreen :auto-exit="true" v-slot="{ isFullscreen, toggle }">
    <div class="auto-exit-demo">
      <p>Auto-exits fullscreen when component unmounts</p>
      <button @click="toggle">Toggle Fullscreen</button>
      <p>Status: {{ isFullscreen ? 'Fullscreen' : 'Windowed' }}</p>
    </div>
  </UseFullscreen>
</template>

<script setup>
import { ref } from 'vue';
import { UseFullscreen } from '@vueuse/components';

const videoRef = ref();
</script>

<style>
.fullscreen-demo {
  border: 2px solid #ccc;
  border-radius: 8px;
  overflow: hidden;
}

.content {
  padding: 20px;
  background: #f9f9f9;
  min-height: 200px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

.fullscreen-content {
  background: #1976d2;
  color: white;
  min-height: 100vh;
}

.fullscreen-btn {
  padding: 12px 24px;
  font-size: 16px;
  border: none;
  border-radius: 6px;
  background: #2196f3;
  color: white;
  cursor: pointer;
}

.video-controls {
  padding: 10px;
  display: flex;
  gap: 10px;
}
</style>

UseEyeDropper Component

Provides color picking functionality using the EyeDropper API.

/**
 * Component that provides color picker functionality
 * @example
 * <UseEyeDropper v-slot="{ isSupported, sRGBHex, open }">
 *   <div>
 *     <button @click="open">Pick Color</button>
 *     <div>Color: {{ sRGBHex }}</div>
 *   </div>
 * </UseEyeDropper>
 */
interface UseEyeDropperProps {
  // No props - uses browser EyeDropper API directly
}

/** Slot data exposed by UseEyeDropper component */
interface UseEyeDropperReturn {
  /** Whether EyeDropper API is supported */
  isSupported: Ref<boolean>;
  /** Selected color in sRGB hex format */
  sRGBHex: Ref<string>;
  /** Open color picker */
  open: (options?: EyeDropperOpenOptions) => Promise<void>;
}

interface EyeDropperOpenOptions {
  signal?: AbortSignal;
}

Usage Examples:

<template>
  <!-- Basic color picker -->
  <UseEyeDropper v-slot="{ isSupported, sRGBHex, open }">
    <div v-if="isSupported" class="color-picker">
      <h3>Color Picker</h3>
      <div class="color-display">
        <div 
          class="color-swatch"
          :style="{ backgroundColor: sRGBHex || '#transparent' }"
        ></div>
        <span class="color-value">{{ sRGBHex || 'No color selected' }}</span>
      </div>
      <button @click="open" class="pick-button">
        🎨 Pick Color from Screen
      </button>
    </div>
    <div v-else>EyeDropper API not supported</div>
  </UseEyeDropper>

  <!-- Color palette builder -->
  <UseEyeDropper v-slot="{ isSupported, sRGBHex, open }">
    <div v-if="isSupported" class="palette-builder">
      <h3>Build Color Palette</h3>
      <div class="palette">
        <div 
          v-for="(color, index) in palette" 
          :key="index"
          class="palette-color"
          :style="{ backgroundColor: color }"
          :title="color"
        ></div>
      </div>
      <div class="palette-actions">
        <button @click="addColor" :disabled="!sRGBHex">
          Add Current Color
        </button>
        <button @click="open">Pick New Color</button>
        <button @click="clearPalette">Clear Palette</button>
      </div>
      <p>Current: {{ sRGBHex || 'Pick a color' }}</p>
    </div>
  </UseEyeDropper>
</template>

<script setup>
import { ref, watch } from 'vue';
import { UseEyeDropper } from '@vueuse/components';

const palette = ref([]);

const addColor = () => {
  // This would need access to sRGBHex from the slot
  // In practice, you'd use a composable or different pattern
};

const clearPalette = () => {
  palette.value = [];
};
</script>

<style>
.color-display {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 15px 0;
}

.color-swatch {
  width: 50px;
  height: 50px;
  border: 2px solid #ccc;
  border-radius: 6px;
  box-shadow: inset 0 0 10px rgba(0,0,0,0.1);
}

.pick-button {
  padding: 10px 20px;
  background: #ff5722;
  color: white;
  border: none;
  border-radius: 6px;
  cursor: pointer;
  font-size: 16px;
}

.palette {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  margin: 15px 0;
  min-height: 60px;
  border: 2px dashed #ccc;
  border-radius: 6px;
  padding: 10px;
}

.palette-color {
  width: 40px;
  height: 40px;
  border-radius: 4px;
  border: 1px solid #ddd;
}

.palette-actions {
  display: flex;
  gap: 10px;
}

.palette-actions button {
  padding: 8px 16px;
  border: 1px solid #ccc;
  border-radius: 4px;
  cursor: pointer;
}
</style>

UseBrowserLocation Component

Tracks browser location and URL information.

/**
 * Component that tracks browser location/URL information
 * @example
 * <UseBrowserLocation v-slot="{ url, origin, pathname, search }">
 *   <div>Current path: {{ pathname }}{{ search }}</div>
 * </UseBrowserLocation>
 */
interface UseBrowserLocationProps {
  // No props - uses browser location API directly
}

/** Slot data exposed by UseBrowserLocation component */
interface BrowserLocationState {
  /** Reactive trigger ref for updates */
  trigger: Ref<string>;
  /** Complete URL */
  url: ComputedRef<string>;
  /** URL origin */
  origin: ComputedRef<string>;
  /** URL protocol */
  protocol: ComputedRef<string>;
  /** URL host */
  host: ComputedRef<string>;
  /** URL hostname */
  hostname: ComputedRef<string>;
  /** URL port */
  port: ComputedRef<string>;
  /** URL pathname */
  pathname: ComputedRef<string>;
  /** URL search params */
  search: ComputedRef<string>;
  /** URL hash */
  hash: ComputedRef<string>;
}

Usage Examples:

<template>
  <!-- Basic location info -->
  <UseBrowserLocation v-slot="{ url, pathname, search, hash, origin }">
    <div class="location-info">
      <h3>Browser Location</h3>
      <div class="url-parts">
        <p><strong>Full URL:</strong> {{ url }}</p>
        <p><strong>Origin:</strong> {{ origin }}</p>
        <p><strong>Path:</strong> {{ pathname }}</p>
        <p><strong>Search:</strong> {{ search || '(none)' }}</p>
        <p><strong>Hash:</strong> {{ hash || '(none)' }}</p>
      </div>
    </div>
  </UseBrowserLocation>

  <!-- URL analyzer -->
  <UseBrowserLocation v-slot="{ protocol, host, port, pathname, search }">
    <div class="url-analyzer">
      <h3>URL Analysis</h3>
      <table class="url-table">
        <tr><td>Protocol:</td><td>{{ protocol }}</td></tr>
        <tr><td>Host:</td><td>{{ host }}</td></tr>
        <tr><td>Port:</td><td>{{ port || '(default)' }}</td></tr>
        <tr><td>Path:</td><td>{{ pathname }}</td></tr>
        <tr><td>Query:</td><td>{{ search || '(none)' }}</td></tr>
      </table>
    </div>
  </UseBrowserLocation>
</template>

<script setup>
import { UseBrowserLocation } from '@vueuse/components';
</script>

<style>
.location-info, .url-analyzer {
  border: 1px solid #ddd;
  border-radius: 6px;
  padding: 15px;
  margin: 15px 0;
}

.url-table {
  width: 100%;
  border-collapse: collapse;
}

.url-table td {
  padding: 8px;
  border-bottom: 1px solid #eee;
}

.url-table td:first-child {
  font-weight: bold;
  width: 100px;
}
</style>

Type Definitions

/** Common types used across browser API components */
type MaybeRefOrGetter<T> = T | Ref<T> | (() => T);

interface RenderableComponent {
  /** The element that the component should be rendered as @default 'div' */
  as?: object | string;
}

/** Clipboard API types */
interface ClipboardItem {
  readonly types: string[];
  getType(type: string): Promise<Blob>;
}

/** Geolocation API types */
interface GeolocationPosition {
  coords: GeolocationCoordinates;
  timestamp: number;
}

interface GeolocationPositionError {
  readonly code: number;
  readonly message: string;
  readonly PERMISSION_DENIED: number;
  readonly POSITION_UNAVAILABLE: number;
  readonly TIMEOUT: number;
}

/** Fullscreen API types */
interface FullscreenOptions {
  navigationUI?: 'auto' | 'show' | 'hide';
}

/** EyeDropper API types */
interface EyeDropper {
  open(options?: EyeDropperOpenOptions): Promise<ColorSelectionResult>;
}

interface ColorSelectionResult {
  sRGBHex: string;
}

Install with Tessl CLI

npx tessl i tessl/npm-vueuse--components

docs

browser-apis.md

browser-events.md

device-sensors.md

element-tracking.md

index.md

mouse-pointer.md

scroll-resize.md

theme-preferences.md

utilities-advanced.md

window-document.md

tile.json