Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
3a17389
feat: refactor getValue to static parseAttribute for JSX compilation
thescientist13 Feb 1, 2026
4c36114
feat: #232 refactor attributeChangedCallback and remove this.update
thescientist13 Feb 1, 2026
2084a43
feat: #232 remove inferredObservability instruction markers
thescientist13 Feb 1, 2026
eb3f3cc
feat: #232 local WCC override patches
thescientist13 Feb 7, 2026
6099a63
feat: #232 local WCC override patches
thescientist13 Feb 7, 2026
9b25cff
feat: #232 local WCC override patches
thescientist13 Feb 7, 2026
8d4a412
feat: #232 remove unused getters and hoist observed attributes tracki…
thescientist13 Feb 7, 2026
17d515a
feat: #232 update TODO comments
thescientist13 Feb 7, 2026
58861b2
feat: #232 prune computed signals from observed attributes and handle…
thescientist13 Feb 8, 2026
7cef2f0
feat: #232 transform template signals usage into effects and templates
thescientist13 Feb 16, 2026
21bbaf5
feat: #232 update comments
thescientist13 Feb 16, 2026
25cc9d0
feat: #232 move effects inlining to connectedCallback
thescientist13 Feb 16, 2026
9b92365
feat: #232 cache DOM elements referenced by effects
thescientist13 Feb 16, 2026
a1289dd
feat: #232 clean up DOM shim
thescientist13 Feb 16, 2026
f218762
feat: #232 trim templates
thescientist13 Feb 19, 2026
976693e
chore: fix lock file
thescientist13 Feb 19, 2026
2e3befc
feat: #232 refactor shadow root detection
thescientist13 Feb 21, 2026
ec654a3
feat: #232 handle reactive attributes and tracking reactivity across …
thescientist13 Feb 22, 2026
61f1f17
feat: #232 fix reactivity detection
thescientist13 Feb 22, 2026
139e02c
feat: #232 update component example use cases
thescientist13 Feb 22, 2026
7f7f19e
feat: #232 update component example use cases
thescientist13 Feb 22, 2026
f621287
feat: #232 support tracking multiple of the same top level tags
thescientist13 Feb 22, 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
7 changes: 7 additions & 0 deletions docs/components/sandbox/signal-counter.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.even {
color: green;
}

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

export const inferredObservability = true;

export default class SignalCounter extends HTMLElement {
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>
<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>
<span>{isLarge.get()}</span>
<p data-count={count.get()}>{isLarge.get()}</p>
</div>
);
}
}

customElements.define('sb-signal-counter-jsx', SignalCounter);
80 changes: 63 additions & 17 deletions docs/pages/sandbox.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
}
</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 +42,84 @@

<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.jsx"></script>
</head>

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

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

<sb-signal-counter-jsx count="0"></sb-signal-counter-jsx>
<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
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 +257,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