diff --git a/packages/@glimmer/validator/index.ts b/packages/@glimmer/validator/index.ts index a3abcaa5007..e003f4620d5 100644 --- a/packages/@glimmer/validator/index.ts +++ b/packages/@glimmer/validator/index.ts @@ -8,6 +8,7 @@ if (Reflect.has(globalThis, GLIMMER_VALIDATOR_REGISTRATION)) { Reflect.set(globalThis, GLIMMER_VALIDATOR_REGISTRATION, true); +export { cell } from './lib/cell'; export { trackedArray } from './lib/collections/array'; export { trackedMap } from './lib/collections/map'; export { trackedObject } from './lib/collections/object'; diff --git a/packages/@glimmer/validator/lib/cell.ts b/packages/@glimmer/validator/lib/cell.ts new file mode 100644 index 00000000000..0a3020ce2b6 --- /dev/null +++ b/packages/@glimmer/validator/lib/cell.ts @@ -0,0 +1,60 @@ +import type { UpdatableTag } from '@glimmer/interfaces'; + +import { consumeTag } from './tracking'; +import { createUpdatableTag, DIRTY_TAG } from './validators'; +import type { ReactiveOptions } from './collections/types'; + +class Cell { + #options: ReactiveOptions; + #value: Value; + readonly #tag: UpdatableTag; + + constructor(value: Value, options: ReactiveOptions) { + this.#value = value; + this.#options = options; + this.#tag = createUpdatableTag(); + } + + get current() { + consumeTag(this.#tag); + + return this.#value; + } + + read(): Value { + consumeTag(this.#tag); + + return this.#value; + } + + set(value: Value): boolean { + if (this.#options.equals?.(this.#value, value)) { + return false; + } + + this.#value = value; + + DIRTY_TAG(this.#tag); + + return true; + } + + update(updater: (value: Value) => Value): void { + this.set(updater(this.#value)); + } + + freeze(): void { + throw new Error(`Not Implemented`); + } +} + +export function cell( + value: T, + + options?: { equals?: (a: T, b: T) => boolean; description?: string } +) { + return new Cell(value, { + equals: options?.equals ?? Object.is, + description: options?.description, + }); +} diff --git a/packages/@glimmer/validator/test/cell-test.ts b/packages/@glimmer/validator/test/cell-test.ts new file mode 100644 index 00000000000..0fffc20cd9c --- /dev/null +++ b/packages/@glimmer/validator/test/cell-test.ts @@ -0,0 +1,28 @@ +import { cell } from '@glimmer/validator'; + +import { module, test } from './-utils'; + +module('cell', () => { + test('creates reactive storage', (assert) => { + const x = cell('hello'); + assert.strictEqual(x.read(), 'hello'); + }); + + test('updates when set', (assert) => { + const x = cell('hello'); + x.set('world'); + assert.strictEqual(x.read(), 'world'); + }); + + test('updates when update() is called', (assert) => { + const x = cell('hello'); + x.update((value) => value + ' world'); + assert.strictEqual(x.read(), 'hello world'); + }); + + test('is frozen when freeze() is called', (assert) => { + const x = cell('hello'); + x.freeze(); + assert.throws(() => x.set('world')); + }); +});