@tempots/dom 5.0.2 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,9 @@
1
1
  import { type Signal } from '../prop';
2
+ import { SeparatorProps } from './Repeat';
2
3
  import { type JSX } from '../jsx-runtime';
3
4
  export interface ForProps<T> {
4
5
  of: Signal<T[]>;
6
+ separator?: (value: Signal<SeparatorProps>) => JSX.DOMNode;
5
7
  children?: (value: Signal<T>, index: number) => JSX.DOMNode;
6
8
  }
7
- export declare function For<T>({ of, children: render }: ForProps<T>): JSX.DOMNode;
9
+ export declare function For<T>({ of, children: render, separator }: ForProps<T>): JSX.DOMNode;
package/components/For.js CHANGED
@@ -3,7 +3,7 @@ import { makeRenderable } from '../jsx-runtime';
3
3
  import { FragmentImpl } from './Fragment';
4
4
  import { OnRemoveImpl } from './OnRemove';
5
5
  // <For of={values}>{(value) => <span>{value}</span>}</For>
6
- export function For({ of, children: render }) {
6
+ export function For({ of, children: render, separator }) {
7
7
  const times = of.map(v => v.length);
8
8
  return new RepeatImpl(times, (index) => {
9
9
  const value = of.at(index);
@@ -11,5 +11,5 @@ export function For({ of, children: render }) {
11
11
  makeRenderable(render?.(value, index)),
12
12
  new OnRemoveImpl(value.clean)
13
13
  ]);
14
- });
14
+ }, separator);
15
15
  }
@@ -1,15 +1,25 @@
1
- import { type Signal } from '../prop';
1
+ import { Signal } from '../prop';
2
2
  import { type Clear } from '../clean';
3
3
  import { type DOMContext } from '../dom-context';
4
4
  import { type Renderable } from '../renderable';
5
+ import { JSX } from '../jsx';
6
+ export interface SeparatorProps {
7
+ first: boolean;
8
+ last: boolean;
9
+ index: number;
10
+ }
5
11
  export declare class RepeatImpl implements Renderable {
6
12
  private readonly times;
7
13
  private readonly children;
8
- constructor(times: Signal<number>, children: (index: number) => Renderable);
14
+ private readonly separator?;
15
+ constructor(times: Signal<number>, children: (index: number) => JSX.DOMNode, separator?: ((sep: Signal<SeparatorProps>) => JSX.DOMNode) | undefined);
9
16
  readonly appendTo: (ctx: DOMContext) => Clear;
17
+ readonly appendToWithoutSeparator: (ctx: DOMContext) => Clear;
18
+ readonly appendToWithSeparator: (ctx: DOMContext, separator: (sep: Signal<SeparatorProps>) => JSX.DOMNode) => Clear;
10
19
  }
11
20
  export interface RepeatProps {
12
21
  times: Signal<number>;
13
- children?: (index: number) => Renderable;
22
+ children?: (index: number) => JSX.DOMNode;
23
+ separator?: (sep: Signal<SeparatorProps>) => JSX.DOMNode;
14
24
  }
15
25
  export declare function Repeat(props: RepeatProps): Renderable;
@@ -1,33 +1,103 @@
1
+ import { Prop } from '../prop';
1
2
  import { Fragment } from './Fragment';
3
+ import { makeRenderable } from '../jsx-runtime';
2
4
  export class RepeatImpl {
3
5
  times;
4
6
  children;
5
- constructor(times, children) {
7
+ separator;
8
+ constructor(times, children, separator) {
6
9
  this.times = times;
7
10
  this.children = children;
11
+ this.separator = separator;
8
12
  }
9
13
  appendTo = (ctx) => {
14
+ if (!this.separator) {
15
+ return this.appendToWithoutSeparator(ctx);
16
+ }
17
+ else {
18
+ return this.appendToWithSeparator(ctx, this.separator);
19
+ }
20
+ };
21
+ appendToWithoutSeparator = (ctx) => {
22
+ const newCtx = ctx.makeReference();
23
+ const count = this.times.get();
24
+ const clears = new Array(count);
25
+ for (let i = 0; i < count; i++) {
26
+ clears[i] = makeRenderable(this.children(i)).appendTo(newCtx);
27
+ }
28
+ const cancel = this.times.subscribe((newCount) => {
29
+ while (newCount < clears.length) {
30
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
31
+ clears.pop()(true);
32
+ }
33
+ for (let i = clears.length; i < newCount; i++) {
34
+ clears[i] = makeRenderable(this.children(i)).appendTo(newCtx);
35
+ }
36
+ });
37
+ return (removeTree) => {
38
+ newCtx.requestClear(removeTree, () => {
39
+ cancel();
40
+ clears.forEach(clear => { clear(removeTree); });
41
+ });
42
+ };
43
+ };
44
+ appendToWithSeparator = (ctx, separator) => {
10
45
  const newCtx = ctx.makeReference();
11
46
  const count = this.times.get();
47
+ const separatorProps = new Array(Math.max(0, count - 1));
48
+ const separatorClears = new Array(Math.max(0, count - 1));
12
49
  const clears = new Array(count);
13
50
  for (let i = 0; i < count; i++) {
14
- clears[i] = this.children(i).appendTo(newCtx);
51
+ clears[i] = makeRenderable(this.children(i)).appendTo(newCtx);
52
+ if (i < count - 1) {
53
+ separatorProps[i] = Prop.of({
54
+ first: i === 0,
55
+ last: i === count - 2,
56
+ index: i
57
+ });
58
+ separatorClears[i] = makeRenderable(separator(separatorProps[i])).appendTo(newCtx);
59
+ }
15
60
  }
16
61
  const cancel = this.times.subscribe((newCount) => {
17
62
  while (newCount < clears.length) {
18
63
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
19
64
  clears.pop()(true);
65
+ if (separatorClears.length > 0) {
66
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
67
+ separatorClears.pop()(true);
68
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
69
+ separatorProps.pop().clean();
70
+ }
71
+ }
72
+ for (let i = 0; i < separatorProps.length; i++) {
73
+ separatorProps[i].set({
74
+ first: i === 0,
75
+ last: i === newCount - 2,
76
+ index: i
77
+ });
20
78
  }
21
79
  for (let i = clears.length; i < newCount; i++) {
22
- clears[i] = this.children(i).appendTo(newCtx);
80
+ clears[i] = makeRenderable(this.children(i)).appendTo(newCtx);
81
+ if (i < newCount - 1) {
82
+ separatorProps[i] = Prop.of({
83
+ first: i === 0,
84
+ last: i === newCount - 2,
85
+ index: i
86
+ });
87
+ separatorClears[i] = makeRenderable(separator(separatorProps[i])).appendTo(newCtx);
88
+ }
23
89
  }
24
90
  });
25
91
  return (removeTree) => {
26
92
  newCtx.requestClear(removeTree, () => {
27
93
  cancel();
28
94
  clears.forEach(clear => { clear(removeTree); });
95
+ separatorClears.forEach(clear => { clear(removeTree); });
96
+ separatorProps.forEach(signal => { signal.clean(); });
29
97
  });
30
98
  };
31
99
  };
32
100
  }
33
- export function Repeat(props) { return new RepeatImpl(props.times, props.children ?? (() => Fragment({ children: [] }))); }
101
+ export function Repeat(props) {
102
+ return new RepeatImpl(props.times, props.children ?? (() => Fragment({ children: [] })), props.separator);
103
+ }
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@tempots/dom",
3
- "version": "5.0.2",
4
3
  "main": "index.js",
5
4
  "types": "index.d.ts",
5
+ "version": "5.1.0",
6
6
  "scripts": {
7
7
  "watch": "tsc --watch",
8
8
  "build": "tsc",
package/prop.d.ts CHANGED
@@ -29,6 +29,8 @@ export declare class Signal<T> {
29
29
  readonly feed: (prop: Prop<T>) => Prop<T>;
30
30
  readonly deriveProp: () => Prop<T>;
31
31
  readonly clean: () => void;
32
+ readonly count: () => Signal<number>;
33
+ readonly animate: (duration: number, interpolate: (start: T, end: T, delta: number) => T, initialValue?: T | null, easing?: (t: number) => number) => Signal<T>;
32
34
  }
33
35
  export declare class Prop<T> extends Signal<T> {
34
36
  static isProp<T = unknown>(x: unknown): x is Prop<T>;
package/prop.js CHANGED
@@ -137,6 +137,47 @@ export class Signal {
137
137
  clean = () => {
138
138
  this._listeners.length = 0;
139
139
  };
140
+ count = () => {
141
+ let count = 0;
142
+ return this.map(() => ++count);
143
+ };
144
+ animate = (duration, interpolate, initialValue = null, easing = t => t) => {
145
+ let startValue = initialValue ?? this.get();
146
+ let endValue = this.get();
147
+ const prop = new Prop(startValue);
148
+ let startTime = 0;
149
+ let endTime = 0;
150
+ let animationFrame = null;
151
+ const animate = (time) => {
152
+ if (this._listeners.length === 0) {
153
+ animationFrame = null;
154
+ return;
155
+ }
156
+ if (time < endTime) {
157
+ const delta = (time - startTime) / (endTime - startTime);
158
+ prop.set(interpolate(startValue, endValue, easing(delta)));
159
+ animationFrame = requestAnimationFrame(animate);
160
+ }
161
+ else {
162
+ prop.set(endValue);
163
+ animationFrame = null;
164
+ }
165
+ };
166
+ this.subscribe(value => {
167
+ if (animationFrame != null)
168
+ cancelAnimationFrame(animationFrame);
169
+ if (this._listeners.length === 0) {
170
+ animationFrame = null;
171
+ return;
172
+ }
173
+ startValue = prop.get();
174
+ endValue = value;
175
+ startTime = performance.now();
176
+ endTime = startTime + duration;
177
+ animationFrame = requestAnimationFrame(animate);
178
+ });
179
+ return prop;
180
+ };
140
181
  }
141
182
  export class Prop extends Signal {
142
183
  static isProp(x) {