Published
Edited
Sep 29, 2022
Importers
8 stars
Also listed in…
Observable Controls
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
viewof seed = seedButton()
Insert cell
seed
Insert cell
Insert cell
viewof aSeedBtn = seedButton("Randomize")
Insert cell
aSeedBtn
Insert cell
Insert cell
viewof bSeedBtn = seedButton("Randomize", {
disabled: true, // Disables whole component
undoLabel: "Back" // Replace label of undu button
})
Insert cell
Insert cell
viewof cSeedBtn = seedButton()
Insert cell
cSeedBtn
Insert cell
Inputs.button("Write a specific seed", {
reduce: () => {
viewof cSeedBtn.value = 1111;
}
})
Insert cell
Insert cell
function seedButton(label = "🎲 Generate seed", opts = {}) {
const { disabled, undoLabel, redoLabel } = Object.assign(
{
disabled: false,
undoLabel: "⏪ Undo",
redoLabel: "⏩ Redo"
},
opts
);

const machine = createSeedButtonMachine(generateRandomSeed);
const service = xState.interpret(machine).start();

const undoButton = html`<button
type="button"
class="${blockClass}__undo"
disabled=${disabled}
onClick=${() => service.send("UNDO")}
>${undoLabel}</button>`;

const redoButton = html`<button
type="button"
class="${blockClass}__redo"
disabled=${disabled}
onClick=${() => service.send("REDO")}
>${redoLabel}</button>`;

const form = html`<form class="${ns} ${blockClass}">
<button
type="button"
onclick=${() => service.send("GENERATE")}
disabled=${disabled}>${label}</button>
${undoButton}
${redoButton}
</form>`;

function dispatchInputEvent() {
form.dispatchEvent(new Event("input", { bubbles: true }));
}

function updateUi(state) {
undoButton.disabled = state.value === "idle" || state.value === "bigbang";
redoButton.disabled = state.value === "idle" || state.value === "present";
form.dataset.seedButtonState = state.value;
}

service.subscribe((state) => {
updateUi(state);
dispatchInputEvent();
});

attachStyles(invalidation);
invalidation.then(() => service.stop());

return Object.defineProperty(form, "value", {
get() {
const { seeds, head } = service.state.context;
return seeds[head];
},
set(value) {
service.send({ type: "GENERATE", seed: value });
}
});
}
Insert cell
function getSeedFromUrl() {
const s = params.get("seed");

return s === null ? null : +s;
}
Insert cell
blockClass = `${msns}-form`
Insert cell
params = new URLSearchParams(location.search)
Insert cell
function generateRandomSeed() {
return Math.floor(Math.random() * 1_000_000_000);
}
Insert cell
ns = Inputs.text().classList[0]
Insert cell
msns = {
return ns.replace("oi-", "sb-");
}
Insert cell
newId = {
let nextId = 0;

return function newId() {
return `${msns}-${++nextId}`;
};
}
Insert cell
attachStyles = (placeOfUseInvalidation) => {
const elId = `${msns}-seed-button`;

if (document.getElementById(elId)) return;

const style = html`<style id=${elId}>
.${blockClass}__undo, .${blockClass}__redo {
margin-inline-start: 0.5rem;
}

.${blockClass} button[disabled] {
opacity: .6;
cursor: not-allowed;
}
</style>`;

document.head.append(style);

placeOfUseInvalidation.then(() => style.remove());
invalidation.then(() => style.remove());
}
Insert cell
function createSeedButtonMachine(seedGenerator) {
const { createMachine, assign } = xState;

const commonEvents = {
GENERATE: {
target: "present",
actions: "generateSeed"
}
};

const guards = {
isHeadPointingSecond: (context) => context.head === 1,
isHeadPointingSecondLast: (context) =>
context.head === context.seeds.length - 2
};

return createMachine(
{
id: "seed-button",
initial: "idle",
context: {
seeds: [getSeedFromUrl() || seedGenerator()],
head: 0
},
states: {
idle: {
on: {
...commonEvents
}
},
bigbang: {
on: {
...commonEvents,
REDO: [
{
cond: guards.isHeadPointingSecondLast,
target: "present",
actions: "redo"
},
{
target: "past",
actions: "redo"
}
]
}
},
past: {
on: {
...commonEvents,
UNDO: [
{
cond: guards.isHeadPointingSecond,
target: "bigbang",
actions: "undo"
},
{
target: "past",
actions: "undo"
}
],
REDO: [
{
cond: guards.isHeadPointingSecondLast,
target: "present",
actions: "redo"
},
{
target: "past",
actions: "redo"
}
]
}
},
present: {
on: {
...commonEvents,
UNDO: [
{
cond: guards.isHeadPointingSecond,
target: "bigbang",
actions: "undo"
},
{
target: "past",
actions: "undo"
}
]
}
}
}
},
{
actions: {
generateSeed: assign({
head: (context) => context.head + 1,
seeds: (context, event) => [
...context.seeds.slice(0, context.head + 1),
event.seed || seedGenerator()
]
}),
undo: assign({
head: (context) => Math.max(context.head - 1, 0)
}),
redo: assign({
head: (context) => context.head + 1
})
}
}
);
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more