https://github.com/ericvera/vue-bare-composables
Vue composables for a frustration-free development experience
https://github.com/ericvera/vue-bare-composables
Last synced: 4 months ago
JSON representation
Vue composables for a frustration-free development experience
- Host: GitHub
- URL: https://github.com/ericvera/vue-bare-composables
- Owner: ericvera
- License: mit
- Created: 2025-01-29T21:09:10.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2025-04-07T06:16:07.000Z (about 1 year ago)
- Last Synced: 2025-04-07T07:25:17.271Z (about 1 year ago)
- Language: TypeScript
- Homepage:
- Size: 1.52 MB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Vue Bare Composables
**Vue composables for a frustration-free development experience**
[](https://github.com/ericvera/vue-bare-composables/blob/main/LICENSE)
[](https://npmjs.org/package/vue-bare-composables)
## Features
- đ¯ **Type-safe**: Built with TypeScript using the strictest configuration
- đĒļ **Lightweight**: Zero dependencies besides Vue (Pinia required for useSnackbarStore)
- đ§Š **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
```bash
# 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)](#useform-form-handling)
- [useFixToVisualViewport (Visual Viewport Fixed Positioning)](#usefixtovisualviewport-visual-viewport-fixed-positioning)
- [useIsWindowFocused (Window Focus Detection)](#useiswindowfocused-window-focus-detection)
- [useSnackbarStore (Toast/Snackbar Notifications)](#usesnackbarstore-toastsnackbar-notifications)
### useForm (Form Handling)
```ts
import { useForm } from 'vue-bare-composables'
// In your Vue component
const { state, getProps, getListeners, handleSubmit, reset } = 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:
```vue
{{ state.globalError.value }}
{{ state.errors.email.value }}
{{ state.errors.password.value }}
Submit
Reset
```
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
- **Dirty state tracking**: `state.isDirty` indicates whether form values differ from initial values (useful for unsaved-changes warnings, enabling reset buttons, etc.)
- **Form-level errors**: `state.globalError` displays validation errors from `globalValidate` (e.g., cross-field validation)
- **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
```ts
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)
```ts
import { useFixToVisualViewport } from 'vue-bare-composables'
// In your Vue component
const element = ref(null)
// For bottom positioning
useFixToVisualViewport(element, {
layoutViewportId: 'viewport',
location: 'bottom',
})
// For top positioning
useFixToVisualViewport(element, {
layoutViewportId: 'viewport',
location: 'top',
})
// For positioning above another element (pass the element, e.g. anotherElement.value if using a ref)
useFixToVisualViewport(element, {
layoutViewportId: 'viewport',
location: 'above',
relativeElement: anotherElement.value,
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:
```vue
This element will maintain its position relative to the visual viewport
```
### useIsWindowFocused (Window Focus Detection)
```ts
import { useIsWindowFocused } from 'vue-bare-composables'
// In your Vue component
const isFocused = useIsWindowFocused()
```
In your template:
```vue
Window is currently {{ isFocused.value ? 'focused' : 'not focused' }}
```
### 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
```ts
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:
```vue
```
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](./LICENSE)