diff --git a/CHANGELOG.md b/CHANGELOG.md
index fc89d01..4def281 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
+## [0.4.0] - 2024-01-17
+### Added
+ - `Paradox.buildApp` function to create reactive components
+
+### Changed
+ - Typescript types
+
## [0.3.5] - 2024-01-13
### Added
- Docs for the paradox-app example
diff --git a/README.MD b/README.MD
index 423034e..1fb6f3f 100644
--- a/README.MD
+++ b/README.MD
@@ -13,6 +13,12 @@
ยท
Request Features
+
+
+
+
+
+
IMPORTANT: Things are changing a lot right now, so please be patient, this is a work in progress
@@ -32,7 +38,6 @@
- [Project structure example](#project-structure-example)
- [Webpack config](#webpack-config)
- [Paradox as a module in a simple html project](#paradox-as-a-module-in-a-simple-html-project)
- - [Paradox on development mode](#paradox-on-development-mode)
- [Documentation](#documentation)
- [Build an element with `Paradox.buildElement`](#build-an-element-with-paradoxbuildelement)
- [Routes with `Paradox.Router`](#routes-with-paradoxrouter)
@@ -54,11 +59,11 @@ Contributions are welcome thogh xd, please [start a discussion](https://github.c
## Getting Started
-Paradox is a simple vanilla javascript library for DOM manipulation that also provides a simple router and pubsub implementation.
+Paradox is a simple vanilla javascript library for DOM manipulation that also provides a simple router and PubSub implementation.
### Requirements
-- [Node.js](https://nodejs.org/en/) >= v18.17.1 (For development mode)
+- [Node.js](https://nodejs.org/en/) >= v16.16.0 (For development mode)
### Installation
@@ -68,11 +73,10 @@ Paradox is a simple vanilla javascript library for DOM manipulation that also pr
```
2. Install NPM packages
```sh
+ cd penrose-paradox
npm install
```
-
-
## Usage
Now things can change depending on what you want to do, so here are some options:
@@ -156,38 +160,16 @@ module.exports = {
}
```
-### Paradox as a module in a simple html project
+### Paradox as a module in a simple HTML project
-If you want to use it as a module in a simple html project, you should import it in your script like this:
+If you want to use it as a module in a simple HTML project, you should import it in your script like this:
```html
```
-### Paradox on development mode
-
-If you want to build a project that uses paradox, you can run the build script:
-```sh
-npm run paradox-app
-```
-**This will generate the following:**
-- A `server` folder with a simple express server that serves the `dist` folder and redirects all the requests to the `index.html` file.
-- A `scss` folder with a simple `main.scss` file that imports bootstrap.
-- An `app` folder with a simple html project that uses paradox and a `main.js` file that imports paradox and shows a simple example of how to use it.
-- A `webpack.config.js` file that you can use to build your project.
-
-Then, the script will run the dev script, so you can start developing your project by changing the `app` folder and running the dev script again.
-**This will generate a `dist` folder with the built project.**
-
-**Notes:**
-- The `server` folder is just a simple example, you can delete it and create your own server.
-- The `scss` folder is just a simple example, you can delete it and create your own scss files.
-- The `app` folder is just a simple example, you can delete it and create your own project.
-- The `webpack.config.js` file is just a simple example, you can delete it and create your own webpack config file.
-- The `dist` folder will contain the javascript bundle and a css file with the styles. (The css file is generated from the `scss/main.scss` file)
-
## Documentation
Paradox includes the following features:
@@ -209,7 +191,7 @@ Paradox provides a simple way to build an element with the `buildElement` functi
| children | array | The element children |
```javascript
-import Paradox from "paradox";
+import Paradox from "penrose-paradox";
function handleButtonClick() {
alert("Hello World!");
@@ -252,7 +234,7 @@ document.body.appendChild(Paradox.buildElement("button", myButton));
Paradox provides a simple router to handle the navigation between pages.
```javascript
-import Paradox from "paradox";
+import Paradox from "penrose-paradox";
function Home(props) {
const { root } = props;
@@ -319,7 +301,7 @@ In the PubSub pattern, publishers send messages without knowing who the subscrib
This pattern is widely used in event-driven programming and can help to decouple the components of an application, leading to code that is easier to maintain and extend. In the context of Paradox, it allows components to communicate with each other in a decoupled manner.
```javascript
-import Paradox from "paradox";
+import Paradox from "penrose-paradox";
Paradox.pubsub.subscribe("myEvent", (data) => {
console.log(data);
diff --git a/ROADMAP.md b/ROADMAP.md
index c36c567..b6296f3 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -16,7 +16,7 @@ The following features are not directly related to the library itself, but are s
- [ ] Finish community guidelines
- [ ] Automate release process
- [ ] Automate changelog generation
-- [ ] Add badges to README
+- [x] Add badges to README
### Features
@@ -26,7 +26,8 @@ The following features are planned to be implemented before the first stable rel
- [x] Add typescript watch mode to `npm run dev`
- [ ] Add state management support
-- [ ] Add `Paradox.app`. This will allow users to create reactive components.
+- [X] Add `Paradox.buildApp`. This will allow users to create reactive components.
+- [ ] Add `Paradox.buildApp` tests
#### buildElement
@@ -41,3 +42,11 @@ The following features are planned to be implemented before the first stable rel
- [ ] Add `once` method so that a subscriber can be removed after it has been called once
- [ ] Add `clear` method to remove all subscribers
+#### utils
+
+- [ ] Add `debounce` method
+- [ ] Add `throttle` method
+
+#### Docs
+
+- [ ] Turn examples into docs
diff --git a/build/core/buildApp/helpers/buildVirtualDOM.d.ts b/build/core/buildApp/helpers/buildVirtualDOM.d.ts
new file mode 100644
index 0000000..52374db
--- /dev/null
+++ b/build/core/buildApp/helpers/buildVirtualDOM.d.ts
@@ -0,0 +1,2 @@
+import { ParadoxElement, ParadoxVirtualElement } from "../types";
+export default function buildVirtualDOM(vTree: ParadoxElement | ParadoxElement[]): ParadoxVirtualElement[];
diff --git a/build/core/buildApp/helpers/buildVirtualDOM.js b/build/core/buildApp/helpers/buildVirtualDOM.js
new file mode 100644
index 0000000..785861a
--- /dev/null
+++ b/build/core/buildApp/helpers/buildVirtualDOM.js
@@ -0,0 +1,29 @@
+"use strict";
+var __importDefault = (this && this.__importDefault) || function (mod) {
+ return (mod && mod.__esModule) ? mod : { "default": mod };
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+const createElement_1 = __importDefault(require("./createElement"));
+function buildVirtualDOM(vTree) {
+ let vDOM = [];
+ if (!Array.isArray(vTree))
+ vTree = [vTree];
+ for (const elementObj of vTree) {
+ let elementObject = elementObj;
+ if (typeof elementObj === "function")
+ elementObject = elementObj();
+ if (typeof elementObject === "string") {
+ vDOM.push(elementObject);
+ continue;
+ }
+ for (const [key, value] of Object.entries(elementObject)) {
+ let { attrs = {}, events = {}, children = [] } = value;
+ if (children.length) {
+ children = buildVirtualDOM(children);
+ }
+ vDOM.push((0, createElement_1.default)(key, { attrs, events, children }));
+ }
+ }
+ return vDOM;
+}
+exports.default = buildVirtualDOM;
diff --git a/build/core/buildApp/helpers/createElement.d.ts b/build/core/buildApp/helpers/createElement.d.ts
new file mode 100644
index 0000000..3c0b21c
--- /dev/null
+++ b/build/core/buildApp/helpers/createElement.d.ts
@@ -0,0 +1,2 @@
+import { ParadoxElement, ParadoxVirtualElement } from '../types';
+export default function createElement(tagName: string, options?: ParadoxElement): ParadoxVirtualElement;
diff --git a/build/core/buildApp/helpers/createElement.js b/build/core/buildApp/helpers/createElement.js
new file mode 100644
index 0000000..d9debbb
--- /dev/null
+++ b/build/core/buildApp/helpers/createElement.js
@@ -0,0 +1,22 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+function createElement(tagName, options = { children: [], events: {}, attrs: {} }) {
+ if (!tagName)
+ throw new Error("tagName is required");
+ let children = [];
+ let events = {};
+ let attrs = {};
+ if (typeof options === 'object' && !Array.isArray(options) && 'children' in options) {
+ children = options.children || [];
+ events = options.events || {};
+ attrs = options.attrs || {};
+ }
+ return {
+ tagName,
+ attrs,
+ children,
+ events,
+ };
+}
+exports.default = createElement;
+;
diff --git a/build/core/buildApp/helpers/createVirtualDOM.d.ts b/build/core/buildApp/helpers/createVirtualDOM.d.ts
new file mode 100644
index 0000000..20308d9
--- /dev/null
+++ b/build/core/buildApp/helpers/createVirtualDOM.d.ts
@@ -0,0 +1,2 @@
+import { ParadoxElement } from "../types";
+export default function createVirtualDOM(treeFunc: Function): ParadoxElement | ParadoxElement[];
diff --git a/build/core/buildApp/helpers/createVirtualDOM.js b/build/core/buildApp/helpers/createVirtualDOM.js
new file mode 100644
index 0000000..b3a6d85
--- /dev/null
+++ b/build/core/buildApp/helpers/createVirtualDOM.js
@@ -0,0 +1,6 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+function createVirtualDOM(treeFunc) {
+ return treeFunc();
+}
+exports.default = createVirtualDOM;
diff --git a/build/core/buildApp/helpers/diff.d.ts b/build/core/buildApp/helpers/diff.d.ts
new file mode 100644
index 0000000..0f16e69
--- /dev/null
+++ b/build/core/buildApp/helpers/diff.d.ts
@@ -0,0 +1,2 @@
+import { ParadoxVirtualElement, Patch } from "../types";
+export default function diff(originalOldTree: ParadoxVirtualElement[], originalNewTree: ParadoxVirtualElement[]): Patch;
diff --git a/build/core/buildApp/helpers/diff.js b/build/core/buildApp/helpers/diff.js
new file mode 100644
index 0000000..fe8ecbd
--- /dev/null
+++ b/build/core/buildApp/helpers/diff.js
@@ -0,0 +1,95 @@
+"use strict";
+var __importDefault = (this && this.__importDefault) || function (mod) {
+ return (mod && mod.__esModule) ? mod : { "default": mod };
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+const render_1 = __importDefault(require("./render"));
+function zip(xs, ys) {
+ const zipped = [];
+ for (let i = 0; i < Math.min(xs.length, ys.length); i++) {
+ zipped.push([xs[i], ys[i]]);
+ }
+ return zipped;
+}
+function diffAttrs(oldAttrs, newAttrs) {
+ const patches = [];
+ for (const [key, value] of Object.entries(newAttrs)) {
+ patches.push(node => {
+ node.setAttribute(key, value);
+ return node;
+ });
+ }
+ for (const key of Object.keys(oldAttrs)) {
+ if (!(key in newAttrs)) {
+ patches.push(node => {
+ node.removeAttribute(key);
+ return node;
+ });
+ }
+ }
+ return (node) => {
+ for (const patch of patches) {
+ patch(node);
+ }
+ };
+}
+function diffChildren(oldChildren, newChildren) {
+ const patches = [];
+ for (const [oldChild, newChild] of zip(oldChildren, newChildren)) {
+ patches.push(diff(oldChild, newChild));
+ }
+ const additionalPatches = [];
+ for (const additionalChild of newChildren.slice(oldChildren.length)) {
+ additionalPatches.push(node => {
+ node.appendChild((0, render_1.default)(additionalChild));
+ return node;
+ });
+ }
+ return (parent) => {
+ for (const [patch, child] of zip(patches, parent.childNodes)) {
+ patch(child);
+ }
+ for (const patch of additionalPatches) {
+ patch(parent);
+ }
+ return parent;
+ };
+}
+function diff(originalOldTree, originalNewTree) {
+ const oldTree = originalOldTree[0];
+ const newTree = originalNewTree[0];
+ if (!newTree) {
+ return (node) => {
+ node.remove();
+ return undefined;
+ };
+ }
+ if (typeof oldTree === "string" || typeof newTree === "string") {
+ if (oldTree !== newTree) {
+ return (node) => {
+ const newNode = (0, render_1.default)(newTree);
+ node.replaceWith(newNode);
+ return newNode;
+ };
+ }
+ else {
+ return (node) => undefined;
+ }
+ }
+ if (oldTree.tagName !== newTree.tagName) {
+ return (node) => {
+ const newNode = (0, render_1.default)(newTree);
+ node.replaceWith(newNode);
+ return newTree;
+ };
+ }
+ const patchAttr = diffAttrs(oldTree.attrs, newTree.attrs);
+ const patchChildren = diffChildren(oldTree.children, newTree.children);
+ return (node) => {
+ patchAttr(node);
+ patchChildren(node);
+ return node;
+ };
+}
+exports.default = diff;
+;
diff --git a/build/core/buildApp/helpers/mount.d.ts b/build/core/buildApp/helpers/mount.d.ts
new file mode 100644
index 0000000..bf781be
--- /dev/null
+++ b/build/core/buildApp/helpers/mount.d.ts
@@ -0,0 +1 @@
+export default function mount(vnode: HTMLElement, target: HTMLElement): HTMLElement;
diff --git a/build/core/buildApp/helpers/mount.js b/build/core/buildApp/helpers/mount.js
new file mode 100644
index 0000000..5f438bb
--- /dev/null
+++ b/build/core/buildApp/helpers/mount.js
@@ -0,0 +1,7 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+function mount(vnode, target) {
+ target.replaceWith(vnode);
+ return vnode;
+}
+exports.default = mount;
diff --git a/build/core/buildApp/helpers/render.d.ts b/build/core/buildApp/helpers/render.d.ts
new file mode 100644
index 0000000..e2d6285
--- /dev/null
+++ b/build/core/buildApp/helpers/render.d.ts
@@ -0,0 +1,2 @@
+import { ParadoxVirtualElement } from "../types";
+export default function render(vnode: ParadoxVirtualElement): HTMLElement | Text;
diff --git a/build/core/buildApp/helpers/render.js b/build/core/buildApp/helpers/render.js
new file mode 100644
index 0000000..443a80f
--- /dev/null
+++ b/build/core/buildApp/helpers/render.js
@@ -0,0 +1,43 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+function renderEle(vnode) {
+ let tagName = "";
+ let attrs = {};
+ let children = [];
+ let events = {};
+ if (typeof vnode === "object") {
+ tagName = vnode.tagName;
+ attrs = vnode.attrs;
+ children = vnode.children;
+ events = vnode.events || {};
+ }
+ const element = document.createElement(tagName);
+ for (const [key, value] of Object.entries(attrs)) {
+ element.setAttribute(key, value.toString());
+ }
+ for (const child of children) {
+ const $child = render(child);
+ element.appendChild($child);
+ }
+ for (const [key, value] of Object.entries(events)) {
+ if (Array.isArray(value)) {
+ for (const event of value) {
+ element.addEventListener(key, event);
+ }
+ continue;
+ }
+ else {
+ element.addEventListener(key, value);
+ }
+ }
+ return element;
+}
+;
+function render(vnode) {
+ if (typeof vnode === "string") {
+ return document.createTextNode(vnode);
+ }
+ return renderEle(vnode);
+}
+exports.default = render;
+;
diff --git a/build/core/buildApp/helpers/renderVirtualDOM.d.ts b/build/core/buildApp/helpers/renderVirtualDOM.d.ts
new file mode 100644
index 0000000..f1c551b
--- /dev/null
+++ b/build/core/buildApp/helpers/renderVirtualDOM.d.ts
@@ -0,0 +1,4 @@
+import { ParadoxVirtualElement } from '../types';
+export declare let targetNodeCache: HTMLElement;
+export declare function setTargetNodeCache(targetNode: HTMLElement): void;
+export default function renderVirtualDOM(vDOM: ParadoxVirtualElement[], targetNode: HTMLElement): void;
diff --git a/build/core/buildApp/helpers/renderVirtualDOM.js b/build/core/buildApp/helpers/renderVirtualDOM.js
new file mode 100644
index 0000000..1cbf8e5
--- /dev/null
+++ b/build/core/buildApp/helpers/renderVirtualDOM.js
@@ -0,0 +1,20 @@
+"use strict";
+var __importDefault = (this && this.__importDefault) || function (mod) {
+ return (mod && mod.__esModule) ? mod : { "default": mod };
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.setTargetNodeCache = exports.targetNodeCache = void 0;
+const render_1 = __importDefault(require("./render"));
+const mount_1 = __importDefault(require("./mount"));
+exports.targetNodeCache = document.body;
+function setTargetNodeCache(targetNode) {
+ exports.targetNodeCache = targetNode;
+}
+exports.setTargetNodeCache = setTargetNodeCache;
+function renderVirtualDOM(vDOM, targetNode) {
+ vDOM.forEach((vnode) => {
+ const $node = (0, render_1.default)(vnode);
+ exports.targetNodeCache = (0, mount_1.default)($node, targetNode);
+ });
+}
+exports.default = renderVirtualDOM;
diff --git a/build/core/buildApp/index.d.ts b/build/core/buildApp/index.d.ts
index 644ae62..a3fec5a 100644
--- a/build/core/buildApp/index.d.ts
+++ b/build/core/buildApp/index.d.ts
@@ -1,19 +1,4 @@
-type DataSet = {
- [key: string]: string;
-};
-type HTMLAttributes = {
- [key: string]: string | number | boolean | DataSet;
-};
-type ParadoxEvents = {
- [key: string]: EventListener | EventListener[];
-};
-type ParadoxAppFunction = () => ParadoxElement | ParadoxElement[];
-type ParadoxElementChildren = (ParadoxElement | string)[];
-type ParadoxElement = {
- attrs: HTMLAttributes;
- events?: ParadoxEvents;
- children: ParadoxElementChildren;
-} | ParadoxAppFunction | string | ParadoxElement[];
+import { ParadoxAppFunction } from "./types";
type State = any;
type StateCallback = (val: any) => void;
export declare function addState(value: any): [State, StateCallback];
diff --git a/build/core/buildApp/index.js b/build/core/buildApp/index.js
index f409401..a2cd348 100644
--- a/build/core/buildApp/index.js
+++ b/build/core/buildApp/index.js
@@ -1,198 +1,46 @@
"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ var desc = Object.getOwnPropertyDescriptor(m, k);
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
+ desc = { enumerable: true, get: function() { return m[k]; } };
+ }
+ Object.defineProperty(o, k2, desc);
+}) : (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ o[k2] = m[k];
+}));
+var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
+}) : function(o, v) {
+ o["default"] = v;
+});
+var __importStar = (this && this.__importStar) || function (mod) {
+ if (mod && mod.__esModule) return mod;
+ var result = {};
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
+ __setModuleDefault(result, mod);
+ return result;
+};
+var __importDefault = (this && this.__importDefault) || function (mod) {
+ return (mod && mod.__esModule) ? mod : { "default": mod };
+};
Object.defineProperty(exports, "__esModule", { value: true });
exports.addState = void 0;
-function createVirtualDOM(treeFunc) {
- return treeFunc();
-}
-function createElement(tagName, options = { children: [], events: {}, attrs: {} }) {
- if (!tagName)
- throw new Error("tagName is required");
- let children = [];
- let events = {};
- let attrs = {};
- if (typeof options === 'object' && !Array.isArray(options) && 'children' in options) {
- children = options.children || [];
- events = options.events || {};
- attrs = options.attrs || {};
- }
- return {
- tagName,
- attrs,
- children,
- events,
- };
-}
-;
-function buildVirtualDOM(vTree) {
- let vDOM = [];
- if (!Array.isArray(vTree))
- vTree = [vTree];
- for (const elementObj of vTree) {
- let elementObject = elementObj;
- if (typeof elementObj === "function")
- elementObject = elementObj();
- if (typeof elementObject === "string") {
- vDOM.push(elementObject);
- continue;
- }
- for (const [key, value] of Object.entries(elementObject)) {
- let { attrs = {}, events = {}, children = [] } = value;
- if (children.length) {
- children = buildVirtualDOM(children);
- }
- vDOM.push(createElement(key, { attrs, events, children }));
- }
- }
- return vDOM;
-}
-function renderEle(vnode) {
- let tagName = "";
- let attrs = {};
- let children = [];
- let events = {};
- if (typeof vnode === "object") {
- tagName = vnode.tagName;
- attrs = vnode.attrs;
- children = vnode.children;
- events = vnode.events || {};
- }
- const element = document.createElement(tagName);
- for (const [key, value] of Object.entries(attrs)) {
- element.setAttribute(key, value.toString());
- }
- for (const child of children) {
- const $child = render(child);
- element.appendChild($child);
- }
- for (const [key, value] of Object.entries(events)) {
- if (Array.isArray(value)) {
- for (const event of value) {
- element.addEventListener(key, event);
- }
- continue;
- }
- else {
- element.addEventListener(key, value);
- }
- }
- return element;
-}
-;
-function render(vnode) {
- if (typeof vnode === "string") {
- return document.createTextNode(vnode);
- }
- return renderEle(vnode);
-}
-;
-function mount(vnode, target) {
- target.replaceWith(vnode);
- return vnode;
-}
-function renderVirtualDOM(vDOM, targetNode) {
- vDOM.forEach((vnode) => {
- const $node = render(vnode);
- targetNodeCache = mount($node, targetNode);
- });
-}
-function diffAttrs(oldAttrs, newAttrs) {
- const patches = [];
- for (const [key, value] of Object.entries(newAttrs)) {
- patches.push(node => {
- node.setAttribute(key, value);
- return node;
- });
- }
- for (const key of Object.keys(oldAttrs)) {
- if (!(key in newAttrs)) {
- patches.push(node => {
- node.removeAttribute(key);
- return node;
- });
- }
- }
- return (node) => {
- for (const patch of patches) {
- patch(node);
- }
- };
-}
-function zip(xs, ys) {
- const zipped = [];
- for (let i = 0; i < Math.min(xs.length, ys.length); i++) {
- zipped.push([xs[i], ys[i]]);
- }
- return zipped;
-}
-function diffChildren(oldChildren, newChildren) {
- const patches = [];
- for (const [oldChild, newChild] of zip(oldChildren, newChildren)) {
- patches.push(diff(oldChild, newChild));
- }
- const additionalPatches = [];
- for (const additionalChild of newChildren.slice(oldChildren.length)) {
- additionalPatches.push(node => {
- node.appendChild(render(additionalChild));
- return node;
- });
- }
- return (parent) => {
- for (const [patch, child] of zip(patches, parent.childNodes)) {
- patch(child);
- }
- for (const patch of additionalPatches) {
- patch(parent);
- }
- return parent;
- };
-}
-function diff(originalOldTree, originalNewTree) {
- const oldTree = originalOldTree[0];
- const newTree = originalNewTree[0];
- if (!newTree) {
- return (node) => {
- node.remove();
- return undefined;
- };
- }
- if (typeof oldTree === "string" || typeof newTree === "string") {
- if (oldTree !== newTree) {
- return (node) => {
- const newNode = render(newTree);
- node.replaceWith(newNode);
- return newNode;
- };
- }
- else {
- return (node) => undefined;
- }
- }
- if (oldTree.tagName !== newTree.tagName) {
- return (node) => {
- const newNode = render(newTree);
- node.replaceWith(newNode);
- return newTree;
- };
- }
- const patchAttr = diffAttrs(oldTree.attrs, newTree.attrs);
- const patchChildren = diffChildren(oldTree.children, newTree.children);
- return (node) => {
- patchAttr(node);
- patchChildren(node);
- return node;
- };
-}
-;
+const createVirtualDOM_1 = __importDefault(require("./helpers/createVirtualDOM"));
+const buildVirtualDOM_1 = __importDefault(require("./helpers/buildVirtualDOM"));
+const renderVirtualDOM_1 = __importStar(require("./helpers/renderVirtualDOM"));
+const diff_1 = __importDefault(require("./helpers/diff"));
function addState(value) {
let state = value;
const callback = (val) => {
state = val;
- const newVTree = createVirtualDOM(treeFuncCache);
- const newVDOM = buildVirtualDOM(newVTree);
- if (diff(vDOM, newVDOM)) {
+ const newVTree = (0, createVirtualDOM_1.default)(treeFuncCache);
+ const newVDOM = (0, buildVirtualDOM_1.default)(newVTree);
+ if ((0, diff_1.default)(vDOM, newVDOM)) {
vDOM = newVDOM;
console.log("rendering");
- renderVirtualDOM(vDOM, targetNodeCache);
+ (0, renderVirtualDOM_1.default)(vDOM, renderVirtualDOM_1.targetNodeCache);
}
};
return [state, callback];
@@ -201,22 +49,11 @@ exports.addState = addState;
let vTree = {};
let vDOM = {};
let treeFuncCache;
-let targetNodeCache = document.body;
function app(treeFunc, targetNode) {
treeFuncCache = treeFunc;
- targetNodeCache = targetNode;
- vTree = createVirtualDOM(treeFunc);
- vDOM = buildVirtualDOM(vTree);
- renderVirtualDOM(vDOM, targetNode);
- // onStateChange(proxyObj, () => {
- // console.log(proxyObj);
- // const newVTree = createVirtualDOM(treeFunc);
- // const newVDOM = buildVirtualDOM(newVTree);
- // console.log(vDOM, newVDOM);
- // // if (diff(vDOM, newVDOM)) {
- // // vDOM = newVDOM;
- // // renderVirtualDOM(vDOM, targetNode);
- // // }
- // });
+ (0, renderVirtualDOM_1.setTargetNodeCache)(targetNode);
+ vTree = (0, createVirtualDOM_1.default)(treeFunc);
+ vDOM = (0, buildVirtualDOM_1.default)(vTree);
+ (0, renderVirtualDOM_1.default)(vDOM, targetNode);
}
exports.default = app;
diff --git a/build/core/buildApp/types/index.d.ts b/build/core/buildApp/types/index.d.ts
new file mode 100644
index 0000000..4864f82
--- /dev/null
+++ b/build/core/buildApp/types/index.d.ts
@@ -0,0 +1,24 @@
+type DataSet = {
+ [key: string]: string;
+};
+export type HTMLAttributes = {
+ [key: string]: string | number | boolean | DataSet;
+};
+export type ParadoxEvents = {
+ [key: string]: EventListener | EventListener[];
+};
+export type ParadoxElementChildren = (ParadoxElement | string)[];
+export type ParadoxAppFunction = () => ParadoxElement | ParadoxElement[];
+export type ParadoxElement = {
+ attrs: HTMLAttributes;
+ events?: ParadoxEvents;
+ children: ParadoxElementChildren;
+} | ParadoxAppFunction | string | ParadoxElement[];
+export type ParadoxVirtualElement = {
+ tagName: string;
+ attrs: HTMLAttributes;
+ children: ParadoxElementChildren;
+ events?: ParadoxEvents;
+} | string;
+export type Patch = (node: HTMLElement) => HTMLElement | Text | undefined | ParadoxVirtualElement;
+export {};
diff --git a/build/core/buildApp/types/index.js b/build/core/buildApp/types/index.js
new file mode 100644
index 0000000..c8ad2e5
--- /dev/null
+++ b/build/core/buildApp/types/index.js
@@ -0,0 +1,2 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
diff --git a/build/core/buildElement/helpers/appendChildren.d.ts b/build/core/buildElement/helpers/appendChildren.d.ts
index 7e654a1..f9774bd 100644
--- a/build/core/buildElement/helpers/appendChildren.d.ts
+++ b/build/core/buildElement/helpers/appendChildren.d.ts
@@ -1,2 +1,9 @@
+/**
+ * Appends an array of child elements to a parent element.
+ *
+ * @param element - The parent element to append the children to.
+ * @param children - An array of child elements to append.
+ * @param buildElement - A function that builds an element based on a tag and options.
+ */
declare function appendChildren(element: HTMLElement, children: Array, buildElement: Function): void;
export default appendChildren;
diff --git a/build/core/buildElement/helpers/appendChildren.js b/build/core/buildElement/helpers/appendChildren.js
index d451b06..98bc34d 100644
--- a/build/core/buildElement/helpers/appendChildren.js
+++ b/build/core/buildElement/helpers/appendChildren.js
@@ -1,5 +1,12 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
+/**
+ * Appends an array of child elements to a parent element.
+ *
+ * @param element - The parent element to append the children to.
+ * @param children - An array of child elements to append.
+ * @param buildElement - A function that builds an element based on a tag and options.
+ */
function appendChildren(element, children, buildElement) {
// Create a Document Fragment to efficiently append children
const fragment = document.createDocumentFragment();
diff --git a/build/core/buildElement/helpers/applyStyles.d.ts b/build/core/buildElement/helpers/applyStyles.d.ts
index e13aab9..b8db7a1 100644
--- a/build/core/buildElement/helpers/applyStyles.d.ts
+++ b/build/core/buildElement/helpers/applyStyles.d.ts
@@ -1,4 +1,11 @@
-declare function applyStyles(element: HTMLElement, style: {
+type ParadoxStyleKeys = {
[key: string]: string;
-}): void;
+};
+/**
+ * Applies the given styles to the specified HTML element.
+ *
+ * @param element - The HTML element to apply the styles to.
+ * @param style - The styles to apply, represented as an object with keys and values.
+ */
+declare function applyStyles(element: HTMLElement, style: ParadoxStyleKeys): void;
export default applyStyles;
diff --git a/build/core/buildElement/helpers/applyStyles.js b/build/core/buildElement/helpers/applyStyles.js
index d2d22ff..7400d28 100644
--- a/build/core/buildElement/helpers/applyStyles.js
+++ b/build/core/buildElement/helpers/applyStyles.js
@@ -3,6 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
// Object for caching converted style keys
const memoizedStyleKeys = {};
// Function to convert camelCase into kebab-case for CSS properties
+/**
+ * Converts a camelCase style key to kebab-case and returns the converted key.
+ * If the key has already been processed, the cached value is returned.
+ * @param key - The style key to convert.
+ * @returns The converted style key.
+ */
function getStyleKey(key = "") {
// Check if the key is already processed and return the cached value if so
if (memoizedStyleKeys[key] !== undefined) {
@@ -15,6 +21,12 @@ function getStyleKey(key = "") {
// Return the converted key
return styleKey;
}
+/**
+ * Applies the given styles to the specified HTML element.
+ *
+ * @param element - The HTML element to apply the styles to.
+ * @param style - The styles to apply, represented as an object with keys and values.
+ */
function applyStyles(element, style) {
const styleDeclaration = element.style;
// Apply inline style to the element by converting keys from camelCase
diff --git a/build/core/buildElement/helpers/createElement.d.ts b/build/core/buildElement/helpers/createElement.d.ts
index b56d93b..09183a5 100644
--- a/build/core/buildElement/helpers/createElement.d.ts
+++ b/build/core/buildElement/helpers/createElement.d.ts
@@ -1,2 +1,8 @@
+/**
+ * Creates an HTML element with the specified element name.
+ * If the element has been created before, it returns a clone of the cached element.
+ * @param elementName - The name of the HTML element to create.
+ * @returns The created HTML element.
+ */
declare function createElement(elementName: string): HTMLElement;
export default createElement;
diff --git a/build/core/buildElement/helpers/createElement.js b/build/core/buildElement/helpers/createElement.js
index 41237f0..de44f22 100644
--- a/build/core/buildElement/helpers/createElement.js
+++ b/build/core/buildElement/helpers/createElement.js
@@ -1,6 +1,12 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const elementsCache = {};
+/**
+ * Creates an HTML element with the specified element name.
+ * If the element has been created before, it returns a clone of the cached element.
+ * @param elementName - The name of the HTML element to create.
+ * @returns The created HTML element.
+ */
function createElement(elementName) {
if (elementsCache[elementName])
return elementsCache[elementName].cloneNode();
diff --git a/build/core/buildElement/helpers/getText.d.ts b/build/core/buildElement/helpers/getText.d.ts
index 1dea82f..d55b7c7 100644
--- a/build/core/buildElement/helpers/getText.d.ts
+++ b/build/core/buildElement/helpers/getText.d.ts
@@ -1,2 +1,9 @@
+/**
+ * Retrieves the formatted text for a given input.
+ * If the text has been processed and cached, it returns the cached result.
+ * If it's the first time, it computes the formatted text, caches it, and returns the result.
+ * @param text - The input text to be formatted.
+ * @returns The formatted text.
+ */
declare function getText(text?: string): string;
export default getText;
diff --git a/build/core/buildElement/helpers/getText.js b/build/core/buildElement/helpers/getText.js
index 5661cf9..852f826 100644
--- a/build/core/buildElement/helpers/getText.js
+++ b/build/core/buildElement/helpers/getText.js
@@ -1,16 +1,31 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
-// Object for caching processed text values
const memoizedText = {};
// Function to retrieve or compute a formatted text value
+/**
+ * Formats the given text by replacing any occurrences of "\\xHH" with the corresponding character.
+ *
+ * @param text The text to format.
+ * @returns The formatted text.
+ */
+function formatText(text = "") {
+ return text.replace(/\\x([0-9A-Fa-f]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)));
+}
+/**
+ * Retrieves the formatted text for a given input.
+ * If the text has been processed and cached, it returns the cached result.
+ * If it's the first time, it computes the formatted text, caches it, and returns the result.
+ * @param text - The input text to be formatted.
+ * @returns The formatted text.
+ */
function getText(text = "") {
// Check if the text has been processed and cached; return it if so
if (memoizedText[text] !== undefined) {
return memoizedText[text];
}
// If it's the first time, compute the formatted text
- const result = typeof text === "string"
- ? text.replace(/\\x([0-9A-Fa-f]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)))
+ const result = (typeof text === "string")
+ ? formatText(text)
: String(text);
// Cache the result for future use
memoizedText[text] = result;
diff --git a/build/core/buildElement/helpers/handleEvents.d.ts b/build/core/buildElement/helpers/handleEvents.d.ts
index 96c7786..030a7dc 100644
--- a/build/core/buildElement/helpers/handleEvents.d.ts
+++ b/build/core/buildElement/helpers/handleEvents.d.ts
@@ -1,3 +1,9 @@
+/**
+ * Attaches event listeners to an HTML element.
+ *
+ * @param element - The HTML element to attach the event listeners to.
+ * @param events - An object containing event names as keys and event listeners as values.
+ */
declare function handleEvents(element: HTMLElement, events: {
[key: string]: EventListener;
}): void;
diff --git a/build/core/buildElement/helpers/handleEvents.js b/build/core/buildElement/helpers/handleEvents.js
index 9078f4d..f5376ea 100644
--- a/build/core/buildElement/helpers/handleEvents.js
+++ b/build/core/buildElement/helpers/handleEvents.js
@@ -2,6 +2,12 @@
Object.defineProperty(exports, "__esModule", { value: true });
// WeakMap to store event listeners for each element
const eventListeners = new WeakMap();
+/**
+ * Attaches event listeners to an HTML element.
+ *
+ * @param element - The HTML element to attach the event listeners to.
+ * @param events - An object containing event names as keys and event listeners as values.
+ */
function handleEvents(element, events) {
// Retrieve or create the event listeners Map for this particular element
let elementEvents = eventListeners.get(element);
diff --git a/build/core/buildElement/helpers/setAttributes.d.ts b/build/core/buildElement/helpers/setAttributes.d.ts
index f7c8a6d..d049eb8 100644
--- a/build/core/buildElement/helpers/setAttributes.d.ts
+++ b/build/core/buildElement/helpers/setAttributes.d.ts
@@ -1,3 +1,9 @@
+/**
+ * Sets the attributes of an HTML element.
+ * If the attribute is a boolean attribute, it will only be set if the value is truthy.
+ * @param element - The HTML element to set the attributes for.
+ * @param attributes - An object containing the attribute key-value pairs.
+ */
declare function setAttributes(element: HTMLElement, attributes: {
[key: string]: string;
}): void;
diff --git a/build/core/buildElement/helpers/setAttributes.js b/build/core/buildElement/helpers/setAttributes.js
index 7f52249..1e34989 100644
--- a/build/core/buildElement/helpers/setAttributes.js
+++ b/build/core/buildElement/helpers/setAttributes.js
@@ -12,6 +12,12 @@ const booleanAttributes = [
"formnovalidate",
"autocompleted",
];
+/**
+ * Sets the attributes of an HTML element.
+ * If the attribute is a boolean attribute, it will only be set if the value is truthy.
+ * @param element - The HTML element to set the attributes for.
+ * @param attributes - An object containing the attribute key-value pairs.
+ */
function setAttributes(element, attributes) {
for (const [key, value] of Object.entries(attributes)) {
// Attributes like disabled, checked, selected need special handling
diff --git a/build/core/buildElement/index.d.ts b/build/core/buildElement/index.d.ts
index 9ad48de..3f6a972 100644
--- a/build/core/buildElement/index.d.ts
+++ b/build/core/buildElement/index.d.ts
@@ -1,7 +1,7 @@
-type ParadoxElement = {
+type ParadoxElementOptions = {
id?: string;
classList?: string;
- children?: ParadoxElement[];
+ children?: ParadoxElementOptions[];
attributes?: {
[key: string]: string;
};
@@ -27,5 +27,5 @@ type ParadoxElement = {
* @param {Object} [options.style={}] - The key-value pairs of inline styles for the element.
* @returns {HTMLElement} - The constructed HTML element.
*/
-export default function buildElement(tag: string, options?: ParadoxElement): HTMLElement;
+export default function buildElement(tag: string, options?: ParadoxElementOptions): HTMLElement;
export {};
diff --git a/build/index.d.ts b/build/index.d.ts
index eaa966f..145b803 100644
--- a/build/index.d.ts
+++ b/build/index.d.ts
@@ -1,5 +1,6 @@
import buildElement from "./core/buildElement";
import Router from "./core/Router";
+import buildApp from "./core/buildApp";
/**
* Represents the Paradox object.
*/
@@ -14,5 +15,6 @@ declare const Paradox: {
unsubscribe(event: string, callback: (data: any) => void): Set<(data: any) => void>;
publish(event: string, data?: object): any[];
};
+ buildApp: typeof buildApp;
};
export default Paradox;
diff --git a/build/index.js b/build/index.js
index d08340d..a32388e 100644
--- a/build/index.js
+++ b/build/index.js
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
const buildElement_1 = __importDefault(require("./core/buildElement"));
const Router_1 = __importDefault(require("./core/Router"));
const Pubsub_1 = __importDefault(require("./core/Pubsub"));
-// import app from "./core/app";
+const buildApp_1 = __importDefault(require("./core/buildApp"));
/**
* Represents the Paradox object.
*/
@@ -14,6 +14,6 @@ const Paradox = {
buildElement: buildElement_1.default,
Router: Router_1.default,
pubsub: Pubsub_1.default,
- // app NOT PULISHED YET
+ buildApp: buildApp_1.default,
};
exports.default = Paradox;
diff --git a/package.json b/package.json
index 8dcef58..cc9f73e 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "penrose-paradox",
- "version": "0.3.5",
+ "version": "0.4.0",
"description": "Simple vanilla JavaScript library for beginners",
"keywords": [
"penrose",
diff --git a/src/core/buildApp/helpers/buildVirtualDOM.ts b/src/core/buildApp/helpers/buildVirtualDOM.ts
new file mode 100644
index 0000000..651e826
--- /dev/null
+++ b/src/core/buildApp/helpers/buildVirtualDOM.ts
@@ -0,0 +1,29 @@
+import createElement from "./createElement";
+
+import { ParadoxElement, ParadoxVirtualElement } from "../types";
+
+export default function buildVirtualDOM(vTree: ParadoxElement | ParadoxElement[]): ParadoxVirtualElement[] {
+ let vDOM: ParadoxVirtualElement[] = [];
+
+ if (!Array.isArray(vTree)) vTree = [vTree];
+
+ for (const elementObj of vTree) {
+ let elementObject = elementObj;
+ if (typeof elementObj === "function") elementObject = elementObj();
+
+ if (typeof elementObject === "string") {
+ vDOM.push(elementObject);
+ continue;
+ }
+
+ for (const [key, value] of Object.entries(elementObject)) {
+ let { attrs = {}, events = {}, children = [] } = value;
+ if (children.length) {
+ children = buildVirtualDOM(children);
+ }
+ vDOM.push(createElement(key, { attrs, events, children } as ParadoxElement));
+ }
+ }
+
+ return vDOM;
+}
\ No newline at end of file
diff --git a/src/core/buildApp/helpers/createElement.ts b/src/core/buildApp/helpers/createElement.ts
new file mode 100644
index 0000000..1266b87
--- /dev/null
+++ b/src/core/buildApp/helpers/createElement.ts
@@ -0,0 +1,21 @@
+import { ParadoxElement, ParadoxVirtualElement, ParadoxElementChildren, ParadoxEvents } from '../types';
+export default function createElement(tagName: string, options: ParadoxElement = { children: [], events: {}, attrs: {} }): ParadoxVirtualElement {
+ if (!tagName) throw new Error("tagName is required");
+
+ let children: ParadoxElementChildren = [];
+ let events: ParadoxEvents = {};
+ let attrs = {};
+
+ if (typeof options === 'object' && !Array.isArray(options) && 'children' in options) {
+ children = options.children || [];
+ events = options.events || {};
+ attrs = options.attrs || {};
+ }
+
+ return {
+ tagName,
+ attrs,
+ children,
+ events,
+ };
+};
\ No newline at end of file
diff --git a/src/core/buildApp/helpers/createVirtualDOM.ts b/src/core/buildApp/helpers/createVirtualDOM.ts
new file mode 100644
index 0000000..aa244fe
--- /dev/null
+++ b/src/core/buildApp/helpers/createVirtualDOM.ts
@@ -0,0 +1,5 @@
+import { ParadoxElement } from "../types";
+
+export default function createVirtualDOM(treeFunc: Function): ParadoxElement | ParadoxElement[] {
+ return treeFunc() as ParadoxElement | ParadoxElement[];
+}
\ No newline at end of file
diff --git a/src/core/buildApp/helpers/diff.ts b/src/core/buildApp/helpers/diff.ts
new file mode 100644
index 0000000..53a201c
--- /dev/null
+++ b/src/core/buildApp/helpers/diff.ts
@@ -0,0 +1,107 @@
+import render from "./render";
+
+import { ParadoxVirtualElement, Patch, ParadoxElementChildren, HTMLAttributes } from "../types";
+
+function zip(xs: Array, ys: Array): Array {
+ const zipped = [];
+
+ for (let i = 0; i < Math.min(xs.length, ys.length); i++) {
+ zipped.push([xs[i], ys[i]]);
+ }
+
+ return zipped;
+}
+
+function diffAttrs (oldAttrs: HTMLAttributes, newAttrs: HTMLAttributes): (node: HTMLElement) => void {
+
+ const patches: Patch[] = [];
+
+ for (const [key, value] of Object.entries(newAttrs)) {
+ patches.push(node => {
+ node.setAttribute(key, value as string);
+ return node;
+ });
+ }
+
+ for (const key of Object.keys(oldAttrs)) {
+ if (!(key in newAttrs)) {
+ patches.push(node => {
+ node.removeAttribute(key);
+ return node;
+ });
+ }
+ }
+
+ return (node: HTMLElement) => {
+ for (const patch of patches) {
+ patch(node);
+ }
+ }
+}
+
+function diffChildren (oldChildren: ParadoxElementChildren, newChildren: ParadoxElementChildren): (node: HTMLElement) => HTMLElement {
+ const patches: Patch[] = [];
+
+ for (const [oldChild, newChild] of zip(oldChildren, newChildren)) {
+ patches.push(diff(oldChild, newChild));
+ }
+
+ const additionalPatches: Patch[] = [];
+
+ for (const additionalChild of newChildren.slice(oldChildren.length)) {
+ additionalPatches.push(node => {
+ node.appendChild(render(additionalChild as ParadoxVirtualElement));
+ return node;
+ });
+ }
+
+ return (parent: HTMLElement) => {
+ for (const [patch, child] of zip(patches, parent.childNodes as any)) {
+ patch(child);
+ }
+
+ for (const patch of additionalPatches) {
+ patch(parent);
+ }
+ return parent;
+ }
+}
+
+export default function diff(originalOldTree: ParadoxVirtualElement[], originalNewTree: ParadoxVirtualElement[]): Patch {
+ const oldTree = originalOldTree[0]
+ const newTree = originalNewTree[0]
+ if (!newTree) {
+ return (node: HTMLElement): undefined => {
+ node.remove();
+ return undefined;
+ }
+ }
+
+ if (typeof oldTree === "string" || typeof newTree === "string") {
+ if (oldTree !== newTree) {
+ return (node: HTMLElement) => {
+ const newNode = render(newTree);
+ node.replaceWith(newNode);
+ return newNode;
+ }
+ } else {
+ return (node: HTMLElement) => undefined;
+ }
+ }
+ if (oldTree.tagName !== newTree.tagName) {
+ return (node: HTMLElement) => {
+ const newNode = render(newTree);
+ node.replaceWith(newNode);
+ return newTree;
+ }
+ }
+
+ const patchAttr = diffAttrs(oldTree.attrs, newTree.attrs);
+ const patchChildren = diffChildren(oldTree.children, newTree.children);
+
+ return (node: HTMLElement) => {
+ patchAttr(node);
+ patchChildren(node);
+ return node;
+ }
+};
\ No newline at end of file
diff --git a/src/core/buildApp/helpers/mount.ts b/src/core/buildApp/helpers/mount.ts
new file mode 100644
index 0000000..492868d
--- /dev/null
+++ b/src/core/buildApp/helpers/mount.ts
@@ -0,0 +1,4 @@
+export default function mount(vnode: HTMLElement, target: HTMLElement): HTMLElement {
+ target.replaceWith(vnode);
+ return vnode;
+}
\ No newline at end of file
diff --git a/src/core/buildApp/helpers/render.ts b/src/core/buildApp/helpers/render.ts
new file mode 100644
index 0000000..995c4c5
--- /dev/null
+++ b/src/core/buildApp/helpers/render.ts
@@ -0,0 +1,46 @@
+import { ParadoxVirtualElement, ParadoxElementChildren, HTMLAttributes } from "../types";
+function renderEle (vnode: ParadoxVirtualElement): HTMLElement {
+ let tagName = "";
+ let attrs: HTMLAttributes = {};
+ let children: ParadoxElementChildren = [];
+ let events = {};
+
+ if (typeof vnode === "object") {
+ tagName = vnode.tagName;
+ attrs = vnode.attrs;
+ children = vnode.children;
+ events = vnode.events || {};
+ }
+
+ const element = document.createElement(tagName);
+
+ for (const [key, value] of Object.entries(attrs)) {
+ element.setAttribute(key, value.toString());
+ }
+
+ for (const child of children) {
+ const $child = render(child as ParadoxVirtualElement);
+ element.appendChild($child);
+ }
+
+ for (const [key, value] of Object.entries(events)) {
+ if (Array.isArray(value)) {
+ for (const event of value) {
+ element.addEventListener(key, event as EventListener);
+ }
+ continue;
+ } else {
+ element.addEventListener(key, value as EventListener);
+ }
+ }
+
+ return element;
+};
+
+export default function render(vnode: ParadoxVirtualElement): HTMLElement | Text {
+ if (typeof vnode === "string") {
+ return document.createTextNode(vnode);
+ }
+
+ return renderEle(vnode as ParadoxVirtualElement);
+};
\ No newline at end of file
diff --git a/src/core/buildApp/helpers/renderVirtualDOM.ts b/src/core/buildApp/helpers/renderVirtualDOM.ts
new file mode 100644
index 0000000..67b5463
--- /dev/null
+++ b/src/core/buildApp/helpers/renderVirtualDOM.ts
@@ -0,0 +1,17 @@
+import render from './render';
+import mount from './mount';
+
+import { ParadoxVirtualElement } from '../types';
+
+export let targetNodeCache: HTMLElement = document.body
+
+export function setTargetNodeCache(targetNode: HTMLElement) {
+ targetNodeCache = targetNode;
+}
+
+export default function renderVirtualDOM(vDOM: ParadoxVirtualElement[], targetNode: HTMLElement) {
+ vDOM.forEach((vnode) => {
+ const $node = render(vnode);
+ targetNodeCache = mount($node as HTMLElement, targetNode);
+ });
+}
\ No newline at end of file
diff --git a/src/core/buildApp/index.ts b/src/core/buildApp/index.ts
index 09a7742..04a2cf8 100644
--- a/src/core/buildApp/index.ts
+++ b/src/core/buildApp/index.ts
@@ -1,245 +1,9 @@
-type DataSet = {
- [key: string]: string;
-};
+import createVirtualDOM from "./helpers/createVirtualDOM";
+import buildVirtualDOM from "./helpers/buildVirtualDOM";
+import renderVirtualDOM, { targetNodeCache, setTargetNodeCache } from "./helpers/renderVirtualDOM";
+import diff from "./helpers/diff";
-type HTMLAttributes = {
- [key: string]: string | number | boolean | DataSet;
-};
-
-type ParadoxEvents = {
- [key: string]:EventListener | EventListener[];
-};
-
-type ParadoxAppFunction = () => ParadoxElement | ParadoxElement[]
-
-type ParadoxElementChildren = (ParadoxElement | string)[];
-type ParadoxElement = {
- attrs: HTMLAttributes;
- events?: ParadoxEvents;
- children: ParadoxElementChildren;
-} | ParadoxAppFunction | string | ParadoxElement[]
-
-function createVirtualDOM(treeFunc: Function): ParadoxElement | ParadoxElement[] {
- return treeFunc() as ParadoxElement | ParadoxElement[];
-}
-
-type ParadoxVirtualElement = {
- tagName: string;
- attrs: HTMLAttributes;
- children: ParadoxElementChildren;
- events?: ParadoxEvents;
-} | string;
-
-function createElement(tagName: string, options: ParadoxElement = { children: [], events: {}, attrs: {} }): ParadoxVirtualElement {
- if (!tagName) throw new Error("tagName is required");
-
- let children: ParadoxElementChildren = [];
- let events: ParadoxEvents = {};
- let attrs = {};
-
- if (typeof options === 'object' && !Array.isArray(options) && 'children' in options) {
- children = options.children || [];
- events = options.events || {};
- attrs = options.attrs || {};
- }
-
- return {
- tagName,
- attrs,
- children,
- events,
- };
-};
-
-function buildVirtualDOM(vTree: ParadoxElement | ParadoxElement[]): ParadoxVirtualElement[] {
- let vDOM: ParadoxVirtualElement[] = [];
-
- if (!Array.isArray(vTree)) vTree = [vTree];
-
- for (const elementObj of vTree) {
- let elementObject = elementObj;
- if (typeof elementObj === "function") elementObject = elementObj();
-
- if (typeof elementObject === "string") {
- vDOM.push(elementObject);
- continue;
- }
-
- for (const [key, value] of Object.entries(elementObject)) {
- let { attrs = {}, events = {}, children = [] } = value;
- if (children.length) {
- children = buildVirtualDOM(children);
- }
- vDOM.push(createElement(key, { attrs, events, children } as ParadoxElement));
- }
- }
-
- return vDOM;
-}
-
-function renderEle (vnode: ParadoxVirtualElement): HTMLElement {
- let tagName = "";
- let attrs: HTMLAttributes = {};
- let children: ParadoxElementChildren = [];
- let events = {};
-
- if (typeof vnode === "object") {
- tagName = vnode.tagName;
- attrs = vnode.attrs;
- children = vnode.children;
- events = vnode.events || {};
- }
-
- const element = document.createElement(tagName);
-
- for (const [key, value] of Object.entries(attrs)) {
- element.setAttribute(key, value.toString());
- }
-
- for (const child of children) {
- const $child = render(child as ParadoxVirtualElement);
- element.appendChild($child);
- }
-
- for (const [key, value] of Object.entries(events)) {
- if (Array.isArray(value)) {
- for (const event of value) {
- element.addEventListener(key, event as EventListener);
- }
- continue;
- } else {
- element.addEventListener(key, value as EventListener);
- }
- }
-
- return element;
-};
-
-function render(vnode: ParadoxVirtualElement): HTMLElement | Text {
- if (typeof vnode === "string") {
- return document.createTextNode(vnode);
- }
-
- return renderEle(vnode as ParadoxVirtualElement);
-};
-
-function mount(vnode: HTMLElement, target: HTMLElement): HTMLElement {
- target.replaceWith(vnode);
- return vnode;
-}
-
-function renderVirtualDOM(vDOM: ParadoxVirtualElement[], targetNode: HTMLElement) {
- vDOM.forEach((vnode) => {
- const $node = render(vnode);
- targetNodeCache = mount($node as HTMLElement, targetNode);
- });
-}
-
-type Patch = (node: HTMLElement) => HTMLElement | Text | undefined | ParadoxVirtualElement;
-
-function diffAttrs (oldAttrs: HTMLAttributes, newAttrs: HTMLAttributes): (node: HTMLElement) => void {
-
- const patches: Patch[] = [];
-
- for (const [key, value] of Object.entries(newAttrs)) {
- patches.push(node => {
- node.setAttribute(key, value as string);
- return node;
- });
- }
-
- for (const key of Object.keys(oldAttrs)) {
- if (!(key in newAttrs)) {
- patches.push(node => {
- node.removeAttribute(key);
- return node;
- });
- }
- }
-
- return (node: HTMLElement) => {
- for (const patch of patches) {
- patch(node);
- }
- }
-}
-
-function zip(xs: Array, ys: Array): Array {
- const zipped = [];
-
- for (let i = 0; i < Math.min(xs.length, ys.length); i++) {
- zipped.push([xs[i], ys[i]]);
- }
-
- return zipped;
-}
-
-function diffChildren (oldChildren: ParadoxElementChildren, newChildren: ParadoxElementChildren): (node: HTMLElement) => HTMLElement {
- const patches: Patch[] = [];
-
- for (const [oldChild, newChild] of zip(oldChildren, newChildren)) {
- patches.push(diff(oldChild, newChild));
- }
-
- const additionalPatches: Patch[] = [];
-
- for (const additionalChild of newChildren.slice(oldChildren.length)) {
- additionalPatches.push(node => {
- node.appendChild(render(additionalChild as ParadoxVirtualElement));
- return node;
- });
- }
-
- return (parent: HTMLElement) => {
- for (const [patch, child] of zip(patches, parent.childNodes as any)) {
- patch(child);
- }
-
- for (const patch of additionalPatches) {
- patch(parent);
- }
- return parent;
- }
-}
-
-function diff(originalOldTree: ParadoxVirtualElement[], originalNewTree: ParadoxVirtualElement[]): Patch {
- const oldTree = originalOldTree[0]
- const newTree = originalNewTree[0]
- if (!newTree) {
- return (node: HTMLElement): undefined => {
- node.remove();
- return undefined;
- }
- }
-
- if (typeof oldTree === "string" || typeof newTree === "string") {
- if (oldTree !== newTree) {
- return (node: HTMLElement) => {
- const newNode = render(newTree);
- node.replaceWith(newNode);
- return newNode;
- }
- } else {
- return (node: HTMLElement) => undefined;
- }
- }
- if (oldTree.tagName !== newTree.tagName) {
- return (node: HTMLElement) => {
- const newNode = render(newTree);
- node.replaceWith(newNode);
- return newTree;
- }
- }
-
- const patchAttr = diffAttrs(oldTree.attrs, newTree.attrs);
- const patchChildren = diffChildren(oldTree.children, newTree.children);
-
- return (node: HTMLElement) => {
- patchAttr(node);
- patchChildren(node);
- return node;
- }
-};
+import { ParadoxElement, ParadoxAppFunction, ParadoxVirtualElement } from "./types";
type State = any;
type StateCallback = (val: any) => void;
@@ -263,28 +27,14 @@ export function addState (value: any): [State, StateCallback] {
let vTree: object | ParadoxElement | ParadoxElement[] | ParadoxVirtualElement = {}
let vDOM: object | ParadoxElement[] = {}
let treeFuncCache: ParadoxAppFunction
-let targetNodeCache: HTMLElement = document.body
export default function app(treeFunc: ParadoxAppFunction, targetNode: HTMLElement) {
treeFuncCache = treeFunc;
- targetNodeCache = targetNode;
+ setTargetNodeCache(targetNode);
vTree = createVirtualDOM(treeFunc);
vDOM = buildVirtualDOM(vTree as ParadoxElement | ParadoxElement[]);
renderVirtualDOM(vDOM as ParadoxVirtualElement[], targetNode);
-
- // onStateChange(proxyObj, () => {
- // console.log(proxyObj);
-
- // const newVTree = createVirtualDOM(treeFunc);
- // const newVDOM = buildVirtualDOM(newVTree);
- // console.log(vDOM, newVDOM);
-
- // // if (diff(vDOM, newVDOM)) {
- // // vDOM = newVDOM;
- // // renderVirtualDOM(vDOM, targetNode);
- // // }
- // });
}
diff --git a/src/core/buildApp/types/index.ts b/src/core/buildApp/types/index.ts
new file mode 100644
index 0000000..0d26891
--- /dev/null
+++ b/src/core/buildApp/types/index.ts
@@ -0,0 +1,30 @@
+type DataSet = {
+ [key: string]: string;
+};
+
+export type HTMLAttributes = {
+ [key: string]: string | number | boolean | DataSet;
+};
+
+export type ParadoxEvents = {
+ [key: string]:EventListener | EventListener[];
+};
+
+export type ParadoxElementChildren = (ParadoxElement | string)[];
+
+export type ParadoxAppFunction = () => ParadoxElement | ParadoxElement[]
+
+export type ParadoxElement = {
+ attrs: HTMLAttributes;
+ events?: ParadoxEvents;
+ children: ParadoxElementChildren;
+} | ParadoxAppFunction | string | ParadoxElement[]
+
+export type ParadoxVirtualElement = {
+ tagName: string;
+ attrs: HTMLAttributes;
+ children: ParadoxElementChildren;
+ events?: ParadoxEvents;
+} | string;
+
+export type Patch = (node: HTMLElement) => HTMLElement | Text | undefined | ParadoxVirtualElement;
\ No newline at end of file
diff --git a/src/core/buildElement/helpers/appendChildren.ts b/src/core/buildElement/helpers/appendChildren.ts
index 0fb4fd2..5a0960f 100644
--- a/src/core/buildElement/helpers/appendChildren.ts
+++ b/src/core/buildElement/helpers/appendChildren.ts
@@ -1,3 +1,10 @@
+/**
+ * Appends an array of child elements to a parent element.
+ *
+ * @param element - The parent element to append the children to.
+ * @param children - An array of child elements to append.
+ * @param buildElement - A function that builds an element based on a tag and options.
+ */
function appendChildren(element: HTMLElement, children: Array, buildElement: Function): void {
// Create a Document Fragment to efficiently append children
const fragment = document.createDocumentFragment() as DocumentFragment;
diff --git a/src/core/buildElement/helpers/applyStyles.ts b/src/core/buildElement/helpers/applyStyles.ts
index c9b8c69..e522ee6 100644
--- a/src/core/buildElement/helpers/applyStyles.ts
+++ b/src/core/buildElement/helpers/applyStyles.ts
@@ -1,7 +1,14 @@
+type ParadoxStyleKeys = { [key: string]: string };
// Object for caching converted style keys
-const memoizedStyleKeys: { [key: string]: string } = {};
+const memoizedStyleKeys: ParadoxStyleKeys = {};
// Function to convert camelCase into kebab-case for CSS properties
+/**
+ * Converts a camelCase style key to kebab-case and returns the converted key.
+ * If the key has already been processed, the cached value is returned.
+ * @param key - The style key to convert.
+ * @returns The converted style key.
+ */
function getStyleKey(key: string = ""): string {
// Check if the key is already processed and return the cached value if so
if (memoizedStyleKeys[key] !== undefined) {
@@ -17,7 +24,13 @@ function getStyleKey(key: string = ""): string {
return styleKey;
}
-function applyStyles(element: HTMLElement, style: { [key: string]: string }): void {
+/**
+ * Applies the given styles to the specified HTML element.
+ *
+ * @param element - The HTML element to apply the styles to.
+ * @param style - The styles to apply, represented as an object with keys and values.
+ */
+function applyStyles(element: HTMLElement, style: ParadoxStyleKeys): void {
const styleDeclaration: CSSStyleDeclaration = element.style;
// Apply inline style to the element by converting keys from camelCase
for (const [key, value] of Object.entries(style)) {
diff --git a/src/core/buildElement/helpers/createElement.ts b/src/core/buildElement/helpers/createElement.ts
index 56d36a8..461600f 100644
--- a/src/core/buildElement/helpers/createElement.ts
+++ b/src/core/buildElement/helpers/createElement.ts
@@ -1,5 +1,12 @@
-const elementsCache: { [key: string]: HTMLElement } = {};
+type ParadoxElementCache = { [key: string]: HTMLElement };
+const elementsCache: ParadoxElementCache = {};
+/**
+ * Creates an HTML element with the specified element name.
+ * If the element has been created before, it returns a clone of the cached element.
+ * @param elementName - The name of the HTML element to create.
+ * @returns The created HTML element.
+ */
function createElement(elementName: string) : HTMLElement {
if (elementsCache[elementName]) return elementsCache[elementName].cloneNode() as HTMLElement;
const element = document.createElement(elementName);
diff --git a/src/core/buildElement/helpers/getText.ts b/src/core/buildElement/helpers/getText.ts
index 38c79f2..efc5397 100644
--- a/src/core/buildElement/helpers/getText.ts
+++ b/src/core/buildElement/helpers/getText.ts
@@ -1,15 +1,34 @@
// Object for caching processed text values
-const memoizedText:{ [key: string]: string } = {};
+type ParadoxElementMemoizedText = { [key: string]: string };
+const memoizedText: ParadoxElementMemoizedText = {};
// Function to retrieve or compute a formatted text value
-function getText(text = ""): string {
+
+/**
+ * Formats the given text by replacing any occurrences of "\\xHH" with the corresponding character.
+ *
+ * @param text The text to format.
+ * @returns The formatted text.
+ */
+function formatText(text: string = ""): string {
+ return text.replace(/\\x([0-9A-Fa-f]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)));
+}
+
+/**
+ * Retrieves the formatted text for a given input.
+ * If the text has been processed and cached, it returns the cached result.
+ * If it's the first time, it computes the formatted text, caches it, and returns the result.
+ * @param text - The input text to be formatted.
+ * @returns The formatted text.
+ */
+function getText(text: string = ""): string {
// Check if the text has been processed and cached; return it if so
if (memoizedText[text] !== undefined) {
return memoizedText[text];
}
// If it's the first time, compute the formatted text
- const result = typeof text === "string"
- ? text.replace(/\\x([0-9A-Fa-f]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)))
+ const result = (typeof text === "string")
+ ? formatText(text)
: String(text);
// Cache the result for future use
diff --git a/src/core/buildElement/helpers/handleEvents.ts b/src/core/buildElement/helpers/handleEvents.ts
index 37611f8..add4b39 100644
--- a/src/core/buildElement/helpers/handleEvents.ts
+++ b/src/core/buildElement/helpers/handleEvents.ts
@@ -1,6 +1,13 @@
+type ParadoxEventListenerWeakMap = WeakMap>;
// WeakMap to store event listeners for each element
-const eventListeners: WeakMap> = new WeakMap();
+const eventListeners: ParadoxEventListenerWeakMap = new WeakMap();
+/**
+ * Attaches event listeners to an HTML element.
+ *
+ * @param element - The HTML element to attach the event listeners to.
+ * @param events - An object containing event names as keys and event listeners as values.
+ */
function handleEvents(element: HTMLElement, events: { [key: string]: EventListener }): void {
// Retrieve or create the event listeners Map for this particular element
let elementEvents = eventListeners.get(element);
diff --git a/src/core/buildElement/helpers/setAttributes.ts b/src/core/buildElement/helpers/setAttributes.ts
index cf56797..0fe3a03 100644
--- a/src/core/buildElement/helpers/setAttributes.ts
+++ b/src/core/buildElement/helpers/setAttributes.ts
@@ -11,6 +11,12 @@ const booleanAttributes: string[] = [
"autocompleted",
];
+/**
+ * Sets the attributes of an HTML element.
+ * If the attribute is a boolean attribute, it will only be set if the value is truthy.
+ * @param element - The HTML element to set the attributes for.
+ * @param attributes - An object containing the attribute key-value pairs.
+ */
function setAttributes(element: HTMLElement, attributes: { [key: string]: string }): void {
for (const [key, value] of Object.entries(attributes)) {
// Attributes like disabled, checked, selected need special handling
diff --git a/src/core/buildElement/index.ts b/src/core/buildElement/index.ts
index 87f9c53..0b91180 100644
--- a/src/core/buildElement/index.ts
+++ b/src/core/buildElement/index.ts
@@ -5,10 +5,10 @@ import handleEvents from "./helpers/handleEvents";
import applyStyles from "./helpers/applyStyles";
import appendChildren from "./helpers/appendChildren";
-type ParadoxElement = {
+type ParadoxElementOptions = {
id?: string;
classList?: string;
- children?: ParadoxElement[];
+ children?: ParadoxElementOptions[];
attributes?: { [key: string]: string };
events?: { [key: string]: EventListener };
text?: string;
@@ -31,7 +31,7 @@ type ParadoxElement = {
*/
export default function buildElement(
tag: string,
- options: ParadoxElement = { id: "", classList: "", children: [], attributes: {}, events: {}, text: "", style: {} }
+ options: ParadoxElementOptions = { id: "", classList: "", children: [], attributes: {}, events: {}, text: "", style: {} }
): HTMLElement {
// Return empty string if tag is not provided
if (!tag) throw new Error("Tag is required");
diff --git a/src/index.ts b/src/index.ts
index 20c7754..df2ba23 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,7 +1,7 @@
import buildElement from "./core/buildElement";
import Router from "./core/Router";
import pubsub from "./core/Pubsub";
-// import app from "./core/app";
+import buildApp from "./core/buildApp";
/**
* Represents the Paradox object.
@@ -10,7 +10,7 @@ const Paradox = {
buildElement,
Router,
pubsub,
- // app NOT PULISHED YET
+ buildApp,
};
export default Paradox;
\ No newline at end of file