@akcelik/strct 4.2.1 → 4.4.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.
- package/fesm2022/akcelik-strct.mjs +382 -71
- package/fesm2022/akcelik-strct.mjs.map +1 -1
- package/package.json +1 -1
- package/types/akcelik-strct.d.ts +107 -3
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, DOCUMENT, signal, computed, Injectable, input, isDevMode, ViewEncapsulation, ChangeDetectionStrategy, Component, ElementRef, NgZone, booleanAttribute, afterNextRender, Directive, model, output, HostListener, contentChildren, effect, ApplicationRef, EnvironmentInjector, createComponent, forwardRef, DestroyRef, TemplateRef, contentChild, viewChild, InjectionToken, untracked, Injector, viewChildren, Renderer2, Pipe,
|
|
2
|
+
import { inject, DOCUMENT, signal, computed, Injectable, input, isDevMode, ViewEncapsulation, ChangeDetectionStrategy, Component, ElementRef, NgZone, booleanAttribute, afterNextRender, Directive, model, output, HostListener, contentChildren, effect, ApplicationRef, EnvironmentInjector, createComponent, PLATFORM_ID, forwardRef, DestroyRef, TemplateRef, contentChild, viewChild, InjectionToken, untracked, Injector, viewChildren, Renderer2, Pipe, afterRenderEffect } from '@angular/core';
|
|
3
3
|
import { DomSanitizer } from '@angular/platform-browser';
|
|
4
4
|
import { NgTemplateOutlet, DOCUMENT as DOCUMENT$1, isPlatformBrowser } from '@angular/common';
|
|
5
5
|
import { RouterLink, RouterLinkActive } from '@angular/router';
|
|
@@ -2791,6 +2791,61 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
|
|
|
2791
2791
|
args: ['contextmenu', ['$event']]
|
|
2792
2792
|
}] } });
|
|
2793
2793
|
|
|
2794
|
+
/**
|
|
2795
|
+
* Screen-reader announcements for state changes that have no visible text —
|
|
2796
|
+
* "12 rows loaded", "Snapshot created" (Material's LiveAnnouncer, strct-sized):
|
|
2797
|
+
*
|
|
2798
|
+
* private announcer = inject(StrctAnnouncer);
|
|
2799
|
+
* this.announcer.announce('12 rows loaded');
|
|
2800
|
+
*
|
|
2801
|
+
* Maintains one visually-hidden live region per politeness level; repeated
|
|
2802
|
+
* identical messages are re-announced (the region is cleared first).
|
|
2803
|
+
*/
|
|
2804
|
+
class StrctAnnouncer {
|
|
2805
|
+
regions = new Map();
|
|
2806
|
+
clearTimers = new Map();
|
|
2807
|
+
isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
|
|
2808
|
+
announce(message, politeness = 'polite') {
|
|
2809
|
+
if (!this.isBrowser)
|
|
2810
|
+
return;
|
|
2811
|
+
const region = this.regionFor(politeness);
|
|
2812
|
+
// Clear first so identical consecutive messages still fire.
|
|
2813
|
+
region.textContent = '';
|
|
2814
|
+
setTimeout(() => (region.textContent = message));
|
|
2815
|
+
clearTimeout(this.clearTimers.get(politeness));
|
|
2816
|
+
// Stale announcements should not linger for the next SR user to stumble on.
|
|
2817
|
+
this.clearTimers.set(politeness, setTimeout(() => (region.textContent = ''), 10_000));
|
|
2818
|
+
}
|
|
2819
|
+
regionFor(politeness) {
|
|
2820
|
+
let region = this.regions.get(politeness);
|
|
2821
|
+
if (!region) {
|
|
2822
|
+
region = document.createElement('div');
|
|
2823
|
+
region.setAttribute('aria-live', politeness);
|
|
2824
|
+
region.setAttribute('aria-atomic', 'true');
|
|
2825
|
+
region.className = 'strct-announcer';
|
|
2826
|
+
region.style.cssText =
|
|
2827
|
+
'position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;';
|
|
2828
|
+
document.body.appendChild(region);
|
|
2829
|
+
this.regions.set(politeness, region);
|
|
2830
|
+
}
|
|
2831
|
+
return region;
|
|
2832
|
+
}
|
|
2833
|
+
ngOnDestroy() {
|
|
2834
|
+
for (const timer of this.clearTimers.values())
|
|
2835
|
+
clearTimeout(timer);
|
|
2836
|
+
this.clearTimers.clear();
|
|
2837
|
+
for (const region of this.regions.values())
|
|
2838
|
+
region.remove();
|
|
2839
|
+
this.regions.clear();
|
|
2840
|
+
}
|
|
2841
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctAnnouncer, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2842
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctAnnouncer, providedIn: 'root' });
|
|
2843
|
+
}
|
|
2844
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctAnnouncer, decorators: [{
|
|
2845
|
+
type: Injectable,
|
|
2846
|
+
args: [{ providedIn: 'root' }]
|
|
2847
|
+
}] });
|
|
2848
|
+
|
|
2794
2849
|
/** Default chevron aria-label factory — mirrors the `chevronAriaLabel` input default. */
|
|
2795
2850
|
const defaultChevronAriaLabel = (expanded, label) => `${expanded ? 'Collapse' : 'Expand'} ${label}`;
|
|
2796
2851
|
let treeNodeCounter = 0;
|
|
@@ -2831,6 +2886,67 @@ class StrctTreeNode {
|
|
|
2831
2886
|
nodeActivated = output();
|
|
2832
2887
|
/** Data-mode right-click menu selection (bubbles to the tree). */
|
|
2833
2888
|
nodeMenuSelect = output();
|
|
2889
|
+
/** Whether this row can be picked up — the tree asks the consumer's `canDrag`.
|
|
2890
|
+
* Reads `nodes()` too, so a refresh that makes a node movable is honoured. */
|
|
2891
|
+
isDraggable = computed(() => {
|
|
2892
|
+
const n = this.node();
|
|
2893
|
+
this.tree?.nodes();
|
|
2894
|
+
return !!n && !!this.tree?.canDragNode(n);
|
|
2895
|
+
}, /* @ts-ignore */
|
|
2896
|
+
...(ngDevMode ? [{ debugName: "isDraggable" }] : /* istanbul ignore next */ []));
|
|
2897
|
+
isDragSource = computed(() => {
|
|
2898
|
+
const n = this.node();
|
|
2899
|
+
return !!n && !!this.tree?.isDragSource(n);
|
|
2900
|
+
}, /* @ts-ignore */
|
|
2901
|
+
...(ngDevMode ? [{ debugName: "isDragSource" }] : /* istanbul ignore next */ []));
|
|
2902
|
+
isDropTarget = computed(() => {
|
|
2903
|
+
const n = this.node();
|
|
2904
|
+
return !!n && !!this.tree?.isDropTarget(n);
|
|
2905
|
+
}, /* @ts-ignore */
|
|
2906
|
+
...(ngDevMode ? [{ debugName: "isDropTarget" }] : /* istanbul ignore next */ []));
|
|
2907
|
+
onDragStart(event) {
|
|
2908
|
+
const n = this.node();
|
|
2909
|
+
if (!n || !this.tree || !this.isDraggable())
|
|
2910
|
+
return;
|
|
2911
|
+
event.stopPropagation();
|
|
2912
|
+
// Firefox starts no drag without data on the transfer.
|
|
2913
|
+
event.dataTransfer?.setData('text/plain', n.label);
|
|
2914
|
+
if (event.dataTransfer)
|
|
2915
|
+
event.dataTransfer.effectAllowed = 'move';
|
|
2916
|
+
this.tree.beginDrag(n);
|
|
2917
|
+
}
|
|
2918
|
+
onDragEnd() {
|
|
2919
|
+
this.tree?.endDrag();
|
|
2920
|
+
}
|
|
2921
|
+
onDragOver(event) {
|
|
2922
|
+
const n = this.node();
|
|
2923
|
+
if (!n || !this.tree)
|
|
2924
|
+
return;
|
|
2925
|
+
event.stopPropagation();
|
|
2926
|
+
this.tree.dragOver(n, event.clientY);
|
|
2927
|
+
if (!this.tree.acceptsDrop(n)) {
|
|
2928
|
+
// No preventDefault: the pointer keeps the browser's "not allowed" cursor.
|
|
2929
|
+
if (event.dataTransfer)
|
|
2930
|
+
event.dataTransfer.dropEffect = 'none';
|
|
2931
|
+
return;
|
|
2932
|
+
}
|
|
2933
|
+
event.preventDefault();
|
|
2934
|
+
if (event.dataTransfer)
|
|
2935
|
+
event.dataTransfer.dropEffect = 'move';
|
|
2936
|
+
}
|
|
2937
|
+
onDragLeave() {
|
|
2938
|
+
const n = this.node();
|
|
2939
|
+
if (n)
|
|
2940
|
+
this.tree?.dragLeave(n);
|
|
2941
|
+
}
|
|
2942
|
+
onDrop(event) {
|
|
2943
|
+
const n = this.node();
|
|
2944
|
+
if (!n || !this.tree)
|
|
2945
|
+
return;
|
|
2946
|
+
event.preventDefault();
|
|
2947
|
+
event.stopPropagation();
|
|
2948
|
+
this.tree.dropOn(n);
|
|
2949
|
+
}
|
|
2834
2950
|
/** Right-click menu items for this node ([] when no resolver / not data mode). */
|
|
2835
2951
|
menuItems = computed(() => {
|
|
2836
2952
|
const fn = this.nodeMenu();
|
|
@@ -2992,9 +3108,17 @@ class StrctTreeNode {
|
|
|
2992
3108
|
[attr.aria-selected]="displayActive()"
|
|
2993
3109
|
[attr.aria-expanded]="hasChildren() ? isOpen() : null"
|
|
2994
3110
|
[attr.aria-owns]="hasChildren() && isOpen() ? groupId : null"
|
|
3111
|
+
[attr.draggable]="isDraggable() ? 'true' : null"
|
|
3112
|
+
[class.strct-tnode__row--dragging]="isDragSource()"
|
|
3113
|
+
[class.strct-tnode__row--droptarget]="isDropTarget()"
|
|
2995
3114
|
[strctContextMenu]="menuItems()"
|
|
2996
3115
|
[strctContextMenuData]="node()"
|
|
2997
3116
|
(menuSelect)="onMenuSelect($event)"
|
|
3117
|
+
(dragstart)="onDragStart($event)"
|
|
3118
|
+
(dragend)="onDragEnd()"
|
|
3119
|
+
(dragover)="onDragOver($event)"
|
|
3120
|
+
(dragleave)="onDragLeave()"
|
|
3121
|
+
(drop)="onDrop($event)"
|
|
2998
3122
|
(click)="onActivate()"
|
|
2999
3123
|
(focus)="onRowFocus()"
|
|
3000
3124
|
(keydown)="onRowKeydown($event)"
|
|
@@ -3045,7 +3169,7 @@ class StrctTreeNode {
|
|
|
3045
3169
|
}
|
|
3046
3170
|
</div>
|
|
3047
3171
|
}
|
|
3048
|
-
`, isInline: true, styles: [".strct-tnode{display:block}.strct-tnode__row{display:flex;align-items:center;gap:6px;padding:4px 9px;border-radius:5px;cursor:pointer;font-size:13px;color:var(--t1);-webkit-user-select:none;user-select:none}.strct-tnode__row:hover{background:var(--bg-3)}.strct-tnode__row:focus-visible{outline:none;box-shadow:inset 0 0 0 2px var(--acc50)}.strct-tnode__row--active{background:var(--acc-m);color:var(--acc);font-weight:500}.strct-tnode__row--active .strct-tnode__icon,.strct-tnode__row--active .strct-tnode__chevron{color:var(--acc)}.strct-tnode__chevron{display:inline-flex;align-items:center;justify-content:center;color:var(--t3);transition:transform .15s ease;width:24px;height:24px;margin:-5px;margin-inline-end:-10px;flex-shrink:0}.strct-tnode__chevron--open{transform:rotate(90deg)}[dir=rtl] .strct-tnode__chevron:not(.strct-tnode__chevron--open){transform:rotate(180deg)}.strct-tnode__spacer{width:14px;flex-shrink:0}@media(prefers-reduced-motion:reduce){.strct-tnode__chevron{transition:none}}.strct-tnode__icon{color:var(--t2);flex-shrink:0}.strct-tnode__label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.strct-tnode__children{margin-inline-start:16px}\n"], dependencies: [{ kind: "component", type: StrctTreeNode, selector: "strct-tree-node", inputs: ["node", "label", "icon", "badge", "active", "expanded", "nodeMenu"], outputs: ["expandedChange", "activated", "nodeActivated", "nodeMenuSelect"] }, { kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "strictName", "size", "strokeWidth", "badge", "ariaLabel"] }, { kind: "directive", type: StrctContextMenuTrigger, selector: "[strctContextMenu]", inputs: ["strctContextMenu", "strctContextMenuData"], outputs: ["menuSelect"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
3172
|
+
`, isInline: true, styles: [".strct-tnode{display:block}.strct-tnode__row{display:flex;align-items:center;gap:6px;padding:4px 9px;border-radius:5px;cursor:pointer;font-size:13px;color:var(--t1);-webkit-user-select:none;user-select:none}.strct-tnode__row:hover{background:var(--bg-3)}.strct-tnode__row--dragging{opacity:.45}.strct-tnode__row--droptarget,.strct-tnode__row--droptarget:hover{background:var(--acc-s);outline:1px solid var(--acc);outline-offset:-1px}.strct-tnode__row:focus-visible{outline:none;box-shadow:inset 0 0 0 2px var(--acc50)}.strct-tnode__row--active{background:var(--acc-m);color:var(--acc);font-weight:500}.strct-tnode__row--active .strct-tnode__icon,.strct-tnode__row--active .strct-tnode__chevron{color:var(--acc)}.strct-tnode__chevron{display:inline-flex;align-items:center;justify-content:center;color:var(--t3);transition:transform .15s ease;width:24px;height:24px;margin:-5px;margin-inline-end:-10px;flex-shrink:0}.strct-tnode__chevron--open{transform:rotate(90deg)}[dir=rtl] .strct-tnode__chevron:not(.strct-tnode__chevron--open){transform:rotate(180deg)}.strct-tnode__spacer{width:14px;flex-shrink:0}@media(prefers-reduced-motion:reduce){.strct-tnode__chevron{transition:none}}.strct-tnode__icon{color:var(--t2);flex-shrink:0}.strct-tnode__label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.strct-tnode__children{margin-inline-start:16px}\n"], dependencies: [{ kind: "component", type: StrctTreeNode, selector: "strct-tree-node", inputs: ["node", "label", "icon", "badge", "active", "expanded", "nodeMenu"], outputs: ["expandedChange", "activated", "nodeActivated", "nodeMenuSelect"] }, { kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "strictName", "size", "strokeWidth", "badge", "ariaLabel"] }, { kind: "directive", type: StrctContextMenuTrigger, selector: "[strctContextMenu]", inputs: ["strctContextMenu", "strctContextMenuData"], outputs: ["menuSelect"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
3049
3173
|
}
|
|
3050
3174
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctTreeNode, decorators: [{
|
|
3051
3175
|
type: Component,
|
|
@@ -3061,9 +3185,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
|
|
|
3061
3185
|
[attr.aria-selected]="displayActive()"
|
|
3062
3186
|
[attr.aria-expanded]="hasChildren() ? isOpen() : null"
|
|
3063
3187
|
[attr.aria-owns]="hasChildren() && isOpen() ? groupId : null"
|
|
3188
|
+
[attr.draggable]="isDraggable() ? 'true' : null"
|
|
3189
|
+
[class.strct-tnode__row--dragging]="isDragSource()"
|
|
3190
|
+
[class.strct-tnode__row--droptarget]="isDropTarget()"
|
|
3064
3191
|
[strctContextMenu]="menuItems()"
|
|
3065
3192
|
[strctContextMenuData]="node()"
|
|
3066
3193
|
(menuSelect)="onMenuSelect($event)"
|
|
3194
|
+
(dragstart)="onDragStart($event)"
|
|
3195
|
+
(dragend)="onDragEnd()"
|
|
3196
|
+
(dragover)="onDragOver($event)"
|
|
3197
|
+
(dragleave)="onDragLeave()"
|
|
3198
|
+
(drop)="onDrop($event)"
|
|
3067
3199
|
(click)="onActivate()"
|
|
3068
3200
|
(focus)="onRowFocus()"
|
|
3069
3201
|
(keydown)="onRowKeydown($event)"
|
|
@@ -3120,7 +3252,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
|
|
|
3120
3252
|
// siblings in the DOM, so the host must not appear in the a11y tree.
|
|
3121
3253
|
role: 'none',
|
|
3122
3254
|
'[attr.data-node-id]': 'node()?.id ?? null',
|
|
3123
|
-
}, styles: [".strct-tnode{display:block}.strct-tnode__row{display:flex;align-items:center;gap:6px;padding:4px 9px;border-radius:5px;cursor:pointer;font-size:13px;color:var(--t1);-webkit-user-select:none;user-select:none}.strct-tnode__row:hover{background:var(--bg-3)}.strct-tnode__row:focus-visible{outline:none;box-shadow:inset 0 0 0 2px var(--acc50)}.strct-tnode__row--active{background:var(--acc-m);color:var(--acc);font-weight:500}.strct-tnode__row--active .strct-tnode__icon,.strct-tnode__row--active .strct-tnode__chevron{color:var(--acc)}.strct-tnode__chevron{display:inline-flex;align-items:center;justify-content:center;color:var(--t3);transition:transform .15s ease;width:24px;height:24px;margin:-5px;margin-inline-end:-10px;flex-shrink:0}.strct-tnode__chevron--open{transform:rotate(90deg)}[dir=rtl] .strct-tnode__chevron:not(.strct-tnode__chevron--open){transform:rotate(180deg)}.strct-tnode__spacer{width:14px;flex-shrink:0}@media(prefers-reduced-motion:reduce){.strct-tnode__chevron{transition:none}}.strct-tnode__icon{color:var(--t2);flex-shrink:0}.strct-tnode__label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.strct-tnode__children{margin-inline-start:16px}\n"] }]
|
|
3255
|
+
}, styles: [".strct-tnode{display:block}.strct-tnode__row{display:flex;align-items:center;gap:6px;padding:4px 9px;border-radius:5px;cursor:pointer;font-size:13px;color:var(--t1);-webkit-user-select:none;user-select:none}.strct-tnode__row:hover{background:var(--bg-3)}.strct-tnode__row--dragging{opacity:.45}.strct-tnode__row--droptarget,.strct-tnode__row--droptarget:hover{background:var(--acc-s);outline:1px solid var(--acc);outline-offset:-1px}.strct-tnode__row:focus-visible{outline:none;box-shadow:inset 0 0 0 2px var(--acc50)}.strct-tnode__row--active{background:var(--acc-m);color:var(--acc);font-weight:500}.strct-tnode__row--active .strct-tnode__icon,.strct-tnode__row--active .strct-tnode__chevron{color:var(--acc)}.strct-tnode__chevron{display:inline-flex;align-items:center;justify-content:center;color:var(--t3);transition:transform .15s ease;width:24px;height:24px;margin:-5px;margin-inline-end:-10px;flex-shrink:0}.strct-tnode__chevron--open{transform:rotate(90deg)}[dir=rtl] .strct-tnode__chevron:not(.strct-tnode__chevron--open){transform:rotate(180deg)}.strct-tnode__spacer{width:14px;flex-shrink:0}@media(prefers-reduced-motion:reduce){.strct-tnode__chevron{transition:none}}.strct-tnode__icon{color:var(--t2);flex-shrink:0}.strct-tnode__label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.strct-tnode__children{margin-inline-start:16px}\n"] }]
|
|
3124
3256
|
}], ctorParameters: () => [], propDecorators: { node: [{ type: i0.Input, args: [{ isSignal: true, alias: "node", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], badge: [{ type: i0.Input, args: [{ isSignal: true, alias: "badge", required: false }] }], active: [{ type: i0.Input, args: [{ isSignal: true, alias: "active", required: false }] }], expanded: [{ type: i0.Input, args: [{ isSignal: true, alias: "expanded", required: false }] }, { type: i0.Output, args: ["expandedChange"] }], nodeMenu: [{ type: i0.Input, args: [{ isSignal: true, alias: "nodeMenu", required: false }] }], activated: [{ type: i0.Output, args: ["activated"] }], nodeActivated: [{ type: i0.Output, args: ["nodeActivated"] }], nodeMenuSelect: [{ type: i0.Output, args: ["nodeMenuSelect"] }], childNodes: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => StrctTreeNode), { isSignal: true }] }], directChildNodes: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => StrctTreeNode), { ...{ descendants: false }, isSignal: true }] }] } });
|
|
3125
3257
|
/**
|
|
3126
3258
|
* Root container for a tree. Either project `<strct-tree-node>` children, or
|
|
@@ -3141,6 +3273,29 @@ class StrctTree {
|
|
|
3141
3273
|
/** Per-node right-click menu resolver. */
|
|
3142
3274
|
nodeMenu = input(null, /* @ts-ignore */
|
|
3143
3275
|
...(ngDevMode ? [{ debugName: "nodeMenu" }] : /* istanbul ignore next */ []));
|
|
3276
|
+
/**
|
|
3277
|
+
* Which nodes can be picked up. Default: none, so a tree without this input
|
|
3278
|
+
* behaves exactly as before. Asked again whenever `nodes` changes, because
|
|
3279
|
+
* a refresh can make a node movable.
|
|
3280
|
+
*/
|
|
3281
|
+
canDrag = input(null, /* @ts-ignore */
|
|
3282
|
+
...(ngDevMode ? [{ debugName: "canDrag" }] : /* istanbul ignore next */ []));
|
|
3283
|
+
/**
|
|
3284
|
+
* Where the dragged node may land, asked during `dragover`. A browser does
|
|
3285
|
+
* not let `dragover` read the drag's data, so the tree keeps the source node
|
|
3286
|
+
* and hands it to you here. Without this input nothing accepts a drop — the
|
|
3287
|
+
* gesture belongs to the tree, the rule belongs to you.
|
|
3288
|
+
*
|
|
3289
|
+
* Two rules are built in whatever this returns: a node is never dropped on
|
|
3290
|
+
* itself, and never into its own subtree (that would be a cycle).
|
|
3291
|
+
*/
|
|
3292
|
+
canDrop = input(null, /* @ts-ignore */
|
|
3293
|
+
...(ngDevMode ? [{ debugName: "canDrop" }] : /* istanbul ignore next */ []));
|
|
3294
|
+
/** How long a collapsed node must be hovered mid-drag before it expands (ms). */
|
|
3295
|
+
dragExpandDelay = input(700, /* @ts-ignore */
|
|
3296
|
+
...(ngDevMode ? [{ debugName: "dragExpandDelay" }] : /* istanbul ignore next */ []));
|
|
3297
|
+
/** Emitted for an accepted drop only. */
|
|
3298
|
+
nodeDrop = output();
|
|
3144
3299
|
/** Accessible label of each node's chevron toggle; receives the expanded state and the node label. */
|
|
3145
3300
|
chevronAriaLabel = input(defaultChevronAriaLabel, /* @ts-ignore */
|
|
3146
3301
|
...(ngDevMode ? [{ debugName: "chevronAriaLabel" }] : /* istanbul ignore next */ []));
|
|
@@ -3324,6 +3479,144 @@ class StrctTree {
|
|
|
3324
3479
|
}
|
|
3325
3480
|
}
|
|
3326
3481
|
}
|
|
3482
|
+
// ── Drag and drop ──────────────────────────────────────────────
|
|
3483
|
+
announcer = inject(StrctAnnouncer);
|
|
3484
|
+
hostEl = inject(ElementRef);
|
|
3485
|
+
dragSourceNode = signal(null, /* @ts-ignore */
|
|
3486
|
+
...(ngDevMode ? [{ debugName: "dragSourceNode" }] : /* istanbul ignore next */ []));
|
|
3487
|
+
dropTargetNode = signal(null, /* @ts-ignore */
|
|
3488
|
+
...(ngDevMode ? [{ debugName: "dropTargetNode" }] : /* istanbul ignore next */ []));
|
|
3489
|
+
/** Pending hover-expand for a collapsed target. */
|
|
3490
|
+
expandTimer = null;
|
|
3491
|
+
expandTimerKey = null;
|
|
3492
|
+
/** Edge auto-scroll while a drag is in flight. */
|
|
3493
|
+
scrollFrame = null;
|
|
3494
|
+
scrollStep = 0;
|
|
3495
|
+
canDragNode(node) {
|
|
3496
|
+
return this.canDrag()?.(node) ?? false;
|
|
3497
|
+
}
|
|
3498
|
+
isDragSource(node) {
|
|
3499
|
+
const src = this.dragSourceNode();
|
|
3500
|
+
return !!src && this.keyOf(src) === this.keyOf(node);
|
|
3501
|
+
}
|
|
3502
|
+
isDropTarget(node) {
|
|
3503
|
+
const t = this.dropTargetNode();
|
|
3504
|
+
return !!t && this.keyOf(t) === this.keyOf(node);
|
|
3505
|
+
}
|
|
3506
|
+
/** Whether the current drag may land on `target`: the built-in guards first,
|
|
3507
|
+
* then the consumer's rule. */
|
|
3508
|
+
acceptsDrop(target) {
|
|
3509
|
+
const source = this.dragSourceNode();
|
|
3510
|
+
if (!source)
|
|
3511
|
+
return false;
|
|
3512
|
+
if (this.keyOf(source) === this.keyOf(target))
|
|
3513
|
+
return false;
|
|
3514
|
+
if (this.containsNode(source, target))
|
|
3515
|
+
return false;
|
|
3516
|
+
return this.canDrop()?.(source, target) ?? false;
|
|
3517
|
+
}
|
|
3518
|
+
/** Whether `node` is somewhere inside `root`'s subtree. */
|
|
3519
|
+
containsNode(root, node) {
|
|
3520
|
+
const key = this.keyOf(node);
|
|
3521
|
+
const walk = (list) => (list ?? []).some((n) => this.keyOf(n) === key || walk(n.children));
|
|
3522
|
+
return walk(root.children);
|
|
3523
|
+
}
|
|
3524
|
+
beginDrag(node) {
|
|
3525
|
+
this.dragSourceNode.set(node);
|
|
3526
|
+
this.announcer.announce(`Dragging ${node.label}`);
|
|
3527
|
+
}
|
|
3528
|
+
/** Called from every `dragover` on a row: highlight, hover-expand, scroll. */
|
|
3529
|
+
dragOver(target, clientY) {
|
|
3530
|
+
if (!this.dragSourceNode())
|
|
3531
|
+
return;
|
|
3532
|
+
const accepts = this.acceptsDrop(target);
|
|
3533
|
+
this.dropTargetNode.set(accepts ? target : null);
|
|
3534
|
+
this.scheduleHoverExpand(target);
|
|
3535
|
+
this.edgeScroll(clientY);
|
|
3536
|
+
}
|
|
3537
|
+
dragLeave(target) {
|
|
3538
|
+
if (this.isDropTarget(target))
|
|
3539
|
+
this.dropTargetNode.set(null);
|
|
3540
|
+
if (this.expandTimerKey === this.keyOf(target))
|
|
3541
|
+
this.clearExpandTimer();
|
|
3542
|
+
this.stopEdgeScroll();
|
|
3543
|
+
}
|
|
3544
|
+
dropOn(target) {
|
|
3545
|
+
const source = this.dragSourceNode();
|
|
3546
|
+
const accepted = !!source && this.acceptsDrop(target);
|
|
3547
|
+
if (source && accepted) {
|
|
3548
|
+
this.nodeDrop.emit({ source, target, position: 'into' });
|
|
3549
|
+
this.announcer.announce(`Dropped ${source.label} on ${target.label}`);
|
|
3550
|
+
}
|
|
3551
|
+
this.endDrag();
|
|
3552
|
+
}
|
|
3553
|
+
/** Always runs, including when the drop lands outside the tree (dragend). */
|
|
3554
|
+
endDrag() {
|
|
3555
|
+
this.dragSourceNode.set(null);
|
|
3556
|
+
this.dropTargetNode.set(null);
|
|
3557
|
+
this.clearExpandTimer();
|
|
3558
|
+
this.stopEdgeScroll();
|
|
3559
|
+
}
|
|
3560
|
+
/** A collapsed node hovered long enough opens, so a target that is not yet
|
|
3561
|
+
* visible can be reached at all — the normal case in a big inventory. */
|
|
3562
|
+
scheduleHoverExpand(target) {
|
|
3563
|
+
const key = this.keyOf(target);
|
|
3564
|
+
if (this.expandTimerKey === key)
|
|
3565
|
+
return;
|
|
3566
|
+
this.clearExpandTimer();
|
|
3567
|
+
if (!target.children?.length || this.currentSet().has(key))
|
|
3568
|
+
return;
|
|
3569
|
+
this.expandTimerKey = key;
|
|
3570
|
+
this.expandTimer = setTimeout(() => {
|
|
3571
|
+
this.expandTimer = null;
|
|
3572
|
+
this.expandTimerKey = null;
|
|
3573
|
+
if (this.dragSourceNode() && !this.currentSet().has(key))
|
|
3574
|
+
this.toggleNode(target);
|
|
3575
|
+
}, this.dragExpandDelay());
|
|
3576
|
+
}
|
|
3577
|
+
clearExpandTimer() {
|
|
3578
|
+
if (this.expandTimer != null)
|
|
3579
|
+
clearTimeout(this.expandTimer);
|
|
3580
|
+
this.expandTimer = null;
|
|
3581
|
+
this.expandTimerKey = null;
|
|
3582
|
+
}
|
|
3583
|
+
/** Nearest scrolling ancestor, so a long tree can be traversed mid-drag. */
|
|
3584
|
+
scrollParent() {
|
|
3585
|
+
let el = this.hostEl.nativeElement;
|
|
3586
|
+
while (el) {
|
|
3587
|
+
const canScroll = el.scrollHeight > el.clientHeight + 1;
|
|
3588
|
+
if (canScroll && /auto|scroll|overlay/.test(getComputedStyle(el).overflowY))
|
|
3589
|
+
return el;
|
|
3590
|
+
el = el.parentElement;
|
|
3591
|
+
}
|
|
3592
|
+
return null;
|
|
3593
|
+
}
|
|
3594
|
+
edgeScroll(clientY) {
|
|
3595
|
+
const box = this.scrollParent();
|
|
3596
|
+
if (!box)
|
|
3597
|
+
return;
|
|
3598
|
+
const r = box.getBoundingClientRect();
|
|
3599
|
+
const zone = 36;
|
|
3600
|
+
const step = clientY < r.top + zone ? -12 : clientY > r.bottom - zone ? 12 : 0;
|
|
3601
|
+
this.scrollStep = step;
|
|
3602
|
+
if (!step)
|
|
3603
|
+
return this.stopEdgeScroll();
|
|
3604
|
+
if (this.scrollFrame != null)
|
|
3605
|
+
return;
|
|
3606
|
+
const tick = () => {
|
|
3607
|
+
if (!this.scrollStep || !this.dragSourceNode())
|
|
3608
|
+
return this.stopEdgeScroll();
|
|
3609
|
+
box.scrollTop += this.scrollStep;
|
|
3610
|
+
this.scrollFrame = requestAnimationFrame(tick);
|
|
3611
|
+
};
|
|
3612
|
+
this.scrollFrame = requestAnimationFrame(tick);
|
|
3613
|
+
}
|
|
3614
|
+
stopEdgeScroll() {
|
|
3615
|
+
if (this.scrollFrame != null)
|
|
3616
|
+
cancelAnimationFrame(this.scrollFrame);
|
|
3617
|
+
this.scrollFrame = null;
|
|
3618
|
+
this.scrollStep = 0;
|
|
3619
|
+
}
|
|
3327
3620
|
/** Toggle a node's expansion, updating state and emitting outputs. */
|
|
3328
3621
|
toggleNode(node) {
|
|
3329
3622
|
const key = this.keyOf(node);
|
|
@@ -3345,7 +3638,7 @@ class StrctTree {
|
|
|
3345
3638
|
this.expandedChange.emit([...this.currentSet()]);
|
|
3346
3639
|
}
|
|
3347
3640
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctTree, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3348
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: StrctTree, isStandalone: true, selector: "strct-tree", inputs: { nodes: { classPropertyName: "nodes", publicName: "nodes", isSignal: true, isRequired: false, transformFunction: null }, density: { classPropertyName: "density", publicName: "density", isSignal: true, isRequired: false, transformFunction: null }, nodeMenu: { classPropertyName: "nodeMenu", publicName: "nodeMenu", isSignal: true, isRequired: false, transformFunction: null }, chevronAriaLabel: { classPropertyName: "chevronAriaLabel", publicName: "chevronAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, expandedIds: { classPropertyName: "expandedIds", publicName: "expandedIds", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { nodeActivated: "nodeActivated", nodeMenuSelect: "nodeMenuSelect", expandedIds: "expandedIdsChange", expandedChange: "expandedChange", nodeToggled: "nodeToggled" }, host: { attributes: { "role": "tree" }, properties: { "class.strct-tree--comfortable": "density() === 'comfortable'" }, classAttribute: "strct-tree" }, queries: [{ propertyName: "projectedNodes", predicate: StrctTreeNode, isSignal: true }], ngImport: i0, template: `
|
|
3641
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: StrctTree, isStandalone: true, selector: "strct-tree", inputs: { nodes: { classPropertyName: "nodes", publicName: "nodes", isSignal: true, isRequired: false, transformFunction: null }, density: { classPropertyName: "density", publicName: "density", isSignal: true, isRequired: false, transformFunction: null }, nodeMenu: { classPropertyName: "nodeMenu", publicName: "nodeMenu", isSignal: true, isRequired: false, transformFunction: null }, canDrag: { classPropertyName: "canDrag", publicName: "canDrag", isSignal: true, isRequired: false, transformFunction: null }, canDrop: { classPropertyName: "canDrop", publicName: "canDrop", isSignal: true, isRequired: false, transformFunction: null }, dragExpandDelay: { classPropertyName: "dragExpandDelay", publicName: "dragExpandDelay", isSignal: true, isRequired: false, transformFunction: null }, chevronAriaLabel: { classPropertyName: "chevronAriaLabel", publicName: "chevronAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, expandedIds: { classPropertyName: "expandedIds", publicName: "expandedIds", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { nodeDrop: "nodeDrop", nodeActivated: "nodeActivated", nodeMenuSelect: "nodeMenuSelect", expandedIds: "expandedIdsChange", expandedChange: "expandedChange", nodeToggled: "nodeToggled" }, host: { attributes: { "role": "tree" }, properties: { "class.strct-tree--comfortable": "density() === 'comfortable'" }, classAttribute: "strct-tree" }, queries: [{ propertyName: "projectedNodes", predicate: StrctTreeNode, isSignal: true }], ngImport: i0, template: `
|
|
3349
3642
|
@if (nodes(); as ns) {
|
|
3350
3643
|
@for (n of ns; track n.id ?? n.label) {
|
|
3351
3644
|
<strct-tree-node
|
|
@@ -3380,7 +3673,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
|
|
|
3380
3673
|
role: 'tree',
|
|
3381
3674
|
'[class.strct-tree--comfortable]': "density() === 'comfortable'",
|
|
3382
3675
|
}, styles: [".strct-tree{display:block}.strct-tree--comfortable .strct-tnode__row{gap:7px;padding:6px 10px;border-radius:6px;font-size:14px}.strct-tree--comfortable .strct-tnode__chevron{margin:-4px;margin-inline-end:-9px}.strct-tree--comfortable .strct-tnode__spacer{width:16px}.strct-tree--comfortable .strct-tnode__children{margin-inline-start:19px}\n"] }]
|
|
3383
|
-
}], propDecorators: { nodes: [{ type: i0.Input, args: [{ isSignal: true, alias: "nodes", required: false }] }], density: [{ type: i0.Input, args: [{ isSignal: true, alias: "density", required: false }] }], nodeMenu: [{ type: i0.Input, args: [{ isSignal: true, alias: "nodeMenu", required: false }] }], chevronAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "chevronAriaLabel", required: false }] }], nodeActivated: [{ type: i0.Output, args: ["nodeActivated"] }], nodeMenuSelect: [{ type: i0.Output, args: ["nodeMenuSelect"] }], expandedIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandedIds", required: false }] }, { type: i0.Output, args: ["expandedIdsChange"] }], expandedChange: [{ type: i0.Output, args: ["expandedChange"] }], nodeToggled: [{ type: i0.Output, args: ["nodeToggled"] }], projectedNodes: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => StrctTreeNode), { ...{ descendants: false }, isSignal: true }] }] } });
|
|
3676
|
+
}], propDecorators: { nodes: [{ type: i0.Input, args: [{ isSignal: true, alias: "nodes", required: false }] }], density: [{ type: i0.Input, args: [{ isSignal: true, alias: "density", required: false }] }], nodeMenu: [{ type: i0.Input, args: [{ isSignal: true, alias: "nodeMenu", required: false }] }], canDrag: [{ type: i0.Input, args: [{ isSignal: true, alias: "canDrag", required: false }] }], canDrop: [{ type: i0.Input, args: [{ isSignal: true, alias: "canDrop", required: false }] }], dragExpandDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "dragExpandDelay", required: false }] }], nodeDrop: [{ type: i0.Output, args: ["nodeDrop"] }], chevronAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "chevronAriaLabel", required: false }] }], nodeActivated: [{ type: i0.Output, args: ["nodeActivated"] }], nodeMenuSelect: [{ type: i0.Output, args: ["nodeMenuSelect"] }], expandedIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandedIds", required: false }] }, { type: i0.Output, args: ["expandedIdsChange"] }], expandedChange: [{ type: i0.Output, args: ["expandedChange"] }], nodeToggled: [{ type: i0.Output, args: ["nodeToggled"] }], projectedNodes: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => StrctTreeNode), { ...{ descendants: false }, isSignal: true }] }] } });
|
|
3384
3677
|
|
|
3385
3678
|
/**
|
|
3386
3679
|
* Dev-mode diagnostics for combinations a component accepts but cannot honour.
|
|
@@ -19254,61 +19547,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
|
|
|
19254
19547
|
args: ['document:keydown.escape', ['$event']]
|
|
19255
19548
|
}] } });
|
|
19256
19549
|
|
|
19257
|
-
/**
|
|
19258
|
-
* Screen-reader announcements for state changes that have no visible text —
|
|
19259
|
-
* "12 rows loaded", "Snapshot created" (Material's LiveAnnouncer, strct-sized):
|
|
19260
|
-
*
|
|
19261
|
-
* private announcer = inject(StrctAnnouncer);
|
|
19262
|
-
* this.announcer.announce('12 rows loaded');
|
|
19263
|
-
*
|
|
19264
|
-
* Maintains one visually-hidden live region per politeness level; repeated
|
|
19265
|
-
* identical messages are re-announced (the region is cleared first).
|
|
19266
|
-
*/
|
|
19267
|
-
class StrctAnnouncer {
|
|
19268
|
-
regions = new Map();
|
|
19269
|
-
clearTimers = new Map();
|
|
19270
|
-
isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
|
|
19271
|
-
announce(message, politeness = 'polite') {
|
|
19272
|
-
if (!this.isBrowser)
|
|
19273
|
-
return;
|
|
19274
|
-
const region = this.regionFor(politeness);
|
|
19275
|
-
// Clear first so identical consecutive messages still fire.
|
|
19276
|
-
region.textContent = '';
|
|
19277
|
-
setTimeout(() => (region.textContent = message));
|
|
19278
|
-
clearTimeout(this.clearTimers.get(politeness));
|
|
19279
|
-
// Stale announcements should not linger for the next SR user to stumble on.
|
|
19280
|
-
this.clearTimers.set(politeness, setTimeout(() => (region.textContent = ''), 10_000));
|
|
19281
|
-
}
|
|
19282
|
-
regionFor(politeness) {
|
|
19283
|
-
let region = this.regions.get(politeness);
|
|
19284
|
-
if (!region) {
|
|
19285
|
-
region = document.createElement('div');
|
|
19286
|
-
region.setAttribute('aria-live', politeness);
|
|
19287
|
-
region.setAttribute('aria-atomic', 'true');
|
|
19288
|
-
region.className = 'strct-announcer';
|
|
19289
|
-
region.style.cssText =
|
|
19290
|
-
'position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;';
|
|
19291
|
-
document.body.appendChild(region);
|
|
19292
|
-
this.regions.set(politeness, region);
|
|
19293
|
-
}
|
|
19294
|
-
return region;
|
|
19295
|
-
}
|
|
19296
|
-
ngOnDestroy() {
|
|
19297
|
-
for (const timer of this.clearTimers.values())
|
|
19298
|
-
clearTimeout(timer);
|
|
19299
|
-
this.clearTimers.clear();
|
|
19300
|
-
for (const region of this.regions.values())
|
|
19301
|
-
region.remove();
|
|
19302
|
-
this.regions.clear();
|
|
19303
|
-
}
|
|
19304
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctAnnouncer, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
19305
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctAnnouncer, providedIn: 'root' });
|
|
19306
|
-
}
|
|
19307
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctAnnouncer, decorators: [{
|
|
19308
|
-
type: Injectable,
|
|
19309
|
-
args: [{ providedIn: 'root' }]
|
|
19310
|
-
}] });
|
|
19311
|
-
|
|
19312
19550
|
function normalize(combo) {
|
|
19313
19551
|
return combo
|
|
19314
19552
|
.toLowerCase()
|
|
@@ -20678,7 +20916,7 @@ class StrctTreeSelect {
|
|
|
20678
20916
|
}
|
|
20679
20917
|
</div>
|
|
20680
20918
|
}
|
|
20681
|
-
`, isInline: true, styles: [".strct-tsel{position:relative;display:block;width:100%}.strct-tsel__field{position:relative}.strct-tsel__trigger{display:flex;align-items:center;gap:8px;padding-inline-end:30px;text-align:start;cursor:pointer}.strct-tsel__trigger--clearable{padding-inline-end:48px}.strct-tsel__trigger:disabled{cursor:not-allowed}.strct-tsel__value{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.strct-tsel__value--empty{color:var(--t3)}.strct-tsel__caret{position:absolute;inset-inline-end:9px;top:50%;transform:translateY(-50%);color:var(--t3);pointer-events:none}.strct-tsel__clear{position:absolute;inset-inline-end:26px;top:50%;transform:translateY(-50%);display:inline-flex;align-items:center;padding:2px;color:var(--t3);background:none;border:none;border-radius:var(--radius-sm);cursor:pointer}.strct-tsel__clear:hover{color:var(--t1)}.strct-tsel__panel{z-index:var(--z-dropdown);max-height:260px;overflow-y:auto;padding:var(--space-1);background:var(--bg-1);border:1px solid var(--b2);border-radius:var(--radius-lg);box-shadow:var(--shh)}.strct-tsel__empty{padding:9px 10px;font-size:13px;color:var(--t3)}\n"], dependencies: [{ kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "strictName", "size", "strokeWidth", "badge", "ariaLabel"] }, { kind: "directive", type: StrctOverlay, selector: "[strctOverlay]", inputs: ["strctOverlay", "strctOverlayPlacement", "strctOverlayMatchWidth", "strctOverlayGap"] }, { kind: "component", type: StrctTree, selector: "strct-tree", inputs: ["nodes", "density", "nodeMenu", "chevronAriaLabel", "expandedIds"], outputs: ["nodeActivated", "nodeMenuSelect", "expandedIdsChange", "expandedChange", "nodeToggled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
20919
|
+
`, isInline: true, styles: [".strct-tsel{position:relative;display:block;width:100%}.strct-tsel__field{position:relative}.strct-tsel__trigger{display:flex;align-items:center;gap:8px;padding-inline-end:30px;text-align:start;cursor:pointer}.strct-tsel__trigger--clearable{padding-inline-end:48px}.strct-tsel__trigger:disabled{cursor:not-allowed}.strct-tsel__value{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.strct-tsel__value--empty{color:var(--t3)}.strct-tsel__caret{position:absolute;inset-inline-end:9px;top:50%;transform:translateY(-50%);color:var(--t3);pointer-events:none}.strct-tsel__clear{position:absolute;inset-inline-end:26px;top:50%;transform:translateY(-50%);display:inline-flex;align-items:center;padding:2px;color:var(--t3);background:none;border:none;border-radius:var(--radius-sm);cursor:pointer}.strct-tsel__clear:hover{color:var(--t1)}.strct-tsel__panel{z-index:var(--z-dropdown);max-height:260px;overflow-y:auto;padding:var(--space-1);background:var(--bg-1);border:1px solid var(--b2);border-radius:var(--radius-lg);box-shadow:var(--shh)}.strct-tsel__empty{padding:9px 10px;font-size:13px;color:var(--t3)}\n"], dependencies: [{ kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "strictName", "size", "strokeWidth", "badge", "ariaLabel"] }, { kind: "directive", type: StrctOverlay, selector: "[strctOverlay]", inputs: ["strctOverlay", "strctOverlayPlacement", "strctOverlayMatchWidth", "strctOverlayGap"] }, { kind: "component", type: StrctTree, selector: "strct-tree", inputs: ["nodes", "density", "nodeMenu", "canDrag", "canDrop", "dragExpandDelay", "chevronAriaLabel", "expandedIds"], outputs: ["nodeDrop", "nodeActivated", "nodeMenuSelect", "expandedIdsChange", "expandedChange", "nodeToggled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
20682
20920
|
}
|
|
20683
20921
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctTreeSelect, decorators: [{
|
|
20684
20922
|
type: Component,
|
|
@@ -21612,6 +21850,34 @@ class StrctHeatmap {
|
|
|
21612
21850
|
/** Scale ceiling — the value that maps to full intensity. Auto (data max) when null. */
|
|
21613
21851
|
max = input(null, /* @ts-ignore */
|
|
21614
21852
|
...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
|
|
21853
|
+
/**
|
|
21854
|
+
* Label every n-th column (1 = every column, today's behaviour). 24 hourly
|
|
21855
|
+
* columns in a narrow card cannot all carry a readable label; labelling every
|
|
21856
|
+
* third keeps "14:00" legible instead of forcing it down to "14".
|
|
21857
|
+
*/
|
|
21858
|
+
colLabelEvery = input(1, /* @ts-ignore */
|
|
21859
|
+
...(ngDevMode ? [{ debugName: "colLabelEvery" }] : /* istanbul ignore next */ []));
|
|
21860
|
+
/**
|
|
21861
|
+
* Column label text, so a column can be keyed by an ISO time and shown as
|
|
21862
|
+
* "14:00". Return '' to drop a label. Applied after `colLabelEvery`.
|
|
21863
|
+
*/
|
|
21864
|
+
colLabel = input(null, /* @ts-ignore */
|
|
21865
|
+
...(ngDevMode ? [{ debugName: "colLabel" }] : /* istanbul ignore next */ []));
|
|
21866
|
+
/**
|
|
21867
|
+
* Cell tooltip text. Without it a cell reads `row × col: value`, which for a
|
|
21868
|
+
* utilisation grid has no unit — `hv-05 · 14:00 — 47% CPU` is the readable form.
|
|
21869
|
+
*/
|
|
21870
|
+
valueFormat = input(null, /* @ts-ignore */
|
|
21871
|
+
...(ngDevMode ? [{ debugName: "valueFormat" }] : /* istanbul ignore next */ []));
|
|
21872
|
+
/**
|
|
21873
|
+
* Colour by band instead of one hue: `status` (accent by default) below
|
|
21874
|
+
* `warning`, the warning hue from there, the critical hue from `critical`.
|
|
21875
|
+
* Intensity is scaled by the value's position WITHIN its band, so a 96% cell
|
|
21876
|
+
* reads darker than an 81% one and both read as critical/warning at a glance.
|
|
21877
|
+
* Unset: today's single-hue ramp across the whole range.
|
|
21878
|
+
*/
|
|
21879
|
+
thresholds = input(null, /* @ts-ignore */
|
|
21880
|
+
...(ngDevMode ? [{ debugName: "thresholds" }] : /* istanbul ignore next */ []));
|
|
21615
21881
|
/** Base color of the intensity ramp. */
|
|
21616
21882
|
status = input('accent', /* @ts-ignore */
|
|
21617
21883
|
...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
|
|
@@ -21710,7 +21976,7 @@ class StrctHeatmap {
|
|
|
21710
21976
|
w: round(w),
|
|
21711
21977
|
h: ch,
|
|
21712
21978
|
fill: value === undefined ? 'var(--bg-2)' : this.fillFor(value),
|
|
21713
|
-
tip: value === undefined ? null :
|
|
21979
|
+
tip: value === undefined ? null : this.tipFor(value, row, col),
|
|
21714
21980
|
});
|
|
21715
21981
|
});
|
|
21716
21982
|
});
|
|
@@ -21732,9 +21998,24 @@ class StrctHeatmap {
|
|
|
21732
21998
|
const gap = this.gap();
|
|
21733
21999
|
const x0 = this.rowLabelWidth();
|
|
21734
22000
|
const y = this.height() - 4;
|
|
21735
|
-
|
|
22001
|
+
const every = Math.max(1, Math.trunc(this.colLabelEvery()));
|
|
22002
|
+
const format = this.colLabel();
|
|
22003
|
+
const out = [];
|
|
22004
|
+
this.colOrder().forEach((col, c) => {
|
|
22005
|
+
if (c % every !== 0)
|
|
22006
|
+
return;
|
|
22007
|
+
const text = format ? format(col, c) : col;
|
|
22008
|
+
if (text === '')
|
|
22009
|
+
return;
|
|
22010
|
+
out.push({ text, x: round(x0 + c * (w + gap) + w / 2), y });
|
|
22011
|
+
});
|
|
22012
|
+
return out;
|
|
21736
22013
|
}, /* @ts-ignore */
|
|
21737
22014
|
...(ngDevMode ? [{ debugName: "colLabels" }] : /* istanbul ignore next */ []));
|
|
22015
|
+
tipFor(value, row, col) {
|
|
22016
|
+
const format = this.valueFormat();
|
|
22017
|
+
return format ? format(value, row, col) : `${row} × ${col}: ${value}`;
|
|
22018
|
+
}
|
|
21738
22019
|
/** Screen-reader summary of the whole grid (role="img" name). */
|
|
21739
22020
|
aria = computed(() => {
|
|
21740
22021
|
const info = {
|
|
@@ -21752,17 +22033,41 @@ class StrctHeatmap {
|
|
|
21752
22033
|
/**
|
|
21753
22034
|
* Intensity ramp: 8% of the status hue for the smallest non-zero value up
|
|
21754
22035
|
* to 100% at the ceiling; zero stays on the empty-cell surface.
|
|
22036
|
+
*
|
|
22037
|
+
* With `thresholds`, the hue comes from the value's band and the intensity
|
|
22038
|
+
* from its position inside that band, floored at 45% — a band has to be
|
|
22039
|
+
* recognisable as itself, and a pale cell must not read as "no data".
|
|
21755
22040
|
*/
|
|
21756
22041
|
fillFor(value) {
|
|
21757
22042
|
if (value <= 0)
|
|
21758
22043
|
return 'var(--bg-2)';
|
|
21759
22044
|
const maxV = this.maxVal();
|
|
21760
|
-
const t =
|
|
21761
|
-
|
|
22045
|
+
const t = this.thresholds();
|
|
22046
|
+
if (t) {
|
|
22047
|
+
const { color, from, to } = this.bandFor(value, t, maxV);
|
|
22048
|
+
const span = to - from;
|
|
22049
|
+
const pos = span > 0 ? Math.min(Math.max((value - from) / span, 0), 1) : 1;
|
|
22050
|
+
const pct = 45 + Math.round(pos * 55);
|
|
22051
|
+
return `color-mix(in srgb, ${color} ${pct}%, var(--bg-1))`;
|
|
22052
|
+
}
|
|
22053
|
+
const ratio = maxV > 0 ? Math.min(value / maxV, 1) : 0;
|
|
22054
|
+
const pct = 8 + Math.round(ratio * 92);
|
|
21762
22055
|
return `color-mix(in srgb, ${this.color()} ${pct}%, var(--bg-1))`;
|
|
21763
22056
|
}
|
|
22057
|
+
/** The band a value falls in: its hue and the range intensity scales over. */
|
|
22058
|
+
bandFor(value, t, maxV) {
|
|
22059
|
+
const ceiling = Math.max(maxV, value);
|
|
22060
|
+
if (t.critical != null && value >= t.critical) {
|
|
22061
|
+
return { color: COLOR.critical, from: t.critical, to: ceiling };
|
|
22062
|
+
}
|
|
22063
|
+
if (t.warning != null && value >= t.warning) {
|
|
22064
|
+
return { color: COLOR.warning, from: t.warning, to: t.critical ?? ceiling };
|
|
22065
|
+
}
|
|
22066
|
+
const upper = t.warning ?? t.critical ?? ceiling;
|
|
22067
|
+
return { color: this.color(), from: 0, to: upper };
|
|
22068
|
+
}
|
|
21764
22069
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctHeatmap, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
21765
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: StrctHeatmap, isStandalone: true, selector: "strct-heatmap", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null }, cols: { classPropertyName: "cols", publicName: "cols", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, summaryFormat: { classPropertyName: "summaryFormat", publicName: "summaryFormat", isSignal: true, isRequired: false, transformFunction: null }, cellHeight: { classPropertyName: "cellHeight", publicName: "cellHeight", isSignal: true, isRequired: false, transformFunction: null }, gap: { classPropertyName: "gap", publicName: "gap", isSignal: true, isRequired: false, transformFunction: null }, rowLabelWidth: { classPropertyName: "rowLabelWidth", publicName: "rowLabelWidth", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "strct-heatmap" }, ngImport: i0, template: `
|
|
22070
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: StrctHeatmap, isStandalone: true, selector: "strct-heatmap", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null }, cols: { classPropertyName: "cols", publicName: "cols", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, colLabelEvery: { classPropertyName: "colLabelEvery", publicName: "colLabelEvery", isSignal: true, isRequired: false, transformFunction: null }, colLabel: { classPropertyName: "colLabel", publicName: "colLabel", isSignal: true, isRequired: false, transformFunction: null }, valueFormat: { classPropertyName: "valueFormat", publicName: "valueFormat", isSignal: true, isRequired: false, transformFunction: null }, thresholds: { classPropertyName: "thresholds", publicName: "thresholds", isSignal: true, isRequired: false, transformFunction: null }, status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, summaryFormat: { classPropertyName: "summaryFormat", publicName: "summaryFormat", isSignal: true, isRequired: false, transformFunction: null }, cellHeight: { classPropertyName: "cellHeight", publicName: "cellHeight", isSignal: true, isRequired: false, transformFunction: null }, gap: { classPropertyName: "gap", publicName: "gap", isSignal: true, isRequired: false, transformFunction: null }, rowLabelWidth: { classPropertyName: "rowLabelWidth", publicName: "rowLabelWidth", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "strct-heatmap" }, ngImport: i0, template: `
|
|
21766
22071
|
@if (isEmpty()) {
|
|
21767
22072
|
<div class="strct-heatmap__empty">{{ emptyText() }}</div>
|
|
21768
22073
|
} @else {
|
|
@@ -21774,7 +22079,10 @@ class StrctHeatmap {
|
|
|
21774
22079
|
[attr.width]="width()"
|
|
21775
22080
|
[attr.height]="height()"
|
|
21776
22081
|
>
|
|
21777
|
-
|
|
22082
|
+
<!-- Tracked by index: label text is data and repeats legitimately (two
|
|
22083
|
+
hosts with the same name, hour 02 twice at the DST fall-back), and
|
|
22084
|
+
a thinned column axis repeats the empty string. -->
|
|
22085
|
+
@for (l of rowLabels(); track $index) {
|
|
21778
22086
|
<text
|
|
21779
22087
|
class="strct-heatmap__label strct-heatmap__label--row"
|
|
21780
22088
|
[attr.x]="l.x"
|
|
@@ -21785,7 +22093,7 @@ class StrctHeatmap {
|
|
|
21785
22093
|
{{ l.text }}
|
|
21786
22094
|
</text>
|
|
21787
22095
|
}
|
|
21788
|
-
@for (l of colLabels(); track
|
|
22096
|
+
@for (l of colLabels(); track $index) {
|
|
21789
22097
|
<text
|
|
21790
22098
|
class="strct-heatmap__label strct-heatmap__label--col"
|
|
21791
22099
|
[attr.x]="l.x"
|
|
@@ -21829,7 +22137,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
|
|
|
21829
22137
|
[attr.width]="width()"
|
|
21830
22138
|
[attr.height]="height()"
|
|
21831
22139
|
>
|
|
21832
|
-
|
|
22140
|
+
<!-- Tracked by index: label text is data and repeats legitimately (two
|
|
22141
|
+
hosts with the same name, hour 02 twice at the DST fall-back), and
|
|
22142
|
+
a thinned column axis repeats the empty string. -->
|
|
22143
|
+
@for (l of rowLabels(); track $index) {
|
|
21833
22144
|
<text
|
|
21834
22145
|
class="strct-heatmap__label strct-heatmap__label--row"
|
|
21835
22146
|
[attr.x]="l.x"
|
|
@@ -21840,7 +22151,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
|
|
|
21840
22151
|
{{ l.text }}
|
|
21841
22152
|
</text>
|
|
21842
22153
|
}
|
|
21843
|
-
@for (l of colLabels(); track
|
|
22154
|
+
@for (l of colLabels(); track $index) {
|
|
21844
22155
|
<text
|
|
21845
22156
|
class="strct-heatmap__label strct-heatmap__label--col"
|
|
21846
22157
|
[attr.x]="l.x"
|
|
@@ -21869,7 +22180,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
|
|
|
21869
22180
|
</svg>
|
|
21870
22181
|
}
|
|
21871
22182
|
`, host: { class: 'strct-heatmap' }, styles: [".strct-heatmap{display:block}.strct-heatmap__svg{width:100%;display:block}.strct-heatmap__empty{display:flex;align-items:center;justify-content:center;min-height:60px;font-size:12px;color:var(--t3)}.strct-heatmap__label{fill:var(--t3);font-size:10px}.strct-heatmap__label--row{font-family:var(--font)}.strct-heatmap__label--col{font-family:var(--mono);font-variant-numeric:tabular-nums}.strct-heatmap__cell{stroke:var(--bg-1);stroke-width:1}@media(prefers-reduced-motion:no-preference){.strct-heatmap__cell{transition:opacity .12s ease}}.strct-heatmap__cell:hover{opacity:.75}\n"] }]
|
|
21872
|
-
}], ctorParameters: () => [], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }], cols: [{ type: i0.Input, args: [{ isSignal: true, alias: "cols", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], summaryFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "summaryFormat", required: false }] }], cellHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "cellHeight", required: false }] }], gap: [{ type: i0.Input, args: [{ isSignal: true, alias: "gap", required: false }] }], rowLabelWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowLabelWidth", required: false }] }] } });
|
|
22183
|
+
}], ctorParameters: () => [], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }], cols: [{ type: i0.Input, args: [{ isSignal: true, alias: "cols", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], colLabelEvery: [{ type: i0.Input, args: [{ isSignal: true, alias: "colLabelEvery", required: false }] }], colLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "colLabel", required: false }] }], valueFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueFormat", required: false }] }], thresholds: [{ type: i0.Input, args: [{ isSignal: true, alias: "thresholds", required: false }] }], status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], summaryFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "summaryFormat", required: false }] }], cellHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "cellHeight", required: false }] }], gap: [{ type: i0.Input, args: [{ isSignal: true, alias: "gap", required: false }] }], rowLabelWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowLabelWidth", required: false }] }] } });
|
|
21873
22184
|
|
|
21874
22185
|
/**
|
|
21875
22186
|
* Public API surface of the strct UI library.
|