Skip to content

Commit

Permalink
publish article
Browse files Browse the repository at this point in the history
also rework context explanation on applications page to match the article
  • Loading branch information
jsebrech committed Oct 7, 2024
1 parent ddca1f4 commit f7f49d3
Show file tree
Hide file tree
Showing 25 changed files with 834 additions and 34 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
export class ContextProvider extends EventTarget {
#value;
get value() { return this.#value }
set value(v) { this.#value = v; this.dispatchEvent(new Event('change')); }

#context;
get context() { return this.#context }

constructor(target, context, initialValue = undefined) {
super();
this.#context = context;
this.#value = initialValue;
this.handle = this.handle.bind(this);
if (target) this.attach(target);
}

attach(target) {
target.addEventListener('context-request', this.handle);
}

detach(target) {
target.removeEventListener('context-request', this.handle);
}

/**
* Handle a context-request event
* @param {ContextRequestEvent} e
*/
handle(e) {
if (e.context === this.context) {
if (e.subscribe) {
const unsubscribe = () => this.removeEventListener('change', update);
const update = () => e.callback(this.value, unsubscribe);
this.addEventListener('change', update);
update();
} else {
e.callback(this.value);
}
e.stopPropagation();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export class ContextRequestEvent extends Event {
constructor(context, callback, subscribe) {
super('context-request', {
bubbles: true,
composed: true,
});
this.context = context;
this.callback = callback;
this.subscribe = subscribe;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
body {
margin: 1em;
font-family: system-ui, sans-serif;
}

theme-panel {
display: block;
border: 1px dotted gray;
min-height: 2em;
max-width: 400px;
padding: 1em;
margin-bottom: 1em;
}

.panel-light {
color: #222;
background: #fff;
}

.panel-dark {
color: #fff;
background: rgb(23, 32, 42);
}

theme-toggle button {
margin: 0;
padding: 5px;
}

.button-light,
.button-dark {
border: 1px solid #777;
}

.button-dark {
background: #222;
color: #fff;
}

.button-light {
background: #fff;
color: #222;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>tiny-context example</title>
<link rel="stylesheet" href="index.css">
</head>
<body>
<script type="module" src="index.js"></script>
<theme-demo></theme-demo>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { ContextRequestEvent } from "./context-request.js";
import "./theme-provider.js"; // global provider on body
import "./theme-context.js"; // element with local provider

customElements.define('theme-demo', class extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<theme-panel id="first">
<theme-toggle></theme-toggle>
</theme-panel>
<theme-context>
<theme-panel id="second">
</theme-panel>
</theme-context>
<button>Reparent toggle</button>
`;
this.querySelector('button').onclick = reparent;
}
});

customElements.define('theme-panel', class extends HTMLElement {
#unsubscribe;

connectedCallback() {
this.dispatchEvent(new ContextRequestEvent('theme', (theme, unsubscribe) => {
this.className = 'panel-' + theme;
this.#unsubscribe = unsubscribe;
}, true));
}

disconnectedCallback() {
this.#unsubscribe?.();
}
});

customElements.define('theme-toggle', class extends HTMLElement {
#unsubscribe;

connectedCallback() {
this.innerHTML = '<button>Toggle</button>';
this.dispatchEvent(new ContextRequestEvent('theme-toggle', (toggle) => {
this.querySelector('button').onclick = toggle;
}));
this.dispatchEvent(new ContextRequestEvent('theme', (theme, unsubscribe) => {
this.querySelector('button').className = 'button-' + theme;
this.#unsubscribe = unsubscribe;
}, true));
}

disconnectedCallback() {
this.#unsubscribe?.();
}
});

function reparent() {
const toggle = document.querySelector('theme-toggle');
const first = document.querySelector('theme-panel#first');
const second = document.querySelector('theme-panel#second');
if (toggle.parentNode === second) {
first.append(toggle);
} else {
second.append(toggle);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { ContextProvider } from "./context-provider.js";

customElements.define('theme-context', class extends HTMLElement {
themeProvider = new ContextProvider(this, 'theme', 'light');
toggleProvider = new ContextProvider(this, 'theme-toggle', () => {
this.themeProvider.value = this.themeProvider.value === 'light' ? 'dark' : 'light';
});
connectedCallback() {
this.style.display = 'contents';
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// loaded with <script type="module" src="theme-provider.js"></script>

import { ContextProvider } from "./context-provider.js";

const themeProvider = new ContextProvider(document.body, 'theme', 'light');
const toggleProvider = new ContextProvider(document.body, 'theme-toggle', () => {
themeProvider.value = themeProvider.value === 'light' ? 'dark' : 'light';
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
export class ContextProvider extends EventTarget {
#value;
get value() { return this.#value }
set value(v) { this.#value = v; this.dispatchEvent(new Event('change')); }

#context;
get context() { return this.#context }

constructor(target, context, initialValue = undefined) {
super();
this.#context = context;
this.#value = initialValue;
this.handle = this.handle.bind(this);
if (target) this.attach(target);
}

attach(target) {
target.addEventListener('context-request', this.handle);
}

detach(target) {
target.removeEventListener('context-request', this.handle);
}

/**
* Handle a context-request event
* @param {ContextRequestEvent} e
*/
handle(e) {
if (e.context === this.context) {
if (e.subscribe) {
const unsubscribe = () => this.removeEventListener('change', update);
const update = () => e.callback(this.value, unsubscribe);
this.addEventListener('change', update);
update();
} else {
e.callback(this.value);
}
e.stopPropagation();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
class ContextRequestEvent extends Event {
constructor(context, callback, subscribe) {
super('context-request', {
bubbles: true,
composed: true,
});
this.context = context;
this.callback = callback;
this.subscribe = subscribe;
}
}

customElements.define('my-component', class extends HTMLElement {
connectedCallback() {
this.dispatchEvent(
new ContextRequestEvent('theme', (theme) => {
// ...
})
);
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
customElements.define('my-component', class extends HTMLElement {
connectedCallback() {
let theme = 'light'; // default value
this.dispatchEvent(
new ContextRequestEvent('theme', t => theme = t)
);
// do something with theme
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
customElements.define('my-component', class extends HTMLElement {
#unsubscribe;
connectedCallback() {
this.dispatchEvent(
new ContextRequestEvent('theme', (theme, unsubscribe) => {
this.#unsubscribe = unsubscribe;
// do something with theme
}, true)
);
}
disconnectedCallback() {
this.#unsubscribe?.();
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
customElements.define('my-component', class extends HTMLElement {
connectedCallback() {
let theme = 'light';
this.dispatchEvent(
new ContextRequestEvent('theme', (t, unsubscribe) => {
theme = t;
unsubscribe?.();
})
);
// do something with theme
}
});
Binary file not shown.
Loading

0 comments on commit f7f49d3

Please sign in to comment.