feat: some refactoring and cleanup
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
node_modules/
|
||||
.idea/
|
||||
3
README.md
Normal file
3
README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Ladder
|
||||
|
||||
Most libraries give you a framework. This is just a ladder.
|
||||
26
index.ts
Normal file
26
index.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
export {
|
||||
ISubscription,
|
||||
IPublisher,
|
||||
Publisher,
|
||||
} from './lib/Publisher';
|
||||
|
||||
export {
|
||||
default as ISubscriber,
|
||||
LEvent,
|
||||
} from './lib/Subscriber';
|
||||
|
||||
export {
|
||||
default as Rung,
|
||||
RungOptions,
|
||||
} from './lib/Rung';
|
||||
|
||||
export {
|
||||
default as Capsule,
|
||||
} from './lib/Capsule';
|
||||
|
||||
export {
|
||||
bootstrap,
|
||||
frag,
|
||||
h,
|
||||
q,
|
||||
} from './lib/helpers';
|
||||
@@ -1,8 +1,6 @@
|
||||
import { ISubscription } from "./Publisher";
|
||||
|
||||
export type MaybeRef<T> = T | Ref<T>;
|
||||
|
||||
class RefSubscription implements ISubscription {
|
||||
class CapsuleSubscription implements ISubscription {
|
||||
private unbindCallback?: () => void;
|
||||
|
||||
constructor(unbindCallback: () => void) {
|
||||
@@ -17,10 +15,10 @@ class RefSubscription implements ISubscription {
|
||||
interface Stringable {
|
||||
toString(): string;
|
||||
}
|
||||
type Capsable = Stringable | string | null;
|
||||
export type MaybeCapsule<T> = T | Capsule<T>;
|
||||
|
||||
type AllowedRef = { toString(): string } | string | null;
|
||||
|
||||
export default class Ref<T extends AllowedRef = Stringable> {
|
||||
export default class Capsule<T extends Capsable = Capsable> {
|
||||
private watchers: Array<(newVal: T) => void> | null = null;
|
||||
private afterWatchers: Array<(newVal: T) => void> | null = null;
|
||||
private value: T;
|
||||
@@ -32,11 +30,11 @@ export default class Ref<T extends AllowedRef = Stringable> {
|
||||
this.isString = typeof val === "string";
|
||||
}
|
||||
|
||||
static new<T extends AllowedRef>(val: MaybeRef<T>): Ref<T> {
|
||||
if (val instanceof Ref) {
|
||||
static new<T extends Capsable>(val: MaybeCapsule<T>): Capsule<T> {
|
||||
if (val instanceof Capsule) {
|
||||
return val;
|
||||
} else {
|
||||
return new Ref<T>(val);
|
||||
return new Capsule<T>(val);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +50,7 @@ export default class Ref<T extends AllowedRef = Stringable> {
|
||||
}
|
||||
this.watchers.push(watcher);
|
||||
}
|
||||
return new RefSubscription(() => this.unbind(watcher, !!after));
|
||||
return new CapsuleSubscription(() => this.unbind(watcher, !!after));
|
||||
}
|
||||
|
||||
private unbind(watcher: (newVal: T) => void, after: boolean): void {
|
||||
@@ -32,7 +32,6 @@ interface EventSubscriberRecord<T extends LEvent> {
|
||||
set<K extends T>(key: K, subscribers: ISubscriber<K>[]): EventSubscriberRecord<T>;
|
||||
}
|
||||
|
||||
|
||||
export class Publisher<EventType extends LEvent, PublisherType> implements IPublisher<EventType> {
|
||||
private subscribers: EventSubscriberRecord<EventType>;
|
||||
private parent: PublisherType;
|
||||
34
lib/Rung.ts
Normal file
34
lib/Rung.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
export type RungOptions = {};
|
||||
|
||||
export default abstract class Rung {
|
||||
protected el: HTMLElement | null = null;
|
||||
|
||||
protected constructor(options: RungOptions) {}
|
||||
|
||||
render(): HTMLElement {
|
||||
if (!this.el) {
|
||||
this.el = this.build();
|
||||
}
|
||||
return this.el;
|
||||
}
|
||||
|
||||
protected getEl(): HTMLElement {
|
||||
return this.render();
|
||||
}
|
||||
|
||||
redraw(): void {
|
||||
const oldNode = this.el;
|
||||
if (!oldNode || !this.el) {
|
||||
return;
|
||||
}
|
||||
const parent = this.el.parentElement;
|
||||
if (parent) {
|
||||
this.el = this.build();
|
||||
parent.replaceChild(this.el, oldNode);
|
||||
} else {
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract build(): HTMLElement;
|
||||
}
|
||||
92
lib/helpers.ts
Normal file
92
lib/helpers.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import Capsule from "./Capsule";
|
||||
import {ISubscription} from "./Publisher";
|
||||
import Rung from "./Rung";
|
||||
|
||||
type IRenderAttributes<T extends keyof HTMLElementTagNameMap> = Partial<{
|
||||
[K in keyof HTMLElementTagNameMap[T]]: HTMLElementTagNameMap[T][K] | Capsule<HTMLElementTagNameMap[T][K]>
|
||||
}> & {
|
||||
classes?: string[],
|
||||
saveTo?: Capsule<HTMLElementTagNameMap[T] | null>,
|
||||
};
|
||||
|
||||
type IdSelector = `#${ string }`;
|
||||
|
||||
export function bootstrap(app: Rung, id: IdSelector) {
|
||||
const rootNode = document.querySelector(id);
|
||||
if (!rootNode) {
|
||||
throw new Error(`No node was found with the id ${id} to attach to`);
|
||||
} else {
|
||||
rootNode.appendChild(app.render());
|
||||
}
|
||||
}
|
||||
|
||||
export function frag(subs?: Node[]): DocumentFragment {
|
||||
const frag = document.createDocumentFragment();
|
||||
if (subs) {
|
||||
attachSubs(frag, subs);
|
||||
}
|
||||
return frag;
|
||||
}
|
||||
|
||||
export function q(text: string): Text {
|
||||
return document.createTextNode(text);
|
||||
}
|
||||
|
||||
export function h<T extends keyof HTMLElementTagNameMap>(type: T, attributes?: IRenderAttributes<T>, subNodes?: (Rung | Node | Capsule)[]): HTMLElementTagNameMap[T] {
|
||||
const element = document.createElement(type);
|
||||
if (attributes) {
|
||||
if (attributes.classes) {
|
||||
element.classList.add(...attributes.classes);
|
||||
}
|
||||
if (attributes.saveTo) {
|
||||
attributes.saveTo.val = element;
|
||||
}
|
||||
applyAttributes(element, attributes);
|
||||
}
|
||||
if (subNodes) {
|
||||
attachSubs(element, subNodes);
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
function nodeCapsuleWatcher<T>(newVal: T extends Capsule<infer U> ? U : never, textNode: Text, sub: ISubscription): void {
|
||||
if (!textNode.parentNode) {
|
||||
sub.unbind();
|
||||
textNode.remove();
|
||||
} else {
|
||||
textNode.replaceWith(newVal?.toString() ?? q("[dead ref]"));
|
||||
}
|
||||
}
|
||||
|
||||
function attachSubs(node: Element | DocumentFragment, subNodes: (Rung | Node | Capsule)[]): void {
|
||||
for (let i = 0; i < subNodes.length; i++) {
|
||||
const subNode = subNodes[i];
|
||||
if (subNode instanceof Rung) {
|
||||
node.append(subNode.render());
|
||||
} else if (subNode instanceof Capsule) {
|
||||
const textNode = q(subNode.toString());
|
||||
const sub = subNode.watch((newVal) => nodeCapsuleWatcher<Capsule>(newVal, textNode, sub));
|
||||
node.append(textNode);
|
||||
} else {
|
||||
node.append(subNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function applyAttributes<T extends keyof HTMLElementTagNameMap>(element: HTMLElement, attributes: IRenderAttributes<T>): void {
|
||||
for (const key in attributes) {
|
||||
if (Object.prototype.hasOwnProperty.call(attributes, key)) {
|
||||
const attribute = (attributes as Record<string, unknown>)[key];
|
||||
if (attribute) {
|
||||
if (attribute instanceof Capsule) {
|
||||
const attributeAsCapsule = attribute as Capsule;
|
||||
const elementWithAttributeKey = element as unknown as Record<string, typeof attributeAsCapsule.val>;
|
||||
elementWithAttributeKey[key] = attributeAsCapsule.val;
|
||||
attribute.watch((newVal) => elementWithAttributeKey[key] = newVal);
|
||||
} else {
|
||||
(element as unknown as Record<string, typeof attribute>)[key] = attribute;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
13
package.json
13
package.json
@@ -1,12 +1,11 @@
|
||||
{
|
||||
"name": "ladder",
|
||||
"name": "@djledda/ladder",
|
||||
"version": "1.0.0",
|
||||
"description": "other libraries provide you with a whole framework - this is just a ladder",
|
||||
"scripts": { },
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.djledda.de/djledda/ladder"
|
||||
},
|
||||
"scripts": {},
|
||||
"author": "Daniel Ledda <dan.j.ledda@gmail.com>",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"typescript": "^4.7.2"
|
||||
}
|
||||
}
|
||||
|
||||
119
src/Rung.ts
119
src/Rung.ts
@@ -1,119 +0,0 @@
|
||||
import Ref from "./Ref";
|
||||
import { ISubscription } from "./Publisher";
|
||||
|
||||
export type RungOptions = {};
|
||||
|
||||
type IRenderAttributes<T extends keyof HTMLElementTagNameMap> = Partial<{
|
||||
[K in keyof HTMLElementTagNameMap[T]]: HTMLElementTagNameMap[T][K] | Ref<HTMLElementTagNameMap[T][K]>
|
||||
}> & {
|
||||
classes?: string[],
|
||||
saveTo?: Ref<HTMLElementTagNameMap[T] | null>,
|
||||
};
|
||||
|
||||
export default abstract class Rung {
|
||||
protected el: HTMLElement | null = null;
|
||||
|
||||
constructor(options: RungOptions) {}
|
||||
|
||||
render(): HTMLElement {
|
||||
if (!this.el) {
|
||||
this.el = this.build();
|
||||
}
|
||||
return this.el;
|
||||
}
|
||||
|
||||
protected getEl(): HTMLElement {
|
||||
if (!this.el) {
|
||||
return this.render();
|
||||
} else {
|
||||
return this.el;
|
||||
}
|
||||
}
|
||||
|
||||
redraw(): void {
|
||||
const oldNode = this.el;
|
||||
if (!oldNode || !this.el) {
|
||||
return;
|
||||
}
|
||||
const parent = this.el.parentElement;
|
||||
if (parent) {
|
||||
this.el = this.build();
|
||||
parent.replaceChild(this.el, oldNode);
|
||||
} else {
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract build(): HTMLElement;
|
||||
}
|
||||
|
||||
export function frag(subs?: Rung[]): DocumentFragment {
|
||||
const frag = document.createDocumentFragment();
|
||||
if (subs) {
|
||||
attachSubs(frag, subs);
|
||||
}
|
||||
return frag;
|
||||
}
|
||||
|
||||
export function q(text: string): Text {
|
||||
return document.createTextNode(text);
|
||||
}
|
||||
|
||||
export function h<T extends keyof HTMLElementTagNameMap>(type: T, attributes?: IRenderAttributes<T>, subNodes?: (Rung | Node | Ref)[]): HTMLElementTagNameMap[T] {
|
||||
const element = document.createElement(type);
|
||||
if (attributes) {
|
||||
if (attributes.classes) {
|
||||
element.classList.add(...attributes.classes);
|
||||
}
|
||||
if (attributes.saveTo) {
|
||||
attributes.saveTo.val = element;
|
||||
}
|
||||
applyAttributes(element, attributes);
|
||||
}
|
||||
if (subNodes) {
|
||||
attachSubs(element, subNodes);
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
function nodeRefWatcher<T>(newVal: T extends Ref<infer U> ? U : never, textNode: Text, sub: ISubscription): void {
|
||||
if (!textNode.parentNode) {
|
||||
sub.unbind();
|
||||
textNode.remove();
|
||||
} else {
|
||||
textNode.replaceWith(newVal?.toString() ?? "<dead ref>");
|
||||
}
|
||||
}
|
||||
|
||||
function attachSubs(node: Element | DocumentFragment, subNodes: (Rung | Node | Ref)[]): void {
|
||||
for (let i = 0; i < subNodes.length; i++) {
|
||||
const subNode = subNodes[i];
|
||||
if (subNode instanceof Rung) {
|
||||
node.append(subNode.render());
|
||||
} else if (subNode instanceof Ref) {
|
||||
const textNode = q(subNode.val.toString());
|
||||
const sub = subNode.watch((newVal) => nodeRefWatcher<Ref>(newVal, textNode, sub));
|
||||
node.append(textNode);
|
||||
} else {
|
||||
node.append(subNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function applyAttributes<T extends keyof HTMLElementTagNameMap>(element: HTMLElement, attributes: IRenderAttributes<T>): void {
|
||||
for (const key in attributes) {
|
||||
if (Object.prototype.hasOwnProperty.call(attributes, key)) {
|
||||
const attribute = (attributes as Record<string, unknown>)[key];
|
||||
if (attribute) {
|
||||
if (attribute instanceof Ref) {
|
||||
const attributeAsRef = attribute as Ref;
|
||||
const elementWithAttributeKey = element as unknown as Record<string, typeof attributeAsRef.val>;
|
||||
elementWithAttributeKey[key] = attributeAsRef.val;
|
||||
attribute.watch((newVal) => elementWithAttributeKey[key] = newVal);
|
||||
} else {
|
||||
(element as unknown as ({ [key: string]: typeof attribute }))[key] = attribute;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
13
tsconfig.json
Normal file
13
tsconfig.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"sourceMap": true
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
],
|
||||
"include": [
|
||||
"index.ts",
|
||||
"lib/**/*.ts"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user