@myrmidon/cadmus-ui 15.0.0 → 15.1.0

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.
@@ -1,24 +1,32 @@
1
1
  import * as i0 from '@angular/core';
2
- import { input, output, Component, signal, effect, ViewChild, Injectable, Inject, model, computed, inject } from '@angular/core';
3
- import { MatButton, MatIconButton, MatAnchor } from '@angular/material/button';
4
- import { MatIcon } from '@angular/material/icon';
2
+ import { input, output, Component, signal, effect, ViewChild, Injectable, Inject, inject, model, computed } from '@angular/core';
3
+ import * as i2$1 from '@angular/material/button';
4
+ import { MatButton, MatIconButton, MatButtonModule } from '@angular/material/button';
5
+ import * as i5 from '@angular/material/icon';
6
+ import { MatIcon, MatIconModule } from '@angular/material/icon';
5
7
  import { SafeHtmlPipe, deepCopy } from '@myrmidon/ngx-tools';
6
8
  import * as i1 from '@myrmidon/cadmus-core';
7
- import { MatTooltip } from '@angular/material/tooltip';
9
+ import * as i6 from '@angular/material/tooltip';
10
+ import { MatTooltip, MatTooltipModule } from '@angular/material/tooltip';
8
11
  import * as i1$1 from '@angular/forms';
9
- import { FormsModule, ReactiveFormsModule, PristineChangeEvent } from '@angular/forms';
10
- import { MatCheckbox } from '@angular/material/checkbox';
12
+ import { FormsModule, ReactiveFormsModule, FormBuilder, FormControl, FormGroup, PristineChangeEvent } from '@angular/forms';
13
+ import * as i3 from '@angular/material/checkbox';
14
+ import { MatCheckbox, MatCheckboxModule } from '@angular/material/checkbox';
11
15
  import * as i2 from '@myrmidon/ngx-mat-tools';
12
- import { AsyncPipe, NgClass } from '@angular/common';
16
+ import { AsyncPipe } from '@angular/common';
13
17
  import { of, BehaviorSubject, map as map$1, Subject } from 'rxjs';
14
18
  import { debounceTime, distinctUntilChanged, switchMap, map, take } from 'rxjs/operators';
15
19
  import { MatAutocomplete, MatAutocompleteTrigger } from '@angular/material/autocomplete';
16
20
  import { MatOption } from '@angular/material/core';
17
- import { MatFormField, MatLabel, MatSuffix } from '@angular/material/form-field';
18
- import { MatInput } from '@angular/material/input';
21
+ import * as i4 from '@angular/material/form-field';
22
+ import { MatFormField, MatFormFieldModule } from '@angular/material/form-field';
23
+ import * as i4$1 from '@angular/material/input';
24
+ import { MatInput, MatInputModule } from '@angular/material/input';
19
25
  import * as i1$2 from '@myrmidon/cadmus-api';
20
- import { NestedTreeControl } from '@angular/cdk/tree';
21
- import { MatTreeNestedDataSource, MatTree, MatTreeNodeDef, MatTreeNode, MatTreeNodeToggle, MatNestedTreeNode, MatTreeNodeOutlet } from '@angular/material/tree';
26
+ import { MatDialogRef, MAT_DIALOG_DATA, MatDialog } from '@angular/material/dialog';
27
+ import * as i7 from '@angular/material/progress-bar';
28
+ import { MatProgressBarModule } from '@angular/material/progress-bar';
29
+ import { PagedTreeStore, BrowserTreeNodeComponent } from '@myrmidon/paged-data-browsers';
22
30
  import { AppRepository } from '@myrmidon/cadmus-state';
23
31
  import * as i1$3 from '@myrmidon/auth-jwt-login';
24
32
 
@@ -427,7 +435,7 @@ class LayerHintsComponent {
427
435
  });
428
436
  }
429
437
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: LayerHintsComponent, deps: [{ token: i1$1.FormBuilder }, { token: i2.DialogService }], target: i0.ɵɵFactoryTarget.Component });
430
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.0", type: LayerHintsComponent, isStandalone: true, selector: "cadmus-layer-hints", inputs: { hints: { classPropertyName: "hints", publicName: "hints", isSignal: true, isRequired: false, transformFunction: null }, targetLocation: { classPropertyName: "targetLocation", publicName: "targetLocation", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { requestEdit: "requestEdit", requestDelete: "requestDelete", requestMove: "requestMove", requestPatch: "requestPatch" }, ngImport: i0, template: "@if (hints().length) {\r\n<form [formGroup]=\"form\" (submit)=\"emitRequestPatch()\">\r\n <table class=\"patches\">\r\n <thead>\r\n <tr>\r\n <th></th>\r\n <th>location</th>\r\n <th>description</th>\r\n <th>patch</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (hint of hints(); track hint; let i = $index) {\r\n <tr>\r\n @if (!readonly()) {\r\n <td>\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Edit this fragment\"\r\n [disabled]=\"disabled()\"\r\n >\r\n <mat-icon>edit</mat-icon>\r\n </button>\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Move this fragment to {{ targetLocation() }}\"\r\n [disabled]=\"\r\n disabled() ||\r\n !targetLocation() ||\r\n targetLocation() === hint.location\r\n \"\r\n (click)=\"emitRequestMove(hint)\"\r\n >\r\n <mat-icon>arrow_forward</mat-icon>\r\n </button>\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Delete this fragment\"\r\n [disabled]=\"disabled()\"\r\n (click)=\"emitRequestDelete(hint)\"\r\n >\r\n <mat-icon class=\"mat-warn\">delete</mat-icon>\r\n </button>\r\n </td>\r\n }\r\n <td>{{ hint.location }}</td>\r\n <td>{{ hint.description }}</td>\r\n <td>\r\n @if (hint.patchOperation) {\r\n <mat-checkbox\r\n matTooltip=\"Apply this patch\"\r\n [disabled]=\"disabled() || false\"\r\n [formControl]=\"checks.at(i)\"\r\n >\r\n {{ hint.patchOperation }}\r\n </mat-checkbox>\r\n }\r\n </td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n @if (targetLocation()) {\r\n <div>move target: {{ targetLocation() }}</div>\r\n }\r\n <button\r\n type=\"submit\"\r\n mat-flat-button\r\n [disabled]=\"readonly() || disabled() || form.invalid\"\r\n >\r\n apply patches\r\n </button>\r\n</form>\r\n}\r\n", styles: ["table.patches th{font-weight:400;color:#bdbdbd}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "directive", type: MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "component", type: MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }] });
438
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.0", type: LayerHintsComponent, isStandalone: true, selector: "cadmus-layer-hints", inputs: { hints: { classPropertyName: "hints", publicName: "hints", isSignal: true, isRequired: false, transformFunction: null }, targetLocation: { classPropertyName: "targetLocation", publicName: "targetLocation", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { requestEdit: "requestEdit", requestDelete: "requestDelete", requestMove: "requestMove", requestPatch: "requestPatch" }, ngImport: i0, template: "@if (hints().length) {\r\n<form [formGroup]=\"form\" (submit)=\"emitRequestPatch()\">\r\n <table class=\"patches\">\r\n <thead>\r\n <tr>\r\n <th></th>\r\n <th>location</th>\r\n <th>description</th>\r\n <th>patch</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (hint of hints(); track hint; let i = $index) {\r\n <tr>\r\n @if (!readonly()) {\r\n <td>\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Edit this fragment\"\r\n [disabled]=\"disabled()\"\r\n >\r\n <mat-icon>edit</mat-icon>\r\n </button>\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Move this fragment to {{ targetLocation() }}\"\r\n [disabled]=\"\r\n disabled() ||\r\n !targetLocation() ||\r\n targetLocation() === hint.location\r\n \"\r\n (click)=\"emitRequestMove(hint)\"\r\n >\r\n <mat-icon>arrow_forward</mat-icon>\r\n </button>\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Delete this fragment\"\r\n [disabled]=\"disabled()\"\r\n (click)=\"emitRequestDelete(hint)\"\r\n >\r\n <mat-icon class=\"mat-warn\">delete</mat-icon>\r\n </button>\r\n </td>\r\n }\r\n <td>{{ hint.location }}</td>\r\n <td>{{ hint.description }}</td>\r\n <td>\r\n @if (hint.patchOperation) {\r\n <mat-checkbox\r\n matTooltip=\"Apply this patch\"\r\n [formControl]=\"checks.at(i)\"\r\n >\r\n {{ hint.patchOperation }}\r\n </mat-checkbox>\r\n }\r\n </td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n @if (targetLocation()) {\r\n <div>move target: {{ targetLocation() }}</div>\r\n }\r\n <button\r\n type=\"submit\"\r\n mat-flat-button\r\n [disabled]=\"readonly() || disabled() || form.invalid\"\r\n >\r\n apply patches\r\n </button>\r\n</form>\r\n}\r\n", styles: ["table.patches th{font-weight:400;color:#bdbdbd}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "directive", type: MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "component", type: MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }] });
431
439
  }
432
440
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: LayerHintsComponent, decorators: [{
433
441
  type: Component,
@@ -439,7 +447,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.0", ngImpor
439
447
  MatIcon,
440
448
  MatCheckbox,
441
449
  MatButton,
442
- ], template: "@if (hints().length) {\r\n<form [formGroup]=\"form\" (submit)=\"emitRequestPatch()\">\r\n <table class=\"patches\">\r\n <thead>\r\n <tr>\r\n <th></th>\r\n <th>location</th>\r\n <th>description</th>\r\n <th>patch</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (hint of hints(); track hint; let i = $index) {\r\n <tr>\r\n @if (!readonly()) {\r\n <td>\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Edit this fragment\"\r\n [disabled]=\"disabled()\"\r\n >\r\n <mat-icon>edit</mat-icon>\r\n </button>\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Move this fragment to {{ targetLocation() }}\"\r\n [disabled]=\"\r\n disabled() ||\r\n !targetLocation() ||\r\n targetLocation() === hint.location\r\n \"\r\n (click)=\"emitRequestMove(hint)\"\r\n >\r\n <mat-icon>arrow_forward</mat-icon>\r\n </button>\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Delete this fragment\"\r\n [disabled]=\"disabled()\"\r\n (click)=\"emitRequestDelete(hint)\"\r\n >\r\n <mat-icon class=\"mat-warn\">delete</mat-icon>\r\n </button>\r\n </td>\r\n }\r\n <td>{{ hint.location }}</td>\r\n <td>{{ hint.description }}</td>\r\n <td>\r\n @if (hint.patchOperation) {\r\n <mat-checkbox\r\n matTooltip=\"Apply this patch\"\r\n [disabled]=\"disabled() || false\"\r\n [formControl]=\"checks.at(i)\"\r\n >\r\n {{ hint.patchOperation }}\r\n </mat-checkbox>\r\n }\r\n </td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n @if (targetLocation()) {\r\n <div>move target: {{ targetLocation() }}</div>\r\n }\r\n <button\r\n type=\"submit\"\r\n mat-flat-button\r\n [disabled]=\"readonly() || disabled() || form.invalid\"\r\n >\r\n apply patches\r\n </button>\r\n</form>\r\n}\r\n", styles: ["table.patches th{font-weight:400;color:#bdbdbd}\n"] }]
450
+ ], template: "@if (hints().length) {\r\n<form [formGroup]=\"form\" (submit)=\"emitRequestPatch()\">\r\n <table class=\"patches\">\r\n <thead>\r\n <tr>\r\n <th></th>\r\n <th>location</th>\r\n <th>description</th>\r\n <th>patch</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (hint of hints(); track hint; let i = $index) {\r\n <tr>\r\n @if (!readonly()) {\r\n <td>\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Edit this fragment\"\r\n [disabled]=\"disabled()\"\r\n >\r\n <mat-icon>edit</mat-icon>\r\n </button>\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Move this fragment to {{ targetLocation() }}\"\r\n [disabled]=\"\r\n disabled() ||\r\n !targetLocation() ||\r\n targetLocation() === hint.location\r\n \"\r\n (click)=\"emitRequestMove(hint)\"\r\n >\r\n <mat-icon>arrow_forward</mat-icon>\r\n </button>\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Delete this fragment\"\r\n [disabled]=\"disabled()\"\r\n (click)=\"emitRequestDelete(hint)\"\r\n >\r\n <mat-icon class=\"mat-warn\">delete</mat-icon>\r\n </button>\r\n </td>\r\n }\r\n <td>{{ hint.location }}</td>\r\n <td>{{ hint.description }}</td>\r\n <td>\r\n @if (hint.patchOperation) {\r\n <mat-checkbox\r\n matTooltip=\"Apply this patch\"\r\n [formControl]=\"checks.at(i)\"\r\n >\r\n {{ hint.patchOperation }}\r\n </mat-checkbox>\r\n }\r\n </td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n @if (targetLocation()) {\r\n <div>move target: {{ targetLocation() }}</div>\r\n }\r\n <button\r\n type=\"submit\"\r\n mat-flat-button\r\n [disabled]=\"readonly() || disabled() || form.invalid\"\r\n >\r\n apply patches\r\n </button>\r\n</form>\r\n}\r\n", styles: ["table.patches th{font-weight:400;color:#bdbdbd}\n"] }]
443
451
  }], ctorParameters: () => [{ type: i1$1.FormBuilder }, { type: i2.DialogService }] });
444
452
 
445
453
  /**
@@ -589,6 +597,387 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.0", ngImpor
589
597
  args: ['indexLookupDefinitions']
590
598
  }] }] });
591
599
 
600
+ /**
601
+ * A label rendering function which removes from a label
602
+ * all the characters past the last colon, trimming the result.
603
+ * This is a typical rendering when dealing with hierarchical
604
+ * thesaurus entries, e.g. "furniture: table: color", where
605
+ * we can shorten the label to just "color", as "furniture"
606
+ * and "table" are its ancestors.
607
+ */
608
+ const renderLabelFromLastColon = (label) => {
609
+ if (!label) {
610
+ return label;
611
+ }
612
+ const i = label.lastIndexOf(':');
613
+ return i > -1 && i + 1 < label.length ? label.substring(i + 1).trim() : label;
614
+ };
615
+ /**
616
+ * A static paged tree store service for thesaurus entries.
617
+ * This builds the tree nodes from the thesaurus entries, assuming
618
+ * that entry IDs are hierarchical and separated by dots (.).
619
+ */
620
+ class StaticThesPagedTreeStoreService {
621
+ _renderLabel;
622
+ _nodes = [];
623
+ _built = false;
624
+ _entries;
625
+ constructor(entries, _renderLabel) {
626
+ this._renderLabel = _renderLabel;
627
+ // assign a number to each entry for stable IDs
628
+ this._entries = entries.map((entry, index) => ({
629
+ ...entry,
630
+ n: index + 1,
631
+ }));
632
+ }
633
+ ensureNodes() {
634
+ // lazily build the nodes only once
635
+ if (this._built) {
636
+ return;
637
+ }
638
+ // map where key is node.id and value is max x value for that parent
639
+ // (0 is used for the virtual root)
640
+ const xMap = new Map();
641
+ for (const entry of this._entries) {
642
+ const hasDot = entry.id.includes('.');
643
+ const keyParts = entry.id.split('.');
644
+ // create the node
645
+ const node = {
646
+ id: entry.n,
647
+ label: hasDot && this._renderLabel
648
+ ? this._renderLabel(entry.value)
649
+ : entry.value,
650
+ key: entry.id,
651
+ value: entry.value,
652
+ paging: { pageNumber: 0, pageCount: 0, total: 0 },
653
+ y: hasDot ? keyParts.length : 1,
654
+ x: 0, // will be set later
655
+ parentId: undefined, // will be set later
656
+ hasChildren: false, // will be set later
657
+ };
658
+ // if it has a dot, find its parent and set parentId
659
+ if (hasDot) {
660
+ // build the parent key
661
+ const parentKey = keyParts.slice(0, keyParts.length - 1).join('.');
662
+ // find the parent node with that key
663
+ const parentNode = this._nodes.find((n) => n.key === parentKey);
664
+ // if found, set parentId and x in child and hasChildren in parent
665
+ if (parentNode) {
666
+ node.parentId = parentNode.id;
667
+ parentNode.hasChildren = true;
668
+ if (xMap.has(parentNode.id)) {
669
+ xMap.set(parentNode.id, xMap.get(parentNode.id) + 1);
670
+ }
671
+ else {
672
+ xMap.set(parentNode.id, 1);
673
+ }
674
+ node.x = xMap.get(parentNode.id);
675
+ }
676
+ else {
677
+ // parent not found, treat as root
678
+ if (xMap.has(0)) {
679
+ xMap.set(0, xMap.get(0) + 1);
680
+ }
681
+ else {
682
+ xMap.set(0, 1);
683
+ }
684
+ node.x = xMap.get(0);
685
+ }
686
+ }
687
+ else {
688
+ // else it's a root node
689
+ if (xMap.has(0)) {
690
+ xMap.set(0, xMap.get(0) + 1);
691
+ }
692
+ else {
693
+ xMap.set(0, 1);
694
+ }
695
+ node.x = xMap.get(0);
696
+ }
697
+ this._nodes.push(node);
698
+ }
699
+ this._built = true;
700
+ }
701
+ /**
702
+ * Get the specified page of nodes.
703
+ * @param filter The filter.
704
+ * @param pageNumber The page number.
705
+ * @param pageSize The page size.
706
+ * @param hasMockRoot Whether the root node is a mock node. Not used here.
707
+ */
708
+ getNodes(filter, pageNumber, pageSize, hasMockRoot) {
709
+ this.ensureNodes();
710
+ // apply filtering
711
+ let nodes = this._nodes.filter((n) => {
712
+ if (filter.parentId !== undefined && filter.parentId !== null) {
713
+ if (n.parentId !== filter.parentId) {
714
+ return false;
715
+ }
716
+ }
717
+ else {
718
+ if (n.parentId) {
719
+ return false;
720
+ }
721
+ }
722
+ if (filter.label) {
723
+ const filterValue = filter.label.toLowerCase();
724
+ if (!n.label.toLowerCase().includes(filterValue)) {
725
+ return false;
726
+ }
727
+ }
728
+ return true;
729
+ });
730
+ // apply paging
731
+ const startIndex = (pageNumber - 1) * pageSize;
732
+ const endIndex = startIndex + pageSize;
733
+ const pagedNodes = nodes.slice(startIndex, endIndex);
734
+ // page and return
735
+ const paged = nodes.slice((pageNumber - 1) * pageSize, pageNumber * pageSize);
736
+ return of({
737
+ items: paged,
738
+ pageNumber: pageNumber,
739
+ pageSize: pageSize,
740
+ pageCount: Math.ceil(nodes.length / pageSize),
741
+ total: nodes.length,
742
+ });
743
+ }
744
+ }
745
+
746
+ /**
747
+ * A filter to be used for thesaurus paged tree browsers.
748
+ */
749
+ class ThesPagedTreeFilterComponent {
750
+ dialogRef = inject(MatDialogRef, {
751
+ optional: true,
752
+ });
753
+ data = inject(MAT_DIALOG_DATA, { optional: true });
754
+ /**
755
+ * The filter.
756
+ */
757
+ filter = model(...(ngDevMode ? [undefined, { debugName: "filter" }] : []));
758
+ wrapped = signal(false, ...(ngDevMode ? [{ debugName: "wrapped" }] : []));
759
+ label;
760
+ form;
761
+ constructor() {
762
+ const formBuilder = inject(FormBuilder);
763
+ const data = this.data;
764
+ // form
765
+ this.label = formBuilder.control(null);
766
+ this.form = formBuilder.group({
767
+ label: this.label,
768
+ });
769
+ // bind dialog data if any
770
+ if (this.dialogRef) {
771
+ this.wrapped.set(true);
772
+ if (data) {
773
+ this.filter.set(data.filter);
774
+ }
775
+ }
776
+ else {
777
+ this.wrapped.set(false);
778
+ }
779
+ // update form when filter changes
780
+ effect(() => {
781
+ this.updateForm(this.filter());
782
+ });
783
+ }
784
+ ngOnInit() {
785
+ this.updateForm(this.filter());
786
+ }
787
+ updateForm(filter) {
788
+ if (!filter) {
789
+ this.form.reset();
790
+ return;
791
+ }
792
+ this.label.setValue(filter.label ?? null);
793
+ this.form.markAsPristine();
794
+ }
795
+ getFilter() {
796
+ return {
797
+ label: this.label.value ?? undefined,
798
+ };
799
+ }
800
+ reset() {
801
+ this.form.reset();
802
+ this.filter.set(null);
803
+ this.dialogRef?.close(null);
804
+ }
805
+ apply() {
806
+ this.filter.set(this.getFilter());
807
+ this.dialogRef?.close(this.filter());
808
+ }
809
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: ThesPagedTreeFilterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
810
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.0", type: ThesPagedTreeFilterComponent, isStandalone: true, selector: "cadmus-thes-paged-tree-filter", inputs: { filter: { classPropertyName: "filter", publicName: "filter", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { filter: "filterChange" }, ngImport: i0, template: "<form\r\n [formGroup]=\"form\"\r\n [style.margin.px]=\"wrapped() ? 16 : 0\"\r\n (submit)=\"apply()\"\r\n>\r\n <div class=\"form-row\">\r\n <!-- label -->\r\n <mat-form-field>\r\n <input matInput placeholder=\"label\" [formControl]=\"label\" />\r\n </mat-form-field>\r\n </div>\r\n\r\n <div class=\"form-row\">\r\n <!-- buttons -->\r\n <button type=\"button\" mat-icon-button matTooltip=\"Reset\" (click)=\"reset()\">\r\n <mat-icon class=\"mat-warn\">clear</mat-icon>\r\n </button>\r\n <button\r\n type=\"submit\"\r\n mat-icon-button\r\n matTooltip=\"Apply\"\r\n [disabled]=\"form.invalid\"\r\n >\r\n <mat-icon class=\"mat-primary\">check_circle</mat-icon>\r\n </button>\r\n </div>\r\n</form>\r\n", styles: ["form{margin:16px}.form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row *{flex:0 0 auto}.nr{width:6em}fieldset{border:1px solid silver;border-radius:6px;padding:8px 16px;margin:8px 0}legend{color:silver}th{text-align:left;font-weight:400}#tag-selector{width:8em}.tag-chip{border:2px solid transparent;border-radius:6px}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i4$1.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i5.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i6.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }] });
811
+ }
812
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: ThesPagedTreeFilterComponent, decorators: [{
813
+ type: Component,
814
+ args: [{ selector: 'cadmus-thes-paged-tree-filter', imports: [
815
+ ReactiveFormsModule,
816
+ MatButtonModule,
817
+ MatFormFieldModule,
818
+ MatInputModule,
819
+ MatIconModule,
820
+ MatTooltipModule,
821
+ ], template: "<form\r\n [formGroup]=\"form\"\r\n [style.margin.px]=\"wrapped() ? 16 : 0\"\r\n (submit)=\"apply()\"\r\n>\r\n <div class=\"form-row\">\r\n <!-- label -->\r\n <mat-form-field>\r\n <input matInput placeholder=\"label\" [formControl]=\"label\" />\r\n </mat-form-field>\r\n </div>\r\n\r\n <div class=\"form-row\">\r\n <!-- buttons -->\r\n <button type=\"button\" mat-icon-button matTooltip=\"Reset\" (click)=\"reset()\">\r\n <mat-icon class=\"mat-warn\">clear</mat-icon>\r\n </button>\r\n <button\r\n type=\"submit\"\r\n mat-icon-button\r\n matTooltip=\"Apply\"\r\n [disabled]=\"form.invalid\"\r\n >\r\n <mat-icon class=\"mat-primary\">check_circle</mat-icon>\r\n </button>\r\n </div>\r\n</form>\r\n", styles: ["form{margin:16px}.form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row *{flex:0 0 auto}.nr{width:6em}fieldset{border:1px solid silver;border-radius:6px;padding:8px 16px;margin:8px 0}legend{color:silver}th{text-align:left;font-weight:400}#tag-selector{width:8em}.tag-chip{border:2px solid transparent;border-radius:6px}\n"] }]
822
+ }], ctorParameters: () => [] });
823
+
824
+ class ThesPagedTreeBrowserComponent {
825
+ _dialog = inject(MatDialog);
826
+ _sub;
827
+ /**
828
+ * The service to use to load the nodes.
829
+ */
830
+ service = input(new StaticThesPagedTreeStoreService([], renderLabelFromLastColon), ...(ngDevMode ? [{ debugName: "service" }] : []));
831
+ /**
832
+ * Emitted when a node is clicked.
833
+ */
834
+ nodePick = output();
835
+ /**
836
+ * The store instance, built from the service.
837
+ */
838
+ store = computed(() => {
839
+ const service = this.service();
840
+ const store = new PagedTreeStore(service);
841
+ this.nodes$ = store.nodes$;
842
+ this.filter$ = store.filter$;
843
+ return store;
844
+ }, ...(ngDevMode ? [{ debugName: "store" }] : []));
845
+ loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
846
+ debug = new FormControl(false, {
847
+ nonNullable: true,
848
+ });
849
+ hideLoc = new FormControl(false, {
850
+ nonNullable: true,
851
+ });
852
+ hideFilter = new FormControl(false, {
853
+ nonNullable: true,
854
+ });
855
+ filter$;
856
+ nodes$;
857
+ label = new FormControl(null);
858
+ form = new FormGroup({
859
+ label: this.label,
860
+ });
861
+ constructor() {
862
+ const store = this.store();
863
+ this.nodes$ = store.nodes$;
864
+ this.filter$ = store.filter$;
865
+ }
866
+ ngOnInit() {
867
+ if (!this.store().getNodes().length) {
868
+ this.loading.set(true);
869
+ this.store()
870
+ .setFilter({})
871
+ .finally(() => {
872
+ this.loading.set(false);
873
+ console.log('nodes loaded', this.store().getNodes());
874
+ });
875
+ }
876
+ }
877
+ ngOnDestroy() {
878
+ this._sub?.unsubscribe();
879
+ }
880
+ reset() {
881
+ this.loading.set(true);
882
+ this.store()
883
+ .reset()
884
+ .finally(() => {
885
+ this.loading.set(false);
886
+ });
887
+ }
888
+ onToggleExpanded(node) {
889
+ this.loading.set(true);
890
+ if (node.expanded) {
891
+ this.store()
892
+ .collapse(node.id)
893
+ .finally(() => {
894
+ this.loading.set(false);
895
+ });
896
+ }
897
+ else {
898
+ this.store()
899
+ .expand(node.id)
900
+ .finally(() => {
901
+ this.loading.set(false);
902
+ });
903
+ }
904
+ }
905
+ onPageChangeRequest(request) {
906
+ this.loading.set(true);
907
+ this.store()
908
+ .changePage(request.node.id, request.paging.pageNumber)
909
+ .finally(() => {
910
+ this.loading.set(false);
911
+ });
912
+ }
913
+ onFilterChange(filter) {
914
+ console.log('filter change', filter);
915
+ this.loading.set(true);
916
+ this.store()
917
+ .setFilter(filter || {})
918
+ .finally(() => {
919
+ this.loading.set(false);
920
+ });
921
+ }
922
+ onEditFilterRequest(node) {
923
+ const dialogRef = this._dialog.open(ThesPagedTreeFilterComponent, {
924
+ data: {
925
+ filter: node.filter,
926
+ },
927
+ });
928
+ dialogRef.afterClosed().subscribe((filter) => {
929
+ // undefined = user dismissed without changes
930
+ if (filter === null) {
931
+ this.store().setNodeFilter(node.id, null);
932
+ }
933
+ else if (filter) {
934
+ this.store().setNodeFilter(node.id, filter);
935
+ }
936
+ });
937
+ }
938
+ expandAll() {
939
+ this.store().expandAll();
940
+ }
941
+ collapseAll() {
942
+ this.store().collapseAll();
943
+ }
944
+ clear() {
945
+ this.store().clear();
946
+ }
947
+ onNodeClick(node) {
948
+ if (!node.hasChildren) {
949
+ this.nodePick.emit(node);
950
+ }
951
+ }
952
+ findLabels() {
953
+ if (!this.label.value) {
954
+ return;
955
+ }
956
+ this.store().findLabels(this.label.value);
957
+ }
958
+ removeHilites() {
959
+ this.store().removeHilites();
960
+ }
961
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: ThesPagedTreeBrowserComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
962
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.0", type: ThesPagedTreeBrowserComponent, isStandalone: true, selector: "cadmus-thes-paged-tree-browser", inputs: { service: { classPropertyName: "service", publicName: "service", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { nodePick: "nodePick" }, ngImport: i0, template: "<div id=\"container\">\r\n <!-- filters -->\r\n <div id=\"filters\" class=\"form-row\">\r\n <form [formGroup]=\"form\" (submit)=\"findLabels()\" class=\"form-row\">\r\n <mat-form-field>\r\n <mat-label>label</mat-label>\r\n <input matInput [formControl]=\"label\" />\r\n @if ($any(label).errors?.required && (label.dirty || label.touched)) {\r\n <mat-error>label required</mat-error>\r\n }\r\n </mat-form-field>\r\n <button type=\"submit\" mat-icon-button class=\"mat-primary\">\r\n <mat-icon>search</mat-icon>\r\n </button>\r\n <button type=\"button\" mat-icon-button (click)=\"removeHilites()\">\r\n <mat-icon class=\"mat-warn\">clear</mat-icon>\r\n </button>\r\n </form>\r\n <fieldset>\r\n <legend>filter</legend>\r\n <cadmus-thes-paged-tree-filter\r\n [filter]=\"filter$ | async\"\r\n (filterChange)=\"onFilterChange($event)\"\r\n />\r\n </fieldset>\r\n </div>\r\n\r\n <!-- tree -->\r\n <div id=\"tree\">\r\n <!-- progress -->\r\n @if (loading()) {\r\n <div>\r\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\r\n </div>\r\n }\r\n <!-- nodes -->\r\n @if (nodes$ | async; as nodes) {\r\n <div>\r\n @for (node of nodes; track node.id; let i = $index) {\r\n <div class=\"button-row\" [class.hilite]=\"node.hilite\">\r\n <pdb-browser-tree-node\r\n [node]=\"node\"\r\n [debug]=\"debug.value\"\r\n [paging]=\"\r\n node.expanded &&\r\n i + 1 < nodes.length &&\r\n nodes[i + 1].paging.pageCount > 1\r\n ? nodes[i + 1].paging\r\n : undefined\r\n \"\r\n [hideFilter]=\"hideFilter.value\"\r\n [hideLoc]=\"hideLoc.value\"\r\n (toggleExpandedRequest)=\"onToggleExpanded($any($event))\"\r\n (changePageRequest)=\"onPageChangeRequest($event)\"\r\n (editNodeFilterRequest)=\"onEditFilterRequest($any($event))\"\r\n />\r\n @if (!node.hasChildren) {\r\n <button type=\"button\" mat-icon-button (click)=\"onNodeClick(node)\">\r\n <mat-icon class=\"mat-primary\">check_circle</mat-icon>\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n <div class=\"form-row\">\r\n <mat-checkbox [formControl]=\"debug\">debug</mat-checkbox>\r\n <mat-checkbox [formControl]=\"hideFilter\">no filter</mat-checkbox>\r\n <mat-checkbox [formControl]=\"hideLoc\">no loc.</mat-checkbox>\r\n <button\r\n type=\"button\"\r\n mat-flat-button\r\n (click)=\"collapseAll()\"\r\n style=\"margin-left: 24px\"\r\n >\r\n collapse\r\n </button>\r\n </div>\r\n </div>\r\n</div>\r\n", styles: ["form{margin:16px}.form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row *{flex:0 0 auto}.nr{width:6em}fieldset{border:1px solid silver;border-radius:6px;padding:8px 16px;margin:8px 0}legend{color:silver}th{text-align:left;font-weight:400}#tag-selector{width:8em}.tag-chip{border:2px solid transparent;border-radius:6px}.button-row{display:flex;align-items:center;flex-wrap:wrap}.button-row *{flex:0 0 auto}.hilite{background-color:#ffffe0;border:2px solid gold;border-radius:6px;padding:2px 4px}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2$1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i2$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i3.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i5.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i4$1.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatProgressBarModule }, { kind: "component", type: i7.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "component", type: ThesPagedTreeFilterComponent, selector: "cadmus-thes-paged-tree-filter", inputs: ["filter"], outputs: ["filterChange"] }, { kind: "component", type: BrowserTreeNodeComponent, selector: "pdb-browser-tree-node", inputs: ["node", "paging", "debug", "hideLabel", "hideLoc", "hidePaging", "hideFilter", "indentSize", "rangeWidth"], outputs: ["toggleExpandedRequest", "changePageRequest", "editNodeFilterRequest"] }, { kind: "pipe", type: AsyncPipe, name: "async" }] });
963
+ }
964
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: ThesPagedTreeBrowserComponent, decorators: [{
965
+ type: Component,
966
+ args: [{ selector: 'cadmus-thes-paged-tree-browser', imports: [
967
+ AsyncPipe,
968
+ ReactiveFormsModule,
969
+ MatButtonModule,
970
+ MatCheckboxModule,
971
+ MatFormFieldModule,
972
+ MatIconModule,
973
+ MatInputModule,
974
+ MatProgressBarModule,
975
+ MatTooltipModule,
976
+ ThesPagedTreeFilterComponent,
977
+ BrowserTreeNodeComponent,
978
+ ], template: "<div id=\"container\">\r\n <!-- filters -->\r\n <div id=\"filters\" class=\"form-row\">\r\n <form [formGroup]=\"form\" (submit)=\"findLabels()\" class=\"form-row\">\r\n <mat-form-field>\r\n <mat-label>label</mat-label>\r\n <input matInput [formControl]=\"label\" />\r\n @if ($any(label).errors?.required && (label.dirty || label.touched)) {\r\n <mat-error>label required</mat-error>\r\n }\r\n </mat-form-field>\r\n <button type=\"submit\" mat-icon-button class=\"mat-primary\">\r\n <mat-icon>search</mat-icon>\r\n </button>\r\n <button type=\"button\" mat-icon-button (click)=\"removeHilites()\">\r\n <mat-icon class=\"mat-warn\">clear</mat-icon>\r\n </button>\r\n </form>\r\n <fieldset>\r\n <legend>filter</legend>\r\n <cadmus-thes-paged-tree-filter\r\n [filter]=\"filter$ | async\"\r\n (filterChange)=\"onFilterChange($event)\"\r\n />\r\n </fieldset>\r\n </div>\r\n\r\n <!-- tree -->\r\n <div id=\"tree\">\r\n <!-- progress -->\r\n @if (loading()) {\r\n <div>\r\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\r\n </div>\r\n }\r\n <!-- nodes -->\r\n @if (nodes$ | async; as nodes) {\r\n <div>\r\n @for (node of nodes; track node.id; let i = $index) {\r\n <div class=\"button-row\" [class.hilite]=\"node.hilite\">\r\n <pdb-browser-tree-node\r\n [node]=\"node\"\r\n [debug]=\"debug.value\"\r\n [paging]=\"\r\n node.expanded &&\r\n i + 1 < nodes.length &&\r\n nodes[i + 1].paging.pageCount > 1\r\n ? nodes[i + 1].paging\r\n : undefined\r\n \"\r\n [hideFilter]=\"hideFilter.value\"\r\n [hideLoc]=\"hideLoc.value\"\r\n (toggleExpandedRequest)=\"onToggleExpanded($any($event))\"\r\n (changePageRequest)=\"onPageChangeRequest($event)\"\r\n (editNodeFilterRequest)=\"onEditFilterRequest($any($event))\"\r\n />\r\n @if (!node.hasChildren) {\r\n <button type=\"button\" mat-icon-button (click)=\"onNodeClick(node)\">\r\n <mat-icon class=\"mat-primary\">check_circle</mat-icon>\r\n </button>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n <div class=\"form-row\">\r\n <mat-checkbox [formControl]=\"debug\">debug</mat-checkbox>\r\n <mat-checkbox [formControl]=\"hideFilter\">no filter</mat-checkbox>\r\n <mat-checkbox [formControl]=\"hideLoc\">no loc.</mat-checkbox>\r\n <button\r\n type=\"button\"\r\n mat-flat-button\r\n (click)=\"collapseAll()\"\r\n style=\"margin-left: 24px\"\r\n >\r\n collapse\r\n </button>\r\n </div>\r\n </div>\r\n</div>\r\n", styles: ["form{margin:16px}.form-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.form-row *{flex:0 0 auto}.nr{width:6em}fieldset{border:1px solid silver;border-radius:6px;padding:8px 16px;margin:8px 0}legend{color:silver}th{text-align:left;font-weight:400}#tag-selector{width:8em}.tag-chip{border:2px solid transparent;border-radius:6px}.button-row{display:flex;align-items:center;flex-wrap:wrap}.button-row *{flex:0 0 auto}.hilite{background-color:#ffffe0;border:2px solid gold;border-radius:6px;padding:2px 4px}\n"] }]
979
+ }], ctorParameters: () => [] });
980
+
592
981
  var PartBadgeType;
593
982
  (function (PartBadgeType) {
594
983
  PartBadgeType[PartBadgeType["partAndRole"] = 0] = "partAndRole";
@@ -711,21 +1100,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.0", ngImpor
711
1100
  args: [{ selector: 'cadmus-part-badge', template: "@if (badgeType() !== 2) {\r\n<span\r\n class=\"part-badge\"\r\n [style.background-color]=\"color()\"\r\n [style.color]=\"contrastColor()\"\r\n>\r\n {{ typeName() }}\r\n</span>\r\n} @if (badgeType() !== 1 && roleName()) {\r\n<span class=\"role-badge\">\r\n {{ roleName() }}\r\n</span>\r\n}\r\n", styles: [".part-badge{padding:4px;border-radius:6px}.role-badge{padding:4px;border:1px solid silver;border-radius:6px}\n"] }]
712
1101
  }], ctorParameters: () => [{ type: i1$2.FacetService }, { type: ColorService }] });
713
1102
 
714
- /**
715
- * A label rendering function which removes from a label
716
- * all the characters past the last colon, trimming the result.
717
- * This is a typical rendering when dealing with hierarchical
718
- * thesaurus entries, e.g. "furniture: table: color", where
719
- * we can shorten the label to just "color", as "furniture"
720
- * and "table" are its ancestors.
721
- */
722
- const renderLabelFromLastColon = (label) => {
723
- if (!label) {
724
- return label;
725
- }
726
- const i = label.lastIndexOf(':');
727
- return i > -1 && i + 1 < label.length ? label.substring(i + 1).trim() : label;
728
- };
729
1103
  /**
730
1104
  * Thesaurus tree component.
731
1105
  * This component displays a set of hierarchical thesaurus entries
@@ -744,203 +1118,42 @@ class ThesaurusTreeComponent {
744
1118
  * The thesaurus entries.
745
1119
  */
746
1120
  entries = input(...(ngDevMode ? [undefined, { debugName: "entries" }] : []));
747
- /**
748
- * The label for the root node.
749
- */
750
- rootLabel = input('-', ...(ngDevMode ? [{ debugName: "rootLabel" }] : []));
751
1121
  /**
752
1122
  * The optional node label rendering function.
753
1123
  */
754
- renderLabel = input(...(ngDevMode ? [undefined, { debugName: "renderLabel" }] : []));
1124
+ renderLabel = input(renderLabelFromLastColon, ...(ngDevMode ? [{ debugName: "renderLabel" }] : []));
755
1125
  /**
756
1126
  * Fired when a thesaurus entry is selected.
757
1127
  */
758
1128
  entryChange = output();
759
- root;
760
- // TODO: use childrenAccessor: https://material.angular.io/cdk/tree/examples
761
- treeControl;
762
- treeDataSource;
763
- filter;
764
- form;
765
- foundNodes;
766
- hasChildren = (index, node) => {
767
- return node && node.children && node.children.length > 0;
768
- };
769
- isRoot = (index, node) => {
770
- return node && node.id === '@root';
771
- };
772
- constructor(formBuilder) {
773
- // tree
774
- this.treeControl = new NestedTreeControl((node) => node.children);
775
- this.treeDataSource = new MatTreeNestedDataSource();
776
- this.root = { id: '@root', label: '-', children: [] };
777
- // filter
778
- this.filter = formBuilder.control(null);
779
- this.form = formBuilder.group({
780
- filter: this.filter,
781
- });
782
- effect(() => {
783
- this.initTree(this.entries());
784
- });
785
- }
786
- ngOnInit() {
787
- this.initTree();
788
- }
789
- initTree(entries) {
790
- this.foundNodes = undefined;
791
- this.root = this.buildTreeModel(entries || []);
792
- this.treeDataSource.data = [this.root];
793
- // https://github.com/angular/components/issues/12469
794
- this.treeControl.dataNodes = this.treeDataSource.data;
795
- }
796
- getLabel(label) {
797
- const renderer = this.renderLabel();
798
- return renderer ? renderer(label) : label;
799
- }
800
- addNode(entry, separator, root) {
801
- const components = entry.id.split(separator);
802
- // walk the tree up to the last existing component
803
- let i = 0;
804
- let node = root;
805
- const idParts = [];
806
- // for each component:
807
- while (i < components.length) {
808
- idParts.push(components[i]);
809
- // stop walking when the node has no more children
810
- if (!node.children) {
811
- break;
812
- }
813
- // find target among children
814
- const targetId = idParts.join(separator);
815
- const existing = node.children.find((c) => {
816
- return c.id === targetId;
817
- });
818
- if (existing) {
819
- node = existing;
820
- i++;
821
- }
822
- else {
823
- break;
824
- }
825
- }
826
- // node is now the last existing component; use it as the ancestor
827
- // for all the remaining components (starting from i)
828
- while (i < components.length) {
829
- if (!node.children) {
830
- node.children = [];
831
- }
832
- const isLast = i + 1 === components.length;
833
- const label = isLast ? entry.value : components[i];
834
- const child = {
835
- label: isLast ? this.getLabel(label) : label,
836
- id: isLast ? entry.id : idParts.join(separator),
837
- parent: node,
838
- children: [],
839
- };
840
- if (label !== child.label) {
841
- child.originalLabel = label;
842
- }
843
- node.children.push(child);
844
- node = child;
845
- i++;
846
- }
847
- }
848
1129
  /**
849
- * Build a tree model from a list of name=value pairs,
850
- * where each value can include one or more components separated by
851
- * the specified separator.
852
- * @param entries The entries to add.
853
- * @param separator string The separator string to use for values.
1130
+ * The tree store service, dependent on the current entries and renderLabel.
854
1131
  */
855
- buildTreeModel(entries, separator = '.') {
856
- const root = {
857
- id: '@root',
858
- label: this.rootLabel() || '-',
859
- };
860
- if (!entries) {
861
- return root;
862
- }
863
- entries.forEach((entry) => {
864
- this.addNode(entry, separator, root);
865
- });
866
- return root;
867
- }
868
- onTreeNodeClick(node) {
869
- if (node.children && node.children.length > 0) {
870
- return;
871
- }
872
- this.entryChange.emit({
873
- id: node.id,
874
- value: node.originalLabel || node.label,
875
- });
876
- }
877
- expandAll() {
878
- this.treeControl.expandAll();
879
- }
880
- collapseAll() {
881
- this.treeControl.collapseAll();
882
- }
883
- expandFromNode(node) {
884
- while (node.parent) {
885
- node = node.parent;
886
- this.treeControl.expand(node);
887
- }
888
- }
889
- expandMatchingNodes(node, filter, found) {
890
- if (node.label.toLowerCase().includes(filter)) {
891
- found.push(node);
892
- this.expandFromNode(node);
893
- }
894
- if (node.children?.length) {
895
- node.children.forEach((child) => {
896
- this.expandMatchingNodes(child, filter, found);
897
- });
898
- }
899
- }
900
- find() {
901
- if (!this.filter.value?.length) {
902
- return;
903
- }
904
- this.treeControl.collapseAll();
905
- const foundNodes = [];
906
- this.expandMatchingNodes(this.treeDataSource.data[0], this.filter.value.toLowerCase(), foundNodes);
907
- this.foundNodes = foundNodes;
908
- }
909
- resetFilter() {
910
- this.filter.reset();
911
- this.foundNodes = undefined;
912
- }
913
- isFoundNode(node) {
914
- if (!this.foundNodes) {
915
- return false;
1132
+ service = computed(() => {
1133
+ const entries = this.entries();
1134
+ return new StaticThesPagedTreeStoreService(entries || [], this.renderLabel());
1135
+ }, ...(ngDevMode ? [{ debugName: "service" }] : []));
1136
+ /**
1137
+ * The filter component class to use.
1138
+ */
1139
+ filterComponent = ThesPagedTreeFilterComponent;
1140
+ onNodePick(node) {
1141
+ // only allow selection of leaf nodes (nodes without children)
1142
+ if (!node.hasChildren) {
1143
+ // find the original entry
1144
+ const entry = this.entries()?.find((e) => e.id === node.key);
1145
+ if (entry) {
1146
+ this.entryChange.emit(entry);
1147
+ }
916
1148
  }
917
- return this.foundNodes?.length > 0 && this.foundNodes.indexOf(node) > -1;
918
1149
  }
919
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: ThesaurusTreeComponent, deps: [{ token: i1$1.FormBuilder }], target: i0.ɵɵFactoryTarget.Component });
920
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.0", type: ThesaurusTreeComponent, isStandalone: true, selector: "cadmus-thesaurus-tree", inputs: { entries: { classPropertyName: "entries", publicName: "entries", isSignal: true, isRequired: false, transformFunction: null }, rootLabel: { classPropertyName: "rootLabel", publicName: "rootLabel", isSignal: true, isRequired: false, transformFunction: null }, renderLabel: { classPropertyName: "renderLabel", publicName: "renderLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { entryChange: "entryChange" }, ngImport: i0, template: "<form [formGroup]=\"form\" (submit)=\"find()\">\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Expand all\"\r\n (click)=\"expandAll()\"\r\n >\r\n <mat-icon>unfold_more</mat-icon>\r\n </button>\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Collapse all\"\r\n (click)=\"collapseAll()\"\r\n >\r\n <mat-icon>unfold_less</mat-icon>\r\n </button>\r\n <mat-form-field>\r\n <mat-label>find</mat-label>\r\n <input matInput [formControl]=\"filter\" />\r\n <button type=\"button\" matSuffix mat-icon-button (click)=\"resetFilter()\">\r\n <mat-icon class=\"mat-warn\">clear</mat-icon>\r\n </button>\r\n </mat-form-field>\r\n @if (foundNodes) {\r\n <span\r\n [class.found-count]=\"foundNodes.length\"\r\n [class.not-found-count]=\"foundNodes.length === 0\"\r\n >{{ foundNodes.length }}</span\r\n >\r\n }\r\n</form>\r\n<mat-tree\r\n [dataSource]=\"treeDataSource\"\r\n [treeControl]=\"treeControl\"\r\n class=\"th-tree\"\r\n>\r\n <!-- leaf node -->\r\n <mat-tree-node *matTreeNodeDef=\"let node\" matTreeNodeToggle>\r\n <li>\r\n <div class=\"mat-tree-node\">\r\n <a\r\n mat-button\r\n (click)=\"onTreeNodeClick(node)\"\r\n [class.hilite]=\"isFoundNode(node)\"\r\n >\r\n {{ node.label }}\r\n </a>\r\n </div>\r\n </li>\r\n </mat-tree-node>\r\n\r\n <!-- root node -->\r\n <mat-nested-tree-node *matTreeNodeDef=\"let node; when: isRoot\">\r\n <div class=\"mat-tree-node\">\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTreeNodeToggle\r\n [attr.aria-label]=\"'Toggle ' + node.label\"\r\n >\r\n <mat-icon>\r\n {{ treeControl.isExpanded(node) ? \"expand_more\" : \"chevron_right\" }}\r\n </mat-icon>\r\n </button>\r\n {{ node.label }}\r\n </div>\r\n <ul class=\"tree-nested-node\">\r\n @if (treeControl.isExpanded(node)) {\r\n <div>\r\n <ng-container matTreeNodeOutlet></ng-container>\r\n </div>\r\n }\r\n </ul>\r\n </mat-nested-tree-node>\r\n\r\n <!-- parent node -->\r\n <mat-nested-tree-node *matTreeNodeDef=\"let node; when: hasChildren\">\r\n <li class=\"tree-container\">\r\n <div class=\"mat-tree-node\">\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTreeNodeToggle\r\n [attr.aria-label]=\"'Toggle ' + node.label\"\r\n >\r\n <mat-icon>\r\n {{ treeControl.isExpanded(node) ? \"expand_more\" : \"chevron_right\" }}\r\n </mat-icon>\r\n </button>\r\n <a mat-button (click)=\"onTreeNodeClick(node)\">\r\n {{ node.label }}\r\n </a>\r\n </div>\r\n <ul class=\"tree-nested-node\">\r\n @if (treeControl.isExpanded(node)) {\r\n <div>\r\n <ng-container matTreeNodeOutlet></ng-container>\r\n </div>\r\n }\r\n </ul>\r\n </li>\r\n </mat-nested-tree-node>\r\n</mat-tree>\r\n", styles: [".th-tree-progress-bar{margin-left:30px}.th-tree-nested-node{padding-left:30px}mat-tree{margin-left:40px}.mat-tree-node{padding:0;background-color:#fff}.mat-nested-tree-node{top:-24px}ul,li{list-style:none;margin:0;padding:0}li.th-tree-container{border-bottom:0}ul{padding-left:40px}li{padding-left:40px;border:1px dotted grey;border-width:0 0 1px 1px;position:relative;top:-24px}li.mat-tree-node,li div{margin:0;position:relative;top:24px}li ul{border-top:1px dotted grey;margin-left:-40px;padding-left:60px}.mat-nested-tree-node:last-child ul{border-left:1px solid white;margin-left:-41px}.mat-mdc-icon-button{z-index:100}span.found-count{margin:0 4px;padding:2px 4px;color:#fff;background-color:#0cc078;border-radius:6px}span.not-found-count{margin:0 4px;padding:2px 4px;color:#fff;background-color:#fb6962;border-radius:6px}.hilite{background-color:#fdfd96}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "directive", type: MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: MatLabel, selector: "mat-label" }, { kind: "directive", type: MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "directive", type: MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "component", type: MatTree, selector: "mat-tree", exportAs: ["matTree"] }, { kind: "directive", type: MatTreeNodeDef, selector: "[matTreeNodeDef]", inputs: ["matTreeNodeDefWhen", "matTreeNode"] }, { kind: "directive", type: MatTreeNode, selector: "mat-tree-node", inputs: ["tabIndex", "disabled"], outputs: ["activation", "expandedChange"], exportAs: ["matTreeNode"] }, { kind: "directive", type: MatTreeNodeToggle, selector: "[matTreeNodeToggle]", inputs: ["matTreeNodeToggleRecursive"] }, { kind: "component", type: MatAnchor, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "directive", type: MatNestedTreeNode, selector: "mat-nested-tree-node", inputs: ["matNestedTreeNode", "disabled", "tabIndex"], outputs: ["activation", "expandedChange"], exportAs: ["matNestedTreeNode"] }, { kind: "directive", type: MatTreeNodeOutlet, selector: "[matTreeNodeOutlet]" }] });
1150
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: ThesaurusTreeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1151
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.0", type: ThesaurusTreeComponent, isStandalone: true, selector: "cadmus-thesaurus-tree", inputs: { entries: { classPropertyName: "entries", publicName: "entries", isSignal: true, isRequired: false, transformFunction: null }, renderLabel: { classPropertyName: "renderLabel", publicName: "renderLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { entryChange: "entryChange" }, ngImport: i0, template: "<div>\r\n <!-- tree -->\r\n @if (service() && entries()?.length) {\r\n <div class=\"tree-wrapper\">\r\n <cadmus-thes-paged-tree-browser\r\n [service]=\"service()\"\r\n (nodePick)=\"onNodePick($event)\"\r\n />\r\n </div>\r\n }\r\n</div>\r\n", styles: [".th-tree-progress-bar{margin-left:30px}.th-tree-nested-node{padding-left:30px}mat-tree{margin-left:40px}.mat-tree-node{padding:0;background-color:#fff}.mat-nested-tree-node{top:-24px}ul,li{list-style:none;margin:0;padding:0}li.th-tree-container{border-bottom:0}ul{padding-left:40px}li{padding-left:40px;border:1px dotted grey;border-width:0 0 1px 1px;position:relative;top:-24px}li.mat-tree-node,li div{margin:0;position:relative;top:24px}li ul{border-top:1px dotted grey;margin-left:-40px;padding-left:60px}.mat-nested-tree-node:last-child ul{border-left:1px solid white;margin-left:-41px}.mat-mdc-icon-button{z-index:100}span.found-count{margin:0 4px;padding:2px 4px;color:#fff;background-color:#0cc078;border-radius:6px}span.not-found-count{margin:0 4px;padding:2px 4px;color:#fff;background-color:#fb6962;border-radius:6px}.hilite{background-color:#fdfd96}.filter-field{width:100%;margin-bottom:1rem}.tree-container{border:1px solid #e0e0e0;border-radius:4px;overflow:auto;max-height:500px}.tree-wrapper{border:1px solid #e0e0e0;border-radius:4px;overflow:hidden;background:#fff}.no-entries{text-align:center;padding:2rem;color:#666;font-style:italic;border:1px solid #e0e0e0;border-radius:4px;background:#f9f9f9}.no-entries p{margin:0}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: ThesPagedTreeBrowserComponent, selector: "cadmus-thes-paged-tree-browser", inputs: ["service"], outputs: ["nodePick"] }] });
921
1152
  }
922
1153
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.0", ngImport: i0, type: ThesaurusTreeComponent, decorators: [{
923
1154
  type: Component,
924
- args: [{ selector: 'cadmus-thesaurus-tree', imports: [
925
- FormsModule,
926
- ReactiveFormsModule,
927
- MatIconButton,
928
- MatTooltip,
929
- MatIcon,
930
- MatFormField,
931
- MatLabel,
932
- MatInput,
933
- MatSuffix,
934
- NgClass,
935
- MatTree,
936
- MatTreeNodeDef,
937
- MatTreeNode,
938
- MatTreeNodeToggle,
939
- MatAnchor,
940
- MatNestedTreeNode,
941
- MatTreeNodeOutlet,
942
- ], template: "<form [formGroup]=\"form\" (submit)=\"find()\">\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Expand all\"\r\n (click)=\"expandAll()\"\r\n >\r\n <mat-icon>unfold_more</mat-icon>\r\n </button>\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTooltip=\"Collapse all\"\r\n (click)=\"collapseAll()\"\r\n >\r\n <mat-icon>unfold_less</mat-icon>\r\n </button>\r\n <mat-form-field>\r\n <mat-label>find</mat-label>\r\n <input matInput [formControl]=\"filter\" />\r\n <button type=\"button\" matSuffix mat-icon-button (click)=\"resetFilter()\">\r\n <mat-icon class=\"mat-warn\">clear</mat-icon>\r\n </button>\r\n </mat-form-field>\r\n @if (foundNodes) {\r\n <span\r\n [class.found-count]=\"foundNodes.length\"\r\n [class.not-found-count]=\"foundNodes.length === 0\"\r\n >{{ foundNodes.length }}</span\r\n >\r\n }\r\n</form>\r\n<mat-tree\r\n [dataSource]=\"treeDataSource\"\r\n [treeControl]=\"treeControl\"\r\n class=\"th-tree\"\r\n>\r\n <!-- leaf node -->\r\n <mat-tree-node *matTreeNodeDef=\"let node\" matTreeNodeToggle>\r\n <li>\r\n <div class=\"mat-tree-node\">\r\n <a\r\n mat-button\r\n (click)=\"onTreeNodeClick(node)\"\r\n [class.hilite]=\"isFoundNode(node)\"\r\n >\r\n {{ node.label }}\r\n </a>\r\n </div>\r\n </li>\r\n </mat-tree-node>\r\n\r\n <!-- root node -->\r\n <mat-nested-tree-node *matTreeNodeDef=\"let node; when: isRoot\">\r\n <div class=\"mat-tree-node\">\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTreeNodeToggle\r\n [attr.aria-label]=\"'Toggle ' + node.label\"\r\n >\r\n <mat-icon>\r\n {{ treeControl.isExpanded(node) ? \"expand_more\" : \"chevron_right\" }}\r\n </mat-icon>\r\n </button>\r\n {{ node.label }}\r\n </div>\r\n <ul class=\"tree-nested-node\">\r\n @if (treeControl.isExpanded(node)) {\r\n <div>\r\n <ng-container matTreeNodeOutlet></ng-container>\r\n </div>\r\n }\r\n </ul>\r\n </mat-nested-tree-node>\r\n\r\n <!-- parent node -->\r\n <mat-nested-tree-node *matTreeNodeDef=\"let node; when: hasChildren\">\r\n <li class=\"tree-container\">\r\n <div class=\"mat-tree-node\">\r\n <button\r\n type=\"button\"\r\n mat-icon-button\r\n matTreeNodeToggle\r\n [attr.aria-label]=\"'Toggle ' + node.label\"\r\n >\r\n <mat-icon>\r\n {{ treeControl.isExpanded(node) ? \"expand_more\" : \"chevron_right\" }}\r\n </mat-icon>\r\n </button>\r\n <a mat-button (click)=\"onTreeNodeClick(node)\">\r\n {{ node.label }}\r\n </a>\r\n </div>\r\n <ul class=\"tree-nested-node\">\r\n @if (treeControl.isExpanded(node)) {\r\n <div>\r\n <ng-container matTreeNodeOutlet></ng-container>\r\n </div>\r\n }\r\n </ul>\r\n </li>\r\n </mat-nested-tree-node>\r\n</mat-tree>\r\n", styles: [".th-tree-progress-bar{margin-left:30px}.th-tree-nested-node{padding-left:30px}mat-tree{margin-left:40px}.mat-tree-node{padding:0;background-color:#fff}.mat-nested-tree-node{top:-24px}ul,li{list-style:none;margin:0;padding:0}li.th-tree-container{border-bottom:0}ul{padding-left:40px}li{padding-left:40px;border:1px dotted grey;border-width:0 0 1px 1px;position:relative;top:-24px}li.mat-tree-node,li div{margin:0;position:relative;top:24px}li ul{border-top:1px dotted grey;margin-left:-40px;padding-left:60px}.mat-nested-tree-node:last-child ul{border-left:1px solid white;margin-left:-41px}.mat-mdc-icon-button{z-index:100}span.found-count{margin:0 4px;padding:2px 4px;color:#fff;background-color:#0cc078;border-radius:6px}span.not-found-count{margin:0 4px;padding:2px 4px;color:#fff;background-color:#fb6962;border-radius:6px}.hilite{background-color:#fdfd96}\n"] }]
943
- }], ctorParameters: () => [{ type: i1$1.FormBuilder }] });
1155
+ args: [{ selector: 'cadmus-thesaurus-tree', imports: [FormsModule, ReactiveFormsModule, ThesPagedTreeBrowserComponent], template: "<div>\r\n <!-- tree -->\r\n @if (service() && entries()?.length) {\r\n <div class=\"tree-wrapper\">\r\n <cadmus-thes-paged-tree-browser\r\n [service]=\"service()\"\r\n (nodePick)=\"onNodePick($event)\"\r\n />\r\n </div>\r\n }\r\n</div>\r\n", styles: [".th-tree-progress-bar{margin-left:30px}.th-tree-nested-node{padding-left:30px}mat-tree{margin-left:40px}.mat-tree-node{padding:0;background-color:#fff}.mat-nested-tree-node{top:-24px}ul,li{list-style:none;margin:0;padding:0}li.th-tree-container{border-bottom:0}ul{padding-left:40px}li{padding-left:40px;border:1px dotted grey;border-width:0 0 1px 1px;position:relative;top:-24px}li.mat-tree-node,li div{margin:0;position:relative;top:24px}li ul{border-top:1px dotted grey;margin-left:-40px;padding-left:60px}.mat-nested-tree-node:last-child ul{border-left:1px solid white;margin-left:-41px}.mat-mdc-icon-button{z-index:100}span.found-count{margin:0 4px;padding:2px 4px;color:#fff;background-color:#0cc078;border-radius:6px}span.not-found-count{margin:0 4px;padding:2px 4px;color:#fff;background-color:#fb6962;border-radius:6px}.hilite{background-color:#fdfd96}.filter-field{width:100%;margin-bottom:1rem}.tree-container{border:1px solid #e0e0e0;border-radius:4px;overflow:auto;max-height:500px}.tree-wrapper{border:1px solid #e0e0e0;border-radius:4px;overflow:hidden;background:#fff}.no-entries{text-align:center;padding:2rem;color:#666;font-style:italic;border:1px solid #e0e0e0;border-radius:4px;background:#f9f9f9}.no-entries p{margin:0}\n"] }]
1156
+ }] });
944
1157
 
945
1158
  /**
946
1159
  * Base class for part/fragment editors dumb components.
@@ -1325,5 +1538,5 @@ const extractPristineChanges = (control) => {
1325
1538
  * Generated bundle index. Do not edit.
1326
1539
  */
1327
1540
 
1328
- export { CloseSaveButtonsComponent, ColorService, CustomValidators, DecoratedTokenTextComponent, ErrorListComponent, FacetBadgeComponent, FlagsBadgeComponent, JsonValidators, LayerHintsComponent, LookupPinComponent, ModelEditorComponentBase, PartBadgeComponent, PartBadgeType, ThesaurusTreeComponent, UserRefLookupService, extractPristineChanges, extractTouchedChanges, getPartIdName, renderLabelFromLastColon };
1541
+ export { CloseSaveButtonsComponent, ColorService, CustomValidators, DecoratedTokenTextComponent, ErrorListComponent, FacetBadgeComponent, FlagsBadgeComponent, JsonValidators, LayerHintsComponent, LookupPinComponent, ModelEditorComponentBase, PartBadgeComponent, PartBadgeType, StaticThesPagedTreeStoreService, ThesPagedTreeBrowserComponent, ThesaurusTreeComponent, UserRefLookupService, extractPristineChanges, extractTouchedChanges, getPartIdName, renderLabelFromLastColon };
1329
1542
  //# sourceMappingURL=myrmidon-cadmus-ui.mjs.map