@smartbit4all/ng-client 6.0.0 → 6.0.1

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.
@@ -25,4 +25,4 @@ export function resolveSlot(view) {
25
25
  containerId: ROUTER_SLOT_ID,
26
26
  };
27
27
  }
28
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmlldy1zbG90LXJlc29sdXRpb24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi9wcm9qZWN0cy9zbWFydC1uZy1jbGllbnQvc3JjL2xpYi92aWV3LWNvbnRleHQvdmlldy1zbG90LXJlc29sdXRpb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQ0EsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBRWhEOzJGQUMyRjtBQUMzRixNQUFNLENBQUMsTUFBTSxtQkFBbUIsR0FBRyxVQUFVLENBQUM7QUFFOUMsaUdBQWlHO0FBQ2pHLE1BQU0sQ0FBQyxNQUFNLGNBQWMsR0FBRyxlQUFlLENBQUM7QUFPOUM7OztHQUdHO0FBQ0gsTUFBTSxVQUFVLFdBQVcsQ0FBQyxJQUFjO0lBQ3hDLElBQUksSUFBSSxDQUFDLElBQUksS0FBSyxRQUFRLENBQUMsUUFBUSxFQUFFLENBQUM7UUFDcEMsSUFBSSxDQUFDLElBQUksQ0FBQyxhQUFhLElBQUksQ0FBQyxJQUFJLENBQUMsV0FBVyxFQUFFLENBQUM7WUFDN0MsT0FBTyxDQUFDLEtBQUssQ0FDWCxrQkFBa0IsSUFBSSxDQUFDLFFBQVEsbUVBQW1FO2dCQUNoRyxzQkFBc0IsSUFBSSxDQUFDLGFBQWEsbUJBQW1CLElBQUksQ0FBQyxXQUFXLElBQUksQ0FDbEYsQ0FBQztRQUNKLENBQUM7UUFDRCxPQUFPLEVBQUUsYUFBYSxFQUFFLElBQUksQ0FBQyxhQUFjLEVBQUUsV0FBVyxFQUFFLElBQUksQ0FBQyxXQUFZLEVBQUUsQ0FBQztJQUNoRixDQUFDO0lBQ0QsSUFBSSxJQUFJLENBQUMsV0FBVyxFQUFFLENBQUM7UUFDckIsT0FBTyxDQUFDLEtBQUssQ0FDWCxnQkFBZ0IsSUFBSSxDQUFDLFFBQVEsc0JBQXNCLElBQUksQ0FBQyxXQUFXLFNBQVM7WUFDMUUsMERBQTBELGNBQWMsSUFBSSxDQUMvRSxDQUFDO0lBQ0osQ0FBQztJQUNELE9BQU87UUFDTCxhQUFhLEVBQUUsSUFBSSxDQUFDLGFBQWEsSUFBSSxtQkFBbUI7UUFDeEQsV0FBVyxFQUFFLGNBQWM7S0FDNUIsQ0FBQztBQUNKLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBWaWV3RGF0YSB9IGZyb20gJy4vYXBpL21vZGVsL3ZpZXdEYXRhJztcbmltcG9ydCB7IFZpZXdUeXBlIH0gZnJvbSAnLi9hcGkvbW9kZWwvdmlld1R5cGUnO1xuXG4vKiogU2VudGluZWwgY29udGFpbmVyVXVpZCBmb3IgdGhlIGhvc3Qgcm9vdCBzbG90LCByZWdpc3RlcmVkIGJ5IHRoZSBzaGVsbCdzIHJvb3RcbiAqICBTbWFydEVtYmVkZGVkU2xvdERpcmVjdGl2ZTsgb3BlblZpZXcgbWFwcyBhIE5PUk1BTCB2aWV3IHdpdGggbm8gY29udGFpbmVyVXVpZCB0byBpdC4gKi9cbmV4cG9ydCBjb25zdCBST09UX0NPTlRBSU5FUl9VVUlEID0gJ19fUk9PVF9fJztcblxuLyoqIENvbnZlbnRpb25hbCBjb250YWluZXJJZCBmb3IgdGhlIHNpbmdsZSBcImN1cnJlbnQgcGFnZVwiIHNsb3QgdGhhdCByZXBsYWNlcyA8cm91dGVyLW91dGxldD4uICovXG5leHBvcnQgY29uc3QgUk9VVEVSX1NMT1RfSUQgPSAncm91dGVyLW91dGxldCc7XG5cbmV4cG9ydCBpbnRlcmZhY2UgUmVzb2x2ZWRTbG90IHtcbiAgY29udGFpbmVyVXVpZDogc3RyaW5nO1xuICBjb250YWluZXJJZDogc3RyaW5nO1xufVxuXG4vKipcbiAqIFJlc29sdmVzIHRoZSBlbWJlZGRlZCBzbG90IGZvciBOT1JNQUwgYW5kIEVNQkVEREVEIHZpZXdzIG9ubHkuXG4gKiBESUFMT0cgdmlld3MgYXJlIGhhbmRsZWQgc2VwYXJhdGVseSB1cHN0cmVhbSBpbiBvcGVuVmlldyBhbmQgbXVzdCBub3QgYmUgcGFzc2VkIGhlcmUuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiByZXNvbHZlU2xvdCh2aWV3OiBWaWV3RGF0YSk6IFJlc29sdmVkU2xvdCB7XG4gIGlmICh2aWV3LnR5cGUgPT09IFZpZXdUeXBlLkVNQkVEREVEKSB7XG4gICAgaWYgKCF2aWV3LmNvbnRhaW5lclV1aWQgfHwgIXZpZXcuY29udGFpbmVySWQpIHtcbiAgICAgIGNvbnNvbGUuZXJyb3IoXG4gICAgICAgIGBFTUJFRERFRCB2aWV3ICcke3ZpZXcudmlld05hbWV9JyBpcyBtYWxmb3JtZWQ6IGJvdGggY29udGFpbmVyVXVpZCBhbmQgY29udGFpbmVySWQgYXJlIHJlcXVpcmVkLCBgICtcbiAgICAgICAgICBgZ290IGNvbnRhaW5lclV1aWQ9JyR7dmlldy5jb250YWluZXJVdWlkfScsIGNvbnRhaW5lcklkPScke3ZpZXcuY29udGFpbmVySWR9Jy5gLFxuICAgICAgKTtcbiAgICB9XG4gICAgcmV0dXJuIHsgY29udGFpbmVyVXVpZDogdmlldy5jb250YWluZXJVdWlkISwgY29udGFpbmVySWQ6IHZpZXcuY29udGFpbmVySWQhIH07XG4gIH1cbiAgaWYgKHZpZXcuY29udGFpbmVySWQpIHtcbiAgICBjb25zb2xlLmVycm9yKFxuICAgICAgYE5PUk1BTCB2aWV3ICcke3ZpZXcudmlld05hbWV9JyBoYXMgY29udGFpbmVySWQgJyR7dmlldy5jb250YWluZXJJZH0nIHNldDsgYCArXG4gICAgICAgIGBjb250YWluZXJJZCBpbXBsaWVzIGFuIEVNQkVEREVEIHZpZXcuIEZhbGxpbmcgYmFjayB0byAnJHtST1VURVJfU0xPVF9JRH0nLmAsXG4gICAgKTtcbiAgfVxuICByZXR1cm4ge1xuICAgIGNvbnRhaW5lclV1aWQ6IHZpZXcuY29udGFpbmVyVXVpZCA/PyBST09UX0NPTlRBSU5FUl9VVUlELFxuICAgIGNvbnRhaW5lcklkOiBST1VURVJfU0xPVF9JRCxcbiAgfTtcbn1cbiJdfQ==
28
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmlldy1zbG90LXJlc29sdXRpb24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi9wcm9qZWN0cy9zbWFydC1uZy1jbGllbnQvc3JjL2xpYi92aWV3LWNvbnRleHQvdmlldy1zbG90LXJlc29sdXRpb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQ0EsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBRWhEOzJGQUMyRjtBQUMzRixNQUFNLENBQUMsTUFBTSxtQkFBbUIsR0FBRyxVQUFVLENBQUM7QUFFOUMsaUdBQWlHO0FBQ2pHLE1BQU0sQ0FBQyxNQUFNLGNBQWMsR0FBRyxlQUFlLENBQUM7QUFvQjlDOzs7R0FHRztBQUNILE1BQU0sVUFBVSxXQUFXLENBQUMsSUFBYztJQUN4QyxJQUFJLElBQUksQ0FBQyxJQUFJLEtBQUssUUFBUSxDQUFDLFFBQVEsRUFBRSxDQUFDO1FBQ3BDLElBQUksQ0FBQyxJQUFJLENBQUMsYUFBYSxJQUFJLENBQUMsSUFBSSxDQUFDLFdBQVcsRUFBRSxDQUFDO1lBQzdDLE9BQU8sQ0FBQyxLQUFLLENBQ1gsa0JBQWtCLElBQUksQ0FBQyxRQUFRLG1FQUFtRTtnQkFDaEcsc0JBQXNCLElBQUksQ0FBQyxhQUFhLG1CQUFtQixJQUFJLENBQUMsV0FBVyxJQUFJLENBQ2xGLENBQUM7UUFDSixDQUFDO1FBQ0QsT0FBTyxFQUFFLGFBQWEsRUFBRSxJQUFJLENBQUMsYUFBYyxFQUFFLFdBQVcsRUFBRSxJQUFJLENBQUMsV0FBWSxFQUFFLENBQUM7SUFDaEYsQ0FBQztJQUNELElBQUksSUFBSSxDQUFDLFdBQVcsRUFBRSxDQUFDO1FBQ3JCLE9BQU8sQ0FBQyxLQUFLLENBQ1gsZ0JBQWdCLElBQUksQ0FBQyxRQUFRLHNCQUFzQixJQUFJLENBQUMsV0FBVyxTQUFTO1lBQzFFLDBEQUEwRCxjQUFjLElBQUksQ0FDL0UsQ0FBQztJQUNKLENBQUM7SUFDRCxPQUFPO1FBQ0wsYUFBYSxFQUFFLElBQUksQ0FBQyxhQUFhLElBQUksbUJBQW1CO1FBQ3hELFdBQVcsRUFBRSxjQUFjO0tBQzVCLENBQUM7QUFDSixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgVmlld0RhdGEgfSBmcm9tICcuL2FwaS9tb2RlbC92aWV3RGF0YSc7XG5pbXBvcnQgeyBWaWV3VHlwZSB9IGZyb20gJy4vYXBpL21vZGVsL3ZpZXdUeXBlJztcblxuLyoqIFNlbnRpbmVsIGNvbnRhaW5lclV1aWQgZm9yIHRoZSBob3N0IHJvb3Qgc2xvdCwgcmVnaXN0ZXJlZCBieSB0aGUgc2hlbGwncyByb290XG4gKiAgU21hcnRFbWJlZGRlZFNsb3REaXJlY3RpdmU7IG9wZW5WaWV3IG1hcHMgYSBOT1JNQUwgdmlldyB3aXRoIG5vIGNvbnRhaW5lclV1aWQgdG8gaXQuICovXG5leHBvcnQgY29uc3QgUk9PVF9DT05UQUlORVJfVVVJRCA9ICdfX1JPT1RfXyc7XG5cbi8qKiBDb252ZW50aW9uYWwgY29udGFpbmVySWQgZm9yIHRoZSBzaW5nbGUgXCJjdXJyZW50IHBhZ2VcIiBzbG90IHRoYXQgcmVwbGFjZXMgPHJvdXRlci1vdXRsZXQ+LiAqL1xuZXhwb3J0IGNvbnN0IFJPVVRFUl9TTE9UX0lEID0gJ3JvdXRlci1vdXRsZXQnO1xuXG4vKipcbiAqIExpZmVjeWNsZSBjbGFzcyBvZiBhbiBlbWJlZGRlZCBzbG90LCBkZWNsYXJlZCBieSB3aG9ldmVyIHJlZ2lzdGVycyBpdDpcbiAqICAtICdzdGFibGUnOiBhIFNtYXJ0RW1iZWRkZWRTbG90RGlyZWN0aXZlIHNsb3QgKGUuZy4gdGhlIGhvc3Qgcm9vdCBzbG90IG9yIGFcbiAqICAgIHBhZ2UncyByb3V0ZXItb3V0bGV0IHNsb3QpLiBSZWdpc3RlcmVkIG9uY2UgaW4gbmdPbkluaXQgYW5kIGxpdmVzIE9VVFNJREUgYW55XG4gKiAgICBsYXlvdXQgKm5nRm9yLCBzbyBpdCBzdXJ2aXZlcyBhIHBhcmVudCBsYXlvdXQgcmUtcmVuZGVyLiBJdHMgbW91bnRlZCBjaGlsZCBtdXN0XG4gKiAgICBOT1QgYmUgZGV0YWNoZWQgYnkgdGhlIGxheW91dCBkZXRhY2gvY2xlYW51cCBkYW5jZS5cbiAqICAtICdsYXlvdXQnOiBhIFNtYXJ0Q29tcG9uZW50TGF5b3V0Q29tcG9uZW50IEVNQkVEREVEX1NMT1Qgd2lkZ2V0IHNsb3QuIExpdmVzXG4gKiAgICBJTlNJREUgdGhlIHJlLXJlbmRlcmluZyBsYXlvdXQgKm5nRm9yIHRyZWUsIHNvIGEgdG9wLWxldmVsIGxheW91dCByZS1yZW5kZXJcbiAqICAgIGRlc3Ryb3lzIGFuZCByZWNyZWF0ZXMgaXQ7IGl0cyBjaGlsZCBtdXN0IGJlIGRldGFjaGVkIGZpcnN0IHRvIHN1cnZpdmUgdGhlXG4gKiAgICAqbmdGb3IgZGVzdHJ1Y3Rpb24sIHRoZW4gcmVhdHRhY2hlcyBvbiByZS1yZWdpc3RyYXRpb24uICgjMjg4NzApXG4gKi9cbmV4cG9ydCB0eXBlIFNsb3RLaW5kID0gJ3N0YWJsZScgfCAnbGF5b3V0JztcblxuZXhwb3J0IGludGVyZmFjZSBSZXNvbHZlZFNsb3Qge1xuICBjb250YWluZXJVdWlkOiBzdHJpbmc7XG4gIGNvbnRhaW5lcklkOiBzdHJpbmc7XG59XG5cbi8qKlxuICogUmVzb2x2ZXMgdGhlIGVtYmVkZGVkIHNsb3QgZm9yIE5PUk1BTCBhbmQgRU1CRURERUQgdmlld3Mgb25seS5cbiAqIERJQUxPRyB2aWV3cyBhcmUgaGFuZGxlZCBzZXBhcmF0ZWx5IHVwc3RyZWFtIGluIG9wZW5WaWV3IGFuZCBtdXN0IG5vdCBiZSBwYXNzZWQgaGVyZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHJlc29sdmVTbG90KHZpZXc6IFZpZXdEYXRhKTogUmVzb2x2ZWRTbG90IHtcbiAgaWYgKHZpZXcudHlwZSA9PT0gVmlld1R5cGUuRU1CRURERUQpIHtcbiAgICBpZiAoIXZpZXcuY29udGFpbmVyVXVpZCB8fCAhdmlldy5jb250YWluZXJJZCkge1xuICAgICAgY29uc29sZS5lcnJvcihcbiAgICAgICAgYEVNQkVEREVEIHZpZXcgJyR7dmlldy52aWV3TmFtZX0nIGlzIG1hbGZvcm1lZDogYm90aCBjb250YWluZXJVdWlkIGFuZCBjb250YWluZXJJZCBhcmUgcmVxdWlyZWQsIGAgK1xuICAgICAgICAgIGBnb3QgY29udGFpbmVyVXVpZD0nJHt2aWV3LmNvbnRhaW5lclV1aWR9JywgY29udGFpbmVySWQ9JyR7dmlldy5jb250YWluZXJJZH0nLmAsXG4gICAgICApO1xuICAgIH1cbiAgICByZXR1cm4geyBjb250YWluZXJVdWlkOiB2aWV3LmNvbnRhaW5lclV1aWQhLCBjb250YWluZXJJZDogdmlldy5jb250YWluZXJJZCEgfTtcbiAgfVxuICBpZiAodmlldy5jb250YWluZXJJZCkge1xuICAgIGNvbnNvbGUuZXJyb3IoXG4gICAgICBgTk9STUFMIHZpZXcgJyR7dmlldy52aWV3TmFtZX0nIGhhcyBjb250YWluZXJJZCAnJHt2aWV3LmNvbnRhaW5lcklkfScgc2V0OyBgICtcbiAgICAgICAgYGNvbnRhaW5lcklkIGltcGxpZXMgYW4gRU1CRURERUQgdmlldy4gRmFsbGluZyBiYWNrIHRvICcke1JPVVRFUl9TTE9UX0lEfScuYCxcbiAgICApO1xuICB9XG4gIHJldHVybiB7XG4gICAgY29udGFpbmVyVXVpZDogdmlldy5jb250YWluZXJVdWlkID8/IFJPT1RfQ09OVEFJTkVSX1VVSUQsXG4gICAgY29udGFpbmVySWQ6IFJPVVRFUl9TTE9UX0lELFxuICB9O1xufVxuIl19
@@ -3578,13 +3578,13 @@ class SmartViewContextService {
3578
3578
  unfollow(uuid) {
3579
3579
  this.smartApiClients.delete(uuid);
3580
3580
  }
3581
- registerEmbeddedSlot(containerUuid, containerId, vcRef) {
3581
+ registerEmbeddedSlot(containerUuid, containerId, vcRef, kind) {
3582
3582
  let innerMap = this.embeddedContainers.get(containerUuid);
3583
3583
  if (!innerMap) {
3584
3584
  innerMap = new Map();
3585
3585
  this.embeddedContainers.set(containerUuid, innerMap);
3586
3586
  }
3587
- innerMap.set(containerId, vcRef);
3587
+ innerMap.set(containerId, { vcRef, kind });
3588
3588
  // Reattach embedded views that were detached before layout re-render
3589
3589
  const existing = this.embeddedViewRefs.filter((ref) => ref.containerUuid === containerUuid && ref.containerId === containerId);
3590
3590
  for (const ref of existing) {
@@ -3610,26 +3610,41 @@ class SmartViewContextService {
3610
3610
  if (child.componentRef.hostView.destroyed) {
3611
3611
  continue;
3612
3612
  }
3613
- const vcRef = this.getEmbeddedSlot(containerUuid, child.containerId);
3614
- if (vcRef) {
3615
- const idx = vcRef.indexOf(child.componentRef.hostView);
3616
- if (idx >= 0) {
3617
- vcRef.detach(idx);
3618
- }
3613
+ // Only detach views living in a re-rendering LAYOUT slot. A STABLE directive
3614
+ // slot (router-outlet) lives outside the layout *ngFor and survives the parent
3615
+ // re-render, so its child needs no detach protection — detaching it here is
3616
+ // what produced the false-orphan destroy in multi-level NORMAL nesting
3617
+ // (Home -> Main -> page): Main got swept up by Home's layout dance. (#28870)
3618
+ const entry = this.embeddedContainers.get(containerUuid)?.get(child.containerId);
3619
+ if (!entry || entry.kind === 'stable') {
3620
+ continue;
3621
+ }
3622
+ const idx = entry.vcRef.indexOf(child.componentRef.hostView);
3623
+ if (idx >= 0) {
3624
+ entry.vcRef.detach(idx);
3619
3625
  }
3620
3626
  }
3621
3627
  }
3622
3628
  cleanupOrphanedEmbeddedViews(containerUuid) {
3623
- const orphaned = this.embeddedViewRefs.filter((ref) => {
3624
- if (ref.containerUuid !== containerUuid) {
3625
- return false;
3626
- }
3629
+ const refs = this.embeddedViewRefs.filter((ref) => ref.containerUuid === containerUuid);
3630
+ const orphaned = [];
3631
+ for (const ref of refs) {
3627
3632
  if (ref.componentRef.hostView.destroyed) {
3628
- return true;
3633
+ orphaned.push(ref);
3634
+ continue;
3629
3635
  }
3630
3636
  const vcRef = this.getEmbeddedSlot(containerUuid, ref.containerId);
3631
- return !vcRef || vcRef.indexOf(ref.componentRef.hostView) < 0;
3632
- });
3637
+ if (!vcRef) {
3638
+ // The slot is truly gone (its layout host was torn down and not recreated):
3639
+ // a real orphan to destroy. A LAYOUT slot that came back re-registers and
3640
+ // reattaches its child (registerEmbeddedSlot); a STABLE slot is never detached
3641
+ // in the first place. So a still-registered slot here always holds its view —
3642
+ // no reattach branch is needed (that branch only ever patched the stable
3643
+ // false-orphan, which detachAllEmbeddedViews no longer creates). (#28870)
3644
+ orphaned.push(ref);
3645
+ continue;
3646
+ }
3647
+ }
3633
3648
  for (const ref of orphaned) {
3634
3649
  if (!ref.componentRef.hostView.destroyed) {
3635
3650
  ref.componentRef.destroy();
@@ -3679,7 +3694,7 @@ class SmartViewContextService {
3679
3694
  }
3680
3695
  }
3681
3696
  getEmbeddedSlot(containerUuid, containerId) {
3682
- return this.embeddedContainers.get(containerUuid)?.get(containerId);
3697
+ return this.embeddedContainers.get(containerUuid)?.get(containerId)?.vcRef;
3683
3698
  }
3684
3699
  /**
3685
3700
  * Create the view's component into its resolved slot. Returns false and queues
@@ -3689,13 +3704,29 @@ class SmartViewContextService {
3689
3704
  const { containerUuid, containerId } = resolveSlot(view);
3690
3705
  const vcRef = this.getEmbeddedSlot(containerUuid, containerId);
3691
3706
  if (!vcRef) {
3707
+ // Slot not registered yet: queue the view and drain it when its slot registers
3708
+ // (registerEmbeddedSlot). The synchronous detectChanges below renders a freshly
3709
+ // mounted parent's template in the same syncView pass, so a parent -> child ->
3710
+ // grandchild chain (Home -> Main -> page) registers every slot immediately and
3711
+ // the queue drains within the pass — even when restoreViews opens a child before
3712
+ // its parent. (#28870)
3692
3713
  this.pendingEmbeddedViews.push(view);
3693
3714
  return false;
3694
3715
  }
3695
3716
  const componentRef = vcRef.createComponent(component);
3696
3717
  componentRef.instance.uuid = view.uuid;
3697
- componentRef.instance.run();
3698
3718
  this.embeddedViewRefs.push({ uuid: view.uuid, containerUuid, containerId, componentRef });
3719
+ // Render the freshly created component synchronously so its own embedded-slot
3720
+ // directives (the router-outlet slot of a nested NORMAL view) register their
3721
+ // containers immediately. This lets a parent -> child -> grandchild chain
3722
+ // (e.g. Home -> Main -> page) mount in a single synchronous syncView pass
3723
+ // instead of depending on a change-detection tick firing between each level.
3724
+ // Without it the child cannot resolve its slot, queues into
3725
+ // pendingEmbeddedViews, and the drain races the async updateViewContext
3726
+ // round-trips — intermittently leaving a parent OPENED in backend bookkeeping
3727
+ // but never mounted on the client, so its slot child stays blank. (#28870)
3728
+ componentRef.changeDetectorRef.detectChanges();
3729
+ componentRef.instance.run();
3699
3730
  return true;
3700
3731
  }
3701
3732
  setActionDescriptors(actionDescriptors) {
@@ -12153,7 +12184,9 @@ class SmartEmbeddedSlotDirective {
12153
12184
  tryRegister() {
12154
12185
  // console.log('[EmbeddedSlot] tryRegister', { containerId: this.containerId, containerUuid: this.containerUuid });
12155
12186
  if (this.containerUuid && this.containerId) {
12156
- this.viewContext.registerEmbeddedSlot(this.containerUuid, this.containerId, this.vcRef);
12187
+ // A directive slot is STABLE: it registers once in ngOnInit and lives outside
12188
+ // any layout *ngFor, so it survives a parent layout re-render. (#28870)
12189
+ this.viewContext.registerEmbeddedSlot(this.containerUuid, this.containerId, this.vcRef, 'stable');
12157
12190
  this.registered = true;
12158
12191
  this.registeredContainerUuid = this.containerUuid;
12159
12192
  this.registeredContainerId = this.containerId;
@@ -18377,8 +18410,11 @@ class SmarttreeGenericService extends SmarttreeService {
18377
18410
  }
18378
18411
  async syncTree() {
18379
18412
  if (this.treeFromBackend === null || this.treeFromBackend === undefined) {
18380
- //throw new Error('There is no treeFromBackend available!');
18381
- console.warn('There is no treeFromBackend available in syncTree!');
18413
+ // Expected transient: syncTree can be triggered (e.g. by a host dataChanged or a
18414
+ // push update) before the tree's first downloadTree() has populated treeFromBackend.
18415
+ // There is simply nothing to render yet, so this is a no-op, not an error. Kept at
18416
+ // debug level so it stays discoverable without polluting the console on every load.
18417
+ console.debug('syncTree skipped: treeFromBackend not downloaded yet');
18382
18418
  return;
18383
18419
  }
18384
18420
  await this.cacheActionDesciptors(this.getAllNodes(this.treeFromBackend));
@@ -18705,11 +18741,11 @@ class SmartTreeComponent {
18705
18741
  return `${cssClass}-${plusProperty}`;
18706
18742
  }
18707
18743
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: SmartTreeComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
18708
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.11", type: SmartTreeComponent, selector: "smart-tree", inputs: { treeStyle: "treeStyle", treeService: "treeService" }, host: { properties: { "attr.data-testid": "this.testId" } }, viewQueries: [{ propertyName: "tree", first: true, predicate: ["tree"], descendants: true }, { propertyName: "trigger", predicate: MatMenuTrigger, descendants: true }], ngImport: i0, template: "<div class=\"smartTreeContainer\">\n <smart-ui-action-toolbar\n *ngIf=\"uiActionModels.length\"\n [uiActionModels]=\"uiActionModels\"\n ></smart-ui-action-toolbar>\n <mat-tree\n #tree\n *ngIf=\"treeData\"\n [dataSource]=\"dataSource\"\n [treeControl]=\"treeControl\"\n class=\"sm-tree\"\n >\n <mat-nested-tree-node\n *matTreeNodeDef=\"let node; when: hasChild\"\n matTreeNodeToggle=\"{{ getIfExpanded(node) }}\"\n [ngClass]=\"getClassesForTreeNode(node)\"\n [ngStyle]=\"getNodeStyle(node)\"\n >\n <div\n [ngStyle]=\"getNodePadding(node)\"\n [ngClass]=\"getInnerClassesForTreeNode(node)\"\n class=\"mat-tree-node sm-tree-node\"\n (click)=\"onNodeClick($event, node)\"\n >\n <button mat-icon-button matTreeNodeToggle [attr.aria-label]=\"'Toggle ' + node.name\">\n <mat-icon class=\"mat-icon-rtl-mirror\" (click)=\"onOpenNode($event, node)\">\n <div *ngIf=\"hasChild(node.level, node)\">\n {{ treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right' }}\n </div>\n </mat-icon>\n </button>\n <smart-icon [icon]=\"node.icon\"> </smart-icon>\n <div class=\"sm-tree-row\" [ngClass]=\"node.classes\">\n <div class=\"sm-tree-row-caption\">\n {{ node.caption }}\n </div>\n <div class=\"sm-shortDescription-spacer\"></div>\n <div class=\"sm-tree-row-shortDescription\">\n {{ node.shortDescription }}\n </div>\n <div class=\"sm-shortDescription-button-spacer\"></div>\n <div *ngIf=\"node.button\" class=\"sm-tree-node-button\" [ngSwitch]=\"node.button.type\">\n <button\n (click)=\"customButtonClicked($event, node.button)\"\n *ngSwitchCase=\"smartTreeNodeButtonType.ICON\"\n mat-icon-button\n >\n <smart-icon title=\"{{ node.button.icon }}\" [icon]=\"node.button.icon\"></smart-icon>\n </button>\n <div *ngSwitchCase=\"smartTreeNodeButtonType.MENU\">\n <button\n mat-button\n [matMenuTriggerFor]=\"menu\"\n (click)=\"customButtonClicked($event, node.button, true)\"\n >\n <smart-icon *ngIf=\"node.button.icon\" [icon]=\"node.button.icon\"></smart-icon\n >{{ node.button.label }}\n </button>\n <mat-menu #menu=\"matMenu\">\n <button\n *ngFor=\"let button of node.button.menuItemButtons\"\n (click)=\"customButtonClicked($event, button, true)\"\n mat-menu-item\n [attr.data-testid]=\"button.code ?? null\"\n >\n <smart-icon *ngIf=\"button.icon\" [icon]=\"button.icon\"></smart-icon\n >{{ button.label }}\n </button>\n </mat-menu>\n </div>\n <button\n (click)=\"customButtonClicked($event, node.button)\"\n *ngSwitchCase=\"smartTreeNodeButtonType.NORMAL\"\n mat-raised-button\n >\n <smart-icon *ngIf=\"node.button.icon\" [icon]=\"node.button.icon\"></smart-icon>\n {{ node.button.icon }}\n </button>\n </div>\n </div>\n </div>\n <div\n [class.sm-tree-invisible]=\"!treeControl.isExpanded(node)\"\n [ngClass]=\"getClassesForTreeNodeChildren(node)\"\n role=\"group\"\n >\n <ng-container matTreeNodeOutlet></ng-container>\n </div>\n </mat-nested-tree-node>\n </mat-tree>\n <div *ngIf=\"!treeData\">\n <h3>\n {{ errorMessage }}\n </h3>\n </div>\n</div>\n", styles: [".smartTreeContainer{display:flex;flex-direction:column;gap:.5rem}.sm-tree-invisible{display:none}.sm-tree ul,.sm-tree li{margin-top:0;margin-bottom:0;list-style-type:none}.sm-tree div[role=group]>.mat-tree-node{padding-left:40px}.sm-tee-node{padding-left:40px}.sm-tree-node-name{padding-left:15px;padding-top:15px;display:flex;flex-direction:column}.sm-tree-node-name-row{padding-left:15px;padding-top:15px;display:flex;flex-direction:row;justify-content:space-between}.sm-tree-node-name-col{padding-left:15px;padding-top:15px;display:flex;flex-direction:column}.sm-tee-node-id{font-weight:lighter}.mat-tree-node:hover{cursor:pointer}::ng-deep .mat-icon-rtl-mirror{display:flex;flex-direction:row}.sm-tree-row{display:flex;flex-direction:row;flex:1;align-items:center}.sm-shortDescription-spacer{flex:1}\n"], dependencies: [{ kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i1$2.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1$2.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "component", type: i5.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i5.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: i11$1.MatNestedTreeNode, selector: "mat-nested-tree-node", inputs: ["matNestedTreeNode", "disabled", "tabIndex"], exportAs: ["matNestedTreeNode"] }, { kind: "directive", type: i11$1.MatTreeNodeDef, selector: "[matTreeNodeDef]", inputs: ["matTreeNodeDefWhen", "matTreeNode"] }, { kind: "directive", type: i11$1.MatTreeNodeToggle, selector: "[matTreeNodeToggle]", inputs: ["matTreeNodeToggleRecursive"] }, { kind: "component", type: i11$1.MatTree, selector: "mat-tree", exportAs: ["matTree"] }, { kind: "directive", type: i11$1.MatTreeNodeOutlet, selector: "[matTreeNodeOutlet]" }, { kind: "component", type: i5$1.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i5$1.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i5$1.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: SmartIconComponent, selector: "smart-icon", inputs: ["icon", "color", "imageResource"] }, { kind: "component", type: UiActionToolbarComponent, selector: "smart-ui-action-toolbar", inputs: ["uiActionModels", "uiActionDescriptorService", "id", "scrollOnWrap", "toolbarPropertes"] }] }); }
18744
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.11", type: SmartTreeComponent, selector: "smart-tree", inputs: { treeStyle: "treeStyle", treeService: "treeService" }, host: { properties: { "attr.data-testid": "this.testId" } }, viewQueries: [{ propertyName: "tree", first: true, predicate: ["tree"], descendants: true }, { propertyName: "trigger", predicate: MatMenuTrigger, descendants: true }], ngImport: i0, template: "<div class=\"smartTreeContainer\">\n <smart-ui-action-toolbar\n *ngIf=\"uiActionModels.length\"\n [uiActionModels]=\"uiActionModels\"\n ></smart-ui-action-toolbar>\n <mat-tree\n #tree\n *ngIf=\"treeData\"\n [dataSource]=\"dataSource\"\n [treeControl]=\"treeControl\"\n class=\"sm-tree\"\n >\n <mat-nested-tree-node\n *matTreeNodeDef=\"let node; when: hasChild\"\n matTreeNodeToggle=\"{{ getIfExpanded(node) }}\"\n [ngClass]=\"getClassesForTreeNode(node)\"\n [ngStyle]=\"getNodeStyle(node)\"\n >\n <div\n [ngStyle]=\"getNodePadding(node)\"\n [ngClass]=\"getInnerClassesForTreeNode(node)\"\n class=\"mat-tree-node sm-tree-node\"\n (click)=\"onNodeClick($event, node)\"\n >\n <button\n mat-icon-button\n matTreeNodeToggle\n (click)=\"onOpenNode($event, node)\"\n [attr.aria-label]=\"'Toggle ' + node.name\"\n >\n <mat-icon class=\"mat-icon-rtl-mirror\">\n <div *ngIf=\"hasChild(node.level, node)\">\n {{ treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right' }}\n </div>\n </mat-icon>\n </button>\n <smart-icon [icon]=\"node.icon\"> </smart-icon>\n <div class=\"sm-tree-row\" [ngClass]=\"node.classes\">\n <div class=\"sm-tree-row-caption\">\n {{ node.caption }}\n </div>\n <div class=\"sm-shortDescription-spacer\"></div>\n <div class=\"sm-tree-row-shortDescription\">\n {{ node.shortDescription }}\n </div>\n <div class=\"sm-shortDescription-button-spacer\"></div>\n <div *ngIf=\"node.button\" class=\"sm-tree-node-button\" [ngSwitch]=\"node.button.type\">\n <button\n (click)=\"customButtonClicked($event, node.button)\"\n *ngSwitchCase=\"smartTreeNodeButtonType.ICON\"\n mat-icon-button\n >\n <smart-icon title=\"{{ node.button.icon }}\" [icon]=\"node.button.icon\"></smart-icon>\n </button>\n <div *ngSwitchCase=\"smartTreeNodeButtonType.MENU\">\n <button\n mat-button\n [matMenuTriggerFor]=\"menu\"\n (click)=\"customButtonClicked($event, node.button, true)\"\n >\n <smart-icon *ngIf=\"node.button.icon\" [icon]=\"node.button.icon\"></smart-icon\n >{{ node.button.label }}\n </button>\n <mat-menu #menu=\"matMenu\">\n <button\n *ngFor=\"let button of node.button.menuItemButtons\"\n (click)=\"customButtonClicked($event, button, true)\"\n mat-menu-item\n [attr.data-testid]=\"button.code ?? null\"\n >\n <smart-icon *ngIf=\"button.icon\" [icon]=\"button.icon\"></smart-icon\n >{{ button.label }}\n </button>\n </mat-menu>\n </div>\n <button\n (click)=\"customButtonClicked($event, node.button)\"\n *ngSwitchCase=\"smartTreeNodeButtonType.NORMAL\"\n mat-raised-button\n >\n <smart-icon *ngIf=\"node.button.icon\" [icon]=\"node.button.icon\"></smart-icon>\n {{ node.button.icon }}\n </button>\n </div>\n </div>\n </div>\n <div\n [class.sm-tree-invisible]=\"!treeControl.isExpanded(node)\"\n [ngClass]=\"getClassesForTreeNodeChildren(node)\"\n role=\"group\"\n >\n <ng-container matTreeNodeOutlet></ng-container>\n </div>\n </mat-nested-tree-node>\n </mat-tree>\n <div *ngIf=\"!treeData\">\n <h3>\n {{ errorMessage }}\n </h3>\n </div>\n</div>\n", styles: [".smartTreeContainer{display:flex;flex-direction:column;gap:.5rem}.sm-tree-invisible{display:none}.sm-tree ul,.sm-tree li{margin-top:0;margin-bottom:0;list-style-type:none}.sm-tree div[role=group]>.mat-tree-node{padding-left:40px}.sm-tee-node{padding-left:40px}.sm-tree-node-name{padding-left:15px;padding-top:15px;display:flex;flex-direction:column}.sm-tree-node-name-row{padding-left:15px;padding-top:15px;display:flex;flex-direction:row;justify-content:space-between}.sm-tree-node-name-col{padding-left:15px;padding-top:15px;display:flex;flex-direction:column}.sm-tee-node-id{font-weight:lighter}.mat-tree-node:hover{cursor:pointer}::ng-deep .mat-icon-rtl-mirror{display:flex;flex-direction:row}.sm-tree-row{display:flex;flex-direction:row;flex:1;align-items:center}.sm-shortDescription-spacer{flex:1}\n"], dependencies: [{ kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i1$2.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1$2.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "component", type: i5.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i5.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: i11$1.MatNestedTreeNode, selector: "mat-nested-tree-node", inputs: ["matNestedTreeNode", "disabled", "tabIndex"], exportAs: ["matNestedTreeNode"] }, { kind: "directive", type: i11$1.MatTreeNodeDef, selector: "[matTreeNodeDef]", inputs: ["matTreeNodeDefWhen", "matTreeNode"] }, { kind: "directive", type: i11$1.MatTreeNodeToggle, selector: "[matTreeNodeToggle]", inputs: ["matTreeNodeToggleRecursive"] }, { kind: "component", type: i11$1.MatTree, selector: "mat-tree", exportAs: ["matTree"] }, { kind: "directive", type: i11$1.MatTreeNodeOutlet, selector: "[matTreeNodeOutlet]" }, { kind: "component", type: i5$1.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i5$1.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i5$1.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: SmartIconComponent, selector: "smart-icon", inputs: ["icon", "color", "imageResource"] }, { kind: "component", type: UiActionToolbarComponent, selector: "smart-ui-action-toolbar", inputs: ["uiActionModels", "uiActionDescriptorService", "id", "scrollOnWrap", "toolbarPropertes"] }] }); }
18709
18745
  }
18710
18746
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: SmartTreeComponent, decorators: [{
18711
18747
  type: Component,
18712
- args: [{ selector: 'smart-tree', template: "<div class=\"smartTreeContainer\">\n <smart-ui-action-toolbar\n *ngIf=\"uiActionModels.length\"\n [uiActionModels]=\"uiActionModels\"\n ></smart-ui-action-toolbar>\n <mat-tree\n #tree\n *ngIf=\"treeData\"\n [dataSource]=\"dataSource\"\n [treeControl]=\"treeControl\"\n class=\"sm-tree\"\n >\n <mat-nested-tree-node\n *matTreeNodeDef=\"let node; when: hasChild\"\n matTreeNodeToggle=\"{{ getIfExpanded(node) }}\"\n [ngClass]=\"getClassesForTreeNode(node)\"\n [ngStyle]=\"getNodeStyle(node)\"\n >\n <div\n [ngStyle]=\"getNodePadding(node)\"\n [ngClass]=\"getInnerClassesForTreeNode(node)\"\n class=\"mat-tree-node sm-tree-node\"\n (click)=\"onNodeClick($event, node)\"\n >\n <button mat-icon-button matTreeNodeToggle [attr.aria-label]=\"'Toggle ' + node.name\">\n <mat-icon class=\"mat-icon-rtl-mirror\" (click)=\"onOpenNode($event, node)\">\n <div *ngIf=\"hasChild(node.level, node)\">\n {{ treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right' }}\n </div>\n </mat-icon>\n </button>\n <smart-icon [icon]=\"node.icon\"> </smart-icon>\n <div class=\"sm-tree-row\" [ngClass]=\"node.classes\">\n <div class=\"sm-tree-row-caption\">\n {{ node.caption }}\n </div>\n <div class=\"sm-shortDescription-spacer\"></div>\n <div class=\"sm-tree-row-shortDescription\">\n {{ node.shortDescription }}\n </div>\n <div class=\"sm-shortDescription-button-spacer\"></div>\n <div *ngIf=\"node.button\" class=\"sm-tree-node-button\" [ngSwitch]=\"node.button.type\">\n <button\n (click)=\"customButtonClicked($event, node.button)\"\n *ngSwitchCase=\"smartTreeNodeButtonType.ICON\"\n mat-icon-button\n >\n <smart-icon title=\"{{ node.button.icon }}\" [icon]=\"node.button.icon\"></smart-icon>\n </button>\n <div *ngSwitchCase=\"smartTreeNodeButtonType.MENU\">\n <button\n mat-button\n [matMenuTriggerFor]=\"menu\"\n (click)=\"customButtonClicked($event, node.button, true)\"\n >\n <smart-icon *ngIf=\"node.button.icon\" [icon]=\"node.button.icon\"></smart-icon\n >{{ node.button.label }}\n </button>\n <mat-menu #menu=\"matMenu\">\n <button\n *ngFor=\"let button of node.button.menuItemButtons\"\n (click)=\"customButtonClicked($event, button, true)\"\n mat-menu-item\n [attr.data-testid]=\"button.code ?? null\"\n >\n <smart-icon *ngIf=\"button.icon\" [icon]=\"button.icon\"></smart-icon\n >{{ button.label }}\n </button>\n </mat-menu>\n </div>\n <button\n (click)=\"customButtonClicked($event, node.button)\"\n *ngSwitchCase=\"smartTreeNodeButtonType.NORMAL\"\n mat-raised-button\n >\n <smart-icon *ngIf=\"node.button.icon\" [icon]=\"node.button.icon\"></smart-icon>\n {{ node.button.icon }}\n </button>\n </div>\n </div>\n </div>\n <div\n [class.sm-tree-invisible]=\"!treeControl.isExpanded(node)\"\n [ngClass]=\"getClassesForTreeNodeChildren(node)\"\n role=\"group\"\n >\n <ng-container matTreeNodeOutlet></ng-container>\n </div>\n </mat-nested-tree-node>\n </mat-tree>\n <div *ngIf=\"!treeData\">\n <h3>\n {{ errorMessage }}\n </h3>\n </div>\n</div>\n", styles: [".smartTreeContainer{display:flex;flex-direction:column;gap:.5rem}.sm-tree-invisible{display:none}.sm-tree ul,.sm-tree li{margin-top:0;margin-bottom:0;list-style-type:none}.sm-tree div[role=group]>.mat-tree-node{padding-left:40px}.sm-tee-node{padding-left:40px}.sm-tree-node-name{padding-left:15px;padding-top:15px;display:flex;flex-direction:column}.sm-tree-node-name-row{padding-left:15px;padding-top:15px;display:flex;flex-direction:row;justify-content:space-between}.sm-tree-node-name-col{padding-left:15px;padding-top:15px;display:flex;flex-direction:column}.sm-tee-node-id{font-weight:lighter}.mat-tree-node:hover{cursor:pointer}::ng-deep .mat-icon-rtl-mirror{display:flex;flex-direction:row}.sm-tree-row{display:flex;flex-direction:row;flex:1;align-items:center}.sm-shortDescription-spacer{flex:1}\n"] }]
18748
+ args: [{ selector: 'smart-tree', template: "<div class=\"smartTreeContainer\">\n <smart-ui-action-toolbar\n *ngIf=\"uiActionModels.length\"\n [uiActionModels]=\"uiActionModels\"\n ></smart-ui-action-toolbar>\n <mat-tree\n #tree\n *ngIf=\"treeData\"\n [dataSource]=\"dataSource\"\n [treeControl]=\"treeControl\"\n class=\"sm-tree\"\n >\n <mat-nested-tree-node\n *matTreeNodeDef=\"let node; when: hasChild\"\n matTreeNodeToggle=\"{{ getIfExpanded(node) }}\"\n [ngClass]=\"getClassesForTreeNode(node)\"\n [ngStyle]=\"getNodeStyle(node)\"\n >\n <div\n [ngStyle]=\"getNodePadding(node)\"\n [ngClass]=\"getInnerClassesForTreeNode(node)\"\n class=\"mat-tree-node sm-tree-node\"\n (click)=\"onNodeClick($event, node)\"\n >\n <button\n mat-icon-button\n matTreeNodeToggle\n (click)=\"onOpenNode($event, node)\"\n [attr.aria-label]=\"'Toggle ' + node.name\"\n >\n <mat-icon class=\"mat-icon-rtl-mirror\">\n <div *ngIf=\"hasChild(node.level, node)\">\n {{ treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right' }}\n </div>\n </mat-icon>\n </button>\n <smart-icon [icon]=\"node.icon\"> </smart-icon>\n <div class=\"sm-tree-row\" [ngClass]=\"node.classes\">\n <div class=\"sm-tree-row-caption\">\n {{ node.caption }}\n </div>\n <div class=\"sm-shortDescription-spacer\"></div>\n <div class=\"sm-tree-row-shortDescription\">\n {{ node.shortDescription }}\n </div>\n <div class=\"sm-shortDescription-button-spacer\"></div>\n <div *ngIf=\"node.button\" class=\"sm-tree-node-button\" [ngSwitch]=\"node.button.type\">\n <button\n (click)=\"customButtonClicked($event, node.button)\"\n *ngSwitchCase=\"smartTreeNodeButtonType.ICON\"\n mat-icon-button\n >\n <smart-icon title=\"{{ node.button.icon }}\" [icon]=\"node.button.icon\"></smart-icon>\n </button>\n <div *ngSwitchCase=\"smartTreeNodeButtonType.MENU\">\n <button\n mat-button\n [matMenuTriggerFor]=\"menu\"\n (click)=\"customButtonClicked($event, node.button, true)\"\n >\n <smart-icon *ngIf=\"node.button.icon\" [icon]=\"node.button.icon\"></smart-icon\n >{{ node.button.label }}\n </button>\n <mat-menu #menu=\"matMenu\">\n <button\n *ngFor=\"let button of node.button.menuItemButtons\"\n (click)=\"customButtonClicked($event, button, true)\"\n mat-menu-item\n [attr.data-testid]=\"button.code ?? null\"\n >\n <smart-icon *ngIf=\"button.icon\" [icon]=\"button.icon\"></smart-icon\n >{{ button.label }}\n </button>\n </mat-menu>\n </div>\n <button\n (click)=\"customButtonClicked($event, node.button)\"\n *ngSwitchCase=\"smartTreeNodeButtonType.NORMAL\"\n mat-raised-button\n >\n <smart-icon *ngIf=\"node.button.icon\" [icon]=\"node.button.icon\"></smart-icon>\n {{ node.button.icon }}\n </button>\n </div>\n </div>\n </div>\n <div\n [class.sm-tree-invisible]=\"!treeControl.isExpanded(node)\"\n [ngClass]=\"getClassesForTreeNodeChildren(node)\"\n role=\"group\"\n >\n <ng-container matTreeNodeOutlet></ng-container>\n </div>\n </mat-nested-tree-node>\n </mat-tree>\n <div *ngIf=\"!treeData\">\n <h3>\n {{ errorMessage }}\n </h3>\n </div>\n</div>\n", styles: [".smartTreeContainer{display:flex;flex-direction:column;gap:.5rem}.sm-tree-invisible{display:none}.sm-tree ul,.sm-tree li{margin-top:0;margin-bottom:0;list-style-type:none}.sm-tree div[role=group]>.mat-tree-node{padding-left:40px}.sm-tee-node{padding-left:40px}.sm-tree-node-name{padding-left:15px;padding-top:15px;display:flex;flex-direction:column}.sm-tree-node-name-row{padding-left:15px;padding-top:15px;display:flex;flex-direction:row;justify-content:space-between}.sm-tree-node-name-col{padding-left:15px;padding-top:15px;display:flex;flex-direction:column}.sm-tee-node-id{font-weight:lighter}.mat-tree-node:hover{cursor:pointer}::ng-deep .mat-icon-rtl-mirror{display:flex;flex-direction:row}.sm-tree-row{display:flex;flex-direction:row;flex:1;align-items:center}.sm-shortDescription-spacer{flex:1}\n"] }]
18713
18749
  }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { trigger: [{
18714
18750
  type: ViewChildren,
18715
18751
  args: [MatMenuTrigger]
@@ -21839,7 +21875,10 @@ class SmartComponentLayoutComponent {
21839
21875
  }
21840
21876
  tryRegisterEmbeddedSlot() {
21841
21877
  if (this.embeddedSlotContainerId && this.uuid && this._embeddedSlotVcRef) {
21842
- this.viewContext.registerEmbeddedSlot(this.uuid, this.embeddedSlotContainerId, this._embeddedSlotVcRef);
21878
+ // An EMBEDDED_SLOT widget slot is LAYOUT-kind: it lives inside the re-rendering
21879
+ // layout *ngFor, so its child must be detached before a top-level re-render and
21880
+ // reattaches when this slot re-registers. (#28870)
21881
+ this.viewContext.registerEmbeddedSlot(this.uuid, this.embeddedSlotContainerId, this._embeddedSlotVcRef, 'layout');
21843
21882
  }
21844
21883
  }
21845
21884
  updateUuid(newUuid) {
@@ -23387,9 +23426,15 @@ class SmartComponentApiClient {
23387
23426
  this.getAllSmartGridComponents()
23388
23427
  .filter((grid) => (grid?.smartGrid?.gridIdentifier ? true : false))
23389
23428
  .forEach((grid) => widgets.set(grid.smartGrid.gridIdentifier, grid));
23429
+ // Key trees by their treeId (set in the service constructor), NOT by
23430
+ // smartTreeModel.identifier (only set after the first successful syncTree). A tree
23431
+ // that exists but has not finished its initial download would otherwise be absent
23432
+ // from the widget map, so a push update could not resolve it
23433
+ // ("Provided reference for <treeId> is undefined"). treeId equals the backend
23434
+ // widget identifier, so push routing matches regardless of download timing. (#28361)
23390
23435
  this.getAllSmartTreeComponents()
23391
- .filter((tree) => (tree?.smartTreeModel?.identifier ? true : false))
23392
- .forEach((tree) => widgets.set(tree.smartTreeModel.identifier, tree));
23436
+ .filter((tree) => !!tree?.treeId)
23437
+ .forEach((tree) => widgets.set(tree.treeId, tree));
23393
23438
  this.getAllSmartFilterEditorContentComponents()
23394
23439
  .filter((filter) => (filter.service.config.identifier ? true : false))
23395
23440
  .forEach((filter) => widgets.set(filter.service.config.identifier, filter));
@@ -23841,12 +23886,12 @@ class SmartComponentApiClient {
23841
23886
  }
23842
23887
  createTreeService(treeId) {
23843
23888
  const treeService = new SmarttreeGenericService(this.inject, this.pageName, treeId);
23889
+ // In the useQueryLists model the tree is routed via the SmartTreeComponent
23890
+ // QueryList + treeId (see getWidgets), so no manual widgets.set is needed; the
23891
+ // legacy non-QueryList path still registers it explicitly.
23844
23892
  if (!this.useQueryLists) {
23845
23893
  this.widgets.set(treeId, treeService);
23846
23894
  }
23847
- else {
23848
- console.warn(`Added smartTree when useQueryLists === true (${treeId})`);
23849
- }
23850
23895
  return treeService;
23851
23896
  }
23852
23897
  addFileUploader(uiActionCode, fileFormats, maxSizeMb, isMultiple) {