mono-jsx-dom 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -5
- package/index.mjs +23 -1
- package/jsx-runtime.mjs +73 -71
- package/package.json +1 -1
- package/types/index.d.ts +6 -1
package/README.md
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
- 🚦 Signals as reactive primitives
|
|
13
13
|
- 💡 Complete Web API TypeScript definitions
|
|
14
14
|
- ⏳ Streaming rendering
|
|
15
|
+
- 🎨 Builtin [TailwindCSS](https://tailwindcss.com/) integration
|
|
15
16
|
- 🔩 Builtin dev/build/deploy toolchain
|
|
16
17
|
|
|
17
18
|
Playground: https://val.town/x/ije/mono-jsx-dom
|
|
@@ -35,13 +36,32 @@ mono-jsx-dom adds a `mount` method to the `HTMLElement` prototype to allow you t
|
|
|
35
36
|
```tsx
|
|
36
37
|
// app.tsx
|
|
37
38
|
|
|
38
|
-
function App(this: FC) {
|
|
39
|
-
|
|
39
|
+
async function App(this: FC<{ word: string }>) {
|
|
40
|
+
this.word = await fetch("/data/word").then(res => res.text());
|
|
41
|
+
return <div>Hello, {this.word}!</div>;
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
document.body.mount(<App />);
|
|
43
45
|
```
|
|
44
46
|
|
|
47
|
+
You can also define [custom elements](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements) with the `defineComponent` function:
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
import { defineComponent } from "mono-jsx-dom";
|
|
51
|
+
|
|
52
|
+
function App(this: FC<{ word: string }>) {
|
|
53
|
+
return <div>Hello, {this.word}!</div>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
defineComponent("my-app", App);
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Then you can use the `<my-app>` element in your HTML.
|
|
60
|
+
|
|
61
|
+
```html
|
|
62
|
+
<my-app word="world"></my-app>
|
|
63
|
+
```
|
|
64
|
+
|
|
45
65
|
>[!TIP]
|
|
46
66
|
> `mono-jsx-dom` is designed for client-side rendering. You can use [mono-jsx](https://github.com/ije/mono-jsx) to render the UI on the server side.
|
|
47
67
|
|
|
@@ -645,7 +665,7 @@ function App(this: FC) {
|
|
|
645
665
|
}
|
|
646
666
|
```
|
|
647
667
|
|
|
648
|
-
2\. Signals
|
|
668
|
+
2\. Signals would not be computed automatically outside of the `this.computed` method.
|
|
649
669
|
|
|
650
670
|
```tsx
|
|
651
671
|
// ❌ Won't work - updates of a signal won't refresh the view
|
|
@@ -653,7 +673,7 @@ function App(this: FC<{ message: string }>) {
|
|
|
653
673
|
this.message = "Welcome to mono-jsx";
|
|
654
674
|
return (
|
|
655
675
|
<div>
|
|
656
|
-
<h1
|
|
676
|
+
<h1>{this.message + "!"}</h1>
|
|
657
677
|
<button onClick={() => this.message = "Clicked"}>
|
|
658
678
|
Click Me
|
|
659
679
|
</button>
|
|
@@ -666,7 +686,7 @@ function App(this: FC) {
|
|
|
666
686
|
this.message = "Welcome to mono-jsx";
|
|
667
687
|
return (
|
|
668
688
|
<div>
|
|
669
|
-
<h1
|
|
689
|
+
<h1>{this.computed(() => this.message + "!")}</h1>
|
|
670
690
|
<button onClick={() => this.message = "Clicked"}>
|
|
671
691
|
Click Me
|
|
672
692
|
</button>
|
|
@@ -675,6 +695,34 @@ function App(this: FC) {
|
|
|
675
695
|
}
|
|
676
696
|
```
|
|
677
697
|
|
|
698
|
+
3\. `this` in nested functions in a component function would not be bound to the component. You can use an arrow function that automatically binds `this` to the component.
|
|
699
|
+
|
|
700
|
+
```tsx
|
|
701
|
+
function App(this: FC<{ count: number }>) {
|
|
702
|
+
function increment() {
|
|
703
|
+
this.count++; // ❌ `this` is not bound to the component.
|
|
704
|
+
}
|
|
705
|
+
return (
|
|
706
|
+
<div>
|
|
707
|
+
<span>{this.count}</span>
|
|
708
|
+
<button onClick={increment}>{this.count}</button>
|
|
709
|
+
</div>
|
|
710
|
+
)
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
function App(this: FC) {
|
|
714
|
+
const increment = () => {
|
|
715
|
+
this.count++; // ✅ `this` is bound to the component.
|
|
716
|
+
}
|
|
717
|
+
return (
|
|
718
|
+
<div>
|
|
719
|
+
<span>{this.count}</span>
|
|
720
|
+
<button onClick={increment}>{this.count}</button>
|
|
721
|
+
</div>
|
|
722
|
+
)
|
|
723
|
+
}
|
|
724
|
+
```
|
|
725
|
+
|
|
678
726
|
## Using `this` in Components
|
|
679
727
|
|
|
680
728
|
mono-jsx-dom binds a scoped signals object to `this` in your component functions. This allows you to access signals, context, and request information directly in your components.
|
package/index.mjs
CHANGED
|
@@ -1,6 +1,28 @@
|
|
|
1
1
|
// index.ts
|
|
2
|
-
import { atom, store } from "./jsx-runtime.mjs";
|
|
2
|
+
import { atom, jsx, render, store } from "./jsx-runtime.mjs";
|
|
3
|
+
function defineComponent(name, Component, attachShadow) {
|
|
4
|
+
customElements.define(
|
|
5
|
+
name,
|
|
6
|
+
class extends HTMLElement {
|
|
7
|
+
#ac;
|
|
8
|
+
connectedCallback() {
|
|
9
|
+
this.#ac ??= new AbortController();
|
|
10
|
+
const props = Object.fromEntries(this.getAttributeNames().map((name2) => [name2, this.getAttribute(name2)]));
|
|
11
|
+
if (attachShadow) {
|
|
12
|
+
const shadowRoot = this.attachShadow(typeof attachShadow === "boolean" ? { mode: "open" } : attachShadow);
|
|
13
|
+
render(null, jsx(Component, props), shadowRoot, this.#ac.signal);
|
|
14
|
+
} else {
|
|
15
|
+
this.mount(jsx(Component, props), this.#ac.signal);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
disconnectedCallback() {
|
|
19
|
+
this.#ac?.abort();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
);
|
|
23
|
+
}
|
|
3
24
|
export {
|
|
4
25
|
atom,
|
|
26
|
+
defineComponent,
|
|
5
27
|
store
|
|
6
28
|
};
|
package/jsx-runtime.mjs
CHANGED
|
@@ -48,7 +48,7 @@ var NullPrototypeObject = /* @__PURE__ */ (() => {
|
|
|
48
48
|
ONP.prototype = /* @__PURE__ */ Object.create(null);
|
|
49
49
|
return ONP;
|
|
50
50
|
})();
|
|
51
|
-
var
|
|
51
|
+
var applyCSS = (css) => {
|
|
52
52
|
const hash = hashCode(css.join("")).toString(36);
|
|
53
53
|
const className = "css-" + hash;
|
|
54
54
|
if (!document.getElementById(className)) {
|
|
@@ -65,6 +65,11 @@ var domEscapeHTML = (text) => {
|
|
|
65
65
|
};
|
|
66
66
|
|
|
67
67
|
// render.ts
|
|
68
|
+
var $get = /* @__PURE__ */ Symbol();
|
|
69
|
+
var $watch = /* @__PURE__ */ Symbol();
|
|
70
|
+
var $delegated = /* @__PURE__ */ Symbol();
|
|
71
|
+
var $slots = /* @__PURE__ */ Symbol();
|
|
72
|
+
var orphanScopes = /* @__PURE__ */ new Set();
|
|
68
73
|
var Reactive = class {
|
|
69
74
|
reactive(effect, abortSignal) {
|
|
70
75
|
const update = () => effect(this.get());
|
|
@@ -175,22 +180,17 @@ var InsertMark = class {
|
|
|
175
180
|
return () => childNodes.forEach((node) => node.remove());
|
|
176
181
|
}
|
|
177
182
|
};
|
|
178
|
-
var globalScopes = /* @__PURE__ */ new Set();
|
|
179
|
-
var $get = /* @__PURE__ */ Symbol();
|
|
180
|
-
var $watch = /* @__PURE__ */ Symbol();
|
|
181
|
-
var $expr = /* @__PURE__ */ Symbol();
|
|
182
|
-
var $slots = /* @__PURE__ */ Symbol();
|
|
183
183
|
var appScope;
|
|
184
184
|
var atomIndex = 0;
|
|
185
185
|
var depsMark;
|
|
186
186
|
var createScope = (slots, abortSignal) => {
|
|
187
|
-
let
|
|
187
|
+
let delegated2 = false;
|
|
188
188
|
let watchHandlers = /* @__PURE__ */ new Map();
|
|
189
189
|
let refElements = /* @__PURE__ */ new Map();
|
|
190
190
|
let signals = /* @__PURE__ */ new Map();
|
|
191
191
|
let refs = new Proxy(new NullPrototypeObject(), {
|
|
192
192
|
get(_, key) {
|
|
193
|
-
if (!
|
|
193
|
+
if (!delegated2 || depsMark) {
|
|
194
194
|
return refElements.get(key);
|
|
195
195
|
}
|
|
196
196
|
return new Ref(refElements, key);
|
|
@@ -211,8 +211,8 @@ var createScope = (slots, abortSignal) => {
|
|
|
211
211
|
handlers.add(effect);
|
|
212
212
|
return () => handlers.delete(effect);
|
|
213
213
|
};
|
|
214
|
-
case $
|
|
215
|
-
return (ok) =>
|
|
214
|
+
case $delegated:
|
|
215
|
+
return (ok) => delegated2 = ok;
|
|
216
216
|
case $slots:
|
|
217
217
|
return slots;
|
|
218
218
|
case "init":
|
|
@@ -270,7 +270,7 @@ var createScope = (slots, abortSignal) => {
|
|
|
270
270
|
if (typeof key === "symbol" || isFunction(value)) {
|
|
271
271
|
return value;
|
|
272
272
|
}
|
|
273
|
-
const getRawValue = !
|
|
273
|
+
const getRawValue = !delegated2 || depsMark !== void 0;
|
|
274
274
|
if (value instanceof Reactive) {
|
|
275
275
|
if (getRawValue) {
|
|
276
276
|
if (value instanceof Signal) {
|
|
@@ -319,7 +319,7 @@ var atom = (value) => {
|
|
|
319
319
|
};
|
|
320
320
|
var store = (props) => {
|
|
321
321
|
const scope = createScope();
|
|
322
|
-
|
|
322
|
+
orphanScopes.add(scope);
|
|
323
323
|
return scope.store(props);
|
|
324
324
|
};
|
|
325
325
|
var render = (scope, child, root, abortSignal) => {
|
|
@@ -511,7 +511,59 @@ var render = (scope, child, root, abortSignal) => {
|
|
|
511
511
|
el.style.cssText = attrValue;
|
|
512
512
|
} else {
|
|
513
513
|
let mark = /* @__PURE__ */ new Set();
|
|
514
|
-
let update = () =>
|
|
514
|
+
let update = () => {
|
|
515
|
+
const { classList } = el;
|
|
516
|
+
const style = $(attrValue, mark);
|
|
517
|
+
if (isPlainObject(style)) {
|
|
518
|
+
let inline;
|
|
519
|
+
let css = [];
|
|
520
|
+
for (let [k, v] of Object.entries(style)) {
|
|
521
|
+
v = $(v, mark);
|
|
522
|
+
switch (k.charCodeAt(0)) {
|
|
523
|
+
case /* ':' */
|
|
524
|
+
58:
|
|
525
|
+
if (isPlainObject(v)) {
|
|
526
|
+
css.push(k.startsWith("::view-") ? "" : null, k + renderStyle(v, mark));
|
|
527
|
+
}
|
|
528
|
+
break;
|
|
529
|
+
case /* '@' */
|
|
530
|
+
64:
|
|
531
|
+
if (isPlainObject(v)) {
|
|
532
|
+
if (k.startsWith("@keyframes ")) {
|
|
533
|
+
css.push(
|
|
534
|
+
k + "{" + Object.entries(v).map(([k2, v2]) => isPlainObject(v2) ? k2 + renderStyle(v2, mark) : "").join("") + "}"
|
|
535
|
+
);
|
|
536
|
+
} else if (k.startsWith("@view-")) {
|
|
537
|
+
css.push(k + renderStyle(v, mark));
|
|
538
|
+
} else {
|
|
539
|
+
css.push(k + "{", null, renderStyle(v, mark) + "}");
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
break;
|
|
543
|
+
case /* '&' */
|
|
544
|
+
38:
|
|
545
|
+
if (isPlainObject(v)) {
|
|
546
|
+
css.push(null, k.slice(1) + renderStyle(v, mark));
|
|
547
|
+
}
|
|
548
|
+
break;
|
|
549
|
+
default:
|
|
550
|
+
inline ??= {};
|
|
551
|
+
inline[k] = v;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
if (css.length > 0) {
|
|
555
|
+
classList.remove(...classList.values().filter((key) => key.startsWith("css-")));
|
|
556
|
+
if (inline) {
|
|
557
|
+
css.unshift(null, renderStyle(inline));
|
|
558
|
+
}
|
|
559
|
+
classList.add(applyCSS(css));
|
|
560
|
+
} else if (inline) {
|
|
561
|
+
el.style.cssText = renderStyle(inline).slice(1, -1);
|
|
562
|
+
}
|
|
563
|
+
} else if (isString(style)) {
|
|
564
|
+
el.style.cssText = style;
|
|
565
|
+
}
|
|
566
|
+
};
|
|
515
567
|
update();
|
|
516
568
|
for (const reactive of mark) {
|
|
517
569
|
reactive.watch(update, abortSignal);
|
|
@@ -600,7 +652,7 @@ var renderFC = (fc, props, root, abortSignal) => {
|
|
|
600
652
|
let el;
|
|
601
653
|
let scope = createScope(props.children, abortSignal);
|
|
602
654
|
let catchFn = props.catch;
|
|
603
|
-
|
|
655
|
+
delegated(scope, true);
|
|
604
656
|
try {
|
|
605
657
|
el = fc.call(scope, props);
|
|
606
658
|
} catch (err) {
|
|
@@ -620,7 +672,7 @@ var renderFC = (fc, props, root, abortSignal) => {
|
|
|
620
672
|
}
|
|
621
673
|
root.append(...pendingNodes);
|
|
622
674
|
el.then((nodes) => {
|
|
623
|
-
|
|
675
|
+
delegated(scope, false);
|
|
624
676
|
pendingNodes[0].replaceWith(...renderToFragment(scope, nodes, abortSignal).childNodes);
|
|
625
677
|
}).catch((err) => {
|
|
626
678
|
if (!catchFn) {
|
|
@@ -628,11 +680,11 @@ var renderFC = (fc, props, root, abortSignal) => {
|
|
|
628
680
|
}
|
|
629
681
|
pendingNodes[0].replaceWith(...renderToFragment(scope, catchFn(err), abortSignal).childNodes);
|
|
630
682
|
}).finally(() => {
|
|
631
|
-
|
|
683
|
+
delegated(scope, false);
|
|
632
684
|
pendingNodes.forEach((node) => node.remove());
|
|
633
685
|
});
|
|
634
686
|
} else {
|
|
635
|
-
|
|
687
|
+
delegated(scope, false);
|
|
636
688
|
if (isPlainObject(el) && !isVNode(el)) {
|
|
637
689
|
if (Symbol.asyncIterator in el) {
|
|
638
690
|
} else if (Symbol.iterator in el) {
|
|
@@ -673,9 +725,9 @@ var $ = (value, mark) => {
|
|
|
673
725
|
}
|
|
674
726
|
return value;
|
|
675
727
|
};
|
|
676
|
-
var
|
|
677
|
-
scope[$
|
|
678
|
-
|
|
728
|
+
var delegated = (scope, ok) => {
|
|
729
|
+
scope[$delegated](ok);
|
|
730
|
+
orphanScopes.forEach((s) => s[$delegated](ok));
|
|
679
731
|
};
|
|
680
732
|
var cx = (className, mark) => {
|
|
681
733
|
className = $(className, mark);
|
|
@@ -687,57 +739,6 @@ var cx = (className, mark) => {
|
|
|
687
739
|
}
|
|
688
740
|
return "";
|
|
689
741
|
};
|
|
690
|
-
var applyStyle = (el, style, mark) => {
|
|
691
|
-
style = $(style, mark);
|
|
692
|
-
if (isPlainObject(style)) {
|
|
693
|
-
let { classList } = el;
|
|
694
|
-
let inline;
|
|
695
|
-
let css = [];
|
|
696
|
-
classList.remove(...classList.values().filter((key) => key.startsWith("css-")));
|
|
697
|
-
for (let [k, v] of Object.entries(style)) {
|
|
698
|
-
v = $(v, mark);
|
|
699
|
-
switch (k.charCodeAt(0)) {
|
|
700
|
-
case /* ':' */
|
|
701
|
-
58:
|
|
702
|
-
if (isPlainObject(v)) {
|
|
703
|
-
css.push(k.startsWith("::view-") ? "" : null, k + renderStyle(v, mark));
|
|
704
|
-
}
|
|
705
|
-
break;
|
|
706
|
-
case /* '@' */
|
|
707
|
-
64:
|
|
708
|
-
if (isPlainObject(v)) {
|
|
709
|
-
if (k.startsWith("@keyframes ")) {
|
|
710
|
-
css.push(k + "{" + Object.entries(v).map(([k2, v2]) => isPlainObject(v2) ? k2 + renderStyle(v2, mark) : "").join("") + "}");
|
|
711
|
-
} else if (k.startsWith("@view-")) {
|
|
712
|
-
css.push(k + renderStyle(v, mark));
|
|
713
|
-
} else {
|
|
714
|
-
css.push(k + "{", null, renderStyle(v, mark) + "}");
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
break;
|
|
718
|
-
case /* '&' */
|
|
719
|
-
38:
|
|
720
|
-
if (isPlainObject(v)) {
|
|
721
|
-
css.push(null, k.slice(1) + renderStyle(v, mark));
|
|
722
|
-
}
|
|
723
|
-
break;
|
|
724
|
-
default:
|
|
725
|
-
inline ??= {};
|
|
726
|
-
inline[k] = v;
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
if (css.length > 0) {
|
|
730
|
-
if (inline) {
|
|
731
|
-
css.unshift(null, renderStyle(inline));
|
|
732
|
-
}
|
|
733
|
-
classList.add(createStyleElement(css));
|
|
734
|
-
} else if (inline) {
|
|
735
|
-
el.style.cssText = renderStyle(inline).slice(1, -1);
|
|
736
|
-
}
|
|
737
|
-
} else if (isString(style)) {
|
|
738
|
-
el.style.cssText = style;
|
|
739
|
-
}
|
|
740
|
-
};
|
|
741
742
|
|
|
742
743
|
// jsx-runtime.ts
|
|
743
744
|
var Fragment = $fragment;
|
|
@@ -785,5 +786,6 @@ export {
|
|
|
785
786
|
jsx,
|
|
786
787
|
jsx as jsxDEV,
|
|
787
788
|
jsx as jsxs,
|
|
789
|
+
render,
|
|
788
790
|
store
|
|
789
791
|
};
|
package/package.json
CHANGED
package/types/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Atom } from "./jsx.d.ts";
|
|
1
|
+
import type { Atom, ComponentType } from "./jsx.d.ts";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Creates an atom signal.
|
|
@@ -9,3 +9,8 @@ export const atom: <T>(initValue: T) => Atom<T>;
|
|
|
9
9
|
* Creates a signal store.
|
|
10
10
|
*/
|
|
11
11
|
export const store: <T extends Record<string, unknown>>(initValue: T) => T;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Defines a custom element with the given name and component.
|
|
15
|
+
*/
|
|
16
|
+
export const defineComponent: (name: string, Component: ComponentType<any>, attachShadow?: boolean | { mode: "open" | "closed" }) => void;
|