what-core 0.5.6 → 0.6.0

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.
@@ -0,0 +1,51 @@
1
+ // packages/core/src/h.js
2
+ var EMPTY_OBJ = /* @__PURE__ */ Object.create(null);
3
+ function h(tag, props, ...children) {
4
+ props = props || EMPTY_OBJ;
5
+ const flat = flattenChildren(children);
6
+ const key = props.key ?? null;
7
+ if (props.key !== void 0) {
8
+ props = { ...props };
9
+ delete props.key;
10
+ }
11
+ return { tag, props, children: flat, key, _vnode: true };
12
+ }
13
+ function Fragment({ children }) {
14
+ return children;
15
+ }
16
+ function flattenChildren(children) {
17
+ const out = [];
18
+ for (let i = 0; i < children.length; i++) {
19
+ const child = children[i];
20
+ if (child == null || child === false || child === true) continue;
21
+ if (Array.isArray(child)) {
22
+ out.push(...flattenChildren(child));
23
+ } else if (typeof child === "object" && child._vnode) {
24
+ out.push(child);
25
+ } else if (typeof child === "function") {
26
+ out.push(child);
27
+ } else {
28
+ out.push(String(child));
29
+ }
30
+ }
31
+ return out;
32
+ }
33
+
34
+ // packages/core/src/jsx-dev-runtime.js
35
+ function jsxDEV(type, props, key) {
36
+ if (props == null) return h(type, null);
37
+ const { children, ...rest } = props;
38
+ if (key !== void 0) rest.key = key;
39
+ if (children === void 0) return h(type, rest);
40
+ if (Array.isArray(children)) return h(type, rest, ...children);
41
+ return h(type, rest, children);
42
+ }
43
+ var jsx = jsxDEV;
44
+ var jsxs = jsxDEV;
45
+ export {
46
+ Fragment,
47
+ jsx,
48
+ jsxDEV,
49
+ jsxs
50
+ };
51
+ //# sourceMappingURL=jsx-dev-runtime.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/h.js", "../src/jsx-dev-runtime.js"],
4
+ "sourcesContent": ["// What Framework - Element Descriptors\n// Minimal element descriptor objects. No classes, no fibers, just plain objects.\n\n// h(tag, props, ...children) -> VNode\n// h(Component, props, ...children) -> VNode\n// VNode = { tag, props, children, key }\n\nconst EMPTY_OBJ = Object.create(null);\nconst EMPTY_ARR = [];\n\nexport function h(tag, props, ...children) {\n props = props || EMPTY_OBJ;\n const flat = flattenChildren(children);\n const key = props.key ?? null;\n\n // Strip key from props passed to component/element\n if (props.key !== undefined) {\n props = { ...props };\n delete props.key;\n }\n\n return { tag, props, children: flat, key, _vnode: true };\n}\n\n// Fragment \u2014 just returns children\nexport function Fragment({ children }) {\n return children;\n}\n\nfunction flattenChildren(children) {\n const out = [];\n for (let i = 0; i < children.length; i++) {\n const child = children[i];\n if (child == null || child === false || child === true) continue;\n if (Array.isArray(child)) {\n out.push(...flattenChildren(child));\n } else if (typeof child === 'object' && child._vnode) {\n out.push(child);\n } else if (typeof child === 'function') {\n // Reactive child \u2014 preserve function for fine-grained DOM updates\n out.push(child);\n } else {\n // Text node\n out.push(String(child));\n }\n }\n return out;\n}\n\n// JSX-like tagged template alternative (no build step needed)\n// html`<div class=\"foo\">${content}</div>`\n// Uses a simple parser, not full HTML \u2014 good enough for most cases.\n\nexport function html(strings, ...values) {\n const src = strings.reduce((acc, str, i) =>\n acc + str + (i < values.length ? `\\x00${i}\\x00` : ''), '');\n return parseTemplate(src, values);\n}\n\nfunction parseTemplate(src, values) {\n // Lightweight tagged template parser\n // Supports: <tag attr=\"val\">, <tag ...${spread}>, ${children}, self-closing\n src = src.trim();\n const nodes = [];\n let i = 0;\n\n while (i < src.length) {\n if (src[i] === '<') {\n const result = parseElement(src, i, values);\n if (result) {\n nodes.push(result.node);\n i = result.end;\n continue;\n }\n }\n // Text or interpolation\n const result = parseText(src, i, values);\n if (result.text) nodes.push(result.text);\n i = result.end;\n }\n\n return nodes.length === 1 ? nodes[0] : nodes;\n}\n\nfunction parseElement(src, start, values) {\n // Opening tag\n const openMatch = src.slice(start).match(/^<([a-zA-Z][a-zA-Z0-9-]*|[A-Z]\\w*)/);\n if (!openMatch) return null;\n\n const tag = openMatch[1];\n let i = start + openMatch[0].length;\n const props = {};\n\n // Parse attributes\n while (i < src.length) {\n // Skip whitespace\n while (i < src.length && /\\s/.test(src[i])) i++;\n\n // Self-closing or end of opening tag\n if (src.slice(i, i + 2) === '/>') {\n return { node: h(tag, Object.keys(props).length ? props : null), end: i + 2 };\n }\n if (src[i] === '>') {\n i++;\n break;\n }\n\n // Spread: ...${obj}\n if (src.slice(i, i + 3) === '...') {\n const placeholder = src.slice(i + 3).match(/^\\x00(\\d+)\\x00/);\n if (placeholder) {\n Object.assign(props, values[Number(placeholder[1])]);\n i += 3 + placeholder[0].length;\n continue;\n }\n }\n\n // Attribute: name=${val} or name=\"val\" or name\n const attrMatch = src.slice(i).match(/^([a-zA-Z_@:][a-zA-Z0-9_:.-]*)/);\n if (!attrMatch) break;\n\n const attrName = attrMatch[1];\n i += attrMatch[0].length;\n\n // Skip whitespace around =\n while (i < src.length && /\\s/.test(src[i])) i++;\n\n if (src[i] === '=') {\n i++;\n while (i < src.length && /\\s/.test(src[i])) i++;\n\n // Value is a placeholder\n const ph = src.slice(i).match(/^\\x00(\\d+)\\x00/);\n if (ph) {\n props[attrName] = values[Number(ph[1])];\n i += ph[0].length;\n } else if (src[i] === '\"' || src[i] === \"'\") {\n const q = src[i];\n i++;\n let val = '';\n while (i < src.length && src[i] !== q) {\n const tph = src.slice(i).match(/^\\x00(\\d+)\\x00/);\n if (tph) {\n val += String(values[Number(tph[1])]);\n i += tph[0].length;\n } else {\n val += src[i];\n i++;\n }\n }\n i++; // closing quote\n props[attrName] = val;\n }\n } else {\n props[attrName] = true;\n }\n }\n\n // Parse children until closing tag\n const children = [];\n const closeTag = `</${tag}>`;\n\n while (i < src.length) {\n if (src.slice(i, i + closeTag.length) === closeTag) {\n i += closeTag.length;\n break;\n }\n\n if (src[i] === '<') {\n const child = parseElement(src, i, values);\n if (child) {\n children.push(child.node);\n i = child.end;\n continue;\n }\n }\n\n const text = parseText(src, i, values);\n if (text.text != null) children.push(text.text);\n i = text.end;\n }\n\n return {\n node: h(tag, Object.keys(props).length ? props : null, ...children),\n end: i,\n };\n}\n\nfunction parseText(src, start, values) {\n let i = start;\n let text = '';\n\n while (i < src.length && src[i] !== '<') {\n const ph = src.slice(i).match(/^\\x00(\\d+)\\x00/);\n if (ph) {\n if (text.trim()) {\n return { text: text.trim(), end: i };\n }\n return { text: values[Number(ph[1])], end: i + ph[0].length };\n }\n text += src[i];\n i++;\n }\n\n return { text: text.trim() || null, end: i };\n}\n", "// What Framework \u2014 JSX Dev Runtime\n// Same as jsx-runtime but used in development mode by Vite.\n\nimport { h, Fragment } from './h.js';\n\nexport { Fragment };\n\nexport function jsxDEV(type, props, key) {\n if (props == null) return h(type, null);\n const { children, ...rest } = props;\n if (key !== undefined) rest.key = key;\n if (children === undefined) return h(type, rest);\n if (Array.isArray(children)) return h(type, rest, ...children);\n return h(type, rest, children);\n}\n\n// Also export jsx/jsxs for compatibility \u2014 some bundlers use these even in dev\nexport const jsx = jsxDEV;\nexport const jsxs = jsxDEV;\n"],
5
+ "mappings": ";AAOA,IAAM,YAAY,uBAAO,OAAO,IAAI;AAG7B,SAAS,EAAE,KAAK,UAAU,UAAU;AACzC,UAAQ,SAAS;AACjB,QAAM,OAAO,gBAAgB,QAAQ;AACrC,QAAM,MAAM,MAAM,OAAO;AAGzB,MAAI,MAAM,QAAQ,QAAW;AAC3B,YAAQ,EAAE,GAAG,MAAM;AACnB,WAAO,MAAM;AAAA,EACf;AAEA,SAAO,EAAE,KAAK,OAAO,UAAU,MAAM,KAAK,QAAQ,KAAK;AACzD;AAGO,SAAS,SAAS,EAAE,SAAS,GAAG;AACrC,SAAO;AACT;AAEA,SAAS,gBAAgB,UAAU;AACjC,QAAM,MAAM,CAAC;AACb,WAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACxC,UAAM,QAAQ,SAAS,CAAC;AACxB,QAAI,SAAS,QAAQ,UAAU,SAAS,UAAU,KAAM;AACxD,QAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,UAAI,KAAK,GAAG,gBAAgB,KAAK,CAAC;AAAA,IACpC,WAAW,OAAO,UAAU,YAAY,MAAM,QAAQ;AACpD,UAAI,KAAK,KAAK;AAAA,IAChB,WAAW,OAAO,UAAU,YAAY;AAEtC,UAAI,KAAK,KAAK;AAAA,IAChB,OAAO;AAEL,UAAI,KAAK,OAAO,KAAK,CAAC;AAAA,IACxB;AAAA,EACF;AACA,SAAO;AACT;;;ACxCO,SAAS,OAAO,MAAM,OAAO,KAAK;AACvC,MAAI,SAAS,KAAM,QAAO,EAAE,MAAM,IAAI;AACtC,QAAM,EAAE,UAAU,GAAG,KAAK,IAAI;AAC9B,MAAI,QAAQ,OAAW,MAAK,MAAM;AAClC,MAAI,aAAa,OAAW,QAAO,EAAE,MAAM,IAAI;AAC/C,MAAI,MAAM,QAAQ,QAAQ,EAAG,QAAO,EAAE,MAAM,MAAM,GAAG,QAAQ;AAC7D,SAAO,EAAE,MAAM,MAAM,QAAQ;AAC/B;AAGO,IAAM,MAAM;AACZ,IAAM,OAAO;",
6
+ "names": []
7
+ }
@@ -0,0 +1,2 @@
1
+ var f=Object.create(null);function h(n,t,...l){t=t||f;let e=o(l),i=t.key??null;return t.key!==void 0&&(t={...t},delete t.key),{tag:n,props:t,children:e,key:i,_vnode:!0}}function r({children:n}){return n}function o(n){let t=[];for(let l=0;l<n.length;l++){let e=n[l];e==null||e===!1||e===!0||(Array.isArray(e)?t.push(...o(e)):typeof e=="object"&&e._vnode||typeof e=="function"?t.push(e):t.push(String(e)))}return t}function u(n,t,l){if(t==null)return h(n,null);let{children:e,...i}=t;return l!==void 0&&(i.key=l),e===void 0?h(n,i):Array.isArray(e)?h(n,i,...e):h(n,i,e)}var a=u,x=u;export{r as Fragment,a as jsx,u as jsxDEV,x as jsxs};
2
+ //# sourceMappingURL=jsx-dev-runtime.min.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/h.js", "../src/jsx-dev-runtime.js"],
4
+ "sourcesContent": ["// What Framework - Element Descriptors\n// Minimal element descriptor objects. No classes, no fibers, just plain objects.\n\n// h(tag, props, ...children) -> VNode\n// h(Component, props, ...children) -> VNode\n// VNode = { tag, props, children, key }\n\nconst EMPTY_OBJ = Object.create(null);\nconst EMPTY_ARR = [];\n\nexport function h(tag, props, ...children) {\n props = props || EMPTY_OBJ;\n const flat = flattenChildren(children);\n const key = props.key ?? null;\n\n // Strip key from props passed to component/element\n if (props.key !== undefined) {\n props = { ...props };\n delete props.key;\n }\n\n return { tag, props, children: flat, key, _vnode: true };\n}\n\n// Fragment \u2014 just returns children\nexport function Fragment({ children }) {\n return children;\n}\n\nfunction flattenChildren(children) {\n const out = [];\n for (let i = 0; i < children.length; i++) {\n const child = children[i];\n if (child == null || child === false || child === true) continue;\n if (Array.isArray(child)) {\n out.push(...flattenChildren(child));\n } else if (typeof child === 'object' && child._vnode) {\n out.push(child);\n } else if (typeof child === 'function') {\n // Reactive child \u2014 preserve function for fine-grained DOM updates\n out.push(child);\n } else {\n // Text node\n out.push(String(child));\n }\n }\n return out;\n}\n\n// JSX-like tagged template alternative (no build step needed)\n// html`<div class=\"foo\">${content}</div>`\n// Uses a simple parser, not full HTML \u2014 good enough for most cases.\n\nexport function html(strings, ...values) {\n const src = strings.reduce((acc, str, i) =>\n acc + str + (i < values.length ? `\\x00${i}\\x00` : ''), '');\n return parseTemplate(src, values);\n}\n\nfunction parseTemplate(src, values) {\n // Lightweight tagged template parser\n // Supports: <tag attr=\"val\">, <tag ...${spread}>, ${children}, self-closing\n src = src.trim();\n const nodes = [];\n let i = 0;\n\n while (i < src.length) {\n if (src[i] === '<') {\n const result = parseElement(src, i, values);\n if (result) {\n nodes.push(result.node);\n i = result.end;\n continue;\n }\n }\n // Text or interpolation\n const result = parseText(src, i, values);\n if (result.text) nodes.push(result.text);\n i = result.end;\n }\n\n return nodes.length === 1 ? nodes[0] : nodes;\n}\n\nfunction parseElement(src, start, values) {\n // Opening tag\n const openMatch = src.slice(start).match(/^<([a-zA-Z][a-zA-Z0-9-]*|[A-Z]\\w*)/);\n if (!openMatch) return null;\n\n const tag = openMatch[1];\n let i = start + openMatch[0].length;\n const props = {};\n\n // Parse attributes\n while (i < src.length) {\n // Skip whitespace\n while (i < src.length && /\\s/.test(src[i])) i++;\n\n // Self-closing or end of opening tag\n if (src.slice(i, i + 2) === '/>') {\n return { node: h(tag, Object.keys(props).length ? props : null), end: i + 2 };\n }\n if (src[i] === '>') {\n i++;\n break;\n }\n\n // Spread: ...${obj}\n if (src.slice(i, i + 3) === '...') {\n const placeholder = src.slice(i + 3).match(/^\\x00(\\d+)\\x00/);\n if (placeholder) {\n Object.assign(props, values[Number(placeholder[1])]);\n i += 3 + placeholder[0].length;\n continue;\n }\n }\n\n // Attribute: name=${val} or name=\"val\" or name\n const attrMatch = src.slice(i).match(/^([a-zA-Z_@:][a-zA-Z0-9_:.-]*)/);\n if (!attrMatch) break;\n\n const attrName = attrMatch[1];\n i += attrMatch[0].length;\n\n // Skip whitespace around =\n while (i < src.length && /\\s/.test(src[i])) i++;\n\n if (src[i] === '=') {\n i++;\n while (i < src.length && /\\s/.test(src[i])) i++;\n\n // Value is a placeholder\n const ph = src.slice(i).match(/^\\x00(\\d+)\\x00/);\n if (ph) {\n props[attrName] = values[Number(ph[1])];\n i += ph[0].length;\n } else if (src[i] === '\"' || src[i] === \"'\") {\n const q = src[i];\n i++;\n let val = '';\n while (i < src.length && src[i] !== q) {\n const tph = src.slice(i).match(/^\\x00(\\d+)\\x00/);\n if (tph) {\n val += String(values[Number(tph[1])]);\n i += tph[0].length;\n } else {\n val += src[i];\n i++;\n }\n }\n i++; // closing quote\n props[attrName] = val;\n }\n } else {\n props[attrName] = true;\n }\n }\n\n // Parse children until closing tag\n const children = [];\n const closeTag = `</${tag}>`;\n\n while (i < src.length) {\n if (src.slice(i, i + closeTag.length) === closeTag) {\n i += closeTag.length;\n break;\n }\n\n if (src[i] === '<') {\n const child = parseElement(src, i, values);\n if (child) {\n children.push(child.node);\n i = child.end;\n continue;\n }\n }\n\n const text = parseText(src, i, values);\n if (text.text != null) children.push(text.text);\n i = text.end;\n }\n\n return {\n node: h(tag, Object.keys(props).length ? props : null, ...children),\n end: i,\n };\n}\n\nfunction parseText(src, start, values) {\n let i = start;\n let text = '';\n\n while (i < src.length && src[i] !== '<') {\n const ph = src.slice(i).match(/^\\x00(\\d+)\\x00/);\n if (ph) {\n if (text.trim()) {\n return { text: text.trim(), end: i };\n }\n return { text: values[Number(ph[1])], end: i + ph[0].length };\n }\n text += src[i];\n i++;\n }\n\n return { text: text.trim() || null, end: i };\n}\n", "// What Framework \u2014 JSX Dev Runtime\n// Same as jsx-runtime but used in development mode by Vite.\n\nimport { h, Fragment } from './h.js';\n\nexport { Fragment };\n\nexport function jsxDEV(type, props, key) {\n if (props == null) return h(type, null);\n const { children, ...rest } = props;\n if (key !== undefined) rest.key = key;\n if (children === undefined) return h(type, rest);\n if (Array.isArray(children)) return h(type, rest, ...children);\n return h(type, rest, children);\n}\n\n// Also export jsx/jsxs for compatibility \u2014 some bundlers use these even in dev\nexport const jsx = jsxDEV;\nexport const jsxs = jsxDEV;\n"],
5
+ "mappings": "AAOA,IAAMA,EAAY,OAAO,OAAO,IAAI,EAG7B,SAAS,EAAEC,EAAKC,KAAUC,EAAU,CACzCD,EAAQA,GAASE,EACjB,IAAMC,EAAOC,EAAgBH,CAAQ,EAC/BI,EAAML,EAAM,KAAO,KAGzB,OAAIA,EAAM,MAAQ,SAChBA,EAAQ,CAAE,GAAGA,CAAM,EACnB,OAAOA,EAAM,KAGR,CAAE,IAAAD,EAAK,MAAAC,EAAO,SAAUG,EAAM,IAAAE,EAAK,OAAQ,EAAK,CACzD,CAGO,SAASC,EAAS,CAAE,SAAAL,CAAS,EAAG,CACrC,OAAOA,CACT,CAEA,SAASG,EAAgBH,EAAU,CACjC,IAAMM,EAAM,CAAC,EACb,QAASC,EAAI,EAAGA,EAAIP,EAAS,OAAQO,IAAK,CACxC,IAAMC,EAAQR,EAASO,CAAC,EACpBC,GAAS,MAAQA,IAAU,IAASA,IAAU,KAC9C,MAAM,QAAQA,CAAK,EACrBF,EAAI,KAAK,GAAGH,EAAgBK,CAAK,CAAC,EACzB,OAAOA,GAAU,UAAYA,EAAM,QAEnC,OAAOA,GAAU,WAD1BF,EAAI,KAAKE,CAAK,EAMdF,EAAI,KAAK,OAAOE,CAAK,CAAC,EAE1B,CACA,OAAOF,CACT,CCxCO,SAASG,EAAOC,EAAMC,EAAOC,EAAK,CACvC,GAAID,GAAS,KAAM,OAAO,EAAED,EAAM,IAAI,EACtC,GAAM,CAAE,SAAAG,EAAU,GAAGC,CAAK,EAAIH,EAE9B,OADIC,IAAQ,SAAWE,EAAK,IAAMF,GAC9BC,IAAa,OAAkB,EAAEH,EAAMI,CAAI,EAC3C,MAAM,QAAQD,CAAQ,EAAU,EAAEH,EAAMI,EAAM,GAAGD,CAAQ,EACtD,EAAEH,EAAMI,EAAMD,CAAQ,CAC/B,CAGO,IAAME,EAAMN,EACNO,EAAOP",
6
+ "names": ["EMPTY_OBJ", "tag", "props", "children", "EMPTY_OBJ", "flat", "flattenChildren", "key", "Fragment", "out", "i", "child", "jsxDEV", "type", "props", "key", "children", "rest", "jsx", "jsxs"]
7
+ }
@@ -0,0 +1,49 @@
1
+ // packages/core/src/h.js
2
+ var EMPTY_OBJ = /* @__PURE__ */ Object.create(null);
3
+ function h(tag, props, ...children) {
4
+ props = props || EMPTY_OBJ;
5
+ const flat = flattenChildren(children);
6
+ const key = props.key ?? null;
7
+ if (props.key !== void 0) {
8
+ props = { ...props };
9
+ delete props.key;
10
+ }
11
+ return { tag, props, children: flat, key, _vnode: true };
12
+ }
13
+ function Fragment({ children }) {
14
+ return children;
15
+ }
16
+ function flattenChildren(children) {
17
+ const out = [];
18
+ for (let i = 0; i < children.length; i++) {
19
+ const child = children[i];
20
+ if (child == null || child === false || child === true) continue;
21
+ if (Array.isArray(child)) {
22
+ out.push(...flattenChildren(child));
23
+ } else if (typeof child === "object" && child._vnode) {
24
+ out.push(child);
25
+ } else if (typeof child === "function") {
26
+ out.push(child);
27
+ } else {
28
+ out.push(String(child));
29
+ }
30
+ }
31
+ return out;
32
+ }
33
+
34
+ // packages/core/src/jsx-runtime.js
35
+ function jsx(type, props, key) {
36
+ if (props == null) return h(type, null);
37
+ const { children, ...rest } = props;
38
+ if (key !== void 0) rest.key = key;
39
+ if (children === void 0) return h(type, rest);
40
+ if (Array.isArray(children)) return h(type, rest, ...children);
41
+ return h(type, rest, children);
42
+ }
43
+ var jsxs = jsx;
44
+ export {
45
+ Fragment,
46
+ jsx,
47
+ jsxs
48
+ };
49
+ //# sourceMappingURL=jsx-runtime.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/h.js", "../src/jsx-runtime.js"],
4
+ "sourcesContent": ["// What Framework - Element Descriptors\n// Minimal element descriptor objects. No classes, no fibers, just plain objects.\n\n// h(tag, props, ...children) -> VNode\n// h(Component, props, ...children) -> VNode\n// VNode = { tag, props, children, key }\n\nconst EMPTY_OBJ = Object.create(null);\nconst EMPTY_ARR = [];\n\nexport function h(tag, props, ...children) {\n props = props || EMPTY_OBJ;\n const flat = flattenChildren(children);\n const key = props.key ?? null;\n\n // Strip key from props passed to component/element\n if (props.key !== undefined) {\n props = { ...props };\n delete props.key;\n }\n\n return { tag, props, children: flat, key, _vnode: true };\n}\n\n// Fragment \u2014 just returns children\nexport function Fragment({ children }) {\n return children;\n}\n\nfunction flattenChildren(children) {\n const out = [];\n for (let i = 0; i < children.length; i++) {\n const child = children[i];\n if (child == null || child === false || child === true) continue;\n if (Array.isArray(child)) {\n out.push(...flattenChildren(child));\n } else if (typeof child === 'object' && child._vnode) {\n out.push(child);\n } else if (typeof child === 'function') {\n // Reactive child \u2014 preserve function for fine-grained DOM updates\n out.push(child);\n } else {\n // Text node\n out.push(String(child));\n }\n }\n return out;\n}\n\n// JSX-like tagged template alternative (no build step needed)\n// html`<div class=\"foo\">${content}</div>`\n// Uses a simple parser, not full HTML \u2014 good enough for most cases.\n\nexport function html(strings, ...values) {\n const src = strings.reduce((acc, str, i) =>\n acc + str + (i < values.length ? `\\x00${i}\\x00` : ''), '');\n return parseTemplate(src, values);\n}\n\nfunction parseTemplate(src, values) {\n // Lightweight tagged template parser\n // Supports: <tag attr=\"val\">, <tag ...${spread}>, ${children}, self-closing\n src = src.trim();\n const nodes = [];\n let i = 0;\n\n while (i < src.length) {\n if (src[i] === '<') {\n const result = parseElement(src, i, values);\n if (result) {\n nodes.push(result.node);\n i = result.end;\n continue;\n }\n }\n // Text or interpolation\n const result = parseText(src, i, values);\n if (result.text) nodes.push(result.text);\n i = result.end;\n }\n\n return nodes.length === 1 ? nodes[0] : nodes;\n}\n\nfunction parseElement(src, start, values) {\n // Opening tag\n const openMatch = src.slice(start).match(/^<([a-zA-Z][a-zA-Z0-9-]*|[A-Z]\\w*)/);\n if (!openMatch) return null;\n\n const tag = openMatch[1];\n let i = start + openMatch[0].length;\n const props = {};\n\n // Parse attributes\n while (i < src.length) {\n // Skip whitespace\n while (i < src.length && /\\s/.test(src[i])) i++;\n\n // Self-closing or end of opening tag\n if (src.slice(i, i + 2) === '/>') {\n return { node: h(tag, Object.keys(props).length ? props : null), end: i + 2 };\n }\n if (src[i] === '>') {\n i++;\n break;\n }\n\n // Spread: ...${obj}\n if (src.slice(i, i + 3) === '...') {\n const placeholder = src.slice(i + 3).match(/^\\x00(\\d+)\\x00/);\n if (placeholder) {\n Object.assign(props, values[Number(placeholder[1])]);\n i += 3 + placeholder[0].length;\n continue;\n }\n }\n\n // Attribute: name=${val} or name=\"val\" or name\n const attrMatch = src.slice(i).match(/^([a-zA-Z_@:][a-zA-Z0-9_:.-]*)/);\n if (!attrMatch) break;\n\n const attrName = attrMatch[1];\n i += attrMatch[0].length;\n\n // Skip whitespace around =\n while (i < src.length && /\\s/.test(src[i])) i++;\n\n if (src[i] === '=') {\n i++;\n while (i < src.length && /\\s/.test(src[i])) i++;\n\n // Value is a placeholder\n const ph = src.slice(i).match(/^\\x00(\\d+)\\x00/);\n if (ph) {\n props[attrName] = values[Number(ph[1])];\n i += ph[0].length;\n } else if (src[i] === '\"' || src[i] === \"'\") {\n const q = src[i];\n i++;\n let val = '';\n while (i < src.length && src[i] !== q) {\n const tph = src.slice(i).match(/^\\x00(\\d+)\\x00/);\n if (tph) {\n val += String(values[Number(tph[1])]);\n i += tph[0].length;\n } else {\n val += src[i];\n i++;\n }\n }\n i++; // closing quote\n props[attrName] = val;\n }\n } else {\n props[attrName] = true;\n }\n }\n\n // Parse children until closing tag\n const children = [];\n const closeTag = `</${tag}>`;\n\n while (i < src.length) {\n if (src.slice(i, i + closeTag.length) === closeTag) {\n i += closeTag.length;\n break;\n }\n\n if (src[i] === '<') {\n const child = parseElement(src, i, values);\n if (child) {\n children.push(child.node);\n i = child.end;\n continue;\n }\n }\n\n const text = parseText(src, i, values);\n if (text.text != null) children.push(text.text);\n i = text.end;\n }\n\n return {\n node: h(tag, Object.keys(props).length ? props : null, ...children),\n end: i,\n };\n}\n\nfunction parseText(src, start, values) {\n let i = start;\n let text = '';\n\n while (i < src.length && src[i] !== '<') {\n const ph = src.slice(i).match(/^\\x00(\\d+)\\x00/);\n if (ph) {\n if (text.trim()) {\n return { text: text.trim(), end: i };\n }\n return { text: values[Number(ph[1])], end: i + ph[0].length };\n }\n text += src[i];\n i++;\n }\n\n return { text: text.trim() || null, end: i };\n}\n", "// What Framework \u2014 JSX Automatic Runtime\n// Used by: jsxImportSource: \"what-framework\" (or \"what-core\")\n// Vite/esbuild import this automatically when using the \"react-jsx\" transform.\n\nimport { h, Fragment } from './h.js';\n\nexport { Fragment };\n\n// Automatic JSX transform signature: jsx(type, { children, ...props }, key)\n// What's h() signature: h(type, props, ...children)\nexport function jsx(type, props, key) {\n if (props == null) return h(type, null);\n const { children, ...rest } = props;\n if (key !== undefined) rest.key = key;\n if (children === undefined) return h(type, rest);\n if (Array.isArray(children)) return h(type, rest, ...children);\n return h(type, rest, children);\n}\n\n// jsxs = jsx for static children (multiple). Same behavior for What.\nexport const jsxs = jsx;\n"],
5
+ "mappings": ";AAOA,IAAM,YAAY,uBAAO,OAAO,IAAI;AAG7B,SAAS,EAAE,KAAK,UAAU,UAAU;AACzC,UAAQ,SAAS;AACjB,QAAM,OAAO,gBAAgB,QAAQ;AACrC,QAAM,MAAM,MAAM,OAAO;AAGzB,MAAI,MAAM,QAAQ,QAAW;AAC3B,YAAQ,EAAE,GAAG,MAAM;AACnB,WAAO,MAAM;AAAA,EACf;AAEA,SAAO,EAAE,KAAK,OAAO,UAAU,MAAM,KAAK,QAAQ,KAAK;AACzD;AAGO,SAAS,SAAS,EAAE,SAAS,GAAG;AACrC,SAAO;AACT;AAEA,SAAS,gBAAgB,UAAU;AACjC,QAAM,MAAM,CAAC;AACb,WAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACxC,UAAM,QAAQ,SAAS,CAAC;AACxB,QAAI,SAAS,QAAQ,UAAU,SAAS,UAAU,KAAM;AACxD,QAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,UAAI,KAAK,GAAG,gBAAgB,KAAK,CAAC;AAAA,IACpC,WAAW,OAAO,UAAU,YAAY,MAAM,QAAQ;AACpD,UAAI,KAAK,KAAK;AAAA,IAChB,WAAW,OAAO,UAAU,YAAY;AAEtC,UAAI,KAAK,KAAK;AAAA,IAChB,OAAO;AAEL,UAAI,KAAK,OAAO,KAAK,CAAC;AAAA,IACxB;AAAA,EACF;AACA,SAAO;AACT;;;ACrCO,SAAS,IAAI,MAAM,OAAO,KAAK;AACpC,MAAI,SAAS,KAAM,QAAO,EAAE,MAAM,IAAI;AACtC,QAAM,EAAE,UAAU,GAAG,KAAK,IAAI;AAC9B,MAAI,QAAQ,OAAW,MAAK,MAAM;AAClC,MAAI,aAAa,OAAW,QAAO,EAAE,MAAM,IAAI;AAC/C,MAAI,MAAM,QAAQ,QAAQ,EAAG,QAAO,EAAE,MAAM,MAAM,GAAG,QAAQ;AAC7D,SAAO,EAAE,MAAM,MAAM,QAAQ;AAC/B;AAGO,IAAM,OAAO;",
6
+ "names": []
7
+ }
@@ -0,0 +1,2 @@
1
+ var u=Object.create(null);function h(n,t,...l){t=t||u;let e=o(l),i=t.key??null;return t.key!==void 0&&(t={...t},delete t.key),{tag:n,props:t,children:e,key:i,_vnode:!0}}function f({children:n}){return n}function o(n){let t=[];for(let l=0;l<n.length;l++){let e=n[l];e==null||e===!1||e===!0||(Array.isArray(e)?t.push(...o(e)):typeof e=="object"&&e._vnode||typeof e=="function"?t.push(e):t.push(String(e)))}return t}function r(n,t,l){if(t==null)return h(n,null);let{children:e,...i}=t;return l!==void 0&&(i.key=l),e===void 0?h(n,i):Array.isArray(e)?h(n,i,...e):h(n,i,e)}var a=r;export{f as Fragment,r as jsx,a as jsxs};
2
+ //# sourceMappingURL=jsx-runtime.min.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/h.js", "../src/jsx-runtime.js"],
4
+ "sourcesContent": ["// What Framework - Element Descriptors\n// Minimal element descriptor objects. No classes, no fibers, just plain objects.\n\n// h(tag, props, ...children) -> VNode\n// h(Component, props, ...children) -> VNode\n// VNode = { tag, props, children, key }\n\nconst EMPTY_OBJ = Object.create(null);\nconst EMPTY_ARR = [];\n\nexport function h(tag, props, ...children) {\n props = props || EMPTY_OBJ;\n const flat = flattenChildren(children);\n const key = props.key ?? null;\n\n // Strip key from props passed to component/element\n if (props.key !== undefined) {\n props = { ...props };\n delete props.key;\n }\n\n return { tag, props, children: flat, key, _vnode: true };\n}\n\n// Fragment \u2014 just returns children\nexport function Fragment({ children }) {\n return children;\n}\n\nfunction flattenChildren(children) {\n const out = [];\n for (let i = 0; i < children.length; i++) {\n const child = children[i];\n if (child == null || child === false || child === true) continue;\n if (Array.isArray(child)) {\n out.push(...flattenChildren(child));\n } else if (typeof child === 'object' && child._vnode) {\n out.push(child);\n } else if (typeof child === 'function') {\n // Reactive child \u2014 preserve function for fine-grained DOM updates\n out.push(child);\n } else {\n // Text node\n out.push(String(child));\n }\n }\n return out;\n}\n\n// JSX-like tagged template alternative (no build step needed)\n// html`<div class=\"foo\">${content}</div>`\n// Uses a simple parser, not full HTML \u2014 good enough for most cases.\n\nexport function html(strings, ...values) {\n const src = strings.reduce((acc, str, i) =>\n acc + str + (i < values.length ? `\\x00${i}\\x00` : ''), '');\n return parseTemplate(src, values);\n}\n\nfunction parseTemplate(src, values) {\n // Lightweight tagged template parser\n // Supports: <tag attr=\"val\">, <tag ...${spread}>, ${children}, self-closing\n src = src.trim();\n const nodes = [];\n let i = 0;\n\n while (i < src.length) {\n if (src[i] === '<') {\n const result = parseElement(src, i, values);\n if (result) {\n nodes.push(result.node);\n i = result.end;\n continue;\n }\n }\n // Text or interpolation\n const result = parseText(src, i, values);\n if (result.text) nodes.push(result.text);\n i = result.end;\n }\n\n return nodes.length === 1 ? nodes[0] : nodes;\n}\n\nfunction parseElement(src, start, values) {\n // Opening tag\n const openMatch = src.slice(start).match(/^<([a-zA-Z][a-zA-Z0-9-]*|[A-Z]\\w*)/);\n if (!openMatch) return null;\n\n const tag = openMatch[1];\n let i = start + openMatch[0].length;\n const props = {};\n\n // Parse attributes\n while (i < src.length) {\n // Skip whitespace\n while (i < src.length && /\\s/.test(src[i])) i++;\n\n // Self-closing or end of opening tag\n if (src.slice(i, i + 2) === '/>') {\n return { node: h(tag, Object.keys(props).length ? props : null), end: i + 2 };\n }\n if (src[i] === '>') {\n i++;\n break;\n }\n\n // Spread: ...${obj}\n if (src.slice(i, i + 3) === '...') {\n const placeholder = src.slice(i + 3).match(/^\\x00(\\d+)\\x00/);\n if (placeholder) {\n Object.assign(props, values[Number(placeholder[1])]);\n i += 3 + placeholder[0].length;\n continue;\n }\n }\n\n // Attribute: name=${val} or name=\"val\" or name\n const attrMatch = src.slice(i).match(/^([a-zA-Z_@:][a-zA-Z0-9_:.-]*)/);\n if (!attrMatch) break;\n\n const attrName = attrMatch[1];\n i += attrMatch[0].length;\n\n // Skip whitespace around =\n while (i < src.length && /\\s/.test(src[i])) i++;\n\n if (src[i] === '=') {\n i++;\n while (i < src.length && /\\s/.test(src[i])) i++;\n\n // Value is a placeholder\n const ph = src.slice(i).match(/^\\x00(\\d+)\\x00/);\n if (ph) {\n props[attrName] = values[Number(ph[1])];\n i += ph[0].length;\n } else if (src[i] === '\"' || src[i] === \"'\") {\n const q = src[i];\n i++;\n let val = '';\n while (i < src.length && src[i] !== q) {\n const tph = src.slice(i).match(/^\\x00(\\d+)\\x00/);\n if (tph) {\n val += String(values[Number(tph[1])]);\n i += tph[0].length;\n } else {\n val += src[i];\n i++;\n }\n }\n i++; // closing quote\n props[attrName] = val;\n }\n } else {\n props[attrName] = true;\n }\n }\n\n // Parse children until closing tag\n const children = [];\n const closeTag = `</${tag}>`;\n\n while (i < src.length) {\n if (src.slice(i, i + closeTag.length) === closeTag) {\n i += closeTag.length;\n break;\n }\n\n if (src[i] === '<') {\n const child = parseElement(src, i, values);\n if (child) {\n children.push(child.node);\n i = child.end;\n continue;\n }\n }\n\n const text = parseText(src, i, values);\n if (text.text != null) children.push(text.text);\n i = text.end;\n }\n\n return {\n node: h(tag, Object.keys(props).length ? props : null, ...children),\n end: i,\n };\n}\n\nfunction parseText(src, start, values) {\n let i = start;\n let text = '';\n\n while (i < src.length && src[i] !== '<') {\n const ph = src.slice(i).match(/^\\x00(\\d+)\\x00/);\n if (ph) {\n if (text.trim()) {\n return { text: text.trim(), end: i };\n }\n return { text: values[Number(ph[1])], end: i + ph[0].length };\n }\n text += src[i];\n i++;\n }\n\n return { text: text.trim() || null, end: i };\n}\n", "// What Framework \u2014 JSX Automatic Runtime\n// Used by: jsxImportSource: \"what-framework\" (or \"what-core\")\n// Vite/esbuild import this automatically when using the \"react-jsx\" transform.\n\nimport { h, Fragment } from './h.js';\n\nexport { Fragment };\n\n// Automatic JSX transform signature: jsx(type, { children, ...props }, key)\n// What's h() signature: h(type, props, ...children)\nexport function jsx(type, props, key) {\n if (props == null) return h(type, null);\n const { children, ...rest } = props;\n if (key !== undefined) rest.key = key;\n if (children === undefined) return h(type, rest);\n if (Array.isArray(children)) return h(type, rest, ...children);\n return h(type, rest, children);\n}\n\n// jsxs = jsx for static children (multiple). Same behavior for What.\nexport const jsxs = jsx;\n"],
5
+ "mappings": "AAOA,IAAMA,EAAY,OAAO,OAAO,IAAI,EAG7B,SAAS,EAAEC,EAAKC,KAAUC,EAAU,CACzCD,EAAQA,GAASE,EACjB,IAAMC,EAAOC,EAAgBH,CAAQ,EAC/BI,EAAML,EAAM,KAAO,KAGzB,OAAIA,EAAM,MAAQ,SAChBA,EAAQ,CAAE,GAAGA,CAAM,EACnB,OAAOA,EAAM,KAGR,CAAE,IAAAD,EAAK,MAAAC,EAAO,SAAUG,EAAM,IAAAE,EAAK,OAAQ,EAAK,CACzD,CAGO,SAASC,EAAS,CAAE,SAAAL,CAAS,EAAG,CACrC,OAAOA,CACT,CAEA,SAASG,EAAgBH,EAAU,CACjC,IAAMM,EAAM,CAAC,EACb,QAASC,EAAI,EAAGA,EAAIP,EAAS,OAAQO,IAAK,CACxC,IAAMC,EAAQR,EAASO,CAAC,EACpBC,GAAS,MAAQA,IAAU,IAASA,IAAU,KAC9C,MAAM,QAAQA,CAAK,EACrBF,EAAI,KAAK,GAAGH,EAAgBK,CAAK,CAAC,EACzB,OAAOA,GAAU,UAAYA,EAAM,QAEnC,OAAOA,GAAU,WAD1BF,EAAI,KAAKE,CAAK,EAMdF,EAAI,KAAK,OAAOE,CAAK,CAAC,EAE1B,CACA,OAAOF,CACT,CCrCO,SAASG,EAAIC,EAAMC,EAAOC,EAAK,CACpC,GAAID,GAAS,KAAM,OAAO,EAAED,EAAM,IAAI,EACtC,GAAM,CAAE,SAAAG,EAAU,GAAGC,CAAK,EAAIH,EAE9B,OADIC,IAAQ,SAAWE,EAAK,IAAMF,GAC9BC,IAAa,OAAkB,EAAEH,EAAMI,CAAI,EAC3C,MAAM,QAAQD,CAAQ,EAAU,EAAEH,EAAMI,EAAM,GAAGD,CAAQ,EACtD,EAAEH,EAAMI,EAAMD,CAAQ,CAC/B,CAGO,IAAME,EAAON",
6
+ "names": ["EMPTY_OBJ", "tag", "props", "children", "EMPTY_OBJ", "flat", "flattenChildren", "key", "Fragment", "out", "i", "child", "jsx", "type", "props", "key", "children", "rest", "jsxs"]
7
+ }
package/dist/reactive.js CHANGED
@@ -1,9 +1,19 @@
1
- export const __DEV__ = typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production' || true;
1
+ export const __DEV__ = typeof process !== 'undefined'
2
+ ? process.env?.NODE_ENV !== 'production'
3
+ : true;
4
+ export let __devtools = null;
5
+ export function __setDevToolsHooks(hooks) {
6
+ if (__DEV__) __devtools = hooks;
7
+ }
2
8
  let currentEffect = null;
3
9
  let currentRoot = null;
10
+ let currentOwner = null;
4
11
  let batchDepth = 0;
5
12
  let pendingEffects = [];
6
- export function signal(initial) {
13
+ const subSetOwner = new WeakMap();
14
+ const NEEDS_UPSTREAM = Symbol('needs_upstream');
15
+ let iterativeEvalStack = null;
16
+ export function signal(initial, debugName) {
7
17
  let value = initial;
8
18
  const subs = new Set();
9
19
  function sig(...args) {
@@ -17,12 +27,14 @@ return value;
17
27
  const nextVal = typeof args[0] === 'function' ? args[0](value) : args[0];
18
28
  if (Object.is(value, nextVal)) return;
19
29
  value = nextVal;
30
+ if (__DEV__ && __devtools) __devtools.onSignalUpdate(sig);
20
31
  notify(subs);
21
32
  }
22
33
  sig.set = (next) => {
23
34
  const nextVal = typeof next === 'function' ? next(value) : next;
24
35
  if (Object.is(value, nextVal)) return;
25
36
  value = nextVal;
37
+ if (__DEV__ && __devtools) __devtools.onSignalUpdate(sig);
26
38
  notify(subs);
27
39
  };
28
40
  sig.peek = () => value;
@@ -30,6 +42,11 @@ sig.subscribe = (fn) => {
30
42
  return effect(() => fn(sig()));
31
43
  };
32
44
  sig._signal = true;
45
+ if (__DEV__) {
46
+ sig._subs = subs;
47
+ if (debugName) sig._debugName = debugName;
48
+ }
49
+ if (__DEV__ && __devtools) __devtools.onSignalCreate(sig);
33
50
  return sig;
34
51
  }
35
52
  export function computed(fn) {
@@ -39,12 +56,18 @@ const inner = _createEffect(() => {
39
56
  value = fn();
40
57
  dirty = false;
41
58
  }, true);
59
+ inner._level = 1;
60
+ inner._computed = true;
61
+ inner._computedSubs = subs;
62
+ subSetOwner.set(subs, inner);
63
+ inner._markDirty = () => { dirty = true; };
64
+ inner._isDirty = () => dirty;
42
65
  function read() {
43
66
  if (currentEffect) {
44
67
  subs.add(currentEffect);
45
68
  currentEffect.deps.push(subs);
46
69
  }
47
- if (dirty) _runEffect(inner);
70
+ if (dirty) _evaluateComputed(inner);
48
71
  return value;
49
72
  }
50
73
  inner._onNotify = () => {
@@ -53,13 +76,53 @@ notify(subs);
53
76
  };
54
77
  read._signal = true;
55
78
  read.peek = () => {
56
- if (dirty) _runEffect(inner);
79
+ if (dirty) _evaluateComputed(inner);
57
80
  return value;
58
81
  };
59
82
  return read;
60
83
  }
84
+ function _evaluateComputed(computedEffect) {
85
+ if (iterativeEvalStack !== null) {
86
+ iterativeEvalStack.push(computedEffect);
87
+ throw NEEDS_UPSTREAM;
88
+ }
89
+ const stack = [computedEffect];
90
+ iterativeEvalStack = stack;
91
+ try {
92
+ while (stack.length > 0) {
93
+ const current = stack[stack.length - 1];
94
+ if (!current._isDirty || !current._isDirty()) {
95
+ stack.pop();
96
+ continue;
97
+ }
98
+ try {
99
+ _runEffect(current);
100
+ _updateLevel(current);
101
+ stack.pop();
102
+ } catch (err) {
103
+ if (err === NEEDS_UPSTREAM) {
104
+ current._markDirty();
105
+ } else {
106
+ throw err;
107
+ }
108
+ }
109
+ }
110
+ } finally {
111
+ iterativeEvalStack = null;
112
+ }
113
+ }
114
+ function _updateLevel(e) {
115
+ let maxDepLevel = 0;
116
+ for (let i = 0; i < e.deps.length; i++) {
117
+ const owner = subSetOwner.get(e.deps[i]);
118
+ const depLevel = owner ? owner._level : 0;
119
+ if (depLevel > maxDepLevel) maxDepLevel = depLevel;
120
+ }
121
+ e._level = maxDepLevel + 1;
122
+ }
61
123
  export function effect(fn, opts) {
62
124
  const e = _createEffect(fn);
125
+ e._level = 1;
63
126
  const prev = currentEffect;
64
127
  currentEffect = e;
65
128
  try {
@@ -68,6 +131,7 @@ if (typeof result === 'function') e._cleanup = result;
68
131
  } finally {
69
132
  currentEffect = prev;
70
133
  }
134
+ _updateEffectLevel(e);
71
135
  if (opts?.stable) e._stable = true;
72
136
  const dispose = () => _disposeEffect(e);
73
137
  if (currentRoot) {
@@ -75,6 +139,15 @@ currentRoot.disposals.push(dispose);
75
139
  }
76
140
  return dispose;
77
141
  }
142
+ function _updateEffectLevel(e) {
143
+ let maxDepLevel = 0;
144
+ for (let i = 0; i < e.deps.length; i++) {
145
+ const owner = subSetOwner.get(e.deps[i]);
146
+ const depLevel = owner ? owner._level : 0;
147
+ if (depLevel > maxDepLevel) maxDepLevel = depLevel;
148
+ }
149
+ e._level = maxDepLevel + 1;
150
+ }
78
151
  export function batch(fn) {
79
152
  batchDepth++;
80
153
  try {
@@ -85,7 +158,7 @@ if (batchDepth === 0) flush();
85
158
  }
86
159
  }
87
160
  function _createEffect(fn, lazy) {
88
- return {
161
+ const e = {
89
162
  fn,
90
163
  deps: [],
91
164
  lazy: lazy || false,
@@ -93,7 +166,14 @@ _onNotify: null,
93
166
  disposed: false,
94
167
  _pending: false,
95
168
  _stable: false,
169
+ _level: 0,
170
+ _computed: false,
171
+ _computedSubs: null,
172
+ _isDirty: null,
173
+ _markDirty: null,
96
174
  };
175
+ if (__DEV__ && __devtools) __devtools.onEffectCreate(e);
176
+ return e;
97
177
  }
98
178
  function _runEffect(e) {
99
179
  if (e.disposed) return;
@@ -109,14 +189,19 @@ currentEffect = null;
109
189
  try {
110
190
  const result = e.fn();
111
191
  if (typeof result === 'function') e._cleanup = result;
192
+ } catch (err) {
193
+ if (__devtools?.onError) __devtools.onError(err, { type: 'effect', effect: e });
194
+ if (__DEV__) console.warn('[what] Error in stable effect:', err);
112
195
  } finally {
113
196
  currentEffect = prev;
114
197
  }
198
+ if (__DEV__ && __devtools?.onEffectRun) __devtools.onEffectRun(e);
115
199
  return;
116
200
  }
117
201
  cleanup(e);
118
202
  if (e._cleanup) {
119
203
  try { e._cleanup(); } catch (err) {
204
+ if (__devtools?.onError) __devtools.onError(err, { type: 'effect-cleanup', effect: e });
120
205
  if (__DEV__) console.warn('[what] Error in effect cleanup:', err);
121
206
  }
122
207
  e._cleanup = null;
@@ -128,12 +213,18 @@ const result = e.fn();
128
213
  if (typeof result === 'function') {
129
214
  e._cleanup = result;
130
215
  }
216
+ } catch (err) {
217
+ if (err === NEEDS_UPSTREAM) throw err;
218
+ if (__devtools?.onError) __devtools.onError(err, { type: 'effect', effect: e });
219
+ throw err;
131
220
  } finally {
132
221
  currentEffect = prev;
133
222
  }
223
+ if (__DEV__ && __devtools?.onEffectRun) __devtools.onEffectRun(e);
134
224
  }
135
225
  function _disposeEffect(e) {
136
226
  e.disposed = true;
227
+ if (__DEV__ && __devtools) __devtools.onEffectDispose(e);
137
228
  cleanup(e);
138
229
  if (e._cleanup) {
139
230
  try { e._cleanup(); } catch (err) {
@@ -147,8 +238,16 @@ const deps = e.deps;
147
238
  for (let i = 0; i < deps.length; i++) deps[i].delete(e);
148
239
  deps.length = 0;
149
240
  }
241
+ let notifyQueue = null;
150
242
  function notify(subs) {
151
- for (const e of subs) {
243
+ const isOutermost = notifyQueue === null;
244
+ if (isOutermost) notifyQueue = [];
245
+ notifyQueue.push(subs);
246
+ if (!isOutermost) return;
247
+ try {
248
+ while (notifyQueue.length > 0) {
249
+ const currentSubs = notifyQueue.shift();
250
+ for (const e of currentSubs) {
152
251
  if (e.disposed) continue;
153
252
  if (e._onNotify) {
154
253
  e._onNotify();
@@ -162,6 +261,7 @@ if (e._cleanup) try { e._cleanup(); } catch (err) {}
162
261
  e._cleanup = result;
163
262
  }
164
263
  } catch (err) {
264
+ if (__devtools?.onError) __devtools.onError(err, { type: 'effect', effect: e });
165
265
  if (__DEV__) console.warn('[what] Error in stable effect:', err);
166
266
  } finally {
167
267
  currentEffect = prev;
@@ -171,6 +271,10 @@ e._pending = true;
171
271
  pendingEffects.push(e);
172
272
  }
173
273
  }
274
+ }
275
+ } finally {
276
+ notifyQueue = null;
277
+ }
174
278
  if (batchDepth === 0 && pendingEffects.length > 0) scheduleMicrotask();
175
279
  }
176
280
  let microtaskScheduled = false;
@@ -185,22 +289,26 @@ flush();
185
289
  }
186
290
  function flush() {
187
291
  let iterations = 0;
188
- while (pendingEffects.length > 0 && iterations < 100) {
292
+ while (pendingEffects.length > 0 && iterations < 25) {
189
293
  const batch = pendingEffects;
190
294
  pendingEffects = [];
295
+ batch.sort((a, b) => a._level - b._level);
191
296
  for (let i = 0; i < batch.length; i++) {
192
297
  const e = batch[i];
193
298
  e._pending = false;
194
- if (!e.disposed && !e._onNotify) _runEffect(e);
299
+ if (!e.disposed && !e._onNotify) {
300
+ _runEffect(e);
301
+ if (!e._computed) _updateEffectLevel(e);
302
+ }
195
303
  }
196
304
  iterations++;
197
305
  }
198
- if (iterations >= 100) {
306
+ if (iterations >= 25) {
199
307
  if (__DEV__) {
200
308
  const remaining = pendingEffects.slice(0, 3);
201
309
  const effectNames = remaining.map(e => e.fn?.name || e.fn?.toString().slice(0, 60) || '(anonymous)');
202
310
  console.warn(
203
- `[what] Possible infinite effect loop detected (100 iterations). ` +
311
+ `[what] Possible infinite effect loop detected (25 iterations). ` +
204
312
  `Likely cause: an effect writes to a signal it also reads, creating a cycle. ` +
205
313
  `Use untrack() to read signals without subscribing. ` +
206
314
  `Looping effects: ${effectNames.join(', ')}`
@@ -222,7 +330,10 @@ value = next;
222
330
  notify(subs);
223
331
  }
224
332
  });
333
+ e._level = 1;
225
334
  _runEffect(e);
335
+ _updateEffectLevel(e);
336
+ subSetOwner.set(subs, e);
226
337
  if (currentRoot) {
227
338
  currentRoot.disposals.push(() => _disposeEffect(e));
228
339
  }
@@ -250,19 +361,72 @@ return fn();
250
361
  currentEffect = prev;
251
362
  }
252
363
  }
364
+ export function getOwner() {
365
+ return currentOwner;
366
+ }
367
+ export function runWithOwner(owner, fn) {
368
+ const prev = currentOwner;
369
+ const prevRoot = currentRoot;
370
+ currentOwner = owner;
371
+ currentRoot = owner;
372
+ try {
373
+ return fn();
374
+ } finally {
375
+ currentOwner = prev;
376
+ currentRoot = prevRoot;
377
+ }
378
+ }
253
379
  export function createRoot(fn) {
254
380
  const prevRoot = currentRoot;
255
- const root = { disposals: [], owner: currentRoot };
381
+ const prevOwner = currentOwner;
382
+ const root = {
383
+ disposals: [],
384
+ owner: currentOwner,
385
+ children: [],
386
+ _disposed: false,
387
+ };
388
+ if (currentOwner) {
389
+ currentOwner.children.push(root);
390
+ }
256
391
  currentRoot = root;
392
+ currentOwner = root;
257
393
  try {
258
394
  const dispose = () => {
395
+ if (root._disposed) return;
396
+ root._disposed = true;
397
+ for (let i = root.children.length - 1; i >= 0; i--) {
398
+ _disposeRoot(root.children[i]);
399
+ }
400
+ root.children.length = 0;
259
401
  for (let i = root.disposals.length - 1; i >= 0; i--) {
260
402
  root.disposals[i]();
261
403
  }
262
404
  root.disposals.length = 0;
405
+ if (root.owner) {
406
+ const idx = root.owner.children.indexOf(root);
407
+ if (idx >= 0) root.owner.children.splice(idx, 1);
408
+ }
263
409
  };
264
410
  return fn(dispose);
265
411
  } finally {
266
412
  currentRoot = prevRoot;
413
+ currentOwner = prevOwner;
414
+ }
415
+ }
416
+ function _disposeRoot(root) {
417
+ if (root._disposed) return;
418
+ root._disposed = true;
419
+ for (let i = root.children.length - 1; i >= 0; i--) {
420
+ _disposeRoot(root.children[i]);
421
+ }
422
+ root.children.length = 0;
423
+ for (let i = root.disposals.length - 1; i >= 0; i--) {
424
+ root.disposals[i]();
425
+ }
426
+ root.disposals.length = 0;
427
+ }
428
+ export function onCleanup(fn) {
429
+ if (currentRoot) {
430
+ currentRoot.disposals.push(fn);
267
431
  }
268
432
  }