Skip to content

Instantly share code, notes, and snippets.

@unlocomqx
Forked from jrmoynihan/App.svelte
Created September 28, 2023 06:01
Show Gist options
  • Save unlocomqx/4789da779287f894000a8e605545a5f6 to your computer and use it in GitHub Desktop.
Save unlocomqx/4789da779287f894000a8e605545a5f6 to your computer and use it in GitHub Desktop.
A helper function for setting Svelte 5 $state() runes on objects
<script lang="ts">
import { gettable, settable, runed } from './runes.js'
const createFruit = (obj) => {
// An optional object of default values if the provided prop is nullish
const defaults = {
color: 'purple',
consumed: true,
count: 0
}
// An optional array of keys, indicating which properties should be readonly
const readonly_props = []
/* 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)
/* 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
}
/* Note that these objects don't all contain the same properties. We can handle that! */
let data = [
{name: 'apple', color: 'red', consumed: true},
{name: 'orange', color: 'orange', consumed: false},
{name: 'pear', color: 'green'},
{name: 'grape'}
]
let fruits = $state(data.map(f => createFruit(f)))
const addFruit = (name) => {
const fruit_to_add = createFruit({name}) // Create a fruit with our helper function, passing only part of the full object in
fruits = [...fruits, fruit_to_add] // Push the new stateful fruit to the array, and reassign to trigger reactive updates
fruit = '' // Reset the input field
}
const removeFruit = (name) => {
fruits = fruits.filter(f => f.name !== name)
}
</script>
<input type="text" bind:value={fruit} />
<button on:click={()=>addFruit(fruit)}>Add</button>
{#each fruits as fruit (fruit)}
<p>
<span style:background={fruit.color}>{fruit.name}</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={()=>removeFruit(fruit.name)}>Remove</button>
</p>
{/each}
<style>
span{
padding: 0.5rem 1rem;
}
</style>
export const settable = (key, initial_value = undefined, obj = {}) => {
let value = $state(initial_value);
Object.defineProperty(obj, key, {
get(){return value},
set(new_val){
value = new_val
},
enumerable: true,
});
return obj
}
export const gettable = (key, initial_value = undefined, obj = {}) => {
let value = $state(initial_value);
Object.defineProperty(obj, key, {
get(){return value},
enumerable: true,
});
return obj
}
export const runed = (obj,defaults={}, readonly = []) => {
let defaulted = {...defaults, ...obj}
iterate_over_properties(defaulted, readonly)
return defaulted
}
const iterate_over_properties = (obj, readonly=[]) => {
for(const property in obj){
if(readonly.includes(property)){
gettable(property, obj[property], obj)
}else{
settable(property, obj[property], obj)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment