Skip to content

Commit

Permalink
🗝️ Add KeyedSwitch
Browse files Browse the repository at this point in the history
 - KeyedSwitch displays exactly 1 child at a time
   specified by a string key.
  • Loading branch information
KimlikDAO-bot committed Feb 20, 2025
1 parent caec874 commit 4e28981
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 10 deletions.
57 changes: 57 additions & 0 deletions kastro/KeyedSwitch.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import dom from "../util/dom";

/**
* @constructor
* @param {{
* id: string,
* initialPane: (string | undefined),
* children: !Array<?function():void>,
* keyToIndex: !Object<string, number>,
* }} props
*/
const KeyedSwitch = function ({ id, initialPane, children, keyToIndex }) {
/** @type {number} */
this.selectedPane = dom.GEN ? 0 : initialPane ? keyToIndex[initialPane] : 0;
/** @const {!Array<?function():void>} */
this.initializers = children;
/** @const {!Object<string, number>} */
this.keyToIndex = keyToIndex;
/** @const {!HTMLDivElement} */
const Root = dom.div(id);
/** @const {!HTMLDivElement} */
this.root = Root;

return (
<Root modifiesChildren>
{children.modify((c, i) => {
c.nodisplay = initialPane ? c.key != initialPane : i != this.selectedPane;
delete c.key;
})}
</Root>
);
}

/**
* Shows the child with the given key and hides the currently shown child.
* If the child is being shown for the first time, initializes it.
*
* @param {string} key Key of the child to show
*/
KeyedSwitch.prototype.showPane = function (key) {
/** @const {number} */
const idx = this.keyToIndex[key];
/** @const {number} */
const old = this.selectedPane;
if (idx == old) return;
/** @const {?function():void} */
const f = this.initializers[idx];
if (f) {
f();
this.initializers[idx] = null;
}
this.selectedPane = idx;
dom.show(this.root.children[idx]);
dom.hide(this.root.children[old]);
}

export default KeyedSwitch;
19 changes: 19 additions & 0 deletions kastro/Router.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import dom from "../util/dom";

/**
* @param {{
* routeHandler: function(string):void
* }} props
*/
const Router = ({ routeHandler }) => {
const onHashChange = () => routeHandler(window.location.hash.slice(1));
window.onhashchange = onHashChange;
dom.schedule(onHashChange, 0);
}

/**
* @param {string} route
*/
Router.navigate = (route) => window.location.hash = route;

export default Router;
12 changes: 6 additions & 6 deletions kastro/Switch.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import dom from "../util/dom";
* @constructor
* @param {{
* id: string,
* initialSelected: number,
* initialPane: number,
* children: !Array<?function():void>,
* }} props
*/
const Switch = function ({ id, initialSelected, children }) {
const Switch = function ({ id, initialPane = 0, children }) {
/** @type {number} */
this.selectedChild = initialSelected;
this.selectedPane = initialPane;
/** @const {!Array<?function():void>} */
this.initializers = children;
/** @const {!HTMLDivElement} */
Expand All @@ -20,7 +20,7 @@ const Switch = function ({ id, initialSelected, children }) {

return (
<Root modifiesChildren>
{children.modify((c, i) => c.nodisplay = i != initialSelected)}
{children.modify((c, i) => c.nodisplay = i != initialPane)}
</Root>
);
}
Expand All @@ -33,15 +33,15 @@ const Switch = function ({ id, initialSelected, children }) {
*/
Switch.prototype.showPane = function (idx) {
/** @const {number} */
const old = this.selectedChild;
const old = this.selectedPane;
if (idx == old) return;
/** @const {?function():void} */
const f = this.initializers[idx];
if (f) {
f();
this.initializers[idx] = null;
}
this.selectedChild = idx;
this.selectedPane = idx;
dom.show(this.root.children[idx]);
dom.hide(this.root.children[old]);
}
Expand Down
3 changes: 3 additions & 0 deletions kastro/kastro.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ const setupKastro = () => {
ethereum: {
isRabby: false
},
location: {
hash: "",
},
addEventListener(name, handler) { },
dispatchEvent(event) { }
};
Expand Down
48 changes: 44 additions & 4 deletions kastro/transpiler/jsx.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,11 @@ const transpile = (isEntry, file, content, domIdMapper, globals) => {
const length = node.children.length;
if (!length) return;
/** @const {number} */
const selected = props.initialSelected.expression.value;
const selectedPane = props.initialPane ? props.initialPane.expression.value : 0;
const fnExprs = Array(length);
for (let i = 0; i < length; ++i) {
const statements = [];
if (i != selected)
if (i != selectedPane)
processJsxElement(node.children[i], node, i, statements);
const fnExpr = statements.length
? statements.length == 1
Expand All @@ -85,7 +85,45 @@ const transpile = (isEntry, file, content, domIdMapper, globals) => {
fnExprs[i] = fnExpr;
}
props.children = `[${fnExprs.join(", ")}]`;
node.children[0] = node.children[selected];
node.children[0] = node.children[selectedPane];
node.children.length = 1;
},

KeyedSwitch: (node, props) => {
node.children = node.children.filter((c) => c.type == "JSXElement");
const length = node.children.length;
if (!length) return;
// Build keyToIndex map and collect keys
const keyToIndex = {};
node.children.forEach((child, i) => {
const keyProp = child.openingElement.attributes
.find(attr => attr.name.name === "key");
if (!keyProp)
throw new Error("KeyedSwitch children must have key prop");
const key = keyProp.value.value;
keyToIndex[key] = i;
});

/** @const {string} */
const initialPane = props.initialPane ? props.initialPane.expression.value : "";
/** @const {number} */
const selectedPane = initialPane ? keyToIndex[initialPane] : 0;

const fnExprs = Array(length);
for (let i = 0; i < length; ++i) {
const statements = [];
if (i != selectedPane)
processJsxElement(node.children[i], node, i, statements);
const fnExpr = statements.length
? statements.length == 1
? `() => ${statements[0]}`
: `() => {\n ${statements.join(";\n ")}\n}`
: "null";
fnExprs[i] = fnExpr;
}
props.keyToIndex = `${JSON.stringify(keyToIndex)}`;
props.children = `[${fnExprs.join(", ")}]`;
node.children[0] = node.children[selectedPane];
node.children.length = 1;
}
}
Expand Down Expand Up @@ -146,6 +184,8 @@ const transpile = (isEntry, file, content, domIdMapper, globals) => {
} else if (name == "instance") {
keepImport = true;
instance = content.slice(attr.value.start + 1, attr.value.end - 1);
} else if (parent.type == "JSXElement" && parent.openingElement.name.name == "KeyedSwitch" && name == "key") {
// Ignore key prop for KeyedSwitch
} else if (!name.endsWith("$"))
props[name] = attr.value;
else
Expand All @@ -169,7 +209,7 @@ const transpile = (isEntry, file, content, domIdMapper, globals) => {
? `{\n ${Object.entries(props).map(([k, v]) => `${k}: ${serialize(v)}`).join(",\n ")}\n }`
: "";
const call = `${tagName}(${callParams})`;
statements.push(instance ? `/** @const {${tagName}} */\n ${instance} = new ${call}` : call);
statements.push(instance ? `/** @const {!${tagName}} */\n ${instance} = new ${call}` : call);
}
if (keepImport && info)
info.state = SpecifierState.Keep;
Expand Down
1 change: 1 addition & 0 deletions util/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ const schedule = (f, ms) => (GEN && globalThis["GEN"]) ? {} : setTimeout(f, ms);
const run = (f) => (GEN && globalThis["GEN"]) ? {} : f();

export default {
GEN,
Lang,
// Elements
a,
Expand Down

0 comments on commit 4e28981

Please sign in to comment.