# Managing state with `Observable` and `State`

Nyooom offers two types of state container:

`Observable`s are a higher level, multi-value container with batched events
executed in microtasks.

`State`s are simpler, single-valued but composable event containers that emit
immediate change events.

`State`s can be composed into `ComputedState`s, which are read-only and compute
their value lazily using dirty-tracking.

## Observable objects

Observables are multi-value state containers that can be observed via event
listeners, promises, or states.

An observables values are stored in the `values` property, which returns a proxy
that will queue a change event whenever its value is changed. These changes can
also be reacted to immediately via the cancellable `'change'` event

Whenever there are changes, the observable will emit an event in the next
microtask that has a list of all the changes in order as an array of objects
with the following properties:

- `property`: String or Symbol
- `from`: The value of the property before the change
- `to`: The new value of the property
- `mutation`: Whether the value (must be observable) was mutated rather than changed
- `source`: The object responsible for the change (used for avoiding loops)

When using the `values`-Proxy to set a new property, this proxy will be listed
as the `source` of the change. To set a custom source, the `update(prop, value,
source)` method may be used instead.

If desired, the batched `"changed"`-event can be emitted prematurely with the
`emitQueue` method. This will not remove a queued microtask, but will clear the
event queue so the microtask becomes a nop unless new changes are made before it
runs.

### Mutation Events

When passing the option `children: true` to the Observable constructor, it will
listen for changes on any direct children which are either Observables or an
observable's proxy.

Changes on the child (including, if the child itself listens to nested changes,
those as well), will trigger a change event on the parent where both `to` and
`from` point to the unchanged value while the `mutation` flag is sest to true.

### Promises

To listen for the single next change of a property, the `when(prop)` method
returns a proise that will resolve on the next `"changed"` event that affects
the given property with the last change in the list.

### States

To listen to multipla updates to the same change and/or modify the state, the
`property(prop, {readonly=false,initial=undefined})` method returns a
`WriteableState` corresponding to a single property on the observable object.

This `State` will be notified of any change to the property in the next event
emision microtask, including changes made through the state itself.

Property states can be set to read-only to prevent them from writing data back
to the observable.

Properties can also be initialized with a value in case they're missing on the
observable's target. Note that this uses `property in target`, rather than
`target[property] !== undefined` to check for presence.

The `property` method is also memoized, meaning it will repeatedly return the
same state object for any given property name.

### Filtering / Consolidating changes

The list of events can be filtered before dispatching the event by overriding
the `consolidate(changes)` method on a subclass or instance of `Observable`.

The consolidate method should return the new list of changes
and is allowed to reuse/mutate the old list passed in as its
only argument.

This callback can be used, for example, to join changes on the same property
into a single change from first to last value (for example join 1 to 2 and 2 to
3 into a single 1 to 3), to remove insignificant changes (for example from "1"
to "1.0" if the application value only cares about the numeric value)

Note that adding new changes won't update an attribute's actual value. Should
that be necessary, the callback should use the `target` property to access the
underlying object without emitting a new change event.

The callback might also need to manually emit a `'change'` event to notify other
event handlers of the new event and allow them to cancel it, or might choose to
intentionally omit this event and force the change.

### Skipping non-changes

By redefining the `same(a, b)` method, it is possible to define more granular
checks for whether two objects are the same when assigning, or this check can be
disabled entirely by returning `false` so change events are queued even if the
new valueis identical to the old one (this can still be handled in the
`filterChanges` method later on)

## States

States are single-value containers that emit a `"changed"`-event immediately
after their value changes.

The `subscribe((value) => void)` method provides slightly less cumbersome way of
listening to changes on a state. The method returns a function `() => void` to
cancel the event listener.

Note: the name was chosen so that states conform to the svelte [store
contract](https://svelte.dev/docs/svelte/stores#Store-contract) as this comes
with no additional cost.

### Writeable States

Writeable states are simple state containers whose value can be set via the
`value`-attribute or the `update(value, source=undefined)`-method.

### Computed States

Computed states cannot be written to but depend on other states and a compute
function for their value. Computed states autonomously keep track of upstream
changes and recompute their value only on demand when an upstream value has
changed since the last computation.

Computed states are created by passing a list of input states into a generator
function which in turn is created by calling `State.computed` with a computation
function. The computation function is called with the values of the input states
in the order they were passed into the generator.

```js
const difference = State.compute((a, b) => a - b)

const progress = new Observable({target: 100, current: 30})

const remaining = difference(
	progress.property("target"),
	progress.property("current")
)

remaining.addEventListener("changed", ({value}) => {
	console.log(`Only ${value}% remaining`)
})

progress.current = 40
```

Computed states with a single input can also be created using the
`map(fn)` function on a state instance:

```js
const number = State.value(10)

const double = number.map(n => n*2)
```

### Custom Elements

The Observable module also exports a `ReactiveElement` class which inherits from
`HTMLElement` but extends the constructor by creating a new `Observable` and
linking it to the element's attributes using a `MutationObserver`.

For even easier usage, it also exports the `element` function, which maps a
render function to a reactive custom element class and registers it under the
function's name converted to kebab-case.

On creation, the element's content will be replaced with the result of the
render function, unles it returns nothing. Re-rendering can be triggered
manually by calling the `render` instance method.

The `render` function is bound to the new element before calling, meaning arrow
functions won't have access to the element during rendering. This is by design
to encourage object-oriented functions when accessing the element is desired.

```js
	const nyooomCounter = state => fragment(
		html.span(state.property("count")), " ",
		html.button("Increase", {
			click() { state.values.count++ }
		})
	)

	component(nyooomCounter)
```

```html
<nyooom-counter count="1"></nyooom-counter>
```

The logic to attach a state to an element is also exposed in the static method
`ReactiveElement.attachObserver` which can be used in constructors of custom
element classes that cannot inherit from `ReactiveElement` or even built-in
elements. The method returns the ovservable.
