diff --git a/packages/@glimmer/validator/index.ts b/packages/@glimmer/validator/index.ts index 0fad5d7e3..da085fc4b 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 { debug } from './lib/debug'; export { dirtyTagFor, tagFor, type TagMeta, tagMetaFor } from './lib/meta'; export { trackedData } from './lib/tracked-data'; diff --git a/packages/@glimmer/validator/lib/cell.ts b/packages/@glimmer/validator/lib/cell.ts new file mode 100644 index 000000000..2be455727 --- /dev/null +++ b/packages/@glimmer/validator/lib/cell.ts @@ -0,0 +1,54 @@ +import type { UpdatableTag } from '@glimmer/interfaces'; + +import { consumeTag } from './tracking'; +import { createUpdatableTag, DIRTY_TAG } from './validators'; + +type Equality = (a: T, b: T) => boolean; + +export class Cell { + static create(value: T, equals?: Equality): Cell { + return new Cell(value, equals); + } + + #value: Value; + readonly #equals?: Equality | undefined; + readonly #tag: UpdatableTag; + + private constructor(value: Value, equals?: Equality) { + this.#value = value; + this.#equals = equals; + 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.#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`); + } +} diff --git a/packages/@glimmer/validator/test/-utils.ts b/packages/@glimmer/validator/test/-utils.ts index 30d281ae8..9b46a1712 100644 --- a/packages/@glimmer/validator/test/-utils.ts +++ b/packages/@glimmer/validator/test/-utils.ts @@ -1,2 +1,3 @@ export const module = QUnit.module; export const test = QUnit.test; +export const skip = QUnit.skip; diff --git a/packages/@glimmer/validator/test/cell-test.ts b/packages/@glimmer/validator/test/cell-test.ts new file mode 100644 index 000000000..46c1dbf60 --- /dev/null +++ b/packages/@glimmer/validator/test/cell-test.ts @@ -0,0 +1,28 @@ +import { Cell } from '@glimmer/validator'; + +import { module, skip, test } from './-utils'; + +module('Cell', () => { + test('creates reactive storage', (assert) => { + const cell = Cell.create('hello'); + assert.strictEqual(cell.read(), 'hello'); + }); + + test('updates when set', (assert) => { + const cell = Cell.create('hello'); + cell.set('world'); + assert.strictEqual(cell.read(), 'world'); + }); + + test('updates when update() is called', (assert) => { + const cell = Cell.create('hello'); + cell.update((value) => value + ' world'); + assert.strictEqual(cell.read(), 'hello world'); + }); + + skip('is frozen when freeze() is called', (assert) => { + const cell = Cell.create('hello'); + cell.freeze(); + assert.throws(() => cell.set('world')); + }); +});