Skip to content

Releases: lume/classy-solid

v0.4.3

16 Oct 19:32
Compare
Choose a tag to compare

What's Changed

  • fix: ensure that signalifying properties already signalified by the @signal decorator does not create double signals by @trusktr in #10

Full Changelog: v0.4.0...v0.4.3

v0.4.0 - private signals

25 Sep 22:35
Compare
Choose a tag to compare

What's Changed

  • @signal decorator improvements by @trusktr in #9

Details

BREAKING: There's a breaking change on how to apply @signal to a getter/setter, see below.

  • feat: Place @signal decorator descriptors in the same locations as decorated class members to more closely align the object shape with what a user wrote. For example if a field was decorated with @signal, the signal descriptor will be on the instance, and if an auto or getter/setter accessor was decorated with @signal then the descriptor will be on the prototype
    • BREAKING: Getters/setters decorated with @signal no longer result in own properties on instances, but result in a prototype property. For example, any checks like Object.hasOwn(this, 'prop') for a property named prop defined as @signal get prop() {} will now return false instead of true. Features like Object.assign will no longer work on those properties. Etc. If you need the properties to be own, migrate to using @signal prop instead of a getter/setter, or update the dependent code to do something else.
  • feat: add the ability to finalize @signal fields without a class decorator by defining @signal #finalize after all fields are defined. For example instead of this,
    @reactive
    class MyClass {
      @signal foo = 1
      @signal bar = 2
    }
    we can write this:
    class MyClass {
      @signal foo = 1
      @signal bar = 2
      @signal #finalize
    }
    The latter version has an advantage: fields are signalified by the time any code in constructor runs, making it possible to rely on the signal fields inside constructor. For example the following will not work when using the @reactive class decorator approach:
    @reactive
    class MyClass {
      @signal foo = 1
      constructor() {
        createEffect(() => console.log(this.foo)) // This effect will never re-run (do not rely on signal fields in constructor in this case)
      }
    }
    but the following will work:
    class MyClass {
      @signal foo = 1
      @signal #finalize
      constructor() {
        createEffect(() => console.log(this.foo)) // This effect will re-run (signal fields are reliable in constructor)
      }
    }
    Just make sure that @signal #finalize comes after all class field definitions; any fields that come after @signal #finalize will not be signalified.
    It is up to you which form you prefer. If you do not need the signals within constructor, you might like the aesthetic look of the @reactive class decorator more than an unused #finalize field, or, at an implementation level you might like the purity of the @signal #finalize approach because it does not make a subclass like @reactive does.
  • feat: refactor @signal on getters/setters to work without a class decorator (adding support for #private getters/setters, but now requiring the @signal decorator to be placed on both the getter and the setter otherwise it won't work and an error will be shown in console)
    • BREAKING: getter/setter pairs no longer require the use of the @reactive class decorator, but the @signal decorator must now be placed on both the getter and setter of a property definition:
      // Bad (this example will show a helpful error in console in case you forget to decorate *both* the getter and setter, and the property will not be reactive)
      class MyClass {
        @signal
        get foo() {...}
        set foo(value) {...}
      }
      
      // Good (the property will be reactive)
      class MyClass {
        @signal get foo() {...}
        @signal set foo(value) {...}
      }
      To migrate, add an extra @signal to your getter/setter so that both are decorated. Optionally delete the @reactive decorator from your class if you are using the new @signal #finalize or no fields at all (f.e. only auto accessors).
    • This now supports #private getters/setters:
      class MyClass {
        @signal get #foo() {...}
        @signal set #foo(value) {...}
      }
  • feat: add support for public and #private auto accessors. For example, and as an alternative to @reactive or @signal #finalize approaches, we can now write "auto accessor" fields:
    class MyClass {
      @signal accessor foo = 1
      constructor() {
        createEffect(() => console.log(this.foo)) // This effect will re-run.
      }
    }
    It also supports #private "auto accessor" fields:
    class MyClass {
      @signal accessor #foo = 1
    }

The main benefit of this update is that we now have a way to apply @signal to #private properties, but note that only auto accessors and getters/setters are supported when it comes to making them #private, and fields cannot not be private due to the current version of the decorator spec:

@reactive
class MyClass {
  // This is impossible with the current decorator spec, an error will be thrown to let you know.
  @signal #foo = 1

  // In TypeScript you can at least mark the field "private" for type checking if you prefer to use fields:
  @signal private foo = 1 // (not actually private at runtime)

  // This works.
  @signal accessor #foo = 1

  // This works too.
  @signal get #foo() {...}
  @signal set #foo(v) {...}
}

Full Changelog: v0.3.9...v0.4.0

v0.3.8

12 Sep 02:16
Compare
Choose a tag to compare

What's Changed

  • chore: update lowclass to ^8.0.0 by @trusktr in #8

Full Changelog: v0.3.7...v0.3.8

v0.3.7

12 Sep 02:16
Compare
Choose a tag to compare
  • fix: improve Effectful.createEffect so that nested calls work as expected just as regular Solid.js createEffect calls do, otherwise they unintentionally outlive their parent effect db3a106
  • feat: add syncSignals, createSyncedSignals, and createStoppableEffect (see README for details) db3a106

Full Changelog: v0.3.6...v0.3.7

v0.3.6

12 Sep 02:15
Compare
Choose a tag to compare
  • fix: ensure that signalify() does not count as reading a dependency, to avoid infinite loops. Only a user's explicit read of a property after they signalify a property should count as a dependency ready (this was namely a problem when signalify() was a no-op when the property was already signalified before hand elsewhere, such as a signalified object from a library) e89bbe0

Full Changelog: v0.3.5...v0.3.6

v0.3.5

12 Sep 02:13
Compare
Choose a tag to compare

Full Changelog: v0.3.4...v0.3.5

v0.3.4

12 Sep 02:12
Compare
Choose a tag to compare

Full Changelog: v0.3.3...v0.3.4

v0.3.3 - Effectfully effectful

28 Nov 05:16
Compare
Choose a tag to compare

Features

Effectful

Effectful is a class-factory mixin that gives your class a createEffect()
method, along with a stopEffects() method that will stop all current effects.

Here's an example that shows a custom element that starts effects on connected,
and cleans them up on disconnect:

import {reactive, signal, Effectful} from 'classy-solid'

@reactive
class CounterDisplay extends Effectful(HTMLElement) {
	@signal count

	get double() {
		return this.count * 2
	}

	constructor() {
		super()
		this.attachShadow({mode: 'open'})
		this.shadowRoot.innerHTML = `<div></div>`
	}

	connectedCallback() {
		// Create some effects
		this.createEffect(() => {
			this.shadowRoot.firstElementChild.textContent = `Count is: ${this.count}`
		})

		this.createEffect(() => {
			console.log('count:', this.count)
		})

		this.createEffect(() => {
			console.log('double:', this.double)
		})
	}

	disconnectedCallback() {
		// Stop the effects
		this.stopEffects()
	}
}

customElements.define('counter-display', CounterDisplay)

createEffect() creates a single owner root for all effects for the current
instance, unless it is called inside another root in which case it'll use that
root.

Effects

An instantiation of Effectful(Object) as a shortcut.

Useful when not extending from a mixin:

class MyClass extends Effects {
    constructor() {
        this.createEffect(() => {...})
        this.createEffect(() => {...})
    }

    dispose() {
        this.stopEffects()
    }
}

const o = new MyClass()

// ...later, when finished...
o.dispose()

Useful when separate groups of effects are needed where each group can be stopped indepently of others.

class MyClass {
    specialEffects = new Effects()
    otherEffects = new Effects()

    doSpecialStuff() {
        this.specialEffects.createEffect(() => {...})
        this.specialEffects.createEffect(() => {...})
    }

    cleanupSpecialStuff() {
        this.specialEffects.stopEffects()
    }

    doOtherStuff() {
        this.otherEffects.createEffect(() => {...})
        this.otherEffects.createEffect(() => {...})
    }

    cleanupOtherStuff() {
        this.otherEffects.stopEffects()
    }
}

Full Changelog: v0.3.0...v0.3.3

v0.3.0 - Getting classier!

17 Nov 22:59
Compare
Choose a tag to compare

Fixes:

  • ensure that subclass fields decorated with @signal do not lose reactivity

BREAKING:

  • We updated to the latest TypeScript and removed our custom decorator parameter type definitions and are now using types from TypeScript proper.
    • You may need to update your TypeScript version.
    • There will now be valid type errors that can happen with certain uses of decorators, which could require code changes. F.e. TypeScript will show a type error for class decorators that return a subclass if the decorated class has a private constructor, etc.

Full Changelog: v0.2.3...v0.3.0

v0.2.3 - Buildless Unconstructive Reactivity

26 Sep 17:43
Compare
Choose a tag to compare
  • Ensure that constructors are not reactive, i.e. that if you new YourClass inside of an effect, the constructor will be untracked and reading any properties during construction will not cause the effect to track them. It makes more sense that, when you're creating something, you can read your variables to map to further variables, without that counting as something that you want to automatically run again, otherwise you'll re-create the object unintentionally, which is probably not what you want. If you do want that, you can read properties outside of the constructor, before or after you new YourClass to make the intent explicit.
  • Also commit dist/ build outputs. This makes things easier for people, so they can consume the build outputs without needing to run a build (f.e. avoiding potential build issues in our less-tested environments such as Windows PowerShell).