@expressive/react
Version:
Use classes to define state in React!
173 lines (171 loc) • 4.73 kB
JavaScript
import { Layers, provide } from "./context.js";
import { Context, State, event, watch } from "@expressive/state";
//#region src/state.ts
const Pragma = {};
const OUTER = /* @__PURE__ */ new WeakMap();
const PROPS = /* @__PURE__ */ new WeakMap();
var ReactState = class ReactState extends State {
/**
* Create and manage instance of this State within React component.
*
* @param args Arguments to pass to constructor or `use` method (if defined).
* @returns Managed instance of this State.
*/
static use(...args) {
const ambient = Context.use();
const state = Pragma.useState(() => {
let ready;
let active;
let use = (...args) => Promise.all(args.flat().map((arg) => typeof arg == "object" && instance.set(arg)));
const instance = new this((x) => {
if (x instanceof ReactState && x.use) {
use = x.use?.bind(x);
use(...args);
} else return args;
});
const context = ambient.push(instance);
watch(instance, (current) => {
active = current;
if (ready) state[1]((x) => x.bind(null));
});
function didMount() {
ready = true;
return () => {
context.pop();
instance.set(null);
};
}
return (...args) => {
Pragma.useEffect(didMount, []);
if (ready) {
ready = false;
Promise.resolve(use(...args)).finally(() => ready = true);
}
return active;
};
});
return state[0](...args);
}
static get(argument) {
const context = Context.use();
const state = Pragma.useState(() => {
const instance = context.get(this);
if (!instance) if (argument === false) return () => void 0;
else throw new Error(`Could not find ${this} in context.`);
let ready;
let value;
function render() {
state[1]((x) => x.bind(null));
}
function refresh(action) {
if (typeof action == "function") action = action();
render();
if (action) return action.finally(render);
}
const unwatch = watch(instance, (current) => {
if (typeof argument === "function") {
const next = argument.call(current, current, refresh);
if (next === value) return;
value = next;
} else value = current;
if (ready) render();
}, argument === true);
if (value instanceof Promise) {
let error;
unwatch();
value.then((x) => value = x, (e) => error = e).finally(render);
value = null;
return () => {
if (error) throw error;
return value === void 0 ? null : value;
};
}
if (value === null) {
unwatch();
return () => null;
}
function onMount() {
ready = true;
return unwatch;
}
return () => {
Pragma.useEffect(onMount, []);
return value === void 0 ? null : value;
};
});
return state[0]();
}
static as(argument) {
const Type = this;
const render = typeof argument === "function" ? argument : void 0;
class ReactType extends Type {
static {
this.contextType = Layers;
}
get props() {
return PROPS.get(this);
}
set props(props) {
PROPS.set(this, props);
this.set(props);
}
get context() {
return Context.get(this);
}
set context(context) {
if (OUTER.get(this) === context) return;
OUTER.set(this, context);
context.push(this);
}
get state() {
return this.get();
}
set state(_state) {}
constructor(nextProps, ...rest) {
const { is } = nextProps;
const defaults = typeof argument === "object" ? argument : {};
if (rest[0] instanceof Context) rest.shift();
super(nextProps, defaults, is, rest);
this.fallback = void 0;
PROPS.set(this, nextProps);
if (render) {
const Self = Render.bind(this, render);
this.render = () => Pragma.createElement(Self);
} else if (!this.render) this.render = () => provide(this.context, this.props.children || null, this.props.fallback, String(this));
}
}
Object.defineProperty(ReactType, "name", { value: "React" + this.name });
Object.defineProperty(ReactType.prototype, "isReactComponent", { get: () => true });
return ReactType;
}
};
function Render(render) {
const state = Pragma.useState(() => {
event(this);
const { context } = this;
let ready;
let active;
watch(this, (current) => {
active = current;
if (ready) state[1]((x) => x.bind(null));
});
const didMount = () => {
ready = true;
return () => {
context.pop();
this.set(null);
};
};
const View = () => render.call(active, this.props, active);
return () => {
ready = false;
Pragma.useEffect(didMount, []);
setTimeout(() => ready = true, 0);
return provide(context, Pragma.createElement(View), this.props.fallback, String(this));
};
});
return state[0]();
}
//#endregion
export { Pragma, ReactState };
//# sourceMappingURL=state.js.map