From ea21f1b3f340eb773fa1c18c546e30171a860c13 Mon Sep 17 00:00:00 2001 From: alexsc6955 Date: Wed, 17 Jan 2024 14:28:12 -0500 Subject: [PATCH 01/11] Refactor ParadoxElement type to ParadoxElementOptions --- build/core/buildElement/index.d.ts | 6 +++--- src/core/buildElement/index.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) 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/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"); From f0dd87c1d037c0af2a43149517ab5672215bce27 Mon Sep 17 00:00:00 2001 From: alexsc6955 Date: Wed, 17 Jan 2024 14:32:53 -0500 Subject: [PATCH 02/11] Refactor getText function to improve code readability and maintainability --- build/core/buildElement/helpers/getText.d.ts | 7 +++++ build/core/buildElement/helpers/getText.js | 21 ++++++++++++--- src/core/buildElement/helpers/getText.ts | 27 +++++++++++++++++--- 3 files changed, 48 insertions(+), 7 deletions(-) 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/src/core/buildElement/helpers/getText.ts b/src/core/buildElement/helpers/getText.ts index 38c79f2..8bb37d3 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 MemoizedText = { [key: string]: string }; +const memoizedText: MemoizedText = {}; // 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 From 9cf279475c5c046250ba08ccae0a13f12088cd78 Mon Sep 17 00:00:00 2001 From: alexsc6955 Date: Wed, 17 Jan 2024 14:34:54 -0500 Subject: [PATCH 03/11] Add documentation for createElement function --- build/core/buildElement/helpers/createElement.d.ts | 6 ++++++ build/core/buildElement/helpers/createElement.js | 6 ++++++ src/core/buildElement/helpers/createElement.ts | 9 ++++++++- src/core/buildElement/helpers/getText.ts | 4 ++-- 4 files changed, 22 insertions(+), 3 deletions(-) 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/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 8bb37d3..efc5397 100644 --- a/src/core/buildElement/helpers/getText.ts +++ b/src/core/buildElement/helpers/getText.ts @@ -1,6 +1,6 @@ // Object for caching processed text values -type MemoizedText = { [key: string]: string }; -const memoizedText: MemoizedText = {}; +type ParadoxElementMemoizedText = { [key: string]: string }; +const memoizedText: ParadoxElementMemoizedText = {}; // Function to retrieve or compute a formatted text value /** From 8c33af1521abe5951a7c339bcf679cac1bc3039d Mon Sep 17 00:00:00 2001 From: alexsc6955 Date: Wed, 17 Jan 2024 14:37:14 -0500 Subject: [PATCH 04/11] Add setAttributes function documentation --- build/core/buildElement/helpers/setAttributes.d.ts | 6 ++++++ build/core/buildElement/helpers/setAttributes.js | 6 ++++++ src/core/buildElement/helpers/setAttributes.ts | 6 ++++++ 3 files changed, 18 insertions(+) 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/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 From 55bb677444c1c1286ee3509e309f0e95670ef982 Mon Sep 17 00:00:00 2001 From: alexsc6955 Date: Wed, 17 Jan 2024 14:38:18 -0500 Subject: [PATCH 05/11] Update handleEvents function signature --- build/core/buildElement/helpers/handleEvents.d.ts | 6 ++++++ build/core/buildElement/helpers/handleEvents.js | 6 ++++++ src/core/buildElement/helpers/handleEvents.ts | 9 ++++++++- 3 files changed, 20 insertions(+), 1 deletion(-) 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/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); From a47e7812b18ed09f99f24a097d607377f8541467 Mon Sep 17 00:00:00 2001 From: alexsc6955 Date: Wed, 17 Jan 2024 14:39:47 -0500 Subject: [PATCH 06/11] Refactor applyStyles function to use type alias for style keys --- .../core/buildElement/helpers/applyStyles.d.ts | 11 +++++++++-- build/core/buildElement/helpers/applyStyles.js | 12 ++++++++++++ src/core/buildElement/helpers/applyStyles.ts | 17 +++++++++++++++-- 3 files changed, 36 insertions(+), 4 deletions(-) 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/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)) { From 8ce56f91ac7b6a3948d8a7fe7aee9d925c82f24f Mon Sep 17 00:00:00 2001 From: alexsc6955 Date: Wed, 17 Jan 2024 14:40:33 -0500 Subject: [PATCH 07/11] Add appendChildren function documentation --- build/core/buildElement/helpers/appendChildren.d.ts | 7 +++++++ build/core/buildElement/helpers/appendChildren.js | 7 +++++++ src/core/buildElement/helpers/appendChildren.ts | 7 +++++++ 3 files changed, 21 insertions(+) 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/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; From 89ba9ef105df2d623ca5d6df21b096a08fc8e401 Mon Sep 17 00:00:00 2001 From: alexsc6955 Date: Wed, 17 Jan 2024 15:02:11 -0500 Subject: [PATCH 08/11] Refactor buildApp module --- .../buildApp/helpers/buildVirtualDOM.d.ts | 2 + .../core/buildApp/helpers/buildVirtualDOM.js | 29 ++ .../core/buildApp/helpers/createElement.d.ts | 2 + build/core/buildApp/helpers/createElement.js | 22 ++ .../buildApp/helpers/createVirtualDOM.d.ts | 2 + .../core/buildApp/helpers/createVirtualDOM.js | 6 + build/core/buildApp/helpers/diff.d.ts | 2 + build/core/buildApp/helpers/diff.js | 95 +++++++ build/core/buildApp/helpers/mount.d.ts | 1 + build/core/buildApp/helpers/mount.js | 7 + build/core/buildApp/helpers/render.d.ts | 2 + build/core/buildApp/helpers/render.js | 43 +++ .../buildApp/helpers/renderVirtualDOM.d.ts | 4 + .../core/buildApp/helpers/renderVirtualDOM.js | 20 ++ build/core/buildApp/index.d.ts | 17 +- build/core/buildApp/index.js | 239 +++------------- build/core/buildApp/types/index.d.ts | 24 ++ build/core/buildApp/types/index.js | 2 + src/core/buildApp/helpers/buildVirtualDOM.ts | 29 ++ src/core/buildApp/helpers/createElement.ts | 21 ++ src/core/buildApp/helpers/createVirtualDOM.ts | 5 + src/core/buildApp/helpers/diff.ts | 107 +++++++ src/core/buildApp/helpers/mount.ts | 4 + src/core/buildApp/helpers/render.ts | 46 +++ src/core/buildApp/helpers/renderVirtualDOM.ts | 17 ++ src/core/buildApp/index.ts | 262 +----------------- src/core/buildApp/types/index.ts | 30 ++ 27 files changed, 567 insertions(+), 473 deletions(-) create mode 100644 build/core/buildApp/helpers/buildVirtualDOM.d.ts create mode 100644 build/core/buildApp/helpers/buildVirtualDOM.js create mode 100644 build/core/buildApp/helpers/createElement.d.ts create mode 100644 build/core/buildApp/helpers/createElement.js create mode 100644 build/core/buildApp/helpers/createVirtualDOM.d.ts create mode 100644 build/core/buildApp/helpers/createVirtualDOM.js create mode 100644 build/core/buildApp/helpers/diff.d.ts create mode 100644 build/core/buildApp/helpers/diff.js create mode 100644 build/core/buildApp/helpers/mount.d.ts create mode 100644 build/core/buildApp/helpers/mount.js create mode 100644 build/core/buildApp/helpers/render.d.ts create mode 100644 build/core/buildApp/helpers/render.js create mode 100644 build/core/buildApp/helpers/renderVirtualDOM.d.ts create mode 100644 build/core/buildApp/helpers/renderVirtualDOM.js create mode 100644 build/core/buildApp/types/index.d.ts create mode 100644 build/core/buildApp/types/index.js create mode 100644 src/core/buildApp/helpers/buildVirtualDOM.ts create mode 100644 src/core/buildApp/helpers/createElement.ts create mode 100644 src/core/buildApp/helpers/createVirtualDOM.ts create mode 100644 src/core/buildApp/helpers/diff.ts create mode 100644 src/core/buildApp/helpers/mount.ts create mode 100644 src/core/buildApp/helpers/render.ts create mode 100644 src/core/buildApp/helpers/renderVirtualDOM.ts create mode 100644 src/core/buildApp/types/index.ts 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/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 From dbedd2d84b7b5e12e6f330caaa43b1be8964b5aa Mon Sep 17 00:00:00 2001 From: alexsc6955 Date: Wed, 17 Jan 2024 15:06:01 -0500 Subject: [PATCH 09/11] Add Paradox.buildApp function and update version to 0.4.0 --- CHANGELOG.md | 7 +++++++ ROADMAP.md | 3 ++- build/index.d.ts | 2 ++ build/index.js | 4 ++-- package.json | 2 +- src/index.ts | 4 ++-- 6 files changed, 16 insertions(+), 6 deletions(-) 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/ROADMAP.md b/ROADMAP.md index c36c567..d44e6db 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -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 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/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 From 4ffd9ed097b03cafa582526a9d7abe00f625d320 Mon Sep 17 00:00:00 2001 From: alexsc6955 Date: Wed, 17 Jan 2024 16:25:29 -0500 Subject: [PATCH 10/11] Update Paradox library version and fix typos --- README.MD | 48 +++++++++++++++--------------------------------- 1 file changed, 15 insertions(+), 33 deletions(-) diff --git a/README.MD b/README.MD index 423034e..1fb6f3f 100644 --- a/README.MD +++ b/README.MD @@ -13,6 +13,12 @@ ยท Request Features

+

+ GitHub contributors + GitHub commit activity + GitHub issues + GitHub pull requests +

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); From e0a3dd5356790db53c28d21ff76854cf42bdef69 Mon Sep 17 00:00:00 2001 From: alexsc6955 Date: Wed, 17 Jan 2024 16:30:56 -0500 Subject: [PATCH 11/11] Add badges to README --- ROADMAP.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/ROADMAP.md b/ROADMAP.md index d44e6db..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 @@ -42,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