@aglyn/aglyn-node-renderer 1.0.0-beta.224 → 1.0.0-beta.225

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aglyn/aglyn-node-renderer",
3
- "version": "1.0.0-beta.224",
3
+ "version": "1.0.0-beta.225",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -24,12 +24,12 @@
24
24
  "./package.json": "./package.json"
25
25
  },
26
26
  "dependencies": {
27
- "@aglyn/aglyn": "1.0.0-beta.224",
28
- "@aglyn/shared-data-types": "1.0.0-beta.224",
29
- "@aglyn/shared-ui-jsx": "1.0.0-beta.224",
30
- "@aglyn/shared-ui-theme": "1.0.0-beta.224",
31
- "@aglyn/shared-util-tools": "1.0.0-beta.224",
32
- "@aglyn/shared-util-vendor": "1.0.0-beta.224",
27
+ "@aglyn/aglyn": "1.0.0-beta.225",
28
+ "@aglyn/shared-data-types": "1.0.0-beta.225",
29
+ "@aglyn/shared-ui-jsx": "1.0.0-beta.225",
30
+ "@aglyn/shared-ui-theme": "1.0.0-beta.225",
31
+ "@aglyn/shared-util-tools": "1.0.0-beta.225",
32
+ "@aglyn/shared-util-vendor": "1.0.0-beta.225",
33
33
  "mobx-react-lite": "^5.1.0",
34
34
  "react-is": "^19.3.0"
35
35
  },
@@ -0,0 +1,107 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import type * as Aglyn from '@aglyn/aglyn';
18
+ /**
19
+ * Static subtrees of a published page skip hydration until the visitor
20
+ * nears them (AGL-3581).
21
+ *
22
+ * ## What it does
23
+ *
24
+ * A published page arrives as complete server HTML, and before this every
25
+ * node of it hydrated at once: on `aglyn.com/` that was ~600 elements and the
26
+ * largest single long task of the load. Most of them are copy, images and
27
+ * layout — nothing a visitor operates — so hydrating them buys nothing until
28
+ * the visitor reaches them.
29
+ *
30
+ * While the page hydrates, a static subtree that is not near the viewport
31
+ * renders its root element with `dangerouslySetInnerHTML` in place of its
32
+ * children. React hydrates that root and treats its contents as markup it
33
+ * does not own, so the server's HTML stays exactly as it is — painted,
34
+ * selectable, links working natively — and none of it costs hydration. When
35
+ * the subtree comes within {@link NEAR_VIEWPORT_PX} of the viewport the
36
+ * children render, and React builds them on the client in place of the held
37
+ * markup: the same HTML, now live.
38
+ *
39
+ * ## Why not a Suspense boundary
40
+ *
41
+ * A boundary that suspends during hydration would keep the server HTML and
42
+ * hydrate it in place, which is the textbook mechanism. React 19.2's server
43
+ * renderer outlines every boundary over 500 bytes once the shell passes its
44
+ * progressive chunk size (12.8 KB, not configurable through Next): each one
45
+ * ships in a hidden `<div>` revealed by an inline script on a later animation
46
+ * frame. That is content missing from first paint, a layout shift when it
47
+ * appears, nothing at all in a background tab, and markup a non-rendering
48
+ * crawler reads as hidden. A held root changes nothing in the server HTML.
49
+ *
50
+ * ## Which subtrees
51
+ *
52
+ * Every component in the subtree declares `flags.lazyHydration` — nothing in
53
+ * it responds to the visitor before hydration beyond what HTML does on its
54
+ * own — so the safe answer is the default: an element without the flag keeps
55
+ * its whole ancestry hydrating now. The subtree's ROOT also declares
56
+ * `flags.childrenInRoot`, the contract that lets it take
57
+ * `dangerouslySetInnerHTML` instead of its children.
58
+ *
59
+ * A subtree near the viewport at hydration is not held, and its children
60
+ * hydrate normally — each of them then decides for itself. So a page-wide
61
+ * static wrapper does not hold the whole page: it hydrates, and the sections
62
+ * inside it that are further down are what wait.
63
+ *
64
+ * ## Only during hydration
65
+ *
66
+ * The decision is taken once, when the leaf first renders, and only if that
67
+ * render is part of the tree's first commit (see `treeHydrated`). A subtree
68
+ * mounted by a client-side navigation renders at once and can never be left
69
+ * empty.
70
+ *
71
+ * ## Interaction before release
72
+ *
73
+ * Nothing is released on a press: that would replace the element under the
74
+ * visitor's finger between `pointerdown` and `click`, and the click would be
75
+ * lost. The held HTML already does what a static element does — a link
76
+ * navigates, text selects, focus moves.
77
+ *
78
+ * ## Fails open
79
+ *
80
+ * If the leaf's element is not in the document, or the browser has no
81
+ * `IntersectionObserver`, nothing is held: the worst case is the old
82
+ * behavior.
83
+ */
84
+ /** How far outside the viewport a held subtree renders, in pixels. */
85
+ export declare const NEAR_VIEWPORT_PX = 600;
86
+ /** The empty markup a held root is given, and React leaves alone. */
87
+ export declare const HELD_SERVER_HTML: Readonly<{
88
+ __html: "";
89
+ }>;
90
+ /** Called by `TreeRoot` once its first commit has landed. */
91
+ export declare function markTreeHydrated(): void;
92
+ /**
93
+ * Should this leaf render its root with {@link HELD_SERVER_HTML} instead of
94
+ * its children? True only from a hydration render of an `eligible` leaf that
95
+ * is far from the viewport, until it comes near.
96
+ */
97
+ export declare function useHeldServerHtml(eligible: boolean, nodeId: string): boolean;
98
+ /**
99
+ * Is every node in this subtree a component that declared `lazyHydration`?
100
+ * Cached per node OBJECT: a published page's tree does not change under it,
101
+ * and the editor, whose tree does, never turns deferral on.
102
+ */
103
+ export declare function isLazyHydrationSubtree(node: Aglyn.NodeSchema<any>): boolean;
104
+ /** May this node hold its server HTML: a static subtree under a plain root? */
105
+ export declare function mayHoldServerHtml(node: Aglyn.NodeSchema<any> | undefined): boolean;
106
+ /** Test seam: forget every hold, and that the tree ever hydrated. */
107
+ export declare function resetDeferredHydrationForTests(): void;
@@ -0,0 +1,225 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ // Type-only, for the reason in `stem.tsx`.
17
+ import { components } from "@aglyn/aglyn/aglyn";
18
+ import { FEATURE_FLAG } from "@aglyn/aglyn/foundation/constants/shared";
19
+ import { isNamespacedComponentId } from "@aglyn/aglyn/plugin-manager/plugin-contributions";
20
+ import { useCallback, useState, useSyncExternalStore } from "react";
21
+ /**
22
+ * Static subtrees of a published page skip hydration until the visitor
23
+ * nears them (AGL-3581).
24
+ *
25
+ * ## What it does
26
+ *
27
+ * A published page arrives as complete server HTML, and before this every
28
+ * node of it hydrated at once: on `aglyn.com/` that was ~600 elements and the
29
+ * largest single long task of the load. Most of them are copy, images and
30
+ * layout — nothing a visitor operates — so hydrating them buys nothing until
31
+ * the visitor reaches them.
32
+ *
33
+ * While the page hydrates, a static subtree that is not near the viewport
34
+ * renders its root element with `dangerouslySetInnerHTML` in place of its
35
+ * children. React hydrates that root and treats its contents as markup it
36
+ * does not own, so the server's HTML stays exactly as it is — painted,
37
+ * selectable, links working natively — and none of it costs hydration. When
38
+ * the subtree comes within {@link NEAR_VIEWPORT_PX} of the viewport the
39
+ * children render, and React builds them on the client in place of the held
40
+ * markup: the same HTML, now live.
41
+ *
42
+ * ## Why not a Suspense boundary
43
+ *
44
+ * A boundary that suspends during hydration would keep the server HTML and
45
+ * hydrate it in place, which is the textbook mechanism. React 19.2's server
46
+ * renderer outlines every boundary over 500 bytes once the shell passes its
47
+ * progressive chunk size (12.8 KB, not configurable through Next): each one
48
+ * ships in a hidden `<div>` revealed by an inline script on a later animation
49
+ * frame. That is content missing from first paint, a layout shift when it
50
+ * appears, nothing at all in a background tab, and markup a non-rendering
51
+ * crawler reads as hidden. A held root changes nothing in the server HTML.
52
+ *
53
+ * ## Which subtrees
54
+ *
55
+ * Every component in the subtree declares `flags.lazyHydration` — nothing in
56
+ * it responds to the visitor before hydration beyond what HTML does on its
57
+ * own — so the safe answer is the default: an element without the flag keeps
58
+ * its whole ancestry hydrating now. The subtree's ROOT also declares
59
+ * `flags.childrenInRoot`, the contract that lets it take
60
+ * `dangerouslySetInnerHTML` instead of its children.
61
+ *
62
+ * A subtree near the viewport at hydration is not held, and its children
63
+ * hydrate normally — each of them then decides for itself. So a page-wide
64
+ * static wrapper does not hold the whole page: it hydrates, and the sections
65
+ * inside it that are further down are what wait.
66
+ *
67
+ * ## Only during hydration
68
+ *
69
+ * The decision is taken once, when the leaf first renders, and only if that
70
+ * render is part of the tree's first commit (see `treeHydrated`). A subtree
71
+ * mounted by a client-side navigation renders at once and can never be left
72
+ * empty.
73
+ *
74
+ * ## Interaction before release
75
+ *
76
+ * Nothing is released on a press: that would replace the element under the
77
+ * visitor's finger between `pointerdown` and `click`, and the click would be
78
+ * lost. The held HTML already does what a static element does — a link
79
+ * navigates, text selects, focus moves.
80
+ *
81
+ * ## Fails open
82
+ *
83
+ * If the leaf's element is not in the document, or the browser has no
84
+ * `IntersectionObserver`, nothing is held: the worst case is the old
85
+ * behavior.
86
+ */ /** How far outside the viewport a held subtree renders, in pixels. */ export const NEAR_VIEWPORT_PX = 600;
87
+ const LEAF_ATTRIBUTE = 'data-aglyn';
88
+ const LEAF_PREFIX = 'leaf:';
89
+ /** The empty markup a held root is given, and React leaves alone. */ export const HELD_SERVER_HTML = Object.freeze({
90
+ __html: ''
91
+ });
92
+ const holds = new Map();
93
+ let observer = null;
94
+ function release(nodeId) {
95
+ const hold = holds.get(nodeId);
96
+ if (!hold || hold.released) return;
97
+ hold.released = true;
98
+ for (const listener of [
99
+ ...hold.listeners
100
+ ])listener();
101
+ }
102
+ function nearViewportObserver() {
103
+ if (observer) return observer;
104
+ if (typeof IntersectionObserver !== 'function') return null;
105
+ observer = new IntersectionObserver((entries)=>{
106
+ for (const entry of entries){
107
+ var _entry_target_getAttribute;
108
+ if (!entry.isIntersecting) continue;
109
+ observer == null ? void 0 : observer.unobserve(entry.target);
110
+ const value = (_entry_target_getAttribute = entry.target.getAttribute(LEAF_ATTRIBUTE)) != null ? _entry_target_getAttribute : '';
111
+ release(value.slice(LEAF_PREFIX.length));
112
+ }
113
+ }, {
114
+ rootMargin: `${NEAR_VIEWPORT_PX}px 0px`
115
+ });
116
+ return observer;
117
+ }
118
+ function findLeafElement(nodeId) {
119
+ try {
120
+ // A quoted attribute value needs only its quote and backslash escaped.
121
+ const value = `${LEAF_PREFIX}${nodeId}`.replace(/["\\]/g, '\\$&');
122
+ return document.querySelector(`[${LEAF_ATTRIBUTE}="${value}"]`);
123
+ } catch (unused) {
124
+ return null;
125
+ }
126
+ }
127
+ function nearViewport(element) {
128
+ const { top, bottom } = element.getBoundingClientRect();
129
+ return bottom >= -NEAR_VIEWPORT_PX && top <= window.innerHeight + NEAR_VIEWPORT_PX;
130
+ }
131
+ /**
132
+ * Hold this node's server HTML? Idempotent per node, because React may run a
133
+ * render's state initializer more than once before it commits.
134
+ */ function holdIfFar(nodeId) {
135
+ const existing = holds.get(nodeId);
136
+ if (existing) return !existing.released;
137
+ const element = findLeafElement(nodeId);
138
+ const io = element ? nearViewportObserver() : null;
139
+ if (!element || !io || nearViewport(element)) return false;
140
+ holds.set(nodeId, {
141
+ released: false,
142
+ listeners: new Set()
143
+ });
144
+ io.observe(element);
145
+ return true;
146
+ }
147
+ /**
148
+ * Has the published tree committed its first render? Until it has, every
149
+ * leaf rendering on the client is hydrating. Set from `TreeRoot`'s effect,
150
+ * which runs after the whole first commit — so it is false for every leaf of
151
+ * the hydration and true for anything a later navigation mounts.
152
+ *
153
+ * A module flag rather than `useSyncExternalStore`'s server snapshot: a store
154
+ * whose client and server snapshots differ makes React re-render every
155
+ * component reading it right after hydration, which here would be every leaf
156
+ * on the page — the work this module exists to avoid.
157
+ */ let treeHydrated = false;
158
+ /** Called by `TreeRoot` once its first commit has landed. */ export function markTreeHydrated() {
159
+ treeHydrated = true;
160
+ }
161
+ /**
162
+ * Should this leaf render its root with {@link HELD_SERVER_HTML} instead of
163
+ * its children? True only from a hydration render of an `eligible` leaf that
164
+ * is far from the viewport, until it comes near.
165
+ */ export function useHeldServerHtml(eligible, nodeId) {
166
+ const [held] = useState(()=>eligible && !treeHydrated && typeof document !== 'undefined' && holdIfFar(nodeId));
167
+ const subscribe = useCallback((listener)=>{
168
+ const hold = holds.get(nodeId);
169
+ if (!hold) return ()=>undefined;
170
+ hold.listeners.add(listener);
171
+ return ()=>{
172
+ hold.listeners.delete(listener);
173
+ };
174
+ }, [
175
+ nodeId
176
+ ]);
177
+ // Both snapshots read `false` for a leaf that holds nothing, so no leaf is
178
+ // re-rendered for having read this store.
179
+ const released = useSyncExternalStore(subscribe, ()=>{
180
+ var _ref;
181
+ var _holds_get;
182
+ return (_ref = (_holds_get = holds.get(nodeId)) == null ? void 0 : _holds_get.released) != null ? _ref : false;
183
+ }, ()=>false);
184
+ return held && !released;
185
+ }
186
+ const staticSubtrees = new WeakMap();
187
+ function declares(componentId, flag) {
188
+ var _ref;
189
+ var _components_getSchema_flags, _components_getSchema;
190
+ if (!componentId || isNamespacedComponentId(componentId)) return false;
191
+ const value = (_ref = (_components_getSchema = components.getSchema(componentId)) == null ? void 0 : (_components_getSchema_flags = _components_getSchema.flags) == null ? void 0 : _components_getSchema_flags[flag]) != null ? _ref : 0;
192
+ return Boolean(value & FEATURE_FLAG.ENABLED);
193
+ }
194
+ /**
195
+ * Is every node in this subtree a component that declared `lazyHydration`?
196
+ * Cached per node OBJECT: a published page's tree does not change under it,
197
+ * and the editor, whose tree does, never turns deferral on.
198
+ */ export function isLazyHydrationSubtree(node) {
199
+ const cached = staticSubtrees.get(node);
200
+ if (cached !== undefined) return cached;
201
+ let answer = declares(node.componentId, 'lazyHydration');
202
+ if (answer) {
203
+ var _node_children;
204
+ for (const child of (_node_children = node.children) != null ? _node_children : []){
205
+ if (!child || !isLazyHydrationSubtree(child)) {
206
+ answer = false;
207
+ break;
208
+ }
209
+ }
210
+ }
211
+ staticSubtrees.set(node, answer);
212
+ return answer;
213
+ }
214
+ /** May this node hold its server HTML: a static subtree under a plain root? */ export function mayHoldServerHtml(node) {
215
+ var _node_children;
216
+ return Boolean((node == null ? void 0 : (_node_children = node.children) == null ? void 0 : _node_children.length) && declares(node.componentId, 'childrenInRoot') && isLazyHydrationSubtree(node));
217
+ }
218
+ /** Test seam: forget every hold, and that the tree ever hydrated. */ export function resetDeferredHydrationForTests() {
219
+ treeHydrated = false;
220
+ holds.clear();
221
+ observer == null ? void 0 : observer.disconnect();
222
+ observer = null;
223
+ }
224
+
225
+ //# sourceMappingURL=deferred-hydration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/aglyn-node-renderer/src/lib/components/deferred-hydration.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Type-only, for the reason in `stem.tsx`.\nimport type * as Aglyn from '@aglyn/aglyn'\nimport { components } from '@aglyn/aglyn/aglyn'\nimport { FEATURE_FLAG } from '@aglyn/aglyn/foundation/constants/shared'\nimport { isNamespacedComponentId } from '@aglyn/aglyn/plugin-manager/plugin-contributions'\nimport { useCallback, useState, useSyncExternalStore } from 'react'\n\n/**\n * Static subtrees of a published page skip hydration until the visitor\n * nears them (AGL-3581).\n *\n * ## What it does\n *\n * A published page arrives as complete server HTML, and before this every\n * node of it hydrated at once: on `aglyn.com/` that was ~600 elements and the\n * largest single long task of the load. Most of them are copy, images and\n * layout — nothing a visitor operates — so hydrating them buys nothing until\n * the visitor reaches them.\n *\n * While the page hydrates, a static subtree that is not near the viewport\n * renders its root element with `dangerouslySetInnerHTML` in place of its\n * children. React hydrates that root and treats its contents as markup it\n * does not own, so the server's HTML stays exactly as it is — painted,\n * selectable, links working natively — and none of it costs hydration. When\n * the subtree comes within {@link NEAR_VIEWPORT_PX} of the viewport the\n * children render, and React builds them on the client in place of the held\n * markup: the same HTML, now live.\n *\n * ## Why not a Suspense boundary\n *\n * A boundary that suspends during hydration would keep the server HTML and\n * hydrate it in place, which is the textbook mechanism. React 19.2's server\n * renderer outlines every boundary over 500 bytes once the shell passes its\n * progressive chunk size (12.8 KB, not configurable through Next): each one\n * ships in a hidden `<div>` revealed by an inline script on a later animation\n * frame. That is content missing from first paint, a layout shift when it\n * appears, nothing at all in a background tab, and markup a non-rendering\n * crawler reads as hidden. A held root changes nothing in the server HTML.\n *\n * ## Which subtrees\n *\n * Every component in the subtree declares `flags.lazyHydration` — nothing in\n * it responds to the visitor before hydration beyond what HTML does on its\n * own — so the safe answer is the default: an element without the flag keeps\n * its whole ancestry hydrating now. The subtree's ROOT also declares\n * `flags.childrenInRoot`, the contract that lets it take\n * `dangerouslySetInnerHTML` instead of its children.\n *\n * A subtree near the viewport at hydration is not held, and its children\n * hydrate normally — each of them then decides for itself. So a page-wide\n * static wrapper does not hold the whole page: it hydrates, and the sections\n * inside it that are further down are what wait.\n *\n * ## Only during hydration\n *\n * The decision is taken once, when the leaf first renders, and only if that\n * render is part of the tree's first commit (see `treeHydrated`). A subtree\n * mounted by a client-side navigation renders at once and can never be left\n * empty.\n *\n * ## Interaction before release\n *\n * Nothing is released on a press: that would replace the element under the\n * visitor's finger between `pointerdown` and `click`, and the click would be\n * lost. The held HTML already does what a static element does — a link\n * navigates, text selects, focus moves.\n *\n * ## Fails open\n *\n * If the leaf's element is not in the document, or the browser has no\n * `IntersectionObserver`, nothing is held: the worst case is the old\n * behavior.\n */\n\n/** How far outside the viewport a held subtree renders, in pixels. */\nexport const NEAR_VIEWPORT_PX = 600\n\nconst LEAF_ATTRIBUTE = 'data-aglyn'\nconst LEAF_PREFIX = 'leaf:'\n\n/** The empty markup a held root is given, and React leaves alone. */\nexport const HELD_SERVER_HTML = Object.freeze({ __html: '' })\n\ninterface Hold {\n released: boolean\n readonly listeners: Set<() => void>\n}\n\nconst holds = new Map<string, Hold>()\nlet observer: IntersectionObserver | null = null\n\nfunction release(nodeId: string): void {\n const hold = holds.get(nodeId)\n if (!hold || hold.released) return\n hold.released = true\n for (const listener of [...hold.listeners]) listener()\n}\n\nfunction nearViewportObserver(): IntersectionObserver | null {\n if (observer) return observer\n if (typeof IntersectionObserver !== 'function') return null\n observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (!entry.isIntersecting) continue\n observer?.unobserve(entry.target)\n const value = entry.target.getAttribute(LEAF_ATTRIBUTE) ?? ''\n release(value.slice(LEAF_PREFIX.length))\n }\n },\n { rootMargin: `${NEAR_VIEWPORT_PX}px 0px` },\n )\n return observer\n}\n\nfunction findLeafElement(nodeId: string): Element | null {\n try {\n // A quoted attribute value needs only its quote and backslash escaped.\n const value = `${LEAF_PREFIX}${nodeId}`.replace(/[\"\\\\]/g, '\\\\$&')\n return document.querySelector(`[${LEAF_ATTRIBUTE}=\"${value}\"]`)\n } catch {\n return null\n }\n}\n\nfunction nearViewport(element: Element): boolean {\n const { top, bottom } = element.getBoundingClientRect()\n return (\n bottom >= -NEAR_VIEWPORT_PX && top <= window.innerHeight + NEAR_VIEWPORT_PX\n )\n}\n\n/**\n * Hold this node's server HTML? Idempotent per node, because React may run a\n * render's state initializer more than once before it commits.\n */\nfunction holdIfFar(nodeId: string): boolean {\n const existing = holds.get(nodeId)\n if (existing) return !existing.released\n const element = findLeafElement(nodeId)\n const io = element ? nearViewportObserver() : null\n if (!element || !io || nearViewport(element)) return false\n holds.set(nodeId, { released: false, listeners: new Set() })\n io.observe(element)\n return true\n}\n\n/**\n * Has the published tree committed its first render? Until it has, every\n * leaf rendering on the client is hydrating. Set from `TreeRoot`'s effect,\n * which runs after the whole first commit — so it is false for every leaf of\n * the hydration and true for anything a later navigation mounts.\n *\n * A module flag rather than `useSyncExternalStore`'s server snapshot: a store\n * whose client and server snapshots differ makes React re-render every\n * component reading it right after hydration, which here would be every leaf\n * on the page — the work this module exists to avoid.\n */\nlet treeHydrated = false\n\n/** Called by `TreeRoot` once its first commit has landed. */\nexport function markTreeHydrated(): void {\n treeHydrated = true\n}\n\n/**\n * Should this leaf render its root with {@link HELD_SERVER_HTML} instead of\n * its children? True only from a hydration render of an `eligible` leaf that\n * is far from the viewport, until it comes near.\n */\nexport function useHeldServerHtml(eligible: boolean, nodeId: string): boolean {\n const [held] = useState(\n () =>\n eligible &&\n !treeHydrated &&\n typeof document !== 'undefined' &&\n holdIfFar(nodeId),\n )\n const subscribe = useCallback(\n (listener: () => void) => {\n const hold = holds.get(nodeId)\n if (!hold) return () => undefined\n hold.listeners.add(listener)\n return () => {\n hold.listeners.delete(listener)\n }\n },\n [nodeId],\n )\n // Both snapshots read `false` for a leaf that holds nothing, so no leaf is\n // re-rendered for having read this store.\n const released = useSyncExternalStore(\n subscribe,\n () => holds.get(nodeId)?.released ?? false,\n () => false,\n )\n return held && !released\n}\n\nconst staticSubtrees = new WeakMap<object, boolean>()\n\nfunction declares(\n componentId: string | undefined,\n flag: 'lazyHydration' | 'childrenInRoot',\n): boolean {\n if (!componentId || isNamespacedComponentId(componentId)) return false\n const value = components.getSchema(componentId)?.flags?.[flag] ?? 0\n return Boolean(value & FEATURE_FLAG.ENABLED)\n}\n\n/**\n * Is every node in this subtree a component that declared `lazyHydration`?\n * Cached per node OBJECT: a published page's tree does not change under it,\n * and the editor, whose tree does, never turns deferral on.\n */\nexport function isLazyHydrationSubtree(node: Aglyn.NodeSchema<any>): boolean {\n const cached = staticSubtrees.get(node)\n if (cached !== undefined) return cached\n let answer = declares(node.componentId, 'lazyHydration')\n if (answer) {\n for (const child of (node.children ?? []) as Aglyn.NodeSchema<any>[]) {\n if (!child || !isLazyHydrationSubtree(child)) {\n answer = false\n break\n }\n }\n }\n staticSubtrees.set(node, answer)\n return answer\n}\n\n/** May this node hold its server HTML: a static subtree under a plain root? */\nexport function mayHoldServerHtml(\n node: Aglyn.NodeSchema<any> | undefined,\n): boolean {\n return Boolean(\n node?.children?.length &&\n declares(node.componentId, 'childrenInRoot') &&\n isLazyHydrationSubtree(node),\n )\n}\n\n/** Test seam: forget every hold, and that the tree ever hydrated. */\nexport function resetDeferredHydrationForTests(): void {\n treeHydrated = false\n holds.clear()\n observer?.disconnect()\n observer = null\n}\n"],"names":["components","FEATURE_FLAG","isNamespacedComponentId","useCallback","useState","useSyncExternalStore","NEAR_VIEWPORT_PX","LEAF_ATTRIBUTE","LEAF_PREFIX","HELD_SERVER_HTML","Object","freeze","__html","holds","Map","observer","release","nodeId","hold","get","released","listener","listeners","nearViewportObserver","IntersectionObserver","entries","entry","isIntersecting","unobserve","target","value","getAttribute","slice","length","rootMargin","findLeafElement","replace","document","querySelector","nearViewport","element","top","bottom","getBoundingClientRect","window","innerHeight","holdIfFar","existing","io","set","Set","observe","treeHydrated","markTreeHydrated","useHeldServerHtml","eligible","held","subscribe","undefined","add","delete","staticSubtrees","WeakMap","declares","componentId","flag","getSchema","flags","Boolean","ENABLED","isLazyHydrationSubtree","node","cached","answer","child","children","mayHoldServerHtml","resetDeferredHydrationForTests","clear","disconnect"],"mappings":"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"}
@@ -46,10 +46,18 @@ import { isValidElementType } from "react-is";
46
46
  import RendererComponents from "../contexts/renderer-components.js";
47
47
  import { LeafSxTransformContext } from "../contexts/leaf-sx-transform.js";
48
48
  import { RealmBoundaryContext } from "../contexts/realm-boundary.js";
49
+ import { DeferredHydrationContext } from "../contexts/deferred-hydration.js";
50
+ import { HELD_SERVER_HTML, mayHoldServerHtml, useHeldServerHtml } from "./deferred-hydration.js";
49
51
  import { resolvePaletteVarsSx, resolveSchemeSx } from "../utils/scheme-sx.js";
50
52
  const DefaultComponent = styled('div')({});
53
+ /** Leaf props with any `children` a resolved prop carried, for a held root. */ function withoutChildren(props) {
54
+ const { children: _children } = props, rest = _object_without_properties_loose(props, [
55
+ "children"
56
+ ]);
57
+ return rest;
58
+ }
51
59
  export const Leaf = observer(/*#__PURE__*/ forwardRef((props, ref)=>{
52
- var _ref, _ref1, _ref2, _ref3, _ref4, _ref5;
60
+ var _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _ref6;
53
61
  var _schema_flags, _theme_palette, _forcedTheme_palette, _schema_flags1;
54
62
  const { children, node, sx, className, style } = props, rest = _object_without_properties_loose(props, [
55
63
  "children",
@@ -68,7 +76,7 @@ export const Leaf = observer(/*#__PURE__*/ forwardRef((props, ref)=>{
68
76
  // purpose (the author has to see and select the part a page will hide),
69
77
  // and there the raw `hideIf` would spread onto the element as an
70
78
  // unknown `hideif` DOM attribute.
71
- const _ref6 = (_ref = (_ref1 = node == null ? void 0 : node.resolvedProps) != null ? _ref1 : node == null ? void 0 : node.props) != null ? _ref : {}, { sx: propsSx, className: propsClassName, style: propsStyle, [NODE_HIDE_IF_PROP]: _hideIf, [NODE_HIDE_UNLESS_PROP]: _hideUnless, // Element animation (AGL-2486): the reserved animation props come
79
+ const _ref7 = (_ref = (_ref1 = node == null ? void 0 : node.resolvedProps) != null ? _ref1 : node == null ? void 0 : node.props) != null ? _ref : {}, { sx: propsSx, className: propsClassName, style: propsStyle, [NODE_HIDE_IF_PROP]: _hideIf, [NODE_HIDE_UNLESS_PROP]: _hideUnless, // Element animation (AGL-2486): the reserved animation props come
72
80
  // out here for the same reason the visibility directives above do —
73
81
  // they are directives to the RENDERER, and spreading them onto the
74
82
  // element would land `aglynanimation="slide-up"` as an unknown DOM
@@ -78,7 +86,7 @@ export const Leaf = observer(/*#__PURE__*/ forwardRef((props, ref)=>{
78
86
  // shape.
79
87
  [NODE_ANIMATION_PROP]: _animation, [NODE_ANIMATION_TRIGGER_PROP]: _animationTrigger, [NODE_ANIMATION_DURATION_PROP]: _animationDuration, [NODE_ANIMATION_DELAY_PROP]: _animationDelay, [NODE_ANIMATION_REPEAT_PROP]: _animationRepeat, [NODE_ANIMATION_EASE_PROP]: _animationEase, [NODE_ANIMATION_STAGGER_PROP]: _animationStagger, [NODE_ANIMATION_STAGGER_STEP_PROP]: _animationStaggerStep, // A pinned color scheme (AGL-3284) is a directive to the renderer, like
80
88
  // the two groups above: consumed below, never handed to the component.
81
- [NODE_COLOR_SCHEME_PROP]: _colorScheme } = _ref6, resolvedProps = _object_without_properties_loose(_ref6, [
89
+ [NODE_COLOR_SCHEME_PROP]: _colorScheme } = _ref7, resolvedProps = _object_without_properties_loose(_ref7, [
82
90
  "sx",
83
91
  "className",
84
92
  "style",
@@ -175,6 +183,12 @@ export const Leaf = observer(/*#__PURE__*/ forwardRef((props, ref)=>{
175
183
  const pinnedScheme = resolveElementColorScheme(_colorScheme);
176
184
  const schemeThemes = useSiteSchemeThemes();
177
185
  const forcedTheme = pinnedScheme && pinnedScheme !== activeScheme && schemeThemes ? schemeThemes(pinnedScheme) : undefined;
186
+ // A static subtree far from the viewport keeps its server HTML through
187
+ // hydration and renders when the visitor nears it (AGL-3581). Only on a
188
+ // tree that asked for it — the published page — and decided once, on the
189
+ // leaf's first render. See `deferred-hydration.tsx`.
190
+ const deferHydration = useContext(DeferredHydrationContext);
191
+ const holdServerHtml = useHeldServerHtml(deferHydration && !offForSite && mayHoldServerHtml(node), (_ref3 = node == null ? void 0 : node.$id) != null ? _ref3 : '');
178
192
  // Each realm element hydrates in a boundary of its own (AGL-3390), so one
179
193
  // waiting for its bundle holds only itself. Rendered on the server too:
180
194
  // the browser's tree has to have the same boundary to hydrate into.
@@ -276,9 +290,13 @@ export const Leaf = observer(/*#__PURE__*/ forwardRef((props, ref)=>{
276
290
  // React child per node child. The default single `<Branch>` element reads
277
291
  // as ONE child to `Children.toArray`, so the first slot swallowed the
278
292
  // whole subtree and every later slot got nothing.
279
- const positional = Boolean(((_ref3 = schema == null ? void 0 : (_schema_flags1 = schema.flags) == null ? void 0 : _schema_flags1.positionalChildren) != null ? _ref3 : 0) & FEATURE_FLAG.ENABLED);
280
- const childNodes = positional ? (_ref4 = node == null ? void 0 : node.children) != null ? _ref4 : [] : null;
281
- const element = selfClosing ? /*#__PURE__*/ _jsx(Component, _extends({}, leafProps)) : positional ? /*#__PURE__*/ _jsx(RendererComponents.Consumer, {
293
+ const positional = Boolean(((_ref4 = schema == null ? void 0 : (_schema_flags1 = schema.flags) == null ? void 0 : _schema_flags1.positionalChildren) != null ? _ref4 : 0) & FEATURE_FLAG.ENABLED);
294
+ const childNodes = positional ? (_ref5 = node == null ? void 0 : node.children) != null ? _ref5 : [] : null;
295
+ const element = holdServerHtml ? // The root hydrates; its contents stay the server's until released.
296
+ /*#__PURE__*/ _jsx(Component, _extends({}, withoutChildren(leafProps), {
297
+ dangerouslySetInnerHTML: HELD_SERVER_HTML,
298
+ suppressHydrationWarning: true
299
+ })) : selfClosing ? /*#__PURE__*/ _jsx(Component, _extends({}, leafProps)) : positional ? /*#__PURE__*/ _jsx(RendererComponents.Consumer, {
282
300
  children: ({ StemComponent })=>/*#__PURE__*/ _jsxs(Component, _extends({}, leafProps, {
283
301
  children: [
284
302
  childNodes.map((child, key)=>{
@@ -301,7 +319,7 @@ export const Leaf = observer(/*#__PURE__*/ forwardRef((props, ref)=>{
301
319
  ]
302
320
  }));
303
321
  return /*#__PURE__*/ _jsx(NodeIdentityContext.Provider, {
304
- value: (_ref5 = node == null ? void 0 : node.$id) != null ? _ref5 : '',
322
+ value: (_ref6 = node == null ? void 0 : node.$id) != null ? _ref6 : '',
305
323
  children: element
306
324
  });
307
325
  }));
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/aglyn-node-renderer/src/lib/components/leaf.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2023 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Type-only, with every value this file reads named below (AGL-2486's rule,\n// applied to the render path's hottest file): a VALUE namespace import of the\n// core barrel is opaque to a bundler — it cannot know which exports are read,\n// so every module the barrel reaches is pinned into the published page's first\n// load.\nimport type * as Aglyn from '@aglyn/aglyn'\nimport { components } from '@aglyn/aglyn/aglyn'\nimport { sanitizeAuthorSx } from '@aglyn/aglyn/app-utils/author-css'\nimport { pinRampedTypographySx } from '@aglyn/aglyn/app-utils/responsive-typography-sx'\nimport {\n NODE_HIDE_IF_PROP,\n NODE_HIDE_UNLESS_PROP,\n} from '@aglyn/aglyn/app-utils/reusable-component-keys'\nimport {\n NODE_ANIMATION_DELAY_PROP,\n NODE_ANIMATION_DURATION_PROP,\n NODE_ANIMATION_EASE_PROP,\n NODE_ANIMATION_PROP,\n NODE_ANIMATION_REPEAT_PROP,\n NODE_ANIMATION_STAGGER_PROP,\n NODE_ANIMATION_STAGGER_STEP_PROP,\n NODE_ANIMATION_TRIGGER_PROP,\n resolveElementAnimation,\n} from '@aglyn/aglyn/app-utils/element-animation'\nimport {\n EnabledPluginsContext,\n isSwitchedOffForRenderedSite,\n} from '@aglyn/aglyn/app-utils/enabled-plugins-context'\nimport { NodeIdentityContext } from '@aglyn/aglyn/app-utils/node-identity'\nimport {\n NODE_COLOR_SCHEME_PROP,\n resolveElementColorScheme,\n} from '@aglyn/aglyn/app-utils/element-color-scheme'\nimport { FEATURE_FLAG } from '@aglyn/aglyn/foundation/constants/shared'\n// Deep import, NOT the barrel: `@aglyn/shared-ui-jsx`'s index re-exports the\n// Pages Router hooks (`next/router`), the inline SVG icon set and the whole\n// ~12,000-module MDI catalog, none of which a rendered node needs. This file\n// is on every published page, so the barrel's reach is that page's reach.\nimport { AglynText } from '@aglyn/shared-ui-jsx/components/aglyn-text'\nimport {\n styled,\n ThemeProvider,\n useSiteSchemeThemes,\n useTheme,\n} from '@aglyn/shared-ui-theme'\nimport { mergeSxProps } from '@aglyn/shared-ui-theme'\nimport { observer } from 'mobx-react-lite'\nimport { isNamespacedComponentId } from '@aglyn/aglyn/plugin-manager/plugin-contributions'\nimport { RealmElementsContext } from '@aglyn/aglyn/app-utils/realm-elements-context'\nimport {\n forwardRef,\n type HTMLAttributes,\n Suspense,\n use,\n useContext,\n} from 'react'\nimport { isValidElementType } from 'react-is'\nimport RendererComponents from '../contexts/renderer-components'\nimport { LeafSxTransformContext } from '../contexts/leaf-sx-transform'\nimport { RealmBoundaryContext } from '../contexts/realm-boundary'\nimport { resolvePaletteVarsSx, resolveSchemeSx } from '../utils/scheme-sx'\n\nconst DefaultComponent = styled('div')({})\n\nexport interface LeafProps extends HTMLAttributes<any> {\n children?: any\n node: Aglyn.NodeSchema\n sx?: JSX.SxProps\n}\n\nexport const Leaf = observer(\n forwardRef<any, LeafProps>((props, ref) => {\n const { children, node, sx, className, style, ...rest } = props\n\n // Pull sx/className/style out of the node's props so the spreads below\n // can never clobber the merged values composed explicitly afterwards\n // (AGL-569: `props.sx` used to overwrite the node-level sx entirely).\n //\n // The visibility directives (AGL-1314) come out here too. The compose\n // step consumes and strips them, so a rendered page never carries one —\n // but the component EDITOR renders definition nodes ungrafted, on\n // purpose (the author has to see and select the part a page will hide),\n // and there the raw `hideIf` would spread onto the element as an\n // unknown `hideif` DOM attribute.\n const {\n sx: propsSx,\n className: propsClassName,\n style: propsStyle,\n [NODE_HIDE_IF_PROP]: _hideIf,\n [NODE_HIDE_UNLESS_PROP]: _hideUnless,\n // Element animation (AGL-2486): the reserved animation props come\n // out here for the same reason the visibility directives above do —\n // they are directives to the RENDERER, and spreading them onto the\n // element would land `aglynanimation=\"slide-up\"` as an unknown DOM\n // attribute. Destructured rather than `delete`d after the fact so the\n // `none` case is stripped too (it resolves to no animation, but it is\n // still a prop an author saved) and so the props object keeps one\n // shape.\n [NODE_ANIMATION_PROP]: _animation,\n [NODE_ANIMATION_TRIGGER_PROP]: _animationTrigger,\n [NODE_ANIMATION_DURATION_PROP]: _animationDuration,\n [NODE_ANIMATION_DELAY_PROP]: _animationDelay,\n [NODE_ANIMATION_REPEAT_PROP]: _animationRepeat,\n [NODE_ANIMATION_EASE_PROP]: _animationEase,\n [NODE_ANIMATION_STAGGER_PROP]: _animationStagger,\n [NODE_ANIMATION_STAGGER_STEP_PROP]: _animationStaggerStep,\n // A pinned color scheme (AGL-3284) is a directive to the renderer, like\n // the two groups above: consumed below, never handed to the component.\n [NODE_COLOR_SCHEME_PROP]: _colorScheme,\n ...resolvedProps\n } = (node?.resolvedProps ?? node?.props ?? {}) as Record<string, any>\n\n // Resolves to a class, a `data-` attribute and two custom properties; the\n // keyframes themselves live in the tenant's stylesheet, so this costs no\n // JS on any surface. The besigner canvas never ships that stylesheet\n // (same posture as the AGL-562 hidden class), so an author sees the\n // element in its normal, selectable, un-animated state while editing.\n const animation = resolveElementAnimation({\n [NODE_ANIMATION_PROP]: _animation,\n [NODE_ANIMATION_TRIGGER_PROP]: _animationTrigger,\n [NODE_ANIMATION_DURATION_PROP]: _animationDuration,\n [NODE_ANIMATION_DELAY_PROP]: _animationDelay,\n [NODE_ANIMATION_REPEAT_PROP]: _animationRepeat,\n [NODE_ANIMATION_EASE_PROP]: _animationEase,\n [NODE_ANIMATION_STAGGER_PROP]: _animationStagger,\n [NODE_ANIMATION_STAGGER_STEP_PROP]: _animationStaggerStep,\n })\n // A component whose first-party plugin does not run on the rendered site\n // is drawn exactly as an unregistered one is (AGL-3033): the registry is\n // process-global and outlives the site that loaded the bundle, and the\n // site's own browser never loads it. Its schema goes with it, so the\n // void-element and positional flags cannot tell the server a different\n // story from the client either.\n const enabledPlugins = useContext(EnabledPluginsContext)\n const registeredSchema = components.getSchema(node?.componentId)\n const offForSite = isSwitchedOffForRenderedSite(\n registeredSchema?.pluginId,\n enabledPlugins,\n )\n const Factory = offForSite ? undefined : components.getFactory(node?.componentId)\n // A signed marketplace plugin's element (AGL-3390) renders on the server,\n // and the browser registers its component a moment later. Inside the\n // element's own boundary (below) it waits for that load rather than\n // hydrating the server's HTML as an unregistered element, which would\n // not match it.\n const realmElement = isNamespacedComponentId(node?.componentId)\n const realmBoundary = useContext(RealmBoundaryContext)\n const realmLoadFor = useContext(RealmElementsContext)\n if (realmElement && realmBoundary === node?.$id && !Factory && !offForSite) {\n const loading = realmLoadFor?.(node.componentId)\n if (loading) use(loading)\n }\n const Component = isValidElementType(Factory) ? Factory : DefaultComponent\n\n // Self-closing components (AGL-579): a component whose schema flags it\n // selfClosing renders a void DOM element (e.g. Image -> <img>), and React\n // throws during SSR when ANY children value reaches one — even the empty\n // `[undefined, false]` the JSX below always produces. Rendering the whole\n // page 500s off one image node (blog covers, AGL-579), so honor the flag\n // here: no JSX children, and strip a stray `children` prop too.\n const schema = offForSite ? undefined : registeredSchema\n const selfClosing = Boolean(\n (schema?.flags?.selfClosing ?? 0) & FEATURE_FLAG.ENABLED,\n )\n if (selfClosing) delete resolvedProps['children']\n\n const textContent = selfClosing ? null : resolvedProps?.['children']\n\n const mergedClassName =\n [propsClassName, node?.className, className, animation?.className]\n .filter(Boolean)\n .join(' ') || undefined\n // The animation's custom properties go in FIRST so an author who typed\n // `--aglyn-anim-duration` into the Custom CSS tab still wins.\n const mergedStyle =\n animation || propsStyle || style\n ? { ...animation?.style, ...propsStyle, ...style }\n : undefined\n\n // Canvas-only hook (AGL-581): the besigner provides a transform that\n // re-targets viewport media queries at the artboard device width.\n // Undefined everywhere else, keeping the tenant path unchanged.\n const transformSx = useContext(LeafSxTransformContext)\n // Scheme-scoped styles (AGL-588): sites swap a single-mode theme for\n // light/dark (tenant HostThemeProvider, canvas useAglynSiteTheme), so\n // '@scheme dark' sx slices resolve here against the ACTIVE theme's\n // palette mode — the one signal that is correct in both places.\n const theme = useTheme() as {\n palette?: { mode?: string } & Record<string, unknown>\n } | null\n const activeScheme = theme?.palette?.mode === 'dark' ? 'dark' : 'light'\n // \"Always light\" / \"Always dark\" (AGL-3284). When the pinned scheme is not\n // the active one, the element re-renders itself under the site's theme\n // for that scheme, so its OWN sx — not only its descendants' — resolves\n // palette tokens and `@scheme dark` slices for the pinned scheme. The\n // inner render then sees the pinned scheme as active and takes the plain\n // path; the mode check below is what guarantees that, because a site with\n // one theme for both schemes (a single `fallback`) hands back a theme\n // whose mode never changes, and re-wrapping it would never terminate.\n // No provider (an email render, a bare test) means nothing to force.\n // Every hook above runs on both paths, so the early return keeps the\n // hook order stable when an author flips the setting.\n const pinnedScheme = resolveElementColorScheme(_colorScheme)\n const schemeThemes = useSiteSchemeThemes()\n const forcedTheme =\n pinnedScheme && pinnedScheme !== activeScheme && schemeThemes\n ? schemeThemes(pinnedScheme)\n : undefined\n // Each realm element hydrates in a boundary of its own (AGL-3390), so one\n // waiting for its bundle holds only itself. Rendered on the server too:\n // the browser's tree has to have the same boundary to hydrate into.\n if (realmElement && realmBoundary !== node?.$id) {\n return (\n <Suspense fallback={null}>\n <RealmBoundaryContext.Provider value={node?.$id}>\n <Leaf {...props} ref={ref} />\n </RealmBoundaryContext.Provider>\n </Suspense>\n )\n }\n if (forcedTheme && forcedTheme.palette?.mode === pinnedScheme) {\n return (\n <ThemeProvider theme={forcedTheme}>\n <Leaf {...props} ref={ref} />\n </ThemeProvider>\n )\n }\n // MUI array composition: later entries win on key conflicts, so the\n // node-level sx (Styles panel output) overrides props.sx.\n //\n // Palette token references (AGL-1331): the Background Fill field\n // persists a token-bound gradient stop as\n // `var(--mui-palette-primary-main, #00B0FF)`, because `backgroundImage`\n // is NOT one of the sx keys MUI resolves against the palette. They are\n // substituted here, against the site theme, so the canvas cannot pick\n // up the console's `--mui-palette-*` and the tenant (whose theme has no\n // CSS variables at all) still tracks palette changes. Values carrying\n // no reference come back by identity.\n // Author CSS (AGL-1725): `node.sx` is the Styles panel's output, and it\n // is free text — `backgroundImage` is a first-class field and the Custom\n // CSS tab is free-solo, so any property and any value can be typed. On a\n // published site that lands in the visitor's document as a real `url()`,\n // which no input validator and no lint rule can see. Scrubbed HERE, at\n // the last point before the merge, so it also catches values already\n // stored; `sx` and `props.sx` are OUR components' own styles and are\n // deliberately left alone. The scrub is scheme-only and returns its\n // input by identity when nothing is refused, so the overwhelmingly\n // common case (no `url()` at all) costs one walk and no new object.\n // Responsive type ramp: LAST of the three sx passes, and it has to be —\n // it moves a scalar `fontSize` into the `xs` slice so the author's\n // explicit size sorts after the variant's `responsiveFontSizes` at-rules,\n // and the scheme pass above is what collapses a dark-scheme slice down\n // into that same top level. Running it earlier would leave a dark\n // override's font size unpinned.\n //\n // A pinned element paints its scheme's page background and text color\n // (AGL-3284), UNDER every other style so an author's own background wins.\n // Without it an \"Always dark\" band with no background of its own is\n // light text on the light page behind it, and text that sets no color\n // inherits the page's dark body color. `color-scheme` makes the browser's\n // own controls and scrollbars inside the band match. Only a pinned\n // element carries this, so an unpinned one renders exactly as before —\n // and so does a pinned one on a surface with no site themes to pin to.\n const authorSx = sanitizeAuthorSx(node?.sx)\n const composedSx = pinRampedTypographySx(\n resolvePaletteVarsSx(\n resolveSchemeSx(\n pinnedScheme && schemeThemes\n ? mergeSxProps(\n {\n colorScheme: pinnedScheme,\n color: 'text.primary',\n bgcolor: 'background.default',\n } as any,\n sx as any,\n propsSx as any,\n authorSx as any,\n )\n : mergeSxProps(sx as any, propsSx as any, authorSx as any),\n activeScheme,\n ),\n theme?.palette,\n ),\n )\n /**\n * Author visibility — the eye on the hierarchy row.\n *\n * LAST, and deliberately so. It has to override the element's own\n * display, not replace it: an author who hides a Stack laid out with\n * `display: flex` gets that flex back the moment they show it again,\n * because the stored style was never touched. Writing `display: none`\n * into `node.sx` would have destroyed the value it overwrote, and there\n * would be nothing left to restore.\n *\n * The same rule on both surfaces. A hidden element is absent from the\n * published page and from the canvas — \"hidden\" that still renders in the\n * editor is a preview of a page nobody will ever see.\n */\n const mergedSx = node?.hidden\n ? mergeSxProps(composedSx as any, { display: 'none' } as any)\n : composedSx\n\n // Shared leaf attributes; self-closing components must receive NO\n // children at all (AGL-579) — a separate element expression keeps the\n // childless case genuinely childless instead of `[undefined, false]`.\n const leafProps = {\n ref,\n 'data-aglyn': `leaf:${node?.$id}`,\n // Before the spreads: the animation trigger/repeat attributes are ours,\n // but an element that genuinely declared one of these should win.\n ...animation?.attributes,\n ...resolvedProps,\n ...rest,\n className: mergedClassName,\n style: mergedStyle,\n sx: transformSx\n ? (transformSx(mergedSx, node?.$id) as typeof mergedSx)\n : mergedSx,\n }\n\n // Node identity (AGL-659): components get their node id through a\n // context rather than a prop, so a block can look up the server-seeded\n // slice of `pageData` that belongs to IT — two grids on one page have\n // different queries and must not share a seed. Nothing is added to any\n // component's props, so no unknown attribute reaches the DOM.\n // Positional children (AGL-1237): a component that splits its children\n // by index — MUI's Accordion is `[summary, ...rest]` — must receive one\n // React child per node child. The default single `<Branch>` element reads\n // as ONE child to `Children.toArray`, so the first slot swallowed the\n // whole subtree and every later slot got nothing.\n const positional = Boolean(\n (schema?.flags?.positionalChildren ?? 0) & FEATURE_FLAG.ENABLED,\n )\n const childNodes = positional ? (node?.children ?? []) : null\n\n const element = selfClosing ? (\n <Component {...leafProps} />\n ) : positional ? (\n <RendererComponents.Consumer>\n {({ StemComponent }) => (\n <Component {...leafProps}>\n {childNodes!.map((child: any, key: number) => (\n <StemComponent key={child?.$id ?? key} node={child} />\n ))}\n {textContent != null && (\n <AglynText>{textContent as any}</AglynText>\n )}\n </Component>\n )}\n </RendererComponents.Consumer>\n ) : (\n <Component {...leafProps}>\n {children}\n\n {textContent != null && (\n <AglynText>{textContent as any}</AglynText>\n )}\n </Component>\n )\n\n return (\n <NodeIdentityContext.Provider value={node?.$id ?? ''}>\n {element}\n </NodeIdentityContext.Provider>\n )\n }),\n)\nLeaf.displayName = 'Leaf'\nLeaf['aglyn'] = true\n\nexport default Leaf\n"],"names":["components","sanitizeAuthorSx","pinRampedTypographySx","NODE_HIDE_IF_PROP","NODE_HIDE_UNLESS_PROP","NODE_ANIMATION_DELAY_PROP","NODE_ANIMATION_DURATION_PROP","NODE_ANIMATION_EASE_PROP","NODE_ANIMATION_PROP","NODE_ANIMATION_REPEAT_PROP","NODE_ANIMATION_STAGGER_PROP","NODE_ANIMATION_STAGGER_STEP_PROP","NODE_ANIMATION_TRIGGER_PROP","resolveElementAnimation","EnabledPluginsContext","isSwitchedOffForRenderedSite","NodeIdentityContext","NODE_COLOR_SCHEME_PROP","resolveElementColorScheme","FEATURE_FLAG","AglynText","styled","ThemeProvider","useSiteSchemeThemes","useTheme","mergeSxProps","observer","isNamespacedComponentId","RealmElementsContext","forwardRef","Suspense","use","useContext","isValidElementType","RendererComponents","LeafSxTransformContext","RealmBoundaryContext","resolvePaletteVarsSx","resolveSchemeSx","DefaultComponent","Leaf","props","ref","node","schema","theme","forcedTheme","children","sx","className","style","rest","resolvedProps","propsSx","propsClassName","propsStyle","_hideIf","_hideUnless","_animation","_animationTrigger","_animationDuration","_animationDelay","_animationRepeat","_animationEase","_animationStagger","_animationStaggerStep","_colorScheme","animation","enabledPlugins","registeredSchema","getSchema","componentId","offForSite","pluginId","Factory","undefined","getFactory","realmElement","realmBoundary","realmLoadFor","$id","loading","Component","selfClosing","Boolean","flags","ENABLED","textContent","mergedClassName","filter","join","mergedStyle","transformSx","activeScheme","palette","mode","pinnedScheme","schemeThemes","fallback","Provider","value","authorSx","composedSx","colorScheme","color","bgcolor","mergedSx","hidden","display","leafProps","attributes","positional","positionalChildren","childNodes","element","Consumer","StemComponent","map","child","key","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../libs/aglyn-node-renderer/src/lib/components/leaf.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2023 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Type-only, with every value this file reads named below (AGL-2486's rule,\n// applied to the render path's hottest file): a VALUE namespace import of the\n// core barrel is opaque to a bundler — it cannot know which exports are read,\n// so every module the barrel reaches is pinned into the published page's first\n// load.\nimport type * as Aglyn from '@aglyn/aglyn'\nimport { components } from '@aglyn/aglyn/aglyn'\nimport { sanitizeAuthorSx } from '@aglyn/aglyn/app-utils/author-css'\nimport { pinRampedTypographySx } from '@aglyn/aglyn/app-utils/responsive-typography-sx'\nimport {\n NODE_HIDE_IF_PROP,\n NODE_HIDE_UNLESS_PROP,\n} from '@aglyn/aglyn/app-utils/reusable-component-keys'\nimport {\n NODE_ANIMATION_DELAY_PROP,\n NODE_ANIMATION_DURATION_PROP,\n NODE_ANIMATION_EASE_PROP,\n NODE_ANIMATION_PROP,\n NODE_ANIMATION_REPEAT_PROP,\n NODE_ANIMATION_STAGGER_PROP,\n NODE_ANIMATION_STAGGER_STEP_PROP,\n NODE_ANIMATION_TRIGGER_PROP,\n resolveElementAnimation,\n} from '@aglyn/aglyn/app-utils/element-animation'\nimport {\n EnabledPluginsContext,\n isSwitchedOffForRenderedSite,\n} from '@aglyn/aglyn/app-utils/enabled-plugins-context'\nimport { NodeIdentityContext } from '@aglyn/aglyn/app-utils/node-identity'\nimport {\n NODE_COLOR_SCHEME_PROP,\n resolveElementColorScheme,\n} from '@aglyn/aglyn/app-utils/element-color-scheme'\nimport { FEATURE_FLAG } from '@aglyn/aglyn/foundation/constants/shared'\n// Deep import, NOT the barrel: `@aglyn/shared-ui-jsx`'s index re-exports the\n// Pages Router hooks (`next/router`), the inline SVG icon set and the whole\n// ~12,000-module MDI catalog, none of which a rendered node needs. This file\n// is on every published page, so the barrel's reach is that page's reach.\nimport { AglynText } from '@aglyn/shared-ui-jsx/components/aglyn-text'\nimport {\n styled,\n ThemeProvider,\n useSiteSchemeThemes,\n useTheme,\n} from '@aglyn/shared-ui-theme'\nimport { mergeSxProps } from '@aglyn/shared-ui-theme'\nimport { observer } from 'mobx-react-lite'\nimport { isNamespacedComponentId } from '@aglyn/aglyn/plugin-manager/plugin-contributions'\nimport { RealmElementsContext } from '@aglyn/aglyn/app-utils/realm-elements-context'\nimport {\n forwardRef,\n type HTMLAttributes,\n Suspense,\n use,\n useContext,\n} from 'react'\nimport { isValidElementType } from 'react-is'\nimport RendererComponents from '../contexts/renderer-components'\nimport { LeafSxTransformContext } from '../contexts/leaf-sx-transform'\nimport { RealmBoundaryContext } from '../contexts/realm-boundary'\nimport { DeferredHydrationContext } from '../contexts/deferred-hydration'\nimport {\n HELD_SERVER_HTML,\n mayHoldServerHtml,\n useHeldServerHtml,\n} from './deferred-hydration'\nimport { resolvePaletteVarsSx, resolveSchemeSx } from '../utils/scheme-sx'\n\nconst DefaultComponent = styled('div')({})\n\n/** Leaf props with any `children` a resolved prop carried, for a held root. */\nfunction withoutChildren<T extends Record<string, unknown>>(\n props: T,\n): Omit<T, 'children'> {\n const { children: _children, ...rest } = props\n return rest\n}\n\nexport interface LeafProps extends HTMLAttributes<any> {\n children?: any\n node: Aglyn.NodeSchema\n sx?: JSX.SxProps\n}\n\nexport const Leaf = observer(\n forwardRef<any, LeafProps>((props, ref) => {\n const { children, node, sx, className, style, ...rest } = props\n\n // Pull sx/className/style out of the node's props so the spreads below\n // can never clobber the merged values composed explicitly afterwards\n // (AGL-569: `props.sx` used to overwrite the node-level sx entirely).\n //\n // The visibility directives (AGL-1314) come out here too. The compose\n // step consumes and strips them, so a rendered page never carries one —\n // but the component EDITOR renders definition nodes ungrafted, on\n // purpose (the author has to see and select the part a page will hide),\n // and there the raw `hideIf` would spread onto the element as an\n // unknown `hideif` DOM attribute.\n const {\n sx: propsSx,\n className: propsClassName,\n style: propsStyle,\n [NODE_HIDE_IF_PROP]: _hideIf,\n [NODE_HIDE_UNLESS_PROP]: _hideUnless,\n // Element animation (AGL-2486): the reserved animation props come\n // out here for the same reason the visibility directives above do —\n // they are directives to the RENDERER, and spreading them onto the\n // element would land `aglynanimation=\"slide-up\"` as an unknown DOM\n // attribute. Destructured rather than `delete`d after the fact so the\n // `none` case is stripped too (it resolves to no animation, but it is\n // still a prop an author saved) and so the props object keeps one\n // shape.\n [NODE_ANIMATION_PROP]: _animation,\n [NODE_ANIMATION_TRIGGER_PROP]: _animationTrigger,\n [NODE_ANIMATION_DURATION_PROP]: _animationDuration,\n [NODE_ANIMATION_DELAY_PROP]: _animationDelay,\n [NODE_ANIMATION_REPEAT_PROP]: _animationRepeat,\n [NODE_ANIMATION_EASE_PROP]: _animationEase,\n [NODE_ANIMATION_STAGGER_PROP]: _animationStagger,\n [NODE_ANIMATION_STAGGER_STEP_PROP]: _animationStaggerStep,\n // A pinned color scheme (AGL-3284) is a directive to the renderer, like\n // the two groups above: consumed below, never handed to the component.\n [NODE_COLOR_SCHEME_PROP]: _colorScheme,\n ...resolvedProps\n } = (node?.resolvedProps ?? node?.props ?? {}) as Record<string, any>\n\n // Resolves to a class, a `data-` attribute and two custom properties; the\n // keyframes themselves live in the tenant's stylesheet, so this costs no\n // JS on any surface. The besigner canvas never ships that stylesheet\n // (same posture as the AGL-562 hidden class), so an author sees the\n // element in its normal, selectable, un-animated state while editing.\n const animation = resolveElementAnimation({\n [NODE_ANIMATION_PROP]: _animation,\n [NODE_ANIMATION_TRIGGER_PROP]: _animationTrigger,\n [NODE_ANIMATION_DURATION_PROP]: _animationDuration,\n [NODE_ANIMATION_DELAY_PROP]: _animationDelay,\n [NODE_ANIMATION_REPEAT_PROP]: _animationRepeat,\n [NODE_ANIMATION_EASE_PROP]: _animationEase,\n [NODE_ANIMATION_STAGGER_PROP]: _animationStagger,\n [NODE_ANIMATION_STAGGER_STEP_PROP]: _animationStaggerStep,\n })\n // A component whose first-party plugin does not run on the rendered site\n // is drawn exactly as an unregistered one is (AGL-3033): the registry is\n // process-global and outlives the site that loaded the bundle, and the\n // site's own browser never loads it. Its schema goes with it, so the\n // void-element and positional flags cannot tell the server a different\n // story from the client either.\n const enabledPlugins = useContext(EnabledPluginsContext)\n const registeredSchema = components.getSchema(node?.componentId)\n const offForSite = isSwitchedOffForRenderedSite(\n registeredSchema?.pluginId,\n enabledPlugins,\n )\n const Factory = offForSite ? undefined : components.getFactory(node?.componentId)\n // A signed marketplace plugin's element (AGL-3390) renders on the server,\n // and the browser registers its component a moment later. Inside the\n // element's own boundary (below) it waits for that load rather than\n // hydrating the server's HTML as an unregistered element, which would\n // not match it.\n const realmElement = isNamespacedComponentId(node?.componentId)\n const realmBoundary = useContext(RealmBoundaryContext)\n const realmLoadFor = useContext(RealmElementsContext)\n if (realmElement && realmBoundary === node?.$id && !Factory && !offForSite) {\n const loading = realmLoadFor?.(node.componentId)\n if (loading) use(loading)\n }\n const Component = isValidElementType(Factory) ? Factory : DefaultComponent\n\n // Self-closing components (AGL-579): a component whose schema flags it\n // selfClosing renders a void DOM element (e.g. Image -> <img>), and React\n // throws during SSR when ANY children value reaches one — even the empty\n // `[undefined, false]` the JSX below always produces. Rendering the whole\n // page 500s off one image node (blog covers, AGL-579), so honor the flag\n // here: no JSX children, and strip a stray `children` prop too.\n const schema = offForSite ? undefined : registeredSchema\n const selfClosing = Boolean(\n (schema?.flags?.selfClosing ?? 0) & FEATURE_FLAG.ENABLED,\n )\n if (selfClosing) delete resolvedProps['children']\n\n const textContent = selfClosing ? null : resolvedProps?.['children']\n\n const mergedClassName =\n [propsClassName, node?.className, className, animation?.className]\n .filter(Boolean)\n .join(' ') || undefined\n // The animation's custom properties go in FIRST so an author who typed\n // `--aglyn-anim-duration` into the Custom CSS tab still wins.\n const mergedStyle =\n animation || propsStyle || style\n ? { ...animation?.style, ...propsStyle, ...style }\n : undefined\n\n // Canvas-only hook (AGL-581): the besigner provides a transform that\n // re-targets viewport media queries at the artboard device width.\n // Undefined everywhere else, keeping the tenant path unchanged.\n const transformSx = useContext(LeafSxTransformContext)\n // Scheme-scoped styles (AGL-588): sites swap a single-mode theme for\n // light/dark (tenant HostThemeProvider, canvas useAglynSiteTheme), so\n // '@scheme dark' sx slices resolve here against the ACTIVE theme's\n // palette mode — the one signal that is correct in both places.\n const theme = useTheme() as {\n palette?: { mode?: string } & Record<string, unknown>\n } | null\n const activeScheme = theme?.palette?.mode === 'dark' ? 'dark' : 'light'\n // \"Always light\" / \"Always dark\" (AGL-3284). When the pinned scheme is not\n // the active one, the element re-renders itself under the site's theme\n // for that scheme, so its OWN sx — not only its descendants' — resolves\n // palette tokens and `@scheme dark` slices for the pinned scheme. The\n // inner render then sees the pinned scheme as active and takes the plain\n // path; the mode check below is what guarantees that, because a site with\n // one theme for both schemes (a single `fallback`) hands back a theme\n // whose mode never changes, and re-wrapping it would never terminate.\n // No provider (an email render, a bare test) means nothing to force.\n // Every hook above runs on both paths, so the early return keeps the\n // hook order stable when an author flips the setting.\n const pinnedScheme = resolveElementColorScheme(_colorScheme)\n const schemeThemes = useSiteSchemeThemes()\n const forcedTheme =\n pinnedScheme && pinnedScheme !== activeScheme && schemeThemes\n ? schemeThemes(pinnedScheme)\n : undefined\n // A static subtree far from the viewport keeps its server HTML through\n // hydration and renders when the visitor nears it (AGL-3581). Only on a\n // tree that asked for it — the published page — and decided once, on the\n // leaf's first render. See `deferred-hydration.tsx`.\n const deferHydration = useContext(DeferredHydrationContext)\n const holdServerHtml = useHeldServerHtml(\n deferHydration && !offForSite && mayHoldServerHtml(node),\n node?.$id ?? '',\n )\n // Each realm element hydrates in a boundary of its own (AGL-3390), so one\n // waiting for its bundle holds only itself. Rendered on the server too:\n // the browser's tree has to have the same boundary to hydrate into.\n if (realmElement && realmBoundary !== node?.$id) {\n return (\n <Suspense fallback={null}>\n <RealmBoundaryContext.Provider value={node?.$id}>\n <Leaf {...props} ref={ref} />\n </RealmBoundaryContext.Provider>\n </Suspense>\n )\n }\n if (forcedTheme && forcedTheme.palette?.mode === pinnedScheme) {\n return (\n <ThemeProvider theme={forcedTheme}>\n <Leaf {...props} ref={ref} />\n </ThemeProvider>\n )\n }\n // MUI array composition: later entries win on key conflicts, so the\n // node-level sx (Styles panel output) overrides props.sx.\n //\n // Palette token references (AGL-1331): the Background Fill field\n // persists a token-bound gradient stop as\n // `var(--mui-palette-primary-main, #00B0FF)`, because `backgroundImage`\n // is NOT one of the sx keys MUI resolves against the palette. They are\n // substituted here, against the site theme, so the canvas cannot pick\n // up the console's `--mui-palette-*` and the tenant (whose theme has no\n // CSS variables at all) still tracks palette changes. Values carrying\n // no reference come back by identity.\n // Author CSS (AGL-1725): `node.sx` is the Styles panel's output, and it\n // is free text — `backgroundImage` is a first-class field and the Custom\n // CSS tab is free-solo, so any property and any value can be typed. On a\n // published site that lands in the visitor's document as a real `url()`,\n // which no input validator and no lint rule can see. Scrubbed HERE, at\n // the last point before the merge, so it also catches values already\n // stored; `sx` and `props.sx` are OUR components' own styles and are\n // deliberately left alone. The scrub is scheme-only and returns its\n // input by identity when nothing is refused, so the overwhelmingly\n // common case (no `url()` at all) costs one walk and no new object.\n // Responsive type ramp: LAST of the three sx passes, and it has to be —\n // it moves a scalar `fontSize` into the `xs` slice so the author's\n // explicit size sorts after the variant's `responsiveFontSizes` at-rules,\n // and the scheme pass above is what collapses a dark-scheme slice down\n // into that same top level. Running it earlier would leave a dark\n // override's font size unpinned.\n //\n // A pinned element paints its scheme's page background and text color\n // (AGL-3284), UNDER every other style so an author's own background wins.\n // Without it an \"Always dark\" band with no background of its own is\n // light text on the light page behind it, and text that sets no color\n // inherits the page's dark body color. `color-scheme` makes the browser's\n // own controls and scrollbars inside the band match. Only a pinned\n // element carries this, so an unpinned one renders exactly as before —\n // and so does a pinned one on a surface with no site themes to pin to.\n const authorSx = sanitizeAuthorSx(node?.sx)\n const composedSx = pinRampedTypographySx(\n resolvePaletteVarsSx(\n resolveSchemeSx(\n pinnedScheme && schemeThemes\n ? mergeSxProps(\n {\n colorScheme: pinnedScheme,\n color: 'text.primary',\n bgcolor: 'background.default',\n } as any,\n sx as any,\n propsSx as any,\n authorSx as any,\n )\n : mergeSxProps(sx as any, propsSx as any, authorSx as any),\n activeScheme,\n ),\n theme?.palette,\n ),\n )\n /**\n * Author visibility — the eye on the hierarchy row.\n *\n * LAST, and deliberately so. It has to override the element's own\n * display, not replace it: an author who hides a Stack laid out with\n * `display: flex` gets that flex back the moment they show it again,\n * because the stored style was never touched. Writing `display: none`\n * into `node.sx` would have destroyed the value it overwrote, and there\n * would be nothing left to restore.\n *\n * The same rule on both surfaces. A hidden element is absent from the\n * published page and from the canvas — \"hidden\" that still renders in the\n * editor is a preview of a page nobody will ever see.\n */\n const mergedSx = node?.hidden\n ? mergeSxProps(composedSx as any, { display: 'none' } as any)\n : composedSx\n\n // Shared leaf attributes; self-closing components must receive NO\n // children at all (AGL-579) — a separate element expression keeps the\n // childless case genuinely childless instead of `[undefined, false]`.\n const leafProps = {\n ref,\n 'data-aglyn': `leaf:${node?.$id}`,\n // Before the spreads: the animation trigger/repeat attributes are ours,\n // but an element that genuinely declared one of these should win.\n ...animation?.attributes,\n ...resolvedProps,\n ...rest,\n className: mergedClassName,\n style: mergedStyle,\n sx: transformSx\n ? (transformSx(mergedSx, node?.$id) as typeof mergedSx)\n : mergedSx,\n }\n\n // Node identity (AGL-659): components get their node id through a\n // context rather than a prop, so a block can look up the server-seeded\n // slice of `pageData` that belongs to IT — two grids on one page have\n // different queries and must not share a seed. Nothing is added to any\n // component's props, so no unknown attribute reaches the DOM.\n // Positional children (AGL-1237): a component that splits its children\n // by index — MUI's Accordion is `[summary, ...rest]` — must receive one\n // React child per node child. The default single `<Branch>` element reads\n // as ONE child to `Children.toArray`, so the first slot swallowed the\n // whole subtree and every later slot got nothing.\n const positional = Boolean(\n (schema?.flags?.positionalChildren ?? 0) & FEATURE_FLAG.ENABLED,\n )\n const childNodes = positional ? (node?.children ?? []) : null\n\n const element = holdServerHtml ? (\n // The root hydrates; its contents stay the server's until released.\n <Component\n {...withoutChildren(leafProps)}\n dangerouslySetInnerHTML={HELD_SERVER_HTML}\n suppressHydrationWarning\n />\n ) : selfClosing ? (\n <Component {...leafProps} />\n ) : positional ? (\n <RendererComponents.Consumer>\n {({ StemComponent }) => (\n <Component {...leafProps}>\n {childNodes!.map((child: any, key: number) => (\n <StemComponent key={child?.$id ?? key} node={child} />\n ))}\n {textContent != null && (\n <AglynText>{textContent as any}</AglynText>\n )}\n </Component>\n )}\n </RendererComponents.Consumer>\n ) : (\n <Component {...leafProps}>\n {children}\n\n {textContent != null && (\n <AglynText>{textContent as any}</AglynText>\n )}\n </Component>\n )\n\n return (\n <NodeIdentityContext.Provider value={node?.$id ?? ''}>\n {element}\n </NodeIdentityContext.Provider>\n )\n }),\n)\nLeaf.displayName = 'Leaf'\nLeaf['aglyn'] = true\n\nexport default Leaf\n"],"names":["components","sanitizeAuthorSx","pinRampedTypographySx","NODE_HIDE_IF_PROP","NODE_HIDE_UNLESS_PROP","NODE_ANIMATION_DELAY_PROP","NODE_ANIMATION_DURATION_PROP","NODE_ANIMATION_EASE_PROP","NODE_ANIMATION_PROP","NODE_ANIMATION_REPEAT_PROP","NODE_ANIMATION_STAGGER_PROP","NODE_ANIMATION_STAGGER_STEP_PROP","NODE_ANIMATION_TRIGGER_PROP","resolveElementAnimation","EnabledPluginsContext","isSwitchedOffForRenderedSite","NodeIdentityContext","NODE_COLOR_SCHEME_PROP","resolveElementColorScheme","FEATURE_FLAG","AglynText","styled","ThemeProvider","useSiteSchemeThemes","useTheme","mergeSxProps","observer","isNamespacedComponentId","RealmElementsContext","forwardRef","Suspense","use","useContext","isValidElementType","RendererComponents","LeafSxTransformContext","RealmBoundaryContext","DeferredHydrationContext","HELD_SERVER_HTML","mayHoldServerHtml","useHeldServerHtml","resolvePaletteVarsSx","resolveSchemeSx","DefaultComponent","withoutChildren","props","children","_children","rest","Leaf","ref","node","schema","theme","forcedTheme","sx","className","style","resolvedProps","propsSx","propsClassName","propsStyle","_hideIf","_hideUnless","_animation","_animationTrigger","_animationDuration","_animationDelay","_animationRepeat","_animationEase","_animationStagger","_animationStaggerStep","_colorScheme","animation","enabledPlugins","registeredSchema","getSchema","componentId","offForSite","pluginId","Factory","undefined","getFactory","realmElement","realmBoundary","realmLoadFor","$id","loading","Component","selfClosing","Boolean","flags","ENABLED","textContent","mergedClassName","filter","join","mergedStyle","transformSx","activeScheme","palette","mode","pinnedScheme","schemeThemes","deferHydration","holdServerHtml","fallback","Provider","value","authorSx","composedSx","colorScheme","color","bgcolor","mergedSx","hidden","display","leafProps","attributes","positional","positionalChildren","childNodes","element","dangerouslySetInnerHTML","suppressHydrationWarning","Consumer","StemComponent","map","child","key","displayName"],"mappings":"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"}
@@ -18,6 +18,12 @@ import type * as Aglyn from '@aglyn/aglyn';
18
18
  import { type RenderComponentsContext } from '../contexts/renderer-components';
19
19
  export interface TreeRootProps extends Partial<RenderComponentsContext> {
20
20
  node: Aglyn.NodeSchema;
21
+ /**
22
+ * Keep static subtrees' server HTML through hydration and render them
23
+ * when the visitor nears them (AGL-3581). Only for a tree that arrived as
24
+ * server HTML — the published page. See `deferred-hydration.tsx`.
25
+ */
26
+ deferHydration?: boolean;
21
27
  }
22
28
  export declare const TreeRoot: import("react").ForwardRefExoticComponent<TreeRootProps & import("react").RefAttributes<any>> & import("react").NamedExoticComponent<TreeRootProps & import("react").RefAttributes<any>> & {
23
29
  readonly type: import("react").ForwardRefExoticComponent<TreeRootProps & import("react").RefAttributes<any>>;
@@ -19,14 +19,16 @@
19
19
  // is pinned into the published page's first load.
20
20
  import { jsx as _jsx } from "react/jsx-runtime";
21
21
  import { observer } from "mobx-react-lite";
22
- import { forwardRef, useMemo } from "react";
22
+ import { forwardRef, useEffect, useMemo } from "react";
23
+ import { DeferredHydrationContext } from "../contexts/deferred-hydration.js";
23
24
  import RendererComponents from "../contexts/renderer-components.js";
24
25
  import Branch from "./branch.js";
26
+ import { markTreeHydrated } from "./deferred-hydration.js";
25
27
  import Leaf from "./leaf.js";
26
28
  import Stem from "./stem.js";
27
29
  import Trunk from "./trunk.js";
28
30
  export const TreeRoot = observer(/*#__PURE__*/ forwardRef((props, ref)=>{
29
- const { node, TrunkComponent, StemComponent, BranchComponent, LeafComponent } = props;
31
+ const { node, TrunkComponent, StemComponent, BranchComponent, LeafComponent, deferHydration } = props;
30
32
  const Components = useMemo(()=>({
31
33
  TrunkComponent: TrunkComponent != null ? TrunkComponent : Trunk,
32
34
  StemComponent: StemComponent != null ? StemComponent : Stem,
@@ -38,11 +40,21 @@ export const TreeRoot = observer(/*#__PURE__*/ forwardRef((props, ref)=>{
38
40
  BranchComponent,
39
41
  LeafComponent
40
42
  ]);
41
- return /*#__PURE__*/ _jsx(RendererComponents.Provider, {
42
- value: Components,
43
- children: /*#__PURE__*/ _jsx(Components.TrunkComponent, {
44
- ref: ref,
45
- node: node
43
+ // After the first commit — which is the hydration — no leaf mounting
44
+ // later may hold server HTML it never had (AGL-3581).
45
+ useEffect(()=>{
46
+ if (deferHydration) markTreeHydrated();
47
+ }, [
48
+ deferHydration
49
+ ]);
50
+ return /*#__PURE__*/ _jsx(DeferredHydrationContext.Provider, {
51
+ value: Boolean(deferHydration),
52
+ children: /*#__PURE__*/ _jsx(RendererComponents.Provider, {
53
+ value: Components,
54
+ children: /*#__PURE__*/ _jsx(Components.TrunkComponent, {
55
+ ref: ref,
56
+ node: node
57
+ })
46
58
  })
47
59
  });
48
60
  }));
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/aglyn-node-renderer/src/lib/components/tree-root.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2023 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Type-only: this file names `Aglyn.NodeSchema` and nothing else, and a\n// VALUE namespace import of the core barrel is opaque to a bundler — it\n// cannot know which exports are read, so every module the barrel reaches\n// is pinned into the published page's first load.\nimport type * as Aglyn from '@aglyn/aglyn'\nimport { observer } from 'mobx-react-lite'\nimport { forwardRef, useMemo } from 'react'\nimport RendererComponents, {\n type RenderComponentsContext,\n} from '../contexts/renderer-components'\nimport Branch from './branch'\nimport Leaf from './leaf'\nimport Stem from './stem'\nimport Trunk from './trunk'\n\nexport interface TreeRootProps extends Partial<RenderComponentsContext> {\n node: Aglyn.NodeSchema\n}\n\nexport const TreeRoot = observer(\n forwardRef<any, TreeRootProps>((props, ref) => {\n const {\n node,\n TrunkComponent,\n StemComponent,\n BranchComponent,\n LeafComponent,\n } = props\n\n const Components = useMemo<RenderComponentsContext>(\n () => ({\n TrunkComponent: (TrunkComponent ?? Trunk) as RenderComponentsContext['TrunkComponent'],\n StemComponent: (StemComponent ?? Stem) as RenderComponentsContext['StemComponent'],\n BranchComponent: (BranchComponent ?? Branch) as RenderComponentsContext['BranchComponent'],\n LeafComponent: (LeafComponent ?? Leaf) as RenderComponentsContext['LeafComponent'],\n }),\n [TrunkComponent, StemComponent, BranchComponent, LeafComponent],\n )\n\n return (\n <RendererComponents.Provider value={Components}>\n <Components.TrunkComponent ref={ref} node={node} />\n </RendererComponents.Provider>\n )\n }),\n)\nTreeRoot.displayName = 'TreeRoot'\nTreeRoot['aglyn'] = true\n\nexport default TreeRoot\n"],"names":["observer","forwardRef","useMemo","RendererComponents","Branch","Leaf","Stem","Trunk","TreeRoot","props","ref","node","TrunkComponent","StemComponent","BranchComponent","LeafComponent","Components","Provider","value","displayName"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,wEAAwE;AACxE,wEAAwE;AACxE,yEAAyE;AACzE,kDAAkD;;AAElD,SAASA,QAAQ,QAAQ,kBAAiB;AAC1C,SAASC,UAAU,EAAEC,OAAO,QAAQ,QAAO;AAC3C,OAAOC,wBAEA,qCAAiC;AACxC,OAAOC,YAAY,cAAU;AAC7B,OAAOC,UAAU,YAAQ;AACzB,OAAOC,UAAU,YAAQ;AACzB,OAAOC,WAAW,aAAS;AAM3B,OAAO,MAAMC,WAAWR,uBACtBC,WAA+B,CAACQ,OAAOC;IACrC,MAAM,EACJC,IAAI,EACJC,cAAc,EACdC,aAAa,EACbC,eAAe,EACfC,aAAa,EACd,GAAGN;IAEJ,MAAMO,aAAad,QACjB,IAAO,CAAA;YACLU,cAAc,EAAGA,yBAAAA,iBAAkBL;YACnCM,aAAa,EAAGA,wBAAAA,gBAAiBP;YACjCQ,eAAe,EAAGA,0BAAAA,kBAAmBV;YACrCW,aAAa,EAAGA,wBAAAA,gBAAiBV;QACnC,CAAA,GACA;QAACO;QAAgBC;QAAeC;QAAiBC;KAAc;IAGjE,qBACE,KAACZ,mBAAmBc,QAAQ;QAACC,OAAOF;kBAClC,cAAA,KAACA,WAAWJ,cAAc;YAACF,KAAKA;YAAKC,MAAMA;;;AAGjD,IACD;AACDH,SAASW,WAAW,GAAG;AACvBX,QAAQ,CAAC,QAAQ,GAAG;AAEpB,eAAeA,SAAQ"}
1
+ {"version":3,"sources":["../../../../../../libs/aglyn-node-renderer/src/lib/components/tree-root.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2023 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Type-only: this file names `Aglyn.NodeSchema` and nothing else, and a\n// VALUE namespace import of the core barrel is opaque to a bundler — it\n// cannot know which exports are read, so every module the barrel reaches\n// is pinned into the published page's first load.\nimport type * as Aglyn from '@aglyn/aglyn'\nimport { observer } from 'mobx-react-lite'\nimport { forwardRef, useEffect, useMemo } from 'react'\nimport { DeferredHydrationContext } from '../contexts/deferred-hydration'\nimport RendererComponents, {\n type RenderComponentsContext,\n} from '../contexts/renderer-components'\nimport Branch from './branch'\nimport { markTreeHydrated } from './deferred-hydration'\nimport Leaf from './leaf'\nimport Stem from './stem'\nimport Trunk from './trunk'\n\nexport interface TreeRootProps extends Partial<RenderComponentsContext> {\n node: Aglyn.NodeSchema\n /**\n * Keep static subtrees' server HTML through hydration and render them\n * when the visitor nears them (AGL-3581). Only for a tree that arrived as\n * server HTML — the published page. See `deferred-hydration.tsx`.\n */\n deferHydration?: boolean\n}\n\nexport const TreeRoot = observer(\n forwardRef<any, TreeRootProps>((props, ref) => {\n const {\n node,\n TrunkComponent,\n StemComponent,\n BranchComponent,\n LeafComponent,\n deferHydration,\n } = props\n\n const Components = useMemo<RenderComponentsContext>(\n () => ({\n TrunkComponent: (TrunkComponent ?? Trunk) as RenderComponentsContext['TrunkComponent'],\n StemComponent: (StemComponent ?? Stem) as RenderComponentsContext['StemComponent'],\n BranchComponent: (BranchComponent ?? Branch) as RenderComponentsContext['BranchComponent'],\n LeafComponent: (LeafComponent ?? Leaf) as RenderComponentsContext['LeafComponent'],\n }),\n [TrunkComponent, StemComponent, BranchComponent, LeafComponent],\n )\n\n // After the first commit — which is the hydration — no leaf mounting\n // later may hold server HTML it never had (AGL-3581).\n useEffect(() => {\n if (deferHydration) markTreeHydrated()\n }, [deferHydration])\n\n return (\n <DeferredHydrationContext.Provider value={Boolean(deferHydration)}>\n <RendererComponents.Provider value={Components}>\n <Components.TrunkComponent ref={ref} node={node} />\n </RendererComponents.Provider>\n </DeferredHydrationContext.Provider>\n )\n }),\n)\nTreeRoot.displayName = 'TreeRoot'\nTreeRoot['aglyn'] = true\n\nexport default TreeRoot\n"],"names":["observer","forwardRef","useEffect","useMemo","DeferredHydrationContext","RendererComponents","Branch","markTreeHydrated","Leaf","Stem","Trunk","TreeRoot","props","ref","node","TrunkComponent","StemComponent","BranchComponent","LeafComponent","deferHydration","Components","Provider","value","Boolean","displayName"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,wEAAwE;AACxE,wEAAwE;AACxE,yEAAyE;AACzE,kDAAkD;;AAElD,SAASA,QAAQ,QAAQ,kBAAiB;AAC1C,SAASC,UAAU,EAAEC,SAAS,EAAEC,OAAO,QAAQ,QAAO;AACtD,SAASC,wBAAwB,QAAQ,oCAAgC;AACzE,OAAOC,wBAEA,qCAAiC;AACxC,OAAOC,YAAY,cAAU;AAC7B,SAASC,gBAAgB,QAAQ,0BAAsB;AACvD,OAAOC,UAAU,YAAQ;AACzB,OAAOC,UAAU,YAAQ;AACzB,OAAOC,WAAW,aAAS;AAY3B,OAAO,MAAMC,WAAWX,uBACtBC,WAA+B,CAACW,OAAOC;IACrC,MAAM,EACJC,IAAI,EACJC,cAAc,EACdC,aAAa,EACbC,eAAe,EACfC,aAAa,EACbC,cAAc,EACf,GAAGP;IAEJ,MAAMQ,aAAajB,QACjB,IAAO,CAAA;YACLY,cAAc,EAAGA,yBAAAA,iBAAkBL;YACnCM,aAAa,EAAGA,wBAAAA,gBAAiBP;YACjCQ,eAAe,EAAGA,0BAAAA,kBAAmBX;YACrCY,aAAa,EAAGA,wBAAAA,gBAAiBV;QACnC,CAAA,GACA;QAACO;QAAgBC;QAAeC;QAAiBC;KAAc;IAGjE,qEAAqE;IACrE,sDAAsD;IACtDhB,UAAU;QACR,IAAIiB,gBAAgBZ;IACtB,GAAG;QAACY;KAAe;IAEnB,qBACE,KAACf,yBAAyBiB,QAAQ;QAACC,OAAOC,QAAQJ;kBAChD,cAAA,KAACd,mBAAmBgB,QAAQ;YAACC,OAAOF;sBAClC,cAAA,KAACA,WAAWL,cAAc;gBAACF,KAAKA;gBAAKC,MAAMA;;;;AAInD,IACD;AACDH,SAASa,WAAW,GAAG;AACvBb,QAAQ,CAAC,QAAQ,GAAG;AAEpB,eAAeA,SAAQ"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Whether this tree may hold static subtrees' server HTML through hydration
3
+ * (AGL-3581). See `deferred-hydration.tsx`.
4
+ *
5
+ * Off by default, which is every surface that renders a tree it did not
6
+ * receive as server HTML — the Besigner canvas, the console previews — and
7
+ * for which "keep the server HTML" means nothing. The published tenant page
8
+ * turns it on through `TreeRoot`'s `deferHydration`.
9
+ */
10
+ export declare const DeferredHydrationContext: import("react").Context<boolean>;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ import { createContext } from "react";
17
+ /**
18
+ * Whether this tree may hold static subtrees' server HTML through hydration
19
+ * (AGL-3581). See `deferred-hydration.tsx`.
20
+ *
21
+ * Off by default, which is every surface that renders a tree it did not
22
+ * receive as server HTML — the Besigner canvas, the console previews — and
23
+ * for which "keep the server HTML" means nothing. The published tenant page
24
+ * turns it on through `TreeRoot`'s `deferHydration`.
25
+ */ export const DeferredHydrationContext = createContext(false);
26
+ DeferredHydrationContext.displayName = 'DeferredHydrationContext';
27
+
28
+ //# sourceMappingURL=deferred-hydration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/aglyn-node-renderer/src/lib/contexts/deferred-hydration.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { createContext } from 'react'\n\n/**\n * Whether this tree may hold static subtrees' server HTML through hydration\n * (AGL-3581). See `deferred-hydration.tsx`.\n *\n * Off by default, which is every surface that renders a tree it did not\n * receive as server HTML — the Besigner canvas, the console previews — and\n * for which \"keep the server HTML\" means nothing. The published tenant page\n * turns it on through `TreeRoot`'s `deferHydration`.\n */\nexport const DeferredHydrationContext = createContext(false)\nDeferredHydrationContext.displayName = 'DeferredHydrationContext'\n"],"names":["createContext","DeferredHydrationContext","displayName"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD,SAASA,aAAa,QAAQ,QAAO;AAErC;;;;;;;;CAQC,GACD,OAAO,MAAMC,2BAA2BD,cAAc,OAAM;AAC5DC,yBAAyBC,WAAW,GAAG"}