base.js 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. // src/jsx/base.ts
  2. import { raw } from "../helper/html/index.js";
  3. import { escapeToBuffer, resolveCallbackSync, stringBufferToString } from "../utils/html.js";
  4. import { DOM_RENDERER, DOM_MEMO } from "./constants.js";
  5. import { createContext, globalContexts, useContext } from "./context.js";
  6. import { domRenderers } from "./intrinsic-element/common.js";
  7. import * as intrinsicElementTags from "./intrinsic-element/components.js";
  8. import { normalizeIntrinsicElementKey, styleObjectForEach } from "./utils.js";
  9. var nameSpaceContext = void 0;
  10. var getNameSpaceContext = () => nameSpaceContext;
  11. var toSVGAttributeName = (key) => /[A-Z]/.test(key) && // Presentation attributes are findable in style object. "clip-path", "font-size", "stroke-width", etc.
  12. // Or other un-deprecated kebab-case attributes. "overline-position", "paint-order", "strikethrough-position", etc.
  13. key.match(
  14. /^(?:al|basel|clip(?:Path|Rule)$|co|do|fill|fl|fo|gl|let|lig|i|marker[EMS]|o|pai|pointe|sh|st[or]|text[^L]|tr|u|ve|w)/
  15. ) ? key.replace(/([A-Z])/g, "-$1").toLowerCase() : key;
  16. var emptyTags = [
  17. "area",
  18. "base",
  19. "br",
  20. "col",
  21. "embed",
  22. "hr",
  23. "img",
  24. "input",
  25. "keygen",
  26. "link",
  27. "meta",
  28. "param",
  29. "source",
  30. "track",
  31. "wbr"
  32. ];
  33. var booleanAttributes = [
  34. "allowfullscreen",
  35. "async",
  36. "autofocus",
  37. "autoplay",
  38. "checked",
  39. "controls",
  40. "default",
  41. "defer",
  42. "disabled",
  43. "download",
  44. "formnovalidate",
  45. "hidden",
  46. "inert",
  47. "ismap",
  48. "itemscope",
  49. "loop",
  50. "multiple",
  51. "muted",
  52. "nomodule",
  53. "novalidate",
  54. "open",
  55. "playsinline",
  56. "readonly",
  57. "required",
  58. "reversed",
  59. "selected"
  60. ];
  61. var childrenToStringToBuffer = (children, buffer) => {
  62. for (let i = 0, len = children.length; i < len; i++) {
  63. const child = children[i];
  64. if (typeof child === "string") {
  65. escapeToBuffer(child, buffer);
  66. } else if (typeof child === "boolean" || child === null || child === void 0) {
  67. continue;
  68. } else if (child instanceof JSXNode) {
  69. child.toStringToBuffer(buffer);
  70. } else if (typeof child === "number" || child.isEscaped) {
  71. ;
  72. buffer[0] += child;
  73. } else if (child instanceof Promise) {
  74. buffer.unshift("", child);
  75. } else {
  76. childrenToStringToBuffer(child, buffer);
  77. }
  78. }
  79. };
  80. var JSXNode = class {
  81. tag;
  82. props;
  83. key;
  84. children;
  85. isEscaped = true;
  86. localContexts;
  87. constructor(tag, props, children) {
  88. this.tag = tag;
  89. this.props = props;
  90. this.children = children;
  91. }
  92. get type() {
  93. return this.tag;
  94. }
  95. // Added for compatibility with libraries that rely on React's internal structure
  96. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  97. get ref() {
  98. return this.props.ref || null;
  99. }
  100. toString() {
  101. const buffer = [""];
  102. this.localContexts?.forEach(([context, value]) => {
  103. context.values.push(value);
  104. });
  105. try {
  106. this.toStringToBuffer(buffer);
  107. } finally {
  108. this.localContexts?.forEach(([context]) => {
  109. context.values.pop();
  110. });
  111. }
  112. return buffer.length === 1 ? "callbacks" in buffer ? resolveCallbackSync(raw(buffer[0], buffer.callbacks)).toString() : buffer[0] : stringBufferToString(buffer, buffer.callbacks);
  113. }
  114. toStringToBuffer(buffer) {
  115. const tag = this.tag;
  116. const props = this.props;
  117. let { children } = this;
  118. buffer[0] += `<${tag}`;
  119. const normalizeKey = nameSpaceContext && useContext(nameSpaceContext) === "svg" ? (key) => toSVGAttributeName(normalizeIntrinsicElementKey(key)) : (key) => normalizeIntrinsicElementKey(key);
  120. for (let [key, v] of Object.entries(props)) {
  121. key = normalizeKey(key);
  122. if (key === "children") {
  123. } else if (key === "style" && typeof v === "object") {
  124. let styleStr = "";
  125. styleObjectForEach(v, (property, value) => {
  126. if (value != null) {
  127. styleStr += `${styleStr ? ";" : ""}${property}:${value}`;
  128. }
  129. });
  130. buffer[0] += ' style="';
  131. escapeToBuffer(styleStr, buffer);
  132. buffer[0] += '"';
  133. } else if (typeof v === "string") {
  134. buffer[0] += ` ${key}="`;
  135. escapeToBuffer(v, buffer);
  136. buffer[0] += '"';
  137. } else if (v === null || v === void 0) {
  138. } else if (typeof v === "number" || v.isEscaped) {
  139. buffer[0] += ` ${key}="${v}"`;
  140. } else if (typeof v === "boolean" && booleanAttributes.includes(key)) {
  141. if (v) {
  142. buffer[0] += ` ${key}=""`;
  143. }
  144. } else if (key === "dangerouslySetInnerHTML") {
  145. if (children.length > 0) {
  146. throw new Error("Can only set one of `children` or `props.dangerouslySetInnerHTML`.");
  147. }
  148. children = [raw(v.__html)];
  149. } else if (v instanceof Promise) {
  150. buffer[0] += ` ${key}="`;
  151. buffer.unshift('"', v);
  152. } else if (typeof v === "function") {
  153. if (!key.startsWith("on") && key !== "ref") {
  154. throw new Error(`Invalid prop '${key}' of type 'function' supplied to '${tag}'.`);
  155. }
  156. } else {
  157. buffer[0] += ` ${key}="`;
  158. escapeToBuffer(v.toString(), buffer);
  159. buffer[0] += '"';
  160. }
  161. }
  162. if (emptyTags.includes(tag) && children.length === 0) {
  163. buffer[0] += "/>";
  164. return;
  165. }
  166. buffer[0] += ">";
  167. childrenToStringToBuffer(children, buffer);
  168. buffer[0] += `</${tag}>`;
  169. }
  170. };
  171. var JSXFunctionNode = class extends JSXNode {
  172. toStringToBuffer(buffer) {
  173. const { children } = this;
  174. const props = { ...this.props };
  175. if (children.length) {
  176. props.children = children.length === 1 ? children[0] : children;
  177. }
  178. const res = this.tag.call(null, props);
  179. if (typeof res === "boolean" || res == null) {
  180. return;
  181. } else if (res instanceof Promise) {
  182. if (globalContexts.length === 0) {
  183. buffer.unshift("", res);
  184. } else {
  185. const currentContexts = globalContexts.map((c) => [c, c.values.at(-1)]);
  186. buffer.unshift(
  187. "",
  188. res.then((childRes) => {
  189. if (childRes instanceof JSXNode) {
  190. childRes.localContexts = currentContexts;
  191. }
  192. return childRes;
  193. })
  194. );
  195. }
  196. } else if (res instanceof JSXNode) {
  197. res.toStringToBuffer(buffer);
  198. } else if (typeof res === "number" || res.isEscaped) {
  199. buffer[0] += res;
  200. if (res.callbacks) {
  201. buffer.callbacks ||= [];
  202. buffer.callbacks.push(...res.callbacks);
  203. }
  204. } else {
  205. escapeToBuffer(res, buffer);
  206. }
  207. }
  208. };
  209. var JSXFragmentNode = class extends JSXNode {
  210. toStringToBuffer(buffer) {
  211. childrenToStringToBuffer(this.children, buffer);
  212. }
  213. };
  214. var jsx = (tag, props, ...children) => {
  215. props ??= {};
  216. if (children.length) {
  217. props.children = children.length === 1 ? children[0] : children;
  218. }
  219. const key = props.key;
  220. delete props["key"];
  221. const node = jsxFn(tag, props, children);
  222. node.key = key;
  223. return node;
  224. };
  225. var initDomRenderer = false;
  226. var jsxFn = (tag, props, children) => {
  227. if (!initDomRenderer) {
  228. for (const k in domRenderers) {
  229. ;
  230. intrinsicElementTags[k][DOM_RENDERER] = domRenderers[k];
  231. }
  232. initDomRenderer = true;
  233. }
  234. if (typeof tag === "function") {
  235. return new JSXFunctionNode(tag, props, children);
  236. } else if (intrinsicElementTags[tag]) {
  237. return new JSXFunctionNode(
  238. intrinsicElementTags[tag],
  239. props,
  240. children
  241. );
  242. } else if (tag === "svg" || tag === "head") {
  243. nameSpaceContext ||= createContext("");
  244. return new JSXNode(tag, props, [
  245. new JSXFunctionNode(
  246. nameSpaceContext,
  247. {
  248. value: tag
  249. },
  250. children
  251. )
  252. ]);
  253. } else {
  254. return new JSXNode(tag, props, children);
  255. }
  256. };
  257. var shallowEqual = (a, b) => {
  258. if (a === b) {
  259. return true;
  260. }
  261. const aKeys = Object.keys(a).sort();
  262. const bKeys = Object.keys(b).sort();
  263. if (aKeys.length !== bKeys.length) {
  264. return false;
  265. }
  266. for (let i = 0, len = aKeys.length; i < len; i++) {
  267. if (aKeys[i] === "children" && bKeys[i] === "children" && !a.children?.length && !b.children?.length) {
  268. continue;
  269. } else if (a[aKeys[i]] !== b[aKeys[i]]) {
  270. return false;
  271. }
  272. }
  273. return true;
  274. };
  275. var memo = (component, propsAreEqual = shallowEqual) => {
  276. let computed = null;
  277. let prevProps = void 0;
  278. const wrapper = ((props) => {
  279. if (prevProps && !propsAreEqual(prevProps, props)) {
  280. computed = null;
  281. }
  282. prevProps = props;
  283. return computed ||= component(props);
  284. });
  285. wrapper[DOM_MEMO] = propsAreEqual;
  286. wrapper[DOM_RENDERER] = component;
  287. return wrapper;
  288. };
  289. var Fragment = ({
  290. children
  291. }) => {
  292. return new JSXFragmentNode(
  293. "",
  294. {
  295. children
  296. },
  297. Array.isArray(children) ? children : children ? [children] : []
  298. );
  299. };
  300. var isValidElement = (element) => {
  301. return !!(element && typeof element === "object" && "tag" in element && "props" in element);
  302. };
  303. var cloneElement = (element, props, ...children) => {
  304. let childrenToClone;
  305. if (children.length > 0) {
  306. childrenToClone = children;
  307. } else {
  308. const c = element.props.children;
  309. childrenToClone = Array.isArray(c) ? c : [c];
  310. }
  311. return jsx(
  312. element.tag,
  313. { ...element.props, ...props },
  314. ...childrenToClone
  315. );
  316. };
  317. var reactAPICompatVersion = "19.0.0-hono-jsx";
  318. export {
  319. Fragment,
  320. JSXFragmentNode,
  321. JSXNode,
  322. booleanAttributes,
  323. cloneElement,
  324. getNameSpaceContext,
  325. isValidElement,
  326. jsx,
  327. jsxFn,
  328. memo,
  329. reactAPICompatVersion,
  330. shallowEqual
  331. };