Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
26b760f
feat: refactor getValue to static parseAttribute for JSX compilation
thescientist13 Feb 1, 2026
9c79e63
feat: #232 refactor attributeChangedCallback and remove this.update
thescientist13 Feb 1, 2026
2f991d9
feat: #232 remove inferredObservability instruction markers
thescientist13 Feb 1, 2026
f1b66c4
feat: #232 local WCC override patches
thescientist13 Feb 7, 2026
9cf328d
feat: #232 local WCC override patches
thescientist13 Feb 7, 2026
6d63605
feat: #232 local WCC override patches
thescientist13 Feb 7, 2026
b7a7dc5
feat: #232 remove unused getters and hoist observed attributes tracki…
thescientist13 Feb 7, 2026
78491e5
feat: #232 update TODO comments
thescientist13 Feb 7, 2026
2ef55d0
feat: #232 prune computed signals from observed attributes and handle…
thescientist13 Feb 8, 2026
efd20aa
feat: #232 transform template signals usage into effects and templates
thescientist13 Feb 16, 2026
9c43a92
feat: #232 update comments
thescientist13 Feb 16, 2026
c65833a
feat: #232 move effects inlining to connectedCallback
thescientist13 Feb 16, 2026
36fa5b8
feat: #232 cache DOM elements referenced by effects
thescientist13 Feb 16, 2026
8a48ceb
feat: #232 clean up DOM shim
thescientist13 Feb 16, 2026
3da2294
feat: #232 trim templates
thescientist13 Feb 19, 2026
9627abf
chore: fix lock file
thescientist13 Feb 19, 2026
b0d4975
feat: #232 refactor shadow root detection
thescientist13 Feb 21, 2026
b18c7b6
feat: #232 handle reactive attributes and tracking reactivity across …
thescientist13 Feb 22, 2026
e4b7cde
feat: #232 fix reactivity detection
thescientist13 Feb 22, 2026
dbdd6eb
feat: #232 update component example use cases
thescientist13 Feb 22, 2026
662c21c
feat: #232 update component example use cases
thescientist13 Feb 22, 2026
a032c23
feat: #232 support tracking multiple of the same top level tags
thescientist13 Feb 22, 2026
8936351
feat: #232 correctly track reactive elements in order
thescientist13 Feb 23, 2026
74db496
feat: #232 style signal counter sandbox demo
thescientist13 Feb 25, 2026
536f5eb
feat: #232 add TODOs and refine comments
thescientist13 Feb 26, 2026
4fcd2ad
feat: #232 update comment around custom wcc tagged template
thescientist13 Feb 28, 2026
006dde9
feat: #232 typescript working
thescientist13 Feb 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions docs/components/sandbox/signal-counter.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
:host,
:root {
text-align: center;
}

.heading {
display: block;
text-decoration: underline;
}

.even {
color: green;
display: block;
}

.odd {
color: red;
display: block;
}
62 changes: 62 additions & 0 deletions docs/components/sandbox/signal-counter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import sheet from './signal-counter.css' with { type: 'css' };

export const inferredObservability = true;

export default class SignalCounter extends HTMLElement {
count;
parity;
isLarge;

constructor() {
super();
this.count = new Signal.State(0);
this.parity = new Signal.Computed(() => (this.count.get() % 2 === 0 ? 'even' : 'odd'));
this.isLarge = new Signal.Computed(() =>
this.count.get() >= 100 ? 'Wow!!!' : 'Keep Going...',
);
}

connectedCallback() {
if (!this.shadowRoot) {
this.attachShadow({
mode: 'open',
});
this.render();
}

this.shadowRoot.adoptedStyleSheets = [sheet];
}

increment() {
this.count.set(this.count.get() + 1);
}

decrement() {
this.count.set(this.count.get() - 1);
}

double() {
this.count.set(this.count.get() * 2);
}

render() {
const { count, parity, isLarge } = this;

return (
<div>
<span class="heading">My Signal Counter</span>
<button onclick={this.increment}>Increment (+)</button>
<button onclick={this.decrement}>Decrement (-)</button>
{/* TODO: inline version breaks with effects */}
{/* <button onclick={() => this.count.set(this.count.get() * 2)}>Double (++)</button> */}
<button onclick={this.double}>Double (++)</button>
<span class={parity.get()}>
The count is {count.get()} ({parity.get()})
</span>
<p data-count={count.get()}>({isLarge.get()})</p>
</div>
);
}
}

customElements.define('sb-signal-counter-jsx', SignalCounter);
90 changes: 73 additions & 17 deletions docs/pages/sandbox.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,15 @@
min-width: 5%;
margin: 0 auto;
}

sb-signal-counter-jsx {
display: block;
margin: 0 auto;
width: 50%;
}
</style>

<script type="module" src="../components/sandbox/card.js"></script>
<!-- <script type="module" src="../components/sandbox/card.js"></script>
<script type="module" src="../components/sandbox/card.jsx"></script>

<script type="module" src="../components/sandbox/counter-dsd.jsx"></script>
Expand All @@ -42,38 +48,88 @@

<script type="module" src="../components/sandbox/header.js"></script>
<script type="module" src="../components/sandbox/header.jsx"></script>
<script type="module" src="../components/sandbox/picture-frame.js"></script>
<script type="module" src="../components/sandbox/picture-frame.js"></script> -->
<script type="importmap">
{
"imports": {
"signal-polyfill": "../node_modules/signal-polyfill/dist/index.js",
"wc-compiler/effect": "../node_modules/wc-compiler/src/effect.js"
}
}
</script>
<script type="module">
import { Signal } from 'signal-polyfill';

globalThis.Signal = Signal;
</script>
<script type="module">
// NOTE: `Signal` has to be defined first
import { effect } from 'wc-compiler/effect';
globalThis.effect = effect;

globalThis._wcc = function (strings, ...values) {
let str = '';
strings.forEach((string, i) => {
str += string + (values[i] === 0 ? '0' : values[i] || '');
});
return str;
};
</script>
<script>
function randomReset() {
return Math.floor(Math.random() * 100);
}

globalThis.document.addEventListener('DOMContentLoaded', () => {
const counterJsxResetButton = document.getElementById('counter-jsx-reset');
const counterJsxDsdResetButton = document.getElementById('counter-jsx-dsd-reset');
const counterTsxDsdResetButton = document.getElementById('counter-tsx-dsd-reset');

counterJsxResetButton.addEventListener('click', () => {
document.querySelector('sb-counter-jsx').setAttribute('count', randomReset());
const signalCounterJsxResetButton = document.getElementById('signal-counter-reset');
// const counterJsxResetButton = document.getElementById('counter-jsx-reset');
// const counterJsxDsdResetButton = document.getElementById('counter-jsx-dsd-reset');
// const counterTsxDsdResetButton = document.getElementById('counter-tsx-dsd-reset');

signalCounterJsxResetButton.addEventListener('click', () => {
document.querySelectorAll('sb-signal-counter-jsx').forEach((el) => {
el.setAttribute('count', randomReset());
});
});

counterJsxDsdResetButton.addEventListener('click', () => {
document.querySelector('sb-counter-dsd-jsx').setAttribute('count', randomReset());
});
// counterJsxResetButton.addEventListener('click', () => {
// document.querySelector('sb-counter-jsx').setAttribute('count', randomReset());
// });

counterTsxDsdResetButton.addEventListener('click', () => {
document.querySelector('sb-counter-dsd-tsx').setAttribute('count', randomReset());
});
// counterJsxDsdResetButton.addEventListener('click', () => {
// document.querySelector('sb-counter-dsd-jsx').setAttribute('count', randomReset());
// });

// counterTsxDsdResetButton.addEventListener('click', () => {
// document.querySelector('sb-counter-dsd-tsx').setAttribute('count', randomReset());
// });
});
</script>

<!-- interactive scripts can be configured in sandbox.js -->
<script type="module" src="../components/sandbox/signal-counter.tsx"></script>
</head>

<body>
<h1>WCC Sandbox</h1>

<h2>Light DOM (no JS)</h2>
<h2>JSX + inferredObservability</h2>

<sb-signal-counter-jsx></sb-signal-counter-jsx>
<hr />
<sb-signal-counter-jsx count="3"></sb-signal-counter-jsx>

<button class="reset" id="signal-counter-reset">Random Reset</button>

<pre>
&lt;sb-signal-counter-jsx&gt;&lt;/sb-signal-counter-jsx&gt;
</pre>
<pre>
&lt;sb-signal-counter-jsx
count="3"
&gt;&lt;/sb-signal-counter-jsx&gt;
</pre>

<!-- <h2>Light DOM (no JS)</h2>

<sb-header></sb-header>

Expand Down Expand Up @@ -211,6 +267,6 @@ <h2>TSX + DSD + inferredObservability</h2>
&lt;sb-counter-dsd-tsx
count="3"
&gt;&lt;/sb-counter-dsd-tsx&gt;
</pre>
</pre> -->
</body>
</html>
28 changes: 27 additions & 1 deletion greenwood.config.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,33 @@
import type { Config } from '@greenwood/cli';
import type { Config, CopyPlugin } from '@greenwood/cli';
import { greenwoodPluginMarkdown } from '@greenwood/plugin-markdown';
import { greenwoodPluginImportJsx } from '@greenwood/plugin-import-jsx';
import { greenwoodPluginCssModules } from '@greenwood/plugin-css-modules';
import { greenwoodPluginImportRaw } from '@greenwood/plugin-import-raw';
import fs from 'node:fs';

// TODO: this does not run in dev :/
function copyEffectPlugin(): CopyPlugin {
console.log('herere???');
return {
type: 'copy',
name: 'plugin-copy-wcc-effect',
provider: async () => {
console.log('copy???');
return [
{
// copy a file
from: new URL('./src/effect.js', import.meta.url),
to: new URL('./node_modules/wc-compiler/src/effect.js', import.meta.url),
},
];
},
};
}

fs.copyFileSync(
new URL('./src/effect.js', import.meta.url),
new URL('./node_modules/wc-compiler/src/effect.js', import.meta.url),
);

const config: Config = {
activeContent: true,
Expand All @@ -12,6 +37,7 @@ const config: Config = {
importAttributes: ['css'],
},
plugins: [
// copyEffectPlugin(),
greenwoodPluginImportRaw(),
greenwoodPluginCssModules(),
greenwoodPluginImportJsx(),
Expand Down
Loading