Skip to content

Instantly share code, notes, and snippets.

@jrmoynihan
Last active October 2, 2023 20:22
Show Gist options
  • Save jrmoynihan/57d78df7b53159ca98403d0220993f48 to your computer and use it in GitHub Desktop.
Save jrmoynihan/57d78df7b53159ca98403d0220993f48 to your computer and use it in GitHub Desktop.
A helper function for setting Svelte 5 $state() runes on objects
<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>
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