Skip to content

Commit

Permalink
add code comments
Browse files Browse the repository at this point in the history
  • Loading branch information
Bloomca committed Jun 15, 2024
1 parent ccbef59 commit ae4fbbc
Show file tree
Hide file tree
Showing 7 changed files with 77 additions and 24 deletions.
9 changes: 9 additions & 0 deletions src/attach-component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ import { createElement } from "./create-element";

import type { VelesElement, VelesComponentObject } from "./types";


/**
* Attach Veles component tree to a regular HTML node.
* Right now it will wrap the app into an additional `<div>` tag.
*
* It returns a function which when executed, will remove the Veles
* tree from DOM and remove all subscriptions.
*/
function attachComponent({
htmlElement,
component,
Expand All @@ -19,6 +27,7 @@ function attachComponent({
// for the consumers, it greatly simplifies some things, namely, mount callbacks
// for components or supporting conditional rendering at the top level
const wrappedApp = createElement("div", { children: [component] });
// convert Veles tree into a tree which contains rendered Nodes
const wrappedAppTree = renderTree(wrappedApp);
const velesElementNode = getExecutedComponentVelesNode(wrappedAppTree);
htmlElement.appendChild(velesElementNode.html);
Expand Down
5 changes: 5 additions & 0 deletions src/create-ref.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/**
* Create a reference which has special treatment if passed as
* ref={ref} to any DOM Node. `ref.current` will contain the
* rendered node, even if it changes.
*/
function createRef<T>(initialRefValue: T | null = null): {
velesRef: true;
current: T | null;
Expand Down
11 changes: 11 additions & 0 deletions src/create-state/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@ import type {
TrackingSelectorElement,
} from "./types";

/**
* Main state factory function.
*
* This primitive is pretty much a simple observable implementation,
* which is tightly integrated with the UI framework for two things:
*
* - based on subscription callback, update DOM node and replace it
* - correctly unsbuscribe when the Node/component is unmounted
*/

function createState<T>(
initialValue: T,
subscribeCallback?: (
Expand All @@ -29,6 +39,7 @@ function createState<T>(
let value = initialValue;
let previousValue: undefined | T = undefined;

// all subscription types we track
const trackers: StateTrackers = {
trackingEffects: [],
trackingSelectorElements: [],
Expand Down
8 changes: 8 additions & 0 deletions src/create-state/update-useattribute-value.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,16 @@ function updateUseAttributeValue<T>({
const { cb, htmlElement, attributeName, attributeValue } = element;
const newAttributeValue = cb ? cb(value) : value;

// Boolean elements require either setting an empty string as a value,
// or duplicate the attribute name. A lack of the attribute means
// the value is `false`, so we need to treat it differently.
if (typeof newAttributeValue === "boolean") {
if (newAttributeValue) {
htmlElement.setAttribute(attributeName, "");
} else {
htmlElement.removeAttribute(attributeName);
}
// check whether we are dealing with event handlers
} else if (attributeName.startsWith("on")) {
// if the value is the same, it is either not set
// or we received the same event handler
Expand All @@ -27,13 +31,17 @@ function updateUseAttributeValue<T>({
const eventName =
attributeName[2].toLocaleLowerCase() + attributeName.slice(3);
if (attributeValue) {
// we remove the previous value, `removeEventListener` needs
// to have the same value as the one that was added
htmlElement.removeEventListener(eventName, attributeValue);
}
if (newAttributeValue && typeof newAttributeValue === "function") {
htmlElement.addEventListener(eventName, newAttributeValue);
}
// not the best approach, but it should work as expected
// basically, update the array value in-place
// we update it so that we can compare to the previous value if needed
// and to remove a correct event handler
element.attributeValue = newAttributeValue;
} else {
htmlElement.setAttribute(attributeName, newAttributeValue);
Expand Down
22 changes: 21 additions & 1 deletion src/create-state/update-usevalue-selector-value.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,23 @@ function updateUseValueSelector<T>({
const newSelectedValue = selector ? selector(value) : value;

if (comparator(selectedValue, newSelectedValue)) {
/**
* if there is no need for update, we push the existing element
* to the new array. once we merge all subscriptions, we run
* `unique` function which will make sure there are no double
* subscriptions.
*
* This is needed because using `map` can potentially create
* some weird side effects, since in case the node changed,
* some elements will be dynamically removed from the array
*/

newTrackingSelectorElements.push(selectorTrackingElement);
return;
}

// we need to re-execute the rendering callback with the same
// context values as before
addPublicContext(savedContext);
const returnednewNode = cb
? cb(newSelectedValue)
Expand All @@ -46,7 +59,11 @@ function updateUseValueSelector<T>({
? createTextElement(returnednewNode as string)
: returnednewNode;

// Since we render a new Node, we need to insert it into the DOM
// manually and immediately. So we render the full HTML tree.
const newRenderedNode = renderTree(newNode);
// this should remove our saved context value from the stack
// so that other components will be executed within their own context
popPublicContext();
newNode.executedVersion = newRenderedNode;

Expand All @@ -67,6 +84,8 @@ function updateUseValueSelector<T>({
const parentVelesElement = node.parentVelesElement;
const parentVelesElementRendered = oldVelesElementNode.parentVelesElement;

// at this point we can construct the new tracking selector element
// the old will be removed by the unmount lifecycle hook from the node
const newTrackingSelectorElement: TrackingSelectorElement = {
selector,
selectedValue: newSelectedValue,
Expand Down Expand Up @@ -214,6 +233,7 @@ function updateUseValueSelector<T>({
}

// we call unmount handlers right after we replace it
// this is where the old
callUnmountHandlers(node.executedVersion);

addUseValueMountHandler({
Expand All @@ -223,7 +243,7 @@ function updateUseValueSelector<T>({
trackingSelectorElement: newTrackingSelectorElement,
});
// at this point the new Node is mounted, childComponents are updated
// and unmount handlers for the old node are called
// old tracking selector element will be removed in the `unmount` handler
callMountHandlers(newRenderedNode);

// right after that, we add the callback back
Expand Down
40 changes: 20 additions & 20 deletions src/create-state/update-usevalueiterator-value.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ function updateUseValueIteratorValue<T>({
[calculatedKey: string]: boolean;
} = {};

elements.forEach((element, index) => {
elements.forEach((element, index) => {
let calculatedKey: string = "";
if (
typeof key === "string" &&
Expand All @@ -99,6 +99,25 @@ function updateUseValueIteratorValue<T>({
return;
}

// first, we check if there is a node by this key
// if there is, we do `getValue()` and compare whether the
// item is the same.
// if it is not, we need to do `elementState.setValue()`
// with the new value
// if the value is the same, nothing to do.
//
// after that, we need to put the new position down
// (we'll reshuffle items at the end)
//
// if there is no node by this key, we need to:
// 1. create a state for it
// 2. create a node for it
// 3. mark the new index for that node
//
// at the end, we need to find elements which were rendered, but are
// not rendered anymore, and remove them from DOM and trigger `onUnmount`
// for them.

const existingElement = elementsByKey[calculatedKey];

if (existingElement) {
Expand Down Expand Up @@ -144,25 +163,6 @@ function updateUseValueIteratorValue<T>({
node,
};
}

// first, we check if there is a node by this key
// if there is, we do `getValue()` and compare whether the
// item is the same.
// if it is not, we need to do `elementState.setValue()`
// with the new value
// if the value is the same, nothing to do.
//
// after that, we need to put the new position down
// (we'll reshuffle items at the end)
//
// if there is no node by this key, we need to:
// 1. create a state for it
// 2. create a node for it
// 3. mark the new index for that node
//
// at the end, we need to find elements which were rendered, but are
// not rendered anymore, and remove them from DOM and trigger `onUnmount`
// for them.
});

// to replace old wrapper's children to make sure they are removed correctly
Expand Down
6 changes: 3 additions & 3 deletions src/hooks/lifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import { ComponentAPI } from "../types";
// lifecycle hooks
// currently, all components need to be synchronous
// so we execute them and set background context
// since components can be nested, we need to keep the array
// since components can be nested, we need to use the stack
const contextStack: ComponentAPI[] = [];
// all hooks need to know the current context
// it should way more convenient this way compared to passing
// `componentAPI` to every method
// it should be way more convenient to use it this way
// compared to passing `componentAPI` to every method
let currentContext: ComponentAPI | null = null;

function addContext(newContext: ComponentAPI) {
Expand Down

0 comments on commit ae4fbbc

Please sign in to comment.