feat: moved css to html file, updated readme

This commit is contained in:
Daniel Ledda
2022-05-28 13:23:25 +02:00
parent e5e0c47f68
commit db4a332990
8 changed files with 535 additions and 48 deletions

7
.npmignore Normal file
View File

@@ -0,0 +1,7 @@
node_modules/
.idea/
index.html
test.tsx
package-lock.json
tsconfig.json
.gitignore

225
README.md
View File

@@ -1,7 +1,228 @@
# Ladder # Ladder
Most libraries give you a framework. This is just a ladder. Most libraries give you a whole framework. This is just a ladder.
## What's in the box? ## What's in the box?
- JSX-friendly Ladder is a tiny* TypeScript-only library to quickly start using "hyperscript" style functions and optionally JSX in your code.
Ladder includes:
- An (almost) bare-bones `h` function, JSX compatible.
- Optional `Rung` hierarchical UI-node primitive.
- Optional `Capsule` reactive value primitive.
- Optional basic implementation of the pub/sub model: `Publisher`s and `Subscriber`s
`Capsules` can be saved to directly from the `h`-function to insert the resultant node into the capsule.
They can also be used as the value of a prop to automatically watch for updates and update the HTML node they were used on accordingly.
Everything else is up to you. You have full control over how the app works.
Here is an example app:
```tsx
import { Rung, Capsule, bootstrap, h, frag } from 'ladder';
class App extends Rung {
private counter = Capsule.new<number>(0);
private rungs = Capsule.new<HTMLDivElement | null>(null);
constructor() {
super({});
this.counter.watch((count) => this.onCounterUpdate(count));
}
private onCounterUpdate(count: number) {
const rungs = Array<Node>(count);
for (let i = 0; i < rungs.length; i++) {
rungs[i] = <div className={'rung'}/>;
}
this.rungs.val?.replaceChildren(...rungs);
}
// using JSX
build() {
return <>
<h1>Ladder</h1>
<button onclick={() => this.counter.val--}>-</button>
<span>{this.counter}</span>
<button onclick={() => this.counter.val++}>+</button>
<div saveTo={this.rungs}/>
</>;
}
// using pure ts
build() {
return frag(null,
h("h1", {}, "Ladder"),
h("button", {onclick: () => this.counter.val--}, "-"),
h("span", {}, this.counter),
h("button", {onclick: () => this.counter.val++}, "+"),
h("div", {saveTo: this.rungs}),
);
}
}
bootstrap(new App(), "app");
```
The bootstrap function injects the results of `build` into the HTML node with the `app` id.
### Rungs
A Rung is any class derived from the internal `Rung` abstract class.
A Rung must implement the `build` method, returning an instance of the HTML `Node` primitive (e.g. using the `h` function included). Once a Rung is 'built', it is done. All subsequent `render` calls to the Rung will return the prebuilt DOM tree. It is then up to you manipulate the Rung's internal DOM-tree yourself, should anything need to change.
Rungs can be included directly as a child in the JSX and the `render` function is called automatically. Should you need to rerun the `build` function, you can call `redraw` internally and the resultant node will be inserted at its predecessor's position in the DOM directly.
This `build` function will insert the result of the `render` call of the `this.coolRung` instance directly.
```tsx
class MyCoolRung extends Rung {
// ... great code ...
}
class SuperRung extends Rung {
private coolRung = new MyCoolRung();
constructor() {
super({});
}
build() {
return <div>Check out this rung here: {this.coolRung}</div>;
}
}
```
Rungs are intentionally not able to be used in JSX. JSX should only be used for `HTMLElement`s, as Rungs are not declarative, rather, they are simple objects.
### Capsules
A Capsule is a primitive used to store a single value that can be watched for changes.
It can be any object fulfilling the following interface (included in the library):
```ts
interface ICapsule<T extends Captable = Captable> {
watch(watcher: (newVal: T) => void, after?: boolean): ISubscription;
toString(): string;
val: T;
}
type Captable = { toString(): string; } | string | null;
interface ISubscription {
unbind(): void;
}
```
I.e. Capsules must encapsulate values that can either be null or be able to be cast to a `string`.
Capsules can be used in `h`/JSX as a HTML attribute, a child node, or as the value of the special `saveTo` property:
Taking the `build` method from the initial example:
```tsx
class App extends Rung {
// ...
build() {
return <>
<h1>Ladder</h1>
<button onclick={() => this.counter.val--}>-</button>
<span>{this.counter}</span>
<button onclick={() => this.counter.val++}>+</button>
<div saveTo={this.rungs}/>
</>;
}
// ...
}
```
`this.counter` and `this.rungs` are both Capsules and as such the node generated as a child of the `<span>` for `this.counter` will update when the watcher callback is fired.
Similarly, the `<div>` node at the end of the fragment is saved to `this.rungs` to be used in the Rung.
### Pub/Sub
Often, littering your code with reactive primitives isn't the best idea. You might want to notify your dependants of any updates after a series of complex operations that are applied to multiple different values that are used in different places. Notifying your dependants manually is useful for this kind of use case.
Ladder includes `Publisher` and `Subscription` primitives to include in your Rungs or elsewhere in your program.
For example, suppose you have a `Track` data class that emits events:
```ts
const enum TrackEvents {
NewTimeSig="tr-0",
NewBarCount="tr-1",
NewName="tr-2",
DisplayTypeChanged="tr-3",
Baked="tr-4",
DeepChange="tr-5",
}
class Track implements IPublisher<TrackEvents> {
// ...
}
```
And a Rung that listens for some of them:
```ts
type TrackSubs =
| TrackEvents.NewName
| TrackEvents.NewTimeSig
| TrackEvents.NewBarCount
| TrackEvents.DisplayTypeChanged;
class TrackView extends Rung implements ISubscriber<TrackSubs> {
// ...
}
```
The track view can then subscribe to its track instance member using the same strings using `track.addSubscriber(this, <array-of-track-subs>)`.
To respond to fired events, TrackView implements `notify`:
```ts
class TrackView extends Rung implements ISubscriber<TrackSubs> {
// ...
notify(publisher: Track, event: TrackSubs): void {
switch (event) {
case TrackEvents.NewName:
case TrackEvents.NewTimeSig:
case TrackEvents.NewBarCount:
case TrackEvents.DisplayTypeChanged:
case TrackEvents.LoopLengthChanged:
// respond!
break;
}
}
// ...
}
```
`addSubscriber` returns `ISubscription`, with the same interface as a Capsule. You must call `unbind` if you want to stop listening e.g. when doing cleanup tasks.
Here is a trick to write the list of subscribed events once and generate a type from it, reducing duplicate code and consistency mess:
```ts
const TrackSubs = [
TrackEvents.NewName,
TrackEvents.NewTimeSig,
TrackEvents.NewBarCount,
TrackEvents.DisplayTypeChanged,
];
type TrackSubs = typeof TrackSubs[number]; // Yes, the names can be identical!
class TrackView extends Rung implements ISubscriber<TrackSubs> {
// ...
}
```
### Miscellaneous Helpers
There are also the two methods `q` and `frag` that wrap `document.createTextNode` and `document.createDocumentFragment` respectively to reduce bloat when using pure JavaScript.
---
That's about it. You can use as much or as little as you want, hopefully you find it useful for simple or complicated apps for which modern JS Frameworks are just too much overhead in terms of either setup, performance, or restrictivity.
*All components of Ladder are about ~2.5KiB transpiled and minified, ~1KiB gzipped.

View File

@@ -3,6 +3,20 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Ladder Test Playground</title> <title>Ladder Test Playground</title>
<style>
.rung {
width: 30px;
height: 30px;
border: solid black;
border-width: 0 2px 2px 2px;
}
.rung:last-of-type {
border-width: 0 2px 0 2px;
}
.rung:first-of-type {
border-width: 0 2px 2px 2px;
}
</style>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>

View File

@@ -14,10 +14,6 @@ export default abstract class Rung {
return this.node; return this.node;
} }
protected getEl(): Node {
return this.render();
}
redraw(): void { redraw(): void {
const oldNode = this.node; const oldNode = this.node;
if (!oldNode || !this.node) { if (!oldNode || !this.node) {

View File

@@ -24,14 +24,18 @@ export function bootstrap(app: Rung, id: string) {
} }
} }
export function frag(attributes: IRenderAttributes<DocumentFragment> | null, subs?: SubNode[]): DocumentFragment { export function frag(attributes: IRenderAttributes<DocumentFragment> | null, ...subs: SubNode[]): DocumentFragment {
const frag = document.createDocumentFragment(); const frag = document.createDocumentFragment();
if (attributes) { if (attributes) {
applyAttributes(frag, attributes); applyAttributes(frag, attributes);
} }
if (subs) { if (subs) {
if (Array.isArray(subs[0])) {
attachSubs(frag, subs[0]);
} else {
attachSubs(frag, subs); attachSubs(frag, subs);
} }
}
return frag; return frag;
} }
@@ -50,7 +54,7 @@ type Props<T> =
? CommonRenderAttributes<T> ? CommonRenderAttributes<T>
: never; : never;
export type SubNode = Rung | Node | ICapsule; export type SubNode = Rung | Node | ICapsule | string;
export function h<T extends keyof HTMLElementTagNameMap>(type: T, attributes?: Props<T>, ...subNodes: SubNode[]): HTMLElementTagNameMap[T]; export function h<T extends keyof HTMLElementTagNameMap>(type: T, attributes?: Props<T>, ...subNodes: SubNode[]): HTMLElementTagNameMap[T];
export function h<T extends FunctionalRung<any, any>, U extends Props<T>>(type: T, attributes?: U, ...subNodes: SubNode[]): ReturnType<T>; export function h<T extends FunctionalRung<any, any>, U extends Props<T>>(type: T, attributes?: U, ...subNodes: SubNode[]): ReturnType<T>;

253
package-lock.json generated Normal file
View File

@@ -0,0 +1,253 @@
{
"name": "@djledda/ladder",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"esbuild": {
"version": "0.14.39",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.39.tgz",
"integrity": "sha512-2kKujuzvRWYtwvNjYDY444LQIA3TyJhJIX3Yo4+qkFlDDtGlSicWgeHVJqMUP/2sSfH10PGwfsj+O2ro1m10xQ==",
"requires": {
"esbuild-android-64": "0.14.39",
"esbuild-android-arm64": "0.14.39",
"esbuild-darwin-64": "0.14.39",
"esbuild-darwin-arm64": "0.14.39",
"esbuild-freebsd-64": "0.14.39",
"esbuild-freebsd-arm64": "0.14.39",
"esbuild-linux-32": "0.14.39",
"esbuild-linux-64": "0.14.39",
"esbuild-linux-arm": "0.14.39",
"esbuild-linux-arm64": "0.14.39",
"esbuild-linux-mips64le": "0.14.39",
"esbuild-linux-ppc64le": "0.14.39",
"esbuild-linux-riscv64": "0.14.39",
"esbuild-linux-s390x": "0.14.39",
"esbuild-netbsd-64": "0.14.39",
"esbuild-openbsd-64": "0.14.39",
"esbuild-sunos-64": "0.14.39",
"esbuild-windows-32": "0.14.39",
"esbuild-windows-64": "0.14.39",
"esbuild-windows-arm64": "0.14.39"
}
},
"esbuild-android-64": {
"version": "0.14.39",
"resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.39.tgz",
"integrity": "sha512-EJOu04p9WgZk0UoKTqLId9VnIsotmI/Z98EXrKURGb3LPNunkeffqQIkjS2cAvidh+OK5uVrXaIP229zK6GvhQ==",
"optional": true
},
"esbuild-android-arm64": {
"version": "0.14.39",
"resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.39.tgz",
"integrity": "sha512-+twajJqO7n3MrCz9e+2lVOnFplRsaGRwsq1KL/uOy7xK7QdRSprRQcObGDeDZUZsacD5gUkk6OiHiYp6RzU3CA==",
"optional": true
},
"esbuild-darwin-64": {
"version": "0.14.39",
"resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.39.tgz",
"integrity": "sha512-ImT6eUw3kcGcHoUxEcdBpi6LfTRWaV6+qf32iYYAfwOeV+XaQ/Xp5XQIBiijLeo+LpGci9M0FVec09nUw41a5g==",
"optional": true
},
"esbuild-darwin-arm64": {
"version": "0.14.39",
"resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.39.tgz",
"integrity": "sha512-/fcQ5UhE05OiT+bW5v7/up1bDsnvaRZPJxXwzXsMRrr7rZqPa85vayrD723oWMT64dhrgWeA3FIneF8yER0XTw==",
"optional": true
},
"esbuild-freebsd-64": {
"version": "0.14.39",
"resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.39.tgz",
"integrity": "sha512-oMNH8lJI4wtgN5oxuFP7BQ22vgB/e3Tl5Woehcd6i2r6F3TszpCnNl8wo2d/KvyQ4zvLvCWAlRciumhQg88+kQ==",
"optional": true
},
"esbuild-freebsd-arm64": {
"version": "0.14.39",
"resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.39.tgz",
"integrity": "sha512-1GHK7kwk57ukY2yI4ILWKJXaxfr+8HcM/r/JKCGCPziIVlL+Wi7RbJ2OzMcTKZ1HpvEqCTBT/J6cO4ZEwW4Ypg==",
"optional": true
},
"esbuild-linux-32": {
"version": "0.14.39",
"resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.39.tgz",
"integrity": "sha512-g97Sbb6g4zfRLIxHgW2pc393DjnkTRMeq3N1rmjDUABxpx8SjocK4jLen+/mq55G46eE2TA0MkJ4R3SpKMu7dg==",
"optional": true
},
"esbuild-linux-64": {
"version": "0.14.39",
"resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.39.tgz",
"integrity": "sha512-4tcgFDYWdI+UbNMGlua9u1Zhu0N5R6u9tl5WOM8aVnNX143JZoBZLpCuUr5lCKhnD0SCO+5gUyMfupGrHtfggQ==",
"optional": true
},
"esbuild-linux-arm": {
"version": "0.14.39",
"resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.39.tgz",
"integrity": "sha512-t0Hn1kWVx5UpCzAJkKRfHeYOLyFnXwYynIkK54/h3tbMweGI7dj400D1k0Vvtj2u1P+JTRT9tx3AjtLEMmfVBQ==",
"optional": true
},
"esbuild-linux-arm64": {
"version": "0.14.39",
"resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.39.tgz",
"integrity": "sha512-23pc8MlD2D6Px1mV8GMglZlKgwgNKAO8gsgsLLcXWSs9lQsCYkIlMo/2Ycfo5JrDIbLdwgP8D2vpfH2KcBqrDQ==",
"optional": true
},
"esbuild-linux-mips64le": {
"version": "0.14.39",
"resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.39.tgz",
"integrity": "sha512-epwlYgVdbmkuRr5n4es3B+yDI0I2e/nxhKejT9H0OLxFAlMkeQZxSpxATpDc9m8NqRci6Kwyb/SfmD1koG2Zuw==",
"optional": true
},
"esbuild-linux-ppc64le": {
"version": "0.14.39",
"resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.39.tgz",
"integrity": "sha512-W/5ezaq+rQiQBThIjLMNjsuhPHg+ApVAdTz2LvcuesZFMsJoQAW2hutoyg47XxpWi7aEjJGrkS26qCJKhRn3QQ==",
"optional": true
},
"esbuild-linux-riscv64": {
"version": "0.14.39",
"resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.39.tgz",
"integrity": "sha512-IS48xeokcCTKeQIOke2O0t9t14HPvwnZcy+5baG13Z1wxs9ZrC5ig5ypEQQh4QMKxURD5TpCLHw2W42CLuVZaA==",
"optional": true
},
"esbuild-linux-s390x": {
"version": "0.14.39",
"resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.39.tgz",
"integrity": "sha512-zEfunpqR8sMomqXhNTFEKDs+ik7HC01m3M60MsEjZOqaywHu5e5682fMsqOlZbesEAAaO9aAtRBsU7CHnSZWyA==",
"optional": true
},
"esbuild-netbsd-64": {
"version": "0.14.39",
"resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.39.tgz",
"integrity": "sha512-Uo2suJBSIlrZCe4E0k75VDIFJWfZy+bOV6ih3T4MVMRJh1lHJ2UyGoaX4bOxomYN3t+IakHPyEoln1+qJ1qYaA==",
"optional": true
},
"esbuild-openbsd-64": {
"version": "0.14.39",
"resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.39.tgz",
"integrity": "sha512-secQU+EpgUPpYjJe3OecoeGKVvRMLeKUxSMGHnK+aK5uQM3n1FPXNJzyz1LHFOo0WOyw+uoCxBYdM4O10oaCAA==",
"optional": true
},
"esbuild-sunos-64": {
"version": "0.14.39",
"resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.39.tgz",
"integrity": "sha512-qHq0t5gePEDm2nqZLb+35p/qkaXVS7oIe32R0ECh2HOdiXXkj/1uQI9IRogGqKkK+QjDG+DhwiUw7QoHur/Rwg==",
"optional": true
},
"esbuild-windows-32": {
"version": "0.14.39",
"resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.39.tgz",
"integrity": "sha512-XPjwp2OgtEX0JnOlTgT6E5txbRp6Uw54Isorm3CwOtloJazeIWXuiwK0ONJBVb/CGbiCpS7iP2UahGgd2p1x+Q==",
"optional": true
},
"esbuild-windows-64": {
"version": "0.14.39",
"resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.39.tgz",
"integrity": "sha512-E2wm+5FwCcLpKsBHRw28bSYQw0Ikxb7zIMxw3OPAkiaQhLVr3dnVO8DofmbWhhf6b97bWzg37iSZ45ZDpLw7Ow==",
"optional": true
},
"esbuild-windows-arm64": {
"version": "0.14.39",
"resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.39.tgz",
"integrity": "sha512-sBZQz5D+Gd0EQ09tZRnz/PpVdLwvp/ufMtJ1iDFYddDaPpZXKqPyaxfYBLs3ueiaksQ26GGa7sci0OqFzNs7KA==",
"optional": true
},
"fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"optional": true
},
"function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
"has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"requires": {
"function-bind": "^1.1.1"
}
},
"is-core-module": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz",
"integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==",
"requires": {
"has": "^1.0.3"
}
},
"nanoid": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw=="
},
"path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
},
"picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
},
"postcss": {
"version": "8.4.14",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz",
"integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==",
"requires": {
"nanoid": "^3.3.4",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
}
},
"resolve": {
"version": "1.22.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz",
"integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==",
"requires": {
"is-core-module": "^2.8.1",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
}
},
"rollup": {
"version": "2.74.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.74.1.tgz",
"integrity": "sha512-K2zW7kV8Voua5eGkbnBtWYfMIhYhT9Pel2uhBk2WO5eMee161nPze/XRfvEQPFYz7KgrCCnmh2Wy0AMFLGGmMA==",
"requires": {
"fsevents": "~2.3.2"
}
},
"source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw=="
},
"supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="
},
"typescript": {
"version": "4.7.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.2.tgz",
"integrity": "sha512-Mamb1iX2FDUpcTRzltPxgWMKy3fhg0TN378ylbktPGPK/99KbDtMQ4W1hwgsbPAsG3a0xKa1vmw4VKZQbkvz5A==",
"dev": true
},
"vite": {
"version": "2.9.9",
"resolved": "https://registry.npmjs.org/vite/-/vite-2.9.9.tgz",
"integrity": "sha512-ffaam+NgHfbEmfw/Vuh6BHKKlI/XIAhxE5QSS7gFLIngxg171mg1P3a4LSRME0z2ZU1ScxoKzphkipcYwSD5Ew==",
"requires": {
"esbuild": "^0.14.27",
"fsevents": "~2.3.2",
"postcss": "^8.4.13",
"resolve": "^1.22.0",
"rollup": "^2.59.0"
}
}
}
}

View File

@@ -1,12 +1,8 @@
{ {
"name": "@djledda/ladder", "name": "@djledda/ladder",
"version": "1.0.0", "version": "1.0.1",
"description": "other libraries provide you with a whole framework - this is just a ladder", "description": "other libraries provide you with a whole framework - this is just a ladder",
"scripts": {},
"author": "Daniel Ledda <dan.j.ledda@gmail.com>", "author": "Daniel Ledda <dan.j.ledda@gmail.com>",
"license": "MIT", "license": "MIT",
"devDependencies": { "main": "index.ts"
"typescript": "^4.7.2",
"vite": "^2.9.9"
},
} }

View File

@@ -1,47 +1,43 @@
import { h, q, frag, bootstrap, Rung, Capsule } from "./index"; import { h, frag, bootstrap, Rung, Capsule } from "./index";
import {SubNode} from "./lib/helpers";
const MyCoolDiv = (props: { isRed: boolean }, subNodes?: SubNode[]) => h("div", { classes: props.isRed ? ["red"] : [] }, ...subNodes ?? []); class CoolRung extends Rung {
constructor() {
super({});
}
build() {
return <div>
My Cool Rung
</div>;
}
}
class App extends Rung { class App extends Rung {
private counter = Capsule.new<number>(0); private counter = Capsule.new<number>(0);
private rungs = Capsule.new<HTMLDivElement | null>(null); private rungs = Capsule.new<HTMLDivElement | null>(null);
private coolRung = new CoolRung();
constructor() { constructor() {
super({}); super({});
this.counter.watch((count) => { this.counter.watch((count) => this.onCounterUpdate(count));
if (this.rungs.val) {
this.rungs.val.replaceChildren(
...new Array(count).fill(null).map((_, i) => {
return <div className={'rung'}/>;
})
);
} }
});
private onCounterUpdate(count: number) {
const rungs = Array<Node>(count);
for (let i = 0; i < rungs.length; i++) {
rungs[i] = <div className={'rung'}/>;
}
this.rungs.val?.replaceChildren(...rungs);
} }
build() { build() {
return <> return frag(null,
<style>{` h("h1", {}, "Ladder"),
.rung { h("button", {onclick: () => this.counter.val--}, "-"),
width: 30px; h("span", {}, this.counter),
height: 30px; h("button", {onclick: () => this.counter.val++}, "+"),
border: solid black; h("div", {saveTo: this.rungs}),
border-width: 0 2px 2px 2px; );
}
.rung:last-of-type {
border-width: 0 2px 0 2px;
}
.rung:first-of-type {
border-width: 0 2px 2px 2px;
}
`}</style>
<h1>Ladder</h1>
<button onclick={() => this.counter.val--}>-</button>
<span>{this.counter}</span>
<button onclick={() => this.counter.val++}>+</button>
<div saveTo={this.rungs}/>
</>;
} }
} }