sv-pdf
v1.1.4
Published
A comprehensive PDF viewer and acceptance component library for Svelte
Maintainers
Readme
sv-pdf
A PDF viewer and acceptance component library for Svelte applications. Built with TypeScript and powered by PDF.js.
Features
- PDFViewer: Full-featured PDF viewer with zoom, pan, and navigation controls
- PDFAccept: PDF acceptance component that tracks user progress through document
- Modular Controls: Override individual control sections (navigation, zoom, actions) with custom snippets
- Granular Control Disabling: Disable entire control sections OR individual buttons (prev, next, zoom in/out, reset, etc.)
- Flexible Customization: Customize all button text, labels, and ARIA attributes via props
- Reset Zoom Modes: Configure reset button behavior (fit to width/height or 100% scale)
- Zoom Controls: Zoom in/out, auto-fit, and configurable reset functionality
- Pan & Navigate: Mouse and touch support for panning and navigation
- Responsive: Mobile-friendly with touch gestures and auto-fit scaling
- Progress Tracking: Built-in progress indicator for acceptance workflows with auto-completion
- TypeScript: Full TypeScript support with proper type definitions
- Accessible: Customizable ARIA labels and keyboard-friendly controls
Installation
npm install sv-pdf pdfjs-distRequirements
- Tailwind CSS: This library uses Tailwind CSS classes. You must have Tailwind CSS configured in your project.
- PDF.js Worker: Copy
pdf.worker.min.mjsfrompdfjs-dist/build/to yourstatic(orpublic) folder.
If you don't have Tailwind CSS set up yet:
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss initMake sure your tailwind.config.js includes the library's components:
export default {
content: [
'./src/**/*.{html,js,svelte,ts}',
'./node_modules/sv-pdf/dist/**/*.svelte'
],
theme: {
extend: {},
},
plugins: [],
}Basic Usage
PDFViewer
<script>
import { PDFViewer } from 'sv-pdf';
</script>
<PDFViewer
src="/path/to/document.pdf"
width={800}
height={600}
/>PDFAccept
<script>
import { PDFAccept } from 'sv-pdf';
function handleComplete() {
console.log('User has accepted the document');
}
</script>
<PDFAccept
src="/path/to/document.pdf"
width={800}
height={600}
onComplete={handleComplete}
/>Component APIs
PDFViewer Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| src | string | - | URL path to PDF file |
| base64 | string | - | Base64 encoded PDF data |
| width | number \| string | 800 | Viewer width |
| height | number \| string | 600 | Viewer height |
| autoFitHeight | boolean | false | Auto-fit PDF to container height |
| showControls | boolean | true | Show navigation/zoom controls |
| controlsPosition | 'top' \| 'bottom' | 'top' | Position of controls |
| resetZoomMode | 'width' \| 'height' \| '100%' | 'width' | Reset button zoom behavior |
| controls | Snippet<[ControlsProps]> | - | Custom controls snippet (full override) |
| navigationControls | Snippet<[ControlsProps]> | - | Custom navigation controls snippet |
| zoomControls | Snippet<[ControlsProps]> | - | Custom zoom controls snippet |
| actionControls | Snippet<[ControlsProps]> | - | Custom action controls snippet |
| disableControls | PDFViewerDisableControls | {} | Selectively disable control sections or individual buttons |
| controlsContent | PDFViewerControlsContent | {} | Customize button text and labels |
| baseStyling | PDFViewerStyling | {} | Customize component styling with Tailwind classes |
| onPageChange | (page: number) => void | - | Callback when page changes |
| api | any | - | Bindable API object for programmatic control |
PDFViewerDisableControls Object
{
// Section-level controls (hides entire section)
navigation?: boolean; // Hide entire navigation section
zoom?: boolean; // Hide entire zoom section
actions?: boolean; // Hide entire actions section
// Granular button-level controls (hide individual buttons)
prev?: boolean; // Hide previous page button
next?: boolean; // Hide next page button
zoomIn?: boolean; // Hide zoom in button
zoomOut?: boolean; // Hide zoom out button
reset?: boolean; // Hide reset zoom button
autoFit?: boolean; // Hide auto-fit button
}Note: Section-level flags take precedence over button-level flags. For example, if navigation: true, the prev and next flags are ignored since the entire navigation section is hidden.
PDFViewerControlsContent Object
{
prevButton?: string; // Default: "Prev"
nextButton?: string; // Default: "Next"
zoomOutButton?: string; // Default: "−"
zoomInButton?: string; // Default: "+"
autoFitButton?: string; // Default: "Auto Fit"
resetButton?: string; // Default: "Reset"
loadingText?: string; // Default: "Loading PDF..."
errorText?: string; // Default: "Error: {msg}" (use {msg} for error message)
ariaLabel?: string; // Default: "PDF viewer - Use mouse to pan and scroll to zoom"
}PDFAccept Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| src | string | - | URL path to PDF file |
| base64 | string | - | Base64 encoded PDF data |
| width | number \| string | 800 | Viewer width |
| height | number \| string | 600 | Viewer height |
| autoFitHeight | boolean | false | Auto-fit PDF to container height |
| showControls | boolean | true | Show navigation/zoom controls |
| controlsPosition | 'top' \| 'bottom' | 'bottom' | Position of controls |
| resetZoomMode | 'width' \| 'height' \| '100%' | 'width' | Reset button zoom behavior |
| controls | Snippet<[AcceptControlsProps]> | - | Custom controls snippet (full override) |
| navigationControls | Snippet<[AcceptControlsProps]> | - | Custom navigation controls snippet |
| zoomControls | Snippet<[AcceptControlsProps]> | - | Custom zoom controls snippet |
| actionControls | Snippet<[AcceptControlsProps]> | - | Custom action controls snippet |
| progressIndicator | Snippet<[AcceptControlsProps]> | - | Custom progress indicator snippet |
| disableControls | PDFAcceptDisableControls | {} | Selectively disable control sections or individual buttons |
| controlsContent | PDFAcceptControlsContent | {} | Customize button text and labels |
| baseStyling | PDFAcceptStyling | {} | Customize component styling with Tailwind classes |
| onPageChange | (page: number) => void | - | Callback when page changes |
| onComplete | () => void | - | Callback when user completes acceptance |
| api | any | - | Bindable API object for programmatic control |
PDFAcceptDisableControls Object
{
// Section-level controls (hides entire section)
navigation?: boolean; // Hide entire navigation section
zoom?: boolean; // Hide entire zoom section
actions?: boolean; // Hide entire actions section
progress?: boolean; // Hide progress indicator overlay
// Granular button-level controls (hide individual buttons)
prev?: boolean; // Hide previous page button
next?: boolean; // Hide next page button
zoomIn?: boolean; // Hide zoom in button
zoomOut?: boolean; // Hide zoom out button
reset?: boolean; // Hide reset zoom button
done?: boolean; // Hide done/complete button
}Note: When done: true, the progress indicator automatically shows "Completed" once the user reaches the last page, since there's no button to click.
PDFAcceptControlsContent Object
{
prevPage?: string; // Default: "Prev"
nextPage?: string; // Default: "Next"
prevPageAriaLabel?: string; // Default: undefined
nextPageAriaLabel?: string; // Default: undefined
pageIndicator?: string; // Default: "{current} / {total}" (use {current} and {total} placeholders)
zoomOutButton?: string; // Default: "−"
zoomInButton?: string; // Default: "+"
resetButton?: string; // Default: "Reset"
doneButton?: string; // Default: "Done"
completedText?: string; // Default: "✓ Completed"
progressText?: string; // Default: "Progress: {current}/{total}" (use placeholders)
loadingText?: string; // Default: "Loading PDF..."
errorText?: string; // Default: "Error: {msg}"
ariaLabel?: string; // Default: "PDF viewer - Use mouse to pan and scroll to zoom"
}Advanced Usage
Reset Zoom Modes
Control how the reset button behaves with the resetZoomMode prop:
<script>
import { PDFViewer } from 'sv-pdf';
</script>
<!-- Reset to fit width (default) -->
<PDFViewer
src="/document.pdf"
resetZoomMode="width"
/>
<!-- Reset to fit height -->
<PDFViewer
src="/document.pdf"
resetZoomMode="height"
/>
<!-- Reset to 100% scale -->
<PDFViewer
src="/document.pdf"
resetZoomMode="100%"
/>Granular Control Disabling
Disable individual buttons without hiding entire control sections:
<script>
import { PDFViewer, PDFAccept } from 'sv-pdf';
</script>
<!-- PDFViewer: Hide only the previous button and zoom out -->
<PDFViewer
src="/document.pdf"
disableControls={{
prev: true,
zoomOut: true
}}
/>
<!-- PDFViewer: Hide auto-fit but keep reset -->
<PDFViewer
src="/document.pdf"
disableControls={{
autoFit: true
}}
/>
<!-- PDFAccept: Hide done button (auto-completes on last page) -->
<PDFAccept
src="/terms.pdf"
disableControls={{
done: true
}}
onComplete={() => console.log('Auto-completed')}
/>
<!-- PDFAccept: Forward-only navigation (no prev button) -->
<PDFAccept
src="/contract.pdf"
disableControls={{
prev: true
}}
/>
<!-- Mix section and button-level disabling -->
<PDFViewer
src="/document.pdf"
disableControls={{
navigation: false, // Keep navigation section
prev: true, // But hide prev button
actions: false, // Keep actions section
autoFit: true // But hide autoFit button
}}
/>Granular Control Behavior:
- Section-level flags (
navigation,zoom,actions) hide entire control groups - Button-level flags (
prev,next,zoomIn,zoomOut,reset,autoFit,done) hide individual buttons - Section-level takes precedence: if
navigation: true, individualprev/nextflags are ignored - PDFAccept auto-completion: When
done: true, the progress indicator automatically shows "Completed" when the user reaches the last page
Customizing Text and Labels
<script>
import { PDFViewer, PDFAccept } from 'sv-pdf';
</script>
<!-- PDFViewer with custom text -->
<PDFViewer
src="/document.pdf"
controlsContent={{
prevButton: '◀ Previous',
nextButton: 'Next ▶',
zoomInButton: '🔍+',
zoomOutButton: '🔍−',
resetButton: '↺ Reset View',
autoFitButton: '⛶ Fit Screen',
loadingText: 'Loading your document...',
errorText: 'Failed to load: {msg}'
}}
/>
<!-- PDFAccept with custom text and placeholders -->
<PDFAccept
src="/terms.pdf"
controlsContent={{
prevPage: '← Back',
nextPage: 'Continue →',
pageIndicator: '{current} of {total}',
doneButton: 'I Accept',
completedText: '✅ Accepted',
progressText: 'Read: {current}/{total} pages',
prevPageAriaLabel: 'Go to previous page',
nextPageAriaLabel: 'Go to next page'
}}
/>Disabling Specific Controls
<script>
import { PDFViewer, PDFAccept } from 'sv-pdf';
</script>
<!-- Hide entire zoom section -->
<PDFViewer
src="/document.pdf"
disableControls={{ zoom: true, actions: true }}
/>
<!-- Hide specific buttons only -->
<PDFViewer
src="/document.pdf"
disableControls={{
prev: true, // Hide previous button
zoomOut: true, // Hide zoom out button
autoFit: true // Hide auto-fit button
}}
/>
<!-- PDFAccept: Read-only mode (no done button, auto-completes) -->
<PDFAccept
src="/document.pdf"
disableControls={{ done: true }}
/>Modular Custom Controls
Override individual control sections while keeping others default:
<script>
import { PDFViewer } from 'sv-pdf';
</script>
<PDFViewer src="/document.pdf">
{#snippet navigationControls({ currentPage, totalPages, nextPage, prevPage })}
<div class="flex gap-2">
<button onclick={prevPage} class="custom-btn">← Back</button>
<span class="font-bold">{currentPage} of {totalPages}</span>
<button onclick={nextPage} class="custom-btn">Forward →</button>
</div>
{/snippet}
<!-- Zoom and action controls remain default -->
</PDFViewer>PDFAccept with Custom Progress Indicator
<script>
import { PDFAccept } from 'sv-pdf';
</script>
<PDFAccept
src="/terms.pdf"
controlsContent={{
doneButton: 'I Accept',
completedText: '✅ Accepted',
progressText: 'Read: {current}/{total} pages'
}}
onComplete={() => alert('Terms accepted!')}
>
{#snippet progressIndicator({ isCompleted, maxPageReached, totalPages })}
<div class="absolute top-2 left-2 px-3 py-1 bg-blue-500 text-white rounded-md">
{#if isCompleted}
✓ Document Accepted
{:else}
📖 Pages read: {maxPageReached}/{totalPages}
{/if}
</div>
{/snippet}
</PDFAccept>Full Custom Controls
<script>
import { PDFViewer } from 'sv-pdf';
</script>
<PDFViewer src="/document.pdf">
{#snippet controls({ currentPage, totalPages, zoomIn, zoomOut, nextPage, prevPage })}
<div class="custom-controls">
<button onclick={prevPage}>Previous</button>
<span>{currentPage} / {totalPages}</span>
<button onclick={nextPage}>Next</button>
<button onclick={zoomOut}>-</button>
<button onclick={zoomIn}>+</button>
</div>
{/snippet}
</PDFViewer>Base64 PDF Data
<script>
import { PDFViewer } from 'sv-pdf';
let base64Data = 'JVBERi0xLjMKJcTl8uXrp/Og0MTGCjQgMCBvYmoKPD...';
</script>
<PDFViewer base64={base64Data} />Programmatic Control with API Binding
Access the PDF viewer's API to control it programmatically:
<script>
import { PDFViewer } from 'sv-pdf';
let pdfApi;
function jumpToPage(page: number) {
pdfApi?.setPage(page);
}
function handlePageChange(page: number) {
console.log('Current page:', page);
}
</script>
<PDFViewer
src="/document.pdf"
bind:api={pdfApi}
onPageChange={handlePageChange}
/>
<div class="controls">
<button onclick={() => jumpToPage(1)}>Go to Page 1</button>
<button onclick={() => pdfApi?.zoomIn()}>Zoom In</button>
<button onclick={() => pdfApi?.zoomOut()}>Zoom Out</button>
<button onclick={() => pdfApi?.resetZoom()}>Reset Zoom</button>
<button onclick={() => pdfApi?.toggleAutoFit()}>Toggle Auto Fit</button>
{#if pdfApi}
<p>Current Page: {pdfApi.currentPage} / {pdfApi.totalPages}</p>
<p>Zoom: {Math.round(pdfApi.scale * 100)}%</p>
<p>Auto Fit: {pdfApi.autoFitEnabled ? 'On' : 'Off'}</p>
{/if}
</div>Available API Methods:
setPage(page: number): Promise<void>- Navigate to specific pageprevPage(): Promise<void>- Go to previous pagenextPage(): Promise<void>- Go to next pagezoomIn(): Promise<void>- Zoom inzoomOut(): Promise<void>- Zoom outresetZoom(): Promise<void>- Reset zoom (behavior based onresetZoomModeprop)toggleAutoFit(): Promise<void>- Toggle auto-fit mode
Available API Properties (read-only):
currentPage: number- Current page numbertotalPages: number- Total number of pagesscale: number- Current zoom scaleautoFitEnabled: boolean- Auto-fit mode stateisRendering: boolean- Whether currently renderingisLoading: boolean- Whether PDF is loadingerror: string- Error message if any
TypeScript Support
All types are exported from the package for full type safety:
import {
PDFViewer,
PDFAccept,
type PDFViewerProps,
type PDFAcceptProps,
type PDFViewerDisableControls,
type PDFAcceptDisableControls,
type PDFViewerControlsContent,
type PDFAcceptControlsContent,
type ControlsProps,
type AcceptControlsProps,
type PDFBaseStyling,
type PDFViewerStyling,
type PDFAcceptStyling
} from 'sv-pdf';
// Use types in your code
const viewerControls: PDFViewerDisableControls = {
prev: true,
zoomOut: true,
autoFit: false
};
const acceptContent: PDFAcceptControlsContent = {
prevPage: 'Back',
nextPage: 'Continue',
doneButton: 'I Accept',
pageIndicator: 'Page {current} of {total}'
};
// Custom snippet with typed props
function MyCustomControls(props: ControlsProps) {
// props.currentPage, props.totalPages, etc. are all typed
}Custom Styling with Tailwind CSS
Both PDFViewer and PDFAccept support complete style customization through the baseStyling prop. You can override any element's Tailwind classes while keeping the default styling for elements you don't customize.
How It Works
Each styling property accepts an array of Tailwind class strings. When provided, it completely replaces the default classes for that element:
<script>
import { PDFViewer, type PDFViewerStyling } from 'sv-pdf';
const customStyling: PDFViewerStyling = {
// Replace wrapper classes (default is removed, these apply instead)
wrapper: ['flex', 'flex-col', 'bg-blue-50', 'rounded-xl', 'shadow-2xl'],
// Replace navigation button classes
navigationButton: ['px-4', 'py-2', 'bg-purple-500', 'text-white', 'rounded-lg'],
// Leave other elements with default styling by not specifying them
};
</script>
<PDFViewer
src="/document.pdf"
baseStyling={customStyling}
/>PDFViewerStyling Interface
interface PDFViewerStyling {
// Inherited from PDFBaseStyling (affects PDF canvas and container)
container?: string[]; // PDF canvas container
loadingIndicator?: string[]; // Loading state display
errorMessage?: string[]; // Error state display
canvasWrapper?: string[]; // Canvas wrapper with pan transform
canvas?: string[]; // PDF canvas element
// PDFViewer specific elements
wrapper?: string[]; // Main component wrapper
controls?: string[]; // Controls bar container
navigationSection?: string[]; // Navigation buttons container
navigationButton?: string[]; // Previous/Next buttons
pageIndicator?: string[]; // Page counter display
zoomSection?: string[]; // Zoom controls container
zoomButton?: string[]; // Zoom in/out buttons
zoomScale?: string[]; // Zoom percentage display
actionsSection?: string[]; // Action buttons container
autoFitButton?: string[]; // Auto-fit toggle button
resetButton?: string[]; // Reset zoom button
}Default Classes:
{
wrapper: ['flex', 'flex-col', 'bg-gray-50', 'rounded-lg', 'shadow-lg', 'overflow-hidden'],
controls: ['flex', 'items-center', 'justify-between', 'gap-4', 'p-4', 'bg-white', 'border-gray-200'],
navigationSection: ['flex', 'items-center', 'gap-2', 'flex-shrink-0'],
navigationButton: ['px-3', 'py-2', 'text-sm', 'font-semibold', 'bg-blue-50', 'text-blue-700', 'rounded-full', 'hover:bg-blue-100', 'disabled:bg-gray-100', 'disabled:text-gray-400', 'disabled:cursor-not-allowed', 'transition-colors'],
pageIndicator: ['text-sm', 'font-medium', 'text-gray-700', 'text-center', 'bg-gray-50', 'px-3', 'py-2', 'rounded-full', 'whitespace-nowrap'],
// ... see source code for complete defaults
}PDFAcceptStyling Interface
interface PDFAcceptStyling extends PDFViewerStyling {
// All PDFViewerStyling properties plus:
doneButton?: string[]; // Done/Accept button
progressIndicator?: string[]; // Progress overlay container
progressCompleted?: string[]; // Completed state text
}Additional Defaults:
{
doneButton: ['px-4', 'py-2', 'text-sm', 'font-semibold', 'bg-emerald-50', 'text-emerald-700', 'rounded-full', 'hover:bg-emerald-100', 'transition-colors', 'border-2', 'border-emerald-200', 'whitespace-nowrap'],
progressIndicator: ['absolute', 'top-4', 'right-4', 'bg-white/90', 'backdrop-blur-sm', 'rounded-full', 'px-3', 'py-1', 'text-xs', 'font-medium', 'text-gray-600', 'shadow-md'],
progressCompleted: ['text-emerald-600']
}Styling Examples
Example 1: Custom Theme Colors
<script>
import { PDFViewer } from 'sv-pdf';
</script>
<PDFViewer
src="/document.pdf"
baseStyling={{
wrapper: ['flex', 'flex-col', 'bg-indigo-50', 'rounded-2xl', 'shadow-xl'],
navigationButton: ['px-4', 'py-2', 'bg-indigo-500', 'text-white', 'rounded-lg', 'hover:bg-indigo-600'],
zoomButton: ['px-3', 'py-2', 'bg-purple-500', 'text-white', 'rounded-lg', 'hover:bg-purple-600'],
resetButton: ['px-4', 'py-2', 'bg-gray-600', 'text-white', 'rounded-lg', 'hover:bg-gray-700']
}}
/>Example 2: Minimalist Dark Theme
<script>
import { PDFAccept } from 'sv-pdf';
</script>
<PDFAccept
src="/terms.pdf"
baseStyling={{
wrapper: ['flex', 'flex-col', 'bg-gray-900', 'rounded-lg'],
controls: ['flex', 'items-center', 'justify-between', 'p-4', 'bg-gray-800', 'border-t', 'border-gray-700'],
navigationButton: ['px-3', 'py-2', 'bg-gray-700', 'text-gray-200', 'rounded', 'hover:bg-gray-600'],
pageIndicator: ['text-gray-300', 'bg-gray-700', 'px-3', 'py-1', 'rounded'],
zoomButton: ['px-2', 'py-1', 'bg-gray-700', 'text-gray-200', 'rounded', 'hover:bg-gray-600'],
doneButton: ['px-4', 'py-2', 'bg-green-600', 'text-white', 'rounded', 'hover:bg-green-500'],
progressIndicator: ['absolute', 'top-4', 'right-4', 'bg-gray-800', 'text-gray-200', 'px-3', 'py-1', 'rounded', 'text-xs'],
container: ['bg-gray-800'],
canvas: ['shadow-2xl']
}}
/>Example 3: Partial Styling Override
<script>
import { PDFViewer } from 'sv-pdf';
</script>
<!-- Only customize specific elements, others keep defaults -->
<PDFViewer
src="/document.pdf"
baseStyling={{
// Custom wrapper
wrapper: ['flex', 'flex-col', 'bg-gradient-to-br', 'from-blue-50', 'to-purple-50', 'rounded-3xl', 'shadow-2xl', 'p-2'],
// Custom navigation buttons with animations
navigationButton: ['px-4', 'py-2', 'bg-blue-500', 'text-white', 'rounded-full', 'hover:scale-105', 'transform', 'transition-all', 'duration-200', 'disabled:opacity-50', 'disabled:hover:scale-100'],
// Everything else uses defaults
}}
/>Example 4: Compact Mobile-Friendly Styling
<script>
import { PDFAccept } from 'sv-pdf';
</script>
<PDFAccept
src="/contract.pdf"
baseStyling={{
controls: ['flex', 'items-center', 'justify-between', 'p-2', 'bg-white', 'border-t', 'text-xs'],
navigationButton: ['px-2', 'py-1', 'text-xs', 'bg-blue-50', 'text-blue-700', 'rounded'],
pageIndicator: ['text-xs', 'bg-gray-100', 'px-2', 'py-1', 'rounded'],
zoomButton: ['px-2', 'py-1', 'text-xs', 'bg-green-50', 'text-green-700', 'rounded'],
zoomScale: ['text-xs', 'bg-gray-100', 'px-2', 'py-1', 'rounded', 'min-w-[50px]'],
doneButton: ['px-3', 'py-1', 'text-xs', 'bg-emerald-500', 'text-white', 'rounded'],
progressIndicator: ['absolute', 'top-2', 'right-2', 'bg-white', 'px-2', 'py-1', 'text-xs', 'rounded', 'shadow']
}}
/>Styling Best Practices
- Use Tailwind's Design System: Stick to Tailwind's spacing, color, and typography scales for consistency
- Consider States: Include hover, disabled, and active states in your custom classes
- Responsive Design: Add responsive prefixes (
sm:,md:,lg:) for different screen sizes - Accessibility: Maintain sufficient color contrast and interactive element sizing
- Transitions: Add transition classes for smooth interactions
- Test Thoroughly: Verify your styling works in all component states (loading, error, rendering)
Working with Tailwind JIT
Since the library uses Tailwind classes, make sure your tailwind.config.js includes the library:
export default {
content: [
'./src/**/*.{html,js,svelte,ts}',
'./node_modules/sv-pdf/dist/**/*.svelte' // Include library components
],
// ... rest of config
}Styling
The components use Tailwind CSS classes by default. You can override styles by targeting the component classes or providing your own CSS.
/* Custom styling example */
.pdf-viewer canvas {
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}Requirements
- Svelte 4.x or 5.x
- Tailwind CSS 3.x or 4.x
- PDF.js 4.x or 5.x
- Modern browser with ES6+ support
Development
# Install dependencies
npm install
# Start development server
npm run dev
# Build package
npm run package
# Run type checking
npm run checkBrowser Support
- Chrome/Edge 88+
- Firefox 85+
- Safari 14+
License
MIT
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
