@tempots/dom 5.0.2 → 6.0.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.
@@ -5,14 +5,16 @@ import { Animatable } from "./animatable";
5
5
  export declare class FadeInImpl implements Renderable {
6
6
  private readonly end;
7
7
  private readonly start;
8
+ private readonly easing;
8
9
  private readonly duration;
9
10
  private readonly delay;
10
- constructor(end: Animatable, start: Animatable | undefined, duration: number, delay: number);
11
+ constructor(end: Animatable, start: Animatable | undefined, easing: (t: number) => number, duration: number, delay: number);
11
12
  appendTo(ctx: DOMContext): Clear;
12
13
  }
13
14
  export interface FadeInProps extends Animatable {
14
15
  start?: Animatable;
15
16
  duration?: number;
17
+ easing?: (t: number) => number;
16
18
  delay?: number;
17
19
  }
18
20
  export declare function FadeIn(props: FadeInProps): Renderable;
@@ -2,11 +2,13 @@ import { getComputedAnimatable, applyInterpolatedAnimatable, applyAnimatable } f
2
2
  export class FadeInImpl {
3
3
  end;
4
4
  start;
5
+ easing;
5
6
  duration;
6
7
  delay;
7
- constructor(end, start, duration, delay) {
8
+ constructor(end, start, easing, duration, delay) {
8
9
  this.end = end;
9
10
  this.start = start;
11
+ this.easing = easing;
10
12
  this.duration = duration;
11
13
  this.delay = delay;
12
14
  }
@@ -22,7 +24,7 @@ export class FadeInImpl {
22
24
  }
23
25
  })();
24
26
  const startTime = Date.now() + this.delay;
25
- const { duration, end } = this;
27
+ const { duration, end, easing } = this;
26
28
  let nextFrameId = null;
27
29
  function frame() {
28
30
  const now = Date.now();
@@ -31,7 +33,7 @@ export class FadeInImpl {
31
33
  return;
32
34
  }
33
35
  const progress = Math.min((now - startTime) / duration, 1);
34
- applyInterpolatedAnimatable(el, start, end, progress);
36
+ applyInterpolatedAnimatable(el, start, end, easing(progress));
35
37
  if (progress < 1) {
36
38
  nextFrameId = requestAnimationFrame(frame);
37
39
  }
@@ -47,6 +49,6 @@ export class FadeInImpl {
47
49
  }
48
50
  }
49
51
  export function FadeIn(props) {
50
- const { start, duration, delay, ...end } = props;
51
- return new FadeInImpl(end, start, duration ?? 200, delay ?? 0);
52
+ const { start, duration, easing, delay, ...end } = props;
53
+ return new FadeInImpl(end, start, easing ?? (v => v), duration ?? 200, delay ?? 0);
52
54
  }
@@ -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
@@ -1,15 +1,15 @@
1
1
  import { RepeatImpl } from './Repeat';
2
2
  import { makeRenderable } from '../jsx-runtime';
3
3
  import { FragmentImpl } from './Fragment';
4
- import { OnRemoveImpl } from './OnRemove';
5
- // <For of={values}>{(value) => <span>{value}</span>}</For>
6
- export function For({ of, children: render }) {
4
+ import { OnRemove } from './OnRemove';
5
+ // <For of={values} separator={() => ", "}>{(value) => <span>{value}</span>}</For>
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);
10
10
  return new FragmentImpl([
11
11
  makeRenderable(render?.(value, index)),
12
- new OnRemoveImpl(value.clean)
12
+ OnRemove({ clear: value.clean })
13
13
  ]);
14
- });
14
+ }, separator);
15
15
  }
@@ -1,9 +1,10 @@
1
1
  export class HiddenWhenEmptyImpl {
2
2
  appendTo(ctx) {
3
+ const initial = ctx.getStyle(':empty');
3
4
  ctx.setStyle(':empty', 'display: none');
4
5
  return (removeTree) => {
5
6
  if (removeTree)
6
- ctx.setStyle(':empty', null);
7
+ ctx.setStyle(':empty', initial);
7
8
  };
8
9
  }
9
10
  }
package/components/If.js CHANGED
@@ -1,18 +1,18 @@
1
1
  import { OneOfImpl } from './OneOf';
2
2
  export function If({ is, then, otherwise }) {
3
- return new OneOfImpl(is.map(v => v ? [1, true] : [2, false]), {
3
+ return new OneOfImpl(is.map(v => v ? { 1: true } : { 2: false }), {
4
4
  1: () => then,
5
5
  2: () => otherwise
6
6
  });
7
7
  }
8
8
  export function When({ is, children }) {
9
- return new OneOfImpl(is.map(v => v ? [1, true] : [2, false]), {
9
+ return new OneOfImpl(is.map(v => v ? { 1: true } : { 2: false }), {
10
10
  1: () => children,
11
11
  2: () => null
12
12
  });
13
13
  }
14
14
  export function Unless({ is, children }) {
15
- return new OneOfImpl(is.map(v => v ? [1, true] : [2, false]), {
15
+ return new OneOfImpl(is.map(v => v ? { 1: true } : { 2: false }), {
16
16
  1: () => null,
17
17
  2: () => children
18
18
  });
@@ -4,11 +4,11 @@ import { type Renderable } from '../renderable';
4
4
  export declare class LifecycleImpl implements Renderable {
5
5
  private readonly onMount;
6
6
  private readonly onUnmount;
7
- constructor(onMount: (el: HTMLElement) => void, onUnmount: (el: HTMLElement) => void);
7
+ constructor(onMount: (el: HTMLElement) => void, onUnmount: (el: HTMLElement, removeTree: boolean) => void);
8
8
  readonly appendTo: (ctx: DOMContext) => Clear;
9
9
  }
10
10
  export interface LifecycleProps {
11
11
  onMount?: (el: HTMLElement) => void;
12
- onUnmount?: (el: HTMLElement) => void;
12
+ onUnmount?: (el: HTMLElement, removeTree: boolean) => void;
13
13
  }
14
14
  export declare function Lifecycle({ onMount, onUnmount }: LifecycleProps): Renderable;
@@ -7,8 +7,8 @@ export class LifecycleImpl {
7
7
  }
8
8
  appendTo = (ctx) => {
9
9
  this.onMount(ctx.getElement());
10
- return () => {
11
- this.onUnmount(ctx.getElement());
10
+ return (removeTree) => {
11
+ this.onUnmount(ctx.getElement(), removeTree);
12
12
  };
13
13
  };
14
14
  }
@@ -1,10 +1,9 @@
1
1
  /** @jsxImportSource .. */
2
2
  import { type Signal } from '../prop';
3
- import { type AnyKey } from './OneOf';
4
3
  import { type JSX } from '../jsx-runtime';
5
4
  export interface NotEmptyProps<T> {
6
5
  on: Signal<T>;
7
6
  whenEmpty?: JSX.DOMNode;
8
7
  display: JSX.DOMNode;
9
8
  }
10
- export declare function NotEmpty<T extends unknown[] | Record<AnyKey, unknown>>({ on, display, whenEmpty }: NotEmptyProps<T>): JSX.DOMNode;
9
+ export declare function NotEmpty<T extends unknown[] | Record<any, unknown>>({ on, display, whenEmpty }: NotEmptyProps<T>): JSX.DOMNode;
@@ -1,11 +1,6 @@
1
1
  import { type Clear } from '../clean';
2
- import { type Renderable } from '../renderable';
3
- export declare class OnRemoveImpl implements Renderable {
4
- private readonly clear;
5
- constructor(clear: Clear);
6
- readonly appendTo: () => Clear;
7
- }
2
+ import { LifecycleImpl } from './Lifecycle';
8
3
  export interface OnRemoveProps {
9
4
  clear: Clear;
10
5
  }
11
- export declare function OnRemove(props: OnRemoveProps): OnRemoveImpl;
6
+ export declare function OnRemove(props: OnRemoveProps): LifecycleImpl;
@@ -1,14 +1,4 @@
1
- export class OnRemoveImpl {
2
- clear;
3
- constructor(clear) {
4
- this.clear = clear;
5
- }
6
- appendTo = () => {
7
- return (removeTree) => {
8
- this.clear(removeTree);
9
- };
10
- };
11
- }
1
+ import { LifecycleImpl } from './Lifecycle';
12
2
  export function OnRemove(props) {
13
- return new OnRemoveImpl(props.clear);
3
+ return new LifecycleImpl(() => { }, (_, removeTree) => props.clear(removeTree));
14
4
  }
@@ -3,18 +3,60 @@ import { type Clear } from '../clean';
3
3
  import { type DOMContext } from '../dom-context';
4
4
  import { type Renderable } from '../renderable';
5
5
  import { type JSX } from '../jsx';
6
- export type AnyKey = string | number | symbol;
7
- export declare class OneOfImpl<T extends [AnyKey, unknown]> implements Renderable {
6
+ export declare class OneOfImpl<T extends Record<string, unknown>> implements Renderable {
8
7
  private readonly match;
9
8
  private readonly cases;
10
9
  constructor(match: Signal<T>, cases: {
11
- [KK in T[0]]: (value: Signal<T[1]>) => JSX.DOMNode;
10
+ [KK in keyof T]: (value: Signal<T[KK]>) => JSX.DOMNode;
12
11
  });
13
12
  readonly appendTo: (ctx: DOMContext) => Clear;
14
13
  }
15
- export type OneOfProps<T extends [AnyKey, unknown]> = {
14
+ export type OneOfProps<T extends Record<string, unknown>> = {
16
15
  match: Signal<T>;
17
16
  } & {
18
- [KK in T[0]]: (value: Signal<T[1]>) => JSX.DOMNode;
17
+ [KK in keyof T]: (value: Signal<T[KK]>) => JSX.DOMNode;
19
18
  };
20
- export declare function OneOf<T extends [AnyKey, unknown]>(props: OneOfProps<T>): JSX.DOMNode;
19
+ export declare function OneOf<T extends Record<string, unknown>>(props: OneOfProps<T>): JSX.DOMNode;
20
+ export type OneOfLiteralProps<K extends string> = {
21
+ match: Signal<K>;
22
+ } & {
23
+ [KK in K]: JSX.DOMNode;
24
+ };
25
+ export declare function OneOfLiteral<K extends string>(props: OneOfLiteralProps<K>): JSX.DOMNode;
26
+ export type OneOfUnionProps<T extends {
27
+ [_ in K]: string;
28
+ }, K extends string> = {
29
+ match: Signal<T>;
30
+ using: K;
31
+ } & {
32
+ [KK in T[K]]: (value: Signal<T extends {
33
+ [_ in K]: KK;
34
+ } ? T : never>) => JSX.DOMNode;
35
+ };
36
+ export declare function OneOfUnion<T extends {
37
+ [_ in K]: string;
38
+ }, K extends string>(props: OneOfUnionProps<T, K>): JSX.DOMNode;
39
+ export type OneOfUnionTypeProps<T extends {
40
+ [_ in "type"]: string;
41
+ }> = {
42
+ match: Signal<T>;
43
+ } & {
44
+ [KK in T["type"]]: (value: Signal<T extends {
45
+ [_ in "type"]: KK;
46
+ } ? T : never>) => JSX.DOMNode;
47
+ };
48
+ export declare function OneOfUnionType<T extends {
49
+ type: string;
50
+ }>(props: OneOfUnionTypeProps<T>): JSX.DOMNode;
51
+ export type OneOfUnionKindProps<T extends {
52
+ [_ in "kind"]: string;
53
+ }> = {
54
+ match: Signal<T>;
55
+ } & {
56
+ [KK in T["kind"]]: (value: Signal<T extends {
57
+ [_ in "kind"]: KK;
58
+ } ? T : never>) => JSX.DOMNode;
59
+ };
60
+ export declare function OneOfUnionKind<T extends {
61
+ kind: string;
62
+ }>(props: OneOfUnionKindProps<T>): JSX.DOMNode;
@@ -9,16 +9,17 @@ export class OneOfImpl {
9
9
  }
10
10
  appendTo = (ctx) => {
11
11
  const pair = this.match.get();
12
- let key = pair[0];
13
- const value = pair[1];
14
- const stableCtx = ctx.makeReference();
12
+ let key = Object.keys(pair)[0];
13
+ const value = pair[key];
15
14
  let prop = new Prop(value);
16
- let newCtx = stableCtx.makeReference();
15
+ let newCtx = ctx.makeReference();
17
16
  let clear = makeRenderable(this.cases[key](prop)).appendTo(newCtx);
18
- const cancel = this.match.subscribe(([newKey, newValue]) => {
17
+ const cancel = this.match.subscribe((newPair) => {
18
+ const newKey = Object.keys(newPair)[0];
19
+ const newValue = newPair[newKey];
19
20
  if (newKey !== key) {
20
21
  newCtx.requestClear(true, () => {
21
- newCtx = stableCtx.makeReference();
22
+ newCtx = newCtx.makeReference();
22
23
  key = newKey;
23
24
  prop.clean();
24
25
  prop = new Prop(newValue);
@@ -32,14 +33,33 @@ export class OneOfImpl {
32
33
  });
33
34
  return (removeTree) => {
34
35
  newCtx.requestClear(removeTree, () => {
36
+ clear(removeTree);
35
37
  cancel();
36
38
  prop.clean();
37
- stableCtx.requestClear(removeTree, () => { });
38
39
  });
39
40
  };
40
41
  };
41
42
  }
42
- // <OneOf match={counter.map(v => v % 2 == 0 ? [1, "odd"] : [2, "even"])} 1={t => <b>{t}</b>} 2={t => <i>{t}</i>} /
43
+ // <OneOf match={counter.map(v => v % 2 == 0 ? {1: "odd"} : {2: "even"})} 1={t => <b>{t}</b>} 2={t => <i>{t}</i>} /
43
44
  export function OneOf(props) {
44
45
  return new OneOfImpl(props.match, props);
45
46
  }
47
+ export function OneOfLiteral(props) {
48
+ const { match, ...cases } = props;
49
+ const keys = Object.keys(cases);
50
+ const obj = keys.reduce((acc, k) => {
51
+ acc[k] = () => cases[k];
52
+ return acc;
53
+ }, {});
54
+ return new OneOfImpl(match.map(k => ({ [k]: null })), obj);
55
+ }
56
+ export function OneOfUnion(props) {
57
+ const { match, using, ...cases } = props;
58
+ return new OneOfImpl(match.map(t => ({ [t[using]]: t })), cases);
59
+ }
60
+ export function OneOfUnionType(props) {
61
+ return OneOfUnion({ ...props, using: "type" });
62
+ }
63
+ export function OneOfUnionKind(props) {
64
+ return OneOfUnion({ ...props, using: "kind" });
65
+ }
@@ -1,15 +1,26 @@
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;
26
+ export declare function conjuctions(other: JSX.DOMNode, lastConjunction?: JSX.DOMNode, firstConjunction?: JSX.DOMNode): (sep: Signal<SeparatorProps>) => JSX.DOMNode;
@@ -1,33 +1,118 @@
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
+ }
104
+ export function conjuctions(other, lastConjunction, firstConjunction) {
105
+ return (sep) => {
106
+ return sep.map(({ first, last }) => {
107
+ if (last) {
108
+ return lastConjunction ?? other;
109
+ }
110
+ else if (first) {
111
+ return firstConjunction ?? other;
112
+ }
113
+ else {
114
+ return other;
115
+ }
116
+ });
117
+ };
118
+ }
@@ -23,8 +23,15 @@ export interface Animatable {
23
23
  marginRight?: number;
24
24
  fontSize?: number;
25
25
  letterSpacing?: number;
26
+ color?: string;
27
+ backgroundColor?: string;
28
+ borderColor?: string;
26
29
  borderWidth?: number;
27
30
  borderRadius?: number;
31
+ boxShadow?: string;
32
+ textShadow?: string;
33
+ outlineWidth?: number;
34
+ outlineColor?: string;
28
35
  translateX?: number;
29
36
  translateY?: number;
30
37
  translateZ?: number;
@@ -45,6 +52,19 @@ export interface Animatable {
45
52
  contrast?: number;
46
53
  blur?: number;
47
54
  }
55
+ export type ColorChannels = [number, number, number, number, 'rgba' | 'hex' | 'hsla'];
56
+ export declare function parseColorChannels(color: string): ColorChannels;
57
+ export interface BoxShadow {
58
+ inset: boolean;
59
+ x: number;
60
+ y: number;
61
+ blur: number;
62
+ spread: number;
63
+ color: string;
64
+ }
65
+ export declare function colorChannelsToString(channels: ColorChannels): string;
66
+ export declare function interpolateColor(startColor: string, endColor: string): (t: number) => string;
67
+ export declare function interpolateShadow(startShadow: string, endShadow: string): (t: number) => string;
48
68
  export declare function getComputedAnimatableProp(styles: CSSStyleDeclaration, key: keyof Animatable): Animatable[typeof key];
49
69
  export declare function getComputedAnimatable(el: HTMLElement, styles: Animatable): Animatable;
50
70
  export declare function applyAnimatableProp(el: HTMLElement, key: keyof Animatable, value: Animatable[typeof key]): void;
@@ -1,3 +1,104 @@
1
+ export function parseColorChannels(color) {
2
+ let match = color.match(/rgba?\((\d+), (\d+), (\d+)(, (\d+))?\)/);
3
+ if (match) {
4
+ return [
5
+ Number(match[1]),
6
+ Number(match[2]),
7
+ Number(match[3]),
8
+ match[4] ? Number(match[5]) : 1,
9
+ 'rgba'
10
+ ];
11
+ }
12
+ else {
13
+ match = color.match(/#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})/);
14
+ if (match) {
15
+ return [
16
+ parseInt(match[1], 16),
17
+ parseInt(match[2], 16),
18
+ parseInt(match[3], 16),
19
+ 1,
20
+ 'hex'
21
+ ];
22
+ }
23
+ else {
24
+ match = color.match(/hsla?\((\d+), (\d+)%?, (\d+)%?(, (\d+))?\)/);
25
+ if (match) {
26
+ return [
27
+ Number(match[1]),
28
+ Number(match[2]),
29
+ Number(match[3]),
30
+ match[4] ? Number(match[5]) : 1,
31
+ 'hsla'
32
+ ];
33
+ }
34
+ }
35
+ }
36
+ return [0, 0, 0, 1, 'rgba'];
37
+ }
38
+ function parseBoxShadow(cssString) {
39
+ const boxShadowRegex = /^(inset\s)?(-?\d+)([a-zA-Z]*)(\s+)(-?\d+)([a-zA-Z]*)(?:\s+(-?\d+)([a-zA-Z]*))?(?:\s+(-?\d+)([a-zA-Z]*))?(?:\s+(-?\d+)([a-zA-Z]*))?(?:\s+)([a-zA-Z0-9(),.]+)$/i;
40
+ const match = cssString.match(boxShadowRegex);
41
+ if (!match) {
42
+ return {
43
+ inset: false,
44
+ x: 0,
45
+ y: 0,
46
+ blur: 0,
47
+ spread: 0,
48
+ color: 'rgba(0, 0, 0, 0)'
49
+ };
50
+ }
51
+ const [, inset, x, , , y, , blur, , spread, , color] = match;
52
+ const parsedBlur = blur ? parseInt(blur, 10) : 0;
53
+ const parsedSpread = spread ? parseInt(spread, 10) : 0;
54
+ return {
55
+ inset: !!inset,
56
+ x: parseInt(x, 10),
57
+ y: parseInt(y, 10),
58
+ blur: parsedBlur,
59
+ spread: parsedSpread,
60
+ color
61
+ };
62
+ }
63
+ function boxShadowToString(shadow) {
64
+ const { inset, x, y, blur, spread, color } = shadow;
65
+ return `${inset ? 'inset ' : ''}${x}px ${y}px ${blur}px ${spread}px ${color}`;
66
+ }
67
+ export function colorChannelsToString(channels) {
68
+ if (channels[4] === 'rgba') {
69
+ return `rgba(${channels[0]}, ${channels[1]}, ${channels[2]}, ${channels[3]})`;
70
+ }
71
+ else if (channels[4] === 'hex') {
72
+ return `#${channels[0].toString(16).padStart(2, '0')}${channels[1].toString(16).padStart(2, '0')}${channels[2].toString(16).padStart(2, '0')}`;
73
+ }
74
+ else if (channels[4] === 'hsla') {
75
+ return `hsla(${channels[0]}, ${channels[1]}%, ${channels[2]}%, ${channels[3]})`;
76
+ }
77
+ return '';
78
+ }
79
+ export function interpolateColor(startColor, endColor) {
80
+ const [startR, startG, startB, startA, startType] = parseColorChannels(startColor);
81
+ const [endR, endG, endB, endA, endType] = parseColorChannels(endColor);
82
+ return (t) => {
83
+ const r = startR + (endR - startR) * t;
84
+ const g = startG + (endG - startG) * t;
85
+ const b = startB + (endB - startB) * t;
86
+ const a = startA + (endA - startA) * t;
87
+ return colorChannelsToString([r, g, b, a, startType]);
88
+ };
89
+ }
90
+ export function interpolateShadow(startShadow, endShadow) {
91
+ const start = parseBoxShadow(startShadow);
92
+ const end = parseBoxShadow(endShadow);
93
+ return (t) => {
94
+ const x = start.x + (end.x - start.x) * t;
95
+ const y = start.y + (end.y - start.y) * t;
96
+ const blur = start.blur + (end.blur - start.blur) * t;
97
+ const spread = start.spread + (end.spread - start.spread) * t;
98
+ const color = getColorInterpolation(start.color, end.color)(t);
99
+ return boxShadowToString({ inset: start.inset, x, y, blur, spread, color });
100
+ };
101
+ }
1
102
  export function getComputedAnimatableProp(styles, key) {
2
103
  if (key === 'translateX') {
3
104
  return new WebKitCSSMatrix(styles.transform).m41;
@@ -131,10 +232,35 @@ export function applyAnimatableProp(el, key, value) {
131
232
  }
132
233
  el.style.setProperty(key, String(value));
133
234
  }
235
+ const interpolationCache = new Map();
236
+ function getInterpolate(from, to, type) {
237
+ if (interpolationCache.has(type + ":" + from + to)) {
238
+ return interpolationCache.get(from + to);
239
+ }
240
+ const f = interpolateColor(from, to);
241
+ interpolationCache.set(type + ":" + from + to, f);
242
+ return f;
243
+ }
244
+ function getColorInterpolation(from, to) {
245
+ return getInterpolate(from, to, 'c');
246
+ }
247
+ function getShadowInterpolation(from, to) {
248
+ return getInterpolate(from, to, 's');
249
+ }
134
250
  export function applyInterpolatedAnimatableProp(el, key, from, to, progress) {
135
251
  if (from != null && to != null) {
136
- const value = from + (to - from) * progress;
137
- applyAnimatableProp(el, key, value);
252
+ if (typeof from === 'number' && typeof to === 'number') {
253
+ const value = from + (to - from) * progress;
254
+ applyAnimatableProp(el, key, value);
255
+ }
256
+ else if (key === 'boxShadow' || key === 'textShadow') {
257
+ const value = getShadowInterpolation(from, to)(progress);
258
+ applyAnimatableProp(el, key, value);
259
+ }
260
+ else if (key === 'color' || key === 'backgroundColor' || key === 'borderColor' || key === 'outlineColor') {
261
+ const value = getColorInterpolation(from, to)(progress);
262
+ applyAnimatableProp(el, key, value);
263
+ }
138
264
  }
139
265
  }
140
266
  export function applyInterpolatedAnimatable(el, from, to, progress) {
package/dom-context.d.ts CHANGED
@@ -33,6 +33,7 @@ export declare class DOMContext {
33
33
  private clear;
34
34
  withProvider<T>(mark: ProviderMark<T>, provider: T): DOMContext;
35
35
  getProvider<T>(mark: ProviderMark<T>): T;
36
+ getStyle(name: string): string | undefined | null;
36
37
  setStyle(name: string, value: string | undefined | null): void;
37
38
  createStyle(name: string, value: string | undefined | null): [(newValue: string) => void, Clear];
38
39
  }
package/dom-context.js CHANGED
@@ -235,6 +235,9 @@ export class DOMContext {
235
235
  getProvider(mark) {
236
236
  return this.providers[mark];
237
237
  }
238
+ getStyle(name) {
239
+ return this.element.style.getPropertyValue(name);
240
+ }
238
241
  setStyle(name, value) {
239
242
  if (value == null) {
240
243
  this.element.style.removeProperty(name);
package/index.d.ts CHANGED
@@ -1,36 +1,33 @@
1
- import { type Clear, type Clean } from './clean';
2
- import { makeProviderMark, DOMContext, type ProviderMark } from './dom-context';
3
- import { Prop, Signal } from './prop';
4
- import { render } from './render';
5
- import { type Renderable } from './renderable';
6
- import { isEmptyElement } from './helpers/is-empty-element';
7
- import { handleTextInput } from './helpers/handle-text-input';
8
- import { handleAnchorClick } from './helpers/handle-anchor-click';
9
- import { Animatable, applyInterpolatedAnimatableProp, applyInterpolatedAnimatable, applyAnimatableProp, applyAnimatable, getComputedAnimatableProp, getComputedAnimatable } from './components/animatable';
10
- import { AttributeImpl, Attribute, type AttributeProps } from './components/Attribute';
11
- import { BooleanAttributeImpl, BooleanAttribute, type BooleanAttributeProps } from './components/BooleanAttribute';
12
- import { ClassNameImpl, ClassName, type ClassNameProps } from './components/ClassName';
13
- import { ConsumerImpl, Consumer, type ConsumerProps, ProviderImpl, Provider, type ProviderProps } from './components/Provider';
14
- import { ElImpl, El, type ElProps } from './components/El';
15
- import { FadeIn, FadeInImpl, FadeInProps } from './components/FadeIn';
16
- import { FadeOut, FadeOutImpl, FadeOutProps } from './components/FadeOut';
17
- import { For, type ForProps } from './components/For';
18
- import { FragmentImpl, Fragment } from './components/Fragment';
19
- import { HiddenWhenEmptyImpl, HiddenWhenEmpty } from './components/HiddenWhenEmpty';
20
- import { If, type IfProps, Unless, When, type WhenProps } from './components/If';
21
- import { InnerHTMLImpl, InnerHTML, type InnerHTMLProps } from './components/InnerHTML';
22
- import { Lifecycle, LifecycleImpl, type LifecycleProps } from './components/Lifecycle';
23
- import { MatchImpl, Match, type MatchProps } from './components/Match';
24
- import { NotEmpty, type NotEmptyProps } from './components/NotEmpty';
25
- import { OnImpl, On, type OnProps } from './components/On';
26
- import { OnRemoveImpl, OnRemove, type OnRemoveProps } from './components/OnRemove';
27
- import { OneOfImpl, OneOf, type OneOfProps } from './components/OneOf';
28
- import { PortalImpl, Portal, type PortalProps } from './components/Portal';
29
- import { PropertyImpl, Property, type PropertyProps } from './components/Property';
30
- import { RepeatImpl, Repeat, type RepeatProps } from './components/Repeat';
31
- import { ShowImpl, Show, type ShowProps } from './components/Show';
32
- import { TextImpl, Text, type TextProps } from './components/Text';
33
- import { TextContentImpl, TextContent, type TextContentProps } from './components/TextContent';
34
- import type { JSX } from './jsx-runtime';
35
- export { applyInterpolatedAnimatableProp, applyInterpolatedAnimatable, applyAnimatableProp, applyAnimatable, getComputedAnimatableProp, getComputedAnimatable, AttributeImpl, Attribute, BooleanAttributeImpl, BooleanAttribute, ClassNameImpl, ClassName, ConsumerImpl, Consumer, DOMContext, ElImpl, El, FadeIn, FadeInImpl, FadeOut, FadeOutImpl, For, FragmentImpl, Fragment, handleTextInput, handleAnchorClick, HiddenWhenEmptyImpl, HiddenWhenEmpty, If, InnerHTMLImpl, InnerHTML, isEmptyElement, Lifecycle, LifecycleImpl, makeProviderMark, MatchImpl, Match, NotEmpty, OnImpl, On, OnRemoveImpl, OnRemove, OneOfImpl, OneOf, PortalImpl, Portal, Prop, PropertyImpl, Property, ProviderImpl, Provider, render, RepeatImpl, Repeat, ShowImpl, Show, Signal, TextImpl, Text, TextContentImpl, TextContent, Unless, When };
36
- export type { AttributeProps, BooleanAttributeProps, ClassNameProps, Clean, Clear, ConsumerProps, ElProps, FadeInProps, FadeOutProps, ForProps, InnerHTMLProps, IfProps, JSX, LifecycleProps, MatchProps, NotEmptyProps, OnProps, OnRemoveProps, OneOfProps, PortalProps, PropertyProps, ProviderMark, ProviderProps, Renderable, RepeatProps, ShowProps, TextProps, TextContentProps, Animatable, WhenProps };
1
+ export { type Clear, type Clean } from './clean';
2
+ export { makeProviderMark, DOMContext, type ProviderMark } from './dom-context';
3
+ export { Prop, Signal } from './prop';
4
+ export { render } from './render';
5
+ export { type Renderable } from './renderable';
6
+ export { isEmptyElement } from './helpers/is-empty-element';
7
+ export { handleTextInput } from './helpers/handle-text-input';
8
+ export { handleAnchorClick } from './helpers/handle-anchor-click';
9
+ export { type Animatable, applyInterpolatedAnimatableProp, applyInterpolatedAnimatable, applyAnimatableProp, applyAnimatable, getComputedAnimatableProp, getComputedAnimatable } from './components/animatable';
10
+ export { AttributeImpl, Attribute, type AttributeProps } from './components/Attribute';
11
+ export { BooleanAttributeImpl, BooleanAttribute, type BooleanAttributeProps } from './components/BooleanAttribute';
12
+ export { ClassNameImpl, ClassName, type ClassNameProps } from './components/ClassName';
13
+ export { ConsumerImpl, Consumer, type ConsumerProps, ProviderImpl, Provider, type ProviderProps } from './components/Provider';
14
+ export { ElImpl, El, type ElProps } from './components/El';
15
+ export { FadeIn, FadeInImpl, type FadeInProps } from './components/FadeIn';
16
+ export { FadeOut, FadeOutImpl, type FadeOutProps } from './components/FadeOut';
17
+ export { For, type ForProps } from './components/For';
18
+ export { FragmentImpl, Fragment } from './components/Fragment';
19
+ export { HiddenWhenEmptyImpl, HiddenWhenEmpty } from './components/HiddenWhenEmpty';
20
+ export { If, type IfProps, Unless, When, type WhenProps } from './components/If';
21
+ export { InnerHTMLImpl, InnerHTML, type InnerHTMLProps } from './components/InnerHTML';
22
+ export { Lifecycle, LifecycleImpl, type LifecycleProps } from './components/Lifecycle';
23
+ export { NotEmpty, type NotEmptyProps } from './components/NotEmpty';
24
+ export { OnImpl, On, type OnProps } from './components/On';
25
+ export { OnRemove, type OnRemoveProps } from './components/OnRemove';
26
+ export { OneOfImpl, OneOf, type OneOfProps, OneOfLiteral, type OneOfLiteralProps, OneOfUnion, type OneOfUnionProps, OneOfUnionKind, type OneOfUnionKindProps, OneOfUnionType, type OneOfUnionTypeProps } from './components/OneOf';
27
+ export { PortalImpl, Portal, type PortalProps } from './components/Portal';
28
+ export { PropertyImpl, Property, type PropertyProps } from './components/Property';
29
+ export { RepeatImpl, Repeat, type RepeatProps, conjuctions } from './components/Repeat';
30
+ export { ShowImpl, Show, type ShowProps } from './components/Show';
31
+ export { TextImpl, Text, type TextProps } from './components/Text';
32
+ export { TextContentImpl, TextContent, type TextContentProps } from './components/TextContent';
33
+ export type { JSX } from './jsx-runtime';
package/index.js CHANGED
@@ -1,32 +1,30 @@
1
- import { makeProviderMark, DOMContext } from './dom-context';
2
- import { Prop, Signal } from './prop';
3
- import { render } from './render';
4
- import { isEmptyElement } from './helpers/is-empty-element';
5
- import { handleTextInput } from './helpers/handle-text-input';
6
- import { handleAnchorClick } from './helpers/handle-anchor-click';
7
- import { applyInterpolatedAnimatableProp, applyInterpolatedAnimatable, applyAnimatableProp, applyAnimatable, getComputedAnimatableProp, getComputedAnimatable } from './components/animatable';
8
- import { AttributeImpl, Attribute } from './components/Attribute';
9
- import { BooleanAttributeImpl, BooleanAttribute } from './components/BooleanAttribute';
10
- import { ClassNameImpl, ClassName } from './components/ClassName';
11
- import { ConsumerImpl, Consumer, ProviderImpl, Provider } from './components/Provider';
12
- import { ElImpl, El } from './components/El';
13
- import { FadeIn, FadeInImpl } from './components/FadeIn';
14
- import { FadeOut, FadeOutImpl } from './components/FadeOut';
15
- import { For } from './components/For';
16
- import { FragmentImpl, Fragment } from './components/Fragment';
17
- import { HiddenWhenEmptyImpl, HiddenWhenEmpty } from './components/HiddenWhenEmpty';
18
- import { If, Unless, When } from './components/If';
19
- import { InnerHTMLImpl, InnerHTML } from './components/InnerHTML';
20
- import { Lifecycle, LifecycleImpl } from './components/Lifecycle';
21
- import { MatchImpl, Match } from './components/Match';
22
- import { NotEmpty } from './components/NotEmpty';
23
- import { OnImpl, On } from './components/On';
24
- import { OnRemoveImpl, OnRemove } from './components/OnRemove';
25
- import { OneOfImpl, OneOf } from './components/OneOf';
26
- import { PortalImpl, Portal } from './components/Portal';
27
- import { PropertyImpl, Property } from './components/Property';
28
- import { RepeatImpl, Repeat } from './components/Repeat';
29
- import { ShowImpl, Show } from './components/Show';
30
- import { TextImpl, Text } from './components/Text';
31
- import { TextContentImpl, TextContent } from './components/TextContent';
32
- export { applyInterpolatedAnimatableProp, applyInterpolatedAnimatable, applyAnimatableProp, applyAnimatable, getComputedAnimatableProp, getComputedAnimatable, AttributeImpl, Attribute, BooleanAttributeImpl, BooleanAttribute, ClassNameImpl, ClassName, ConsumerImpl, Consumer, DOMContext, ElImpl, El, FadeIn, FadeInImpl, FadeOut, FadeOutImpl, For, FragmentImpl, Fragment, handleTextInput, handleAnchorClick, HiddenWhenEmptyImpl, HiddenWhenEmpty, If, InnerHTMLImpl, InnerHTML, isEmptyElement, Lifecycle, LifecycleImpl, makeProviderMark, MatchImpl, Match, NotEmpty, OnImpl, On, OnRemoveImpl, OnRemove, OneOfImpl, OneOf, PortalImpl, Portal, Prop, PropertyImpl, Property, ProviderImpl, Provider, render, RepeatImpl, Repeat, ShowImpl, Show, Signal, TextImpl, Text, TextContentImpl, TextContent, Unless, When };
1
+ export { makeProviderMark, DOMContext } from './dom-context';
2
+ export { Prop, Signal } from './prop';
3
+ export { render } from './render';
4
+ export { isEmptyElement } from './helpers/is-empty-element';
5
+ export { handleTextInput } from './helpers/handle-text-input';
6
+ export { handleAnchorClick } from './helpers/handle-anchor-click';
7
+ export { applyInterpolatedAnimatableProp, applyInterpolatedAnimatable, applyAnimatableProp, applyAnimatable, getComputedAnimatableProp, getComputedAnimatable } from './components/animatable';
8
+ export { AttributeImpl, Attribute } from './components/Attribute';
9
+ export { BooleanAttributeImpl, BooleanAttribute } from './components/BooleanAttribute';
10
+ export { ClassNameImpl, ClassName } from './components/ClassName';
11
+ export { ConsumerImpl, Consumer, ProviderImpl, Provider } from './components/Provider';
12
+ export { ElImpl, El } from './components/El';
13
+ export { FadeIn, FadeInImpl } from './components/FadeIn';
14
+ export { FadeOut, FadeOutImpl } from './components/FadeOut';
15
+ export { For } from './components/For';
16
+ export { FragmentImpl, Fragment } from './components/Fragment';
17
+ export { HiddenWhenEmptyImpl, HiddenWhenEmpty } from './components/HiddenWhenEmpty';
18
+ export { If, Unless, When } from './components/If';
19
+ export { InnerHTMLImpl, InnerHTML } from './components/InnerHTML';
20
+ export { Lifecycle, LifecycleImpl } from './components/Lifecycle';
21
+ export { NotEmpty } from './components/NotEmpty';
22
+ export { OnImpl, On } from './components/On';
23
+ export { OnRemove } from './components/OnRemove';
24
+ export { OneOfImpl, OneOf, OneOfLiteral, OneOfUnion, OneOfUnionKind, OneOfUnionType } from './components/OneOf';
25
+ export { PortalImpl, Portal } from './components/Portal';
26
+ export { PropertyImpl, Property } from './components/Property';
27
+ export { RepeatImpl, Repeat, conjuctions } from './components/Repeat';
28
+ export { ShowImpl, Show } from './components/Show';
29
+ export { TextImpl, Text } from './components/Text';
30
+ export { TextContentImpl, TextContent } from './components/TextContent';
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": "6.0.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) {