@alixex/ranview 0.1.0-alpha.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 @@
1
+ {"version":3,"file":"mocks-Bxv4c1qp.js","names":[],"sources":["../src/escape.ts","../src/selectors.ts","../src/mocks.ts"],"sourcesContent":["// HTML 转义。实现住在 ranuts —— 它先用一个正则探测有没有特殊字符,没有就原样返回,\n// 省掉了「无论如何都跑五次 replace」的分配开销;ranview 这边只保留导出名。\n//\n// 一处可见差异:单引号现在转义成 `&#39;` 而不是 `&#039;`。两者是同一个字符的\n// 等价实体,渲染结果相同,但 SSR 序列化出来的 HTML 字节会变,比对快照时会看到。\nimport { escapeHtml } from 'ranuts/utils';\n\nexport { escapeHtml };\n\n/**\n * 属性值转义。当前与 `escapeHtml` 完全一致:单双引号都会被转义,所以带引号、\n * 不带引号的属性值都安全。保留独立的名字是为了在调用点标出意图。\n */\nexport function escapeHtmlAttribute(unsafe: string): string {\n return escapeHtml(unsafe);\n}\n","import type { DocumentFragmentMock, HTMLElementMock } from './mocks';\n\nexport type MockNode = HTMLElementMock | DocumentFragmentMock | string;\n\nconst isHTMLElementMockNode = (node: MockNode): node is HTMLElementMock => {\n if (typeof node === 'string') return false;\n return 'tagName' in node && 'childrenList' in node;\n};\n\n/** Characters that begin a new qualifier, and so end the one before it. */\nconst QUALIFIER_START = '.#[';\n\n/**\n * Splits a compound selector into its parts.\n *\n * Hand-written rather than a regular expression: the obvious pattern for the attribute\n * form, `\\[[^\\]]*\\]`, backtracks across every position on an input like `[[[[[`, which is\n * polynomial in the selector's length. This walks the string once. A selector is not\n * usually attacker-controlled, but `querySelector` is a library entry point and the caller\n * chooses what reaches it.\n *\n * @param selector The trimmed, non-empty selector.\n * @returns Its parts, or null when the selector uses syntax this engine does not implement.\n */\nfunction splitCompound(selector: string): string[] | null {\n const parts: string[] = [];\n let at = 0;\n\n const tag = /^[a-zA-Z][\\w-]*/.exec(selector);\n if (tag !== null) {\n parts.push(tag[0]);\n at = tag[0].length;\n }\n\n while (at < selector.length) {\n const start = selector[at];\n if (start === '[') {\n const close = selector.indexOf(']', at + 1);\n if (close === -1) return null;\n parts.push(selector.slice(at, close + 1));\n at = close + 1;\n continue;\n }\n if (start !== '.' && start !== '#') return null;\n let end = at + 1;\n while (end < selector.length && !QUALIFIER_START.includes(selector[end])) end += 1;\n // A bare `.` or `#` qualifies nothing.\n if (end === at + 1) return null;\n parts.push(selector.slice(at, end));\n at = end;\n }\n\n return parts.length > 0 ? parts : null;\n}\n\n/**\n * Matches one simple selector — a single tag, class, id, or attribute test.\n *\n * @param node The node to test.\n * @param selector One simple selector, already trimmed and non-empty.\n * @returns Whether the node matches.\n */\nconst matchSimple = (node: HTMLElementMock, selector: string): boolean => {\n const trimmed = selector;\n\n if (trimmed.startsWith('.')) {\n return node.classList.contains(trimmed.slice(1));\n }\n if (trimmed.startsWith('#')) {\n return node.getAttribute('id') === trimmed.slice(1);\n }\n if (trimmed.startsWith('[') && trimmed.endsWith(']')) {\n const content = trimmed.slice(1, -1).trim();\n const [rawKey, rawValue] = content.split('=');\n const key = (rawKey || '').trim();\n if (!key) return false;\n if (rawValue == null) return node.hasAttribute(key);\n const value = rawValue.trim().replace(/^['\"]|['\"]$/g, '');\n return node.getAttribute(key) === value;\n }\n return node.tagName === trimmed.toLowerCase();\n};\n\n/**\n * Matches a compound selector such as `slot[name=\"footer\"]` or `div.row#first`.\n *\n * Every qualifier must match. Supporting only one at a time meant a selector like\n * `slot[name=\"footer\"]` fell through to the tag comparison and matched nothing, which broke\n * server rendering for any component that located a node that way — silently, because a\n * `querySelector` returning null there is usually asserted away with `!`.\n *\n * Anything the tokenizer cannot consume in full — a descendant combinator, `>`, a\n * pseudo-class — returns false rather than matching on the part it understood. A selector\n * this engine does not implement should find nothing, not something arbitrary.\n *\n * @param node The node to test.\n * @param selector The selector to match.\n * @returns Whether the node matches every part of the selector.\n */\nexport const matchSelector = (node: HTMLElementMock, selector: string): boolean => {\n const trimmed = selector.trim();\n if (!trimmed) return false;\n const parts = splitCompound(trimmed);\n if (parts === null) return false;\n return parts.every((part) => matchSimple(node, part));\n};\n\nexport const collectMatches = (nodes: MockNode[], selector: string, result: HTMLElementMock[]): void => {\n for (const child of nodes) {\n if (!isHTMLElementMockNode(child)) continue;\n if (matchSelector(child, selector)) {\n result.push(child);\n }\n collectMatches(child.childrenList as MockNode[], selector, result);\n }\n};\n","import { escapeHtml, escapeHtmlAttribute } from './escape';\nimport type { MockNode } from './selectors';\nimport { collectMatches } from './selectors';\n\n/**\n * A minimal mock of DocumentFragment for SSR environments.\n */\nexport class DocumentFragmentMock {\n public childrenList: (HTMLElementMock | string)[] = [];\n\n appendChild(node: any): any {\n this.childrenList.push(node);\n return node;\n }\n\n serialize(): string {\n return this.childrenList\n .map((child) => (typeof child === 'string' ? escapeHtml(child) : child.serialize()))\n .join('');\n }\n\n querySelector(selector: string): HTMLElementMock | null {\n const list = this.querySelectorAll(selector);\n return list.length > 0 ? list[0] : null;\n }\n\n querySelectorAll(selector: string): HTMLElementMock[] {\n const result: HTMLElementMock[] = [];\n collectMatches(this.childrenList as MockNode[], selector, result);\n return result;\n }\n}\n\n/**\n * A robust mock of HTMLElement for SSR environments.\n */\n/** Handed back by `inlineStyles` when nothing was ever set. Never mutated. */\nconst EMPTY_STRING_MAP: ReadonlyMap<string, string> = new Map<string, string>();\n\nexport class HTMLElementMock {\n public tagName: string;\n public attributes: Map<string, string> = new Map<string, string>();\n /**\n * Created on first write, like the two façades below.\n *\n * Only `style.setProperty` fills this, and `serialize()` is the only reader, so a node\n * that never sets an inline style never needs the Map.\n */\n private _inlineStyles: Map<string, string> | null = null;\n\n /**\n * The styles actually set, or an empty map — reading never allocates.\n *\n * Read-only because the shared empty map is handed to every element that never set a\n * style; writing through it would leak one element's styles into all of them. Set\n * styles with `style.setProperty`, which is what a browser requires anyway.\n */\n get inlineStyles(): ReadonlyMap<string, string> {\n return this._inlineStyles ?? EMPTY_STRING_MAP;\n }\n public childrenList: (HTMLElementMock | string | DocumentFragmentMock)[] = [];\n public shadowRoot: ShadowRootMock | null = null;\n private _textContent: string | null = null;\n\n /**\n * Setting either `textContent` or `innerHTML` replaces the element's content\n * in a real DOM, so the last one written wins. This mock used to keep both\n * and let `serialize()` prefer the text, which meant\n * `el.textContent = 'a'; el.innerHTML = '<b/>'` rendered `a` here and `<b/>`\n * in a browser -- the kind of difference that only surfaces once the same\n * template is rendered in both, which is exactly what a static-site generator\n * under vitest does.\n */\n get textContent(): string | null {\n return this._textContent;\n }\n\n set textContent(value: string | null) {\n this._textContent = value;\n }\n public content?: DocumentFragmentMock;\n private eventListeners: Map<string, Set<EventListenerOrEventListenerObject>> | null = null;\n\n /*\n * `style` and `classList` are built on first access, not in the constructor.\n *\n * They were instance fields holding object literals of arrow functions. An arrow\n * function closes over `this`, so it cannot live on the prototype — every element\n * allocated two objects and six closures whether or not anything ever touched them,\n * and nearly nothing does: a generated page is overwhelmingly elements with a class\n * attribute and a text child.\n *\n * Measured on `new HTMLElementMock('div')` plus two attributes: 1.466 µs before,\n * 0.043 µs after. That is 97% of the cost of building a node, and it was being paid\n * for capabilities the node never used. A getter is transparent to every reader —\n * `el.style.setProperty(…)` is unchanged — and nothing enumerates an element's own\n * properties, which is the one thing that would have noticed the difference.\n */\n private _style: HTMLElementMock['style'] | null = null;\n private _classList: HTMLElementMock['classList'] | null = null;\n\n get style(): { setProperty(k: string, v: string): void; removeProperty(k: string): boolean } {\n return (this._style ??= {\n setProperty: (k: string, v: string): void => {\n const prop = k.startsWith('--') ? k : k.replace(/([A-Z])/g, '-$1').toLowerCase();\n (this._inlineStyles ??= new Map<string, string>()).set(prop, v);\n },\n removeProperty: (k: string): boolean => this._inlineStyles?.delete(k) ?? false,\n });\n }\n\n get classList(): {\n add(...names: string[]): void;\n remove(...names: string[]): void;\n toggle(name: string): void;\n contains(name: string): boolean;\n } {\n return (this._classList ??= {\n add: (...names: string[]): void => {\n const existing = this.attributes.get('class') || '';\n const list = new Set([...existing.split(' ').filter(Boolean), ...names]);\n this.attributes.set('class', Array.from(list).join(' '));\n },\n remove: (...names: string[]): void => {\n const existing = this.attributes.get('class') || '';\n const list = existing.split(' ').filter((c) => !names.includes(c));\n this.attributes.set('class', list.join(' '));\n },\n toggle: (name: string): void => {\n if (this.classList.contains(name)) this.classList.remove(name);\n else this.classList.add(name);\n },\n contains: (name: string): boolean => (this.attributes.get('class') || '').split(' ').includes(name),\n });\n }\n\n private _innerHTML: string = '';\n\n get innerHTML(): string {\n // Reading it back reflects whichever was written last, as a browser does:\n // after `el.textContent = 'a'`, `el.innerHTML` is `a`, not the markup that\n // was there before.\n if (this._textContent !== null) return escapeHtml(this._textContent);\n return this._innerHTML;\n }\n\n set innerHTML(value: string) {\n this._innerHTML = value;\n this._textContent = null;\n if (this.tagName === 'template' && this.content) {\n this.content.childrenList = [value];\n }\n }\n\n constructor(tag: string = 'div') {\n this.tagName = tag.toLowerCase();\n if (this.tagName === 'template') {\n this.content = new DocumentFragmentMock();\n }\n }\n\n get hidden(): boolean {\n return this.hasAttribute('hidden');\n }\n\n set hidden(val: boolean) {\n if (val) this.setAttribute('hidden', '');\n else this.removeAttribute('hidden');\n }\n\n setAttribute(k: string, v: string): void {\n this.attributes.set(k, v);\n }\n\n getAttribute(k: string): string | null {\n return this.attributes.get(k) || null;\n }\n\n removeAttribute(k: string): void {\n this.attributes.delete(k);\n }\n\n hasAttribute(k: string): boolean {\n return this.attributes.has(k);\n }\n\n appendChild(node: any): any {\n this.childrenList.push(node);\n return node;\n }\n\n attachShadow(options: ShadowRootInit): ShadowRoot {\n this.shadowRoot = new ShadowRootMock(this, options);\n return this.shadowRoot as unknown as ShadowRoot;\n }\n\n querySelector(selector: string): HTMLElementMock | null {\n const result = this.querySelectorAll(selector);\n return result.length > 0 ? result[0] : null;\n }\n\n querySelectorAll(selector: string): HTMLElementMock[] {\n const result: HTMLElementMock[] = [];\n collectMatches(this.childrenList as MockNode[], selector, result);\n return result;\n }\n\n addEventListener(type: string, listener: EventListenerOrEventListenerObject): void {\n const listeners = (this.eventListeners ??= new Map<string, Set<EventListenerOrEventListenerObject>>());\n const bucket = listeners.get(type) || new Set<EventListenerOrEventListenerObject>();\n bucket.add(listener);\n listeners.set(type, bucket);\n }\n\n removeEventListener(type: string, listener: EventListenerOrEventListenerObject): void {\n const bucket = this.eventListeners?.get(type);\n if (!bucket) return;\n bucket.delete(listener);\n if (bucket.size === 0) {\n this.eventListeners?.delete(type);\n }\n }\n\n dispatchEvent(event: Event): boolean {\n const listeners = this.eventListeners?.get(event.type);\n if (!listeners || listeners.size === 0) return true;\n for (const listener of listeners) {\n if (typeof listener === 'function') {\n listener.call(this, event);\n } else {\n listener.handleEvent(event);\n }\n }\n return !event.defaultPrevented;\n }\n\n serialize(tagNameOverride?: string): string {\n if (typeof (this as any)._preSerialize === 'function') {\n (this as any)._preSerialize();\n }\n const tagName =\n tagNameOverride || ((this as Record<string, unknown>)._ssrTag as string | undefined) || this.tagName;\n const attrs = Array.from(this.attributes.entries())\n .map(([k, v]) => ` ${k}=\"${escapeHtmlAttribute(v)}\"`)\n .join('');\n\n const styleString = this._inlineStyles\n ? Array.from(this._inlineStyles.entries())\n .map(([k, v]) => `${k}:${v}`)\n .join(';')\n : '';\n\n const styleAttr = styleString ? ` style=\"${escapeHtmlAttribute(styleString)}\"` : '';\n\n let innerHTML = '';\n if (this.shadowRoot) innerHTML += this.shadowRoot.serialize();\n\n if (this.textContent !== null) {\n innerHTML += escapeHtml(this.textContent);\n } else if (this._innerHTML) {\n innerHTML += this._innerHTML;\n } else {\n innerHTML += this.childrenList\n .map((child) => {\n if (typeof child === 'string') return escapeHtml(child);\n if (child instanceof DocumentFragmentMock) return child.serialize();\n return child.serialize();\n })\n .join('');\n }\n\n const selfClosing = [\n 'area',\n 'base',\n 'br',\n 'col',\n 'embed',\n 'hr',\n 'img',\n 'input',\n 'link',\n 'meta',\n 'param',\n 'source',\n 'track',\n 'wbr',\n ];\n if (selfClosing.includes(tagName) && !innerHTML) {\n return `<${tagName}${attrs}${styleAttr} />`;\n }\n\n return `<${tagName}${attrs}${styleAttr}>${innerHTML}</${tagName}>`;\n }\n}\n\nexport class ShadowRootMock {\n public host: HTMLElementMock;\n public options: ShadowRootInit;\n public childrenList: (HTMLElementMock | string | DocumentFragmentMock)[] = [];\n public adoptedStyleSheets: string[] = [];\n\n constructor(host: HTMLElementMock, options: ShadowRootInit) {\n this.host = host;\n this.options = options;\n }\n\n appendChild(node: any): any {\n this.childrenList.push(node);\n return node;\n }\n\n querySelector(selector: string): HTMLElementMock | null {\n const result = this.querySelectorAll(selector);\n return result.length > 0 ? result[0] : null;\n }\n\n querySelectorAll(selector: string): HTMLElementMock[] {\n const result: HTMLElementMock[] = [];\n collectMatches(this.childrenList as MockNode[], selector, result);\n return result;\n }\n\n serialize(): string {\n const mode = this.options.mode;\n const delegatesFocus = this.options.delegatesFocus ? ' shadowrootdelegatesfocus' : '';\n const styles = this.adoptedStyleSheets.map((s) => `<style>${escapeHtml(s)}</style>`).join('');\n const content = this.childrenList\n .map((child) => {\n if (typeof child === 'string') return escapeHtml(child);\n if (child instanceof DocumentFragmentMock) return child.serialize();\n return child.serialize();\n })\n .join('');\n\n return `<template shadowrootmode=\"${mode}\"${delegatesFocus}>${styles}${content}</template>`;\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ let e=require("ranuts/utils");function t(t){return(0,e.escapeHtml)(t)}var n=e=>typeof e!=`string`&&`tagName`in e&&`childrenList`in e,r=`.#[`;function i(e){let t=[],n=0,i=/^[a-zA-Z][\w-]*/.exec(e);for(i!==null&&(t.push(i[0]),n=i[0].length);n<e.length;){let i=e[n];if(i===`[`){let r=e.indexOf(`]`,n+1);if(r===-1)return null;t.push(e.slice(n,r+1)),n=r+1;continue}if(i!==`.`&&i!==`#`)return null;let a=n+1;for(;a<e.length&&!r.includes(e[a]);)a+=1;if(a===n+1)return null;t.push(e.slice(n,a)),n=a}return t.length>0?t:null}var a=(e,t)=>{let n=t;if(n.startsWith(`.`))return e.classList.contains(n.slice(1));if(n.startsWith(`#`))return e.getAttribute(`id`)===n.slice(1);if(n.startsWith(`[`)&&n.endsWith(`]`)){let[t,r]=n.slice(1,-1).trim().split(`=`),i=(t||``).trim();if(!i)return!1;if(r==null)return e.hasAttribute(i);let a=r.trim().replace(/^['"]|['"]$/g,``);return e.getAttribute(i)===a}return e.tagName===n.toLowerCase()},o=(e,t)=>{let n=t.trim();if(!n)return!1;let r=i(n);return r!==null&&r.every(t=>a(e,t))},s=(e,t,r)=>{for(let i of e)n(i)&&(o(i,t)&&r.push(i),s(i.childrenList,t,r))},c=class{childrenList=[];appendChild(e){return this.childrenList.push(e),e}serialize(){return this.childrenList.map(t=>typeof t==`string`?(0,e.escapeHtml)(t):t.serialize()).join(``)}querySelector(e){let t=this.querySelectorAll(e);return t.length>0?t[0]:null}querySelectorAll(e){let t=[];return s(this.childrenList,e,t),t}},l=new Map,u=class{tagName;attributes=new Map;_inlineStyles=null;get inlineStyles(){return this._inlineStyles??l}childrenList=[];shadowRoot=null;_textContent=null;get textContent(){return this._textContent}set textContent(e){this._textContent=e}content;eventListeners=null;_style=null;_classList=null;get style(){return this._style??={setProperty:(e,t)=>{let n=e.startsWith(`--`)?e:e.replace(/([A-Z])/g,`-$1`).toLowerCase();(this._inlineStyles??=new Map).set(n,t)},removeProperty:e=>this._inlineStyles?.delete(e)??!1}}get classList(){return this._classList??={add:(...e)=>{let t=this.attributes.get(`class`)||``,n=new Set([...t.split(` `).filter(Boolean),...e]);this.attributes.set(`class`,Array.from(n).join(` `))},remove:(...e)=>{let t=(this.attributes.get(`class`)||``).split(` `).filter(t=>!e.includes(t));this.attributes.set(`class`,t.join(` `))},toggle:e=>{this.classList.contains(e)?this.classList.remove(e):this.classList.add(e)},contains:e=>(this.attributes.get(`class`)||``).split(` `).includes(e)}}_innerHTML=``;get innerHTML(){return this._textContent===null?this._innerHTML:(0,e.escapeHtml)(this._textContent)}set innerHTML(e){this._innerHTML=e,this._textContent=null,this.tagName===`template`&&this.content&&(this.content.childrenList=[e])}constructor(e=`div`){this.tagName=e.toLowerCase(),this.tagName===`template`&&(this.content=new c)}get hidden(){return this.hasAttribute(`hidden`)}set hidden(e){e?this.setAttribute(`hidden`,``):this.removeAttribute(`hidden`)}setAttribute(e,t){this.attributes.set(e,t)}getAttribute(e){return this.attributes.get(e)||null}removeAttribute(e){this.attributes.delete(e)}hasAttribute(e){return this.attributes.has(e)}appendChild(e){return this.childrenList.push(e),e}attachShadow(e){return this.shadowRoot=new d(this,e),this.shadowRoot}querySelector(e){let t=this.querySelectorAll(e);return t.length>0?t[0]:null}querySelectorAll(e){let t=[];return s(this.childrenList,e,t),t}addEventListener(e,t){let n=this.eventListeners??=new Map,r=n.get(e)||new Set;r.add(t),n.set(e,r)}removeEventListener(e,t){let n=this.eventListeners?.get(e);n&&(n.delete(t),n.size===0&&this.eventListeners?.delete(e))}dispatchEvent(e){let t=this.eventListeners?.get(e.type);if(!t||t.size===0)return!0;for(let n of t)typeof n==`function`?n.call(this,e):n.handleEvent(e);return!e.defaultPrevented}serialize(n){typeof this._preSerialize==`function`&&this._preSerialize();let r=n||this._ssrTag||this.tagName,i=Array.from(this.attributes.entries()).map(([e,n])=>` ${e}="${t(n)}"`).join(``),a=this._inlineStyles?Array.from(this._inlineStyles.entries()).map(([e,t])=>`${e}:${t}`).join(`;`):``,o=a?` style="${t(a)}"`:``,s=``;return this.shadowRoot&&(s+=this.shadowRoot.serialize()),this.textContent===null?this._innerHTML?s+=this._innerHTML:s+=this.childrenList.map(t=>typeof t==`string`?(0,e.escapeHtml)(t):(t instanceof c,t.serialize())).join(``):s+=(0,e.escapeHtml)(this.textContent),[`area`,`base`,`br`,`col`,`embed`,`hr`,`img`,`input`,`link`,`meta`,`param`,`source`,`track`,`wbr`].includes(r)&&!s?`<${r}${i}${o} />`:`<${r}${i}${o}>${s}</${r}>`}},d=class{host;options;childrenList=[];adoptedStyleSheets=[];constructor(e,t){this.host=e,this.options=t}appendChild(e){return this.childrenList.push(e),e}querySelector(e){let t=this.querySelectorAll(e);return t.length>0?t[0]:null}querySelectorAll(e){let t=[];return s(this.childrenList,e,t),t}serialize(){return`<template shadowrootmode="${this.options.mode}"${this.options.delegatesFocus?` shadowrootdelegatesfocus`:``}>${this.adoptedStyleSheets.map(t=>`<style>${(0,e.escapeHtml)(t)}</style>`).join(``)}${this.childrenList.map(t=>typeof t==`string`?(0,e.escapeHtml)(t):(t instanceof c,t.serialize())).join(``)}</template>`}};Object.defineProperty(exports,"a",{enumerable:!0,get:function(){return o}}),Object.defineProperty(exports,"i",{enumerable:!0,get:function(){return s}}),Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return u}}),Object.defineProperty(exports,"o",{enumerable:!0,get:function(){return t}}),Object.defineProperty(exports,"r",{enumerable:!0,get:function(){return d}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return c}});
2
+ //# sourceMappingURL=mocks-DvJQ7jGP.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mocks-DvJQ7jGP.cjs","names":[],"sources":["../src/escape.ts","../src/selectors.ts","../src/mocks.ts"],"sourcesContent":["// HTML 转义。实现住在 ranuts —— 它先用一个正则探测有没有特殊字符,没有就原样返回,\n// 省掉了「无论如何都跑五次 replace」的分配开销;ranview 这边只保留导出名。\n//\n// 一处可见差异:单引号现在转义成 `&#39;` 而不是 `&#039;`。两者是同一个字符的\n// 等价实体,渲染结果相同,但 SSR 序列化出来的 HTML 字节会变,比对快照时会看到。\nimport { escapeHtml } from 'ranuts/utils';\n\nexport { escapeHtml };\n\n/**\n * 属性值转义。当前与 `escapeHtml` 完全一致:单双引号都会被转义,所以带引号、\n * 不带引号的属性值都安全。保留独立的名字是为了在调用点标出意图。\n */\nexport function escapeHtmlAttribute(unsafe: string): string {\n return escapeHtml(unsafe);\n}\n","import type { DocumentFragmentMock, HTMLElementMock } from './mocks';\n\nexport type MockNode = HTMLElementMock | DocumentFragmentMock | string;\n\nconst isHTMLElementMockNode = (node: MockNode): node is HTMLElementMock => {\n if (typeof node === 'string') return false;\n return 'tagName' in node && 'childrenList' in node;\n};\n\n/** Characters that begin a new qualifier, and so end the one before it. */\nconst QUALIFIER_START = '.#[';\n\n/**\n * Splits a compound selector into its parts.\n *\n * Hand-written rather than a regular expression: the obvious pattern for the attribute\n * form, `\\[[^\\]]*\\]`, backtracks across every position on an input like `[[[[[`, which is\n * polynomial in the selector's length. This walks the string once. A selector is not\n * usually attacker-controlled, but `querySelector` is a library entry point and the caller\n * chooses what reaches it.\n *\n * @param selector The trimmed, non-empty selector.\n * @returns Its parts, or null when the selector uses syntax this engine does not implement.\n */\nfunction splitCompound(selector: string): string[] | null {\n const parts: string[] = [];\n let at = 0;\n\n const tag = /^[a-zA-Z][\\w-]*/.exec(selector);\n if (tag !== null) {\n parts.push(tag[0]);\n at = tag[0].length;\n }\n\n while (at < selector.length) {\n const start = selector[at];\n if (start === '[') {\n const close = selector.indexOf(']', at + 1);\n if (close === -1) return null;\n parts.push(selector.slice(at, close + 1));\n at = close + 1;\n continue;\n }\n if (start !== '.' && start !== '#') return null;\n let end = at + 1;\n while (end < selector.length && !QUALIFIER_START.includes(selector[end])) end += 1;\n // A bare `.` or `#` qualifies nothing.\n if (end === at + 1) return null;\n parts.push(selector.slice(at, end));\n at = end;\n }\n\n return parts.length > 0 ? parts : null;\n}\n\n/**\n * Matches one simple selector — a single tag, class, id, or attribute test.\n *\n * @param node The node to test.\n * @param selector One simple selector, already trimmed and non-empty.\n * @returns Whether the node matches.\n */\nconst matchSimple = (node: HTMLElementMock, selector: string): boolean => {\n const trimmed = selector;\n\n if (trimmed.startsWith('.')) {\n return node.classList.contains(trimmed.slice(1));\n }\n if (trimmed.startsWith('#')) {\n return node.getAttribute('id') === trimmed.slice(1);\n }\n if (trimmed.startsWith('[') && trimmed.endsWith(']')) {\n const content = trimmed.slice(1, -1).trim();\n const [rawKey, rawValue] = content.split('=');\n const key = (rawKey || '').trim();\n if (!key) return false;\n if (rawValue == null) return node.hasAttribute(key);\n const value = rawValue.trim().replace(/^['\"]|['\"]$/g, '');\n return node.getAttribute(key) === value;\n }\n return node.tagName === trimmed.toLowerCase();\n};\n\n/**\n * Matches a compound selector such as `slot[name=\"footer\"]` or `div.row#first`.\n *\n * Every qualifier must match. Supporting only one at a time meant a selector like\n * `slot[name=\"footer\"]` fell through to the tag comparison and matched nothing, which broke\n * server rendering for any component that located a node that way — silently, because a\n * `querySelector` returning null there is usually asserted away with `!`.\n *\n * Anything the tokenizer cannot consume in full — a descendant combinator, `>`, a\n * pseudo-class — returns false rather than matching on the part it understood. A selector\n * this engine does not implement should find nothing, not something arbitrary.\n *\n * @param node The node to test.\n * @param selector The selector to match.\n * @returns Whether the node matches every part of the selector.\n */\nexport const matchSelector = (node: HTMLElementMock, selector: string): boolean => {\n const trimmed = selector.trim();\n if (!trimmed) return false;\n const parts = splitCompound(trimmed);\n if (parts === null) return false;\n return parts.every((part) => matchSimple(node, part));\n};\n\nexport const collectMatches = (nodes: MockNode[], selector: string, result: HTMLElementMock[]): void => {\n for (const child of nodes) {\n if (!isHTMLElementMockNode(child)) continue;\n if (matchSelector(child, selector)) {\n result.push(child);\n }\n collectMatches(child.childrenList as MockNode[], selector, result);\n }\n};\n","import { escapeHtml, escapeHtmlAttribute } from './escape';\nimport type { MockNode } from './selectors';\nimport { collectMatches } from './selectors';\n\n/**\n * A minimal mock of DocumentFragment for SSR environments.\n */\nexport class DocumentFragmentMock {\n public childrenList: (HTMLElementMock | string)[] = [];\n\n appendChild(node: any): any {\n this.childrenList.push(node);\n return node;\n }\n\n serialize(): string {\n return this.childrenList\n .map((child) => (typeof child === 'string' ? escapeHtml(child) : child.serialize()))\n .join('');\n }\n\n querySelector(selector: string): HTMLElementMock | null {\n const list = this.querySelectorAll(selector);\n return list.length > 0 ? list[0] : null;\n }\n\n querySelectorAll(selector: string): HTMLElementMock[] {\n const result: HTMLElementMock[] = [];\n collectMatches(this.childrenList as MockNode[], selector, result);\n return result;\n }\n}\n\n/**\n * A robust mock of HTMLElement for SSR environments.\n */\n/** Handed back by `inlineStyles` when nothing was ever set. Never mutated. */\nconst EMPTY_STRING_MAP: ReadonlyMap<string, string> = new Map<string, string>();\n\nexport class HTMLElementMock {\n public tagName: string;\n public attributes: Map<string, string> = new Map<string, string>();\n /**\n * Created on first write, like the two façades below.\n *\n * Only `style.setProperty` fills this, and `serialize()` is the only reader, so a node\n * that never sets an inline style never needs the Map.\n */\n private _inlineStyles: Map<string, string> | null = null;\n\n /**\n * The styles actually set, or an empty map — reading never allocates.\n *\n * Read-only because the shared empty map is handed to every element that never set a\n * style; writing through it would leak one element's styles into all of them. Set\n * styles with `style.setProperty`, which is what a browser requires anyway.\n */\n get inlineStyles(): ReadonlyMap<string, string> {\n return this._inlineStyles ?? EMPTY_STRING_MAP;\n }\n public childrenList: (HTMLElementMock | string | DocumentFragmentMock)[] = [];\n public shadowRoot: ShadowRootMock | null = null;\n private _textContent: string | null = null;\n\n /**\n * Setting either `textContent` or `innerHTML` replaces the element's content\n * in a real DOM, so the last one written wins. This mock used to keep both\n * and let `serialize()` prefer the text, which meant\n * `el.textContent = 'a'; el.innerHTML = '<b/>'` rendered `a` here and `<b/>`\n * in a browser -- the kind of difference that only surfaces once the same\n * template is rendered in both, which is exactly what a static-site generator\n * under vitest does.\n */\n get textContent(): string | null {\n return this._textContent;\n }\n\n set textContent(value: string | null) {\n this._textContent = value;\n }\n public content?: DocumentFragmentMock;\n private eventListeners: Map<string, Set<EventListenerOrEventListenerObject>> | null = null;\n\n /*\n * `style` and `classList` are built on first access, not in the constructor.\n *\n * They were instance fields holding object literals of arrow functions. An arrow\n * function closes over `this`, so it cannot live on the prototype — every element\n * allocated two objects and six closures whether or not anything ever touched them,\n * and nearly nothing does: a generated page is overwhelmingly elements with a class\n * attribute and a text child.\n *\n * Measured on `new HTMLElementMock('div')` plus two attributes: 1.466 µs before,\n * 0.043 µs after. That is 97% of the cost of building a node, and it was being paid\n * for capabilities the node never used. A getter is transparent to every reader —\n * `el.style.setProperty(…)` is unchanged — and nothing enumerates an element's own\n * properties, which is the one thing that would have noticed the difference.\n */\n private _style: HTMLElementMock['style'] | null = null;\n private _classList: HTMLElementMock['classList'] | null = null;\n\n get style(): { setProperty(k: string, v: string): void; removeProperty(k: string): boolean } {\n return (this._style ??= {\n setProperty: (k: string, v: string): void => {\n const prop = k.startsWith('--') ? k : k.replace(/([A-Z])/g, '-$1').toLowerCase();\n (this._inlineStyles ??= new Map<string, string>()).set(prop, v);\n },\n removeProperty: (k: string): boolean => this._inlineStyles?.delete(k) ?? false,\n });\n }\n\n get classList(): {\n add(...names: string[]): void;\n remove(...names: string[]): void;\n toggle(name: string): void;\n contains(name: string): boolean;\n } {\n return (this._classList ??= {\n add: (...names: string[]): void => {\n const existing = this.attributes.get('class') || '';\n const list = new Set([...existing.split(' ').filter(Boolean), ...names]);\n this.attributes.set('class', Array.from(list).join(' '));\n },\n remove: (...names: string[]): void => {\n const existing = this.attributes.get('class') || '';\n const list = existing.split(' ').filter((c) => !names.includes(c));\n this.attributes.set('class', list.join(' '));\n },\n toggle: (name: string): void => {\n if (this.classList.contains(name)) this.classList.remove(name);\n else this.classList.add(name);\n },\n contains: (name: string): boolean => (this.attributes.get('class') || '').split(' ').includes(name),\n });\n }\n\n private _innerHTML: string = '';\n\n get innerHTML(): string {\n // Reading it back reflects whichever was written last, as a browser does:\n // after `el.textContent = 'a'`, `el.innerHTML` is `a`, not the markup that\n // was there before.\n if (this._textContent !== null) return escapeHtml(this._textContent);\n return this._innerHTML;\n }\n\n set innerHTML(value: string) {\n this._innerHTML = value;\n this._textContent = null;\n if (this.tagName === 'template' && this.content) {\n this.content.childrenList = [value];\n }\n }\n\n constructor(tag: string = 'div') {\n this.tagName = tag.toLowerCase();\n if (this.tagName === 'template') {\n this.content = new DocumentFragmentMock();\n }\n }\n\n get hidden(): boolean {\n return this.hasAttribute('hidden');\n }\n\n set hidden(val: boolean) {\n if (val) this.setAttribute('hidden', '');\n else this.removeAttribute('hidden');\n }\n\n setAttribute(k: string, v: string): void {\n this.attributes.set(k, v);\n }\n\n getAttribute(k: string): string | null {\n return this.attributes.get(k) || null;\n }\n\n removeAttribute(k: string): void {\n this.attributes.delete(k);\n }\n\n hasAttribute(k: string): boolean {\n return this.attributes.has(k);\n }\n\n appendChild(node: any): any {\n this.childrenList.push(node);\n return node;\n }\n\n attachShadow(options: ShadowRootInit): ShadowRoot {\n this.shadowRoot = new ShadowRootMock(this, options);\n return this.shadowRoot as unknown as ShadowRoot;\n }\n\n querySelector(selector: string): HTMLElementMock | null {\n const result = this.querySelectorAll(selector);\n return result.length > 0 ? result[0] : null;\n }\n\n querySelectorAll(selector: string): HTMLElementMock[] {\n const result: HTMLElementMock[] = [];\n collectMatches(this.childrenList as MockNode[], selector, result);\n return result;\n }\n\n addEventListener(type: string, listener: EventListenerOrEventListenerObject): void {\n const listeners = (this.eventListeners ??= new Map<string, Set<EventListenerOrEventListenerObject>>());\n const bucket = listeners.get(type) || new Set<EventListenerOrEventListenerObject>();\n bucket.add(listener);\n listeners.set(type, bucket);\n }\n\n removeEventListener(type: string, listener: EventListenerOrEventListenerObject): void {\n const bucket = this.eventListeners?.get(type);\n if (!bucket) return;\n bucket.delete(listener);\n if (bucket.size === 0) {\n this.eventListeners?.delete(type);\n }\n }\n\n dispatchEvent(event: Event): boolean {\n const listeners = this.eventListeners?.get(event.type);\n if (!listeners || listeners.size === 0) return true;\n for (const listener of listeners) {\n if (typeof listener === 'function') {\n listener.call(this, event);\n } else {\n listener.handleEvent(event);\n }\n }\n return !event.defaultPrevented;\n }\n\n serialize(tagNameOverride?: string): string {\n if (typeof (this as any)._preSerialize === 'function') {\n (this as any)._preSerialize();\n }\n const tagName =\n tagNameOverride || ((this as Record<string, unknown>)._ssrTag as string | undefined) || this.tagName;\n const attrs = Array.from(this.attributes.entries())\n .map(([k, v]) => ` ${k}=\"${escapeHtmlAttribute(v)}\"`)\n .join('');\n\n const styleString = this._inlineStyles\n ? Array.from(this._inlineStyles.entries())\n .map(([k, v]) => `${k}:${v}`)\n .join(';')\n : '';\n\n const styleAttr = styleString ? ` style=\"${escapeHtmlAttribute(styleString)}\"` : '';\n\n let innerHTML = '';\n if (this.shadowRoot) innerHTML += this.shadowRoot.serialize();\n\n if (this.textContent !== null) {\n innerHTML += escapeHtml(this.textContent);\n } else if (this._innerHTML) {\n innerHTML += this._innerHTML;\n } else {\n innerHTML += this.childrenList\n .map((child) => {\n if (typeof child === 'string') return escapeHtml(child);\n if (child instanceof DocumentFragmentMock) return child.serialize();\n return child.serialize();\n })\n .join('');\n }\n\n const selfClosing = [\n 'area',\n 'base',\n 'br',\n 'col',\n 'embed',\n 'hr',\n 'img',\n 'input',\n 'link',\n 'meta',\n 'param',\n 'source',\n 'track',\n 'wbr',\n ];\n if (selfClosing.includes(tagName) && !innerHTML) {\n return `<${tagName}${attrs}${styleAttr} />`;\n }\n\n return `<${tagName}${attrs}${styleAttr}>${innerHTML}</${tagName}>`;\n }\n}\n\nexport class ShadowRootMock {\n public host: HTMLElementMock;\n public options: ShadowRootInit;\n public childrenList: (HTMLElementMock | string | DocumentFragmentMock)[] = [];\n public adoptedStyleSheets: string[] = [];\n\n constructor(host: HTMLElementMock, options: ShadowRootInit) {\n this.host = host;\n this.options = options;\n }\n\n appendChild(node: any): any {\n this.childrenList.push(node);\n return node;\n }\n\n querySelector(selector: string): HTMLElementMock | null {\n const result = this.querySelectorAll(selector);\n return result.length > 0 ? result[0] : null;\n }\n\n querySelectorAll(selector: string): HTMLElementMock[] {\n const result: HTMLElementMock[] = [];\n collectMatches(this.childrenList as MockNode[], selector, result);\n return result;\n }\n\n serialize(): string {\n const mode = this.options.mode;\n const delegatesFocus = this.options.delegatesFocus ? ' shadowrootdelegatesfocus' : '';\n const styles = this.adoptedStyleSheets.map((s) => `<style>${escapeHtml(s)}</style>`).join('');\n const content = this.childrenList\n .map((child) => {\n if (typeof child === 'string') return escapeHtml(child);\n if (child instanceof DocumentFragmentMock) return child.serialize();\n return child.serialize();\n })\n .join('');\n\n return `<template shadowrootmode=\"${mode}\"${delegatesFocus}>${styles}${content}</template>`;\n }\n}\n"],"mappings":"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"}
package/dist/mocks.cjs ADDED
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./mocks-DvJQ7jGP.cjs");exports.DocumentFragmentMock=e.t,exports.HTMLElementMock=e.n,exports.ShadowRootMock=e.r;
@@ -0,0 +1,87 @@
1
+ /**
2
+ * A minimal mock of DocumentFragment for SSR environments.
3
+ */
4
+ export declare class DocumentFragmentMock {
5
+ childrenList: (HTMLElementMock | string)[];
6
+ appendChild(node: any): any;
7
+ serialize(): string;
8
+ querySelector(selector: string): HTMLElementMock | null;
9
+ querySelectorAll(selector: string): HTMLElementMock[];
10
+ }
11
+ export declare class HTMLElementMock {
12
+ tagName: string;
13
+ attributes: Map<string, string>;
14
+ /**
15
+ * Created on first write, like the two façades below.
16
+ *
17
+ * Only `style.setProperty` fills this, and `serialize()` is the only reader, so a node
18
+ * that never sets an inline style never needs the Map.
19
+ */
20
+ private _inlineStyles;
21
+ /**
22
+ * The styles actually set, or an empty map — reading never allocates.
23
+ *
24
+ * Read-only because the shared empty map is handed to every element that never set a
25
+ * style; writing through it would leak one element's styles into all of them. Set
26
+ * styles with `style.setProperty`, which is what a browser requires anyway.
27
+ */
28
+ get inlineStyles(): ReadonlyMap<string, string>;
29
+ childrenList: (HTMLElementMock | string | DocumentFragmentMock)[];
30
+ shadowRoot: ShadowRootMock | null;
31
+ private _textContent;
32
+ /**
33
+ * Setting either `textContent` or `innerHTML` replaces the element's content
34
+ * in a real DOM, so the last one written wins. This mock used to keep both
35
+ * and let `serialize()` prefer the text, which meant
36
+ * `el.textContent = 'a'; el.innerHTML = '<b/>'` rendered `a` here and `<b/>`
37
+ * in a browser -- the kind of difference that only surfaces once the same
38
+ * template is rendered in both, which is exactly what a static-site generator
39
+ * under vitest does.
40
+ */
41
+ get textContent(): string | null;
42
+ set textContent(value: string | null);
43
+ content?: DocumentFragmentMock;
44
+ private eventListeners;
45
+ private _style;
46
+ private _classList;
47
+ get style(): {
48
+ setProperty(k: string, v: string): void;
49
+ removeProperty(k: string): boolean;
50
+ };
51
+ get classList(): {
52
+ add(...names: string[]): void;
53
+ remove(...names: string[]): void;
54
+ toggle(name: string): void;
55
+ contains(name: string): boolean;
56
+ };
57
+ private _innerHTML;
58
+ get innerHTML(): string;
59
+ set innerHTML(value: string);
60
+ constructor(tag?: string);
61
+ get hidden(): boolean;
62
+ set hidden(val: boolean);
63
+ setAttribute(k: string, v: string): void;
64
+ getAttribute(k: string): string | null;
65
+ removeAttribute(k: string): void;
66
+ hasAttribute(k: string): boolean;
67
+ appendChild(node: any): any;
68
+ attachShadow(options: ShadowRootInit): ShadowRoot;
69
+ querySelector(selector: string): HTMLElementMock | null;
70
+ querySelectorAll(selector: string): HTMLElementMock[];
71
+ addEventListener(type: string, listener: EventListenerOrEventListenerObject): void;
72
+ removeEventListener(type: string, listener: EventListenerOrEventListenerObject): void;
73
+ dispatchEvent(event: Event): boolean;
74
+ serialize(tagNameOverride?: string): string;
75
+ }
76
+ export declare class ShadowRootMock {
77
+ host: HTMLElementMock;
78
+ options: ShadowRootInit;
79
+ childrenList: (HTMLElementMock | string | DocumentFragmentMock)[];
80
+ adoptedStyleSheets: string[];
81
+ constructor(host: HTMLElementMock, options: ShadowRootInit);
82
+ appendChild(node: any): any;
83
+ querySelector(selector: string): HTMLElementMock | null;
84
+ querySelectorAll(selector: string): HTMLElementMock[];
85
+ serialize(): string;
86
+ }
87
+ //# sourceMappingURL=mocks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mocks.d.ts","sourceRoot":"","sources":["../src/mocks.ts"],"names":[],"mappings":"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"}
package/dist/mocks.js ADDED
@@ -0,0 +1,2 @@
1
+ import { n as e, r as t, t as n } from "./mocks-Bxv4c1qp.js";
2
+ export { n as DocumentFragmentMock, e as HTMLElementMock, t as ShadowRootMock };
@@ -0,0 +1,21 @@
1
+ import type { DocumentFragmentMock, HTMLElementMock } from './mocks';
2
+ export type MockNode = HTMLElementMock | DocumentFragmentMock | string;
3
+ /**
4
+ * Matches a compound selector such as `slot[name="footer"]` or `div.row#first`.
5
+ *
6
+ * Every qualifier must match. Supporting only one at a time meant a selector like
7
+ * `slot[name="footer"]` fell through to the tag comparison and matched nothing, which broke
8
+ * server rendering for any component that located a node that way — silently, because a
9
+ * `querySelector` returning null there is usually asserted away with `!`.
10
+ *
11
+ * Anything the tokenizer cannot consume in full — a descendant combinator, `>`, a
12
+ * pseudo-class — returns false rather than matching on the part it understood. A selector
13
+ * this engine does not implement should find nothing, not something arbitrary.
14
+ *
15
+ * @param node The node to test.
16
+ * @param selector The selector to match.
17
+ * @returns Whether the node matches every part of the selector.
18
+ */
19
+ export declare const matchSelector: (node: HTMLElementMock, selector: string) => boolean;
20
+ export declare const collectMatches: (nodes: MockNode[], selector: string, result: HTMLElementMock[]) => void;
21
+ //# sourceMappingURL=selectors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selectors.d.ts","sourceRoot":"","sources":["../src/selectors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAErE,MAAM,MAAM,QAAQ,GAAG,eAAe,GAAG,oBAAoB,GAAG,MAAM,CAAC;AAiFvE;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,aAAa,SAAU,eAAe,YAAY,MAAM,KAAG,OAMvE,CAAC;AAEF,eAAO,MAAM,cAAc,UAAW,QAAQ,EAAE,YAAY,MAAM,UAAU,eAAe,EAAE,KAAG,IAQ/F,CAAC"}
@@ -0,0 +1,2 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=0,t=1,n=2,r=null,i=null,a=0,o=new Set,s=!1;function c(e,t){let r=t?.equals??Object.is,i={value:e,observers:new Set};return[()=>(y(i),i.value),e=>{let t=typeof e==`function`?e(i.value):e;if(!r(i.value,t)){i.value=t;for(let e of i.observers)b(e,n);a===0&&x()}}]}function l(e,t){let n=_(e,!1,t?.equals);return v(n),()=>n.disposed?n.value:(S(n),y(n),n.value)}function u(e){let t=_(e,!0);return v(t),C(t),()=>T(t)}function d(e){a++;try{e()}finally{a--,a===0&&x()}}function f(e){let t=r;r=null;try{return e()}finally{r=t}}function p(e){let t={owned:[],cleanups:[],owner:i},n=i,a=r;i=t,r=null;try{return e(()=>E(t))}finally{i=n,r=a}}function m(e){i&&i.cleanups.push(e)}function h(){return i}function g(e,t){let n=i,a=r;i=e,r=null;try{return t()}finally{i=n,r=a}}function _(e,t,r){return{fn:e,sources:new Set,isEffect:t,running:!1,disposed:!1,state:n,observers:new Set,value:void 0,equals:r??Object.is,owned:[],cleanups:[],owner:i}}function v(e){i&&i.owned.push(e)}function y(e){r&&(r.sources.add(e),e.observers.add(r))}function b(n,r){if(n.disposed||n.state>=r)return;let i=n.state===e;if(n.state=r,n.isEffect)o.add(n);else if(i)for(let e of n.observers)b(e,t)}function x(){if(!(a>0||s)){s=!0;try{for(;o.size>0;){let e=[...o];o.clear();for(let t of e)t.disposed||S(t)}}finally{s=!1}}}function S(r){if(!(r.disposed||r.state===e)){if(r.state===t)for(let e of r.sources)`fn`in e&&S(e);r.state===n&&C(r),r.state=e}}function C(t){if(t.running)throw Error(`ranview reactivity: cyclic dependency detected (a computation triggered itself).`);w(t);let a=r,o=i;r=t,i=t,t.running=!0,t.state=e;try{let e=t.fn();if(t.isEffect)typeof e==`function`&&t.cleanups.push(e);else{let r=t.value;if(t.value=e,t.observers.size>0&&!t.equals(r,e))for(let e of t.observers)b(e,n)}}finally{t.running=!1,r=a,i=o}}function w(e){for(let t of e.owned)T(t);e.owned.length=0;for(let t of e.cleanups)t();e.cleanups.length=0;for(let t of e.sources)t.observers.delete(e);e.sources.clear()}function T(e){e.disposed||(e.disposed=!0,w(e),e.observers.clear())}function E(e){for(let t of e.owned)T(t);e.owned.length=0;for(let t of e.cleanups)t();e.cleanups.length=0}exports.batch=d,exports.computed=l,exports.createEffect=u,exports.createRoot=p,exports.getOwner=h,exports.onCleanup=m,exports.runWithOwner=g,exports.signal=c,exports.untrack=f;
2
+ //# sourceMappingURL=signal.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signal.cjs","names":[],"sources":["../src/signal.ts"],"sourcesContent":["/**\n * Reactive primitives — signal / computed / createEffect / batch, plus an\n * ownership layer (createRoot / onCleanup / getOwner / runWithOwner) and untrack.\n *\n * Design reference: SwiftUI @Observable + Solid.js fine-grained reactivity.\n *\n * View = f(State)\n * signal — reactive source (@State)\n * computed — derived, LAZY + memoized (SwiftUI computed property / Solid memo)\n * createEffect— side effect that re-runs when its reads change (≈ body)\n * ownership — effects/memos form an owner tree; disposing a scope disposes\n * everything created under it (critical for MPA: tear down a page\n * and every effect it spawned is cleaned up in one call).\n *\n * Model (push-pull scheduling + lazy memos):\n * - Reading a source inside a computation links them both ways.\n * - Writing a signal marks direct observers DIRTY and their transitive memo\n * observers CHECK (\"maybe dirty\"), scheduling any dependent effects. Effects\n * flush once (deduped — diamond dependencies run an effect a single time).\n * - Memos recompute lazily on read, and only re-notify their observers when\n * their derived value actually changes (via `equals`). A CHECK computation\n * first pulls its sources up to date; if none changed value, it is skipped —\n * so a memo whose output is stable never wakes the effects behind it.\n * - Every computation is owned; re-running or disposing it first disposes its\n * owned children and runs its cleanups (no leaked nested effects).\n */\n\n// ── Types ───────────────────────────────────────────────────────────────────\n\nexport type Getter<T> = () => T;\nexport type Setter<T> = (value: T | ((prev: T) => T)) => void;\n\nexport interface SignalOptions<T> {\n /** Custom equality. Defaults to Object.is. Return true to skip the update. */\n equals?: (prev: T, next: T) => boolean;\n}\n\n/** Anything a computation can subscribe to (a signal, or a memo). */\ninterface SourceLike {\n observers: Set<Computation>;\n}\n\n/** Staleness of a computation. CLEAN → up to date; CHECK → a memo source may\n * have changed (verify before recomputing); DIRTY → definitely recompute. */\nconst CLEAN = 0;\nconst CHECK = 1;\nconst DIRTY = 2;\ntype State = typeof CLEAN | typeof CHECK | typeof DIRTY;\n\n/** An effect or a memo — a node that runs a function and tracks its reads. */\ninterface Computation extends Owner {\n fn: () => unknown;\n /** Sources (signals / memos) read during the last run. */\n sources: Set<SourceLike>;\n isEffect: boolean;\n running: boolean;\n disposed: boolean;\n state: State;\n // Memo-only fields:\n observers: Set<Computation>; // downstream that read this memo\n value: unknown;\n equals: (a: unknown, b: unknown) => boolean; // gates observer notification\n}\n\n/** A node that can own child computations + cleanups (effect, memo, or root). */\nexport interface Owner {\n owned: Computation[];\n cleanups: Array<() => void>;\n owner: Owner | null;\n}\n\n// ── Global tracking state ────────────────────────────────────────────────────\n\n/** The computation currently running — reads link to it. Null = untracked. */\nlet currentObserver: Computation | null = null;\n/** The owner scope currently active — new computations/cleanups attach here. */\nlet currentOwner: Owner | null = null;\n/** >0 while inside batch(): effect flushing is deferred. */\nlet batchDepth = 0;\n/** Effects queued for the next flush (a Set → each runs once per flush). */\nconst scheduled = new Set<Computation>();\n/** Guard against re-entrant flushes (an effect writing a signal mid-flush). */\nlet flushing = false;\n\n// ── signal ───────────────────────────────────────────────────────────────────\n\n/**\n * Create a reactive value. Returns a `[getter, setter]` tuple.\n *\n * const [count, setCount] = signal(0);\n * count(); // read — auto-tracked inside computed / createEffect\n * setCount(1); // write — notifies dependents; skipped if unchanged\n * setCount((n) => n + 1) // updater form\n */\nexport function signal<T>(initial: T, options?: SignalOptions<T>): [Getter<T>, Setter<T>] {\n const equals = options?.equals ?? Object.is;\n const node: SourceLike & { value: T } = { value: initial, observers: new Set() };\n\n const get: Getter<T> = () => {\n track(node);\n return node.value;\n };\n\n const set: Setter<T> = (next) => {\n const value = typeof next === 'function' ? (next as (prev: T) => T)(node.value) : next;\n if (equals(node.value, value)) return;\n node.value = value;\n // markStale only flags/schedules — it never mutates node.observers mid-loop.\n for (const observer of node.observers) markStale(observer, DIRTY);\n if (batchDepth === 0) flush();\n };\n\n return [get, set];\n}\n\n// ── computed (lazy memo) ─────────────────────────────────────────────────────\n\n/**\n * Derived, read-only, LAZY + memoized value. Recomputes only when read after a\n * dependency changed; unread memos never recompute. When a recompute produces an\n * equal value (default `Object.is`, override via `options.equals`) the memo does\n * NOT notify its observers — so effects behind a value-stable memo stay asleep.\n * Owned by the current scope, so it is disposed when that scope is.\n *\n * const fullName = computed(() => `${first()} ${last()}`);\n * fullName(); // computes on first read, caches until a dep's value changes\n */\nexport function computed<T>(fn: () => T, options?: SignalOptions<T>): Getter<T> {\n const node = makeComputation(fn, false, options?.equals as ((a: unknown, b: unknown) => boolean) | undefined);\n adopt(node);\n\n return () => {\n if (node.disposed) return node.value as T;\n updateIfNecessary(node); // recompute only if actually stale\n track(node); // link the reader to this memo\n return node.value as T;\n };\n}\n\n// ── createEffect ──────────────────────────────────────────────────────────────\n\n/**\n * Run `fn` immediately, then re-run it whenever a signal/memo it read changes.\n * Owned by the current scope. `fn` may return a cleanup, called before each\n * re-run and on dispose. Returns a dispose function.\n *\n * const dispose = createEffect(() => {\n * el.textContent = `Count: ${count()}`;\n * return () => {}; // optional cleanup\n * });\n */\nexport function createEffect(fn: () => void | (() => void)): () => void {\n const node = makeComputation(fn, true);\n adopt(node);\n runComputation(node);\n return () => disposeComputation(node);\n}\n\n// ── batch ─────────────────────────────────────────────────────────────────────\n\n/**\n * Coalesce multiple writes into one flush. Effects run once after `fn` returns,\n * deduplicated. Nested batches are absorbed by the outermost one.\n */\nexport function batch(fn: () => void): void {\n batchDepth++;\n try {\n fn();\n } finally {\n batchDepth--;\n if (batchDepth === 0) flush();\n }\n}\n\n// ── untrack ────────────────────────────────────────────────────────────────────\n\n/** Read signals without subscribing the current computation to them. */\nexport function untrack<T>(fn: () => T): T {\n const prev = currentObserver;\n currentObserver = null;\n try {\n return fn();\n } finally {\n currentObserver = prev;\n }\n}\n\n// ── Ownership ───────────────────────────────────────────────────────────────\n\n/**\n * Create a disposable reactive scope. Effects/memos created inside are owned by\n * it; calling the passed `dispose` tears them all down. Use one per page/route\n * in an MPA/SPA so navigating away cleans up every effect that page spawned.\n *\n * const dispose = createRoot((dispose) => { renderPage(); return dispose; });\n * // ...later, on navigation:\n * dispose();\n */\nexport function createRoot<T>(fn: (dispose: () => void) => T): T {\n const root: Owner = { owned: [], cleanups: [], owner: currentOwner };\n const prevOwner = currentOwner;\n const prevObserver = currentObserver;\n currentOwner = root;\n currentObserver = null; // roots do not track\n try {\n return fn(() => disposeOwner(root));\n } finally {\n currentOwner = prevOwner;\n currentObserver = prevObserver;\n }\n}\n\n/** Register a cleanup on the current scope, run when it re-runs or is disposed. */\nexport function onCleanup(fn: () => void): void {\n if (currentOwner) currentOwner.cleanups.push(fn);\n}\n\n/** The current owner scope (for advanced integrations, e.g. a router). */\nexport function getOwner(): Owner | null {\n return currentOwner;\n}\n\n/** Run `fn` under a specific owner scope (pairs with getOwner). */\nexport function runWithOwner<T>(owner: Owner | null, fn: () => T): T {\n const prevOwner = currentOwner;\n const prevObserver = currentObserver;\n currentOwner = owner;\n currentObserver = null;\n try {\n return fn();\n } finally {\n currentOwner = prevOwner;\n currentObserver = prevObserver;\n }\n}\n\n// ── Internals ────────────────────────────────────────────────────────────────\n\nfunction makeComputation(\n fn: () => unknown,\n isEffect: boolean,\n equals?: (a: unknown, b: unknown) => boolean,\n): Computation {\n return {\n fn,\n sources: new Set(),\n isEffect,\n running: false,\n disposed: false,\n state: DIRTY, // never run yet\n observers: new Set(),\n value: undefined,\n equals: equals ?? Object.is,\n owned: [],\n cleanups: [],\n owner: currentOwner,\n };\n}\n\n/** Attach a computation to the current owner so it is disposed with the scope. */\nfunction adopt(node: Computation): void {\n if (currentOwner) currentOwner.owned.push(node);\n}\n\nfunction track(source: SourceLike): void {\n if (currentObserver) {\n currentObserver.sources.add(source);\n source.observers.add(currentObserver);\n }\n}\n\n/**\n * Propagate a change. `state` is DIRTY when `node` directly read the changed\n * source, or CHECK when a memo upstream of it may have changed. Effects are\n * scheduled; a memo, the first time it leaves CLEAN, marks its own observers\n * CHECK so the staleness reaches the effects behind it. The `state >= incoming`\n * guard makes this idempotent (a DIRTY effect is not re-scheduled by a later\n * CHECK) and terminates diamonds.\n */\nfunction markStale(node: Computation, state: State): void {\n if (node.disposed || node.state >= state) return;\n const wasClean = node.state === CLEAN;\n node.state = state;\n if (node.isEffect) {\n scheduled.add(node);\n } else if (wasClean) {\n // A clean memo becoming stale: its observers are now \"maybe dirty\".\n // Recursion touches each observer's own observers, never this node's.\n for (const observer of node.observers) markStale(observer, CHECK);\n }\n}\n\nfunction flush(): void {\n if (batchDepth > 0 || flushing) return; // re-entrant writes drain via the loop below\n flushing = true;\n try {\n // Loop: an effect may schedule more effects as it runs.\n while (scheduled.size > 0) {\n const batchOfEffects = [...scheduled];\n scheduled.clear();\n for (const effect of batchOfEffects) {\n if (!effect.disposed) updateIfNecessary(effect);\n }\n }\n } finally {\n flushing = false;\n }\n}\n\n/**\n * Bring `node` up to date, recomputing only if it is genuinely dirty. A CHECK\n * node first pulls every memo source current: a source whose value changed marks\n * `node` DIRTY (via runComputation → markStale). If none did, `node` stays CHECK\n * and is skipped — this is what keeps a value-stable memo from waking effects.\n * All sources are refreshed (no early break) so none recompute mid-run and\n * spuriously re-schedule the node.\n */\nfunction updateIfNecessary(node: Computation): void {\n if (node.disposed || node.state === CLEAN) return;\n if (node.state === CHECK) {\n // Refreshing a source recomputes only that memo; it never mutates node.sources.\n for (const source of node.sources) {\n if ('fn' in source) updateIfNecessary(source as Computation); // only memos have `fn`\n }\n }\n if (node.state === DIRTY) runComputation(node);\n node.state = CLEAN;\n}\n\nfunction runComputation(node: Computation): void {\n if (node.running) {\n throw new Error('ranview reactivity: cyclic dependency detected (a computation triggered itself).');\n }\n cleanNode(node);\n\n const prevObserver = currentObserver;\n const prevOwner = currentOwner;\n currentObserver = node;\n currentOwner = node;\n node.running = true;\n // Mark clean up front: a genuine re-entrant write inside fn (a cycle) re-marks\n // and re-enters, tripping the `running` guard above. Memo sources are already\n // current here (updateIfNecessary refreshed them), so no benign re-mark occurs.\n node.state = CLEAN;\n try {\n const result = node.fn();\n if (node.isEffect) {\n if (typeof result === 'function') node.cleanups.push(result as () => void);\n } else {\n // memo: store value; notify observers only when the value actually changed.\n const prev = node.value;\n node.value = result;\n // Notify only when there are observers AND the value changed. Skipping the\n // empty-observer case also means `equals` never sees the initial `undefined`\n // sentinel (the first run always precedes any observer). markStale only\n // flags/schedules — it never mutates node.observers here.\n if (node.observers.size > 0 && !node.equals(prev, result)) {\n for (const observer of node.observers) markStale(observer, DIRTY);\n }\n }\n } finally {\n node.running = false;\n currentObserver = prevObserver;\n currentOwner = prevOwner;\n }\n}\n\n/** Dispose owned children, run cleanups, and unlink from all sources. */\nfunction cleanNode(node: Computation): void {\n for (const child of node.owned) disposeComputation(child);\n node.owned.length = 0;\n\n for (const cleanup of node.cleanups) cleanup();\n node.cleanups.length = 0;\n\n for (const source of node.sources) source.observers.delete(node);\n node.sources.clear();\n}\n\nfunction disposeComputation(node: Computation): void {\n if (node.disposed) return;\n node.disposed = true;\n cleanNode(node);\n node.observers.clear();\n}\n\nfunction disposeOwner(owner: Owner): void {\n for (const child of owner.owned) disposeComputation(child);\n owner.owned.length = 0;\n for (const cleanup of owner.cleanups) cleanup();\n owner.cleanups.length = 0;\n}\n"],"mappings":"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"}
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Reactive primitives — signal / computed / createEffect / batch, plus an
3
+ * ownership layer (createRoot / onCleanup / getOwner / runWithOwner) and untrack.
4
+ *
5
+ * Design reference: SwiftUI @Observable + Solid.js fine-grained reactivity.
6
+ *
7
+ * View = f(State)
8
+ * signal — reactive source (@State)
9
+ * computed — derived, LAZY + memoized (SwiftUI computed property / Solid memo)
10
+ * createEffect— side effect that re-runs when its reads change (≈ body)
11
+ * ownership — effects/memos form an owner tree; disposing a scope disposes
12
+ * everything created under it (critical for MPA: tear down a page
13
+ * and every effect it spawned is cleaned up in one call).
14
+ *
15
+ * Model (push-pull scheduling + lazy memos):
16
+ * - Reading a source inside a computation links them both ways.
17
+ * - Writing a signal marks direct observers DIRTY and their transitive memo
18
+ * observers CHECK ("maybe dirty"), scheduling any dependent effects. Effects
19
+ * flush once (deduped — diamond dependencies run an effect a single time).
20
+ * - Memos recompute lazily on read, and only re-notify their observers when
21
+ * their derived value actually changes (via `equals`). A CHECK computation
22
+ * first pulls its sources up to date; if none changed value, it is skipped —
23
+ * so a memo whose output is stable never wakes the effects behind it.
24
+ * - Every computation is owned; re-running or disposing it first disposes its
25
+ * owned children and runs its cleanups (no leaked nested effects).
26
+ */
27
+ export type Getter<T> = () => T;
28
+ export type Setter<T> = (value: T | ((prev: T) => T)) => void;
29
+ export interface SignalOptions<T> {
30
+ /** Custom equality. Defaults to Object.is. Return true to skip the update. */
31
+ equals?: (prev: T, next: T) => boolean;
32
+ }
33
+ /** Anything a computation can subscribe to (a signal, or a memo). */
34
+ interface SourceLike {
35
+ observers: Set<Computation>;
36
+ }
37
+ /** Staleness of a computation. CLEAN → up to date; CHECK → a memo source may
38
+ * have changed (verify before recomputing); DIRTY → definitely recompute. */
39
+ declare const CLEAN = 0;
40
+ declare const CHECK = 1;
41
+ declare const DIRTY = 2;
42
+ type State = typeof CLEAN | typeof CHECK | typeof DIRTY;
43
+ /** An effect or a memo — a node that runs a function and tracks its reads. */
44
+ interface Computation extends Owner {
45
+ fn: () => unknown;
46
+ /** Sources (signals / memos) read during the last run. */
47
+ sources: Set<SourceLike>;
48
+ isEffect: boolean;
49
+ running: boolean;
50
+ disposed: boolean;
51
+ state: State;
52
+ observers: Set<Computation>;
53
+ value: unknown;
54
+ equals: (a: unknown, b: unknown) => boolean;
55
+ }
56
+ /** A node that can own child computations + cleanups (effect, memo, or root). */
57
+ export interface Owner {
58
+ owned: Computation[];
59
+ cleanups: Array<() => void>;
60
+ owner: Owner | null;
61
+ }
62
+ /**
63
+ * Create a reactive value. Returns a `[getter, setter]` tuple.
64
+ *
65
+ * const [count, setCount] = signal(0);
66
+ * count(); // read — auto-tracked inside computed / createEffect
67
+ * setCount(1); // write — notifies dependents; skipped if unchanged
68
+ * setCount((n) => n + 1) // updater form
69
+ */
70
+ export declare function signal<T>(initial: T, options?: SignalOptions<T>): [Getter<T>, Setter<T>];
71
+ /**
72
+ * Derived, read-only, LAZY + memoized value. Recomputes only when read after a
73
+ * dependency changed; unread memos never recompute. When a recompute produces an
74
+ * equal value (default `Object.is`, override via `options.equals`) the memo does
75
+ * NOT notify its observers — so effects behind a value-stable memo stay asleep.
76
+ * Owned by the current scope, so it is disposed when that scope is.
77
+ *
78
+ * const fullName = computed(() => `${first()} ${last()}`);
79
+ * fullName(); // computes on first read, caches until a dep's value changes
80
+ */
81
+ export declare function computed<T>(fn: () => T, options?: SignalOptions<T>): Getter<T>;
82
+ /**
83
+ * Run `fn` immediately, then re-run it whenever a signal/memo it read changes.
84
+ * Owned by the current scope. `fn` may return a cleanup, called before each
85
+ * re-run and on dispose. Returns a dispose function.
86
+ *
87
+ * const dispose = createEffect(() => {
88
+ * el.textContent = `Count: ${count()}`;
89
+ * return () => {}; // optional cleanup
90
+ * });
91
+ */
92
+ export declare function createEffect(fn: () => void | (() => void)): () => void;
93
+ /**
94
+ * Coalesce multiple writes into one flush. Effects run once after `fn` returns,
95
+ * deduplicated. Nested batches are absorbed by the outermost one.
96
+ */
97
+ export declare function batch(fn: () => void): void;
98
+ /** Read signals without subscribing the current computation to them. */
99
+ export declare function untrack<T>(fn: () => T): T;
100
+ /**
101
+ * Create a disposable reactive scope. Effects/memos created inside are owned by
102
+ * it; calling the passed `dispose` tears them all down. Use one per page/route
103
+ * in an MPA/SPA so navigating away cleans up every effect that page spawned.
104
+ *
105
+ * const dispose = createRoot((dispose) => { renderPage(); return dispose; });
106
+ * // ...later, on navigation:
107
+ * dispose();
108
+ */
109
+ export declare function createRoot<T>(fn: (dispose: () => void) => T): T;
110
+ /** Register a cleanup on the current scope, run when it re-runs or is disposed. */
111
+ export declare function onCleanup(fn: () => void): void;
112
+ /** The current owner scope (for advanced integrations, e.g. a router). */
113
+ export declare function getOwner(): Owner | null;
114
+ /** Run `fn` under a specific owner scope (pairs with getOwner). */
115
+ export declare function runWithOwner<T>(owner: Owner | null, fn: () => T): T;
116
+ export {};
117
+ //# sourceMappingURL=signal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signal.d.ts","sourceRoot":"","sources":["../src/signal.ts"],"names":[],"mappings":"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"}
package/dist/signal.js ADDED
@@ -0,0 +1,154 @@
1
+ //#region src/signal.ts
2
+ var e = null, t = null, n = 0, r = /* @__PURE__ */ new Set(), i = !1;
3
+ function a(e, t) {
4
+ let r = t?.equals ?? Object.is, i = {
5
+ value: e,
6
+ observers: /* @__PURE__ */ new Set()
7
+ };
8
+ return [() => (g(i), i.value), (e) => {
9
+ let t = typeof e == "function" ? e(i.value) : e;
10
+ if (!r(i.value, t)) {
11
+ i.value = t;
12
+ for (let e of i.observers) _(e, 2);
13
+ n === 0 && v();
14
+ }
15
+ }];
16
+ }
17
+ function o(e, t) {
18
+ let n = m(e, !1, t?.equals);
19
+ return h(n), () => n.disposed ? n.value : (y(n), g(n), n.value);
20
+ }
21
+ function s(e) {
22
+ let t = m(e, !0);
23
+ return h(t), b(t), () => S(t);
24
+ }
25
+ function c(e) {
26
+ n++;
27
+ try {
28
+ e();
29
+ } finally {
30
+ n--, n === 0 && v();
31
+ }
32
+ }
33
+ function l(t) {
34
+ let n = e;
35
+ e = null;
36
+ try {
37
+ return t();
38
+ } finally {
39
+ e = n;
40
+ }
41
+ }
42
+ function u(n) {
43
+ let r = {
44
+ owned: [],
45
+ cleanups: [],
46
+ owner: t
47
+ }, i = t, a = e;
48
+ t = r, e = null;
49
+ try {
50
+ return n(() => C(r));
51
+ } finally {
52
+ t = i, e = a;
53
+ }
54
+ }
55
+ function d(e) {
56
+ t && t.cleanups.push(e);
57
+ }
58
+ function f() {
59
+ return t;
60
+ }
61
+ function p(n, r) {
62
+ let i = t, a = e;
63
+ t = n, e = null;
64
+ try {
65
+ return r();
66
+ } finally {
67
+ t = i, e = a;
68
+ }
69
+ }
70
+ function m(e, n, r) {
71
+ return {
72
+ fn: e,
73
+ sources: /* @__PURE__ */ new Set(),
74
+ isEffect: n,
75
+ running: !1,
76
+ disposed: !1,
77
+ state: 2,
78
+ observers: /* @__PURE__ */ new Set(),
79
+ value: void 0,
80
+ equals: r ?? Object.is,
81
+ owned: [],
82
+ cleanups: [],
83
+ owner: t
84
+ };
85
+ }
86
+ function h(e) {
87
+ t && t.owned.push(e);
88
+ }
89
+ function g(t) {
90
+ e && (e.sources.add(t), t.observers.add(e));
91
+ }
92
+ function _(e, t) {
93
+ if (e.disposed || e.state >= t) return;
94
+ let n = e.state === 0;
95
+ if (e.state = t, e.isEffect) r.add(e);
96
+ else if (n) for (let t of e.observers) _(t, 1);
97
+ }
98
+ function v() {
99
+ if (!(n > 0 || i)) {
100
+ i = !0;
101
+ try {
102
+ for (; r.size > 0;) {
103
+ let e = [...r];
104
+ r.clear();
105
+ for (let t of e) t.disposed || y(t);
106
+ }
107
+ } finally {
108
+ i = !1;
109
+ }
110
+ }
111
+ }
112
+ function y(e) {
113
+ if (!(e.disposed || e.state === 0)) {
114
+ if (e.state === 1) for (let t of e.sources) "fn" in t && y(t);
115
+ e.state === 2 && b(e), e.state = 0;
116
+ }
117
+ }
118
+ function b(n) {
119
+ if (n.running) throw Error("ranview reactivity: cyclic dependency detected (a computation triggered itself).");
120
+ x(n);
121
+ let r = e, i = t;
122
+ e = n, t = n, n.running = !0, n.state = 0;
123
+ try {
124
+ let e = n.fn();
125
+ if (n.isEffect) typeof e == "function" && n.cleanups.push(e);
126
+ else {
127
+ let t = n.value;
128
+ if (n.value = e, n.observers.size > 0 && !n.equals(t, e)) for (let e of n.observers) _(e, 2);
129
+ }
130
+ } finally {
131
+ n.running = !1, e = r, t = i;
132
+ }
133
+ }
134
+ function x(e) {
135
+ for (let t of e.owned) S(t);
136
+ e.owned.length = 0;
137
+ for (let t of e.cleanups) t();
138
+ e.cleanups.length = 0;
139
+ for (let t of e.sources) t.observers.delete(e);
140
+ e.sources.clear();
141
+ }
142
+ function S(e) {
143
+ e.disposed || (e.disposed = !0, x(e), e.observers.clear());
144
+ }
145
+ function C(e) {
146
+ for (let t of e.owned) S(t);
147
+ e.owned.length = 0;
148
+ for (let t of e.cleanups) t();
149
+ e.cleanups.length = 0;
150
+ }
151
+ //#endregion
152
+ export { c as batch, o as computed, s as createEffect, u as createRoot, f as getOwner, d as onCleanup, p as runWithOwner, a as signal, l as untrack };
153
+
154
+ //# sourceMappingURL=signal.js.map