Public
Edited
Dec 17, 2022
Insert cell
Insert cell
Insert cell
Base = component(({ children }) => {
const { Base } = styled({
Base: {
"@": [
"import url('https://fonts.googleapis.com/css2?family=Cousine:wght@400;700&Karla:wght@400;500;600;700;800&display=swap')"
],
fontSize: "1rem",
lineHeight: "1.25em",
fontFamily: "Karla",
fontWeight: 200
}
});
return Base("Hello", children);
})
Insert cell
Insert cell
GraphNode.effector
Insert cell
on = (node, handlers) => {
for (let k in handlers || {}) {
node.addEventListener(k, handlers[k]);
}
return handlers;
}
Insert cell
off = (node, handlers) => {
for (let k in handlers || {}) {
node.removeEventListener(k, handlers[k]);
}
return handlers;
}
Insert cell
NodeHandler = component(({ x, y }) => {
return H.circle({
cx: x,
cy: y,
stroke: "#FF0000",
fill: "#00FFFF",
r: 10,
onMouseDown:
(event, path, self) =>
({ dragging, x, y }, update) => {
console.log("ORIGINAL", { x, y });
off(window.document.body, dragging?.handlers);
const context = {
xo: event.pageX,
yo: event.pageY
};
const handlers = {
mousemove: (event) => {
const dx = event.pageX - context.xo;
const dy = event.pageY - context.yo;
update({ x: (x || 0) + dx, y: (y || 0) + dy });
},
mouseup: (event) => {
const dx = event.pageX - context.xo;
const dy = event.pageY - context.yo;
off(window.document.body, handlers);
}
};
on(window.document.body, handlers);
return { dragging: { context, handlers } };
}
});
})
Insert cell
GraphNode = component(({ x, y, dx, dy, width, height, dragging }) => {
// TODO: We should have a cell for the current components (which we can send signals)
// TODO: Lifecycle events, ie. mount, unmount, etc
return H.g(
H.foreignObject(
{
x,
/*: $([x, dx], ([x, dx]) => {
console.log("XXX", (x || 0) + (dx || 0));
return (x || 0) + (dx || 0);
})*/ y,
width,
height,
onMouseDown:
(event, path) =>
({ dragging, x, y }, update) => {
console.log("ORIGINAL", { x, y, path });
off(window.document.body, dragging?.handlers);
const context = {
xo: event.pageX,
yo: event.pageY
};
const handlers = {
mousemove: (event) => {
const dx = event.pageX - context.xo;
const dy = event.pageY - context.yo;
//console.log("UPDATE", { dx, dy, update });
return update({ dx, dy });
},
mouseup: (event) => {
const dx = event.pageX - context.xo;
const dy = event.pageY - context.yo;
off(window.document.body, handlers);
}
};
on(window.document.body, handlers);
return { x: 40, y: 40, dragging: { context, handlers } };
}
},
H.div(
{
xmlns: "http://www.w3.org/1999/xhtml",
class: "handler",
style: {
cursor: "move",
border: "1px solid red",
background: "#FF0000A0"
}
},
H.p("Lorem ipsum"),
H.button("Hello")
)
),
);
})
Insert cell
GraphCanvas = component(({ children, width, dragging }) => {
return H.svg(
{ width: "100%", height: "800px" },
H.defs(grid("grid", 20)),
H.rect({ fill: "url(#grid)", width: "100%", height: "100%" }),
// Some props are unmapped
GraphNode({ x: 100, y: 40, width: 100, height: 100 })
/* NodeHandler({
x: $([x, width], ([x, width]) => (x || 0) + (width || 0)),
y: y
})*/
);
})
Insert cell
render(GraphCanvas, {})
Insert cell
Insert cell
Insert cell
InputStyles = styled({
Input: {
display: "flex",
alignItems: "center",
gap: "8px",
fontFamily: "Karla",
margin: "4px 0px"
},
Output: {
display: "flex",
alignItems: "center",
gap: "4px",
fontFamily: "Karla"
},
Type: {
cursor: "pointer",
textTransform: "uppercase",
borderRadius: "2px",
color: "#E1D73E",
backgroundColor: "#160D40",
minWidth: "1.5em",
fontSize: "80%",
lineHeight: "1.5em",
textAlign: "center",
display: "inline-block",
"&:hover": {
backgroundColor: "red"
}
},
Label: {}
})
Insert cell
Input = component(({ type, children }) => {
const { Input, Type, Label } = InputStyles;
return Input(Type(type), Label(children));
})
Insert cell
Output = component(({ type, children }) => {
const { Output, Type, Label } = InputStyles;
return Output(Label(children), Type(type));
})
Insert cell
render(Input({ type: "F", label: "Input" }))
Insert cell
Block = component(({ title }) => {
const { Title, Block, Header, IO, Inputs, Outputs } = styled({
Block: {
backgroundColor: "#F0F0F0",
padding: "0px",
borderRadius: "4px",
color: "#606060",
width: "320px",
fontFamily: "Karla",
fontSize: "16px",
lineHeight: "1.25em"
},
Header: {
padding: "8px 12px",
display: "flex",
gap: "8px",
alignItems: "center",
background: "#160D40",
color: "#E1D73E",
borderBottom: "2px solid #F0F0F0"
},
Title: {
flexGrow: 1,
fontWeight: "bold",
textTransform: "uppercase"
},
IO: {
display: "grid",
padding: "8px",
gridTemplateColumns: "1fr 1fr"
},
Inputs: {
display: "flex",
flexDirection: "column",
alignItems: "flex-start"
},
Outputs: {
display: "flex",
flexDirection: "column",
alignItems: "flex-end"
}
});
return Block(
Header(icon("code-brackets"), Title(title), icon("cancel")),
IO(
Inputs(Input({ type: "F" }, "A"), Input({ type: "F" }, "B")),
Outputs(Output({ type: "F" }, "Out"))
),
Editor("a + b * 10")
);
})
Insert cell
icon("cancel")
Insert cell
Editor = component(({ value }) => {
const { Input } = styled({
"Input:textarea": {
fontFamily: "monospace",
fontSize: "13px",
lineHeight: "1.25em",
border: "0px solid transparent",
outline: "0px solid transparent",
background: "#FFFFFF",
border: "1px solid #E0E0E0",
borderTopWidth: "0px",
borderBottomWidth: "0px",
padding: "12px 8px",
color: "#909090",
width: "100%",
fontFamily: "Cousine",
boxSizing: "border-box",
resize: "none"
}
});
return Input({
value,
onInput: (event) => {
event.target.style.height = `${event.target.scrollHeight}px`;
}
});
})
Insert cell
render(Block, { title: "Addition" })
Insert cell
$(H.div(icon("white-flag")), {}).node
Insert cell
Insert cell
Tokens = ({
Blue: "#198ebe",

Background: "#FFFFFF",
Focused: "#FFFF00",

pad: (_, v) => {
return unit((v * 1) / 14, "em", 3);
},
// Focused has luminance boosted by 15%
focused: (_) => _.color.tint(_.color.luminance + 0.15),

// Border is 50% grey, 0.25 opaque
border: (_) => _.color.grey(0.5).alpha(0.25),

// Background is 75% blend to background color
background: (_) => _.color.blend(_("Background"), 0.75)
})
Insert cell
Insert cell
Insert cell
Insert cell
styled = (styles) => {
const res = {};
for (let k in styles) {
const [styledName, tagName] = k.split(":");
const normalizedStyle = Object.entries(styles[k]).reduce(
(r, [k, v]) => {
if (k.startsWith("@") || k.startsWith("&")) {
r[k] = v;
} else {
r["&"][k] = v;
}
return r;
},
{ "&": {} }
);
const s = registerStyle(
style(normalizedStyle, Tokens, undefined, parseToken)
);
const n = className(s);
res[styledName] = Object.assign(
(
(styleName, tagName = "div") =>
(...value) => {
const node = H[tagName || "div"](...value);
const klass = node.attributes["class"];
node.attributes["class"] = klass
? `${styleName} ${klass}`
: styleName;
return node;
}
)(n, tagName),
{ className: n }
);
}
return res;
}
Insert cell
parseToken = (text, context) => {
const res = token(text).eval(context);
return res.color || res.value;
}
Insert cell
mutable Styles = ({})
Insert cell
Rules = []
Insert cell
registerStyle = (style) => {
const delta = Rules;
for (let k in style) {
if (Styles[k] === undefined) {
Styles[k] = style[k];
compileStyle({ [k]: style[k] }).reduce((r, v) => {
v.startsWith("@") ? r.splice(0, 0, v) : r.push(v);
return r;
}, delta);
}
}
const existing = document.getElementById("stylesheet");
existing?.parentElement.removeChild(existing);
const current = html`<style id="stylesheet">${Rules.join("\n")}</style>`;
document.body.appendChild(current);

return style;
}
Insert cell
Insert cell
$(
H.svg(
{ width: 600, height: 600, viewBox: "0 0 600 600" },
H.defs(grid("grid", 20)),
H.rect({ fill: "url(#grid)", width: "100%", height: "100%" }),
H.foreignObject(
{ x: 100, y: 100, width: 320, height: 200 },
H.div(
{ xmlns: "http://www.w3.org/1999/xhtml" },
H.p("Lorem ipsum"),
H.button("Hello")
)
)
)
).node
Insert cell
Icons
Insert cell
import { component, $, H, render } from "@sebastien/ui"
Insert cell
import { Icons, icon } from "@sebastien/icons"
Insert cell
import { style, unit, compileStyle, className } from "@sebastien/styling"
Insert cell
import { token } from "@sebastien/tokens"
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more