mono-jsx 0.1.1 → 0.1.3

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
@@ -5,9 +5,9 @@
5
5
  mono-jsx is a JSX runtime that renders `<html>` element to a `Response` object in JavaScript runtimes like Node.js, Deno, Bun, Cloudflare Workers, etc.
6
6
 
7
7
  - 🚀 No build step needed
8
- - 🦋 Lightweight(8KB gzipped), zero dependencies
8
+ - 🦋 Lightweight (8KB gzipped), zero dependencies
9
9
  - 🔫 Minimal state runtime
10
- - 🚨 Full Web API types
10
+ - 🚨 Complete Web API Typescript definitions
11
11
  - ⏳ Streaming rendering
12
12
  - 🌎 Universal, works in Node.js, Deno, Bun, Cloudflare Workers, etc.
13
13
 
@@ -57,7 +57,7 @@ bunx mono-jsx setup
57
57
 
58
58
  ## Usage
59
59
 
60
- To create a html response in server-side, you just need to return a `<html>` element in the `fetch` handler.
60
+ mono-jsx allows you to return an `<html>` JSX element as a `Response` object in the `fetch` handler.
61
61
 
62
62
  ```tsx
63
63
  // app.tsx
@@ -65,7 +65,7 @@ To create a html response in server-side, you just need to return a `<html>` ele
65
65
  export default {
66
66
  fetch: (req) => (
67
67
  <html>
68
- <h1>Hello World!</h1>
68
+ <h1>Welcome to mono-jsx!</h1>
69
69
  </html>
70
70
  ),
71
71
  };
@@ -95,7 +95,7 @@ serve({
95
95
  port: 3000,
96
96
  fetch: (req) => (
97
97
  <html>
98
- <h1>Hello World!</h1>
98
+ <h1>Welcome to mono-jsx!</h1>
99
99
  </html>
100
100
  ),
101
101
  });
@@ -113,11 +113,11 @@ mono-jsx uses [**JSX**](https://react.dev/learn/describing-the-ui) to describe t
113
113
 
114
114
  ### Using Standard HTML Property Names
115
115
 
116
- mono-jsx uses standard HTML property names instead of React's overthinked property names.
116
+ mono-jsx adopts standard HTML property names, avoiding React's custom property naming conventions.
117
117
 
118
- - `className` -> `class`
119
- - `htmlFor` -> `for`
120
- - `onChange` -> `onInput`
118
+ - `className` becomes `class`
119
+ - `htmlFor` becomes `for`
120
+ - `onChange` becomes `onInput`
121
121
 
122
122
  ### Composition `class`
123
123
 
@@ -148,7 +148,7 @@ mono-jsx allows you to use [pseudo classes](https://developer.mozilla.org/en-US/
148
148
 
149
149
  ### `<slot>` Element
150
150
 
151
- mono-jsx uses [`<slot>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot) element to render the slotted content(Equivalent to React's `children` proptery). Plus, you also can add the `name` attribute to define a named slot.
151
+ mono-jsx uses [`<slot>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot) element to render the slotted content (Equivalent to React's `children` property). Plus, you also can add the `name` attribute to define a named slot.
152
152
 
153
153
  ```jsx
154
154
  function Container() {
@@ -208,7 +208,7 @@ function Button() {
208
208
  ```
209
209
 
210
210
  > [!NOTE]
211
- > the event handler would never be called in server-side. Instead it will be serialized to a string and sent to the client-side. **This means you should NOT use any server-side variables or functions in the event handler.**
211
+ > the event handler would never be called in server-side. It will be serialized to a string and sent to the client-side. **This means you should NOT use any server-side variables or functions in the event handler.**
212
212
 
213
213
  ```tsx
214
214
  function Button(this: FC, props: { role: string }) {
@@ -238,12 +238,30 @@ Plus, mono-jsx supports the `mount` event that will be triggered when the elemen
238
238
  function App() {
239
239
  return (
240
240
  <div onMount={(evt) => console.log(evt.target, "Mounted!")}>
241
- <h1>Hello World!</h1>
241
+ <h1>Welcome to mono-jsx!</h1>
242
242
  </div>
243
243
  );
244
244
  }
245
245
  ```
246
246
 
247
+ mono-jsx also accepts a function as the `action` property for `form` elements, which will be called when the form is submitted.
248
+
249
+ ```tsx
250
+ function App() {
251
+ return (
252
+ <form
253
+ action={(data: FormData, evt) => {
254
+ evt.defaultPrevented // true
255
+ console.log(data.get("name"));
256
+ }}
257
+ >
258
+ <input type="text" name="name" />
259
+ <button type="submit">Submit</button>
260
+ </form>
261
+ );
262
+ }
263
+ ```
264
+
247
265
  ## Using State
248
266
 
249
267
  mono-jsx provides a minimal state runtime that allows you to update view based on state changes in client-side.
@@ -298,7 +316,7 @@ function App(this: FC<{ show: boolean }>) {
298
316
  return (
299
317
  <div>
300
318
  <toggle value={this.show}>
301
- <h1>Hello World!</h1>
319
+ <h1>Welcome to mono-jsx!</h1>
302
320
  </toggle>
303
321
  <button onClick={toggle}>{this.computed(() => this.show ? "Hide" : "Show")}</button>
304
322
  </div>
@@ -333,18 +351,18 @@ function App(this: FC<{ lang: "en" | "zh" | "emoji" }>) {
333
351
 
334
352
  ## Streaming Rendering
335
353
 
336
- mono-jsx renders your `<html>` as a readable stream, that allows async function components are rendered asynchrously. You can set a `placeholder` attribute to show a loading state while the async component is loading.
354
+ mono-jsx renders your `<html>` as a readable stream, that allows async function components are rendered asynchronously. You can set a `placeholder` attribute to show a loading state while the async component is loading.
337
355
 
338
356
  ```jsx
339
357
  async function Sleep(ms) {
340
358
  await new Promise((resolve) => setTimeout(resolve, ms));
341
- return <solt />;
359
+ return <slot />;
342
360
  }
343
361
 
344
362
  export default {
345
363
  fetch: (req) => (
346
364
  <html>
347
- <h1>Hello World!</h1>
365
+ <h1>Welcome to mono-jsx!</h1>
348
366
  <Sleep ms={1000} placeholder={<p>Sleeping...</p>}>
349
367
  <p>After 1 second</p>
350
368
  </Sleep>
@@ -358,13 +376,13 @@ You can also set `rendering` attribute to "eager" to render the async component
358
376
  ```jsx
359
377
  async function Sleep(ms) {
360
378
  await new Promise((resolve) => setTimeout(resolve, ms));
361
- return <solt />;
379
+ return <slot />;
362
380
  }
363
381
 
364
382
  export default {
365
383
  fetch: (req) => (
366
384
  <html>
367
- <h1>Hello World!</h1>
385
+ <h1>Welcome to mono-jsx!</h1>
368
386
  <Sleep ms={1000} rendering="eager">
369
387
  <p>After 1 second</p>
370
388
  </Sleep>
package/jsx-runtime.mjs CHANGED
@@ -46,9 +46,16 @@ function createState(request) {
46
46
  }
47
47
 
48
48
  // runtime/index.ts
49
- var RUNTIME_STATE_JS = "const p=(e,i)=>e.getAttribute(i),m=(e,i)=>e.hasAttribute(i),M=new Map,T=e=>M.get(e)??M.set(e,L()).get(e);function L(){const e=Object.create(null),i=new Map;function f(n,o){let a=o;Object.defineProperty(e,n,{get:()=>a,set:u=>{if(u!==a){const r=i.get(n);r&&queueMicrotask(()=>r.forEach(s=>s())),a=u}}})}function d(n,o,a,u){let r;if(o==='toggle'){let s;r=()=>{if(!s){const t=n.firstElementChild;t&&t.tagName==='TEMPLATE'&&m(t,'m-slot')?(s=t.content.childNodes,n.innerHTML=''):s=n.childNodes}a()?n.append(...s):n.innerHTML=''}}else if(o==='switch'){let s=p(n,'match'),t,l,E=c=>t.get(c)??t.set(c,[]).get(c),h;r=()=>{if(!t){t=new Map,l=[];for(const c of n.childNodes)if(c.nodeType===1&&c.tagName==='TEMPLATE'&&m(c,'m-slot')){for(const g of c.content.childNodes)g.nodeType===1&&m(g,'slot')?E(p(g,'slot')).push(g):l.push(g);c.remove()}else s?E(s).push(c):l.push(c)}h=a(),n.innerHTML='',n.append(...t.has(h)?t.get(h):l)}}else if(o&&o.length>2&&o.startsWith('[')&&o.endsWith(']')){let s=o.slice(1,-1),t=n.parentElement;t.tagName==='M-GROUP'&&(t=t.previousElementSibling),r=()=>{const l=a();l===!1?t.removeAttribute(s):(s==='class'||s==='style')&&l&&typeof l=='object'?t.setAttribute(s,s==='class'?cx(l):styleToCSS(l)):t.setAttribute(s,l===!0?'':''+l)}}else r=()=>n.textContent=''+a();if(r)for(const s of u){let t=i.get(s);t||(t=[],i.set(s,t)),t.push(r)}}return{store:e,define:f,createEffect:d}}customElements.define('m-state',class extends HTMLElement{connectedCallback(){const e=this,i=p(e,'mode'),f=p(e,'key'),d=T(p(e,'fc'));f?d.createEffect(e,i,()=>d.store[f],[f]):m(e,'computed')&&setTimeout(()=>{const n=e.firstChild;if(n&&n.nodeType===1&&n.type==='computed'){const o=n.textContent;o&&new Function('$',o).call(d.store,(a,u)=>d.createEffect(e,i,a,u))}})}}),Object.assign(globalThis,{$state:e=>T(e).store,$defineState:(e,i)=>{const f=e.indexOf(':');f>0&&T(e.slice(0,f)).define(e.slice(f+1),i)}});";
50
- var RUNTIME_SUSPENSE_JS = "const n={},o=e=>e.getAttribute('chunk-id');c('m-portal',e=>{n[o(e)]=e}),c('m-chunk',e=>{const t=o(e),s=n[t];s&&setTimeout(()=>{s.replaceWith(...e.firstChild.content.childNodes),delete n[t],e.remove()})});function c(e,t){customElements.define(e,class extends HTMLElement{connectedCallback(){t(this)}})}";
51
- var RUNTIME_COMPONENTS_JS = { "cx": "var cx=(()=>{var e=r=>typeof r=='string',n=r=>typeof r=='object'&&r!==null;function t(r){return e(r)?r:n(r)?Array.isArray(r)?r.map(t).filter(Boolean).join(' '):Object.entries(r).filter(([,o])=>!!o).map(([o])=>o).join(' '):''}return t;})();", "styleToCSS": "var styleToCSS=(()=>{var a=new Set(['animation-iteration-count','aspect-ratio','border-image-outset','border-image-slice','border-image-width','box-flex-group','box-flex','box-ordinal-group','column-count','columns','fill-opacity','flex-grow','flex-negative','flex-order','flex-positive','flex-shrink','flex','flood-opacity','font-weight','grid-area','grid-column-end','grid-column-span','grid-column-start','grid-column','grid-row-end','grid-row-span','grid-row-start','grid-row','line-clamp','line-height','opacity','order','orphans','stop-opacity','stroke-dasharray','stroke-dashoffset','stroke-miterlimit','stroke-opacity','stroke-width','tab-size','widows','z-index','zoom']),s=r=>typeof r=='string',c=r=>typeof r=='object'&&r!==null,f=r=>r.replace(/[a-z][A-Z]/g,t=>t.charAt(0)+'-'+t.charAt(1).toLowerCase());function u(r){if(s(r))return r;if(!c(r))return'';let t='';for(let[n,o]of Array.isArray(r)?r:Object.entries(r)){if(o==null||o===!1||Number.isNaN(o)||!s(n))return'';let e=f(n),i=typeof o=='number'?a.has(e)?''+o:o+'px':''+o;t+=(t!==''?';':'')+e+':'+(e==='content'?JSON.stringify(i):i)}return t}return u;})();", "event": "window.$emit=(evt,el,fn,fc)=>fn.call(window.$state?.(fc)??el,evt);window.$onsubmit=(evt,el,fn,fc)=>{evt.preventDefault();fn.call(window.$state?.(fc)??el,new FormData(el),evt)};" };
49
+ var RUNTIME_STATE_JS = `const p=(e,i)=>e.getAttribute(i),m=(e,i)=>e.hasAttribute(i),M=new Map,T=e=>M.get(e)??M.set(e,L()).get(e);function L(){const e=Object.create(null),i=new Map;function f(n,o){let a=o;Object.defineProperty(e,n,{get:()=>a,set:u=>{if(u!==a){const r=i.get(n);r&&queueMicrotask(()=>r.forEach(s=>s())),a=u}}})}function d(n,o,a,u){let r;if(o==="toggle"){let s;r=()=>{if(!s){const t=n.firstElementChild;t&&t.tagName==="TEMPLATE"&&m(t,"m-slot")?(s=t.content.childNodes,n.innerHTML=""):s=n.childNodes}a()?n.append(...s):n.innerHTML=""}}else if(o==="switch"){let s=p(n,"match"),t,l,E=c=>t.get(c)??t.set(c,[]).get(c),h;r=()=>{if(!t){t=new Map,l=[];for(const c of n.childNodes)if(c.nodeType===1&&c.tagName==="TEMPLATE"&&m(c,"m-slot")){for(const g of c.content.childNodes)g.nodeType===1&&m(g,"slot")?E(p(g,"slot")).push(g):l.push(g);c.remove()}else s?E(s).push(c):l.push(c)}h=a(),n.innerHTML="",n.append(...t.has(h)?t.get(h):l)}}else if(o&&o.length>2&&o.startsWith("[")&&o.endsWith("]")){let s=o.slice(1,-1),t=n.parentElement;t.tagName==="M-GROUP"&&(t=t.previousElementSibling),r=()=>{const l=a();l===!1?t.removeAttribute(s):(s==="class"||s==="style")&&l&&typeof l=="object"?t.setAttribute(s,s==="class"?cx(l):styleToCSS(l)):t.setAttribute(s,l===!0?"":""+l)}}else r=()=>n.textContent=""+a();if(r)for(const s of u){let t=i.get(s);t||(t=[],i.set(s,t)),t.push(r)}}return{store:e,define:f,createEffect:d}}customElements.define("m-state",class extends HTMLElement{connectedCallback(){const e=this,i=p(e,"mode"),f=p(e,"key"),d=T(p(e,"fc"));f?d.createEffect(e,i,()=>d.store[f],[f]):m(e,"computed")&&setTimeout(()=>{const n=e.firstChild;if(n&&n.nodeType===1&&n.type==="computed"){const o=n.textContent;o&&new Function("$",o).call(d.store,(a,u)=>d.createEffect(e,i,a,u))}})}}),Object.assign(globalThis,{$state:e=>T(e).store,$defineState:(e,i)=>{const f=e.indexOf(":");f>0&&T(e.slice(0,f)).define(e.slice(f+1),i)}});`;
50
+ var RUNTIME_SUSPENSE_JS = `const n={},o=e=>e.getAttribute("chunk-id");c("m-portal",e=>{n[o(e)]=e}),c("m-chunk",e=>{const t=o(e),s=n[t];s&&setTimeout(()=>{s.replaceWith(...e.firstChild.content.childNodes),delete n[t],e.remove()})});function c(e,t){customElements.define(e,class extends HTMLElement{connectedCallback(){t(this)}})}`;
51
+ var RUNTIME_COMPONENTS_JS = {
52
+ /** cx.js (239 bytes) */
53
+ cx: `var cx=(()=>{var n=e=>typeof e=="string",o=e=>typeof e=="object"&&e!==null;function t(e){return n(e)?e:o(e)?Array.isArray(e)?e.map(t).filter(Boolean).join(" "):Object.entries(e).filter(([,r])=>!!r).map(([r])=>r).join(" "):""}return t;})();`,
54
+ /** styleToCSS.js (1203 bytes) */
55
+ styleToCSS: `var styleToCSS=(()=>{var c=new Set(["animation-iteration-count","aspect-ratio","border-image-outset","border-image-slice","border-image-width","box-flex-group","box-flex","box-ordinal-group","column-count","columns","fill-opacity","flex-grow","flex-negative","flex-order","flex-positive","flex-shrink","flex","flood-opacity","font-weight","grid-area","grid-column-end","grid-column-span","grid-column-start","grid-column","grid-row-end","grid-row-span","grid-row-start","grid-row","line-clamp","line-height","opacity","order","orphans","stop-opacity","stroke-dasharray","stroke-dashoffset","stroke-miterlimit","stroke-opacity","stroke-width","tab-size","widows","z-index","zoom"]),s=e=>typeof e=="string",u=e=>typeof e=="object"&&e!==null,f=e=>e.replace(/[a-z][A-Z]/g,r=>r.charAt(0)+"-"+r.charAt(1).toLowerCase());function l(e){if(s(e))return e;if(u(e)){let r="";for(let[o,t]of Array.isArray(e)?e:Object.entries(e)){if(t==null||t===!1||Number.isNaN(t)||!s(o))return"";let n=f(o),i=typeof t=="number"?c.has(n)?""+t:t+"px":a(""+t);r+=(r!==""?";":"")+a(n)+":"+(n==="content"?JSON.stringify(i):i)}return r}return""}function a(e){return e.replace(/["<>]/g,r=>r==="<"?"&lt;":r===">"?"&gt;":"'")}return l;})();`,
56
+ /** event.js (176 bytes) */
57
+ event: `window.$emit=(evt,el,fn,fc)=>fn.call(window.$state?.(fc)??el,evt);window.$onsubmit=(evt,el,fn,fc)=>{evt.preventDefault();fn.call(window.$state?.(fc)??el,new FormData(el),evt)};`
58
+ };
52
59
 
53
60
  // runtime/utils.ts
54
61
  var cssBareUnitProps = /* @__PURE__ */ new Set([
@@ -112,26 +119,75 @@ function cx(className) {
112
119
  return "";
113
120
  }
114
121
  function styleToCSS(style) {
115
- if (isString(style)) return style;
116
- if (!isObject(style)) return "";
117
- let css = "";
118
- for (const [k, v] of Array.isArray(style) ? style : Object.entries(style)) {
119
- if (v === null || v === void 0 || v === false || Number.isNaN(v) || !isString(k)) return "";
120
- const cssKey = toHyphenCase(k);
121
- const cssValue = typeof v === "number" ? cssBareUnitProps.has(cssKey) ? "" + v : v + "px" : "" + v;
122
- css += (css !== "" ? ";" : "") + cssKey + ":" + (cssKey === "content" ? JSON.stringify(cssValue) : cssValue);
122
+ if (isString(style)) {
123
+ return style;
124
+ }
125
+ if (isObject(style)) {
126
+ let css = "";
127
+ for (const [k, v] of Array.isArray(style) ? style : Object.entries(style)) {
128
+ if (v === null || v === void 0 || v === false || Number.isNaN(v) || !isString(k)) return "";
129
+ const cssKey = toHyphenCase(k);
130
+ const cssValue = typeof v === "number" ? cssBareUnitProps.has(cssKey) ? "" + v : v + "px" : escapeCSSText("" + v);
131
+ css += (css !== "" ? ";" : "") + escapeCSSText(cssKey) + ":" + (cssKey === "content" ? JSON.stringify(cssValue) : cssValue);
132
+ }
133
+ return css;
134
+ }
135
+ return "";
136
+ }
137
+ function escapeCSSText(str) {
138
+ return str.replace(/["<>]/g, (m) => {
139
+ if (m === "<") return "&lt;";
140
+ if (m === ">") return "&gt;";
141
+ return "'";
142
+ });
143
+ }
144
+ var regexpHtmlSafe = /["'&<>]/;
145
+ function escapeHTML(str) {
146
+ if (typeof Bun === "object" && "escapeHTML" in Bun) return Bun.escapeHTML(str);
147
+ const match = regexpHtmlSafe.exec(str);
148
+ if (!match) {
149
+ return str;
150
+ }
151
+ let escape;
152
+ let index;
153
+ let lastIndex = 0;
154
+ let html2 = "";
155
+ for (index = match.index; index < str.length; index++) {
156
+ switch (str.charCodeAt(index)) {
157
+ case 34:
158
+ escape = "&quot;";
159
+ break;
160
+ case 38:
161
+ escape = "&amp;";
162
+ break;
163
+ case 39:
164
+ escape = "&#x27;";
165
+ break;
166
+ case 60:
167
+ escape = "&lt;";
168
+ break;
169
+ case 62:
170
+ escape = "&gt;";
171
+ break;
172
+ default:
173
+ continue;
174
+ }
175
+ if (lastIndex !== index) {
176
+ html2 += str.slice(lastIndex, index);
177
+ }
178
+ lastIndex = index + 1;
179
+ html2 += escape;
123
180
  }
124
- return css;
181
+ return lastIndex !== index ? html2 + str.slice(lastIndex, index) : html2;
125
182
  }
126
183
 
127
184
  // render.ts
128
185
  var encoder = new TextEncoder();
129
186
  var regexpHtmlTag = /^[a-z][\w\-$]*$/;
130
- var regexpHtmlSafe = /["'&<>]/;
131
187
  var selfClosingTags = new Set("area,base,br,col,embed,hr,img,input,keygen,link,meta,param,source,track,wbr".split(","));
132
- var toAttrStringLit = (str) => JSON.stringify(escapeHTML(str));
133
188
  var isVNode = (v) => Array.isArray(v) && v.length === 3 && v[2] === $vnode;
134
189
  var hashCode = (s) => [...s].reduce((hash, c) => Math.imul(31, hash) + c.charCodeAt(0) | 0, 0);
190
+ var toAttrStringLit = (str) => '"' + escapeHTML(str).replaceAll('"', '\\"') + '"';
135
191
  async function renderNode(ctx, node, stripSlotProp) {
136
192
  const { write, stateStore } = ctx;
137
193
  switch (typeof node) {
@@ -185,7 +241,7 @@ async function renderNode(ctx, node, stripSlotProp) {
185
241
  }
186
242
  break;
187
243
  }
188
- const fcIndex = toString36(ctx.index.fc);
244
+ const fcIndex = ctx.index.fc.toString(36);
189
245
  if (tag === $state) {
190
246
  const { key, value } = props;
191
247
  write('<m-state fc="' + fcIndex + '" key=' + toAttrStringLit(key) + ">");
@@ -325,7 +381,7 @@ async function renderNode(ctx, node, stripSlotProp) {
325
381
  if (eager) {
326
382
  await renderNode({ ...ctx, eager: true, slots: children }, await v);
327
383
  } else {
328
- const chunkId = toString36(ctx.suspenses.length + 1);
384
+ const chunkId = (ctx.suspenses.length + 1).toString(36);
329
385
  ctx.suspenses.push(v.then(async (c) => {
330
386
  write('<m-chunk chunk-id="' + chunkId + '"><template>');
331
387
  await renderNode({ ...ctx, eager, slots: children }, c);
@@ -395,11 +451,11 @@ async function renderNode(ctx, node, stripSlotProp) {
395
451
  }
396
452
  switch (propName) {
397
453
  case "class":
398
- buffer += " " + renderAttr(propName, cx(propValue));
454
+ buffer += " class=" + toAttrStringLit(cx(propValue));
399
455
  break;
400
456
  case "style":
401
457
  if (isString(propValue) && propValue !== "") {
402
- buffer += " " + renderAttr(propName, cx(propValue));
458
+ buffer += ' style="' + escapeCSSText(propValue) + '"';
403
459
  } else if (isObject(propValue) && !Array.isArray(propValue)) {
404
460
  const style = [];
405
461
  const pseudoStyles = [];
@@ -409,55 +465,53 @@ async function renderNode(ctx, node, stripSlotProp) {
409
465
  switch (k.charCodeAt(0)) {
410
466
  case /* ':' */
411
467
  58:
412
- pseudoStyles.push([k, styleToCSS(v)]);
468
+ pseudoStyles.push([escapeCSSText(k), styleToCSS(v)]);
413
469
  break;
414
470
  case /* '@' */
415
471
  64:
416
- atRuleStyles.push([k, styleToCSS(v)]);
472
+ atRuleStyles.push([escapeCSSText(k), styleToCSS(v)]);
417
473
  break;
418
474
  case /* '&' */
419
475
  38:
420
- nestingStyles.push([k, styleToCSS(v)]);
476
+ nestingStyles.push([escapeCSSText(k), styleToCSS(v)]);
421
477
  break;
422
478
  default:
423
479
  style.push([k, v]);
424
480
  }
425
481
  }
426
482
  if (pseudoStyles.length > 0 || atRuleStyles.length > 0 || nestingStyles.length > 0) {
427
- let raw = "";
428
483
  let css = "";
484
+ let raw = "";
429
485
  let styleIds;
430
486
  let id;
431
487
  let cssSelector;
432
- let key;
433
- let value;
434
488
  if (style.length > 0) {
435
489
  css = styleToCSS(style);
436
490
  raw += css + "|";
437
491
  }
438
492
  raw += [pseudoStyles, atRuleStyles, nestingStyles].flat(1).map(([k, v]) => k + ">" + v).join("|");
439
493
  styleIds = ctx.styleIds ?? (ctx.styleIds = /* @__PURE__ */ new Set());
440
- id = toString36(hashCode(raw));
494
+ id = hashCode(raw).toString(36);
441
495
  cssSelector = "[data-css-" + id + "]";
442
496
  if (!styleIds.has(id)) {
443
497
  styleIds.add(id);
444
498
  if (css) {
445
499
  css = cssSelector + "{" + css + "}";
446
500
  }
447
- for ([key, value] of pseudoStyles) {
448
- css += cssSelector + key + "{" + value + "}";
501
+ for (const [p, styles] of pseudoStyles) {
502
+ css += cssSelector + p + "{" + styles + "}";
449
503
  }
450
- for ([key, value] of atRuleStyles) {
451
- css += key + "{" + cssSelector + "{" + value + "}}";
504
+ for (const [at, styles] of atRuleStyles) {
505
+ css += at + "{" + cssSelector + "{" + styles + "}}";
452
506
  }
453
- for ([key, value] of nestingStyles) {
454
- css += cssSelector + key.slice(1) + "{" + value + "}";
507
+ for (const [n, styles] of nestingStyles) {
508
+ css += cssSelector + n.slice(1) + "{" + styles + "}";
455
509
  }
456
510
  write('<style id="css-' + id + '">' + css + "</style>");
457
511
  }
458
512
  buffer += " data-css-" + id;
459
513
  } else if (style.length > 0) {
460
- buffer += " " + renderAttr(propName, styleToCSS(style));
514
+ buffer += ' style="' + styleToCSS(style) + '"';
461
515
  }
462
516
  }
463
517
  break;
@@ -468,23 +522,23 @@ async function renderNode(ctx, node, stripSlotProp) {
468
522
  break;
469
523
  case "action":
470
524
  if (typeof propValue === "function" && tag === "form") {
471
- const id = "$MF_" + toString36(ctx.index.mf++);
525
+ const id = "$MF_" + (ctx.index.mf++).toString(36);
472
526
  write("<script>function " + id + "(fd){(" + propValue.toString() + ")(fd)}<\/script>");
473
527
  buffer += ' onsubmit="$onsubmit(event,this,' + id + ",'" + fcIndex + `')"`;
474
528
  } else if (isString(propValue)) {
475
- buffer += " " + renderAttr(propName, propValue);
529
+ buffer += " action=" + toAttrStringLit(propValue);
476
530
  }
477
531
  break;
478
532
  case "slot":
479
533
  if (!stripSlotProp && isString(propValue)) {
480
- buffer += " " + renderAttr(propName, propValue);
534
+ buffer += " slot=" + toAttrStringLit(propValue);
481
535
  }
482
536
  break;
483
537
  default:
484
538
  if (regexpHtmlTag.test(propName) && propValue !== void 0) {
485
539
  if (propName.startsWith("on")) {
486
540
  if (typeof propValue === "function") {
487
- const id = "$MF_" + toString36(ctx.index.mf++);
541
+ const id = "$MF_" + (ctx.index.mf++).toString(36);
488
542
  write("<script>function " + id + "(e){(" + propValue.toString() + ")(e)}<\/script>");
489
543
  buffer += " " + propName.toLowerCase() + '="$emit(event,this,' + id + ",'" + fcIndex + `')"`;
490
544
  }
@@ -493,7 +547,7 @@ async function renderNode(ctx, node, stripSlotProp) {
493
547
  buffer += " " + propName;
494
548
  }
495
549
  } else {
496
- buffer += " " + renderAttr(propName, propValue);
550
+ buffer += " " + (propValue === true ? escapeHTML(propName) : escapeHTML(propName) + "=" + toAttrStringLit("" + propValue));
497
551
  }
498
552
  }
499
553
  }
@@ -538,50 +592,6 @@ async function renderChildren(ctx, children, stripSlotProp) {
538
592
  await renderNode(ctx, children, stripSlotProp);
539
593
  }
540
594
  }
541
- function renderAttr(key, value) {
542
- return value === true ? key : key + "=" + toAttrStringLit("" + value);
543
- }
544
- function toString36(num) {
545
- return num.toString(36);
546
- }
547
- function escapeHTML(str) {
548
- const match = regexpHtmlSafe.exec(str);
549
- if (!match) {
550
- return str;
551
- }
552
- if (typeof Bun === "object" && "escapeHTML" in Bun) return Bun.escapeHTML(str);
553
- let escape;
554
- let index;
555
- let lastIndex = 0;
556
- let html2 = "";
557
- for (index = match.index; index < str.length; index++) {
558
- switch (str.charCodeAt(index)) {
559
- case 34:
560
- escape = "&quot;";
561
- break;
562
- case 38:
563
- escape = "&amp;";
564
- break;
565
- case 39:
566
- escape = "&#x27;";
567
- break;
568
- case 60:
569
- escape = "&lt;";
570
- break;
571
- case 62:
572
- escape = "&gt;";
573
- break;
574
- default:
575
- continue;
576
- }
577
- if (lastIndex !== index) {
578
- html2 += str.slice(lastIndex, index);
579
- }
580
- lastIndex = index + 1;
581
- html2 += escape;
582
- }
583
- return lastIndex !== index ? html2 + str.slice(lastIndex, index) : html2;
584
- }
585
595
  function render(node, renderOptions = {}) {
586
596
  const { request, status, headers: headersRaw, rendering } = renderOptions;
587
597
  const headers = /* @__PURE__ */ Object.create(null);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mono-jsx",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "`<html>` as a `Response`.",
5
5
  "type": "module",
6
6
  "module": "./index.mjs",
package/types/aria.d.ts CHANGED
@@ -231,7 +231,7 @@ export interface Attributes {
231
231
  }
232
232
 
233
233
  // All the WAI-ARIA 1.2 role attribute values from https://www.w3.org/TR/wai-aria-1.2/#role_definitions
234
- type Role =
234
+ export type Role =
235
235
  | "alert"
236
236
  | "alertdialog"
237
237
  | "application"