@react5/dom-jsx 0.1.5 → 0.1.7

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
@@ -24,31 +24,31 @@ The runtime also works with Vite. Vite discovers the package's ESM `jsx-runtime`
24
24
  Function components may return DOM nodes with an attached API:
25
25
 
26
26
  ```tsx
27
- type ModalContext = { open(): void; close(): void }
27
+ type ModalApi = { open(): void; close(): void }
28
28
 
29
29
  function Modal() {
30
30
  const element = <dialog /> as HTMLDialogElement
31
- const context: ModalContext = {
31
+ const api: ModalApi = {
32
32
  open: () => element.showModal(),
33
33
  close: () => element.close()
34
34
  }
35
35
 
36
- return Object.assign(element, { context }) satisfies
37
- HTMLDialogElement & { context: ModalContext }
36
+ return Object.assign(element, { api }) satisfies
37
+ HTMLDialogElement & { api: ModalApi }
38
38
  }
39
39
 
40
40
  const modal = <Modal />
41
- modal.context?.open()
41
+ modal.api?.open()
42
42
  ```
43
43
 
44
44
  TypeScript represents all JSX expressions with one `JSX.Element` type, so it
45
45
  cannot preserve the exact intersection type of an individual component in
46
- TSX. The library's JSX element type therefore allows an optional `context`
46
+ TSX. The library's JSX element type therefore allows an optional `api`
47
47
  property, typed loosely as `Record<string, any>`, on every JSX result.
48
48
  Accessing any other property directly on the node (e.g. a typo'd DOM
49
- property) is still checked normally. `context` is optional because plain
49
+ property) is still checked normally. `api` is optional because plain
50
50
  intrinsic elements don't have one, so calling through it via `<Tag />` JSX
51
- syntax needs `?.`. To get the component's exact, non-optional `context`
51
+ syntax needs `?.`. To get the component's exact, non-optional `api`
52
52
  type instead, call the component directly or call `jsx(Modal, null)` /
53
53
  `jsxDEV(Modal, null)`.
54
54
 
@@ -59,7 +59,7 @@ Intrinsic JSX tags become DOM elements. `class` sets `className`, `style` accept
59
59
  The public entry points are:
60
60
 
61
61
  ```ts
62
- import { createRef, jsx, jsxs, Fragment } from '@react5/dom-jsx'
62
+ import { createRef, createContext, useContext, onCleanup, onDispose, dispose, jsx, jsxs, Fragment } from '@react5/dom-jsx'
63
63
  import { jsx, jsxs, jsxDEV, Fragment } from '@react5/dom-jsx/jsx-dev-runtime'
64
64
  ```
65
65
 
@@ -84,6 +84,84 @@ inputRef.current // HTMLInputElement
84
84
 
85
85
  A callback ref (`ref={(el) => ...}`) is also supported and is invoked with the element once it's created.
86
86
 
87
+ ### Context
88
+
89
+ Pass values down to nested components without threading props through every level:
90
+
91
+ ```tsx
92
+ import { createContext, useContext } from '@react5/dom-jsx'
93
+
94
+ const ThemeContext = createContext({ color: 'black' })
95
+
96
+ function Title(props: { text: string }) {
97
+ const theme = useContext(ThemeContext)
98
+ return <h1 style={{ color: theme.color }}>{props.text}</h1>
99
+ }
100
+
101
+ const page = (
102
+ <ThemeContext.Provider value={{ color: 'red' }}>
103
+ {() => <Title text="Hello" />}
104
+ </ThemeContext.Provider>
105
+ )
106
+ ```
107
+
108
+ JSX in this runtime is evaluated eagerly from the inside out: children are
109
+ created before their parent component runs. A Provider's child must
110
+ therefore be a render function (`{() => ...}`); the Provider calls it while
111
+ its value is active, so every component rendered inside sees that value.
112
+ Providers can be nested, and the innermost value wins.
113
+
114
+ `useContext` returns the nearest Provider's value only while rendering is in
115
+ progress. Call it synchronously in a component body and keep the result.
116
+ Called later, for example from an event handler or a `setTimeout`, it
117
+ returns the context's default value.
118
+
119
+ ### Cleanup
120
+
121
+ Release subscriptions, timers, and other resources when content is discarded.
122
+ Register cleanups with `onCleanup` in a component body, or with
123
+ `onDispose(node, fn)` anywhere else:
124
+
125
+ ```tsx
126
+ import { dispose, onCleanup, onDispose } from '@react5/dom-jsx'
127
+
128
+ function Clock() {
129
+ const el = <time /> as HTMLTimeElement
130
+ const id = setInterval(() => { el.textContent = new Date().toLocaleTimeString() }, 1000)
131
+ onCleanup(() => clearInterval(id))
132
+ return el
133
+ }
134
+
135
+ const node = <Clock />
136
+ onDispose(node, () => console.log('disposed'))
137
+
138
+ // Later, where content is swapped:
139
+ old.replaceWith(next)
140
+ dispose(old)
141
+ ```
142
+
143
+ The DOM gives no synchronous signal when a node is removed, so `dispose` must
144
+ be called explicitly. It runs the cleanups of the node and of every node inside
145
+ it, descendants first, each node's cleanups in reverse registration order.
146
+ Disposal follows the DOM tree, so components passed as children are covered
147
+ even though they render before their parent. Each cleanup runs at most once;
148
+ if some throw, the rest still run and the error (or an `AggregateError`) is
149
+ rethrown afterwards.
150
+
151
+ A component's cleanups are attached to the node it returns. When it returns a
152
+ fragment, they are attached to the fragment's top-level children and run once
153
+ all of them are disposed, so dispose every one of them, or a common ancestor.
154
+ An empty fragment gets an empty comment node to carry its cleanups.
155
+
156
+ If a component throws while rendering, the cleanups it already registered run
157
+ immediately. So do those of components it rendered and of JSX passed to it in
158
+ props, unless those nodes are already in the document.
159
+
160
+ `onCleanup` throws when called outside a component render, e.g. from an event
161
+ handler or after an `await`; use `onDispose(node, fn)` there. Moving a node
162
+ never disposes it. A node that is discarded without `dispose` keeps its
163
+ cleanups until it is garbage-collected, and they never run.
164
+
87
165
  ## Development
88
166
 
89
167
  ```sh
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{Fragment as r,createRef as m,jsx as o,jsxs as t}from"./jsx-runtime.js";export{r as Fragment,m as createRef,o as jsx,t as jsxs};
1
+ import{Fragment as r,createContext as m,createRef as o,dispose as t,jsx as e,jsxs as i,onCleanup as j,onDispose as p,useContext as s}from"./jsx-runtime.js";export{r as Fragment,m as createContext,o as createRef,t as dispose,e as jsx,i as jsxs,j as onCleanup,p as onDispose,s as useContext};
@@ -25,15 +25,27 @@ export type ElementProps<T extends HTMLElement> = Partial<Omit<T, 'children' | '
25
25
  export declare function createRef<T>(): {
26
26
  current: T | null;
27
27
  };
28
+ export type ProviderProps<T> = {
29
+ value: T;
30
+ children: () => Child;
31
+ };
32
+ export type Context<T> = {
33
+ Provider: (props: ProviderProps<T>) => Node;
34
+ };
35
+ export declare function createContext<T>(defaultValue: T): Context<T>;
36
+ export declare function useContext<T>(context: Context<T>): T;
37
+ export declare function onDispose(node: Node, fn: () => void): void;
38
+ export declare function onCleanup(fn: () => void): void;
39
+ export declare function dispose(node: Node): void;
28
40
  export declare namespace JSX {
29
41
  type Element = Node & {
30
- context?: Record<string, any>;
42
+ api?: Record<string, any>;
31
43
  };
32
44
  type IntrinsicElements = {
33
45
  [K in keyof HTMLElementTagNameMap]: ElementProps<HTMLElementTagNameMap[K]>;
34
46
  };
35
47
  }
36
- export declare function jsx<R extends Node>(tag: (props: any) => R, props: Props | null, _key?: string | number): R;
48
+ export declare function jsx<P, R extends Node>(tag: (props: P) => R, props: {} extends P ? P | null : P, _key?: string | number): R;
37
49
  export declare function jsx(tag: string, props: Props | null, _key?: string | number): Node;
38
50
  export declare const jsxs: typeof jsx;
39
51
  export declare function Fragment(props: Props): DocumentFragment;
@@ -1 +1 @@
1
- function e(){return{current:null}}function t(e,t,n){return function(e,t){const n=t??{};if("function"==typeof e)return e(n);const r=document.createElement(e);for(const[i,o]of Object.entries(n))if("children"!==i&&"ref"!==i)if(i.startsWith("on")&&"function"==typeof o)r.addEventListener(i.slice(2).toLowerCase(),o);else if("class"!==i){if("style"===i&&o&&"object"==typeof o)Object.assign(r.style,o);else if(null!=o)if(i.includes("-"))r.setAttribute(i,String(o));else{if(i in r){if(!1===o)continue;try{r[i]=o;continue}catch{}}r.setAttribute(i,!0===o?"":String(o))}}else r.className=String(o??"");i(r,n.children);const c=n.ref;"function"==typeof c?c(r):c&&(c.current=r);return r}(e,t)}var n=t;function r(e){const t=document.createDocumentFragment();return i(t,e.children),t}function i(e,t){if(Array.isArray(t))for(const n of t)i(e,n);else t instanceof Node?e.appendChild(t):null!=t&&!1!==t&&!0!==t&&e.appendChild(document.createTextNode(String(t)))}export{r as Fragment,e as createRef,t as jsx,n as jsxs};
1
+ function e(){return{current:null}}var t=/* @__PURE__ */new WeakMap;function n(e){const n=[],o={Provider({value:e,children:t}){if("function"!=typeof t)throw new TypeError("Context Provider expects a single function child: {() => ...}");n.push(e);try{const e=t();if(e instanceof Node)return e;const n=document.createDocumentFragment();return m(n,e),n}finally{n.pop()}}};return t.set(o,{defaultValue:e,stack:n}),o}function o(e){const n=t.get(e);if(!n)throw new TypeError("useContext expects a context created by createContext");const{defaultValue:o,stack:r}=n;return r.length>0?r[r.length-1]:o}var r=/* @__PURE__ */new WeakMap,c=[];function s(e,t){const n=r.get(e);n?n.push(t):r.set(e,[t])}function i(e,t){s(e,()=>t())}function f(e){const t=c[c.length-1];if(!t)throw new Error("onCleanup must be called while a component is rendering; use onDispose(node, fn) instead");t.cleanups.push(e)}function u(e){const t=[];if(l(e,t),1===t.length)throw t[0];if(t.length>1)throw new AggregateError(t,"Multiple cleanups failed during dispose")}function l(e,t){for(const r of Array.from(e.childNodes))l(r,t);const n=r.get(e);if(n){r.delete(e);for(let e=n.length-1;e>=0;e--)try{n[e](t)}catch(o){t.push(o)}}}function a(e,t){for(let o=e.length-1;o>=0;o--)try{e[o]()}catch(n){t.push(n)}}function d(e,t){if(e instanceof Node)t.push(e);else if(Array.isArray(e))for(const n of e)d(n,t)}function p(e,t){const n={cleanups:[],rendered:[]};let o;c.push(n);try{o=e(t)}catch(f){throw c.pop(),function(e,t){const n=[],o=[...e.rendered];for(const r of Object.values(t))d(r,o);for(const r of o)r.isConnected||l(r,n);a(e.cleanups,n)}(n,t),f}c.pop();let r=[o];if(o instanceof DocumentFragment&&(!o.firstChild&&n.cleanups.length>0&&o.appendChild(document.createComment("")),r=Array.from(o.childNodes)),n.cleanups.length>0)if(1===r.length)for(const c of n.cleanups)i(r[0],c);else{let e=r.length;for(const t of r)s(t,t=>{0===--e&&a(n.cleanups,t)})}return c[c.length-1]?.rendered.push(...r),o}function h(e,t,n){return function(e,t){const n=t??{};if("function"==typeof e)return p(e,n);const o=document.createElement(e);for(const[c,s]of Object.entries(n))if("children"!==c&&"ref"!==c)if(c.startsWith("on")&&"function"==typeof s)o.addEventListener(c.slice(2).toLowerCase(),s);else if("class"!==c){if("style"===c&&s&&"object"==typeof s)Object.assign(o.style,s);else if(null!=s)if(c.includes("-"))o.setAttribute(c,String(s));else{if(c in o){if(!1===s)continue;try{o[c]=s;continue}catch{}}o.setAttribute(c,!0===s?"":String(s))}}else o.className=String(s??"");m(o,n.children);const r=n.ref;"function"==typeof r?r(o):r&&(r.current=o);return o}(e,t)}var g=h;function y(e){const t=document.createDocumentFragment();return m(t,e.children),t}function m(e,t){if(Array.isArray(t))for(const n of t)m(e,n);else t instanceof Node?e.appendChild(t):null!=t&&!1!==t&&!0!==t&&e.appendChild(document.createTextNode(String(t)))}export{y as Fragment,n as createContext,e as createRef,u as dispose,h as jsx,g as jsxs,f as onCleanup,i as onDispose,o as useContext};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react5/dom-jsx",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "JSX runtime for vanilla TS/JS projects.",
5
5
  "keywords": [
6
6
  "JSX"