Skip to content

Commit

Permalink
Progress.
Browse files Browse the repository at this point in the history
  • Loading branch information
gdotdesign committed Jan 5, 2024
1 parent 34943d3 commit 3ea4aa2
Show file tree
Hide file tree
Showing 94 changed files with 3,114 additions and 525 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
/.shards/
.vscode
node_modules
coverage
1 change: 0 additions & 1 deletion core/source/Test/Html.mint
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ module Test.Html {
document.body.appendChild(root)
#{%testRender%}(#{node}, root)
return new #{%testContext%}(root, () => {
#{%testRender%}(null, root)
document.body.removeChild(root)
})
})()
Expand Down
2 changes: 1 addition & 1 deletion runtime/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export { createElement, Fragment as fragment } from "preact";
export { useEffect, useMemo } from "preact/hooks";
export { createPortal } from "preact/compat";
export { useEffect } from "preact/hooks";

export {
useComputed,
Expand Down
9 changes: 7 additions & 2 deletions runtime/package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
{
"license": "MIT",
"private": true,
"dependencies": {
"@preact/signals": "^1.2.2",
"@testing-library/preact": "^3.2.3",
"@vitest/coverage-v8": "^1.1.1",
"esbuild": "^0.19.8",
"event-propagation-path": "^1.0.5",
"fast-equals": "^5.0.1",
"indent-string": "^5.0.0",
"jsdom": "^23.0.1",
"preact": "^10.19.2",
"prettier": "^3.1.0",
"route-parser": "mint-lang/mint-route-parser"
"route-parser": "mint-lang/mint-route-parser",
"uuid-random": "^1.3.2",
"vitest": "^1.1.1"
}
}
38 changes: 35 additions & 3 deletions runtime/src/equality.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
// This file contains code to have value equality instead of reference equality.
// We use a `Symbol` to have a custom equality functions and then use these
// functions when comparing two values.

const Equals = Symbol("Equals");
export const Equals = Symbol("Equals");

Boolean.prototype[Equals] =
Symbol.prototype[Equals] =
Expand Down Expand Up @@ -88,6 +87,32 @@ FormData.prototype[Equals] = function (other) {
}
};

// Maps need to have the same keys and values to be equal.
Map.prototype[Equals] = function (other) {
if (other === null || other === undefined) {
return false;
}

const aKeys = Array.from(this.keys()).sort();
const bKeys = Array.from(other.keys()).sort();

if (compare(aKeys, bKeys)) {
if (aKeys.length == 0) {
return true;
}

for (let key of aKeys) {
if (!compare(this.get(key), other.get(key))) {
return false;
}
}

return true;
} else {
return false;
}
};

// This is the custom comparison function.
export const compare = (a, b) => {
if ((a === undefined && b === undefined) || (a === null && b === null)) {
Expand All @@ -104,7 +129,14 @@ export const compare = (a, b) => {
// This is the custom comparison function for plain objects.
export const compareObjects = (a, b) => {
if (a instanceof Object && b instanceof Object) {
const keys = new Set(Object.keys(a).concat(Object.keys(b)));
const aKeys = Object.keys(a);
const bKeys = Object.keys(b);

if (aKeys.length !== bKeys.length) {
return false;
}

const keys = new Set(aKeys.concat(bKeys));

for (let key of keys) {
if (!compare(a[key], b[key])) {
Expand Down
27 changes: 27 additions & 0 deletions runtime/src/normalize_event.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,32 @@
import { options } from "preact";

// Polyfill DataTransfer
if (!("DataTransfer" in window)) {
window.DataTransfer = class {
constructor() {
this.effectAllowed = "none";
this.dropEffect = "none";
this.files = [];
this.types = [];
this.cache = {};
}

getData(format) {
return this.cache[format] || "";
}

setData(format, data) {
this.cache[format] = data;
return null;
}

clearData() {
this.cache = {};
return null;
}
};
}

// Set the event option hook to normalize the event so we can use one type
// for events (`Html.Event``) instead of multiple event types like in
// JavaScript (`MouseEvent`, `KeyboardEvent`, etc...). Basically we make sure
Expand Down
3 changes: 1 addition & 2 deletions runtime/src/pattern_matching.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,7 @@ export const destructure = (value, pattern, values = []) => {
for (let index in pattern.patterns) {
const item = pattern.patterns[index];

// TODO: This needs to be updated when new mapping is figured out.
if (!destructure(value[value[item[0]]], item[1], values)) {
if (!destructure(value[item[0]], item[1], values)) {
return false;
}
}
Expand Down
68 changes: 34 additions & 34 deletions runtime/src/provider.js
Original file line number Diff line number Diff line change
@@ -1,45 +1,45 @@
import { useEffect, useMemo } from "preact/hooks";
import { untracked } from "@preact/signals";
import { useEffect } from "preact/hooks";
import { compare } from "./equality";
import uuid from "uuid-random";

// This creates a function which is used for subscribing to a provider. We use
// `untracked` to not to subscribe to any outside signals.
export const createProvider = (subscriptions, update) => {
// This is the subscription function.
return (subscription) => {
untracked(() => {
subscriptions.value = [...subscriptions.value, subscription];
update();
});
return (owner, object) => {
const unsubscribe = () => {
if (subscriptions.has(owner)) {
subscriptions.delete(owner);
untracked(update);
}
}

// Cleanup function.
return () => {
untracked(() => {
subscriptions.value = subscriptions.value.filter(
(item) => item != subscription,
);
update();
});
};
};
};
// This will only run when the component unmounts.
useEffect(() => {
return unsubscribe
}, []);

// A hook for using providers. On mount and every change we subscribe to the
// given providers (if the condition is missing or true). The cleanup runs
// before the resubscription so there won't be any invalid subscriptions.
export const useProviders = (providers) => {
useEffect(() => {
const cleanups = providers.map((provider) => {
if (provider.length === 1 || provider[1]) {
return provider[0]();
}
});
// This runs on every update so we don't return a cleanup function.
useEffect(() => {
// If the object is null that means we need to unsubscribe.
if (object === null) {
unsubscribe();
} else {
const current = subscriptions.get(owner);

return () => {
cleanups.forEach((item) => {
if (item) {
item();
if (!compare(current, object)) {
subscriptions.set(owner, object);
untracked(update);
}
});
};
});
}
})
};
};

// Returns the subscriptions as an array.
export const subscriptions = (items) => Array.from(items.values());

// Returns a unique ID for a component which doesn't change.
export const useId = () => useMemo(uuid, []);

15 changes: 14 additions & 1 deletion runtime/src/utilities.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect } from "preact/hooks";
import { useEffect, useRef, useCallback } from "preact/hooks";

// We need to have a different function for accessing array items because there
// is no concept of `null` in Mint so we return `Just(a)` or `Nothing`.
Expand All @@ -10,6 +10,19 @@ export const arrayAccess = (array, index, just, nothing) => {
}
};

// This is needed for functions inside components so they would remain
// referrencially the same.
export const useFunction = (fn) => {
return useCallback(fn, []);
};

// This sets the references to an element or component.
export const setRef = (value, just) => (element) => {
if (value.value._0 !== element) {
value.value = new just(element)
}
}

// A hook to replace the `componentDidUpdate` function.
export const useDidUpdate = (callback) => {
const hasMount = useRef(false);
Expand Down
37 changes: 35 additions & 2 deletions runtime/src/variant.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,46 @@
import { Equals, compareObjects, compare } from "./equality";

// The base class for variants.
class Variant {
[Equals](other) {
if (!(other instanceof this.constructor)) {
return false;
}

if (other.length !== this.length) {
return false;
}

if (this.record) {
return compareObjects(this, other);
}

for (let index = 0; index < this.length; index++) {
if (!compare(this["_" + index], other["_" + index])) {
return false;
}
}

return true;
}
}

// Creates an type variant class, this is needed so we can do proper
// comparisons and pattern matching / destructuring.
export const variant = (input) => {
return class {
return class extends Variant {
constructor(...args) {
super();
if (Array.isArray(input)) {
for (let index = 0; index < input; index++) {
this.length = input.length;
this.record = true;

for (let index = 0; index < input.length; index++) {
this[input[index]] = args[index];
}
} else {
this.length = input;

for (let index = 0; index < input; index++) {
this[`_${index}`] = args[index];
}
Expand Down
Loading

0 comments on commit 3ea4aa2

Please sign in to comment.