@smartbit4all/ng-client 6.0.0 → 6.0.2

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,43 @@ 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.
3640
+ orphaned.push(ref);
3641
+ continue;
3642
+ }
3643
+ // Slot still registered but its view was detached by detachAllEmbeddedViews and
3644
+ // never reattached, because the *ngFor reused the slot host (only a sibling
3645
+ // widget changed) so no re-register fired. Reattach so every detach is paired.
3646
+ if (vcRef.indexOf(ref.componentRef.hostView) < 0) {
3647
+ vcRef.insert(ref.componentRef.hostView);
3648
+ }
3649
+ }
3633
3650
  for (const ref of orphaned) {
3634
3651
  if (!ref.componentRef.hostView.destroyed) {
3635
3652
  ref.componentRef.destroy();
@@ -3679,7 +3696,7 @@ class SmartViewContextService {
3679
3696
  }
3680
3697
  }
3681
3698
  getEmbeddedSlot(containerUuid, containerId) {
3682
- return this.embeddedContainers.get(containerUuid)?.get(containerId);
3699
+ return this.embeddedContainers.get(containerUuid)?.get(containerId)?.vcRef;
3683
3700
  }
3684
3701
  /**
3685
3702
  * Create the view's component into its resolved slot. Returns false and queues
@@ -3689,13 +3706,29 @@ class SmartViewContextService {
3689
3706
  const { containerUuid, containerId } = resolveSlot(view);
3690
3707
  const vcRef = this.getEmbeddedSlot(containerUuid, containerId);
3691
3708
  if (!vcRef) {
3709
+ // Slot not registered yet: queue the view and drain it when its slot registers
3710
+ // (registerEmbeddedSlot). The synchronous detectChanges below renders a freshly
3711
+ // mounted parent's template in the same syncView pass, so a parent -> child ->
3712
+ // grandchild chain (Home -> Main -> page) registers every slot immediately and
3713
+ // the queue drains within the pass — even when restoreViews opens a child before
3714
+ // its parent. (#28870)
3692
3715
  this.pendingEmbeddedViews.push(view);
3693
3716
  return false;
3694
3717
  }
3695
3718
  const componentRef = vcRef.createComponent(component);
3696
3719
  componentRef.instance.uuid = view.uuid;
3697
- componentRef.instance.run();
3698
3720
  this.embeddedViewRefs.push({ uuid: view.uuid, containerUuid, containerId, componentRef });
3721
+ // Render the freshly created component synchronously so its own embedded-slot
3722
+ // directives (the router-outlet slot of a nested NORMAL view) register their
3723
+ // containers immediately. This lets a parent -> child -> grandchild chain
3724
+ // (e.g. Home -> Main -> page) mount in a single synchronous syncView pass
3725
+ // instead of depending on a change-detection tick firing between each level.
3726
+ // Without it the child cannot resolve its slot, queues into
3727
+ // pendingEmbeddedViews, and the drain races the async updateViewContext
3728
+ // round-trips — intermittently leaving a parent OPENED in backend bookkeeping
3729
+ // but never mounted on the client, so its slot child stays blank. (#28870)
3730
+ componentRef.changeDetectorRef.detectChanges();
3731
+ componentRef.instance.run();
3699
3732
  return true;
3700
3733
  }
3701
3734
  setActionDescriptors(actionDescriptors) {
@@ -12153,7 +12186,9 @@ class SmartEmbeddedSlotDirective {
12153
12186
  tryRegister() {
12154
12187
  // console.log('[EmbeddedSlot] tryRegister', { containerId: this.containerId, containerUuid: this.containerUuid });
12155
12188
  if (this.containerUuid && this.containerId) {
12156
- this.viewContext.registerEmbeddedSlot(this.containerUuid, this.containerId, this.vcRef);
12189
+ // A directive slot is STABLE: it registers once in ngOnInit and lives outside
12190
+ // any layout *ngFor, so it survives a parent layout re-render. (#28870)
12191
+ this.viewContext.registerEmbeddedSlot(this.containerUuid, this.containerId, this.vcRef, 'stable');
12157
12192
  this.registered = true;
12158
12193
  this.registeredContainerUuid = this.containerUuid;
12159
12194
  this.registeredContainerId = this.containerId;
@@ -18377,8 +18412,11 @@ class SmarttreeGenericService extends SmarttreeService {
18377
18412
  }
18378
18413
  async syncTree() {
18379
18414
  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!');
18415
+ // Expected transient: syncTree can be triggered (e.g. by a host dataChanged or a
18416
+ // push update) before the tree's first downloadTree() has populated treeFromBackend.
18417
+ // There is simply nothing to render yet, so this is a no-op, not an error. Kept at
18418
+ // debug level so it stays discoverable without polluting the console on every load.
18419
+ console.debug('syncTree skipped: treeFromBackend not downloaded yet');
18382
18420
  return;
18383
18421
  }
18384
18422
  await this.cacheActionDesciptors(this.getAllNodes(this.treeFromBackend));
@@ -18705,11 +18743,11 @@ class SmartTreeComponent {
18705
18743
  return `${cssClass}-${plusProperty}`;
18706
18744
  }
18707
18745
  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"] }] }); }
18746
+ 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
18747
  }
18710
18748
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.11", ngImport: i0, type: SmartTreeComponent, decorators: [{
18711
18749
  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"] }]
18750
+ 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
18751
  }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { trigger: [{
18714
18752
  type: ViewChildren,
18715
18753
  args: [MatMenuTrigger]
@@ -21839,7 +21877,10 @@ class SmartComponentLayoutComponent {
21839
21877
  }
21840
21878
  tryRegisterEmbeddedSlot() {
21841
21879
  if (this.embeddedSlotContainerId && this.uuid && this._embeddedSlotVcRef) {
21842
- this.viewContext.registerEmbeddedSlot(this.uuid, this.embeddedSlotContainerId, this._embeddedSlotVcRef);
21880
+ // An EMBEDDED_SLOT widget slot is LAYOUT-kind: it lives inside the re-rendering
21881
+ // layout *ngFor, so its child must be detached before a top-level re-render and
21882
+ // reattaches when this slot re-registers. (#28870)
21883
+ this.viewContext.registerEmbeddedSlot(this.uuid, this.embeddedSlotContainerId, this._embeddedSlotVcRef, 'layout');
21843
21884
  }
21844
21885
  }
21845
21886
  updateUuid(newUuid) {
@@ -23387,9 +23428,15 @@ class SmartComponentApiClient {
23387
23428
  this.getAllSmartGridComponents()
23388
23429
  .filter((grid) => (grid?.smartGrid?.gridIdentifier ? true : false))
23389
23430
  .forEach((grid) => widgets.set(grid.smartGrid.gridIdentifier, grid));
23431
+ // Key trees by their treeId (set in the service constructor), NOT by
23432
+ // smartTreeModel.identifier (only set after the first successful syncTree). A tree
23433
+ // that exists but has not finished its initial download would otherwise be absent
23434
+ // from the widget map, so a push update could not resolve it
23435
+ // ("Provided reference for <treeId> is undefined"). treeId equals the backend
23436
+ // widget identifier, so push routing matches regardless of download timing. (#28361)
23390
23437
  this.getAllSmartTreeComponents()
23391
- .filter((tree) => (tree?.smartTreeModel?.identifier ? true : false))
23392
- .forEach((tree) => widgets.set(tree.smartTreeModel.identifier, tree));
23438
+ .filter((tree) => !!tree?.treeId)
23439
+ .forEach((tree) => widgets.set(tree.treeId, tree));
23393
23440
  this.getAllSmartFilterEditorContentComponents()
23394
23441
  .filter((filter) => (filter.service.config.identifier ? true : false))
23395
23442
  .forEach((filter) => widgets.set(filter.service.config.identifier, filter));
@@ -23841,12 +23888,12 @@ class SmartComponentApiClient {
23841
23888
  }
23842
23889
  createTreeService(treeId) {
23843
23890
  const treeService = new SmarttreeGenericService(this.inject, this.pageName, treeId);
23891
+ // In the useQueryLists model the tree is routed via the SmartTreeComponent
23892
+ // QueryList + treeId (see getWidgets), so no manual widgets.set is needed; the
23893
+ // legacy non-QueryList path still registers it explicitly.
23844
23894
  if (!this.useQueryLists) {
23845
23895
  this.widgets.set(treeId, treeService);
23846
23896
  }
23847
- else {
23848
- console.warn(`Added smartTree when useQueryLists === true (${treeId})`);
23849
- }
23850
23897
  return treeService;
23851
23898
  }
23852
23899
  addFileUploader(uiActionCode, fileFormats, maxSizeMb, isMultiple) {