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

window-document.mddocs/

Window and Document

Components for tracking window and document state, focus, visibility, and dimensions.

Capabilities

UseWindowSize Component

Tracks window dimensions with reactive updates.

/**
 * Component that tracks window dimensions
 * @example
 * <UseWindowSize v-slot="{ width, height }">
 *   <div>Window size: {{ width }} × {{ height }}</div>
 * </UseWindowSize>
 */
interface UseWindowSizeProps {
  /** Initial width value @default Infinity */
  initialWidth?: number;
  /** Initial height value @default Infinity */
  initialHeight?: number;
  /** Listen to orientationchange event @default true */
  listenOrientation?: boolean;
  /** Include scrollbars in dimensions @default true */
  includeScrollbar?: boolean;
  /** Window object @default defaultWindow */
  window?: Window;
}

/** Slot data exposed by UseWindowSize component */
interface UseWindowSizeReturn {
  /** Current window width */
  width: Ref<number>;
  /** Current window height */
  height: Ref<number>;
}

Usage Examples:

<template>
  <!-- Basic window size tracking -->
  <UseWindowSize v-slot="{ width, height }">
    <div class="window-info">
      <h3>Window Information</h3>
      <div class="size-display">
        <div class="dimension">
          <span class="label">Width:</span>
          <span class="value">{{ width }}px</span>
        </div>
        <div class="dimension">
          <span class="label">Height:</span>
          <span class="value">{{ height }}px</span>
        </div>
        <div class="dimension">
          <span class="label">Aspect Ratio:</span>
          <span class="value">{{ (width / height).toFixed(2) }}</span>
        </div>
      </div>
    </div>
  </UseWindowSize>

  <!-- Responsive breakpoint detection -->
  <UseWindowSize v-slot="{ width, height }">
    <div class="breakpoint-info">
      <h3>Responsive Breakpoints</h3>
      <div class="breakpoint" :class="getBreakpointClass(width)">
        <span class="breakpoint-name">{{ getBreakpointName(width) }}</span>
        <span class="breakpoint-size">{{ width }}px</span>
      </div>
      <div class="orientation">
        Orientation: {{ width > height ? 'Landscape' : 'Portrait' }}
      </div>
    </div>
  </UseWindowSize>

  <!-- Window size visualization -->
  <UseWindowSize v-slot="{ width, height }">
    <div class="size-viz">
      <h3>Size Visualization</h3>
      <div 
        class="window-preview"
        :style="{ 
          width: Math.max(50, width / 10) + 'px',
          height: Math.max(30, height / 10) + 'px'
        }"
      >
        <span class="preview-text">{{ width }}×{{ height }}</span>
      </div>
    </div>
  </UseWindowSize>
</template>

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

function getBreakpointName(width) {
  if (width < 640) return 'Mobile';
  if (width < 768) return 'Small Tablet';
  if (width < 1024) return 'Tablet';
  if (width < 1280) return 'Desktop';
  return 'Large Desktop';
}

function getBreakpointClass(width) {
  if (width < 640) return 'mobile';
  if (width < 768) return 'sm';
  if (width < 1024) return 'tablet';
  if (width < 1280) return 'desktop';
  return 'xl';
}
</script>

<style>
.window-info, .breakpoint-info, .size-viz {
  border: 2px solid #ddd;
  border-radius: 8px;
  padding: 20px;
  margin: 15px 0;
}

.size-display {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 15px;
  margin-top: 15px;
}

.dimension {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 10px;
  background: #f5f5f5;
  border-radius: 6px;
}

.label {
  font-size: 0.9em;
  color: #666;
  margin-bottom: 5px;
}

.value {
  font-size: 1.5em;
  font-weight: bold;
  color: #2196f3;
}

.breakpoint {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 15px;
  border-radius: 8px;
  margin: 15px 0;
  font-weight: bold;
}

.breakpoint.mobile { background: #ffebee; }
.breakpoint.sm { background: #e8f5e8; }
.breakpoint.tablet { background: #e3f2fd; }
.breakpoint.desktop { background: #f3e5f5; }
.breakpoint.xl { background: #fff3e0; }

.window-preview {
  border: 2px solid #2196f3;
  background: #e3f2fd;
  border-radius: 4px;
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 50px;
  min-height: 30px;
  margin: 15px 0;
}

.preview-text {
  font-size: 10px;
  font-weight: bold;
  color: #1976d2;
}
</style>

UseWindowFocus Component

Tracks window focus state.

/**
 * Component that tracks window focus state
 * @example
 * <UseWindowFocus v-slot="{ focused }">
 *   <div>Window focused: {{ focused ? 'Yes' : 'No' }}</div>
 * </UseWindowFocus>
 */
interface UseWindowFocusProps {
  /** Window object @default defaultWindow */
  window?: Window;
}

/** Slot data exposed by UseWindowFocus component */
interface UseWindowFocusReturn {
  /** Whether window is focused */
  focused: Ref<boolean>;
}

Usage Examples:

<template>
  <!-- Basic focus tracking -->
  <UseWindowFocus v-slot="{ focused }">
    <div class="focus-indicator" :class="{ focused, blurred: !focused }">
      <div class="status-icon">
        {{ focused ? '👁️' : '😴' }}
      </div>
      <div class="status-text">
        Window is {{ focused ? 'focused' : 'blurred' }}
      </div>
      <div class="hint">
        {{ focused ? 'You are viewing this tab' : 'Switch to another tab to see blur state' }}
      </div>
    </div>
  </UseWindowFocus>

  <!-- Focus-based features -->
  <UseWindowFocus v-slot="{ focused }">
    <div class="focus-features">
      <h3>Focus-Aware Features</h3>
      <div class="feature" :class="{ active: focused }">
        <span class="feature-name">Auto-refresh</span>
        <span class="feature-status">{{ focused ? 'Active' : 'Paused' }}</span>
      </div>
      <div class="feature" :class="{ active: focused }">
        <span class="feature-name">Real-time updates</span>
        <span class="feature-status">{{ focused ? 'Enabled' : 'Disabled' }}</span>
      </div>
      <div class="feature" :class="{ active: !focused }">
        <span class="feature-name">Battery saver</span>
        <span class="feature-status">{{ !focused ? 'Active' : 'Inactive' }}</span>
      </div>
    </div>
  </UseWindowFocus>

  <!-- Notification helper -->
  <UseWindowFocus v-slot="{ focused }">
    <div class="notification-helper">
      <h3>Smart Notifications</h3>
      <p v-if="!focused" class="notification-tip">
        🔔 Perfect time to show notifications - user is not actively viewing
      </p>
      <p v-else class="notification-tip">
        👀 User is active - consider in-app notifications instead
      </p>
      <button @click="simulateNotification(focused)">
        Test Notification Strategy
      </button>
    </div>
  </UseWindowFocus>
</template>

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

function simulateNotification(focused) {
  if (focused) {
    alert('In-app notification (user is active)');
  } else {
    console.log('Browser notification sent (user is away)');
  }
}
</script>

<style>
.focus-indicator {
  padding: 20px;
  border-radius: 8px;
  text-align: center;
  transition: all 0.3s;
  border: 2px solid #ddd;
}

.focus-indicator.focused {
  background: #e8f5e8;
  border-color: #4caf50;
}

.focus-indicator.blurred {
  background: #ffebee;
  border-color: #f44336;
}

.status-icon {
  font-size: 2em;
  margin-bottom: 10px;
}

.status-text {
  font-size: 1.2em;
  font-weight: bold;
  margin-bottom: 5px;
}

.hint {
  font-size: 0.9em;
  color: #666;
  font-style: italic;
}

.focus-features {
  border: 1px solid #ddd;
  border-radius: 8px;
  padding: 20px;
}

.feature {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px;
  margin: 8px 0;
  border-radius: 6px;
  background: #f5f5f5;
  transition: background 0.3s;
}

.feature.active {
  background: #e8f5e8;
}

.feature-name {
  font-weight: bold;
}

.feature-status {
  font-size: 0.9em;
  color: #666;
}

.notification-helper {
  border: 1px solid #ddd;
  border-radius: 8px;
  padding: 20px;
}

.notification-tip {
  padding: 10px;
  border-radius: 6px;
  background: #f0f8ff;
  border-left: 4px solid #2196f3;
  margin: 15px 0;
}
</style>

UseDocumentVisibility Component

Tracks document visibility state using the Page Visibility API.

/**
 * Component that tracks document visibility state
 * @example
 * <UseDocumentVisibility v-slot="{ visibility }">
 *   <div>Page visibility: {{ visibility }}</div>
 * </UseDocumentVisibility>
 */
interface UseDocumentVisibilityProps {
  /** Document object @default defaultDocument */
  document?: Document;
}

/** Slot data exposed by UseDocumentVisibility component */
interface UseDocumentVisibilityReturn {
  /** Current document visibility state */
  visibility: Ref<DocumentVisibilityState>;
}

type DocumentVisibilityState = 'visible' | 'hidden' | 'prerender';

Usage Examples:

<template>
  <!-- Basic visibility tracking -->
  <UseDocumentVisibility v-slot="{ visibility }">
    <div class="visibility-tracker" :class="visibility">
      <div class="visibility-indicator">
        <span class="status-dot"></span>
        <span class="status-text">{{ getVisibilityText(visibility) }}</span>
      </div>
      <div class="visibility-description">
        {{ getVisibilityDescription(visibility) }}
      </div>
    </div>
  </UseDocumentVisibility>

  <!-- Performance optimization guide -->
  <UseDocumentVisibility v-slot="{ visibility }">
    <div class="performance-guide">
      <h3>Performance Optimization</h3>
      <div class="optimization-item" :class="{ active: visibility === 'visible' }">
        <span class="opt-name">Animations</span>
        <span class="opt-status">{{ visibility === 'visible' ? 'Running' : 'Paused' }}</span>
      </div>
      <div class="optimization-item" :class="{ active: visibility === 'visible' }">
        <span class="opt-name">API Polling</span>
        <span class="opt-status">{{ visibility === 'visible' ? 'Active' : 'Reduced' }}</span>
      </div>
      <div class="optimization-item" :class="{ active: visibility === 'hidden' }">
        <span class="opt-name">Background Tasks</span>
        <span class="opt-status">{{ visibility === 'hidden' ? 'Optimized' : 'Normal' }}</span>
      </div>
    </div>
  </UseDocumentVisibility>

  <!-- User engagement tracking -->
  <UseDocumentVisibility v-slot="{ visibility }">
    <div class="engagement-tracker">
      <h3>User Engagement</h3>
      <div class="engagement-stats">
        <div class="stat">
          <span class="stat-label">Current Status:</span>
          <span class="stat-value" :class="visibility">{{ visibility }}</span>
        </div>
        <div class="stat">
          <span class="stat-label">Engagement Level:</span>
          <span class="stat-value">{{ getEngagementLevel(visibility) }}</span>
        </div>
      </div>
      <div class="engagement-actions">
        <button 
          @click="logEngagement(visibility)"
          :disabled="visibility !== 'visible'"
        >
          Log User Action
        </button>
      </div>
    </div>
  </UseDocumentVisibility>
</template>

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

function getVisibilityText(visibility) {
  switch (visibility) {
    case 'visible': return 'Page is Visible';
    case 'hidden': return 'Page is Hidden';
    case 'prerender': return 'Page is Pre-rendering';
    default: return 'Unknown State';
  }
}

function getVisibilityDescription(visibility) {
  switch (visibility) {
    case 'visible':
      return 'The page is currently visible and active.';
    case 'hidden':
      return 'The page is hidden (minimized, in background tab, etc.).';
    case 'prerender':
      return 'The page is being pre-rendered in the background.';
    default:
      return 'Unable to determine page visibility state.';
  }
}

function getEngagementLevel(visibility) {
  switch (visibility) {
    case 'visible': return 'High';
    case 'hidden': return 'Low';
    case 'prerender': return 'None';
    default: return 'Unknown';
  }
}

function logEngagement(visibility) {
  console.log(`User action logged - Page visibility: ${visibility}`);
}
</script>

<style>
.visibility-tracker {
  padding: 20px;
  border-radius: 8px;
  border: 2px solid #ddd;
  transition: all 0.3s;
}

.visibility-tracker.visible {
  background: #e8f5e8;
  border-color: #4caf50;
}

.visibility-tracker.hidden {
  background: #ffebee;
  border-color: #f44336;
}

.visibility-tracker.prerender {
  background: #fff3e0;
  border-color: #ff9800;
}

.visibility-indicator {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 1.2em;
  font-weight: bold;
  margin-bottom: 10px;
}

.status-dot {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: currentColor;
}

.visibility-description {
  font-size: 0.9em;
  color: #666;
}

.performance-guide, .engagement-tracker {
  border: 1px solid #ddd;
  border-radius: 8px;
  padding: 20px;
}

.optimization-item, .stat {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 8px 12px;
  margin: 5px 0;
  border-radius: 6px;
  background: #f5f5f5;
}

.optimization-item.active {
  background: #e3f2fd;
}

.engagement-stats {
  margin: 15px 0;
}

.stat-value.visible {
  color: #4caf50;
  font-weight: bold;
}

.stat-value.hidden {
  color: #f44336;
  font-weight: bold;
}

.stat-value.prerender {
  color: #ff9800;
  font-weight: bold;
}
</style>

UsePageLeave Component

Detects when the cursor leaves the page viewport.

/**
 * Component that detects when cursor leaves page
 * @example
 * <UsePageLeave v-slot="{ isLeft }">
 *   <div>Cursor left page: {{ isLeft ? 'Yes' : 'No' }}</div>
 * </UsePageLeave>
 */
interface UsePageLeaveProps {
  /** Window object @default defaultWindow */
  window?: Window;
}

/** Slot data exposed by UsePageLeave component */
interface UsePageLeaveReturn {
  /** Whether cursor has left the page */
  isLeft: Ref<boolean>;
}

Usage Examples:

<template>
  <!-- Basic page leave detection -->
  <UsePageLeave v-slot="{ isLeft }">
    <div class="page-leave-indicator" :class="{ left: isLeft, present: !isLeft }">
      <div class="cursor-status">
        <span class="cursor-icon">{{ isLeft ? '📤' : '📍' }}</span>
        <span class="cursor-text">
          Cursor {{ isLeft ? 'left the page' : 'is on the page' }}
        </span>
      </div>
    </div>
  </UsePageLeave>

  <!-- Exit intent detection -->
  <UsePageLeave v-slot="{ isLeft }">
    <div class="exit-intent" v-if="isLeft">
      <div class="exit-modal">
        <h3>Wait! Don't leave yet! 👋</h3>
        <p>Are you sure you want to leave? You might miss out on:</p>
        <ul>
          <li>✨ Special offers</li>
          <li>🎯 Personalized content</li>
          <li>📚 Helpful resources</li>
        </ul>
        <div class="exit-actions">
          <button @click="stayOnPage" class="stay-btn">Stay</button>
          <button @click="dismissModal" class="leave-btn">Leave</button>
        </div>
      </div>
      <div class="exit-overlay" @click="dismissModal"></div>
    </div>
  </UsePageLeave>

  <!-- User engagement tracking -->
  <UsePageLeave v-slot="{ isLeft }">
    <div class="engagement-monitor">
      <h3>Engagement Monitor</h3>
      <div class="engagement-indicator" :class="{ engaged: !isLeft, disengaged: isLeft }">
        <span class="status">{{ isLeft ? '⚠️ Potential Exit' : '✅ User Engaged' }}</span>
      </div>
      <div class="engagement-tips" v-if="isLeft">
        <p>💡 Consider:</p>
        <ul>
          <li>Showing exit-intent popup</li>
          <li>Saving user progress</li>
          <li>Triggering retention campaigns</li>
        </ul>
      </div>
    </div>
  </UsePageLeave>
</template>

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

const showExitModal = ref(true);

function stayOnPage() {
  showExitModal.value = false;
  // Additional logic to engage user
}

function dismissModal() {
  showExitModal.value = false;
}
</script>

<style>
.page-leave-indicator {
  padding: 20px;
  border-radius: 8px;
  border: 2px solid #ddd;
  text-align: center;
  transition: all 0.3s;
}

.page-leave-indicator.present {
  background: #e8f5e8;
  border-color: #4caf50;
}

.page-leave-indicator.left {
  background: #fff3cd;
  border-color: #ffc107;
}

.cursor-status {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  font-size: 1.2em;
}

.cursor-icon {
  font-size: 1.5em;
}

.exit-intent {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
}

.exit-overlay {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.7);
}

.exit-modal {
  position: relative;
  background: white;
  border-radius: 12px;
  padding: 30px;
  max-width: 400px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
  text-align: center;
}

.exit-modal h3 {
  color: #333;
  margin-bottom: 15px;
}

.exit-modal ul {
  text-align: left;
  margin: 15px 0;
}

.exit-actions {
  display: flex;
  gap: 15px;
  margin-top: 20px;
}

.stay-btn, .leave-btn {
  flex: 1;
  padding: 12px 20px;
  border: none;
  border-radius: 6px;
  font-size: 16px;
  cursor: pointer;
}

.stay-btn {
  background: #4caf50;
  color: white;
}

.leave-btn {
  background: #f5f5f5;
  color: #666;
}

.engagement-monitor {
  border: 1px solid #ddd;
  border-radius: 8px;
  padding: 20px;
}

.engagement-indicator {
  padding: 15px;
  border-radius: 8px;
  text-align: center;
  font-weight: bold;
  margin: 15px 0;
}

.engagement-indicator.engaged {
  background: #e8f5e8;
  color: #2e7d32;
}

.engagement-indicator.disengaged {
  background: #fff3cd;
  color: #f57c00;
}

.engagement-tips {
  background: #f0f8ff;
  border-left: 4px solid #2196f3;
  padding: 15px;
  margin-top: 15px;
}

.engagement-tips ul {
  margin: 10px 0;
  padding-left: 20px;
}
</style>

Type Definitions

/** Common types used across window and document components */
type MaybeRefOrGetter<T> = T | Ref<T> | (() => T);

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

/** Document visibility states */
type DocumentVisibilityState = 'visible' | 'hidden' | 'prerender';

/** Window size information */
interface WindowSize {
  width: number;
  height: number;
}

/** Configuration interfaces */
interface ConfigurableWindow {
  /** Window object @default defaultWindow */
  window?: Window;
}

interface ConfigurableDocument {
  /** Document object @default defaultDocument */
  document?: Document;
}

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