Skip to content

Commit

Permalink
improved structure a bit, added useHostElement and useShadowRoot hook…
Browse files Browse the repository at this point in the history
…s and used slots in todo example
  • Loading branch information
michael-klein committed Jan 11, 2019
1 parent f35d19a commit 5e3c3a8
Show file tree
Hide file tree
Showing 7 changed files with 302 additions and 284 deletions.
7 changes: 6 additions & 1 deletion examples/todo.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<meta charset="UTF-8" />
</head>
<body>
<todo-list></todo-list>
<todo-list> <h1 slot="hello">Todo-List</h1> </todo-list>
<style>
html {
height: 100%;
Expand All @@ -19,6 +19,11 @@
margin: 0;
padding-top: 200px;
}
h1 {
color: dimgrey;
font-size: 2.4em;
text-align: center;
}
</style>
<script type="module" src="./todo.js"></script>
</body>
Expand Down
3 changes: 2 additions & 1 deletion examples/todo.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import {
defineComponent,
useExposeMethod,
useState,
usePreactHtm,
useCSS,
prps,
useAttribute
} from "../src/index.mjs";
import { usePreactHtm } from "../src/hooks/usePreactHtm.mjs";

defineComponent("todo-list", () => {
const css = useCSS;
Expand Down Expand Up @@ -59,6 +59,7 @@ defineComponent("todo-list", () => {
);
return html`
<div>
<slot name="hello"></slot>
<todo-header total=${total} done=${numDone}></todo-header>
<ul>
${
Expand Down
8 changes: 8 additions & 0 deletions src/hooks/usePreactHtm.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { createHook, useRenderer } from "../index.mjs";
import { html, render } from "https://unpkg.com/htm/preact/standalone.mjs";
export const usePreactHtm = createHook(() => {
useRenderer((view, shadowRoot) => {
render(view, shadowRoot);
});
return [html];
});
67 changes: 8 additions & 59 deletions src/index.mjs
Original file line number Diff line number Diff line change
@@ -1,65 +1,14 @@
import { queueRender, defaultRenderer, getPassableProps } from "./renderer.mjs";
export { prps } from "./renderer.mjs";
export { defineComponent } from "./lib/component.mjs";
export { prps } from "./lib/renderer.mjs";
export {
useReducer,
useState,
usePreactHtm,
useEffect,
useAttribute,
useCSS,
useExposeMethod
} from "./hooks.mjs";
const componentMap = new Map();
function addComponent(name, options) {
class Component extends HTMLElement {
constructor() {
super();
this.props = {};
this.renderer = defaultRenderer;
}
connectedCallback() {
if (!this._shadowRoot) {
this._shadowRoot = this.attachShadow({ mode: "open" });
queueRender(this);
}
}
render() {
const propsId = this.getAttribute("data-props");
if (propsId) {
this.props = getPassableProps(propsId);
this.skipQueue = true;
this.removeAttribute("data-props");
}
const view = componentMap.get(name)(this.props);
this.renderer(view, this._shadowRoot);
this.init = false;
}
attributeChangedCallback(attrName, oldVal, newVal) {
if (this.init) {
return;
}
if (!this.skipQueue && oldVal !== newVal) {
queueRender(this);
}
this.skipQueue = false;
}
static get observedAttributes() {
let observedAttributes = ["data-props"];
if (options.observedAttributes) {
observedAttributes = observedAttributes.concat(
options.observedAttributes
);
}
return observedAttributes;
}
}
customElements.define(name, Component);
}
export const defineComponent = (name, component, options = {}) => {
if (!componentMap.has(name)) {
componentMap.set(name, component);
addComponent(name, options);
} else {
console.warn(`Component ${name} was already defined.`);
}
};
useExposeMethod,
useRenderer,
useHostElement,
useShadowRoot,
createHook
} from "./lib/base_hooks.mjs";
243 changes: 121 additions & 122 deletions src/hooks.mjs → src/lib/base_hooks.mjs
Original file line number Diff line number Diff line change
@@ -1,122 +1,121 @@
import {
getCurrentElement,
getCurrentHookState,
queueRender,
queueAfterRender,
nextHook
} from "./renderer.mjs";
export const createHook = hook => (...args) => {
nextHook();
return hook(...args);
};
export const useReducer = createHook((reducer, initialState) => {
const hookState = getCurrentHookState({
reducer,
state: initialState
});
const element = getCurrentElement();
return [
hookState.state,
action => {
hookState.state = hookState.reducer(hookState.state, action);
queueRender(element);
}
];
});
export const useState = createHook(initialState => {
const [state, dispatch] = useReducer((_, action) => {
return action.value;
}, initialState);

return [
state,
newState =>
dispatch({
type: "set_state",
value: newState
})
];
});
export const useRenderer = createHook(rendererIn => {
const renderer = getCurrentHookState(rendererIn);
const element = getCurrentElement();
element.renderer = renderer;
});

import { html, render } from "https://unpkg.com/htm/preact/standalone.mjs";
export const usePreactHtm = createHook(() => {
useRenderer((view, shadowRoot) => {
render(view, shadowRoot);
});
return [html];
});
export const useEffect = createHook((effect, values) => {
const state = getCurrentHookState({
effect,
values,
cleanUp: () => {}
});
let nothingChanged = false;
if (state.values !== values && state.values && state.values.length > 0) {
nothingChanged = true;
let index = state.values.length;

while (index--) {
if (values[index] !== state.values[index]) {
nothingChanged = false;
break;
}
}
state.values = values;
}
if (!nothingChanged) {
state.cleanUp();
queueAfterRender(() => {
const cleanUp = state.effect();
if (cleanUp) {
state.cleanUp = cleanUp;
}
});
}
});
export const useAttribute = createHook(attributeName => {
const element = getCurrentElement();
const attributeValue = element.getAttribute(attributeName);
return [
attributeValue,
value => {
element.skipQueue = true;
element.setAttribute(attributeName, value);
}
];
});
export const useCSS = createHook((parts, ...slots) => {
let styles;
if (parts instanceof Array) {
styles = parts
.map((part, index) => {
if (slots[index]) {
return part + slots[index];
} else {
return part;
}
})
.join("");
} else {
styles = parts;
}
styles = styles.replace(/ +(?= )/g, "").replace(/\n/g, "");
const element = getCurrentElement();
const style = document.createElement("style");
style.innerHTML = styles;
useEffect(() => {
element._shadowRoot.appendChild(style);
return () => {
element._shadowRoot.removeChild(style);
};
});
});
export const useExposeMethod = createHook((name, method) => {
const element = getCurrentElement();
element[name] = (...args) => method(...args);
});
import {
getCurrentElement,
getCurrentHookState,
queueRender,
queueAfterRender,
nextHook
} from "./renderer.mjs";
export const createHook = hook => (...args) => {
nextHook();
return hook(...args);
};

export const useHostElement = createHook(() => {
return getCurrentElement();
});
export const useShadowRoot = createHook(() => {
return useHostElement()._shadowRoot;
});
export const useReducer = createHook((reducer, initialState) => {
const hookState = getCurrentHookState({
reducer,
state: initialState
});
const element = useHostElement();
return [
hookState.state,
action => {
hookState.state = hookState.reducer(hookState.state, action);
queueRender(element);
}
];
});
export const useState = createHook(initialState => {
const [state, dispatch] = useReducer((_, action) => {
return action.value;
}, initialState);

return [
state,
newState =>
dispatch({
type: "set_state",
value: newState
})
];
});
export const useRenderer = createHook(rendererIn => {
const renderer = getCurrentHookState(rendererIn);
const element = useHostElement();
element.renderer = renderer;
});
export const useEffect = createHook((effect, values) => {
const state = getCurrentHookState({
effect,
values,
cleanUp: () => {}
});
let nothingChanged = false;
if (state.values !== values && state.values && state.values.length > 0) {
nothingChanged = true;
let index = state.values.length;

while (index--) {
if (values[index] !== state.values[index]) {
nothingChanged = false;
break;
}
}
state.values = values;
}
if (!nothingChanged) {
state.cleanUp();
queueAfterRender(() => {
const cleanUp = state.effect();
if (cleanUp) {
state.cleanUp = cleanUp;
}
});
}
});
export const useAttribute = createHook(attributeName => {
const element = useHostElement();
const attributeValue = element.getAttribute(attributeName);
return [
attributeValue,
value => {
element.skipQueue = true;
element.setAttribute(attributeName, value);
}
];
});
export const useCSS = createHook((parts, ...slots) => {
let styles;
if (parts instanceof Array) {
styles = parts
.map((part, index) => {
if (slots[index]) {
return part + slots[index];
} else {
return part;
}
})
.join("");
} else {
styles = parts;
}
styles = styles.replace(/ +(?= )/g, "").replace(/\n/g, "");
const shadowRoot = useShadowRoot();
const style = document.createElement("style");
style.innerHTML = styles;
useEffect(() => {
shadowRoot.appendChild(style);
return () => {
shadowRoot.removeChild(style);
};
});
});
export const useExposeMethod = createHook((name, method) => {
const element = useHostElement();
element[name] = (...args) => method(...args);
});
Loading

0 comments on commit 5e3c3a8

Please sign in to comment.