mono-jsx-dom 0.1.7 → 0.1.8

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 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
- return <div>Hello, world!</div>;
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 cannot be computed outside of the `this.computed` method.
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 title={this.message + "!"}>{this.message + "!"}</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 title={this.$(() => this.message + "!")}>{this.$(() => this.message + "!")}</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,23 @@
1
1
  // index.ts
2
- import { atom, store } from "./jsx-runtime.mjs";
2
+ import { atom, jsx, store } from "./jsx-runtime.mjs";
3
+ function defineComponent(name, Component) {
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
+ this.mount(jsx(Component, props), this.#ac.signal);
12
+ }
13
+ disconnectedCallback() {
14
+ this.#ac?.abort();
15
+ }
16
+ }
17
+ );
18
+ }
3
19
  export {
4
20
  atom,
21
+ defineComponent,
5
22
  store
6
23
  };
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 createStyleElement = (css) => {
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 exprMode = false;
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 (!exprMode || depsMark) {
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 $expr:
215
- return (ok) => exprMode = 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 = !exprMode || depsMark !== void 0;
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
- globalScopes.add(scope);
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 = () => applyStyle(el, attrValue, mark);
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
- setExpr(scope, true);
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
- setExpr(scope, false);
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
- setExpr(scope, false);
683
+ delegated(scope, false);
632
684
  pendingNodes.forEach((node) => node.remove());
633
685
  });
634
686
  } else {
635
- setExpr(scope, false);
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 setExpr = (scope, ok) => {
677
- scope[$expr](ok);
678
- globalScopes.forEach((s) => s[$expr](ok));
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mono-jsx-dom",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "A JSX runtime for browsers.",
5
5
  "keywords": [
6
6
  "jsx",
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: <P = {}>(name: string, Component: ComponentType<P>) => void;