npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

vue-bare-composables

v3.5.1

Published

**Vue composables for a frustration-free development experience**

Readme

Vue Bare Composables

Vue composables for a frustration-free development experience

github license npm version

Features

  • 🎯 Type-safe: Built with TypeScript using the strictest configuration
  • 🪶 Lightweight: Zero dependencies besides Vue
  • 🧩 Modular: Use only what you need
  • 📦 Tree-shakeable: Unused code is removed in production builds
  • 🔍 Form validation: Built-in support for field and form-level validation

Installation

# npm
npm install vue-bare-composables

# yarn
yarn add vue-bare-composables

# pnpm
pnpm add vue-bare-composables

Requirements

  • Vue 3.x or higher
  • Node.js 22 or higher

Usage

Available composables:

useForm (Form Handling)

import { useForm } from 'vue-bare-composables'

// In your Vue component
const { state, getProps, getListeners, handleSubmit } = useForm(
  {
    email: '',
    password: '',
  },
  {
    validate: {
      email: (value) => {
        if (!value) {
          return 'Email is required'
        }

        if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value as string)) {
          return 'Invalid email format'
        }
      },
      password: (value) => {
        if (!value) {
          return 'Password is required'
        }

        if ((value as string).length < 8) {
          return 'Password must be at least 8 characters'
        }
      },
    },
    globalValidate: async (values, { setError, setGlobalError }) => {
      // Example of form-level validation
      if (values.password === '12345678') {
        setError('password', 'Password is too common')
      }
    },
    // Automatically trim string values before validation and submission
    trimStrings: true,
  },
)

// Use in template
const onSubmit = handleSubmit(async (data) => {
  // Handle form submission
  console.log(data)
})

In your template:

<template>
  <form @submit.prevent="onSubmit">
    <input
      id="email"
      type="email"
      v-bind="getProps('email')"
      v-on="getListeners('email')"
    />
    <span v-if="state.errors.email.value">
      {{ state.errors.email.value }}
    </span>

    <input
      id="password"
      type="password"
      v-bind="getProps('password')"
      v-on="getListeners('password')"
    />
    <span v-if="state.errors.password.value">
      {{ state.errors.password.value }}
    </span>

    <button type="submit" :disabled="state.submitting.value">Submit</button>
  </form>
</template>

The useForm composable provides the following features:

  • Type-safe form handling: Full TypeScript support with strict type checking
  • Field-level validation: Validate individual fields with custom validation functions
  • Form-level validation: Validate multiple fields together or perform cross-field validation
  • Automatic error handling: Display validation errors and manage error states
  • Form submission handling: Handle form submissions with loading states
  • String trimming: Optionally trim string values before validation and submission
  • Form reset: Reset form to initial values
  • Reactive state: All form state is reactive and can be watched for changes

Options

The useForm composable accepts the following options:

  • validate: Object containing field-level validation functions
  • globalValidate: Function for form-level validation
  • trimStrings: Boolean to enable automatic trimming of string values before validation and submission (defaults to false)
  • trimStringExclude: Array of field names to exclude from string trimming (only available when trimStrings is true)

Example with String Trimming

const { state, handleSubmit } = useForm(
  {
    name: '',
    email: '',
    password: '',
  },
  {
    trimStrings: true, // Enable automatic string trimming
    trimStringExclude: ['password'], // Exclude password from trimming
    validate: {
      name: (value) => {
        // Value will be automatically trimmed before validation
        if (!value) {
          return 'Name is required'
        }

        if (value.length < 3) {
          return 'Name must be at least 3 characters'
        }
      },
      password: (value) => {
        // Value will NOT be trimmed because it's in trimStringExclude
        if (!value) {
          return 'Password is required'
        }

        if (value.length < 8) {
          return 'Password must be at least 8 characters'
        }
      },
    },
  },
)

// When submitting, string values will be automatically trimmed except for excluded fields
const onSubmit = handleSubmit(async (data) => {
  // data.name and data.email will be trimmed, but data.password won't
  // The form's internal state will also be updated with the trimmed values
  console.log(data)
})

Note: The trimStringExclude option is only available when trimStrings is set to true. TypeScript will enforce this constraint at compile time.

When string trimming is enabled, the form's internal state will be automatically updated with the trimmed values after a successful submission. This ensures that the form's state always matches what was actually submitted.

useFixToVisualViewport (Visual Viewport Fixed Positioning)

import { useFixToVisualViewport } from 'vue-bare-composables'

// In your Vue component
const element = ref<HTMLElement | null>(null)

// For bottom positioning
useFixToVisualViewport(element, {
  layoutViewportId: 'viewport',
  location: 'bottom',
})

// For top positioning
useFixToVisualViewport(element, {
  layoutViewportId: 'viewport',
  location: 'top',
})

// For positioning above another element
useFixToVisualViewport(element, {
  layoutViewportId: 'viewport',
  location: 'above',
  relativeElement: anotherElement,
  distance: 10,
})

// With reactive options (options that can change over time)
const viewportOptions = ref({
  layoutViewportId: 'viewport',
  location: 'bottom',
})

useFixToVisualViewport(element, viewportOptions)

// The position will update reactively when options change
viewportOptions.value.location = 'top'

In your template:

<template>
  <!-- Your content -->
  <div ref="element">
    This element will maintain its position relative to the visual viewport
  </div>

  <!-- This should be in the main page body -->
  <div id="viewport" />
</template>

useIsWindowFocused (Window Focus Detection)

import { useIsWindowFocused } from 'vue-bare-composables'

// In your Vue component
const isFocused = useIsWindowFocused()

In your template:

<template>
  <div>
    Window is currently {{ isFocused.value ? 'focused' : 'not focused' }}
  </div>
</template>

useSnackbarStore (Toast/Snackbar Notifications)

A Pinia store for managing toast/snackbar notifications with support for:

  • Message queueing
  • Route-specific messages
  • Custom actions
  • Auto-dismissal
  • Manual dismissal
import { useSnackbarStore } from 'vue-bare-composables'

// In your Vue component
const snackbar = useSnackbarStore()

// If used within Nuxt, you need to pass the Pinia store instance
// const pinia = usePinia()
// const snackbarStore = useSnackbarStore(pinia as Pinia)

// Simple message
snackbar.enqueueMessage({ message: 'Operation successful!' })

// Message with custom actions
snackbar.enqueueMessage({
  message: 'Item deleted',
  actions: [
    {
      text: 'Undo',
      callback: () => {
        // Handle undo action
      },
    },
  ],
})

// Route-specific message (only shows on specified route)
snackbar.enqueueMessage({
  message: 'Welcome to the dashboard',
  route: '/dashboard',
})

// Set the current route whenever the route changes
snackbar.setRouteFullPath('/dashboard')

In your template:

<template>
  <div v-if="snackbar.message" class="snackbar">
    {{ snackbar.message }}

    <div v-if="snackbar.actions" class="actions">
      <button
        v-for="action in snackbar.actions"
        :key="action.text"
        @click="action.callback"
      >
        {{ action.text }}
      </button>
    </div>
  </div>
</template>

Key features:

  • Messages are queued and shown in order
  • Messages auto-dismiss after 8 seconds
  • Messages can be manually dismissed
  • Route-specific messages only show on matching routes
  • Custom actions with callbacks
  • Messages older than 30 seconds are automatically cleaned up
  • Reactive state management with Pinia

License

MIT