@joshuafcole/fluorine
Version:
A highly reactive, optionally compiled, and strongly typed view layer for TypeScript.
289 lines (288 loc) • 9.54 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("efreet/utils");
function embed(val) {
return val === undefined
? "undefined"
: val instanceof Compilable
? val.compile()
: JSON.stringify(val);
}
exports.embed = embed;
//----------------------------------------------------------------------
// Event Binder (statically generated event handlers)
//----------------------------------------------------------------------
class EventBinder {
// @NOTE: Working around a gnarly issue in the type system.
constructor(_handle, _handler, _prior_clause) {
this._handle = _handle;
this._handler = _handler;
this._prior_clause = _prior_clause;
this._thens = [];
}
compile_inner() {
// pad_chunk(2)
let inner = `
${this._handler}
${this._preventer}
${this._stopper}
${this._thens.join("\n")}
`;
if (this._when) {
// pad_chunk(2)
inner = `
if(${this._when}) {
${inner}
}
`;
}
if (this._prior_clause) {
// The inverted order here is important because we're unwinding the linked-list :/
inner +=
"\n\n // OR\n binder = binder._prior_clause;\n" +
this._prior_clause.compile_inner();
}
return inner;
}
compile() {
let start = utils_1.now();
let inner = this.compile_inner();
let compiled = new Function("binder",
// pad_chunk(2)
`
return function catalyst_compiled_handler(event, elem) {
var target = event.currentTarget || event.target;
${inner}
};
`)(this);
// console.log("COMPILED", this._handle && this._handle.name, now() - start);
// console.log(compiled);
return compiled;
}
/** Trigger the handler with the given properties when this event fires. */
static handle(handler, ...args) {
return new EventBinder(handler,
// inline_chunk
`
// ${handler.name}
binder._handle(${args.map(embed).join(", ")});
`);
}
/** Add another non-exclusive handling clause. Useful in conjunction with `when()`. */
or(handler, ...args) {
return new EventBinder(handler,
// inline_chunk
`
// ${handler.name}
binder._handle(${args.map(embed).join(", ")});
`, this);
}
/** Add a condition which must be true for the handler to fire. */
when(prop) {
this._when = embed(prop);
return this;
}
/** Add a side effect to be triggered after the handler fires. */
then(effect) {
this._thens.push(effect.compile());
return this;
}
/** Always stop propagation on this event. */
stop() {
this._stopper = `event.stopPropagation();`;
return this;
}
/** Always prevent default on this event. */
prevent() {
this._preventer = `event.preventDefault();`;
return this;
}
/** Stop propagation on this event if the predicate returns true. */
stop_if(predicate, ...args) {
this._stop = predicate;
// inline_chunk
this._stopper = `
// ${predicate.name}
if(binder._stop(${args.map(embed).join(", ")})) {
event.stopPropagation();
}
`;
return this;
}
/** Prevent default on this event if the predicate returns true. */
prevent_if(predicate, ...args) {
this._prevent = predicate;
// inline_chunk
this._preventer = `
// ${predicate.name}
if(binder._prevent(${args.map(embed).join(", ")})) {
event.preventDefault();
}
`;
return this;
}
}
exports.EventBinder = EventBinder;
//----------------------------------------------------------------------
// Compilables (statically defined snippets for generating event handlers)
//----------------------------------------------------------------------
class Compilable {
equals(rhs) {
return new Compilable.Infix("===", this, rhs);
}
is_not(rhs) {
return new Compilable.Infix("!==", this, rhs);
}
dot(property) {
return new Compilable.Dot(this, property);
}
and(rhs) {
return new Compilable.Infix("&&", this, rhs);
}
or(rhs) {
return new Compilable.Infix("||", this, rhs);
}
to_string() {
return new Compilable.Computed(`""+$1`, this);
}
to_number() {
return new Compilable.Computed(`!isNaN(+$1) ? +$1 : undefined`, this);
}
}
exports.Compilable = Compilable;
(function (Compilable) {
class Snippet extends Compilable {
constructor(code) {
super();
this.code = code;
}
compile() {
return this.code;
}
}
Compilable.Snippet = Snippet;
class Computed extends Compilable {
constructor(template, ...terms) {
super();
this.template = template;
this.terms = terms;
}
compile() {
let code = this.template;
for (let ix = 0; ix < this.terms.length; ix += 1) {
code = code.replace(new RegExp(`\\$${ix + 1}`, "g"), embed(this.terms[ix]));
}
return code;
}
}
Compilable.Computed = Computed;
class Infix extends Compilable {
constructor(op, lhs, rhs) {
super();
this.op = op;
this.lhs = lhs;
this.rhs = rhs;
}
compile() {
return `(${embed(this.lhs)} ${this.op} ${embed(this.rhs)})`;
}
}
Compilable.Infix = Infix;
class Dot extends Compilable {
constructor(parent, property) {
super();
this.parent = parent;
this.property = property;
}
compile() {
return `(${this.parent.compile()})["${this.property}"]`;
}
}
Compilable.Dot = Dot;
class Property extends Compilable {
constructor(prefix, property) {
super();
this.prefix = prefix;
this.property = property;
}
static create(prefix, property) {
return new Property(prefix, property);
}
static create_event(eventCtor, property) {
return new Property("event", property);
}
compile() {
return `${this.prefix}["${this.property}"]`;
}
}
Compilable.Property = Property;
class CategoryProperty extends Compilable {
constructor(prefix, property, categories) {
super();
this.prefix = prefix;
this.property = property;
this.categories = categories;
for (let name in categories) {
this[name] = this.equals(categories[name]);
}
}
static create(prefix, property, categories) {
let prop = new CategoryProperty(prefix, property, categories);
return prop;
}
static create_event(obj, property, categories) {
let prop = new CategoryProperty("event", property, categories);
return prop;
}
compile() {
return `${this.prefix}["${this.property}"]`;
}
}
Compilable.CategoryProperty = CategoryProperty;
})(Compilable = exports.Compilable || (exports.Compilable = {}));
//----------------------------------------------------------------------
// Public API
//----------------------------------------------------------------------
/** Trigger the handler with the given properties when this event fires. */
exports.handle = EventBinder.handle;
exports.get = {
elem: (prop) => {
return Compilable.Property.create("elem", prop);
},
target: {
value: Compilable.Property.create("target", "value"),
content: Compilable.Property.create("target", "textContent"),
selected: new Compilable.Computed(`[].slice.apply($1).map((opt) => opt.value)`, Compilable.Property.create("target", "selectedOptions")),
bounds: new Compilable.Computed(`target.getBoundingClientRect()`)
},
node: new Compilable.Computed(`event.target`),
event: new Compilable.Computed(`event`),
mouse: {
button: Compilable.CategoryProperty.create_event(MouseEvent, "button", {
left: 0,
middle: 1,
right: 2
}),
x: Compilable.Property.create_event(MouseEvent, "clientX"),
y: Compilable.Property.create_event(MouseEvent, "clientY"),
wheel: {
delta_mode: Compilable.Property.create_event(WheelEvent, "deltaMode"),
delta_x: Compilable.Property.create_event(WheelEvent, "deltaX"),
delta_y: Compilable.Property.create_event(WheelEvent, "deltaY"),
delta_z: Compilable.Property.create_event(WheelEvent, "deltaZ")
}
},
keyboard: {
key: Compilable.Property.create_event(KeyboardEvent, "key"),
mod: (mod) => new Compilable.Computed(`event.getModifierState("${mod}")`)
},
do: {
target: {
clear: new Compilable.Snippet(`target.value = ""`),
style: (property, value) => new Compilable.Snippet(`target.style.setProperty(${embed(property)}, ${embed(value)})`)
}
}
};
/** Registry for compiled event handlers (automatically maintained when compiled). */
exports.handlers = {};
//# sourceMappingURL=catalyst.js.map