Skip to content

Commit

Permalink
Added some more docs and updated example codes
Browse files Browse the repository at this point in the history
  • Loading branch information
DeepDoge committed Oct 14, 2024
1 parent b506c61 commit 9fda15d
Show file tree
Hide file tree
Showing 13 changed files with 1,681 additions and 56 deletions.
6 changes: 1 addition & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@

### Syntax

[Compare Syntax](https://bafybeigez3hth3c4ryra4g4itnertz5mdyhqegv6tzfntnchhtocayx2l4.ipfs.dweb.link)
[Compare Syntax](https://k51qzi5uqu5dmeqvzecv0iezk1gjnyesz1stniesrhjv1xuie6ru4kj1or1l3f.ipns.dweb.link)

## Installation 🍙

Expand Down Expand Up @@ -132,7 +132,3 @@ shouldn't cause any issues.
attributes and properties of an element while building it, which is not
currently possible with JSX. This distinction enhances clarity and control
when defining element characteristics.

---

> > > > > > > e309d77 (...)
7 changes: 5 additions & 2 deletions apps/compare/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@
"scripts": {
"dev": "vite --host",
"build": "vite build",
"preview": "vite preview --host"
"preview": "vite preview --host",
"publish": "npm run build && tsx ./publish.ts"
},
"dependencies": {
"@picocss/pico": "^2.0.6",
"highlight.js": "^11.10.0"
},
"devDependencies": {
"vite": "^5.4.8",
"vite-plugin-singlefile": "^2.0.2"
"vite-plugin-singlefile": "^2.0.2",
"tsx": "^4.19.1",
"ipfs-http-client": "^60.0.1"
}
}
35 changes: 35 additions & 0 deletions apps/compare/publish.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { exec } from "child_process"
import fs from "fs/promises"
import { create } from "ipfs-http-client"
import path from "path"
import util from "util"

const execPromise = util.promisify(exec)

const ipfs = create({ url: `http://${await getHostIP()}:5001` })
const filePath = path.resolve("./dist/index.html")
const ipnsKeyName = "purifyjs-compare"

const hostIP = await getHostIP()
if (!hostIP) {
throw new Error("Could not retrieve host IP. Exiting.")
}

console.log("Pinning file...")
const file = await fs.readFile(filePath)
const { cid } = await ipfs.add(file)
console.log("Pinned file CID:", cid.toString())

console.log("Updating IPNS record to:", cid)
const result = await ipfs.name.publish(cid, { key: ipnsKeyName })
console.log("IPNS record updated to:", result.value)

async function getHostIP() {
try {
const { stdout } = await execPromise("ip route | awk 'NR==1 {print $3}'")
return stdout.trim()
} catch (error) {
console.error("Error getting host IP:", error)
return null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { tags } from "@purifyjs/core";
import { css, scope } from "./util-css";

const { div, h1, button } = tags;

export function CssStyle() {
return div()
.use(scope(CssStyleCss))
.children(
h1({ class: "title" }).textContent("I am red"),
button({
style: "font-size: 10rem",
}).textContent("I am a button"),
);
}

const CssStyleCss = css`
.title {
color: red;
}
`;
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import { fragment, tags } from "@purifyjs/core";
import css from "./style.css?raw";

const sheet = new CSSStyleSheet();
sheet.replaceSync(css);
import { css, sheet } from "./util-css";

const { div, h1, button } = tags;

Expand All @@ -11,7 +8,7 @@ export function CssStyle() {
const shadow = host.element.attachShadow({
mode: "open",
});
shadow.adoptedStyleSheets.push(sheet);
shadow.adoptedStyleSheets.push(CssStyleSheet);

shadow.append(
fragment(
Expand All @@ -24,3 +21,9 @@ export function CssStyle() {

return host;
}

const CssStyleSheet = sheet(css`
.title {
color: red;
}
`);

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Lifecycle } from "@purifyjs/core";

declare global {
interface DOMStringMap {
scope?: string;
}
}

export const css = String.raw;

export function sheet(css: string) {
const sheet = new CSSStyleSheet();
sheet.replaceSync(css);
return sheet;
}

export function scope(css: string): Lifecycle.OnConnected {
return (element) => {
if (element.dataset.scope) return;

const scopeId = Math.random().toString(36).slice(2);
document.adoptedStyleSheets.push(
sheet(
`@scope ([data-scope="${scopeId}"]) to ([data-scope]:not([data-scope="${scopeId}"]) > *) {${css}}`,
),
);
element.dataset.scope = scopeId;
};
}
2 changes: 1 addition & 1 deletion jsr.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@purifyjs/core",
"version": "0.0.302",
"version": "0.0.303",
"exports": "./lib/all.ts",
"publish": {
"include": ["lib", "LICENSE", "README.md"]
Expand Down
44 changes: 44 additions & 0 deletions lib/dependency.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,54 @@ import { Signal } from "./signals"

let dependencyTrackingStack: (Set<Signal<unknown>> | undefined)[] = []

/**
* Adds a signal to the most recent dependency tracking set on the stack, if it exists.
*
* @param {Signal<unknown>} signal - The signal to be added to the current tracking set.
*
* @example
* ```ts
* let signal = new Signal<number>(10);
* track(() => {
* add(signal); // This is automatically done when getting a value from State or Computed signals
* });
* ```
*/
export let add = (signal: Signal<unknown>): void => {
dependencyTrackingStack.at(-1)?.add(signal)
}

/**
* Tracks dependencies by pushing a new set onto the stack, invoking a function,
* and then popping the set off. This allows signals to be tracked during the execution
* of `callAndTrack`.
*
* @template R
* @param {() => R} callAndTrack - The function to invoke while tracking dependencies.
* @param {Set<Signal<unknown>>} [set] - Optional set to track the signals in. If not provided, undefined is pushed.
* @returns {R} - The result of the `callAndTrack` function.
*
* @example
* ```ts
* const signal = new Signal<number>(10);
* const signalSet = new Set<Signal<unknown>>();
* const result = track(() => {
* return signal.val * 2; // result is 20
* }, signalSet);
*
* result; // 20
* signalSet; // [signal]
* ```
*
* @example
* ```ts
* // Using track in a computed context to ignore further tracking
* computed(() => {
* add(this); // Add the current signal for tracking
* return track(() => getter(this.val)); // Ignore further adds
* });
* ```
*/
export let track = <R>(callAndTrack: () => R, set?: Set<Signal<unknown>>): R => {
dependencyTrackingStack.push(set)
let result = callAndTrack()
Expand Down
95 changes: 82 additions & 13 deletions lib/signals.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,40 @@
import * as Dependency from "./dependency"
export { Dependency }

/**
* An abstract class representing a signal that holds a value and allows followers to react to changes.
*
* @template T The type of the value held by the signal.
*/
export abstract class Signal<T> {
/** The current value of the signal. */
public abstract readonly val: T

/**
* Registers a follower that will be called when the signal's value changes.
*
* @param {Signal.Follower<T>} follower - The function to be called with the updated value.
* @param {boolean} [immediate] - Whether to call the follower immediately with the current value.
* @returns {Signal.Unfollower} A function to unregister the follower.
*/
public abstract follow(
follower: Signal.Follower<T>,
immediate?: boolean
): Signal.Unfollower

/**
* Derives a new computed signal from this signal using a getter function.
*
* @template R The type of the derived value.
* @param {function(T): R} getter - A function that computes a value based on this signal's value.
* @returns {Signal.Computed<R>} A new computed signal.
*
* @example
* ```ts
* // Updates only when `urlHref` changes
* urlHref.derive(() => urlSearchParams.val.get('foo') ?? urlPathname.val)
* ```
*/
public derive<R>(getter: (value: T) => R): Signal.Computed<R> {
return computed(() => {
Dependency.add(this)
Expand All @@ -16,31 +43,72 @@ export abstract class Signal<T> {
}
}

/** A namespace for signal-related types and classes. */
export declare namespace Signal {
/** Signal follower function type. */
type Follower<T> = (value: T) => void
/** Unfollow signal function type. */
type Unfollower = () => void

/**
* Writable State Signal.
*
* @template T The type of the value.
*/
class State<T> extends Signal<T> {
constructor(initial: T, startStop?: Signal.State.Start<T>)
public get val(): T
public set val(newValue: T)
public follow(follower: Follower<T>, immediate?: boolean): Signal.Unfollower
/**
* Notifies all followers of the current value without changing it.
*
* @example
* ```ts
* const arraySignal = ref([] as number[])
* const lastAdded = computed(() => arraySignal.val.at(-1))
* lastAdded.follow(console.log)
* arraySignal.push(123)
* arraySignal.emit() // logs: 123
**/
public emit(): void
}

namespace State {
/**
* Signal setter.
*
* @template T - The type of the value to set.
* @param value - The value to set.
*/
type Setter<T> = (value: T) => void

/**
* State start callback.
*
* @template T The type of the value held by the state signal.
* @param set A function to set the value of the signal.
* @returns A function to stop the signal or nothing.
*/
type Start<T> = (set: Setter<T>) => Stop | void

/** State stop callback. */
type Stop = () => void
}

/**
* Readonly Computed Signal that derives its value from other signals.
*
* @template T The type of the computed value.
*/
class Computed<T> extends Signal<T> {
constructor(getter: Signal.Computed.Getter<T>)
public get val(): T
public follow(follower: Follower<T>, immediate?: boolean): Signal.Unfollower
}

namespace Computed {
/** A function that computes the value of a computed signal. */
type Getter<T> = () => T
}
}
Expand Down Expand Up @@ -152,13 +220,14 @@ Signal.Computed = class<T> extends Signal<T> {
*
* @template T The type of the value.
* @param value The initial value of the signal.
* @returns A new signal with the given initial value.
* @param {Signal.State.Start<T>} [startStop] An optional start/stop function.
* @returns {Signal.State<T>} A new state signal with the given initial value.
*
* @example
* const count = ref(0);
* console.log(count.val); // logs: 0
* count.val = 5;
* console.log(count.val); // logs: 5
* count.follow(console.log)
* count.val = 5; // logs: 5
* count.val = 10; // logs: 10
*/
export let ref = <T>(value: T, startStop?: Signal.State.Start<T>): Signal.State<T> =>
new Signal.State(value, startStop)
Expand All @@ -167,15 +236,15 @@ export let ref = <T>(value: T, startStop?: Signal.State.Start<T>): Signal.State<
* Creates a new computed signal from other signals.
*
* @template T The type of the computed value.
* @param dependencies The signals that the computed signal depends on.
* @param getter A function that computes the value based on the dependencies.
* @returns A new computed signal.
* @param {Signal.Computed.Getter<T>} getter A function that computes the value based on the dependencies.
* @returns {Signal.Computed<T>} A new computed signal.
*
* @example
* const a = ref(1);
* const b = ref(2);
* const sum = computed([a, b], () => a.val + b.val);
* console.log(sum.val); // logs: 3
* const sum = computed(() => a.val + b.val);
* sum.follow(console.log)
* a.val++ // logs: 4
*/
export let computed = <T>(getter: Signal.Computed.Getter<T>): Signal.Computed<T> =>
new Signal.Computed(getter)
Expand All @@ -185,13 +254,13 @@ export let computed = <T>(getter: Signal.Computed.Getter<T>): Signal.Computed<T>
*
* @template T The type of the resolved value.
* @template U The type of the initial value (usually `null`).
* @param promise The promise that will resolve the value.
* @param until The initial value before the promise resolves.
* @returns A signal that updates when the promise resolves.
* @param {Promise<T>} promise The promise that will resolve the value.
* @param {U} [until] The initial value before the promise resolves.
* @returns {Signal<T | U>} A signal that updates when the promise resolves.
*
* @example
* const dataSignal = awaited(fetchDataPromise, null);
* dataSignal.follow(data => console.log(data)); // logs the resolved data when ready
* dataSignal.follow((data) => console.log(data)); // logs the resolved data when ready
*/
export let awaited = <T, const U = null>(
promise: Promise<T>,
Expand Down
Loading

0 comments on commit 9fda15d

Please sign in to comment.