@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.
@@ -1779,7 +1779,30 @@ func.runtime.platform.emit = function (name, data) {
1779
1779
  };
1780
1780
 
1781
1781
  // ── Platform helpers for DOM-independent resource loading ──
1782
- func.runtime.platform.load_script = function (url, type, callback) {
1782
+ func.runtime.platform.apply_element_attributes = function (node, attributes, excluded_keys = []) {
1783
+ if (!node?.setAttribute || !attributes) {
1784
+ return node;
1785
+ }
1786
+
1787
+ const excluded = new Set(excluded_keys || []);
1788
+ const attr_keys = Object.keys(attributes);
1789
+ for (let index = 0; index < attr_keys.length; index++) {
1790
+ const key = attr_keys[index];
1791
+ if (!key || excluded.has(key)) {
1792
+ continue;
1793
+ }
1794
+
1795
+ const value = attributes[key];
1796
+ if (value === false || typeof value === 'undefined') {
1797
+ continue;
1798
+ }
1799
+
1800
+ node.setAttribute(key, value === null ? '' : `${value}`);
1801
+ }
1802
+
1803
+ return node;
1804
+ };
1805
+ func.runtime.platform.load_script = function (url, type, callback, attributes) {
1783
1806
  const doc = func.runtime.platform.get_document();
1784
1807
  if (!doc?.createElement || !doc?.head?.appendChild) {
1785
1808
  if (callback) {
@@ -1787,19 +1810,63 @@ func.runtime.platform.load_script = function (url, type, callback) {
1787
1810
  }
1788
1811
  return;
1789
1812
  }
1813
+ const find_existing_script = function () {
1814
+ const asset_key = attributes?.['data-xuda-asset-key'];
1815
+ const scripts = doc.querySelectorAll ? Array.from(doc.querySelectorAll('script')) : [];
1816
+ return scripts.find(function (script) {
1817
+ if (asset_key && script.getAttribute('data-xuda-asset-key') === asset_key) {
1818
+ return true;
1819
+ }
1820
+ return !!(url && script.getAttribute('src') === url);
1821
+ }) || null;
1822
+ };
1823
+
1824
+ const existing_script = find_existing_script();
1825
+ if (existing_script) {
1826
+ if (callback) {
1827
+ if (existing_script.getAttribute('data-xuda-loaded') === 'true' || !url) {
1828
+ callback();
1829
+ } else {
1830
+ existing_script.addEventListener('load', callback, { once: true });
1831
+ existing_script.addEventListener('error', callback, { once: true });
1832
+ }
1833
+ }
1834
+ return existing_script;
1835
+ }
1836
+
1790
1837
  const script = doc.createElement('script');
1791
1838
  script.src = url;
1792
1839
  if (type) script.type = type;
1793
- script.onload = callback;
1840
+ func.runtime.platform.apply_element_attributes(script, attributes, ['src', 'type']);
1841
+ script.onload = function () {
1842
+ script.setAttribute('data-xuda-loaded', 'true');
1843
+ if (callback) {
1844
+ callback();
1845
+ }
1846
+ };
1847
+ script.onerror = function () {
1848
+ if (callback) {
1849
+ callback();
1850
+ }
1851
+ };
1794
1852
  doc.head.appendChild(script);
1853
+ return script;
1795
1854
  };
1796
- func.runtime.platform.load_css = function (href) {
1855
+ func.runtime.platform.load_css = function (href, attributes) {
1797
1856
  const doc = func.runtime.platform.get_document();
1798
1857
  if (!doc?.createElement || !doc?.head) {
1799
1858
  return;
1800
1859
  }
1801
1860
  try {
1802
- if (doc.querySelector('link[href="' + href + '"]')) return;
1861
+ const asset_key = attributes?.['data-xuda-asset-key'];
1862
+ const existing_links = doc.querySelectorAll ? Array.from(doc.querySelectorAll('link')) : [];
1863
+ const existing = existing_links.find(function (link) {
1864
+ if (asset_key && link.getAttribute('data-xuda-asset-key') === asset_key) {
1865
+ return true;
1866
+ }
1867
+ return !!(href && link.getAttribute('href') === href);
1868
+ });
1869
+ if (existing) return existing;
1803
1870
  } catch (err) {
1804
1871
  return;
1805
1872
  }
@@ -1807,7 +1874,9 @@ func.runtime.platform.load_css = function (href) {
1807
1874
  link.rel = 'stylesheet';
1808
1875
  link.type = 'text/css';
1809
1876
  link.href = href;
1877
+ func.runtime.platform.apply_element_attributes(link, attributes, ['href']);
1810
1878
  doc.head.insertBefore(link, doc.head.firstChild);
1879
+ return link;
1811
1880
  };
1812
1881
  func.runtime.platform.remove_js_css = function (filename, filetype) {
1813
1882
  const doc = func.runtime.platform.get_document();
@@ -1841,6 +1910,128 @@ func.runtime.platform.set_cursor = function (element, cursor) {
1841
1910
  node.style.cursor = cursor;
1842
1911
  }
1843
1912
  };
1913
+ func.runtime.program.normalize_doc_for_runtime = function (doc) {
1914
+ if (!doc || doc.__xudaRuntimeNormalized || !Array.isArray(doc.progUi) || !doc.progUi.length) {
1915
+ return doc;
1916
+ }
1917
+
1918
+ const normalize_tag_name = function (tag_name) {
1919
+ return `${tag_name || ''}`.trim().toLowerCase();
1920
+ };
1921
+ const merge_attributes = function (target, source) {
1922
+ const merged = { ...(target || {}) };
1923
+ const source_attributes = source || {};
1924
+ const keys = Object.keys(source_attributes);
1925
+
1926
+ for (let index = 0; index < keys.length; index++) {
1927
+ const key = keys[index];
1928
+ const value = source_attributes[key];
1929
+ if (typeof value === 'undefined') {
1930
+ continue;
1931
+ }
1932
+
1933
+ if (key === 'class' && merged.class && value) {
1934
+ const next_value = `${merged.class} ${value}`.trim();
1935
+ merged.class = Array.from(new Set(next_value.split(/\s+/).filter(Boolean))).join(' ');
1936
+ continue;
1937
+ }
1938
+
1939
+ if (key === 'style' && merged.style && value) {
1940
+ merged.style = `${merged.style}; ${value}`.trim();
1941
+ continue;
1942
+ }
1943
+
1944
+ if (typeof merged[key] === 'undefined') {
1945
+ merged[key] = value;
1946
+ }
1947
+ }
1948
+
1949
+ return merged;
1950
+ };
1951
+ const normalize_nodes = function (nodes, state) {
1952
+ const normalized_nodes = [];
1953
+
1954
+ for (let index = 0; index < (nodes || []).length; index++) {
1955
+ const node = nodes[index];
1956
+ if (!node || typeof node !== 'object') {
1957
+ continue;
1958
+ }
1959
+
1960
+ const tag_name = normalize_tag_name(node.tagName);
1961
+
1962
+ if (tag_name === '!doctype') {
1963
+ state.changed = true;
1964
+ continue;
1965
+ }
1966
+
1967
+ if (tag_name === 'html') {
1968
+ state.changed = true;
1969
+ state.root_attributes = merge_attributes(state.root_attributes, node.attributes);
1970
+ normalized_nodes.push.apply(normalized_nodes, normalize_nodes(node.children, state));
1971
+ continue;
1972
+ }
1973
+
1974
+ if (tag_name === 'head') {
1975
+ state.changed = true;
1976
+ normalized_nodes.push.apply(normalized_nodes, normalize_nodes(node.children, state));
1977
+ continue;
1978
+ }
1979
+
1980
+ if (tag_name === 'body') {
1981
+ state.changed = true;
1982
+ state.root_attributes = merge_attributes(state.root_attributes, node.attributes);
1983
+ normalized_nodes.push.apply(normalized_nodes, normalize_nodes(node.children, state));
1984
+ continue;
1985
+ }
1986
+
1987
+ let next_node = node;
1988
+ if (Array.isArray(node.children) && node.children.length) {
1989
+ const next_children = normalize_nodes(node.children, state);
1990
+ if (next_children !== node.children) {
1991
+ next_node = {
1992
+ ...node,
1993
+ children: next_children,
1994
+ };
1995
+ state.changed = true;
1996
+ }
1997
+ }
1998
+
1999
+ normalized_nodes.push(next_node);
2000
+ }
2001
+
2002
+ return normalized_nodes;
2003
+ };
2004
+
2005
+ const [root_node, ...extra_nodes] = doc.progUi;
2006
+ if (!root_node || typeof root_node !== 'object') {
2007
+ return doc;
2008
+ }
2009
+
2010
+ const state = {
2011
+ changed: false,
2012
+ root_attributes: {},
2013
+ };
2014
+
2015
+ const normalized_children = normalize_nodes([...(root_node.children || []), ...extra_nodes], state);
2016
+ const merged_attributes = merge_attributes(root_node.attributes, state.root_attributes);
2017
+
2018
+ if (!state.changed && !Object.keys(state.root_attributes).length) {
2019
+ doc.__xudaRuntimeNormalized = true;
2020
+ return doc;
2021
+ }
2022
+
2023
+ return {
2024
+ ...doc,
2025
+ __xudaRuntimeNormalized: true,
2026
+ progUi: [
2027
+ {
2028
+ ...root_node,
2029
+ attributes: merged_attributes,
2030
+ children: normalized_children,
2031
+ },
2032
+ ],
2033
+ };
2034
+ };
1844
2035
 
1845
2036
  func.runtime.env = {
1846
2037
  get_url_params: function () {
@@ -2098,6 +2289,147 @@ func.runtime.workers.delete_promise = function (SESSION_ID, worker_id, promise_q
2098
2289
  delete registry_entry.promise_queue[promise_queue_id];
2099
2290
  return true;
2100
2291
  };
2292
+ func.runtime.render.clone_runtime_options = function (value) {
2293
+ if (typeof structuredClone === 'function') {
2294
+ try {
2295
+ return structuredClone(value);
2296
+ } catch (_) {}
2297
+ }
2298
+
2299
+ if (Array.isArray(value)) {
2300
+ return value.map(function (item) {
2301
+ return func.runtime.render.clone_runtime_options(item);
2302
+ });
2303
+ }
2304
+
2305
+ if (value && typeof value === 'object') {
2306
+ const cloned = {};
2307
+ const keys = Object.keys(value);
2308
+ for (let index = 0; index < keys.length; index++) {
2309
+ const key = keys[index];
2310
+ cloned[key] = func.runtime.render.clone_runtime_options(value[key]);
2311
+ }
2312
+ return cloned;
2313
+ }
2314
+
2315
+ return value;
2316
+ };
2317
+ func.runtime.render.normalize_runtime_bootstrap = function (raw_options = {}) {
2318
+ const options = raw_options || {};
2319
+ let app_computing_mode = options.app_computing_mode || '';
2320
+ let app_render_mode = options.app_render_mode || '';
2321
+ let app_client_activation = options.app_client_activation || '';
2322
+ let ssr_payload = options.ssr_payload || null;
2323
+
2324
+ if (typeof ssr_payload === 'string') {
2325
+ try {
2326
+ ssr_payload = JSON.parse(ssr_payload);
2327
+ } catch (_) {
2328
+ ssr_payload = null;
2329
+ }
2330
+ }
2331
+
2332
+ if (ssr_payload && typeof ssr_payload === 'object') {
2333
+ ssr_payload = func.runtime.render.clone_runtime_options(ssr_payload);
2334
+ }
2335
+
2336
+ if (!app_computing_mode) {
2337
+ if (app_render_mode === 'ssr_first_page' || app_render_mode === 'ssr_full') {
2338
+ app_computing_mode = 'server';
2339
+ } else {
2340
+ app_computing_mode = 'main';
2341
+ }
2342
+ }
2343
+
2344
+ switch (app_computing_mode) {
2345
+ case 'main':
2346
+ app_render_mode = 'csr';
2347
+ app_client_activation = 'none';
2348
+ break;
2349
+
2350
+ case 'worker':
2351
+ app_render_mode = 'csr';
2352
+ app_client_activation = 'none';
2353
+ break;
2354
+
2355
+ default:
2356
+ app_computing_mode = 'server';
2357
+ if (app_render_mode !== 'ssr_full') {
2358
+ app_render_mode = 'ssr_first_page';
2359
+ }
2360
+ app_client_activation = app_render_mode === 'ssr_full' ? 'hydrate' : 'takeover';
2361
+ break;
2362
+ }
2363
+
2364
+ if (ssr_payload && typeof ssr_payload === 'object') {
2365
+ if (!ssr_payload.app_render_mode) {
2366
+ ssr_payload.app_render_mode = app_render_mode;
2367
+ }
2368
+ if (!ssr_payload.app_client_activation) {
2369
+ ssr_payload.app_client_activation = app_client_activation;
2370
+ }
2371
+ if (!ssr_payload.app_computing_mode) {
2372
+ ssr_payload.app_computing_mode = app_computing_mode;
2373
+ }
2374
+ }
2375
+
2376
+ return {
2377
+ app_computing_mode,
2378
+ app_render_mode,
2379
+ app_client_activation,
2380
+ ssr_payload,
2381
+ };
2382
+ };
2383
+ func.runtime.render.apply_runtime_bootstrap_defaults = function (target = {}) {
2384
+ const normalized = func.runtime.render.normalize_runtime_bootstrap(target);
2385
+ target.app_computing_mode = normalized.app_computing_mode;
2386
+ target.app_render_mode = normalized.app_render_mode;
2387
+ target.app_client_activation = normalized.app_client_activation;
2388
+ target.ssr_payload = normalized.ssr_payload;
2389
+ return normalized;
2390
+ };
2391
+ func.runtime.render.is_server_render_mode = function (target = {}) {
2392
+ const normalized = func.runtime.render.normalize_runtime_bootstrap(target?.opt || target);
2393
+ return normalized.app_computing_mode === 'server' && normalized.app_render_mode !== 'csr';
2394
+ };
2395
+ func.runtime.render.is_takeover_mode = function (target = {}) {
2396
+ const normalized = func.runtime.render.normalize_runtime_bootstrap(target?.opt || target);
2397
+ return normalized.app_client_activation === 'takeover';
2398
+ };
2399
+ func.runtime.render.is_hydration_mode = function (target = {}) {
2400
+ const normalized = func.runtime.render.normalize_runtime_bootstrap(target?.opt || target);
2401
+ return normalized.app_client_activation === 'hydrate';
2402
+ };
2403
+ func.runtime.render.get_ssr_payload = function (target = {}) {
2404
+ if (target?.opt?.ssr_payload) {
2405
+ return target.opt.ssr_payload;
2406
+ }
2407
+ if (target?.ssr_payload) {
2408
+ return target.ssr_payload;
2409
+ }
2410
+ const win = func.runtime.platform.get_window();
2411
+ return win?.__XUDA_SSR__ || null;
2412
+ };
2413
+ func.runtime.render.should_use_ssr_payload = function (SESSION_ID, paramsP) {
2414
+ const session = SESSION_OBJ?.[SESSION_ID];
2415
+ const payload = func.runtime.render.get_ssr_payload(session);
2416
+ if (!payload || payload._consumed) {
2417
+ return false;
2418
+ }
2419
+ if (paramsP?.prog_id && payload.prog_id && payload.prog_id !== paramsP.prog_id) {
2420
+ return false;
2421
+ }
2422
+ return true;
2423
+ };
2424
+ func.runtime.render.mark_ssr_payload_consumed = function (SESSION_ID) {
2425
+ const session = SESSION_OBJ?.[SESSION_ID];
2426
+ const payload = func.runtime.render.get_ssr_payload(session);
2427
+ if (!payload || typeof payload !== 'object') {
2428
+ return false;
2429
+ }
2430
+ payload._consumed = true;
2431
+ return true;
2432
+ };
2101
2433
  func.runtime.render.get_root_data_system = function (SESSION_ID) {
2102
2434
  return SESSION_OBJ[SESSION_ID]?.DS_GLB?.[0]?.data_system || null;
2103
2435
  };
@@ -2501,10 +2833,10 @@ func.runtime.resources.load_cdn = async function (SESSION_ID, resource) {
2501
2833
  await func.utils.load_js_on_demand(normalized_resource.src);
2502
2834
  break;
2503
2835
  case 'css':
2504
- await func.utils.load_js_on_demand(normalized_resource.src);
2836
+ func.runtime.platform.load_css(normalized_resource.src);
2505
2837
  break;
2506
2838
  case 'module':
2507
- func.utils.load_js_on_demand(normalized_resource.src, 'module');
2839
+ await func.utils.load_js_on_demand(normalized_resource.src, 'module');
2508
2840
  break;
2509
2841
  default:
2510
2842
  await func.utils.load_js_on_demand(normalized_resource.src);
@@ -3840,7 +4172,692 @@ func.common.fastHash = function (inputString) {
3840
4172
  return ((hash >>> 0).toString(36) + '0000000000').slice(0, 10);
3841
4173
  };
3842
4174
 
3843
- glb.new_xu_render = false;
4175
+ glb.new_xu_render = false;
4176
+ func.runtime = func.runtime || {};
4177
+ func.runtime.ui = func.runtime.ui || {};
4178
+ func.runtime.render = func.runtime.render || {};
4179
+ func.runtime.widgets = func.runtime.widgets || {};
4180
+
4181
+ // Shared render-tree contract helpers live here so browser and headless runtimes can resolve the same UI structure.
4182
+
4183
+ func.runtime.render.TREE_CONTRACT_VERSION = func.runtime.render.TREE_CONTRACT_VERSION || 'xuda.render_tree.v1';
4184
+ func.runtime.render._tree_widget_capability_cache = func.runtime.render._tree_widget_capability_cache || {};
4185
+
4186
+ func.runtime.render.safe_clone_tree_value = function (value) {
4187
+ if (typeof structuredClone === 'function') {
4188
+ try {
4189
+ return structuredClone(value);
4190
+ } catch (_) {
4191
+ // Fall through to the recursive clone below.
4192
+ }
4193
+ }
4194
+
4195
+ if (Array.isArray(value)) {
4196
+ return value.map(function (item) {
4197
+ return func.runtime.render.safe_clone_tree_value(item);
4198
+ });
4199
+ }
4200
+
4201
+ if (value && typeof value === 'object') {
4202
+ const cloned = {};
4203
+ const keys = Object.keys(value);
4204
+ for (let index = 0; index < keys.length; index++) {
4205
+ const key = keys[index];
4206
+ cloned[key] = func.runtime.render.safe_clone_tree_value(value[key]);
4207
+ }
4208
+ return cloned;
4209
+ }
4210
+
4211
+ return value;
4212
+ };
4213
+ func.runtime.render.sort_tree_debug_value = function (value) {
4214
+ if (Array.isArray(value)) {
4215
+ return value.map(function (item) {
4216
+ return func.runtime.render.sort_tree_debug_value(item);
4217
+ });
4218
+ }
4219
+
4220
+ if (value && typeof value === 'object') {
4221
+ const sorted = {};
4222
+ const keys = Object.keys(value).sort();
4223
+ for (let index = 0; index < keys.length; index++) {
4224
+ const key = keys[index];
4225
+ sorted[key] = func.runtime.render.sort_tree_debug_value(value[key]);
4226
+ }
4227
+ return sorted;
4228
+ }
4229
+
4230
+ return value;
4231
+ };
4232
+ func.runtime.render.is_tree_node = function (nodeP) {
4233
+ return !!nodeP?.contract && nodeP.contract === func.runtime.render.TREE_CONTRACT_VERSION;
4234
+ };
4235
+ func.runtime.render.get_tree_source_node = function (nodeP) {
4236
+ if (!func.runtime.render.is_tree_node(nodeP)) {
4237
+ return nodeP || null;
4238
+ }
4239
+ return nodeP?.meta?.source_node || null;
4240
+ };
4241
+ func.runtime.render.get_tree_source_snapshot = function (nodeP) {
4242
+ if (!func.runtime.render.is_tree_node(nodeP)) {
4243
+ return func.runtime.render.safe_clone_tree_value(nodeP);
4244
+ }
4245
+ return nodeP?.meta?.source_snapshot || null;
4246
+ };
4247
+ func.runtime.render.get_tree_node_kind = function (nodeP) {
4248
+ const tag_name = typeof nodeP?.tagName === 'string' ? nodeP.tagName.toLowerCase() : '';
4249
+ const node_type = typeof nodeP?.type === 'string' ? nodeP.type.toLowerCase() : '';
4250
+
4251
+ if (tag_name === 'xu-widget') return 'widget';
4252
+ if (tag_name === 'xu-single-view') return 'single_view';
4253
+ if (tag_name === 'xu-multi-view') return 'multi_view';
4254
+ if (tag_name === 'xu-panel') return 'panel';
4255
+ if (tag_name === 'xu-teleport') return 'teleport';
4256
+ if (tag_name === 'xurender') return 'placeholder';
4257
+ if (tag_name === '#text' || node_type === 'text') return 'text';
4258
+ if (!tag_name && typeof nodeP?.content === 'string' && !Array.isArray(nodeP?.children)) return 'text';
4259
+ return 'element';
4260
+ };
4261
+ func.runtime.render.get_tree_node_id = function (nodeP, pathP) {
4262
+ if (nodeP?.id) {
4263
+ return nodeP.id;
4264
+ }
4265
+ if (nodeP?.id_org) {
4266
+ return nodeP.id_org;
4267
+ }
4268
+ const normalized_path = Array.isArray(pathP) && pathP.length ? pathP.join('.') : 'root';
4269
+ return `tree-node-${normalized_path}`;
4270
+ };
4271
+ func.runtime.render.get_tree_controls = function (attributes) {
4272
+ const attrs = attributes || {};
4273
+ const get_first_defined = function (keys) {
4274
+ for (let index = 0; index < keys.length; index++) {
4275
+ const key = keys[index];
4276
+ if (Object.prototype.hasOwnProperty.call(attrs, key)) {
4277
+ return attrs[key];
4278
+ }
4279
+ }
4280
+ return null;
4281
+ };
4282
+ return {
4283
+ xu_for: get_first_defined(['xu-for', 'xu-exp:xu-for']),
4284
+ xu_if: get_first_defined(['xu-if', 'xu-exp:xu-if']),
4285
+ xu_render: get_first_defined(['xu-render', 'xu-exp:xu-render']),
4286
+ };
4287
+ };
4288
+ func.runtime.render.get_tree_node_capabilities = async function (options) {
4289
+ const attributes = options?.attributes || {};
4290
+ const plugin_name = attributes['xu-widget'];
4291
+ if (!plugin_name) {
4292
+ return null;
4293
+ }
4294
+
4295
+ const cache = func.runtime.render._tree_widget_capability_cache;
4296
+ if (cache[plugin_name]) {
4297
+ return func.runtime.render.safe_clone_tree_value(cache[plugin_name]);
4298
+ }
4299
+
4300
+ let capabilities = {
4301
+ browser: true,
4302
+ headless: false,
4303
+ };
4304
+
4305
+ try {
4306
+ if (options.SESSION_ID && options.paramsP && func.runtime.widgets?.create_context && func.runtime.widgets?.get_definition) {
4307
+ const widget_context = func.runtime.widgets.create_context(options.SESSION_ID, options.paramsP, attributes);
4308
+ const definition = await func.runtime.widgets.get_definition(widget_context);
4309
+ capabilities = func.runtime.widgets.normalize_capabilities(definition);
4310
+ }
4311
+ } catch (_) {
4312
+ // Keep the safe browser-only default when the widget definition is unavailable.
4313
+ }
4314
+
4315
+ cache[plugin_name] = capabilities;
4316
+ return func.runtime.render.safe_clone_tree_value(capabilities);
4317
+ };
4318
+ func.runtime.render.ensure_tree_node = async function (options) {
4319
+ if (!options?.nodeP) {
4320
+ return null;
4321
+ }
4322
+ if (func.runtime.render.is_tree_node(options.nodeP)) {
4323
+ return options.nodeP;
4324
+ }
4325
+ return await func.runtime.render.build_tree(options);
4326
+ };
4327
+ func.runtime.render.build_tree = async function (options) {
4328
+ if (Array.isArray(options?.nodeP)) {
4329
+ return await func.runtime.render.build_tree_list({
4330
+ ...options,
4331
+ nodesP: options.nodeP,
4332
+ });
4333
+ }
4334
+
4335
+ const nodeP = options?.nodeP;
4336
+ if (!nodeP) {
4337
+ return null;
4338
+ }
4339
+ if (func.runtime.render.is_tree_node(nodeP)) {
4340
+ return nodeP;
4341
+ }
4342
+
4343
+ const pathP = Array.isArray(options?.pathP) ? options.pathP.slice() : [];
4344
+ const tree_path = pathP.length ? pathP.slice() : [0];
4345
+ const attributes = func.runtime.render.safe_clone_tree_value(nodeP.attributes || {});
4346
+ if (typeof nodeP.content !== 'undefined' && typeof attributes['xu-content'] === 'undefined') {
4347
+ attributes['xu-content'] = func.runtime.render.safe_clone_tree_value(nodeP.content);
4348
+ }
4349
+
4350
+ const widget_capabilities = await func.runtime.render.get_tree_node_capabilities({
4351
+ SESSION_ID: options?.SESSION_ID,
4352
+ paramsP: options?.paramsP,
4353
+ attributes,
4354
+ });
4355
+ const children = [];
4356
+ const child_nodes = Array.isArray(nodeP.children) ? nodeP.children : [];
4357
+ const parent_tree_id = tree_path.join('.');
4358
+
4359
+ for (let index = 0; index < child_nodes.length; index++) {
4360
+ const child_tree = await func.runtime.render.build_tree({
4361
+ ...options,
4362
+ nodeP: child_nodes[index],
4363
+ pathP: tree_path.concat(index),
4364
+ parent_tree_id: parent_tree_id,
4365
+ keyP: index,
4366
+ parent_nodeP: nodeP,
4367
+ });
4368
+ if (child_tree) {
4369
+ children.push(child_tree);
4370
+ }
4371
+ }
4372
+
4373
+ const tree = {
4374
+ contract: func.runtime.render.TREE_CONTRACT_VERSION,
4375
+ id: func.runtime.render.get_tree_node_id(nodeP, tree_path),
4376
+ xu_tree_id: `tree.${tree_path.join('.')}`,
4377
+ kind: func.runtime.render.get_tree_node_kind(nodeP),
4378
+ tagName: nodeP.tagName || null,
4379
+ attributes,
4380
+ text: typeof nodeP.text !== 'undefined' ? func.runtime.render.safe_clone_tree_value(nodeP.text) : null,
4381
+ content: typeof nodeP.content !== 'undefined' ? func.runtime.render.safe_clone_tree_value(nodeP.content) : null,
4382
+ children,
4383
+ meta: {
4384
+ tree_id: tree_path.join('.'),
4385
+ path: tree_path,
4386
+ parent_tree_id: options?.parent_tree_id || null,
4387
+ key: typeof options?.keyP === 'undefined' ? null : options.keyP,
4388
+ recordid: nodeP?.recordid || null,
4389
+ dependency_fields: func.runtime.render.safe_clone_tree_value(nodeP?.dependency_fields || null),
4390
+ iterate_info: func.runtime.render.safe_clone_tree_value(options?.parent_infoP?.iterate_info || nodeP?.iterate_info || null),
4391
+ controls: func.runtime.render.get_tree_controls(attributes),
4392
+ capabilities: widget_capabilities,
4393
+ widget: attributes['xu-widget']
4394
+ ? {
4395
+ plugin_name: attributes['xu-widget'],
4396
+ method: attributes['xu-method'] || '_default',
4397
+ capabilities: widget_capabilities,
4398
+ }
4399
+ : null,
4400
+ source_node_id: nodeP?.id || nodeP?.id_org || null,
4401
+ source_node: nodeP,
4402
+ source_snapshot: func.runtime.ui?.get_node_snapshot
4403
+ ? func.runtime.ui.get_node_snapshot(nodeP)
4404
+ : func.runtime.render.safe_clone_tree_value(nodeP),
4405
+ },
4406
+ };
4407
+
4408
+ return tree;
4409
+ };
4410
+ func.runtime.render.build_tree_list = async function (options) {
4411
+ const nodes = Array.isArray(options?.nodesP) ? options.nodesP : [];
4412
+ const trees = [];
4413
+
4414
+ for (let index = 0; index < nodes.length; index++) {
4415
+ const tree = await func.runtime.render.build_tree({
4416
+ ...options,
4417
+ nodeP: nodes[index],
4418
+ pathP: Array.isArray(options?.pathP) && options.pathP.length ? options.pathP.concat(index) : [index],
4419
+ keyP: index,
4420
+ });
4421
+ if (tree) {
4422
+ trees.push(tree);
4423
+ }
4424
+ }
4425
+
4426
+ return trees;
4427
+ };
4428
+ func.runtime.render.sanitize_tree_for_debug = function (treeP) {
4429
+ if (Array.isArray(treeP)) {
4430
+ return treeP.map(function (child) {
4431
+ return func.runtime.render.sanitize_tree_for_debug(child);
4432
+ });
4433
+ }
4434
+
4435
+ if (!func.runtime.render.is_tree_node(treeP)) {
4436
+ return func.runtime.render.sort_tree_debug_value(func.runtime.render.safe_clone_tree_value(treeP));
4437
+ }
4438
+
4439
+ return {
4440
+ contract: treeP.contract,
4441
+ id: treeP.id,
4442
+ xu_tree_id: treeP.xu_tree_id || null,
4443
+ kind: treeP.kind,
4444
+ tagName: treeP.tagName,
4445
+ attributes: func.runtime.render.sort_tree_debug_value(treeP.attributes || {}),
4446
+ text: treeP.text,
4447
+ content: treeP.content,
4448
+ children: treeP.children.map(function (child) {
4449
+ return func.runtime.render.sanitize_tree_for_debug(child);
4450
+ }),
4451
+ meta: {
4452
+ tree_id: treeP.meta?.tree_id || null,
4453
+ path: func.runtime.render.safe_clone_tree_value(treeP.meta?.path || []),
4454
+ parent_tree_id: treeP.meta?.parent_tree_id || null,
4455
+ key: typeof treeP.meta?.key === 'undefined' ? null : treeP.meta.key,
4456
+ recordid: treeP.meta?.recordid || null,
4457
+ dependency_fields: func.runtime.render.sort_tree_debug_value(treeP.meta?.dependency_fields || null),
4458
+ iterate_info: func.runtime.render.sort_tree_debug_value(treeP.meta?.iterate_info || null),
4459
+ controls: func.runtime.render.sort_tree_debug_value(treeP.meta?.controls || null),
4460
+ capabilities: func.runtime.render.sort_tree_debug_value(treeP.meta?.capabilities || null),
4461
+ widget: treeP.meta?.widget
4462
+ ? {
4463
+ plugin_name: treeP.meta.widget.plugin_name,
4464
+ method: treeP.meta.widget.method,
4465
+ capabilities: func.runtime.render.sort_tree_debug_value(treeP.meta.widget.capabilities || null),
4466
+ }
4467
+ : null,
4468
+ source_node_id: treeP.meta?.source_node_id || null,
4469
+ },
4470
+ };
4471
+ };
4472
+ func.runtime.render.serialize_tree = function (treeP, spacing = 2) {
4473
+ return JSON.stringify(func.runtime.render.sanitize_tree_for_debug(treeP), null, spacing);
4474
+ };
4475
+ func.runtime = func.runtime || {};
4476
+ func.runtime.ui = func.runtime.ui || {};
4477
+ func.runtime.render = func.runtime.render || {};
4478
+ func.runtime.widgets = func.runtime.widgets || {};
4479
+
4480
+ // Shared string-renderer helpers live here so headless/server runtimes can materialize the render tree without a DOM.
4481
+
4482
+ func.runtime.render.HTML_VOID_TAGS = func.runtime.render.HTML_VOID_TAGS || {
4483
+ area: true,
4484
+ base: true,
4485
+ br: true,
4486
+ col: true,
4487
+ embed: true,
4488
+ hr: true,
4489
+ img: true,
4490
+ input: true,
4491
+ link: true,
4492
+ meta: true,
4493
+ param: true,
4494
+ source: true,
4495
+ track: true,
4496
+ wbr: true,
4497
+ };
4498
+ func.runtime.render.escape_html = function (value) {
4499
+ return `${value ?? ''}`
4500
+ .replaceAll('&', '&amp;')
4501
+ .replaceAll('<', '&lt;')
4502
+ .replaceAll('>', '&gt;')
4503
+ .replaceAll('"', '&quot;')
4504
+ .replaceAll("'", '&#039;');
4505
+ };
4506
+ func.runtime.render.escape_html_attribute = function (value) {
4507
+ return func.runtime.render.escape_html(value);
4508
+ };
4509
+ func.runtime.render.is_html_void_tag = function (tag_name) {
4510
+ return !!func.runtime.render.HTML_VOID_TAGS[(tag_name || '').toLowerCase()];
4511
+ };
4512
+ func.runtime.render.is_falsey_render_value = function (value) {
4513
+ if (value === false || value === null || typeof value === 'undefined') {
4514
+ return true;
4515
+ }
4516
+ if (typeof value === 'number') {
4517
+ return value === 0;
4518
+ }
4519
+ if (typeof value === 'string') {
4520
+ const normalized = value.trim().toLowerCase();
4521
+ return normalized === '' || normalized === 'false' || normalized === '0' || normalized === 'null' || normalized === 'undefined' || normalized === 'off' || normalized === 'no';
4522
+ }
4523
+ return false;
4524
+ };
4525
+ func.runtime.render.should_render_tree_node = function (treeP) {
4526
+ const controls = treeP?.meta?.controls || {};
4527
+ if (controls.xu_if !== null && controls.xu_if !== undefined && func.runtime.render.is_falsey_render_value(controls.xu_if)) {
4528
+ return false;
4529
+ }
4530
+ if (controls.xu_render !== null && controls.xu_render !== undefined && func.runtime.render.is_falsey_render_value(controls.xu_render)) {
4531
+ return false;
4532
+ }
4533
+ return true;
4534
+ };
4535
+ func.runtime.render.is_tree_control_attribute = function (key) {
4536
+ if (!key) {
4537
+ return false;
4538
+ }
4539
+ return (
4540
+ key.startsWith('xu-exp:') ||
4541
+ key === 'xu-widget' ||
4542
+ key === 'xu-method' ||
4543
+ key === 'xu-for' ||
4544
+ key === 'xu-for-key' ||
4545
+ key === 'xu-for-val' ||
4546
+ key === 'xu-if' ||
4547
+ key === 'xu-render' ||
4548
+ key === 'xu-bind' ||
4549
+ key === 'xu-content' ||
4550
+ key === 'xu-text' ||
4551
+ key === 'xu-html' ||
4552
+ key === 'xu-show' ||
4553
+ key === 'xu-panel-program' ||
4554
+ key === 'xu-teleport'
4555
+ );
4556
+ };
4557
+ func.runtime.render.get_string_renderer_tag_name = function (treeP) {
4558
+ switch (treeP?.kind) {
4559
+ case 'widget':
4560
+ case 'single_view':
4561
+ case 'multi_view':
4562
+ case 'panel':
4563
+ case 'teleport':
4564
+ return 'div';
4565
+ case 'placeholder':
4566
+ return null;
4567
+ case 'text':
4568
+ return null;
4569
+ default:
4570
+ return treeP?.tagName || 'div';
4571
+ }
4572
+ };
4573
+ func.runtime.render.get_tree_terminal_content = function (treeP) {
4574
+ const attributes = treeP?.attributes || {};
4575
+ if (typeof attributes['xu-html'] !== 'undefined' && attributes['xu-html'] !== null) {
4576
+ return {
4577
+ value: `${attributes['xu-html']}`,
4578
+ mode: 'html',
4579
+ };
4580
+ }
4581
+ if (typeof attributes['xu-content'] !== 'undefined' && attributes['xu-content'] !== null) {
4582
+ return {
4583
+ value: `${attributes['xu-content']}`,
4584
+ mode: 'html',
4585
+ };
4586
+ }
4587
+ if (typeof attributes['xu-text'] !== 'undefined' && attributes['xu-text'] !== null) {
4588
+ return {
4589
+ value: `${attributes['xu-text']}`,
4590
+ mode: 'text',
4591
+ };
4592
+ }
4593
+ if (treeP?.kind === 'text') {
4594
+ return {
4595
+ value: typeof treeP?.text !== 'undefined' && treeP?.text !== null ? `${treeP.text}` : `${treeP?.content || ''}`,
4596
+ mode: 'text',
4597
+ };
4598
+ }
4599
+ return null;
4600
+ };
4601
+ func.runtime.render.render_tree_terminal_content = function (treeP) {
4602
+ const terminal = func.runtime.render.get_tree_terminal_content(treeP);
4603
+ if (!terminal) {
4604
+ return null;
4605
+ }
4606
+ if (terminal.mode === 'html') {
4607
+ return terminal.value;
4608
+ }
4609
+ return func.runtime.render.escape_html(terminal.value);
4610
+ };
4611
+ func.runtime.render.get_widget_fallback_markup = function (treeP) {
4612
+ const widget_meta = treeP?.meta?.widget || {};
4613
+ const capability_state = widget_meta?.capabilities?.headless ? 'headless-capable' : 'browser-only';
4614
+ return `<!--xuda-widget:${func.runtime.render.escape_html(widget_meta.plugin_name || 'unknown')}:${capability_state}-->`;
4615
+ };
4616
+ func.runtime.render.get_tree_string_attributes = function (treeP, renderer_context) {
4617
+ const attributes = func.runtime.render.safe_clone_tree_value(treeP?.attributes || {});
4618
+ const attr_pairs = [];
4619
+ const keys = Object.keys(attributes);
4620
+
4621
+ for (let index = 0; index < keys.length; index++) {
4622
+ const key = keys[index];
4623
+ if (func.runtime.render.is_tree_control_attribute(key)) {
4624
+ continue;
4625
+ }
4626
+ const value = attributes[key];
4627
+ if (value === false || value === null || typeof value === 'undefined') {
4628
+ continue;
4629
+ }
4630
+ if (value === true) {
4631
+ attr_pairs.push(key);
4632
+ continue;
4633
+ }
4634
+ const normalized_value = typeof value === 'object' ? JSON.stringify(value) : `${value}`;
4635
+ attr_pairs.push(`${key}="${func.runtime.render.escape_html_attribute(normalized_value)}"`);
4636
+ }
4637
+
4638
+ attr_pairs.push(`data-xuda-kind="${func.runtime.render.escape_html_attribute(treeP?.kind || 'element')}"`);
4639
+ attr_pairs.push(`data-xuda-node-id="${func.runtime.render.escape_html_attribute(treeP?.id || treeP?.meta?.source_node_id || '')}"`);
4640
+ attr_pairs.push(`data-xuda-tree-id="${func.runtime.render.escape_html_attribute(treeP?.meta?.tree_id || '')}"`);
4641
+
4642
+ if (treeP?.kind === 'widget' && treeP?.meta?.widget) {
4643
+ attr_pairs.push(`data-xuda-widget="${func.runtime.render.escape_html_attribute(treeP.meta.widget.plugin_name || '')}"`);
4644
+ attr_pairs.push(`data-xuda-widget-method="${func.runtime.render.escape_html_attribute(treeP.meta.widget.method || '_default')}"`);
4645
+ attr_pairs.push(`data-xuda-widget-capability="${func.runtime.render.escape_html_attribute(treeP.meta.widget.capabilities?.headless ? 'headless' : 'browser')}"`);
4646
+ }
4647
+
4648
+ if (treeP?.kind === 'teleport' && treeP?.attributes?.['xu-teleport']) {
4649
+ attr_pairs.push(`data-xuda-teleport-target="${func.runtime.render.escape_html_attribute(treeP.attributes['xu-teleport'])}"`);
4650
+ }
4651
+
4652
+ if ((treeP?.meta?.controls?.xu_for !== null && treeP?.meta?.controls?.xu_for !== undefined) && !renderer_context?.strip_iteration_markers) {
4653
+ attr_pairs.push('data-xuda-xu-for="pending"');
4654
+ }
4655
+
4656
+ return attr_pairs.length ? ' ' + attr_pairs.join(' ') : '';
4657
+ };
4658
+ func.runtime.render.render_tree_children_to_string = async function (treeP, renderer_context) {
4659
+ if (!Array.isArray(treeP?.children) || !treeP.children.length) {
4660
+ return '';
4661
+ }
4662
+ let html = '';
4663
+ for (let index = 0; index < treeP.children.length; index++) {
4664
+ html += await func.runtime.render.render_tree_to_string(treeP.children[index], {
4665
+ ...renderer_context,
4666
+ parent_tree: treeP,
4667
+ });
4668
+ }
4669
+ return html;
4670
+ };
4671
+ func.runtime.render.render_tree_to_string = async function (treeP, renderer_context = {}) {
4672
+ if (!treeP) {
4673
+ return '';
4674
+ }
4675
+ if (Array.isArray(treeP)) {
4676
+ let html = '';
4677
+ for (let index = 0; index < treeP.length; index++) {
4678
+ html += await func.runtime.render.render_tree_to_string(treeP[index], renderer_context);
4679
+ }
4680
+ return html;
4681
+ }
4682
+
4683
+ const ensured_tree = await func.runtime.render.ensure_tree_node({
4684
+ SESSION_ID: renderer_context?.SESSION_ID,
4685
+ nodeP: treeP,
4686
+ paramsP: renderer_context?.paramsP,
4687
+ parent_infoP: renderer_context?.parent_infoP,
4688
+ keyP: renderer_context?.keyP,
4689
+ parent_nodeP: renderer_context?.parent_nodeP,
4690
+ });
4691
+
4692
+ if (!ensured_tree || !func.runtime.render.should_render_tree_node(ensured_tree)) {
4693
+ return '';
4694
+ }
4695
+
4696
+ if (ensured_tree.kind === 'placeholder') {
4697
+ if (renderer_context?.include_placeholders) {
4698
+ return `<!--xuda-placeholder:${func.runtime.render.escape_html(ensured_tree.id || '')}-->`;
4699
+ }
4700
+ return '';
4701
+ }
4702
+
4703
+ if (ensured_tree.kind === 'text') {
4704
+ return func.runtime.render.render_tree_terminal_content(ensured_tree) || '';
4705
+ }
4706
+
4707
+ const tag_name = func.runtime.render.get_string_renderer_tag_name(ensured_tree);
4708
+ if (!tag_name || tag_name.toLowerCase() === 'script') {
4709
+ return '';
4710
+ }
4711
+
4712
+ const attributes = func.runtime.render.get_tree_string_attributes(ensured_tree, renderer_context);
4713
+ const terminal_content = func.runtime.render.render_tree_terminal_content(ensured_tree);
4714
+ let children_html = terminal_content !== null ? terminal_content : await func.runtime.render.render_tree_children_to_string(ensured_tree, renderer_context);
4715
+
4716
+ if (ensured_tree.kind === 'widget' && !children_html) {
4717
+ children_html = func.runtime.render.get_widget_fallback_markup(ensured_tree);
4718
+ }
4719
+
4720
+ if (func.runtime.render.is_html_void_tag(tag_name)) {
4721
+ return `<${tag_name}${attributes}>`;
4722
+ }
4723
+
4724
+ return `<${tag_name}${attributes}>${children_html}</${tag_name}>`;
4725
+ };
4726
+ func.runtime.render.render_to_string = async function (options = {}) {
4727
+ const treeP = await func.runtime.render.ensure_tree_node({
4728
+ SESSION_ID: options.SESSION_ID,
4729
+ nodeP: options.treeP || options.nodeP,
4730
+ paramsP: options.paramsP,
4731
+ parent_infoP: options.parent_infoP,
4732
+ keyP: options.keyP,
4733
+ parent_nodeP: options.parent_nodeP,
4734
+ });
4735
+
4736
+ return await func.runtime.render.render_tree_to_string(treeP, options);
4737
+ };
4738
+ func.runtime.render.get_server_render_mode = function (options = {}) {
4739
+ const normalized = func.runtime.render.normalize_runtime_bootstrap({
4740
+ app_computing_mode: options.app_computing_mode,
4741
+ app_render_mode: options.app_render_mode,
4742
+ app_client_activation: options.app_client_activation,
4743
+ });
4744
+
4745
+ return normalized;
4746
+ };
4747
+ func.runtime.render.build_server_render_params = async function (options = {}) {
4748
+ const SESSION_ID = options.SESSION_ID;
4749
+ const prog_id = options.prog_id;
4750
+ const dsSessionP = options.dsSessionP;
4751
+ const _session = SESSION_OBJ?.[SESSION_ID] || {};
4752
+ const _ds = _session?.DS_GLB?.[dsSessionP] || {};
4753
+ const viewDoc = options.viewDoc || (await func.utils?.VIEWS_OBJ?.get?.(SESSION_ID, prog_id));
4754
+
4755
+ if (!viewDoc?.properties) {
4756
+ throw new Error(`view document not found for ${prog_id}`);
4757
+ }
4758
+
4759
+ const base_params = _ds?.screen_params ? func.runtime.render.safe_clone_tree_value(_ds.screen_params) : {};
4760
+ const screenId = options.screenId || base_params.screenId || `ssr_${prog_id}_${dsSessionP || '0'}`;
4761
+ const paramsP = {
4762
+ ...base_params,
4763
+ prog_id,
4764
+ sourceScreenP: null,
4765
+ $callingContainerP: null,
4766
+ triggerIdP: null,
4767
+ callingDataSource_objP: _ds,
4768
+ rowIdP: typeof options.rowIdP !== 'undefined' ? options.rowIdP : (_ds?.currentRecordId || null),
4769
+ renderType: viewDoc.properties?.renderType,
4770
+ parameters_obj_inP: options.parameters_obj_inP || base_params.parameters_obj_inP || options.parameters_raw_obj || {},
4771
+ source_functionP: options.source_functionP || base_params.source_functionP || 'render_string',
4772
+ is_panelP: false,
4773
+ screen_type: options.screen_type || base_params.screen_type || 'render_string',
4774
+ screenInfo: viewDoc,
4775
+ call_screen_propertiesP: base_params.call_screen_propertiesP,
4776
+ parentDataSourceNoP: typeof _ds?.parentDataSourceNo === 'undefined' || _ds?.parentDataSourceNo === null ? 0 : _ds.parentDataSourceNo,
4777
+ parameters_raw_obj: options.parameters_raw_obj || base_params.parameters_raw_obj || {},
4778
+ dsSessionP,
4779
+ screenId,
4780
+ containerIdP: base_params.containerIdP || `ssr_container_${screenId}`,
4781
+ };
4782
+
4783
+ if (_ds) {
4784
+ _ds.screen_params = paramsP;
4785
+ }
4786
+
4787
+ return paramsP;
4788
+ };
4789
+ func.runtime.render.build_prog_tree = async function (options = {}) {
4790
+ const SESSION_ID = options.SESSION_ID;
4791
+ const prog_id = options.prog_id;
4792
+ const viewDoc = options.viewDoc || (await func.utils?.VIEWS_OBJ?.get?.(SESSION_ID, prog_id));
4793
+
4794
+ if (!viewDoc?.progUi?.length) {
4795
+ throw new Error(`progUi not found for ${prog_id}`);
4796
+ }
4797
+
4798
+ const paramsP = options.paramsP || (await func.runtime.render.build_server_render_params({
4799
+ ...options,
4800
+ SESSION_ID,
4801
+ prog_id,
4802
+ viewDoc,
4803
+ }));
4804
+ const root_index = typeof options.root_index === 'number' ? options.root_index : 0;
4805
+ const root_node = func.runtime.render.safe_clone_tree_value(viewDoc.progUi[root_index]);
4806
+ const tree = await func.runtime.render.build_tree({
4807
+ SESSION_ID,
4808
+ nodeP: root_node,
4809
+ paramsP,
4810
+ });
4811
+
4812
+ return {
4813
+ tree,
4814
+ paramsP,
4815
+ viewDoc,
4816
+ };
4817
+ };
4818
+ func.runtime.render.build_ssr_payload = function (render_program, options = {}) {
4819
+ const runtime_profile = func.runtime.render.get_server_render_mode(options);
4820
+ return {
4821
+ contract: 'xuda.ssr.v1',
4822
+ prog_id: options.prog_id,
4823
+ screenId: render_program.paramsP.screenId,
4824
+ containerId: render_program.paramsP.containerIdP,
4825
+ app_computing_mode: runtime_profile.app_computing_mode,
4826
+ app_render_mode: runtime_profile.app_render_mode,
4827
+ app_client_activation: runtime_profile.app_client_activation,
4828
+ tree_contract: func.runtime.render.TREE_CONTRACT_VERSION,
4829
+ };
4830
+ };
4831
+ func.runtime.render.build_ssr_screen_html = function (html, render_program, options = {}) {
4832
+ const payload = func.runtime.render.build_ssr_payload(render_program, options);
4833
+ const screenId = func.runtime.render.escape_html_attribute(payload.screenId || '');
4834
+ const containerId = func.runtime.render.escape_html_attribute(payload.containerId || '');
4835
+ const activation = func.runtime.render.escape_html_attribute(payload.app_client_activation || 'takeover');
4836
+
4837
+ 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>`;
4838
+ };
4839
+ func.runtime.render.render_prog_to_string = async function (options = {}) {
4840
+ const render_program = await func.runtime.render.build_prog_tree(options);
4841
+ const html = await func.runtime.render.render_to_string({
4842
+ ...options,
4843
+ SESSION_ID: options.SESSION_ID,
4844
+ treeP: render_program.tree,
4845
+ paramsP: render_program.paramsP,
4846
+ });
4847
+ const ssr_payload = func.runtime.render.build_ssr_payload(render_program, options);
4848
+ const screen_html = func.runtime.render.build_ssr_screen_html(html, render_program, options);
4849
+
4850
+ return {
4851
+ prog_id: options.prog_id,
4852
+ dsSessionP: render_program.paramsP.dsSessionP,
4853
+ screenId: render_program.paramsP.screenId,
4854
+ html,
4855
+ screen_html,
4856
+ tree_json: func.runtime.render.serialize_tree(render_program.tree),
4857
+ paramsP: render_program.paramsP,
4858
+ ssr_payload,
4859
+ };
4860
+ };
3844
4861
  func.runtime = func.runtime || {};
3845
4862
  func.runtime.platform = func.runtime.platform || {};
3846
4863
 
@@ -7130,6 +8147,13 @@ func.utils.DOCS_OBJ = {};
7130
8147
  func.utils.DOCS_OBJ.get = async function (SESSION_ID, idP) {
7131
8148
  if (!idP || idP === '0') return;
7132
8149
 
8150
+ const normalize_runtime_doc = function (doc) {
8151
+ if (!doc || !func.runtime.program?.normalize_doc_for_runtime) {
8152
+ return doc;
8153
+ }
8154
+ return func.runtime.program.normalize_doc_for_runtime(doc);
8155
+ };
8156
+
7133
8157
  var _session = SESSION_OBJ[SESSION_ID];
7134
8158
  const _app_id = _session.app_id;
7135
8159
  if (!DOCS_OBJ[_app_id]) {
@@ -7151,7 +8175,8 @@ func.utils.DOCS_OBJ.get = async function (SESSION_ID, idP) {
7151
8175
  }
7152
8176
  let val = _session.project_data?.programs?.[idP];
7153
8177
  if (val) {
7154
- return (DOCS_OBJ[_app_id][idP] = val);
8178
+ DOCS_OBJ[_app_id][idP] = normalize_runtime_doc(val);
8179
+ return DOCS_OBJ[_app_id][idP];
7155
8180
  }
7156
8181
  }
7157
8182
 
@@ -7159,7 +8184,7 @@ func.utils.DOCS_OBJ.get = async function (SESSION_ID, idP) {
7159
8184
  const module = await func.common.get_module(SESSION_ID, `xuda-progs-loader-module.mjs`);
7160
8185
 
7161
8186
  if (idP !== 'system') {
7162
- DOCS_OBJ[_app_id][idP] = await module.DOCS_OBJ_get(SESSION_ID, idP);
8187
+ DOCS_OBJ[_app_id][idP] = normalize_runtime_doc(await module.DOCS_OBJ_get(SESSION_ID, idP));
7163
8188
  if (DOCS_OBJ[_app_id][idP] && xu_isEmpty(DOCS_OBJ[_app_id][idP])) {
7164
8189
  await func.utils.remove_cached_objects(SESSION_ID);
7165
8190
 
@@ -9230,6 +10255,15 @@ func.runtime.ui.ensure_embed_container = function (SESSION_ID) {
9230
10255
  const $root_element = func.runtime.ui.get_root_element(SESSION_ID);
9231
10256
  let $embed_container = func.runtime.ui.find_by_selector($root_element, `#embed_${SESSION_ID}`, true);
9232
10257
 
10258
+ if (!$embed_container.length) {
10259
+ const $ssr_embed_container = func.runtime.ui.find_by_selector($root_element, `[data-xuda-ssr-embed="true"]`, true);
10260
+ const ssr_embed_node = func.runtime.ui.get_first_node($ssr_embed_container);
10261
+ if (ssr_embed_node) {
10262
+ ssr_embed_node.id = 'embed_' + SESSION_ID;
10263
+ $embed_container = func.runtime.ui._wrap_matches([ssr_embed_node]);
10264
+ }
10265
+ }
10266
+
9233
10267
  if (!$embed_container.length) {
9234
10268
  const embed_node = document.createElement('div');
9235
10269
  embed_node.id = 'embed_' + SESSION_ID;
@@ -9265,12 +10299,44 @@ func.runtime.ui.get_root_tag_name = function () {
9265
10299
  }
9266
10300
  return root_tag_name;
9267
10301
  };
10302
+ func.runtime.ui.find_ssr_screen_host = function ($container, screenId, containerId) {
10303
+ const container_node = func.runtime.ui.get_first_node($container);
10304
+ if (!container_node || !screenId) {
10305
+ return null;
10306
+ }
10307
+
10308
+ const dialog_node =
10309
+ container_node.querySelector?.(`#${CSS?.escape ? CSS.escape(screenId) : screenId}`) ||
10310
+ container_node.querySelector?.(`[data-xuda-ssr-screen-id="${screenId}"]`);
10311
+ if (!dialog_node) {
10312
+ return null;
10313
+ }
10314
+
10315
+ const root_frame_node =
10316
+ (containerId ? dialog_node.querySelector?.(`#${CSS?.escape ? CSS.escape(containerId) : containerId}`) : null) ||
10317
+ dialog_node.querySelector?.('[data-xuda-ssr-root-frame="true"]');
10318
+ if (!root_frame_node) {
10319
+ return null;
10320
+ }
10321
+
10322
+ return {
10323
+ $dialogDiv: func.runtime.ui._wrap_matches([dialog_node]),
10324
+ $rootFrame: func.runtime.ui._wrap_matches([root_frame_node]),
10325
+ reused_ssr_host: true,
10326
+ };
10327
+ };
9268
10328
  func.runtime.ui.create_screen_host = function (SESSION_ID, screen_type, params, $callingContainerP, screenId) {
9269
10329
  var $dialogDiv;
9270
10330
  var $rootFrame;
10331
+ let reused_ssr_host = false;
9271
10332
 
9272
10333
  switch (screen_type) {
9273
10334
  case 'embed': {
10335
+ const ssr_host = func.runtime.ui.find_ssr_screen_host($callingContainerP, screenId, params?.containerIdP);
10336
+ if (ssr_host) {
10337
+ return ssr_host;
10338
+ }
10339
+
9274
10340
  const dialogNode = document.createElement('div');
9275
10341
  dialogNode.id = screenId;
9276
10342
  dialogNode.setAttribute('ui_engine', UI_FRAMEWORK_INSTALLED);
@@ -9320,6 +10386,7 @@ func.runtime.ui.create_screen_host = function (SESSION_ID, screen_type, params,
9320
10386
  return {
9321
10387
  $dialogDiv,
9322
10388
  $rootFrame,
10389
+ reused_ssr_host,
9323
10390
  };
9324
10391
  };
9325
10392
  func.runtime.ui.find_xu_ui_in_root = function (SESSION_ID, xu_ui_id) {
@@ -9884,6 +10951,11 @@ func.runtime.ui.build_container_xu_data = function (options) {
9884
10951
  func.runtime.ui.apply_container_meta = function ($div, options) {
9885
10952
  const div_node = func.runtime.ui.get_first_node($div);
9886
10953
  func.runtime.ui.set_attr(div_node, 'xu-ui-id', options.ui_id);
10954
+ func.runtime.ui.set_attr(div_node, 'data-xuda-kind', options.treeP?.kind || options.nodeP?.tagName || 'element');
10955
+ func.runtime.ui.set_attr(div_node, 'data-xuda-node-id', options.nodeP?.id || options.nodeP?.id_org || '');
10956
+ if (options.treeP?.meta?.tree_id !== null && typeof options.treeP?.meta?.tree_id !== 'undefined') {
10957
+ func.runtime.ui.set_attr(div_node, 'data-xuda-tree-id', options.treeP.meta.tree_id);
10958
+ }
9887
10959
  const xuData = func.runtime.ui.build_container_xu_data(options);
9888
10960
  if (options.parent_infoP?.iterate_info) {
9889
10961
  xuData.iterate_info = options.parent_infoP.iterate_info;
@@ -9978,6 +11050,38 @@ func.runtime.ui.create_container_element = function (div_typeP, attr_str, prop,
9978
11050
  }
9979
11051
  return func.runtime.ui.create_element(div, attr_str);
9980
11052
  };
11053
+ func.runtime.ui.find_hydration_candidate = function (options) {
11054
+ if (!func.runtime.render.is_hydration_mode(SESSION_OBJ?.[options.SESSION_ID])) {
11055
+ return null;
11056
+ }
11057
+ if (!func.runtime.render.should_use_ssr_payload(options.SESSION_ID, options.paramsP)) {
11058
+ return null;
11059
+ }
11060
+ if (options.is_placeholder || !options.treeP?.meta?.tree_id) {
11061
+ return null;
11062
+ }
11063
+
11064
+ const append_node = func.runtime.ui.get_first_node(options.$appendTo || options.$container);
11065
+ if (!append_node) {
11066
+ return null;
11067
+ }
11068
+
11069
+ const children = func.runtime.ui.get_children(append_node);
11070
+ for (let index = 0; index < children.length; index++) {
11071
+ const child = children[index];
11072
+ if (child?.__xuda_hydration_claimed) {
11073
+ continue;
11074
+ }
11075
+ if (func.runtime.ui.get_attr(child, 'data-xuda-tree-id') !== `${options.treeP.meta.tree_id}`) {
11076
+ continue;
11077
+ }
11078
+ child.__xuda_hydration_claimed = true;
11079
+ func.runtime.ui.set_attr(child, 'data-xuda-client-activation', 'hydrate');
11080
+ return child;
11081
+ }
11082
+
11083
+ return null;
11084
+ };
9981
11085
  func.runtime.ui.build_xu_ui_id_seed = function (nodeP, dsSessionP, key_path, currentRecordId) {
9982
11086
  const nodeId = nodeP.xu_tree_id || nodeP.id;
9983
11087
  const elem_key = `${nodeId}-${key_path}-${currentRecordId}`;
@@ -10030,7 +11134,11 @@ func.runtime.ui.create_container = async function (options) {
10030
11134
  try {
10031
11135
  const key_path = func.runtime.ui.build_container_key_path(container_xu_data, options.keyP, options.parent_infoP, options.nodeP, options.parent_nodeP);
10032
11136
  const elem_key = `${options.nodeP.xu_tree_id || options.nodeP.id}-${key_path}-${currentRecordId}`;
10033
- const $div = func.runtime.ui.create_container_element(options.div_typeP, options.attr_str, options.prop, options.nodeP, $appendTo);
11137
+ const hydration_candidate = func.runtime.ui.find_hydration_candidate({
11138
+ ...options,
11139
+ $appendTo,
11140
+ });
11141
+ const $div = hydration_candidate || func.runtime.ui.create_container_element(options.div_typeP, options.attr_str, options.prop, options.nodeP, $appendTo);
10034
11142
  const new_ui_id = await func.runtime.ui.generate_xu_ui_id(options.SESSION_ID, options.nodeP, options.$container, options.paramsP, options.keyP, {
10035
11143
  container_xu_data,
10036
11144
  currentRecordId,
@@ -10056,9 +11164,10 @@ func.runtime.ui.create_container = async function (options) {
10056
11164
  parent_infoP: options.parent_infoP,
10057
11165
  is_placeholder: options.is_placeholder,
10058
11166
  classP: options.classP,
11167
+ treeP: options.treeP,
10059
11168
  });
10060
11169
 
10061
- if (options.div_typeP !== 'svg') {
11170
+ if (!hydration_candidate && options.div_typeP !== 'svg') {
10062
11171
  func.runtime.ui.append_to($div, $appendTo);
10063
11172
  }
10064
11173
  return $div;
@@ -12739,9 +13848,10 @@ func.runtime.ui.init_screen = async function (options) {
12739
13848
 
12740
13849
  const _session = SESSION_OBJ[SESSION_ID];
12741
13850
  const screenInfo = structuredClone(screen_ret);
13851
+ const ssr_payload = func.runtime.render.should_use_ssr_payload(SESSION_ID, { prog_id }) ? func.runtime.render.get_ssr_payload(_session) : null;
12742
13852
 
12743
13853
  const screen_type = source_functionP?.split('_')?.[1];
12744
- const screenId = (glb.screen_num++).toString();
13854
+ const screenId = ssr_payload?.screenId || (glb.screen_num++).toString();
12745
13855
 
12746
13856
  if (SCREEN_BLOCKER_OBJ[prog_id + (sourceScreenP ? '_' + sourceScreenP : '')]) {
12747
13857
  const wait_for_SCREEN_BLOCKER_release = function () {
@@ -12783,6 +13893,8 @@ func.runtime.ui.init_screen = async function (options) {
12783
13893
  call_screen_propertiesP,
12784
13894
  parentDataSourceNoP: _session.DS_GLB?.[callingDataSource_objP?.dsSession]?.dsSession || callingDataSource_objP?.parentDataSourceNo || 0,
12785
13895
  parameters_raw_obj,
13896
+ containerIdP: ssr_payload?.containerId || null,
13897
+ ssr_payload,
12786
13898
  };
12787
13899
 
12788
13900
  const screen_host = func.runtime.ui.create_screen_host(SESSION_ID, screen_type, params, $callingContainerP, screenId);
@@ -12815,6 +13927,10 @@ func.runtime.ui.init_screen = async function (options) {
12815
13927
  func.runtime.ui.set_style($rootFrame, 'display', 'contents');
12816
13928
  }
12817
13929
 
13930
+ if (screen_host.reused_ssr_host && func.runtime.render.is_takeover_mode(_session)) {
13931
+ func.runtime.ui.empty($rootFrame);
13932
+ }
13933
+
12818
13934
  if (!is_panelP) func.UI.utils.indicator.screen.busy();
12819
13935
 
12820
13936
  const ret = await func.datasource.create(
@@ -12856,7 +13972,24 @@ func.runtime.ui.init_screen = async function (options) {
12856
13972
  }
12857
13973
  let node = structuredClone(viewDoc.progUi);
12858
13974
  if (!node.length) return console.warn('ui node empty');
12859
- 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);
13975
+ const root_tree = await func.runtime.render.build_tree({
13976
+ SESSION_ID,
13977
+ nodeP: node[0],
13978
+ paramsP: params,
13979
+ });
13980
+ const ret_render_$container = await func.runtime.render.render_tree(root_tree, {
13981
+ SESSION_ID,
13982
+ $container: $rootFrame,
13983
+ parent_infoP: null,
13984
+ paramsP: params,
13985
+ jobNoP,
13986
+ is_skeleton: null,
13987
+ keyP: null,
13988
+ refreshed_ds: null,
13989
+ parent_nodeP: null,
13990
+ check_existP: null,
13991
+ $root_container: $rootFrame,
13992
+ });
12860
13993
 
12861
13994
  if (!is_panelP) func.UI.utils.indicator.screen.normal();
12862
13995
 
@@ -13281,6 +14414,7 @@ func.runtime.ui.render_single_view_node = async function (options) {
13281
14414
  parent_infoP: options.parent_infoP,
13282
14415
  jobNoP: options.jobNoP,
13283
14416
  keyP: options.keyP,
14417
+ treeP: options.treeP,
13284
14418
  parent_nodeP: options.parent_nodeP,
13285
14419
  prop: options.prop,
13286
14420
  div_typeP: 'div',
@@ -13409,6 +14543,7 @@ func.runtime.ui.render_panel_node = async function (options) {
13409
14543
  parent_infoP: options.parent_infoP,
13410
14544
  jobNoP: options.jobNoP,
13411
14545
  keyP: options.keyP,
14546
+ treeP: options.treeP,
13412
14547
  parent_nodeP: options.parent_nodeP,
13413
14548
  prop: options.prop,
13414
14549
  $appendToP: $wrapper,
@@ -13738,6 +14873,14 @@ func.runtime.ui.screen_loading_done = async function (options) {
13738
14873
  });
13739
14874
 
13740
14875
  const _session = SESSION_OBJ[options.SESSION_ID];
14876
+ if (func.runtime.render.should_use_ssr_payload(options.SESSION_ID, options.paramsP)) {
14877
+ const root_node = func.runtime.ui.get_root_node(options.SESSION_ID);
14878
+ if (root_node) {
14879
+ func.runtime.ui.set_attr(root_node, 'data-xuda-client-activation', _session.opt.app_client_activation || 'none');
14880
+ func.runtime.ui.set_attr(root_node, 'data-xuda-ssr-status', _session.opt.app_client_activation === 'hydrate' ? 'hydrated' : 'taken-over');
14881
+ }
14882
+ func.runtime.render.mark_ssr_payload_consumed(options.SESSION_ID);
14883
+ }
13741
14884
  func.events.delete_job(options.SESSION_ID, options.jobNoP);
13742
14885
  func.UI.utils.screen_blocker(false, options.paramsP.prog_id + (options.paramsP.sourceScreenP ? '_' + options.paramsP.sourceScreenP : ''));
13743
14886
  if (_session.prog_id === options.paramsP.prog_id) {
@@ -15580,6 +16723,9 @@ func.runtime.render.get_screen_context = function (SESSION_ID, $container, param
15580
16723
  };
15581
16724
  func.runtime.render.get_node_attributes = function (nodeP) {
15582
16725
  try {
16726
+ if (func.runtime.render.is_tree_node?.(nodeP)) {
16727
+ return nodeP.attributes;
16728
+ }
15583
16729
  return nodeP?.attributes;
15584
16730
  } catch (error) {
15585
16731
  return undefined;
@@ -15697,6 +16843,7 @@ func.runtime.render.prepare_draw_context = async function (options) {
15697
16843
  parent_infoP: options.parent_infoP,
15698
16844
  jobNoP: options.jobNoP,
15699
16845
  keyP: options.keyP,
16846
+ treeP: options.treeP,
15700
16847
  parent_nodeP: options.parent_nodeP,
15701
16848
  prop: options.prop,
15702
16849
  div_typeP: options.element,
@@ -15719,6 +16866,7 @@ func.runtime.render.prepare_draw_context = async function (options) {
15719
16866
  parent_infoP: options.parent_infoP,
15720
16867
  jobNoP: options.jobNoP,
15721
16868
  keyP: options.keyP,
16869
+ treeP: options.treeP,
15722
16870
  parent_nodeP: options.parent_nodeP,
15723
16871
  prop: options.prop,
15724
16872
  div_typeP: options.element,
@@ -16575,11 +17723,16 @@ func.runtime.widgets.render_node = async function (options) {
16575
17723
  parent_infoP: options.parent_infoP,
16576
17724
  jobNoP: options.jobNoP,
16577
17725
  keyP: options.keyP,
17726
+ treeP: options.treeP,
16578
17727
  parent_nodeP: options.parent_nodeP,
16579
17728
  prop: options.prop,
16580
17729
  classP: 'widget_wrapper',
16581
17730
  });
16582
17731
 
17732
+ if (func.runtime.render.is_hydration_mode(SESSION_OBJ?.[options.SESSION_ID]) && func.runtime.render.should_use_ssr_payload(options.SESSION_ID, options.paramsP)) {
17733
+ func.runtime.ui.empty($div);
17734
+ }
17735
+
16583
17736
  const widget_context = func.runtime.widgets.create_context(options.SESSION_ID, options.paramsP, options.prop);
16584
17737
  const { plugin_name, method, propsP, plugin: _plugin } = widget_context;
16585
17738
  const report_error = function (descP, warn) {
@@ -16682,9 +17835,370 @@ func.runtime.widgets = func.runtime.widgets || {};
16682
17835
 
16683
17836
  // Browser-only special node renderers live here so the core render tree can stay focused.
16684
17837
 
17838
+ const normalize_runtime_tag_name = function (tag_name) {
17839
+ return `${tag_name || ''}`.trim().toLowerCase();
17840
+ };
17841
+
17842
+ const get_runtime_node_attributes = function (nodeP) {
17843
+ if (!nodeP?.attributes || typeof nodeP.attributes !== 'object') {
17844
+ return {};
17845
+ }
17846
+
17847
+ return nodeP.attributes;
17848
+ };
17849
+
17850
+ const get_runtime_node_content = function (nodeP) {
17851
+ if (typeof nodeP?.content === 'string') {
17852
+ return nodeP.content;
17853
+ }
17854
+
17855
+ if (typeof nodeP?.text === 'string') {
17856
+ return nodeP.text;
17857
+ }
17858
+
17859
+ if (!Array.isArray(nodeP?.children)) {
17860
+ return '';
17861
+ }
17862
+
17863
+ return nodeP.children
17864
+ .map(function (child) {
17865
+ if (typeof child === 'string') {
17866
+ return child;
17867
+ }
17868
+ if (typeof child?.content === 'string') {
17869
+ return child.content;
17870
+ }
17871
+ if (typeof child?.text === 'string') {
17872
+ return child.text;
17873
+ }
17874
+ return '';
17875
+ })
17876
+ .join('');
17877
+ };
17878
+
17879
+ const get_runtime_asset_key = function (options, tag_name) {
17880
+ const source_node = options.treeP || options.nodeP || {};
17881
+ const parts = [
17882
+ 'xuda-html-asset',
17883
+ options.paramsP?.prog_id || '',
17884
+ tag_name,
17885
+ source_node.id || source_node.id_org || '',
17886
+ typeof options.keyP === 'undefined' || options.keyP === null ? '' : `${options.keyP}`,
17887
+ ].filter(function (part) {
17888
+ return `${part || ''}`.trim() !== '';
17889
+ });
17890
+
17891
+ return parts.join(':');
17892
+ };
17893
+
17894
+ const get_runtime_asset_signature = function (attributes, content) {
17895
+ const normalized_attributes = {};
17896
+ const attr_keys = Object.keys(attributes || {}).sort();
17897
+
17898
+ for (let index = 0; index < attr_keys.length; index++) {
17899
+ const key = attr_keys[index];
17900
+ normalized_attributes[key] = attributes[key];
17901
+ }
17902
+
17903
+ return JSON.stringify({
17904
+ attributes: normalized_attributes,
17905
+ content: `${content || ''}`,
17906
+ });
17907
+ };
17908
+
17909
+ const escape_runtime_asset_selector_value = function (value) {
17910
+ const win = func.runtime.platform.get_window?.();
17911
+ if (win?.CSS?.escape) {
17912
+ return win.CSS.escape(value);
17913
+ }
17914
+
17915
+ return `${value || ''}`.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
17916
+ };
17917
+
17918
+ const find_runtime_head_asset = function (head, tag_name, asset_key) {
17919
+ if (!head?.querySelector || !asset_key) {
17920
+ return null;
17921
+ }
17922
+
17923
+ return head.querySelector(`${tag_name}[data-xuda-asset-key="${escape_runtime_asset_selector_value(asset_key)}"]`);
17924
+ };
17925
+
17926
+ const find_runtime_head_asset_by_attr = function (head, tag_name, attr_name, attr_value) {
17927
+ if (!head?.querySelectorAll || !attr_name || !attr_value) {
17928
+ return null;
17929
+ }
17930
+
17931
+ const candidates = Array.from(head.querySelectorAll(tag_name));
17932
+ return (
17933
+ candidates.find(function (candidate) {
17934
+ return candidate.getAttribute(attr_name) === attr_value || candidate[attr_name] === attr_value;
17935
+ }) || null
17936
+ );
17937
+ };
17938
+
17939
+ const wait_for_runtime_asset_load = function (node) {
17940
+ if (!node?.addEventListener) {
17941
+ return Promise.resolve(node);
17942
+ }
17943
+
17944
+ if (node.getAttribute?.('data-xuda-loaded') === 'true') {
17945
+ return Promise.resolve(node);
17946
+ }
17947
+
17948
+ if (!node.getAttribute?.('data-xuda-asset-key')) {
17949
+ return Promise.resolve(node);
17950
+ }
17951
+
17952
+ const tag_name = normalize_runtime_tag_name(node.tagName);
17953
+ if (tag_name !== 'script' && !(tag_name === 'link' && normalize_runtime_tag_name(node.getAttribute?.('rel')) === 'stylesheet')) {
17954
+ return Promise.resolve(node);
17955
+ }
17956
+
17957
+ return new Promise(function (resolve) {
17958
+ const done = function () {
17959
+ node.setAttribute?.('data-xuda-loaded', 'true');
17960
+ resolve(node);
17961
+ };
17962
+
17963
+ node.addEventListener('load', done, { once: true });
17964
+ node.addEventListener('error', done, { once: true });
17965
+ });
17966
+ };
17967
+
17968
+ const remove_runtime_head_asset = function (node) {
17969
+ if (node?.parentNode?.removeChild) {
17970
+ node.parentNode.removeChild(node);
17971
+ }
17972
+ };
17973
+
17974
+ const apply_runtime_asset_metadata = function (node, asset_key, signature) {
17975
+ if (!node?.setAttribute) {
17976
+ return node;
17977
+ }
17978
+
17979
+ if (asset_key) {
17980
+ node.setAttribute('data-xuda-asset-key', asset_key);
17981
+ }
17982
+ node.setAttribute('data-xuda-asset-signature', signature);
17983
+ return node;
17984
+ };
17985
+
17986
+ const create_runtime_head_element = function (doc, tag_name, attributes, asset_key, signature) {
17987
+ const node = doc.createElement(tag_name);
17988
+ apply_runtime_asset_metadata(node, asset_key, signature);
17989
+ func.runtime.platform.apply_element_attributes(node, attributes);
17990
+ return node;
17991
+ };
17992
+
17993
+ const queue_runtime_html_script_task = function (task) {
17994
+ const queue =
17995
+ func.runtime.render._html_script_queue ||
17996
+ (func.runtime.render._html_script_queue = {
17997
+ items: [],
17998
+ scheduled: false,
17999
+ running: false,
18000
+ });
18001
+
18002
+ queue.items.push(task);
18003
+
18004
+ if (queue.scheduled || queue.running) {
18005
+ return;
18006
+ }
18007
+
18008
+ queue.scheduled = true;
18009
+
18010
+ const drain_queue = async function () {
18011
+ if (queue.running) {
18012
+ return;
18013
+ }
18014
+
18015
+ queue.running = true;
18016
+ queue.scheduled = false;
18017
+
18018
+ try {
18019
+ while (queue.items.length) {
18020
+ const next_task = queue.items.shift();
18021
+ await next_task();
18022
+ }
18023
+ } catch (error) {
18024
+ console.error(error);
18025
+ } finally {
18026
+ queue.running = false;
18027
+ if (queue.items.length && !queue.scheduled) {
18028
+ queue.scheduled = true;
18029
+ schedule_runtime_html_script_queue();
18030
+ }
18031
+ }
18032
+ };
18033
+
18034
+ const schedule_runtime_html_script_queue = function () {
18035
+ const win = func.runtime.platform.get_window?.();
18036
+ const run_later = function () {
18037
+ setTimeout(function () {
18038
+ drain_queue();
18039
+ }, 0);
18040
+ };
18041
+
18042
+ if (win?.requestAnimationFrame) {
18043
+ win.requestAnimationFrame(function () {
18044
+ win.requestAnimationFrame(run_later);
18045
+ });
18046
+ return;
18047
+ }
18048
+
18049
+ run_later();
18050
+ };
18051
+
18052
+ schedule_runtime_html_script_queue();
18053
+ };
18054
+
18055
+ const upsert_runtime_head_element = async function (options) {
18056
+ const doc = func.runtime.platform.get_document?.();
18057
+ const head = doc?.head;
18058
+ const tag_name = normalize_runtime_tag_name(options.tag_name);
18059
+
18060
+ if (!doc?.createElement || !head?.appendChild || !tag_name) {
18061
+ return null;
18062
+ }
18063
+
18064
+ const asset_key = options.asset_key || '';
18065
+ const signature = options.signature || '';
18066
+ const attributes = options.attributes || {};
18067
+ const content = typeof options.content === 'string' ? options.content : '';
18068
+
18069
+ const existing_by_key = find_runtime_head_asset(head, tag_name, asset_key);
18070
+ if (existing_by_key && existing_by_key.getAttribute('data-xuda-asset-signature') === signature) {
18071
+ return options.await_load ? await wait_for_runtime_asset_load(existing_by_key) : existing_by_key;
18072
+ }
18073
+
18074
+ if (existing_by_key) {
18075
+ remove_runtime_head_asset(existing_by_key);
18076
+ }
18077
+
18078
+ if (options.find_existing_attr?.name && options.find_existing_attr?.value) {
18079
+ const existing_by_attr = find_runtime_head_asset_by_attr(head, tag_name, options.find_existing_attr.name, options.find_existing_attr.value);
18080
+ if (existing_by_attr) {
18081
+ apply_runtime_asset_metadata(existing_by_attr, asset_key, signature);
18082
+ existing_by_attr.setAttribute?.('data-xuda-loaded', 'true');
18083
+ return existing_by_attr;
18084
+ }
18085
+ }
18086
+
18087
+ const node = create_runtime_head_element(doc, tag_name, attributes, asset_key, signature);
18088
+
18089
+ if (tag_name === 'script' && attributes.src && !Object.prototype.hasOwnProperty.call(attributes, 'async')) {
18090
+ const script_type = normalize_runtime_tag_name(attributes.type);
18091
+ if (script_type !== 'module') {
18092
+ node.async = false;
18093
+ }
18094
+ }
18095
+
18096
+ if (tag_name === 'style' || (tag_name === 'script' && !attributes.src)) {
18097
+ node.textContent = content;
18098
+ }
18099
+
18100
+ head.appendChild(node);
18101
+
18102
+ if (tag_name !== 'script' && tag_name !== 'link') {
18103
+ node.setAttribute('data-xuda-loaded', 'true');
18104
+ }
18105
+
18106
+ return options.await_load ? await wait_for_runtime_asset_load(node) : node;
18107
+ };
18108
+
18109
+ const render_runtime_html_asset = async function (options) {
18110
+ if (options.is_skeleton) {
18111
+ return options.$container;
18112
+ }
18113
+
18114
+ const nodeP = options.treeP || options.nodeP || {};
18115
+ const tag_name = normalize_runtime_tag_name(nodeP.tagName);
18116
+ const attributes = { ...get_runtime_node_attributes(nodeP) };
18117
+ const content = get_runtime_node_content(nodeP);
18118
+ const asset_key = get_runtime_asset_key(options, tag_name);
18119
+ const signature = get_runtime_asset_signature(attributes, content);
18120
+
18121
+ switch (tag_name) {
18122
+ case 'title':
18123
+ func.runtime.platform.set_title(content);
18124
+ return options.$container;
18125
+ case 'style':
18126
+ await upsert_runtime_head_element({
18127
+ tag_name,
18128
+ attributes,
18129
+ content,
18130
+ asset_key,
18131
+ signature,
18132
+ });
18133
+ return options.$container;
18134
+ case 'meta':
18135
+ if (!Object.keys(attributes).length) {
18136
+ return options.$container;
18137
+ }
18138
+ await upsert_runtime_head_element({
18139
+ tag_name,
18140
+ attributes,
18141
+ asset_key,
18142
+ signature,
18143
+ });
18144
+ return options.$container;
18145
+ case 'link': {
18146
+ const href = `${attributes.href || ''}`.trim();
18147
+ if (!href) {
18148
+ return options.$container;
18149
+ }
18150
+ await upsert_runtime_head_element({
18151
+ tag_name,
18152
+ attributes,
18153
+ asset_key,
18154
+ signature,
18155
+ find_existing_attr: {
18156
+ name: 'href',
18157
+ value: href,
18158
+ },
18159
+ await_load: normalize_runtime_tag_name(attributes.rel) === 'stylesheet',
18160
+ });
18161
+ return options.$container;
18162
+ }
18163
+ case 'script': {
18164
+ const src = `${attributes.src || ''}`.trim();
18165
+ if (!src && !content.trim()) {
18166
+ return options.$container;
18167
+ }
18168
+ queue_runtime_html_script_task(async function () {
18169
+ await upsert_runtime_head_element({
18170
+ tag_name,
18171
+ attributes,
18172
+ content,
18173
+ asset_key,
18174
+ signature,
18175
+ find_existing_attr: src
18176
+ ? {
18177
+ name: 'src',
18178
+ value: src,
18179
+ }
18180
+ : null,
18181
+ await_load: !!src,
18182
+ });
18183
+ });
18184
+ return options.$container;
18185
+ }
18186
+ default:
18187
+ return null;
18188
+ }
18189
+ };
18190
+
16685
18191
  func.runtime.render.render_special_node = async function (options) {
16686
- if (options.nodeP.content && options.nodeP.attributes) {
16687
- options.nodeP.attributes['xu-content'] = options.nodeP.content;
18192
+ const treeP = options.treeP || null;
18193
+ const nodeP = options.nodeP || func.runtime.render.get_tree_source_node(treeP);
18194
+ const render_tag_name = treeP?.tagName || nodeP?.tagName;
18195
+ const normalized_render_tag_name = normalize_runtime_tag_name(render_tag_name);
18196
+ const is_native_html_asset = ['title', 'style', 'meta', 'link', 'script'].includes(normalized_render_tag_name);
18197
+
18198
+ if (!is_native_html_asset && treeP?.content && nodeP?.attributes) {
18199
+ nodeP.attributes['xu-content'] = treeP.content;
18200
+ } else if (!is_native_html_asset && nodeP?.content && nodeP.attributes) {
18201
+ nodeP.attributes['xu-content'] = nodeP.content;
16688
18202
  }
16689
18203
 
16690
18204
  const renderers = {
@@ -16694,6 +18208,7 @@ func.runtime.render.render_special_node = async function (options) {
16694
18208
  SESSION_ID: options.SESSION_ID,
16695
18209
  $container: options.$container,
16696
18210
  $root_container: options.$root_container,
18211
+ treeP,
16697
18212
  nodeP: options.nodeP,
16698
18213
  parent_infoP: options.parent_infoP,
16699
18214
  paramsP: options.paramsP,
@@ -16710,6 +18225,7 @@ func.runtime.render.render_special_node = async function (options) {
16710
18225
  SESSION_ID: options.SESSION_ID,
16711
18226
  $container: options.$container,
16712
18227
  $root_container: options.$root_container,
18228
+ treeP,
16713
18229
  nodeP: options.nodeP,
16714
18230
  parent_infoP: options.parent_infoP,
16715
18231
  paramsP: options.paramsP,
@@ -16729,6 +18245,7 @@ func.runtime.render.render_special_node = async function (options) {
16729
18245
  SESSION_ID: options.SESSION_ID,
16730
18246
  $container: options.$container,
16731
18247
  $root_container: options.$root_container,
18248
+ treeP,
16732
18249
  nodeP: options.nodeP,
16733
18250
  parent_infoP: options.parent_infoP,
16734
18251
  paramsP: options.paramsP,
@@ -16745,6 +18262,7 @@ func.runtime.render.render_special_node = async function (options) {
16745
18262
  SESSION_ID: options.SESSION_ID,
16746
18263
  $container: options.$container,
16747
18264
  $root_container: options.$root_container,
18265
+ treeP,
16748
18266
  nodeP: options.nodeP,
16749
18267
  parent_infoP: options.parent_infoP,
16750
18268
  paramsP: options.paramsP,
@@ -16756,9 +18274,24 @@ func.runtime.render.render_special_node = async function (options) {
16756
18274
  refreshed_ds: options.refreshed_ds,
16757
18275
  });
16758
18276
  },
18277
+ title: async function () {
18278
+ return await render_runtime_html_asset(options);
18279
+ },
18280
+ style: async function () {
18281
+ return await render_runtime_html_asset(options);
18282
+ },
18283
+ meta: async function () {
18284
+ return await render_runtime_html_asset(options);
18285
+ },
18286
+ link: async function () {
18287
+ return await render_runtime_html_asset(options);
18288
+ },
18289
+ script: async function () {
18290
+ return await render_runtime_html_asset(options);
18291
+ },
16759
18292
  };
16760
18293
 
16761
- const renderer = renderers[options.nodeP.tagName];
18294
+ const renderer = renderers[normalized_render_tag_name];
16762
18295
  if (!renderer) {
16763
18296
  return { handled: false };
16764
18297
  }
@@ -16872,8 +18405,9 @@ func.runtime.widgets = func.runtime.widgets || {};
16872
18405
  // Browser-only render tree entrypoints live here so draw/cache helpers can stay focused.
16873
18406
 
16874
18407
  func.runtime.render.create_tree_runtime = function (options) {
18408
+ const render_node = options.nodeP || func.runtime.render.get_tree_source_node(options.treeP);
16875
18409
  const render_context = func.runtime.render.get_screen_context(options.SESSION_ID, options.$container, options.paramsP, options.is_skeleton);
16876
- const prop = func.runtime.render.get_node_attributes(options.nodeP);
18410
+ const prop = func.runtime.render.get_node_attributes(options.treeP || render_node);
16877
18411
  const is_mobile = render_context.is_mobile ? true : false;
16878
18412
  const hover_handlers = func.runtime.render.create_hover_handlers({
16879
18413
  SESSION_ID: options.SESSION_ID,
@@ -16888,13 +18422,22 @@ func.runtime.render.create_tree_runtime = function (options) {
16888
18422
  return await func.runtime.ui.close_modal_session(options.SESSION_ID, modal_id);
16889
18423
  };
16890
18424
  const iterate_child = async function ($divP, nodeP, parent_infoP, $root_container, before_record_function) {
18425
+ const child_tree = await func.runtime.render.ensure_tree_node({
18426
+ SESSION_ID: options.SESSION_ID,
18427
+ nodeP: nodeP || options.treeP || render_node,
18428
+ parent_infoP,
18429
+ paramsP: options.paramsP,
18430
+ keyP: options.keyP,
18431
+ parent_nodeP: render_node,
18432
+ pathP: options.treeP?.meta?.path || [],
18433
+ });
16891
18434
  return await func.runtime.render.iterate_children({
16892
18435
  $divP,
16893
- nodeP,
18436
+ nodeP: child_tree,
16894
18437
  is_mobile,
16895
18438
  before_record_function,
16896
18439
  render_child: async function (key, child) {
16897
- await options.render_child($divP, child, parent_infoP, key, nodeP, $root_container);
18440
+ await options.render_child($divP, child, parent_infoP, key, render_node, $root_container);
16898
18441
  },
16899
18442
  });
16900
18443
  };
@@ -16924,7 +18467,7 @@ func.runtime.render.draw_node = async function (options) {
16924
18467
  check_existP: options.check_existP,
16925
18468
  $root_container: options.$root_container,
16926
18469
  prop: options.prop,
16927
- element: options.nodeP.tagName,
18470
+ element: options.treeP?.tagName || options.nodeP.tagName,
16928
18471
  hover_handlers: options.hover_handlers,
16929
18472
  include_hover_click: options.include_hover_click,
16930
18473
  iterate_child: options.iterate_child,
@@ -16947,21 +18490,33 @@ func.runtime.render.draw_node = async function (options) {
16947
18490
  nodeP: options.nodeP,
16948
18491
  });
16949
18492
  };
16950
- 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) {
16951
- if (!nodeP) return;
16952
- const perf_end = func.runtime?.perf?.start?.(SESSION_ID, 'render_ui_tree');
16953
- func.runtime?.perf?.increment_map?.(SESSION_ID, 'render_node_counts', nodeP.id || nodeP.id_org || nodeP.tagName || 'unknown');
18493
+ func.runtime.render.render_tree = async function (treeP, renderer_context) {
18494
+ if (!treeP) return;
18495
+ const nodeP = func.runtime.render.get_tree_source_node(treeP);
18496
+ const perf_end = func.runtime?.perf?.start?.(renderer_context.SESSION_ID, 'render_ui_tree');
18497
+ func.runtime?.perf?.increment_map?.(renderer_context.SESSION_ID, 'render_node_counts', treeP.id || nodeP?.id || nodeP?.id_org || treeP.tagName || 'unknown');
16954
18498
  try {
16955
18499
  const tree_runtime = func.runtime.render.create_tree_runtime({
16956
- SESSION_ID,
16957
- $container,
18500
+ SESSION_ID: renderer_context.SESSION_ID,
18501
+ $container: renderer_context.$container,
18502
+ treeP,
16958
18503
  nodeP,
16959
- parent_infoP,
16960
- paramsP,
16961
- jobNoP,
16962
- is_skeleton,
18504
+ parent_infoP: renderer_context.parent_infoP,
18505
+ paramsP: renderer_context.paramsP,
18506
+ jobNoP: renderer_context.jobNoP,
18507
+ is_skeleton: renderer_context.is_skeleton,
18508
+ keyP: renderer_context.keyP,
16963
18509
  render_child: async function ($divP, child, parent_infoP, key, parentNodeP, rootContainerP) {
16964
- await func.runtime.render.render_ui_tree(SESSION_ID, $divP, child, parent_infoP, paramsP, jobNoP, is_skeleton, key, null, parentNodeP, null, rootContainerP);
18510
+ await func.runtime.render.render_tree(child, {
18511
+ ...renderer_context,
18512
+ $container: $divP,
18513
+ parent_infoP,
18514
+ keyP: key,
18515
+ refreshed_ds: null,
18516
+ parent_nodeP: parentNodeP,
18517
+ check_existP: null,
18518
+ $root_container: rootContainerP,
18519
+ });
16965
18520
  },
16966
18521
  });
16967
18522
  const render_context = tree_runtime.render_context;
@@ -16973,23 +18528,24 @@ func.runtime.render.render_ui_tree = async function (SESSION_ID, $container, nod
16973
18528
  const iterate_child = tree_runtime.iterate_child;
16974
18529
 
16975
18530
  func.runtime.render.log_tree_debug({
16976
- SESSION_ID,
16977
- paramsP,
18531
+ SESSION_ID: renderer_context.SESSION_ID,
18532
+ paramsP: renderer_context.paramsP,
16978
18533
  nodeP,
16979
18534
  _ds,
16980
18535
  });
16981
18536
  const special_render = await func.runtime.render.render_special_node({
16982
- SESSION_ID,
16983
- $container,
16984
- $root_container,
18537
+ SESSION_ID: renderer_context.SESSION_ID,
18538
+ $container: renderer_context.$container,
18539
+ $root_container: renderer_context.$root_container,
18540
+ treeP,
16985
18541
  nodeP,
16986
- parent_infoP,
16987
- paramsP,
16988
- jobNoP,
16989
- is_skeleton,
16990
- keyP,
16991
- refreshed_ds,
16992
- parent_nodeP,
18542
+ parent_infoP: renderer_context.parent_infoP,
18543
+ paramsP: renderer_context.paramsP,
18544
+ jobNoP: renderer_context.jobNoP,
18545
+ is_skeleton: renderer_context.is_skeleton,
18546
+ keyP: renderer_context.keyP,
18547
+ refreshed_ds: renderer_context.refreshed_ds,
18548
+ parent_nodeP: renderer_context.parent_nodeP,
16993
18549
  prop,
16994
18550
  render_context,
16995
18551
  hover_handlers,
@@ -16997,22 +18553,23 @@ func.runtime.render.render_ui_tree = async function (SESSION_ID, $container, nod
16997
18553
  close_modal,
16998
18554
  });
16999
18555
  if (special_render.handled) {
17000
- func.runtime?.perf?.increment?.(SESSION_ID, 'render_special_node_hits');
18556
+ func.runtime?.perf?.increment?.(renderer_context.SESSION_ID, 'render_special_node_hits');
17001
18557
  return special_render.result;
17002
18558
  }
17003
18559
  return await func.runtime.render.draw_node({
17004
- SESSION_ID,
17005
- $container,
17006
- $root_container,
18560
+ SESSION_ID: renderer_context.SESSION_ID,
18561
+ $container: renderer_context.$container,
18562
+ $root_container: renderer_context.$root_container,
18563
+ treeP,
17007
18564
  nodeP,
17008
- parent_infoP,
17009
- paramsP,
17010
- jobNoP,
17011
- is_skeleton,
17012
- keyP,
17013
- refreshed_ds,
17014
- parent_nodeP,
17015
- check_existP,
18565
+ parent_infoP: renderer_context.parent_infoP,
18566
+ paramsP: renderer_context.paramsP,
18567
+ jobNoP: renderer_context.jobNoP,
18568
+ is_skeleton: renderer_context.is_skeleton,
18569
+ keyP: renderer_context.keyP,
18570
+ refreshed_ds: renderer_context.refreshed_ds,
18571
+ parent_nodeP: renderer_context.parent_nodeP,
18572
+ check_existP: renderer_context.check_existP,
17016
18573
  prop,
17017
18574
  hover_handlers,
17018
18575
  include_hover_click,
@@ -17021,6 +18578,31 @@ func.runtime.render.render_ui_tree = async function (SESSION_ID, $container, nod
17021
18578
  } finally {
17022
18579
  perf_end?.();
17023
18580
  }
18581
+ };
18582
+ 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) {
18583
+ if (!nodeP) return;
18584
+ const treeP = await func.runtime.render.ensure_tree_node({
18585
+ SESSION_ID,
18586
+ nodeP,
18587
+ parent_infoP,
18588
+ paramsP,
18589
+ keyP,
18590
+ parent_nodeP,
18591
+ });
18592
+
18593
+ return await func.runtime.render.render_tree(treeP, {
18594
+ SESSION_ID,
18595
+ $container,
18596
+ parent_infoP,
18597
+ paramsP,
18598
+ jobNoP,
18599
+ is_skeleton,
18600
+ keyP,
18601
+ refreshed_ds,
18602
+ parent_nodeP,
18603
+ check_existP,
18604
+ $root_container,
18605
+ });
17024
18606
  };
17025
18607
  func.runtime = func.runtime || {};
17026
18608
  func.runtime.ui = func.runtime.ui || {};
@@ -18544,6 +20126,7 @@ func.runtime.render.handle_xu_panel_program = async function (options) {
18544
20126
  parent_infoP: options.parent_infoP,
18545
20127
  jobNoP: options.jobNoP,
18546
20128
  keyP: options.keyP,
20129
+ treeP: options.treeP,
18547
20130
  parent_nodeP: options.parent_nodeP,
18548
20131
  prop: options.nodeP.attributes,
18549
20132
  $appendToP: $wrapper,
@@ -22557,6 +24140,12 @@ function xuda(...args) {
22557
24140
  if (typeof opt !== 'object') {
22558
24141
  return console.error('Xuda Error - opt argument is not an object');
22559
24142
  }
24143
+
24144
+ if (!opt.ssr_payload && func.runtime.platform.get_window()?.__XUDA_SSR__) {
24145
+ opt.ssr_payload = func.runtime.platform.get_window().__XUDA_SSR__;
24146
+ }
24147
+ func.runtime.render.apply_runtime_bootstrap_defaults(opt);
24148
+
22560
24149
  glb.URL_PARAMS = func.common.getJsonFromUrl(platform.get_url_href());
22561
24150
 
22562
24151
  glb.worker_type = 'Worker';