@xuda.io/runtime-bundle 1.0.1412 → 1.0.1413

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.
@@ -4282,6 +4282,23 @@ func.datasource.execute = async function (SESSION_ID, dataSourceSession, IS_DATA
4282
4282
  let prog_obj = await func.utils.VIEWS_OBJ.get(SESSION_ID, _ds.prog_id);
4283
4283
 
4284
4284
  const callback_datasource = async function () {
4285
+ const run_on_load_events = async function () {
4286
+ if (!(await func.datasource.get_view_events_count(SESSION_ID, dataSourceSession, 'on_load'))) {
4287
+ return false;
4288
+ }
4289
+ await func.datasource.execute_view_events(SESSION_ID, dataSourceSession, 'on_load');
4290
+ return true;
4291
+ };
4292
+ const schedule_panel_on_load_events = function () {
4293
+ setTimeout(async function () {
4294
+ try {
4295
+ await run_on_load_events();
4296
+ } catch (error) {
4297
+ console.error(error);
4298
+ }
4299
+ }, 0);
4300
+ };
4301
+
4285
4302
  if (typeof IS_WORKER === 'undefined' && typeof IS_DOCKER === 'undefined' && typeof IS_PROCESS_SERVER === 'undefined' && _ds.viewSourceProp === 'globals') {
4286
4303
  if (!['main'].includes(_session.opt.app_computing_mode)) {
4287
4304
  await func.index.call_worker(SESSION_ID, {
@@ -4291,10 +4308,13 @@ func.datasource.execute = async function (SESSION_ID, dataSourceSession, IS_DATA
4291
4308
  }
4292
4309
  }
4293
4310
 
4294
- if (await func.datasource.get_view_events_count(SESSION_ID, dataSourceSession, 'on_load')) {
4295
- await func.datasource.execute_view_events(SESSION_ID, dataSourceSession, 'on_load');
4311
+ if (args.is_panelP) {
4312
+ const callback_ret = await func.datasource.callback(SESSION_ID, dataSourceSession, args.rowIdP, args.jobNoP, _ds.prog_id);
4313
+ schedule_panel_on_load_events();
4314
+ return callback_ret;
4296
4315
  }
4297
- // }
4316
+
4317
+ await run_on_load_events();
4298
4318
  return await func.datasource.callback(SESSION_ID, dataSourceSession, args.rowIdP, args.jobNoP, _ds.prog_id);
4299
4319
  };
4300
4320
 
@@ -5832,6 +5852,7 @@ func.datasource.update = async function (SESSION_ID, datasource_changes, update_
5832
5852
  dsSession_changed: findMin(datasource_changed),
5833
5853
  avoid_xu_for_refresh,
5834
5854
  trigger,
5855
+ ignore_screen_blocker: true,
5835
5856
  });
5836
5857
  // await removed from the below function cause to dead lock Mar 3 25
5837
5858
  await func.runtime.ui.refresh_screen({
@@ -10131,7 +10152,7 @@ func.UI.utils.get_panels_wrapper_from_dom = async function (SESSION_ID, $xu_embe
10131
10152
  const panel_keys = Object.keys(refresh_state?.panel_wrappers_cache || {});
10132
10153
  for (let index = 0; index < panel_keys.length; index++) {
10133
10154
  const panel_entry = refresh_state.panel_wrappers_cache[panel_keys[index]];
10134
- if (!$panel_entry?.$panel_div?.length || !$panel_entry.$panel_div[0]?.isConnected) {
10155
+ if (!panel_entry?.$panel_div?.length || !panel_entry.$panel_div[0]?.isConnected) {
10135
10156
  requires_rebuild = true;
10136
10157
  break;
10137
10158
  }
@@ -10584,15 +10605,52 @@ func.UI.worker.init = async function (SESSION_ID) {
10584
10605
  return elementBottom > viewportTop && elementTop < viewportBottom;
10585
10606
  };
10586
10607
 
10587
- const job_iterator = async function (run_mode = 'data') {
10588
- const contains_job_element = function ($parent, $child) {
10589
- const parent_node = $parent?.[0];
10590
- const child_node = $child?.[0];
10591
- if (!parent_node || !child_node) {
10592
- return false;
10608
+ if (!UI_WORKER_OBJ.in_flight_job_elements) {
10609
+ UI_WORKER_OBJ.in_flight_job_elements = [];
10610
+ }
10611
+
10612
+ const contains_job_element = function ($parent, $child) {
10613
+ const parent_node = $parent?.[0];
10614
+ const child_node = $child?.[0];
10615
+ if (!parent_node || !child_node) {
10616
+ return false;
10617
+ }
10618
+ return parent_node.contains(child_node);
10619
+ };
10620
+
10621
+ // Cancel in-flight child jobs when a parent element is being removed/hidden.
10622
+ // This prevents orphaned jobs whose DOM elements no longer exist.
10623
+ func.UI.worker.cancel_child_in_flight_jobs = function ($parent_element) {
10624
+ if (!$parent_element?.length || !UI_WORKER_OBJ.in_flight_job_elements.length) return;
10625
+ const parent_node = $parent_element[0];
10626
+ if (!parent_node) return;
10627
+ for (let i = UI_WORKER_OBJ.in_flight_job_elements.length - 1; i >= 0; i--) {
10628
+ const child_el = UI_WORKER_OBJ.in_flight_job_elements[i];
10629
+ const child_node = child_el?.[0];
10630
+ if (child_node && parent_node.contains(child_node)) {
10631
+ UI_WORKER_OBJ.in_flight_job_elements.splice(i, 1);
10593
10632
  }
10594
- return parent_node.contains(child_node);
10595
- };
10633
+ }
10634
+ };
10635
+
10636
+ const overlaps_any_in_flight = function (job) {
10637
+ if (!job?.elementP) {
10638
+ return false;
10639
+ }
10640
+ for (let index = 0; index < UI_WORKER_OBJ.in_flight_job_elements.length; index++) {
10641
+ const active_el = UI_WORKER_OBJ.in_flight_job_elements[index];
10642
+ if (!active_el) continue;
10643
+ // Block only when the new job is a CHILD of an in-flight element.
10644
+ // If the new job is a PARENT of an in-flight element, allow it to run
10645
+ // (e.g. xu-render hiding a container while a child panel's on_load is still running).
10646
+ if (contains_job_element(active_el, job.elementP)) {
10647
+ return true;
10648
+ }
10649
+ }
10650
+ return false;
10651
+ };
10652
+
10653
+ const job_iterator = async function (run_mode = 'data') {
10596
10654
  const mark_pending_delete_on_descendants = function ($element) {
10597
10655
  if (!$element?.length) {
10598
10656
  return;
@@ -10611,6 +10669,7 @@ func.UI.worker.init = async function (SESSION_ID) {
10611
10669
  };
10612
10670
  const dom_job_budget = run_mode === 'dom' ? UI_WORKER_OBJ.dom_jobs_per_frame : Number.POSITIVE_INFINITY;
10613
10671
  let dom_jobs_processed = 0;
10672
+ const dispatched_jobs = [];
10614
10673
 
10615
10674
  if (UI_WORKER_OBJ.active_jobs_count) {
10616
10675
  func.UI.worker.idle = 0;
@@ -10624,9 +10683,15 @@ func.UI.worker.init = async function (SESSION_ID) {
10624
10683
 
10625
10684
  if (run_mode === 'data' && job_lane !== 'data') continue;
10626
10685
  if (job_lane === 'dom' && dom_jobs_processed >= dom_job_budget) continue;
10686
+ // skip jobs whose element overlaps with any currently in-flight job (parent/child)
10687
+ // sibling containers can run in parallel
10688
+ if (overlaps_any_in_flight(val)) continue;
10627
10689
 
10628
10690
  if (!val.elementP) {
10629
- await func.UI.worker.execute(val.SESSION_ID, val);
10691
+ const job_promise = func.UI.worker.execute(val.SESSION_ID, val).catch(function (err) {
10692
+ console.error(err);
10693
+ });
10694
+ dispatched_jobs.push(job_promise);
10630
10695
  if (job_lane === 'dom') {
10631
10696
  dom_jobs_processed++;
10632
10697
  }
@@ -10646,67 +10711,48 @@ func.UI.worker.init = async function (SESSION_ID) {
10646
10711
  continue;
10647
10712
  }
10648
10713
 
10649
- // abort - if active job element exist in the current job ui (parent element changed)
10714
+ // keep overlapping parent/child jobs serialized; siblings can run in-flight together
10650
10715
  if (contains_job_element(val.elementP, running_job_obj.elementP)) {
10651
- if (val.functionP === 'execute_xu_render_attributes') {
10652
- mark_pending_delete_on_descendants(val.elementP);
10653
- func.UI.worker.mark_pending_delete_session(SESSION_ID);
10654
- await func.UI.worker.delete_job(SESSION_ID, running_job_obj.job_num);
10655
- break;
10656
- }
10716
+ continue;
10657
10717
  }
10658
10718
  }
10659
10719
 
10660
- // execute - if active job element not exist the current job ui and not detected as parent element
10661
- await func.UI.worker.execute(val.SESSION_ID, val);
10720
+ // Track this element as in-flight so future schedule_run passes skip overlapping jobs
10721
+ const in_flight_entry = val.elementP;
10722
+ UI_WORKER_OBJ.in_flight_job_elements.push(in_flight_entry);
10723
+
10724
+ // execute siblings/non-overlapping jobs — fire and forget, re-trigger scheduler on completion
10725
+ const job_promise = func.UI.worker.execute(val.SESSION_ID, val).catch(function (err) {
10726
+ console.error(err);
10727
+ }).finally(function () {
10728
+ // remove this element from in-flight tracking
10729
+ const idx = UI_WORKER_OBJ.in_flight_job_elements.indexOf(in_flight_entry);
10730
+ if (idx !== -1) {
10731
+ UI_WORKER_OBJ.in_flight_job_elements.splice(idx, 1);
10732
+ }
10733
+ // re-trigger scheduler so blocked sibling jobs can now run
10734
+ func.UI.worker.schedule_run(0);
10735
+ });
10736
+ dispatched_jobs.push(job_promise);
10662
10737
  if (job_lane === 'dom') {
10663
10738
  dom_jobs_processed++;
10664
10739
  }
10665
10740
  continue;
10666
-
10667
- // active_job_children_elements = [];
10668
- // if (val.elementP) {
10669
- // // get active_job children elements only if job handle ui job (not update)
10670
- // $.each(val.elementP.find('*'), function (key, val) {
10671
- // const xu_ui_id = val.elementP.attr('xu-ui-id');
10672
- // if (!xu_ui_id) return true;
10673
- // active_job_children_elements.push(xu_ui_id);
10674
- // });
10675
- // func.UI.worker.execute(val.SESSION_ID, val);
10676
- // continue;
10677
- // } else {
10678
- // // non ui job
10679
- // await func.UI.worker.execute(val.SESSION_ID, val);
10680
- // break;
10681
- // }
10682
-
10683
- // if (UI_WORKER_OBJ.jobs.length > 1 && val.elementP) {
10684
- // const xu_ui_id = val.elementP.attr('xu-ui-id');
10685
- // if (!xu_ui_id) continue;
10686
- // // skip - if job element exist in the active job ui as child element
10687
- // if (active_job_children_elements.includes(xu_ui_id)) {
10688
- // continue;
10689
- // }
10690
- // // abort - if active job element exist in the current job ui (parent element changed)
10691
- // $.each(val.elementP.find('*'), function (key, val) {
10692
- // const xu_ui_id = val.elementP.attr('xu-ui-id');
10693
- // if (!xu_ui_id) return true;
10694
- // active_job_children_elements.push(xu_ui_id);
10695
- // });
10696
- // // execute - if active job element not exist the current job ui and not detected as parent element
10697
- // const active_job = UI_WORKER_OBJ.jobs[0];
10698
- // }
10699
-
10700
- // break;
10701
10741
  } catch (err) {
10702
10742
  console.error(err);
10703
10743
  }
10704
10744
  }
10745
+
10746
+ // Only await jobs that have no element (data jobs) — don't block on long-running UI jobs
10747
+ // UI jobs with elements will re-trigger schedule_run when they complete
10705
10748
  }
10706
10749
  };
10707
10750
  func.UI.worker.schedule_run = function (delay = 0, preferred_mode) {
10708
10751
  func.UI.worker.ensure_runtime_indexes();
10709
10752
  if (UI_WORKER_OBJ.run_in_progress) {
10753
+ // Allow re-entry: even though a scan is in progress, mark run_again
10754
+ // so the scheduler re-scans after the current pass. This lets sibling
10755
+ // jobs that were queued after the scan started get picked up quickly.
10710
10756
  UI_WORKER_OBJ.run_again = true;
10711
10757
  return;
10712
10758
  }
@@ -10952,8 +10998,69 @@ func.UI.worker.execute = async function (SESSION_ID, queue_obj) {
10952
10998
  }
10953
10999
  return $root.find(`[xu-ui-id="${xu_ui_id}"]`);
10954
11000
  },
10955
- get_live_element_context: function (elem_key, fallback_$elm) {
10956
- const $elm = elem_key ? fx.get_element_by_ui_id(elem_key) : fallback_$elm;
11001
+ get_live_element_context: function (elem_key, fallback_$elm, context = {}) {
11002
+ let raw_$elm = elem_key ? fx.get_element_by_ui_id(elem_key) : fallback_$elm;
11003
+ let $elm = func.runtime?.ui?.get_preferred_live_element ? func.runtime.ui.get_preferred_live_element(raw_$elm) : raw_$elm;
11004
+
11005
+ if (context.node_id) {
11006
+ const $root = func.UI.worker.get_session_root(SESSION_ID);
11007
+ const runtime_nodes = func.runtime?.ui?.get_refresh_index_elements
11008
+ ? func.runtime.ui.get_refresh_index_elements(SESSION_ID, $root).toArray()
11009
+ : $root.find('[xu-ui-id]').toArray();
11010
+ let matching_nodes = [];
11011
+
11012
+ for (let index = 0; index < runtime_nodes.length; index++) {
11013
+ const node = runtime_nodes[index];
11014
+ const node_data = func.runtime.ui.get_data(node);
11015
+ const node_xu_data = node_data?.xuData;
11016
+ if (!node_xu_data) {
11017
+ continue;
11018
+ }
11019
+ if (node_xu_data.nodeid !== context.node_id) {
11020
+ continue;
11021
+ }
11022
+ if (context.recordid && node_xu_data.recordid !== context.recordid) {
11023
+ continue;
11024
+ }
11025
+ if (context.prog_id && node_xu_data.paramsP?.prog_id !== context.prog_id) {
11026
+ continue;
11027
+ }
11028
+ if (context.parent_element_ui_id && node_xu_data.parent_element_ui_id !== context.parent_element_ui_id) {
11029
+ continue;
11030
+ }
11031
+ if (node_xu_data.pending_to_delete) {
11032
+ continue;
11033
+ }
11034
+ matching_nodes.push(node);
11035
+ }
11036
+
11037
+ if (!matching_nodes.length) {
11038
+ for (let index = 0; index < runtime_nodes.length; index++) {
11039
+ const node = runtime_nodes[index];
11040
+ const node_data = func.runtime.ui.get_data(node);
11041
+ const node_xu_data = node_data?.xuData;
11042
+ if (!node_xu_data) {
11043
+ continue;
11044
+ }
11045
+ if (node_xu_data.nodeid !== context.node_id) {
11046
+ continue;
11047
+ }
11048
+ if (context.recordid && node_xu_data.recordid !== context.recordid) {
11049
+ continue;
11050
+ }
11051
+ if (node_xu_data.pending_to_delete) {
11052
+ continue;
11053
+ }
11054
+ matching_nodes.push(node);
11055
+ }
11056
+ }
11057
+
11058
+ if (matching_nodes.length) {
11059
+ raw_$elm = $(matching_nodes);
11060
+ $elm = func.runtime?.ui?.get_preferred_live_element ? func.runtime.ui.get_preferred_live_element(raw_$elm) : raw_$elm;
11061
+ }
11062
+ }
11063
+
10957
11064
  if (!$elm?.length) {
10958
11065
  return {
10959
11066
  $elm: $(),
@@ -11008,6 +11115,13 @@ func.UI.worker.execute = async function (SESSION_ID, queue_obj) {
11008
11115
  'xu-for': true,
11009
11116
  'xu-exp:xu-bind': true,
11010
11117
  };
11118
+ const raw_value_attributes = {
11119
+ 'xu-bind': true,
11120
+ 'xu-ref': true,
11121
+ 'xu-on': true,
11122
+ 'xu-for-key': true,
11123
+ 'xu-for-val': true,
11124
+ };
11011
11125
 
11012
11126
  const execution_plan = [];
11013
11127
  for (let index = 0; index < (refresh_attributes || []).length; index++) {
@@ -11023,7 +11137,7 @@ func.UI.worker.execute = async function (SESSION_ID, queue_obj) {
11023
11137
  attr_new,
11024
11138
  xu_func,
11025
11139
  is_regular_attribute: !!(attr_new && attr_new.substr(0, 2) !== 'xu'),
11026
- requires_expression: attr !== 'xu-class' && attr !== 'xu-ui-plugin',
11140
+ requires_expression: !raw_value_attributes[attr] && attr !== 'xu-class' && attr !== 'xu-ui-plugin',
11027
11141
  regular_attr_name: attr_new ? (attr_new !== 'viewBox' ? attr_new.toLowerCase() : attr_new) : null,
11028
11142
  });
11029
11143
  }
@@ -11099,23 +11213,26 @@ func.UI.worker.execute = async function (SESSION_ID, queue_obj) {
11099
11213
  },
11100
11214
  execute_xu_render_attributes: async function () {
11101
11215
  const perf_end = func.runtime?.perf?.start?.(SESSION_ID, 'execute_xu_render_attributes');
11102
- const live_context = fx.get_live_element_context(queue_obj.paramsP?.elem_key, queue_obj.elementP || queue_obj.paramsP?.elem_val?.$elm);
11216
+ const live_context = fx.get_live_element_context(queue_obj.paramsP?.elem_key, queue_obj.elementP || queue_obj.paramsP?.elem_val?.$elm, queue_obj.paramsP);
11103
11217
  const _data = live_context.data;
11218
+ console.log('[xu-render EXEC]', { attr_value: queue_obj.paramsP.attr_value, has_data: !!_data?.xuData?.paramsP, elem_key: queue_obj.paramsP?.elem_key, tagName: live_context.$elm?.[0]?.tagName, elm_length: live_context.$elm?.length });
11104
11219
  try {
11105
11220
  if (_data?.xuData?.paramsP) {
11221
+ const live_xu_data = _data.xuData;
11106
11222
  await func.runtime.render.execute_xu_function({
11107
11223
  SESSION_ID,
11108
11224
  is_skeleton: null,
11109
- $root_container: _data.xuData.$root_container,
11110
- nodeP: _data.xuData.node,
11111
- $container: fx.get_element_by_ui_id(_data.xuData.parent_element_ui_id),
11112
- paramsP: _data.xuData.paramsP,
11113
- parent_infoP: {},
11225
+ $root_container: live_xu_data.$root_container,
11226
+ nodeP: live_xu_data.node,
11227
+ $container: fx.get_element_by_ui_id(live_xu_data.parent_element_ui_id) || live_xu_data.$container,
11228
+ paramsP: live_xu_data.paramsP,
11229
+ parent_infoP: live_xu_data.iterate_info ? { iterate_info: live_xu_data.iterate_info } : {},
11114
11230
  jobNoP: queue_obj.jobNoP,
11115
- keyP: _data.xuData.key,
11116
- parent_nodeP: _data.xuData.parent_node,
11231
+ keyP: live_xu_data.key,
11232
+ parent_nodeP: live_xu_data.parent_node,
11117
11233
  xu_func: 'xu-render',
11118
11234
  $elm: live_context.$elm,
11235
+ $live_elm: live_context.$elm,
11119
11236
  val: {
11120
11237
  key: 'xu-render',
11121
11238
  value: queue_obj.paramsP.attr_value,
@@ -11138,7 +11255,7 @@ func.UI.worker.execute = async function (SESSION_ID, queue_obj) {
11138
11255
  };
11139
11256
 
11140
11257
  try {
11141
- const live_context = fx.get_live_element_context(queue_obj.paramsP?.elem_key, queue_obj.elementP || queue_obj.paramsP?.elem_val?.$elm);
11258
+ const live_context = fx.get_live_element_context(queue_obj.paramsP?.elem_key, queue_obj.elementP || queue_obj.paramsP?.elem_val?.$elm, queue_obj.paramsP);
11142
11259
  const $elm = live_context.$elm;
11143
11260
  const live_data = live_context.data;
11144
11261
  const elm_node = $elm?.[0];
@@ -11151,18 +11268,20 @@ func.UI.worker.execute = async function (SESSION_ID, queue_obj) {
11151
11268
  const refresh_attributes = queue_obj.paramsP?.elem_val?.attributes || [];
11152
11269
  const execution_plan = fx.get_refresh_execution_plan(live_data, refresh_attributes);
11153
11270
  const expression_results_cache = {};
11271
+ const live_parent_container = fx.get_element_by_ui_id(live_xu_data.parent_element_ui_id);
11154
11272
  const xu_execution_context = {
11155
11273
  SESSION_ID,
11156
11274
  is_skeleton: null,
11157
11275
  $root_container: live_xu_data.$root_container,
11158
11276
  nodeP: live_xu_data.node,
11159
- $container: live_xu_data.$container,
11277
+ $container: live_parent_container?.length ? live_parent_container : live_xu_data.$container,
11160
11278
  paramsP: live_xu_data.paramsP,
11161
- parent_infoP: {},
11279
+ parent_infoP: live_xu_data.iterate_info ? { iterate_info: live_xu_data.iterate_info } : {},
11162
11280
  jobNoP: queue_obj.jobNoP,
11163
- keyP: null,
11164
- parent_nodeP: null,
11281
+ keyP: live_xu_data.key,
11282
+ parent_nodeP: live_xu_data.parent_node,
11165
11283
  $elm,
11284
+ $live_elm: $elm,
11166
11285
  get_params_obj_new: func.runtime.program.get_params_obj,
11167
11286
  };
11168
11287
  const handler_bundle = func.runtime.render.build_xu_handlers(xu_execution_context, SESSION_OBJ[SESSION_ID].DS_GLB[live_xu_data.paramsP.dsSessionP]);
@@ -11217,7 +11336,7 @@ func.UI.worker.execute = async function (SESSION_ID, queue_obj) {
11217
11336
  execute_xu_for: async function () {
11218
11337
  const perf_end = func.runtime?.perf?.start?.(SESSION_ID, 'execute_xu_for');
11219
11338
  try {
11220
- const live_context = fx.get_live_element_context(queue_obj.paramsP?.elem_key, queue_obj.elementP || queue_obj?.paramsP?.elem_val?.$elm);
11339
+ const live_context = fx.get_live_element_context(queue_obj.paramsP?.elem_key, queue_obj.elementP || queue_obj?.paramsP?.elem_val?.$elm, queue_obj.paramsP);
11221
11340
  var $elm = live_context.$elm; // $(SESSION_OBJ[SESSION_ID].root_element).find(`[xu-ui-id=${queue_obj.paramsP.elem_key}]`)
11222
11341
 
11223
11342
  if (!$elm?.length) {
@@ -11294,7 +11413,7 @@ func.UI.worker.execute = async function (SESSION_ID, queue_obj) {
11294
11413
  },
11295
11414
  execute_xu_widget: async function () {
11296
11415
  try {
11297
- const live_context = fx.get_live_element_context(queue_obj.paramsP?.elem_key, queue_obj.elementP || queue_obj?.paramsP?.elem_val?.$elm);
11416
+ const live_context = fx.get_live_element_context(queue_obj.paramsP?.elem_key, queue_obj.elementP || queue_obj?.paramsP?.elem_val?.$elm, queue_obj.paramsP);
11298
11417
  var $elm = live_context.$elm; // $(SESSION_OBJ[SESSION_ID].root_element).find(`[xu-ui-id=${queue_obj.paramsP.elem_key}]`)
11299
11418
 
11300
11419
  if (!$elm?.length) {
@@ -11323,7 +11442,16 @@ func.UI.worker.execute = async function (SESSION_ID, queue_obj) {
11323
11442
  },
11324
11443
  };
11325
11444
 
11326
- return await fx[queue_obj.functionP]();
11445
+ try {
11446
+ return await fx[queue_obj.functionP]();
11447
+ } catch (error) {
11448
+ console.error(error);
11449
+ const failed_job_index = func.UI.worker.find_job_index(SESSION_ID, queue_obj.job_num);
11450
+ if (failed_job_index !== null && typeof failed_job_index !== 'undefined' && UI_WORKER_OBJ.jobs[failed_job_index]) {
11451
+ await func.UI.worker.delete_job(SESSION_ID, queue_obj.job_num);
11452
+ }
11453
+ return null;
11454
+ }
11327
11455
  };
11328
11456
  func.UI.worker.find_job_index = function (SESSION_ID, jobNoP) {
11329
11457
  var ret = null;
@@ -13509,6 +13637,41 @@ func.runtime.ui.find_refresh_elements_by_attr = function ($root, attr_name, attr
13509
13637
 
13510
13638
  return $(elements);
13511
13639
  };
13640
+ func.runtime.ui.get_preferred_live_element = function (target) {
13641
+ const elements = func.runtime.ui.as_jquery(target).toArray();
13642
+ if (!elements.length) {
13643
+ return $();
13644
+ }
13645
+
13646
+ let best_node = null;
13647
+ let best_score = -Infinity;
13648
+ for (let index = 0; index < elements.length; index++) {
13649
+ const node = elements[index];
13650
+ if (!node) {
13651
+ continue;
13652
+ }
13653
+ const node_data = func.runtime.ui.get_data(node);
13654
+ let score = 0;
13655
+ if (node.isConnected) {
13656
+ score += 100;
13657
+ }
13658
+ if (!node_data?.xuData?.pending_to_delete) {
13659
+ score += 50;
13660
+ }
13661
+ if (node.getClientRects?.().length) {
13662
+ score += 25;
13663
+ }
13664
+ if (!node.hidden) {
13665
+ score += 10;
13666
+ }
13667
+ if (score >= best_score) {
13668
+ best_score = score;
13669
+ best_node = node;
13670
+ }
13671
+ }
13672
+
13673
+ return best_node ? $(best_node) : $(elements[elements.length - 1]);
13674
+ };
13512
13675
  func.runtime.ui.get_refresh_indexed_element_by_ui_id = function (SESSION_ID, selector_id) {
13513
13676
  const state = func.runtime.ui.ensure_refresh_dependency_state(SESSION_ID);
13514
13677
  if (!state) {
@@ -13519,12 +13682,14 @@ func.runtime.ui.get_refresh_indexed_element_by_ui_id = function (SESSION_ID, sel
13519
13682
  }
13520
13683
 
13521
13684
  let $elm = state.elements_by_ui_id?.[selector_id];
13685
+ $elm = func.runtime.ui.get_preferred_live_element($elm);
13522
13686
  if ($elm?.length && $elm[0]?.isConnected) {
13687
+ state.elements_by_ui_id[selector_id] = $elm;
13523
13688
  return $elm;
13524
13689
  }
13525
13690
 
13526
13691
  const $root = func.runtime.ui.get_refresh_index_root(SESSION_ID);
13527
- $elm = func.runtime.ui.find_refresh_elements_by_attr($root, 'xu-ui-id', selector_id, true);
13692
+ $elm = func.runtime.ui.get_preferred_live_element(func.runtime.ui.find_refresh_elements_by_attr($root, 'xu-ui-id', selector_id));
13528
13693
  if ($elm?.length) {
13529
13694
  state.elements_by_ui_id[selector_id] = $elm;
13530
13695
  state.runtime_elements_cache = null;
@@ -13628,12 +13793,14 @@ func.runtime.ui.get_refresh_indexed_panel_wrapper_by_id = function (SESSION_ID,
13628
13793
  }
13629
13794
 
13630
13795
  let $elm = state.panel_wrappers_by_id?.[wrapper_id];
13796
+ $elm = func.runtime.ui.get_preferred_live_element($elm);
13631
13797
  if ($elm?.length && $elm[0]?.isConnected) {
13798
+ state.panel_wrappers_by_id[wrapper_id] = $elm;
13632
13799
  return $elm;
13633
13800
  }
13634
13801
 
13635
13802
  const $root = func.runtime.ui.get_refresh_index_root(SESSION_ID);
13636
- $elm = func.runtime.ui.find_refresh_elements_by_attr($root, 'xu-panel-wrapper-id', wrapper_id, true);
13803
+ $elm = func.runtime.ui.get_preferred_live_element(func.runtime.ui.find_refresh_elements_by_attr($root, 'xu-panel-wrapper-id', wrapper_id));
13637
13804
  if ($elm?.length) {
13638
13805
  state.panel_wrappers_by_id[wrapper_id] = $elm;
13639
13806
  state.panel_wrapper_elements_cache = null;
@@ -13865,6 +14032,10 @@ func.runtime.ui.build_refresh_job_obj = function (options, elem_key, elem_val, e
13865
14032
  SESSION_ID: options.SESSION_ID,
13866
14033
  fields_arr: options.fields_arr,
13867
14034
  elem_key,
14035
+ node_id: elm_data?.xuData?.nodeid,
14036
+ recordid: elm_data?.xuData?.recordid,
14037
+ parent_element_ui_id: elm_data?.xuData?.parent_element_ui_id,
14038
+ prog_id: elm_data?.xuData?.paramsP?.prog_id,
13868
14039
  elem_val: {
13869
14040
  attributes: [...(elem_val.attributes || [])],
13870
14041
  },
@@ -14189,7 +14360,7 @@ func.runtime.render = func.runtime.render || {};
14189
14360
  // Browser-only xu-attribute refresh helpers live here so screen refresh orchestration can stay focused.
14190
14361
 
14191
14362
  func.runtime.ui.refresh_xu_attributes = async function (options) {
14192
- if (options.trigger !== 'click' && !_.isEmpty(SCREEN_BLOCKER_OBJ)) {
14363
+ if (!options.ignore_screen_blocker && options.trigger !== 'click' && !_.isEmpty(SCREEN_BLOCKER_OBJ)) {
14193
14364
  setTimeout(() => {
14194
14365
  func.runtime.ui.refresh_xu_attributes(options);
14195
14366
  }, 100);
@@ -16710,11 +16881,19 @@ func.runtime.render.handle_xu_ref = async function (options) {
16710
16881
  func.runtime.render.handle_xu_bind = async function (options) {
16711
16882
  if (options.is_skeleton) return {};
16712
16883
 
16713
- const elm_data = func.runtime.ui.get_data(options.$elm);
16884
+ const $elm = func.runtime?.ui?.get_preferred_live_element ? func.runtime.ui.get_preferred_live_element(options.$elm) : func.runtime.ui.as_jquery(options.$elm);
16885
+ const elm_data = func.runtime.ui.get_data($elm);
16714
16886
  const xuData = elm_data?.xuData;
16887
+ const bind_expression =
16888
+ typeof options?.val?.value === 'string'
16889
+ ? options.val.value
16890
+ : elm_data?.xuAttributes?.['xu-bind'] || func.runtime.ui.get_attr($elm, 'xu-bind');
16715
16891
  if (!xuData?.paramsP) {
16716
16892
  return {};
16717
16893
  }
16894
+ if (!bind_expression) {
16895
+ return {};
16896
+ }
16718
16897
 
16719
16898
  let val_is_reference_field = false;
16720
16899
  let _prog_id = xuData.paramsP.prog_id;
@@ -16722,10 +16901,94 @@ func.runtime.render.handle_xu_bind = async function (options) {
16722
16901
  let is_dynamic_field = false;
16723
16902
  let field_prop;
16724
16903
  let bind_field_id;
16725
- const input_field_type = options.$elm?.[0]?.type || func.runtime.ui.get_attr(options.$elm, 'type');
16904
+ const input_field_type = $elm?.[0]?.type || func.runtime.ui.get_attr($elm, 'type');
16905
+ const bind_ui_id = xuData.ui_id || func.runtime.ui.get_attr($elm, 'xu-ui-id');
16906
+ const get_bind_targets = function () {
16907
+ const $root = func.runtime.ui.get_refresh_index_root?.(options.SESSION_ID) || func.runtime.ui.get_root_element?.(options.SESSION_ID) || $('body');
16908
+ const live_targets = [];
16909
+ const seen_nodes = new Set();
16910
+ const add_candidate = function (node) {
16911
+ if (!node || seen_nodes.has(node)) {
16912
+ return;
16913
+ }
16914
+ const target_data = func.runtime.ui.get_data(node);
16915
+ if (!node.isConnected || target_data?.xuData?.pending_to_delete) {
16916
+ return;
16917
+ }
16918
+ seen_nodes.add(node);
16919
+ live_targets.push(node);
16920
+ };
16921
+
16922
+ if (bind_ui_id && options.SESSION_ID && func.runtime?.ui?.find_refresh_elements_by_attr) {
16923
+ const ui_id_targets = func.runtime.ui.find_refresh_elements_by_attr($root, 'xu-ui-id', bind_ui_id).toArray();
16924
+ for (let index = 0; index < ui_id_targets.length; index++) {
16925
+ add_candidate(ui_id_targets[index]);
16926
+ }
16927
+ }
16928
+
16929
+ const runtime_nodes = func.runtime?.ui?.get_refresh_index_elements
16930
+ ? func.runtime.ui.get_refresh_index_elements(options.SESSION_ID, $root).toArray()
16931
+ : $root.find('[xu-ui-id]').toArray();
16932
+ const target_parent_ui_id = xuData.parent_element_ui_id;
16933
+ const target_node_id = xuData.nodeid;
16934
+ const target_recordid = xuData.recordid;
16935
+ const target_prog_id = xuData.paramsP?.prog_id;
16936
+ for (let index = 0; index < runtime_nodes.length; index++) {
16937
+ const node = runtime_nodes[index];
16938
+ const node_data = func.runtime.ui.get_data(node);
16939
+ const node_xu_data = node_data?.xuData;
16940
+ if (!node_xu_data) {
16941
+ continue;
16942
+ }
16943
+ if (target_node_id && node_xu_data.nodeid !== target_node_id) {
16944
+ continue;
16945
+ }
16946
+ if (target_parent_ui_id && node_xu_data.parent_element_ui_id !== target_parent_ui_id) {
16947
+ continue;
16948
+ }
16949
+ if (target_prog_id && node_xu_data.paramsP?.prog_id !== target_prog_id) {
16950
+ continue;
16951
+ }
16952
+ if (typeof target_recordid !== 'undefined' && target_recordid !== null && node_xu_data.recordid !== target_recordid) {
16953
+ continue;
16954
+ }
16955
+ const node_bind_expression = node_data?.xuAttributes?.['xu-bind'] || func.runtime.ui.get_attr(node, 'xu-bind');
16956
+ if (node_bind_expression !== bind_expression) {
16957
+ continue;
16958
+ }
16959
+ add_candidate(node);
16960
+ }
16961
+
16962
+ if (!live_targets.length) {
16963
+ return $elm;
16964
+ }
16965
+
16966
+ for (let index = 0; index < live_targets.length; index++) {
16967
+ const node = live_targets[index];
16968
+ const target_data = func.runtime.ui.get_data(node);
16969
+ if (!node?.isConnected || target_data?.xuData?.pending_to_delete) {
16970
+ continue;
16971
+ }
16972
+ add_candidate(node);
16973
+ }
16974
+
16975
+ if (!live_targets.length) {
16976
+ return $elm;
16977
+ }
16978
+
16979
+ const target_nodes = live_targets;
16980
+ const visible_targets = [];
16981
+ for (let index = 0; index < target_nodes.length; index++) {
16982
+ const node = target_nodes[index];
16983
+ if (node.getClientRects?.().length && !node.hidden) {
16984
+ visible_targets.push(node);
16985
+ }
16986
+ }
16987
+ return $(visible_targets.length ? visible_targets : live_targets);
16988
+ };
16726
16989
 
16727
16990
  try {
16728
- const bind_field = await func.runtime.bind.resolve_field(options.SESSION_ID, _prog_id, _dsP, options.val.value.split('.')[0]);
16991
+ const bind_field = await func.runtime.bind.resolve_field(options.SESSION_ID, _prog_id, _dsP, bind_expression.split('.')[0]);
16729
16992
  bind_field_id = bind_field.bind_field_id;
16730
16993
  field_prop = bind_field.field_prop;
16731
16994
  is_dynamic_field = bind_field.is_dynamic_field;
@@ -16774,38 +17037,80 @@ func.runtime.render.handle_xu_bind = async function (options) {
16774
17037
  field_prop,
16775
17038
  val_is_reference_field,
16776
17039
  input_field_type,
16777
- expression_value: options.val.value,
17040
+ expression_value: bind_expression,
16778
17041
  value,
16779
17042
  });
16780
17043
 
16781
17044
  await func.datasource.update_changes_for_out_parameter(options.SESSION_ID, _dsP, _ds.parentDataSourceNo);
16782
17045
  };
16783
17046
 
16784
- bind.listener(options.$elm[0], field_changed);
17047
+ const bind_targets = get_bind_targets();
17048
+ const target_nodes = bind_targets.toArray();
17049
+ for (let index = 0; index < target_nodes.length; index++) {
17050
+ const target = target_nodes[index];
17051
+ const target_data = func.runtime.ui.get_data(target);
17052
+ if (!target_data?.xuData || target_data.xuData.bind_listener_attached) {
17053
+ continue;
17054
+ }
17055
+ bind.listener(target, field_changed);
17056
+ target_data.xuData.bind_listener_attached = true;
17057
+ }
16785
17058
 
16786
- const set_value = function () {
16787
- const _ds = SESSION_OBJ[options.SESSION_ID].DS_GLB[options.paramsP.dsSessionP];
16788
- if (!_ds.currentRecordId) return;
17059
+ const set_value = async function () {
17060
+ const _ds = SESSION_OBJ[options.SESSION_ID].DS_GLB[_dsP];
17061
+ if (!_ds) return;
17062
+ const target_record_id = xuData.recordid || _ds.currentRecordId;
17063
+ if (!target_record_id) return;
16789
17064
  let value;
16790
17065
  try {
16791
17066
  if (val_is_reference_field) {
16792
- value = func.runtime.bind.get_source_value(_ds, bind_field_id, is_dynamic_field);
16793
- value = func.runtime.bind.format_display_value(options.$elm, field_prop, bind_field_id, options.val.value, value, input_field_type);
17067
+ const resolved_value = await func.datasource.get_value(options.SESSION_ID, bind_field_id, _dsP, target_record_id);
17068
+ if (resolved_value?.found) {
17069
+ value = resolved_value.ret.value;
17070
+ } else {
17071
+ value = func.runtime.bind.get_source_value(_ds, bind_field_id, is_dynamic_field);
17072
+ }
17073
+ value = func.runtime.bind.format_display_value($elm, field_prop, bind_field_id, bind_expression, value, input_field_type);
16794
17074
  } else {
16795
- value = options.val.value;
17075
+ value = bind_expression;
16796
17076
  }
16797
17077
  if (typeof value === 'undefined') return;
16798
- bind.setter(options.$elm[0], value);
17078
+
17079
+ const live_bind_targets = get_bind_targets().toArray();
17080
+ for (let index = 0; index < live_bind_targets.length; index++) {
17081
+ const elm = live_bind_targets[index];
17082
+ if (!elm) {
17083
+ continue;
17084
+ }
17085
+
17086
+ bind.setter(elm, value);
17087
+ const target_input_type = elm.type || input_field_type;
17088
+ switch (target_input_type) {
17089
+ case 'radio':
17090
+ elm.checked = elm.value === _.toString(value);
17091
+ break;
17092
+ case 'checkbox':
17093
+ elm.checked = !!value;
17094
+ break;
17095
+ default:
17096
+ if (typeof elm.value !== 'undefined') {
17097
+ elm.value = value === null ? '' : _.toString(value);
17098
+ }
17099
+ break;
17100
+ }
17101
+ }
16799
17102
  } catch (err) {
16800
17103
  console.error(err);
16801
17104
  }
16802
17105
  };
16803
17106
 
16804
- $('body').on('xu-bind-refresh.' + options.ds.dsSession.toString(), () => {
16805
- set_value();
17107
+ const bind_refresh_event = 'xu-bind-refresh.' + _dsP.toString();
17108
+ const bind_refresh_namespace = `.ui${bind_ui_id || bind_field_id || 'bind'}`;
17109
+ $('body').off(bind_refresh_namespace).on(bind_refresh_event + bind_refresh_namespace, async () => {
17110
+ await set_value();
16806
17111
  });
16807
17112
 
16808
- set_value();
17113
+ await set_value();
16809
17114
  return {};
16810
17115
  };
16811
17116
  func.runtime.render.apply_xu_class = async function (options) {
@@ -17163,12 +17468,24 @@ func.runtime.render.handle_modern_xu_render = async function (options) {
17163
17468
  }
17164
17469
 
17165
17470
  func.runtime.render.insert_ordered_child(options.$container, new_$div, options.keyP);
17471
+ // Remove the XURENDER placeholder now that the real content has been inserted.
17472
+ if (options.$elm?.[0]?.tagName === 'XURENDER') {
17473
+ func.runtime.ui.remove(options.$elm);
17474
+ }
17166
17475
  } catch (error) {
17476
+ console.error('[xu-render post_render ERROR]', error, { keyP: options.keyP, container_tag: options.$container?.[0]?.tagName, has_node: !!nodeP, xu_ui_id: func.runtime.ui.get_attr(options.$elm, 'xu-ui-id') });
17167
17477
  func.events.delete_job(options.SESSION_ID, options.jobNoP);
17168
17478
  }
17169
17479
  return;
17170
17480
  }
17171
17481
 
17482
+ // Cancel any in-flight child jobs before removing the element.
17483
+ // This prevents orphaned jobs (e.g. a child panel's long-running on_load delay)
17484
+ // from blocking future scheduler passes after the parent element is gone.
17485
+ if (func.UI?.worker?.cancel_child_in_flight_jobs) {
17486
+ func.UI.worker.cancel_child_in_flight_jobs(options.$elm);
17487
+ }
17488
+
17172
17489
  const xu_ui_id = func.runtime.ui.get_attr(options.$elm, 'xu-ui-id');
17173
17490
  const exclude_fields = func.runtime.render.get_xu_render_exclude_fields(options.$elm);
17174
17491
  const cache_str = await func.runtime.render.get_xu_render_cache_str(