diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..e626cac --- /dev/null +++ b/.npmignore @@ -0,0 +1,7 @@ +node_modules/ +.idea/ +index.html +test.tsx +package-lock.json +tsconfig.json +.gitignore diff --git a/README.md b/README.md index 4fbc073..4df3293 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,228 @@ # 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? -- JSX-friendly \ No newline at end of file +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(0); + private rungs = Capsule.new(null); + + constructor() { + super({}); + this.counter.watch((count) => this.onCounterUpdate(count)); + } + + private onCounterUpdate(count: number) { + const rungs = Array(count); + for (let i = 0; i < rungs.length; i++) { + rungs[i] =
; + } + this.rungs.val?.replaceChildren(...rungs); + } + + // using JSX + build() { + return <> +

Ladder

+ + {this.counter} + +
+ ; + } + + // 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
Check out this rung here: {this.coolRung}
; + } +} +``` + +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 { + 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 <> +

Ladder

+ + {this.counter} + +
+ ; + } + // ... +} +``` + +`this.counter` and `this.rungs` are both Capsules and as such the node generated as a child of the `` for `this.counter` will update when the watcher callback is fired. +Similarly, the `
` 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 { + // ... +} +``` + +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 { + // ... +} +``` + +The track view can then subscribe to its track instance member using the same strings using `track.addSubscriber(this, )`. +To respond to fired events, TrackView implements `notify`: + +```ts +class TrackView extends Rung implements ISubscriber { + // ... + 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 { + // ... +} +``` + +### 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. \ No newline at end of file diff --git a/index.html b/index.html index 6ca205c..a85762f 100644 --- a/index.html +++ b/index.html @@ -3,6 +3,20 @@ Ladder Test Playground +
diff --git a/lib/Rung.ts b/lib/Rung.ts index 09c6a98..d311c63 100644 --- a/lib/Rung.ts +++ b/lib/Rung.ts @@ -14,10 +14,6 @@ export default abstract class Rung { return this.node; } - protected getEl(): Node { - return this.render(); - } - redraw(): void { const oldNode = this.node; if (!oldNode || !this.node) { diff --git a/lib/helpers.ts b/lib/helpers.ts index 1a6ed44..7213472 100644 --- a/lib/helpers.ts +++ b/lib/helpers.ts @@ -24,13 +24,17 @@ export function bootstrap(app: Rung, id: string) { } } -export function frag(attributes: IRenderAttributes | null, subs?: SubNode[]): DocumentFragment { +export function frag(attributes: IRenderAttributes | null, ...subs: SubNode[]): DocumentFragment { const frag = document.createDocumentFragment(); if (attributes) { applyAttributes(frag, attributes); } if (subs) { - attachSubs(frag, subs); + if (Array.isArray(subs[0])) { + attachSubs(frag, subs[0]); + } else { + attachSubs(frag, subs); + } } return frag; } @@ -50,7 +54,7 @@ type Props = ? CommonRenderAttributes : never; -export type SubNode = Rung | Node | ICapsule; +export type SubNode = Rung | Node | ICapsule | string; export function h(type: T, attributes?: Props, ...subNodes: SubNode[]): HTMLElementTagNameMap[T]; export function h, U extends Props>(type: T, attributes?: U, ...subNodes: SubNode[]): ReturnType; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..5a847e2 --- /dev/null +++ b/package-lock.json @@ -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" + } + } + } +} diff --git a/package.json b/package.json index 3401deb..b800ca4 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,8 @@ { "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", - "scripts": {}, "author": "Daniel Ledda ", "license": "MIT", - "devDependencies": { - "typescript": "^4.7.2", - "vite": "^2.9.9" - }, + "main": "index.ts" } diff --git a/test.tsx b/test.tsx index 8c99134..9ff5ff0 100644 --- a/test.tsx +++ b/test.tsx @@ -1,47 +1,43 @@ -import { h, q, frag, bootstrap, Rung, Capsule } from "./index"; -import {SubNode} from "./lib/helpers"; +import { h, frag, bootstrap, Rung, Capsule } from "./index"; -const MyCoolDiv = (props: { isRed: boolean }, subNodes?: SubNode[]) => h("div", { classes: props.isRed ? ["red"] : [] }, ...subNodes ?? []); +class CoolRung extends Rung { + constructor() { + super({}); + } + + build() { + return
+ My Cool Rung +
; + } +} class App extends Rung { private counter = Capsule.new(0); private rungs = Capsule.new(null); + private coolRung = new CoolRung(); constructor() { super({}); - this.counter.watch((count) => { - if (this.rungs.val) { - this.rungs.val.replaceChildren( - ...new Array(count).fill(null).map((_, i) => { - return
; - }) - ); - } - }); + this.counter.watch((count) => this.onCounterUpdate(count)); + } + + private onCounterUpdate(count: number) { + const rungs = Array(count); + for (let i = 0; i < rungs.length; i++) { + rungs[i] =
; + } + this.rungs.val?.replaceChildren(...rungs); } build() { - return <> - -

Ladder

- - {this.counter} - -
- ; + 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}), + ); } }