
Security News
PodRocket Podcast: Inside the Recent npm Supply Chain Attacks
Socket CEO Feross Aboukhadijeh discusses the recent npm supply chain attacks on PodRocket, covering novel attack vectors and how developers can protect themselves.
@cosider.construction/v-set
Advanced tools
A Vue 3 directive that simplifies bidirectional data binding between parent and child components, with advanced transformations and modular architecture.
v2.0.5
Revolutionary two-way data binding with advanced transformations, modular architecture, and professional development tools for Vue 3.
📖 [View Full HTML Documentation](https://www.npmjs.com/package/@cosider.construction/v-set/ README.html) - Complete interactive documentation with examples and styling
Vue 3 Modular Production Ready
See the dramatic difference in code simplicity:
Feature | v-set | Standard Vue |
---|---|---|
Simple binding | v-set.name | @input="$emit('update:name', $event.target.value)" |
Nested objects | v-set.user.name | @update:name="$emit('update:user', {...user, name: $event})" |
Array updates | v-set.users[i]:user="{i}" | @update:user="$emit('update:users', [...users.slice(0, i), { ...users[i], ...$event }, ...users.slice(i + 1)])" |
Transformations | v-set.price$*tax$tofixed(2) | Manual calculation in handler |
Custom events | v-set.name@change€modified | Multiple separate event handlers |
Math operations | v-set.count$+1 | @click="$emit('update:count', count + 1)" |
Features | ||
Intuitive Syntax | Modular Architecture | Advanced Debugging |
Clean, readable syntax that makes complex data binding simple and maintainable. | Specialized modules for maximum maintainability and tree-shaking optimization. | Professional logging system with call tracing, timestamps, and development tools. |
Rich Transformations | Smart Defaults | Production Ready |
20+ built-in modifiers plus custom modifier support for any transformation need. | Automatic handling of native elements and Vue components with intelligent defaults. | Separate dev/prod modes, performance optimized, and battle-tested architecture. |
Install Package | Import & Setup | Configure & Mount |
---|---|---|
npm install @cosider.construction/v-set | import { vSetInstaller } from '@cosider.construction/v-set' | vSetInstaller.dev(app, modifiers) |
import { createApp } from 'vue'
import { vSetInstaller } from '@cosider.construction/v-set'
const app = createApp({})
vSetInstaller.dev(app, {})
app.mount('#app')
import { createApp } from 'vue'
import { vSetInstaller } from '@cosider.construction/v-set'
const app = createApp({})
vSetInstaller.prod(app, {})
app.mount('#app')
import { createApp } from 'vue'
import { vSetInstaller } from '@cosider.construction/v-set'
const app = createApp({})
vSetInstaller.custom(app, {
directiveName: 'set',
enableCallTracing: true,
enableDebugTracing: false,
customModifiers: {}
})
app.mount('#app')
import { vSet, vSetEmit } from '@cosider.construction/v-set'
app.directive('set', vSet)
app.mixin(vSetEmit)
app.config.globalProperties.µ = {}
app.mount('#app')
code Syntax Overview
v-set.{whatToUpdate} .@{whenToUpdate} .€{whatToEmit} .${howToUpdate} ="{UpdateWith}"
Symbol | Purpose | Example | Description |
---|---|---|---|
. | What to update | .name , .user.email , .items[i] | Property path to update |
@ | When to update | @click , @change , @input | Event to listen for |
€ | What to emit | €modified , €changed | Custom emit event |
$ | How to transform | $upper , $trim , $*tax | Modifiers and operations |
= | Update with | ="{index}" , ="newValue" | Additional values |
timeline How We Got v-set Syntax
Understanding the evolution from standard Vue event handling to v-set directive syntax:
Let's trace the evolution step by step:
Step | Comment | Transformation |
---|---|---|
1 | Starting with standard Vue | @update:name="$emit('update:name', $event)" |
2 | Since the default event to emit is 'update', we can remove it | @update:name="$emit('name', $event)" |
3 | Since the default event to listen is 'update' too, we can remove it | @name="$emit('name', $event)" |
4 | Since we deal with the listen event to emit it, we can remove it too | @name="$emit('name')" |
5 | Since we got the same prop name and bind name, we can omit it too | @name |
6 | Since it's a directive, we end by | v-set.name |
When the binding name is not like the property name:
Step | Comment | Transformation |
---|---|---|
1 | Starting with | @update:nom="$emit('update:name', $event)" |
2 | Evolution - remove 'update' from emit | @update:nom="$emit('name', $event)" |
3 | Remove 'update' from listen event | @nom="$emit('name', $event)" |
4 | Remove $event parameter | @nom="$emit('name')" |
5 | Since it's a directive we end by | v-set.name:nom |
Why it's flipped? Because we are aiming to update the 'name' with the value from 'nom' |
When the listen event is not the default 'update':
Step | Comment | Transformation |
---|---|---|
1 | Starting with | @select:nom="$emit('update:name', $event)" |
2 | Evolution - remove 'update' from emit | @select:nom="$emit('name', $event)" |
3 | Remove $event parameter | @select:nom="$emit('name')" |
4 | Since it's a directive we end by | v-set.name.@select:nom |
5 | Or if you want (alternative syntax) | v-set.name:nom.@select |
It's like we say: set name to the value of nom when on select |
When the emitted event is not the default 'update':
Step | Comment | Transformation |
---|---|---|
1 | Starting with | @update:name="$emit('modified:name', $event)" |
2 | Evolution - remove 'update' from listen event | @name="$emit('modified:name', $event)" |
3 | Remove $event parameter | @name="$emit('modified:name')" |
4 | Remove ':name' from emit (implied) | @name="$emit('modified')" |
5 | We shortcut $emit by € | @name="€modified" |
6 | Since it's a directive we end by | v-set.name.€modified |
Step | Standard Vue | v-set Evolution |
---|---|---|
1. Basic | @update:name="$emit('update:name', $event)" | v-set.name |
2. Different prop | @update:nom="$emit('update:name', $event)" | v-set.name:nom |
3. Custom listen | @select:nom="$emit('update:name', $event)" | v-set.name:nom.@select |
4. Custom emit | @update:name="$emit('modified:name', $event)" | v-set.name.€modified |
description Complete Syntax Reference
v-set.{whatToUpdate} .@{whenToUpdate} .€{whatToEmit} .${howToUpdate} ="{UpdateWith}"
Component | Description | Example |
---|---|---|
whatToUpdate | The prop or data path to update (e.g., name, user.name, users[0].name) | .name , .user.name , .users[0].name |
whenToUpdate | The event to listen for. Defaults to update:propName for components or element-specific events | @input , @click , @update:name |
whatToEmit | The event to emit. Defaults to update:propName | €update:name , €modified |
howToUpdate | Transformation to apply to the listened or emitted value | $upper , $+5 , $trim |
UpdateWith | The value or expression used like extra function parameters | ="{i}" , ="newValue" |
Understanding the relationship between Props and Binds:
<child :nom="name" @update:nom="$emit('update:name', $event)" />
nom is called Bind and name is prop. Why name is called prop? Because it's a prop in the component that uses child and we are binding it to child.
The whatToUpdate follows the pattern: Prop.P1....Pn:Bind
or Prop.P1....Pn
In the simplest case: Prop:Bind
or just Prop
when Prop === Bind
Example | Explanation | Emitted Update |
---|---|---|
<input :value="form.user.name" v-set.form.user.name /> | Update nested object property | Send update form with new value of name |
<input :value="names[i]" v-set.names[i] /> | Update array element | Send update names with new value of names[i] |
This part should start with @
to keep the Vue world. We listen to 'event:Bind'.
Context | Default Behavior | Example |
---|---|---|
Components | Listen to update:bind by default | <comp :nom="name" v-set.name:nom /> |
Native Elements | Listen to default events (input, change, select) | <input :value="name" v-set.name /> |
Path Transformation | For native elements: e.target.value | @event:Bind.pathToTheValue |
The event name we want to emit. By default update:prop
where prop is the first part of whatToUpdate.
Format: event:Prop
where event can be update, select, or whatever you like.
Value transformation and wrapping process:
Why do we need wrapping? When updating nested properties or array elements, we can't just emit the new value directly. We need to preserve the immutability principle and create new objects/arrays with the updated values. This is where wrapping comes in - it automatically handles the complex object/array reconstruction for you.
value = vListen
↓
value = path(value)
↓
value = wrapper(value)
↓
emit(value)
Pattern | Listened Value | Emitted Value | Why Wrapping is Needed |
---|---|---|---|
v-set.user.name | $value | {...user, name: $value} | Preserve other user properties while updating name |
v-set.users[i].name="{i}" | $event | [...users.slice(0,i), {...users[i], name: $event}, ...users.slice(i+1)] | Maintain array immutability while updating specific element |
In standard Vue, modifiers are simple flags like v-model.trim
or @click.prevent
. They provide basic transformations but are limited in scope.
The $ symbol is used to detect the transformation part of the directive. As Vue uses modifiers separated by .
, we use something similar, but it's not modifiers - it's mutators.
Why "Mutators"?
modifier + operator → mod_ifier + oper_ator → modator → motator → mutator 😊
Type | Purpose | Syntax | Example |
---|---|---|---|
Modifiers | Function-based transformations | .functionName | .upper , .max(x,y,2) , .random(()) |
Operators | Mathematical/Object operations | .operator var , .var operator | .+1 , .v* |
Mutators | Combined modifiers + operators | .operator modifier | .+max(7) |
value = vListen
↓
value = path(value)
↓
value = mutators(value) 🔄 m1→m2→m3...
↓
value = wrapper(value)
↓
value = mutators(value) 🔄 m4→m5→m6...
↓
emit(value)
Understanding how multiple mutators are chained together:
mutators(value) = mutator1.mutator2.mutator3...mutatorN(value)
Example: $trim.upper.max(25)
value = trim(value) // mutator1
value = upper(value) // mutator2
value = max(value, 25) // mutator3
Understanding the transformation from verbose processing to mathematical notation:
Step | Verbose Processing | Mathematical Notation |
---|---|---|
$ | value = path(value) | v0 = $ |
↓ | value = mutator1_1(value) ... value = mutator1_n(value) | v1 = mutator1_1(v0) = v0.m1_1 ... v1 = $.m1_1...m1_n |
↓ | value = wrapper(value) | v2 = wrapper(v1) = w(v1) = $$(v0.m1_1...m1_n) = $.m1_1...m1_n.$$ |
↓ | value = mutator2_1(value) ... value = mutator2_n(value) | v3 = mutator2(v2) = v2.mutator2 = $.m1_1...m1_n.$$.m2_1...m2_n |
Final syntax with $ prefix to detect transformations | $.m1_1...m1_n.$$.m2_1...m2_n $.µBeforWarping.$$.µAfterWarping |
you van also use µ/µµ instead of $/$$ to mark begin of mutators
Mathematical and logical operations on values:
Pattern | Syntax | Result |
---|---|---|
token operator | .+x | value = value + x |
operator token | .x% | value = x / value (% used as /) |
token only | .x | value = x |
special cases | .++ | value = value + 1 |
special cases | .! | value = ! value |
object proprety | .proprety: | {...value,proprety:v} v is taking from updateWithPart/compoenent |
object proprety | .~obj | {...value,...obj} obj is found in the updateWithPart/component |
Built-in function-based transformations for common operations:
Modifier | Syntax | Result | Example |
---|---|---|---|
trim | $trim | Remove whitespace | " hello " → "hello" |
upper | $upper | Convert to uppercase | "hello" → "HELLO" |
lower | $lower | Convert to lowercase | "HELLO" → "hello" |
toNumber | $toNumber | Convert to number | "123" → 123 |
toString | $toString | Convert to string | 123 → "123" |
reverse | $reverse | Reverse string/array | "hello" → "olleh" |
Note: You can also access modifiers in component methods using this..modifierName
(e.g., this.µ.trim(value)
).
Important Feature: unLike Vue modifiers, you can use double parentheses (())to prevent automatic value injection. This is crucial when you want the modifier to work without the current value being passed as a parameter.
Syntax | Behavior | Example |
---|---|---|
modifier | Auto-inject current value:$v | trim → trim($v) |
modifier | Auto-inject current value:$v | maxmax(i,j) → max($v,i,j) |
modifier(()) | No injection | getTimestamp(()) → getTimestamp() |
modifier | noinject | max((i,j)) → max(i,j) |
You can extend the system with your own transformations. There is a list of defined modifiers, but you can add your own globally or locally by calling methods of your component or using arrow functions defined in the directive's UpdateWith.
Scope | How to Define | Example |
---|---|---|
Global | Add to global modifiers registry | app.config.globalProperties.µ.myModifier = (v) => v.toUpperCase() |
Component | Define as component method | methods: { myModifier(value) { return value.trim() } } |
Local | Arrow function in UpdateWith | ="myModifier: (v) => v.toLowerCase()" |
export const customModifiers = {
// Currency formatting
currency: (value) => '$'+Number(value).toFixed(2),
euro: (value) => '€'+Number(value).toFixed(2),
// Text transformations
percentage: (value) => (Number(value) \* 100).toFixed(1)+'%',
slugify: (str) => str.toLowerCase().replace(/\\s+/g, '-'),
capitalize: (str) => str.charAt(0).toUpperCase() + str.slice(1),
// Phone formatting
phone: (str) => str.replace(/(\\d{3})(\\d{3})(\\d{4})/, '($1) $2-$3'),
// Date formatting
dateShort: (date) => new Date(date).toLocaleDateString(),
timestamp: () => Date.now()
};
import { createApp } from 'vue'
import { vSetInstaller } from '@cosider.construction/v-set'
import { customModifiers } from './modifiers/custom-modifiers.js'
const app = createApp({})
vSetInstaller.dev(app, customModifiers)
app.mount('#app')
💡 Benefits:
Why do we need UpdateWith? Vue has limitations that v-set solves:
Use Case | Example | Explanation |
---|---|---|
v-for context | <comp v-for="(user,i) in users" v-set.users[i]="i" /> | Pass loop variable 'i' to directive since Vue doesn't provide it |
Arrow functions | <button v-set.cpt@click.$f(()) ="f:()=>{cpt+1}" /> | Define custom transformation without value injection |
Value injection | <button v-set.cpt@click.µ.f ="f:(v)=>{cpt+1}" /> | Arrow function with value parameter (though not used in this example) |
Multiple variables | v-set.data.+x.-y="{x: valueX, y: valueY}" | Pass object with multiple runtime values |
When v-set looks for a function or modifier, it checks in this order:
integration_instructions Usage Examples
<input :value="name" v-set.name />
Direct value assignment without wrapping:
Step | Process | Value |
---|---|---|
↓ | Listen to input event | <input v-set.name /> |
↓ | Extract value from path | event.target.value |
↓ | No mutators applied | value |
↓ | No wrapper needed | value (direct assignment) |
↓ | Emit final value | $emit('update:name', value) |
<input :value="name" v-set.name.$.upper.trim />
Value transformation before wrapping:
Step | Process | Value |
---|---|---|
↓ | Listen to input event | <input v-set.name.$.upper.trim /> |
↓ | Extract value from path | event.target.value |
↓ | Apply mutators chain | trim(upper(value)) |
↓ | No wrapper needed | mutatedValue (direct assignment) |
↓ | Emit final value | $emit('update:name', mutatedValue) |
HT: <input :value="ht" v-set.ht />
TTC: <input :value="ht \* tax" v-set.ht.$.%tax />
Value transformation with mathematical operators:
Step | Process | Value |
---|---|---|
↓ | Listen to input event | <input v-set.ht.$.%tax /> |
↓ | Extract value from path | event.target.value |
↓ | Apply mathematical operator | value / tax (% used as division) |
↓ | No wrapper needed | calculatedValue (direct assignment) |
↓ | Emit final value | $emit('update:ht', calculatedValue) |
<button v-set.count.$.count.+1\>+1</button>
<button v-set.count.$.count.-1\>-1</button>
<button v-set.count.$.count.\*2\>×2</button>
Simple mathematical operations on click events:
Step | Process | Value |
---|---|---|
↓ | Listen to click event | <button v-set.count.$.count.+1 /> |
↓ | No path extraction needed | clickEvent (not used) |
↓ | init value with count | <button v-set.count.$.count.+1 /> |
↓ | Apply mathematical operator | <button v-set.count.$.count.+1 /> |
↓ | No wrapper needed | incrementedValue (direct assignment) |
↓ | Emit final value | $emit('update:count', count+1) |
<button v-set.date$.now(())>Set Current Time
Using double parentheses to prevent automatic value injection:
Step | Process | Value |
---|---|---|
↓ | Listen to click event | <button v-set.date.$.now(()) /> |
↓ | No path extraction needed | clickEvent (not used) |
↓ | Call modifier without value injection | now(()) (no parameters passed) |
↓ | No wrapper needed | date (direct assignment) |
↓ | Emit final value | $emit('update:date', now()) |
<User v-for="(user, i) in users" :user="users[i]" v-set.users[i]:user="i" />
Array wrapping with immutable slice operations:
Step | Process | Value |
---|---|---|
↓ | Listen to update event | <User v-set.users[i]:user="{i}" /> |
↓ | Extract value from event | $event (user object) |
↓ | No mutators applied | value |
↓ | Apply array wrapper | [...users.slice(0,i), value, ...users.slice(i+1)] |
↓ | Emit wrapped array | $emit('update:users', wrappedArray) |
<button v-set.todos$push(newTodo())>Add Todo</button>
Array push operation with immutable array creation:
Step | Process | Value |
---|---|---|
↓ | Listen to click event | <button v-set.todos$push(newTodo()) /> |
↓ | Get UpdateWith value | newTodo |
↓ | Apply push mutator | [...todos, newTodo] |
↓ | No wrapper needed | newArray (direct assignment) |
↓ | Emit final array | $emit('update:todos', newArray) |
<input :value="user.name" v-set.user.name />
Object wrapping to preserve immutability:
Step | Process | Value |
---|---|---|
↓ | Listen to input event | <input v-set.user.name /> |
↓ | Extract value from path | event.target.value |
↓ | No mutators applied | value |
↓ | Apply object wrapper | {...user, name: value} |
↓ | Emit wrapped object | $emit('update:user', wrappedUser) |
<input :value="form.user.profile.bio" v-set.form.user.profile.bio />
Deep object wrapping to preserve immutability:
Step | Process | Value |
---|---|---|
↓ | Listen to input event | <input v-set.form.user.profile.bio /> |
↓ | Extract value from path | event.target.value |
↓ | No mutators applied | value |
↓ | Apply deep object wrapper | {...form, user: {...form.user, profile: {...form.user.profile, bio: value}}} |
↓ | Emit wrapped object | $emit('update:form', wrappedForm) |
<Child v-set.name@change />
Listening to custom event instead of default update:
Step | Process | Value |
---|---|---|
↓ | Listen to change event | <Child v-set.name@change /> |
↓ | Extract value from event | $event |
↓ | No mutators applied | value |
↓ | No wrapper needed | value (direct assignment) |
↓ | Emit default event | $emit('update:name', value) |
<Child v-set.name€modified />
Emitting custom event instead of default update:
Step | Process | Value |
---|---|---|
↓ | Listen to default event | <Child v-set.name€modified /> |
↓ | Extract value from event | $event |
↓ | No mutators applied | value |
↓ | No wrapper needed | value (direct assignment) |
↓ | Emit custom event | $emit('modified:name', value) |
<Child v-set.name@change€modified />
Custom listen and emit events:
Step | Process | Value |
---|---|---|
↓ | Listen to change event | <Child v-set.name@change€modified /> |
↓ | Extract value from event | $event |
↓ | No mutators applied | value |
↓ | No wrapper needed | value (direct assignment) |
↓ | Emit custom event | $emit('modified:name', value) |
tune Built-in Modifiers
Modifier | Description |
---|---|
upper | Convert to UPPERCASE |
Upper | Capitalize first letter |
lower | Convert to lowercase |
trim | Remove whitespace |
replace(old,new) | Replace text |
replaceAll(old,new) | Replace all occurrences |
split(',') | Split into array |
Modifier | Description |
---|---|
parseint | Convert to integer |
parsefloat | Convert to float |
round | Round to nearest integer |
ceil | Round up |
floor | Round down |
abs | Absolute value |
tofixed(2) | Format to 2 decimals |
min | Get minimum value |
max | Get maximum value |
Modifier | Description |
---|---|
push(item) | Add item to end |
pop | Remove last item |
shift | Remove first item |
unshift(item) | Add item to beginning |
join('-') | Join with separator |
Operation | Description |
---|---|
$+5 | Add 5 to value |
$-1 | Subtract 1 from value |
$*tax | Multiply by tax variable |
$%tax | Divide by tax variable (% used as /) |
$++ | Increment by 1 |
$-- | Decrement by 1 |
bug_report Development & Debugging
// Enable call tracing (shows function calls with timestamps) this.$vSetInstaller.configureLogging({ enableCalls: true, enableTrace: true }); // Or use vLog directly this.$vLog.enableCalls().enableTrace();
🔧 [14:23:45.123] vSet.mounted 🔧 [14:23:45.124] vSetConf.get 🔧 [14:23:45.125] vSetConf.getDomInfo 🔧 [14:23:45.126] vSetDom.listen
Join thousands of developers who have already upgraded their Vue applications with v-set.
NPM Package 📖 HTML Documentation View Examples API Documentation
Made with ❤️ by Cosider Construction Transform your Vue development experience with the power of v-set!
FAQs
A Vue 3 directive that simplifies bidirectional data binding between parent and child components, with advanced transformations and modular architecture.
The npm package @cosider.construction/v-set receives a total of 50 weekly downloads. As such, @cosider.construction/v-set popularity was classified as not popular.
We found that @cosider.construction/v-set demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Socket CEO Feross Aboukhadijeh discusses the recent npm supply chain attacks on PodRocket, covering novel attack vectors and how developers can protect themselves.
Security News
Maintainers back GitHub’s npm security overhaul but raise concerns about CI/CD workflows, enterprise support, and token management.
Product
Socket Firewall is a free tool that blocks malicious packages at install time, giving developers proactive protection against rising supply chain attacks.