diff --git a/.gitignore b/.gitignore
index 25cbf7b..3363dc1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,4 @@
.DS_Store
-CLAUDE.md
+.vscode/
dist/
-node_modules/
+node_modules/
\ No newline at end of file
diff --git a/README.md b/README.md
index b642196..95af95a 100644
--- a/README.md
+++ b/README.md
@@ -5,21 +5,50 @@
Lightweight library to block user interactions in browsers.
+## ⚠️ Breaking Changes in v0.3.0
+
+Version 0.3.0 introduces significant API changes from v0.2.x:
+
+- **Factory function instead of singleton**: `blokr()` returns an instance instead of being a singleton object
+- **Options-based API**: `lock({ timeout, scope })` instead of separate `setTimeout()` method
+- **No reference counting**: Multiple `lock()` calls return `false` instead of incrementing a counter
+- **No `setTimeout()` method**: Use `lock({ timeout })` option instead
+- **No `unlock(abort)` parameter**: `unlock()` always releases the lock immediately
+
+**Migration guide:** See [Migration from v0.2.x](#migration-from-v02x) below.
+
+**Note:** This library is under active development. Future versions may introduce additional breaking changes. Please refer to the changelog before upgrading.
+
## Features
+- **Factory-based API**: Support for both global and element-specific locks
+- **Scope filtering**: Control which events to block (`inside`, `outside`, `self`)
- **No overlay elements**: Blocks interactions without adding elements to the DOM
-- **Event blocking**: Prevents mouse, keyboard, and touch interactions
-- **Auto-timeout**: Configurable timeout to prevent permanent blocking (default: 10 seconds)
-- **Lightweight**: Minimal footprint with no dependencies
+- **All interaction types**: Blocks mouse, keyboard, touch, and wheel events
+- **Per-lock timeout**: Optional automatic unlock after specified time
+- **No dependencies**: Zero external dependencies
- **TypeScript**: Full type support included
-- **Singleton**: Simple, predictable API
-## Use Cases
+## Why Blokr?
+
+### Problems with CSS-based Solutions
-- **POST processing**: Block interactions during data submission
-- **Form submission**: Prevent double-submission
-- **Animations**: Disable interaction during transitions
-- **Game pausing**: Temporarily disable game controls
+While CSS `pointer-events: none` can disable interactions, it has several limitations:
+
+1. **Cannot block keyboard events**: Tab navigation and keyboard shortcuts still work
+2. **No timeout protection**: No automatic unlock if code fails to re-enable interactions
+3. **Requires DOM manipulation**: Must add/remove CSS classes or inline styles
+4. **Cannot scope events**: Cannot selectively block events inside/outside an element
+5. **z-index issues**: Overlay approaches require careful z-index management
+
+### How Blokr Solves These Problems
+
+- ✅ **Blocks all interaction types**: Mouse, keyboard, touch, and wheel events
+- ✅ **Optional timeout protection**: Automatically unlock after specified time
+- ✅ **No DOM changes**: Works via event listeners only
+- ✅ **Flexible scoping**: Block events inside, outside, or only on specific elements
+- ✅ **No z-index conflicts**: No overlay elements needed
+- ✅ **TypeScript support**: Full type definitions included
## Installation
@@ -29,109 +58,187 @@ npm install blokr
## Usage
-### ES Modules
+### Basic Usage (ES Modules)
```typescript
import blokr from 'blokr';
-// Block user interactions
-blokr.lock();
+// Global lock - blocks all user interactions
+const instance = blokr();
+instance.lock();
-// Check if blocked
-if (blokr.isLocked()) {
+// Check if locked
+if (instance.isLocked()) {
console.log('User interactions are blocked');
}
-// Unblock after some work
-setTimeout(() => {
- blokr.unlock();
-}, 2000);
+// Unlock
+instance.unlock();
+```
+
+### Element-specific Locking
+
+```typescript
+import blokr from 'blokr';
+
+const container = document.querySelector('.container');
+const instance = blokr(container);
+
+// Block events inside the container (default scope)
+instance.lock();
+
+// Or explicitly specify scope
+instance.lock({ scope: 'inside' }); // Block events inside container
+instance.lock({ scope: 'outside' }); // Block events outside container
+instance.lock({ scope: 'self' }); // Block events on container itself only
+```
+
+### Auto-timeout
+
+```typescript
+import blokr from 'blokr';
+
+const instance = blokr();
+
+// Auto-unlock after 5 seconds
+instance.lock({ timeout: 5000 });
+
+// Disable timeout (lock indefinitely)
+instance.lock({ timeout: 0 });
```
-### CDN
+### CDN Usage (UMD)
```html
```
-## API
+### CDN Usage (ES Modules)
+
+```html
+
+```
+
+## API Reference
+
+### `blokr(target?: Element): BlokrInstance`
+
+Returns a Blokr instance. If no target is specified, creates a global instance that blocks all events. If the same target is provided multiple times, returns the cached instance.
+
+**Parameters:**
+- `target` (optional): DOM element to scope the lock to
-### `blokr.lock()`
+**Returns:** `BlokrInstance`
-Blocks user interactions. Multiple calls are counted internally, requiring the same number of `unlock()` calls to fully unblock.
+**Examples:**
```typescript
-blokr.lock(); // Call count: 1
-blokr.lock(); // Call count: 2
-blokr.unlock(); // Call count: 1 (still blocked)
-blokr.unlock(); // Call count: 0 (unblocked)
+// Global instance (blocks all events)
+const global = blokr();
+
+// Element-specific instance
+const container = document.querySelector('.modal');
+const modal = blokr(container);
+
+// Same element returns same instance
+const modal2 = blokr(container);
+console.log(modal === modal2); // true
```
-### `blokr.unlock(abort?: boolean)`
+### `instance.lock(options?: Options): boolean`
-Unblocks user interactions. By default, decrements the internal counter. When `abort` is `true`, immediately resets the counter to zero and releases all locks.
+Locks user interactions. Returns `true` if lock was applied, `false` if already locked.
**Parameters:**
+- `options.timeout` (optional): Auto-unlock timeout in milliseconds. Default: `0` (no timeout)
+- `options.scope` (optional): Event blocking scope. Default: `'inside'`
+ - `'inside'`: Block events inside target element (default)
+ - `'outside'`: Block events outside target element
+ - `'self'`: Block events on target element itself only
+
+**Returns:** `true` if lock was applied, `false` if already locked
-- `abort` (optional): When `true`, immediately unlocks all locks. Default: `false`
+**Examples:**
```typescript
-// Normal unlock behavior (decrements counter)
-blokr.lock(); // Lock count: 1
-blokr.lock(); // Lock count: 2
+const instance = blokr();
-blokr.unlock(); // Lock count: 1 (still locked)
-blokr.unlock(); // Lock count: 0 (unlocked)
+// Basic lock
+instance.lock(); // Returns true
-// Emergency unlock with abort
-blokr.lock(); // Lock count: 1
-blokr.lock(); // Lock count: 2
-blokr.unlock(true); // Lock count: 0 (immediately unlocked)
+// Already locked
+instance.lock(); // Returns false
+
+// Lock with timeout
+instance.lock({ timeout: 5000 });
+
+// Lock with scope (requires target element)
+const container = document.querySelector('.panel');
+const panelInstance = blokr(container);
+panelInstance.lock({ scope: 'inside' });
```
-### `blokr.isLocked(): boolean`
+### `instance.unlock(): void`
+
+Unlocks user interactions and clears any pending timeout. Safe to call even when not locked.
-Returns `true` if user interactions are currently blocked.
+**Examples:**
```typescript
-blokr.isLocked(); // false
-blokr.lock();
-blokr.isLocked(); // true
+const instance = blokr();
+instance.lock();
+instance.unlock();
+
+// Safe to call multiple times
+instance.unlock();
+instance.unlock();
```
-### `blokr.setTimeout(timeout: number): boolean`
+### `instance.isLocked(): boolean`
+
+Returns `true` if user interactions are currently locked.
+
+**Returns:** `boolean`
-Sets the auto-unlock timeout in milliseconds (default: 10000). Cannot be changed while locked. Returns `true` if successfully set, `false` if currently locked.
+**Examples:**
```typescript
-// Set 5-second timeout (only works when unlocked)
-blokr.setTimeout(5000);
+const instance = blokr();
+console.log(instance.isLocked()); // false
-// Disable auto-timeout
-blokr.setTimeout(0);
+instance.lock();
+console.log(instance.isLocked()); // true
-// Cannot change timeout while locked
-blokr.lock();
-blokr.setTimeout(1000); // returns false
+instance.unlock();
+console.log(instance.isLocked()); // false
```
-## Example: POST Processing
+## Examples
+
+### POST Processing with Timeout
```typescript
import blokr from 'blokr';
-async function saveUserProfile(formData) {
- // Block all interactions during save
- blokr.lock();
+async function saveUserProfile(formData: FormData) {
+ const instance = blokr();
+
+ // Block all interactions with 10-second timeout
+ instance.lock({ timeout: 10000 });
try {
const response = await fetch('/api/profile', {
@@ -143,34 +250,133 @@ async function saveUserProfile(formData) {
showSuccessMessage();
}
} finally {
- blokr.unlock();
+ instance.unlock();
}
}
```
-## Example: Animation Blocking
+### Modal Dialog
```typescript
import blokr from 'blokr';
-function slidePanel() {
- // Block interactions during animation
- blokr.lock();
+function openModal() {
+ const modal = document.querySelector('.modal');
+ const instance = blokr(modal);
- // Start CSS animation
- panel.classList.add('sliding');
+ modal.classList.add('visible');
- // Re-enable interactions when animation completes
- setTimeout(() => {
- blokr.unlock();
- }, 500);
+ // Block all interactions outside the modal
+ instance.lock({ scope: 'outside' });
+}
+
+function closeModal() {
+ const modal = document.querySelector('.modal');
+ const instance = blokr(modal);
+
+ modal.classList.remove('visible');
+ instance.unlock();
+}
+```
+
+### Form Panel Lock
+
+```typescript
+import blokr from 'blokr';
+
+function disableFormPanel() {
+ const panel = document.querySelector('.settings-panel');
+ const instance = blokr(panel);
+
+ // Disable interactions only inside the panel
+ instance.lock({ scope: 'inside' });
+}
+
+function enableFormPanel() {
+ const panel = document.querySelector('.settings-panel');
+ const instance = blokr(panel);
+
+ instance.unlock();
+}
+```
+
+### Loading Overlay Alternative
+
+```typescript
+import blokr from 'blokr';
+
+async function loadData() {
+ const instance = blokr();
+
+ // No overlay element needed!
+ instance.lock({ timeout: 30000 });
+
+ try {
+ const data = await fetch('/api/data').then(r => r.json());
+ renderData(data);
+ } finally {
+ instance.unlock();
+ }
}
```
+## Migration from v0.2.x
+
+### API Changes
+
+| v0.2.x | v0.3.0 |
+|--------|--------|
+| `blokr.lock()` | `blokr().lock()` |
+| `blokr.unlock()` | `blokr().unlock()` |
+| `blokr.unlock(true)` | `blokr().unlock()` (always immediate) |
+| `blokr.setTimeout(ms)` | `blokr().lock({ timeout: ms })` |
+| `blokr.isLocked()` | `blokr().isLocked()` |
+| `window.Blokr` (UMD) | `window.blokr` (UMD) |
+
+### Reference Counting Removed
+
+In v0.2.x, multiple `lock()` calls incremented a counter:
+
+```typescript
+// v0.2.x
+blokr.lock(); // Count: 1
+blokr.lock(); // Count: 2
+blokr.unlock(); // Count: 1 (still locked)
+blokr.unlock(); // Count: 0 (unlocked)
+```
+
+In v0.3.0, `lock()` returns `false` if already locked:
+
+```typescript
+// v0.3.0
+const instance = blokr();
+instance.lock(); // Returns true
+instance.lock(); // Returns false (already locked)
+instance.unlock(); // Unlocked
+```
+
+### Element-specific Locking (New Feature)
+
+```typescript
+// v0.3.0 only - new feature not available in v0.2.x
+const container = document.querySelector('.container');
+const instance = blokr(container);
+
+// Block events inside container
+instance.lock({ scope: 'inside' });
+
+// Block events outside container
+instance.lock({ scope: 'outside' });
+
+// Block events on container itself only
+instance.lock({ scope: 'self' });
+```
+
## Limitations
-- Only blocks genuine user interactions. Programmatically triggered events (e.g., `element.click()`) are not blocked.
-- May not work when used with event delegation libraries. Loading Blokr before other libraries may resolve this issue.
+- **Only blocks genuine user interactions**: Programmatically triggered events (e.g., `element.click()`) are not blocked.
+- **Event listener priority**: Event listeners are registered at the capture phase. May not work correctly when used with event delegation libraries. Loading Blokr before other libraries may resolve this issue.
+- **Target-specific locks accept Elements only**: The `blokr(target)` factory function only accepts DOM `Element` nodes. To block interactions across the entire page, use the global lock: `blokr()` (without a target parameter).
## License
diff --git a/eslint.config.js b/eslint.config.js
index f7cbab3..098189c 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -1,110 +1,61 @@
+// @ts-check
+import { defineConfig } from 'eslint/config';
+import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
+import stylistic from '@stylistic/eslint-plugin';
-export default tseslint.config(
+export default defineConfig(
{
- ignores: ['dist']
+ ignores: [
+ 'dist',
+ 'eslint.config.js',
+ 'vite.config.ts',
+ 'vitest.config.ts'
+ ]
},
- ...tseslint.configs.strict,
- ...tseslint.configs.stylistic,
+ eslint.configs.recommended,
+ tseslint.configs.strictTypeChecked,
+ tseslint.configs.stylisticTypeChecked,
{
- files: ['src/**/*.ts'],
+ plugins: {
+ '@stylistic': stylistic
+ },
languageOptions: {
ecmaVersion: 2015,
sourceType: 'module',
- globals: {
- self: 'readonly'
- },
parserOptions: {
- project: './tsconfig.json'
+ projectService: true
}
},
rules: {
+ '@typescript-eslint/no-confusing-void-expression': ['error', { ignoreArrowShorthand: true }],
'@typescript-eslint/no-extraneous-class': 'off',
- '@typescript-eslint/no-unused-vars': ['error', { 'caughtErrors': 'none' }],
- '@typescript-eslint/unified-signatures': ['error', { 'ignoreDifferentlyNamedParameters': true }],
- 'array-bracket-spacing': ['warn', 'never'],
- 'array-callback-return': 'error',
- 'constructor-super': 'error',
- 'for-direction': 'error',
- 'getter-return': 'error',
- 'no-async-promise-executor': 'error',
- 'no-class-assign': 'error',
- 'no-compare-neg-zero': 'error',
- 'no-cond-assign': 'error',
- 'no-const-assign': 'error',
- 'no-constant-binary-expression': 'error',
- 'no-constant-condition': 'error',
- 'no-constructor-return': 'error',
- 'no-control-regex': 'error',
- 'no-debugger': 'error',
- 'no-dupe-args': 'error',
- 'no-dupe-class-members': 'error',
- 'no-dupe-else-if': 'error',
- 'no-dupe-keys': 'error',
- 'no-duplicate-case': 'error',
- 'no-empty-character-class': 'error',
- 'no-empty-pattern': 'error',
- 'no-ex-assign': 'error',
- 'no-fallthrough': 'error',
- 'no-func-assign': 'error',
- 'no-import-assign': 'error',
- 'no-inner-declarations': 'error',
- 'no-invalid-regexp': 'error',
- 'no-irregular-whitespace': 'error',
- 'no-loss-of-precision': 'error',
- 'no-misleading-character-class': 'error',
- 'no-new-native-nonconstructor': 'error',
- 'no-new-symbol': 'error',
- 'no-obj-calls': 'error',
- 'no-self-assign': 'error',
- 'no-self-compare': 'error',
- 'no-setter-return': 'error',
- 'no-sparse-arrays': 'error',
- 'no-template-curly-in-string': 'error',
- 'no-this-before-super': 'error',
- 'no-undef': 'error',
- 'no-unexpected-multiline': 'error',
- 'no-unmodified-loop-condition': 'error',
- 'no-unreachable': 'error',
- 'no-unreachable-loop': 'error',
- 'no-unsafe-finally': 'error',
- 'no-unsafe-negation': 'error',
- 'no-unsafe-optional-chaining': 'error',
- 'no-unused-private-class-members': 'error',
- 'no-use-before-define': 'error',
- 'no-useless-backreference': 'error',
- 'require-atomic-updates': 'error',
- 'use-isnan': 'error',
- 'valid-typeof': 'error',
+ '@typescript-eslint/no-unused-vars': ['error', { caughtErrors: 'none' }],
+ '@typescript-eslint/restrict-template-expressions': ['error', { allowNever: true }],
+ '@typescript-eslint/unified-signatures': ['error', { ignoreDifferentlyNamedParameters: true }],
+
'accessor-pairs': 'error',
+ 'array-callback-return': 'error',
'block-scoped-var': 'error',
'consistent-return': 'error',
'curly': 'error',
'default-case-last': 'error',
- 'dot-notation': 'error',
'eqeqeq': ['error', 'smart'],
'func-name-matching': 'error',
- 'func-style': ['error', 'expression', { 'overrides': { 'namedExports': 'ignore' } }],
+ 'func-style': ['error', 'expression', { overrides: { namedExports: 'ignore' } }],
'grouped-accessor-pairs': 'error',
- 'id-denylist': 'error',
- 'id-match': 'error',
'max-depth': 'error',
'max-nested-callbacks': 'error',
'new-cap': 'error',
- 'no-array-constructor': 'error',
'no-caller': 'error',
- 'no-delete-var': 'error',
+ 'no-constructor-return': 'error',
'no-div-regex': 'error',
'no-else-return': 'error',
'no-empty-static-block': 'error',
'no-eval': 'error',
'no-extend-native': 'error',
'no-extra-bind': 'error',
- 'no-extra-boolean-cast': 'error',
'no-extra-label': 'error',
- 'no-extra-semi': 'error',
- 'no-floating-decimal': 'error',
- 'no-global-assign': 'error',
'no-implicit-globals': 'error',
'no-implied-eval': 'error',
'no-iterator': 'error',
@@ -112,103 +63,93 @@ export default tseslint.config(
'no-labels': 'error',
'no-lone-blocks': 'error',
'no-lonely-if': 'error',
- 'no-loop-func': 'error',
- 'no-multi-assign': ['error', { 'ignoreNonDeclaration': true }],
+ 'no-multi-assign': ['error', { ignoreNonDeclaration: true }],
'no-multi-str': 'error',
'no-negated-condition': 'error',
'no-new': 'error',
'no-new-func': 'error',
- 'no-new-object': 'error',
'no-new-wrappers': 'error',
- 'no-nonoctal-decimal-escape': 'error',
- 'no-octal': 'error',
+ 'no-object-constructor': 'error',
'no-octal-escape': 'error',
'no-proto': 'error',
- 'no-regex-spaces': 'error',
- 'no-restricted-exports': 'error',
- 'no-restricted-globals': 'error',
- 'no-restricted-imports': 'error',
- 'no-restricted-properties': 'error',
- 'no-restricted-syntax': 'error',
'no-return-assign': 'error',
- 'no-return-await': 'error',
'no-script-url': 'error',
+ 'no-self-compare': 'error',
'no-sequences': 'error',
- 'no-shadow': 'error',
'no-shadow-restricted-names': 'error',
- 'no-throw-literal': 'error',
+ 'no-template-curly-in-string': 'error',
'no-undef-init': 'error',
+ 'no-unmodified-loop-condition': 'error',
'no-unneeded-ternary': 'error',
- 'no-unused-expressions': 'error',
- 'no-unused-labels': 'error',
+ 'no-unreachable-loop': 'error',
'no-useless-call': 'error',
- 'no-useless-catch': 'error',
'no-useless-computed-key': 'error',
'no-useless-concat': 'error',
- 'no-useless-escape': 'error',
'no-useless-rename': 'error',
'no-useless-return': 'error',
'no-void': 'error',
- 'no-warning-comments': 'warn',
- 'no-with': 'error',
+ 'no-warning-comments': 'error',
'operator-assignment': 'error',
- 'prefer-const': 'error',
'prefer-exponentiation-operator': 'error',
'prefer-numeric-literals': 'error',
'prefer-object-has-own': 'error',
'prefer-object-spread': 'error',
- 'prefer-promise-reject-errors': ['error', { 'allowEmptyReject': true }],
+ 'prefer-promise-reject-errors': ['error', { allowEmptyReject: true }],
'prefer-regex-literals': 'error',
'prefer-rest-params': 'error',
'prefer-spread': 'error',
'radix': 'error',
- 'require-await': 'error',
- 'require-yield': 'error',
+ 'require-atomic-updates': 'error',
'symbol-description': 'error',
+ 'unicode-bom': 'error',
'yoda': 'error',
- 'arrow-spacing': 'warn',
- 'block-spacing': 'warn',
- 'comma-dangle': 'warn',
- 'comma-spacing': 'warn',
- 'comma-style': 'warn',
- 'computed-property-spacing': 'warn',
- 'dot-location': ['warn', 'property'],
- 'eol-last': 'warn',
- 'func-call-spacing': 'warn',
- 'generator-star-spacing': 'warn',
- 'implicit-arrow-linebreak': 'warn',
- 'indent': ['warn', 2, { 'ignoreComments': true }],
- 'jsx-quotes': 'warn',
- 'key-spacing': 'warn',
- 'keyword-spacing': 'warn',
- 'linebreak-style': 'warn',
- 'lines-between-class-members': 'warn',
- 'new-parens': 'warn',
- 'no-extra-parens': ['warn', 'functions'],
- 'no-mixed-spaces-and-tabs': 'warn',
- 'no-multi-spaces': ['warn', { 'ignoreEOLComments': true }],
- 'no-tabs': 'warn',
- 'no-trailing-spaces': 'warn',
- 'no-whitespace-before-property': 'warn',
- 'nonblock-statement-body-position': 'warn',
- 'object-curly-newline': 'warn',
- 'object-curly-spacing': ['warn', 'always'],
- 'padding-line-between-statements': 'warn',
- 'quotes': ['warn', 'single'],
- 'rest-spread-spacing': 'warn',
- 'semi': 'warn',
- 'semi-spacing': 'warn',
- 'semi-style': 'warn',
- 'space-before-blocks': 'warn',
- 'space-in-parens': 'warn',
- 'space-infix-ops': 'warn',
- 'space-unary-ops': 'warn',
- 'switch-colon-spacing': 'warn',
- 'template-curly-spacing': 'warn',
- 'template-tag-spacing': 'warn',
- 'unicode-bom': 'warn',
- 'wrap-iife': ['warn', 'any'],
- 'yield-star-spacing': 'warn'
+
+ '@stylistic/array-bracket-spacing': ['warn', 'never'],
+ '@stylistic/arrow-spacing': 'warn',
+ '@stylistic/block-spacing': 'warn',
+ '@stylistic/comma-dangle': 'warn',
+ '@stylistic/comma-spacing': 'warn',
+ '@stylistic/comma-style': 'warn',
+ '@stylistic/computed-property-spacing': 'warn',
+ '@stylistic/dot-location': ['warn', 'property'],
+ '@stylistic/eol-last': 'warn',
+ '@stylistic/function-call-spacing': 'warn',
+ '@stylistic/generator-star-spacing': 'warn',
+ '@stylistic/implicit-arrow-linebreak': 'warn',
+ '@stylistic/indent': ['warn', 2, { ignoreComments: true }],
+ '@stylistic/jsx-quotes': 'warn',
+ '@stylistic/key-spacing': 'warn',
+ '@stylistic/keyword-spacing': 'warn',
+ '@stylistic/linebreak-style': 'warn',
+ '@stylistic/lines-between-class-members': 'warn',
+ '@stylistic/member-delimiter-style': ['warn', { multiline: { delimiter: 'semi', requireLast: true }, singleline: { delimiter: 'semi', requireLast: false } }],
+ '@stylistic/new-parens': 'warn',
+ '@stylistic/no-extra-parens': ['warn', 'functions'],
+ '@stylistic/no-extra-semi': 'warn',
+ '@stylistic/no-floating-decimal': 'warn',
+ '@stylistic/no-mixed-spaces-and-tabs': 'warn',
+ '@stylistic/no-multi-spaces': ['warn', { ignoreEOLComments: true }],
+ '@stylistic/no-tabs': 'warn',
+ '@stylistic/no-trailing-spaces': 'warn',
+ '@stylistic/no-whitespace-before-property': 'warn',
+ '@stylistic/nonblock-statement-body-position': 'warn',
+ '@stylistic/object-curly-newline': 'warn',
+ '@stylistic/object-curly-spacing': ['warn', 'always'],
+ '@stylistic/padding-line-between-statements': 'warn',
+ '@stylistic/quotes': ['warn', 'single'],
+ '@stylistic/rest-spread-spacing': 'warn',
+ '@stylistic/semi': 'warn',
+ '@stylistic/semi-spacing': 'warn',
+ '@stylistic/semi-style': 'warn',
+ '@stylistic/space-before-blocks': 'warn',
+ '@stylistic/space-in-parens': 'warn',
+ '@stylistic/space-infix-ops': 'warn',
+ '@stylistic/space-unary-ops': 'warn',
+ '@stylistic/switch-colon-spacing': 'warn',
+ '@stylistic/template-curly-spacing': 'warn',
+ '@stylistic/template-tag-spacing': 'warn',
+ '@stylistic/wrap-iife': ['warn', 'any'],
+ '@stylistic/yield-star-spacing': 'warn'
}
}
);
diff --git a/package-lock.json b/package-lock.json
index a462f15..bc3a209 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,24 +1,25 @@
{
"name": "blokr",
- "version": "0.2.1",
+ "version": "0.3.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "blokr",
- "version": "0.2.1",
+ "version": "0.3.0",
"license": "MIT",
"devDependencies": {
"@rollup/plugin-terser": "^0.4.4",
- "@vitest/browser": "^4.0.12",
- "@vitest/browser-playwright": "^4.0.12",
- "eslint": "^9.39.1",
- "playwright": "^1.56.1",
+ "@stylistic/eslint-plugin": "^5.6.1",
+ "@vitest/browser": "^4.0.16",
+ "@vitest/browser-playwright": "^4.0.16",
+ "eslint": "^9.39.2",
+ "playwright": "^1.57.0",
"rollup-plugin-license": "^3.6.0",
- "typescript-eslint": "^8.47.0",
- "vite": "^7.2.4",
+ "typescript-eslint": "^8.51.0",
+ "vite": "^7.3.0",
"vite-plugin-dts": "^4.5.4",
- "vitest": "^4.0.12"
+ "vitest": "^4.0.16"
}
},
"node_modules/@babel/helper-string-parser": {
@@ -72,9 +73,9 @@
}
},
"node_modules/@esbuild/aix-ppc64": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz",
- "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz",
+ "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==",
"cpu": [
"ppc64"
],
@@ -89,9 +90,9 @@
}
},
"node_modules/@esbuild/android-arm": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz",
- "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz",
+ "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==",
"cpu": [
"arm"
],
@@ -106,9 +107,9 @@
}
},
"node_modules/@esbuild/android-arm64": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz",
- "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz",
+ "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==",
"cpu": [
"arm64"
],
@@ -123,9 +124,9 @@
}
},
"node_modules/@esbuild/android-x64": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz",
- "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz",
+ "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==",
"cpu": [
"x64"
],
@@ -140,9 +141,9 @@
}
},
"node_modules/@esbuild/darwin-arm64": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz",
- "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz",
+ "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==",
"cpu": [
"arm64"
],
@@ -157,9 +158,9 @@
}
},
"node_modules/@esbuild/darwin-x64": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz",
- "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz",
+ "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==",
"cpu": [
"x64"
],
@@ -174,9 +175,9 @@
}
},
"node_modules/@esbuild/freebsd-arm64": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz",
- "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz",
+ "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==",
"cpu": [
"arm64"
],
@@ -191,9 +192,9 @@
}
},
"node_modules/@esbuild/freebsd-x64": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz",
- "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz",
+ "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==",
"cpu": [
"x64"
],
@@ -208,9 +209,9 @@
}
},
"node_modules/@esbuild/linux-arm": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz",
- "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz",
+ "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==",
"cpu": [
"arm"
],
@@ -225,9 +226,9 @@
}
},
"node_modules/@esbuild/linux-arm64": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz",
- "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz",
+ "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==",
"cpu": [
"arm64"
],
@@ -242,9 +243,9 @@
}
},
"node_modules/@esbuild/linux-ia32": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz",
- "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz",
+ "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==",
"cpu": [
"ia32"
],
@@ -259,9 +260,9 @@
}
},
"node_modules/@esbuild/linux-loong64": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz",
- "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz",
+ "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==",
"cpu": [
"loong64"
],
@@ -276,9 +277,9 @@
}
},
"node_modules/@esbuild/linux-mips64el": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz",
- "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz",
+ "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==",
"cpu": [
"mips64el"
],
@@ -293,9 +294,9 @@
}
},
"node_modules/@esbuild/linux-ppc64": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz",
- "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz",
+ "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==",
"cpu": [
"ppc64"
],
@@ -310,9 +311,9 @@
}
},
"node_modules/@esbuild/linux-riscv64": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz",
- "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz",
+ "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==",
"cpu": [
"riscv64"
],
@@ -327,9 +328,9 @@
}
},
"node_modules/@esbuild/linux-s390x": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz",
- "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz",
+ "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==",
"cpu": [
"s390x"
],
@@ -344,9 +345,9 @@
}
},
"node_modules/@esbuild/linux-x64": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz",
- "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz",
+ "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==",
"cpu": [
"x64"
],
@@ -361,9 +362,9 @@
}
},
"node_modules/@esbuild/netbsd-arm64": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz",
- "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz",
+ "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==",
"cpu": [
"arm64"
],
@@ -378,9 +379,9 @@
}
},
"node_modules/@esbuild/netbsd-x64": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz",
- "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz",
+ "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==",
"cpu": [
"x64"
],
@@ -395,9 +396,9 @@
}
},
"node_modules/@esbuild/openbsd-arm64": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz",
- "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz",
+ "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==",
"cpu": [
"arm64"
],
@@ -412,9 +413,9 @@
}
},
"node_modules/@esbuild/openbsd-x64": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz",
- "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz",
+ "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==",
"cpu": [
"x64"
],
@@ -429,9 +430,9 @@
}
},
"node_modules/@esbuild/openharmony-arm64": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz",
- "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz",
+ "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==",
"cpu": [
"arm64"
],
@@ -446,9 +447,9 @@
}
},
"node_modules/@esbuild/sunos-x64": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz",
- "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz",
+ "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==",
"cpu": [
"x64"
],
@@ -463,9 +464,9 @@
}
},
"node_modules/@esbuild/win32-arm64": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz",
- "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz",
+ "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==",
"cpu": [
"arm64"
],
@@ -480,9 +481,9 @@
}
},
"node_modules/@esbuild/win32-ia32": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz",
- "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz",
+ "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==",
"cpu": [
"ia32"
],
@@ -497,9 +498,9 @@
}
},
"node_modules/@esbuild/win32-x64": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz",
- "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz",
+ "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==",
"cpu": [
"x64"
],
@@ -621,9 +622,9 @@
}
},
"node_modules/@eslint/js": {
- "version": "9.39.1",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz",
- "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==",
+ "version": "9.39.2",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz",
+ "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -923,44 +924,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/@nodelib/fs.scandir": {
- "version": "2.1.5",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
- "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@nodelib/fs.stat": "2.0.5",
- "run-parallel": "^1.1.9"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/@nodelib/fs.stat": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
- "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/@nodelib/fs.walk": {
- "version": "1.2.8",
- "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
- "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@nodelib/fs.scandir": "2.1.5",
- "fastq": "^1.6.0"
- },
- "engines": {
- "node": ">= 8"
- }
- },
"node_modules/@polka/url": {
"version": "1.0.0-next.29",
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz",
@@ -1451,12 +1414,33 @@
}
},
"node_modules/@standard-schema/spec": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz",
- "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==",
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
+ "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
"dev": true,
"license": "MIT"
},
+ "node_modules/@stylistic/eslint-plugin": {
+ "version": "5.6.1",
+ "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.6.1.tgz",
+ "integrity": "sha512-JCs+MqoXfXrRPGbGmho/zGS/jMcn3ieKl/A8YImqib76C8kjgZwq5uUFzc30lJkMvcchuRn6/v8IApLxli3Jyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.9.0",
+ "@typescript-eslint/types": "^8.47.0",
+ "eslint-visitor-keys": "^4.2.1",
+ "espree": "^10.4.0",
+ "estraverse": "^5.3.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "peerDependencies": {
+ "eslint": ">=9.0.0"
+ }
+ },
"node_modules/@types/argparse": {
"version": "1.0.38",
"resolved": "https://registry.npmjs.org/@types/argparse/-/argparse-1.0.38.tgz",
@@ -1497,21 +1481,20 @@
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.47.0.tgz",
- "integrity": "sha512-fe0rz9WJQ5t2iaLfdbDc9T80GJy0AeO453q8C3YCilnGozvOyCG5t+EZtg7j7D88+c3FipfP/x+wzGnh1xp8ZA==",
+ "version": "8.51.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.51.0.tgz",
+ "integrity": "sha512-XtssGWJvypyM2ytBnSnKtHYOGT+4ZwTnBVl36TA4nRO2f4PRNGz5/1OszHzcZCvcBMh+qb7I06uoCmLTRdR9og==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
- "@typescript-eslint/scope-manager": "8.47.0",
- "@typescript-eslint/type-utils": "8.47.0",
- "@typescript-eslint/utils": "8.47.0",
- "@typescript-eslint/visitor-keys": "8.47.0",
- "graphemer": "^1.4.0",
+ "@typescript-eslint/scope-manager": "8.51.0",
+ "@typescript-eslint/type-utils": "8.51.0",
+ "@typescript-eslint/utils": "8.51.0",
+ "@typescript-eslint/visitor-keys": "8.51.0",
"ignore": "^7.0.0",
"natural-compare": "^1.4.0",
- "ts-api-utils": "^2.1.0"
+ "ts-api-utils": "^2.2.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1521,7 +1504,7 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "@typescript-eslint/parser": "^8.47.0",
+ "@typescript-eslint/parser": "^8.51.0",
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <6.0.0"
}
@@ -1537,16 +1520,16 @@
}
},
"node_modules/@typescript-eslint/parser": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.47.0.tgz",
- "integrity": "sha512-lJi3PfxVmo0AkEY93ecfN+r8SofEqZNGByvHAI3GBLrvt1Cw6H5k1IM02nSzu0RfUafr2EvFSw0wAsZgubNplQ==",
+ "version": "8.51.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.51.0.tgz",
+ "integrity": "sha512-3xP4XzzDNQOIqBMWogftkwxhg5oMKApqY0BAflmLZiFYHqyhSOxv/cd/zPQLTcCXr4AkaKb25joocY0BD1WC6A==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/scope-manager": "8.47.0",
- "@typescript-eslint/types": "8.47.0",
- "@typescript-eslint/typescript-estree": "8.47.0",
- "@typescript-eslint/visitor-keys": "8.47.0",
+ "@typescript-eslint/scope-manager": "8.51.0",
+ "@typescript-eslint/types": "8.51.0",
+ "@typescript-eslint/typescript-estree": "8.51.0",
+ "@typescript-eslint/visitor-keys": "8.51.0",
"debug": "^4.3.4"
},
"engines": {
@@ -1562,14 +1545,14 @@
}
},
"node_modules/@typescript-eslint/project-service": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.47.0.tgz",
- "integrity": "sha512-2X4BX8hUeB5JcA1TQJ7GjcgulXQ+5UkNb0DL8gHsHUHdFoiCTJoYLTpib3LtSDPZsRET5ygN4qqIWrHyYIKERA==",
+ "version": "8.51.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.51.0.tgz",
+ "integrity": "sha512-Luv/GafO07Z7HpiI7qeEW5NW8HUtZI/fo/kE0YbtQEFpJRUuR0ajcWfCE5bnMvL7QQFrmT/odMe8QZww8X2nfQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/tsconfig-utils": "^8.47.0",
- "@typescript-eslint/types": "^8.47.0",
+ "@typescript-eslint/tsconfig-utils": "^8.51.0",
+ "@typescript-eslint/types": "^8.51.0",
"debug": "^4.3.4"
},
"engines": {
@@ -1584,14 +1567,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.47.0.tgz",
- "integrity": "sha512-a0TTJk4HXMkfpFkL9/WaGTNuv7JWfFTQFJd6zS9dVAjKsojmv9HT55xzbEpnZoY+VUb+YXLMp+ihMLz/UlZfDg==",
+ "version": "8.51.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.51.0.tgz",
+ "integrity": "sha512-JhhJDVwsSx4hiOEQPeajGhCWgBMBwVkxC/Pet53EpBVs7zHHtayKefw1jtPaNRXpI9RA2uocdmpdfE7T+NrizA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.47.0",
- "@typescript-eslint/visitor-keys": "8.47.0"
+ "@typescript-eslint/types": "8.51.0",
+ "@typescript-eslint/visitor-keys": "8.51.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1602,9 +1585,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.47.0.tgz",
- "integrity": "sha512-ybUAvjy4ZCL11uryalkKxuT3w3sXJAuWhOoGS3T/Wu+iUu1tGJmk5ytSY8gbdACNARmcYEB0COksD2j6hfGK2g==",
+ "version": "8.51.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.51.0.tgz",
+ "integrity": "sha512-Qi5bSy/vuHeWyir2C8u/uqGMIlIDu8fuiYWv48ZGlZ/k+PRPHtaAu7erpc7p5bzw2WNNSniuxoMSO4Ar6V9OXw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1619,17 +1602,17 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.47.0.tgz",
- "integrity": "sha512-QC9RiCmZ2HmIdCEvhd1aJELBlD93ErziOXXlHEZyuBo3tBiAZieya0HLIxp+DoDWlsQqDawyKuNEhORyku+P8A==",
+ "version": "8.51.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.51.0.tgz",
+ "integrity": "sha512-0XVtYzxnobc9K0VU7wRWg1yiUrw4oQzexCG2V2IDxxCxhqBMSMbjB+6o91A+Uc0GWtgjCa3Y8bi7hwI0Tu4n5Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.47.0",
- "@typescript-eslint/typescript-estree": "8.47.0",
- "@typescript-eslint/utils": "8.47.0",
+ "@typescript-eslint/types": "8.51.0",
+ "@typescript-eslint/typescript-estree": "8.51.0",
+ "@typescript-eslint/utils": "8.51.0",
"debug": "^4.3.4",
- "ts-api-utils": "^2.1.0"
+ "ts-api-utils": "^2.2.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1644,9 +1627,9 @@
}
},
"node_modules/@typescript-eslint/types": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.47.0.tgz",
- "integrity": "sha512-nHAE6bMKsizhA2uuYZbEbmp5z2UpffNrPEqiKIeN7VsV6UY/roxanWfoRrf6x/k9+Obf+GQdkm0nPU+vnMXo9A==",
+ "version": "8.51.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.51.0.tgz",
+ "integrity": "sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1658,22 +1641,21 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.47.0.tgz",
- "integrity": "sha512-k6ti9UepJf5NpzCjH31hQNLHQWupTRPhZ+KFF8WtTuTpy7uHPfeg2NM7cP27aCGajoEplxJDFVCEm9TGPYyiVg==",
+ "version": "8.51.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.51.0.tgz",
+ "integrity": "sha512-1qNjGqFRmlq0VW5iVlcyHBbCjPB7y6SxpBkrbhNWMy/65ZoncXCEPJxkRZL8McrseNH6lFhaxCIaX+vBuFnRng==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/project-service": "8.47.0",
- "@typescript-eslint/tsconfig-utils": "8.47.0",
- "@typescript-eslint/types": "8.47.0",
- "@typescript-eslint/visitor-keys": "8.47.0",
+ "@typescript-eslint/project-service": "8.51.0",
+ "@typescript-eslint/tsconfig-utils": "8.51.0",
+ "@typescript-eslint/types": "8.51.0",
+ "@typescript-eslint/visitor-keys": "8.51.0",
"debug": "^4.3.4",
- "fast-glob": "^3.3.2",
- "is-glob": "^4.0.3",
"minimatch": "^9.0.4",
"semver": "^7.6.0",
- "ts-api-utils": "^2.1.0"
+ "tinyglobby": "^0.2.15",
+ "ts-api-utils": "^2.2.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1713,16 +1695,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.47.0.tgz",
- "integrity": "sha512-g7XrNf25iL4TJOiPqatNuaChyqt49a/onq5YsJ9+hXeugK+41LVg7AxikMfM02PC6jbNtZLCJj6AUcQXJS/jGQ==",
+ "version": "8.51.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.51.0.tgz",
+ "integrity": "sha512-11rZYxSe0zabiKaCP2QAwRf/dnmgFgvTmeDTtZvUvXG3UuAdg/GU02NExmmIXzz3vLGgMdtrIosI84jITQOxUA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.7.0",
- "@typescript-eslint/scope-manager": "8.47.0",
- "@typescript-eslint/types": "8.47.0",
- "@typescript-eslint/typescript-estree": "8.47.0"
+ "@typescript-eslint/scope-manager": "8.51.0",
+ "@typescript-eslint/types": "8.51.0",
+ "@typescript-eslint/typescript-estree": "8.51.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1737,13 +1719,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.47.0.tgz",
- "integrity": "sha512-SIV3/6eftCy1bNzCQoPmbWsRLujS8t5iDIZ4spZOBHqrM+yfX2ogg8Tt3PDTAVKw3sSCiUgg30uOAvK2r9zGjQ==",
+ "version": "8.51.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.51.0.tgz",
+ "integrity": "sha512-mM/JRQOzhVN1ykejrvwnBRV3+7yTKK8tVANVN3o1O0t0v7o+jqdVu9crPy5Y9dov15TJk/FTIgoUGHrTOVL3Zg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.47.0",
+ "@typescript-eslint/types": "8.51.0",
"eslint-visitor-keys": "^4.2.1"
},
"engines": {
@@ -1755,14 +1737,14 @@
}
},
"node_modules/@vitest/browser": {
- "version": "4.0.12",
- "resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-4.0.12.tgz",
- "integrity": "sha512-8zE2ksJ7V4B7Mc++L6rBRZOZHnE/f9URvj7oLYKIS5wcDaSi6EhfalN0EG6+R/OlTYZarbK6RqmhKDLYNC9KfQ==",
+ "version": "4.0.16",
+ "resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-4.0.16.tgz",
+ "integrity": "sha512-t4toy8X/YTnjYEPoY0pbDBg3EvDPg1elCDrfc+VupPHwoN/5/FNQ8Z+xBYIaEnOE2vVEyKwqYBzZ9h9rJtZVcg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/mocker": "4.0.12",
- "@vitest/utils": "4.0.12",
+ "@vitest/mocker": "4.0.16",
+ "@vitest/utils": "4.0.16",
"magic-string": "^0.30.21",
"pixelmatch": "7.1.0",
"pngjs": "^7.0.0",
@@ -1774,18 +1756,18 @@
"url": "https://opencollective.com/vitest"
},
"peerDependencies": {
- "vitest": "4.0.12"
+ "vitest": "4.0.16"
}
},
"node_modules/@vitest/browser-playwright": {
- "version": "4.0.12",
- "resolved": "https://registry.npmjs.org/@vitest/browser-playwright/-/browser-playwright-4.0.12.tgz",
- "integrity": "sha512-TCFuvEHVLHDuK4HmKJNSuYQwefpXBnWNuf3J8pLsXF3Q2sgRZJs+L+aUF2xjtJT5OBEJsgX5jENvlGKc596XNw==",
+ "version": "4.0.16",
+ "resolved": "https://registry.npmjs.org/@vitest/browser-playwright/-/browser-playwright-4.0.16.tgz",
+ "integrity": "sha512-I2Fy/ANdphi1yI46d15o0M1M4M0UJrUiVKkH5oKeRZZCdPg0fw/cfTKZzv9Ge9eobtJYp4BGblMzXdXH0vcl5g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/browser": "4.0.12",
- "@vitest/mocker": "4.0.12",
+ "@vitest/browser": "4.0.16",
+ "@vitest/mocker": "4.0.16",
"tinyrainbow": "^3.0.3"
},
"funding": {
@@ -1793,7 +1775,7 @@
},
"peerDependencies": {
"playwright": "*",
- "vitest": "4.0.12"
+ "vitest": "4.0.16"
},
"peerDependenciesMeta": {
"playwright": {
@@ -1802,16 +1784,16 @@
}
},
"node_modules/@vitest/expect": {
- "version": "4.0.12",
- "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.12.tgz",
- "integrity": "sha512-is+g0w8V3/ZhRNrRizrJNr8PFQKwYmctWlU4qg8zy5r9aIV5w8IxXLlfbbxJCwSpsVl2PXPTm2/zruqTqz3QSg==",
+ "version": "4.0.16",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.16.tgz",
+ "integrity": "sha512-eshqULT2It7McaJkQGLkPjPjNph+uevROGuIMJdG3V+0BSR2w9u6J9Lwu+E8cK5TETlfou8GRijhafIMhXsimA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@standard-schema/spec": "^1.0.0",
"@types/chai": "^5.2.2",
- "@vitest/spy": "4.0.12",
- "@vitest/utils": "4.0.12",
+ "@vitest/spy": "4.0.16",
+ "@vitest/utils": "4.0.16",
"chai": "^6.2.1",
"tinyrainbow": "^3.0.3"
},
@@ -1820,13 +1802,13 @@
}
},
"node_modules/@vitest/mocker": {
- "version": "4.0.12",
- "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.12.tgz",
- "integrity": "sha512-GsmA/tD5Ht3RUFoz41mZsMU1AXch3lhmgbTnoSPTdH231g7S3ytNN1aU0bZDSyxWs8WA7KDyMPD5L4q6V6vj9w==",
+ "version": "4.0.16",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.16.tgz",
+ "integrity": "sha512-yb6k4AZxJTB+q9ycAvsoxGn+j/po0UaPgajllBgt1PzoMAAmJGYFdDk0uCcRcxb3BrME34I6u8gHZTQlkqSZpg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/spy": "4.0.12",
+ "@vitest/spy": "4.0.16",
"estree-walker": "^3.0.3",
"magic-string": "^0.30.21"
},
@@ -1847,9 +1829,9 @@
}
},
"node_modules/@vitest/pretty-format": {
- "version": "4.0.12",
- "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.12.tgz",
- "integrity": "sha512-R7nMAcnienG17MvRN8TPMJiCG8rrZJblV9mhT7oMFdBXvS0x+QD6S1G4DxFusR2E0QIS73f7DqSR1n87rrmE+g==",
+ "version": "4.0.16",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.16.tgz",
+ "integrity": "sha512-eNCYNsSty9xJKi/UdVD8Ou16alu7AYiS2fCPRs0b1OdhJiV89buAXQLpTbe+X8V9L6qrs9CqyvU7OaAopJYPsA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1860,13 +1842,13 @@
}
},
"node_modules/@vitest/runner": {
- "version": "4.0.12",
- "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.12.tgz",
- "integrity": "sha512-hDlCIJWuwlcLumfukPsNfPDOJokTv79hnOlf11V+n7E14rHNPz0Sp/BO6h8sh9qw4/UjZiKyYpVxK2ZNi+3ceQ==",
+ "version": "4.0.16",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.16.tgz",
+ "integrity": "sha512-VWEDm5Wv9xEo80ctjORcTQRJ539EGPB3Pb9ApvVRAY1U/WkHXmmYISqU5E79uCwcW7xYUV38gwZD+RV755fu3Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/utils": "4.0.12",
+ "@vitest/utils": "4.0.16",
"pathe": "^2.0.3"
},
"funding": {
@@ -1874,13 +1856,13 @@
}
},
"node_modules/@vitest/snapshot": {
- "version": "4.0.12",
- "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.12.tgz",
- "integrity": "sha512-2jz9zAuBDUSbnfyixnyOd1S2YDBrZO23rt1bicAb6MA/ya5rHdKFRikPIDpBj/Dwvh6cbImDmudegnDAkHvmRQ==",
+ "version": "4.0.16",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.16.tgz",
+ "integrity": "sha512-sf6NcrYhYBsSYefxnry+DR8n3UV4xWZwWxYbCJUt2YdvtqzSPR7VfGrY0zsv090DAbjFZsi7ZaMi1KnSRyK1XA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/pretty-format": "4.0.12",
+ "@vitest/pretty-format": "4.0.16",
"magic-string": "^0.30.21",
"pathe": "^2.0.3"
},
@@ -1889,9 +1871,9 @@
}
},
"node_modules/@vitest/spy": {
- "version": "4.0.12",
- "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.12.tgz",
- "integrity": "sha512-GZjI9PPhiOYNX8Nsyqdw7JQB+u0BptL5fSnXiottAUBHlcMzgADV58A7SLTXXQwcN1yZ6gfd1DH+2bqjuUlCzw==",
+ "version": "4.0.16",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.16.tgz",
+ "integrity": "sha512-4jIOWjKP0ZUaEmJm00E0cOBLU+5WE0BpeNr3XN6TEF05ltro6NJqHWxXD0kA8/Zc8Nh23AT8WQxwNG+WeROupw==",
"dev": true,
"license": "MIT",
"funding": {
@@ -1899,13 +1881,13 @@
}
},
"node_modules/@vitest/utils": {
- "version": "4.0.12",
- "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.12.tgz",
- "integrity": "sha512-DVS/TLkLdvGvj1avRy0LSmKfrcI9MNFvNGN6ECjTUHWJdlcgPDOXhjMis5Dh7rBH62nAmSXnkPbE+DZ5YD75Rw==",
+ "version": "4.0.16",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.16.tgz",
+ "integrity": "sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/pretty-format": "4.0.12",
+ "@vitest/pretty-format": "4.0.16",
"tinyrainbow": "^3.0.3"
},
"funding": {
@@ -2192,19 +2174,6 @@
"concat-map": "0.0.1"
}
},
- "node_modules/braces": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
- "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "fill-range": "^7.1.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
@@ -2223,9 +2192,9 @@
}
},
"node_modules/chai": {
- "version": "6.2.1",
- "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.1.tgz",
- "integrity": "sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg==",
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
+ "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2372,9 +2341,9 @@
"license": "MIT"
},
"node_modules/esbuild": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz",
- "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==",
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz",
+ "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@@ -2385,32 +2354,32 @@
"node": ">=18"
},
"optionalDependencies": {
- "@esbuild/aix-ppc64": "0.25.9",
- "@esbuild/android-arm": "0.25.9",
- "@esbuild/android-arm64": "0.25.9",
- "@esbuild/android-x64": "0.25.9",
- "@esbuild/darwin-arm64": "0.25.9",
- "@esbuild/darwin-x64": "0.25.9",
- "@esbuild/freebsd-arm64": "0.25.9",
- "@esbuild/freebsd-x64": "0.25.9",
- "@esbuild/linux-arm": "0.25.9",
- "@esbuild/linux-arm64": "0.25.9",
- "@esbuild/linux-ia32": "0.25.9",
- "@esbuild/linux-loong64": "0.25.9",
- "@esbuild/linux-mips64el": "0.25.9",
- "@esbuild/linux-ppc64": "0.25.9",
- "@esbuild/linux-riscv64": "0.25.9",
- "@esbuild/linux-s390x": "0.25.9",
- "@esbuild/linux-x64": "0.25.9",
- "@esbuild/netbsd-arm64": "0.25.9",
- "@esbuild/netbsd-x64": "0.25.9",
- "@esbuild/openbsd-arm64": "0.25.9",
- "@esbuild/openbsd-x64": "0.25.9",
- "@esbuild/openharmony-arm64": "0.25.9",
- "@esbuild/sunos-x64": "0.25.9",
- "@esbuild/win32-arm64": "0.25.9",
- "@esbuild/win32-ia32": "0.25.9",
- "@esbuild/win32-x64": "0.25.9"
+ "@esbuild/aix-ppc64": "0.27.2",
+ "@esbuild/android-arm": "0.27.2",
+ "@esbuild/android-arm64": "0.27.2",
+ "@esbuild/android-x64": "0.27.2",
+ "@esbuild/darwin-arm64": "0.27.2",
+ "@esbuild/darwin-x64": "0.27.2",
+ "@esbuild/freebsd-arm64": "0.27.2",
+ "@esbuild/freebsd-x64": "0.27.2",
+ "@esbuild/linux-arm": "0.27.2",
+ "@esbuild/linux-arm64": "0.27.2",
+ "@esbuild/linux-ia32": "0.27.2",
+ "@esbuild/linux-loong64": "0.27.2",
+ "@esbuild/linux-mips64el": "0.27.2",
+ "@esbuild/linux-ppc64": "0.27.2",
+ "@esbuild/linux-riscv64": "0.27.2",
+ "@esbuild/linux-s390x": "0.27.2",
+ "@esbuild/linux-x64": "0.27.2",
+ "@esbuild/netbsd-arm64": "0.27.2",
+ "@esbuild/netbsd-x64": "0.27.2",
+ "@esbuild/openbsd-arm64": "0.27.2",
+ "@esbuild/openbsd-x64": "0.27.2",
+ "@esbuild/openharmony-arm64": "0.27.2",
+ "@esbuild/sunos-x64": "0.27.2",
+ "@esbuild/win32-arm64": "0.27.2",
+ "@esbuild/win32-ia32": "0.27.2",
+ "@esbuild/win32-x64": "0.27.2"
}
},
"node_modules/escape-string-regexp": {
@@ -2427,9 +2396,9 @@
}
},
"node_modules/eslint": {
- "version": "9.39.1",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz",
- "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
+ "version": "9.39.2",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz",
+ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2439,7 +2408,7 @@
"@eslint/config-helpers": "^0.4.2",
"@eslint/core": "^0.17.0",
"@eslint/eslintrc": "^3.3.1",
- "@eslint/js": "9.39.1",
+ "@eslint/js": "9.39.2",
"@eslint/plugin-kit": "^0.4.1",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
@@ -2591,9 +2560,9 @@
}
},
"node_modules/expect-type": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz",
- "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==",
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz",
+ "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==",
"dev": true,
"license": "Apache-2.0",
"engines": {
@@ -2614,36 +2583,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/fast-glob": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
- "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@nodelib/fs.stat": "^2.0.2",
- "@nodelib/fs.walk": "^1.2.3",
- "glob-parent": "^5.1.2",
- "merge2": "^1.3.0",
- "micromatch": "^4.0.8"
- },
- "engines": {
- "node": ">=8.6.0"
- }
- },
- "node_modules/fast-glob/node_modules/glob-parent": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
- "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "is-glob": "^4.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
- },
"node_modules/fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
@@ -2675,16 +2614,6 @@
],
"license": "BSD-3-Clause"
},
- "node_modules/fastq": {
- "version": "1.19.1",
- "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
- "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "reusify": "^1.0.4"
- }
- },
"node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
@@ -2716,19 +2645,6 @@
"node": ">=16.0.0"
}
},
- "node_modules/fill-range": {
- "version": "7.1.1",
- "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
- "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "to-regex-range": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/find-up": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
@@ -2840,13 +2756,6 @@
"dev": true,
"license": "ISC"
},
- "node_modules/graphemer": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
- "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -2966,16 +2875,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/is-number": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
- "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.12.0"
- }
- },
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -3139,43 +3038,6 @@
"@jridgewell/sourcemap-codec": "^1.5.5"
}
},
- "node_modules/merge2": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
- "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/micromatch": {
- "version": "4.0.8",
- "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
- "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "braces": "^3.0.3",
- "picomatch": "^2.3.1"
- },
- "engines": {
- "node": ">=8.6"
- }
- },
- "node_modules/micromatch/node_modules/picomatch": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
- "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -3281,6 +3143,17 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/obug": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz",
+ "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==",
+ "dev": true,
+ "funding": [
+ "https://github.com/sponsors/sxzz",
+ "https://opencollective.com/debug"
+ ],
+ "license": "MIT"
+ },
"node_modules/optionator": {
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
@@ -3444,13 +3317,13 @@
}
},
"node_modules/playwright": {
- "version": "1.56.1",
- "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.1.tgz",
- "integrity": "sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw==",
+ "version": "1.57.0",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz",
+ "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "playwright-core": "1.56.1"
+ "playwright-core": "1.57.0"
},
"bin": {
"playwright": "cli.js"
@@ -3463,9 +3336,9 @@
}
},
"node_modules/playwright-core": {
- "version": "1.56.1",
- "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.1.tgz",
- "integrity": "sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==",
+ "version": "1.57.0",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz",
+ "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@@ -3551,27 +3424,6 @@
],
"license": "MIT"
},
- "node_modules/queue-microtask": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
- "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
"node_modules/randombytes": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
@@ -3623,17 +3475,6 @@
"node": ">=4"
}
},
- "node_modules/reusify": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
- "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "iojs": ">=1.0.0",
- "node": ">=0.10.0"
- }
- },
"node_modules/rollup": {
"version": "4.48.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.48.1.tgz",
@@ -3697,30 +3538,6 @@
"rollup": "^1.0.0 || ^2.0.0 || ^3.0.0 || ^4.0.0"
}
},
- "node_modules/run-parallel": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
- "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "queue-microtask": "^1.2.2"
- }
- },
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -4011,11 +3828,14 @@
"license": "MIT"
},
"node_modules/tinyexec": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
- "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==",
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz",
+ "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
},
"node_modules/tinyglobby": {
"version": "0.2.15",
@@ -4044,19 +3864,6 @@
"node": ">=14.0.0"
}
},
- "node_modules/to-regex-range": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
- "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-number": "^7.0.0"
- },
- "engines": {
- "node": ">=8.0"
- }
- },
"node_modules/totalist": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz",
@@ -4068,9 +3875,9 @@
}
},
"node_modules/ts-api-utils": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
- "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==",
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.3.0.tgz",
+ "integrity": "sha512-6eg3Y9SF7SsAvGzRHQvvc1skDAhwI4YQ32ui1scxD1Ccr0G5qIIbUBT3pFTKX8kmWIQClHobtUdNuaBgwdfdWg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -4109,16 +3916,16 @@
}
},
"node_modules/typescript-eslint": {
- "version": "8.47.0",
- "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.47.0.tgz",
- "integrity": "sha512-Lwe8i2XQ3WoMjua/r1PHrCTpkubPYJCAfOurtn+mtTzqB6jNd+14n9UN1bJ4s3F49x9ixAm0FLflB/JzQ57M8Q==",
+ "version": "8.51.0",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.51.0.tgz",
+ "integrity": "sha512-jh8ZuM5oEh2PSdyQG9YAEM1TCGuWenLSuSUhf/irbVUNW9O5FhbFVONviN2TgMTBnUmyHv7E56rYnfLZK6TkiA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/eslint-plugin": "8.47.0",
- "@typescript-eslint/parser": "8.47.0",
- "@typescript-eslint/typescript-estree": "8.47.0",
- "@typescript-eslint/utils": "8.47.0"
+ "@typescript-eslint/eslint-plugin": "8.51.0",
+ "@typescript-eslint/parser": "8.51.0",
+ "@typescript-eslint/typescript-estree": "8.51.0",
+ "@typescript-eslint/utils": "8.51.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -4160,13 +3967,13 @@
}
},
"node_modules/vite": {
- "version": "7.2.4",
- "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.4.tgz",
- "integrity": "sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==",
+ "version": "7.3.0",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz",
+ "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "esbuild": "^0.25.0",
+ "esbuild": "^0.27.0",
"fdir": "^6.5.0",
"picomatch": "^4.0.3",
"postcss": "^8.5.6",
@@ -4277,28 +4084,28 @@
}
},
"node_modules/vitest": {
- "version": "4.0.12",
- "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.12.tgz",
- "integrity": "sha512-pmW4GCKQ8t5Ko1jYjC3SqOr7TUKN7uHOHB/XGsAIb69eYu6d1ionGSsb5H9chmPf+WeXt0VE7jTXsB1IvWoNbw==",
+ "version": "4.0.16",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.16.tgz",
+ "integrity": "sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/expect": "4.0.12",
- "@vitest/mocker": "4.0.12",
- "@vitest/pretty-format": "4.0.12",
- "@vitest/runner": "4.0.12",
- "@vitest/snapshot": "4.0.12",
- "@vitest/spy": "4.0.12",
- "@vitest/utils": "4.0.12",
- "debug": "^4.4.3",
+ "@vitest/expect": "4.0.16",
+ "@vitest/mocker": "4.0.16",
+ "@vitest/pretty-format": "4.0.16",
+ "@vitest/runner": "4.0.16",
+ "@vitest/snapshot": "4.0.16",
+ "@vitest/spy": "4.0.16",
+ "@vitest/utils": "4.0.16",
"es-module-lexer": "^1.7.0",
"expect-type": "^1.2.2",
"magic-string": "^0.30.21",
+ "obug": "^2.1.1",
"pathe": "^2.0.3",
"picomatch": "^4.0.3",
"std-env": "^3.10.0",
"tinybench": "^2.9.0",
- "tinyexec": "^0.3.2",
+ "tinyexec": "^1.0.2",
"tinyglobby": "^0.2.15",
"tinyrainbow": "^3.0.3",
"vite": "^6.0.0 || ^7.0.0",
@@ -4316,12 +4123,11 @@
"peerDependencies": {
"@edge-runtime/vm": "*",
"@opentelemetry/api": "^1.9.0",
- "@types/debug": "^4.1.12",
"@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
- "@vitest/browser-playwright": "4.0.12",
- "@vitest/browser-preview": "4.0.12",
- "@vitest/browser-webdriverio": "4.0.12",
- "@vitest/ui": "4.0.12",
+ "@vitest/browser-playwright": "4.0.16",
+ "@vitest/browser-preview": "4.0.16",
+ "@vitest/browser-webdriverio": "4.0.16",
+ "@vitest/ui": "4.0.16",
"happy-dom": "*",
"jsdom": "*"
},
@@ -4332,9 +4138,6 @@
"@opentelemetry/api": {
"optional": true
},
- "@types/debug": {
- "optional": true
- },
"@types/node": {
"optional": true
},
diff --git a/package.json b/package.json
index a1ccbc5..d3412da 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "blokr",
- "version": "0.2.1",
+ "version": "0.3.0",
"description": "Lightweight library to block user interactions in browsers",
"keywords": [
"event",
@@ -9,7 +9,13 @@
"disable",
"interaction",
"ui",
- "browser"
+ "browser",
+ "factory",
+ "scope",
+ "element",
+ "modal",
+ "overlay",
+ "pointer-events"
],
"author": "KNOWLEDGECODE",
"license": "MIT",
@@ -44,14 +50,15 @@
},
"devDependencies": {
"@rollup/plugin-terser": "^0.4.4",
- "@vitest/browser": "^4.0.12",
- "@vitest/browser-playwright": "^4.0.12",
- "eslint": "^9.39.1",
- "playwright": "^1.56.1",
+ "@stylistic/eslint-plugin": "^5.6.1",
+ "@vitest/browser": "^4.0.16",
+ "@vitest/browser-playwright": "^4.0.16",
+ "eslint": "^9.39.2",
+ "playwright": "^1.57.0",
"rollup-plugin-license": "^3.6.0",
- "typescript-eslint": "^8.47.0",
- "vite": "^7.2.4",
+ "typescript-eslint": "^8.51.0",
+ "vite": "^7.3.0",
"vite-plugin-dts": "^4.5.4",
- "vitest": "^4.0.12"
+ "vitest": "^4.0.16"
}
}
diff --git a/src/blokr.ts b/src/blokr.ts
index 4fb941f..8594fb7 100644
--- a/src/blokr.ts
+++ b/src/blokr.ts
@@ -1,84 +1,95 @@
import lock from './lock.ts';
+import type { Filter } from './lock.ts';
+
+export type Scope = 'inside' | 'outside' | 'self';
+
+export interface Options {
+ scope?: Scope;
+ timeout?: number;
+}
+
+const blokrs = new WeakMap();
class Blokr {
- private _timeout: number;
+ private _target: Element | undefined;
- private _timerId: number;
+ private _timerId: number | undefined;
- private _counter: number;
+ private _filter: Filter | undefined;
/**
* Creates the Blokr singleton instance.
*/
- constructor () {
- this._timeout = 10000; // Default timeout of 10 seconds
- this._timerId = 0;
- this._counter = 0;
+ constructor (target?: Element) {
+ this._target = target;
+ this._timerId = undefined;
}
/**
- * Prevents user interactions.
+ * Locks user interactions with optional timeout and scope configuration.
+ * Returns false if already locked without making any changes.
+ * @param [options] - Lock configuration options.
+ * @returns true if lock was applied, false if already locked.
*/
- lock () {
- lock.on();
- this._counter++;
-
- if (this._timerId) {
- self.clearTimeout(this._timerId);
- this._timerId = 0;
+ lock (options?: Options) {
+ if (this.isLocked()) {
+ return false;
}
- if (this._timeout) {
- this._timerId = self.setTimeout(() => {
- this._timerId = 0;
- this._counter = 0;
- lock.off();
- }, this._timeout);
+
+ const scope = options?.scope ?? 'inside';
+ const timeout = options?.timeout ?? 0;
+
+ this._filter = (eventTarget: Element) => {
+ if (this._target) {
+ if (scope === 'self') {
+ return this._target === eventTarget;
+ }
+ const contains = this._target.contains(eventTarget);
+ // For 'outside' scope, block events outside target; otherwise block events inside target
+ return scope === 'outside' ? !contains : contains;
+ }
+ // No target specified: block all events
+ return true;
+ };
+ lock.register(this._filter);
+
+ if (timeout > 0) {
+ this._timerId = globalThis.setTimeout(() => this.unlock(), timeout);
}
+
+ return true;
}
/**
- * Checks if user interactions are currently prevented.
- * @returns {boolean} Returns true if interactions are blocked, false otherwise.
+ * Checks if user interactions are currently locked.
+ * @returns true if locked, false otherwise.
*/
isLocked () {
- return this._counter > 0;
+ return !!this._filter;
}
/**
- * Sets the timeout duration for automatic unlock.
- * @param timeout - The timeout in milliseconds. Set to 0 to disable automatic unlock. Negative values are treated as 0.
- * @returns {boolean} Returns true if the timeout was set successfully, false if currently locked.
+ * Unlocks user interactions and clears any pending timeout.
+ * Safe to call even when not locked.
*/
- setTimeout(timeout: number) {
- if (!this.isLocked()) {
- this._timeout = timeout < 0 ? 0 : timeout;
- return true;
+ unlock () {
+ if (this._timerId) {
+ globalThis.clearTimeout(this._timerId);
+ this._timerId = undefined;
}
- return false;
- }
-
- /**
- * Decrements the internal counter and releases the lock when the counter reaches zero.
- * If abort is true, the counter is reset to zero immediately, effectively releasing the lock.
- * Clears any pending timeout and triggers the unlock event.
- * @param abort - If true, immediately resets the counter to zero and releases the lock
- */
- unlock (abort = false) {
- if (this._counter > 0) {
- this._counter--;
-
- if (abort) {
- this._counter = 0;
- }
- if (this._counter === 0) {
- if (this._timerId) {
- self.clearTimeout(this._timerId);
- this._timerId = 0;
- }
- lock.off();
- }
+ if (this._filter) {
+ lock.unregister(this._filter);
}
+ this._filter = undefined;
}
}
-export default new Blokr();
+const blokr = (target?: Element) => {
+ return blokrs.get(target ?? globalThis) ?? (() => {
+ const instance = new Blokr(target);
+ blokrs.set(target ?? globalThis, instance);
+ return instance;
+ })();
+};
+
+export default blokr;
diff --git a/src/lock.ts b/src/lock.ts
index ef9d0dd..20a5511 100644
--- a/src/lock.ts
+++ b/src/lock.ts
@@ -1,42 +1,58 @@
+export type Filter = (eventTarget: Element) => boolean;
+
const eventNames = [
'contextmenu', 'keydown', 'mousedown', 'touchmove', 'touchstart', 'wheel'
];
-const options = { capture: true, passive: false };
-
class Lock {
- private _locked: boolean;
+ private _filters: Set;
+ /**
+ * Creates the Lock singleton instance.
+ */
constructor () {
- this._locked = false;
- this._listener = this._listener.bind(this);
- if (typeof self !== 'undefined') {
- eventNames.forEach(eventName => self.addEventListener(eventName, this._listener, options));
+ this._filters = new Set();
+
+ if ('addEventListener' in globalThis) {
+ eventNames.forEach(eventName => globalThis.addEventListener(
+ eventName,
+ this._listener.bind(this),
+ { capture: true, passive: false }
+ ));
}
}
+ /**
+ * Blocks user interactions when the lock is active.
+ * @param evt - The event to be blocked.
+ */
private _listener (evt: Event) {
- if (this._locked) {
- evt.stopImmediatePropagation();
- evt.stopPropagation();
- evt.preventDefault();
+ if (evt.target instanceof Element) {
+ for (const filter of this._filters.values()) {
+ if (filter(evt.target)) {
+ evt.stopImmediatePropagation();
+ evt.stopPropagation();
+ evt.preventDefault();
+ break;
+ }
+ }
}
}
- on () {
- if (this._locked) {
- return false;
- }
- this._locked = true;
-
- return true;
+ /**
+ * Registers a filter function to block events matching the filter criteria.
+ * @param filter - Filter function that determines which events to block.
+ */
+ register (filter: Filter) {
+ this._filters.add(filter);
}
- off () {
- if (!this._locked) {
- return;
- }
- this._locked = false;
+ /**
+ * Unregisters a previously registered filter function.
+ * @param filter - The filter function to remove.
+ */
+ unregister (filter: Filter) {
+ this._filters.delete(filter);
}
}
diff --git a/tests/blokr.test.ts b/tests/blokr.test.ts
index 774861f..4ef49a8 100644
--- a/tests/blokr.test.ts
+++ b/tests/blokr.test.ts
@@ -1,251 +1,293 @@
-import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
+import { describe, it, expect, afterEach, vi } from 'vitest';
import blokr from '../src/blokr.ts';
-describe('Blokr Singleton', () => {
- beforeEach(() => {
- // Reset the singleton state
- blokr.setTimeout(10000);
- // Unlock any existing locks
- while (blokr.isLocked()) {
- blokr.unlock();
- }
- });
-
+describe('Blokr Factory Function', () => {
afterEach(() => {
// Clean up any locks after each test
- while (blokr.isLocked()) {
- blokr.unlock();
+ const globalInstance = blokr();
+ if (globalInstance.isLocked()) {
+ globalInstance.unlock();
}
});
- describe('Singleton Instance', () => {
- it('should be available as singleton', () => {
- expect(blokr).toBeDefined();
- expect(typeof blokr.lock).toBe('function');
- expect(typeof blokr.unlock).toBe('function');
- expect(typeof blokr.isLocked).toBe('function');
- expect(typeof blokr.setTimeout).toBe('function');
+ describe('Factory Behavior', () => {
+ it('should return a Blokr instance', () => {
+ const instance = blokr();
+ expect(instance).toBeDefined();
+ expect(typeof instance.lock).toBe('function');
+ expect(typeof instance.unlock).toBe('function');
+ expect(typeof instance.isLocked).toBe('function');
+ });
+
+ it('should return the same instance for global (no target)', () => {
+ const instance1 = blokr();
+ const instance2 = blokr();
+ expect(instance1).toBe(instance2);
+ });
+
+ it('should return the same instance for same element', () => {
+ const element = document.createElement('div');
+ const instance1 = blokr(element);
+ const instance2 = blokr(element);
+ expect(instance1).toBe(instance2);
+ });
+
+ it('should return different instances for different elements', () => {
+ const element1 = document.createElement('div');
+ const element2 = document.createElement('div');
+ const instance1 = blokr(element1);
+ const instance2 = blokr(element2);
+ expect(instance1).not.toBe(instance2);
});
});
describe('lock()', () => {
- it('should increment counter on first lock', () => {
- blokr.lock();
- // We can test the behavior indirectly by checking unlock behavior
- blokr.unlock();
- // If lock/unlock worked correctly, no exception should be thrown
- expect(() => blokr.unlock()).not.toThrow();
+ it('should lock and return true on first call', () => {
+ const instance = blokr();
+ const result = instance.lock();
+ expect(result).toBe(true);
+ expect(instance.isLocked()).toBe(true);
+ instance.unlock();
});
- it('should increment counter on multiple locks', () => {
- blokr.lock();
- blokr.lock();
- blokr.lock();
- // Counter should be 3, test through unlock behavior
- blokr.unlock();
- blokr.unlock();
- blokr.unlock();
- // Should be fully unlocked after 3 unlocks
- expect(() => blokr.unlock()).not.toThrow();
+ it('should return false if already locked', () => {
+ const instance = blokr();
+ const result1 = instance.lock();
+ expect(result1).toBe(true);
+
+ const result2 = instance.lock();
+ expect(result2).toBe(false);
+ expect(instance.isLocked()).toBe(true);
+
+ instance.unlock();
});
- it('should set timeout when timeout > 0', () => {
- const setTimeoutSpy = vi.spyOn(self, 'setTimeout');
- blokr.setTimeout(5000);
+ it('should accept timeout option', () => {
+ const instance = blokr();
+ const setTimeoutSpy = vi.spyOn(globalThis, 'setTimeout');
- blokr.lock();
+ instance.lock({ timeout: 5000 });
expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), 5000);
+ expect(instance.isLocked()).toBe(true);
+
setTimeoutSpy.mockRestore();
+ instance.unlock();
});
it('should not set timeout when timeout is 0', () => {
- const setTimeoutSpy = vi.spyOn(self, 'setTimeout');
- blokr.setTimeout(0);
+ const instance = blokr();
+ const setTimeoutSpy = vi.spyOn(globalThis, 'setTimeout');
- blokr.lock();
+ instance.lock({ timeout: 0 });
expect(setTimeoutSpy).not.toHaveBeenCalled();
+ expect(instance.isLocked()).toBe(true);
+
setTimeoutSpy.mockRestore();
+ instance.unlock();
});
- it('should clear existing timeout on subsequent locks', () => {
- const clearTimeoutSpy = vi.spyOn(self, 'clearTimeout');
- blokr.setTimeout(5000);
+ it('should accept scope option', () => {
+ const instance = blokr(document.createElement('div'));
- blokr.lock();
- blokr.lock();
+ const result1 = instance.lock({ scope: 'inside' });
+ expect(result1).toBe(true);
+ instance.unlock();
- expect(clearTimeoutSpy).toHaveBeenCalled();
- clearTimeoutSpy.mockRestore();
+ const result2 = instance.lock({ scope: 'outside' });
+ expect(result2).toBe(true);
+ instance.unlock();
+
+ const result3 = instance.lock({ scope: 'self' });
+ expect(result3).toBe(true);
+ instance.unlock();
+ });
+
+ it('should use default scope "inside" if not specified', () => {
+ const instance = blokr(document.createElement('div'));
+ const result = instance.lock();
+ expect(result).toBe(true);
+ expect(instance.isLocked()).toBe(true);
+ instance.unlock();
});
});
describe('unlock()', () => {
- it('should decrement counter when locked', () => {
- blokr.lock();
- blokr.unlock();
- // Counter should be 0 now, verify by checking unlock doesn't throw
- expect(() => blokr.unlock()).not.toThrow();
+ it('should unlock when locked', () => {
+ const instance = blokr();
+ instance.lock();
+ expect(instance.isLocked()).toBe(true);
+
+ instance.unlock();
+ expect(instance.isLocked()).toBe(false);
});
- it('should not decrement counter when not locked', () => {
- blokr.unlock(); // Should do nothing
- blokr.unlock(); // Should do nothing
- // Should not throw errors when unlocking without locking
- expect(() => blokr.unlock()).not.toThrow();
+ it('should do nothing when not locked', () => {
+ const instance = blokr();
+ expect(instance.isLocked()).toBe(false);
+
+ instance.unlock();
+ expect(instance.isLocked()).toBe(false);
+
+ // Should not throw
+ expect(() => instance.unlock()).not.toThrow();
});
- it('should clear timeout when counter reaches 0', () => {
- const clearTimeoutSpy = vi.spyOn(self, 'clearTimeout');
- blokr.setTimeout(5000);
+ it('should clear timeout when unlocking', () => {
+ const instance = blokr();
+ const clearTimeoutSpy = vi.spyOn(globalThis, 'clearTimeout');
- blokr.lock();
- blokr.unlock();
+ instance.lock({ timeout: 5000 });
+ instance.unlock();
expect(clearTimeoutSpy).toHaveBeenCalled();
clearTimeoutSpy.mockRestore();
});
- it('should handle multiple lock/unlock cycles', () => {
- blokr.lock();
- blokr.lock();
- blokr.lock(); // Counter = 3
+ it('should clear timeout only when timeout was set', () => {
+ const instance = blokr();
+ const clearTimeoutSpy = vi.spyOn(globalThis, 'clearTimeout');
- blokr.unlock(); // Counter = 2
- blokr.unlock(); // Counter = 1
- blokr.unlock(); // Counter = 0, should unlock
+ instance.lock({ timeout: 0 });
+ instance.unlock();
- blokr.unlock(); // Should do nothing (counter already 0)
-
- // Verify final state is correct
- expect(() => blokr.unlock()).not.toThrow();
+ expect(clearTimeoutSpy).not.toHaveBeenCalled();
+ clearTimeoutSpy.mockRestore();
});
+ });
- it('should immediately unlock when abort is true', () => {
- blokr.lock();
- blokr.lock();
- blokr.lock(); // Counter = 3
-
- blokr.unlock(true); // Should reset counter to 0 immediately
+ describe('isLocked()', () => {
+ it('should return false when not locked', () => {
+ const instance = blokr();
+ expect(instance.isLocked()).toBe(false);
+ });
- expect(blokr.isLocked()).toBe(false);
+ it('should return true when locked', () => {
+ const instance = blokr();
+ instance.lock();
+ expect(instance.isLocked()).toBe(true);
+ instance.unlock();
});
- it('should clear timeout when abort is true', () => {
- const clearTimeoutSpy = vi.spyOn(self, 'clearTimeout');
- blokr.setTimeout(5000);
+ it('should return false after unlock', () => {
+ const instance = blokr();
+ instance.lock();
+ instance.unlock();
+ expect(instance.isLocked()).toBe(false);
+ });
+ });
- blokr.lock();
- blokr.lock(); // Counter = 2
- blokr.unlock(true); // Should clear timeout and reset counter
+ describe('Timeout Behavior', () => {
+ it('should auto-unlock after timeout', () => {
+ vi.useFakeTimers();
- expect(clearTimeoutSpy).toHaveBeenCalled();
- expect(blokr.isLocked()).toBe(false);
- clearTimeoutSpy.mockRestore();
- });
+ const instance = blokr();
+ instance.lock({ timeout: 1000 });
- it('should work normally when abort is false (default)', () => {
- blokr.lock();
- blokr.lock();
- blokr.lock(); // Counter = 3
+ expect(instance.isLocked()).toBe(true);
- blokr.unlock(false); // Counter = 2
- expect(blokr.isLocked()).toBe(true);
+ // Fast-forward time
+ vi.advanceTimersByTime(1000);
- blokr.unlock(); // Counter = 1 (default false)
- expect(blokr.isLocked()).toBe(true);
+ // Should be unlocked after timeout
+ expect(instance.isLocked()).toBe(false);
- blokr.unlock(); // Counter = 0
- expect(blokr.isLocked()).toBe(false);
+ vi.useRealTimers();
});
- });
- describe('setTimeout()', () => {
- it('should set timeout when not locked', () => {
- const result = blokr.setTimeout(5000);
- expect(result).toBe(true);
- });
+ it('should not set timeout when timeout is 0', () => {
+ const instance = blokr();
+ const setTimeoutSpy = vi.spyOn(globalThis, 'setTimeout');
- it('should not set timeout when locked', () => {
- blokr.lock();
- const result = blokr.setTimeout(5000);
- expect(result).toBe(false);
- });
+ instance.lock({ timeout: 0 });
- it('should handle negative timeout values', () => {
- const result = blokr.setTimeout(-1000);
- expect(result).toBe(true);
- // Verify timeout was set to 0 by checking lock behavior
- const setTimeoutSpy = vi.spyOn(self, 'setTimeout');
- blokr.lock();
expect(setTimeoutSpy).not.toHaveBeenCalled();
+
setTimeoutSpy.mockRestore();
+ instance.unlock();
});
- });
- describe('isLocked()', () => {
- it('should return false when not locked', () => {
- expect(blokr.isLocked()).toBe(false);
- });
+ it('should clear timeout on manual unlock', () => {
+ vi.useFakeTimers();
- it('should return true when locked', () => {
- blokr.lock();
- expect(blokr.isLocked()).toBe(true);
- });
+ const instance = blokr();
+ instance.lock({ timeout: 5000 });
- it('should return false after unlock', () => {
- blokr.lock();
- blokr.unlock();
- expect(blokr.isLocked()).toBe(false);
- });
-
- it('should return true until all locks are released', () => {
- blokr.lock();
- blokr.lock();
- blokr.lock();
-
- expect(blokr.isLocked()).toBe(true);
- blokr.unlock();
- expect(blokr.isLocked()).toBe(true);
- blokr.unlock();
- expect(blokr.isLocked()).toBe(true);
- blokr.unlock();
- expect(blokr.isLocked()).toBe(false);
+ // Unlock before timeout
+ instance.unlock();
+
+ // Fast-forward past the timeout
+ vi.advanceTimersByTime(5000);
+
+ // Should still be unlocked (timeout was cleared)
+ expect(instance.isLocked()).toBe(false);
+
+ vi.useRealTimers();
});
});
- describe('timeout behavior', () => {
- it('should auto-unlock after timeout', async () => {
- vi.useFakeTimers();
+ describe('Independent Instances', () => {
+ it('should support independent locks on different elements', () => {
+ const element1 = document.createElement('div');
+ const element2 = document.createElement('div');
- blokr.setTimeout(1000);
- blokr.lock();
+ const instance1 = blokr(element1);
+ const instance2 = blokr(element2);
- // Fast-forward time
- vi.advanceTimersByTime(1000);
+ instance1.lock();
+ expect(instance1.isLocked()).toBe(true);
+ expect(instance2.isLocked()).toBe(false);
- // Should be unlocked after timeout
- expect(blokr.isLocked()).toBe(false);
+ instance2.lock();
+ expect(instance1.isLocked()).toBe(true);
+ expect(instance2.isLocked()).toBe(true);
- vi.useRealTimers();
+ instance1.unlock();
+ expect(instance1.isLocked()).toBe(false);
+ expect(instance2.isLocked()).toBe(true);
+
+ instance2.unlock();
});
- it('should reset counter to 0 on timeout', async () => {
+ it('should maintain independent timeout states', () => {
vi.useFakeTimers();
- blokr.setTimeout(1000);
- blokr.lock();
- blokr.lock();
- blokr.lock(); // Counter = 3
+ const element1 = document.createElement('div');
+ const element2 = document.createElement('div');
+
+ const instance1 = blokr(element1);
+ const instance2 = blokr(element2);
+
+ instance1.lock({ timeout: 1000 });
+ instance2.lock({ timeout: 2000 });
- // Fast-forward time
vi.advanceTimersByTime(1000);
+ expect(instance1.isLocked()).toBe(false);
+ expect(instance2.isLocked()).toBe(true);
- // After timeout, should be fully unlocked
- expect(blokr.isLocked()).toBe(false);
+ vi.advanceTimersByTime(1000);
+ expect(instance1.isLocked()).toBe(false);
+ expect(instance2.isLocked()).toBe(false);
vi.useRealTimers();
});
});
-});
\ No newline at end of file
+
+ describe('Global Instance', () => {
+ it('should block all events when no target specified', () => {
+ const instance = blokr();
+ instance.lock();
+ expect(instance.isLocked()).toBe(true);
+ instance.unlock();
+ });
+
+ it('should use default scope "inside" for global instance', () => {
+ const instance = blokr();
+ // Global instance with no target should still work (no filtering)
+ const result = instance.lock();
+ expect(result).toBe(true);
+ instance.unlock();
+ });
+ });
+});
diff --git a/tests/event-blocking.test.ts b/tests/event-blocking.test.ts
index 3525fb1..b879116 100644
--- a/tests/event-blocking.test.ts
+++ b/tests/event-blocking.test.ts
@@ -1,188 +1,378 @@
-import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
+import { describe, it, expect, afterEach, vi } from 'vitest';
import blokr from '../src/blokr.ts';
describe('Event Blocking Integration', () => {
- let testElement: HTMLElement;
-
- beforeEach(() => {
- // Reset singleton state
- while (blokr.isLocked()) {
- blokr.unlock();
- }
- testElement = document.createElement('button');
- testElement.textContent = 'Test Button';
- document.body.appendChild(testElement);
- });
-
afterEach(() => {
- // Clean up locks
- while (blokr.isLocked()) {
- blokr.unlock();
+ // Clean up any locks after each test
+ const globalInstance = blokr();
+ if (globalInstance.isLocked()) {
+ globalInstance.unlock();
}
- document.body.removeChild(testElement);
});
- describe('Mousedown Events', () => {
- it('should block mousedown events when locked', () => {
- const mousedownHandler = vi.fn();
- testElement.addEventListener('mousedown', mousedownHandler);
+ describe('Global Event Blocking', () => {
+ it('should block mousedown events when locked globally', () => {
+ const handler = vi.fn();
+ const element = document.createElement('button');
+ element.addEventListener('mousedown', handler);
+ document.body.appendChild(element);
- blokr.lock();
+ const instance = blokr();
+ instance.lock();
- // Simulate mousedown event
- const mousedownEvent = new MouseEvent('mousedown', {
+ const event = new MouseEvent('mousedown', {
bubbles: true,
cancelable: true
});
- testElement.dispatchEvent(mousedownEvent);
+ element.dispatchEvent(event);
+
+ expect(handler).not.toHaveBeenCalled();
- expect(mousedownHandler).not.toHaveBeenCalled();
+ instance.unlock();
+ document.body.removeChild(element);
});
it('should allow mousedown events when unlocked', () => {
- const mousedownHandler = vi.fn();
- testElement.addEventListener('mousedown', mousedownHandler);
+ const handler = vi.fn();
+ const element = document.createElement('button');
+ element.addEventListener('mousedown', handler);
+ document.body.appendChild(element);
// Don't lock
- // Simulate mousedown event
- const mousedownEvent = new MouseEvent('mousedown', {
- bubbles: true,
- cancelable: true
- });
-
- testElement.dispatchEvent(mousedownEvent);
-
- expect(mousedownHandler).toHaveBeenCalled();
- });
- });
-
- describe('Keyboard Events', () => {
- it('should block keydown events when locked', () => {
- const keydownHandler = vi.fn();
- document.addEventListener('keydown', keydownHandler);
-
- blokr.lock();
-
- // Simulate keydown event
- const keydownEvent = new KeyboardEvent('keydown', {
- key: 'Enter',
+ const event = new MouseEvent('mousedown', {
bubbles: true,
cancelable: true
});
- document.dispatchEvent(keydownEvent);
+ element.dispatchEvent(event);
- expect(keydownHandler).not.toHaveBeenCalled();
+ expect(handler).toHaveBeenCalled();
- document.removeEventListener('keydown', keydownHandler);
+ document.body.removeChild(element);
});
- it('should allow keydown events when unlocked', () => {
- const keydownHandler = vi.fn();
- document.addEventListener('keydown', keydownHandler);
+ it('should block keydown events when locked globally', () => {
+ const handler = vi.fn();
+ const element = document.createElement('input');
+ element.addEventListener('keydown', handler);
+ document.body.appendChild(element);
- // Don't lock
+ const instance = blokr();
+ instance.lock();
- // Simulate keydown event
- const keydownEvent = new KeyboardEvent('keydown', {
+ const event = new KeyboardEvent('keydown', {
key: 'Enter',
bubbles: true,
cancelable: true
});
- document.dispatchEvent(keydownEvent);
+ element.dispatchEvent(event);
- expect(keydownHandler).toHaveBeenCalled();
+ expect(handler).not.toHaveBeenCalled();
- document.removeEventListener('keydown', keydownHandler);
+ instance.unlock();
+ document.body.removeChild(element);
});
- });
- describe('Mouse Events', () => {
+ it('should block contextmenu events when locked globally', () => {
+ const handler = vi.fn();
+ const element = document.createElement('div');
+ element.addEventListener('contextmenu', handler);
+ document.body.appendChild(element);
- it('should block contextmenu events when locked', () => {
- const contextmenuHandler = vi.fn();
- testElement.addEventListener('contextmenu', contextmenuHandler);
+ const instance = blokr();
+ instance.lock();
- blokr.lock();
-
- // Simulate contextmenu event
- const contextmenuEvent = new MouseEvent('contextmenu', {
+ const event = new MouseEvent('contextmenu', {
bubbles: true,
cancelable: true
});
- testElement.dispatchEvent(contextmenuEvent);
+ element.dispatchEvent(event);
+
+ expect(handler).not.toHaveBeenCalled();
- expect(contextmenuHandler).not.toHaveBeenCalled();
+ instance.unlock();
+ document.body.removeChild(element);
});
- it('should block wheel events when locked', () => {
- const wheelHandler = vi.fn();
- testElement.addEventListener('wheel', wheelHandler);
+ it('should block wheel events when locked globally', () => {
+ const handler = vi.fn();
+ const element = document.createElement('div');
+ element.addEventListener('wheel', handler);
+ document.body.appendChild(element);
- blokr.lock();
+ const instance = blokr();
+ instance.lock();
- // Simulate wheel event
- const wheelEvent = new WheelEvent('wheel', {
+ const event = new WheelEvent('wheel', {
bubbles: true,
cancelable: true,
deltaY: 100
});
- testElement.dispatchEvent(wheelEvent);
+ element.dispatchEvent(event);
- expect(wheelHandler).not.toHaveBeenCalled();
+ expect(handler).not.toHaveBeenCalled();
+
+ instance.unlock();
+ document.body.removeChild(element);
});
- });
- describe('Touch Events', () => {
- it('should block touchstart events when locked', () => {
- const touchstartHandler = vi.fn();
- testElement.addEventListener('touchstart', touchstartHandler);
+ it('should block touchstart events when locked globally', () => {
+ const handler = vi.fn();
+ const element = document.createElement('div');
+ element.addEventListener('touchstart', handler);
+ document.body.appendChild(element);
- blokr.lock();
+ const instance = blokr();
+ instance.lock();
- // Simulate touchstart event
- const touchstartEvent = new TouchEvent('touchstart', {
+ const event = new TouchEvent('touchstart', {
bubbles: true,
cancelable: true,
touches: [new Touch({
identifier: 0,
- target: testElement,
+ target: element,
clientX: 100,
clientY: 100
})]
});
- testElement.dispatchEvent(touchstartEvent);
+ element.dispatchEvent(event);
+
+ expect(handler).not.toHaveBeenCalled();
- expect(touchstartHandler).not.toHaveBeenCalled();
+ instance.unlock();
+ document.body.removeChild(element);
});
- it('should block touchmove events when locked', () => {
- const touchmoveHandler = vi.fn();
- testElement.addEventListener('touchmove', touchmoveHandler);
+ it('should block touchmove events when locked globally', () => {
+ const handler = vi.fn();
+ const element = document.createElement('div');
+ element.addEventListener('touchmove', handler);
+ document.body.appendChild(element);
- blokr.lock();
+ const instance = blokr();
+ instance.lock();
- // Simulate touchmove event
- const touchmoveEvent = new TouchEvent('touchmove', {
+ const event = new TouchEvent('touchmove', {
bubbles: true,
cancelable: true,
touches: [new Touch({
identifier: 0,
- target: testElement,
+ target: element,
clientX: 110,
clientY: 110
})]
});
- testElement.dispatchEvent(touchmoveEvent);
+ element.dispatchEvent(event);
- expect(touchmoveHandler).not.toHaveBeenCalled();
+ expect(handler).not.toHaveBeenCalled();
+
+ instance.unlock();
+ document.body.removeChild(element);
+ });
+ });
+
+ describe('Target-specific Event Blocking (scope: inside)', () => {
+ it('should block events inside target with default scope', () => {
+ const container = document.createElement('div');
+ const button = document.createElement('button');
+ const handler = vi.fn();
+ button.addEventListener('mousedown', handler);
+ container.appendChild(button);
+ document.body.appendChild(container);
+
+ const instance = blokr(container);
+ instance.lock();
+
+ const event = new MouseEvent('mousedown', {
+ bubbles: true,
+ cancelable: true
+ });
+
+ button.dispatchEvent(event);
+
+ expect(handler).not.toHaveBeenCalled();
+
+ instance.unlock();
+ document.body.removeChild(container);
+ });
+
+ it('should allow events inside target when unlocked', () => {
+ const container = document.createElement('div');
+ const button = document.createElement('button');
+ const handler = vi.fn();
+ button.addEventListener('mousedown', handler);
+ container.appendChild(button);
+ document.body.appendChild(container);
+
+ const instance = blokr(container);
+ instance.lock();
+ instance.unlock();
+
+ const event = new MouseEvent('mousedown', {
+ bubbles: true,
+ cancelable: true
+ });
+
+ button.dispatchEvent(event);
+
+ expect(handler).toHaveBeenCalled();
+
+ document.body.removeChild(container);
+ });
+
+ it('should block events on child elements of target', () => {
+ const container = document.createElement('div');
+ const child = document.createElement('span');
+ const handler = vi.fn();
+ child.addEventListener('mousedown', handler);
+ container.appendChild(child);
+ document.body.appendChild(container);
+
+ const instance = blokr(container);
+ instance.lock({ scope: 'inside' });
+
+ const event = new MouseEvent('mousedown', {
+ bubbles: true,
+ cancelable: true
+ });
+
+ child.dispatchEvent(event);
+
+ expect(handler).not.toHaveBeenCalled();
+
+ instance.unlock();
+ document.body.removeChild(container);
+ });
+ });
+
+ describe('Target-specific Event Blocking (scope: outside)', () => {
+ it('should block events outside target', () => {
+ const container = document.createElement('div');
+ const outside = document.createElement('button');
+ const handler = vi.fn();
+ outside.addEventListener('mousedown', handler);
+ document.body.appendChild(container);
+ document.body.appendChild(outside);
+
+ const instance = blokr(container);
+ instance.lock({ scope: 'outside' });
+
+ const event = new MouseEvent('mousedown', {
+ bubbles: true,
+ cancelable: true
+ });
+
+ outside.dispatchEvent(event);
+
+ expect(handler).not.toHaveBeenCalled();
+
+ instance.unlock();
+ document.body.removeChild(container);
+ document.body.removeChild(outside);
+ });
+
+ it('should allow events inside target with scope outside', () => {
+ const container = document.createElement('div');
+ const button = document.createElement('button');
+ const handler = vi.fn();
+ button.addEventListener('mousedown', handler);
+ container.appendChild(button);
+ document.body.appendChild(container);
+
+ const instance = blokr(container);
+ instance.lock({ scope: 'outside' });
+
+ const event = new MouseEvent('mousedown', {
+ bubbles: true,
+ cancelable: true
+ });
+
+ button.dispatchEvent(event);
+
+ expect(handler).toHaveBeenCalled();
+
+ instance.unlock();
+ document.body.removeChild(container);
+ });
+ });
+
+ describe('Target-specific Event Blocking (scope: self)', () => {
+ it('should block events on target itself', () => {
+ const target = document.createElement('button');
+ const handler = vi.fn();
+ target.addEventListener('mousedown', handler);
+ document.body.appendChild(target);
+
+ const instance = blokr(target);
+ instance.lock({ scope: 'self' });
+
+ const event = new MouseEvent('mousedown', {
+ bubbles: true,
+ cancelable: true
+ });
+
+ target.dispatchEvent(event);
+
+ expect(handler).not.toHaveBeenCalled();
+
+ instance.unlock();
+ document.body.removeChild(target);
+ });
+
+ it('should allow events on child elements with scope self', () => {
+ const parent = document.createElement('div');
+ const child = document.createElement('button');
+ const handler = vi.fn();
+ child.addEventListener('mousedown', handler);
+ parent.appendChild(child);
+ document.body.appendChild(parent);
+
+ const instance = blokr(parent);
+ instance.lock({ scope: 'self' });
+
+ const event = new MouseEvent('mousedown', {
+ bubbles: true,
+ cancelable: true
+ });
+
+ child.dispatchEvent(event);
+
+ expect(handler).toHaveBeenCalled();
+
+ instance.unlock();
+ document.body.removeChild(parent);
+ });
+
+ it('should allow events outside target with scope self', () => {
+ const target = document.createElement('div');
+ const outside = document.createElement('button');
+ const handler = vi.fn();
+ outside.addEventListener('mousedown', handler);
+ document.body.appendChild(target);
+ document.body.appendChild(outside);
+
+ const instance = blokr(target);
+ instance.lock({ scope: 'self' });
+
+ const event = new MouseEvent('mousedown', {
+ bubbles: true,
+ cancelable: true
+ });
+
+ outside.dispatchEvent(event);
+
+ expect(handler).toHaveBeenCalled();
+
+ instance.unlock();
+ document.body.removeChild(target);
+ document.body.removeChild(outside);
});
});
@@ -190,74 +380,200 @@ describe('Event Blocking Integration', () => {
it('should stop immediate propagation of blocked events', () => {
const handler1 = vi.fn();
const handler2 = vi.fn();
+ const element = document.createElement('button');
+ element.addEventListener('mousedown', handler1);
+ element.addEventListener('mousedown', handler2);
+ document.body.appendChild(element);
- testElement.addEventListener('mousedown', handler1);
- testElement.addEventListener('mousedown', handler2);
-
- blokr.lock();
+ const instance = blokr();
+ instance.lock();
- const mousedownEvent = new MouseEvent('mousedown', {
+ const event = new MouseEvent('mousedown', {
bubbles: true,
cancelable: true
});
- testElement.dispatchEvent(mousedownEvent);
+ element.dispatchEvent(event);
expect(handler1).not.toHaveBeenCalled();
expect(handler2).not.toHaveBeenCalled();
+
+ instance.unlock();
+ document.body.removeChild(element);
});
it('should prevent default behavior of blocked events', () => {
- blokr.lock();
+ const element = document.createElement('a');
+ element.href = '#';
+ document.body.appendChild(element);
- const mousedownEvent = new MouseEvent('mousedown', {
+ const instance = blokr();
+ instance.lock();
+
+ const event = new MouseEvent('mousedown', {
bubbles: true,
cancelable: true
});
- const preventDefaultSpy = vi.spyOn(mousedownEvent, 'preventDefault');
-
- testElement.dispatchEvent(mousedownEvent);
+ const preventDefaultSpy = vi.spyOn(event, 'preventDefault');
+ element.dispatchEvent(event);
expect(preventDefaultSpy).toHaveBeenCalled();
+
+ instance.unlock();
+ document.body.removeChild(element);
+ });
+ });
+
+ describe('Multiple Independent Locks', () => {
+ it('should support independent locks on different targets', () => {
+ const container1 = document.createElement('div');
+ const button1 = document.createElement('button');
+ const handler1 = vi.fn();
+ button1.addEventListener('mousedown', handler1);
+ container1.appendChild(button1);
+ document.body.appendChild(container1);
+
+ const container2 = document.createElement('div');
+ const button2 = document.createElement('button');
+ const handler2 = vi.fn();
+ button2.addEventListener('mousedown', handler2);
+ container2.appendChild(button2);
+ document.body.appendChild(container2);
+
+ const instance1 = blokr(container1);
+ const instance2 = blokr(container2);
+
+ // Lock only container1
+ instance1.lock();
+
+ const event = new MouseEvent('mousedown', {
+ bubbles: true,
+ cancelable: true
+ });
+
+ button1.dispatchEvent(event);
+ button2.dispatchEvent(event);
+
+ expect(handler1).not.toHaveBeenCalled();
+ expect(handler2).toHaveBeenCalled();
+
+ // Lock container2
+ instance2.lock();
+ handler2.mockClear();
+
+ button1.dispatchEvent(event);
+ button2.dispatchEvent(event);
+
+ expect(handler1).not.toHaveBeenCalled();
+ expect(handler2).not.toHaveBeenCalled();
+
+ instance1.unlock();
+ instance2.unlock();
+ document.body.removeChild(container1);
+ document.body.removeChild(container2);
+ });
+
+ it('should respect each instance scope independently', () => {
+ const container1 = document.createElement('div');
+ const button1 = document.createElement('button');
+ const handler1 = vi.fn();
+ button1.addEventListener('mousedown', handler1);
+ container1.appendChild(button1);
+ document.body.appendChild(container1);
+
+ const container2 = document.createElement('div');
+ const button2 = document.createElement('button');
+ const handler2 = vi.fn();
+ button2.addEventListener('mousedown', handler2);
+ container2.appendChild(button2);
+ document.body.appendChild(container2);
+
+ const outside = document.createElement('button');
+ const outsideHandler = vi.fn();
+ outside.addEventListener('mousedown', outsideHandler);
+ document.body.appendChild(outside);
+
+ const instance1 = blokr(container1);
+ const instance2 = blokr(container2);
+
+ // Container1: scope inside (blocks events inside container1)
+ instance1.lock({ scope: 'inside' });
+ // Container2: scope outside (blocks events outside container2)
+ instance2.lock({ scope: 'outside' });
+
+ const event = new MouseEvent('mousedown', {
+ bubbles: true,
+ cancelable: true
+ });
+
+ // Event inside container1 - should be blocked by instance1
+ button1.dispatchEvent(event);
+ expect(handler1).not.toHaveBeenCalled();
+
+ // Event inside container2 - should not be blocked (outside scope)
+ button2.dispatchEvent(event);
+ expect(handler2).toHaveBeenCalled();
+
+ // Event outside both containers - should be blocked by instance2
+ outside.dispatchEvent(event);
+ expect(outsideHandler).not.toHaveBeenCalled();
+
+ instance1.unlock();
+ instance2.unlock();
+ document.body.removeChild(container1);
+ document.body.removeChild(container2);
+ document.body.removeChild(outside);
});
});
describe('Lock/Unlock State Changes', () => {
it('should allow events after unlocking', () => {
- const mousedownHandler = vi.fn();
- testElement.addEventListener('mousedown', mousedownHandler);
+ const handler = vi.fn();
+ const element = document.createElement('button');
+ element.addEventListener('mousedown', handler);
+ document.body.appendChild(element);
- blokr.lock();
- blokr.unlock();
+ const instance = blokr();
+ instance.lock();
+ instance.unlock();
- const mousedownEvent = new MouseEvent('mousedown', {
+ const event = new MouseEvent('mousedown', {
bubbles: true,
cancelable: true
});
- testElement.dispatchEvent(mousedownEvent);
+ element.dispatchEvent(event);
+
+ expect(handler).toHaveBeenCalled();
- expect(mousedownHandler).toHaveBeenCalled();
+ document.body.removeChild(element);
});
it('should handle rapid lock/unlock cycles', () => {
- const mousedownHandler = vi.fn();
- testElement.addEventListener('mousedown', mousedownHandler);
+ const handler = vi.fn();
+ const element = document.createElement('button');
+ element.addEventListener('mousedown', handler);
+ document.body.appendChild(element);
- blokr.lock();
- blokr.unlock();
- blokr.lock();
- blokr.unlock();
+ const instance = blokr();
- const mousedownEvent = new MouseEvent('mousedown', {
+ const event = new MouseEvent('mousedown', {
bubbles: true,
cancelable: true
});
- testElement.dispatchEvent(mousedownEvent);
+ // Rapid cycle
+ instance.lock();
+ instance.unlock();
+ instance.lock();
+ instance.unlock();
+
+ element.dispatchEvent(event);
+
+ expect(handler).toHaveBeenCalled();
- expect(mousedownHandler).toHaveBeenCalled();
+ document.body.removeChild(element);
});
});
-});
\ No newline at end of file
+});
diff --git a/tsconfig.json b/tsconfig.json
index 8091116..38fa989 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -105,5 +105,5 @@
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
},
- "include": ["src"]
+ "include": ["src", "tests"]
}
diff --git a/vite.config.ts b/vite.config.ts
index 0e3517d..a43ea70 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -8,7 +8,7 @@ export default defineConfig({
minify: false,
lib: {
entry: 'src/index.ts',
- name: 'Blokr',
+ name: 'blokr',
formats: ['es', 'umd'],
fileName: format => format === 'es' ? 'index.js' : 'blokr.js',
},
@@ -16,15 +16,17 @@ export default defineConfig({
output: {
exports: 'default',
plugins: [
- terser(),
license({
banner: '@license\nCopyright 2025 KNOWLEDGECODE\nSPDX-License-Identifier: MIT'
- })
+ }),
+ terser()
]
}
}
},
plugins: [
- dts()
+ dts({
+ include: ['src/**/*']
+ })
]
});