@xuda.io/runtime-bundle 1.0.1436 → 1.0.1438

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.
@@ -1705,7 +1705,30 @@ func.runtime.platform.emit = function (name, data) {
1705
1705
  };
1706
1706
 
1707
1707
  // ── Platform helpers for DOM-independent resource loading ──
1708
- func.runtime.platform.load_script = function (url, type, callback) {
1708
+ func.runtime.platform.apply_element_attributes = function (node, attributes, excluded_keys = []) {
1709
+ if (!node?.setAttribute || !attributes) {
1710
+ return node;
1711
+ }
1712
+
1713
+ const excluded = new Set(excluded_keys || []);
1714
+ const attr_keys = Object.keys(attributes);
1715
+ for (let index = 0; index < attr_keys.length; index++) {
1716
+ const key = attr_keys[index];
1717
+ if (!key || excluded.has(key)) {
1718
+ continue;
1719
+ }
1720
+
1721
+ const value = attributes[key];
1722
+ if (value === false || typeof value === 'undefined') {
1723
+ continue;
1724
+ }
1725
+
1726
+ node.setAttribute(key, value === null ? '' : `${value}`);
1727
+ }
1728
+
1729
+ return node;
1730
+ };
1731
+ func.runtime.platform.load_script = function (url, type, callback, attributes) {
1709
1732
  const doc = func.runtime.platform.get_document();
1710
1733
  if (!doc?.createElement || !doc?.head?.appendChild) {
1711
1734
  if (callback) {
@@ -1713,19 +1736,63 @@ func.runtime.platform.load_script = function (url, type, callback) {
1713
1736
  }
1714
1737
  return;
1715
1738
  }
1739
+ const find_existing_script = function () {
1740
+ const asset_key = attributes?.['data-xuda-asset-key'];
1741
+ const scripts = doc.querySelectorAll ? Array.from(doc.querySelectorAll('script')) : [];
1742
+ return scripts.find(function (script) {
1743
+ if (asset_key && script.getAttribute('data-xuda-asset-key') === asset_key) {
1744
+ return true;
1745
+ }
1746
+ return !!(url && script.getAttribute('src') === url);
1747
+ }) || null;
1748
+ };
1749
+
1750
+ const existing_script = find_existing_script();
1751
+ if (existing_script) {
1752
+ if (callback) {
1753
+ if (existing_script.getAttribute('data-xuda-loaded') === 'true' || !url) {
1754
+ callback();
1755
+ } else {
1756
+ existing_script.addEventListener('load', callback, { once: true });
1757
+ existing_script.addEventListener('error', callback, { once: true });
1758
+ }
1759
+ }
1760
+ return existing_script;
1761
+ }
1762
+
1716
1763
  const script = doc.createElement('script');
1717
1764
  script.src = url;
1718
1765
  if (type) script.type = type;
1719
- script.onload = callback;
1766
+ func.runtime.platform.apply_element_attributes(script, attributes, ['src', 'type']);
1767
+ script.onload = function () {
1768
+ script.setAttribute('data-xuda-loaded', 'true');
1769
+ if (callback) {
1770
+ callback();
1771
+ }
1772
+ };
1773
+ script.onerror = function () {
1774
+ if (callback) {
1775
+ callback();
1776
+ }
1777
+ };
1720
1778
  doc.head.appendChild(script);
1779
+ return script;
1721
1780
  };
1722
- func.runtime.platform.load_css = function (href) {
1781
+ func.runtime.platform.load_css = function (href, attributes) {
1723
1782
  const doc = func.runtime.platform.get_document();
1724
1783
  if (!doc?.createElement || !doc?.head) {
1725
1784
  return;
1726
1785
  }
1727
1786
  try {
1728
- if (doc.querySelector('link[href="' + href + '"]')) return;
1787
+ const asset_key = attributes?.['data-xuda-asset-key'];
1788
+ const existing_links = doc.querySelectorAll ? Array.from(doc.querySelectorAll('link')) : [];
1789
+ const existing = existing_links.find(function (link) {
1790
+ if (asset_key && link.getAttribute('data-xuda-asset-key') === asset_key) {
1791
+ return true;
1792
+ }
1793
+ return !!(href && link.getAttribute('href') === href);
1794
+ });
1795
+ if (existing) return existing;
1729
1796
  } catch (err) {
1730
1797
  return;
1731
1798
  }
@@ -1733,7 +1800,9 @@ func.runtime.platform.load_css = function (href) {
1733
1800
  link.rel = 'stylesheet';
1734
1801
  link.type = 'text/css';
1735
1802
  link.href = href;
1803
+ func.runtime.platform.apply_element_attributes(link, attributes, ['href']);
1736
1804
  doc.head.insertBefore(link, doc.head.firstChild);
1805
+ return link;
1737
1806
  };
1738
1807
  func.runtime.platform.remove_js_css = function (filename, filetype) {
1739
1808
  const doc = func.runtime.platform.get_document();
@@ -1767,6 +1836,128 @@ func.runtime.platform.set_cursor = function (element, cursor) {
1767
1836
  node.style.cursor = cursor;
1768
1837
  }
1769
1838
  };
1839
+ func.runtime.program.normalize_doc_for_runtime = function (doc) {
1840
+ if (!doc || doc.__xudaRuntimeNormalized || !Array.isArray(doc.progUi) || !doc.progUi.length) {
1841
+ return doc;
1842
+ }
1843
+
1844
+ const normalize_tag_name = function (tag_name) {
1845
+ return `${tag_name || ''}`.trim().toLowerCase();
1846
+ };
1847
+ const merge_attributes = function (target, source) {
1848
+ const merged = { ...(target || {}) };
1849
+ const source_attributes = source || {};
1850
+ const keys = Object.keys(source_attributes);
1851
+
1852
+ for (let index = 0; index < keys.length; index++) {
1853
+ const key = keys[index];
1854
+ const value = source_attributes[key];
1855
+ if (typeof value === 'undefined') {
1856
+ continue;
1857
+ }
1858
+
1859
+ if (key === 'class' && merged.class && value) {
1860
+ const next_value = `${merged.class} ${value}`.trim();
1861
+ merged.class = Array.from(new Set(next_value.split(/\s+/).filter(Boolean))).join(' ');
1862
+ continue;
1863
+ }
1864
+
1865
+ if (key === 'style' && merged.style && value) {
1866
+ merged.style = `${merged.style}; ${value}`.trim();
1867
+ continue;
1868
+ }
1869
+
1870
+ if (typeof merged[key] === 'undefined') {
1871
+ merged[key] = value;
1872
+ }
1873
+ }
1874
+
1875
+ return merged;
1876
+ };
1877
+ const normalize_nodes = function (nodes, state) {
1878
+ const normalized_nodes = [];
1879
+
1880
+ for (let index = 0; index < (nodes || []).length; index++) {
1881
+ const node = nodes[index];
1882
+ if (!node || typeof node !== 'object') {
1883
+ continue;
1884
+ }
1885
+
1886
+ const tag_name = normalize_tag_name(node.tagName);
1887
+
1888
+ if (tag_name === '!doctype') {
1889
+ state.changed = true;
1890
+ continue;
1891
+ }
1892
+
1893
+ if (tag_name === 'html') {
1894
+ state.changed = true;
1895
+ state.root_attributes = merge_attributes(state.root_attributes, node.attributes);
1896
+ normalized_nodes.push.apply(normalized_nodes, normalize_nodes(node.children, state));
1897
+ continue;
1898
+ }
1899
+
1900
+ if (tag_name === 'head') {
1901
+ state.changed = true;
1902
+ normalized_nodes.push.apply(normalized_nodes, normalize_nodes(node.children, state));
1903
+ continue;
1904
+ }
1905
+
1906
+ if (tag_name === 'body') {
1907
+ state.changed = true;
1908
+ state.root_attributes = merge_attributes(state.root_attributes, node.attributes);
1909
+ normalized_nodes.push.apply(normalized_nodes, normalize_nodes(node.children, state));
1910
+ continue;
1911
+ }
1912
+
1913
+ let next_node = node;
1914
+ if (Array.isArray(node.children) && node.children.length) {
1915
+ const next_children = normalize_nodes(node.children, state);
1916
+ if (next_children !== node.children) {
1917
+ next_node = {
1918
+ ...node,
1919
+ children: next_children,
1920
+ };
1921
+ state.changed = true;
1922
+ }
1923
+ }
1924
+
1925
+ normalized_nodes.push(next_node);
1926
+ }
1927
+
1928
+ return normalized_nodes;
1929
+ };
1930
+
1931
+ const [root_node, ...extra_nodes] = doc.progUi;
1932
+ if (!root_node || typeof root_node !== 'object') {
1933
+ return doc;
1934
+ }
1935
+
1936
+ const state = {
1937
+ changed: false,
1938
+ root_attributes: {},
1939
+ };
1940
+
1941
+ const normalized_children = normalize_nodes([...(root_node.children || []), ...extra_nodes], state);
1942
+ const merged_attributes = merge_attributes(root_node.attributes, state.root_attributes);
1943
+
1944
+ if (!state.changed && !Object.keys(state.root_attributes).length) {
1945
+ doc.__xudaRuntimeNormalized = true;
1946
+ return doc;
1947
+ }
1948
+
1949
+ return {
1950
+ ...doc,
1951
+ __xudaRuntimeNormalized: true,
1952
+ progUi: [
1953
+ {
1954
+ ...root_node,
1955
+ attributes: merged_attributes,
1956
+ children: normalized_children,
1957
+ },
1958
+ ],
1959
+ };
1960
+ };
1770
1961
 
1771
1962
  func.runtime.env = {
1772
1963
  get_url_params: function () {
@@ -2024,6 +2215,147 @@ func.runtime.workers.delete_promise = function (SESSION_ID, worker_id, promise_q
2024
2215
  delete registry_entry.promise_queue[promise_queue_id];
2025
2216
  return true;
2026
2217
  };
2218
+ func.runtime.render.clone_runtime_options = function (value) {
2219
+ if (typeof structuredClone === 'function') {
2220
+ try {
2221
+ return structuredClone(value);
2222
+ } catch (_) {}
2223
+ }
2224
+
2225
+ if (Array.isArray(value)) {
2226
+ return value.map(function (item) {
2227
+ return func.runtime.render.clone_runtime_options(item);
2228
+ });
2229
+ }
2230
+
2231
+ if (value && typeof value === 'object') {
2232
+ const cloned = {};
2233
+ const keys = Object.keys(value);
2234
+ for (let index = 0; index < keys.length; index++) {
2235
+ const key = keys[index];
2236
+ cloned[key] = func.runtime.render.clone_runtime_options(value[key]);
2237
+ }
2238
+ return cloned;
2239
+ }
2240
+
2241
+ return value;
2242
+ };
2243
+ func.runtime.render.normalize_runtime_bootstrap = function (raw_options = {}) {
2244
+ const options = raw_options || {};
2245
+ let app_computing_mode = options.app_computing_mode || '';
2246
+ let app_render_mode = options.app_render_mode || '';
2247
+ let app_client_activation = options.app_client_activation || '';
2248
+ let ssr_payload = options.ssr_payload || null;
2249
+
2250
+ if (typeof ssr_payload === 'string') {
2251
+ try {
2252
+ ssr_payload = JSON.parse(ssr_payload);
2253
+ } catch (_) {
2254
+ ssr_payload = null;
2255
+ }
2256
+ }
2257
+
2258
+ if (ssr_payload && typeof ssr_payload === 'object') {
2259
+ ssr_payload = func.runtime.render.clone_runtime_options(ssr_payload);
2260
+ }
2261
+
2262
+ if (!app_computing_mode) {
2263
+ if (app_render_mode === 'ssr_first_page' || app_render_mode === 'ssr_full') {
2264
+ app_computing_mode = 'server';
2265
+ } else {
2266
+ app_computing_mode = 'main';
2267
+ }
2268
+ }
2269
+
2270
+ switch (app_computing_mode) {
2271
+ case 'main':
2272
+ app_render_mode = 'csr';
2273
+ app_client_activation = 'none';
2274
+ break;
2275
+
2276
+ case 'worker':
2277
+ app_render_mode = 'csr';
2278
+ app_client_activation = 'none';
2279
+ break;
2280
+
2281
+ default:
2282
+ app_computing_mode = 'server';
2283
+ if (app_render_mode !== 'ssr_full') {
2284
+ app_render_mode = 'ssr_first_page';
2285
+ }
2286
+ app_client_activation = app_render_mode === 'ssr_full' ? 'hydrate' : 'takeover';
2287
+ break;
2288
+ }
2289
+
2290
+ if (ssr_payload && typeof ssr_payload === 'object') {
2291
+ if (!ssr_payload.app_render_mode) {
2292
+ ssr_payload.app_render_mode = app_render_mode;
2293
+ }
2294
+ if (!ssr_payload.app_client_activation) {
2295
+ ssr_payload.app_client_activation = app_client_activation;
2296
+ }
2297
+ if (!ssr_payload.app_computing_mode) {
2298
+ ssr_payload.app_computing_mode = app_computing_mode;
2299
+ }
2300
+ }
2301
+
2302
+ return {
2303
+ app_computing_mode,
2304
+ app_render_mode,
2305
+ app_client_activation,
2306
+ ssr_payload,
2307
+ };
2308
+ };
2309
+ func.runtime.render.apply_runtime_bootstrap_defaults = function (target = {}) {
2310
+ const normalized = func.runtime.render.normalize_runtime_bootstrap(target);
2311
+ target.app_computing_mode = normalized.app_computing_mode;
2312
+ target.app_render_mode = normalized.app_render_mode;
2313
+ target.app_client_activation = normalized.app_client_activation;
2314
+ target.ssr_payload = normalized.ssr_payload;
2315
+ return normalized;
2316
+ };
2317
+ func.runtime.render.is_server_render_mode = function (target = {}) {
2318
+ const normalized = func.runtime.render.normalize_runtime_bootstrap(target?.opt || target);
2319
+ return normalized.app_computing_mode === 'server' && normalized.app_render_mode !== 'csr';
2320
+ };
2321
+ func.runtime.render.is_takeover_mode = function (target = {}) {
2322
+ const normalized = func.runtime.render.normalize_runtime_bootstrap(target?.opt || target);
2323
+ return normalized.app_client_activation === 'takeover';
2324
+ };
2325
+ func.runtime.render.is_hydration_mode = function (target = {}) {
2326
+ const normalized = func.runtime.render.normalize_runtime_bootstrap(target?.opt || target);
2327
+ return normalized.app_client_activation === 'hydrate';
2328
+ };
2329
+ func.runtime.render.get_ssr_payload = function (target = {}) {
2330
+ if (target?.opt?.ssr_payload) {
2331
+ return target.opt.ssr_payload;
2332
+ }
2333
+ if (target?.ssr_payload) {
2334
+ return target.ssr_payload;
2335
+ }
2336
+ const win = func.runtime.platform.get_window();
2337
+ return win?.__XUDA_SSR__ || null;
2338
+ };
2339
+ func.runtime.render.should_use_ssr_payload = function (SESSION_ID, paramsP) {
2340
+ const session = SESSION_OBJ?.[SESSION_ID];
2341
+ const payload = func.runtime.render.get_ssr_payload(session);
2342
+ if (!payload || payload._consumed) {
2343
+ return false;
2344
+ }
2345
+ if (paramsP?.prog_id && payload.prog_id && payload.prog_id !== paramsP.prog_id) {
2346
+ return false;
2347
+ }
2348
+ return true;
2349
+ };
2350
+ func.runtime.render.mark_ssr_payload_consumed = function (SESSION_ID) {
2351
+ const session = SESSION_OBJ?.[SESSION_ID];
2352
+ const payload = func.runtime.render.get_ssr_payload(session);
2353
+ if (!payload || typeof payload !== 'object') {
2354
+ return false;
2355
+ }
2356
+ payload._consumed = true;
2357
+ return true;
2358
+ };
2027
2359
  func.runtime.render.get_root_data_system = function (SESSION_ID) {
2028
2360
  return SESSION_OBJ[SESSION_ID]?.DS_GLB?.[0]?.data_system || null;
2029
2361
  };
@@ -2427,10 +2759,10 @@ func.runtime.resources.load_cdn = async function (SESSION_ID, resource) {
2427
2759
  await func.utils.load_js_on_demand(normalized_resource.src);
2428
2760
  break;
2429
2761
  case 'css':
2430
- await func.utils.load_js_on_demand(normalized_resource.src);
2762
+ func.runtime.platform.load_css(normalized_resource.src);
2431
2763
  break;
2432
2764
  case 'module':
2433
- func.utils.load_js_on_demand(normalized_resource.src, 'module');
2765
+ await func.utils.load_js_on_demand(normalized_resource.src, 'module');
2434
2766
  break;
2435
2767
  default:
2436
2768
  await func.utils.load_js_on_demand(normalized_resource.src);
@@ -3766,7 +4098,692 @@ func.common.fastHash = function (inputString) {
3766
4098
  return ((hash >>> 0).toString(36) + '0000000000').slice(0, 10);
3767
4099
  };
3768
4100
 
3769
- glb.new_xu_render = false;
4101
+ glb.new_xu_render = false;
4102
+ func.runtime = func.runtime || {};
4103
+ func.runtime.ui = func.runtime.ui || {};
4104
+ func.runtime.render = func.runtime.render || {};
4105
+ func.runtime.widgets = func.runtime.widgets || {};
4106
+
4107
+ // Shared render-tree contract helpers live here so browser and headless runtimes can resolve the same UI structure.
4108
+
4109
+ func.runtime.render.TREE_CONTRACT_VERSION = func.runtime.render.TREE_CONTRACT_VERSION || 'xuda.render_tree.v1';
4110
+ func.runtime.render._tree_widget_capability_cache = func.runtime.render._tree_widget_capability_cache || {};
4111
+
4112
+ func.runtime.render.safe_clone_tree_value = function (value) {
4113
+ if (typeof structuredClone === 'function') {
4114
+ try {
4115
+ return structuredClone(value);
4116
+ } catch (_) {
4117
+ // Fall through to the recursive clone below.
4118
+ }
4119
+ }
4120
+
4121
+ if (Array.isArray(value)) {
4122
+ return value.map(function (item) {
4123
+ return func.runtime.render.safe_clone_tree_value(item);
4124
+ });
4125
+ }
4126
+
4127
+ if (value && typeof value === 'object') {
4128
+ const cloned = {};
4129
+ const keys = Object.keys(value);
4130
+ for (let index = 0; index < keys.length; index++) {
4131
+ const key = keys[index];
4132
+ cloned[key] = func.runtime.render.safe_clone_tree_value(value[key]);
4133
+ }
4134
+ return cloned;
4135
+ }
4136
+
4137
+ return value;
4138
+ };
4139
+ func.runtime.render.sort_tree_debug_value = function (value) {
4140
+ if (Array.isArray(value)) {
4141
+ return value.map(function (item) {
4142
+ return func.runtime.render.sort_tree_debug_value(item);
4143
+ });
4144
+ }
4145
+
4146
+ if (value && typeof value === 'object') {
4147
+ const sorted = {};
4148
+ const keys = Object.keys(value).sort();
4149
+ for (let index = 0; index < keys.length; index++) {
4150
+ const key = keys[index];
4151
+ sorted[key] = func.runtime.render.sort_tree_debug_value(value[key]);
4152
+ }
4153
+ return sorted;
4154
+ }
4155
+
4156
+ return value;
4157
+ };
4158
+ func.runtime.render.is_tree_node = function (nodeP) {
4159
+ return !!nodeP?.contract && nodeP.contract === func.runtime.render.TREE_CONTRACT_VERSION;
4160
+ };
4161
+ func.runtime.render.get_tree_source_node = function (nodeP) {
4162
+ if (!func.runtime.render.is_tree_node(nodeP)) {
4163
+ return nodeP || null;
4164
+ }
4165
+ return nodeP?.meta?.source_node || null;
4166
+ };
4167
+ func.runtime.render.get_tree_source_snapshot = function (nodeP) {
4168
+ if (!func.runtime.render.is_tree_node(nodeP)) {
4169
+ return func.runtime.render.safe_clone_tree_value(nodeP);
4170
+ }
4171
+ return nodeP?.meta?.source_snapshot || null;
4172
+ };
4173
+ func.runtime.render.get_tree_node_kind = function (nodeP) {
4174
+ const tag_name = typeof nodeP?.tagName === 'string' ? nodeP.tagName.toLowerCase() : '';
4175
+ const node_type = typeof nodeP?.type === 'string' ? nodeP.type.toLowerCase() : '';
4176
+
4177
+ if (tag_name === 'xu-widget') return 'widget';
4178
+ if (tag_name === 'xu-single-view') return 'single_view';
4179
+ if (tag_name === 'xu-multi-view') return 'multi_view';
4180
+ if (tag_name === 'xu-panel') return 'panel';
4181
+ if (tag_name === 'xu-teleport') return 'teleport';
4182
+ if (tag_name === 'xurender') return 'placeholder';
4183
+ if (tag_name === '#text' || node_type === 'text') return 'text';
4184
+ if (!tag_name && typeof nodeP?.content === 'string' && !Array.isArray(nodeP?.children)) return 'text';
4185
+ return 'element';
4186
+ };
4187
+ func.runtime.render.get_tree_node_id = function (nodeP, pathP) {
4188
+ if (nodeP?.id) {
4189
+ return nodeP.id;
4190
+ }
4191
+ if (nodeP?.id_org) {
4192
+ return nodeP.id_org;
4193
+ }
4194
+ const normalized_path = Array.isArray(pathP) && pathP.length ? pathP.join('.') : 'root';
4195
+ return `tree-node-${normalized_path}`;
4196
+ };
4197
+ func.runtime.render.get_tree_controls = function (attributes) {
4198
+ const attrs = attributes || {};
4199
+ const get_first_defined = function (keys) {
4200
+ for (let index = 0; index < keys.length; index++) {
4201
+ const key = keys[index];
4202
+ if (Object.prototype.hasOwnProperty.call(attrs, key)) {
4203
+ return attrs[key];
4204
+ }
4205
+ }
4206
+ return null;
4207
+ };
4208
+ return {
4209
+ xu_for: get_first_defined(['xu-for', 'xu-exp:xu-for']),
4210
+ xu_if: get_first_defined(['xu-if', 'xu-exp:xu-if']),
4211
+ xu_render: get_first_defined(['xu-render', 'xu-exp:xu-render']),
4212
+ };
4213
+ };
4214
+ func.runtime.render.get_tree_node_capabilities = async function (options) {
4215
+ const attributes = options?.attributes || {};
4216
+ const plugin_name = attributes['xu-widget'];
4217
+ if (!plugin_name) {
4218
+ return null;
4219
+ }
4220
+
4221
+ const cache = func.runtime.render._tree_widget_capability_cache;
4222
+ if (cache[plugin_name]) {
4223
+ return func.runtime.render.safe_clone_tree_value(cache[plugin_name]);
4224
+ }
4225
+
4226
+ let capabilities = {
4227
+ browser: true,
4228
+ headless: false,
4229
+ };
4230
+
4231
+ try {
4232
+ if (options.SESSION_ID && options.paramsP && func.runtime.widgets?.create_context && func.runtime.widgets?.get_definition) {
4233
+ const widget_context = func.runtime.widgets.create_context(options.SESSION_ID, options.paramsP, attributes);
4234
+ const definition = await func.runtime.widgets.get_definition(widget_context);
4235
+ capabilities = func.runtime.widgets.normalize_capabilities(definition);
4236
+ }
4237
+ } catch (_) {
4238
+ // Keep the safe browser-only default when the widget definition is unavailable.
4239
+ }
4240
+
4241
+ cache[plugin_name] = capabilities;
4242
+ return func.runtime.render.safe_clone_tree_value(capabilities);
4243
+ };
4244
+ func.runtime.render.ensure_tree_node = async function (options) {
4245
+ if (!options?.nodeP) {
4246
+ return null;
4247
+ }
4248
+ if (func.runtime.render.is_tree_node(options.nodeP)) {
4249
+ return options.nodeP;
4250
+ }
4251
+ return await func.runtime.render.build_tree(options);
4252
+ };
4253
+ func.runtime.render.build_tree = async function (options) {
4254
+ if (Array.isArray(options?.nodeP)) {
4255
+ return await func.runtime.render.build_tree_list({
4256
+ ...options,
4257
+ nodesP: options.nodeP,
4258
+ });
4259
+ }
4260
+
4261
+ const nodeP = options?.nodeP;
4262
+ if (!nodeP) {
4263
+ return null;
4264
+ }
4265
+ if (func.runtime.render.is_tree_node(nodeP)) {
4266
+ return nodeP;
4267
+ }
4268
+
4269
+ const pathP = Array.isArray(options?.pathP) ? options.pathP.slice() : [];
4270
+ const tree_path = pathP.length ? pathP.slice() : [0];
4271
+ const attributes = func.runtime.render.safe_clone_tree_value(nodeP.attributes || {});
4272
+ if (typeof nodeP.content !== 'undefined' && typeof attributes['xu-content'] === 'undefined') {
4273
+ attributes['xu-content'] = func.runtime.render.safe_clone_tree_value(nodeP.content);
4274
+ }
4275
+
4276
+ const widget_capabilities = await func.runtime.render.get_tree_node_capabilities({
4277
+ SESSION_ID: options?.SESSION_ID,
4278
+ paramsP: options?.paramsP,
4279
+ attributes,
4280
+ });
4281
+ const children = [];
4282
+ const child_nodes = Array.isArray(nodeP.children) ? nodeP.children : [];
4283
+ const parent_tree_id = tree_path.join('.');
4284
+
4285
+ for (let index = 0; index < child_nodes.length; index++) {
4286
+ const child_tree = await func.runtime.render.build_tree({
4287
+ ...options,
4288
+ nodeP: child_nodes[index],
4289
+ pathP: tree_path.concat(index),
4290
+ parent_tree_id: parent_tree_id,
4291
+ keyP: index,
4292
+ parent_nodeP: nodeP,
4293
+ });
4294
+ if (child_tree) {
4295
+ children.push(child_tree);
4296
+ }
4297
+ }
4298
+
4299
+ const tree = {
4300
+ contract: func.runtime.render.TREE_CONTRACT_VERSION,
4301
+ id: func.runtime.render.get_tree_node_id(nodeP, tree_path),
4302
+ xu_tree_id: `tree.${tree_path.join('.')}`,
4303
+ kind: func.runtime.render.get_tree_node_kind(nodeP),
4304
+ tagName: nodeP.tagName || null,
4305
+ attributes,
4306
+ text: typeof nodeP.text !== 'undefined' ? func.runtime.render.safe_clone_tree_value(nodeP.text) : null,
4307
+ content: typeof nodeP.content !== 'undefined' ? func.runtime.render.safe_clone_tree_value(nodeP.content) : null,
4308
+ children,
4309
+ meta: {
4310
+ tree_id: tree_path.join('.'),
4311
+ path: tree_path,
4312
+ parent_tree_id: options?.parent_tree_id || null,
4313
+ key: typeof options?.keyP === 'undefined' ? null : options.keyP,
4314
+ recordid: nodeP?.recordid || null,
4315
+ dependency_fields: func.runtime.render.safe_clone_tree_value(nodeP?.dependency_fields || null),
4316
+ iterate_info: func.runtime.render.safe_clone_tree_value(options?.parent_infoP?.iterate_info || nodeP?.iterate_info || null),
4317
+ controls: func.runtime.render.get_tree_controls(attributes),
4318
+ capabilities: widget_capabilities,
4319
+ widget: attributes['xu-widget']
4320
+ ? {
4321
+ plugin_name: attributes['xu-widget'],
4322
+ method: attributes['xu-method'] || '_default',
4323
+ capabilities: widget_capabilities,
4324
+ }
4325
+ : null,
4326
+ source_node_id: nodeP?.id || nodeP?.id_org || null,
4327
+ source_node: nodeP,
4328
+ source_snapshot: func.runtime.ui?.get_node_snapshot
4329
+ ? func.runtime.ui.get_node_snapshot(nodeP)
4330
+ : func.runtime.render.safe_clone_tree_value(nodeP),
4331
+ },
4332
+ };
4333
+
4334
+ return tree;
4335
+ };
4336
+ func.runtime.render.build_tree_list = async function (options) {
4337
+ const nodes = Array.isArray(options?.nodesP) ? options.nodesP : [];
4338
+ const trees = [];
4339
+
4340
+ for (let index = 0; index < nodes.length; index++) {
4341
+ const tree = await func.runtime.render.build_tree({
4342
+ ...options,
4343
+ nodeP: nodes[index],
4344
+ pathP: Array.isArray(options?.pathP) && options.pathP.length ? options.pathP.concat(index) : [index],
4345
+ keyP: index,
4346
+ });
4347
+ if (tree) {
4348
+ trees.push(tree);
4349
+ }
4350
+ }
4351
+
4352
+ return trees;
4353
+ };
4354
+ func.runtime.render.sanitize_tree_for_debug = function (treeP) {
4355
+ if (Array.isArray(treeP)) {
4356
+ return treeP.map(function (child) {
4357
+ return func.runtime.render.sanitize_tree_for_debug(child);
4358
+ });
4359
+ }
4360
+
4361
+ if (!func.runtime.render.is_tree_node(treeP)) {
4362
+ return func.runtime.render.sort_tree_debug_value(func.runtime.render.safe_clone_tree_value(treeP));
4363
+ }
4364
+
4365
+ return {
4366
+ contract: treeP.contract,
4367
+ id: treeP.id,
4368
+ xu_tree_id: treeP.xu_tree_id || null,
4369
+ kind: treeP.kind,
4370
+ tagName: treeP.tagName,
4371
+ attributes: func.runtime.render.sort_tree_debug_value(treeP.attributes || {}),
4372
+ text: treeP.text,
4373
+ content: treeP.content,
4374
+ children: treeP.children.map(function (child) {
4375
+ return func.runtime.render.sanitize_tree_for_debug(child);
4376
+ }),
4377
+ meta: {
4378
+ tree_id: treeP.meta?.tree_id || null,
4379
+ path: func.runtime.render.safe_clone_tree_value(treeP.meta?.path || []),
4380
+ parent_tree_id: treeP.meta?.parent_tree_id || null,
4381
+ key: typeof treeP.meta?.key === 'undefined' ? null : treeP.meta.key,
4382
+ recordid: treeP.meta?.recordid || null,
4383
+ dependency_fields: func.runtime.render.sort_tree_debug_value(treeP.meta?.dependency_fields || null),
4384
+ iterate_info: func.runtime.render.sort_tree_debug_value(treeP.meta?.iterate_info || null),
4385
+ controls: func.runtime.render.sort_tree_debug_value(treeP.meta?.controls || null),
4386
+ capabilities: func.runtime.render.sort_tree_debug_value(treeP.meta?.capabilities || null),
4387
+ widget: treeP.meta?.widget
4388
+ ? {
4389
+ plugin_name: treeP.meta.widget.plugin_name,
4390
+ method: treeP.meta.widget.method,
4391
+ capabilities: func.runtime.render.sort_tree_debug_value(treeP.meta.widget.capabilities || null),
4392
+ }
4393
+ : null,
4394
+ source_node_id: treeP.meta?.source_node_id || null,
4395
+ },
4396
+ };
4397
+ };
4398
+ func.runtime.render.serialize_tree = function (treeP, spacing = 2) {
4399
+ return JSON.stringify(func.runtime.render.sanitize_tree_for_debug(treeP), null, spacing);
4400
+ };
4401
+ func.runtime = func.runtime || {};
4402
+ func.runtime.ui = func.runtime.ui || {};
4403
+ func.runtime.render = func.runtime.render || {};
4404
+ func.runtime.widgets = func.runtime.widgets || {};
4405
+
4406
+ // Shared string-renderer helpers live here so headless/server runtimes can materialize the render tree without a DOM.
4407
+
4408
+ func.runtime.render.HTML_VOID_TAGS = func.runtime.render.HTML_VOID_TAGS || {
4409
+ area: true,
4410
+ base: true,
4411
+ br: true,
4412
+ col: true,
4413
+ embed: true,
4414
+ hr: true,
4415
+ img: true,
4416
+ input: true,
4417
+ link: true,
4418
+ meta: true,
4419
+ param: true,
4420
+ source: true,
4421
+ track: true,
4422
+ wbr: true,
4423
+ };
4424
+ func.runtime.render.escape_html = function (value) {
4425
+ return `${value ?? ''}`
4426
+ .replaceAll('&', '&amp;')
4427
+ .replaceAll('<', '&lt;')
4428
+ .replaceAll('>', '&gt;')
4429
+ .replaceAll('"', '&quot;')
4430
+ .replaceAll("'", '&#039;');
4431
+ };
4432
+ func.runtime.render.escape_html_attribute = function (value) {
4433
+ return func.runtime.render.escape_html(value);
4434
+ };
4435
+ func.runtime.render.is_html_void_tag = function (tag_name) {
4436
+ return !!func.runtime.render.HTML_VOID_TAGS[(tag_name || '').toLowerCase()];
4437
+ };
4438
+ func.runtime.render.is_falsey_render_value = function (value) {
4439
+ if (value === false || value === null || typeof value === 'undefined') {
4440
+ return true;
4441
+ }
4442
+ if (typeof value === 'number') {
4443
+ return value === 0;
4444
+ }
4445
+ if (typeof value === 'string') {
4446
+ const normalized = value.trim().toLowerCase();
4447
+ return normalized === '' || normalized === 'false' || normalized === '0' || normalized === 'null' || normalized === 'undefined' || normalized === 'off' || normalized === 'no';
4448
+ }
4449
+ return false;
4450
+ };
4451
+ func.runtime.render.should_render_tree_node = function (treeP) {
4452
+ const controls = treeP?.meta?.controls || {};
4453
+ if (controls.xu_if !== null && controls.xu_if !== undefined && func.runtime.render.is_falsey_render_value(controls.xu_if)) {
4454
+ return false;
4455
+ }
4456
+ if (controls.xu_render !== null && controls.xu_render !== undefined && func.runtime.render.is_falsey_render_value(controls.xu_render)) {
4457
+ return false;
4458
+ }
4459
+ return true;
4460
+ };
4461
+ func.runtime.render.is_tree_control_attribute = function (key) {
4462
+ if (!key) {
4463
+ return false;
4464
+ }
4465
+ return (
4466
+ key.startsWith('xu-exp:') ||
4467
+ key === 'xu-widget' ||
4468
+ key === 'xu-method' ||
4469
+ key === 'xu-for' ||
4470
+ key === 'xu-for-key' ||
4471
+ key === 'xu-for-val' ||
4472
+ key === 'xu-if' ||
4473
+ key === 'xu-render' ||
4474
+ key === 'xu-bind' ||
4475
+ key === 'xu-content' ||
4476
+ key === 'xu-text' ||
4477
+ key === 'xu-html' ||
4478
+ key === 'xu-show' ||
4479
+ key === 'xu-panel-program' ||
4480
+ key === 'xu-teleport'
4481
+ );
4482
+ };
4483
+ func.runtime.render.get_string_renderer_tag_name = function (treeP) {
4484
+ switch (treeP?.kind) {
4485
+ case 'widget':
4486
+ case 'single_view':
4487
+ case 'multi_view':
4488
+ case 'panel':
4489
+ case 'teleport':
4490
+ return 'div';
4491
+ case 'placeholder':
4492
+ return null;
4493
+ case 'text':
4494
+ return null;
4495
+ default:
4496
+ return treeP?.tagName || 'div';
4497
+ }
4498
+ };
4499
+ func.runtime.render.get_tree_terminal_content = function (treeP) {
4500
+ const attributes = treeP?.attributes || {};
4501
+ if (typeof attributes['xu-html'] !== 'undefined' && attributes['xu-html'] !== null) {
4502
+ return {
4503
+ value: `${attributes['xu-html']}`,
4504
+ mode: 'html',
4505
+ };
4506
+ }
4507
+ if (typeof attributes['xu-content'] !== 'undefined' && attributes['xu-content'] !== null) {
4508
+ return {
4509
+ value: `${attributes['xu-content']}`,
4510
+ mode: 'html',
4511
+ };
4512
+ }
4513
+ if (typeof attributes['xu-text'] !== 'undefined' && attributes['xu-text'] !== null) {
4514
+ return {
4515
+ value: `${attributes['xu-text']}`,
4516
+ mode: 'text',
4517
+ };
4518
+ }
4519
+ if (treeP?.kind === 'text') {
4520
+ return {
4521
+ value: typeof treeP?.text !== 'undefined' && treeP?.text !== null ? `${treeP.text}` : `${treeP?.content || ''}`,
4522
+ mode: 'text',
4523
+ };
4524
+ }
4525
+ return null;
4526
+ };
4527
+ func.runtime.render.render_tree_terminal_content = function (treeP) {
4528
+ const terminal = func.runtime.render.get_tree_terminal_content(treeP);
4529
+ if (!terminal) {
4530
+ return null;
4531
+ }
4532
+ if (terminal.mode === 'html') {
4533
+ return terminal.value;
4534
+ }
4535
+ return func.runtime.render.escape_html(terminal.value);
4536
+ };
4537
+ func.runtime.render.get_widget_fallback_markup = function (treeP) {
4538
+ const widget_meta = treeP?.meta?.widget || {};
4539
+ const capability_state = widget_meta?.capabilities?.headless ? 'headless-capable' : 'browser-only';
4540
+ return `<!--xuda-widget:${func.runtime.render.escape_html(widget_meta.plugin_name || 'unknown')}:${capability_state}-->`;
4541
+ };
4542
+ func.runtime.render.get_tree_string_attributes = function (treeP, renderer_context) {
4543
+ const attributes = func.runtime.render.safe_clone_tree_value(treeP?.attributes || {});
4544
+ const attr_pairs = [];
4545
+ const keys = Object.keys(attributes);
4546
+
4547
+ for (let index = 0; index < keys.length; index++) {
4548
+ const key = keys[index];
4549
+ if (func.runtime.render.is_tree_control_attribute(key)) {
4550
+ continue;
4551
+ }
4552
+ const value = attributes[key];
4553
+ if (value === false || value === null || typeof value === 'undefined') {
4554
+ continue;
4555
+ }
4556
+ if (value === true) {
4557
+ attr_pairs.push(key);
4558
+ continue;
4559
+ }
4560
+ const normalized_value = typeof value === 'object' ? JSON.stringify(value) : `${value}`;
4561
+ attr_pairs.push(`${key}="${func.runtime.render.escape_html_attribute(normalized_value)}"`);
4562
+ }
4563
+
4564
+ attr_pairs.push(`data-xuda-kind="${func.runtime.render.escape_html_attribute(treeP?.kind || 'element')}"`);
4565
+ attr_pairs.push(`data-xuda-node-id="${func.runtime.render.escape_html_attribute(treeP?.id || treeP?.meta?.source_node_id || '')}"`);
4566
+ attr_pairs.push(`data-xuda-tree-id="${func.runtime.render.escape_html_attribute(treeP?.meta?.tree_id || '')}"`);
4567
+
4568
+ if (treeP?.kind === 'widget' && treeP?.meta?.widget) {
4569
+ attr_pairs.push(`data-xuda-widget="${func.runtime.render.escape_html_attribute(treeP.meta.widget.plugin_name || '')}"`);
4570
+ attr_pairs.push(`data-xuda-widget-method="${func.runtime.render.escape_html_attribute(treeP.meta.widget.method || '_default')}"`);
4571
+ attr_pairs.push(`data-xuda-widget-capability="${func.runtime.render.escape_html_attribute(treeP.meta.widget.capabilities?.headless ? 'headless' : 'browser')}"`);
4572
+ }
4573
+
4574
+ if (treeP?.kind === 'teleport' && treeP?.attributes?.['xu-teleport']) {
4575
+ attr_pairs.push(`data-xuda-teleport-target="${func.runtime.render.escape_html_attribute(treeP.attributes['xu-teleport'])}"`);
4576
+ }
4577
+
4578
+ if ((treeP?.meta?.controls?.xu_for !== null && treeP?.meta?.controls?.xu_for !== undefined) && !renderer_context?.strip_iteration_markers) {
4579
+ attr_pairs.push('data-xuda-xu-for="pending"');
4580
+ }
4581
+
4582
+ return attr_pairs.length ? ' ' + attr_pairs.join(' ') : '';
4583
+ };
4584
+ func.runtime.render.render_tree_children_to_string = async function (treeP, renderer_context) {
4585
+ if (!Array.isArray(treeP?.children) || !treeP.children.length) {
4586
+ return '';
4587
+ }
4588
+ let html = '';
4589
+ for (let index = 0; index < treeP.children.length; index++) {
4590
+ html += await func.runtime.render.render_tree_to_string(treeP.children[index], {
4591
+ ...renderer_context,
4592
+ parent_tree: treeP,
4593
+ });
4594
+ }
4595
+ return html;
4596
+ };
4597
+ func.runtime.render.render_tree_to_string = async function (treeP, renderer_context = {}) {
4598
+ if (!treeP) {
4599
+ return '';
4600
+ }
4601
+ if (Array.isArray(treeP)) {
4602
+ let html = '';
4603
+ for (let index = 0; index < treeP.length; index++) {
4604
+ html += await func.runtime.render.render_tree_to_string(treeP[index], renderer_context);
4605
+ }
4606
+ return html;
4607
+ }
4608
+
4609
+ const ensured_tree = await func.runtime.render.ensure_tree_node({
4610
+ SESSION_ID: renderer_context?.SESSION_ID,
4611
+ nodeP: treeP,
4612
+ paramsP: renderer_context?.paramsP,
4613
+ parent_infoP: renderer_context?.parent_infoP,
4614
+ keyP: renderer_context?.keyP,
4615
+ parent_nodeP: renderer_context?.parent_nodeP,
4616
+ });
4617
+
4618
+ if (!ensured_tree || !func.runtime.render.should_render_tree_node(ensured_tree)) {
4619
+ return '';
4620
+ }
4621
+
4622
+ if (ensured_tree.kind === 'placeholder') {
4623
+ if (renderer_context?.include_placeholders) {
4624
+ return `<!--xuda-placeholder:${func.runtime.render.escape_html(ensured_tree.id || '')}-->`;
4625
+ }
4626
+ return '';
4627
+ }
4628
+
4629
+ if (ensured_tree.kind === 'text') {
4630
+ return func.runtime.render.render_tree_terminal_content(ensured_tree) || '';
4631
+ }
4632
+
4633
+ const tag_name = func.runtime.render.get_string_renderer_tag_name(ensured_tree);
4634
+ if (!tag_name || tag_name.toLowerCase() === 'script') {
4635
+ return '';
4636
+ }
4637
+
4638
+ const attributes = func.runtime.render.get_tree_string_attributes(ensured_tree, renderer_context);
4639
+ const terminal_content = func.runtime.render.render_tree_terminal_content(ensured_tree);
4640
+ let children_html = terminal_content !== null ? terminal_content : await func.runtime.render.render_tree_children_to_string(ensured_tree, renderer_context);
4641
+
4642
+ if (ensured_tree.kind === 'widget' && !children_html) {
4643
+ children_html = func.runtime.render.get_widget_fallback_markup(ensured_tree);
4644
+ }
4645
+
4646
+ if (func.runtime.render.is_html_void_tag(tag_name)) {
4647
+ return `<${tag_name}${attributes}>`;
4648
+ }
4649
+
4650
+ return `<${tag_name}${attributes}>${children_html}</${tag_name}>`;
4651
+ };
4652
+ func.runtime.render.render_to_string = async function (options = {}) {
4653
+ const treeP = await func.runtime.render.ensure_tree_node({
4654
+ SESSION_ID: options.SESSION_ID,
4655
+ nodeP: options.treeP || options.nodeP,
4656
+ paramsP: options.paramsP,
4657
+ parent_infoP: options.parent_infoP,
4658
+ keyP: options.keyP,
4659
+ parent_nodeP: options.parent_nodeP,
4660
+ });
4661
+
4662
+ return await func.runtime.render.render_tree_to_string(treeP, options);
4663
+ };
4664
+ func.runtime.render.get_server_render_mode = function (options = {}) {
4665
+ const normalized = func.runtime.render.normalize_runtime_bootstrap({
4666
+ app_computing_mode: options.app_computing_mode,
4667
+ app_render_mode: options.app_render_mode,
4668
+ app_client_activation: options.app_client_activation,
4669
+ });
4670
+
4671
+ return normalized;
4672
+ };
4673
+ func.runtime.render.build_server_render_params = async function (options = {}) {
4674
+ const SESSION_ID = options.SESSION_ID;
4675
+ const prog_id = options.prog_id;
4676
+ const dsSessionP = options.dsSessionP;
4677
+ const _session = SESSION_OBJ?.[SESSION_ID] || {};
4678
+ const _ds = _session?.DS_GLB?.[dsSessionP] || {};
4679
+ const viewDoc = options.viewDoc || (await func.utils?.VIEWS_OBJ?.get?.(SESSION_ID, prog_id));
4680
+
4681
+ if (!viewDoc?.properties) {
4682
+ throw new Error(`view document not found for ${prog_id}`);
4683
+ }
4684
+
4685
+ const base_params = _ds?.screen_params ? func.runtime.render.safe_clone_tree_value(_ds.screen_params) : {};
4686
+ const screenId = options.screenId || base_params.screenId || `ssr_${prog_id}_${dsSessionP || '0'}`;
4687
+ const paramsP = {
4688
+ ...base_params,
4689
+ prog_id,
4690
+ sourceScreenP: null,
4691
+ $callingContainerP: null,
4692
+ triggerIdP: null,
4693
+ callingDataSource_objP: _ds,
4694
+ rowIdP: typeof options.rowIdP !== 'undefined' ? options.rowIdP : (_ds?.currentRecordId || null),
4695
+ renderType: viewDoc.properties?.renderType,
4696
+ parameters_obj_inP: options.parameters_obj_inP || base_params.parameters_obj_inP || options.parameters_raw_obj || {},
4697
+ source_functionP: options.source_functionP || base_params.source_functionP || 'render_string',
4698
+ is_panelP: false,
4699
+ screen_type: options.screen_type || base_params.screen_type || 'render_string',
4700
+ screenInfo: viewDoc,
4701
+ call_screen_propertiesP: base_params.call_screen_propertiesP,
4702
+ parentDataSourceNoP: typeof _ds?.parentDataSourceNo === 'undefined' || _ds?.parentDataSourceNo === null ? 0 : _ds.parentDataSourceNo,
4703
+ parameters_raw_obj: options.parameters_raw_obj || base_params.parameters_raw_obj || {},
4704
+ dsSessionP,
4705
+ screenId,
4706
+ containerIdP: base_params.containerIdP || `ssr_container_${screenId}`,
4707
+ };
4708
+
4709
+ if (_ds) {
4710
+ _ds.screen_params = paramsP;
4711
+ }
4712
+
4713
+ return paramsP;
4714
+ };
4715
+ func.runtime.render.build_prog_tree = async function (options = {}) {
4716
+ const SESSION_ID = options.SESSION_ID;
4717
+ const prog_id = options.prog_id;
4718
+ const viewDoc = options.viewDoc || (await func.utils?.VIEWS_OBJ?.get?.(SESSION_ID, prog_id));
4719
+
4720
+ if (!viewDoc?.progUi?.length) {
4721
+ throw new Error(`progUi not found for ${prog_id}`);
4722
+ }
4723
+
4724
+ const paramsP = options.paramsP || (await func.runtime.render.build_server_render_params({
4725
+ ...options,
4726
+ SESSION_ID,
4727
+ prog_id,
4728
+ viewDoc,
4729
+ }));
4730
+ const root_index = typeof options.root_index === 'number' ? options.root_index : 0;
4731
+ const root_node = func.runtime.render.safe_clone_tree_value(viewDoc.progUi[root_index]);
4732
+ const tree = await func.runtime.render.build_tree({
4733
+ SESSION_ID,
4734
+ nodeP: root_node,
4735
+ paramsP,
4736
+ });
4737
+
4738
+ return {
4739
+ tree,
4740
+ paramsP,
4741
+ viewDoc,
4742
+ };
4743
+ };
4744
+ func.runtime.render.build_ssr_payload = function (render_program, options = {}) {
4745
+ const runtime_profile = func.runtime.render.get_server_render_mode(options);
4746
+ return {
4747
+ contract: 'xuda.ssr.v1',
4748
+ prog_id: options.prog_id,
4749
+ screenId: render_program.paramsP.screenId,
4750
+ containerId: render_program.paramsP.containerIdP,
4751
+ app_computing_mode: runtime_profile.app_computing_mode,
4752
+ app_render_mode: runtime_profile.app_render_mode,
4753
+ app_client_activation: runtime_profile.app_client_activation,
4754
+ tree_contract: func.runtime.render.TREE_CONTRACT_VERSION,
4755
+ };
4756
+ };
4757
+ func.runtime.render.build_ssr_screen_html = function (html, render_program, options = {}) {
4758
+ const payload = func.runtime.render.build_ssr_payload(render_program, options);
4759
+ const screenId = func.runtime.render.escape_html_attribute(payload.screenId || '');
4760
+ const containerId = func.runtime.render.escape_html_attribute(payload.containerId || '');
4761
+ const activation = func.runtime.render.escape_html_attribute(payload.app_client_activation || 'takeover');
4762
+
4763
+ return `<div data-xuda-ssr-embed="true" class="xu_embed_div"><div id="${screenId}" class="xu_embed_container" data-xuda-ssr-screen="true" data-xuda-ssr-screen-id="${screenId}" data-xuda-activation="${activation}" style="display: contents;"><div id="${containerId}" data-xuda-ssr-root-frame="true" data-xuda-ssr-screen-id="${screenId}" data-xuda-activation="${activation}" style="display: contents;">${html}</div></div></div>`;
4764
+ };
4765
+ func.runtime.render.render_prog_to_string = async function (options = {}) {
4766
+ const render_program = await func.runtime.render.build_prog_tree(options);
4767
+ const html = await func.runtime.render.render_to_string({
4768
+ ...options,
4769
+ SESSION_ID: options.SESSION_ID,
4770
+ treeP: render_program.tree,
4771
+ paramsP: render_program.paramsP,
4772
+ });
4773
+ const ssr_payload = func.runtime.render.build_ssr_payload(render_program, options);
4774
+ const screen_html = func.runtime.render.build_ssr_screen_html(html, render_program, options);
4775
+
4776
+ return {
4777
+ prog_id: options.prog_id,
4778
+ dsSessionP: render_program.paramsP.dsSessionP,
4779
+ screenId: render_program.paramsP.screenId,
4780
+ html,
4781
+ screen_html,
4782
+ tree_json: func.runtime.render.serialize_tree(render_program.tree),
4783
+ paramsP: render_program.paramsP,
4784
+ ssr_payload,
4785
+ };
4786
+ };
3770
4787
  func.runtime = func.runtime || {};
3771
4788
  func.runtime.platform = func.runtime.platform || {};
3772
4789
 
@@ -7056,6 +8073,13 @@ func.utils.DOCS_OBJ = {};
7056
8073
  func.utils.DOCS_OBJ.get = async function (SESSION_ID, idP) {
7057
8074
  if (!idP || idP === '0') return;
7058
8075
 
8076
+ const normalize_runtime_doc = function (doc) {
8077
+ if (!doc || !func.runtime.program?.normalize_doc_for_runtime) {
8078
+ return doc;
8079
+ }
8080
+ return func.runtime.program.normalize_doc_for_runtime(doc);
8081
+ };
8082
+
7059
8083
  var _session = SESSION_OBJ[SESSION_ID];
7060
8084
  const _app_id = _session.app_id;
7061
8085
  if (!DOCS_OBJ[_app_id]) {
@@ -7077,7 +8101,8 @@ func.utils.DOCS_OBJ.get = async function (SESSION_ID, idP) {
7077
8101
  }
7078
8102
  let val = _session.project_data?.programs?.[idP];
7079
8103
  if (val) {
7080
- return (DOCS_OBJ[_app_id][idP] = val);
8104
+ DOCS_OBJ[_app_id][idP] = normalize_runtime_doc(val);
8105
+ return DOCS_OBJ[_app_id][idP];
7081
8106
  }
7082
8107
  }
7083
8108
 
@@ -7085,7 +8110,7 @@ func.utils.DOCS_OBJ.get = async function (SESSION_ID, idP) {
7085
8110
  const module = await func.common.get_module(SESSION_ID, `xuda-progs-loader-module.mjs`);
7086
8111
 
7087
8112
  if (idP !== 'system') {
7088
- DOCS_OBJ[_app_id][idP] = await module.DOCS_OBJ_get(SESSION_ID, idP);
8113
+ DOCS_OBJ[_app_id][idP] = normalize_runtime_doc(await module.DOCS_OBJ_get(SESSION_ID, idP));
7089
8114
  if (DOCS_OBJ[_app_id][idP] && xu_isEmpty(DOCS_OBJ[_app_id][idP])) {
7090
8115
  await func.utils.remove_cached_objects(SESSION_ID);
7091
8116
 
@@ -9156,6 +10181,15 @@ func.runtime.ui.ensure_embed_container = function (SESSION_ID) {
9156
10181
  const $root_element = func.runtime.ui.get_root_element(SESSION_ID);
9157
10182
  let $embed_container = func.runtime.ui.find_by_selector($root_element, `#embed_${SESSION_ID}`, true);
9158
10183
 
10184
+ if (!$embed_container.length) {
10185
+ const $ssr_embed_container = func.runtime.ui.find_by_selector($root_element, `[data-xuda-ssr-embed="true"]`, true);
10186
+ const ssr_embed_node = func.runtime.ui.get_first_node($ssr_embed_container);
10187
+ if (ssr_embed_node) {
10188
+ ssr_embed_node.id = 'embed_' + SESSION_ID;
10189
+ $embed_container = func.runtime.ui._wrap_matches([ssr_embed_node]);
10190
+ }
10191
+ }
10192
+
9159
10193
  if (!$embed_container.length) {
9160
10194
  const embed_node = document.createElement('div');
9161
10195
  embed_node.id = 'embed_' + SESSION_ID;
@@ -9191,12 +10225,44 @@ func.runtime.ui.get_root_tag_name = function () {
9191
10225
  }
9192
10226
  return root_tag_name;
9193
10227
  };
10228
+ func.runtime.ui.find_ssr_screen_host = function ($container, screenId, containerId) {
10229
+ const container_node = func.runtime.ui.get_first_node($container);
10230
+ if (!container_node || !screenId) {
10231
+ return null;
10232
+ }
10233
+
10234
+ const dialog_node =
10235
+ container_node.querySelector?.(`#${CSS?.escape ? CSS.escape(screenId) : screenId}`) ||
10236
+ container_node.querySelector?.(`[data-xuda-ssr-screen-id="${screenId}"]`);
10237
+ if (!dialog_node) {
10238
+ return null;
10239
+ }
10240
+
10241
+ const root_frame_node =
10242
+ (containerId ? dialog_node.querySelector?.(`#${CSS?.escape ? CSS.escape(containerId) : containerId}`) : null) ||
10243
+ dialog_node.querySelector?.('[data-xuda-ssr-root-frame="true"]');
10244
+ if (!root_frame_node) {
10245
+ return null;
10246
+ }
10247
+
10248
+ return {
10249
+ $dialogDiv: func.runtime.ui._wrap_matches([dialog_node]),
10250
+ $rootFrame: func.runtime.ui._wrap_matches([root_frame_node]),
10251
+ reused_ssr_host: true,
10252
+ };
10253
+ };
9194
10254
  func.runtime.ui.create_screen_host = function (SESSION_ID, screen_type, params, $callingContainerP, screenId) {
9195
10255
  var $dialogDiv;
9196
10256
  var $rootFrame;
10257
+ let reused_ssr_host = false;
9197
10258
 
9198
10259
  switch (screen_type) {
9199
10260
  case 'embed': {
10261
+ const ssr_host = func.runtime.ui.find_ssr_screen_host($callingContainerP, screenId, params?.containerIdP);
10262
+ if (ssr_host) {
10263
+ return ssr_host;
10264
+ }
10265
+
9200
10266
  const dialogNode = document.createElement('div');
9201
10267
  dialogNode.id = screenId;
9202
10268
  dialogNode.setAttribute('ui_engine', UI_FRAMEWORK_INSTALLED);
@@ -9246,6 +10312,7 @@ func.runtime.ui.create_screen_host = function (SESSION_ID, screen_type, params,
9246
10312
  return {
9247
10313
  $dialogDiv,
9248
10314
  $rootFrame,
10315
+ reused_ssr_host,
9249
10316
  };
9250
10317
  };
9251
10318
  func.runtime.ui.find_xu_ui_in_root = function (SESSION_ID, xu_ui_id) {
@@ -9810,6 +10877,11 @@ func.runtime.ui.build_container_xu_data = function (options) {
9810
10877
  func.runtime.ui.apply_container_meta = function ($div, options) {
9811
10878
  const div_node = func.runtime.ui.get_first_node($div);
9812
10879
  func.runtime.ui.set_attr(div_node, 'xu-ui-id', options.ui_id);
10880
+ func.runtime.ui.set_attr(div_node, 'data-xuda-kind', options.treeP?.kind || options.nodeP?.tagName || 'element');
10881
+ func.runtime.ui.set_attr(div_node, 'data-xuda-node-id', options.nodeP?.id || options.nodeP?.id_org || '');
10882
+ if (options.treeP?.meta?.tree_id !== null && typeof options.treeP?.meta?.tree_id !== 'undefined') {
10883
+ func.runtime.ui.set_attr(div_node, 'data-xuda-tree-id', options.treeP.meta.tree_id);
10884
+ }
9813
10885
  const xuData = func.runtime.ui.build_container_xu_data(options);
9814
10886
  if (options.parent_infoP?.iterate_info) {
9815
10887
  xuData.iterate_info = options.parent_infoP.iterate_info;
@@ -9904,6 +10976,38 @@ func.runtime.ui.create_container_element = function (div_typeP, attr_str, prop,
9904
10976
  }
9905
10977
  return func.runtime.ui.create_element(div, attr_str);
9906
10978
  };
10979
+ func.runtime.ui.find_hydration_candidate = function (options) {
10980
+ if (!func.runtime.render.is_hydration_mode(SESSION_OBJ?.[options.SESSION_ID])) {
10981
+ return null;
10982
+ }
10983
+ if (!func.runtime.render.should_use_ssr_payload(options.SESSION_ID, options.paramsP)) {
10984
+ return null;
10985
+ }
10986
+ if (options.is_placeholder || !options.treeP?.meta?.tree_id) {
10987
+ return null;
10988
+ }
10989
+
10990
+ const append_node = func.runtime.ui.get_first_node(options.$appendTo || options.$container);
10991
+ if (!append_node) {
10992
+ return null;
10993
+ }
10994
+
10995
+ const children = func.runtime.ui.get_children(append_node);
10996
+ for (let index = 0; index < children.length; index++) {
10997
+ const child = children[index];
10998
+ if (child?.__xuda_hydration_claimed) {
10999
+ continue;
11000
+ }
11001
+ if (func.runtime.ui.get_attr(child, 'data-xuda-tree-id') !== `${options.treeP.meta.tree_id}`) {
11002
+ continue;
11003
+ }
11004
+ child.__xuda_hydration_claimed = true;
11005
+ func.runtime.ui.set_attr(child, 'data-xuda-client-activation', 'hydrate');
11006
+ return child;
11007
+ }
11008
+
11009
+ return null;
11010
+ };
9907
11011
  func.runtime.ui.build_xu_ui_id_seed = function (nodeP, dsSessionP, key_path, currentRecordId) {
9908
11012
  const nodeId = nodeP.xu_tree_id || nodeP.id;
9909
11013
  const elem_key = `${nodeId}-${key_path}-${currentRecordId}`;
@@ -9956,7 +11060,11 @@ func.runtime.ui.create_container = async function (options) {
9956
11060
  try {
9957
11061
  const key_path = func.runtime.ui.build_container_key_path(container_xu_data, options.keyP, options.parent_infoP, options.nodeP, options.parent_nodeP);
9958
11062
  const elem_key = `${options.nodeP.xu_tree_id || options.nodeP.id}-${key_path}-${currentRecordId}`;
9959
- const $div = func.runtime.ui.create_container_element(options.div_typeP, options.attr_str, options.prop, options.nodeP, $appendTo);
11063
+ const hydration_candidate = func.runtime.ui.find_hydration_candidate({
11064
+ ...options,
11065
+ $appendTo,
11066
+ });
11067
+ const $div = hydration_candidate || func.runtime.ui.create_container_element(options.div_typeP, options.attr_str, options.prop, options.nodeP, $appendTo);
9960
11068
  const new_ui_id = await func.runtime.ui.generate_xu_ui_id(options.SESSION_ID, options.nodeP, options.$container, options.paramsP, options.keyP, {
9961
11069
  container_xu_data,
9962
11070
  currentRecordId,
@@ -9982,9 +11090,10 @@ func.runtime.ui.create_container = async function (options) {
9982
11090
  parent_infoP: options.parent_infoP,
9983
11091
  is_placeholder: options.is_placeholder,
9984
11092
  classP: options.classP,
11093
+ treeP: options.treeP,
9985
11094
  });
9986
11095
 
9987
- if (options.div_typeP !== 'svg') {
11096
+ if (!hydration_candidate && options.div_typeP !== 'svg') {
9988
11097
  func.runtime.ui.append_to($div, $appendTo);
9989
11098
  }
9990
11099
  return $div;
@@ -12665,9 +13774,10 @@ func.runtime.ui.init_screen = async function (options) {
12665
13774
 
12666
13775
  const _session = SESSION_OBJ[SESSION_ID];
12667
13776
  const screenInfo = structuredClone(screen_ret);
13777
+ const ssr_payload = func.runtime.render.should_use_ssr_payload(SESSION_ID, { prog_id }) ? func.runtime.render.get_ssr_payload(_session) : null;
12668
13778
 
12669
13779
  const screen_type = source_functionP?.split('_')?.[1];
12670
- const screenId = (glb.screen_num++).toString();
13780
+ const screenId = ssr_payload?.screenId || (glb.screen_num++).toString();
12671
13781
 
12672
13782
  if (SCREEN_BLOCKER_OBJ[prog_id + (sourceScreenP ? '_' + sourceScreenP : '')]) {
12673
13783
  const wait_for_SCREEN_BLOCKER_release = function () {
@@ -12709,6 +13819,8 @@ func.runtime.ui.init_screen = async function (options) {
12709
13819
  call_screen_propertiesP,
12710
13820
  parentDataSourceNoP: _session.DS_GLB?.[callingDataSource_objP?.dsSession]?.dsSession || callingDataSource_objP?.parentDataSourceNo || 0,
12711
13821
  parameters_raw_obj,
13822
+ containerIdP: ssr_payload?.containerId || null,
13823
+ ssr_payload,
12712
13824
  };
12713
13825
 
12714
13826
  const screen_host = func.runtime.ui.create_screen_host(SESSION_ID, screen_type, params, $callingContainerP, screenId);
@@ -12741,6 +13853,10 @@ func.runtime.ui.init_screen = async function (options) {
12741
13853
  func.runtime.ui.set_style($rootFrame, 'display', 'contents');
12742
13854
  }
12743
13855
 
13856
+ if (screen_host.reused_ssr_host && func.runtime.render.is_takeover_mode(_session)) {
13857
+ func.runtime.ui.empty($rootFrame);
13858
+ }
13859
+
12744
13860
  if (!is_panelP) func.UI.utils.indicator.screen.busy();
12745
13861
 
12746
13862
  const ret = await func.datasource.create(
@@ -12782,7 +13898,24 @@ func.runtime.ui.init_screen = async function (options) {
12782
13898
  }
12783
13899
  let node = structuredClone(viewDoc.progUi);
12784
13900
  if (!node.length) return console.warn('ui node empty');
12785
- const ret_render_$container = await func.runtime.render.render_ui_tree(SESSION_ID, $rootFrame, node[0], null, params, jobNoP, null, null, null, null, null, $rootFrame);
13901
+ const root_tree = await func.runtime.render.build_tree({
13902
+ SESSION_ID,
13903
+ nodeP: node[0],
13904
+ paramsP: params,
13905
+ });
13906
+ const ret_render_$container = await func.runtime.render.render_tree(root_tree, {
13907
+ SESSION_ID,
13908
+ $container: $rootFrame,
13909
+ parent_infoP: null,
13910
+ paramsP: params,
13911
+ jobNoP,
13912
+ is_skeleton: null,
13913
+ keyP: null,
13914
+ refreshed_ds: null,
13915
+ parent_nodeP: null,
13916
+ check_existP: null,
13917
+ $root_container: $rootFrame,
13918
+ });
12786
13919
 
12787
13920
  if (!is_panelP) func.UI.utils.indicator.screen.normal();
12788
13921
 
@@ -13207,6 +14340,7 @@ func.runtime.ui.render_single_view_node = async function (options) {
13207
14340
  parent_infoP: options.parent_infoP,
13208
14341
  jobNoP: options.jobNoP,
13209
14342
  keyP: options.keyP,
14343
+ treeP: options.treeP,
13210
14344
  parent_nodeP: options.parent_nodeP,
13211
14345
  prop: options.prop,
13212
14346
  div_typeP: 'div',
@@ -13335,6 +14469,7 @@ func.runtime.ui.render_panel_node = async function (options) {
13335
14469
  parent_infoP: options.parent_infoP,
13336
14470
  jobNoP: options.jobNoP,
13337
14471
  keyP: options.keyP,
14472
+ treeP: options.treeP,
13338
14473
  parent_nodeP: options.parent_nodeP,
13339
14474
  prop: options.prop,
13340
14475
  $appendToP: $wrapper,
@@ -13664,6 +14799,14 @@ func.runtime.ui.screen_loading_done = async function (options) {
13664
14799
  });
13665
14800
 
13666
14801
  const _session = SESSION_OBJ[options.SESSION_ID];
14802
+ if (func.runtime.render.should_use_ssr_payload(options.SESSION_ID, options.paramsP)) {
14803
+ const root_node = func.runtime.ui.get_root_node(options.SESSION_ID);
14804
+ if (root_node) {
14805
+ func.runtime.ui.set_attr(root_node, 'data-xuda-client-activation', _session.opt.app_client_activation || 'none');
14806
+ func.runtime.ui.set_attr(root_node, 'data-xuda-ssr-status', _session.opt.app_client_activation === 'hydrate' ? 'hydrated' : 'taken-over');
14807
+ }
14808
+ func.runtime.render.mark_ssr_payload_consumed(options.SESSION_ID);
14809
+ }
13667
14810
  func.events.delete_job(options.SESSION_ID, options.jobNoP);
13668
14811
  func.UI.utils.screen_blocker(false, options.paramsP.prog_id + (options.paramsP.sourceScreenP ? '_' + options.paramsP.sourceScreenP : ''));
13669
14812
  if (_session.prog_id === options.paramsP.prog_id) {
@@ -15506,6 +16649,9 @@ func.runtime.render.get_screen_context = function (SESSION_ID, $container, param
15506
16649
  };
15507
16650
  func.runtime.render.get_node_attributes = function (nodeP) {
15508
16651
  try {
16652
+ if (func.runtime.render.is_tree_node?.(nodeP)) {
16653
+ return nodeP.attributes;
16654
+ }
15509
16655
  return nodeP?.attributes;
15510
16656
  } catch (error) {
15511
16657
  return undefined;
@@ -15623,6 +16769,7 @@ func.runtime.render.prepare_draw_context = async function (options) {
15623
16769
  parent_infoP: options.parent_infoP,
15624
16770
  jobNoP: options.jobNoP,
15625
16771
  keyP: options.keyP,
16772
+ treeP: options.treeP,
15626
16773
  parent_nodeP: options.parent_nodeP,
15627
16774
  prop: options.prop,
15628
16775
  div_typeP: options.element,
@@ -15645,6 +16792,7 @@ func.runtime.render.prepare_draw_context = async function (options) {
15645
16792
  parent_infoP: options.parent_infoP,
15646
16793
  jobNoP: options.jobNoP,
15647
16794
  keyP: options.keyP,
16795
+ treeP: options.treeP,
15648
16796
  parent_nodeP: options.parent_nodeP,
15649
16797
  prop: options.prop,
15650
16798
  div_typeP: options.element,
@@ -16501,11 +17649,16 @@ func.runtime.widgets.render_node = async function (options) {
16501
17649
  parent_infoP: options.parent_infoP,
16502
17650
  jobNoP: options.jobNoP,
16503
17651
  keyP: options.keyP,
17652
+ treeP: options.treeP,
16504
17653
  parent_nodeP: options.parent_nodeP,
16505
17654
  prop: options.prop,
16506
17655
  classP: 'widget_wrapper',
16507
17656
  });
16508
17657
 
17658
+ if (func.runtime.render.is_hydration_mode(SESSION_OBJ?.[options.SESSION_ID]) && func.runtime.render.should_use_ssr_payload(options.SESSION_ID, options.paramsP)) {
17659
+ func.runtime.ui.empty($div);
17660
+ }
17661
+
16509
17662
  const widget_context = func.runtime.widgets.create_context(options.SESSION_ID, options.paramsP, options.prop);
16510
17663
  const { plugin_name, method, propsP, plugin: _plugin } = widget_context;
16511
17664
  const report_error = function (descP, warn) {
@@ -16608,9 +17761,370 @@ func.runtime.widgets = func.runtime.widgets || {};
16608
17761
 
16609
17762
  // Browser-only special node renderers live here so the core render tree can stay focused.
16610
17763
 
17764
+ const normalize_runtime_tag_name = function (tag_name) {
17765
+ return `${tag_name || ''}`.trim().toLowerCase();
17766
+ };
17767
+
17768
+ const get_runtime_node_attributes = function (nodeP) {
17769
+ if (!nodeP?.attributes || typeof nodeP.attributes !== 'object') {
17770
+ return {};
17771
+ }
17772
+
17773
+ return nodeP.attributes;
17774
+ };
17775
+
17776
+ const get_runtime_node_content = function (nodeP) {
17777
+ if (typeof nodeP?.content === 'string') {
17778
+ return nodeP.content;
17779
+ }
17780
+
17781
+ if (typeof nodeP?.text === 'string') {
17782
+ return nodeP.text;
17783
+ }
17784
+
17785
+ if (!Array.isArray(nodeP?.children)) {
17786
+ return '';
17787
+ }
17788
+
17789
+ return nodeP.children
17790
+ .map(function (child) {
17791
+ if (typeof child === 'string') {
17792
+ return child;
17793
+ }
17794
+ if (typeof child?.content === 'string') {
17795
+ return child.content;
17796
+ }
17797
+ if (typeof child?.text === 'string') {
17798
+ return child.text;
17799
+ }
17800
+ return '';
17801
+ })
17802
+ .join('');
17803
+ };
17804
+
17805
+ const get_runtime_asset_key = function (options, tag_name) {
17806
+ const source_node = options.treeP || options.nodeP || {};
17807
+ const parts = [
17808
+ 'xuda-html-asset',
17809
+ options.paramsP?.prog_id || '',
17810
+ tag_name,
17811
+ source_node.id || source_node.id_org || '',
17812
+ typeof options.keyP === 'undefined' || options.keyP === null ? '' : `${options.keyP}`,
17813
+ ].filter(function (part) {
17814
+ return `${part || ''}`.trim() !== '';
17815
+ });
17816
+
17817
+ return parts.join(':');
17818
+ };
17819
+
17820
+ const get_runtime_asset_signature = function (attributes, content) {
17821
+ const normalized_attributes = {};
17822
+ const attr_keys = Object.keys(attributes || {}).sort();
17823
+
17824
+ for (let index = 0; index < attr_keys.length; index++) {
17825
+ const key = attr_keys[index];
17826
+ normalized_attributes[key] = attributes[key];
17827
+ }
17828
+
17829
+ return JSON.stringify({
17830
+ attributes: normalized_attributes,
17831
+ content: `${content || ''}`,
17832
+ });
17833
+ };
17834
+
17835
+ const escape_runtime_asset_selector_value = function (value) {
17836
+ const win = func.runtime.platform.get_window?.();
17837
+ if (win?.CSS?.escape) {
17838
+ return win.CSS.escape(value);
17839
+ }
17840
+
17841
+ return `${value || ''}`.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
17842
+ };
17843
+
17844
+ const find_runtime_head_asset = function (head, tag_name, asset_key) {
17845
+ if (!head?.querySelector || !asset_key) {
17846
+ return null;
17847
+ }
17848
+
17849
+ return head.querySelector(`${tag_name}[data-xuda-asset-key="${escape_runtime_asset_selector_value(asset_key)}"]`);
17850
+ };
17851
+
17852
+ const find_runtime_head_asset_by_attr = function (head, tag_name, attr_name, attr_value) {
17853
+ if (!head?.querySelectorAll || !attr_name || !attr_value) {
17854
+ return null;
17855
+ }
17856
+
17857
+ const candidates = Array.from(head.querySelectorAll(tag_name));
17858
+ return (
17859
+ candidates.find(function (candidate) {
17860
+ return candidate.getAttribute(attr_name) === attr_value || candidate[attr_name] === attr_value;
17861
+ }) || null
17862
+ );
17863
+ };
17864
+
17865
+ const wait_for_runtime_asset_load = function (node) {
17866
+ if (!node?.addEventListener) {
17867
+ return Promise.resolve(node);
17868
+ }
17869
+
17870
+ if (node.getAttribute?.('data-xuda-loaded') === 'true') {
17871
+ return Promise.resolve(node);
17872
+ }
17873
+
17874
+ if (!node.getAttribute?.('data-xuda-asset-key')) {
17875
+ return Promise.resolve(node);
17876
+ }
17877
+
17878
+ const tag_name = normalize_runtime_tag_name(node.tagName);
17879
+ if (tag_name !== 'script' && !(tag_name === 'link' && normalize_runtime_tag_name(node.getAttribute?.('rel')) === 'stylesheet')) {
17880
+ return Promise.resolve(node);
17881
+ }
17882
+
17883
+ return new Promise(function (resolve) {
17884
+ const done = function () {
17885
+ node.setAttribute?.('data-xuda-loaded', 'true');
17886
+ resolve(node);
17887
+ };
17888
+
17889
+ node.addEventListener('load', done, { once: true });
17890
+ node.addEventListener('error', done, { once: true });
17891
+ });
17892
+ };
17893
+
17894
+ const remove_runtime_head_asset = function (node) {
17895
+ if (node?.parentNode?.removeChild) {
17896
+ node.parentNode.removeChild(node);
17897
+ }
17898
+ };
17899
+
17900
+ const apply_runtime_asset_metadata = function (node, asset_key, signature) {
17901
+ if (!node?.setAttribute) {
17902
+ return node;
17903
+ }
17904
+
17905
+ if (asset_key) {
17906
+ node.setAttribute('data-xuda-asset-key', asset_key);
17907
+ }
17908
+ node.setAttribute('data-xuda-asset-signature', signature);
17909
+ return node;
17910
+ };
17911
+
17912
+ const create_runtime_head_element = function (doc, tag_name, attributes, asset_key, signature) {
17913
+ const node = doc.createElement(tag_name);
17914
+ apply_runtime_asset_metadata(node, asset_key, signature);
17915
+ func.runtime.platform.apply_element_attributes(node, attributes);
17916
+ return node;
17917
+ };
17918
+
17919
+ const queue_runtime_html_script_task = function (task) {
17920
+ const queue =
17921
+ func.runtime.render._html_script_queue ||
17922
+ (func.runtime.render._html_script_queue = {
17923
+ items: [],
17924
+ scheduled: false,
17925
+ running: false,
17926
+ });
17927
+
17928
+ queue.items.push(task);
17929
+
17930
+ if (queue.scheduled || queue.running) {
17931
+ return;
17932
+ }
17933
+
17934
+ queue.scheduled = true;
17935
+
17936
+ const drain_queue = async function () {
17937
+ if (queue.running) {
17938
+ return;
17939
+ }
17940
+
17941
+ queue.running = true;
17942
+ queue.scheduled = false;
17943
+
17944
+ try {
17945
+ while (queue.items.length) {
17946
+ const next_task = queue.items.shift();
17947
+ await next_task();
17948
+ }
17949
+ } catch (error) {
17950
+ console.error(error);
17951
+ } finally {
17952
+ queue.running = false;
17953
+ if (queue.items.length && !queue.scheduled) {
17954
+ queue.scheduled = true;
17955
+ schedule_runtime_html_script_queue();
17956
+ }
17957
+ }
17958
+ };
17959
+
17960
+ const schedule_runtime_html_script_queue = function () {
17961
+ const win = func.runtime.platform.get_window?.();
17962
+ const run_later = function () {
17963
+ setTimeout(function () {
17964
+ drain_queue();
17965
+ }, 0);
17966
+ };
17967
+
17968
+ if (win?.requestAnimationFrame) {
17969
+ win.requestAnimationFrame(function () {
17970
+ win.requestAnimationFrame(run_later);
17971
+ });
17972
+ return;
17973
+ }
17974
+
17975
+ run_later();
17976
+ };
17977
+
17978
+ schedule_runtime_html_script_queue();
17979
+ };
17980
+
17981
+ const upsert_runtime_head_element = async function (options) {
17982
+ const doc = func.runtime.platform.get_document?.();
17983
+ const head = doc?.head;
17984
+ const tag_name = normalize_runtime_tag_name(options.tag_name);
17985
+
17986
+ if (!doc?.createElement || !head?.appendChild || !tag_name) {
17987
+ return null;
17988
+ }
17989
+
17990
+ const asset_key = options.asset_key || '';
17991
+ const signature = options.signature || '';
17992
+ const attributes = options.attributes || {};
17993
+ const content = typeof options.content === 'string' ? options.content : '';
17994
+
17995
+ const existing_by_key = find_runtime_head_asset(head, tag_name, asset_key);
17996
+ if (existing_by_key && existing_by_key.getAttribute('data-xuda-asset-signature') === signature) {
17997
+ return options.await_load ? await wait_for_runtime_asset_load(existing_by_key) : existing_by_key;
17998
+ }
17999
+
18000
+ if (existing_by_key) {
18001
+ remove_runtime_head_asset(existing_by_key);
18002
+ }
18003
+
18004
+ if (options.find_existing_attr?.name && options.find_existing_attr?.value) {
18005
+ const existing_by_attr = find_runtime_head_asset_by_attr(head, tag_name, options.find_existing_attr.name, options.find_existing_attr.value);
18006
+ if (existing_by_attr) {
18007
+ apply_runtime_asset_metadata(existing_by_attr, asset_key, signature);
18008
+ existing_by_attr.setAttribute?.('data-xuda-loaded', 'true');
18009
+ return existing_by_attr;
18010
+ }
18011
+ }
18012
+
18013
+ const node = create_runtime_head_element(doc, tag_name, attributes, asset_key, signature);
18014
+
18015
+ if (tag_name === 'script' && attributes.src && !Object.prototype.hasOwnProperty.call(attributes, 'async')) {
18016
+ const script_type = normalize_runtime_tag_name(attributes.type);
18017
+ if (script_type !== 'module') {
18018
+ node.async = false;
18019
+ }
18020
+ }
18021
+
18022
+ if (tag_name === 'style' || (tag_name === 'script' && !attributes.src)) {
18023
+ node.textContent = content;
18024
+ }
18025
+
18026
+ head.appendChild(node);
18027
+
18028
+ if (tag_name !== 'script' && tag_name !== 'link') {
18029
+ node.setAttribute('data-xuda-loaded', 'true');
18030
+ }
18031
+
18032
+ return options.await_load ? await wait_for_runtime_asset_load(node) : node;
18033
+ };
18034
+
18035
+ const render_runtime_html_asset = async function (options) {
18036
+ if (options.is_skeleton) {
18037
+ return options.$container;
18038
+ }
18039
+
18040
+ const nodeP = options.treeP || options.nodeP || {};
18041
+ const tag_name = normalize_runtime_tag_name(nodeP.tagName);
18042
+ const attributes = { ...get_runtime_node_attributes(nodeP) };
18043
+ const content = get_runtime_node_content(nodeP);
18044
+ const asset_key = get_runtime_asset_key(options, tag_name);
18045
+ const signature = get_runtime_asset_signature(attributes, content);
18046
+
18047
+ switch (tag_name) {
18048
+ case 'title':
18049
+ func.runtime.platform.set_title(content);
18050
+ return options.$container;
18051
+ case 'style':
18052
+ await upsert_runtime_head_element({
18053
+ tag_name,
18054
+ attributes,
18055
+ content,
18056
+ asset_key,
18057
+ signature,
18058
+ });
18059
+ return options.$container;
18060
+ case 'meta':
18061
+ if (!Object.keys(attributes).length) {
18062
+ return options.$container;
18063
+ }
18064
+ await upsert_runtime_head_element({
18065
+ tag_name,
18066
+ attributes,
18067
+ asset_key,
18068
+ signature,
18069
+ });
18070
+ return options.$container;
18071
+ case 'link': {
18072
+ const href = `${attributes.href || ''}`.trim();
18073
+ if (!href) {
18074
+ return options.$container;
18075
+ }
18076
+ await upsert_runtime_head_element({
18077
+ tag_name,
18078
+ attributes,
18079
+ asset_key,
18080
+ signature,
18081
+ find_existing_attr: {
18082
+ name: 'href',
18083
+ value: href,
18084
+ },
18085
+ await_load: normalize_runtime_tag_name(attributes.rel) === 'stylesheet',
18086
+ });
18087
+ return options.$container;
18088
+ }
18089
+ case 'script': {
18090
+ const src = `${attributes.src || ''}`.trim();
18091
+ if (!src && !content.trim()) {
18092
+ return options.$container;
18093
+ }
18094
+ queue_runtime_html_script_task(async function () {
18095
+ await upsert_runtime_head_element({
18096
+ tag_name,
18097
+ attributes,
18098
+ content,
18099
+ asset_key,
18100
+ signature,
18101
+ find_existing_attr: src
18102
+ ? {
18103
+ name: 'src',
18104
+ value: src,
18105
+ }
18106
+ : null,
18107
+ await_load: !!src,
18108
+ });
18109
+ });
18110
+ return options.$container;
18111
+ }
18112
+ default:
18113
+ return null;
18114
+ }
18115
+ };
18116
+
16611
18117
  func.runtime.render.render_special_node = async function (options) {
16612
- if (options.nodeP.content && options.nodeP.attributes) {
16613
- options.nodeP.attributes['xu-content'] = options.nodeP.content;
18118
+ const treeP = options.treeP || null;
18119
+ const nodeP = options.nodeP || func.runtime.render.get_tree_source_node(treeP);
18120
+ const render_tag_name = treeP?.tagName || nodeP?.tagName;
18121
+ const normalized_render_tag_name = normalize_runtime_tag_name(render_tag_name);
18122
+ const is_native_html_asset = ['title', 'style', 'meta', 'link', 'script'].includes(normalized_render_tag_name);
18123
+
18124
+ if (!is_native_html_asset && treeP?.content && nodeP?.attributes) {
18125
+ nodeP.attributes['xu-content'] = treeP.content;
18126
+ } else if (!is_native_html_asset && nodeP?.content && nodeP.attributes) {
18127
+ nodeP.attributes['xu-content'] = nodeP.content;
16614
18128
  }
16615
18129
 
16616
18130
  const renderers = {
@@ -16620,6 +18134,7 @@ func.runtime.render.render_special_node = async function (options) {
16620
18134
  SESSION_ID: options.SESSION_ID,
16621
18135
  $container: options.$container,
16622
18136
  $root_container: options.$root_container,
18137
+ treeP,
16623
18138
  nodeP: options.nodeP,
16624
18139
  parent_infoP: options.parent_infoP,
16625
18140
  paramsP: options.paramsP,
@@ -16636,6 +18151,7 @@ func.runtime.render.render_special_node = async function (options) {
16636
18151
  SESSION_ID: options.SESSION_ID,
16637
18152
  $container: options.$container,
16638
18153
  $root_container: options.$root_container,
18154
+ treeP,
16639
18155
  nodeP: options.nodeP,
16640
18156
  parent_infoP: options.parent_infoP,
16641
18157
  paramsP: options.paramsP,
@@ -16655,6 +18171,7 @@ func.runtime.render.render_special_node = async function (options) {
16655
18171
  SESSION_ID: options.SESSION_ID,
16656
18172
  $container: options.$container,
16657
18173
  $root_container: options.$root_container,
18174
+ treeP,
16658
18175
  nodeP: options.nodeP,
16659
18176
  parent_infoP: options.parent_infoP,
16660
18177
  paramsP: options.paramsP,
@@ -16671,6 +18188,7 @@ func.runtime.render.render_special_node = async function (options) {
16671
18188
  SESSION_ID: options.SESSION_ID,
16672
18189
  $container: options.$container,
16673
18190
  $root_container: options.$root_container,
18191
+ treeP,
16674
18192
  nodeP: options.nodeP,
16675
18193
  parent_infoP: options.parent_infoP,
16676
18194
  paramsP: options.paramsP,
@@ -16682,9 +18200,24 @@ func.runtime.render.render_special_node = async function (options) {
16682
18200
  refreshed_ds: options.refreshed_ds,
16683
18201
  });
16684
18202
  },
18203
+ title: async function () {
18204
+ return await render_runtime_html_asset(options);
18205
+ },
18206
+ style: async function () {
18207
+ return await render_runtime_html_asset(options);
18208
+ },
18209
+ meta: async function () {
18210
+ return await render_runtime_html_asset(options);
18211
+ },
18212
+ link: async function () {
18213
+ return await render_runtime_html_asset(options);
18214
+ },
18215
+ script: async function () {
18216
+ return await render_runtime_html_asset(options);
18217
+ },
16685
18218
  };
16686
18219
 
16687
- const renderer = renderers[options.nodeP.tagName];
18220
+ const renderer = renderers[normalized_render_tag_name];
16688
18221
  if (!renderer) {
16689
18222
  return { handled: false };
16690
18223
  }
@@ -16798,8 +18331,9 @@ func.runtime.widgets = func.runtime.widgets || {};
16798
18331
  // Browser-only render tree entrypoints live here so draw/cache helpers can stay focused.
16799
18332
 
16800
18333
  func.runtime.render.create_tree_runtime = function (options) {
18334
+ const render_node = options.nodeP || func.runtime.render.get_tree_source_node(options.treeP);
16801
18335
  const render_context = func.runtime.render.get_screen_context(options.SESSION_ID, options.$container, options.paramsP, options.is_skeleton);
16802
- const prop = func.runtime.render.get_node_attributes(options.nodeP);
18336
+ const prop = func.runtime.render.get_node_attributes(options.treeP || render_node);
16803
18337
  const is_mobile = render_context.is_mobile ? true : false;
16804
18338
  const hover_handlers = func.runtime.render.create_hover_handlers({
16805
18339
  SESSION_ID: options.SESSION_ID,
@@ -16814,13 +18348,22 @@ func.runtime.render.create_tree_runtime = function (options) {
16814
18348
  return await func.runtime.ui.close_modal_session(options.SESSION_ID, modal_id);
16815
18349
  };
16816
18350
  const iterate_child = async function ($divP, nodeP, parent_infoP, $root_container, before_record_function) {
18351
+ const child_tree = await func.runtime.render.ensure_tree_node({
18352
+ SESSION_ID: options.SESSION_ID,
18353
+ nodeP: nodeP || options.treeP || render_node,
18354
+ parent_infoP,
18355
+ paramsP: options.paramsP,
18356
+ keyP: options.keyP,
18357
+ parent_nodeP: render_node,
18358
+ pathP: options.treeP?.meta?.path || [],
18359
+ });
16817
18360
  return await func.runtime.render.iterate_children({
16818
18361
  $divP,
16819
- nodeP,
18362
+ nodeP: child_tree,
16820
18363
  is_mobile,
16821
18364
  before_record_function,
16822
18365
  render_child: async function (key, child) {
16823
- await options.render_child($divP, child, parent_infoP, key, nodeP, $root_container);
18366
+ await options.render_child($divP, child, parent_infoP, key, render_node, $root_container);
16824
18367
  },
16825
18368
  });
16826
18369
  };
@@ -16850,7 +18393,7 @@ func.runtime.render.draw_node = async function (options) {
16850
18393
  check_existP: options.check_existP,
16851
18394
  $root_container: options.$root_container,
16852
18395
  prop: options.prop,
16853
- element: options.nodeP.tagName,
18396
+ element: options.treeP?.tagName || options.nodeP.tagName,
16854
18397
  hover_handlers: options.hover_handlers,
16855
18398
  include_hover_click: options.include_hover_click,
16856
18399
  iterate_child: options.iterate_child,
@@ -16873,21 +18416,33 @@ func.runtime.render.draw_node = async function (options) {
16873
18416
  nodeP: options.nodeP,
16874
18417
  });
16875
18418
  };
16876
- func.runtime.render.render_ui_tree = async function (SESSION_ID, $container, nodeP, parent_infoP, paramsP, jobNoP, is_skeleton, keyP, refreshed_ds, parent_nodeP, check_existP, $root_container) {
16877
- if (!nodeP) return;
16878
- const perf_end = func.runtime?.perf?.start?.(SESSION_ID, 'render_ui_tree');
16879
- func.runtime?.perf?.increment_map?.(SESSION_ID, 'render_node_counts', nodeP.id || nodeP.id_org || nodeP.tagName || 'unknown');
18419
+ func.runtime.render.render_tree = async function (treeP, renderer_context) {
18420
+ if (!treeP) return;
18421
+ const nodeP = func.runtime.render.get_tree_source_node(treeP);
18422
+ const perf_end = func.runtime?.perf?.start?.(renderer_context.SESSION_ID, 'render_ui_tree');
18423
+ func.runtime?.perf?.increment_map?.(renderer_context.SESSION_ID, 'render_node_counts', treeP.id || nodeP?.id || nodeP?.id_org || treeP.tagName || 'unknown');
16880
18424
  try {
16881
18425
  const tree_runtime = func.runtime.render.create_tree_runtime({
16882
- SESSION_ID,
16883
- $container,
18426
+ SESSION_ID: renderer_context.SESSION_ID,
18427
+ $container: renderer_context.$container,
18428
+ treeP,
16884
18429
  nodeP,
16885
- parent_infoP,
16886
- paramsP,
16887
- jobNoP,
16888
- is_skeleton,
18430
+ parent_infoP: renderer_context.parent_infoP,
18431
+ paramsP: renderer_context.paramsP,
18432
+ jobNoP: renderer_context.jobNoP,
18433
+ is_skeleton: renderer_context.is_skeleton,
18434
+ keyP: renderer_context.keyP,
16889
18435
  render_child: async function ($divP, child, parent_infoP, key, parentNodeP, rootContainerP) {
16890
- await func.runtime.render.render_ui_tree(SESSION_ID, $divP, child, parent_infoP, paramsP, jobNoP, is_skeleton, key, null, parentNodeP, null, rootContainerP);
18436
+ await func.runtime.render.render_tree(child, {
18437
+ ...renderer_context,
18438
+ $container: $divP,
18439
+ parent_infoP,
18440
+ keyP: key,
18441
+ refreshed_ds: null,
18442
+ parent_nodeP: parentNodeP,
18443
+ check_existP: null,
18444
+ $root_container: rootContainerP,
18445
+ });
16891
18446
  },
16892
18447
  });
16893
18448
  const render_context = tree_runtime.render_context;
@@ -16899,23 +18454,24 @@ func.runtime.render.render_ui_tree = async function (SESSION_ID, $container, nod
16899
18454
  const iterate_child = tree_runtime.iterate_child;
16900
18455
 
16901
18456
  func.runtime.render.log_tree_debug({
16902
- SESSION_ID,
16903
- paramsP,
18457
+ SESSION_ID: renderer_context.SESSION_ID,
18458
+ paramsP: renderer_context.paramsP,
16904
18459
  nodeP,
16905
18460
  _ds,
16906
18461
  });
16907
18462
  const special_render = await func.runtime.render.render_special_node({
16908
- SESSION_ID,
16909
- $container,
16910
- $root_container,
18463
+ SESSION_ID: renderer_context.SESSION_ID,
18464
+ $container: renderer_context.$container,
18465
+ $root_container: renderer_context.$root_container,
18466
+ treeP,
16911
18467
  nodeP,
16912
- parent_infoP,
16913
- paramsP,
16914
- jobNoP,
16915
- is_skeleton,
16916
- keyP,
16917
- refreshed_ds,
16918
- parent_nodeP,
18468
+ parent_infoP: renderer_context.parent_infoP,
18469
+ paramsP: renderer_context.paramsP,
18470
+ jobNoP: renderer_context.jobNoP,
18471
+ is_skeleton: renderer_context.is_skeleton,
18472
+ keyP: renderer_context.keyP,
18473
+ refreshed_ds: renderer_context.refreshed_ds,
18474
+ parent_nodeP: renderer_context.parent_nodeP,
16919
18475
  prop,
16920
18476
  render_context,
16921
18477
  hover_handlers,
@@ -16923,22 +18479,23 @@ func.runtime.render.render_ui_tree = async function (SESSION_ID, $container, nod
16923
18479
  close_modal,
16924
18480
  });
16925
18481
  if (special_render.handled) {
16926
- func.runtime?.perf?.increment?.(SESSION_ID, 'render_special_node_hits');
18482
+ func.runtime?.perf?.increment?.(renderer_context.SESSION_ID, 'render_special_node_hits');
16927
18483
  return special_render.result;
16928
18484
  }
16929
18485
  return await func.runtime.render.draw_node({
16930
- SESSION_ID,
16931
- $container,
16932
- $root_container,
18486
+ SESSION_ID: renderer_context.SESSION_ID,
18487
+ $container: renderer_context.$container,
18488
+ $root_container: renderer_context.$root_container,
18489
+ treeP,
16933
18490
  nodeP,
16934
- parent_infoP,
16935
- paramsP,
16936
- jobNoP,
16937
- is_skeleton,
16938
- keyP,
16939
- refreshed_ds,
16940
- parent_nodeP,
16941
- check_existP,
18491
+ parent_infoP: renderer_context.parent_infoP,
18492
+ paramsP: renderer_context.paramsP,
18493
+ jobNoP: renderer_context.jobNoP,
18494
+ is_skeleton: renderer_context.is_skeleton,
18495
+ keyP: renderer_context.keyP,
18496
+ refreshed_ds: renderer_context.refreshed_ds,
18497
+ parent_nodeP: renderer_context.parent_nodeP,
18498
+ check_existP: renderer_context.check_existP,
16942
18499
  prop,
16943
18500
  hover_handlers,
16944
18501
  include_hover_click,
@@ -16947,6 +18504,31 @@ func.runtime.render.render_ui_tree = async function (SESSION_ID, $container, nod
16947
18504
  } finally {
16948
18505
  perf_end?.();
16949
18506
  }
18507
+ };
18508
+ func.runtime.render.render_ui_tree = async function (SESSION_ID, $container, nodeP, parent_infoP, paramsP, jobNoP, is_skeleton, keyP, refreshed_ds, parent_nodeP, check_existP, $root_container) {
18509
+ if (!nodeP) return;
18510
+ const treeP = await func.runtime.render.ensure_tree_node({
18511
+ SESSION_ID,
18512
+ nodeP,
18513
+ parent_infoP,
18514
+ paramsP,
18515
+ keyP,
18516
+ parent_nodeP,
18517
+ });
18518
+
18519
+ return await func.runtime.render.render_tree(treeP, {
18520
+ SESSION_ID,
18521
+ $container,
18522
+ parent_infoP,
18523
+ paramsP,
18524
+ jobNoP,
18525
+ is_skeleton,
18526
+ keyP,
18527
+ refreshed_ds,
18528
+ parent_nodeP,
18529
+ check_existP,
18530
+ $root_container,
18531
+ });
16950
18532
  };
16951
18533
  func.runtime = func.runtime || {};
16952
18534
  func.runtime.ui = func.runtime.ui || {};
@@ -18470,6 +20052,7 @@ func.runtime.render.handle_xu_panel_program = async function (options) {
18470
20052
  parent_infoP: options.parent_infoP,
18471
20053
  jobNoP: options.jobNoP,
18472
20054
  keyP: options.keyP,
20055
+ treeP: options.treeP,
18473
20056
  parent_nodeP: options.parent_nodeP,
18474
20057
  prop: options.nodeP.attributes,
18475
20058
  $appendToP: $wrapper,
@@ -22483,6 +24066,12 @@ function xuda(...args) {
22483
24066
  if (typeof opt !== 'object') {
22484
24067
  return console.error('Xuda Error - opt argument is not an object');
22485
24068
  }
24069
+
24070
+ if (!opt.ssr_payload && func.runtime.platform.get_window()?.__XUDA_SSR__) {
24071
+ opt.ssr_payload = func.runtime.platform.get_window().__XUDA_SSR__;
24072
+ }
24073
+ func.runtime.render.apply_runtime_bootstrap_defaults(opt);
24074
+
22486
24075
  glb.URL_PARAMS = func.common.getJsonFromUrl(platform.get_url_href());
22487
24076
 
22488
24077
  glb.worker_type = 'Worker';