@acorex/components 22.1.0-next.16 → 22.1.0-next.18

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.
@@ -575,32 +575,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
575
575
  }], propDecorators: { dataSource: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataSource", required: true }] }], valueField: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueField", required: false }] }], textField: [{ type: i0.Input, args: [{ isSignal: true, alias: "textField", required: false }] }], disabledField: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabledField", required: false }] }], selectedValues: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedValues", required: false }] }], nodeTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "nodeTemplate", required: false }] }], itemClick: [{ type: i0.Output, args: ["itemClick"] }] } });
576
576
 
577
577
  /**
578
- * Internal mini component for the `multi-column` lookup mode:
579
- * renders selectable rows in an `ax-data-table` grid and loads further
580
- * pages as the user scrolls (no pager UI).
578
+ * Internal mini component for the `multi-column-tree` lookup mode:
579
+ * a multi-column `ax-data-table` with a drop-down tree inside it.
580
+ * Rows expand/collapse via the data-table hierarchy (`parentField` / nested `children`).
581
581
  *
582
582
  * @category Components
583
583
  */
584
- class AXLookupMultiColumnComponent {
585
- /** Accumulated rows fed to the table (grows as pages are scrolled in). */
586
- #loadedItems;
587
- /** Total matching rows from the source (used to size the popup). */
588
- #totalCount;
589
- #host;
584
+ class AXLookupMultiColumnTreeComponent {
590
585
  constructor() {
591
586
  /**
592
- * The datasource providing the grid rows (paged, remote-capable).
593
- * Pages are fetched on demand as the user scrolls the table body.
587
+ * The datasource providing hierarchical rows (roots on load, children via `parentField`).
594
588
  */
595
589
  this.dataSource = input.required(/* @ts-ignore */
596
590
  ...(ngDevMode ? [{ debugName: "dataSource" }] : /* istanbul ignore next */ []));
597
591
  /**
598
- * The item property used as the selection value.
592
+ * The item property used as the selection value (also the data-table row key).
599
593
  */
600
594
  this.valueField = input('id', /* @ts-ignore */
601
595
  ...(ngDevMode ? [{ debugName: "valueField" }] : /* istanbul ignore next */ []));
602
596
  /**
603
- * The item property used as the display text.
597
+ * The item property used as the display text. The matching column shows the expand control
598
+ * unless another column sets `expandHandler`.
604
599
  */
605
600
  this.textField = input('text', /* @ts-ignore */
606
601
  ...(ngDevMode ? [{ debugName: "textField" }] : /* istanbul ignore next */ []));
@@ -609,6 +604,16 @@ class AXLookupMultiColumnComponent {
609
604
  */
610
605
  this.disabledField = input('disabled', /* @ts-ignore */
611
606
  ...(ngDevMode ? [{ debugName: "disabledField" }] : /* istanbul ignore next */ []));
607
+ /**
608
+ * Field used by the data table to fetch children (`AXDataSource` filter on expand).
609
+ */
610
+ this.parentField = input('parentId', /* @ts-ignore */
611
+ ...(ngDevMode ? [{ debugName: "parentField" }] : /* istanbul ignore next */ []));
612
+ /**
613
+ * Field used by the data table to decide whether a row can expand.
614
+ */
615
+ this.hasChildrenField = input('hasChild', /* @ts-ignore */
616
+ ...(ngDevMode ? [{ debugName: "hasChildrenField" }] : /* istanbul ignore next */ []));
612
617
  /**
613
618
  * Currently selected values (always an array, even for single selection).
614
619
  */
@@ -651,40 +656,13 @@ class AXLookupMultiColumnComponent {
651
656
  this.alternate = input(false, /* @ts-ignore */
652
657
  ...(ngDevMode ? [{ debugName: "alternate" }] : /* istanbul ignore next */ []));
653
658
  /**
654
- * Emitted when the user picks a row.
659
+ * Emitted when the user picks a row (not when expanding/collapsing).
655
660
  */
656
661
  this.itemClick = output();
657
- this.tableRef = viewChild(AXDataTableComponent, /* @ts-ignore */
658
- ...(ngDevMode ? [{ debugName: "tableRef" }] : /* istanbul ignore next */ []));
659
- /** Accumulated rows fed to the table (grows as pages are scrolled in). */
660
- this.#loadedItems = [];
661
- /** Total matching rows from the source (used to size the popup). */
662
- this.#totalCount = signal(0, /* @ts-ignore */
663
- ...(ngDevMode ? [{ debugName: "#totalCount" }] : /* istanbul ignore next */ []));
664
662
  /**
665
- * Local table source: one page of whatever has been scrolled in so far.
666
- * Kept as a stable instance so the table host is not recreated on each page load.
667
- * `pageSize` grows with `#loadedItems` — never seed with a huge empty array (data-table
668
- * initializes `displayedRows` as `new Array(pageSize)`, which caused NG0955-level jank).
669
- */
670
- this.tableDataSource = new AXDataSource({
671
- pageSize: 1,
672
- key: 'id',
673
- load: async () => ({
674
- items: this.#loadedItems,
675
- total: this.#loadedItems.length,
676
- }),
677
- });
678
- /**
679
- * Header (~40px) + body rows. Grows with results up to `maxVisibleItems`,
680
- * then the table body scrolls for the rest.
663
+ * Header (~40px) + up to `maxVisibleItems` body rows. Expanded children scroll inside.
681
664
  */
682
- this.tableHeight = computed(() => {
683
- const header = 40;
684
- const total = this.#totalCount();
685
- const rows = total > 0 ? total : Math.min(3, this.maxVisibleItems());
686
- return header + Math.min(rows, this.maxVisibleItems()) * this.itemHeight();
687
- }, /* @ts-ignore */
665
+ this.tableHeight = computed(() => 40 + this.maxVisibleItems() * this.itemHeight(), /* @ts-ignore */
688
666
  ...(ngDevMode ? [{ debugName: "tableHeight" }] : /* istanbul ignore next */ []));
689
667
  this.loadingConfig = computed(() => ({
690
668
  enabled: true,
@@ -692,7 +670,6 @@ class AXLookupMultiColumnComponent {
692
670
  loadingTemplate: this.loadingTemplate(),
693
671
  }), /* @ts-ignore */
694
672
  ...(ngDevMode ? [{ debugName: "loadingConfig" }] : /* istanbul ignore next */ []));
695
- this.#host = inject((ElementRef));
696
673
  this.resolveRowCssClass = (row) => {
697
674
  const item = row;
698
675
  if (!item) {
@@ -707,44 +684,6 @@ class AXLookupMultiColumnComponent {
707
684
  }
708
685
  return classes.join(' ');
709
686
  };
710
- effect((onCleanup) => {
711
- const source = this.dataSource();
712
- this.tableDataSource.config.key = source.config.key || this.valueField();
713
- untracked(() => {
714
- this.#applyLoadedItems(source.cachedItems, source.totalCount);
715
- });
716
- const changedSub = source.onChanged.subscribe((event) => {
717
- this.#applyLoadedItems(event.cachedItems, event.totalCount);
718
- });
719
- // Fetch page 0 only when the source has never loaded. Reopen remounts this
720
- // view; setPage(0) is a no-op on a warm source and must not force a new API call.
721
- queueMicrotask(() => {
722
- if (!source.cachedItems.some((item) => item != null)) {
723
- source.setPage(0);
724
- }
725
- });
726
- onCleanup(() => changedSub.unsubscribe());
727
- });
728
- effect((onCleanup) => {
729
- // Rebind when the table instance appears / source changes.
730
- this.tableRef();
731
- this.dataSource();
732
- let scrollSub;
733
- const timer = setTimeout(() => {
734
- const wrapper = this.tableRef()?.getHostElement()?.querySelector('.ax-data-table-wrapper') ??
735
- this.#host.nativeElement.querySelector('.ax-data-table-wrapper');
736
- if (!wrapper) {
737
- return;
738
- }
739
- scrollSub = fromEvent(wrapper, 'scroll')
740
- .pipe(auditTime(80))
741
- .subscribe(() => this.#tryLoadMore(wrapper));
742
- }, 0);
743
- onCleanup(() => {
744
- clearTimeout(timer);
745
- scrollSub?.unsubscribe();
746
- });
747
- });
748
687
  }
749
688
  /** Stable unique key for `@for` — avoids NG0955 when `field` is missing or duplicated. */
750
689
  trackColumnKey(index, column) {
@@ -757,14 +696,15 @@ class AXLookupMultiColumnComponent {
757
696
  }
758
697
  return width;
759
698
  }
760
- #applyLoadedItems(cachedItems, totalCount) {
761
- this.#loadedItems = cachedItems.filter((item) => item != null);
762
- this.#totalCount.set(totalCount);
763
- this.#syncTablePageSize();
764
- this.tableDataSource.refresh({ reset: true });
765
- }
766
- #syncTablePageSize() {
767
- this.tableDataSource.config.pageSize = Math.max(this.#loadedItems.length, 1);
699
+ isExpandColumn(index, column) {
700
+ if (column.expandHandler === true) {
701
+ return true;
702
+ }
703
+ if (this.columns().some((item) => item.expandHandler === true)) {
704
+ return false;
705
+ }
706
+ const textIndex = this.columns().findIndex((item) => item.field === this.textField());
707
+ return (textIndex === -1 ? 0 : textIndex) === index;
768
708
  }
769
709
  handleRowClick(event) {
770
710
  this.handleItemClick(event.data);
@@ -787,31 +727,17 @@ class AXLookupMultiColumnComponent {
787
727
  isDisabled(item) {
788
728
  return Boolean(item?.[this.disabledField()]);
789
729
  }
790
- #tryLoadMore(wrapper) {
791
- const nearBottom = wrapper.scrollTop + wrapper.clientHeight >= wrapper.scrollHeight - 48;
792
- if (!nearBottom) {
793
- return;
794
- }
795
- const source = this.dataSource();
796
- if (source.isLoading || source.totalCount === 0) {
797
- return;
798
- }
799
- const loadedCount = source.cachedItems.filter((item) => item != null).length;
800
- if (loadedCount >= source.totalCount) {
801
- return;
802
- }
803
- const nextPage = Math.floor(loadedCount / source.pageSize);
804
- source.setPage(nextPage);
805
- }
806
730
  getValue(item) {
807
731
  return item?.[this.valueField()];
808
732
  }
809
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXLookupMultiColumnComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
810
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXLookupMultiColumnComponent, isStandalone: true, selector: "ax-lookup-multi-column", inputs: { dataSource: { classPropertyName: "dataSource", publicName: "dataSource", isSignal: true, isRequired: true, transformFunction: null }, valueField: { classPropertyName: "valueField", publicName: "valueField", isSignal: true, isRequired: false, transformFunction: null }, textField: { classPropertyName: "textField", publicName: "textField", isSignal: true, isRequired: false, transformFunction: null }, disabledField: { classPropertyName: "disabledField", publicName: "disabledField", isSignal: true, isRequired: false, transformFunction: null }, selectedValues: { classPropertyName: "selectedValues", publicName: "selectedValues", isSignal: true, isRequired: false, transformFunction: null }, itemHeight: { classPropertyName: "itemHeight", publicName: "itemHeight", isSignal: true, isRequired: false, transformFunction: null }, maxVisibleItems: { classPropertyName: "maxVisibleItems", publicName: "maxVisibleItems", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, itemTemplate: { classPropertyName: "itemTemplate", publicName: "itemTemplate", isSignal: true, isRequired: false, transformFunction: null }, emptyTemplate: { classPropertyName: "emptyTemplate", publicName: "emptyTemplate", isSignal: true, isRequired: false, transformFunction: null }, loadingTemplate: { classPropertyName: "loadingTemplate", publicName: "loadingTemplate", isSignal: true, isRequired: false, transformFunction: null }, alternate: { classPropertyName: "alternate", publicName: "alternate", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { itemClick: "itemClick" }, host: { properties: { "style.height.px": "tableHeight()" }, classAttribute: "ax-lookup-multi-column" }, viewQueries: [{ propertyName: "tableRef", first: true, predicate: AXDataTableComponent, descendants: true, isSignal: true }], ngImport: i0, template: `
733
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXLookupMultiColumnTreeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
734
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXLookupMultiColumnTreeComponent, isStandalone: true, selector: "ax-lookup-multi-column-tree", inputs: { dataSource: { classPropertyName: "dataSource", publicName: "dataSource", isSignal: true, isRequired: true, transformFunction: null }, valueField: { classPropertyName: "valueField", publicName: "valueField", isSignal: true, isRequired: false, transformFunction: null }, textField: { classPropertyName: "textField", publicName: "textField", isSignal: true, isRequired: false, transformFunction: null }, disabledField: { classPropertyName: "disabledField", publicName: "disabledField", isSignal: true, isRequired: false, transformFunction: null }, parentField: { classPropertyName: "parentField", publicName: "parentField", isSignal: true, isRequired: false, transformFunction: null }, hasChildrenField: { classPropertyName: "hasChildrenField", publicName: "hasChildrenField", isSignal: true, isRequired: false, transformFunction: null }, selectedValues: { classPropertyName: "selectedValues", publicName: "selectedValues", isSignal: true, isRequired: false, transformFunction: null }, itemHeight: { classPropertyName: "itemHeight", publicName: "itemHeight", isSignal: true, isRequired: false, transformFunction: null }, maxVisibleItems: { classPropertyName: "maxVisibleItems", publicName: "maxVisibleItems", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, itemTemplate: { classPropertyName: "itemTemplate", publicName: "itemTemplate", isSignal: true, isRequired: false, transformFunction: null }, emptyTemplate: { classPropertyName: "emptyTemplate", publicName: "emptyTemplate", isSignal: true, isRequired: false, transformFunction: null }, loadingTemplate: { classPropertyName: "loadingTemplate", publicName: "loadingTemplate", isSignal: true, isRequired: false, transformFunction: null }, alternate: { classPropertyName: "alternate", publicName: "alternate", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { itemClick: "itemClick" }, host: { properties: { "style.height.px": "tableHeight()" }, classAttribute: "ax-lookup-multi-column ax-lookup-multi-column-tree" }, ngImport: i0, template: `
811
735
  <ax-data-table
812
736
  class="ax-lookup-multi-column-table"
813
737
  look="minimal"
814
- [dataSource]="tableDataSource"
738
+ [dataSource]="dataSource()"
739
+ [parentField]="parentField()"
740
+ [hasChildrenField]="hasChildrenField()"
815
741
  [itemHeight]="itemHeight()"
816
742
  [alternative]="alternate()"
817
743
  [emptyTemplate]="emptyTemplate()"
@@ -830,6 +756,7 @@ class AXLookupMultiColumnComponent {
830
756
  [dataField]="column.field"
831
757
  [caption]="column.title"
832
758
  [width]="resolveColumnWidth(column)"
759
+ [expandHandler]="isExpandColumn($index, column)"
833
760
  [allowSorting]="false"
834
761
  [allowResizing]="false"
835
762
  />
@@ -856,22 +783,24 @@ class AXLookupMultiColumnComponent {
856
783
  </ng-template>
857
784
  `, isInline: true, dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: AXDataTableComponent, selector: "ax-data-table", inputs: ["dataSource", "selectedRows", "parentField", "hasChildrenField", "rowDetailsTemplate", "rowCssClass", "cellCssClass", "highlightText", "look", "rowTemplate", "emptyTemplate", "noDataTemplate", "alternative", "showHeader", "fixedHeader", "showFooter", "fixedFooter", "itemHeight", "allowReordering", "paging", "fetchDataMode", "loading", "focusedRow"], outputs: ["selectedRowsChange", "focusedRowChange", "onRowClick", "onRowDbClick", "onColumnsOrderChanged", "onColumnSizeChanged", "onPageChanged"] }, { kind: "component", type: AXDataTableTextColumnComponent, selector: "ax-text-column", inputs: ["width", "caption", "allowSorting", "allowResizing", "fixed", "hasTitle", "customExpandIcon", "customCollapseIcon", "dataField", "expandHandler", "wrapText", "cellTemplate", "footerTemplate", "headerTemplate", "format", "formatOptions"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
858
785
  }
859
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXLookupMultiColumnComponent, decorators: [{
786
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXLookupMultiColumnTreeComponent, decorators: [{
860
787
  type: Component,
861
788
  args: [{
862
- selector: 'ax-lookup-multi-column',
789
+ selector: 'ax-lookup-multi-column-tree',
863
790
  changeDetection: ChangeDetectionStrategy.OnPush,
864
791
  encapsulation: ViewEncapsulation.None,
865
792
  imports: [NgTemplateOutlet, AXDataTableComponent, AXDataTableTextColumnComponent],
866
793
  host: {
867
- class: 'ax-lookup-multi-column',
794
+ class: 'ax-lookup-multi-column ax-lookup-multi-column-tree',
868
795
  '[style.height.px]': 'tableHeight()',
869
796
  },
870
797
  template: `
871
798
  <ax-data-table
872
799
  class="ax-lookup-multi-column-table"
873
800
  look="minimal"
874
- [dataSource]="tableDataSource"
801
+ [dataSource]="dataSource()"
802
+ [parentField]="parentField()"
803
+ [hasChildrenField]="hasChildrenField()"
875
804
  [itemHeight]="itemHeight()"
876
805
  [alternative]="alternate()"
877
806
  [emptyTemplate]="emptyTemplate()"
@@ -890,6 +819,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
890
819
  [dataField]="column.field"
891
820
  [caption]="column.title"
892
821
  [width]="resolveColumnWidth(column)"
822
+ [expandHandler]="isExpandColumn($index, column)"
893
823
  [allowSorting]="false"
894
824
  [allowResizing]="false"
895
825
  />
@@ -916,30 +846,35 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
916
846
  </ng-template>
917
847
  `,
918
848
  }]
919
- }], ctorParameters: () => [], propDecorators: { dataSource: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataSource", required: true }] }], valueField: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueField", required: false }] }], textField: [{ type: i0.Input, args: [{ isSignal: true, alias: "textField", required: false }] }], disabledField: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabledField", required: false }] }], selectedValues: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedValues", required: false }] }], itemHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemHeight", required: false }] }], maxVisibleItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxVisibleItems", required: false }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], itemTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemTemplate", required: false }] }], emptyTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyTemplate", required: false }] }], loadingTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadingTemplate", required: false }] }], alternate: [{ type: i0.Input, args: [{ isSignal: true, alias: "alternate", required: false }] }], itemClick: [{ type: i0.Output, args: ["itemClick"] }], tableRef: [{ type: i0.ViewChild, args: [i0.forwardRef(() => AXDataTableComponent), { isSignal: true }] }] } });
849
+ }], propDecorators: { dataSource: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataSource", required: true }] }], valueField: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueField", required: false }] }], textField: [{ type: i0.Input, args: [{ isSignal: true, alias: "textField", required: false }] }], disabledField: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabledField", required: false }] }], parentField: [{ type: i0.Input, args: [{ isSignal: true, alias: "parentField", required: false }] }], hasChildrenField: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasChildrenField", required: false }] }], selectedValues: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedValues", required: false }] }], itemHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemHeight", required: false }] }], maxVisibleItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxVisibleItems", required: false }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], itemTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemTemplate", required: false }] }], emptyTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyTemplate", required: false }] }], loadingTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadingTemplate", required: false }] }], alternate: [{ type: i0.Input, args: [{ isSignal: true, alias: "alternate", required: false }] }], itemClick: [{ type: i0.Output, args: ["itemClick"] }] } });
920
850
 
921
851
  /**
922
- * Internal mini component for the `multi-column-tree` lookup mode:
923
- * a multi-column `ax-data-table` with a drop-down tree inside it.
924
- * Rows expand/collapse via the data-table hierarchy (`parentField` / nested `children`).
852
+ * Internal mini component for the `multi-column` lookup mode:
853
+ * renders selectable rows in an `ax-data-table` grid and loads further
854
+ * pages as the user scrolls (no pager UI).
925
855
  *
926
856
  * @category Components
927
857
  */
928
- class AXLookupMultiColumnTreeComponent {
858
+ class AXLookupMultiColumnComponent {
859
+ /** Accumulated rows fed to the table (grows as pages are scrolled in). */
860
+ #loadedItems;
861
+ /** Total matching rows from the source (used to size the popup). */
862
+ #totalCount;
863
+ #host;
929
864
  constructor() {
930
865
  /**
931
- * The datasource providing hierarchical rows (roots on load, children via `parentField`).
866
+ * The datasource providing the grid rows (paged, remote-capable).
867
+ * Pages are fetched on demand as the user scrolls the table body.
932
868
  */
933
869
  this.dataSource = input.required(/* @ts-ignore */
934
870
  ...(ngDevMode ? [{ debugName: "dataSource" }] : /* istanbul ignore next */ []));
935
871
  /**
936
- * The item property used as the selection value (also the data-table row key).
872
+ * The item property used as the selection value.
937
873
  */
938
874
  this.valueField = input('id', /* @ts-ignore */
939
875
  ...(ngDevMode ? [{ debugName: "valueField" }] : /* istanbul ignore next */ []));
940
876
  /**
941
- * The item property used as the display text. The matching column shows the expand control
942
- * unless another column sets `expandHandler`.
877
+ * The item property used as the display text.
943
878
  */
944
879
  this.textField = input('text', /* @ts-ignore */
945
880
  ...(ngDevMode ? [{ debugName: "textField" }] : /* istanbul ignore next */ []));
@@ -948,16 +883,6 @@ class AXLookupMultiColumnTreeComponent {
948
883
  */
949
884
  this.disabledField = input('disabled', /* @ts-ignore */
950
885
  ...(ngDevMode ? [{ debugName: "disabledField" }] : /* istanbul ignore next */ []));
951
- /**
952
- * Field used by the data table to fetch children (`AXDataSource` filter on expand).
953
- */
954
- this.parentField = input('parentId', /* @ts-ignore */
955
- ...(ngDevMode ? [{ debugName: "parentField" }] : /* istanbul ignore next */ []));
956
- /**
957
- * Field used by the data table to decide whether a row can expand.
958
- */
959
- this.hasChildrenField = input('hasChild', /* @ts-ignore */
960
- ...(ngDevMode ? [{ debugName: "hasChildrenField" }] : /* istanbul ignore next */ []));
961
886
  /**
962
887
  * Currently selected values (always an array, even for single selection).
963
888
  */
@@ -1000,13 +925,40 @@ class AXLookupMultiColumnTreeComponent {
1000
925
  this.alternate = input(false, /* @ts-ignore */
1001
926
  ...(ngDevMode ? [{ debugName: "alternate" }] : /* istanbul ignore next */ []));
1002
927
  /**
1003
- * Emitted when the user picks a row (not when expanding/collapsing).
928
+ * Emitted when the user picks a row.
1004
929
  */
1005
930
  this.itemClick = output();
931
+ this.tableRef = viewChild(AXDataTableComponent, /* @ts-ignore */
932
+ ...(ngDevMode ? [{ debugName: "tableRef" }] : /* istanbul ignore next */ []));
933
+ /** Accumulated rows fed to the table (grows as pages are scrolled in). */
934
+ this.#loadedItems = [];
935
+ /** Total matching rows from the source (used to size the popup). */
936
+ this.#totalCount = signal(0, /* @ts-ignore */
937
+ ...(ngDevMode ? [{ debugName: "#totalCount" }] : /* istanbul ignore next */ []));
1006
938
  /**
1007
- * Header (~40px) + up to `maxVisibleItems` body rows. Expanded children scroll inside.
939
+ * Local table source: one page of whatever has been scrolled in so far.
940
+ * Kept as a stable instance so the table host is not recreated on each page load.
941
+ * `pageSize` grows with `#loadedItems` — never seed with a huge empty array (data-table
942
+ * initializes `displayedRows` as `new Array(pageSize)`, which caused NG0955-level jank).
1008
943
  */
1009
- this.tableHeight = computed(() => 40 + this.maxVisibleItems() * this.itemHeight(), /* @ts-ignore */
944
+ this.tableDataSource = new AXDataSource({
945
+ pageSize: 1,
946
+ key: 'id',
947
+ load: async () => ({
948
+ items: this.#loadedItems,
949
+ total: this.#loadedItems.length,
950
+ }),
951
+ });
952
+ /**
953
+ * Header (~40px) + body rows. Grows with results up to `maxVisibleItems`,
954
+ * then the table body scrolls for the rest.
955
+ */
956
+ this.tableHeight = computed(() => {
957
+ const header = 40;
958
+ const total = this.#totalCount();
959
+ const rows = total > 0 ? total : Math.min(3, this.maxVisibleItems());
960
+ return header + Math.min(rows, this.maxVisibleItems()) * this.itemHeight();
961
+ }, /* @ts-ignore */
1010
962
  ...(ngDevMode ? [{ debugName: "tableHeight" }] : /* istanbul ignore next */ []));
1011
963
  this.loadingConfig = computed(() => ({
1012
964
  enabled: true,
@@ -1014,6 +966,7 @@ class AXLookupMultiColumnTreeComponent {
1014
966
  loadingTemplate: this.loadingTemplate(),
1015
967
  }), /* @ts-ignore */
1016
968
  ...(ngDevMode ? [{ debugName: "loadingConfig" }] : /* istanbul ignore next */ []));
969
+ this.#host = inject((ElementRef));
1017
970
  this.resolveRowCssClass = (row) => {
1018
971
  const item = row;
1019
972
  if (!item) {
@@ -1028,6 +981,44 @@ class AXLookupMultiColumnTreeComponent {
1028
981
  }
1029
982
  return classes.join(' ');
1030
983
  };
984
+ effect((onCleanup) => {
985
+ const source = this.dataSource();
986
+ this.tableDataSource.config.key = source.config.key || this.valueField();
987
+ untracked(() => {
988
+ this.#applyLoadedItems(source.cachedItems, source.totalCount);
989
+ });
990
+ const changedSub = source.onChanged.subscribe((event) => {
991
+ this.#applyLoadedItems(event.cachedItems, event.totalCount);
992
+ });
993
+ // Fetch page 0 only when the source has never loaded. Reopen remounts this
994
+ // view; setPage(0) is a no-op on a warm source and must not force a new API call.
995
+ queueMicrotask(() => {
996
+ if (!source.cachedItems.some((item) => item != null)) {
997
+ source.setPage(0);
998
+ }
999
+ });
1000
+ onCleanup(() => changedSub.unsubscribe());
1001
+ });
1002
+ effect((onCleanup) => {
1003
+ // Rebind when the table instance appears / source changes.
1004
+ this.tableRef();
1005
+ this.dataSource();
1006
+ let scrollSub;
1007
+ const timer = setTimeout(() => {
1008
+ const wrapper = this.tableRef()?.getHostElement()?.querySelector('.ax-data-table-wrapper') ??
1009
+ this.#host.nativeElement.querySelector('.ax-data-table-wrapper');
1010
+ if (!wrapper) {
1011
+ return;
1012
+ }
1013
+ scrollSub = fromEvent(wrapper, 'scroll')
1014
+ .pipe(auditTime(80))
1015
+ .subscribe(() => this.#tryLoadMore(wrapper));
1016
+ }, 0);
1017
+ onCleanup(() => {
1018
+ clearTimeout(timer);
1019
+ scrollSub?.unsubscribe();
1020
+ });
1021
+ });
1031
1022
  }
1032
1023
  /** Stable unique key for `@for` — avoids NG0955 when `field` is missing or duplicated. */
1033
1024
  trackColumnKey(index, column) {
@@ -1040,15 +1031,14 @@ class AXLookupMultiColumnTreeComponent {
1040
1031
  }
1041
1032
  return width;
1042
1033
  }
1043
- isExpandColumn(index, column) {
1044
- if (column.expandHandler === true) {
1045
- return true;
1046
- }
1047
- if (this.columns().some((item) => item.expandHandler === true)) {
1048
- return false;
1049
- }
1050
- const textIndex = this.columns().findIndex((item) => item.field === this.textField());
1051
- return (textIndex === -1 ? 0 : textIndex) === index;
1034
+ #applyLoadedItems(cachedItems, totalCount) {
1035
+ this.#loadedItems = cachedItems.filter((item) => item != null);
1036
+ this.#totalCount.set(totalCount);
1037
+ this.#syncTablePageSize();
1038
+ this.tableDataSource.refresh({ reset: true });
1039
+ }
1040
+ #syncTablePageSize() {
1041
+ this.tableDataSource.config.pageSize = Math.max(this.#loadedItems.length, 1);
1052
1042
  }
1053
1043
  handleRowClick(event) {
1054
1044
  this.handleItemClick(event.data);
@@ -1071,17 +1061,31 @@ class AXLookupMultiColumnTreeComponent {
1071
1061
  isDisabled(item) {
1072
1062
  return Boolean(item?.[this.disabledField()]);
1073
1063
  }
1064
+ #tryLoadMore(wrapper) {
1065
+ const nearBottom = wrapper.scrollTop + wrapper.clientHeight >= wrapper.scrollHeight - 48;
1066
+ if (!nearBottom) {
1067
+ return;
1068
+ }
1069
+ const source = this.dataSource();
1070
+ if (source.isLoading || source.totalCount === 0) {
1071
+ return;
1072
+ }
1073
+ const loadedCount = source.cachedItems.filter((item) => item != null).length;
1074
+ if (loadedCount >= source.totalCount) {
1075
+ return;
1076
+ }
1077
+ const nextPage = Math.floor(loadedCount / source.pageSize);
1078
+ source.setPage(nextPage);
1079
+ }
1074
1080
  getValue(item) {
1075
1081
  return item?.[this.valueField()];
1076
1082
  }
1077
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXLookupMultiColumnTreeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1078
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXLookupMultiColumnTreeComponent, isStandalone: true, selector: "ax-lookup-multi-column-tree", inputs: { dataSource: { classPropertyName: "dataSource", publicName: "dataSource", isSignal: true, isRequired: true, transformFunction: null }, valueField: { classPropertyName: "valueField", publicName: "valueField", isSignal: true, isRequired: false, transformFunction: null }, textField: { classPropertyName: "textField", publicName: "textField", isSignal: true, isRequired: false, transformFunction: null }, disabledField: { classPropertyName: "disabledField", publicName: "disabledField", isSignal: true, isRequired: false, transformFunction: null }, parentField: { classPropertyName: "parentField", publicName: "parentField", isSignal: true, isRequired: false, transformFunction: null }, hasChildrenField: { classPropertyName: "hasChildrenField", publicName: "hasChildrenField", isSignal: true, isRequired: false, transformFunction: null }, selectedValues: { classPropertyName: "selectedValues", publicName: "selectedValues", isSignal: true, isRequired: false, transformFunction: null }, itemHeight: { classPropertyName: "itemHeight", publicName: "itemHeight", isSignal: true, isRequired: false, transformFunction: null }, maxVisibleItems: { classPropertyName: "maxVisibleItems", publicName: "maxVisibleItems", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, itemTemplate: { classPropertyName: "itemTemplate", publicName: "itemTemplate", isSignal: true, isRequired: false, transformFunction: null }, emptyTemplate: { classPropertyName: "emptyTemplate", publicName: "emptyTemplate", isSignal: true, isRequired: false, transformFunction: null }, loadingTemplate: { classPropertyName: "loadingTemplate", publicName: "loadingTemplate", isSignal: true, isRequired: false, transformFunction: null }, alternate: { classPropertyName: "alternate", publicName: "alternate", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { itemClick: "itemClick" }, host: { properties: { "style.height.px": "tableHeight()" }, classAttribute: "ax-lookup-multi-column ax-lookup-multi-column-tree" }, ngImport: i0, template: `
1083
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXLookupMultiColumnComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1084
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXLookupMultiColumnComponent, isStandalone: true, selector: "ax-lookup-multi-column", inputs: { dataSource: { classPropertyName: "dataSource", publicName: "dataSource", isSignal: true, isRequired: true, transformFunction: null }, valueField: { classPropertyName: "valueField", publicName: "valueField", isSignal: true, isRequired: false, transformFunction: null }, textField: { classPropertyName: "textField", publicName: "textField", isSignal: true, isRequired: false, transformFunction: null }, disabledField: { classPropertyName: "disabledField", publicName: "disabledField", isSignal: true, isRequired: false, transformFunction: null }, selectedValues: { classPropertyName: "selectedValues", publicName: "selectedValues", isSignal: true, isRequired: false, transformFunction: null }, itemHeight: { classPropertyName: "itemHeight", publicName: "itemHeight", isSignal: true, isRequired: false, transformFunction: null }, maxVisibleItems: { classPropertyName: "maxVisibleItems", publicName: "maxVisibleItems", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, itemTemplate: { classPropertyName: "itemTemplate", publicName: "itemTemplate", isSignal: true, isRequired: false, transformFunction: null }, emptyTemplate: { classPropertyName: "emptyTemplate", publicName: "emptyTemplate", isSignal: true, isRequired: false, transformFunction: null }, loadingTemplate: { classPropertyName: "loadingTemplate", publicName: "loadingTemplate", isSignal: true, isRequired: false, transformFunction: null }, alternate: { classPropertyName: "alternate", publicName: "alternate", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { itemClick: "itemClick" }, host: { properties: { "style.height.px": "tableHeight()" }, classAttribute: "ax-lookup-multi-column" }, viewQueries: [{ propertyName: "tableRef", first: true, predicate: AXDataTableComponent, descendants: true, isSignal: true }], ngImport: i0, template: `
1079
1085
  <ax-data-table
1080
1086
  class="ax-lookup-multi-column-table"
1081
1087
  look="minimal"
1082
- [dataSource]="dataSource()"
1083
- [parentField]="parentField()"
1084
- [hasChildrenField]="hasChildrenField()"
1088
+ [dataSource]="tableDataSource"
1085
1089
  [itemHeight]="itemHeight()"
1086
1090
  [alternative]="alternate()"
1087
1091
  [emptyTemplate]="emptyTemplate()"
@@ -1100,7 +1104,6 @@ class AXLookupMultiColumnTreeComponent {
1100
1104
  [dataField]="column.field"
1101
1105
  [caption]="column.title"
1102
1106
  [width]="resolveColumnWidth(column)"
1103
- [expandHandler]="isExpandColumn($index, column)"
1104
1107
  [allowSorting]="false"
1105
1108
  [allowResizing]="false"
1106
1109
  />
@@ -1127,24 +1130,22 @@ class AXLookupMultiColumnTreeComponent {
1127
1130
  </ng-template>
1128
1131
  `, isInline: true, dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: AXDataTableComponent, selector: "ax-data-table", inputs: ["dataSource", "selectedRows", "parentField", "hasChildrenField", "rowDetailsTemplate", "rowCssClass", "cellCssClass", "highlightText", "look", "rowTemplate", "emptyTemplate", "noDataTemplate", "alternative", "showHeader", "fixedHeader", "showFooter", "fixedFooter", "itemHeight", "allowReordering", "paging", "fetchDataMode", "loading", "focusedRow"], outputs: ["selectedRowsChange", "focusedRowChange", "onRowClick", "onRowDbClick", "onColumnsOrderChanged", "onColumnSizeChanged", "onPageChanged"] }, { kind: "component", type: AXDataTableTextColumnComponent, selector: "ax-text-column", inputs: ["width", "caption", "allowSorting", "allowResizing", "fixed", "hasTitle", "customExpandIcon", "customCollapseIcon", "dataField", "expandHandler", "wrapText", "cellTemplate", "footerTemplate", "headerTemplate", "format", "formatOptions"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
1129
1132
  }
1130
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXLookupMultiColumnTreeComponent, decorators: [{
1133
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXLookupMultiColumnComponent, decorators: [{
1131
1134
  type: Component,
1132
1135
  args: [{
1133
- selector: 'ax-lookup-multi-column-tree',
1136
+ selector: 'ax-lookup-multi-column',
1134
1137
  changeDetection: ChangeDetectionStrategy.OnPush,
1135
1138
  encapsulation: ViewEncapsulation.None,
1136
1139
  imports: [NgTemplateOutlet, AXDataTableComponent, AXDataTableTextColumnComponent],
1137
1140
  host: {
1138
- class: 'ax-lookup-multi-column ax-lookup-multi-column-tree',
1141
+ class: 'ax-lookup-multi-column',
1139
1142
  '[style.height.px]': 'tableHeight()',
1140
1143
  },
1141
1144
  template: `
1142
1145
  <ax-data-table
1143
1146
  class="ax-lookup-multi-column-table"
1144
1147
  look="minimal"
1145
- [dataSource]="dataSource()"
1146
- [parentField]="parentField()"
1147
- [hasChildrenField]="hasChildrenField()"
1148
+ [dataSource]="tableDataSource"
1148
1149
  [itemHeight]="itemHeight()"
1149
1150
  [alternative]="alternate()"
1150
1151
  [emptyTemplate]="emptyTemplate()"
@@ -1163,7 +1164,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
1163
1164
  [dataField]="column.field"
1164
1165
  [caption]="column.title"
1165
1166
  [width]="resolveColumnWidth(column)"
1166
- [expandHandler]="isExpandColumn($index, column)"
1167
1167
  [allowSorting]="false"
1168
1168
  [allowResizing]="false"
1169
1169
  />
@@ -1190,7 +1190,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
1190
1190
  </ng-template>
1191
1191
  `,
1192
1192
  }]
1193
- }], propDecorators: { dataSource: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataSource", required: true }] }], valueField: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueField", required: false }] }], textField: [{ type: i0.Input, args: [{ isSignal: true, alias: "textField", required: false }] }], disabledField: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabledField", required: false }] }], parentField: [{ type: i0.Input, args: [{ isSignal: true, alias: "parentField", required: false }] }], hasChildrenField: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasChildrenField", required: false }] }], selectedValues: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedValues", required: false }] }], itemHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemHeight", required: false }] }], maxVisibleItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxVisibleItems", required: false }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], itemTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemTemplate", required: false }] }], emptyTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyTemplate", required: false }] }], loadingTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadingTemplate", required: false }] }], alternate: [{ type: i0.Input, args: [{ isSignal: true, alias: "alternate", required: false }] }], itemClick: [{ type: i0.Output, args: ["itemClick"] }] } });
1193
+ }], ctorParameters: () => [], propDecorators: { dataSource: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataSource", required: true }] }], valueField: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueField", required: false }] }], textField: [{ type: i0.Input, args: [{ isSignal: true, alias: "textField", required: false }] }], disabledField: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabledField", required: false }] }], selectedValues: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedValues", required: false }] }], itemHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemHeight", required: false }] }], maxVisibleItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxVisibleItems", required: false }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], itemTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemTemplate", required: false }] }], emptyTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyTemplate", required: false }] }], loadingTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadingTemplate", required: false }] }], alternate: [{ type: i0.Input, args: [{ isSignal: true, alias: "alternate", required: false }] }], itemClick: [{ type: i0.Output, args: ["itemClick"] }], tableRef: [{ type: i0.ViewChild, args: [i0.forwardRef(() => AXDataTableComponent), { isSignal: true }] }] } });
1194
1194
 
1195
1195
  /**
1196
1196
  * Internal mini component for the `multi-select-tree` lookup mode:
@@ -1466,6 +1466,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
1466
1466
  class AXLookupComponent extends NXValueComponent {
1467
1467
  #platform;
1468
1468
  #destroyRef;
1469
+ /** Reused so closing/reopening the popup does not create a new AXDataSource (which would miss cache). */
1470
+ #multiColumnTreeTableSource;
1471
+ #multiColumnTreeTableKey;
1472
+ #multiColumnTreeInput;
1469
1473
  /**
1470
1474
  * Tree callback results keyed by parent id. Survives popup remount so
1471
1475
  * drop-down-tree / multi-select-tree do not refetch on every open.
@@ -1713,15 +1717,24 @@ class AXLookupComponent extends NXValueComponent {
1713
1717
  this.resolvedDataSource = computed(() => {
1714
1718
  const tree = this.treeDataSource();
1715
1719
  if (this.mode() === 'multi-column-tree' && tree) {
1720
+ const key = `${this.valueField()}|${this.textField()}|${this.treeParentField()}|${this.hasChildrenField()}`;
1721
+ if (this.#multiColumnTreeTableSource &&
1722
+ this.#multiColumnTreeInput === tree &&
1723
+ this.#multiColumnTreeTableKey === key) {
1724
+ return this.#multiColumnTreeTableSource;
1725
+ }
1716
1726
  const cachedTree = Array.isArray(tree)
1717
1727
  ? tree
1718
1728
  : (nodeId) => this.#loadCachedTreeNodes(tree, nodeId);
1719
- return createLookupTreeTableDataSource(cachedTree, {
1729
+ this.#multiColumnTreeInput = tree;
1730
+ this.#multiColumnTreeTableKey = key;
1731
+ this.#multiColumnTreeTableSource = createLookupTreeTableDataSource(cachedTree, {
1720
1732
  valueField: this.valueField(),
1721
1733
  textField: this.textField(),
1722
1734
  parentField: this.treeParentField(),
1723
1735
  hasChildrenField: this.hasChildrenField(),
1724
1736
  });
1737
+ return this.#multiColumnTreeTableSource;
1725
1738
  }
1726
1739
  const source = this.dataSource();
1727
1740
  if (!source) {