@tempots/dom 5.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.
- package/.eslintignore +10 -0
- package/.eslintrc.cjs +28 -0
- package/coverage/clover.xml +39 -0
- package/coverage/coverage-final.json +2 -0
- package/coverage/lcov-report/OneOf.ts.html +256 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/dom-context.ts.html +928 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +116 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/prop.ts.html +691 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +196 -0
- package/coverage/lcov-report/src/components/Attribute.ts.html +154 -0
- package/coverage/lcov-report/src/components/BooleanAttribute.ts.html +154 -0
- package/coverage/lcov-report/src/components/ClassName.ts.html +151 -0
- package/coverage/lcov-report/src/components/El.ts.html +166 -0
- package/coverage/lcov-report/src/components/FadeIn.ts.html +262 -0
- package/coverage/lcov-report/src/components/FadeOut.ts.html +226 -0
- package/coverage/lcov-report/src/components/For.ts.html +151 -0
- package/coverage/lcov-report/src/components/Fragment.ts.html +142 -0
- package/coverage/lcov-report/src/components/HiddenWhenEmpty.ts.html +133 -0
- package/coverage/lcov-report/src/components/If.ts.html +217 -0
- package/coverage/lcov-report/src/components/InnerHTML.ts.html +178 -0
- package/coverage/lcov-report/src/components/Lifecycle.ts.html +157 -0
- package/coverage/lcov-report/src/components/Match.ts.html +286 -0
- package/coverage/lcov-report/src/components/NotEmpty.tsx.html +184 -0
- package/coverage/lcov-report/src/components/On.ts.html +151 -0
- package/coverage/lcov-report/src/components/OnRemove.ts.html +145 -0
- package/coverage/lcov-report/src/components/OneOf.ts.html +256 -0
- package/coverage/lcov-report/src/components/Portal.ts.html +175 -0
- package/coverage/lcov-report/src/components/Property.ts.html +154 -0
- package/coverage/lcov-report/src/components/Provider.ts.html +244 -0
- package/coverage/lcov-report/src/components/Repeat.ts.html +223 -0
- package/coverage/lcov-report/src/components/Show.tsx.html +190 -0
- package/coverage/lcov-report/src/components/Text.ts.html +229 -0
- package/coverage/lcov-report/src/components/TextContent.ts.html +178 -0
- package/coverage/lcov-report/src/components/Tween.tsx.html +943 -0
- package/coverage/lcov-report/src/components/animatable.ts.html +619 -0
- package/coverage/lcov-report/src/components/index.html +476 -0
- package/coverage/lcov-report/src/dom-context.ts.html +928 -0
- package/coverage/lcov-report/src/helpers/handle-anchor-click.ts.html +277 -0
- package/coverage/lcov-report/src/helpers/handle-text-input.ts.html +100 -0
- package/coverage/lcov-report/src/helpers/index.html +146 -0
- package/coverage/lcov-report/src/helpers/is-empty-element.ts.html +112 -0
- package/coverage/lcov-report/src/index.html +176 -0
- package/coverage/lcov-report/src/index.ts.html +412 -0
- package/coverage/lcov-report/src/jsx-runtime.ts.html +601 -0
- package/coverage/lcov-report/src/prop.ts.html +691 -0
- package/coverage/lcov-report/src/render.ts.html +112 -0
- package/coverage/lcov-report/src/types/idom-context.ts.html +184 -0
- package/coverage/lcov-report/src/types/index.html +116 -0
- package/coverage/lcov-report/test/common.ts.html +112 -0
- package/coverage/lcov-report/test/index.html +116 -0
- package/coverage/lcov.info +57 -0
- package/dist/index.js +32 -0
- package/jest.config.js +5 -0
- package/package.json +39 -0
- package/src/clean.ts +2 -0
- package/src/components/Attribute.ts +23 -0
- package/src/components/BooleanAttribute.ts +23 -0
- package/src/components/ClassName.ts +22 -0
- package/src/components/El.ts +27 -0
- package/src/components/FadeIn.ts +59 -0
- package/src/components/FadeOut.ts +47 -0
- package/src/components/For.ts +22 -0
- package/src/components/Fragment.ts +19 -0
- package/src/components/HiddenWhenEmpty.ts +16 -0
- package/src/components/If.ts +44 -0
- package/src/components/InnerHTML.ts +31 -0
- package/src/components/Lifecycle.ts +24 -0
- package/src/components/Match.ts +67 -0
- package/src/components/NotEmpty.tsx +33 -0
- package/src/components/On.ts +22 -0
- package/src/components/OnRemove.ts +20 -0
- package/src/components/OneOf.ts +57 -0
- package/src/components/Portal.ts +30 -0
- package/src/components/Property.ts +23 -0
- package/src/components/Provider.ts +53 -0
- package/src/components/Repeat.ts +46 -0
- package/src/components/Show.tsx +35 -0
- package/src/components/Text.ts +48 -0
- package/src/components/TextContent.ts +31 -0
- package/src/components/animatable.ts +178 -0
- package/src/dom-context.ts +281 -0
- package/src/helpers/handle-anchor-click.ts +64 -0
- package/src/helpers/handle-text-input.ts +5 -0
- package/src/helpers/is-empty-element.ts +9 -0
- package/src/index.ts +109 -0
- package/src/jsx-dev-runtime.ts +8 -0
- package/src/jsx-runtime.ts +172 -0
- package/src/jsx.ts +1046 -0
- package/src/prop.ts +202 -0
- package/src/render.ts +9 -0
- package/src/renderable.ts +6 -0
- package/test/common.ts +9 -0
- package/test/component.spec.tsx +27 -0
- package/test/domcontext.spec.ts +36 -0
- package/test/fadein.spec.tsx +36 -0
- package/test/fadeout.spec.tsx +41 -0
- package/test/if.spec.tsx +30 -0
- package/test/innerhtml.spec.tsx +45 -0
- package/test/prop.spec.ts +10 -0
- package/test/render.spec.tsx +19 -0
- package/test/textcontent.spec.tsx +45 -0
- package/test/when.spec.tsx +30 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
TN:
|
|
2
|
+
SF:src/components/OneOf.ts
|
|
3
|
+
FN:10,(anonymous_0)
|
|
4
|
+
FN:16,(anonymous_1)
|
|
5
|
+
FN:24,(anonymous_2)
|
|
6
|
+
FN:26,(anonymous_3)
|
|
7
|
+
FN:38,(anonymous_4)
|
|
8
|
+
FN:39,(anonymous_5)
|
|
9
|
+
FN:42,(anonymous_6)
|
|
10
|
+
FN:55,OneOf
|
|
11
|
+
FNF:8
|
|
12
|
+
FNH:4
|
|
13
|
+
FNDA:7,(anonymous_0)
|
|
14
|
+
FNDA:7,(anonymous_1)
|
|
15
|
+
FNDA:7,(anonymous_2)
|
|
16
|
+
FNDA:7,(anonymous_3)
|
|
17
|
+
FNDA:0,(anonymous_4)
|
|
18
|
+
FNDA:0,(anonymous_5)
|
|
19
|
+
FNDA:0,(anonymous_6)
|
|
20
|
+
FNDA:0,OneOf
|
|
21
|
+
DA:1,8
|
|
22
|
+
DA:6,8
|
|
23
|
+
DA:9,8
|
|
24
|
+
DA:11,7
|
|
25
|
+
DA:12,7
|
|
26
|
+
DA:16,7
|
|
27
|
+
DA:17,7
|
|
28
|
+
DA:18,7
|
|
29
|
+
DA:19,7
|
|
30
|
+
DA:20,7
|
|
31
|
+
DA:21,7
|
|
32
|
+
DA:22,7
|
|
33
|
+
DA:23,7
|
|
34
|
+
DA:24,7
|
|
35
|
+
DA:25,7
|
|
36
|
+
DA:26,7
|
|
37
|
+
DA:27,7
|
|
38
|
+
DA:28,7
|
|
39
|
+
DA:29,7
|
|
40
|
+
DA:30,7
|
|
41
|
+
DA:31,7
|
|
42
|
+
DA:32,7
|
|
43
|
+
DA:35,0
|
|
44
|
+
DA:38,7
|
|
45
|
+
DA:39,0
|
|
46
|
+
DA:40,0
|
|
47
|
+
DA:41,0
|
|
48
|
+
DA:42,0
|
|
49
|
+
DA:55,8
|
|
50
|
+
DA:56,0
|
|
51
|
+
LF:30
|
|
52
|
+
LH:24
|
|
53
|
+
BRDA:25,0,0,7
|
|
54
|
+
BRDA:25,0,1,0
|
|
55
|
+
BRF:2
|
|
56
|
+
BRH:1
|
|
57
|
+
end_of_record
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
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 };
|
package/jest.config.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tempots/dom",
|
|
3
|
+
"version": "5.0.0",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"watch": "tsc --watch",
|
|
7
|
+
"build": "tsc",
|
|
8
|
+
"test": "jest",
|
|
9
|
+
"test:watch": "jest --watch",
|
|
10
|
+
"coverage": "jest --coverage",
|
|
11
|
+
"npm:publish": "yarn publish --access public"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@happy-dom/jest-environment": "^9.1.7",
|
|
16
|
+
"@types/jest": "^29.5.0",
|
|
17
|
+
"@types/node": "^18.15.11",
|
|
18
|
+
"@typescript-eslint/eslint-plugin": "^5.43.0",
|
|
19
|
+
"eslint": "^8.0.1",
|
|
20
|
+
"eslint-config-standard-with-typescript": "^34.0.1",
|
|
21
|
+
"eslint-plugin-import": "^2.25.2",
|
|
22
|
+
"eslint-plugin-n": "^15.0.0",
|
|
23
|
+
"eslint-plugin-promise": "^6.0.0",
|
|
24
|
+
"happy-dom": "^9.1.0",
|
|
25
|
+
"jest": "^29.5.0",
|
|
26
|
+
"ts-jest": "^29.1.0",
|
|
27
|
+
"typescript": "^5.0.3"
|
|
28
|
+
},
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/fponticelli/tempots-dom.git"
|
|
32
|
+
},
|
|
33
|
+
"author": "Franco Ponticelli",
|
|
34
|
+
"license": "Apache-2.0",
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/fponticelli/tempots-dom/issues"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://github.com/fponticelli/tempots-dom#readme"
|
|
39
|
+
}
|
package/src/clean.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type DOMContext } from '../dom-context'
|
|
2
|
+
import { type Signal } from '../prop'
|
|
3
|
+
import { type Clear } from '../clean'
|
|
4
|
+
import { type Renderable } from '../renderable'
|
|
5
|
+
import { subscribeToSignal } from './Text'
|
|
6
|
+
|
|
7
|
+
export class AttributeImpl implements Renderable {
|
|
8
|
+
constructor(private readonly name: string, private readonly value: Signal<string>) { }
|
|
9
|
+
|
|
10
|
+
readonly appendTo = (ctx: DOMContext): Clear => {
|
|
11
|
+
const [set, clear] = ctx.createAttribute(this.name, this.value.get())
|
|
12
|
+
return subscribeToSignal(this.value, set, clear)
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface AttributeProps {
|
|
17
|
+
name: string
|
|
18
|
+
value: Signal<string>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function Attribute({ name, value }: AttributeProps): Renderable {
|
|
22
|
+
return new AttributeImpl(name, value)
|
|
23
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type DOMContext } from '../dom-context'
|
|
2
|
+
import { type Signal } from '../prop'
|
|
3
|
+
import { type Clear } from '../clean'
|
|
4
|
+
import { type Renderable } from '../renderable'
|
|
5
|
+
import { subscribeToSignal } from './Text'
|
|
6
|
+
|
|
7
|
+
export class BooleanAttributeImpl implements Renderable {
|
|
8
|
+
constructor(private readonly name: string, private readonly value: Signal<boolean>) { }
|
|
9
|
+
|
|
10
|
+
readonly appendTo = (ctx: DOMContext): Clear => {
|
|
11
|
+
const [set, clear] = ctx.createBooleanAttribute(this.name, this.value.get())
|
|
12
|
+
return subscribeToSignal(this.value, set, clear)
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface BooleanAttributeProps {
|
|
17
|
+
name: string
|
|
18
|
+
value: Signal<boolean>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function BooleanAttribute({ name, value }: BooleanAttributeProps): Renderable {
|
|
22
|
+
return new BooleanAttributeImpl(name, value)
|
|
23
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type Signal } from '../prop'
|
|
2
|
+
import { type Renderable } from '../renderable'
|
|
3
|
+
import { type Clear } from '../clean'
|
|
4
|
+
import { subscribeToSignal } from './Text'
|
|
5
|
+
import { type DOMContext } from '../dom-context'
|
|
6
|
+
|
|
7
|
+
export class ClassNameImpl implements Renderable {
|
|
8
|
+
constructor(private readonly cls: Signal<string> | Signal<string | undefined>) { }
|
|
9
|
+
|
|
10
|
+
readonly appendTo = (ctx: DOMContext): Clear => {
|
|
11
|
+
const [set, clear] = ctx.createClass(this.cls.get() ?? '')
|
|
12
|
+
return subscribeToSignal(this.cls.map(v => v ?? ''), set, clear)
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ClassNameProps {
|
|
17
|
+
value: Signal<string> | Signal<string | undefined>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function ClassName({ value }: ClassNameProps): Renderable {
|
|
21
|
+
return new ClassNameImpl(value)
|
|
22
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type DOMContext } from '../dom-context'
|
|
2
|
+
import { type Clear } from '../clean'
|
|
3
|
+
import { type Renderable } from '../renderable'
|
|
4
|
+
import { type JSX } from '../jsx'
|
|
5
|
+
import { makeRenderables } from '../jsx-runtime'
|
|
6
|
+
|
|
7
|
+
export class ElImpl implements Renderable {
|
|
8
|
+
constructor(private readonly tagName: string, private readonly children: Renderable[]) { }
|
|
9
|
+
readonly appendTo = (ctx: DOMContext): Clear => {
|
|
10
|
+
const newCtx = ctx.makeElement(this.tagName)
|
|
11
|
+
const clears = this.children.map(child => child.appendTo(newCtx))
|
|
12
|
+
return (removeTree: boolean) => {
|
|
13
|
+
newCtx.requestClear(removeTree, () => {
|
|
14
|
+
clears.forEach(clear => { clear(false) })
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ElProps {
|
|
21
|
+
tagName: string
|
|
22
|
+
children?: JSX.DOMNode
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function El({ tagName, children }: ElProps): Renderable {
|
|
26
|
+
return new ElImpl(tagName, makeRenderables(children))
|
|
27
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Clear } from "../clean"
|
|
2
|
+
import { DOMContext } from "../dom-context"
|
|
3
|
+
import { Renderable } from "../renderable"
|
|
4
|
+
import { getComputedAnimatable, Animatable, applyInterpolatedAnimatable, applyAnimatable } from "./animatable"
|
|
5
|
+
|
|
6
|
+
export class FadeInImpl implements Renderable {
|
|
7
|
+
constructor(
|
|
8
|
+
private readonly end: Animatable,
|
|
9
|
+
private readonly start: Animatable | undefined,
|
|
10
|
+
private readonly duration: number,
|
|
11
|
+
private readonly delay: number
|
|
12
|
+
) { }
|
|
13
|
+
|
|
14
|
+
appendTo(ctx: DOMContext): Clear {
|
|
15
|
+
const el = ctx.getElement()
|
|
16
|
+
const start = (() => {
|
|
17
|
+
if (this.start != null) {
|
|
18
|
+
applyAnimatable(el, this.start)
|
|
19
|
+
return this.start
|
|
20
|
+
} else {
|
|
21
|
+
return getComputedAnimatable(el, this.end)
|
|
22
|
+
}
|
|
23
|
+
})()
|
|
24
|
+
const startTime = Date.now() + this.delay
|
|
25
|
+
const { duration, end } = this
|
|
26
|
+
let nextFrameId: null | number = null
|
|
27
|
+
function frame() {
|
|
28
|
+
const now = Date.now()
|
|
29
|
+
if (now < startTime) {
|
|
30
|
+
nextFrameId = requestAnimationFrame(frame)
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const progress = Math.min((now - startTime) / duration, 1)
|
|
35
|
+
applyInterpolatedAnimatable(el, start, end, progress)
|
|
36
|
+
if (progress < 1) {
|
|
37
|
+
nextFrameId = requestAnimationFrame(frame)
|
|
38
|
+
} else {
|
|
39
|
+
nextFrameId = null
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
frame()
|
|
43
|
+
|
|
44
|
+
return (_: boolean) => {
|
|
45
|
+
if (nextFrameId != null) cancelAnimationFrame(nextFrameId)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface FadeInProps extends Animatable {
|
|
51
|
+
start?: Animatable,
|
|
52
|
+
duration?: number,
|
|
53
|
+
delay?: number
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function FadeIn(props: FadeInProps): Renderable {
|
|
57
|
+
const { start, duration, delay, ...end } = props
|
|
58
|
+
return new FadeInImpl(end, start, duration ?? 200, delay ?? 0)
|
|
59
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Clear } from "../clean"
|
|
2
|
+
import { DOMContext } from "../dom-context"
|
|
3
|
+
import { Renderable } from "../renderable"
|
|
4
|
+
import { getComputedAnimatable, Animatable, applyInterpolatedAnimatable } from "./animatable"
|
|
5
|
+
|
|
6
|
+
export class FadeOutImpl implements Renderable {
|
|
7
|
+
constructor(
|
|
8
|
+
private readonly end: Animatable,
|
|
9
|
+
private readonly duration: number,
|
|
10
|
+
private readonly delay: number
|
|
11
|
+
) { }
|
|
12
|
+
|
|
13
|
+
appendTo(ctx: DOMContext): Clear {
|
|
14
|
+
const el = ctx.getElement()
|
|
15
|
+
const { duration, end } = this
|
|
16
|
+
|
|
17
|
+
return ctx.delayClear((removeTree, clear) => {
|
|
18
|
+
const start = getComputedAnimatable(el, this.end)
|
|
19
|
+
const startTime = Date.now() + this.delay
|
|
20
|
+
function frame() {
|
|
21
|
+
const now = Date.now()
|
|
22
|
+
if (now < startTime) {
|
|
23
|
+
requestAnimationFrame(frame)
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
const progress = Math.min((now - startTime) / duration, 1)
|
|
27
|
+
applyInterpolatedAnimatable(el, start, end, progress)
|
|
28
|
+
if (progress < 1) {
|
|
29
|
+
requestAnimationFrame(frame)
|
|
30
|
+
} else {
|
|
31
|
+
clear()
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
requestAnimationFrame(frame)
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface FadeOutProps extends Animatable {
|
|
40
|
+
duration?: number,
|
|
41
|
+
delay?: number
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function FadeOut(props: FadeOutProps): Renderable {
|
|
45
|
+
const { duration, delay, ...end } = props
|
|
46
|
+
return new FadeOutImpl(end, duration ?? 200, delay ?? 0)
|
|
47
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type Signal } from '../prop'
|
|
2
|
+
import { RepeatImpl } from './Repeat'
|
|
3
|
+
import { type JSX, makeRenderable } from '../jsx-runtime'
|
|
4
|
+
import { FragmentImpl } from './Fragment'
|
|
5
|
+
import { OnRemoveImpl } from './OnRemove'
|
|
6
|
+
|
|
7
|
+
export interface ForProps<T> {
|
|
8
|
+
of: Signal<T[]>
|
|
9
|
+
children?: (value: Signal<T>, index: number) => JSX.DOMNode
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// <For of={values}>{(value) => <span>{value}</span>}</For>
|
|
13
|
+
export function For<T>({ of, children: render }: ForProps<T>): JSX.DOMNode {
|
|
14
|
+
const times = of.map(v => v.length)
|
|
15
|
+
return new RepeatImpl(times, (index: number) => {
|
|
16
|
+
const value = of.at(index)
|
|
17
|
+
return new FragmentImpl([
|
|
18
|
+
makeRenderable(render?.(value, index)),
|
|
19
|
+
new OnRemoveImpl(value.clean)
|
|
20
|
+
])
|
|
21
|
+
})
|
|
22
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type Renderable } from '../renderable'
|
|
2
|
+
import { type Clear } from '../clean'
|
|
3
|
+
import { type DOMContext } from '../dom-context'
|
|
4
|
+
import { type JSX } from '../jsx'
|
|
5
|
+
import { makeRenderables } from '../jsx-runtime'
|
|
6
|
+
|
|
7
|
+
export class FragmentImpl implements Renderable {
|
|
8
|
+
constructor(private readonly children: Renderable[]) { }
|
|
9
|
+
readonly appendTo = (ctx: DOMContext): Clear => {
|
|
10
|
+
const clears = this.children.map(child => child.appendTo(ctx))
|
|
11
|
+
return (removeTree: boolean) => {
|
|
12
|
+
clears.forEach(clear => { clear(removeTree) })
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function Fragment({ children }: { children: JSX.DOMNode }): Renderable {
|
|
18
|
+
return new FragmentImpl(makeRenderables(children))
|
|
19
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type Renderable } from '../renderable'
|
|
2
|
+
import { type DOMContext } from '../dom-context'
|
|
3
|
+
import { type Clear } from '../clean'
|
|
4
|
+
|
|
5
|
+
export class HiddenWhenEmptyImpl implements Renderable {
|
|
6
|
+
appendTo(ctx: DOMContext): Clear {
|
|
7
|
+
ctx.setStyle(':empty', 'display: none')
|
|
8
|
+
return (removeTree) => {
|
|
9
|
+
if (removeTree) ctx.setStyle(':empty', null)
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function HiddenWhenEmpty(): HiddenWhenEmptyImpl {
|
|
15
|
+
return new HiddenWhenEmptyImpl()
|
|
16
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type JSX } from '../jsx'
|
|
2
|
+
import { type Signal } from '../prop'
|
|
3
|
+
import { OneOfImpl } from './OneOf'
|
|
4
|
+
|
|
5
|
+
export interface IfProps {
|
|
6
|
+
is: Signal<boolean>
|
|
7
|
+
then?: JSX.DOMNode
|
|
8
|
+
otherwise?: JSX.DOMNode
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function If({ is, then, otherwise }: IfProps): JSX.DOMNode {
|
|
12
|
+
return new OneOfImpl(
|
|
13
|
+
is.map(v => v ? [1, true] as [1, true] : [2, false] as [2, false]),
|
|
14
|
+
{
|
|
15
|
+
1: () => then,
|
|
16
|
+
2: () => otherwise
|
|
17
|
+
}
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface WhenProps {
|
|
22
|
+
is: Signal<boolean>
|
|
23
|
+
children?: JSX.DOMNode
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function When({ is, children }: WhenProps): JSX.DOMNode {
|
|
27
|
+
return new OneOfImpl(
|
|
28
|
+
is.map(v => v ? [1, true] as [1, true] : [2, false] as [2, false]),
|
|
29
|
+
{
|
|
30
|
+
1: () => children,
|
|
31
|
+
2: () => null
|
|
32
|
+
}
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function Unless({ is, children }: WhenProps): JSX.DOMNode {
|
|
37
|
+
return new OneOfImpl(
|
|
38
|
+
is.map(v => v ? [1, true] as [1, true] : [2, false] as [2, false]),
|
|
39
|
+
{
|
|
40
|
+
1: () => null,
|
|
41
|
+
2: () => children
|
|
42
|
+
}
|
|
43
|
+
)
|
|
44
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type Signal } from '../prop'
|
|
2
|
+
import { type Clear } from '../clean'
|
|
3
|
+
import { type DOMContext } from '../dom-context'
|
|
4
|
+
import { type Renderable } from '../renderable'
|
|
5
|
+
|
|
6
|
+
export class InnerHTMLImpl implements Renderable {
|
|
7
|
+
constructor(private readonly html: Signal<string> | Signal<string | undefined>) { }
|
|
8
|
+
|
|
9
|
+
readonly appendTo = (ctx: DOMContext): Clear => {
|
|
10
|
+
const el = ctx.getElement()
|
|
11
|
+
const previous = el.innerHTML
|
|
12
|
+
el.innerHTML = this.html.get() ?? ''
|
|
13
|
+
const cancel = this.html.subscribe(value => {
|
|
14
|
+
el.innerHTML = value ?? ''
|
|
15
|
+
})
|
|
16
|
+
return (removeTree: boolean) => {
|
|
17
|
+
cancel()
|
|
18
|
+
if (removeTree) {
|
|
19
|
+
el.innerHTML = previous
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface InnerHTMLProps {
|
|
26
|
+
html: Signal<string> | Signal<string | undefined>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function InnerHTML({ html }: InnerHTMLProps): InnerHTMLImpl {
|
|
30
|
+
return new InnerHTMLImpl(html)
|
|
31
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type Clear } from '../clean'
|
|
2
|
+
import { type DOMContext } from '../dom-context'
|
|
3
|
+
import { type Renderable } from '../renderable'
|
|
4
|
+
|
|
5
|
+
export class LifecycleImpl implements Renderable {
|
|
6
|
+
constructor(private readonly onMount: (el: HTMLElement) => void, private readonly onUnmount: (el: HTMLElement) => void) { }
|
|
7
|
+
readonly appendTo = (ctx: DOMContext): Clear => {
|
|
8
|
+
this.onMount(ctx.getElement())
|
|
9
|
+
|
|
10
|
+
return () => {
|
|
11
|
+
this.onUnmount(ctx.getElement())
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface LifecycleProps {
|
|
17
|
+
onMount?: (el: HTMLElement) => void
|
|
18
|
+
onUnmount?: (el: HTMLElement) => void
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function Lifecycle({ onMount, onUnmount }: LifecycleProps): Renderable {
|
|
22
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
23
|
+
return new LifecycleImpl(onMount ?? (() => { }), onUnmount ?? (() => { }))
|
|
24
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { type Signal } from '../prop'
|
|
2
|
+
import { type DOMContext } from '../dom-context'
|
|
3
|
+
import { type AnyKey } from './OneOf'
|
|
4
|
+
import { type JSX, makeRenderable } from '../jsx-runtime'
|
|
5
|
+
import { type Clear } from '../clean'
|
|
6
|
+
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
8
|
+
export class MatchImpl<T extends { [_ in K]: any }, K extends keyof T> {
|
|
9
|
+
constructor(
|
|
10
|
+
private readonly on: Signal<T>,
|
|
11
|
+
private readonly using: K,
|
|
12
|
+
private readonly matches: {
|
|
13
|
+
[KIND in T[K]]: (
|
|
14
|
+
_: Signal<T & { [KK in K]: KIND extends T[KK] ? KIND : never }>
|
|
15
|
+
) => JSX.DOMNode
|
|
16
|
+
}
|
|
17
|
+
) { }
|
|
18
|
+
|
|
19
|
+
readonly appendTo = (ctx: DOMContext): Clear => {
|
|
20
|
+
let newCtx = ctx.makeReference()
|
|
21
|
+
const value = this.on.get()
|
|
22
|
+
let key = value[this.using]
|
|
23
|
+
let derived = this.on.map(v => v)
|
|
24
|
+
let renderable = makeRenderable(this.matches[key](derived))
|
|
25
|
+
let clear = renderable.appendTo(newCtx)
|
|
26
|
+
const cancel = this.on.subscribe(newValue => {
|
|
27
|
+
const newKey = newValue[this.using]
|
|
28
|
+
if (newKey === key) return
|
|
29
|
+
newCtx.requestClear(true, () => {
|
|
30
|
+
clear(true)
|
|
31
|
+
derived.clean()
|
|
32
|
+
key = newKey
|
|
33
|
+
derived = this.on.map(v => v)
|
|
34
|
+
newCtx = newCtx.makeReference()
|
|
35
|
+
renderable = makeRenderable(this.matches[key](derived))
|
|
36
|
+
clear = renderable.appendTo(newCtx)
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
return (removeTree: boolean) => {
|
|
41
|
+
newCtx.requestClear(removeTree, () => {
|
|
42
|
+
cancel()
|
|
43
|
+
derived.clean()
|
|
44
|
+
clear(removeTree)
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type MatchProps<T extends { [_ in K]: AnyKey }, K extends keyof T> = {
|
|
51
|
+
on: Signal<T>
|
|
52
|
+
using: K
|
|
53
|
+
} & {
|
|
54
|
+
matches: {
|
|
55
|
+
[KIND in T[K]]: (
|
|
56
|
+
_: Signal<T & { [KK in K]: KIND extends T[KK] ? KIND : never }>
|
|
57
|
+
) => JSX.DOMNode
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function Match<T extends { [_ in K]: AnyKey }, K extends keyof T>({
|
|
62
|
+
on,
|
|
63
|
+
using,
|
|
64
|
+
matches
|
|
65
|
+
}: MatchProps<T, K>): JSX.DOMNode {
|
|
66
|
+
return new MatchImpl(on, using, matches)
|
|
67
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** @jsxImportSource .. */
|
|
2
|
+
import { type Signal } from '../prop'
|
|
3
|
+
import { If } from './If'
|
|
4
|
+
import { type AnyKey } from './OneOf'
|
|
5
|
+
import { type JSX } from '../jsx-runtime'
|
|
6
|
+
|
|
7
|
+
export interface NotEmptyProps<T> {
|
|
8
|
+
on: Signal<T>
|
|
9
|
+
whenEmpty?: JSX.DOMNode
|
|
10
|
+
display: JSX.DOMNode
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function isEmpty(obj: Record<AnyKey, unknown>): boolean {
|
|
14
|
+
return Object.keys(obj).length === 0
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// <NotEmpty on={maybevalue} whenEmpty={<span>nothing to show</span>}}>{value => value.toText()}</NotEmpty>
|
|
18
|
+
export function NotEmpty<T extends unknown[] | Record<AnyKey, unknown>>({
|
|
19
|
+
on,
|
|
20
|
+
display,
|
|
21
|
+
whenEmpty
|
|
22
|
+
}: NotEmptyProps<T>): JSX.DOMNode {
|
|
23
|
+
return (
|
|
24
|
+
<If
|
|
25
|
+
is={on.map((v: T) =>
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
27
|
+
Array.isArray(v) ? v.length > 0 : v != null && !isEmpty(v as any)
|
|
28
|
+
)}
|
|
29
|
+
then={display}
|
|
30
|
+
otherwise={whenEmpty}
|
|
31
|
+
/>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type Renderable } from '../renderable'
|
|
2
|
+
import { type Clear } from '../clean'
|
|
3
|
+
import { type DOMContext } from '../dom-context'
|
|
4
|
+
|
|
5
|
+
export type OnFn<T> = (value: T) => void
|
|
6
|
+
|
|
7
|
+
export class OnImpl<T> implements Renderable {
|
|
8
|
+
constructor(private readonly name: string, private readonly handler: OnFn<T>) { }
|
|
9
|
+
|
|
10
|
+
readonly appendTo = (ctx: DOMContext): Clear => {
|
|
11
|
+
return ctx.createHandler(this.name, this.handler)
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface OnProps<T> {
|
|
16
|
+
name: string
|
|
17
|
+
handler: OnFn<T>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function On<T>({ name, handler }: OnProps<T>): Renderable {
|
|
21
|
+
return new OnImpl(name, handler)
|
|
22
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type Clear } from '../clean'
|
|
2
|
+
import { type Renderable } from '../renderable'
|
|
3
|
+
|
|
4
|
+
export class OnRemoveImpl implements Renderable {
|
|
5
|
+
constructor(private readonly clear: Clear) { }
|
|
6
|
+
|
|
7
|
+
readonly appendTo = (): Clear => {
|
|
8
|
+
return (removeTree: boolean) => {
|
|
9
|
+
this.clear(removeTree)
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface OnRemoveProps {
|
|
15
|
+
clear: Clear
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function OnRemove(props: OnRemoveProps): OnRemoveImpl {
|
|
19
|
+
return new OnRemoveImpl(props.clear)
|
|
20
|
+
}
|