From 84a66ba70924f7a534792910aaeace3f594ff1db Mon Sep 17 00:00:00 2001 From: Joe Pea Date: Wed, 31 Dec 2025 19:16:24 -0800 Subject: [PATCH 1/2] update examples --- README.md | 37 ++++----- dist/example.d.ts | 7 +- dist/example.d.ts.map | 2 +- dist/example.js | 178 +++++++++++++++++++++++++++++------------- dist/example.js.map | 2 +- example/index.html | 38 ++++++--- src/example.ts | 151 +++++++++++++++++++++++------------ 7 files changed, 269 insertions(+), 146 deletions(-) diff --git a/README.md b/README.md index 76d11e8..2cc1099 100644 --- a/README.md +++ b/README.md @@ -43,8 +43,8 @@ signals, and for using `class`es as Solid.js components. # At a glance ```jsx -import {createEffect, render, batch} from 'solid-js' -import {component, signal, memo} from 'classy-solid' +import {render, batch} from 'solid-js' +import {component, signal, memo, effect, stopEffects} from 'classy-solid' ////////////////////////////////////////////////// // Make plain classes reactive with Solid signals. @@ -53,10 +53,20 @@ class Counter { @signal count = 0 @signal num = 10 - @memo get sum() { + @memo get #sum() { return this.count + this.num } + @effect #logCount() { + // Log the count whenever it changes. + console.log(`Count is: ${this.count}`) + } + + @effect #logSum() { + // Log the sum whenever it changes. + console.log(`Sum is: ${this.#sum}`) + } + increment() { this.count++ } @@ -66,16 +76,6 @@ const counter = new Counter() setInterval(() => counter.increment(), 1000) -createEffect(() => { - // Log the count whenever it changes. - console.log(`Count is: ${counter.count}`) -}) - -createEffect(() => { - // Log the sum whenever it changes. - console.log(`Sum is: ${counter.sum}`) -}) - counter.count = 5 // Logs "Count is: 5" and "Sum is: 15" counter.num = 20 // Logs "Sum is: 25" @@ -87,6 +87,9 @@ batch(() => { counter.num = 15 }) +// ...later, clean up when done... +stopEffects(counter) + ////////////////////////////////////////////////// // Optionally use classes as Solid components. @@ -94,16 +97,14 @@ batch(() => { class MyComp { @signal message = 'Hello, World!' - template(props) { - setTimeout(() => { - this.message = 'Hello after 3 seconds!' - }, 3000) + template() { + setTimeout(() => (this.message = 'Hello after 3 seconds!'), 3000) return (

{this.message}

-

My name is {props.name}.

+

My name is {this.name}.

The count is: {counter.count}

diff --git a/dist/example.d.ts b/dist/example.d.ts index 9c90e13..71d77e6 100644 --- a/dist/example.d.ts +++ b/dist/example.d.ts @@ -1,7 +1,2 @@ -declare class Foo { - foo: number; - get lorem(): number; - set lorem(v: number); -} -export { Foo }; +export {}; //# sourceMappingURL=example.d.ts.map \ No newline at end of file diff --git a/dist/example.d.ts.map b/dist/example.d.ts.map index d02012b..e16b0e4 100644 --- a/dist/example.d.ts.map +++ b/dist/example.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"example.d.ts","sourceRoot":"","sources":["../src/example.ts"],"names":[],"mappings":"AAGA,cAAM,GAAG;IACA,GAAG,SAAM;IAEjB,IAAY,KAAK,WAEhB;IACD,IAAY,KAAK,CAAC,CAAC,QAAA,EAElB;CACD;AAoCD,OAAO,EAAC,GAAG,EAAC,CAAA"} \ No newline at end of file +{"version":3,"file":"example.d.ts","sourceRoot":"","sources":["../src/example.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/example.js b/dist/example.js index 9f19fef..fb55141 100644 --- a/dist/example.js +++ b/dist/example.js @@ -1,81 +1,149 @@ -let _initProto, _init_foo, _init_extra_foo, _initProto2, _init_bar, _init_extra_bar; +let _initProto, _init_count, _init_extra_count, _call_logCount, _initProto2, _init_num, _init_extra_num, _call_sum, _call_logSum, _initClass, _init_message, _init_extra_message, _init_name, _init_extra_name, _initProto3, _init_count2, _init_extra_count2, _call_logCount2, _call_interval; function _applyDecs(e, t, n, r, o, i) { var a, c, u, s, f, l, p, d = Symbol.metadata || Symbol.for("Symbol.metadata"), m = Object.defineProperty, h = Object.create, y = [h(null), h(null)], v = t.length; function g(t, n, r) { return function (o, i) { n && (i = o, o = e); for (var a = 0; a < t.length; a++) i = t[a].apply(o, r ? [i] : []); return r ? i : o; }; } function b(e, t, n, r) { if ("function" != typeof e && (r || void 0 !== e)) throw new TypeError(t + " must " + (n || "be") + " a function" + (r ? "" : " or undefined")); return e; } function applyDec(e, t, n, r, o, i, u, s, f, l, p) { function d(e) { if (!p(e)) throw new TypeError("Attempted to access private element on non-instance"); } var h = [].concat(t[0]), v = t[3], w = !u, D = 1 === o, S = 3 === o, j = 4 === o, E = 2 === o; function I(t, n, r) { return function (o, i) { return n && (i = o, o = e), r && r(o), P[t].call(o, i); }; } if (!w) { var P = {}, k = [], F = S ? "get" : j || D ? "set" : "value"; if (f ? (l || D ? P = { get: _setFunctionName(function () { return v(this); }, r, "get"), set: function (e) { t[4](this, e); } } : P[F] = v, l || _setFunctionName(P[F], r, E ? "" : F)) : l || (P = Object.getOwnPropertyDescriptor(e, r)), !l && !f) { if ((c = y[+s][r]) && 7 !== (c ^ o)) throw Error("Decorating two elements with the same name (" + P[F].name + ") is not supported yet"); y[+s][r] = o < 3 ? 1 : o; } } for (var N = e, O = h.length - 1; O >= 0; O -= n ? 2 : 1) { var T = b(h[O], "A decorator", "be", !0), z = n ? h[O - 1] : void 0, A = {}, H = { kind: ["field", "accessor", "method", "getter", "setter", "class"][o], name: r, metadata: a, addInitializer: function (e, t) { if (e.v) throw new TypeError("attempted to call addInitializer after decoration was finished"); b(t, "An initializer", "be", !0), i.push(t); }.bind(null, A) }; if (w) c = T.call(z, N, H), A.v = 1, b(c, "class decorators", "return") && (N = c);else if (H.static = s, H.private = f, c = H.access = { has: f ? p.bind() : function (e) { return r in e; } }, j || (c.get = f ? E ? function (e) { return d(e), P.value; } : I("get", 0, d) : function (e) { return e[r]; }), E || S || (c.set = f ? I("set", 0, d) : function (e, t) { e[r] = t; }), N = T.call(z, D ? { get: P.get, set: P.set } : P[F], H), A.v = 1, D) { if ("object" == typeof N && N) (c = b(N.get, "accessor.get")) && (P.get = c), (c = b(N.set, "accessor.set")) && (P.set = c), (c = b(N.init, "accessor.init")) && k.unshift(c);else if (void 0 !== N) throw new TypeError("accessor decorators must return an object with get, set, or init properties or undefined"); } else b(N, (l ? "field" : "method") + " decorators", "return") && (l ? k.unshift(N) : P[F] = N); } return o < 2 && u.push(g(k, s, 1), g(i, s, 0)), l || w || (f ? D ? u.splice(-1, 0, I("get", s), I("set", s)) : u.push(E ? P[F] : b.call.bind(P[F])) : m(e, r, P)), N; } function w(e) { return m(e, d, { configurable: !0, enumerable: !0, value: a }); } return void 0 !== i && (a = i[d]), a = h(null == a ? null : a), f = [], l = function (e) { e && f.push(g(e)); }, p = function (t, r) { for (var i = 0; i < n.length; i++) { var a = n[i], c = a[1], l = 7 & c; if ((8 & c) == t && !l == r) { var p = a[2], d = !!a[3], m = 16 & c; applyDec(t ? e : e.prototype, a, m, d ? "#" + p : _toPropertyKey(p), l, l < 2 ? [] : t ? s = s || [] : u = u || [], f, !!t, d, r, t && d ? function (t) { return _checkInRHS(t) === e; } : o); } } }, p(8, 0), p(0, 0), p(8, 1), p(0, 1), l(u), l(s), c = f, v || w(e), { e: c, get c() { var n = []; return v && [w(e = applyDec(e, [t], r, e.name, 5, n)), g(n, 1)]; } }; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } function _setFunctionName(e, t, n) { "symbol" == typeof t && (t = (t = t.description) ? "[" + t + "]" : ""); try { Object.defineProperty(e, "name", { configurable: !0, value: n ? n + " " + t : t }); } catch (e) {} return e; } function _checkInRHS(e) { if (Object(e) !== e) throw TypeError("right-hand side of 'in' should be an object, got " + (null !== e ? typeof e : "null")); return e; } -import { signal, effect } from './index.js'; -import { createEffect } from 'solid-js'; -class Foo { +import { signal, effect, memo, stopEffects, component, startEffects } from './index.js'; +import { render } from 'solid-js/web'; +import html from 'solid-js/html'; +import { onCleanup } from 'solid-js'; + +////////////////////////////////////////////////// +// Make plain classes reactive with Solid signals. + +class Counter { static { - [_init_foo, _init_extra_foo, _initProto] = _applyDecs(this, [], [[signal, 0, "foo"], [signal, 3, "lorem"], [signal, 4, "lorem"]]).e; + [_call_logCount, _init_count, _init_extra_count, _initProto] = _applyDecs(this, [], [[signal, 0, "count"], [effect, 2, "logCount", function () { + // Log the count whenever it changes. + console.log(`Count is: ${this.count}`); + }]], 0, _ => #logCount in _).e; } constructor() { - _init_extra_foo(this); + _init_extra_count(this); } - foo = (_initProto(this), _init_foo(this, 123)); - get lorem() { - return 123; - } - set lorem(v) { - v; + #logCount = _call_logCount; + count = (_initProto(this), _init_count(this, 0)); + + // @ts-expect-error not unused + + increment() { + this.count++; } } -class Bar extends Foo { +class Example extends Counter { static { - [_init_bar, _init_extra_bar, _initProto2] = _applyDecs(this, [], [[signal, 0, "bar"], [signal, 3, "baz"], [signal, 4, "baz"], [effect, 2, "logFoo"], [effect, 2, "logLorem"], [effect, 2, "logBar"], [effect, 2, "logBaz"]], 0, void 0, Foo).e; + [_call_sum, _call_logSum, _init_num, _init_extra_num, _initProto2] = _applyDecs(this, [], [[signal, 0, "num"], [memo, 3, "sum", function () { + return this.count + this.num; + }], [effect, 2, "logSum", function () { + // Log the sum whenever it changes. + console.log(`Sum is: ${this.#sum}`); + }]], 0, _ => #sum in _, Counter).e; } constructor(...args) { super(...args); - _init_extra_bar(this); + _init_extra_num(this); } - // This causes a TDZ error if it comes after bar. See "TDZ" example in signal.test.ts. - // Spec ordering issue: https://github.com/tc39/proposal-decorators/issues/571 - #baz = (_initProto2(this), 789); - bar = _init_bar(this, 456); + #logSum = _call_logSum; + num = (_initProto2(this), _init_num(this, 10)); + get #sum() { + return _call_sum(this); + } // @ts-expect-error not unused +} +const ex = new Example(); // starts effects, logs "Count is: 0", "Sum is: 10" + +ex.count = 5; // Logs "Count is: 5", "Sum is: 15" +ex.num = 20; // Logs "Sum is: 25" - // This would cause a TDZ error. - // #baz = 789 +setInterval(() => ex.increment(), 1000); - get baz() { - return this.#baz; +// ...later, clean up when done... +setTimeout(() => stopEffects(ex), 3000); + +////////////////////////////////////////////////// +// Optionally use classes as Solid components. +let _MyComp; +class MyComp { + static { + ({ + e: [_init_message, _init_extra_message, _init_name, _init_extra_name], + c: [_MyComp, _initClass] + } = _applyDecs(this, [component], [[signal, 0, "message"], [signal, 0, "name"]])); } - set baz(v) { - this.#baz = v; + constructor() { + _init_extra_name(this); } - logFoo() { - console.log('this.foo:', this.foo); + message = _init_message(this, 'Hello, World!'); + name = (_init_extra_message(this), _init_name(this, 'Tom')); + template() { + setTimeout(() => this.message = 'Hello after 3 seconds!', 3000); + return html` +
+

${() => this.message}

+ +

My name is ${() => this.name}.

+ +

The count is: ${() => ex.count}

+
+ `; } - logLorem() { - console.log('this.lorem:', this.lorem); + static { + _initClass(); } - logBar() { - console.log('this.bar:', this.bar); +} +render( +// prettier-ignore +() => html` + <${_MyComp} name="Joe"> + +

(Also see console output.)


+ `, document.body); + +////////////////////////////////////////////////// +// Use reactivity with Custom Elements. +// For an additional set of utilities for concisely defining Custom Elements, +// see the @lume/element package built on top of classy-solid. + +class ElementWithEffects extends HTMLElement { + connectedCallback() { + startEffects(this); } - logBaz() { - console.log('this.baz:', this.baz); + disconnectedCallback() { + stopEffects(this); } } -export { Foo }; -console.log('---------'); -const b = new Bar(); -createEffect(() => { - console.log('b.foo:', b.foo); -}); -createEffect(() => { - console.log('b.lorem:', b.lorem); -}); -createEffect(() => { - console.log('b.bar:', b.bar); -}); -createEffect(() => { - console.log('b.baz:', b.baz); -}); -setInterval(() => { - console.log('---'); - b.foo++; - b.bar++; - b.baz++; - b.lorem++; -}, 1000); +class MyElement extends ElementWithEffects { + static { + [_call_logCount2, _call_interval, _init_count2, _init_extra_count2, _initProto3] = _applyDecs(this, [], [[signal, 0, "count"], [effect, 2, "logCount", function () { + // Show the count whenever it changes. + this.#root.textContent = `<${this.tagName.toLowerCase()}> Count is: ${this.count}`; + }], [effect, 2, "interval", function () { + const int = setInterval(() => this.count++, 1000); + onCleanup(() => clearInterval(int)); + }]], 0, _ => #root in _, ElementWithEffects).e; + } + constructor(...args) { + super(...args); + _init_extra_count2(this); + } + #interval = _call_interval; + #logCount = _call_logCount2; + #root = (_initProto3(this), this.attachShadow({ + mode: 'open' + })); + count = _init_count2(this, 0); + + // @ts-expect-error not unused + + // @ts-expect-error not unused +} +customElements.define('my-element', MyElement); +const el = document.createElement('my-element'); +document.body.append(el); + +// ...Later, remove the element to stop its effects... +setTimeout(() => el.remove(), 3000); + +// ...Later, add the element back to see effects restart... +setTimeout(() => document.body.append(el), 6000); //# sourceMappingURL=example.js.map \ No newline at end of file diff --git a/dist/example.js.map b/dist/example.js.map index a555ffd..1cbd6b8 100644 --- a/dist/example.js.map +++ b/dist/example.js.map @@ -1 +1 @@ -{"version":3,"file":"example.js","names":["signal","effect","createEffect","Foo","_init_foo","_init_extra_foo","_initProto","_applyDecs","e","constructor","foo","lorem","v","Bar","_init_bar","_init_extra_bar","_initProto2","args","baz","bar","logFoo","console","log","logLorem","logBar","logBaz","b","setInterval"],"sources":["../src/example.ts"],"sourcesContent":["import {signal, effect} from './index.js'\nimport {createEffect} from 'solid-js'\n\nclass Foo {\n\t@signal foo = 123\n\n\t@signal get lorem() {\n\t\treturn 123\n\t}\n\t@signal set lorem(v) {\n\t\tv\n\t}\n}\n\nclass Bar extends Foo {\n\t// This causes a TDZ error if it comes after bar. See \"TDZ\" example in signal.test.ts.\n\t// Spec ordering issue: https://github.com/tc39/proposal-decorators/issues/571\n\t#baz = 789\n\n\t@signal bar = 456\n\n\t// This would cause a TDZ error.\n\t// #baz = 789\n\n\t@signal get baz() {\n\t\treturn this.#baz\n\t}\n\t@signal set baz(v) {\n\t\tthis.#baz = v\n\t}\n\n\t@effect logFoo() {\n\t\tconsole.log('this.foo:', this.foo)\n\t}\n\n\t@effect logLorem() {\n\t\tconsole.log('this.lorem:', this.lorem)\n\t}\n\n\t@effect logBar() {\n\t\tconsole.log('this.bar:', this.bar)\n\t}\n\n\t@effect logBaz() {\n\t\tconsole.log('this.baz:', this.baz)\n\t}\n}\n\nexport {Foo}\n\nconsole.log('---------')\nconst b = new Bar()\n\ncreateEffect(() => {\n\tconsole.log('b.foo:', b.foo)\n})\n\ncreateEffect(() => {\n\tconsole.log('b.lorem:', b.lorem)\n})\n\ncreateEffect(() => {\n\tconsole.log('b.bar:', b.bar)\n})\n\ncreateEffect(() => {\n\tconsole.log('b.baz:', b.baz)\n})\n\nsetInterval(() => {\n\tconsole.log('---')\n\tb.foo++\n\tb.bar++\n\tb.baz++\n\tb.lorem++\n}, 1000)\n"],"mappings":";;;;;;AAAA,SAAQA,MAAM,EAAEC,MAAM,QAAO,YAAY;AACzC,SAAQC,YAAY,QAAO,UAAU;AAErC,MAAMC,GAAG,CAAC;EAAA;IAAA,CAAAC,SAAA,EAAAC,eAAA,EAAAC,UAAA,IAAAC,UAAA,aACRP,MAAM,cAENA,MAAM,gBAGNA,MAAM,gBAAAQ,CAAA;EAAA;EAAAC,YAAA;IAAAJ,eAAA;EAAA;EALCK,GAAG,IAAAJ,UAAA,QAAAF,SAAA,OAAG,GAAG;EAEjB,IAAYO,KAAKA,CAAA,EAAG;IACnB,OAAO,GAAG;EACX;EACA,IAAYA,KAAKA,CAACC,CAAC,EAAE;IACpBA,CAAC;EACF;AACD;AAEA,MAAMC,GAAG,SAASV,GAAG,CAAC;EAAA;IAAA,CAAAW,SAAA,EAAAC,eAAA,EAAAC,WAAA,IAAAT,UAAA,aAKpBP,MAAM,cAKNA,MAAM,cAGNA,MAAM,cAINC,MAAM,iBAINA,MAAM,mBAINA,MAAM,iBAINA,MAAM,4BA7BUE,GAAG,EAAAK,CAAA;EAAA;EAAAC,YAAA,GAAAQ,IAAA;IAAA,SAAAA,IAAA;IAAAF,eAAA;EAAA;EACpB;EACA;EACA,CAACG,GAAG,IAAAF,WAAA,QAAG,GAAG;EAEFG,GAAG,GAAAL,SAAA,OAAG,GAAG;;EAEjB;EACA;;EAEA,IAAYI,GAAGA,CAAA,EAAG;IACjB,OAAO,IAAI,CAAC,CAACA,GAAG;EACjB;EACA,IAAYA,GAAGA,CAACN,CAAC,EAAE;IAClB,IAAI,CAAC,CAACM,GAAG,GAAGN,CAAC;EACd;EAEQQ,MAAMA,CAAA,EAAG;IAChBC,OAAO,CAACC,GAAG,CAAC,WAAW,EAAE,IAAI,CAACZ,GAAG,CAAC;EACnC;EAEQa,QAAQA,CAAA,EAAG;IAClBF,OAAO,CAACC,GAAG,CAAC,aAAa,EAAE,IAAI,CAACX,KAAK,CAAC;EACvC;EAEQa,MAAMA,CAAA,EAAG;IAChBH,OAAO,CAACC,GAAG,CAAC,WAAW,EAAE,IAAI,CAACH,GAAG,CAAC;EACnC;EAEQM,MAAMA,CAAA,EAAG;IAChBJ,OAAO,CAACC,GAAG,CAAC,WAAW,EAAE,IAAI,CAACJ,GAAG,CAAC;EACnC;AACD;AAEA,SAAQf,GAAG;AAEXkB,OAAO,CAACC,GAAG,CAAC,WAAW,CAAC;AACxB,MAAMI,CAAC,GAAG,IAAIb,GAAG,CAAC,CAAC;AAEnBX,YAAY,CAAC,MAAM;EAClBmB,OAAO,CAACC,GAAG,CAAC,QAAQ,EAAEI,CAAC,CAAChB,GAAG,CAAC;AAC7B,CAAC,CAAC;AAEFR,YAAY,CAAC,MAAM;EAClBmB,OAAO,CAACC,GAAG,CAAC,UAAU,EAAEI,CAAC,CAACf,KAAK,CAAC;AACjC,CAAC,CAAC;AAEFT,YAAY,CAAC,MAAM;EAClBmB,OAAO,CAACC,GAAG,CAAC,QAAQ,EAAEI,CAAC,CAACP,GAAG,CAAC;AAC7B,CAAC,CAAC;AAEFjB,YAAY,CAAC,MAAM;EAClBmB,OAAO,CAACC,GAAG,CAAC,QAAQ,EAAEI,CAAC,CAACR,GAAG,CAAC;AAC7B,CAAC,CAAC;AAEFS,WAAW,CAAC,MAAM;EACjBN,OAAO,CAACC,GAAG,CAAC,KAAK,CAAC;EAClBI,CAAC,CAAChB,GAAG,EAAE;EACPgB,CAAC,CAACP,GAAG,EAAE;EACPO,CAAC,CAACR,GAAG,EAAE;EACPQ,CAAC,CAACf,KAAK,EAAE;AACV,CAAC,EAAE,IAAI,CAAC","ignoreList":[]} \ No newline at end of file +{"version":3,"file":"example.js","names":["signal","effect","memo","stopEffects","component","startEffects","render","html","onCleanup","Counter","_call_logCount","_init_count","_init_extra_count","_initProto","_applyDecs","console","log","count","_","logCount","e","constructor","increment","Example","_call_sum","_call_logSum","_init_num","_init_extra_num","_initProto2","num","sum","args","logSum","#sum","ex","setInterval","setTimeout","_MyComp","MyComp","_init_message","_init_extra_message","_init_name","_init_extra_name","c","_initClass","message","name","template","document","body","ElementWithEffects","HTMLElement","connectedCallback","disconnectedCallback","MyElement","_call_logCount2","_call_interval","_init_count2","_init_extra_count2","_initProto3","root","textContent","tagName","toLowerCase","int","clearInterval","interval","attachShadow","mode","customElements","define","el","createElement","append","remove"],"sources":["../src/example.ts"],"sourcesContent":["import {signal, effect, memo, stopEffects, component, startEffects} from './index.js'\nimport {render} from 'solid-js/web'\nimport html from 'solid-js/html'\nimport {onCleanup} from 'solid-js'\n\n//////////////////////////////////////////////////\n// Make plain classes reactive with Solid signals.\n\nclass Counter {\n\t@signal count = 0\n\n\t// @ts-expect-error not unused\n\t@effect #logCount() {\n\t\t// Log the count whenever it changes.\n\t\tconsole.log(`Count is: ${this.count}`)\n\t}\n\n\tincrement() {\n\t\tthis.count++\n\t}\n}\n\nclass Example extends Counter {\n\t@signal num = 10\n\n\t@memo get #sum() {\n\t\treturn this.count + this.num\n\t}\n\n\t// @ts-expect-error not unused\n\t@effect #logSum() {\n\t\t// Log the sum whenever it changes.\n\t\tconsole.log(`Sum is: ${this.#sum}`)\n\t}\n}\n\nconst ex = new Example() // starts effects, logs \"Count is: 0\", \"Sum is: 10\"\n\nex.count = 5 // Logs \"Count is: 5\", \"Sum is: 15\"\nex.num = 20 // Logs \"Sum is: 25\"\n\nsetInterval(() => ex.increment(), 1000)\n\n// ...later, clean up when done...\nsetTimeout(() => stopEffects(ex), 3000)\n\n//////////////////////////////////////////////////\n// Optionally use classes as Solid components.\n\n@component\nclass MyComp {\n\t@signal message = 'Hello, World!'\n\t@signal name = 'Tom'\n\n\ttemplate() {\n\t\tsetTimeout(() => (this.message = 'Hello after 3 seconds!'), 3000)\n\n\t\treturn html`\n\t\t\t
\n\t\t\t\t

${() => this.message}

\n\n\t\t\t\t

My name is ${() => this.name}.

\n\n\t\t\t\t

The count is: ${() => ex.count}

\n\t\t\t
\n\t\t`\n\t}\n}\n\nrender(\n\t// prettier-ignore\n\t() => html`\n\t\t<${MyComp} name=\"Joe\">\n\n\t\t

(Also see console output.)


\n\t`,\n\tdocument.body,\n)\n\n//////////////////////////////////////////////////\n// Use reactivity with Custom Elements.\n// For an additional set of utilities for concisely defining Custom Elements,\n// see the @lume/element package built on top of classy-solid.\n\nclass ElementWithEffects extends HTMLElement {\n\tconnectedCallback() {\n\t\tstartEffects(this)\n\t}\n\n\tdisconnectedCallback() {\n\t\tstopEffects(this)\n\t}\n}\n\nclass MyElement extends ElementWithEffects {\n\t#root = this.attachShadow({mode: 'open'})\n\n\t@signal count = 0\n\n\t// @ts-expect-error not unused\n\t@effect #logCount() {\n\t\t// Show the count whenever it changes.\n\t\tthis.#root.textContent = `<${this.tagName.toLowerCase()}> Count is: ${this.count}`\n\t}\n\n\t// @ts-expect-error not unused\n\t@effect #interval() {\n\t\tconst int = setInterval(() => this.count++, 1000)\n\t\tonCleanup(() => clearInterval(int))\n\t}\n}\n\ncustomElements.define('my-element', MyElement)\n\nconst el = document.createElement('my-element')\n\ndocument.body.append(el)\n\n// ...Later, remove the element to stop its effects...\nsetTimeout(() => el.remove(), 3000)\n\n// ...Later, add the element back to see effects restart...\nsetTimeout(() => document.body.append(el), 6000)\n"],"mappings":"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","ignoreList":[]} \ No newline at end of file diff --git a/example/index.html b/example/index.html index 969b93a..1f26b9e 100644 --- a/example/index.html +++ b/example/index.html @@ -1,17 +1,29 @@ - - -

See console

+ diff --git a/src/example.ts b/src/example.ts index 09f8bca..7378007 100644 --- a/src/example.ts +++ b/src/example.ts @@ -1,76 +1,123 @@ -import {signal, effect} from './index.js' -import {createEffect} from 'solid-js' +import {signal, effect, memo, stopEffects, component, startEffects} from './index.js' +import {render} from 'solid-js/web' +import html from 'solid-js/html' +import {onCleanup} from 'solid-js' -class Foo { - @signal foo = 123 +////////////////////////////////////////////////// +// Make plain classes reactive with Solid signals. - @signal get lorem() { - return 123 +class Counter { + @signal count = 0 + + // @ts-expect-error not unused + @effect #logCount() { + // Log the count whenever it changes. + console.log(`Count is: ${this.count}`) } - @signal set lorem(v) { - v + + increment() { + this.count++ } } -class Bar extends Foo { - // This causes a TDZ error if it comes after bar. See "TDZ" example in signal.test.ts. - // Spec ordering issue: https://github.com/tc39/proposal-decorators/issues/571 - #baz = 789 - - @signal bar = 456 +class Example extends Counter { + @signal num = 10 - // This would cause a TDZ error. - // #baz = 789 - - @signal get baz() { - return this.#baz - } - @signal set baz(v) { - this.#baz = v + @memo get #sum() { + return this.count + this.num } - @effect logFoo() { - console.log('this.foo:', this.foo) + // @ts-expect-error not unused + @effect #logSum() { + // Log the sum whenever it changes. + console.log(`Sum is: ${this.#sum}`) } +} + +const ex = new Example() // starts effects, logs "Count is: 0", "Sum is: 10" + +ex.count = 5 // Logs "Count is: 5", "Sum is: 15" +ex.num = 20 // Logs "Sum is: 25" + +setInterval(() => ex.increment(), 1000) + +// ...later, clean up when done... +setTimeout(() => stopEffects(ex), 3000) + +////////////////////////////////////////////////// +// Optionally use classes as Solid components. + +@component +class MyComp { + @signal message = 'Hello, World!' + @signal name = 'Tom' - @effect logLorem() { - console.log('this.lorem:', this.lorem) + template() { + setTimeout(() => (this.message = 'Hello after 3 seconds!'), 3000) + + return html` +
+

${() => this.message}

+ +

My name is ${() => this.name}.

+ +

The count is: ${() => ex.count}

+
+ ` } +} + +render( + // prettier-ignore + () => html` + <${MyComp} name="Joe"> + +

(Also see console output.)


+ `, + document.body, +) - @effect logBar() { - console.log('this.bar:', this.bar) +////////////////////////////////////////////////// +// Use reactivity with Custom Elements. +// For an additional set of utilities for concisely defining Custom Elements, +// see the @lume/element package built on top of classy-solid. + +class ElementWithEffects extends HTMLElement { + connectedCallback() { + startEffects(this) } - @effect logBaz() { - console.log('this.baz:', this.baz) + disconnectedCallback() { + stopEffects(this) } } -export {Foo} +class MyElement extends ElementWithEffects { + #root = this.attachShadow({mode: 'open'}) + + @signal count = 0 + + // @ts-expect-error not unused + @effect #logCount() { + // Show the count whenever it changes. + this.#root.textContent = `<${this.tagName.toLowerCase()}> Count is: ${this.count}` + } -console.log('---------') -const b = new Bar() + // @ts-expect-error not unused + @effect #interval() { + const int = setInterval(() => this.count++, 1000) + onCleanup(() => clearInterval(int)) + } +} -createEffect(() => { - console.log('b.foo:', b.foo) -}) +customElements.define('my-element', MyElement) -createEffect(() => { - console.log('b.lorem:', b.lorem) -}) +const el = document.createElement('my-element') -createEffect(() => { - console.log('b.bar:', b.bar) -}) +document.body.append(el) -createEffect(() => { - console.log('b.baz:', b.baz) -}) +// ...Later, remove the element to stop its effects... +setTimeout(() => el.remove(), 3000) -setInterval(() => { - console.log('---') - b.foo++ - b.bar++ - b.baz++ - b.lorem++ -}, 1000) +// ...Later, add the element back to see effects restart... +setTimeout(() => document.body.append(el), 6000) From 3c885403fdf8b77d86aaa60e6e61ea19da064302 Mon Sep 17 00:00:00 2001 From: Joe Pea Date: Wed, 31 Dec 2025 20:31:09 -0800 Subject: [PATCH 2/2] link to live example and source code --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 2cc1099..84a91d4 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,7 @@ stopEffects(counter) @component class MyComp { @signal message = 'Hello, World!' + @signal name = 'Asa' template() { setTimeout(() => (this.message = 'Hello after 3 seconds!'), 3000) @@ -115,6 +116,8 @@ class MyComp { render(() => , document.body) ``` +See the [live example](https://rawcdn.githack.com/lume/classy-solid/84a66ba70924f7a534792910aaeace3f594ff1db/example/index.html) and its [source code](https://github.com/lume/classy-solid/blob/84a66ba70924f7a534792910aaeace3f594ff1db/src/example.ts). + # Install #### `npm install classy-solid`