Skip to content

Commit 9c8ea15

Browse files
committed
wip: replace rules with auto-tracking
1 parent 0c78864 commit 9c8ea15

File tree

5 files changed

+423
-0
lines changed

5 files changed

+423
-0
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { StyleCollection } from "../injection";
2+
import type {
3+
ContainerContextValue,
4+
VariableContextValue,
5+
} from "../reactivity";
6+
import type { GetFunction } from "../tracking";
7+
import type { Config } from "./useNativeCss";
8+
9+
export function updateRulesNew(
10+
get: GetFunction,
11+
originalProps: Record<string, unknown>,
12+
configs: Config[],
13+
inheritedVariables: VariableContextValue,
14+
inheritedContainers: ContainerContextValue,
15+
) {
16+
let inlineVariables: Record<string, unknown> | undefined;
17+
let animated = false;
18+
19+
for (const config of configs) {
20+
const styleRuleSet = [];
21+
22+
const source = originalProps[config.source];
23+
if (typeof source === "string") {
24+
const classNames = source.split(/\s+/);
25+
for (const className of classNames) {
26+
styleRuleSet.push(...get(StyleCollection.styles(className)));
27+
}
28+
}
29+
}
30+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { type ComponentType } from "react";
2+
3+
import type { Config } from "prettier";
4+
5+
import { ContainerContext, VariableContext } from "../reactivity";
6+
import { useDeepWatcher } from "../tracking";
7+
8+
export function useNativeCss<T extends object>(
9+
type: ComponentType<T>,
10+
originalProps: Record<string, unknown> | undefined | null,
11+
configs: Config[] = [{ source: "className", target: "style" }],
12+
) {
13+
const state = useDeepWatcher(() => {
14+
return {};
15+
}, [configs, originalProps, VariableContext, ContainerContext]);
16+
17+
return null;
18+
}

src/runtime/native/signals.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// signals.ts
2+
3+
type Subscriber = () => void;
4+
5+
export interface Signal<T> {
6+
_get(): T;
7+
_subs: Set<Subscriber>;
8+
set(value: T): void;
9+
subscribe(fn: Subscriber): () => void;
10+
}
11+
12+
export type GetFunction = <T>(sig: Signal<T>) => T;
13+
14+
export function signal<T>(initialValue: T): Signal<T> {
15+
let value = initialValue;
16+
const subscribers = new Set<Subscriber>();
17+
18+
function _get(): T {
19+
return value;
20+
}
21+
22+
function set(newValue: T): void {
23+
if (newValue !== value) {
24+
value = newValue;
25+
for (const fn of subscribers) {
26+
if (isBatching) {
27+
batchQueue.add(fn);
28+
} else {
29+
fn();
30+
}
31+
}
32+
}
33+
}
34+
35+
function subscribe(fn: Subscriber): () => void {
36+
subscribers.add(fn);
37+
return () => subscribers.delete(fn);
38+
}
39+
40+
return { _get, _subs: subscribers, set, subscribe };
41+
}
42+
43+
// computed
44+
45+
export function computed<T>(fn: (get: GetFunction) => T) {
46+
const s = signal<T>(undefined as unknown as T);
47+
48+
const cleanupFns = new Set<() => void>();
49+
50+
const run = (): void => {
51+
s.set(fn(get));
52+
};
53+
54+
const get: GetFunction = (signal) => {
55+
signal._subs.add(run);
56+
cleanupFns.add(() => signal._subs.delete(run));
57+
return signal._get();
58+
};
59+
60+
run();
61+
62+
return s;
63+
}
64+
65+
// Batching system
66+
67+
let isBatching = false;
68+
const batchQueue = new Set<Subscriber>();
69+
70+
function flushBatch(): void {
71+
const queue = Array.from(batchQueue);
72+
batchQueue.clear();
73+
isBatching = false;
74+
for (const fn of queue) fn();
75+
}
76+
77+
export function batch(fn: (get: GetFunction) => void): void {
78+
isBatching = true;
79+
fn((sig) => sig._get());
80+
flushBatch();
81+
}

src/runtime/native/tracking.ts

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
import { use, useState, type Context } from "react";
2+
3+
const LOCK = Symbol("react-native-css-lock");
4+
5+
const IS_EQUAL = Symbol("react-native-css-is-equal");
6+
interface IsEqualObject {
7+
[IS_EQUAL]: (other: unknown) => boolean;
8+
}
9+
10+
export type GetFunction = <T>(sig: Signal<T>) => T;
11+
type Subscriber = () => void;
12+
13+
export interface Signal<T> {
14+
_get(): T;
15+
_subs: Set<Subscriber>;
16+
set(value: T): void;
17+
subscribe(fn: Subscriber): () => void;
18+
}
19+
20+
function cleanupSubscriptions(subscriptions: Set<() => void>): void {
21+
for (const dispose of subscriptions) {
22+
dispose();
23+
}
24+
subscriptions.clear();
25+
}
26+
27+
/**
28+
* A custom built watcher for the library
29+
*/
30+
export function useDeepWatcher<
31+
T extends object,
32+
Configs extends object,
33+
Props extends object,
34+
Variables extends object,
35+
Containers extends object,
36+
>(
37+
fn: (get: GetFunction, ...args: [Configs, Props, Variables, Containers]) => T,
38+
deps: [
39+
Configs,
40+
Props | undefined | null,
41+
Context<Variables>,
42+
Context<Containers>,
43+
],
44+
) {
45+
const [state, setState] = useState(() => {
46+
const subscriptions = new Set<Subscriber>();
47+
48+
const configs = deps[0];
49+
const props = makeAccessTreeProxy(deps[1] ?? ({} as Props));
50+
51+
const lazyVariables = makeLazyContext(deps[2]);
52+
const variables = makeAccessTreeProxy(lazyVariables);
53+
54+
const lazyContainers = makeLazyContext(deps[3]);
55+
const containers = makeAccessTreeProxy(lazyContainers);
56+
57+
const get: GetFunction = (signal) => {
58+
const dispose = signal.subscribe(() => {
59+
cleanupSubscriptions(subscriptions);
60+
61+
lazyVariables[LOCK] = true;
62+
lazyContainers[LOCK] = true;
63+
64+
setState((s) => ({
65+
...s,
66+
value: fn(get, configs, props, variables, containers),
67+
}));
68+
});
69+
70+
subscriptions.add(dispose);
71+
return signal._get();
72+
};
73+
74+
const value = fn(get, configs, props, variables, containers);
75+
76+
return {
77+
value,
78+
subscriptions,
79+
deps: [configs, props, variables, containers],
80+
};
81+
});
82+
83+
if (
84+
state.deps.some((dep, index) => {
85+
return !(dep as IsEqualObject)[IS_EQUAL](deps[index]);
86+
})
87+
) {
88+
setState((s) => {
89+
const subscriptions = s.subscriptions;
90+
cleanupSubscriptions(subscriptions);
91+
92+
const configs = deps[0];
93+
const props = makeAccessTreeProxy(deps[1] ?? ({} as Props));
94+
95+
const lazyVariables = makeLazyContext(deps[2]);
96+
const variables = makeAccessTreeProxy(lazyVariables);
97+
98+
const lazyContainers = makeLazyContext(deps[3]);
99+
const containers = makeAccessTreeProxy(lazyContainers);
100+
101+
const get: GetFunction = (signal) => {
102+
const dispose = signal.subscribe(() => {
103+
cleanupSubscriptions(subscriptions);
104+
105+
lazyVariables[LOCK] = true;
106+
lazyContainers[LOCK] = true;
107+
108+
setState((s) => ({
109+
...s,
110+
value: fn(get, configs, props, variables, containers),
111+
}));
112+
});
113+
114+
subscriptions.add(dispose);
115+
return signal._get();
116+
};
117+
118+
return {
119+
value: fn(get, configs, props, variables, containers),
120+
subscriptions: s.subscriptions,
121+
deps: [configs, props, variables, containers],
122+
};
123+
});
124+
}
125+
126+
return state.value;
127+
}
128+
129+
function makeLazyContext<T extends object>(context: React.Context<T>) {
130+
let locked = false;
131+
let ctx: T | undefined;
132+
133+
return new Proxy(
134+
{},
135+
{
136+
get(_, prop, receiver) {
137+
if (prop === LOCK) {
138+
locked = true;
139+
return undefined;
140+
}
141+
142+
if (locked) {
143+
if (ctx === undefined) {
144+
return;
145+
}
146+
return Reflect.get(ctx, prop, receiver);
147+
}
148+
149+
ctx ??= makeAccessTreeProxy(use(context));
150+
151+
return Reflect.get(ctx, prop, receiver);
152+
},
153+
},
154+
) as T & { [LOCK]: true };
155+
}
156+
157+
function makeAccessTreeProxy<T extends object>(value: T): T {
158+
const branches = new Map<keyof T, object>();
159+
160+
return new Proxy(value, {
161+
get(target, prop, receiver) {
162+
if (prop === IS_EQUAL) {
163+
return (other: T) => {
164+
return (
165+
Object.is(target, other) ||
166+
Array.from(branches).every(([key, child]) => {
167+
return typeof child === "object" && IS_EQUAL in child
168+
? (child as IsEqualObject)[IS_EQUAL](other[key])
169+
: Object.is(child, other[key]);
170+
})
171+
);
172+
};
173+
}
174+
175+
const value = Reflect.get(target, prop, receiver);
176+
177+
if (typeof value === "object" && value !== null) {
178+
const proxy = makeAccessTreeProxy(value);
179+
branches.set(prop as keyof T, proxy);
180+
return proxy;
181+
} else {
182+
return value;
183+
}
184+
},
185+
});
186+
}

0 commit comments

Comments
 (0)