Skip to content

Commit

Permalink
feat(form-models): add ArrayModel iteration
Browse files Browse the repository at this point in the history
  • Loading branch information
Lodin committed Jun 19, 2024
1 parent d5cb6ea commit 1dee408
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 2 deletions.
52 changes: 50 additions & 2 deletions packages/ts/form-models/src/core.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
import type { EmptyObject } from 'type-fest';
import { CoreModelBuilder } from './builders.js';
import { $enum, $itemModel, type $members, type AnyObject, type Enum, Model, type Value } from './model.js';
import {
$enum,
$itemModel,
$key,
type $members,
$name,
$owner,
type AnyObject,
type Enum,
Model,
type Value,
} from './model.js';
import { getValue } from './utils.js';

export type PrimitiveModel<V = unknown> = Model<V>;
export const PrimitiveModel = new CoreModelBuilder(Model, (): unknown => undefined).name('primitive').build();
Expand All @@ -14,11 +26,47 @@ export const NumberModel = new CoreModelBuilder(PrimitiveModel, () => 0).name('n
export type BooleanModel = PrimitiveModel<boolean>;
export const BooleanModel = new CoreModelBuilder(PrimitiveModel, () => false).name('boolean').build();

export type ArrayModel<M extends Model> = Model<Array<Value<M>>, Readonly<{ [$itemModel]: M }>>;
export const $items = Symbol('items');
const arrayItemModels = new WeakMap<ArrayModel, Model[]>();

export type ArrayModel<M extends Model = Model> = Model<
Array<Value<M>>,
Readonly<{
[$itemModel]: M;
[$items](): Generator<M, void, void>;
[Symbol.iterator](): Generator<M, void, void>;
}>
>;

export const ArrayModel = new CoreModelBuilder(Model, (): unknown[] => [])
.name('Array')
.define($itemModel, { value: Model })
.define($items, {
*value(this: ArrayModel) {
return yield* this;
},
})
.define(Symbol.iterator, {
*value(this: ArrayModel) {
const items = arrayItemModels.get(this) ?? [];
arrayItemModels.set(this, items);
const value = getValue(this);

items.length = value.length;

for (let i = 0; i < value.length; i++) {
if (!items[i]) {
items[i] = new CoreModelBuilder(this[$itemModel], () => value[i])
.name(`${this[$itemModel][$name]}[${i}]`)
.define($key, { value: i })
.define($owner, { value: this })
.build();
}

yield items[i];
}
},
})
.build();

export type ObjectModel<V, EX extends AnyObject = EmptyObject, R extends string = never> = Model<V, EX, R>;
Expand Down
27 changes: 27 additions & 0 deletions packages/ts/form-models/test/models.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import m, {
$defaultValue,
$enum,
$itemModel,
$items,
$key,
$members,
$meta,
$name,
Expand Down Expand Up @@ -295,6 +297,31 @@ describe('@vaadin/hilla-form-models', () => {
it('should have a default value', () => {
expect(ArrayModel[$defaultValue]).to.be.like([]);
});

it('should allow to iterate through the item models', () => {
const target: AttachTarget<Comment[]> = {
value: [
{ title: 'FooTitle', text: 'FooText' },
{ title: 'BarTitle', text: 'BarText' },
],
};

const AttachedCommentsModel = m.attach(m.array(CommentModel), target);

expect(AttachedCommentsModel).to.have.property(Symbol.iterator);
expect(AttachedCommentsModel).to.have.property($items);

const items = [...AttachedCommentsModel];

expect(items).to.be.an('array').with.lengthOf(2);

for (let i = 0; i < items.length; i++) {
expect(items[i]).to.be.instanceof(CommentModel);
expect(items[i]).to.have.property($owner).which.is.equal(AttachedCommentsModel);
expect(items[i]).to.have.property($key).which.is.equal(i);
expect(getValue(items[i])).to.be.equal(target.value[i]);
}
});
});

describe('ObjectModel', () => {
Expand Down

0 comments on commit 1dee408

Please sign in to comment.