@llui/dom 0.0.23 → 0.0.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -21
- package/dist/dom-env.d.ts +67 -0
- package/dist/dom-env.d.ts.map +1 -0
- package/dist/dom-env.js +61 -0
- package/dist/dom-env.js.map +1 -0
- package/dist/el-split.d.ts.map +1 -1
- package/dist/el-split.js +4 -4
- package/dist/el-split.js.map +1 -1
- package/dist/el-template.d.ts +14 -1
- package/dist/el-template.d.ts.map +1 -1
- package/dist/el-template.js +41 -7
- package/dist/el-template.js.map +1 -1
- package/dist/elements.js +2 -2
- package/dist/elements.js.map +1 -1
- package/dist/hmr.d.ts.map +1 -1
- package/dist/hmr.js +1 -0
- package/dist/hmr.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/mathml-elements.js +2 -2
- package/dist/mathml-elements.js.map +1 -1
- package/dist/mount.d.ts +8 -0
- package/dist/mount.d.ts.map +1 -1
- package/dist/mount.js +14 -9
- package/dist/mount.js.map +1 -1
- package/dist/primitives/branch.js +1 -1
- package/dist/primitives/branch.js.map +1 -1
- package/dist/primitives/child.d.ts.map +1 -1
- package/dist/primitives/child.js +5 -2
- package/dist/primitives/child.js.map +1 -1
- package/dist/primitives/client-only.d.ts +108 -0
- package/dist/primitives/client-only.d.ts.map +1 -0
- package/dist/primitives/client-only.js +110 -0
- package/dist/primitives/client-only.js.map +1 -0
- package/dist/primitives/each.d.ts.map +1 -1
- package/dist/primitives/each.js +14 -9
- package/dist/primitives/each.js.map +1 -1
- package/dist/primitives/foreign.d.ts.map +1 -1
- package/dist/primitives/foreign.js +83 -44
- package/dist/primitives/foreign.js.map +1 -1
- package/dist/primitives/lazy.js +2 -2
- package/dist/primitives/lazy.js.map +1 -1
- package/dist/primitives/selector.js +1 -1
- package/dist/primitives/selector.js.map +1 -1
- package/dist/primitives/slice.d.ts.map +1 -1
- package/dist/primitives/slice.js +5 -0
- package/dist/primitives/slice.js.map +1 -1
- package/dist/primitives/text.js +3 -3
- package/dist/primitives/text.js.map +1 -1
- package/dist/primitives/unsafe-html.js +11 -13
- package/dist/primitives/unsafe-html.js.map +1 -1
- package/dist/primitives/virtual-each.d.ts.map +1 -1
- package/dist/primitives/virtual-each.js +5 -3
- package/dist/primitives/virtual-each.js.map +1 -1
- package/dist/render-context.d.ts +9 -0
- package/dist/render-context.d.ts.map +1 -1
- package/dist/render-context.js.map +1 -1
- package/dist/ssr/jsdom.d.ts +18 -0
- package/dist/ssr/jsdom.d.ts.map +1 -0
- package/dist/ssr/jsdom.js +38 -0
- package/dist/ssr/jsdom.js.map +1 -0
- package/dist/ssr/legacy.d.ts +25 -0
- package/dist/ssr/legacy.d.ts.map +1 -0
- package/dist/ssr/legacy.js +61 -0
- package/dist/ssr/legacy.js.map +1 -0
- package/dist/ssr/linkedom.d.ts +17 -0
- package/dist/ssr/linkedom.d.ts.map +1 -0
- package/dist/ssr/linkedom.js +36 -0
- package/dist/ssr/linkedom.js.map +1 -0
- package/dist/ssr-dom.d.ts +11 -7
- package/dist/ssr-dom.d.ts.map +1 -1
- package/dist/ssr-dom.js +10 -31
- package/dist/ssr-dom.js.map +1 -1
- package/dist/ssr.d.ts +10 -5
- package/dist/ssr.d.ts.map +1 -1
- package/dist/ssr.js +11 -7
- package/dist/ssr.js.map +1 -1
- package/dist/svg-elements.js +2 -2
- package/dist/svg-elements.js.map +1 -1
- package/dist/types.d.ts +15 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/update-loop.d.ts +3 -1
- package/dist/update-loop.d.ts.map +1 -1
- package/dist/update-loop.js +15 -1
- package/dist/update-loop.js.map +1 -1
- package/dist/view-helpers.d.ts +7 -0
- package/dist/view-helpers.d.ts.map +1 -1
- package/dist/view-helpers.js +2 -0
- package/dist/view-helpers.js.map +1 -1
- package/package.json +25 -1
- package/dist/scope.d.ts +0 -39
- package/dist/scope.d.ts.map +0 -1
- package/dist/scope.js +0 -250
- package/dist/scope.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"branch.js","sourceRoot":"","sources":["../../src/primitives/branch.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BranchOptions, Lifetime } from '../types.js'\nimport { getRenderContext, setRenderContext, clearRenderContext } from '../render-context.js'\nimport { createLifetime, disposeLifetime, addDisposer } from '../lifetime.js'\nimport { setFlatBindings } from '../binding.js'\nimport { createView } from '../view-helpers.js'\nimport { FULL_MASK } from '../update-loop.js'\nimport { pushMountQueue, popMountQueue, flushMountQueue } from './on-mount.js'\nimport type { StructuralBlock } from '../structural.js'\n\nexport function branch<S, M = unknown, K extends string = string>(\n opts: BranchOptions<S, M, K>,\n): Node[] {\n const ctx = getRenderContext('branch')\n const parentLifetime = ctx.rootLifetime\n const blocks = ctx.structuralBlocks\n const send = ctx.send as (msg: M) => void\n\n const anchor = document.createComment('branch')\n\n let currentKey = opts.on(ctx.state as S)\n let currentLifetime: Lifetime | null = null\n let currentNodes: Node[] = []\n\n const block: StructuralBlock = {\n mask: (opts as { __mask?: number }).__mask ?? FULL_MASK,\n reconcile(state: unknown) {\n const newKey = opts.on(state as S)\n if (Object.is(newKey, currentKey)) return\n\n const parent = anchor.parentNode\n if (!parent) return\n\n const leavingNodes = currentNodes\n const leavingLifetime = currentLifetime\n\n // Build new arm first (before removing old — for FLIP animations)\n currentNodes = []\n currentLifetime = null\n currentKey = newKey\n\n const newCaseKey = String(newKey) as K\n const newBuilder = opts.cases?.[newCaseKey] ?? opts.default\n // Collect onMount callbacks from the new case into a local queue,\n // then flush them SYNCHRONOUSLY after the new nodes are inserted.\n // Without this, onMount inside a branch case would see stale DOM\n // (nodes not yet attached) OR fall back to queueMicrotask and\n // race with synchronous event dispatches after the reconcile.\n let onMountQueue: Array<() => void> | null = null\n if (newBuilder) {\n const mq = pushMountQueue()\n onMountQueue = mq.queue\n currentLifetime = createLifetime(parentLifetime)\n currentLifetime._kind =\n opts.__disposalCause === 'show-hide'\n ? 'show'\n : opts.__disposalCause === 'scope-rebuild'\n ? 'scope'\n : 'branch'\n setFlatBindings(ctx.allBindings)\n setRenderContext({ ...ctx, rootLifetime: currentLifetime, state })\n currentNodes = newBuilder(createView<S, M>(send))\n clearRenderContext()\n setFlatBindings(null)\n popMountQueue(mq.prev)\n\n const ref = anchor.nextSibling\n for (const node of currentNodes) {\n parent.insertBefore(node, ref)\n }\n }\n if (onMountQueue) flushMountQueue(onMountQueue)\n\n // Fire enter for new nodes\n if (opts.enter && currentNodes.length > 0) {\n opts.enter(currentNodes)\n }\n\n // Handle leave — may be deferred via Promise\n const removeOld = () => {\n for (const node of leavingNodes) {\n if (node.parentNode) node.parentNode.removeChild(node)\n }\n if (leavingLifetime) {\n // Tag BEFORE dispose so the disposer log records the cause.\n // `show()` passes `__disposalCause: 'show-hide'`; raw branch()\n // defaults to `'branch-swap'`. Tag wins over any pre-existing\n // value set by an inner primitive so the outermost cause is\n // reported (matches how humans describe the event).\n leavingLifetime.disposalCause = opts.__disposalCause ?? 'branch-swap'\n disposeLifetime(leavingLifetime)\n }\n }\n\n if (leavingNodes.length > 0 && opts.leave) {\n const result = opts.leave(leavingNodes)\n if (result && typeof (result as Promise<void>).then === 'function') {\n ;(result as Promise<void>).then(removeOld)\n } else {\n removeOld()\n }\n } else {\n removeOld()\n }\n },\n }\n\n // Register the block BEFORE running the initial builder so that parent\n // blocks always precede their nested children in the flat blocks array.\n // This guarantees correct Phase 1 iteration order: parents reconcile\n // first, so a parent that unmounts its old arm can dispose nested child\n // blocks (splicing them out of this array) without corrupting the loop\n // index — the splice only affects entries to the RIGHT of the parent.\n blocks.push(block)\n\n const caseKey = String(currentKey) as K\n const builder = opts.cases?.[caseKey] ?? opts.default\n // Initial-mount onMount callbacks are handled by the outer mountApp\n // queue — we're still inside the first view() call. branch doesn't\n // insert into the DOM at this point (the anchor + initial children\n // are returned to the parent), so we don't need to flush here.\n if (builder) {\n currentLifetime = createLifetime(parentLifetime)\n currentLifetime._kind =\n opts.__disposalCause === 'show-hide'\n ? 'show'\n : opts.__disposalCause === 'scope-rebuild'\n ? 'scope'\n : 'branch'\n setRenderContext({ ...ctx, rootLifetime: currentLifetime })\n currentNodes = builder(createView<S, M>(send))\n clearRenderContext()\n setRenderContext(ctx)\n\n // Fire enter on initial mount\n if (opts.enter && currentNodes.length > 0) {\n opts.enter(currentNodes)\n }\n }\n\n addDisposer(parentLifetime, () => {\n const idx = blocks.indexOf(block)\n if (idx !== -1) blocks.splice(idx, 1)\n if (currentLifetime) {\n disposeLifetime(currentLifetime)\n currentLifetime = null\n }\n })\n\n return [anchor, ...currentNodes]\n}\n"]}
|
|
1
|
+
{"version":3,"file":"branch.js","sourceRoot":"","sources":["../../src/primitives/branch.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BranchOptions, Lifetime } from '../types.js'\nimport { getRenderContext, setRenderContext, clearRenderContext } from '../render-context.js'\nimport { createLifetime, disposeLifetime, addDisposer } from '../lifetime.js'\nimport { setFlatBindings } from '../binding.js'\nimport { createView } from '../view-helpers.js'\nimport { FULL_MASK } from '../update-loop.js'\nimport { pushMountQueue, popMountQueue, flushMountQueue } from './on-mount.js'\nimport type { StructuralBlock } from '../structural.js'\n\nexport function branch<S, M = unknown, K extends string = string>(\n opts: BranchOptions<S, M, K>,\n): Node[] {\n const ctx = getRenderContext('branch')\n const parentLifetime = ctx.rootLifetime\n const blocks = ctx.structuralBlocks\n const send = ctx.send as (msg: M) => void\n\n const anchor = ctx.dom.createComment('branch')\n\n let currentKey = opts.on(ctx.state as S)\n let currentLifetime: Lifetime | null = null\n let currentNodes: Node[] = []\n\n const block: StructuralBlock = {\n mask: (opts as { __mask?: number }).__mask ?? FULL_MASK,\n reconcile(state: unknown) {\n const newKey = opts.on(state as S)\n if (Object.is(newKey, currentKey)) return\n\n const parent = anchor.parentNode\n if (!parent) return\n\n const leavingNodes = currentNodes\n const leavingLifetime = currentLifetime\n\n // Build new arm first (before removing old — for FLIP animations)\n currentNodes = []\n currentLifetime = null\n currentKey = newKey\n\n const newCaseKey = String(newKey) as K\n const newBuilder = opts.cases?.[newCaseKey] ?? opts.default\n // Collect onMount callbacks from the new case into a local queue,\n // then flush them SYNCHRONOUSLY after the new nodes are inserted.\n // Without this, onMount inside a branch case would see stale DOM\n // (nodes not yet attached) OR fall back to queueMicrotask and\n // race with synchronous event dispatches after the reconcile.\n let onMountQueue: Array<() => void> | null = null\n if (newBuilder) {\n const mq = pushMountQueue()\n onMountQueue = mq.queue\n currentLifetime = createLifetime(parentLifetime)\n currentLifetime._kind =\n opts.__disposalCause === 'show-hide'\n ? 'show'\n : opts.__disposalCause === 'scope-rebuild'\n ? 'scope'\n : 'branch'\n setFlatBindings(ctx.allBindings)\n setRenderContext({ ...ctx, rootLifetime: currentLifetime, state })\n currentNodes = newBuilder(createView<S, M>(send))\n clearRenderContext()\n setFlatBindings(null)\n popMountQueue(mq.prev)\n\n const ref = anchor.nextSibling\n for (const node of currentNodes) {\n parent.insertBefore(node, ref)\n }\n }\n if (onMountQueue) flushMountQueue(onMountQueue)\n\n // Fire enter for new nodes\n if (opts.enter && currentNodes.length > 0) {\n opts.enter(currentNodes)\n }\n\n // Handle leave — may be deferred via Promise\n const removeOld = () => {\n for (const node of leavingNodes) {\n if (node.parentNode) node.parentNode.removeChild(node)\n }\n if (leavingLifetime) {\n // Tag BEFORE dispose so the disposer log records the cause.\n // `show()` passes `__disposalCause: 'show-hide'`; raw branch()\n // defaults to `'branch-swap'`. Tag wins over any pre-existing\n // value set by an inner primitive so the outermost cause is\n // reported (matches how humans describe the event).\n leavingLifetime.disposalCause = opts.__disposalCause ?? 'branch-swap'\n disposeLifetime(leavingLifetime)\n }\n }\n\n if (leavingNodes.length > 0 && opts.leave) {\n const result = opts.leave(leavingNodes)\n if (result && typeof (result as Promise<void>).then === 'function') {\n ;(result as Promise<void>).then(removeOld)\n } else {\n removeOld()\n }\n } else {\n removeOld()\n }\n },\n }\n\n // Register the block BEFORE running the initial builder so that parent\n // blocks always precede their nested children in the flat blocks array.\n // This guarantees correct Phase 1 iteration order: parents reconcile\n // first, so a parent that unmounts its old arm can dispose nested child\n // blocks (splicing them out of this array) without corrupting the loop\n // index — the splice only affects entries to the RIGHT of the parent.\n blocks.push(block)\n\n const caseKey = String(currentKey) as K\n const builder = opts.cases?.[caseKey] ?? opts.default\n // Initial-mount onMount callbacks are handled by the outer mountApp\n // queue — we're still inside the first view() call. branch doesn't\n // insert into the DOM at this point (the anchor + initial children\n // are returned to the parent), so we don't need to flush here.\n if (builder) {\n currentLifetime = createLifetime(parentLifetime)\n currentLifetime._kind =\n opts.__disposalCause === 'show-hide'\n ? 'show'\n : opts.__disposalCause === 'scope-rebuild'\n ? 'scope'\n : 'branch'\n setRenderContext({ ...ctx, rootLifetime: currentLifetime })\n currentNodes = builder(createView<S, M>(send))\n clearRenderContext()\n setRenderContext(ctx)\n\n // Fire enter on initial mount\n if (opts.enter && currentNodes.length > 0) {\n opts.enter(currentNodes)\n }\n }\n\n addDisposer(parentLifetime, () => {\n const idx = blocks.indexOf(block)\n if (idx !== -1) blocks.splice(idx, 1)\n if (currentLifetime) {\n disposeLifetime(currentLifetime)\n currentLifetime = null\n }\n })\n\n return [anchor, ...currentNodes]\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"child.d.ts","sourceRoot":"","sources":["../../src/primitives/child.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAgB,MAAM,aAAa,CAAA;AAU7D,wBAAgB,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,GAAG,IAAI,EAAE,
|
|
1
|
+
{"version":3,"file":"child.d.ts","sourceRoot":"","sources":["../../src/primitives/child.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAgB,MAAM,aAAa,CAAA;AAU7D,wBAAgB,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,GAAG,IAAI,EAAE,CAyGtE"}
|
package/dist/primitives/child.js
CHANGED
|
@@ -25,7 +25,10 @@ export function child(opts) {
|
|
|
25
25
|
const parentSend = parentCtx.send;
|
|
26
26
|
const childDef = opts.def;
|
|
27
27
|
const initialProps = opts.props(parentCtx.state);
|
|
28
|
-
|
|
28
|
+
// Child component inherits the parent's DOM env — render-context
|
|
29
|
+
// threading means the same env flows from mountApp to child() to any
|
|
30
|
+
// nested primitives inside the child's view.
|
|
31
|
+
const childInst = createComponentInstance(childDef, initialProps, null, parentCtx.dom);
|
|
29
32
|
// Wrap child's send to intercept messages for onMsg → parent
|
|
30
33
|
const originalSend = childInst.send;
|
|
31
34
|
childInst.send = (msg) => {
|
|
@@ -71,7 +74,7 @@ export function child(opts) {
|
|
|
71
74
|
prevProps = { ...newProps };
|
|
72
75
|
}),
|
|
73
76
|
kind: 'effect',
|
|
74
|
-
node:
|
|
77
|
+
node: parentCtx.dom.createComment('child:' + opts.key),
|
|
75
78
|
perItem: false,
|
|
76
79
|
});
|
|
77
80
|
// Run the child's view within the child's render context
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"child.js","sourceRoot":"","sources":["../../src/primitives/child.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAC7F,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC7E,OAAO,EAAE,uBAAuB,EAAE,aAAa,EAA0B,MAAM,mBAAmB,CAAA;AAClG,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAC9D,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAE/C,MAAM,SAAS,GAAG,UAAU,CAAA;AAE5B,MAAM,UAAU,KAAK,CAAY,IAA6B;IAC5D,gEAAgE;IAChE,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;QACrC,MAAM,IAAI,KAAK,CACb,UAAU,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,iDAAiD;YACzE,iFAAiF;YACjF,mBAAmB,CACtB,CAAA;IACH,CAAC;IAED,MAAM,SAAS,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAA;IAC3C,MAAM,cAAc,GAAG,SAAS,CAAC,YAAY,CAAA;IAC7C,MAAM,aAAa,GAAG,cAAc,CAAC,cAAc,CAAC,CAAA;IACpD,aAAa,CAAC,KAAK,GAAG,OAAO,CAAA;IAC7B,8EAA8E;IAC9E,yEAAyE;IACzE,sEAAsE;IACtE,0EAA0E;IAC1E,yEAAyE;IACzE,aAAa,CAAC,aAAa,GAAG,eAAe,CAAA;IAC7C,MAAM,UAAU,GAAG,SAAS,CAAC,IAAI,CAAA;IAEjC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAsE,CAAA;IAC5F,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,KAAU,CAAC,CAAA;IACrD,MAAM,SAAS,GAAG,uBAAuB,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"child.js","sourceRoot":"","sources":["../../src/primitives/child.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAC7F,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC7E,OAAO,EAAE,uBAAuB,EAAE,aAAa,EAA0B,MAAM,mBAAmB,CAAA;AAClG,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAC9D,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAE/C,MAAM,SAAS,GAAG,UAAU,CAAA;AAE5B,MAAM,UAAU,KAAK,CAAY,IAA6B;IAC5D,gEAAgE;IAChE,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;QACrC,MAAM,IAAI,KAAK,CACb,UAAU,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,iDAAiD;YACzE,iFAAiF;YACjF,mBAAmB,CACtB,CAAA;IACH,CAAC;IAED,MAAM,SAAS,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAA;IAC3C,MAAM,cAAc,GAAG,SAAS,CAAC,YAAY,CAAA;IAC7C,MAAM,aAAa,GAAG,cAAc,CAAC,cAAc,CAAC,CAAA;IACpD,aAAa,CAAC,KAAK,GAAG,OAAO,CAAA;IAC7B,8EAA8E;IAC9E,yEAAyE;IACzE,sEAAsE;IACtE,0EAA0E;IAC1E,yEAAyE;IACzE,aAAa,CAAC,aAAa,GAAG,eAAe,CAAA;IAC7C,MAAM,UAAU,GAAG,SAAS,CAAC,IAAI,CAAA;IAEjC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAsE,CAAA;IAC5F,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,KAAU,CAAC,CAAA;IACrD,iEAAiE;IACjE,qEAAqE;IACrE,6CAA6C;IAC7C,MAAM,SAAS,GAAG,uBAAuB,CAAC,QAAQ,EAAE,YAAY,EAAE,IAAI,EAAE,SAAS,CAAC,GAAG,CAAC,CAAA;IAEtF,6DAA6D;IAC7D,MAAM,YAAY,GAAG,SAAS,CAAC,IAAI,CAAA;IACnC,SAAS,CAAC,IAAI,GAAG,CAAC,GAAW,EAAE,EAAE;QAC/B,YAAY,CAAC,GAAG,CAAC,CAAA;QACjB,IAAI,IAAI,CAAC,KAAK,IAAI,UAAU,EAAE,CAAC;YAC7B,qDAAqD;YACrD,cAAc,CAAC,GAAG,EAAE;gBAClB,MAAM,SAAS,GAAG,IAAI,CAAC,KAAM,CAAC,GAAG,CAAC,CAAA;gBAClC,IAAI,SAAS,IAAI,IAAI,EAAE,CAAC;oBACtB,UAAU,CAAC,SAAS,CAAC,CAAA;gBACvB,CAAC;YACH,CAAC,CAAC,CAAA;QACJ,CAAC;IACH,CAAC,CAAA;IAED,+BAA+B;IAC/B,IAAI,SAAS,GAA4B,EAAE,GAAG,YAAY,EAAE,CAAA;IAE5D,2EAA2E;IAC3E,0EAA0E;IAC1E,0EAA0E;IAC1E,sEAAsE;IACtE,gDAAgD;IAChD,aAAa,CAAC,aAAa,EAAE;QAC3B,IAAI,EAAE,SAAS;QACf,QAAQ,EAAE,CAAC,CAAC,WAAc,EAAE,EAAE;YAC5B,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAA;YAExC,IAAI,OAAO,GAAG,KAAK,CAAA;YACnB,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACxC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;oBAC9C,OAAO,GAAG,IAAI,CAAA;oBACd,MAAK;gBACP,CAAC;YACH,CAAC;YAED,IAAI,OAAO,IAAI,QAAQ,CAAC,QAAQ,EAAE,CAAC;gBACjC,MAAM,GAAG,GAAG,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;gBACvC,gEAAgE;gBAChE,gEAAgE;gBAChE,gEAAgE;gBAChE,gEAAgE;gBAChE,WAAW;gBACX,YAAY,CAAC,GAAG,CAAC,CAAA;gBACjB,aAAa,CAAC,SAAS,CAAC,CAAA;YAC1B,CAAC;YACD,SAAS,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAA;QAC7B,CAAC,CAA8B;QAC/B,IAAI,EAAE,QAAQ;QACd,IAAI,EAAE,SAAS,CAAC,GAAG,CAAC,aAAa,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC;QACtD,OAAO,EAAE,KAAK;KACf,CAAC,CAAA;IAEF,yDAAyD;IACzD,eAAe,CAAC,SAAS,CAAC,WAAW,CAAC,CAAA;IACtC,gBAAgB,CAAC;QACf,GAAG,SAAS;QACZ,IAAI,EAAE,SAAS,CAAC,IAA8B;QAC9C,QAAQ,EAAE,SAA8B;KACzC,CAAC,CAAA;IACF,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAA;IACvD,kBAAkB,EAAE,CAAA;IACpB,eAAe,CAAC,SAAS,CAAC,WAAW,CAAC,CAAA;IACtC,gBAAgB,CAAC,SAAS,CAAC,CAAA;IAE3B,uDAAuD;IACvD,aAAa,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,SAAS,CAAC,IAA8B,EAAE,CAAC,CAAA;IAE3E,6DAA6D;IAC7D,WAAW,CAAC,aAAa,EAAE,GAAG,EAAE;QAC9B,eAAe,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QACzB,SAAS,CAAC,YAAY,CAAC,aAAa,GAAG,eAAe,CAAA;QACtD,eAAe,CAAC,SAAS,CAAC,YAAY,CAAC,CAAA;IACzC,CAAC,CAAC,CAAA;IAEF,OAAO,KAAK,CAAA;AACd,CAAC","sourcesContent":["import type { ChildOptions, ComponentDef } from '../types.js'\nimport { getRenderContext, setRenderContext, clearRenderContext } from '../render-context.js'\nimport { createLifetime, disposeLifetime, addDisposer } from '../lifetime.js'\nimport { createComponentInstance, flushInstance, type ComponentInstance } from '../update-loop.js'\nimport { createBinding, setFlatBindings } from '../binding.js'\nimport { registerChild, unregisterChild } from '../addressed.js'\nimport { createView } from '../view-helpers.js'\n\nconst FULL_MASK = 0xffffffff\n\nexport function child<S, ChildM>(opts: ChildOptions<S, ChildM>): Node[] {\n // Dev-mode guard: props must be a function, not a static object\n if (typeof opts.props !== 'function') {\n throw new Error(\n `child(\"${String(opts.key)}\"): props must be a reactive accessor function ` +\n `(s => ({ ... })), not a static object. Static props are captured once at mount ` +\n `and never update.`,\n )\n }\n\n const parentCtx = getRenderContext('child')\n const parentLifetime = parentCtx.rootLifetime\n const childLifetime = createLifetime(parentLifetime)\n childLifetime._kind = 'child'\n // Tag eagerly: childLifetime lives as long as the child component is mounted,\n // so disposing it IS the child-unmount event. Setting the cause up front\n // (instead of inside the disposer closure below) ensures the parent's\n // _disposerLog sees it when `disposeLifetime` walks up the parent chain —\n // `childInst.rootLifetime` is an orphan (parent = null) and cannot emit.\n childLifetime.disposalCause = 'child-unmount'\n const parentSend = parentCtx.send\n\n const childDef = opts.def as ComponentDef<unknown, ChildM, unknown, Record<string, unknown>>\n const initialProps = opts.props(parentCtx.state as S)\n // Child component inherits the parent's DOM env — render-context\n // threading means the same env flows from mountApp to child() to any\n // nested primitives inside the child's view.\n const childInst = createComponentInstance(childDef, initialProps, null, parentCtx.dom)\n\n // Wrap child's send to intercept messages for onMsg → parent\n const originalSend = childInst.send\n childInst.send = (msg: ChildM) => {\n originalSend(msg)\n if (opts.onMsg && parentSend) {\n // Defer to after the child processes — use microtask\n queueMicrotask(() => {\n const parentMsg = opts.onMsg!(msg)\n if (parentMsg != null) {\n parentSend(parentMsg)\n }\n })\n }\n }\n\n // Track props for shallow-diff\n let prevProps: Record<string, unknown> = { ...initialProps }\n\n // Register a binding on the child scope that watches parent props changes.\n // This is a side-effect-only (`kind: 'effect'`) binding: Phase 2 runs the\n // accessor purely to fire the diff + propsMsg dispatch below. There is no\n // DOM output — the comment node is a detached anchor kept only so the\n // Binding shape stays uniform with other kinds.\n createBinding(childLifetime, {\n mask: FULL_MASK,\n accessor: ((parentState: S) => {\n const newProps = opts.props(parentState)\n\n let changed = false\n for (const key of Object.keys(newProps)) {\n if (!Object.is(newProps[key], prevProps[key])) {\n changed = true\n break\n }\n }\n\n if (changed && childDef.propsMsg) {\n const msg = childDef.propsMsg(newProps)\n // Dispatch via `originalSend` so framework-synthesized propsMsg\n // traffic bypasses the `onMsg` wrapper below. Otherwise a naive\n // `onMsg: m => forward(m)` echoes props/set back to the parent,\n // which mutates parent state, re-fires this accessor, and loops\n // forever.\n originalSend(msg)\n flushInstance(childInst)\n }\n prevProps = { ...newProps }\n }) as (state: never) => unknown,\n kind: 'effect',\n node: parentCtx.dom.createComment('child:' + opts.key),\n perItem: false,\n })\n\n // Run the child's view within the child's render context\n setFlatBindings(childInst.allBindings)\n setRenderContext({\n ...childInst,\n send: childInst.send as (msg: unknown) => void,\n instance: childInst as ComponentInstance,\n })\n const nodes = childDef.view(createView(childInst.send))\n clearRenderContext()\n setFlatBindings(parentCtx.allBindings)\n setRenderContext(parentCtx)\n\n // Register in component registry for addressed effects\n registerChild(opts.key, { send: childInst.send as (msg: unknown) => void })\n\n // Cleanup: dispose child instance when parent scope disposes\n addDisposer(childLifetime, () => {\n unregisterChild(opts.key)\n childInst.rootLifetime.disposalCause = 'child-unmount'\n disposeLifetime(childInst.rootLifetime)\n })\n\n return nodes\n}\n"]}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `clientOnly()` — a view primitive that marks a subtree as browser-only.
|
|
3
|
+
*
|
|
4
|
+
* Problem it solves: some widgets can't render on the server. Leaflet,
|
|
5
|
+
* Chart.js, Monaco, and most imperative browser libraries touch `window`
|
|
6
|
+
* or `document` at construction time. Wrapping them in `clientOnly`
|
|
7
|
+
* means SSR never invokes the render callback — the server emits a
|
|
8
|
+
* placeholder (optionally backed by a user-supplied fallback subtree)
|
|
9
|
+
* and the real render happens at client mount / hydrate time.
|
|
10
|
+
*
|
|
11
|
+
* How it works:
|
|
12
|
+
*
|
|
13
|
+
* - SSR (env without `isBrowser`): emits
|
|
14
|
+
* `<!--llui-client-only-start-->` + fallback nodes (if any) +
|
|
15
|
+
* `<!--llui-client-only-end-->`. The `render` callback is never
|
|
16
|
+
* invoked.
|
|
17
|
+
* - Client mount / hydrate (env with `isBrowser: true`): runs `render`
|
|
18
|
+
* inline. Because LLui's hydration is an atomic swap — the client
|
|
19
|
+
* builds fresh DOM and `container.replaceChildren` wipes the server
|
|
20
|
+
* HTML — no anchor walking or fallback disposal is needed on the
|
|
21
|
+
* client side. The client simply produces its DOM; the SSR output
|
|
22
|
+
* is discarded by `replaceChildren`.
|
|
23
|
+
*
|
|
24
|
+
* State threading: `render` and `fallback` both receive a `View<S, M>`
|
|
25
|
+
* bag keyed to the host component's state, so inner `text`, `branch`,
|
|
26
|
+
* `each`, etc. behave the same as if the primitive weren't there.
|
|
27
|
+
*
|
|
28
|
+
* Gating heavy imports: if the library itself can't be imported on the
|
|
29
|
+
* server, put the import INSIDE `render` via dynamic `import()`:
|
|
30
|
+
*
|
|
31
|
+
* ...clientOnly({
|
|
32
|
+
* fallback: () => [div({ class: 'skeleton' })],
|
|
33
|
+
* render: () => [foreign({
|
|
34
|
+
* create: async (el) => {
|
|
35
|
+
* const L = await import('leaflet')
|
|
36
|
+
* return L.map(el).setView([0, 0], 13)
|
|
37
|
+
* },
|
|
38
|
+
* // ...
|
|
39
|
+
* })],
|
|
40
|
+
* })
|
|
41
|
+
*
|
|
42
|
+
* The bundler sees `import('leaflet')` only from the client-reachable
|
|
43
|
+
* code path; the SSR bundle elides it.
|
|
44
|
+
*/
|
|
45
|
+
import type { ComponentDef } from '../types.js';
|
|
46
|
+
import { type View } from '../view-helpers.js';
|
|
47
|
+
export interface ClientOnlyOptions<S, M> {
|
|
48
|
+
/**
|
|
49
|
+
* Browser-only render callback. Invoked on client mount and hydrate.
|
|
50
|
+
* NEVER invoked during SSR (`renderToString` / `renderNodes`). Free
|
|
51
|
+
* to touch `window`, `document`, or import browser-only modules.
|
|
52
|
+
*/
|
|
53
|
+
render: (bag: View<S, M>) => Node[];
|
|
54
|
+
/**
|
|
55
|
+
* Server-rendered stand-in. When present, SSR runs `fallback` and
|
|
56
|
+
* serializes its output into the HTML between the anchor comments.
|
|
57
|
+
* Useful for skeleton/shimmer loaders and to preserve layout width
|
|
58
|
+
* before the client widget mounts.
|
|
59
|
+
*
|
|
60
|
+
* Omit to emit only the anchor pair — zero layout impact until the
|
|
61
|
+
* client mounts, which may cause content shift.
|
|
62
|
+
*
|
|
63
|
+
* Both `render` and `fallback` receive the same `View<S, M>` bag;
|
|
64
|
+
* state-dependent fallback bindings are evaluated once at SSR time.
|
|
65
|
+
*/
|
|
66
|
+
fallback?: (bag: View<S, M>) => Node[];
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Mark a view subtree as browser-only. See module doc comment for the
|
|
70
|
+
* full semantics.
|
|
71
|
+
*
|
|
72
|
+
* Returns `Node[]` — spread into a parent element's children array.
|
|
73
|
+
*
|
|
74
|
+
* ```ts
|
|
75
|
+
* view: () => [
|
|
76
|
+
* div({ class: 'dashboard' }, [
|
|
77
|
+
* ...clientOnly({
|
|
78
|
+
* fallback: () => [div({ class: 'chart-skeleton' })],
|
|
79
|
+
* render: () => [foreign({ create: (el) => new Chart(el, cfg) })],
|
|
80
|
+
* }),
|
|
81
|
+
* ]),
|
|
82
|
+
* ]
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
export declare function clientOnly<S, M = unknown>(opts: ClientOnlyOptions<S, M>): Node[];
|
|
86
|
+
/**
|
|
87
|
+
* Generated component stub used by `@llui/vite-plugin`'s `'use client'`
|
|
88
|
+
* directive. For SSR builds, every `export const X = component({...})`
|
|
89
|
+
* in a `'use client'` module is rewritten to
|
|
90
|
+
* `export const X = __clientOnlyStub('X')` — the module's real imports
|
|
91
|
+
* and top-level side effects never run under SSR.
|
|
92
|
+
*
|
|
93
|
+
* The stub is a minimal valid `ComponentDef` whose `view()` emits a
|
|
94
|
+
* `clientOnly` placeholder. The client build imports the ORIGINAL
|
|
95
|
+
* module (directive is a no-op on client), so the real component mounts
|
|
96
|
+
* on hydrate; atomic-swap wipes the SSR stub's empty placeholder.
|
|
97
|
+
*
|
|
98
|
+
* State shape is `{}` and message type is `never` — the stub doesn't
|
|
99
|
+
* participate in any real update cycle. Callers that tried to `send()`
|
|
100
|
+
* messages against the stub during SSR would be dispatching into the
|
|
101
|
+
* void, which is fine: SSR doesn't process messages.
|
|
102
|
+
*
|
|
103
|
+
* App authors should not call this directly — reach for `clientOnly`
|
|
104
|
+
* or the `'use client'` directive depending on the granularity you
|
|
105
|
+
* want.
|
|
106
|
+
*/
|
|
107
|
+
export declare function __clientOnlyStub(name: string): ComponentDef<object, never, never>;
|
|
108
|
+
//# sourceMappingURL=client-only.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-only.d.ts","sourceRoot":"","sources":["../../src/primitives/client-only.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE/C,OAAO,EAAc,KAAK,IAAI,EAAE,MAAM,oBAAoB,CAAA;AAE1D,MAAM,WAAW,iBAAiB,CAAC,CAAC,EAAE,CAAC;IACrC;;;;OAIG;IACH,MAAM,EAAE,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE,CAAA;IAEnC;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE,CAAA;CACvC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,CAkBhF;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,CAOjF"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `clientOnly()` — a view primitive that marks a subtree as browser-only.
|
|
3
|
+
*
|
|
4
|
+
* Problem it solves: some widgets can't render on the server. Leaflet,
|
|
5
|
+
* Chart.js, Monaco, and most imperative browser libraries touch `window`
|
|
6
|
+
* or `document` at construction time. Wrapping them in `clientOnly`
|
|
7
|
+
* means SSR never invokes the render callback — the server emits a
|
|
8
|
+
* placeholder (optionally backed by a user-supplied fallback subtree)
|
|
9
|
+
* and the real render happens at client mount / hydrate time.
|
|
10
|
+
*
|
|
11
|
+
* How it works:
|
|
12
|
+
*
|
|
13
|
+
* - SSR (env without `isBrowser`): emits
|
|
14
|
+
* `<!--llui-client-only-start-->` + fallback nodes (if any) +
|
|
15
|
+
* `<!--llui-client-only-end-->`. The `render` callback is never
|
|
16
|
+
* invoked.
|
|
17
|
+
* - Client mount / hydrate (env with `isBrowser: true`): runs `render`
|
|
18
|
+
* inline. Because LLui's hydration is an atomic swap — the client
|
|
19
|
+
* builds fresh DOM and `container.replaceChildren` wipes the server
|
|
20
|
+
* HTML — no anchor walking or fallback disposal is needed on the
|
|
21
|
+
* client side. The client simply produces its DOM; the SSR output
|
|
22
|
+
* is discarded by `replaceChildren`.
|
|
23
|
+
*
|
|
24
|
+
* State threading: `render` and `fallback` both receive a `View<S, M>`
|
|
25
|
+
* bag keyed to the host component's state, so inner `text`, `branch`,
|
|
26
|
+
* `each`, etc. behave the same as if the primitive weren't there.
|
|
27
|
+
*
|
|
28
|
+
* Gating heavy imports: if the library itself can't be imported on the
|
|
29
|
+
* server, put the import INSIDE `render` via dynamic `import()`:
|
|
30
|
+
*
|
|
31
|
+
* ...clientOnly({
|
|
32
|
+
* fallback: () => [div({ class: 'skeleton' })],
|
|
33
|
+
* render: () => [foreign({
|
|
34
|
+
* create: async (el) => {
|
|
35
|
+
* const L = await import('leaflet')
|
|
36
|
+
* return L.map(el).setView([0, 0], 13)
|
|
37
|
+
* },
|
|
38
|
+
* // ...
|
|
39
|
+
* })],
|
|
40
|
+
* })
|
|
41
|
+
*
|
|
42
|
+
* The bundler sees `import('leaflet')` only from the client-reachable
|
|
43
|
+
* code path; the SSR bundle elides it.
|
|
44
|
+
*/
|
|
45
|
+
import { getRenderContext } from '../render-context.js';
|
|
46
|
+
import { createView } from '../view-helpers.js';
|
|
47
|
+
/**
|
|
48
|
+
* Mark a view subtree as browser-only. See module doc comment for the
|
|
49
|
+
* full semantics.
|
|
50
|
+
*
|
|
51
|
+
* Returns `Node[]` — spread into a parent element's children array.
|
|
52
|
+
*
|
|
53
|
+
* ```ts
|
|
54
|
+
* view: () => [
|
|
55
|
+
* div({ class: 'dashboard' }, [
|
|
56
|
+
* ...clientOnly({
|
|
57
|
+
* fallback: () => [div({ class: 'chart-skeleton' })],
|
|
58
|
+
* render: () => [foreign({ create: (el) => new Chart(el, cfg) })],
|
|
59
|
+
* }),
|
|
60
|
+
* ]),
|
|
61
|
+
* ]
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export function clientOnly(opts) {
|
|
65
|
+
const ctx = getRenderContext('clientOnly');
|
|
66
|
+
const send = ctx.send;
|
|
67
|
+
const bag = createView(send);
|
|
68
|
+
// `ctx.dom.isBrowser` is the discriminator — it's true only when the
|
|
69
|
+
// env wraps the live browser globals (see `browserEnv()`). SSR envs
|
|
70
|
+
// (`jsdomEnv`, `linkedomEnv`, custom adapters) don't set it. This
|
|
71
|
+
// check lets `clientOnly` choose behavior without the primitive
|
|
72
|
+
// needing to know about `renderToString` vs `mountApp` call sites.
|
|
73
|
+
if (ctx.dom.isBrowser) {
|
|
74
|
+
return opts.render(bag);
|
|
75
|
+
}
|
|
76
|
+
const start = ctx.dom.createComment('llui-client-only-start');
|
|
77
|
+
const end = ctx.dom.createComment('llui-client-only-end');
|
|
78
|
+
const fallbackNodes = opts.fallback ? opts.fallback(bag) : [];
|
|
79
|
+
return [start, ...fallbackNodes, end];
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Generated component stub used by `@llui/vite-plugin`'s `'use client'`
|
|
83
|
+
* directive. For SSR builds, every `export const X = component({...})`
|
|
84
|
+
* in a `'use client'` module is rewritten to
|
|
85
|
+
* `export const X = __clientOnlyStub('X')` — the module's real imports
|
|
86
|
+
* and top-level side effects never run under SSR.
|
|
87
|
+
*
|
|
88
|
+
* The stub is a minimal valid `ComponentDef` whose `view()` emits a
|
|
89
|
+
* `clientOnly` placeholder. The client build imports the ORIGINAL
|
|
90
|
+
* module (directive is a no-op on client), so the real component mounts
|
|
91
|
+
* on hydrate; atomic-swap wipes the SSR stub's empty placeholder.
|
|
92
|
+
*
|
|
93
|
+
* State shape is `{}` and message type is `never` — the stub doesn't
|
|
94
|
+
* participate in any real update cycle. Callers that tried to `send()`
|
|
95
|
+
* messages against the stub during SSR would be dispatching into the
|
|
96
|
+
* void, which is fine: SSR doesn't process messages.
|
|
97
|
+
*
|
|
98
|
+
* App authors should not call this directly — reach for `clientOnly`
|
|
99
|
+
* or the `'use client'` directive depending on the granularity you
|
|
100
|
+
* want.
|
|
101
|
+
*/
|
|
102
|
+
export function __clientOnlyStub(name) {
|
|
103
|
+
return {
|
|
104
|
+
name,
|
|
105
|
+
init: () => [{}, []],
|
|
106
|
+
update: (s) => [s, []],
|
|
107
|
+
view: () => clientOnly({ render: () => [] }),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=client-only.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-only.js","sourceRoot":"","sources":["../../src/primitives/client-only.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAGH,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,EAAE,UAAU,EAAa,MAAM,oBAAoB,CAAA;AAyB1D;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,UAAU,CAAiB,IAA6B;IACtE,MAAM,GAAG,GAAG,gBAAgB,CAAC,YAAY,CAAC,CAAA;IAC1C,MAAM,IAAI,GAAG,GAAG,CAAC,IAAwB,CAAA;IACzC,MAAM,GAAG,GAAG,UAAU,CAAO,IAAI,CAAC,CAAA;IAElC,qEAAqE;IACrE,oEAAoE;IACpE,kEAAkE;IAClE,gEAAgE;IAChE,mEAAmE;IACnE,IAAI,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC;QACtB,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IACzB,CAAC;IAED,MAAM,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,aAAa,CAAC,wBAAwB,CAAC,CAAA;IAC7D,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,aAAa,CAAC,sBAAsB,CAAC,CAAA;IACzD,MAAM,aAAa,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IAC7D,OAAO,CAAC,KAAK,EAAE,GAAG,aAAa,EAAE,GAAG,CAAC,CAAA;AACvC,CAAC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,OAAO;QACL,IAAI;QACJ,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC;QACpB,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;QACtB,IAAI,EAAE,GAAG,EAAE,CAAC,UAAU,CAAgB,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;KAC5D,CAAA;AACH,CAAC","sourcesContent":["/**\n * `clientOnly()` — a view primitive that marks a subtree as browser-only.\n *\n * Problem it solves: some widgets can't render on the server. Leaflet,\n * Chart.js, Monaco, and most imperative browser libraries touch `window`\n * or `document` at construction time. Wrapping them in `clientOnly`\n * means SSR never invokes the render callback — the server emits a\n * placeholder (optionally backed by a user-supplied fallback subtree)\n * and the real render happens at client mount / hydrate time.\n *\n * How it works:\n *\n * - SSR (env without `isBrowser`): emits\n * `<!--llui-client-only-start-->` + fallback nodes (if any) +\n * `<!--llui-client-only-end-->`. The `render` callback is never\n * invoked.\n * - Client mount / hydrate (env with `isBrowser: true`): runs `render`\n * inline. Because LLui's hydration is an atomic swap — the client\n * builds fresh DOM and `container.replaceChildren` wipes the server\n * HTML — no anchor walking or fallback disposal is needed on the\n * client side. The client simply produces its DOM; the SSR output\n * is discarded by `replaceChildren`.\n *\n * State threading: `render` and `fallback` both receive a `View<S, M>`\n * bag keyed to the host component's state, so inner `text`, `branch`,\n * `each`, etc. behave the same as if the primitive weren't there.\n *\n * Gating heavy imports: if the library itself can't be imported on the\n * server, put the import INSIDE `render` via dynamic `import()`:\n *\n * ...clientOnly({\n * fallback: () => [div({ class: 'skeleton' })],\n * render: () => [foreign({\n * create: async (el) => {\n * const L = await import('leaflet')\n * return L.map(el).setView([0, 0], 13)\n * },\n * // ...\n * })],\n * })\n *\n * The bundler sees `import('leaflet')` only from the client-reachable\n * code path; the SSR bundle elides it.\n */\n\nimport type { ComponentDef } from '../types.js'\nimport { getRenderContext } from '../render-context.js'\nimport { createView, type View } from '../view-helpers.js'\n\nexport interface ClientOnlyOptions<S, M> {\n /**\n * Browser-only render callback. Invoked on client mount and hydrate.\n * NEVER invoked during SSR (`renderToString` / `renderNodes`). Free\n * to touch `window`, `document`, or import browser-only modules.\n */\n render: (bag: View<S, M>) => Node[]\n\n /**\n * Server-rendered stand-in. When present, SSR runs `fallback` and\n * serializes its output into the HTML between the anchor comments.\n * Useful for skeleton/shimmer loaders and to preserve layout width\n * before the client widget mounts.\n *\n * Omit to emit only the anchor pair — zero layout impact until the\n * client mounts, which may cause content shift.\n *\n * Both `render` and `fallback` receive the same `View<S, M>` bag;\n * state-dependent fallback bindings are evaluated once at SSR time.\n */\n fallback?: (bag: View<S, M>) => Node[]\n}\n\n/**\n * Mark a view subtree as browser-only. See module doc comment for the\n * full semantics.\n *\n * Returns `Node[]` — spread into a parent element's children array.\n *\n * ```ts\n * view: () => [\n * div({ class: 'dashboard' }, [\n * ...clientOnly({\n * fallback: () => [div({ class: 'chart-skeleton' })],\n * render: () => [foreign({ create: (el) => new Chart(el, cfg) })],\n * }),\n * ]),\n * ]\n * ```\n */\nexport function clientOnly<S, M = unknown>(opts: ClientOnlyOptions<S, M>): Node[] {\n const ctx = getRenderContext('clientOnly')\n const send = ctx.send as (msg: M) => void\n const bag = createView<S, M>(send)\n\n // `ctx.dom.isBrowser` is the discriminator — it's true only when the\n // env wraps the live browser globals (see `browserEnv()`). SSR envs\n // (`jsdomEnv`, `linkedomEnv`, custom adapters) don't set it. This\n // check lets `clientOnly` choose behavior without the primitive\n // needing to know about `renderToString` vs `mountApp` call sites.\n if (ctx.dom.isBrowser) {\n return opts.render(bag)\n }\n\n const start = ctx.dom.createComment('llui-client-only-start')\n const end = ctx.dom.createComment('llui-client-only-end')\n const fallbackNodes = opts.fallback ? opts.fallback(bag) : []\n return [start, ...fallbackNodes, end]\n}\n\n/**\n * Generated component stub used by `@llui/vite-plugin`'s `'use client'`\n * directive. For SSR builds, every `export const X = component({...})`\n * in a `'use client'` module is rewritten to\n * `export const X = __clientOnlyStub('X')` — the module's real imports\n * and top-level side effects never run under SSR.\n *\n * The stub is a minimal valid `ComponentDef` whose `view()` emits a\n * `clientOnly` placeholder. The client build imports the ORIGINAL\n * module (directive is a no-op on client), so the real component mounts\n * on hydrate; atomic-swap wipes the SSR stub's empty placeholder.\n *\n * State shape is `{}` and message type is `never` — the stub doesn't\n * participate in any real update cycle. Callers that tried to `send()`\n * messages against the stub during SSR would be dispatching into the\n * void, which is fine: SSR doesn't process messages.\n *\n * App authors should not call this directly — reach for `clientOnly`\n * or the `'use client'` directive depending on the granularity you\n * want.\n */\nexport function __clientOnlyStub(name: string): ComponentDef<object, never, never> {\n return {\n name,\n init: () => [{}, []],\n update: (s) => [s, []],\n view: () => clientOnly<object, never>({ render: () => [] }),\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"each.d.ts","sourceRoot":"","sources":["../../src/primitives/each.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAA0B,MAAM,aAAa,CAAA;AAwBtE,uEAAuE;AACvE,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI,CAEpD;AAED,sEAAsE;AACtE,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,KAAK,IAAI,GAAG,IAAI,CAEzE;
|
|
1
|
+
{"version":3,"file":"each.d.ts","sourceRoot":"","sources":["../../src/primitives/each.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAA0B,MAAM,aAAa,CAAA;AAwBtE,uEAAuE;AACvE,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI,CAEpD;AAED,sEAAsE;AACtE,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,KAAK,IAAI,GAAG,IAAI,CAEzE;AAoCD,wBAAgB,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,CAmR1E"}
|
package/dist/primitives/each.js
CHANGED
|
@@ -17,12 +17,16 @@ export function registerOnRemove(cb) {
|
|
|
17
17
|
if (activeRemoveCallbacks)
|
|
18
18
|
activeRemoveCallbacks.push(cb);
|
|
19
19
|
}
|
|
20
|
-
// Reusable render context for buildEntry — avoids object allocation per entry
|
|
20
|
+
// Reusable render context for buildEntry — avoids object allocation per entry.
|
|
21
|
+
// All fields except `rootLifetime`, `state`, `allBindings`, `structuralBlocks`, `dom`
|
|
22
|
+
// are copied from the surrounding render context per reconcile call, so the
|
|
23
|
+
// initial shape's null/empty values are never observed in practice.
|
|
21
24
|
const buildCtx = {
|
|
22
25
|
rootLifetime: null,
|
|
23
26
|
state: null,
|
|
24
27
|
allBindings: [],
|
|
25
28
|
structuralBlocks: [],
|
|
29
|
+
dom: null,
|
|
26
30
|
};
|
|
27
31
|
// Reusable render bag — mutated per entry instead of allocating new objects
|
|
28
32
|
const buildBag = {
|
|
@@ -38,7 +42,7 @@ export function each(opts) {
|
|
|
38
42
|
const ctx = getRenderContext('each');
|
|
39
43
|
const parentLifetime = ctx.rootLifetime;
|
|
40
44
|
const blocks = ctx.structuralBlocks;
|
|
41
|
-
const anchor =
|
|
45
|
+
const anchor = ctx.dom.createComment('each');
|
|
42
46
|
const entries = [];
|
|
43
47
|
const clearCallbacks = [];
|
|
44
48
|
const removeCallbacks = [];
|
|
@@ -146,7 +150,7 @@ export function each(opts) {
|
|
|
146
150
|
for (let i = 0; i < clearCallbacks.length; i++)
|
|
147
151
|
clearCallbacks[i]();
|
|
148
152
|
// Bulk DOM removal
|
|
149
|
-
const range =
|
|
153
|
+
const range = ctx.dom.createRange();
|
|
150
154
|
range.setStartAfter(anchor);
|
|
151
155
|
const lastEntry = entries[entries.length - 1];
|
|
152
156
|
const lastNode = lastEntry.nodes[lastEntry.nodes.length - 1];
|
|
@@ -378,6 +382,7 @@ function buildEntry(item, index, opts, parentLifetime, ctx, state) {
|
|
|
378
382
|
buildCtx.state = currentState;
|
|
379
383
|
buildCtx.allBindings = ctx.allBindings;
|
|
380
384
|
buildCtx.structuralBlocks = ctx.structuralBlocks;
|
|
385
|
+
buildCtx.dom = ctx.dom;
|
|
381
386
|
buildCtx.instance = ctx.instance;
|
|
382
387
|
const prevFlatBindings = getFlatBindings();
|
|
383
388
|
setFlatBindings(ctx.allBindings);
|
|
@@ -436,7 +441,7 @@ function reconcileEntries(entries, newItems, opts, parentLifetime, parent, ancho
|
|
|
436
441
|
}
|
|
437
442
|
// Remove all DOM nodes in one operation using Range
|
|
438
443
|
if (entries.length > 0) {
|
|
439
|
-
const range =
|
|
444
|
+
const range = ctx.dom.createRange();
|
|
440
445
|
range.setStartAfter(anchor);
|
|
441
446
|
const lastEntry = entries[entries.length - 1];
|
|
442
447
|
const lastNode = lastEntry.nodes[lastEntry.nodes.length - 1];
|
|
@@ -465,7 +470,7 @@ function reconcileEntries(entries, newItems, opts, parentLifetime, parent, ancho
|
|
|
465
470
|
const ref = lastEntry
|
|
466
471
|
? lastEntry.nodes[lastEntry.nodes.length - 1].nextSibling
|
|
467
472
|
: anchor.nextSibling;
|
|
468
|
-
const frag =
|
|
473
|
+
const frag = ctx.dom.createDocumentFragment();
|
|
469
474
|
const newlyAdded = [];
|
|
470
475
|
for (let i = oldLen; i < newLen; i++) {
|
|
471
476
|
const entry = buildEntry(newItems[i], i, opts, parentLifetime, ctx, state);
|
|
@@ -551,7 +556,7 @@ function reconcileEntries(entries, newItems, opts, parentLifetime, parent, ancho
|
|
|
551
556
|
collectNodes(report.leaving, entry.nodes);
|
|
552
557
|
}
|
|
553
558
|
// Bulk DOM removal using Range
|
|
554
|
-
const range =
|
|
559
|
+
const range = ctx.dom.createRange();
|
|
555
560
|
range.setStartAfter(anchor);
|
|
556
561
|
const lastEntry = entries[entries.length - 1];
|
|
557
562
|
range.setEndAfter(lastEntry.nodes[lastEntry.nodes.length - 1]);
|
|
@@ -567,7 +572,7 @@ function reconcileEntries(entries, newItems, opts, parentLifetime, parent, ancho
|
|
|
567
572
|
removeOrphanedChildren(parentLifetime);
|
|
568
573
|
entries.length = 0;
|
|
569
574
|
// Build all new entries into a fragment
|
|
570
|
-
const frag =
|
|
575
|
+
const frag = ctx.dom.createDocumentFragment();
|
|
571
576
|
const newlyAdded = [];
|
|
572
577
|
for (let i = 0; i < newLen; i++) {
|
|
573
578
|
const entry = buildEntry(newItems[i], i, opts, parentLifetime, ctx, state);
|
|
@@ -635,7 +640,7 @@ function reconcileEntries(entries, newItems, opts, parentLifetime, parent, ancho
|
|
|
635
640
|
const hasSurvivors = newEntries.some((e) => oldByKey.has(e.key));
|
|
636
641
|
if (!hasSurvivors || !survivorsInOrder(entries, newEntries, usedKeys)) {
|
|
637
642
|
// Full fragment rebuild — one reflow
|
|
638
|
-
const frag =
|
|
643
|
+
const frag = ctx.dom.createDocumentFragment();
|
|
639
644
|
for (const entry of newEntries) {
|
|
640
645
|
for (const node of entry.nodes)
|
|
641
646
|
frag.appendChild(node);
|
|
@@ -660,7 +665,7 @@ function reconcileEntries(entries, newItems, opts, parentLifetime, parent, ancho
|
|
|
660
665
|
else {
|
|
661
666
|
// Batch new entries into a fragment
|
|
662
667
|
if (!frag)
|
|
663
|
-
frag =
|
|
668
|
+
frag = ctx.dom.createDocumentFragment();
|
|
664
669
|
for (const node of entry.nodes)
|
|
665
670
|
frag.appendChild(node);
|
|
666
671
|
}
|