Last active
October 2, 2023 20:22
-
-
Save jrmoynihan/57d78df7b53159ca98403d0220993f48 to your computer and use it in GitHub Desktop.
A helper function for setting Svelte 5 $state() runes on objects
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script> | |
import { gettable, settable, runed } from './runes.js' | |
const createFruit = (obj) => { | |
// An optional object of default values if the provided prop is nullish (try commenting each out) | |
const defaults = { | |
color: 'purple', | |
consumed: true, | |
count: 0 | |
} | |
// An optional object of options for Object.defineProperty(). You specify the options on a per-property basis. | |
const options = { | |
// consumed: { | |
// enumerable: false, | |
// }, | |
// varieties: { | |
// enumerable: false | |
// } | |
} | |
// An optional array of keys, indicating which properties should be readonly | |
const readonly_props = ['varieties'] | |
/* This function iterates through all provided keys on the object and establishes | |
writable or read-only properties (i.e. getters with or without setters). If you | |
provide an optional array of keys, it will treat those as readonly properties (no setters) */ | |
const runified = runed(obj, defaults, readonly_props, options) | |
// Try tweaking the defaults, options, or readonly_props and see how this changes! | |
console.log(runified) | |
/* And of course, we can still place methods on it before we return: */ | |
runified.log = () => console.log(JSON.stringify(runified)) | |
runified.increment = () => runified.count++ | |
runified.decrement = () => runified.count-- | |
return runified | |
} | |
const addFruit = (name) => { | |
const new_fruit = createFruit({name}) | |
fruits = [...fruits, new_fruit] | |
fruit = '' | |
} | |
const removeFruit = (name) => { | |
fruits = fruits.filter(f => f.name !== name) | |
} | |
let data = [ | |
{name: 'Apple', color: 'red', consumed: true}, | |
{name: 'Orange', color: 'orange', count: 2}, | |
{name: 'Pear', color: 'green', varieties: ['granny smith', 'macintosh', 'red delicious']}, | |
{name: 'Grape', count: 4} | |
] | |
let fruits = $state(data.map(f => createFruit(f))) | |
let fruit = $state('') | |
</script> | |
<input type="text" bind:value={fruit} /> | |
<button on:click={()=>addFruit(fruit)}>Add Fruit</button> | |
<hr/> | |
<section> | |
{#each fruits as fruit (fruit)} | |
<p> | |
<span style:background={fruit.color}>{fruit.name} (count: {fruit.count})</span> | |
<input type="text" bind:value={fruit.name} /> | |
<input type="text" bind:value={fruit.color} /> | |
<input type="checkbox" bind:checked={fruit.consumed} /> | |
<button on:click={()=>fruit.log()}>Log</button> | |
<button on:click={()=>fruit.increment()}>Increment</button> | |
<button on:click={()=>fruit.decrement()}>Decrement</button> | |
<button on:click={()=>removeFruit(fruit.name)}>Remove</button> | |
{#each fruit.varieties as variety} | |
<input bind:value={variety}/> | |
{/each} | |
</p> | |
{/each} | |
</section> | |
<style> | |
p { | |
display: flex; | |
flex: 1; | |
flex-wrap: wrap; | |
/* grid-template-columns: repeat(7, minmax(0, max-content)); */ | |
gap: 0.2rem; | |
} | |
span { | |
padding: 0.5rem 1rem; | |
} | |
</style> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export const settable = (key, initial_value = undefined, obj = {}, options = { enumerable: true }) => { | |
let value = $state(initial_value); | |
Object.defineProperty(obj, key, { | |
...options, | |
get(){return value}, | |
set(new_val){ | |
value = new_val | |
}, | |
}); | |
return obj | |
} | |
export const gettable = (key, initial_value = undefined, obj = {}, options = { enumerable: true, configurable: false}) => { | |
let value = $state(initial_value); | |
Object.defineProperty(obj, key, { | |
...options, | |
value | |
}); | |
return obj | |
} | |
export const runed = (obj,defaults = {}, readonly = [], options = {} ) => { | |
let defaulted = {...defaults, ...obj} | |
iterate_over_properties(defaulted, readonly, options) | |
return defaulted | |
} | |
const iterate_over_properties = (obj, readonly = [], options = {} ) => { | |
for(const property in obj){ | |
if(typeof obj[property] === 'object'){ | |
runed(obj[property], options[property]) | |
} | |
if(readonly.includes(property)){ | |
gettable(property, obj[property], obj, options[property]) | |
}else{ | |
settable(property, obj[property], obj, options[property]) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment