@aglyn/tenant-runtime 1.0.0-beta.143

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.
Files changed (120) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +26 -0
  3. package/package.json +39 -0
  4. package/src/index.d.ts +21 -0
  5. package/src/index.js +22 -0
  6. package/src/index.js.map +1 -0
  7. package/src/lib/apply-publish-schedule.d.ts +34 -0
  8. package/src/lib/apply-publish-schedule.js +285 -0
  9. package/src/lib/apply-publish-schedule.js.map +1 -0
  10. package/src/lib/assign-contact-owner.d.ts +162 -0
  11. package/src/lib/assign-contact-owner.js +322 -0
  12. package/src/lib/assign-contact-owner.js.map +1 -0
  13. package/src/lib/associate-company-by-domain.d.ts +70 -0
  14. package/src/lib/associate-company-by-domain.js +150 -0
  15. package/src/lib/associate-company-by-domain.js.map +1 -0
  16. package/src/lib/author-page-nodes.d.ts +36 -0
  17. package/src/lib/author-page-nodes.js +296 -0
  18. package/src/lib/author-page-nodes.js.map +1 -0
  19. package/src/lib/built-in-page-layout.d.ts +48 -0
  20. package/src/lib/built-in-page-layout.js +107 -0
  21. package/src/lib/built-in-page-layout.js.map +1 -0
  22. package/src/lib/capture-host-contact.d.ts +95 -0
  23. package/src/lib/capture-host-contact.js +137 -0
  24. package/src/lib/capture-host-contact.js.map +1 -0
  25. package/src/lib/collection-fallback-nodes.d.ts +90 -0
  26. package/src/lib/collection-fallback-nodes.js +602 -0
  27. package/src/lib/collection-fallback-nodes.js.map +1 -0
  28. package/src/lib/compose-author-page.d.ts +109 -0
  29. package/src/lib/compose-author-page.js +225 -0
  30. package/src/lib/compose-author-page.js.map +1 -0
  31. package/src/lib/compose-collection-page.d.ts +94 -0
  32. package/src/lib/compose-collection-page.js +256 -0
  33. package/src/lib/compose-collection-page.js.map +1 -0
  34. package/src/lib/compose-screen-nodes.d.ts +156 -0
  35. package/src/lib/compose-screen-nodes.js +547 -0
  36. package/src/lib/compose-screen-nodes.js.map +1 -0
  37. package/src/lib/compose-search-page.d.ts +39 -0
  38. package/src/lib/compose-search-page.js +57 -0
  39. package/src/lib/compose-search-page.js.map +1 -0
  40. package/src/lib/convert-host-lead.d.ts +189 -0
  41. package/src/lib/convert-host-lead.js +443 -0
  42. package/src/lib/convert-host-lead.js.map +1 -0
  43. package/src/lib/defer-lazy-panels.d.ts +58 -0
  44. package/src/lib/defer-lazy-panels.js +171 -0
  45. package/src/lib/defer-lazy-panels.js.map +1 -0
  46. package/src/lib/element-animation-assets.d.ts +62 -0
  47. package/src/lib/element-animation-assets.js +290 -0
  48. package/src/lib/element-animation-assets.js.map +1 -0
  49. package/src/lib/emit-host-event.d.ts +29 -0
  50. package/src/lib/emit-host-event.js +30 -0
  51. package/src/lib/emit-host-event.js.map +1 -0
  52. package/src/lib/enrich-gated-page.d.ts +67 -0
  53. package/src/lib/enrich-gated-page.js +134 -0
  54. package/src/lib/enrich-gated-page.js.map +1 -0
  55. package/src/lib/entry-link-routes.d.ts +61 -0
  56. package/src/lib/entry-link-routes.js +169 -0
  57. package/src/lib/entry-link-routes.js.map +1 -0
  58. package/src/lib/get-author-content.d.ts +119 -0
  59. package/src/lib/get-author-content.js +288 -0
  60. package/src/lib/get-author-content.js.map +1 -0
  61. package/src/lib/get-collection-content.d.ts +290 -0
  62. package/src/lib/get-collection-content.js +565 -0
  63. package/src/lib/get-collection-content.js.map +1 -0
  64. package/src/lib/get-components.d.ts +24 -0
  65. package/src/lib/get-components.js +102 -0
  66. package/src/lib/get-components.js.map +1 -0
  67. package/src/lib/get-datasets.d.ts +45 -0
  68. package/src/lib/get-datasets.js +169 -0
  69. package/src/lib/get-datasets.js.map +1 -0
  70. package/src/lib/get-forms.d.ts +24 -0
  71. package/src/lib/get-forms.js +116 -0
  72. package/src/lib/get-forms.js.map +1 -0
  73. package/src/lib/get-layout-version.d.ts +26 -0
  74. package/src/lib/get-layout-version.js +99 -0
  75. package/src/lib/get-layout-version.js.map +1 -0
  76. package/src/lib/get-media-asset-facts.d.ts +74 -0
  77. package/src/lib/get-media-asset-facts.js +116 -0
  78. package/src/lib/get-media-asset-facts.js.map +1 -0
  79. package/src/lib/get-plugin-installs.d.ts +29 -0
  80. package/src/lib/get-plugin-installs.js +94 -0
  81. package/src/lib/get-plugin-installs.js.map +1 -0
  82. package/src/lib/get-screen-version.d.ts +27 -0
  83. package/src/lib/get-screen-version.js +66 -0
  84. package/src/lib/get-screen-version.js.map +1 -0
  85. package/src/lib/get-screen.d.ts +39 -0
  86. package/src/lib/get-screen.js +126 -0
  87. package/src/lib/get-screen.js.map +1 -0
  88. package/src/lib/get-variables.d.ts +41 -0
  89. package/src/lib/get-variables.js +128 -0
  90. package/src/lib/get-variables.js.map +1 -0
  91. package/src/lib/get-video-asset-facts.d.ts +22 -0
  92. package/src/lib/get-video-asset-facts.js +22 -0
  93. package/src/lib/get-video-asset-facts.js.map +1 -0
  94. package/src/lib/host-event-listeners.d.ts +83 -0
  95. package/src/lib/host-event-listeners.js +91 -0
  96. package/src/lib/host-event-listeners.js.map +1 -0
  97. package/src/lib/org-permissions.d.ts +63 -0
  98. package/src/lib/org-permissions.js +187 -0
  99. package/src/lib/org-permissions.js.map +1 -0
  100. package/src/lib/repeat-record-pages.d.ts +48 -0
  101. package/src/lib/repeat-record-pages.js +64 -0
  102. package/src/lib/repeat-record-pages.js.map +1 -0
  103. package/src/lib/required-site-plugins.d.ts +114 -0
  104. package/src/lib/required-site-plugins.js +121 -0
  105. package/src/lib/required-site-plugins.js.map +1 -0
  106. package/src/lib/resolve-dataset.d.ts +40 -0
  107. package/src/lib/resolve-dataset.js +64 -0
  108. package/src/lib/resolve-dataset.js.map +1 -0
  109. package/src/lib/search-results-nodes.d.ts +48 -0
  110. package/src/lib/search-results-nodes.js +302 -0
  111. package/src/lib/search-results-nodes.js.map +1 -0
  112. package/src/lib/social-image-facts.d.ts +107 -0
  113. package/src/lib/social-image-facts.js +133 -0
  114. package/src/lib/social-image-facts.js.map +1 -0
  115. package/src/lib/stamp-form-dataset-bindings.d.ts +32 -0
  116. package/src/lib/stamp-form-dataset-bindings.js +59 -0
  117. package/src/lib/stamp-form-dataset-bindings.js.map +1 -0
  118. package/src/lib/template-screens.d.ts +227 -0
  119. package/src/lib/template-screens.js +364 -0
  120. package/src/lib/template-screens.js.map +1 -0
@@ -0,0 +1,171 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ /**
3
+ * @license
4
+ * Copyright 2026 Aglyn LLC
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */ /**
18
+ * Withholds the node definitions inside lazy tab panels that will not mount on
19
+ * first render (AGL-1285).
20
+ *
21
+ * Lazy mounting (AGL-1283) already stops a Tabs container rendering anything
22
+ * but the selected panel. The document did not follow: every panel's nodes were
23
+ * still serialized into the page. On `/pricing` that is 1,489 of 2,523 nodes —
24
+ * eight copies of a 50-row table — and the RSC payload is 63% of the page's
25
+ * gzipped weight, so the render knowing they are unneeded was worth nothing.
26
+ *
27
+ * This prunes them server-side. The panel node stays (its tab, its aria wiring
28
+ * and its `label` are all still needed); only its subtree is withheld, and the
29
+ * panel is marked so the client can fetch the rest when the reader first opens
30
+ * one.
31
+ *
32
+ * Lazy is the DEFAULT (AGL-1283 option 3), so this prunes every multi-panel
33
+ * Tabs unless the author set `ssrPanels` — the same predicate the client
34
+ * mount uses (`tabs.tsx`), and the two MUST agree: a panel this function
35
+ * withholds that the client would render arrives empty, and a panel it ships
36
+ * that the client defers is payload for nothing. The legacy `lazyPanels`
37
+ * opt-in is subsumed by the default and no longer consulted.
38
+ *
39
+ * WHICH panel survives follows the author's `opensOn` (AGL-3164) by the same
40
+ * rule the element applies, for the same reason: the tab that opens first is
41
+ * the one whose panel has to be in the page source, or the page paints an
42
+ * empty panel and fills it after a round trip.
43
+ *
44
+ * SEO note: withheld content is not in the HTML. Where a panel holds content
45
+ * that appears nowhere else on the page and must reach crawlers, `ssrPanels`
46
+ * is the escape hatch — it keeps the panels in the payload AND in the markup.
47
+ */ /** Marks a panel whose children were withheld. */ export const DEFERRED_PANEL_PROP = 'aglynDeferred';
48
+ const TABS_ID = 'muiTabs';
49
+ const TAB_PANEL_ID = 'muiTabPanel';
50
+ /**
51
+ * Label parsing is duplicated from the mui plugin's `parseLabels` rather than
52
+ * imported: tenant-runtime must not depend on a plugin bundle (module
53
+ * boundaries), and this needs to agree with it exactly or the wrong panel gets
54
+ * pruned. `defer-lazy-panels.spec.ts` pins the cases that matter.
55
+ */ function parseLabels(value) {
56
+ if (value == null) return [];
57
+ return String(value).split(/[\n,]/).map((label)=>label.trim()).filter(Boolean);
58
+ }
59
+ const labelsMatch = (a, b)=>String(a != null ? a : '').trim().toLowerCase() === String(b != null ? b : '').trim().toLowerCase();
60
+ /**
61
+ * The label the strip opens on, which is NOT always the first one.
62
+ *
63
+ * Two rules, and they must be the mui plugin's `openingTabIndex` exactly —
64
+ * this is the function that decides whose nodes SURVIVE into the page, so a
65
+ * disagreement does not merely paint the wrong panel first, it ships the
66
+ * opening panel empty:
67
+ *
68
+ * 1. the tab the author named in `opensOn` (AGL-3164), refused when that tab
69
+ * carries a screen link, because a linked tab navigates instead of
70
+ * revealing a panel;
71
+ * 2. otherwise the first tab WITHOUT a link (AGL-1312 — a navigation row is
72
+ * placed on each screen it names, and there the tab for that screen is
73
+ * the unlinked one).
74
+ *
75
+ * Every tab linked means no panel is really open, and the client falls back
76
+ * to the first label; this must too, or it withholds the one panel the
77
+ * reader is looking at.
78
+ *
79
+ * Duplicated from the mui plugin's prop names for the same reason
80
+ * `parseLabels` is: tenant-runtime must not depend on a plugin bundle.
81
+ */ function openingLabel(props) {
82
+ const labels = parseLabels(props == null ? void 0 : props.labels);
83
+ const linked = (position)=>!!(props == null ? void 0 : props[`tabLink${position + 1}`]);
84
+ const named = labels.findIndex((label, position)=>!linked(position) && labelsMatch(label, props == null ? void 0 : props.opensOn));
85
+ if (named >= 0) return labels[named];
86
+ const landing = labels.findIndex((_label, position)=>!linked(position));
87
+ return labels[landing < 0 ? 0 : landing];
88
+ }
89
+ /** Every descendant id of `id`, excluding `id` itself. */ function descendantsOf(nodes, id) {
90
+ var _ref;
91
+ var _nodes_id;
92
+ const out = [];
93
+ const stack = [
94
+ ...(_ref = (_nodes_id = nodes[id]) == null ? void 0 : _nodes_id.nodes) != null ? _ref : []
95
+ ];
96
+ while(stack.length){
97
+ var _nodes_next_nodes;
98
+ const next = stack.pop();
99
+ if (typeof next !== 'string' || !nodes[next]) continue;
100
+ out.push(next);
101
+ stack.push(...(_nodes_next_nodes = nodes[next].nodes) != null ? _nodes_next_nodes : []);
102
+ }
103
+ return out;
104
+ }
105
+ export function deferLazyPanelNodes(nodes) {
106
+ if (!nodes) return {
107
+ nodes: nodes,
108
+ deferredPanelIds: [],
109
+ removed: 0
110
+ };
111
+ const drop = new Set();
112
+ const deferredPanelIds = [];
113
+ for (const [id, node] of Object.entries(nodes)){
114
+ var _node_nodes;
115
+ var _node_props;
116
+ if ((node == null ? void 0 : node.componentId) !== TABS_ID) continue;
117
+ // Defer by default; `ssrPanels` is the author's SEO escape hatch
118
+ // (AGL-1283 option 3). Must match the client-side mount predicate.
119
+ if (node == null ? void 0 : (_node_props = node.props) == null ? void 0 : _node_props.ssrPanels) continue;
120
+ const labels = parseLabels(node.props.labels);
121
+ const panelIds = ((_node_nodes = node.nodes) != null ? _node_nodes : []).filter((childId)=>{
122
+ var _nodes_childId;
123
+ return ((_nodes_childId = nodes[childId]) == null ? void 0 : _nodes_childId.componentId) === TAB_PANEL_ID;
124
+ });
125
+ if (!labels.length || panelIds.length < 2) continue;
126
+ // The kept panel is the one matching the OPENING label, not the first
127
+ // child: panels can be reordered in the hierarchy independently of the
128
+ // label list, and pruning the panel that is actually open would leave the
129
+ // reader looking at an empty tab. If no panel matches, defer NOTHING —
130
+ // a mislabelled set is exactly when guessing is most expensive.
131
+ const opening = panelIds.find((panelId)=>{
132
+ var _nodes_panelId_props, _nodes_panelId;
133
+ return labelsMatch((_nodes_panelId = nodes[panelId]) == null ? void 0 : (_nodes_panelId_props = _nodes_panelId.props) == null ? void 0 : _nodes_panelId_props.label, openingLabel(node.props));
134
+ });
135
+ if (!opening) continue;
136
+ for (const panelId of panelIds){
137
+ if (panelId === opening) continue;
138
+ const kids = descendantsOf(nodes, panelId);
139
+ if (!kids.length) continue;
140
+ kids.forEach((kid)=>drop.add(kid));
141
+ deferredPanelIds.push(panelId);
142
+ }
143
+ }
144
+ if (!drop.size) return {
145
+ nodes,
146
+ deferredPanelIds: [],
147
+ removed: 0
148
+ };
149
+ // Rebuild rather than mutate: the composed document is CACHED
150
+ // (`loadPageDataCached`), so mutating it would poison every later request
151
+ // for this screen with a permanently half-empty page.
152
+ const deferred = new Set(deferredPanelIds);
153
+ const out = {};
154
+ for (const [id, node] of Object.entries(nodes)){
155
+ var _node_props1;
156
+ if (drop.has(id)) continue;
157
+ out[id] = deferred.has(id) ? _extends({}, node, {
158
+ nodes: [],
159
+ props: _extends({}, (_node_props1 = node.props) != null ? _node_props1 : {}, {
160
+ [DEFERRED_PANEL_PROP]: true
161
+ })
162
+ }) : node;
163
+ }
164
+ return {
165
+ nodes: out,
166
+ deferredPanelIds,
167
+ removed: drop.size
168
+ };
169
+ }
170
+
171
+ //# sourceMappingURL=defer-lazy-panels.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/tenant/runtime/src/lib/defer-lazy-panels.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 */\n\n/**\n * Withholds the node definitions inside lazy tab panels that will not mount on\n * first render (AGL-1285).\n *\n * Lazy mounting (AGL-1283) already stops a Tabs container rendering anything\n * but the selected panel. The document did not follow: every panel's nodes were\n * still serialized into the page. On `/pricing` that is 1,489 of 2,523 nodes —\n * eight copies of a 50-row table — and the RSC payload is 63% of the page's\n * gzipped weight, so the render knowing they are unneeded was worth nothing.\n *\n * This prunes them server-side. The panel node stays (its tab, its aria wiring\n * and its `label` are all still needed); only its subtree is withheld, and the\n * panel is marked so the client can fetch the rest when the reader first opens\n * one.\n *\n * Lazy is the DEFAULT (AGL-1283 option 3), so this prunes every multi-panel\n * Tabs unless the author set `ssrPanels` — the same predicate the client\n * mount uses (`tabs.tsx`), and the two MUST agree: a panel this function\n * withholds that the client would render arrives empty, and a panel it ships\n * that the client defers is payload for nothing. The legacy `lazyPanels`\n * opt-in is subsumed by the default and no longer consulted.\n *\n * WHICH panel survives follows the author's `opensOn` (AGL-3164) by the same\n * rule the element applies, for the same reason: the tab that opens first is\n * the one whose panel has to be in the page source, or the page paints an\n * empty panel and fills it after a round trip.\n *\n * SEO note: withheld content is not in the HTML. Where a panel holds content\n * that appears nowhere else on the page and must reach crawlers, `ssrPanels`\n * is the escape hatch — it keeps the panels in the payload AND in the markup.\n */\n\n/** Marks a panel whose children were withheld. */\nexport const DEFERRED_PANEL_PROP = 'aglynDeferred'\n\nconst TABS_ID = 'muiTabs'\nconst TAB_PANEL_ID = 'muiTabPanel'\n\nexport interface DeferLazyPanelsResult {\n /** A NEW node map; the input is never mutated. */\n nodes: Record<string, any>\n /** Panel node ids whose children were withheld. */\n deferredPanelIds: string[]\n /** Node definitions removed. */\n removed: number\n}\n\n/**\n * Label parsing is duplicated from the mui plugin's `parseLabels` rather than\n * imported: tenant-runtime must not depend on a plugin bundle (module\n * boundaries), and this needs to agree with it exactly or the wrong panel gets\n * pruned. `defer-lazy-panels.spec.ts` pins the cases that matter.\n */\nfunction parseLabels(value: unknown): string[] {\n if (value == null) return []\n return String(value)\n .split(/[\\n,]/)\n .map((label) => label.trim())\n .filter(Boolean)\n}\n\nconst labelsMatch = (a: unknown, b: unknown): boolean =>\n String(a ?? '')\n .trim()\n .toLowerCase() ===\n String(b ?? '')\n .trim()\n .toLowerCase()\n\n/**\n * The label the strip opens on, which is NOT always the first one.\n *\n * Two rules, and they must be the mui plugin's `openingTabIndex` exactly —\n * this is the function that decides whose nodes SURVIVE into the page, so a\n * disagreement does not merely paint the wrong panel first, it ships the\n * opening panel empty:\n *\n * 1. the tab the author named in `opensOn` (AGL-3164), refused when that tab\n * carries a screen link, because a linked tab navigates instead of\n * revealing a panel;\n * 2. otherwise the first tab WITHOUT a link (AGL-1312 — a navigation row is\n * placed on each screen it names, and there the tab for that screen is\n * the unlinked one).\n *\n * Every tab linked means no panel is really open, and the client falls back\n * to the first label; this must too, or it withholds the one panel the\n * reader is looking at.\n *\n * Duplicated from the mui plugin's prop names for the same reason\n * `parseLabels` is: tenant-runtime must not depend on a plugin bundle.\n */\nfunction openingLabel(props: Record<string, any>): string | undefined {\n const labels = parseLabels(props?.labels)\n const linked = (position: number): boolean =>\n !!props?.[`tabLink${position + 1}`]\n const named = labels.findIndex(\n (label, position) =>\n !linked(position) && labelsMatch(label, props?.opensOn),\n )\n if (named >= 0) return labels[named]\n const landing = labels.findIndex((_label, position) => !linked(position))\n return labels[landing < 0 ? 0 : landing]\n}\n\n/** Every descendant id of `id`, excluding `id` itself. */\nfunction descendantsOf(nodes: Record<string, any>, id: string): string[] {\n const out: string[] = []\n const stack = [...(nodes[id]?.nodes ?? [])]\n while (stack.length) {\n const next = stack.pop()\n if (typeof next !== 'string' || !nodes[next]) continue\n out.push(next)\n stack.push(...(nodes[next].nodes ?? []))\n }\n return out\n}\n\nexport function deferLazyPanelNodes(\n nodes: Record<string, any> | null | undefined,\n): DeferLazyPanelsResult {\n if (!nodes) return { nodes: nodes as any, deferredPanelIds: [], removed: 0 }\n\n const drop = new Set<string>()\n const deferredPanelIds: string[] = []\n\n for (const [id, node] of Object.entries(nodes)) {\n if (node?.componentId !== TABS_ID) continue\n // Defer by default; `ssrPanels` is the author's SEO escape hatch\n // (AGL-1283 option 3). Must match the client-side mount predicate.\n if (node?.props?.ssrPanels) continue\n\n const labels = parseLabels(node.props.labels)\n const panelIds: string[] = (node.nodes ?? []).filter(\n (childId: string) => nodes[childId]?.componentId === TAB_PANEL_ID,\n )\n if (!labels.length || panelIds.length < 2) continue\n\n // The kept panel is the one matching the OPENING label, not the first\n // child: panels can be reordered in the hierarchy independently of the\n // label list, and pruning the panel that is actually open would leave the\n // reader looking at an empty tab. If no panel matches, defer NOTHING —\n // a mislabelled set is exactly when guessing is most expensive.\n const opening = panelIds.find((panelId) =>\n labelsMatch(nodes[panelId]?.props?.label, openingLabel(node.props)),\n )\n if (!opening) continue\n\n for (const panelId of panelIds) {\n if (panelId === opening) continue\n const kids = descendantsOf(nodes, panelId)\n if (!kids.length) continue\n kids.forEach((kid) => drop.add(kid))\n deferredPanelIds.push(panelId)\n }\n }\n\n if (!drop.size) return { nodes, deferredPanelIds: [], removed: 0 }\n\n // Rebuild rather than mutate: the composed document is CACHED\n // (`loadPageDataCached`), so mutating it would poison every later request\n // for this screen with a permanently half-empty page.\n const deferred = new Set(deferredPanelIds)\n const out: Record<string, any> = {}\n for (const [id, node] of Object.entries(nodes)) {\n if (drop.has(id)) continue\n out[id] = deferred.has(id)\n ? {\n ...node,\n nodes: [],\n props: { ...(node.props ?? {}), [DEFERRED_PANEL_PROP]: true },\n }\n : node\n }\n return { nodes: out, deferredPanelIds, removed: drop.size }\n}\n"],"names":["DEFERRED_PANEL_PROP","TABS_ID","TAB_PANEL_ID","parseLabels","value","String","split","map","label","trim","filter","Boolean","labelsMatch","a","b","toLowerCase","openingLabel","props","labels","linked","position","named","findIndex","opensOn","landing","_label","descendantsOf","nodes","id","out","stack","length","next","pop","push","deferLazyPanelNodes","deferredPanelIds","removed","drop","Set","node","Object","entries","componentId","ssrPanels","panelIds","childId","opening","find","panelId","kids","forEach","kid","add","size","deferred","has"],"mappings":"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"}
@@ -0,0 +1,62 @@
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
+ /**
18
+ * The stylesheet. Built once at module load, not per request.
19
+ *
20
+ * The keyframe name is threaded through a custom property so the trigger rules
21
+ * are two selectors rather than two per preset — the sheet stays flat as
22
+ * presets are added.
23
+ */
24
+ export declare const ELEMENT_ANIMATION_STYLE_TEXT: string;
25
+ /** Id of the injected style tag (idempotence marker, mirrors AGL-562). */
26
+ export declare const ELEMENT_ANIMATION_STYLE_ID = "aglyn-element-animation-style";
27
+ /**
28
+ * The scroll runtime, inlined verbatim into a `<script>`.
29
+ *
30
+ * One `IntersectionObserver` for the whole document. Re-scanning is safe and
31
+ * cheap because `observe()` on an already-observed element is a no-op, which
32
+ * is how late-arriving nodes (a deferred lazy tab panel the reader opens, a
33
+ * collection page appending rows) get picked up without a per-element
34
+ * observer. The `MutationObserver` is what makes that correct rather than
35
+ * best-effort: an element inserted after load would otherwise stay at
36
+ * `opacity:0` forever, because the hide rule applies the moment the ready
37
+ * class is on `<html>`.
38
+ *
39
+ * The selector is the trigger ATTRIBUTE alone rather than the base class,
40
+ * because there are now two base classes — an element that animates itself
41
+ * and a stagger host whose children animate. Both carry the attribute, only
42
+ * the renderer ever writes it, and matching on it keeps the runtime unaware
43
+ * that the second shape exists.
44
+ *
45
+ * Written in ES5 with short locals because it ships as source; there is no
46
+ * minifier in this path.
47
+ */
48
+ export declare const ELEMENT_ANIMATION_SCRIPT_TEXT: string;
49
+ export interface PageAnimationAssets {
50
+ styleText: string;
51
+ /** Present only when the page carries a scroll-triggered element. */
52
+ scriptText: string | null;
53
+ }
54
+ /**
55
+ * Decides what a page must ship, from the flat node map the route already
56
+ * holds. Returns `null` — the common case — when nothing on the page animates,
57
+ * and the route then renders neither tag.
58
+ *
59
+ * The scroll runtime is withheld separately: a page whose only animations play
60
+ * on load or on hover is pure CSS and ships no script either.
61
+ */
62
+ export declare function pageAnimationAssets(nodes: Record<string, any> | null | undefined): PageAnimationAssets | null;
@@ -0,0 +1,290 @@
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
+ * The published page's animation stylesheet and scroll runtime (AGL-2486).
18
+ *
19
+ * ## Why this is a server-only module
20
+ *
21
+ * `apps/tenant`'s route is a SERVER component and imports this by subpath, so
22
+ * neither the CSS nor the script text ever enters the client bundle. That is
23
+ * the whole point: a page that animates nothing must pay nothing, and here it
24
+ * pays literally zero bytes — `pageAnimationAssets` returns `null` and the
25
+ * route renders no `<style>` and no `<script>` at all.
26
+ *
27
+ * ## Why the runtime is inline rather than a chunk
28
+ *
29
+ * The only thing that needs JS is the scroll trigger, and it needs one shared
30
+ * `IntersectionObserver` for the whole document — not one per element. That is
31
+ * a few hundred bytes. As a module it would cost a chunk, a request, a
32
+ * parse and a hydration tick; inline in the SSR HTML it costs bytes that gzip
33
+ * with the document and runs before hydration. `apps/tenant` sets NO
34
+ * `script-src` in either CSP header (deliberate — see `middleware.ts`, and
35
+ * `specs/csp-no-script-src.spec.ts` holds it there), so an inline script needs
36
+ * no nonce.
37
+ *
38
+ * ## prefers-reduced-motion
39
+ *
40
+ * EVERY rule below — the keyframes, the transitions, and critically the rule
41
+ * that hides a scroll-triggered element before it plays — lives inside a
42
+ * single `@media (prefers-reduced-motion: no-preference)` block. A visitor who
43
+ * asked their OS to reduce motion therefore gets a page where nothing is
44
+ * hidden and nothing moves, and the switch is live: because the gate is CSS
45
+ * rather than a JS branch, toggling the OS setting after load resolves
46
+ * correctly in BOTH directions. The runtime deliberately does NOT test
47
+ * `matchMedia` itself — an early return there would leave elements hidden by a
48
+ * rule that had just started applying again.
49
+ *
50
+ * ## No layout shift
51
+ *
52
+ * Every keyframe animates `opacity` and `transform` only. Neither affects
53
+ * layout, so an entrance animation contributes nothing to CLS — the element
54
+ * occupies its final box from the first frame.
55
+ *
56
+ * ## Stagger costs no extra JS and no extra observer entry
57
+ *
58
+ * A staggered row is ONE element as far as this file's runtime is concerned:
59
+ * the host is what the observer watches, and the children play off the class
60
+ * the observer puts on it. The per-child offset is a `:nth-child` ladder in
61
+ * the sheet, so twelve cards arriving one after another cost twelve CSS
62
+ * declarations and zero JS — where a runtime would have cost twelve
63
+ * observer entries, twelve callbacks and twelve style writes on the main
64
+ * thread, during scroll.
65
+ */ import { ANIMATION_CLASS, ANIMATION_DELAY_VAR, ANIMATION_DURATION_VAR, ANIMATION_EASE_CLASS_PREFIX, ANIMATION_EASINGS, ANIMATION_GROUP_CLASS, ANIMATION_IN_CLASS, ANIMATION_PRESET_CLASS_PREFIX, ANIMATION_READY_CLASS, ANIMATION_REPEAT_ATTR, ANIMATION_STAGGER_MAX_CHILDREN, ANIMATION_STAGGER_OFFSET_VAR, ANIMATION_STAGGER_STEP_VAR, ANIMATION_TRIGGER_ATTR, NODE_ANIMATION_TRIGGER_PROP, nodePropsAnimate } from "@aglyn/aglyn/server";
66
+ /**
67
+ * The easing curves, keyed by the ids `@aglyn/aglyn` publishes.
68
+ *
69
+ * The ids live in the shared module and the CURVES live here, so a page that
70
+ * animates nothing carries neither. Every one of these is a plain
71
+ * `cubic-bezier`; none is a spring, because a spring is not expressible in
72
+ * CSS and buying one would mean buying a JS runtime.
73
+ *
74
+ * `overshoot` is the only curve that leaves the 0–1 range, and it does so on
75
+ * the way IN only (`.34,1.56,.64,1` is the standard "back out"), so an
76
+ * element settles rather than oscillating. Like every other rule here it sits
77
+ * inside the reduced-motion gate.
78
+ *
79
+ * Typed as a total record so adding an id to `ANIMATION_EASINGS` without a
80
+ * curve is a compile error rather than an element with no easing.
81
+ */ const EASE_CURVES = {
82
+ smooth: 'cubic-bezier(.16,1,.3,1)',
83
+ steady: 'linear',
84
+ 'gentle-start': 'cubic-bezier(.4,0,1,1)',
85
+ 'gentle-end': 'cubic-bezier(0,0,.2,1)',
86
+ 'gentle-both': 'cubic-bezier(.4,0,.2,1)',
87
+ overshoot: 'cubic-bezier(.34,1.56,.64,1)'
88
+ };
89
+ /**
90
+ * The curve an element gets when it carries no easing class at all — which is
91
+ * every element authored before easing shipped. Deliberately the same string
92
+ * `EASE` used to be, so nothing that already exists moves.
93
+ */ const EASE = EASE_CURVES.smooth;
94
+ /**
95
+ * Per-preset custom properties. One rule per preset sets the keyframe name
96
+ * used by the load/scroll triggers AND the transform the hover trigger eases
97
+ * to, which is why the preset table is a single list rather than three.
98
+ *
99
+ * Hover is a TRANSITION to an emphasis state, not a keyframe run: "Slide up"
100
+ * on hover lifts the element, "Zoom in" grows it. That is what an author means
101
+ * when they pick a direction for a hover effect.
102
+ */ const PRESETS = [
103
+ {
104
+ id: 'fade',
105
+ from: 'opacity:0',
106
+ hover: 'none',
107
+ hoverOpacity: '.7'
108
+ },
109
+ {
110
+ id: 'slide-up',
111
+ from: 'opacity:0;transform:translate3d(0,24px,0)',
112
+ hover: 'translate3d(0,-8px,0)'
113
+ },
114
+ {
115
+ id: 'slide-down',
116
+ from: 'opacity:0;transform:translate3d(0,-24px,0)',
117
+ hover: 'translate3d(0,8px,0)'
118
+ },
119
+ {
120
+ id: 'slide-left',
121
+ from: 'opacity:0;transform:translate3d(24px,0,0)',
122
+ hover: 'translate3d(-8px,0,0)'
123
+ },
124
+ {
125
+ id: 'slide-right',
126
+ from: 'opacity:0;transform:translate3d(-24px,0,0)',
127
+ hover: 'translate3d(8px,0,0)'
128
+ },
129
+ {
130
+ id: 'zoom-in',
131
+ from: 'opacity:0;transform:scale3d(.92,.92,1)',
132
+ hover: 'scale3d(1.04,1.04,1)'
133
+ },
134
+ {
135
+ id: 'zoom-out',
136
+ from: 'opacity:0;transform:scale3d(1.08,1.08,1)',
137
+ hover: 'scale3d(.96,.96,1)'
138
+ }
139
+ ];
140
+ const NAME_VAR = '--aglyn-anim-name';
141
+ const HOVER_VAR = '--aglyn-anim-hover';
142
+ const HOVER_OPACITY_VAR = '--aglyn-anim-hover-opacity';
143
+ /**
144
+ * Set by the easing class rules and read by the timing rules. Not exported:
145
+ * the renderer writes a CLASS, never this property, so an author cannot get a
146
+ * raw curve of their own into the page through it.
147
+ */ const EASE_VAR = '--aglyn-anim-ease';
148
+ const ANIMATED = `.${ANIMATION_CLASS}`;
149
+ const GROUP = `.${ANIMATION_GROUP_CLASS}`;
150
+ /**
151
+ * Everything the timing rules apply to: a plain animated element, and the
152
+ * CHILDREN of a stagger host. The host itself is deliberately absent — it
153
+ * sets the custom properties its children inherit and animates nothing.
154
+ */ const TARGETS = `${ANIMATED},${GROUP}>*`;
155
+ const DURATION = `var(${ANIMATION_DURATION_VAR},600ms)`;
156
+ const DELAY = `var(${ANIMATION_DELAY_VAR},0ms)`;
157
+ const STEP = `var(${ANIMATION_STAGGER_STEP_VAR},90ms)`;
158
+ const TIMING = `var(${EASE_VAR},${EASE})`;
159
+ /**
160
+ * The author's own delay plus whatever rung of the stagger ladder this child
161
+ * landed on. A plain animated element never matches a ladder rule, so its
162
+ * offset falls back to `0ms` and the sum is exactly the delay it had before
163
+ * stagger existed.
164
+ */ const TOTAL_DELAY = `calc(${DELAY} + var(${ANIMATION_STAGGER_OFFSET_VAR},0ms))`;
165
+ /**
166
+ * The stagger ladder: one rule per rung, each pushing a child one more STEP
167
+ * behind the one before it.
168
+ *
169
+ * The index has to come from CSS rather than the renderer. The renderer sees
170
+ * a node, not a position — and a node that knew its index would have to
171
+ * re-render every time a sibling was inserted, which is exactly what an
172
+ * author does while building a row of cards.
173
+ *
174
+ * The first child gets NO rule: `var(--aglyn-anim-stagger,0ms)` already falls
175
+ * back to zero, so emitting `calc(STEP * 0)` would be a rule that changes
176
+ * nothing. The last rung is `:nth-child(n+N)` rather than an Nth rule, so a
177
+ * collection with two hundred rows shares the final rung instead of leaving
178
+ * its tail invisible for half a minute.
179
+ */ const STAGGER_LADDER = [
180
+ // Reset first, and it must STAY first. A nested stagger host's own ladder
181
+ // rule has identical specificity (one class + one pseudo-class against two
182
+ // classes), so source order is the only thing that lets the inner host win
183
+ // for its own children. What this catches is the other case: an animated
184
+ // element deeper inside a staggered child, which would otherwise INHERIT
185
+ // its ancestor's rung and start late for no reason an author could see.
186
+ `${GROUP}>* ${ANIMATED}{${ANIMATION_STAGGER_OFFSET_VAR}:0ms}`,
187
+ ...Array.from({
188
+ length: ANIMATION_STAGGER_MAX_CHILDREN - 2
189
+ }, (_unused, index)=>{
190
+ const nth = index + 2;
191
+ return `${GROUP}>*:nth-child(${nth}){${ANIMATION_STAGGER_OFFSET_VAR}:calc(${STEP} * ${nth - 1})}`;
192
+ }),
193
+ `${GROUP}>*:nth-child(n+${ANIMATION_STAGGER_MAX_CHILDREN}){${ANIMATION_STAGGER_OFFSET_VAR}:calc(${STEP} * ${ANIMATION_STAGGER_MAX_CHILDREN - 1})}`
194
+ ];
195
+ /**
196
+ * The stylesheet. Built once at module load, not per request.
197
+ *
198
+ * The keyframe name is threaded through a custom property so the trigger rules
199
+ * are two selectors rather than two per preset — the sheet stays flat as
200
+ * presets are added.
201
+ */ export const ELEMENT_ANIMATION_STYLE_TEXT = [
202
+ '@media (prefers-reduced-motion:no-preference){',
203
+ // Keyframes.
204
+ ...PRESETS.map((preset)=>`@keyframes aglyn-anim-${preset.id}{from{${preset.from}}to{opacity:1;transform:none}}`),
205
+ // Per-preset custom properties. Keyed on the preset class ALONE, not on the
206
+ // base class as well: a stagger host carries `aglyn-anim-group` rather than
207
+ // `aglyn-anim`, and its children read these through inheritance, so a rule
208
+ // that required the base class would leave a whole staggered row with no
209
+ // keyframe name.
210
+ ...PRESETS.map((preset)=>`.${ANIMATION_PRESET_CLASS_PREFIX}${preset.id}{${NAME_VAR}:aglyn-anim-${preset.id};${HOVER_VAR}:${preset.hover}${preset.hoverOpacity ? `;${HOVER_OPACITY_VAR}:${preset.hoverOpacity}` : ''}}`),
211
+ // Per-easing custom property, same shape and the same inheritance.
212
+ ...ANIMATION_EASINGS.map((ease)=>`.${ANIMATION_EASE_CLASS_PREFIX}${ease}{${EASE_VAR}:${EASE_CURVES[ease]}}`),
213
+ // Shared timing for the keyframe triggers.
214
+ `${TARGETS}{animation-duration:${DURATION};animation-delay:${TOTAL_DELAY};animation-timing-function:${TIMING};animation-fill-mode:both}`,
215
+ // The stagger ladder — no-ops for every element that is not inside a host.
216
+ ...STAGGER_LADDER,
217
+ // On load: plays as soon as the element is parsed. No JS involved at all.
218
+ `${ANIMATED}[${ANIMATION_TRIGGER_ATTR}=load],${GROUP}[${ANIMATION_TRIGGER_ATTR}=load]>*{animation-name:var(${NAME_VAR})}`,
219
+ // On scroll: plays when the runtime marks it as entered. For a stagger host
220
+ // the ONE observed element is the host, and its children play off its class
221
+ // — one observer entry for a whole row, not one per card.
222
+ `${ANIMATED}[${ANIMATION_TRIGGER_ATTR}=scroll].${ANIMATION_IN_CLASS},${GROUP}[${ANIMATION_TRIGGER_ATTR}=scroll].${ANIMATION_IN_CLASS}>*{animation-name:var(${NAME_VAR})}`,
223
+ // The ONLY rule that hides anything, and it is scoped under a class the
224
+ // inline runtime adds to <html>. No JS (or no IntersectionObserver) means
225
+ // the class is never added, the rule never matches, and every element is
226
+ // visible in its final state. `opacity` rather than `display`/`visibility`
227
+ // so the content stays in the accessibility tree and in the DOM a crawler
228
+ // reads either way.
229
+ `html.${ANIMATION_READY_CLASS} ${ANIMATED}[${ANIMATION_TRIGGER_ATTR}=scroll]:not(.${ANIMATION_IN_CLASS}),html.${ANIMATION_READY_CLASS} ${GROUP}[${ANIMATION_TRIGGER_ATTR}=scroll]:not(.${ANIMATION_IN_CLASS})>*{opacity:0}`,
230
+ // On hover: a transition, never a keyframe run, so it reverses cleanly when
231
+ // the pointer leaves. Staggering is refused for hover upstream, so this rule
232
+ // reads the author's plain delay rather than the ladder's sum — a
233
+ // transition has no `animation-delay` to add a rung to.
234
+ `${ANIMATED}[${ANIMATION_TRIGGER_ATTR}=hover]{transition:transform ${DURATION} ${TIMING} ${DELAY},opacity ${DURATION} ${TIMING} ${DELAY}}`,
235
+ `${ANIMATED}[${ANIMATION_TRIGGER_ATTR}=hover]:hover{transform:var(${HOVER_VAR},none);opacity:var(${HOVER_OPACITY_VAR},1)}`,
236
+ '}'
237
+ ].join('');
238
+ /** Id of the injected style tag (idempotence marker, mirrors AGL-562). */ export const ELEMENT_ANIMATION_STYLE_ID = 'aglyn-element-animation-style';
239
+ /**
240
+ * The scroll runtime, inlined verbatim into a `<script>`.
241
+ *
242
+ * One `IntersectionObserver` for the whole document. Re-scanning is safe and
243
+ * cheap because `observe()` on an already-observed element is a no-op, which
244
+ * is how late-arriving nodes (a deferred lazy tab panel the reader opens, a
245
+ * collection page appending rows) get picked up without a per-element
246
+ * observer. The `MutationObserver` is what makes that correct rather than
247
+ * best-effort: an element inserted after load would otherwise stay at
248
+ * `opacity:0` forever, because the hide rule applies the moment the ready
249
+ * class is on `<html>`.
250
+ *
251
+ * The selector is the trigger ATTRIBUTE alone rather than the base class,
252
+ * because there are now two base classes — an element that animates itself
253
+ * and a stagger host whose children animate. Both carry the attribute, only
254
+ * the renderer ever writes it, and matching on it keeps the runtime unaware
255
+ * that the second shape exists.
256
+ *
257
+ * Written in ES5 with short locals because it ships as source; there is no
258
+ * minifier in this path.
259
+ */ export const ELEMENT_ANIMATION_SCRIPT_TEXT = `(function(){var w=window,d=document;if(!w.IntersectionObserver||!w.MutationObserver)return;d.documentElement.classList.add(${JSON.stringify(ANIMATION_READY_CLASS)});var S=${JSON.stringify(`[${ANIMATION_TRIGGER_ATTR}=scroll]`)},I=${JSON.stringify(ANIMATION_IN_CLASS)},R=${JSON.stringify(ANIMATION_REPEAT_ATTR)};var o=new w.IntersectionObserver(function(es){for(var i=0;i<es.length;i++){var e=es[i],t=e.target,r=t.getAttribute(R)==='1';if(e.isIntersecting){t.classList.add(I);if(!r)o.unobserve(t)}else if(r)t.classList.remove(I)}},{rootMargin:'0px 0px -10% 0px'});var q=0,scan=function(){q=0;var n=d.querySelectorAll(S);for(var i=0;i<n.length;i++)o.observe(n[i])};scan();if(d.readyState==='loading')d.addEventListener('DOMContentLoaded',scan);var m=new w.MutationObserver(function(){if(q)return;q=w.requestAnimationFrame?w.requestAnimationFrame(scan):w.setTimeout(scan,0)});var start=function(){if(d.body)m.observe(d.body,{childList:true,subtree:true})};if(d.body)start();else d.addEventListener('DOMContentLoaded',start)})()`;
260
+ /**
261
+ * Decides what a page must ship, from the flat node map the route already
262
+ * holds. Returns `null` — the common case — when nothing on the page animates,
263
+ * and the route then renders neither tag.
264
+ *
265
+ * The scroll runtime is withheld separately: a page whose only animations play
266
+ * on load or on hover is pure CSS and ships no script either.
267
+ */ export function pageAnimationAssets(nodes) {
268
+ if (!nodes) return null;
269
+ let animates = false;
270
+ let needsScroll = false;
271
+ for(const id in nodes){
272
+ var _nodes_id;
273
+ const props = (_nodes_id = nodes[id]) == null ? void 0 : _nodes_id.props;
274
+ if (!nodePropsAnimate(props)) continue;
275
+ animates = true;
276
+ // `scroll` is the default trigger, so an unset trigger needs the runtime.
277
+ const trigger = props == null ? void 0 : props[NODE_ANIMATION_TRIGGER_PROP];
278
+ if (trigger === undefined || trigger === null || trigger === 'scroll') {
279
+ needsScroll = true;
280
+ break;
281
+ }
282
+ }
283
+ if (!animates) return null;
284
+ return {
285
+ styleText: ELEMENT_ANIMATION_STYLE_TEXT,
286
+ scriptText: needsScroll ? ELEMENT_ANIMATION_SCRIPT_TEXT : null
287
+ };
288
+ }
289
+
290
+ //# sourceMappingURL=element-animation-assets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/tenant/runtime/src/lib/element-animation-assets.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 */\n\n/**\n * The published page's animation stylesheet and scroll runtime (AGL-2486).\n *\n * ## Why this is a server-only module\n *\n * `apps/tenant`'s route is a SERVER component and imports this by subpath, so\n * neither the CSS nor the script text ever enters the client bundle. That is\n * the whole point: a page that animates nothing must pay nothing, and here it\n * pays literally zero bytes — `pageAnimationAssets` returns `null` and the\n * route renders no `<style>` and no `<script>` at all.\n *\n * ## Why the runtime is inline rather than a chunk\n *\n * The only thing that needs JS is the scroll trigger, and it needs one shared\n * `IntersectionObserver` for the whole document — not one per element. That is\n * a few hundred bytes. As a module it would cost a chunk, a request, a\n * parse and a hydration tick; inline in the SSR HTML it costs bytes that gzip\n * with the document and runs before hydration. `apps/tenant` sets NO\n * `script-src` in either CSP header (deliberate — see `middleware.ts`, and\n * `specs/csp-no-script-src.spec.ts` holds it there), so an inline script needs\n * no nonce.\n *\n * ## prefers-reduced-motion\n *\n * EVERY rule below — the keyframes, the transitions, and critically the rule\n * that hides a scroll-triggered element before it plays — lives inside a\n * single `@media (prefers-reduced-motion: no-preference)` block. A visitor who\n * asked their OS to reduce motion therefore gets a page where nothing is\n * hidden and nothing moves, and the switch is live: because the gate is CSS\n * rather than a JS branch, toggling the OS setting after load resolves\n * correctly in BOTH directions. The runtime deliberately does NOT test\n * `matchMedia` itself — an early return there would leave elements hidden by a\n * rule that had just started applying again.\n *\n * ## No layout shift\n *\n * Every keyframe animates `opacity` and `transform` only. Neither affects\n * layout, so an entrance animation contributes nothing to CLS — the element\n * occupies its final box from the first frame.\n *\n * ## Stagger costs no extra JS and no extra observer entry\n *\n * A staggered row is ONE element as far as this file's runtime is concerned:\n * the host is what the observer watches, and the children play off the class\n * the observer puts on it. The per-child offset is a `:nth-child` ladder in\n * the sheet, so twelve cards arriving one after another cost twelve CSS\n * declarations and zero JS — where a runtime would have cost twelve\n * observer entries, twelve callbacks and twelve style writes on the main\n * thread, during scroll.\n */\n\nimport {\n ANIMATION_CLASS,\n ANIMATION_DELAY_VAR,\n ANIMATION_DURATION_VAR,\n ANIMATION_EASE_CLASS_PREFIX,\n ANIMATION_EASINGS,\n ANIMATION_GROUP_CLASS,\n ANIMATION_IN_CLASS,\n ANIMATION_PRESET_CLASS_PREFIX,\n ANIMATION_READY_CLASS,\n ANIMATION_REPEAT_ATTR,\n ANIMATION_STAGGER_MAX_CHILDREN,\n ANIMATION_STAGGER_OFFSET_VAR,\n ANIMATION_STAGGER_STEP_VAR,\n ANIMATION_TRIGGER_ATTR,\n NODE_ANIMATION_TRIGGER_PROP,\n nodePropsAnimate,\n type AnimationEase,\n} from '@aglyn/aglyn/server'\n\n/**\n * The easing curves, keyed by the ids `@aglyn/aglyn` publishes.\n *\n * The ids live in the shared module and the CURVES live here, so a page that\n * animates nothing carries neither. Every one of these is a plain\n * `cubic-bezier`; none is a spring, because a spring is not expressible in\n * CSS and buying one would mean buying a JS runtime.\n *\n * `overshoot` is the only curve that leaves the 0–1 range, and it does so on\n * the way IN only (`.34,1.56,.64,1` is the standard \"back out\"), so an\n * element settles rather than oscillating. Like every other rule here it sits\n * inside the reduced-motion gate.\n *\n * Typed as a total record so adding an id to `ANIMATION_EASINGS` without a\n * curve is a compile error rather than an element with no easing.\n */\nconst EASE_CURVES: Record<AnimationEase, string> = {\n smooth: 'cubic-bezier(.16,1,.3,1)',\n steady: 'linear',\n 'gentle-start': 'cubic-bezier(.4,0,1,1)',\n 'gentle-end': 'cubic-bezier(0,0,.2,1)',\n 'gentle-both': 'cubic-bezier(.4,0,.2,1)',\n overshoot: 'cubic-bezier(.34,1.56,.64,1)',\n}\n\n/**\n * The curve an element gets when it carries no easing class at all — which is\n * every element authored before easing shipped. Deliberately the same string\n * `EASE` used to be, so nothing that already exists moves.\n */\nconst EASE = EASE_CURVES.smooth\n\n/**\n * Per-preset custom properties. One rule per preset sets the keyframe name\n * used by the load/scroll triggers AND the transform the hover trigger eases\n * to, which is why the preset table is a single list rather than three.\n *\n * Hover is a TRANSITION to an emphasis state, not a keyframe run: \"Slide up\"\n * on hover lifts the element, \"Zoom in\" grows it. That is what an author means\n * when they pick a direction for a hover effect.\n */\nconst PRESETS: Array<{\n id: string\n /** `from` state of the entrance keyframe. */\n from: string\n /** Hover emphasis transform. */\n hover: string\n /** Hover emphasis opacity. */\n hoverOpacity?: string\n}> = [\n { id: 'fade', from: 'opacity:0', hover: 'none', hoverOpacity: '.7' },\n {\n id: 'slide-up',\n from: 'opacity:0;transform:translate3d(0,24px,0)',\n hover: 'translate3d(0,-8px,0)',\n },\n {\n id: 'slide-down',\n from: 'opacity:0;transform:translate3d(0,-24px,0)',\n hover: 'translate3d(0,8px,0)',\n },\n {\n id: 'slide-left',\n from: 'opacity:0;transform:translate3d(24px,0,0)',\n hover: 'translate3d(-8px,0,0)',\n },\n {\n id: 'slide-right',\n from: 'opacity:0;transform:translate3d(-24px,0,0)',\n hover: 'translate3d(8px,0,0)',\n },\n {\n id: 'zoom-in',\n from: 'opacity:0;transform:scale3d(.92,.92,1)',\n hover: 'scale3d(1.04,1.04,1)',\n },\n {\n id: 'zoom-out',\n from: 'opacity:0;transform:scale3d(1.08,1.08,1)',\n hover: 'scale3d(.96,.96,1)',\n },\n]\n\nconst NAME_VAR = '--aglyn-anim-name'\nconst HOVER_VAR = '--aglyn-anim-hover'\nconst HOVER_OPACITY_VAR = '--aglyn-anim-hover-opacity'\n/**\n * Set by the easing class rules and read by the timing rules. Not exported:\n * the renderer writes a CLASS, never this property, so an author cannot get a\n * raw curve of their own into the page through it.\n */\nconst EASE_VAR = '--aglyn-anim-ease'\n\nconst ANIMATED = `.${ANIMATION_CLASS}`\nconst GROUP = `.${ANIMATION_GROUP_CLASS}`\n/**\n * Everything the timing rules apply to: a plain animated element, and the\n * CHILDREN of a stagger host. The host itself is deliberately absent — it\n * sets the custom properties its children inherit and animates nothing.\n */\nconst TARGETS = `${ANIMATED},${GROUP}>*`\nconst DURATION = `var(${ANIMATION_DURATION_VAR},600ms)`\nconst DELAY = `var(${ANIMATION_DELAY_VAR},0ms)`\nconst STEP = `var(${ANIMATION_STAGGER_STEP_VAR},90ms)`\nconst TIMING = `var(${EASE_VAR},${EASE})`\n/**\n * The author's own delay plus whatever rung of the stagger ladder this child\n * landed on. A plain animated element never matches a ladder rule, so its\n * offset falls back to `0ms` and the sum is exactly the delay it had before\n * stagger existed.\n */\nconst TOTAL_DELAY = `calc(${DELAY} + var(${ANIMATION_STAGGER_OFFSET_VAR},0ms))`\n\n/**\n * The stagger ladder: one rule per rung, each pushing a child one more STEP\n * behind the one before it.\n *\n * The index has to come from CSS rather than the renderer. The renderer sees\n * a node, not a position — and a node that knew its index would have to\n * re-render every time a sibling was inserted, which is exactly what an\n * author does while building a row of cards.\n *\n * The first child gets NO rule: `var(--aglyn-anim-stagger,0ms)` already falls\n * back to zero, so emitting `calc(STEP * 0)` would be a rule that changes\n * nothing. The last rung is `:nth-child(n+N)` rather than an Nth rule, so a\n * collection with two hundred rows shares the final rung instead of leaving\n * its tail invisible for half a minute.\n */\nconst STAGGER_LADDER: string[] = [\n // Reset first, and it must STAY first. A nested stagger host's own ladder\n // rule has identical specificity (one class + one pseudo-class against two\n // classes), so source order is the only thing that lets the inner host win\n // for its own children. What this catches is the other case: an animated\n // element deeper inside a staggered child, which would otherwise INHERIT\n // its ancestor's rung and start late for no reason an author could see.\n `${GROUP}>* ${ANIMATED}{${ANIMATION_STAGGER_OFFSET_VAR}:0ms}`,\n ...Array.from(\n { length: ANIMATION_STAGGER_MAX_CHILDREN - 2 },\n (_unused, index) => {\n const nth = index + 2\n return `${GROUP}>*:nth-child(${nth}){${ANIMATION_STAGGER_OFFSET_VAR}:calc(${STEP} * ${nth - 1})}`\n },\n ),\n `${GROUP}>*:nth-child(n+${ANIMATION_STAGGER_MAX_CHILDREN}){${ANIMATION_STAGGER_OFFSET_VAR}:calc(${STEP} * ${\n ANIMATION_STAGGER_MAX_CHILDREN - 1\n })}`,\n]\n\n/**\n * The stylesheet. Built once at module load, not per request.\n *\n * The keyframe name is threaded through a custom property so the trigger rules\n * are two selectors rather than two per preset — the sheet stays flat as\n * presets are added.\n */\nexport const ELEMENT_ANIMATION_STYLE_TEXT = [\n '@media (prefers-reduced-motion:no-preference){',\n // Keyframes.\n ...PRESETS.map(\n (preset) =>\n `@keyframes aglyn-anim-${preset.id}{from{${preset.from}}to{opacity:1;transform:none}}`,\n ),\n // Per-preset custom properties. Keyed on the preset class ALONE, not on the\n // base class as well: a stagger host carries `aglyn-anim-group` rather than\n // `aglyn-anim`, and its children read these through inheritance, so a rule\n // that required the base class would leave a whole staggered row with no\n // keyframe name.\n ...PRESETS.map(\n (preset) =>\n `.${ANIMATION_PRESET_CLASS_PREFIX}${preset.id}{${NAME_VAR}:aglyn-anim-${preset.id};${HOVER_VAR}:${preset.hover}${\n preset.hoverOpacity ? `;${HOVER_OPACITY_VAR}:${preset.hoverOpacity}` : ''\n }}`,\n ),\n // Per-easing custom property, same shape and the same inheritance.\n ...ANIMATION_EASINGS.map(\n (ease) =>\n `.${ANIMATION_EASE_CLASS_PREFIX}${ease}{${EASE_VAR}:${EASE_CURVES[ease]}}`,\n ),\n // Shared timing for the keyframe triggers.\n `${TARGETS}{animation-duration:${DURATION};animation-delay:${TOTAL_DELAY};animation-timing-function:${TIMING};animation-fill-mode:both}`,\n // The stagger ladder — no-ops for every element that is not inside a host.\n ...STAGGER_LADDER,\n // On load: plays as soon as the element is parsed. No JS involved at all.\n `${ANIMATED}[${ANIMATION_TRIGGER_ATTR}=load],${GROUP}[${ANIMATION_TRIGGER_ATTR}=load]>*{animation-name:var(${NAME_VAR})}`,\n // On scroll: plays when the runtime marks it as entered. For a stagger host\n // the ONE observed element is the host, and its children play off its class\n // — one observer entry for a whole row, not one per card.\n `${ANIMATED}[${ANIMATION_TRIGGER_ATTR}=scroll].${ANIMATION_IN_CLASS},${GROUP}[${ANIMATION_TRIGGER_ATTR}=scroll].${ANIMATION_IN_CLASS}>*{animation-name:var(${NAME_VAR})}`,\n // The ONLY rule that hides anything, and it is scoped under a class the\n // inline runtime adds to <html>. No JS (or no IntersectionObserver) means\n // the class is never added, the rule never matches, and every element is\n // visible in its final state. `opacity` rather than `display`/`visibility`\n // so the content stays in the accessibility tree and in the DOM a crawler\n // reads either way.\n `html.${ANIMATION_READY_CLASS} ${ANIMATED}[${ANIMATION_TRIGGER_ATTR}=scroll]:not(.${ANIMATION_IN_CLASS}),html.${ANIMATION_READY_CLASS} ${GROUP}[${ANIMATION_TRIGGER_ATTR}=scroll]:not(.${ANIMATION_IN_CLASS})>*{opacity:0}`,\n // On hover: a transition, never a keyframe run, so it reverses cleanly when\n // the pointer leaves. Staggering is refused for hover upstream, so this rule\n // reads the author's plain delay rather than the ladder's sum — a\n // transition has no `animation-delay` to add a rung to.\n `${ANIMATED}[${ANIMATION_TRIGGER_ATTR}=hover]{transition:transform ${DURATION} ${TIMING} ${DELAY},opacity ${DURATION} ${TIMING} ${DELAY}}`,\n `${ANIMATED}[${ANIMATION_TRIGGER_ATTR}=hover]:hover{transform:var(${HOVER_VAR},none);opacity:var(${HOVER_OPACITY_VAR},1)}`,\n '}',\n].join('')\n\n/** Id of the injected style tag (idempotence marker, mirrors AGL-562). */\nexport const ELEMENT_ANIMATION_STYLE_ID = 'aglyn-element-animation-style'\n\n/**\n * The scroll runtime, inlined verbatim into a `<script>`.\n *\n * One `IntersectionObserver` for the whole document. Re-scanning is safe and\n * cheap because `observe()` on an already-observed element is a no-op, which\n * is how late-arriving nodes (a deferred lazy tab panel the reader opens, a\n * collection page appending rows) get picked up without a per-element\n * observer. The `MutationObserver` is what makes that correct rather than\n * best-effort: an element inserted after load would otherwise stay at\n * `opacity:0` forever, because the hide rule applies the moment the ready\n * class is on `<html>`.\n *\n * The selector is the trigger ATTRIBUTE alone rather than the base class,\n * because there are now two base classes — an element that animates itself\n * and a stagger host whose children animate. Both carry the attribute, only\n * the renderer ever writes it, and matching on it keeps the runtime unaware\n * that the second shape exists.\n *\n * Written in ES5 with short locals because it ships as source; there is no\n * minifier in this path.\n */\nexport const ELEMENT_ANIMATION_SCRIPT_TEXT = `(function(){var w=window,d=document;if(!w.IntersectionObserver||!w.MutationObserver)return;d.documentElement.classList.add(${JSON.stringify(\n ANIMATION_READY_CLASS,\n)});var S=${JSON.stringify(\n `[${ANIMATION_TRIGGER_ATTR}=scroll]`,\n)},I=${JSON.stringify(ANIMATION_IN_CLASS)},R=${JSON.stringify(\n ANIMATION_REPEAT_ATTR,\n)};var o=new w.IntersectionObserver(function(es){for(var i=0;i<es.length;i++){var e=es[i],t=e.target,r=t.getAttribute(R)==='1';if(e.isIntersecting){t.classList.add(I);if(!r)o.unobserve(t)}else if(r)t.classList.remove(I)}},{rootMargin:'0px 0px -10% 0px'});var q=0,scan=function(){q=0;var n=d.querySelectorAll(S);for(var i=0;i<n.length;i++)o.observe(n[i])};scan();if(d.readyState==='loading')d.addEventListener('DOMContentLoaded',scan);var m=new w.MutationObserver(function(){if(q)return;q=w.requestAnimationFrame?w.requestAnimationFrame(scan):w.setTimeout(scan,0)});var start=function(){if(d.body)m.observe(d.body,{childList:true,subtree:true})};if(d.body)start();else d.addEventListener('DOMContentLoaded',start)})()`\n\nexport interface PageAnimationAssets {\n styleText: string\n /** Present only when the page carries a scroll-triggered element. */\n scriptText: string | null\n}\n\n/**\n * Decides what a page must ship, from the flat node map the route already\n * holds. Returns `null` — the common case — when nothing on the page animates,\n * and the route then renders neither tag.\n *\n * The scroll runtime is withheld separately: a page whose only animations play\n * on load or on hover is pure CSS and ships no script either.\n */\nexport function pageAnimationAssets(\n nodes: Record<string, any> | null | undefined,\n): PageAnimationAssets | null {\n if (!nodes) return null\n let animates = false\n let needsScroll = false\n for (const id in nodes) {\n const props = nodes[id]?.props\n if (!nodePropsAnimate(props)) continue\n animates = true\n // `scroll` is the default trigger, so an unset trigger needs the runtime.\n const trigger = props?.[NODE_ANIMATION_TRIGGER_PROP]\n if (trigger === undefined || trigger === null || trigger === 'scroll') {\n needsScroll = true\n break\n }\n }\n if (!animates) return null\n return {\n styleText: ELEMENT_ANIMATION_STYLE_TEXT,\n scriptText: needsScroll ? ELEMENT_ANIMATION_SCRIPT_TEXT : null,\n }\n}\n"],"names":["ANIMATION_CLASS","ANIMATION_DELAY_VAR","ANIMATION_DURATION_VAR","ANIMATION_EASE_CLASS_PREFIX","ANIMATION_EASINGS","ANIMATION_GROUP_CLASS","ANIMATION_IN_CLASS","ANIMATION_PRESET_CLASS_PREFIX","ANIMATION_READY_CLASS","ANIMATION_REPEAT_ATTR","ANIMATION_STAGGER_MAX_CHILDREN","ANIMATION_STAGGER_OFFSET_VAR","ANIMATION_STAGGER_STEP_VAR","ANIMATION_TRIGGER_ATTR","NODE_ANIMATION_TRIGGER_PROP","nodePropsAnimate","EASE_CURVES","smooth","steady","overshoot","EASE","PRESETS","id","from","hover","hoverOpacity","NAME_VAR","HOVER_VAR","HOVER_OPACITY_VAR","EASE_VAR","ANIMATED","GROUP","TARGETS","DURATION","DELAY","STEP","TIMING","TOTAL_DELAY","STAGGER_LADDER","Array","length","_unused","index","nth","ELEMENT_ANIMATION_STYLE_TEXT","map","preset","ease","join","ELEMENT_ANIMATION_STYLE_ID","ELEMENT_ANIMATION_SCRIPT_TEXT","JSON","stringify","pageAnimationAssets","nodes","animates","needsScroll","props","trigger","undefined","styleText","scriptText"],"mappings":"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"}
@@ -0,0 +1,29 @@
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 { HostActionAlert, HostEventType } from '@aglyn/aglyn/server';
18
+ import { type HostEventPayload } from './host-event-listeners';
19
+ /**
20
+ * One emit point for host events (AGL-148): hands the event to every
21
+ * registered host-event listener (see `host-event-listeners.ts`) and returns
22
+ * the site alerts they produced, so request/response emitters (form submit,
23
+ * booking) can surface them; fire-and-forget emitters ignore the result. No
24
+ * listener throws into the emitting request.
25
+ */
26
+ export declare function emitHostEvent(hostId: string, event: HostEventType, payload?: HostEventPayload): Promise<{
27
+ alerts: HostActionAlert[];
28
+ }>;
29
+ export default emitHostEvent;