@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.
Files changed (109) hide show
  1. package/.eslintignore +10 -0
  2. package/.eslintrc.cjs +28 -0
  3. package/coverage/clover.xml +39 -0
  4. package/coverage/coverage-final.json +2 -0
  5. package/coverage/lcov-report/OneOf.ts.html +256 -0
  6. package/coverage/lcov-report/base.css +224 -0
  7. package/coverage/lcov-report/block-navigation.js +87 -0
  8. package/coverage/lcov-report/dom-context.ts.html +928 -0
  9. package/coverage/lcov-report/favicon.png +0 -0
  10. package/coverage/lcov-report/index.html +116 -0
  11. package/coverage/lcov-report/prettify.css +1 -0
  12. package/coverage/lcov-report/prettify.js +2 -0
  13. package/coverage/lcov-report/prop.ts.html +691 -0
  14. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  15. package/coverage/lcov-report/sorter.js +196 -0
  16. package/coverage/lcov-report/src/components/Attribute.ts.html +154 -0
  17. package/coverage/lcov-report/src/components/BooleanAttribute.ts.html +154 -0
  18. package/coverage/lcov-report/src/components/ClassName.ts.html +151 -0
  19. package/coverage/lcov-report/src/components/El.ts.html +166 -0
  20. package/coverage/lcov-report/src/components/FadeIn.ts.html +262 -0
  21. package/coverage/lcov-report/src/components/FadeOut.ts.html +226 -0
  22. package/coverage/lcov-report/src/components/For.ts.html +151 -0
  23. package/coverage/lcov-report/src/components/Fragment.ts.html +142 -0
  24. package/coverage/lcov-report/src/components/HiddenWhenEmpty.ts.html +133 -0
  25. package/coverage/lcov-report/src/components/If.ts.html +217 -0
  26. package/coverage/lcov-report/src/components/InnerHTML.ts.html +178 -0
  27. package/coverage/lcov-report/src/components/Lifecycle.ts.html +157 -0
  28. package/coverage/lcov-report/src/components/Match.ts.html +286 -0
  29. package/coverage/lcov-report/src/components/NotEmpty.tsx.html +184 -0
  30. package/coverage/lcov-report/src/components/On.ts.html +151 -0
  31. package/coverage/lcov-report/src/components/OnRemove.ts.html +145 -0
  32. package/coverage/lcov-report/src/components/OneOf.ts.html +256 -0
  33. package/coverage/lcov-report/src/components/Portal.ts.html +175 -0
  34. package/coverage/lcov-report/src/components/Property.ts.html +154 -0
  35. package/coverage/lcov-report/src/components/Provider.ts.html +244 -0
  36. package/coverage/lcov-report/src/components/Repeat.ts.html +223 -0
  37. package/coverage/lcov-report/src/components/Show.tsx.html +190 -0
  38. package/coverage/lcov-report/src/components/Text.ts.html +229 -0
  39. package/coverage/lcov-report/src/components/TextContent.ts.html +178 -0
  40. package/coverage/lcov-report/src/components/Tween.tsx.html +943 -0
  41. package/coverage/lcov-report/src/components/animatable.ts.html +619 -0
  42. package/coverage/lcov-report/src/components/index.html +476 -0
  43. package/coverage/lcov-report/src/dom-context.ts.html +928 -0
  44. package/coverage/lcov-report/src/helpers/handle-anchor-click.ts.html +277 -0
  45. package/coverage/lcov-report/src/helpers/handle-text-input.ts.html +100 -0
  46. package/coverage/lcov-report/src/helpers/index.html +146 -0
  47. package/coverage/lcov-report/src/helpers/is-empty-element.ts.html +112 -0
  48. package/coverage/lcov-report/src/index.html +176 -0
  49. package/coverage/lcov-report/src/index.ts.html +412 -0
  50. package/coverage/lcov-report/src/jsx-runtime.ts.html +601 -0
  51. package/coverage/lcov-report/src/prop.ts.html +691 -0
  52. package/coverage/lcov-report/src/render.ts.html +112 -0
  53. package/coverage/lcov-report/src/types/idom-context.ts.html +184 -0
  54. package/coverage/lcov-report/src/types/index.html +116 -0
  55. package/coverage/lcov-report/test/common.ts.html +112 -0
  56. package/coverage/lcov-report/test/index.html +116 -0
  57. package/coverage/lcov.info +57 -0
  58. package/dist/index.js +32 -0
  59. package/jest.config.js +5 -0
  60. package/package.json +39 -0
  61. package/src/clean.ts +2 -0
  62. package/src/components/Attribute.ts +23 -0
  63. package/src/components/BooleanAttribute.ts +23 -0
  64. package/src/components/ClassName.ts +22 -0
  65. package/src/components/El.ts +27 -0
  66. package/src/components/FadeIn.ts +59 -0
  67. package/src/components/FadeOut.ts +47 -0
  68. package/src/components/For.ts +22 -0
  69. package/src/components/Fragment.ts +19 -0
  70. package/src/components/HiddenWhenEmpty.ts +16 -0
  71. package/src/components/If.ts +44 -0
  72. package/src/components/InnerHTML.ts +31 -0
  73. package/src/components/Lifecycle.ts +24 -0
  74. package/src/components/Match.ts +67 -0
  75. package/src/components/NotEmpty.tsx +33 -0
  76. package/src/components/On.ts +22 -0
  77. package/src/components/OnRemove.ts +20 -0
  78. package/src/components/OneOf.ts +57 -0
  79. package/src/components/Portal.ts +30 -0
  80. package/src/components/Property.ts +23 -0
  81. package/src/components/Provider.ts +53 -0
  82. package/src/components/Repeat.ts +46 -0
  83. package/src/components/Show.tsx +35 -0
  84. package/src/components/Text.ts +48 -0
  85. package/src/components/TextContent.ts +31 -0
  86. package/src/components/animatable.ts +178 -0
  87. package/src/dom-context.ts +281 -0
  88. package/src/helpers/handle-anchor-click.ts +64 -0
  89. package/src/helpers/handle-text-input.ts +5 -0
  90. package/src/helpers/is-empty-element.ts +9 -0
  91. package/src/index.ts +109 -0
  92. package/src/jsx-dev-runtime.ts +8 -0
  93. package/src/jsx-runtime.ts +172 -0
  94. package/src/jsx.ts +1046 -0
  95. package/src/prop.ts +202 -0
  96. package/src/render.ts +9 -0
  97. package/src/renderable.ts +6 -0
  98. package/test/common.ts +9 -0
  99. package/test/component.spec.tsx +27 -0
  100. package/test/domcontext.spec.ts +36 -0
  101. package/test/fadein.spec.tsx +36 -0
  102. package/test/fadeout.spec.tsx +41 -0
  103. package/test/if.spec.tsx +30 -0
  104. package/test/innerhtml.spec.tsx +45 -0
  105. package/test/prop.spec.ts +10 -0
  106. package/test/render.spec.tsx +19 -0
  107. package/test/textcontent.spec.tsx +45 -0
  108. package/test/when.spec.tsx +30 -0
  109. 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
@@ -0,0 +1,5 @@
1
+ /** @type {import('ts-jest').JestConfigWithTsJest} */
2
+ module.exports = {
3
+ preset: 'ts-jest',
4
+ testEnvironment: '@happy-dom/jest-environment',
5
+ };
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,2 @@
1
+ export type Clear = (removeTree: boolean) => void
2
+ export type Clean = () => void
@@ -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
+ }