@koobiq/components 21.0.0 → 21.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/koobiq-components-code-block.mjs +45 -17
- package/fesm2022/koobiq-components-code-block.mjs.map +1 -1
- package/fesm2022/koobiq-components-core.mjs +1 -1
- package/fesm2022/koobiq-components-core.mjs.map +1 -1
- package/fesm2022/koobiq-components-filter-bar.mjs +9 -5
- package/fesm2022/koobiq-components-filter-bar.mjs.map +1 -1
- package/fesm2022/koobiq-components-icon.mjs +37 -16
- package/fesm2022/koobiq-components-icon.mjs.map +1 -1
- package/fesm2022/koobiq-components-tree-select.mjs +20 -5
- package/fesm2022/koobiq-components-tree-select.mjs.map +1 -1
- package/fesm2022/koobiq-components-tree.mjs +218 -83
- package/fesm2022/koobiq-components-tree.mjs.map +1 -1
- package/package.json +3 -3
- package/schematics/collection.json +94 -89
- package/schematics/migrations/accordion-state-saving-default/{index.js → index.cjs} +5 -4
- package/schematics/migrations/actions-panel-overlay-container/{index.js → index.cjs} +5 -4
- package/schematics/migrations/alert-signals/{index.js → index.cjs} +14 -13
- package/schematics/migrations/angular-animations-removal/index.cjs +21 -0
- package/schematics/migrations/app-switcher-signals/{index.js → index.cjs} +7 -6
- package/schematics/migrations/autocomplete-panel-width-auto/{index.js → index.cjs} +6 -5
- package/schematics/migrations/autocomplete-signals/{index.js → index.cjs} +7 -6
- package/schematics/migrations/badge-signals/{index.js → index.cjs} +7 -6
- package/schematics/migrations/button-state-and-styles/{index.js → index.cjs} +7 -6
- package/schematics/migrations/button-supported-colors/{index.js → index.cjs} +7 -6
- package/schematics/migrations/button-toggle-signals-and-aria/{index.js → index.cjs} +7 -6
- package/schematics/migrations/button-truncation/{index.js → index.cjs} +7 -6
- package/schematics/migrations/checkbox-signals/{index.js → index.cjs} +9 -8
- package/schematics/migrations/clamped-text-disclosure/{index.js → index.cjs} +5 -4
- package/schematics/migrations/code-block-highlight-js-core/README.md +53 -0
- package/schematics/migrations/code-block-highlight-js-core/data.cjs +64 -0
- package/schematics/migrations/code-block-highlight-js-core/index.cjs +319 -0
- package/schematics/migrations/code-block-highlight-js-core/schema.json +20 -0
- package/schematics/migrations/code-block-signals/{index.js → index.cjs} +7 -6
- package/schematics/migrations/color-signals/index.cjs +20 -0
- package/schematics/migrations/core-mixins-removal/index.cjs +18 -0
- package/schematics/migrations/css-selectors/{index.js → index.cjs} +4 -3
- package/schematics/migrations/datepicker-signals/index.cjs +18 -0
- package/schematics/migrations/deprecated-icons/{index.js → index.cjs} +8 -7
- package/schematics/migrations/design-tokens-v4/{index.js → index.cjs} +3 -2
- package/schematics/migrations/divider-signals-and-aria/{index.js → index.cjs} +5 -4
- package/schematics/migrations/dl-attribute-coercion/{index.js → index.cjs} +7 -6
- package/schematics/migrations/dl-standalone-names/{index.js → index.cjs} +11 -10
- package/schematics/migrations/dropdown-demote-overlay/{index.js → index.cjs} +7 -6
- package/schematics/migrations/dropdown-signals/index.cjs +20 -0
- package/schematics/migrations/empty-state-error-color/{index.js → index.cjs} +5 -4
- package/schematics/migrations/empty-state-size-attr/{index.js → index.cjs} +6 -5
- package/schematics/migrations/file-upload-cva-and-primitives/{index.js → index.cjs} +5 -4
- package/schematics/migrations/file-upload-deprecated-outputs/{index.js → index.cjs} +5 -4
- package/schematics/migrations/filter-bar-rename-action/{index.js → index.cjs} +5 -4
- package/schematics/migrations/filter-bar-signals/{index.js → index.cjs} +16 -15
- package/schematics/migrations/filter-bar-state-saving-default/{index.js → index.cjs} +5 -4
- package/schematics/migrations/flag-inner-html/{index.js → index.cjs} +5 -4
- package/schematics/migrations/form-field-signals/{index.js → index.cjs} +18 -17
- package/schematics/migrations/icon-semantics-and-api/{data.js → data.cjs} +4 -4
- package/schematics/migrations/icon-semantics-and-api/{index.js → index.cjs} +6 -5
- package/schematics/migrations/icons-replacement/{index.js → index.cjs} +4 -3
- package/schematics/migrations/inline-edit-a11y-and-types/{index.js → index.cjs} +5 -4
- package/schematics/migrations/input-number-surface/{index.js → index.cjs} +5 -4
- package/schematics/migrations/input-signals/index.cjs +18 -0
- package/schematics/migrations/link-deprecated-path/{index.js → index.cjs} +4 -3
- package/schematics/migrations/link-signals/{index.js → index.cjs} +5 -4
- package/schematics/migrations/list-signals/index.cjs +18 -0
- package/schematics/migrations/list-tree-multiple-input/{index.js → index.cjs} +7 -6
- package/schematics/migrations/loader-overlay-signals/{index.js → index.cjs} +7 -6
- package/schematics/migrations/loader-overlay-size-attr/{index.js → index.cjs} +6 -5
- package/schematics/migrations/locale-configuration-providers/{index.js → index.cjs} +5 -4
- package/schematics/migrations/mandatory-peer-dependencies/{index.js → index.cjs} +3 -2
- package/schematics/migrations/markdown-signals/{index.js → index.cjs} +7 -6
- package/schematics/migrations/modal-dialog-semantics/{index.js → index.cjs} +5 -4
- package/schematics/migrations/modal-signals/index.cjs +20 -0
- package/schematics/migrations/navbar-signals-and-aria/{index.js → index.cjs} +7 -6
- package/schematics/migrations/new-icons-pack/{index.js → index.cjs} +8 -7
- package/schematics/migrations/notification-center-signals/{index.js → index.cjs} +5 -4
- package/schematics/migrations/option-signals/index.cjs +20 -0
- package/schematics/migrations/popover-leave-delay/{index.js → index.cjs} +5 -4
- package/schematics/migrations/progress-bar-percentage-and-aria/{index.js → index.cjs} +5 -4
- package/schematics/migrations/progress-spinner-signals/{index.js → index.cjs} +7 -6
- package/schematics/migrations/query-list-signals/index.cjs +18 -0
- package/schematics/migrations/radio-signals-and-aria/{index.js → index.cjs} +9 -8
- package/schematics/migrations/read-state-dwell-handlers/{index.js → index.cjs} +5 -4
- package/schematics/migrations/scrollbar-deprecated-path/{index.js → index.cjs} +4 -3
- package/schematics/migrations/select-signal-inputs/{index.js → index.cjs} +5 -4
- package/schematics/migrations/sidepanel-non-modal-behavior/{index.js → index.cjs} +5 -4
- package/schematics/migrations/signal-inputs-subclasses/index.cjs +18 -0
- package/schematics/migrations/split-button-optional-disabled/{index.js → index.cjs} +5 -4
- package/schematics/migrations/splitter-deprecated-path/{index.js → index.cjs} +4 -3
- package/schematics/migrations/state-saving-default/{index.js → index.cjs} +5 -4
- package/schematics/migrations/table-cell-content/{index.js → index.cjs} +5 -4
- package/schematics/migrations/tabs-signals-and-aria/{index.js → index.cjs} +5 -4
- package/schematics/migrations/tag-list-cleaner/{index.js → index.cjs} +7 -6
- package/schematics/migrations/tag-slots/{index.js → index.cjs} +6 -5
- package/schematics/migrations/tags-signals/{index.js → index.cjs} +7 -6
- package/schematics/migrations/textarea-signals/{index.js → index.cjs} +7 -6
- package/schematics/migrations/time-range-title-for/{index.js → index.cjs} +5 -4
- package/schematics/migrations/timepicker-signals/{index.js → index.cjs} +7 -6
- package/schematics/migrations/timezone-grouping-utils/{index.js → index.cjs} +5 -4
- package/schematics/migrations/title-encapsulation/{index.js → index.cjs} +5 -4
- package/schematics/migrations/toast-stack-and-defaults/{index.js → index.cjs} +5 -4
- package/schematics/migrations/tooltip-pointer-events-and-types/{index.js → index.cjs} +5 -4
- package/schematics/migrations/top-bar-container-selectors/{index.js → index.cjs} +5 -4
- package/schematics/migrations/tree-select-signals/{index.js → index.cjs} +5 -4
- package/schematics/migrations/tree-signals/{index.js → index.cjs} +5 -4
- package/schematics/migrations/tree-state-saving-default/{index.js → index.cjs} +5 -4
- package/schematics/migrations/username-pipe-injection/{index.js → index.cjs} +5 -4
- package/schematics/migrations/v20-upgrade/{data.js → data.cjs} +7 -7
- package/schematics/migrations/v20-upgrade/index.cjs +19 -0
- package/schematics/migrations/v21-upgrade/{data.js → data.cjs} +22 -22
- package/schematics/migrations/v21-upgrade/{index.js → index.cjs} +11 -10
- package/schematics/migrations/zoneless-change-detection/index.cjs +21 -0
- package/schematics/migrations.json +87 -82
- package/schematics/ng-add/{index.js → index.cjs} +12 -286
- package/schematics/utils/{angular-parsing.js → angular-parsing.cjs} +2 -2
- package/schematics/utils/{deprecated-path-migration.js → deprecated-path-migration.cjs} +4 -3
- package/schematics/utils/html-config.cjs +148 -0
- package/schematics/utils/{icon-migration.js → icon-migration.cjs} +2 -2
- package/schematics/utils/list-item-pattern.cjs +16 -0
- package/schematics/utils/package-config.cjs +80 -0
- package/schematics/utils/project-files.cjs +25 -0
- package/schematics/utils/replacement-driver.cjs +337 -0
- package/schematics/utils/safe-rule.cjs +23 -0
- package/schematics/utils/signal-members-migration.cjs +779 -0
- package/schematics/utils/tag-patterns.cjs +21 -0
- package/schematics/utils/template-ref-access.cjs +155 -0
- package/schematics/utils/{typescript.js → typescript.cjs} +1 -1
- package/schematics/utils/warn-only-migration.cjs +52 -0
- package/schematics/utils/workspace-styles.cjs +123 -0
- package/types/koobiq-components-code-block.d.ts +38 -11
- package/types/koobiq-components-filter-bar.d.ts +2 -2
- package/types/koobiq-components-tree-select.d.ts +11 -8
- package/types/koobiq-components-tree.d.ts +108 -59
- package/schematics/migrations/angular-animations-removal/index.js +0 -20
- package/schematics/migrations/color-signals/index.js +0 -19
- package/schematics/migrations/core-mixins-removal/index.js +0 -17
- package/schematics/migrations/datepicker-signals/index.js +0 -17
- package/schematics/migrations/dropdown-signals/index.js +0 -19
- package/schematics/migrations/input-signals/index.js +0 -17
- package/schematics/migrations/list-signals/index.js +0 -17
- package/schematics/migrations/modal-signals/index.js +0 -19
- package/schematics/migrations/option-signals/index.js +0 -19
- package/schematics/migrations/query-list-signals/index.js +0 -17
- package/schematics/migrations/signal-inputs-subclasses/index.js +0 -17
- package/schematics/migrations/v20-upgrade/index.js +0 -18
- package/schematics/migrations/zoneless-change-detection/index.js +0 -20
- package/schematics/utils/package-config.js +0 -13
- /package/schematics/migrations/accordion-state-saving-default/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/actions-panel-overlay-container/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/alert-signals/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/angular-animations-removal/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/app-switcher-signals/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/autocomplete-signals/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/badge-signals/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/button-state-and-styles/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/button-supported-colors/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/button-toggle-signals-and-aria/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/button-truncation/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/checkbox-signals/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/clamped-text-disclosure/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/code-block-signals/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/color-signals/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/core-mixins-removal/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/css-selectors/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/datepicker-signals/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/deprecated-icons/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/divider-signals-and-aria/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/dl-attribute-coercion/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/dl-standalone-names/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/dropdown-demote-overlay/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/dropdown-signals/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/empty-state-error-color/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/file-upload-cva-and-primitives/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/file-upload-deprecated-outputs/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/filter-bar-rename-action/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/filter-bar-signals/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/filter-bar-state-saving-default/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/flag-inner-html/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/form-field-signals/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/icons-replacement/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/inline-edit-a11y-and-types/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/input-number-surface/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/input-signals/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/link-signals/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/list-signals/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/list-tree-multiple-input/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/loader-overlay-signals/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/locale-configuration-providers/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/markdown-signals/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/modal-dialog-semantics/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/modal-signals/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/navbar-signals-and-aria/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/new-icons-pack/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/notification-center-signals/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/option-signals/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/popover-leave-delay/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/progress-bar-percentage-and-aria/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/progress-spinner-signals/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/query-list-signals/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/radio-signals-and-aria/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/read-state-dwell-handlers/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/select-signal-inputs/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/sidepanel-non-modal-behavior/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/signal-inputs-subclasses/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/split-button-optional-disabled/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/state-saving-default/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/table-cell-content/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/tabs-signals-and-aria/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/tag-list-cleaner/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/tags-signals/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/textarea-signals/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/time-range-title-for/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/timepicker-signals/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/timezone-grouping-utils/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/title-encapsulation/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/toast-stack-and-defaults/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/tooltip-pointer-events-and-types/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/top-bar-container-selectors/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/tree-select-signals/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/tree-signals/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/tree-state-saving-default/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/username-pipe-injection/{data.js → data.cjs} +0 -0
- /package/schematics/migrations/zoneless-change-detection/{data.js → data.cjs} +0 -0
- /package/schematics/utils/{ast.js → ast.cjs} +0 -0
- /package/schematics/utils/{messages.js → messages.cjs} +0 -0
|
@@ -2,9 +2,9 @@ import { SelectionModel, DataSource } from '@angular/cdk/collections';
|
|
|
2
2
|
import { BehaviorSubject, merge, Observable, of, Subject, asyncScheduler } from 'rxjs';
|
|
3
3
|
import * as i1 from '@koobiq/components/core';
|
|
4
4
|
import { escapeRegExp, KbqStateSaving, KbqPseudoCheckbox, KbqOptionActionComponent, kbqFocusAndReveal, kbqGetElementHeight, kbqFocusOptionActionOnTab, hasModifierKey, KbqActionContainer, KBQ_TITLE_TEXT_REF, KBQ_OPTION_ACTION_PARENT, MultipleMode, getSelectAllState, kbqQueryListFrom, KbqLocaleOverridesDirective, KBQ_SELECT_LOCALE_CONFIGURATION, kbqOptionalNumberAttribute, kbqSelectAllHandlerTransform, resolveMultipleMode, FocusKeyManager, kbqLeaveTabOrderOnTabOut, SPACE, LEFT_ARROW, RIGHT_ARROW, isVerticalMovement, isSelectAll, isCopy, TAB, DOWN_ARROW, UP_ARROW, ENTER, HOME, END, PAGE_UP, PAGE_DOWN, toggleSelectAll, getKbqSelectNonArrayValueError, KbqPseudoCheckboxModule } from '@koobiq/components/core';
|
|
5
|
-
import { take, map, delay } from 'rxjs/operators';
|
|
5
|
+
import { take, map, delay, filter } from 'rxjs/operators';
|
|
6
6
|
import * as i0 from '@angular/core';
|
|
7
|
-
import { signal, inject, TemplateRef, input, Directive, ViewContainerRef, IterableDiffers, ChangeDetectorRef, viewChild, contentChildren, DestroyRef, effect, untracked, isDevMode, ElementRef, booleanAttribute, computed, ViewEncapsulation, ChangeDetectionStrategy, Component, InjectionToken, EnvironmentInjector, contentChild, linkedSignal, output, EventEmitter, afterNextRender,
|
|
7
|
+
import { signal, inject, TemplateRef, input, Directive, ViewContainerRef, IterableDiffers, ChangeDetectorRef, viewChild, contentChildren, DestroyRef, effect, untracked, isDevMode, ElementRef, booleanAttribute, computed, ViewEncapsulation, ChangeDetectionStrategy, Component, InjectionToken, EnvironmentInjector, NgZone, Renderer2, contentChild, linkedSignal, output, EventEmitter, afterNextRender, forwardRef, Injector, QueryList, NgModule } from '@angular/core';
|
|
8
8
|
import { Directionality } from '@angular/cdk/bidi';
|
|
9
9
|
import { coerceBooleanProperty, coerceNumberProperty } from '@angular/cdk/coercion';
|
|
10
10
|
import { takeUntilDestroyed, toSignal, outputFromObservable } from '@angular/core/rxjs-interop';
|
|
@@ -21,7 +21,7 @@ import { AsyncScheduler } from 'rxjs/internal/scheduler/AsyncScheduler';
|
|
|
21
21
|
/** Base tree control. It has basic toggle/expand/collapse operations on a single data node. */
|
|
22
22
|
class BaseTreeControl {
|
|
23
23
|
constructor() {
|
|
24
|
-
/** A selection model with multi-selection to track expansion status. */
|
|
24
|
+
/** A selection model with multi-selection to track expansion status: it holds the key of every expanded node. */
|
|
25
25
|
this.expansionModel = new SelectionModel(true);
|
|
26
26
|
this.filterModel = new SelectionModel(true);
|
|
27
27
|
this.filterValue = new BehaviorSubject('');
|
|
@@ -31,29 +31,29 @@ class BaseTreeControl {
|
|
|
31
31
|
if (this.filterValue.value?.length) {
|
|
32
32
|
return;
|
|
33
33
|
}
|
|
34
|
-
this.expansionModel.toggle(dataNode);
|
|
34
|
+
this.expansionModel.toggle(this.expansionKey(dataNode));
|
|
35
35
|
}
|
|
36
36
|
/** Expands one single data node. */
|
|
37
37
|
expand(dataNode) {
|
|
38
38
|
if (this.filterValue.value?.length) {
|
|
39
39
|
return;
|
|
40
40
|
}
|
|
41
|
-
this.expansionModel.select(dataNode);
|
|
41
|
+
this.expansionModel.select(this.expansionKey(dataNode));
|
|
42
42
|
}
|
|
43
43
|
/** Collapses one single data node. */
|
|
44
44
|
collapse(dataNode) {
|
|
45
45
|
if (this.filterValue.value?.length) {
|
|
46
46
|
return;
|
|
47
47
|
}
|
|
48
|
-
this.expansionModel.deselect(dataNode);
|
|
48
|
+
this.expansionModel.deselect(this.expansionKey(dataNode));
|
|
49
49
|
}
|
|
50
50
|
/** Whether a given data node is expanded or not. Returns true if the data node is expanded. */
|
|
51
51
|
isExpanded(dataNode) {
|
|
52
|
-
return this.expansionModel.isSelected(dataNode);
|
|
52
|
+
return this.expansionModel.isSelected(this.expansionKey(dataNode));
|
|
53
53
|
}
|
|
54
54
|
/** Toggles a subtree rooted at `node` recursively. */
|
|
55
55
|
toggleDescendants(dataNode) {
|
|
56
|
-
if (this.
|
|
56
|
+
if (this.isExpanded(dataNode)) {
|
|
57
57
|
this.collapseDescendants(dataNode);
|
|
58
58
|
}
|
|
59
59
|
else {
|
|
@@ -68,13 +68,17 @@ class BaseTreeControl {
|
|
|
68
68
|
expandDescendants(dataNode) {
|
|
69
69
|
const toBeProcessed = [dataNode];
|
|
70
70
|
toBeProcessed.push(...this.getDescendants(dataNode));
|
|
71
|
-
this.expansionModel.select(...toBeProcessed);
|
|
71
|
+
this.expansionModel.select(...toBeProcessed.map((node) => this.expansionKey(node)));
|
|
72
72
|
}
|
|
73
73
|
/** Collapses a subtree rooted at given data node recursively. */
|
|
74
74
|
collapseDescendants(dataNode) {
|
|
75
75
|
const toBeProcessed = [dataNode];
|
|
76
76
|
toBeProcessed.push(...this.getDescendants(dataNode));
|
|
77
|
-
this.expansionModel.deselect(...toBeProcessed);
|
|
77
|
+
this.expansionModel.deselect(...toBeProcessed.map((node) => this.expansionKey(node)));
|
|
78
|
+
}
|
|
79
|
+
/** The key `expansionModel` holds for a data node. */
|
|
80
|
+
expansionKey(dataNode) {
|
|
81
|
+
return this.trackBy ? this.trackBy(dataNode) : dataNode;
|
|
78
82
|
}
|
|
79
83
|
}
|
|
80
84
|
|
|
@@ -161,7 +165,9 @@ class FlatTreeControl extends BaseTreeControl {
|
|
|
161
165
|
/** compareValues will be used to comparing values. */
|
|
162
166
|
compareViewValues = defaultCompareViewValues,
|
|
163
167
|
/** isDisabled will be used to determine if the node is disabled. */
|
|
164
|
-
isDisabled = () => false
|
|
168
|
+
isDisabled = () => false,
|
|
169
|
+
/** Further options: the `trackBy` the nodes are expanded by. */
|
|
170
|
+
options) {
|
|
165
171
|
super();
|
|
166
172
|
this.getLevel = getLevel;
|
|
167
173
|
this.isExpandable = isExpandable;
|
|
@@ -170,6 +176,10 @@ class FlatTreeControl extends BaseTreeControl {
|
|
|
170
176
|
this.compareValues = compareValues;
|
|
171
177
|
this.compareViewValues = compareViewValues;
|
|
172
178
|
this.isDisabled = isDisabled;
|
|
179
|
+
// Positions in each `dataNodes` array: a tree looks up every rendered row, and `indexOf` would make that
|
|
180
|
+
// rows × nodes. Keyed weakly by the array, so replacing `dataNodes` releases the old nodes.
|
|
181
|
+
this.nodeIndexes = new WeakMap();
|
|
182
|
+
this.trackBy = options?.trackBy;
|
|
173
183
|
this.setFilters(new FilterByViewValue(this), new FilterParentsForNodes(this));
|
|
174
184
|
}
|
|
175
185
|
// TODO: nothing in the library uses it; kept as public API at the DS-2196 review. Document or deprecate it.
|
|
@@ -186,7 +196,7 @@ class FlatTreeControl extends BaseTreeControl {
|
|
|
186
196
|
* with correct levels.
|
|
187
197
|
*/
|
|
188
198
|
getDescendants(dataNode) {
|
|
189
|
-
const startIndex = this.
|
|
199
|
+
const startIndex = this.indexOfNode(dataNode);
|
|
190
200
|
const results = [];
|
|
191
201
|
// Goes through flattened tree nodes in the `dataNodes` array, and get all descendants.
|
|
192
202
|
// The level of descendants of a tree node must be greater than the level of the given
|
|
@@ -206,7 +216,7 @@ class FlatTreeControl extends BaseTreeControl {
|
|
|
206
216
|
* data nodes of the tree.
|
|
207
217
|
*/
|
|
208
218
|
expandAll() {
|
|
209
|
-
this.expansionModel.select(...this.dataNodes);
|
|
219
|
+
this.expansionModel.select(...this.dataNodes.map((node) => this.expansionKey(node)));
|
|
210
220
|
}
|
|
211
221
|
/**
|
|
212
222
|
* Ancestors of a data node, outermost first.
|
|
@@ -220,7 +230,7 @@ class FlatTreeControl extends BaseTreeControl {
|
|
|
220
230
|
* an existing array; a fresh one is used when omitted.
|
|
221
231
|
*/
|
|
222
232
|
getParents(node, result = []) {
|
|
223
|
-
const startIndex = this.
|
|
233
|
+
const startIndex = this.indexOfNode(node);
|
|
224
234
|
if (startIndex === -1) {
|
|
225
235
|
return result;
|
|
226
236
|
}
|
|
@@ -248,7 +258,7 @@ class FlatTreeControl extends BaseTreeControl {
|
|
|
248
258
|
this.filterModel.clear();
|
|
249
259
|
this.filterModel.select(...result);
|
|
250
260
|
// set current expansion state according to filtered tree
|
|
251
|
-
this.expansionModel.setSelection(...result.filter((node) => this.isExpandable(node)));
|
|
261
|
+
this.expansionModel.setSelection(...result.filter((node) => this.isExpandable(node)).map((node) => this.expansionKey(node)));
|
|
252
262
|
this.updateFilterValue(value || result);
|
|
253
263
|
Promise.resolve().then(() => this.restoreExpansionState());
|
|
254
264
|
}
|
|
@@ -265,6 +275,34 @@ class FlatTreeControl extends BaseTreeControl {
|
|
|
265
275
|
this.expansionModel.setSelection(...this.expandedItemsBeforeFiltration);
|
|
266
276
|
}
|
|
267
277
|
}
|
|
278
|
+
/**
|
|
279
|
+
* Position of the node in `dataNodes`, constant-time while the index matches the array. With `trackBy` a node
|
|
280
|
+
* rebuilt as a new object since is found by its key, as the expansion methods accept it too.
|
|
281
|
+
*/
|
|
282
|
+
indexOfNode(node) {
|
|
283
|
+
const { dataNodes, trackBy } = this;
|
|
284
|
+
if (!dataNodes)
|
|
285
|
+
return -1;
|
|
286
|
+
const cached = this.nodeIndexes.get(dataNodes)?.get(node);
|
|
287
|
+
// Checked against the array: `dataNodes` can be mutated in place.
|
|
288
|
+
if (cached !== undefined && dataNodes[cached] === node)
|
|
289
|
+
return cached;
|
|
290
|
+
const index = dataNodes.indexOf(node);
|
|
291
|
+
// A node that is not there leaves the index as it is.
|
|
292
|
+
if (index === -1) {
|
|
293
|
+
if (!trackBy)
|
|
294
|
+
return index;
|
|
295
|
+
const key = trackBy(node);
|
|
296
|
+
return dataNodes.findIndex((candidate) => trackBy(candidate) === key);
|
|
297
|
+
}
|
|
298
|
+
const positions = new Map();
|
|
299
|
+
dataNodes.forEach((dataNode, position) => {
|
|
300
|
+
if (!positions.has(dataNode))
|
|
301
|
+
positions.set(dataNode, position);
|
|
302
|
+
});
|
|
303
|
+
this.nodeIndexes.set(dataNodes, positions);
|
|
304
|
+
return index;
|
|
305
|
+
}
|
|
268
306
|
}
|
|
269
307
|
|
|
270
308
|
/** Nested tree control. Able to expand/collapse a subtree recursively for NestedNode type. */
|
|
@@ -276,9 +314,10 @@ class NestedTreeControl extends BaseTreeControl {
|
|
|
276
314
|
* `getDescendants` walks the tree with `take(1)` and returns as soon as the recursion unwinds, so
|
|
277
315
|
* an asynchronous source yields an incomplete — usually empty — descendant list.
|
|
278
316
|
*/
|
|
279
|
-
constructor(getChildren) {
|
|
317
|
+
constructor(getChildren, options) {
|
|
280
318
|
super();
|
|
281
319
|
this.getChildren = getChildren;
|
|
320
|
+
this.trackBy = options?.trackBy;
|
|
282
321
|
}
|
|
283
322
|
/**
|
|
284
323
|
* Expands all dataNodes in the tree.
|
|
@@ -289,7 +328,7 @@ class NestedTreeControl extends BaseTreeControl {
|
|
|
289
328
|
expandAll() {
|
|
290
329
|
this.expansionModel.clear();
|
|
291
330
|
const allNodes = this.dataNodes.reduce((accumulator, dataNode) => [...accumulator, ...this.getDescendants(dataNode), dataNode], []);
|
|
292
|
-
this.expansionModel.select(...allNodes);
|
|
331
|
+
this.expansionModel.select(...allNodes.map((node) => this.expansionKey(node)));
|
|
293
332
|
}
|
|
294
333
|
/** Gets a list of descendant dataNodes of a subtree rooted at given data node recursively. */
|
|
295
334
|
getDescendants(dataNode) {
|
|
@@ -398,6 +437,8 @@ class KbqTreeFlattener {
|
|
|
398
437
|
/**
|
|
399
438
|
* Expand flattened node with current expansion status.
|
|
400
439
|
* The returned list may have different length.
|
|
440
|
+
*
|
|
441
|
+
* Reads only `isExpanded` of the tree control, so it takes a control of any key type.
|
|
401
442
|
*/
|
|
402
443
|
expandFlattenedNodes(nodes, treeControl) {
|
|
403
444
|
const results = [];
|
|
@@ -436,8 +477,10 @@ class KbqTreeFlatDataSource extends DataSource {
|
|
|
436
477
|
}
|
|
437
478
|
set data(value) {
|
|
438
479
|
this._data.set(value);
|
|
439
|
-
this.
|
|
440
|
-
|
|
480
|
+
const flattened = this.treeFlattener.flattenNodes(this.data);
|
|
481
|
+
// Assigned before emitting: the tree renders inside `next`, and its rows look themselves up in `dataNodes`.
|
|
482
|
+
this.treeControl.dataNodes = flattened;
|
|
483
|
+
this.flattenedData.next(flattened);
|
|
441
484
|
}
|
|
442
485
|
constructor(treeControl, treeFlattener, initialData = []) {
|
|
443
486
|
super();
|
|
@@ -611,6 +654,8 @@ const normalizeTreeState = (parsed) => {
|
|
|
611
654
|
* ported by hand.
|
|
612
655
|
*
|
|
613
656
|
* `C` narrows the tree control a concrete tree accepts, and so the type its `treeControl` binding is checked against.
|
|
657
|
+
* The bound leaves out `expansionModel` and `trackBy`, the members typed by the control's key, so that it admits
|
|
658
|
+
* a control of any key type: the model makes a control invariant in that type.
|
|
614
659
|
*/
|
|
615
660
|
class KbqTreeBase {
|
|
616
661
|
/** Controls the expanded state and holds the data nodes of the tree. */
|
|
@@ -652,6 +697,8 @@ class KbqTreeBase {
|
|
|
652
697
|
*
|
|
653
698
|
* Views are reused for nodes the function reports as unchanged, which a data source that rebuilds
|
|
654
699
|
* its node objects on every emission — a flattener among them — otherwise cannot get.
|
|
700
|
+
*
|
|
701
|
+
* Defaults to the `trackBy` of the tree control, read when the tree initializes and when the data source changes.
|
|
655
702
|
*/
|
|
656
703
|
this.trackBy = input(undefined, ...(ngDevMode ? [{ debugName: "trackBy" }] : /* istanbul ignore next */ []));
|
|
657
704
|
this.nodeOutletQuery = viewChild(KbqTreeNodeOutlet, ...(ngDevMode ? [{ debugName: "nodeOutletQuery" }] : /* istanbul ignore next */ []));
|
|
@@ -948,7 +995,9 @@ class KbqTreeBase {
|
|
|
948
995
|
// Straight at the model rather than through `expand()`, which is a no-op while a filter is
|
|
949
996
|
// active — restoring must not depend on what the user happens to be searching for.
|
|
950
997
|
if (found.length) {
|
|
951
|
-
|
|
998
|
+
const { trackBy } = treeControl;
|
|
999
|
+
const keys = trackBy ? found.map((node) => trackBy(node)) : found;
|
|
1000
|
+
this.stateSaving.applying(() => treeControl.expansionModel.select(...keys));
|
|
952
1001
|
}
|
|
953
1002
|
}
|
|
954
1003
|
/** The values of the nodes that are currently expanded, plus the ones still waiting to appear. */
|
|
@@ -956,7 +1005,11 @@ class KbqTreeBase {
|
|
|
956
1005
|
const treeControl = this.flatTreeControl;
|
|
957
1006
|
const expanded = [];
|
|
958
1007
|
let skipped = false;
|
|
959
|
-
|
|
1008
|
+
// With `trackBy` the model holds keys, which have no value: only the current nodes map back to them.
|
|
1009
|
+
const expandedNodes = treeControl.trackBy
|
|
1010
|
+
? (treeControl.dataNodes ?? []).filter((node) => treeControl.isExpanded(node))
|
|
1011
|
+
: treeControl.expansionModel.selected;
|
|
1012
|
+
for (const node of expandedNodes) {
|
|
960
1013
|
const value = treeControl.getValue(node);
|
|
961
1014
|
if (typeof value === 'string') {
|
|
962
1015
|
expanded.push(value);
|
|
@@ -1039,7 +1092,10 @@ class KbqTreeBase {
|
|
|
1039
1092
|
}
|
|
1040
1093
|
}
|
|
1041
1094
|
createDataDiffer() {
|
|
1042
|
-
|
|
1095
|
+
// The bound of `C` leaves `trackBy` out; the key type is the control's own, so it is read as `unknown` here.
|
|
1096
|
+
const controlTrackBy = this.treeControl?.trackBy;
|
|
1097
|
+
const trackBy = this.trackBy() ?? (controlTrackBy && ((_, node) => controlTrackBy(node)));
|
|
1098
|
+
return this.differs.find([]).create(trackBy);
|
|
1043
1099
|
}
|
|
1044
1100
|
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: KbqTreeBase, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1045
1101
|
/** @nocollapse */ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "21.2.25", type: KbqTreeBase, isStandalone: true, inputs: { treeControlInput: { classPropertyName: "treeControlInput", publicName: "treeControl", isSignal: true, isRequired: false, transformFunction: null }, trackBy: { classPropertyName: "trackBy", publicName: "trackBy", isSignal: true, isRequired: false, transformFunction: null }, dataSourceInput: { classPropertyName: "dataSourceInput", publicName: "dataSource", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "nodeDefs", predicate: KbqTreeNodeDef, isSignal: true }], viewQueries: [{ propertyName: "nodeOutletQuery", first: true, predicate: KbqTreeNodeOutlet, descendants: true, isSignal: true }], ngImport: i0 }); }
|
|
@@ -1141,7 +1197,9 @@ class KbqTreeNodeToggleBaseDirective {
|
|
|
1141
1197
|
return this.disabledState();
|
|
1142
1198
|
}
|
|
1143
1199
|
get iconState() {
|
|
1144
|
-
|
|
1200
|
+
// The node `toggle()` acts on when `node` is not bound: an unbound `node` would reach the control's
|
|
1201
|
+
// `trackBy` as `undefined`.
|
|
1202
|
+
return this.tree.treeControl.isExpanded(this.node() ?? this.treeNode.data);
|
|
1145
1203
|
}
|
|
1146
1204
|
toggle(event) {
|
|
1147
1205
|
if (this.disabled)
|
|
@@ -1332,6 +1390,8 @@ class KbqTreeOption extends KbqTreeNode {
|
|
|
1332
1390
|
this.changeDetectorRef = inject(ChangeDetectorRef);
|
|
1333
1391
|
this.environmentInjector = inject(EnvironmentInjector);
|
|
1334
1392
|
this.destroyRef = inject(DestroyRef);
|
|
1393
|
+
this.ngZone = inject(NgZone);
|
|
1394
|
+
this.renderer = inject(Renderer2);
|
|
1335
1395
|
this.focusEvents = new Subject();
|
|
1336
1396
|
this.blurEvents = new Subject();
|
|
1337
1397
|
/** Emits when the option takes focus. */
|
|
@@ -1396,6 +1456,9 @@ class KbqTreeOption extends KbqTreeNode {
|
|
|
1396
1456
|
this.selectedState = signal(false, ...(ngDevMode ? [{ debugName: "selectedState" }] : /* istanbul ignore next */ []));
|
|
1397
1457
|
this._id = `kbq-tree-option-${uniqueIdCounter++}`;
|
|
1398
1458
|
this.focused = signal(false, ...(ngDevMode ? [{ debugName: "focused" }] : /* istanbul ignore next */ []));
|
|
1459
|
+
// Raised from reporting a focus until `hasFocus` is: `kbqFocusAndReveal` fires `focusin` synchronously, and its
|
|
1460
|
+
// listener calls back into `focus()` while `hasFocus` is still false.
|
|
1461
|
+
this.focusReported = false;
|
|
1399
1462
|
this.checkbox = signal('unchecked', ...(ngDevMode ? [{ debugName: "checkbox" }] : /* istanbul ignore next */ []));
|
|
1400
1463
|
this.updateCheckboxState = () => {
|
|
1401
1464
|
if (this.selectAllRow())
|
|
@@ -1404,6 +1467,16 @@ class KbqTreeOption extends KbqTreeNode {
|
|
|
1404
1467
|
this.updateParentsCheckboxState(this.data);
|
|
1405
1468
|
};
|
|
1406
1469
|
this.tree = tree;
|
|
1470
|
+
// Not a host listener: one would check the views on every row the pointer crosses, and only a tree in a select
|
|
1471
|
+
// reacts to it.
|
|
1472
|
+
const unlisten = this.ngZone.runOutsideAngular(() => this.renderer.listen(this.elementRef.nativeElement, 'mouseenter', () => this.onMouseenter()));
|
|
1473
|
+
this.destroyRef.onDestroy(unlisten);
|
|
1474
|
+
}
|
|
1475
|
+
ngOnInit() {
|
|
1476
|
+
// Selected from its first check: the tree's sweep reaches a new row a macrotask later, and a row it marked
|
|
1477
|
+
// would render unselected first and then report a selection nobody made.
|
|
1478
|
+
if (!this.selectAllRow())
|
|
1479
|
+
this.selectedState.set(!!this.tree.selectionModel?.isSelected(this.data));
|
|
1407
1480
|
}
|
|
1408
1481
|
ngDoCheck() {
|
|
1409
1482
|
// Asked once per check, for a predicate reading plain state; a set to an equal value does nothing.
|
|
@@ -1497,11 +1570,16 @@ class KbqTreeOption extends KbqTreeNode {
|
|
|
1497
1570
|
return;
|
|
1498
1571
|
// With the pointer already on this node, revealing it would shift the tree out from under it.
|
|
1499
1572
|
kbqFocusAndReveal(this.elementRef.nativeElement, focusOrigin === 'mouse');
|
|
1500
|
-
if (!this.hasFocus) {
|
|
1501
|
-
|
|
1573
|
+
if (!this.hasFocus && !this.focusReported) {
|
|
1574
|
+
const event = { option: this };
|
|
1575
|
+
this.focusReported = true;
|
|
1576
|
+
this.focusEvents.next(event);
|
|
1577
|
+
this.tree.reportOptionFocus?.(event);
|
|
1502
1578
|
Promise.resolve().then(() => {
|
|
1579
|
+
this.focusReported = false;
|
|
1503
1580
|
this.hasFocus = true;
|
|
1504
|
-
|
|
1581
|
+
// The view only: focus changes no checkbox state.
|
|
1582
|
+
this.changeDetectorRef.markForCheck();
|
|
1505
1583
|
});
|
|
1506
1584
|
}
|
|
1507
1585
|
});
|
|
@@ -1522,7 +1600,9 @@ class KbqTreeOption extends KbqTreeNode {
|
|
|
1522
1600
|
return;
|
|
1523
1601
|
}
|
|
1524
1602
|
this.hasFocus = false;
|
|
1525
|
-
|
|
1603
|
+
const event = { option: this };
|
|
1604
|
+
this.blurEvents.next(event);
|
|
1605
|
+
this.tree.reportOptionBlur?.(event);
|
|
1526
1606
|
},
|
|
1527
1607
|
// The environment injector, not the option's: an option removed while focused is blurred as its view is
|
|
1528
1608
|
// destroyed, and the tree still has to hear about it to move focus.
|
|
@@ -1552,7 +1632,8 @@ class KbqTreeOption extends KbqTreeNode {
|
|
|
1552
1632
|
if (setFocus && !this.hasFocus) {
|
|
1553
1633
|
this.focus();
|
|
1554
1634
|
}
|
|
1555
|
-
this.
|
|
1635
|
+
// Its own state only: an ancestor's reads the selection model, which this leaves as it is.
|
|
1636
|
+
this.syncCheckboxState();
|
|
1556
1637
|
this.emitSelectionChangeEvent();
|
|
1557
1638
|
});
|
|
1558
1639
|
}
|
|
@@ -1562,7 +1643,8 @@ class KbqTreeOption extends KbqTreeNode {
|
|
|
1562
1643
|
return;
|
|
1563
1644
|
}
|
|
1564
1645
|
this.selectedState.set(false);
|
|
1565
|
-
|
|
1646
|
+
// Its own state only, as in `select`.
|
|
1647
|
+
this.syncCheckboxState();
|
|
1566
1648
|
this.emitSelectionChangeEvent();
|
|
1567
1649
|
});
|
|
1568
1650
|
}
|
|
@@ -1594,8 +1676,9 @@ class KbqTreeOption extends KbqTreeNode {
|
|
|
1594
1676
|
}
|
|
1595
1677
|
markForCheck() {
|
|
1596
1678
|
this.changeDetectorRef.markForCheck();
|
|
1597
|
-
// Its own state only: after a selection, data or mode change the tree calls this on every option.
|
|
1598
|
-
|
|
1679
|
+
// Its own state only: after a selection, data or mode change the tree calls this on every option. A projected
|
|
1680
|
+
// checkbox shows the state while the tree's own is hidden.
|
|
1681
|
+
if (this.showCheckbox || this.externalPseudoCheckbox) {
|
|
1599
1682
|
this.syncCheckboxState();
|
|
1600
1683
|
}
|
|
1601
1684
|
}
|
|
@@ -1603,15 +1686,18 @@ class KbqTreeOption extends KbqTreeNode {
|
|
|
1603
1686
|
onMouseenter() {
|
|
1604
1687
|
if (this.disabled || !this.tree.inSelect)
|
|
1605
1688
|
return;
|
|
1606
|
-
|
|
1689
|
+
// Focusing the option it already holds would only run change detection.
|
|
1690
|
+
if (this.hasFocus && _getFocusedElementPierceShadowDom() === this.elementRef.nativeElement)
|
|
1691
|
+
return;
|
|
1692
|
+
this.ngZone.run(() => this.focus('mouse'));
|
|
1607
1693
|
}
|
|
1608
1694
|
/** Recomputes the option's own checkbox state, leaving its ancestors as they are. */
|
|
1609
1695
|
syncCheckboxState() {
|
|
1610
1696
|
if (this.selectAllRow())
|
|
1611
1697
|
return;
|
|
1612
1698
|
if (this.checkboxThirdState() && this.isExpandable) {
|
|
1613
|
-
// One descendant walk, not two:
|
|
1614
|
-
//
|
|
1699
|
+
// One descendant walk, not two: each walk covers the whole subtree, and the roll-up runs this method for
|
|
1700
|
+
// every ancestor.
|
|
1615
1701
|
const descendants = this.tree.treeControl.getDescendants(this.data);
|
|
1616
1702
|
const selectedCount = descendants.filter((child) => this.tree.selectionModel.isSelected(child)).length;
|
|
1617
1703
|
if (selectedCount === descendants.length) {
|
|
@@ -1636,7 +1722,7 @@ class KbqTreeOption extends KbqTreeNode {
|
|
|
1636
1722
|
return { disabled: this.disabled, expanded: branch ? this.isExpanded : null };
|
|
1637
1723
|
}
|
|
1638
1724
|
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: KbqTreeOption, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1639
|
-
/** @nocollapse */ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: KbqTreeOption, isStandalone: true, selector: "kbq-tree-option", inputs: { checkboxThirdState: { classPropertyName: "checkboxThirdState", publicName: "checkboxThirdState", isSignal: true, isRequired: false, transformFunction: null }, disabledInput: { classPropertyName: "disabledInput", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, selectAllRow: { classPropertyName: "selectAllRow", publicName: "selectAllRow", isSignal: true, isRequired: false, transformFunction: null }, showCheckboxInput: { classPropertyName: "showCheckboxInput", publicName: "showCheckbox", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectionChange: "selectionChange", onSelectionChange: "onSelectionChange" }, host: { attributes: { "role": "treeitem" }, listeners: { "focusin": "focus()", "
|
|
1725
|
+
/** @nocollapse */ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: KbqTreeOption, isStandalone: true, selector: "kbq-tree-option", inputs: { checkboxThirdState: { classPropertyName: "checkboxThirdState", publicName: "checkboxThirdState", isSignal: true, isRequired: false, transformFunction: null }, disabledInput: { classPropertyName: "disabledInput", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, selectAllRow: { classPropertyName: "selectAllRow", publicName: "selectAllRow", isSignal: true, isRequired: false, transformFunction: null }, showCheckboxInput: { classPropertyName: "showCheckboxInput", publicName: "showCheckbox", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectionChange: "selectionChange", onSelectionChange: "onSelectionChange" }, host: { attributes: { "role": "treeitem" }, listeners: { "focusin": "focus()", "blur": "blur()", "click": "selectViaInteraction($event)", "keydown": "onKeydown($event)" }, properties: { "class.kbq-tree-option_multiple": "tree.multiple", "class.kbq-selected": "selected", "class.kbq-focused": "hasFocus", "class.kbq-disabled": "checkedDisabled", "class.kbq-action-button-focused": "actionButton()?.active", "attr.id": "id", "attr.tabindex": "-1", "attr.aria-disabled": "checkedDisabled || null", "attr.aria-expanded": "ariaExpanded", "attr.aria-checked": "ariaChecked", "attr.aria-selected": "ariaSelected" }, classAttribute: "kbq-tree-option" }, providers: [
|
|
1640
1726
|
{ provide: KbqTreeNode, useExisting: KbqTreeOption },
|
|
1641
1727
|
{ provide: KBQ_TITLE_TEXT_REF, useExisting: KbqTreeOption },
|
|
1642
1728
|
{ provide: KBQ_OPTION_ACTION_PARENT, useExisting: KbqTreeOption }
|
|
@@ -1669,7 +1755,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImpo
|
|
|
1669
1755
|
'[attr.aria-checked]': 'ariaChecked',
|
|
1670
1756
|
'[attr.aria-selected]': 'ariaSelected',
|
|
1671
1757
|
'(focusin)': 'focus()',
|
|
1672
|
-
'(mouseenter)': 'onMouseenter()',
|
|
1673
1758
|
'(blur)': 'blur()',
|
|
1674
1759
|
'(click)': 'selectViaInteraction($event)',
|
|
1675
1760
|
'(keydown)': 'onKeydown($event)'
|
|
@@ -1861,11 +1946,13 @@ class KbqTreeSelection extends KbqTreeBase {
|
|
|
1861
1946
|
set autoSelect(value) {
|
|
1862
1947
|
this.autoSelectState.set(coerceBooleanProperty(value));
|
|
1863
1948
|
}
|
|
1949
|
+
/** Emits when an option of the tree takes focus. */
|
|
1864
1950
|
get optionFocusChanges() {
|
|
1865
|
-
return
|
|
1951
|
+
return this.optionFocusEvents.asObservable();
|
|
1866
1952
|
}
|
|
1953
|
+
/** Emits when an option of the tree loses focus. */
|
|
1867
1954
|
get optionBlurChanges() {
|
|
1868
|
-
return
|
|
1955
|
+
return this.optionBlurEvents.asObservable();
|
|
1869
1956
|
}
|
|
1870
1957
|
/**
|
|
1871
1958
|
* Selection mode of the tree.
|
|
@@ -1965,16 +2052,16 @@ class KbqTreeSelection extends KbqTreeBase {
|
|
|
1965
2052
|
if (!this.treeControl) {
|
|
1966
2053
|
return [];
|
|
1967
2054
|
}
|
|
1968
|
-
const nonSelectableDataNodes = this.renderedOptions
|
|
2055
|
+
const nonSelectableDataNodes = new Set(this.renderedOptions
|
|
1969
2056
|
.filter((option) => option.disabled || !option.selectable())
|
|
1970
|
-
.map((option) => option.data);
|
|
2057
|
+
.map((option) => option.data));
|
|
1971
2058
|
const candidates = this.treeControl.filterValue.value?.length
|
|
1972
2059
|
? this.renderedOptions.map((option) => option.data)
|
|
1973
2060
|
: (this.treeControl.dataNodes ?? []);
|
|
1974
|
-
// The `
|
|
2061
|
+
// The `has` check comes first on purpose: the "select all" row is itself a non-selectable
|
|
1975
2062
|
// rendered option with no `data`, and `isDisabled` belongs to the consumer — it must never be
|
|
1976
2063
|
// handed a node their data source has never seen.
|
|
1977
|
-
return candidates.filter((node) => !nonSelectableDataNodes.
|
|
2064
|
+
return candidates.filter((node) => !nonSelectableDataNodes.has(node) && !this.treeControl.isDisabled(node));
|
|
1978
2065
|
}
|
|
1979
2066
|
/** Adapter shared by the master checkbox and the Ctrl/Cmd + A handler, so the two cannot drift apart. */
|
|
1980
2067
|
get selectAllAdapter() {
|
|
@@ -2084,6 +2171,9 @@ class KbqTreeSelection extends KbqTreeBase {
|
|
|
2084
2171
|
transform: booleanAttribute });
|
|
2085
2172
|
/** `undefined` while the value is derived from the mode. Written by `KbqTreeSelect` as well. */
|
|
2086
2173
|
this.autoSelectState = linkedSignal(() => this.autoSelectInput(), ...(ngDevMode ? [{ debugName: "autoSelectState" }] : /* istanbul ignore next */ []));
|
|
2174
|
+
// Options report here, so a subscriber also hears the rows rendered after it subscribed.
|
|
2175
|
+
this.optionFocusEvents = new Subject();
|
|
2176
|
+
this.optionBlurEvents = new Subject();
|
|
2087
2177
|
/** @docs-private */
|
|
2088
2178
|
this.multipleInput = input(undefined, { ...(ngDevMode ? { debugName: "multipleInput" } : /* istanbul ignore next */ {}), alias: 'multiple' });
|
|
2089
2179
|
/**
|
|
@@ -2117,6 +2207,9 @@ class KbqTreeSelection extends KbqTreeBase {
|
|
|
2117
2207
|
transform: kbqOptionalNumberAttribute });
|
|
2118
2208
|
this.tabIndexState = signal(0, ...(ngDevMode ? [{ debugName: "tabIndexState" }] : /* istanbul ignore next */ []));
|
|
2119
2209
|
this.selectionModelSubscription = null;
|
|
2210
|
+
// A selection change asks for a sweep here, not through `renderedOptions.changes`: the key manager and an
|
|
2211
|
+
// embedding select listen to that, and no row changed.
|
|
2212
|
+
this.selectionSweeps = new Subject();
|
|
2120
2213
|
/** Whether a value report is queued for the end of the current tick. Cleared by `writeValue` to cancel it. */
|
|
2121
2214
|
this.pendingValueReport = false;
|
|
2122
2215
|
/** Backs {@link syncingSelection}: a change of the selection made by code is no change to report back. */
|
|
@@ -2153,11 +2246,19 @@ class KbqTreeSelection extends KbqTreeBase {
|
|
|
2153
2246
|
if (selectAllOption) {
|
|
2154
2247
|
orderedOptions.push(selectAllOption);
|
|
2155
2248
|
}
|
|
2249
|
+
// Indexed once per render, as a search per node is quadratic in the rows. The first option wins in both maps.
|
|
2250
|
+
const optionsByNode = new Map();
|
|
2251
|
+
const optionsByValue = new Map();
|
|
2252
|
+
this.unorderedOptions.forEach((option) => {
|
|
2253
|
+
if (!optionsByNode.has(option.data))
|
|
2254
|
+
optionsByNode.set(option.data, option);
|
|
2255
|
+
if (!optionsByValue.has(option.value))
|
|
2256
|
+
optionsByValue.set(option.value, option);
|
|
2257
|
+
});
|
|
2156
2258
|
this.sortedNodes.forEach((node) => {
|
|
2157
2259
|
// By node first: nodes do not have to carry distinct values, and a value match would hand every node with
|
|
2158
2260
|
// the same value the first option holding it.
|
|
2159
|
-
const found =
|
|
2160
|
-
this.unorderedOptions.find((option) => option.value === this.treeControl.getValue(node));
|
|
2261
|
+
const found = optionsByNode.get(node) ?? optionsByValue.get(this.treeControl.getValue(node));
|
|
2161
2262
|
if (found) {
|
|
2162
2263
|
orderedOptions.push(found);
|
|
2163
2264
|
}
|
|
@@ -2330,7 +2431,7 @@ class KbqTreeSelection extends KbqTreeBase {
|
|
|
2330
2431
|
if (!this.writingValue) {
|
|
2331
2432
|
this.reportValue();
|
|
2332
2433
|
}
|
|
2333
|
-
this.
|
|
2434
|
+
this.selectionSweeps.next();
|
|
2334
2435
|
// The "select all" row renders the selection state.
|
|
2335
2436
|
this.changeDetectorRef.markForCheck();
|
|
2336
2437
|
});
|
|
@@ -2360,16 +2461,20 @@ class KbqTreeSelection extends KbqTreeBase {
|
|
|
2360
2461
|
});
|
|
2361
2462
|
kbqLeaveTabOrderOnTabOut(this.keyManager.tabOut, this.elementRef.nativeElement, () => this.tabIndex, this.destroyRef);
|
|
2362
2463
|
this.bindSelectionModel();
|
|
2363
|
-
this.
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2464
|
+
this.listenToOptionsFocus();
|
|
2465
|
+
// A render and the selection changes of one task each notify, each with its own timer: the first to fire sweeps
|
|
2466
|
+
// for all of them and the rest are dropped. `auditTime` would tie the later ones to the first one's timer.
|
|
2467
|
+
let sweeps = 0;
|
|
2468
|
+
merge(this.renderedOptions.changes, this.selectionSweeps)
|
|
2469
|
+
.pipe(map(() => sweeps), delay(0, this.scheduler), filter((sweepsWhenNotified) => sweepsWhenNotified === sweeps), takeUntilDestroyed(this.destroyRef))
|
|
2470
|
+
.subscribe(() => {
|
|
2471
|
+
sweeps++;
|
|
2367
2472
|
// Check to see if we need to update our tab index
|
|
2368
2473
|
this.updateTabIndex();
|
|
2369
2474
|
this.syncSelectionModelToDataNodes();
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
if (
|
|
2475
|
+
this.renderedOptions.forEach((option) => {
|
|
2476
|
+
// By node rather than by value: rows may share a value.
|
|
2477
|
+
if (this.selectionModel.isSelected(option.data)) {
|
|
2373
2478
|
option.select(false);
|
|
2374
2479
|
}
|
|
2375
2480
|
else {
|
|
@@ -2410,7 +2515,8 @@ class KbqTreeSelection extends KbqTreeBase {
|
|
|
2410
2515
|
}
|
|
2411
2516
|
ngOnDestroy() {
|
|
2412
2517
|
super.ngOnDestroy();
|
|
2413
|
-
this.
|
|
2518
|
+
this.optionFocusEvents.complete();
|
|
2519
|
+
this.optionBlurEvents.complete();
|
|
2414
2520
|
this.focusMonitor.stopMonitoring(this.elementRef);
|
|
2415
2521
|
}
|
|
2416
2522
|
focus($event) {
|
|
@@ -2576,6 +2682,14 @@ class KbqTreeSelection extends KbqTreeBase {
|
|
|
2576
2682
|
}
|
|
2577
2683
|
}
|
|
2578
2684
|
/** @docs-private */
|
|
2685
|
+
reportOptionFocus(event) {
|
|
2686
|
+
this.optionFocusEvents.next(event);
|
|
2687
|
+
}
|
|
2688
|
+
/** @docs-private */
|
|
2689
|
+
reportOptionBlur(event) {
|
|
2690
|
+
this.optionBlurEvents.next(event);
|
|
2691
|
+
}
|
|
2692
|
+
/** @docs-private */
|
|
2579
2693
|
setSelectedOptionsByClick(option, shiftKey, ctrlKey) {
|
|
2580
2694
|
if (option.disabled || !option.selectable()) {
|
|
2581
2695
|
return;
|
|
@@ -2607,7 +2721,8 @@ class KbqTreeSelection extends KbqTreeBase {
|
|
|
2607
2721
|
const options = this.renderedOptions.toArray();
|
|
2608
2722
|
let fromIndex = this.keyManager.previousActiveItemIndex;
|
|
2609
2723
|
let toIndex = (this.keyManager.previousActiveItemIndex = this.keyManager.activeItemIndex);
|
|
2610
|
-
|
|
2724
|
+
// Asked of the model: an option's own state lags it until the next sweep.
|
|
2725
|
+
const selectedOptionState = this.selectionModel.isSelected(options[fromIndex]?.data);
|
|
2611
2726
|
if (toIndex === fromIndex || fromIndex === -1) {
|
|
2612
2727
|
if (options[toIndex]?.selectable()) {
|
|
2613
2728
|
options[toIndex].toggle();
|
|
@@ -2617,15 +2732,21 @@ class KbqTreeSelection extends KbqTreeBase {
|
|
|
2617
2732
|
if (fromIndex > toIndex) {
|
|
2618
2733
|
[fromIndex, toIndex] = [toIndex, fromIndex];
|
|
2619
2734
|
}
|
|
2620
|
-
options
|
|
2735
|
+
const nodes = options
|
|
2621
2736
|
.slice(fromIndex, toIndex + 1)
|
|
2622
2737
|
.filter((item) => !item.disabled && item.selectable())
|
|
2623
|
-
.
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
}
|
|
2738
|
+
.map((item) => item.data);
|
|
2739
|
+
// One write for the range, so the selection changes once rather than once per row.
|
|
2740
|
+
if (selectedOptionState) {
|
|
2741
|
+
this.selectionModel.select(...nodes);
|
|
2742
|
+
return;
|
|
2743
|
+
}
|
|
2744
|
+
const selected = nodes.filter((node) => this.selectionModel.isSelected(node));
|
|
2745
|
+
// With `noUnselectLast` the last selected node stays, the range giving up its own in order until then.
|
|
2746
|
+
const released = this.noUnselectLast
|
|
2747
|
+
? Math.max(0, Math.min(selected.length, this.selectionModel.selected.length - 1))
|
|
2748
|
+
: selected.length;
|
|
2749
|
+
this.selectionModel.deselect(...selected.slice(0, released));
|
|
2629
2750
|
}
|
|
2630
2751
|
/** @docs-private */
|
|
2631
2752
|
toggleFocusedOption() {
|
|
@@ -2651,12 +2772,16 @@ class KbqTreeSelection extends KbqTreeBase {
|
|
|
2651
2772
|
const changed = this.applyNodeChanges(data, dataDiffer, viewContainer, parentData);
|
|
2652
2773
|
this.sortedNodes = this.getSortedNodes(viewContainer);
|
|
2653
2774
|
this.changeDetectorRef.detectChanges();
|
|
2775
|
+
// The options query lists the options in the order their views were created, not in the order of the rows, so
|
|
2776
|
+
// rows that only moved (reused through `trackBy`) never make it emit.
|
|
2777
|
+
if (changed && this.renderedOptionsOutOfOrder())
|
|
2778
|
+
this.updateRenderedOptions();
|
|
2654
2779
|
this.scheduleCheckboxSync(false);
|
|
2655
2780
|
// The "select all" row is rendered ahead of what its state reads: new rows bind their inputs after it and join
|
|
2656
|
-
// `renderedOptions` only once bound, and
|
|
2657
|
-
// else in the tree reads that state but the checkbox sync above, so a tree without the row is done in
|
|
2658
|
-
// pass. Marked for check, which reaches the views above too: a template reading `allOptionsSelected`
|
|
2659
|
-
// reference reads the same state.
|
|
2781
|
+
// `renderedOptions` only once bound, and a consumer's data source can assign `dataNodes` after it emits.
|
|
2782
|
+
// Nothing else in the tree reads that state but the checkbox sync above, so a tree without the row is done in
|
|
2783
|
+
// this one pass. Marked for check, which reaches the views above too: a template reading `allOptionsSelected`
|
|
2784
|
+
// by reference reads the same state.
|
|
2660
2785
|
if (!this.showSelectAll)
|
|
2661
2786
|
return;
|
|
2662
2787
|
const dataNodes = this.treeControl.dataNodes;
|
|
@@ -2668,7 +2793,7 @@ class KbqTreeSelection extends KbqTreeBase {
|
|
|
2668
2793
|
/**
|
|
2669
2794
|
* Recomputes every option's checkbox state where no selection change follows to do it: after `dataNodes` is
|
|
2670
2795
|
* replaced, which moves the descendants a branch counts, and once a hidden checkbox appears. A microtask later, as
|
|
2671
|
-
* a
|
|
2796
|
+
* a consumer's data source can assign `dataNodes` after it emits.
|
|
2672
2797
|
*/
|
|
2673
2798
|
scheduleCheckboxSync(force) {
|
|
2674
2799
|
Promise.resolve().then(() => {
|
|
@@ -2711,11 +2836,23 @@ class KbqTreeSelection extends KbqTreeBase {
|
|
|
2711
2836
|
// Selection is applied at the data-node level (incl. collapsed/non-rendered nodes unless a filter
|
|
2712
2837
|
// is active), while the emitted events carry the selectable rendered options.
|
|
2713
2838
|
const selectableOptions = this.renderedOptions.filter((option) => !option.disabled && option.selectable());
|
|
2839
|
+
// A multiple selection takes the nodes in one write, so it changes once rather than once per node. A single
|
|
2840
|
+
// one cannot hold them, and keeps the node-by-node writes it always had.
|
|
2841
|
+
const batched = this.selectionModel.isMultipleSelection();
|
|
2842
|
+
const adapter = this.selectAllAdapter;
|
|
2714
2843
|
// `toggleSelectAll` returns the data nodes whose selection actually flipped — the source of
|
|
2715
2844
|
// truth, unlike the cached `option.selected` which lags until change detection.
|
|
2716
|
-
const changed = toggleSelectAll(
|
|
2845
|
+
const changed = toggleSelectAll(batched ? { ...adapter, setSelected: () => { } } : adapter, {
|
|
2717
2846
|
allowDeselect: allowDeselect ?? (this.selectAll || this.selectAllToggle())
|
|
2718
2847
|
});
|
|
2848
|
+
if (batched && changed.length) {
|
|
2849
|
+
if (this.selectionModel.isSelected(changed[0])) {
|
|
2850
|
+
this.selectionModel.deselect(...changed);
|
|
2851
|
+
}
|
|
2852
|
+
else {
|
|
2853
|
+
this.selectionModel.select(...changed);
|
|
2854
|
+
}
|
|
2855
|
+
}
|
|
2719
2856
|
const changedData = new Set(changed);
|
|
2720
2857
|
const changedOptions = selectableOptions.filter((option) => changedData.has(option.data));
|
|
2721
2858
|
// Skip `selectionChange` on a no-op (e.g. Ctrl+A while everything is already selected and
|
|
@@ -2924,6 +3061,17 @@ class KbqTreeSelection extends KbqTreeBase {
|
|
|
2924
3061
|
updateTabIndex() {
|
|
2925
3062
|
this.tabIndexState.set(this.userTabIndex || (this.renderedOptions.length === 0 ? -1 : 0));
|
|
2926
3063
|
}
|
|
3064
|
+
/**
|
|
3065
|
+
* Whether the same options as before now stand for the rows in another order. A different count means rows were
|
|
3066
|
+
* added or removed, which the options query reports itself.
|
|
3067
|
+
*/
|
|
3068
|
+
renderedOptionsOutOfOrder() {
|
|
3069
|
+
const options = this.renderedOptions.toArray();
|
|
3070
|
+
const offset = this.selectAllOption() ? 1 : 0;
|
|
3071
|
+
if (options.length - offset !== this.sortedNodes.length)
|
|
3072
|
+
return false;
|
|
3073
|
+
return this.sortedNodes.some((node, index) => options[index + offset].data !== node);
|
|
3074
|
+
}
|
|
2927
3075
|
getSortedNodes(viewContainer) {
|
|
2928
3076
|
const array = [];
|
|
2929
3077
|
for (let i = 0; i < viewContainer.length; i++) {
|
|
@@ -2932,22 +3080,9 @@ class KbqTreeSelection extends KbqTreeBase {
|
|
|
2932
3080
|
}
|
|
2933
3081
|
return array;
|
|
2934
3082
|
}
|
|
2935
|
-
resetOptions() {
|
|
2936
|
-
this.dropSubscriptions();
|
|
2937
|
-
this.listenToOptionsFocus();
|
|
2938
|
-
}
|
|
2939
|
-
dropSubscriptions() {
|
|
2940
|
-
if (this.optionFocusSubscription) {
|
|
2941
|
-
this.optionFocusSubscription.unsubscribe();
|
|
2942
|
-
this.optionFocusSubscription = null;
|
|
2943
|
-
}
|
|
2944
|
-
if (this.optionBlurSubscription) {
|
|
2945
|
-
this.optionBlurSubscription.unsubscribe();
|
|
2946
|
-
this.optionBlurSubscription = null;
|
|
2947
|
-
}
|
|
2948
|
-
}
|
|
2949
3083
|
listenToOptionsFocus() {
|
|
2950
|
-
|
|
3084
|
+
// Both streams complete with the tree.
|
|
3085
|
+
this.optionFocusEvents.subscribe((event) => {
|
|
2951
3086
|
const index = this.renderedOptions.toArray().indexOf(event.option);
|
|
2952
3087
|
this.renderedOptions.filter((option) => option.hasFocus).forEach((option) => (option.hasFocus = false));
|
|
2953
3088
|
if (this.isValidIndex(index)) {
|
|
@@ -2959,7 +3094,7 @@ class KbqTreeSelection extends KbqTreeBase {
|
|
|
2959
3094
|
// set, because `KbqTreeOption.hasFocus` is cleared synchronously but only raised a microtask later, so
|
|
2960
3095
|
// `hasFocusedOption()` reports nothing focused for the whole move. Only a blur of the option the
|
|
2961
3096
|
// key manager still points at is the focus actually leaving the options.
|
|
2962
|
-
this.
|
|
3097
|
+
this.optionBlurEvents.subscribe(({ option }) => {
|
|
2963
3098
|
if (option === this.keyManager.activeItem && !this.hasFocusedOption() && this.resetFocusedItemOnBlur) {
|
|
2964
3099
|
this.keyManager.setActiveItem(-1);
|
|
2965
3100
|
}
|