diff --git a/package.json b/package.json index c86bd65..5a55001 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "zeed", "type": "module", - "version": "0.19.5", + "version": "0.19.6", "description": "🌱 Simple foundation library", "author": { "name": "Dirk Holtwick", diff --git a/src/common/data/index.ts b/src/common/data/index.ts index 1e88778..11bc25b 100644 --- a/src/common/data/index.ts +++ b/src/common/data/index.ts @@ -20,6 +20,7 @@ export * from './orderby' export * from './path' export * from './regexp' export * from './rounding' +export * from './signal' export * from './sortable' export * from './sorted' export * from './string-deburr' diff --git a/src/common/data/signal.spec.ts b/src/common/data/signal.spec.ts new file mode 100644 index 0000000..3798e74 --- /dev/null +++ b/src/common/data/signal.spec.ts @@ -0,0 +1,53 @@ +import { useSignal } from './signal' + +describe('signal', () => { + it('should signal', async () => { + let copy: any // copy of "flag" + let count = 1 // counter for "name" + + const [flag, setFlag] = useSignal(true, v => copy = v) + const name = useSignal('Henry') + const object = useSignal({ + a: 1, + b: 2, + }) + + const off = name.on(v => ++count) + + // Get values + expect(flag()).toEqual(true) + expect(name.get()).toEqual('Henry') + expect(copy).toEqual(undefined) + expect(object.get()).toEqual({ + a: 1, + b: 2, + }) + + // Change values + setFlag(false) + name.set('Anna') + object.set({ + a: 11, + b: 22, + }) + + // Check changed values + expect(flag()).toEqual(false) + expect(name.get()).toEqual('Anna') + expect(copy).toEqual(false) + expect(object.get()).toEqual({ + a: 11, + b: 22, + }) + + // Skip setting same value + expect(count).toBe(2) + name.set('Anna') + expect(count).toBe(2) + name.set('Cloe') + expect(count).toBe(3) + off() + name.set('Diego') + expect(count).toBe(3) + }) +}) diff --git a/src/common/data/signal.ts b/src/common/data/signal.ts new file mode 100644 index 0000000..f08ca04 --- /dev/null +++ b/src/common/data/signal.ts @@ -0,0 +1,52 @@ +import { arrayRemoveElement } from './array' +import { objectPlain } from './object' + +export type SignalWatcher = (value: T, oldValue: T) => void + +export type Signal = [ + () => T, + (value: T) => void, + (fn: SignalWatcher) => () => void, + (fn: SignalWatcher) => void, +] & { + get: () => T + set: (value: T) => void + on: (fn: SignalWatcher) => () => void + off: (fn: SignalWatcher) => void +} + +/** Super simple signal implementation */ +export function useSignal(value: T, onChange?: SignalWatcher): Signal { + let signal = structuredClone(value) + + const watchers: SignalWatcher[] = [] + + function off(fn: SignalWatcher) { + arrayRemoveElement(watchers, fn) + } + + function on(fn: SignalWatcher) { + watchers.push(fn) + return () => off(fn) + } + + if (onChange) + watchers.push(onChange) + + const get = () => structuredClone(signal) + + const set = (value: T) => { + if (value !== signal) { + const oldValue = signal + signal = value + watchers.forEach(fn => fn(value, oldValue)) + } + } + + const obj: any = [get, set, on, off] + obj.get = get + obj.set = set + obj.on = on + obj.off = off + return obj +}