@akcelik/strct 1.6.0 → 1.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/akcelik-strct.mjs +2752 -36
- package/fesm2022/akcelik-strct.mjs.map +1 -1
- package/package.json +1 -1
- package/styles/_tokens.scss +12 -0
- package/types/akcelik-strct.d.ts +715 -5
|
@@ -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, afterNextRender, Directive, model, output, booleanAttribute, HostListener, contentChildren, effect, ApplicationRef, EnvironmentInjector, createComponent, forwardRef, DestroyRef, contentChild, viewChild, viewChildren, untracked, TemplateRef, Renderer2 } from '@angular/core';
|
|
2
|
+
import { inject, DOCUMENT, signal, computed, Injectable, input, isDevMode, ViewEncapsulation, ChangeDetectionStrategy, Component, ElementRef, NgZone, afterNextRender, Directive, model, output, booleanAttribute, HostListener, contentChildren, effect, ApplicationRef, EnvironmentInjector, createComponent, forwardRef, DestroyRef, contentChild, viewChild, viewChildren, untracked, TemplateRef, Renderer2, Pipe } from '@angular/core';
|
|
3
3
|
import { DomSanitizer } from '@angular/platform-browser';
|
|
4
4
|
import { NgTemplateOutlet, DOCUMENT as DOCUMENT$1 } from '@angular/common';
|
|
5
5
|
import { RouterLink, RouterLinkActive } from '@angular/router';
|
|
@@ -256,6 +256,7 @@ const STRCT_ICON_DEFS = {
|
|
|
256
256
|
megaphone: '<path d="M3 7.3v1.6l7 3V4.3zM10 5.7 12.5 4.2v7.6L10 10.3M4 9.6l.9 3.1h1.6l-.7-2.9"/>',
|
|
257
257
|
flag: '<path d="M4 13.5V3M4 3.6h7.4L9.8 6.2l1.6 2.6H4"/>',
|
|
258
258
|
shieldAlert: '<path d="M8 2.3l5.2 1.9v3.3c0 3.1-2.3 5.1-5.2 6.4C5.1 12.6 2.8 10.6 2.8 7.5V4.2z"/><path d="M8 5.5v3.1M8 10.6v.2"/>',
|
|
259
|
+
rocket: '<path d="M8 1.8c1.9 1.3 2.9 3.1 2.9 5.3 0 1.5-.4 2.9-1 4.1H6.1c-.6-1.2-1-2.6-1-4.1 0-2.2 1-4 2.9-5.3z"/><circle cx="8" cy="6.3" r="1.1"/><path d="M5.1 8.9 3.5 11l2-.2M10.9 8.9l1.6 2.1-2-.2M6.9 11.2 8 14.1l1.1-2.9"/>',
|
|
259
260
|
shieldCheck: '<path d="M8 2.3l5.2 1.9v3.3c0 3.1-2.3 5.1-5.2 6.4C5.1 12.6 2.8 10.6 2.8 7.5V4.2z"/><path d="M5.9 7.9l1.5 1.5 2.8-2.9"/>',
|
|
260
261
|
// ── State / action (also usable as badges via the icon `badge` input) ─
|
|
261
262
|
running: '<path d="M5.5 4l6 4-6 4z"/>',
|
|
@@ -539,6 +540,7 @@ const STRCT_ICON_GROUPS = [
|
|
|
539
540
|
'api',
|
|
540
541
|
'bolt',
|
|
541
542
|
'queue',
|
|
543
|
+
'rocket',
|
|
542
544
|
],
|
|
543
545
|
},
|
|
544
546
|
{
|
|
@@ -3381,6 +3383,13 @@ class StrctDropdown {
|
|
|
3381
3383
|
/** Accessible name of the popover dialog (localizable). */
|
|
3382
3384
|
popoverLabel = input('Filters', ...(ngDevMode ? [{ debugName: "popoverLabel" }] : /* istanbul ignore next */ []));
|
|
3383
3385
|
open = signal(false, ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
|
|
3386
|
+
constructor() {
|
|
3387
|
+
// The static `popover` attribute collides with the native HTML Popover
|
|
3388
|
+
// API: the UA styles [popover] hosts (Canvas background, medium border,
|
|
3389
|
+
// fixed positioning) — a white frame around the trigger in dark themes.
|
|
3390
|
+
// Angular has already read the input by now; the DOM attribute must go.
|
|
3391
|
+
this.host.nativeElement.removeAttribute('popover');
|
|
3392
|
+
}
|
|
3384
3393
|
toggle() {
|
|
3385
3394
|
this.open.update((v) => !v);
|
|
3386
3395
|
}
|
|
@@ -3402,17 +3411,13 @@ class StrctDropdown {
|
|
|
3402
3411
|
}
|
|
3403
3412
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctDropdown, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3404
3413
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: StrctDropdown, isStandalone: true, selector: "strct-dropdown", inputs: { align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null }, popover: { classPropertyName: "popover", publicName: "popover", isSignal: true, isRequired: false, transformFunction: null }, popoverLabel: { classPropertyName: "popoverLabel", publicName: "popoverLabel", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "document:click": "onDocClick($event)", "document:keydown.escape": "onEscape()" }, classAttribute: "strct-dd" }, ngImport: i0, template: `
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
(click)="toggle()"
|
|
3413
|
-
(keydown.enter)="toggle()"
|
|
3414
|
-
(keydown.space)="toggle()"
|
|
3415
|
-
>
|
|
3414
|
+
<!--
|
|
3415
|
+
The wrapper is intentionally inert: the projected trigger should be a
|
|
3416
|
+
real button (nesting interactives fails axe). Its native click —
|
|
3417
|
+
pointer or Enter/Space — bubbles here; the strctDropdownTrigger
|
|
3418
|
+
directive puts aria-haspopup/aria-expanded on the button itself.
|
|
3419
|
+
-->
|
|
3420
|
+
<div #trigger class="strct-dd__trigger" (click)="toggle()">
|
|
3416
3421
|
<ng-content select="[strctDropdownTrigger]" />
|
|
3417
3422
|
</div>
|
|
3418
3423
|
<!--
|
|
@@ -3440,17 +3445,13 @@ class StrctDropdown {
|
|
|
3440
3445
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctDropdown, decorators: [{
|
|
3441
3446
|
type: Component,
|
|
3442
3447
|
args: [{ selector: 'strct-dropdown', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [StrctOverlay], template: `
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
(click)="toggle()"
|
|
3451
|
-
(keydown.enter)="toggle()"
|
|
3452
|
-
(keydown.space)="toggle()"
|
|
3453
|
-
>
|
|
3448
|
+
<!--
|
|
3449
|
+
The wrapper is intentionally inert: the projected trigger should be a
|
|
3450
|
+
real button (nesting interactives fails axe). Its native click —
|
|
3451
|
+
pointer or Enter/Space — bubbles here; the strctDropdownTrigger
|
|
3452
|
+
directive puts aria-haspopup/aria-expanded on the button itself.
|
|
3453
|
+
-->
|
|
3454
|
+
<div #trigger class="strct-dd__trigger" (click)="toggle()">
|
|
3454
3455
|
<ng-content select="[strctDropdownTrigger]" />
|
|
3455
3456
|
</div>
|
|
3456
3457
|
<!--
|
|
@@ -3474,13 +3475,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
|
|
|
3474
3475
|
</div>
|
|
3475
3476
|
}
|
|
3476
3477
|
`, host: { class: 'strct-dd' }, styles: [".strct-dd{position:relative;display:inline-block}.strct-dd__trigger{display:inline-flex}.strct-dd__menu{position:absolute;top:calc(100% + 4px);left:0;z-index:200;min-width:170px;max-width:calc(100vw - 24px);padding:4px;background:var(--bg-1);border:1px solid var(--b2);border-radius:7px;box-shadow:var(--shh);animation:strct-dd-in .1s ease}.strct-dd__menu--end{left:auto;right:0}.strct-dd__menu--popover{min-width:240px;padding:12px 14px}@keyframes strct-dd-in{0%{opacity:0;transform:translateY(-4px)}}\n"] }]
|
|
3477
|
-
}], propDecorators: { align: [{ type: i0.Input, args: [{ isSignal: true, alias: "align", required: false }] }], popover: [{ type: i0.Input, args: [{ isSignal: true, alias: "popover", required: false }] }], popoverLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "popoverLabel", required: false }] }], onDocClick: [{
|
|
3478
|
+
}], ctorParameters: () => [], propDecorators: { align: [{ type: i0.Input, args: [{ isSignal: true, alias: "align", required: false }] }], popover: [{ type: i0.Input, args: [{ isSignal: true, alias: "popover", required: false }] }], popoverLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "popoverLabel", required: false }] }], onDocClick: [{
|
|
3478
3479
|
type: HostListener,
|
|
3479
3480
|
args: ['document:click', ['$event']]
|
|
3480
3481
|
}], onEscape: [{
|
|
3481
3482
|
type: HostListener,
|
|
3482
3483
|
args: ['document:keydown.escape']
|
|
3483
3484
|
}] } });
|
|
3485
|
+
/**
|
|
3486
|
+
* Marks (and upgrades) the dropdown's trigger element. The attribute alone is
|
|
3487
|
+
* enough for content projection; importing the directive additionally wires
|
|
3488
|
+
* `aria-haspopup` / `aria-expanded` onto the real button.
|
|
3489
|
+
*/
|
|
3490
|
+
class StrctDropdownTrigger {
|
|
3491
|
+
dd = inject(StrctDropdown, { optional: true });
|
|
3492
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctDropdownTrigger, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
3493
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.16", type: StrctDropdownTrigger, isStandalone: true, selector: "[strctDropdownTrigger]", host: { properties: { "attr.aria-haspopup": "dd ? (dd.popover() ? 'dialog' : 'menu') : null", "attr.aria-expanded": "dd ? dd.open() : null" } }, ngImport: i0 });
|
|
3494
|
+
}
|
|
3495
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctDropdownTrigger, decorators: [{
|
|
3496
|
+
type: Directive,
|
|
3497
|
+
args: [{
|
|
3498
|
+
selector: '[strctDropdownTrigger]',
|
|
3499
|
+
host: {
|
|
3500
|
+
'[attr.aria-haspopup]': "dd ? (dd.popover() ? 'dialog' : 'menu') : null",
|
|
3501
|
+
'[attr.aria-expanded]': 'dd ? dd.open() : null',
|
|
3502
|
+
},
|
|
3503
|
+
}]
|
|
3504
|
+
}] });
|
|
3484
3505
|
/** A selectable row inside a `<strct-dropdown>`. */
|
|
3485
3506
|
class StrctDropdownItem {
|
|
3486
3507
|
/** Danger. */
|
|
@@ -8262,6 +8283,11 @@ const DG_LABELS = {
|
|
|
8262
8283
|
chooseColumns: 'Choose columns',
|
|
8263
8284
|
refresh: 'Refresh data',
|
|
8264
8285
|
actions: 'Actions',
|
|
8286
|
+
filterColumn: 'Filter',
|
|
8287
|
+
filterPlaceholder: 'Filter…',
|
|
8288
|
+
clearFilter: 'Clear filter',
|
|
8289
|
+
editCell: 'Edit',
|
|
8290
|
+
toggleChildren: 'Toggle children',
|
|
8265
8291
|
};
|
|
8266
8292
|
/** Utility-column widths used when sticky columns are active (px). */
|
|
8267
8293
|
const UTIL_W = { detail: 36, expand: 36, sel: 40 };
|
|
@@ -8389,6 +8415,19 @@ class StrctDatagrid {
|
|
|
8389
8415
|
* groups. Paging is bypassed while grouped; not combinable with `virtual`.
|
|
8390
8416
|
*/
|
|
8391
8417
|
groupBy = input(null, ...(ngDevMode ? [{ debugName: "groupBy" }] : /* istanbul ignore next */ []));
|
|
8418
|
+
/**
|
|
8419
|
+
* Two-way per-column filter state: `key → contains-text` (text filter) or
|
|
8420
|
+
* `key → unknown[]` (checked value set). Applied client-side; in `lazy`
|
|
8421
|
+
* mode it is passed through on `lazyLoad` instead.
|
|
8422
|
+
*/
|
|
8423
|
+
filters = model({}, ...(ngDevMode ? [{ debugName: "filters" }] : /* istanbul ignore next */ []));
|
|
8424
|
+
/**
|
|
8425
|
+
* Tree-grid: the row property holding a row's children array. Rows render
|
|
8426
|
+
* hierarchically with indent + carets; sorting applies per sibling level;
|
|
8427
|
+
* an active filter shows matches with their ancestors, force-expanded.
|
|
8428
|
+
* Not combinable with `groupBy` or `lazy`.
|
|
8429
|
+
*/
|
|
8430
|
+
childrenKey = input(null, ...(ngDevMode ? [{ debugName: "childrenKey" }] : /* istanbul ignore next */ []));
|
|
8392
8431
|
/** Emitted when the selection changes. */
|
|
8393
8432
|
selectionChange = output();
|
|
8394
8433
|
/** Emitted when the refresh button is clicked. */
|
|
@@ -8397,6 +8436,9 @@ class StrctDatagrid {
|
|
|
8397
8436
|
rowAction = output();
|
|
8398
8437
|
/** Server-side data request (lazy mode): load this page / sort and set `rows`. */
|
|
8399
8438
|
lazyLoad = output();
|
|
8439
|
+
/** An editable cell was committed (Enter / blur). The grid does not mutate
|
|
8440
|
+
* rows — apply `value` to your data and pass the updated array back in. */
|
|
8441
|
+
cellEdit = output();
|
|
8400
8442
|
menu = inject(StrctMenuService);
|
|
8401
8443
|
detailDef = contentChild(StrctRowDetailDef, ...(ngDevMode ? [{ debugName: "detailDef" }] : /* istanbul ignore next */ []));
|
|
8402
8444
|
actionBarDef = contentChild(StrctDatagridActionBar, ...(ngDevMode ? [{ debugName: "actionBarDef" }] : /* istanbul ignore next */ []));
|
|
@@ -8493,6 +8535,11 @@ class StrctDatagrid {
|
|
|
8493
8535
|
dragKey = signal(null, ...(ngDevMode ? [{ debugName: "dragKey" }] : /* istanbul ignore next */ []));
|
|
8494
8536
|
dropKey = signal(null, ...(ngDevMode ? [{ debugName: "dropKey" }] : /* istanbul ignore next */ []));
|
|
8495
8537
|
onColDragStart(key, event) {
|
|
8538
|
+
// Text selection inside the filter panel must not start a column drag.
|
|
8539
|
+
if (event.target?.closest?.('.strct-dg__filterpanel')) {
|
|
8540
|
+
event.preventDefault();
|
|
8541
|
+
return;
|
|
8542
|
+
}
|
|
8496
8543
|
if (!this.reorderable())
|
|
8497
8544
|
return;
|
|
8498
8545
|
this.dragKey.set(key);
|
|
@@ -8562,12 +8609,154 @@ class StrctDatagrid {
|
|
|
8562
8609
|
itemKey(it) {
|
|
8563
8610
|
return it.group ? `strct-group:${String(it.group.key)}` : this.rowKey(it.row);
|
|
8564
8611
|
}
|
|
8612
|
+
// ── Column filter popover ──────────────────────────────────────
|
|
8613
|
+
filterOpenKey = signal(null, ...(ngDevMode ? [{ debugName: "filterOpenKey" }] : /* istanbul ignore next */ []));
|
|
8614
|
+
toggleFilterPanel(key, event) {
|
|
8615
|
+
event.stopPropagation();
|
|
8616
|
+
this.filterOpenKey.set(this.filterOpenKey() === key ? null : key);
|
|
8617
|
+
}
|
|
8618
|
+
hasFilter(key) {
|
|
8619
|
+
const v = this.filters()[key];
|
|
8620
|
+
return Array.isArray(v) ? v.length > 0 : v != null && String(v).trim() !== '';
|
|
8621
|
+
}
|
|
8622
|
+
textFilter(key) {
|
|
8623
|
+
const v = this.filters()[key];
|
|
8624
|
+
return typeof v === 'string' ? v : '';
|
|
8625
|
+
}
|
|
8626
|
+
setTextFilter(key, value) {
|
|
8627
|
+
this.filters.update((f) => ({ ...f, [key]: value }));
|
|
8628
|
+
}
|
|
8629
|
+
optionChecked(key, opt) {
|
|
8630
|
+
const v = this.filters()[key];
|
|
8631
|
+
return Array.isArray(v) && v.includes(opt);
|
|
8632
|
+
}
|
|
8633
|
+
toggleFilterOption(key, opt) {
|
|
8634
|
+
this.filters.update((f) => {
|
|
8635
|
+
const cur = Array.isArray(f[key]) ? [...f[key]] : [];
|
|
8636
|
+
const i = cur.indexOf(opt);
|
|
8637
|
+
if (i >= 0)
|
|
8638
|
+
cur.splice(i, 1);
|
|
8639
|
+
else
|
|
8640
|
+
cur.push(opt);
|
|
8641
|
+
return { ...f, [key]: cur };
|
|
8642
|
+
});
|
|
8643
|
+
}
|
|
8644
|
+
clearFilter(key) {
|
|
8645
|
+
this.filters.update((f) => {
|
|
8646
|
+
const next = { ...f };
|
|
8647
|
+
delete next[key];
|
|
8648
|
+
return next;
|
|
8649
|
+
});
|
|
8650
|
+
}
|
|
8651
|
+
onDocClickFilter(event) {
|
|
8652
|
+
if (!this.filterOpenKey())
|
|
8653
|
+
return;
|
|
8654
|
+
const t = event.target;
|
|
8655
|
+
if (t.closest('.strct-dg__filterpanel') || t.closest('.strct-dg__filterbtn'))
|
|
8656
|
+
return;
|
|
8657
|
+
this.filterOpenKey.set(null);
|
|
8658
|
+
}
|
|
8659
|
+
onEscapeFilter() {
|
|
8660
|
+
this.filterOpenKey.set(null);
|
|
8661
|
+
}
|
|
8662
|
+
// ── Tree-grid state ────────────────────────────────────────────
|
|
8663
|
+
expandedTree = signal(new Set(), ...(ngDevMode ? [{ debugName: "expandedTree" }] : /* istanbul ignore next */ []));
|
|
8664
|
+
treeDepth(row) {
|
|
8665
|
+
return this.treeFlat().meta.get(this.rowKey(row))?.depth ?? 0;
|
|
8666
|
+
}
|
|
8667
|
+
treeHasChildren(row) {
|
|
8668
|
+
return this.treeFlat().meta.get(this.rowKey(row))?.hasChildren ?? false;
|
|
8669
|
+
}
|
|
8670
|
+
treeExpanded(row) {
|
|
8671
|
+
return this.treeFlat().meta.get(this.rowKey(row))?.expanded ?? false;
|
|
8672
|
+
}
|
|
8673
|
+
/** Expand / collapse one tree row. */
|
|
8674
|
+
toggleTreeRow(row) {
|
|
8675
|
+
const key = this.rowKey(row);
|
|
8676
|
+
const next = new Set(this.expandedTree());
|
|
8677
|
+
if (next.has(key))
|
|
8678
|
+
next.delete(key);
|
|
8679
|
+
else
|
|
8680
|
+
next.add(key);
|
|
8681
|
+
this.expandedTree.set(next);
|
|
8682
|
+
}
|
|
8683
|
+
// ── Inline cell editing ────────────────────────────────────────
|
|
8684
|
+
hostRef = inject(ElementRef);
|
|
8685
|
+
editing = signal(null, ...(ngDevMode ? [{ debugName: "editing" }] : /* istanbul ignore next */ []));
|
|
8686
|
+
isEditingCell(row, col) {
|
|
8687
|
+
const e = this.editing();
|
|
8688
|
+
return !!e && e.rowKey === this.rowKey(row) && e.colKey === col.key;
|
|
8689
|
+
}
|
|
8690
|
+
startEdit(row, col) {
|
|
8691
|
+
this.editing.set({ rowKey: this.rowKey(row), colKey: col.key });
|
|
8692
|
+
setTimeout(() => this.hostRef.nativeElement.querySelector('.strct-dg__editinput')?.focus());
|
|
8693
|
+
}
|
|
8694
|
+
commitEdit(row, col, event) {
|
|
8695
|
+
if (!this.isEditingCell(row, col))
|
|
8696
|
+
return; // Escape/Enter already closed it
|
|
8697
|
+
const value = event.target.value;
|
|
8698
|
+
this.editing.set(null);
|
|
8699
|
+
const previous = row[col.key];
|
|
8700
|
+
if (String(previous ?? '') !== value)
|
|
8701
|
+
this.cellEdit.emit({ row, column: col, value, previous });
|
|
8702
|
+
}
|
|
8703
|
+
cancelEdit(event) {
|
|
8704
|
+
event?.stopPropagation();
|
|
8705
|
+
this.editing.set(null);
|
|
8706
|
+
}
|
|
8707
|
+
/** Active (non-empty) filter entries. */
|
|
8708
|
+
activeFilters = computed(() => Object.entries(this.filters()).filter(([, v]) => Array.isArray(v) ? v.length > 0 : String(v).trim() !== ''), ...(ngDevMode ? [{ debugName: "activeFilters" }] : /* istanbul ignore next */ []));
|
|
8709
|
+
matchesFilters(row) {
|
|
8710
|
+
return this.activeFilters().every(([key, v]) => Array.isArray(v)
|
|
8711
|
+
? v.some((opt) => opt === row[key])
|
|
8712
|
+
: String(row[key] ?? '')
|
|
8713
|
+
.toLowerCase()
|
|
8714
|
+
.includes(String(v).trim().toLowerCase()));
|
|
8715
|
+
}
|
|
8716
|
+
/** Rows surviving the per-column filters (client-side modes only). */
|
|
8717
|
+
filteredRows = computed(() => {
|
|
8718
|
+
if (this.lazy() || !this.activeFilters().length)
|
|
8719
|
+
return this.rows();
|
|
8720
|
+
return this.rows().filter((r) => this.matchesFilters(r));
|
|
8721
|
+
}, ...(ngDevMode ? [{ debugName: "filteredRows" }] : /* istanbul ignore next */ []));
|
|
8722
|
+
/** Tree mode: flattened visible rows + per-row depth/children/expanded meta. */
|
|
8723
|
+
treeFlat = computed(() => {
|
|
8724
|
+
const ck = this.childrenKey();
|
|
8725
|
+
const { key, dir } = this.sort();
|
|
8726
|
+
const sign = dir === 'asc' ? 1 : -1;
|
|
8727
|
+
const sortRows = (rs) => {
|
|
8728
|
+
const c = [...rs];
|
|
8729
|
+
if (key)
|
|
8730
|
+
c.sort((a, b) => sign * this.compare(a[key], b[key]));
|
|
8731
|
+
return c;
|
|
8732
|
+
};
|
|
8733
|
+
const filtering = this.activeFilters().length > 0;
|
|
8734
|
+
const kidsOf = (r) => r[ck] ?? [];
|
|
8735
|
+
const survives = (r) => this.matchesFilters(r) || kidsOf(r).some(survives);
|
|
8736
|
+
const meta = new Map();
|
|
8737
|
+
const rowsOut = [];
|
|
8738
|
+
const walk = (rs, depth) => {
|
|
8739
|
+
for (const r of sortRows(rs)) {
|
|
8740
|
+
const kids = filtering ? kidsOf(r).filter(survives) : kidsOf(r);
|
|
8741
|
+
// Filtering force-expands so matches are never hidden under a fold.
|
|
8742
|
+
const expanded = filtering ? true : this.expandedTree().has(this.rowKey(r));
|
|
8743
|
+
meta.set(this.rowKey(r), { depth, hasChildren: kids.length > 0, expanded });
|
|
8744
|
+
rowsOut.push(r);
|
|
8745
|
+
if (kids.length && expanded)
|
|
8746
|
+
walk(kids, depth + 1);
|
|
8747
|
+
}
|
|
8748
|
+
};
|
|
8749
|
+
walk(filtering ? this.rows().filter(survives) : this.rows(), 0);
|
|
8750
|
+
return { rows: rowsOut, meta };
|
|
8751
|
+
}, ...(ngDevMode ? [{ debugName: "treeFlat" }] : /* istanbul ignore next */ []));
|
|
8565
8752
|
sorted = computed(() => {
|
|
8566
8753
|
// Server-side mode: rows arrive already ordered / sliced — never touch them.
|
|
8567
8754
|
if (this.lazy())
|
|
8568
8755
|
return this.rows();
|
|
8756
|
+
if (this.childrenKey())
|
|
8757
|
+
return this.treeFlat().rows;
|
|
8569
8758
|
const { key, dir } = this.sort();
|
|
8570
|
-
const data = [...this.
|
|
8759
|
+
const data = [...this.filteredRows()];
|
|
8571
8760
|
if (!key)
|
|
8572
8761
|
return data;
|
|
8573
8762
|
const sign = dir === 'asc' ? 1 : -1;
|
|
@@ -8689,6 +8878,11 @@ class StrctDatagrid {
|
|
|
8689
8878
|
if (this.page() > pageCount)
|
|
8690
8879
|
this.page.set(pageCount);
|
|
8691
8880
|
});
|
|
8881
|
+
// A changed filter always restarts from page 1.
|
|
8882
|
+
effect(() => {
|
|
8883
|
+
this.filters();
|
|
8884
|
+
untracked(() => this.page.set(1));
|
|
8885
|
+
});
|
|
8692
8886
|
// Seed the selection from initialSelection (re-runs only when the input changes,
|
|
8693
8887
|
// so a user's own toggles afterward are untouched).
|
|
8694
8888
|
effect(() => {
|
|
@@ -8707,6 +8901,7 @@ class StrctDatagrid {
|
|
|
8707
8901
|
pageSize: this.pageSize(),
|
|
8708
8902
|
sortKey: this.sort().key,
|
|
8709
8903
|
sortDir: this.sort().dir,
|
|
8904
|
+
filters: this.filters(),
|
|
8710
8905
|
};
|
|
8711
8906
|
const key = JSON.stringify(state);
|
|
8712
8907
|
untracked(() => {
|
|
@@ -8953,7 +9148,7 @@ class StrctDatagrid {
|
|
|
8953
9148
|
return String(a ?? '').localeCompare(String(b ?? ''), undefined, { numeric: true });
|
|
8954
9149
|
}
|
|
8955
9150
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctDatagrid, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
8956
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: StrctDatagrid, isStandalone: true, selector: "strct-datagrid", inputs: { columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: true, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: true, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, expandable: { classPropertyName: "expandable", publicName: "expandable", isSignal: true, isRequired: false, transformFunction: null }, detailPane: { classPropertyName: "detailPane", publicName: "detailPane", isSignal: true, isRequired: false, transformFunction: null }, compact: { classPropertyName: "compact", publicName: "compact", isSignal: true, isRequired: false, transformFunction: null }, singleLine: { classPropertyName: "singleLine", publicName: "singleLine", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, resizable: { classPropertyName: "resizable", publicName: "resizable", isSignal: true, isRequired: false, transformFunction: null }, columnChooser: { classPropertyName: "columnChooser", publicName: "columnChooser", isSignal: true, isRequired: false, transformFunction: null }, sync: { classPropertyName: "sync", publicName: "sync", isSignal: true, isRequired: false, transformFunction: null }, footerActionsDisabled: { classPropertyName: "footerActionsDisabled", publicName: "footerActionsDisabled", isSignal: true, isRequired: false, transformFunction: null }, rowId: { classPropertyName: "rowId", publicName: "rowId", isSignal: true, isRequired: false, transformFunction: null }, rowActions: { classPropertyName: "rowActions", publicName: "rowActions", isSignal: true, isRequired: false, transformFunction: null }, initialSelection: { classPropertyName: "initialSelection", publicName: "initialSelection", isSignal: true, isRequired: false, transformFunction: null }, labels: { classPropertyName: "labels", publicName: "labels", isSignal: true, isRequired: false, transformFunction: null }, virtual: { classPropertyName: "virtual", publicName: "virtual", isSignal: true, isRequired: false, transformFunction: null }, viewportHeight: { classPropertyName: "viewportHeight", publicName: "viewportHeight", isSignal: true, isRequired: false, transformFunction: null }, rowHeight: { classPropertyName: "rowHeight", publicName: "rowHeight", isSignal: true, isRequired: false, transformFunction: null }, lazy: { classPropertyName: "lazy", publicName: "lazy", isSignal: true, isRequired: false, transformFunction: null }, total: { classPropertyName: "total", publicName: "total", isSignal: true, isRequired: false, transformFunction: null }, stateKey: { classPropertyName: "stateKey", publicName: "stateKey", isSignal: true, isRequired: false, transformFunction: null }, columnState: { classPropertyName: "columnState", publicName: "columnState", isSignal: true, isRequired: false, transformFunction: null }, reorderable: { classPropertyName: "reorderable", publicName: "reorderable", isSignal: true, isRequired: false, transformFunction: null }, groupBy: { classPropertyName: "groupBy", publicName: "groupBy", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { columnState: "columnStateChange", selectionChange: "selectionChange", syncChange: "syncChange", rowAction: "rowAction", lazyLoad: "lazyLoad" }, host: { properties: { "class.strct-dg-host--compact": "compact()", "class.strct-dg-host--singleline": "singleLine()", "class.strct-dg-host--virtual": "virtual()", "class.strct-dg-host--sticky": "stickyActive()" }, classAttribute: "strct-dg-host" }, queries: [{ propertyName: "detailDef", first: true, predicate: StrctRowDetailDef, descendants: true, isSignal: true }, { propertyName: "actionBarDef", first: true, predicate: StrctDatagridActionBar, descendants: true, isSignal: true }, { propertyName: "cellDefs", predicate: StrctCellDef, isSignal: true }], ngImport: i0, template: `
|
|
9151
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: StrctDatagrid, isStandalone: true, selector: "strct-datagrid", inputs: { columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: true, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: true, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, expandable: { classPropertyName: "expandable", publicName: "expandable", isSignal: true, isRequired: false, transformFunction: null }, detailPane: { classPropertyName: "detailPane", publicName: "detailPane", isSignal: true, isRequired: false, transformFunction: null }, compact: { classPropertyName: "compact", publicName: "compact", isSignal: true, isRequired: false, transformFunction: null }, singleLine: { classPropertyName: "singleLine", publicName: "singleLine", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, resizable: { classPropertyName: "resizable", publicName: "resizable", isSignal: true, isRequired: false, transformFunction: null }, columnChooser: { classPropertyName: "columnChooser", publicName: "columnChooser", isSignal: true, isRequired: false, transformFunction: null }, sync: { classPropertyName: "sync", publicName: "sync", isSignal: true, isRequired: false, transformFunction: null }, footerActionsDisabled: { classPropertyName: "footerActionsDisabled", publicName: "footerActionsDisabled", isSignal: true, isRequired: false, transformFunction: null }, rowId: { classPropertyName: "rowId", publicName: "rowId", isSignal: true, isRequired: false, transformFunction: null }, rowActions: { classPropertyName: "rowActions", publicName: "rowActions", isSignal: true, isRequired: false, transformFunction: null }, initialSelection: { classPropertyName: "initialSelection", publicName: "initialSelection", isSignal: true, isRequired: false, transformFunction: null }, labels: { classPropertyName: "labels", publicName: "labels", isSignal: true, isRequired: false, transformFunction: null }, virtual: { classPropertyName: "virtual", publicName: "virtual", isSignal: true, isRequired: false, transformFunction: null }, viewportHeight: { classPropertyName: "viewportHeight", publicName: "viewportHeight", isSignal: true, isRequired: false, transformFunction: null }, rowHeight: { classPropertyName: "rowHeight", publicName: "rowHeight", isSignal: true, isRequired: false, transformFunction: null }, lazy: { classPropertyName: "lazy", publicName: "lazy", isSignal: true, isRequired: false, transformFunction: null }, total: { classPropertyName: "total", publicName: "total", isSignal: true, isRequired: false, transformFunction: null }, stateKey: { classPropertyName: "stateKey", publicName: "stateKey", isSignal: true, isRequired: false, transformFunction: null }, columnState: { classPropertyName: "columnState", publicName: "columnState", isSignal: true, isRequired: false, transformFunction: null }, reorderable: { classPropertyName: "reorderable", publicName: "reorderable", isSignal: true, isRequired: false, transformFunction: null }, groupBy: { classPropertyName: "groupBy", publicName: "groupBy", isSignal: true, isRequired: false, transformFunction: null }, filters: { classPropertyName: "filters", publicName: "filters", isSignal: true, isRequired: false, transformFunction: null }, childrenKey: { classPropertyName: "childrenKey", publicName: "childrenKey", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { columnState: "columnStateChange", filters: "filtersChange", selectionChange: "selectionChange", syncChange: "syncChange", rowAction: "rowAction", lazyLoad: "lazyLoad", cellEdit: "cellEdit" }, host: { listeners: { "document:click": "onDocClickFilter($event)", "document:keydown.escape": "onEscapeFilter()" }, properties: { "class.strct-dg-host--compact": "compact()", "class.strct-dg-host--singleline": "singleLine()", "class.strct-dg-host--virtual": "virtual()", "class.strct-dg-host--sticky": "stickyActive()" }, classAttribute: "strct-dg-host" }, queries: [{ propertyName: "detailDef", first: true, predicate: StrctRowDetailDef, descendants: true, isSignal: true }, { propertyName: "actionBarDef", first: true, predicate: StrctDatagridActionBar, descendants: true, isSignal: true }, { propertyName: "cellDefs", predicate: StrctCellDef, isSignal: true }], ngImport: i0, template: `
|
|
8957
9152
|
@if (actionBarDef()) {
|
|
8958
9153
|
<div class="strct-dg__toolbar"><ng-content select="[strctDatagridActionBar]" /></div>
|
|
8959
9154
|
}
|
|
@@ -8967,7 +9162,7 @@ class StrctDatagrid {
|
|
|
8967
9162
|
[style.max-height.px]="virtual() ? viewportHeight() : null"
|
|
8968
9163
|
(scroll)="virtual() && onScroll($event)"
|
|
8969
9164
|
>
|
|
8970
|
-
<table class="strct-dg">
|
|
9165
|
+
<table class="strct-dg" [attr.role]="childrenKey() ? 'treegrid' : null">
|
|
8971
9166
|
<thead>
|
|
8972
9167
|
<tr>
|
|
8973
9168
|
@if (canDetail()) {
|
|
@@ -9027,6 +9222,62 @@ class StrctDatagrid {
|
|
|
9027
9222
|
[size]="13"
|
|
9028
9223
|
/>
|
|
9029
9224
|
}
|
|
9225
|
+
@if (col.filterable || col.filterOptions) {
|
|
9226
|
+
<button
|
|
9227
|
+
#fbtn
|
|
9228
|
+
type="button"
|
|
9229
|
+
class="strct-dg__filterbtn"
|
|
9230
|
+
[class.strct-dg__filterbtn--active]="hasFilter(col.key)"
|
|
9231
|
+
[attr.aria-expanded]="filterOpenKey() === col.key"
|
|
9232
|
+
[attr.aria-label]="L().filterColumn + ': ' + col.label"
|
|
9233
|
+
(click)="toggleFilterPanel(col.key, $event)"
|
|
9234
|
+
(keydown.enter)="$event.stopPropagation()"
|
|
9235
|
+
(keydown.space)="$event.stopPropagation()"
|
|
9236
|
+
>
|
|
9237
|
+
<strct-icon name="filter" [size]="12" />
|
|
9238
|
+
</button>
|
|
9239
|
+
@if (filterOpenKey() === col.key) {
|
|
9240
|
+
<div
|
|
9241
|
+
class="strct-dg__filterpanel"
|
|
9242
|
+
role="dialog"
|
|
9243
|
+
[attr.aria-label]="L().filterColumn + ': ' + col.label"
|
|
9244
|
+
[strctOverlay]="fbtn"
|
|
9245
|
+
strctOverlayPlacement="bottom-start"
|
|
9246
|
+
(click)="$event.stopPropagation()"
|
|
9247
|
+
(keydown.enter)="$event.stopPropagation()"
|
|
9248
|
+
(keydown.space)="$event.stopPropagation()"
|
|
9249
|
+
>
|
|
9250
|
+
@if (col.filterOptions; as opts) {
|
|
9251
|
+
@for (opt of opts; track $index) {
|
|
9252
|
+
<label class="strct-dg__filteropt">
|
|
9253
|
+
<input
|
|
9254
|
+
type="checkbox"
|
|
9255
|
+
[checked]="optionChecked(col.key, opt)"
|
|
9256
|
+
(change)="toggleFilterOption(col.key, opt)"
|
|
9257
|
+
/>
|
|
9258
|
+
<span>{{ opt }}</span>
|
|
9259
|
+
</label>
|
|
9260
|
+
}
|
|
9261
|
+
} @else {
|
|
9262
|
+
<input
|
|
9263
|
+
class="strct-dg__filterinput"
|
|
9264
|
+
type="text"
|
|
9265
|
+
[attr.placeholder]="L().filterPlaceholder"
|
|
9266
|
+
[value]="textFilter(col.key)"
|
|
9267
|
+
(input)="setTextFilter(col.key, $any($event.target).value)"
|
|
9268
|
+
/>
|
|
9269
|
+
}
|
|
9270
|
+
<button
|
|
9271
|
+
type="button"
|
|
9272
|
+
class="strct-dg__filterclear"
|
|
9273
|
+
[disabled]="!hasFilter(col.key)"
|
|
9274
|
+
(click)="clearFilter(col.key)"
|
|
9275
|
+
>
|
|
9276
|
+
{{ L().clearFilter }}
|
|
9277
|
+
</button>
|
|
9278
|
+
</div>
|
|
9279
|
+
}
|
|
9280
|
+
}
|
|
9030
9281
|
</span>
|
|
9031
9282
|
@if (resizable() && col.resizable !== false) {
|
|
9032
9283
|
<div
|
|
@@ -9097,6 +9348,10 @@ class StrctDatagrid {
|
|
|
9097
9348
|
<tr
|
|
9098
9349
|
[class.strct-dg__row--selected]="isSelected(row)"
|
|
9099
9350
|
[class.strct-dg__row--active]="paneOpen() && row === activeRow()"
|
|
9351
|
+
[attr.aria-level]="childrenKey() ? treeDepth(row) + 1 : null"
|
|
9352
|
+
[attr.aria-expanded]="
|
|
9353
|
+
childrenKey() && treeHasChildren(row) ? treeExpanded(row) : null
|
|
9354
|
+
"
|
|
9100
9355
|
>
|
|
9101
9356
|
@if (canDetail()) {
|
|
9102
9357
|
<td
|
|
@@ -9147,14 +9402,51 @@ class StrctDatagrid {
|
|
|
9147
9402
|
/>
|
|
9148
9403
|
</td>
|
|
9149
9404
|
}
|
|
9150
|
-
@for (col of visibleColumns(); track col.key) {
|
|
9405
|
+
@for (col of visibleColumns(); track col.key; let colIdx = $index) {
|
|
9151
9406
|
<td
|
|
9152
9407
|
[style.text-align]="col.align ?? 'start'"
|
|
9153
9408
|
[class.strct-dg__cell--sticky]="isSticky(col)"
|
|
9154
9409
|
[class.strct-dg__cell--sticky-last]="col.key === lastStickyKey()"
|
|
9410
|
+
[class.strct-dg__cell--editable]="col.editable"
|
|
9155
9411
|
[style.insetInlineStart.px]="stickyLeft(col.key)"
|
|
9412
|
+
(dblclick)="col.editable && startEdit(row, col)"
|
|
9156
9413
|
>
|
|
9157
|
-
@if (
|
|
9414
|
+
@if (childrenKey() && colIdx === 0) {
|
|
9415
|
+
<span
|
|
9416
|
+
class="strct-dg__treepad"
|
|
9417
|
+
[style.width.px]="treeDepth(row) * 18"
|
|
9418
|
+
aria-hidden="true"
|
|
9419
|
+
></span>
|
|
9420
|
+
@if (treeHasChildren(row)) {
|
|
9421
|
+
<button
|
|
9422
|
+
type="button"
|
|
9423
|
+
class="strct-dg__treebtn"
|
|
9424
|
+
[class.strct-dg__treebtn--open]="treeExpanded(row)"
|
|
9425
|
+
[attr.aria-expanded]="treeExpanded(row)"
|
|
9426
|
+
[attr.aria-label]="L().toggleChildren"
|
|
9427
|
+
(click)="toggleTreeRow(row)"
|
|
9428
|
+
>
|
|
9429
|
+
<strct-icon name="chevronRight" [size]="12" [strokeWidth]="1.7" />
|
|
9430
|
+
</button>
|
|
9431
|
+
} @else {
|
|
9432
|
+
<span
|
|
9433
|
+
class="strct-dg__treebtn strct-dg__treebtn--leaf"
|
|
9434
|
+
aria-hidden="true"
|
|
9435
|
+
></span>
|
|
9436
|
+
}
|
|
9437
|
+
}
|
|
9438
|
+
@if (isEditingCell(row, col)) {
|
|
9439
|
+
<input
|
|
9440
|
+
class="strct-dg__editinput"
|
|
9441
|
+
type="text"
|
|
9442
|
+
[value]="row[col.key] ?? ''"
|
|
9443
|
+
[attr.aria-label]="L().editCell + ': ' + col.label"
|
|
9444
|
+
(keydown.enter)="commitEdit(row, col, $event)"
|
|
9445
|
+
(keydown.escape)="cancelEdit($event)"
|
|
9446
|
+
(blur)="commitEdit(row, col, $event)"
|
|
9447
|
+
(click)="$event.stopPropagation()"
|
|
9448
|
+
/>
|
|
9449
|
+
} @else if (cellTemplate(col.key); as tpl) {
|
|
9158
9450
|
<ng-container
|
|
9159
9451
|
[ngTemplateOutlet]="tpl"
|
|
9160
9452
|
[ngTemplateOutletContext]="{
|
|
@@ -9292,7 +9584,7 @@ class StrctDatagrid {
|
|
|
9292
9584
|
</div>
|
|
9293
9585
|
</div>
|
|
9294
9586
|
}
|
|
9295
|
-
`, isInline: true, styles: [".strct-dg-host{display:block;border:1px solid var(--b2);border-radius:10px;background:var(--bg-2);box-shadow:var(--shadow-rest)}.strct-dg__scroll{flex:1 1 auto;min-width:0;overflow-x:auto;-webkit-overflow-scrolling:touch}.strct-dg-host--virtual .strct-dg__scroll{overflow-y:auto}.strct-dg-host--virtual .strct-dg thead th{position:sticky;top:0;z-index:5}.strct-dg__vspacer td{padding:0;border:0;background:transparent}.strct-dg th[draggable]{cursor:grab}.strct-dg__th--drop{box-shadow:inset 3px 0 0 var(--acc)}.strct-dg__grouprow td{background:var(--bg-2)!important;padding:0}.strct-dg__groupbtn{display:flex;align-items:center;gap:8px;width:100%;padding:7px 13px;border:0;background:transparent;color:var(--t1);font-family:var(--font);font-size:12.5px;font-weight:600;cursor:pointer;text-align:start}.strct-dg__groupbtn:hover{background:var(--bg-3)}.strct-dg__groupbtn:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-dg__groupchev{display:inline-flex;color:var(--t3);transition:transform .15s ease}.strct-dg__groupchev--open{transform:rotate(90deg)}@media(prefers-reduced-motion:reduce){.strct-dg__groupchev{transition:none}}.strct-dg__groupcount{min-width:18px;height:18px;padding:0 5px;display:inline-flex;align-items:center;justify-content:center;font-size:12px;border-radius:9px;background:var(--acc-m);color:var(--acc);font-variant-numeric:tabular-nums}.strct-dg-host--sticky .strct-dg,.strct-dg-host--virtual .strct-dg{border-collapse:separate;border-spacing:0;overflow:visible;border-radius:0}.strct-dg-host--sticky .strct-dg{width:max-content;min-width:100%}.strct-dg .strct-dg__cell--sticky{position:sticky;z-index:3}.strct-dg thead th.strct-dg__cell--sticky{z-index:6}.strct-dg .strct-dg__cell--sticky-last:after{content:\"\";position:absolute;top:0;bottom:0;inset-inline-end:-1px;width:7px;pointer-events:none;background:linear-gradient(to right,rgba(0,0,0,.14),transparent)}[dir=rtl] .strct-dg .strct-dg__cell--sticky-last:after{background:linear-gradient(to left,rgba(0,0,0,.14),transparent)}.strct-dg-host--sticky .strct-dg__expandcol,.strct-dg-host--sticky .strct-dg__expandcell{width:36px;min-width:36px;max-width:36px;box-sizing:border-box}.strct-dg-host--sticky .strct-dg__sel{width:40px;min-width:40px;max-width:40px;box-sizing:border-box}.strct-dg-host--sticky tbody .strct-dg__cell--sticky{background:var(--bg-1)}[data-theme=dark] .strct-dg-host--sticky tbody .strct-dg__cell--sticky{background:var(--bg-3)}.strct-dg-host--sticky tbody tr:hover .strct-dg__cell--sticky{background:linear-gradient(var(--acc-s),var(--acc-s)) var(--bg-1)}[data-theme=dark] .strct-dg-host--sticky tbody tr:hover .strct-dg__cell--sticky{background:linear-gradient(var(--acc-s),var(--acc-s)) var(--bg-3)}.strct-dg-host--sticky tbody .strct-dg__row--selected .strct-dg__cell--sticky{background:linear-gradient(var(--acc-m),var(--acc-m)) var(--bg-1)}[data-theme=dark] .strct-dg-host--sticky tbody .strct-dg__row--selected .strct-dg__cell--sticky{background:linear-gradient(var(--acc-m),var(--acc-m)) var(--bg-3)}.strct-dg{width:100%;border-collapse:collapse;font-size:13px;overflow:hidden;background:var(--bg-2)}.strct-dg-host:not(:has(.strct-dg__toolbar)) .strct-dg{border-start-start-radius:9px;border-start-end-radius:9px}.strct-dg-host:not(:has(.strct-dg__foot)) .strct-dg{border-end-start-radius:9px;border-end-end-radius:9px}.strct-dg th,.strct-dg td{padding:9px 13px;text-align:start;border-bottom:1px solid var(--b1)}.strct-dg tbody td{background:var(--bg-1)}[data-theme=dark] .strct-dg tbody td{background:var(--bg-3)}.strct-dg-host--compact .strct-dg th,.strct-dg-host--compact .strct-dg td{padding:5px 11px}.strct-dg-host--singleline .strct-dg tbody tr:not(.strct-dg__detailrow)>td{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:360px}.strct-dg th{position:relative;font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.4px;color:var(--t2);background:var(--bg-2);white-space:nowrap;-webkit-user-select:none;user-select:none}.strct-dg__th--sortable{cursor:pointer}.strct-dg__th--sortable:hover{color:var(--t1)}.strct-dg__th--sortable:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-dg__hd{display:inline-flex;align-items:center;gap:5px}.strct-dg__sorticon{color:var(--t3)}.strct-dg__th--sortable:hover .strct-dg__sorticon{color:var(--acc)}.strct-dg__resize{position:absolute;inset-inline-end:0;top:0;bottom:0;width:4px;cursor:col-resize;background:transparent;z-index:2}.strct-dg__resize:hover{background:var(--acc)}.strct-dg td{color:var(--t1)}.strct-dg tbody tr:last-child td{border-bottom:0}.strct-dg tbody tr:not(.strct-dg__detailrow):hover td{background:var(--acc-s)}.strct-dg__row--selected td{background:var(--acc-m)}.strct-dg__sel{width:1%;white-space:nowrap}.strct-dg__sel input{accent-color:var(--acc);width:15px;height:15px;cursor:pointer}.strct-dg__expandcol,.strct-dg__expandcell{width:1%;white-space:nowrap}.strct-dg__actioncol,.strct-dg__actioncell{width:1%;white-space:nowrap;text-align:end}.strct-dg__kebab{display:inline-flex;padding:4px;border:0;border-radius:5px;background:transparent;color:var(--t3);cursor:pointer;transition:color .14s ease,background .14s ease}.strct-dg__kebab:hover{color:var(--t1);background:var(--bg-3)}.strct-dg__expandbtn{display:inline-flex;padding:3px;border:0;border-radius:4px;background:transparent;color:var(--t3);cursor:pointer;transition:transform .15s ease,color .15s ease}.strct-dg__expandbtn:hover{color:var(--t1);background:var(--bg-3)}.strct-dg__expandbtn--open{transform:rotate(90deg);color:var(--acc)}.strct-dg__detailbtn{display:inline-flex;padding:3px;border:0;border-radius:4px;background:transparent;color:var(--t3);cursor:pointer;transition:color .14s ease,background .14s ease}.strct-dg__detailbtn:hover{color:var(--acc);background:var(--bg-3)}.strct-dg__detailbtn--active{color:var(--acc);background:var(--acc-m)}.strct-dg__detailrow td{background:var(--bg-2);padding:0}.strct-dg__detail{padding:14px 16px;font-size:13px;color:var(--t2)}.strct-dg__layout{display:flex;align-items:flex-start;min-width:0}.strct-dg__layout--paned{gap:0}.strct-dg__layout--paned .strct-dg__scroll{flex:0 0 auto;width:260px;min-width:260px;max-width:260px}.strct-dg__layout--paned .strct-dg{width:260px;min-width:260px;max-width:260px;flex-shrink:0;border-top-right-radius:0;border-bottom-right-radius:0}.strct-dg__row--clickable{cursor:pointer}.strct-dg__row--active td{background:var(--acc-m)}.strct-dg__layout--paned .strct-dg__row--active td:last-child{position:relative;padding-inline-end:26px}.strct-dg__layout--paned .strct-dg__row--active td:last-child:after{content:\"\";position:absolute;right:11px;top:50%;width:6px;height:6px;border-top:1.6px solid var(--acc);border-inline-end:1.6px solid var(--acc);transform:translateY(-50%) rotate(45deg)}.strct-dg__pane{flex:1;min-width:0;align-self:stretch;background:var(--bg-1);border:1px solid var(--b2);border-inline-start:2px solid var(--acc);border-radius:0 8px 8px 0;overflow:hidden;animation:strct-dg-pane-in .14s ease}.strct-dg__pane-head{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:11px 14px;border-bottom:1px solid var(--b1);font-size:13px;font-weight:600;color:var(--t1)}.strct-dg__pane-close{display:inline-flex;padding:3px;border:0;border-radius:4px;background:transparent;color:var(--t3);cursor:pointer}.strct-dg__pane-close:hover{color:var(--t1);background:var(--bg-3)}.strct-dg__pane-body{padding:14px 16px;font-size:13px;color:var(--t2)}@keyframes strct-dg-pane-in{0%{opacity:0;transform:translate(8px)}}@keyframes strct-skeleton-pulse{0%,to{opacity:.4}50%{opacity:.7}}.strct-dg__skeleton-block{height:12px;background:var(--bg-3);border-radius:var(--radius-sm);animation:strct-skeleton-pulse 1.4s ease infinite}.strct-dg__skeleton-row td{border-bottom:1px solid var(--b1)}.strct-dg__empty{text-align:center;color:var(--t3);padding:22px}.strct-dg__toolbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:9px 14px;border-bottom:1px solid var(--b2)}.strct-dg__foot{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:9px 14px;border-top:1px solid var(--b2);flex-wrap:wrap}.strct-dg__foot-left,.strct-dg__foot-right{display:flex;align-items:center;gap:12px}.strct-dg__actions{position:relative}.strct-dg__chooser-menu{position:absolute;bottom:calc(100% + 6px);left:0;z-index:10;min-width:180px;background:var(--bg-1);border:1px solid var(--b2);border-radius:8px;box-shadow:var(--shadow-pop);padding:6px;display:flex;flex-direction:column;gap:2px}.strct-dg__chooser-menu--right{inset-inline-start:auto;inset-inline-end:0}.strct-dg__chooser-item{display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:6px;cursor:pointer;font-size:12px;color:var(--t1);transition:background .1s ease;-webkit-user-select:none;user-select:none}.strct-dg__chooser-item:hover{background:var(--bg-3)}.strct-dg__count{font-size:12px;color:var(--t2)}.strct-dg__count-sep{margin:0 8px;color:var(--b2)}.strct-dg__count-sel{color:var(--acc)}\n"], dependencies: [{ kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "size", "strokeWidth", "badge", "ariaLabel"] }, { kind: "component", type: StrctPagination, selector: "strct-pagination", inputs: ["prevLabel", "nextLabel", "regionLabel", "total", "pageSize", "page"], outputs: ["pageChange"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: StrctCheckbox, selector: "strct-checkbox", inputs: ["checked", "isDisabled", "disabled", "indeterminate", "ariaLabel"], outputs: ["checkedChange", "isDisabledChange"] }, { kind: "component", type: StrctButton, selector: "button[strct-button], a[strct-button]", inputs: ["variant", "size", "solid", "block", "iconOnly"] }, { kind: "component", type: StrctButtonGroup, selector: "strct-button-group" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
9587
|
+
`, isInline: true, styles: [".strct-dg-host{display:block;border:1px solid var(--b2);border-radius:10px;background:var(--bg-2);box-shadow:var(--shadow-rest)}.strct-dg__scroll{flex:1 1 auto;min-width:0;overflow-x:auto;-webkit-overflow-scrolling:touch}.strct-dg-host--virtual .strct-dg__scroll{overflow-y:auto}.strct-dg-host--virtual .strct-dg thead th{position:sticky;top:0;z-index:5}.strct-dg__vspacer td{padding:0;border:0;background:transparent}.strct-dg th[draggable]{cursor:grab}.strct-dg__th--drop{box-shadow:inset 3px 0 0 var(--acc)}.strct-dg__grouprow td{background:var(--bg-2)!important;padding:0}.strct-dg__groupbtn{display:flex;align-items:center;gap:8px;width:100%;padding:7px 13px;border:0;background:transparent;color:var(--t1);font-family:var(--font);font-size:12.5px;font-weight:600;cursor:pointer;text-align:start}.strct-dg__groupbtn:hover{background:var(--bg-3)}.strct-dg__groupbtn:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-dg__groupchev{display:inline-flex;color:var(--t3);transition:transform .15s ease}.strct-dg__groupchev--open{transform:rotate(90deg)}@media(prefers-reduced-motion:reduce){.strct-dg__groupchev{transition:none}}.strct-dg__groupcount{min-width:18px;height:18px;padding:0 5px;display:inline-flex;align-items:center;justify-content:center;font-size:12px;border-radius:9px;background:var(--acc-m);color:var(--acc);font-variant-numeric:tabular-nums}.strct-dg-host--sticky .strct-dg,.strct-dg-host--virtual .strct-dg{border-collapse:separate;border-spacing:0;overflow:visible;border-radius:0}.strct-dg-host--sticky .strct-dg{width:max-content;min-width:100%}.strct-dg .strct-dg__cell--sticky{position:sticky;z-index:3}.strct-dg thead th.strct-dg__cell--sticky{z-index:6}.strct-dg .strct-dg__cell--sticky-last:after{content:\"\";position:absolute;top:0;bottom:0;inset-inline-end:-1px;width:7px;pointer-events:none;background:linear-gradient(to right,rgba(0,0,0,.14),transparent)}[dir=rtl] .strct-dg .strct-dg__cell--sticky-last:after{background:linear-gradient(to left,rgba(0,0,0,.14),transparent)}.strct-dg-host--sticky .strct-dg__expandcol,.strct-dg-host--sticky .strct-dg__expandcell{width:36px;min-width:36px;max-width:36px;box-sizing:border-box}.strct-dg-host--sticky .strct-dg__sel{width:40px;min-width:40px;max-width:40px;box-sizing:border-box}.strct-dg-host--sticky tbody .strct-dg__cell--sticky{background:var(--bg-1)}[data-theme=dark] .strct-dg-host--sticky tbody .strct-dg__cell--sticky{background:var(--bg-3)}.strct-dg-host--sticky tbody tr:hover .strct-dg__cell--sticky{background:linear-gradient(var(--acc-s),var(--acc-s)) var(--bg-1)}[data-theme=dark] .strct-dg-host--sticky tbody tr:hover .strct-dg__cell--sticky{background:linear-gradient(var(--acc-s),var(--acc-s)) var(--bg-3)}.strct-dg-host--sticky tbody .strct-dg__row--selected .strct-dg__cell--sticky{background:linear-gradient(var(--acc-m),var(--acc-m)) var(--bg-1)}[data-theme=dark] .strct-dg-host--sticky tbody .strct-dg__row--selected .strct-dg__cell--sticky{background:linear-gradient(var(--acc-m),var(--acc-m)) var(--bg-3)}.strct-dg{width:100%;border-collapse:collapse;font-size:13px;overflow:hidden;background:var(--bg-2)}.strct-dg-host:not(:has(.strct-dg__toolbar)) .strct-dg{border-start-start-radius:9px;border-start-end-radius:9px}.strct-dg-host:not(:has(.strct-dg__foot)) .strct-dg{border-end-start-radius:9px;border-end-end-radius:9px}.strct-dg th,.strct-dg td{padding:9px 13px;text-align:start;border-bottom:1px solid var(--b1)}.strct-dg tbody td{background:var(--bg-1)}[data-theme=dark] .strct-dg tbody td{background:var(--bg-3)}.strct-dg-host--compact .strct-dg th,.strct-dg-host--compact .strct-dg td{padding:5px 11px}.strct-dg-host--singleline .strct-dg tbody tr:not(.strct-dg__detailrow)>td{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:360px}.strct-dg th{position:relative;font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.4px;color:var(--t2);background:var(--bg-2);white-space:nowrap;-webkit-user-select:none;user-select:none}.strct-dg__th--sortable{cursor:pointer}.strct-dg__th--sortable:hover{color:var(--t1)}.strct-dg__th--sortable:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-dg__hd{display:inline-flex;align-items:center;gap:5px}.strct-dg__sorticon{color:var(--t3)}.strct-dg__th--sortable:hover .strct-dg__sorticon{color:var(--acc)}.strct-dg__resize{position:absolute;inset-inline-end:0;top:0;bottom:0;width:4px;cursor:col-resize;background:transparent;z-index:2}.strct-dg__resize:hover{background:var(--acc)}.strct-dg td{color:var(--t1)}.strct-dg tbody tr:last-child td{border-bottom:0}.strct-dg tbody tr:not(.strct-dg__detailrow):hover td{background:var(--acc-s)}.strct-dg__row--selected td{background:var(--acc-m)}.strct-dg__sel{width:1%;white-space:nowrap}.strct-dg__sel input{accent-color:var(--acc);width:15px;height:15px;cursor:pointer}.strct-dg__expandcol,.strct-dg__expandcell{width:1%;white-space:nowrap}.strct-dg__actioncol,.strct-dg__actioncell{width:1%;white-space:nowrap;text-align:end}.strct-dg__kebab{display:inline-flex;padding:4px;border:0;border-radius:5px;background:transparent;color:var(--t3);cursor:pointer;transition:color .14s ease,background .14s ease}.strct-dg__kebab:hover{color:var(--t1);background:var(--bg-3)}.strct-dg__expandbtn{display:inline-flex;padding:3px;border:0;border-radius:4px;background:transparent;color:var(--t3);cursor:pointer;transition:transform .15s ease,color .15s ease}.strct-dg__expandbtn:hover{color:var(--t1);background:var(--bg-3)}.strct-dg__expandbtn--open{transform:rotate(90deg);color:var(--acc)}.strct-dg__detailbtn{display:inline-flex;padding:3px;border:0;border-radius:4px;background:transparent;color:var(--t3);cursor:pointer;transition:color .14s ease,background .14s ease}.strct-dg__detailbtn:hover{color:var(--acc);background:var(--bg-3)}.strct-dg__detailbtn--active{color:var(--acc);background:var(--acc-m)}.strct-dg__detailrow td{background:var(--bg-2);padding:0}.strct-dg__detail{padding:14px 16px;font-size:13px;color:var(--t2)}.strct-dg__layout{display:flex;align-items:flex-start;min-width:0}.strct-dg__layout--paned{gap:0}.strct-dg__layout--paned .strct-dg__scroll{flex:0 0 auto;width:260px;min-width:260px;max-width:260px}.strct-dg__layout--paned .strct-dg{width:260px;min-width:260px;max-width:260px;flex-shrink:0;border-top-right-radius:0;border-bottom-right-radius:0}.strct-dg__row--clickable{cursor:pointer}.strct-dg__row--active td{background:var(--acc-m)}.strct-dg__layout--paned .strct-dg__row--active td:last-child{position:relative;padding-inline-end:26px}.strct-dg__layout--paned .strct-dg__row--active td:last-child:after{content:\"\";position:absolute;right:11px;top:50%;width:6px;height:6px;border-top:1.6px solid var(--acc);border-inline-end:1.6px solid var(--acc);transform:translateY(-50%) rotate(45deg)}.strct-dg__pane{flex:1;min-width:0;align-self:stretch;background:var(--bg-1);border:1px solid var(--b2);border-inline-start:2px solid var(--acc);border-radius:0 8px 8px 0;overflow:hidden;animation:strct-dg-pane-in .14s ease}.strct-dg__pane-head{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:11px 14px;border-bottom:1px solid var(--b1);font-size:13px;font-weight:600;color:var(--t1)}.strct-dg__pane-close{display:inline-flex;padding:3px;border:0;border-radius:4px;background:transparent;color:var(--t3);cursor:pointer}.strct-dg__pane-close:hover{color:var(--t1);background:var(--bg-3)}.strct-dg__pane-body{padding:14px 16px;font-size:13px;color:var(--t2)}@keyframes strct-dg-pane-in{0%{opacity:0;transform:translate(8px)}}@keyframes strct-skeleton-pulse{0%,to{opacity:.4}50%{opacity:.7}}.strct-dg__skeleton-block{height:12px;background:var(--bg-3);border-radius:var(--radius-sm);animation:strct-skeleton-pulse 1.4s ease infinite}.strct-dg__skeleton-row td{border-bottom:1px solid var(--b1)}.strct-dg__empty{text-align:center;color:var(--t3);padding:22px}.strct-dg__toolbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:9px 14px;border-bottom:1px solid var(--b2)}.strct-dg__foot{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:9px 14px;border-top:1px solid var(--b2);flex-wrap:wrap}.strct-dg__foot-left,.strct-dg__foot-right{display:flex;align-items:center;gap:12px}.strct-dg__actions{position:relative}.strct-dg__chooser-menu{position:absolute;bottom:calc(100% + 6px);left:0;z-index:10;min-width:180px;background:var(--bg-1);border:1px solid var(--b2);border-radius:8px;box-shadow:var(--shadow-pop);padding:6px;display:flex;flex-direction:column;gap:2px}.strct-dg__chooser-menu--right{inset-inline-start:auto;inset-inline-end:0}.strct-dg__chooser-item{display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:6px;cursor:pointer;font-size:12px;color:var(--t1);transition:background .1s ease;-webkit-user-select:none;user-select:none}.strct-dg__chooser-item:hover{background:var(--bg-3)}.strct-dg__count{font-size:12px;color:var(--t2)}.strct-dg__count-sep{margin:0 8px;color:var(--b2)}.strct-dg__count-sel{color:var(--acc)}.strct-dg__filterbtn{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;margin-inline-start:2px;padding:0;border:0;border-radius:4px;background:transparent;color:var(--t3);cursor:pointer;vertical-align:middle}.strct-dg__filterbtn:hover{color:var(--t1);background:var(--bg-3)}.strct-dg__filterbtn--active{color:var(--acc)}.strct-dg__filterbtn:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}.strct-dg__filterpanel{position:absolute;z-index:210;display:flex;flex-direction:column;gap:6px;min-width:180px;padding:10px;background:var(--bg-1);border:1px solid var(--b2);border-radius:7px;box-shadow:var(--shh);font-weight:400;text-transform:none;letter-spacing:normal;white-space:nowrap}.strct-dg__filterinput{padding:5px 8px;border:1px solid var(--b2);border-radius:var(--radius-sm);background:var(--bg-2);color:var(--t1);font-family:var(--font);font-size:12px}.strct-dg__filterinput:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}.strct-dg__filteropt{display:flex;align-items:center;gap:7px;font-size:12.5px;color:var(--t1);cursor:pointer}.strct-dg__filterclear{align-self:flex-start;padding:3px 8px;border:1px solid var(--b2);border-radius:var(--radius-sm);background:transparent;color:var(--t2);font-family:var(--font);font-size:11.5px;cursor:pointer}.strct-dg__filterclear:hover:not(:disabled){color:var(--t1);background:var(--bg-3)}.strct-dg__filterclear:disabled{color:var(--t4);cursor:default}.strct-dg__treepad{display:inline-block;height:1px;vertical-align:middle}.strct-dg__treebtn{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;margin-inline-end:4px;padding:0;border:0;border-radius:4px;background:transparent;color:var(--t3);cursor:pointer;vertical-align:middle}.strct-dg__treebtn:hover{color:var(--t1);background:var(--bg-3)}.strct-dg__treebtn:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}.strct-dg__treebtn strct-icon{transition:transform .15s ease}.strct-dg__treebtn--open strct-icon{transform:rotate(90deg)}.strct-dg__treebtn--leaf{cursor:default;pointer-events:none}@media(prefers-reduced-motion:reduce){.strct-dg__treebtn strct-icon{transition:none}}.strct-dg__cell--editable{cursor:text}.strct-dg__cell--editable:hover{box-shadow:inset 0 0 0 1px var(--b2)}.strct-dg__editinput{width:100%;box-sizing:border-box;padding:3px 6px;border:1px solid var(--acc);border-radius:var(--radius-sm);background:var(--bg-1);color:var(--t1);font:inherit}.strct-dg__editinput:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}\n"], dependencies: [{ kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "size", "strokeWidth", "badge", "ariaLabel"] }, { kind: "component", type: StrctPagination, selector: "strct-pagination", inputs: ["prevLabel", "nextLabel", "regionLabel", "total", "pageSize", "page"], outputs: ["pageChange"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: StrctCheckbox, selector: "strct-checkbox", inputs: ["checked", "isDisabled", "disabled", "indeterminate", "ariaLabel"], outputs: ["checkedChange", "isDisabledChange"] }, { kind: "component", type: StrctButton, selector: "button[strct-button], a[strct-button]", inputs: ["variant", "size", "solid", "block", "iconOnly"] }, { kind: "component", type: StrctButtonGroup, selector: "strct-button-group" }, { kind: "directive", type: StrctOverlay, selector: "[strctOverlay]", inputs: ["strctOverlay", "strctOverlayPlacement", "strctOverlayMatchWidth", "strctOverlayGap"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
9296
9588
|
}
|
|
9297
9589
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctDatagrid, decorators: [{
|
|
9298
9590
|
type: Component,
|
|
@@ -9303,6 +9595,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
|
|
|
9303
9595
|
StrctCheckbox,
|
|
9304
9596
|
StrctButton,
|
|
9305
9597
|
StrctButtonGroup,
|
|
9598
|
+
StrctOverlay,
|
|
9306
9599
|
], template: `
|
|
9307
9600
|
@if (actionBarDef()) {
|
|
9308
9601
|
<div class="strct-dg__toolbar"><ng-content select="[strctDatagridActionBar]" /></div>
|
|
@@ -9317,7 +9610,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
|
|
|
9317
9610
|
[style.max-height.px]="virtual() ? viewportHeight() : null"
|
|
9318
9611
|
(scroll)="virtual() && onScroll($event)"
|
|
9319
9612
|
>
|
|
9320
|
-
<table class="strct-dg">
|
|
9613
|
+
<table class="strct-dg" [attr.role]="childrenKey() ? 'treegrid' : null">
|
|
9321
9614
|
<thead>
|
|
9322
9615
|
<tr>
|
|
9323
9616
|
@if (canDetail()) {
|
|
@@ -9377,6 +9670,62 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
|
|
|
9377
9670
|
[size]="13"
|
|
9378
9671
|
/>
|
|
9379
9672
|
}
|
|
9673
|
+
@if (col.filterable || col.filterOptions) {
|
|
9674
|
+
<button
|
|
9675
|
+
#fbtn
|
|
9676
|
+
type="button"
|
|
9677
|
+
class="strct-dg__filterbtn"
|
|
9678
|
+
[class.strct-dg__filterbtn--active]="hasFilter(col.key)"
|
|
9679
|
+
[attr.aria-expanded]="filterOpenKey() === col.key"
|
|
9680
|
+
[attr.aria-label]="L().filterColumn + ': ' + col.label"
|
|
9681
|
+
(click)="toggleFilterPanel(col.key, $event)"
|
|
9682
|
+
(keydown.enter)="$event.stopPropagation()"
|
|
9683
|
+
(keydown.space)="$event.stopPropagation()"
|
|
9684
|
+
>
|
|
9685
|
+
<strct-icon name="filter" [size]="12" />
|
|
9686
|
+
</button>
|
|
9687
|
+
@if (filterOpenKey() === col.key) {
|
|
9688
|
+
<div
|
|
9689
|
+
class="strct-dg__filterpanel"
|
|
9690
|
+
role="dialog"
|
|
9691
|
+
[attr.aria-label]="L().filterColumn + ': ' + col.label"
|
|
9692
|
+
[strctOverlay]="fbtn"
|
|
9693
|
+
strctOverlayPlacement="bottom-start"
|
|
9694
|
+
(click)="$event.stopPropagation()"
|
|
9695
|
+
(keydown.enter)="$event.stopPropagation()"
|
|
9696
|
+
(keydown.space)="$event.stopPropagation()"
|
|
9697
|
+
>
|
|
9698
|
+
@if (col.filterOptions; as opts) {
|
|
9699
|
+
@for (opt of opts; track $index) {
|
|
9700
|
+
<label class="strct-dg__filteropt">
|
|
9701
|
+
<input
|
|
9702
|
+
type="checkbox"
|
|
9703
|
+
[checked]="optionChecked(col.key, opt)"
|
|
9704
|
+
(change)="toggleFilterOption(col.key, opt)"
|
|
9705
|
+
/>
|
|
9706
|
+
<span>{{ opt }}</span>
|
|
9707
|
+
</label>
|
|
9708
|
+
}
|
|
9709
|
+
} @else {
|
|
9710
|
+
<input
|
|
9711
|
+
class="strct-dg__filterinput"
|
|
9712
|
+
type="text"
|
|
9713
|
+
[attr.placeholder]="L().filterPlaceholder"
|
|
9714
|
+
[value]="textFilter(col.key)"
|
|
9715
|
+
(input)="setTextFilter(col.key, $any($event.target).value)"
|
|
9716
|
+
/>
|
|
9717
|
+
}
|
|
9718
|
+
<button
|
|
9719
|
+
type="button"
|
|
9720
|
+
class="strct-dg__filterclear"
|
|
9721
|
+
[disabled]="!hasFilter(col.key)"
|
|
9722
|
+
(click)="clearFilter(col.key)"
|
|
9723
|
+
>
|
|
9724
|
+
{{ L().clearFilter }}
|
|
9725
|
+
</button>
|
|
9726
|
+
</div>
|
|
9727
|
+
}
|
|
9728
|
+
}
|
|
9380
9729
|
</span>
|
|
9381
9730
|
@if (resizable() && col.resizable !== false) {
|
|
9382
9731
|
<div
|
|
@@ -9447,6 +9796,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
|
|
|
9447
9796
|
<tr
|
|
9448
9797
|
[class.strct-dg__row--selected]="isSelected(row)"
|
|
9449
9798
|
[class.strct-dg__row--active]="paneOpen() && row === activeRow()"
|
|
9799
|
+
[attr.aria-level]="childrenKey() ? treeDepth(row) + 1 : null"
|
|
9800
|
+
[attr.aria-expanded]="
|
|
9801
|
+
childrenKey() && treeHasChildren(row) ? treeExpanded(row) : null
|
|
9802
|
+
"
|
|
9450
9803
|
>
|
|
9451
9804
|
@if (canDetail()) {
|
|
9452
9805
|
<td
|
|
@@ -9497,14 +9850,51 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
|
|
|
9497
9850
|
/>
|
|
9498
9851
|
</td>
|
|
9499
9852
|
}
|
|
9500
|
-
@for (col of visibleColumns(); track col.key) {
|
|
9853
|
+
@for (col of visibleColumns(); track col.key; let colIdx = $index) {
|
|
9501
9854
|
<td
|
|
9502
9855
|
[style.text-align]="col.align ?? 'start'"
|
|
9503
9856
|
[class.strct-dg__cell--sticky]="isSticky(col)"
|
|
9504
9857
|
[class.strct-dg__cell--sticky-last]="col.key === lastStickyKey()"
|
|
9858
|
+
[class.strct-dg__cell--editable]="col.editable"
|
|
9505
9859
|
[style.insetInlineStart.px]="stickyLeft(col.key)"
|
|
9860
|
+
(dblclick)="col.editable && startEdit(row, col)"
|
|
9506
9861
|
>
|
|
9507
|
-
@if (
|
|
9862
|
+
@if (childrenKey() && colIdx === 0) {
|
|
9863
|
+
<span
|
|
9864
|
+
class="strct-dg__treepad"
|
|
9865
|
+
[style.width.px]="treeDepth(row) * 18"
|
|
9866
|
+
aria-hidden="true"
|
|
9867
|
+
></span>
|
|
9868
|
+
@if (treeHasChildren(row)) {
|
|
9869
|
+
<button
|
|
9870
|
+
type="button"
|
|
9871
|
+
class="strct-dg__treebtn"
|
|
9872
|
+
[class.strct-dg__treebtn--open]="treeExpanded(row)"
|
|
9873
|
+
[attr.aria-expanded]="treeExpanded(row)"
|
|
9874
|
+
[attr.aria-label]="L().toggleChildren"
|
|
9875
|
+
(click)="toggleTreeRow(row)"
|
|
9876
|
+
>
|
|
9877
|
+
<strct-icon name="chevronRight" [size]="12" [strokeWidth]="1.7" />
|
|
9878
|
+
</button>
|
|
9879
|
+
} @else {
|
|
9880
|
+
<span
|
|
9881
|
+
class="strct-dg__treebtn strct-dg__treebtn--leaf"
|
|
9882
|
+
aria-hidden="true"
|
|
9883
|
+
></span>
|
|
9884
|
+
}
|
|
9885
|
+
}
|
|
9886
|
+
@if (isEditingCell(row, col)) {
|
|
9887
|
+
<input
|
|
9888
|
+
class="strct-dg__editinput"
|
|
9889
|
+
type="text"
|
|
9890
|
+
[value]="row[col.key] ?? ''"
|
|
9891
|
+
[attr.aria-label]="L().editCell + ': ' + col.label"
|
|
9892
|
+
(keydown.enter)="commitEdit(row, col, $event)"
|
|
9893
|
+
(keydown.escape)="cancelEdit($event)"
|
|
9894
|
+
(blur)="commitEdit(row, col, $event)"
|
|
9895
|
+
(click)="$event.stopPropagation()"
|
|
9896
|
+
/>
|
|
9897
|
+
} @else if (cellTemplate(col.key); as tpl) {
|
|
9508
9898
|
<ng-container
|
|
9509
9899
|
[ngTemplateOutlet]="tpl"
|
|
9510
9900
|
[ngTemplateOutletContext]="{
|
|
@@ -9648,8 +10038,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
|
|
|
9648
10038
|
'[class.strct-dg-host--singleline]': 'singleLine()',
|
|
9649
10039
|
'[class.strct-dg-host--virtual]': 'virtual()',
|
|
9650
10040
|
'[class.strct-dg-host--sticky]': 'stickyActive()',
|
|
9651
|
-
}, styles: [".strct-dg-host{display:block;border:1px solid var(--b2);border-radius:10px;background:var(--bg-2);box-shadow:var(--shadow-rest)}.strct-dg__scroll{flex:1 1 auto;min-width:0;overflow-x:auto;-webkit-overflow-scrolling:touch}.strct-dg-host--virtual .strct-dg__scroll{overflow-y:auto}.strct-dg-host--virtual .strct-dg thead th{position:sticky;top:0;z-index:5}.strct-dg__vspacer td{padding:0;border:0;background:transparent}.strct-dg th[draggable]{cursor:grab}.strct-dg__th--drop{box-shadow:inset 3px 0 0 var(--acc)}.strct-dg__grouprow td{background:var(--bg-2)!important;padding:0}.strct-dg__groupbtn{display:flex;align-items:center;gap:8px;width:100%;padding:7px 13px;border:0;background:transparent;color:var(--t1);font-family:var(--font);font-size:12.5px;font-weight:600;cursor:pointer;text-align:start}.strct-dg__groupbtn:hover{background:var(--bg-3)}.strct-dg__groupbtn:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-dg__groupchev{display:inline-flex;color:var(--t3);transition:transform .15s ease}.strct-dg__groupchev--open{transform:rotate(90deg)}@media(prefers-reduced-motion:reduce){.strct-dg__groupchev{transition:none}}.strct-dg__groupcount{min-width:18px;height:18px;padding:0 5px;display:inline-flex;align-items:center;justify-content:center;font-size:12px;border-radius:9px;background:var(--acc-m);color:var(--acc);font-variant-numeric:tabular-nums}.strct-dg-host--sticky .strct-dg,.strct-dg-host--virtual .strct-dg{border-collapse:separate;border-spacing:0;overflow:visible;border-radius:0}.strct-dg-host--sticky .strct-dg{width:max-content;min-width:100%}.strct-dg .strct-dg__cell--sticky{position:sticky;z-index:3}.strct-dg thead th.strct-dg__cell--sticky{z-index:6}.strct-dg .strct-dg__cell--sticky-last:after{content:\"\";position:absolute;top:0;bottom:0;inset-inline-end:-1px;width:7px;pointer-events:none;background:linear-gradient(to right,rgba(0,0,0,.14),transparent)}[dir=rtl] .strct-dg .strct-dg__cell--sticky-last:after{background:linear-gradient(to left,rgba(0,0,0,.14),transparent)}.strct-dg-host--sticky .strct-dg__expandcol,.strct-dg-host--sticky .strct-dg__expandcell{width:36px;min-width:36px;max-width:36px;box-sizing:border-box}.strct-dg-host--sticky .strct-dg__sel{width:40px;min-width:40px;max-width:40px;box-sizing:border-box}.strct-dg-host--sticky tbody .strct-dg__cell--sticky{background:var(--bg-1)}[data-theme=dark] .strct-dg-host--sticky tbody .strct-dg__cell--sticky{background:var(--bg-3)}.strct-dg-host--sticky tbody tr:hover .strct-dg__cell--sticky{background:linear-gradient(var(--acc-s),var(--acc-s)) var(--bg-1)}[data-theme=dark] .strct-dg-host--sticky tbody tr:hover .strct-dg__cell--sticky{background:linear-gradient(var(--acc-s),var(--acc-s)) var(--bg-3)}.strct-dg-host--sticky tbody .strct-dg__row--selected .strct-dg__cell--sticky{background:linear-gradient(var(--acc-m),var(--acc-m)) var(--bg-1)}[data-theme=dark] .strct-dg-host--sticky tbody .strct-dg__row--selected .strct-dg__cell--sticky{background:linear-gradient(var(--acc-m),var(--acc-m)) var(--bg-3)}.strct-dg{width:100%;border-collapse:collapse;font-size:13px;overflow:hidden;background:var(--bg-2)}.strct-dg-host:not(:has(.strct-dg__toolbar)) .strct-dg{border-start-start-radius:9px;border-start-end-radius:9px}.strct-dg-host:not(:has(.strct-dg__foot)) .strct-dg{border-end-start-radius:9px;border-end-end-radius:9px}.strct-dg th,.strct-dg td{padding:9px 13px;text-align:start;border-bottom:1px solid var(--b1)}.strct-dg tbody td{background:var(--bg-1)}[data-theme=dark] .strct-dg tbody td{background:var(--bg-3)}.strct-dg-host--compact .strct-dg th,.strct-dg-host--compact .strct-dg td{padding:5px 11px}.strct-dg-host--singleline .strct-dg tbody tr:not(.strct-dg__detailrow)>td{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:360px}.strct-dg th{position:relative;font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.4px;color:var(--t2);background:var(--bg-2);white-space:nowrap;-webkit-user-select:none;user-select:none}.strct-dg__th--sortable{cursor:pointer}.strct-dg__th--sortable:hover{color:var(--t1)}.strct-dg__th--sortable:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-dg__hd{display:inline-flex;align-items:center;gap:5px}.strct-dg__sorticon{color:var(--t3)}.strct-dg__th--sortable:hover .strct-dg__sorticon{color:var(--acc)}.strct-dg__resize{position:absolute;inset-inline-end:0;top:0;bottom:0;width:4px;cursor:col-resize;background:transparent;z-index:2}.strct-dg__resize:hover{background:var(--acc)}.strct-dg td{color:var(--t1)}.strct-dg tbody tr:last-child td{border-bottom:0}.strct-dg tbody tr:not(.strct-dg__detailrow):hover td{background:var(--acc-s)}.strct-dg__row--selected td{background:var(--acc-m)}.strct-dg__sel{width:1%;white-space:nowrap}.strct-dg__sel input{accent-color:var(--acc);width:15px;height:15px;cursor:pointer}.strct-dg__expandcol,.strct-dg__expandcell{width:1%;white-space:nowrap}.strct-dg__actioncol,.strct-dg__actioncell{width:1%;white-space:nowrap;text-align:end}.strct-dg__kebab{display:inline-flex;padding:4px;border:0;border-radius:5px;background:transparent;color:var(--t3);cursor:pointer;transition:color .14s ease,background .14s ease}.strct-dg__kebab:hover{color:var(--t1);background:var(--bg-3)}.strct-dg__expandbtn{display:inline-flex;padding:3px;border:0;border-radius:4px;background:transparent;color:var(--t3);cursor:pointer;transition:transform .15s ease,color .15s ease}.strct-dg__expandbtn:hover{color:var(--t1);background:var(--bg-3)}.strct-dg__expandbtn--open{transform:rotate(90deg);color:var(--acc)}.strct-dg__detailbtn{display:inline-flex;padding:3px;border:0;border-radius:4px;background:transparent;color:var(--t3);cursor:pointer;transition:color .14s ease,background .14s ease}.strct-dg__detailbtn:hover{color:var(--acc);background:var(--bg-3)}.strct-dg__detailbtn--active{color:var(--acc);background:var(--acc-m)}.strct-dg__detailrow td{background:var(--bg-2);padding:0}.strct-dg__detail{padding:14px 16px;font-size:13px;color:var(--t2)}.strct-dg__layout{display:flex;align-items:flex-start;min-width:0}.strct-dg__layout--paned{gap:0}.strct-dg__layout--paned .strct-dg__scroll{flex:0 0 auto;width:260px;min-width:260px;max-width:260px}.strct-dg__layout--paned .strct-dg{width:260px;min-width:260px;max-width:260px;flex-shrink:0;border-top-right-radius:0;border-bottom-right-radius:0}.strct-dg__row--clickable{cursor:pointer}.strct-dg__row--active td{background:var(--acc-m)}.strct-dg__layout--paned .strct-dg__row--active td:last-child{position:relative;padding-inline-end:26px}.strct-dg__layout--paned .strct-dg__row--active td:last-child:after{content:\"\";position:absolute;right:11px;top:50%;width:6px;height:6px;border-top:1.6px solid var(--acc);border-inline-end:1.6px solid var(--acc);transform:translateY(-50%) rotate(45deg)}.strct-dg__pane{flex:1;min-width:0;align-self:stretch;background:var(--bg-1);border:1px solid var(--b2);border-inline-start:2px solid var(--acc);border-radius:0 8px 8px 0;overflow:hidden;animation:strct-dg-pane-in .14s ease}.strct-dg__pane-head{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:11px 14px;border-bottom:1px solid var(--b1);font-size:13px;font-weight:600;color:var(--t1)}.strct-dg__pane-close{display:inline-flex;padding:3px;border:0;border-radius:4px;background:transparent;color:var(--t3);cursor:pointer}.strct-dg__pane-close:hover{color:var(--t1);background:var(--bg-3)}.strct-dg__pane-body{padding:14px 16px;font-size:13px;color:var(--t2)}@keyframes strct-dg-pane-in{0%{opacity:0;transform:translate(8px)}}@keyframes strct-skeleton-pulse{0%,to{opacity:.4}50%{opacity:.7}}.strct-dg__skeleton-block{height:12px;background:var(--bg-3);border-radius:var(--radius-sm);animation:strct-skeleton-pulse 1.4s ease infinite}.strct-dg__skeleton-row td{border-bottom:1px solid var(--b1)}.strct-dg__empty{text-align:center;color:var(--t3);padding:22px}.strct-dg__toolbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:9px 14px;border-bottom:1px solid var(--b2)}.strct-dg__foot{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:9px 14px;border-top:1px solid var(--b2);flex-wrap:wrap}.strct-dg__foot-left,.strct-dg__foot-right{display:flex;align-items:center;gap:12px}.strct-dg__actions{position:relative}.strct-dg__chooser-menu{position:absolute;bottom:calc(100% + 6px);left:0;z-index:10;min-width:180px;background:var(--bg-1);border:1px solid var(--b2);border-radius:8px;box-shadow:var(--shadow-pop);padding:6px;display:flex;flex-direction:column;gap:2px}.strct-dg__chooser-menu--right{inset-inline-start:auto;inset-inline-end:0}.strct-dg__chooser-item{display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:6px;cursor:pointer;font-size:12px;color:var(--t1);transition:background .1s ease;-webkit-user-select:none;user-select:none}.strct-dg__chooser-item:hover{background:var(--bg-3)}.strct-dg__count{font-size:12px;color:var(--t2)}.strct-dg__count-sep{margin:0 8px;color:var(--b2)}.strct-dg__count-sel{color:var(--acc)}\n"] }]
|
|
9652
|
-
}], ctorParameters: () => [], propDecorators: { columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: true }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: true }] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }], selectable: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectable", required: false }] }], expandable: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandable", required: false }] }], detailPane: [{ type: i0.Input, args: [{ isSignal: true, alias: "detailPane", required: false }] }], compact: [{ type: i0.Input, args: [{ isSignal: true, alias: "compact", required: false }] }], singleLine: [{ type: i0.Input, args: [{ isSignal: true, alias: "singleLine", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], resizable: [{ type: i0.Input, args: [{ isSignal: true, alias: "resizable", required: false }] }], columnChooser: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnChooser", required: false }] }], sync: [{ type: i0.Input, args: [{ isSignal: true, alias: "sync", required: false }] }], footerActionsDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "footerActionsDisabled", required: false }] }], rowId: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowId", required: false }] }], rowActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowActions", required: false }] }], initialSelection: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialSelection", required: false }] }], labels: [{ type: i0.Input, args: [{ isSignal: true, alias: "labels", required: false }] }], virtual: [{ type: i0.Input, args: [{ isSignal: true, alias: "virtual", required: false }] }], viewportHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "viewportHeight", required: false }] }], rowHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowHeight", required: false }] }], lazy: [{ type: i0.Input, args: [{ isSignal: true, alias: "lazy", required: false }] }], total: [{ type: i0.Input, args: [{ isSignal: true, alias: "total", required: false }] }], stateKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "stateKey", required: false }] }], columnState: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnState", required: false }] }, { type: i0.Output, args: ["columnStateChange"] }], reorderable: [{ type: i0.Input, args: [{ isSignal: true, alias: "reorderable", required: false }] }], groupBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupBy", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], syncChange: [{ type: i0.Output, args: ["syncChange"] }], rowAction: [{ type: i0.Output, args: ["rowAction"] }], lazyLoad: [{ type: i0.Output, args: ["lazyLoad"] }], detailDef: [{ type: i0.ContentChild, args: [i0.forwardRef(() => StrctRowDetailDef), { isSignal: true }] }], actionBarDef: [{ type: i0.ContentChild, args: [i0.forwardRef(() => StrctDatagridActionBar), { isSignal: true }] }], cellDefs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => StrctCellDef), { isSignal: true }] }]
|
|
10041
|
+
}, styles: [".strct-dg-host{display:block;border:1px solid var(--b2);border-radius:10px;background:var(--bg-2);box-shadow:var(--shadow-rest)}.strct-dg__scroll{flex:1 1 auto;min-width:0;overflow-x:auto;-webkit-overflow-scrolling:touch}.strct-dg-host--virtual .strct-dg__scroll{overflow-y:auto}.strct-dg-host--virtual .strct-dg thead th{position:sticky;top:0;z-index:5}.strct-dg__vspacer td{padding:0;border:0;background:transparent}.strct-dg th[draggable]{cursor:grab}.strct-dg__th--drop{box-shadow:inset 3px 0 0 var(--acc)}.strct-dg__grouprow td{background:var(--bg-2)!important;padding:0}.strct-dg__groupbtn{display:flex;align-items:center;gap:8px;width:100%;padding:7px 13px;border:0;background:transparent;color:var(--t1);font-family:var(--font);font-size:12.5px;font-weight:600;cursor:pointer;text-align:start}.strct-dg__groupbtn:hover{background:var(--bg-3)}.strct-dg__groupbtn:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-dg__groupchev{display:inline-flex;color:var(--t3);transition:transform .15s ease}.strct-dg__groupchev--open{transform:rotate(90deg)}@media(prefers-reduced-motion:reduce){.strct-dg__groupchev{transition:none}}.strct-dg__groupcount{min-width:18px;height:18px;padding:0 5px;display:inline-flex;align-items:center;justify-content:center;font-size:12px;border-radius:9px;background:var(--acc-m);color:var(--acc);font-variant-numeric:tabular-nums}.strct-dg-host--sticky .strct-dg,.strct-dg-host--virtual .strct-dg{border-collapse:separate;border-spacing:0;overflow:visible;border-radius:0}.strct-dg-host--sticky .strct-dg{width:max-content;min-width:100%}.strct-dg .strct-dg__cell--sticky{position:sticky;z-index:3}.strct-dg thead th.strct-dg__cell--sticky{z-index:6}.strct-dg .strct-dg__cell--sticky-last:after{content:\"\";position:absolute;top:0;bottom:0;inset-inline-end:-1px;width:7px;pointer-events:none;background:linear-gradient(to right,rgba(0,0,0,.14),transparent)}[dir=rtl] .strct-dg .strct-dg__cell--sticky-last:after{background:linear-gradient(to left,rgba(0,0,0,.14),transparent)}.strct-dg-host--sticky .strct-dg__expandcol,.strct-dg-host--sticky .strct-dg__expandcell{width:36px;min-width:36px;max-width:36px;box-sizing:border-box}.strct-dg-host--sticky .strct-dg__sel{width:40px;min-width:40px;max-width:40px;box-sizing:border-box}.strct-dg-host--sticky tbody .strct-dg__cell--sticky{background:var(--bg-1)}[data-theme=dark] .strct-dg-host--sticky tbody .strct-dg__cell--sticky{background:var(--bg-3)}.strct-dg-host--sticky tbody tr:hover .strct-dg__cell--sticky{background:linear-gradient(var(--acc-s),var(--acc-s)) var(--bg-1)}[data-theme=dark] .strct-dg-host--sticky tbody tr:hover .strct-dg__cell--sticky{background:linear-gradient(var(--acc-s),var(--acc-s)) var(--bg-3)}.strct-dg-host--sticky tbody .strct-dg__row--selected .strct-dg__cell--sticky{background:linear-gradient(var(--acc-m),var(--acc-m)) var(--bg-1)}[data-theme=dark] .strct-dg-host--sticky tbody .strct-dg__row--selected .strct-dg__cell--sticky{background:linear-gradient(var(--acc-m),var(--acc-m)) var(--bg-3)}.strct-dg{width:100%;border-collapse:collapse;font-size:13px;overflow:hidden;background:var(--bg-2)}.strct-dg-host:not(:has(.strct-dg__toolbar)) .strct-dg{border-start-start-radius:9px;border-start-end-radius:9px}.strct-dg-host:not(:has(.strct-dg__foot)) .strct-dg{border-end-start-radius:9px;border-end-end-radius:9px}.strct-dg th,.strct-dg td{padding:9px 13px;text-align:start;border-bottom:1px solid var(--b1)}.strct-dg tbody td{background:var(--bg-1)}[data-theme=dark] .strct-dg tbody td{background:var(--bg-3)}.strct-dg-host--compact .strct-dg th,.strct-dg-host--compact .strct-dg td{padding:5px 11px}.strct-dg-host--singleline .strct-dg tbody tr:not(.strct-dg__detailrow)>td{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:360px}.strct-dg th{position:relative;font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.4px;color:var(--t2);background:var(--bg-2);white-space:nowrap;-webkit-user-select:none;user-select:none}.strct-dg__th--sortable{cursor:pointer}.strct-dg__th--sortable:hover{color:var(--t1)}.strct-dg__th--sortable:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-dg__hd{display:inline-flex;align-items:center;gap:5px}.strct-dg__sorticon{color:var(--t3)}.strct-dg__th--sortable:hover .strct-dg__sorticon{color:var(--acc)}.strct-dg__resize{position:absolute;inset-inline-end:0;top:0;bottom:0;width:4px;cursor:col-resize;background:transparent;z-index:2}.strct-dg__resize:hover{background:var(--acc)}.strct-dg td{color:var(--t1)}.strct-dg tbody tr:last-child td{border-bottom:0}.strct-dg tbody tr:not(.strct-dg__detailrow):hover td{background:var(--acc-s)}.strct-dg__row--selected td{background:var(--acc-m)}.strct-dg__sel{width:1%;white-space:nowrap}.strct-dg__sel input{accent-color:var(--acc);width:15px;height:15px;cursor:pointer}.strct-dg__expandcol,.strct-dg__expandcell{width:1%;white-space:nowrap}.strct-dg__actioncol,.strct-dg__actioncell{width:1%;white-space:nowrap;text-align:end}.strct-dg__kebab{display:inline-flex;padding:4px;border:0;border-radius:5px;background:transparent;color:var(--t3);cursor:pointer;transition:color .14s ease,background .14s ease}.strct-dg__kebab:hover{color:var(--t1);background:var(--bg-3)}.strct-dg__expandbtn{display:inline-flex;padding:3px;border:0;border-radius:4px;background:transparent;color:var(--t3);cursor:pointer;transition:transform .15s ease,color .15s ease}.strct-dg__expandbtn:hover{color:var(--t1);background:var(--bg-3)}.strct-dg__expandbtn--open{transform:rotate(90deg);color:var(--acc)}.strct-dg__detailbtn{display:inline-flex;padding:3px;border:0;border-radius:4px;background:transparent;color:var(--t3);cursor:pointer;transition:color .14s ease,background .14s ease}.strct-dg__detailbtn:hover{color:var(--acc);background:var(--bg-3)}.strct-dg__detailbtn--active{color:var(--acc);background:var(--acc-m)}.strct-dg__detailrow td{background:var(--bg-2);padding:0}.strct-dg__detail{padding:14px 16px;font-size:13px;color:var(--t2)}.strct-dg__layout{display:flex;align-items:flex-start;min-width:0}.strct-dg__layout--paned{gap:0}.strct-dg__layout--paned .strct-dg__scroll{flex:0 0 auto;width:260px;min-width:260px;max-width:260px}.strct-dg__layout--paned .strct-dg{width:260px;min-width:260px;max-width:260px;flex-shrink:0;border-top-right-radius:0;border-bottom-right-radius:0}.strct-dg__row--clickable{cursor:pointer}.strct-dg__row--active td{background:var(--acc-m)}.strct-dg__layout--paned .strct-dg__row--active td:last-child{position:relative;padding-inline-end:26px}.strct-dg__layout--paned .strct-dg__row--active td:last-child:after{content:\"\";position:absolute;right:11px;top:50%;width:6px;height:6px;border-top:1.6px solid var(--acc);border-inline-end:1.6px solid var(--acc);transform:translateY(-50%) rotate(45deg)}.strct-dg__pane{flex:1;min-width:0;align-self:stretch;background:var(--bg-1);border:1px solid var(--b2);border-inline-start:2px solid var(--acc);border-radius:0 8px 8px 0;overflow:hidden;animation:strct-dg-pane-in .14s ease}.strct-dg__pane-head{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:11px 14px;border-bottom:1px solid var(--b1);font-size:13px;font-weight:600;color:var(--t1)}.strct-dg__pane-close{display:inline-flex;padding:3px;border:0;border-radius:4px;background:transparent;color:var(--t3);cursor:pointer}.strct-dg__pane-close:hover{color:var(--t1);background:var(--bg-3)}.strct-dg__pane-body{padding:14px 16px;font-size:13px;color:var(--t2)}@keyframes strct-dg-pane-in{0%{opacity:0;transform:translate(8px)}}@keyframes strct-skeleton-pulse{0%,to{opacity:.4}50%{opacity:.7}}.strct-dg__skeleton-block{height:12px;background:var(--bg-3);border-radius:var(--radius-sm);animation:strct-skeleton-pulse 1.4s ease infinite}.strct-dg__skeleton-row td{border-bottom:1px solid var(--b1)}.strct-dg__empty{text-align:center;color:var(--t3);padding:22px}.strct-dg__toolbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:9px 14px;border-bottom:1px solid var(--b2)}.strct-dg__foot{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:9px 14px;border-top:1px solid var(--b2);flex-wrap:wrap}.strct-dg__foot-left,.strct-dg__foot-right{display:flex;align-items:center;gap:12px}.strct-dg__actions{position:relative}.strct-dg__chooser-menu{position:absolute;bottom:calc(100% + 6px);left:0;z-index:10;min-width:180px;background:var(--bg-1);border:1px solid var(--b2);border-radius:8px;box-shadow:var(--shadow-pop);padding:6px;display:flex;flex-direction:column;gap:2px}.strct-dg__chooser-menu--right{inset-inline-start:auto;inset-inline-end:0}.strct-dg__chooser-item{display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:6px;cursor:pointer;font-size:12px;color:var(--t1);transition:background .1s ease;-webkit-user-select:none;user-select:none}.strct-dg__chooser-item:hover{background:var(--bg-3)}.strct-dg__count{font-size:12px;color:var(--t2)}.strct-dg__count-sep{margin:0 8px;color:var(--b2)}.strct-dg__count-sel{color:var(--acc)}.strct-dg__filterbtn{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;margin-inline-start:2px;padding:0;border:0;border-radius:4px;background:transparent;color:var(--t3);cursor:pointer;vertical-align:middle}.strct-dg__filterbtn:hover{color:var(--t1);background:var(--bg-3)}.strct-dg__filterbtn--active{color:var(--acc)}.strct-dg__filterbtn:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}.strct-dg__filterpanel{position:absolute;z-index:210;display:flex;flex-direction:column;gap:6px;min-width:180px;padding:10px;background:var(--bg-1);border:1px solid var(--b2);border-radius:7px;box-shadow:var(--shh);font-weight:400;text-transform:none;letter-spacing:normal;white-space:nowrap}.strct-dg__filterinput{padding:5px 8px;border:1px solid var(--b2);border-radius:var(--radius-sm);background:var(--bg-2);color:var(--t1);font-family:var(--font);font-size:12px}.strct-dg__filterinput:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}.strct-dg__filteropt{display:flex;align-items:center;gap:7px;font-size:12.5px;color:var(--t1);cursor:pointer}.strct-dg__filterclear{align-self:flex-start;padding:3px 8px;border:1px solid var(--b2);border-radius:var(--radius-sm);background:transparent;color:var(--t2);font-family:var(--font);font-size:11.5px;cursor:pointer}.strct-dg__filterclear:hover:not(:disabled){color:var(--t1);background:var(--bg-3)}.strct-dg__filterclear:disabled{color:var(--t4);cursor:default}.strct-dg__treepad{display:inline-block;height:1px;vertical-align:middle}.strct-dg__treebtn{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;margin-inline-end:4px;padding:0;border:0;border-radius:4px;background:transparent;color:var(--t3);cursor:pointer;vertical-align:middle}.strct-dg__treebtn:hover{color:var(--t1);background:var(--bg-3)}.strct-dg__treebtn:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}.strct-dg__treebtn strct-icon{transition:transform .15s ease}.strct-dg__treebtn--open strct-icon{transform:rotate(90deg)}.strct-dg__treebtn--leaf{cursor:default;pointer-events:none}@media(prefers-reduced-motion:reduce){.strct-dg__treebtn strct-icon{transition:none}}.strct-dg__cell--editable{cursor:text}.strct-dg__cell--editable:hover{box-shadow:inset 0 0 0 1px var(--b2)}.strct-dg__editinput{width:100%;box-sizing:border-box;padding:3px 6px;border:1px solid var(--acc);border-radius:var(--radius-sm);background:var(--bg-1);color:var(--t1);font:inherit}.strct-dg__editinput:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}\n"] }]
|
|
10042
|
+
}], ctorParameters: () => [], propDecorators: { columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: true }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: true }] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }], selectable: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectable", required: false }] }], expandable: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandable", required: false }] }], detailPane: [{ type: i0.Input, args: [{ isSignal: true, alias: "detailPane", required: false }] }], compact: [{ type: i0.Input, args: [{ isSignal: true, alias: "compact", required: false }] }], singleLine: [{ type: i0.Input, args: [{ isSignal: true, alias: "singleLine", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], resizable: [{ type: i0.Input, args: [{ isSignal: true, alias: "resizable", required: false }] }], columnChooser: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnChooser", required: false }] }], sync: [{ type: i0.Input, args: [{ isSignal: true, alias: "sync", required: false }] }], footerActionsDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "footerActionsDisabled", required: false }] }], rowId: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowId", required: false }] }], rowActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowActions", required: false }] }], initialSelection: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialSelection", required: false }] }], labels: [{ type: i0.Input, args: [{ isSignal: true, alias: "labels", required: false }] }], virtual: [{ type: i0.Input, args: [{ isSignal: true, alias: "virtual", required: false }] }], viewportHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "viewportHeight", required: false }] }], rowHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowHeight", required: false }] }], lazy: [{ type: i0.Input, args: [{ isSignal: true, alias: "lazy", required: false }] }], total: [{ type: i0.Input, args: [{ isSignal: true, alias: "total", required: false }] }], stateKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "stateKey", required: false }] }], columnState: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnState", required: false }] }, { type: i0.Output, args: ["columnStateChange"] }], reorderable: [{ type: i0.Input, args: [{ isSignal: true, alias: "reorderable", required: false }] }], groupBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupBy", required: false }] }], filters: [{ type: i0.Input, args: [{ isSignal: true, alias: "filters", required: false }] }, { type: i0.Output, args: ["filtersChange"] }], childrenKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "childrenKey", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], syncChange: [{ type: i0.Output, args: ["syncChange"] }], rowAction: [{ type: i0.Output, args: ["rowAction"] }], lazyLoad: [{ type: i0.Output, args: ["lazyLoad"] }], cellEdit: [{ type: i0.Output, args: ["cellEdit"] }], detailDef: [{ type: i0.ContentChild, args: [i0.forwardRef(() => StrctRowDetailDef), { isSignal: true }] }], actionBarDef: [{ type: i0.ContentChild, args: [i0.forwardRef(() => StrctDatagridActionBar), { isSignal: true }] }], cellDefs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => StrctCellDef), { isSignal: true }] }], onDocClickFilter: [{
|
|
10043
|
+
type: HostListener,
|
|
10044
|
+
args: ['document:click', ['$event']]
|
|
10045
|
+
}], onEscapeFilter: [{
|
|
10046
|
+
type: HostListener,
|
|
10047
|
+
args: ['document:keydown.escape']
|
|
10048
|
+
}] } });
|
|
9653
10049
|
|
|
9654
10050
|
/** Vertical timeline container. Wraps `<strct-timeline-item>` children. */
|
|
9655
10051
|
class StrctTimeline {
|
|
@@ -12759,6 +13155,2326 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
|
|
|
12759
13155
|
`, host: { class: 'strct-empty' }, styles: [".strct-empty{display:flex;flex-direction:column;align-items:center;text-align:center;gap:10px;padding:40px 24px}.strct-empty__icon{display:inline-flex;align-items:center;justify-content:center;width:56px;height:56px;border-radius:14px;margin-bottom:2px}.strct-empty__icon--neutral{background:var(--bg-3);color:var(--t2)}.strct-empty__icon--warning{background:var(--warning-bg);color:var(--warning)}.strct-empty__icon--critical{background:var(--critical-bg);color:var(--critical)}.strct-empty__icon--accent{background:var(--acc-m);color:var(--acc)}.strct-empty__title{margin:0;font-size:15px;font-weight:600;color:var(--t1)}.strct-empty__desc{margin:0;max-width:360px;font-size:13px;line-height:1.5;color:var(--t3)}.strct-empty__actions{display:flex;align-items:center;gap:8px;margin-top:6px}.strct-empty__actions:empty{display:none}\n"] }]
|
|
12760
13156
|
}], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], description: [{ type: i0.Input, args: [{ isSignal: true, alias: "description", required: false }] }] } });
|
|
12761
13157
|
|
|
13158
|
+
/**
|
|
13159
|
+
* Unit formatting for infrastructure consoles — bytes, rates, durations and
|
|
13160
|
+
* SI magnitudes, as pure functions plus template pipes. The functions are the
|
|
13161
|
+
* API charts / grids can call from TS; the pipes are the template sugar:
|
|
13162
|
+
*
|
|
13163
|
+
* {{ vm.memBytes | strctBytes }} → 1.5 GiB
|
|
13164
|
+
* {{ nic.rxBps | strctRate }} → 2.4 Gbit/s
|
|
13165
|
+
* {{ task.elapsedMs | strctDuration }} → 2h 14m
|
|
13166
|
+
* {{ cluster.iops | strctSi : 'IOPS' }} → 12.4k IOPS
|
|
13167
|
+
*/
|
|
13168
|
+
const BINARY_UNITS = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB'];
|
|
13169
|
+
const DECIMAL_UNITS = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB'];
|
|
13170
|
+
const SI_SUFFIXES = ['', 'k', 'M', 'G', 'T', 'P'];
|
|
13171
|
+
function trimZeros(v) {
|
|
13172
|
+
return v.includes('.') ? v.replace(/\.?0+$/, '') : v;
|
|
13173
|
+
}
|
|
13174
|
+
/**
|
|
13175
|
+
* Format a byte count. Binary (KiB, 1024) by default — the storage/memory
|
|
13176
|
+
* convention consoles actually mean; pass `binary: false` for decimal (kB).
|
|
13177
|
+
*/
|
|
13178
|
+
function strctFormatBytes(bytes, options = {}) {
|
|
13179
|
+
if (!Number.isFinite(bytes))
|
|
13180
|
+
return '—';
|
|
13181
|
+
const { binary = true, digits = 1 } = options;
|
|
13182
|
+
const base = binary ? 1024 : 1000;
|
|
13183
|
+
const units = binary ? BINARY_UNITS : DECIMAL_UNITS;
|
|
13184
|
+
const neg = bytes < 0 ? '-' : '';
|
|
13185
|
+
let v = Math.abs(bytes);
|
|
13186
|
+
let i = 0;
|
|
13187
|
+
while (v >= base && i < units.length - 1) {
|
|
13188
|
+
v /= base;
|
|
13189
|
+
i++;
|
|
13190
|
+
}
|
|
13191
|
+
return `${neg}${trimZeros(v.toFixed(i === 0 ? 0 : digits))} ${units[i]}`;
|
|
13192
|
+
}
|
|
13193
|
+
/**
|
|
13194
|
+
* Format a data rate from bits per second (network convention). Pass bytes/s
|
|
13195
|
+
* through `strctFormatBytes(...) + '/s'` instead if you mean throughput.
|
|
13196
|
+
*/
|
|
13197
|
+
function strctFormatRate(bitsPerSecond, options = {}) {
|
|
13198
|
+
if (!Number.isFinite(bitsPerSecond))
|
|
13199
|
+
return '—';
|
|
13200
|
+
const { digits = 1 } = options;
|
|
13201
|
+
const units = ['bit/s', 'kbit/s', 'Mbit/s', 'Gbit/s', 'Tbit/s'];
|
|
13202
|
+
const neg = bitsPerSecond < 0 ? '-' : '';
|
|
13203
|
+
let v = Math.abs(bitsPerSecond);
|
|
13204
|
+
let i = 0;
|
|
13205
|
+
while (v >= 1000 && i < units.length - 1) {
|
|
13206
|
+
v /= 1000;
|
|
13207
|
+
i++;
|
|
13208
|
+
}
|
|
13209
|
+
return `${neg}${trimZeros(v.toFixed(i === 0 ? 0 : digits))} ${units[i]}`;
|
|
13210
|
+
}
|
|
13211
|
+
/**
|
|
13212
|
+
* Format a duration in ms as the two most significant units: "2h 14m",
|
|
13213
|
+
* "3d 6h", "45s", "820ms". Sub-second stays in ms; `maxUnits` widens it.
|
|
13214
|
+
*/
|
|
13215
|
+
function strctFormatDuration(ms, options = {}) {
|
|
13216
|
+
if (!Number.isFinite(ms))
|
|
13217
|
+
return '—';
|
|
13218
|
+
const { maxUnits = 2 } = options;
|
|
13219
|
+
const neg = ms < 0 ? '-' : '';
|
|
13220
|
+
let v = Math.abs(ms);
|
|
13221
|
+
if (v < 1000)
|
|
13222
|
+
return `${neg}${Math.round(v)}ms`;
|
|
13223
|
+
const units = [
|
|
13224
|
+
['d', 86_400_000],
|
|
13225
|
+
['h', 3_600_000],
|
|
13226
|
+
['m', 60_000],
|
|
13227
|
+
['s', 1000],
|
|
13228
|
+
];
|
|
13229
|
+
const parts = [];
|
|
13230
|
+
for (const [label, size] of units) {
|
|
13231
|
+
if (parts.length >= maxUnits)
|
|
13232
|
+
break;
|
|
13233
|
+
const n = Math.floor(v / size);
|
|
13234
|
+
if (n > 0 || parts.length > 0) {
|
|
13235
|
+
if (n > 0)
|
|
13236
|
+
parts.push(`${n}${label}`);
|
|
13237
|
+
v -= n * size;
|
|
13238
|
+
}
|
|
13239
|
+
}
|
|
13240
|
+
return neg + (parts.length ? parts.join(' ') : '0s');
|
|
13241
|
+
}
|
|
13242
|
+
/** Format a count with SI magnitude suffixes: 12400 → "12.4k". */
|
|
13243
|
+
function strctFormatSi(value, options = {}) {
|
|
13244
|
+
if (!Number.isFinite(value))
|
|
13245
|
+
return '—';
|
|
13246
|
+
const { digits = 1, unit = '' } = options;
|
|
13247
|
+
const neg = value < 0 ? '-' : '';
|
|
13248
|
+
let v = Math.abs(value);
|
|
13249
|
+
let i = 0;
|
|
13250
|
+
while (v >= 1000 && i < SI_SUFFIXES.length - 1) {
|
|
13251
|
+
v /= 1000;
|
|
13252
|
+
i++;
|
|
13253
|
+
}
|
|
13254
|
+
const num = `${neg}${trimZeros(v.toFixed(i === 0 ? 0 : digits))}${SI_SUFFIXES[i]}`;
|
|
13255
|
+
return unit ? `${num} ${unit}` : num;
|
|
13256
|
+
}
|
|
13257
|
+
/** `{{ bytes | strctBytes }}` / `{{ bytes | strctBytes : false }}` (decimal). */
|
|
13258
|
+
class StrctBytesPipe {
|
|
13259
|
+
transform(value, binary = true, digits = 1) {
|
|
13260
|
+
return strctFormatBytes(value, { binary, digits });
|
|
13261
|
+
}
|
|
13262
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctBytesPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
13263
|
+
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.16", ngImport: i0, type: StrctBytesPipe, isStandalone: true, name: "strctBytes" });
|
|
13264
|
+
}
|
|
13265
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctBytesPipe, decorators: [{
|
|
13266
|
+
type: Pipe,
|
|
13267
|
+
args: [{ name: 'strctBytes' }]
|
|
13268
|
+
}] });
|
|
13269
|
+
/** `{{ bps | strctRate }}` — bits per second with kbit/Mbit/Gbit steps. */
|
|
13270
|
+
class StrctRatePipe {
|
|
13271
|
+
transform(value, digits = 1) {
|
|
13272
|
+
return strctFormatRate(value, { digits });
|
|
13273
|
+
}
|
|
13274
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctRatePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
13275
|
+
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.16", ngImport: i0, type: StrctRatePipe, isStandalone: true, name: "strctRate" });
|
|
13276
|
+
}
|
|
13277
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctRatePipe, decorators: [{
|
|
13278
|
+
type: Pipe,
|
|
13279
|
+
args: [{ name: 'strctRate' }]
|
|
13280
|
+
}] });
|
|
13281
|
+
/** `{{ ms | strctDuration }}` — "2h 14m"; optional max unit count. */
|
|
13282
|
+
class StrctDurationPipe {
|
|
13283
|
+
transform(value, maxUnits = 2) {
|
|
13284
|
+
return strctFormatDuration(value, { maxUnits });
|
|
13285
|
+
}
|
|
13286
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctDurationPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
13287
|
+
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.16", ngImport: i0, type: StrctDurationPipe, isStandalone: true, name: "strctDuration" });
|
|
13288
|
+
}
|
|
13289
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctDurationPipe, decorators: [{
|
|
13290
|
+
type: Pipe,
|
|
13291
|
+
args: [{ name: 'strctDuration' }]
|
|
13292
|
+
}] });
|
|
13293
|
+
/** `{{ n | strctSi : 'IOPS' }}` — SI magnitude with an optional unit label. */
|
|
13294
|
+
class StrctSiPipe {
|
|
13295
|
+
transform(value, unit = '', digits = 1) {
|
|
13296
|
+
return strctFormatSi(value, { unit, digits });
|
|
13297
|
+
}
|
|
13298
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctSiPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
13299
|
+
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.16", ngImport: i0, type: StrctSiPipe, isStandalone: true, name: "strctSi" });
|
|
13300
|
+
}
|
|
13301
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctSiPipe, decorators: [{
|
|
13302
|
+
type: Pipe,
|
|
13303
|
+
args: [{ name: 'strctSi' }]
|
|
13304
|
+
}] });
|
|
13305
|
+
|
|
13306
|
+
/** The Grafana-conventional quick ranges consoles expect. */
|
|
13307
|
+
const STRCT_TIME_RANGE_PRESETS = [
|
|
13308
|
+
{ id: '15m', label: 'Last 15 minutes', ms: 15 * 60_000 },
|
|
13309
|
+
{ id: '1h', label: 'Last 1 hour', ms: 3_600_000 },
|
|
13310
|
+
{ id: '6h', label: 'Last 6 hours', ms: 6 * 3_600_000 },
|
|
13311
|
+
{ id: '24h', label: 'Last 24 hours', ms: 24 * 3_600_000 },
|
|
13312
|
+
{ id: '7d', label: 'Last 7 days', ms: 7 * 86_400_000 },
|
|
13313
|
+
{ id: '30d', label: 'Last 30 days', ms: 30 * 86_400_000 },
|
|
13314
|
+
];
|
|
13315
|
+
/**
|
|
13316
|
+
* Monitoring time-range picker — the "Last 1 hour ▾" control charts hang off:
|
|
13317
|
+
* quick relative ranges plus an absolute from/to editor, in one popover.
|
|
13318
|
+
*
|
|
13319
|
+
* <strct-time-range [(range)]="range" />
|
|
13320
|
+
*
|
|
13321
|
+
* Picking a preset resolves it against "now" immediately and stamps
|
|
13322
|
+
* `presetId`, so a consumer can re-resolve the same preset on its own refresh
|
|
13323
|
+
* tick. The popover reuses `strct-dropdown`'s dialog semantics: inner clicks
|
|
13324
|
+
* keep it open, outside click / Escape dismiss.
|
|
13325
|
+
*/
|
|
13326
|
+
class StrctTimeRangePicker {
|
|
13327
|
+
/** The selected window, two-way. Null until the user (or you) picks one. */
|
|
13328
|
+
range = model(null, ...(ngDevMode ? [{ debugName: "range" }] : /* istanbul ignore next */ []));
|
|
13329
|
+
/** Quick ranges shown in the left column. */
|
|
13330
|
+
presets = input(STRCT_TIME_RANGE_PRESETS, ...(ngDevMode ? [{ debugName: "presets" }] : /* istanbul ignore next */ []));
|
|
13331
|
+
/** Popover alignment, as in strct-dropdown. */
|
|
13332
|
+
align = input('start', ...(ngDevMode ? [{ debugName: "align" }] : /* istanbul ignore next */ []));
|
|
13333
|
+
/** Localizable strings. */
|
|
13334
|
+
dialogLabel = input('Time range', ...(ngDevMode ? [{ debugName: "dialogLabel" }] : /* istanbul ignore next */ []));
|
|
13335
|
+
quickLabel = input('Quick ranges', ...(ngDevMode ? [{ debugName: "quickLabel" }] : /* istanbul ignore next */ []));
|
|
13336
|
+
absoluteLabel = input('Absolute range', ...(ngDevMode ? [{ debugName: "absoluteLabel" }] : /* istanbul ignore next */ []));
|
|
13337
|
+
fromLabel = input('From', ...(ngDevMode ? [{ debugName: "fromLabel" }] : /* istanbul ignore next */ []));
|
|
13338
|
+
toLabel = input('To', ...(ngDevMode ? [{ debugName: "toLabel" }] : /* istanbul ignore next */ []));
|
|
13339
|
+
applyLabel = input('Apply', ...(ngDevMode ? [{ debugName: "applyLabel" }] : /* istanbul ignore next */ []));
|
|
13340
|
+
invalidLabel = input('"From" must be before "To".', ...(ngDevMode ? [{ debugName: "invalidLabel" }] : /* istanbul ignore next */ []));
|
|
13341
|
+
placeholderLabel = input('Select time range', ...(ngDevMode ? [{ debugName: "placeholderLabel" }] : /* istanbul ignore next */ []));
|
|
13342
|
+
/** Fires on every commit (preset pick or absolute apply). */
|
|
13343
|
+
applied = output();
|
|
13344
|
+
dd = viewChild.required(StrctDropdown);
|
|
13345
|
+
// Absolute-editor draft (datetime-local strings), seeded from the range.
|
|
13346
|
+
draftFrom = signal('', ...(ngDevMode ? [{ debugName: "draftFrom" }] : /* istanbul ignore next */ []));
|
|
13347
|
+
draftTo = signal('', ...(ngDevMode ? [{ debugName: "draftTo" }] : /* istanbul ignore next */ []));
|
|
13348
|
+
draftError = computed(() => {
|
|
13349
|
+
const f = this.draftFrom();
|
|
13350
|
+
const t = this.draftTo();
|
|
13351
|
+
if (!f || !t)
|
|
13352
|
+
return false;
|
|
13353
|
+
return new Date(f).getTime() >= new Date(t).getTime();
|
|
13354
|
+
}, ...(ngDevMode ? [{ debugName: "draftError" }] : /* istanbul ignore next */ []));
|
|
13355
|
+
triggerLabel = computed(() => {
|
|
13356
|
+
const r = this.range();
|
|
13357
|
+
if (!r)
|
|
13358
|
+
return this.placeholderLabel();
|
|
13359
|
+
const preset = this.presets().find((p) => p.id === r.presetId);
|
|
13360
|
+
if (preset)
|
|
13361
|
+
return preset.label;
|
|
13362
|
+
return `${this.formatStamp(r.from)} → ${this.formatStamp(r.to)}`;
|
|
13363
|
+
}, ...(ngDevMode ? [{ debugName: "triggerLabel" }] : /* istanbul ignore next */ []));
|
|
13364
|
+
pickPreset(p) {
|
|
13365
|
+
const to = new Date();
|
|
13366
|
+
const next = { from: new Date(to.getTime() - p.ms), to, presetId: p.id };
|
|
13367
|
+
this.range.set(next);
|
|
13368
|
+
this.applied.emit(next);
|
|
13369
|
+
this.seedDraft(next);
|
|
13370
|
+
}
|
|
13371
|
+
applyAbsolute() {
|
|
13372
|
+
const f = this.draftFrom();
|
|
13373
|
+
const t = this.draftTo();
|
|
13374
|
+
if (!f || !t || this.draftError())
|
|
13375
|
+
return false;
|
|
13376
|
+
const next = { from: new Date(f), to: new Date(t) };
|
|
13377
|
+
this.range.set(next);
|
|
13378
|
+
this.applied.emit(next);
|
|
13379
|
+
return true;
|
|
13380
|
+
}
|
|
13381
|
+
/** Re-resolve a preset range against "now" — call from your refresh tick. */
|
|
13382
|
+
refresh() {
|
|
13383
|
+
const r = this.range();
|
|
13384
|
+
const preset = r && this.presets().find((p) => p.id === r.presetId);
|
|
13385
|
+
if (!preset)
|
|
13386
|
+
return;
|
|
13387
|
+
this.pickPreset(preset);
|
|
13388
|
+
}
|
|
13389
|
+
seedDraft(r) {
|
|
13390
|
+
this.draftFrom.set(this.toLocal(r.from));
|
|
13391
|
+
this.draftTo.set(this.toLocal(r.to));
|
|
13392
|
+
}
|
|
13393
|
+
toLocal(d) {
|
|
13394
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
13395
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
|
13396
|
+
}
|
|
13397
|
+
formatStamp(d) {
|
|
13398
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
13399
|
+
return `${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
|
13400
|
+
}
|
|
13401
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctTimeRangePicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
13402
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: StrctTimeRangePicker, isStandalone: true, selector: "strct-time-range", inputs: { range: { classPropertyName: "range", publicName: "range", isSignal: true, isRequired: false, transformFunction: null }, presets: { classPropertyName: "presets", publicName: "presets", isSignal: true, isRequired: false, transformFunction: null }, align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null }, dialogLabel: { classPropertyName: "dialogLabel", publicName: "dialogLabel", isSignal: true, isRequired: false, transformFunction: null }, quickLabel: { classPropertyName: "quickLabel", publicName: "quickLabel", isSignal: true, isRequired: false, transformFunction: null }, absoluteLabel: { classPropertyName: "absoluteLabel", publicName: "absoluteLabel", isSignal: true, isRequired: false, transformFunction: null }, fromLabel: { classPropertyName: "fromLabel", publicName: "fromLabel", isSignal: true, isRequired: false, transformFunction: null }, toLabel: { classPropertyName: "toLabel", publicName: "toLabel", isSignal: true, isRequired: false, transformFunction: null }, applyLabel: { classPropertyName: "applyLabel", publicName: "applyLabel", isSignal: true, isRequired: false, transformFunction: null }, invalidLabel: { classPropertyName: "invalidLabel", publicName: "invalidLabel", isSignal: true, isRequired: false, transformFunction: null }, placeholderLabel: { classPropertyName: "placeholderLabel", publicName: "placeholderLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { range: "rangeChange", applied: "applied" }, host: { classAttribute: "strct-tr" }, viewQueries: [{ propertyName: "dd", first: true, predicate: StrctDropdown, descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
13403
|
+
<strct-dropdown #dd popover [popoverLabel]="dialogLabel()" [align]="align()">
|
|
13404
|
+
<button strct-button variant="neutral" strctDropdownTrigger class="strct-tr__trigger">
|
|
13405
|
+
<strct-icon name="clock" [size]="13" />
|
|
13406
|
+
<span class="strct-tr__label">{{ triggerLabel() }}</span>
|
|
13407
|
+
<strct-icon name="chevronDown" [size]="12" />
|
|
13408
|
+
</button>
|
|
13409
|
+
|
|
13410
|
+
<div class="strct-tr__panel">
|
|
13411
|
+
<div class="strct-tr__group" role="group" [attr.aria-label]="quickLabel()">
|
|
13412
|
+
<div class="strct-tr__head">{{ quickLabel() }}</div>
|
|
13413
|
+
@for (p of presets(); track p.id) {
|
|
13414
|
+
<button
|
|
13415
|
+
type="button"
|
|
13416
|
+
class="strct-tr__preset"
|
|
13417
|
+
[class.strct-tr__preset--active]="range()?.presetId === p.id"
|
|
13418
|
+
[attr.aria-pressed]="range()?.presetId === p.id"
|
|
13419
|
+
(click)="pickPreset(p); dd.close()"
|
|
13420
|
+
>
|
|
13421
|
+
{{ p.label }}
|
|
13422
|
+
</button>
|
|
13423
|
+
}
|
|
13424
|
+
</div>
|
|
13425
|
+
|
|
13426
|
+
<div class="strct-tr__group" role="group" [attr.aria-label]="absoluteLabel()">
|
|
13427
|
+
<div class="strct-tr__head">{{ absoluteLabel() }}</div>
|
|
13428
|
+
<label class="strct-tr__field">
|
|
13429
|
+
<span>{{ fromLabel() }}</span>
|
|
13430
|
+
<input
|
|
13431
|
+
type="datetime-local"
|
|
13432
|
+
class="strct-tr__input"
|
|
13433
|
+
[value]="draftFrom()"
|
|
13434
|
+
(input)="draftFrom.set($any($event.target).value)"
|
|
13435
|
+
/>
|
|
13436
|
+
</label>
|
|
13437
|
+
<label class="strct-tr__field">
|
|
13438
|
+
<span>{{ toLabel() }}</span>
|
|
13439
|
+
<input
|
|
13440
|
+
type="datetime-local"
|
|
13441
|
+
class="strct-tr__input"
|
|
13442
|
+
[value]="draftTo()"
|
|
13443
|
+
(input)="draftTo.set($any($event.target).value)"
|
|
13444
|
+
/>
|
|
13445
|
+
</label>
|
|
13446
|
+
@if (draftError()) {
|
|
13447
|
+
<div class="strct-tr__error" role="alert">{{ invalidLabel() }}</div>
|
|
13448
|
+
}
|
|
13449
|
+
<button
|
|
13450
|
+
strct-button
|
|
13451
|
+
variant="primary"
|
|
13452
|
+
size="sm"
|
|
13453
|
+
class="strct-tr__apply"
|
|
13454
|
+
[disabled]="draftError()"
|
|
13455
|
+
(click)="applyAbsolute() && dd.close()"
|
|
13456
|
+
>
|
|
13457
|
+
{{ applyLabel() }}
|
|
13458
|
+
</button>
|
|
13459
|
+
</div>
|
|
13460
|
+
</div>
|
|
13461
|
+
</strct-dropdown>
|
|
13462
|
+
`, isInline: true, styles: [".strct-tr{display:inline-block}.strct-tr__trigger .strct-tr__label{max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.strct-tr__panel{display:flex;gap:16px}.strct-tr__group{display:flex;flex-direction:column;gap:2px;min-width:150px}.strct-tr__head{font-size:10.5px;font-weight:600;letter-spacing:.7px;text-transform:uppercase;color:var(--t3);margin-bottom:5px}.strct-tr__preset{text-align:start;padding:6px 9px;border:0;border-radius:var(--radius-sm);background:transparent;font-family:var(--font);font-size:12.5px;color:var(--t1);cursor:pointer}.strct-tr__preset:hover{background:var(--bg-3)}.strct-tr__preset--active{background:var(--acc18);color:var(--acc);font-weight:600}.strct-tr__preset:focus-visible,.strct-tr__input:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}.strct-tr__field{display:flex;flex-direction:column;gap:3px;font-size:11.5px;color:var(--t2);margin-bottom:7px}.strct-tr__input{padding:5px 8px;border:1px solid var(--b2);border-radius:var(--radius-sm);background:var(--bg-2);color:var(--t1);font-family:var(--font);font-size:12px}.strct-tr__error{font-size:11.5px;color:var(--critical);margin-bottom:6px}.strct-tr__apply{align-self:flex-start}\n"], dependencies: [{ kind: "component", type: StrctButton, selector: "button[strct-button], a[strct-button]", inputs: ["variant", "size", "solid", "block", "iconOnly"] }, { kind: "component", type: StrctDropdown, selector: "strct-dropdown", inputs: ["align", "popover", "popoverLabel"] }, { kind: "directive", type: StrctDropdownTrigger, selector: "[strctDropdownTrigger]" }, { kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "size", "strokeWidth", "badge", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
13463
|
+
}
|
|
13464
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctTimeRangePicker, decorators: [{
|
|
13465
|
+
type: Component,
|
|
13466
|
+
args: [{ selector: 'strct-time-range', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [StrctButton, StrctDropdown, StrctDropdownTrigger, StrctIcon], template: `
|
|
13467
|
+
<strct-dropdown #dd popover [popoverLabel]="dialogLabel()" [align]="align()">
|
|
13468
|
+
<button strct-button variant="neutral" strctDropdownTrigger class="strct-tr__trigger">
|
|
13469
|
+
<strct-icon name="clock" [size]="13" />
|
|
13470
|
+
<span class="strct-tr__label">{{ triggerLabel() }}</span>
|
|
13471
|
+
<strct-icon name="chevronDown" [size]="12" />
|
|
13472
|
+
</button>
|
|
13473
|
+
|
|
13474
|
+
<div class="strct-tr__panel">
|
|
13475
|
+
<div class="strct-tr__group" role="group" [attr.aria-label]="quickLabel()">
|
|
13476
|
+
<div class="strct-tr__head">{{ quickLabel() }}</div>
|
|
13477
|
+
@for (p of presets(); track p.id) {
|
|
13478
|
+
<button
|
|
13479
|
+
type="button"
|
|
13480
|
+
class="strct-tr__preset"
|
|
13481
|
+
[class.strct-tr__preset--active]="range()?.presetId === p.id"
|
|
13482
|
+
[attr.aria-pressed]="range()?.presetId === p.id"
|
|
13483
|
+
(click)="pickPreset(p); dd.close()"
|
|
13484
|
+
>
|
|
13485
|
+
{{ p.label }}
|
|
13486
|
+
</button>
|
|
13487
|
+
}
|
|
13488
|
+
</div>
|
|
13489
|
+
|
|
13490
|
+
<div class="strct-tr__group" role="group" [attr.aria-label]="absoluteLabel()">
|
|
13491
|
+
<div class="strct-tr__head">{{ absoluteLabel() }}</div>
|
|
13492
|
+
<label class="strct-tr__field">
|
|
13493
|
+
<span>{{ fromLabel() }}</span>
|
|
13494
|
+
<input
|
|
13495
|
+
type="datetime-local"
|
|
13496
|
+
class="strct-tr__input"
|
|
13497
|
+
[value]="draftFrom()"
|
|
13498
|
+
(input)="draftFrom.set($any($event.target).value)"
|
|
13499
|
+
/>
|
|
13500
|
+
</label>
|
|
13501
|
+
<label class="strct-tr__field">
|
|
13502
|
+
<span>{{ toLabel() }}</span>
|
|
13503
|
+
<input
|
|
13504
|
+
type="datetime-local"
|
|
13505
|
+
class="strct-tr__input"
|
|
13506
|
+
[value]="draftTo()"
|
|
13507
|
+
(input)="draftTo.set($any($event.target).value)"
|
|
13508
|
+
/>
|
|
13509
|
+
</label>
|
|
13510
|
+
@if (draftError()) {
|
|
13511
|
+
<div class="strct-tr__error" role="alert">{{ invalidLabel() }}</div>
|
|
13512
|
+
}
|
|
13513
|
+
<button
|
|
13514
|
+
strct-button
|
|
13515
|
+
variant="primary"
|
|
13516
|
+
size="sm"
|
|
13517
|
+
class="strct-tr__apply"
|
|
13518
|
+
[disabled]="draftError()"
|
|
13519
|
+
(click)="applyAbsolute() && dd.close()"
|
|
13520
|
+
>
|
|
13521
|
+
{{ applyLabel() }}
|
|
13522
|
+
</button>
|
|
13523
|
+
</div>
|
|
13524
|
+
</div>
|
|
13525
|
+
</strct-dropdown>
|
|
13526
|
+
`, host: { class: 'strct-tr' }, styles: [".strct-tr{display:inline-block}.strct-tr__trigger .strct-tr__label{max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.strct-tr__panel{display:flex;gap:16px}.strct-tr__group{display:flex;flex-direction:column;gap:2px;min-width:150px}.strct-tr__head{font-size:10.5px;font-weight:600;letter-spacing:.7px;text-transform:uppercase;color:var(--t3);margin-bottom:5px}.strct-tr__preset{text-align:start;padding:6px 9px;border:0;border-radius:var(--radius-sm);background:transparent;font-family:var(--font);font-size:12.5px;color:var(--t1);cursor:pointer}.strct-tr__preset:hover{background:var(--bg-3)}.strct-tr__preset--active{background:var(--acc18);color:var(--acc);font-weight:600}.strct-tr__preset:focus-visible,.strct-tr__input:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}.strct-tr__field{display:flex;flex-direction:column;gap:3px;font-size:11.5px;color:var(--t2);margin-bottom:7px}.strct-tr__input{padding:5px 8px;border:1px solid var(--b2);border-radius:var(--radius-sm);background:var(--bg-2);color:var(--t1);font-family:var(--font);font-size:12px}.strct-tr__error{font-size:11.5px;color:var(--critical);margin-bottom:6px}.strct-tr__apply{align-self:flex-start}\n"] }]
|
|
13527
|
+
}], propDecorators: { range: [{ type: i0.Input, args: [{ isSignal: true, alias: "range", required: false }] }, { type: i0.Output, args: ["rangeChange"] }], presets: [{ type: i0.Input, args: [{ isSignal: true, alias: "presets", required: false }] }], align: [{ type: i0.Input, args: [{ isSignal: true, alias: "align", required: false }] }], dialogLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "dialogLabel", required: false }] }], quickLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "quickLabel", required: false }] }], absoluteLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "absoluteLabel", required: false }] }], fromLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "fromLabel", required: false }] }], toLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "toLabel", required: false }] }], applyLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "applyLabel", required: false }] }], invalidLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalidLabel", required: false }] }], placeholderLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholderLabel", required: false }] }], applied: [{ type: i0.Output, args: ["applied"] }], dd: [{ type: i0.ViewChild, args: [i0.forwardRef(() => StrctDropdown), { isSignal: true }] }] } });
|
|
13528
|
+
|
|
13529
|
+
// SGR code → segment class. Colors map onto chart/status tokens, not raw hues.
|
|
13530
|
+
const ANSI_CLASSES = {
|
|
13531
|
+
1: 'b',
|
|
13532
|
+
30: 'c30',
|
|
13533
|
+
31: 'c31',
|
|
13534
|
+
32: 'c32',
|
|
13535
|
+
33: 'c33',
|
|
13536
|
+
34: 'c34',
|
|
13537
|
+
35: 'c35',
|
|
13538
|
+
36: 'c36',
|
|
13539
|
+
37: 'c37',
|
|
13540
|
+
90: 'c90',
|
|
13541
|
+
91: 'c31',
|
|
13542
|
+
92: 'c32',
|
|
13543
|
+
93: 'c33',
|
|
13544
|
+
94: 'c34',
|
|
13545
|
+
95: 'c35',
|
|
13546
|
+
96: 'c36',
|
|
13547
|
+
97: 'c37',
|
|
13548
|
+
};
|
|
13549
|
+
const LEVEL_RE = /\b(ERROR|ERR|FATAL|WARN(?:ING)?|INFO|DEBUG|TRACE)\b/;
|
|
13550
|
+
/**
|
|
13551
|
+
* Virtualized log tail — the `kubectl logs -f` surface as a component:
|
|
13552
|
+
*
|
|
13553
|
+
* <strct-log-viewer [lines]="lines()" [(follow)]="follow" [height]="360" />
|
|
13554
|
+
*
|
|
13555
|
+
* - **Virtualized**: only the visible window plus overscan is in the DOM, so
|
|
13556
|
+
* 100k lines scroll flat.
|
|
13557
|
+
* - **Follow mode** (two-way): sticks to the tail as lines stream in;
|
|
13558
|
+
* scrolling up pauses it, the button (or scrolling back to the bottom)
|
|
13559
|
+
* resumes it — the Grafana/Loki convention.
|
|
13560
|
+
* - **ANSI SGR colors** (16-color + bold) parsed into safe spans — no
|
|
13561
|
+
* innerHTML — and severity tinting from `StrctLogLine.level` or, for plain
|
|
13562
|
+
* strings, auto-detected ERROR/WARN/INFO/DEBUG tokens.
|
|
13563
|
+
*/
|
|
13564
|
+
class StrctLogViewer {
|
|
13565
|
+
/** Log lines — plain strings or `{ text, level }` objects. */
|
|
13566
|
+
lines = input([], ...(ngDevMode ? [{ debugName: "lines" }] : /* istanbul ignore next */ []));
|
|
13567
|
+
/** Viewport height in px. */
|
|
13568
|
+
height = input(320, ...(ngDevMode ? [{ debugName: "height" }] : /* istanbul ignore next */ []));
|
|
13569
|
+
/** Follow the tail as new lines arrive (two-way; pauses on scroll-up). */
|
|
13570
|
+
follow = model(true, ...(ngDevMode ? [{ debugName: "follow" }] : /* istanbul ignore next */ []));
|
|
13571
|
+
/** Soft-wrap long lines instead of horizontal scrolling (two-way). */
|
|
13572
|
+
wrapMode = model(false, ...(ngDevMode ? [{ debugName: "wrapMode" }] : /* istanbul ignore next */ []));
|
|
13573
|
+
/** Auto-detect ERROR/WARN/INFO/DEBUG tokens on plain-string lines. */
|
|
13574
|
+
autoLevel = input(true, { ...(ngDevMode ? { debugName: "autoLevel" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
13575
|
+
/** Localizable strings. */
|
|
13576
|
+
title = input('Logs', ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
13577
|
+
followLabel = input('Follow', ...(ngDevMode ? [{ debugName: "followLabel" }] : /* istanbul ignore next */ []));
|
|
13578
|
+
wrapLabel = input('Wrap', ...(ngDevMode ? [{ debugName: "wrapLabel" }] : /* istanbul ignore next */ []));
|
|
13579
|
+
linesLabel = input('lines', ...(ngDevMode ? [{ debugName: "linesLabel" }] : /* istanbul ignore next */ []));
|
|
13580
|
+
emptyLabel = input('No log lines.', ...(ngDevMode ? [{ debugName: "emptyLabel" }] : /* istanbul ignore next */ []));
|
|
13581
|
+
scroller = viewChild.required('scroller');
|
|
13582
|
+
scrollTop = signal(0, ...(ngDevMode ? [{ debugName: "scrollTop" }] : /* istanbul ignore next */ []));
|
|
13583
|
+
suppressScrollEvent = false;
|
|
13584
|
+
static LINE_H = 20;
|
|
13585
|
+
static OVERSCAN = 10;
|
|
13586
|
+
normalized = computed(() => this.lines().map((l) => (typeof l === 'string' ? { text: l } : l)), ...(ngDevMode ? [{ debugName: "normalized" }] : /* istanbul ignore next */ []));
|
|
13587
|
+
totalHeight = computed(() => this.normalized().length * StrctLogViewer.LINE_H || StrctLogViewer.LINE_H, ...(ngDevMode ? [{ debugName: "totalHeight" }] : /* istanbul ignore next */ []));
|
|
13588
|
+
windowStart = computed(() => {
|
|
13589
|
+
const start = Math.floor(this.scrollTop() / StrctLogViewer.LINE_H) - StrctLogViewer.OVERSCAN;
|
|
13590
|
+
return Math.max(0, start);
|
|
13591
|
+
}, ...(ngDevMode ? [{ debugName: "windowStart" }] : /* istanbul ignore next */ []));
|
|
13592
|
+
offsetY = computed(() => this.windowStart() * StrctLogViewer.LINE_H, ...(ngDevMode ? [{ debugName: "offsetY" }] : /* istanbul ignore next */ []));
|
|
13593
|
+
window = computed(() => {
|
|
13594
|
+
const all = this.normalized();
|
|
13595
|
+
const start = this.windowStart();
|
|
13596
|
+
const visible = Math.ceil(this.height() / StrctLogViewer.LINE_H) + StrctLogViewer.OVERSCAN * 2;
|
|
13597
|
+
return all.slice(start, start + visible).map((line, i) => ({
|
|
13598
|
+
index: start + i,
|
|
13599
|
+
level: line.level ?? (this.autoLevel() ? detectLevel(line.text) : 'none'),
|
|
13600
|
+
segments: parseAnsi(line.text),
|
|
13601
|
+
}));
|
|
13602
|
+
}, ...(ngDevMode ? [{ debugName: "window" }] : /* istanbul ignore next */ []));
|
|
13603
|
+
constructor() {
|
|
13604
|
+
// Stick to the tail whenever content grows while following.
|
|
13605
|
+
effect(() => {
|
|
13606
|
+
this.totalHeight();
|
|
13607
|
+
if (!this.follow())
|
|
13608
|
+
return;
|
|
13609
|
+
const el = this.scroller().nativeElement;
|
|
13610
|
+
// After the DOM height settles.
|
|
13611
|
+
requestAnimationFrame(() => {
|
|
13612
|
+
this.suppressScrollEvent = true;
|
|
13613
|
+
el.scrollTop = el.scrollHeight;
|
|
13614
|
+
this.scrollTop.set(el.scrollTop);
|
|
13615
|
+
});
|
|
13616
|
+
});
|
|
13617
|
+
}
|
|
13618
|
+
onScroll() {
|
|
13619
|
+
const el = this.scroller().nativeElement;
|
|
13620
|
+
this.scrollTop.set(el.scrollTop);
|
|
13621
|
+
if (this.suppressScrollEvent) {
|
|
13622
|
+
this.suppressScrollEvent = false;
|
|
13623
|
+
return;
|
|
13624
|
+
}
|
|
13625
|
+
const atBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - StrctLogViewer.LINE_H;
|
|
13626
|
+
// Manual scroll: leaving the tail pauses follow, reaching it resumes.
|
|
13627
|
+
if (this.follow() && !atBottom)
|
|
13628
|
+
this.follow.set(false);
|
|
13629
|
+
else if (!this.follow() && atBottom)
|
|
13630
|
+
this.follow.set(true);
|
|
13631
|
+
}
|
|
13632
|
+
toggleFollow() {
|
|
13633
|
+
const next = !this.follow();
|
|
13634
|
+
this.follow.set(next);
|
|
13635
|
+
if (next) {
|
|
13636
|
+
const el = this.scroller().nativeElement;
|
|
13637
|
+
this.suppressScrollEvent = true;
|
|
13638
|
+
el.scrollTop = el.scrollHeight;
|
|
13639
|
+
this.scrollTop.set(el.scrollTop);
|
|
13640
|
+
}
|
|
13641
|
+
}
|
|
13642
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctLogViewer, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
13643
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: StrctLogViewer, isStandalone: true, selector: "strct-log-viewer", inputs: { lines: { classPropertyName: "lines", publicName: "lines", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, follow: { classPropertyName: "follow", publicName: "follow", isSignal: true, isRequired: false, transformFunction: null }, wrapMode: { classPropertyName: "wrapMode", publicName: "wrapMode", isSignal: true, isRequired: false, transformFunction: null }, autoLevel: { classPropertyName: "autoLevel", publicName: "autoLevel", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, followLabel: { classPropertyName: "followLabel", publicName: "followLabel", isSignal: true, isRequired: false, transformFunction: null }, wrapLabel: { classPropertyName: "wrapLabel", publicName: "wrapLabel", isSignal: true, isRequired: false, transformFunction: null }, linesLabel: { classPropertyName: "linesLabel", publicName: "linesLabel", isSignal: true, isRequired: false, transformFunction: null }, emptyLabel: { classPropertyName: "emptyLabel", publicName: "emptyLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { follow: "followChange", wrapMode: "wrapModeChange" }, host: { classAttribute: "strct-lv" }, viewQueries: [{ propertyName: "scroller", first: true, predicate: ["scroller"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
13644
|
+
<div class="strct-lv__bar">
|
|
13645
|
+
<span class="strct-lv__title">{{ title() }}</span>
|
|
13646
|
+
<span class="strct-lv__count">{{ normalized().length }} {{ linesLabel() }}</span>
|
|
13647
|
+
<span class="strct-lv__grow"></span>
|
|
13648
|
+
<button
|
|
13649
|
+
strct-button
|
|
13650
|
+
size="sm"
|
|
13651
|
+
[variant]="wrapMode() ? 'primary' : 'flat'"
|
|
13652
|
+
[attr.aria-pressed]="wrapMode()"
|
|
13653
|
+
(click)="wrapMode.set(!wrapMode())"
|
|
13654
|
+
>
|
|
13655
|
+
{{ wrapLabel() }}
|
|
13656
|
+
</button>
|
|
13657
|
+
<button
|
|
13658
|
+
strct-button
|
|
13659
|
+
size="sm"
|
|
13660
|
+
[variant]="follow() ? 'primary' : 'flat'"
|
|
13661
|
+
[attr.aria-pressed]="follow()"
|
|
13662
|
+
(click)="toggleFollow()"
|
|
13663
|
+
>
|
|
13664
|
+
<strct-icon name="chevronDoubleDown" [size]="12" />
|
|
13665
|
+
{{ followLabel() }}
|
|
13666
|
+
</button>
|
|
13667
|
+
</div>
|
|
13668
|
+
<div
|
|
13669
|
+
#scroller
|
|
13670
|
+
class="strct-lv__scroll"
|
|
13671
|
+
role="log"
|
|
13672
|
+
tabindex="0"
|
|
13673
|
+
[attr.aria-label]="title()"
|
|
13674
|
+
[style.height.px]="height()"
|
|
13675
|
+
(scroll)="onScroll()"
|
|
13676
|
+
>
|
|
13677
|
+
<div class="strct-lv__spacer" [style.height.px]="totalHeight()">
|
|
13678
|
+
<div
|
|
13679
|
+
class="strct-lv__window"
|
|
13680
|
+
[class.strct-lv__window--wrap]="wrapMode()"
|
|
13681
|
+
[style.transform]="'translateY(' + offsetY() + 'px)'"
|
|
13682
|
+
>
|
|
13683
|
+
@for (line of window(); track line.index) {
|
|
13684
|
+
<div class="strct-lv__line strct-lv__line--{{ line.level }}">
|
|
13685
|
+
<span class="strct-lv__no" aria-hidden="true">{{ line.index + 1 }}</span>
|
|
13686
|
+
<span class="strct-lv__text">
|
|
13687
|
+
@for (seg of line.segments; track $index) {
|
|
13688
|
+
<span class="strct-lv__seg-{{ seg.cls }}">{{ seg.text }}</span>
|
|
13689
|
+
}
|
|
13690
|
+
</span>
|
|
13691
|
+
</div>
|
|
13692
|
+
} @empty {
|
|
13693
|
+
<div class="strct-lv__empty">{{ emptyLabel() }}</div>
|
|
13694
|
+
}
|
|
13695
|
+
</div>
|
|
13696
|
+
</div>
|
|
13697
|
+
</div>
|
|
13698
|
+
`, isInline: true, styles: [".strct-lv{display:block;border:1px solid var(--b2);border-radius:9px;background:var(--bg-2);overflow:hidden}.strct-lv__bar{display:flex;align-items:center;gap:8px;padding:6px 10px;border-bottom:1px solid var(--b1)}.strct-lv__title{font-size:12.5px;font-weight:600;color:var(--t1)}.strct-lv__count{font-size:11px;color:var(--t3);font-variant-numeric:tabular-nums}.strct-lv__grow{flex:1}.strct-lv__scroll{overflow:auto;overscroll-behavior:contain}.strct-lv__scroll:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-lv__spacer{position:relative}.strct-lv__window{position:absolute;inset-inline:0;top:0}.strct-lv__line{display:flex;gap:10px;height:var(--strct-lv-line-height, 20px);line-height:var(--strct-lv-line-height, 20px);padding-inline:10px;font-family:var(--mono);font-size:12px;color:var(--t1);white-space:pre}.strct-lv__window--wrap .strct-lv__line{height:auto;min-height:var(--strct-lv-line-height, 20px)}.strct-lv__window--wrap .strct-lv__text{white-space:pre-wrap;overflow-wrap:anywhere}.strct-lv__no{flex-shrink:0;min-width:4ch;text-align:end;color:var(--t4);-webkit-user-select:none;user-select:none;font-variant-numeric:tabular-nums}.strct-lv__line--error{background:var(--critical-bg)}.strct-lv__line--error .strct-lv__text{color:var(--critical)}.strct-lv__line--warn{background:var(--warning-bg)}.strct-lv__line--warn .strct-lv__text{color:var(--warning)}.strct-lv__line--debug .strct-lv__text{color:var(--t3)}.strct-lv__empty{padding:14px 12px;font-size:12.5px;color:var(--t3)}.strct-lv__seg-b{font-weight:700}.strct-lv__seg-c31{color:var(--critical)}.strct-lv__seg-c32{color:var(--success)}.strct-lv__seg-c33{color:var(--warning)}.strct-lv__seg-c34{color:var(--chart-1, var(--acc))}.strct-lv__seg-c35{color:var(--chart-4, var(--acc))}.strct-lv__seg-c36{color:var(--chart-5, var(--acc))}.strct-lv__seg-c37{color:var(--t1)}.strct-lv__seg-c30,.strct-lv__seg-c90{color:var(--t3)}\n"], dependencies: [{ kind: "component", type: StrctButton, selector: "button[strct-button], a[strct-button]", inputs: ["variant", "size", "solid", "block", "iconOnly"] }, { kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "size", "strokeWidth", "badge", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
13699
|
+
}
|
|
13700
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctLogViewer, decorators: [{
|
|
13701
|
+
type: Component,
|
|
13702
|
+
args: [{ selector: 'strct-log-viewer', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [StrctButton, StrctIcon], template: `
|
|
13703
|
+
<div class="strct-lv__bar">
|
|
13704
|
+
<span class="strct-lv__title">{{ title() }}</span>
|
|
13705
|
+
<span class="strct-lv__count">{{ normalized().length }} {{ linesLabel() }}</span>
|
|
13706
|
+
<span class="strct-lv__grow"></span>
|
|
13707
|
+
<button
|
|
13708
|
+
strct-button
|
|
13709
|
+
size="sm"
|
|
13710
|
+
[variant]="wrapMode() ? 'primary' : 'flat'"
|
|
13711
|
+
[attr.aria-pressed]="wrapMode()"
|
|
13712
|
+
(click)="wrapMode.set(!wrapMode())"
|
|
13713
|
+
>
|
|
13714
|
+
{{ wrapLabel() }}
|
|
13715
|
+
</button>
|
|
13716
|
+
<button
|
|
13717
|
+
strct-button
|
|
13718
|
+
size="sm"
|
|
13719
|
+
[variant]="follow() ? 'primary' : 'flat'"
|
|
13720
|
+
[attr.aria-pressed]="follow()"
|
|
13721
|
+
(click)="toggleFollow()"
|
|
13722
|
+
>
|
|
13723
|
+
<strct-icon name="chevronDoubleDown" [size]="12" />
|
|
13724
|
+
{{ followLabel() }}
|
|
13725
|
+
</button>
|
|
13726
|
+
</div>
|
|
13727
|
+
<div
|
|
13728
|
+
#scroller
|
|
13729
|
+
class="strct-lv__scroll"
|
|
13730
|
+
role="log"
|
|
13731
|
+
tabindex="0"
|
|
13732
|
+
[attr.aria-label]="title()"
|
|
13733
|
+
[style.height.px]="height()"
|
|
13734
|
+
(scroll)="onScroll()"
|
|
13735
|
+
>
|
|
13736
|
+
<div class="strct-lv__spacer" [style.height.px]="totalHeight()">
|
|
13737
|
+
<div
|
|
13738
|
+
class="strct-lv__window"
|
|
13739
|
+
[class.strct-lv__window--wrap]="wrapMode()"
|
|
13740
|
+
[style.transform]="'translateY(' + offsetY() + 'px)'"
|
|
13741
|
+
>
|
|
13742
|
+
@for (line of window(); track line.index) {
|
|
13743
|
+
<div class="strct-lv__line strct-lv__line--{{ line.level }}">
|
|
13744
|
+
<span class="strct-lv__no" aria-hidden="true">{{ line.index + 1 }}</span>
|
|
13745
|
+
<span class="strct-lv__text">
|
|
13746
|
+
@for (seg of line.segments; track $index) {
|
|
13747
|
+
<span class="strct-lv__seg-{{ seg.cls }}">{{ seg.text }}</span>
|
|
13748
|
+
}
|
|
13749
|
+
</span>
|
|
13750
|
+
</div>
|
|
13751
|
+
} @empty {
|
|
13752
|
+
<div class="strct-lv__empty">{{ emptyLabel() }}</div>
|
|
13753
|
+
}
|
|
13754
|
+
</div>
|
|
13755
|
+
</div>
|
|
13756
|
+
</div>
|
|
13757
|
+
`, host: { class: 'strct-lv' }, styles: [".strct-lv{display:block;border:1px solid var(--b2);border-radius:9px;background:var(--bg-2);overflow:hidden}.strct-lv__bar{display:flex;align-items:center;gap:8px;padding:6px 10px;border-bottom:1px solid var(--b1)}.strct-lv__title{font-size:12.5px;font-weight:600;color:var(--t1)}.strct-lv__count{font-size:11px;color:var(--t3);font-variant-numeric:tabular-nums}.strct-lv__grow{flex:1}.strct-lv__scroll{overflow:auto;overscroll-behavior:contain}.strct-lv__scroll:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-lv__spacer{position:relative}.strct-lv__window{position:absolute;inset-inline:0;top:0}.strct-lv__line{display:flex;gap:10px;height:var(--strct-lv-line-height, 20px);line-height:var(--strct-lv-line-height, 20px);padding-inline:10px;font-family:var(--mono);font-size:12px;color:var(--t1);white-space:pre}.strct-lv__window--wrap .strct-lv__line{height:auto;min-height:var(--strct-lv-line-height, 20px)}.strct-lv__window--wrap .strct-lv__text{white-space:pre-wrap;overflow-wrap:anywhere}.strct-lv__no{flex-shrink:0;min-width:4ch;text-align:end;color:var(--t4);-webkit-user-select:none;user-select:none;font-variant-numeric:tabular-nums}.strct-lv__line--error{background:var(--critical-bg)}.strct-lv__line--error .strct-lv__text{color:var(--critical)}.strct-lv__line--warn{background:var(--warning-bg)}.strct-lv__line--warn .strct-lv__text{color:var(--warning)}.strct-lv__line--debug .strct-lv__text{color:var(--t3)}.strct-lv__empty{padding:14px 12px;font-size:12.5px;color:var(--t3)}.strct-lv__seg-b{font-weight:700}.strct-lv__seg-c31{color:var(--critical)}.strct-lv__seg-c32{color:var(--success)}.strct-lv__seg-c33{color:var(--warning)}.strct-lv__seg-c34{color:var(--chart-1, var(--acc))}.strct-lv__seg-c35{color:var(--chart-4, var(--acc))}.strct-lv__seg-c36{color:var(--chart-5, var(--acc))}.strct-lv__seg-c37{color:var(--t1)}.strct-lv__seg-c30,.strct-lv__seg-c90{color:var(--t3)}\n"] }]
|
|
13758
|
+
}], ctorParameters: () => [], propDecorators: { lines: [{ type: i0.Input, args: [{ isSignal: true, alias: "lines", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], follow: [{ type: i0.Input, args: [{ isSignal: true, alias: "follow", required: false }] }, { type: i0.Output, args: ["followChange"] }], wrapMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "wrapMode", required: false }] }, { type: i0.Output, args: ["wrapModeChange"] }], autoLevel: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoLevel", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], followLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "followLabel", required: false }] }], wrapLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "wrapLabel", required: false }] }], linesLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "linesLabel", required: false }] }], emptyLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyLabel", required: false }] }], scroller: [{ type: i0.ViewChild, args: ['scroller', { isSignal: true }] }] } });
|
|
13759
|
+
function detectLevel(text) {
|
|
13760
|
+
const m = LEVEL_RE.exec(text);
|
|
13761
|
+
if (!m)
|
|
13762
|
+
return 'none';
|
|
13763
|
+
const tok = m[1];
|
|
13764
|
+
if (tok.startsWith('ERR') || tok === 'FATAL')
|
|
13765
|
+
return 'error';
|
|
13766
|
+
if (tok.startsWith('WARN'))
|
|
13767
|
+
return 'warn';
|
|
13768
|
+
if (tok === 'DEBUG' || tok === 'TRACE')
|
|
13769
|
+
return 'debug';
|
|
13770
|
+
return 'info';
|
|
13771
|
+
}
|
|
13772
|
+
/** Parse ANSI SGR sequences into class-tagged segments (16-color + bold). */
|
|
13773
|
+
function parseAnsi(text) {
|
|
13774
|
+
if (!text.includes('\u001b['))
|
|
13775
|
+
return [{ text, cls: 'c37' }];
|
|
13776
|
+
const segments = [];
|
|
13777
|
+
const re = /\u001b\[([0-9;]*)m/g;
|
|
13778
|
+
let last = 0;
|
|
13779
|
+
let current = new Set();
|
|
13780
|
+
let m;
|
|
13781
|
+
while ((m = re.exec(text))) {
|
|
13782
|
+
if (m.index > last) {
|
|
13783
|
+
segments.push({ text: text.slice(last, m.index), cls: clsOf(current) });
|
|
13784
|
+
}
|
|
13785
|
+
for (const raw of (m[1] || '0').split(';')) {
|
|
13786
|
+
const code = Number(raw || '0');
|
|
13787
|
+
if (code === 0)
|
|
13788
|
+
current = new Set();
|
|
13789
|
+
else if (ANSI_CLASSES[code]) {
|
|
13790
|
+
// A new foreground color replaces the previous one; bold stacks.
|
|
13791
|
+
if (code !== 1)
|
|
13792
|
+
for (const c of [...current])
|
|
13793
|
+
if (c !== 'b')
|
|
13794
|
+
current.delete(c);
|
|
13795
|
+
current.add(ANSI_CLASSES[code]);
|
|
13796
|
+
}
|
|
13797
|
+
}
|
|
13798
|
+
last = re.lastIndex;
|
|
13799
|
+
}
|
|
13800
|
+
if (last < text.length)
|
|
13801
|
+
segments.push({ text: text.slice(last), cls: clsOf(current) });
|
|
13802
|
+
return segments.length ? segments : [{ text: '', cls: 'c37' }];
|
|
13803
|
+
}
|
|
13804
|
+
function clsOf(set) {
|
|
13805
|
+
return set.size ? [...set].join(' strct-lv__seg-') : 'c37';
|
|
13806
|
+
}
|
|
13807
|
+
|
|
13808
|
+
/**
|
|
13809
|
+
* Compute a line diff (LCS-based). Exported for programmatic use — change
|
|
13810
|
+
* counts, gating "anything changed?" logic, custom renderings.
|
|
13811
|
+
*/
|
|
13812
|
+
function strctComputeDiff(before, after) {
|
|
13813
|
+
const a = before.split('\n');
|
|
13814
|
+
const b = after.split('\n');
|
|
13815
|
+
// Trim common prefix/suffix first — configs mostly match, keeping DP small.
|
|
13816
|
+
let start = 0;
|
|
13817
|
+
while (start < a.length && start < b.length && a[start] === b[start])
|
|
13818
|
+
start++;
|
|
13819
|
+
let endA = a.length;
|
|
13820
|
+
let endB = b.length;
|
|
13821
|
+
while (endA > start && endB > start && a[endA - 1] === b[endB - 1]) {
|
|
13822
|
+
endA--;
|
|
13823
|
+
endB--;
|
|
13824
|
+
}
|
|
13825
|
+
const midA = a.slice(start, endA);
|
|
13826
|
+
const midB = b.slice(start, endB);
|
|
13827
|
+
const rows = [];
|
|
13828
|
+
for (let i = 0; i < start; i++)
|
|
13829
|
+
rows.push({ type: 'ctx', oldNo: i + 1, newNo: i + 1, text: a[i] });
|
|
13830
|
+
// LCS DP on the changed middle; fall back to replace-all when it is huge.
|
|
13831
|
+
if (midA.length * midB.length <= 4_000_000) {
|
|
13832
|
+
const n = midA.length;
|
|
13833
|
+
const m = midB.length;
|
|
13834
|
+
const dp = new Uint32Array((n + 1) * (m + 1));
|
|
13835
|
+
for (let i = n - 1; i >= 0; i--) {
|
|
13836
|
+
for (let j = m - 1; j >= 0; j--) {
|
|
13837
|
+
dp[i * (m + 1) + j] =
|
|
13838
|
+
midA[i] === midB[j]
|
|
13839
|
+
? dp[(i + 1) * (m + 1) + j + 1] + 1
|
|
13840
|
+
: Math.max(dp[(i + 1) * (m + 1) + j], dp[i * (m + 1) + j + 1]);
|
|
13841
|
+
}
|
|
13842
|
+
}
|
|
13843
|
+
let i = 0;
|
|
13844
|
+
let j = 0;
|
|
13845
|
+
while (i < n && j < m) {
|
|
13846
|
+
if (midA[i] === midB[j]) {
|
|
13847
|
+
rows.push({ type: 'ctx', oldNo: start + i + 1, newNo: start + j + 1, text: midA[i] });
|
|
13848
|
+
i++;
|
|
13849
|
+
j++;
|
|
13850
|
+
}
|
|
13851
|
+
else if (dp[(i + 1) * (m + 1) + j] >= dp[i * (m + 1) + j + 1]) {
|
|
13852
|
+
rows.push({ type: 'del', oldNo: start + i + 1, newNo: null, text: midA[i] });
|
|
13853
|
+
i++;
|
|
13854
|
+
}
|
|
13855
|
+
else {
|
|
13856
|
+
rows.push({ type: 'add', oldNo: null, newNo: start + j + 1, text: midB[j] });
|
|
13857
|
+
j++;
|
|
13858
|
+
}
|
|
13859
|
+
}
|
|
13860
|
+
while (i < n) {
|
|
13861
|
+
rows.push({ type: 'del', oldNo: start + i + 1, newNo: null, text: midA[i] });
|
|
13862
|
+
i++;
|
|
13863
|
+
}
|
|
13864
|
+
while (j < m) {
|
|
13865
|
+
rows.push({ type: 'add', oldNo: null, newNo: start + j + 1, text: midB[j] });
|
|
13866
|
+
j++;
|
|
13867
|
+
}
|
|
13868
|
+
}
|
|
13869
|
+
else {
|
|
13870
|
+
for (let i = 0; i < midA.length; i++)
|
|
13871
|
+
rows.push({ type: 'del', oldNo: start + i + 1, newNo: null, text: midA[i] });
|
|
13872
|
+
for (let j = 0; j < midB.length; j++)
|
|
13873
|
+
rows.push({ type: 'add', oldNo: null, newNo: start + j + 1, text: midB[j] });
|
|
13874
|
+
}
|
|
13875
|
+
for (let i = endA, j = endB; i < a.length; i++, j++) {
|
|
13876
|
+
rows.push({ type: 'ctx', oldNo: i + 1, newNo: j + 1, text: a[i] });
|
|
13877
|
+
}
|
|
13878
|
+
return rows;
|
|
13879
|
+
}
|
|
13880
|
+
/**
|
|
13881
|
+
* Config / YAML diff viewer — change-approval screens without a hand-rolled
|
|
13882
|
+
* diff. Unified by default, `split` for side-by-side; long unchanged runs
|
|
13883
|
+
* collapse to an expandable "· N unchanged lines ·" row.
|
|
13884
|
+
*
|
|
13885
|
+
* <strct-diff [before]="saved" [after]="draft" beforeLabel="Running" afterLabel="Draft" />
|
|
13886
|
+
*
|
|
13887
|
+
* Rows are marked by symbol (+/−) as well as tint, so the diff reads without
|
|
13888
|
+
* color — the same CVD rule the icon badges follow.
|
|
13889
|
+
*/
|
|
13890
|
+
class StrctDiff {
|
|
13891
|
+
/** The two texts to compare. */
|
|
13892
|
+
before = input.required(...(ngDevMode ? [{ debugName: "before" }] : /* istanbul ignore next */ []));
|
|
13893
|
+
after = input.required(...(ngDevMode ? [{ debugName: "after" }] : /* istanbul ignore next */ []));
|
|
13894
|
+
/** Unified (default) or side-by-side split view. */
|
|
13895
|
+
mode = input('unified', ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
|
|
13896
|
+
/** Context lines kept around changes before collapsing (0 disables collapsing). */
|
|
13897
|
+
context = input(3, ...(ngDevMode ? [{ debugName: "context" }] : /* istanbul ignore next */ []));
|
|
13898
|
+
/** Header bits. */
|
|
13899
|
+
title = input('Diff', ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
13900
|
+
language = input('', ...(ngDevMode ? [{ debugName: "language" }] : /* istanbul ignore next */ []));
|
|
13901
|
+
beforeLabel = input('Before', ...(ngDevMode ? [{ debugName: "beforeLabel" }] : /* istanbul ignore next */ []));
|
|
13902
|
+
afterLabel = input('After', ...(ngDevMode ? [{ debugName: "afterLabel" }] : /* istanbul ignore next */ []));
|
|
13903
|
+
copyable = input(true, { ...(ngDevMode ? { debugName: "copyable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
13904
|
+
copyAfterLabel = input('Copy new version', ...(ngDevMode ? [{ debugName: "copyAfterLabel" }] : /* istanbul ignore next */ []));
|
|
13905
|
+
unchangedLabel = input('unchanged lines', ...(ngDevMode ? [{ debugName: "unchangedLabel" }] : /* istanbul ignore next */ []));
|
|
13906
|
+
/** Scroll the body past this height (px); null grows freely. */
|
|
13907
|
+
maxHeight = input(null, ...(ngDevMode ? [{ debugName: "maxHeight" }] : /* istanbul ignore next */ []));
|
|
13908
|
+
// Indices of collapsed runs the user has expanded.
|
|
13909
|
+
expanded = signal(new Set(), ...(ngDevMode ? [{ debugName: "expanded" }] : /* istanbul ignore next */ []));
|
|
13910
|
+
rows = computed(() => strctComputeDiff(this.before(), this.after()), ...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
|
|
13911
|
+
addCount = computed(() => this.rows().filter((r) => r.type === 'add').length, ...(ngDevMode ? [{ debugName: "addCount" }] : /* istanbul ignore next */ []));
|
|
13912
|
+
delCount = computed(() => this.rows().filter((r) => r.type === 'del').length, ...(ngDevMode ? [{ debugName: "delCount" }] : /* istanbul ignore next */ []));
|
|
13913
|
+
unifiedRows = computed(() => {
|
|
13914
|
+
const out = [];
|
|
13915
|
+
for (const chunk of this.chunks()) {
|
|
13916
|
+
if (chunk.skip)
|
|
13917
|
+
out.push({ kind: 'skip', count: chunk.rows.length, startIndex: chunk.start });
|
|
13918
|
+
else
|
|
13919
|
+
for (const r of chunk.rows)
|
|
13920
|
+
out.push({ kind: 'row', ...r });
|
|
13921
|
+
}
|
|
13922
|
+
return out;
|
|
13923
|
+
}, ...(ngDevMode ? [{ debugName: "unifiedRows" }] : /* istanbul ignore next */ []));
|
|
13924
|
+
splitRows = computed(() => {
|
|
13925
|
+
const out = [];
|
|
13926
|
+
for (const chunk of this.chunks()) {
|
|
13927
|
+
if (chunk.skip) {
|
|
13928
|
+
out.push({ kind: 'skip', count: chunk.rows.length, startIndex: chunk.start });
|
|
13929
|
+
continue;
|
|
13930
|
+
}
|
|
13931
|
+
// Pair del-runs with the add-run that follows them (classic split view).
|
|
13932
|
+
let i = 0;
|
|
13933
|
+
const rows = chunk.rows;
|
|
13934
|
+
while (i < rows.length) {
|
|
13935
|
+
const r = rows[i];
|
|
13936
|
+
if (r.type === 'ctx') {
|
|
13937
|
+
out.push({
|
|
13938
|
+
kind: 'row',
|
|
13939
|
+
left: { type: 'ctx', no: r.oldNo, text: r.text },
|
|
13940
|
+
right: { type: 'ctx', no: r.newNo, text: r.text },
|
|
13941
|
+
});
|
|
13942
|
+
i++;
|
|
13943
|
+
continue;
|
|
13944
|
+
}
|
|
13945
|
+
const dels = [];
|
|
13946
|
+
const adds = [];
|
|
13947
|
+
while (i < rows.length && rows[i].type === 'del')
|
|
13948
|
+
dels.push(rows[i++]);
|
|
13949
|
+
while (i < rows.length && rows[i].type === 'add')
|
|
13950
|
+
adds.push(rows[i++]);
|
|
13951
|
+
const len = Math.max(dels.length, adds.length);
|
|
13952
|
+
for (let k = 0; k < len; k++) {
|
|
13953
|
+
const d = dels[k];
|
|
13954
|
+
const a = adds[k];
|
|
13955
|
+
out.push({
|
|
13956
|
+
kind: 'row',
|
|
13957
|
+
left: d
|
|
13958
|
+
? { type: 'del', no: d.oldNo, text: d.text }
|
|
13959
|
+
: { type: 'empty', no: null, text: '' },
|
|
13960
|
+
right: a
|
|
13961
|
+
? { type: 'add', no: a.newNo, text: a.text }
|
|
13962
|
+
: { type: 'empty', no: null, text: '' },
|
|
13963
|
+
});
|
|
13964
|
+
}
|
|
13965
|
+
}
|
|
13966
|
+
}
|
|
13967
|
+
return out;
|
|
13968
|
+
}, ...(ngDevMode ? [{ debugName: "splitRows" }] : /* istanbul ignore next */ []));
|
|
13969
|
+
/** Group rows into visible chunks and collapsible unchanged runs. */
|
|
13970
|
+
chunks = computed(() => {
|
|
13971
|
+
const rows = this.rows();
|
|
13972
|
+
const ctx = this.context();
|
|
13973
|
+
const expanded = this.expanded();
|
|
13974
|
+
if (ctx <= 0)
|
|
13975
|
+
return [{ skip: false, start: 0, rows }];
|
|
13976
|
+
// Mark rows within `ctx` of any change as visible.
|
|
13977
|
+
const visible = new Array(rows.length).fill(false);
|
|
13978
|
+
rows.forEach((r, i) => {
|
|
13979
|
+
if (r.type !== 'ctx') {
|
|
13980
|
+
for (let k = Math.max(0, i - ctx); k <= Math.min(rows.length - 1, i + ctx); k++) {
|
|
13981
|
+
visible[k] = true;
|
|
13982
|
+
}
|
|
13983
|
+
}
|
|
13984
|
+
});
|
|
13985
|
+
const out = [];
|
|
13986
|
+
let i = 0;
|
|
13987
|
+
while (i < rows.length) {
|
|
13988
|
+
const vis = visible[i] || expanded.has(startOfRun(visible, i));
|
|
13989
|
+
const start = i;
|
|
13990
|
+
while (i < rows.length && (visible[i] || expanded.has(startOfRun(visible, i))) === vis)
|
|
13991
|
+
i++;
|
|
13992
|
+
const slice = rows.slice(start, i);
|
|
13993
|
+
// Tiny hidden runs are not worth a fold row.
|
|
13994
|
+
if (!vis && slice.length <= 2)
|
|
13995
|
+
out.push({ skip: false, start, rows: slice });
|
|
13996
|
+
else
|
|
13997
|
+
out.push({ skip: !vis, start, rows: slice });
|
|
13998
|
+
}
|
|
13999
|
+
return out;
|
|
14000
|
+
}, ...(ngDevMode ? [{ debugName: "chunks" }] : /* istanbul ignore next */ []));
|
|
14001
|
+
expand(startIndex) {
|
|
14002
|
+
const next = new Set(this.expanded());
|
|
14003
|
+
next.add(startIndex);
|
|
14004
|
+
this.expanded.set(next);
|
|
14005
|
+
}
|
|
14006
|
+
sign(type) {
|
|
14007
|
+
return type === 'add' ? '+' : type === 'del' ? '−' : '';
|
|
14008
|
+
}
|
|
14009
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctDiff, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
14010
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: StrctDiff, isStandalone: true, selector: "strct-diff", inputs: { before: { classPropertyName: "before", publicName: "before", isSignal: true, isRequired: true, transformFunction: null }, after: { classPropertyName: "after", publicName: "after", isSignal: true, isRequired: true, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, context: { classPropertyName: "context", publicName: "context", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, language: { classPropertyName: "language", publicName: "language", isSignal: true, isRequired: false, transformFunction: null }, beforeLabel: { classPropertyName: "beforeLabel", publicName: "beforeLabel", isSignal: true, isRequired: false, transformFunction: null }, afterLabel: { classPropertyName: "afterLabel", publicName: "afterLabel", isSignal: true, isRequired: false, transformFunction: null }, copyable: { classPropertyName: "copyable", publicName: "copyable", isSignal: true, isRequired: false, transformFunction: null }, copyAfterLabel: { classPropertyName: "copyAfterLabel", publicName: "copyAfterLabel", isSignal: true, isRequired: false, transformFunction: null }, unchangedLabel: { classPropertyName: "unchangedLabel", publicName: "unchangedLabel", isSignal: true, isRequired: false, transformFunction: null }, maxHeight: { classPropertyName: "maxHeight", publicName: "maxHeight", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "strct-diff" }, ngImport: i0, template: `
|
|
14011
|
+
<div class="strct-diff__head">
|
|
14012
|
+
<span class="strct-diff__title">{{ title() }}</span>
|
|
14013
|
+
@if (language()) {
|
|
14014
|
+
<span class="strct-diff__lang">{{ language() }}</span>
|
|
14015
|
+
}
|
|
14016
|
+
<span class="strct-diff__stat strct-diff__stat--add">+{{ addCount() }}</span>
|
|
14017
|
+
<span class="strct-diff__stat strct-diff__stat--del">−{{ delCount() }}</span>
|
|
14018
|
+
<span class="strct-diff__grow"></span>
|
|
14019
|
+
@if (copyable()) {
|
|
14020
|
+
<strct-copy [text]="after()" [copyLabel]="copyAfterLabel()" />
|
|
14021
|
+
}
|
|
14022
|
+
</div>
|
|
14023
|
+
@if (mode() === 'split') {
|
|
14024
|
+
<div class="strct-diff__cols" aria-hidden="true">
|
|
14025
|
+
<span>{{ beforeLabel() }}</span>
|
|
14026
|
+
<span>{{ afterLabel() }}</span>
|
|
14027
|
+
</div>
|
|
14028
|
+
}
|
|
14029
|
+
<div
|
|
14030
|
+
class="strct-diff__scroll"
|
|
14031
|
+
tabindex="0"
|
|
14032
|
+
role="region"
|
|
14033
|
+
[attr.aria-label]="title()"
|
|
14034
|
+
[style.max-height.px]="maxHeight()"
|
|
14035
|
+
>
|
|
14036
|
+
@if (mode() === 'unified') {
|
|
14037
|
+
<table class="strct-diff__table">
|
|
14038
|
+
<tbody>
|
|
14039
|
+
@for (row of unifiedRows(); track $index) {
|
|
14040
|
+
@if (row.kind === 'skip') {
|
|
14041
|
+
<tr class="strct-diff__skip-row">
|
|
14042
|
+
<td colspan="4">
|
|
14043
|
+
<button type="button" class="strct-diff__skip" (click)="expand(row.startIndex)">
|
|
14044
|
+
· {{ row.count }} {{ unchangedLabel() }} ·
|
|
14045
|
+
</button>
|
|
14046
|
+
</td>
|
|
14047
|
+
</tr>
|
|
14048
|
+
} @else {
|
|
14049
|
+
<tr class="strct-diff__row strct-diff__row--{{ row.type }}">
|
|
14050
|
+
<td class="strct-diff__no">{{ row.oldNo ?? '' }}</td>
|
|
14051
|
+
<td class="strct-diff__no">{{ row.newNo ?? '' }}</td>
|
|
14052
|
+
<td class="strct-diff__sign">{{ sign(row.type) }}</td>
|
|
14053
|
+
<td class="strct-diff__text" [textContent]="row.text"></td>
|
|
14054
|
+
</tr>
|
|
14055
|
+
}
|
|
14056
|
+
}
|
|
14057
|
+
</tbody>
|
|
14058
|
+
</table>
|
|
14059
|
+
} @else {
|
|
14060
|
+
<table class="strct-diff__table strct-diff__table--split">
|
|
14061
|
+
<tbody>
|
|
14062
|
+
@for (row of splitRows(); track $index) {
|
|
14063
|
+
@if (row.kind === 'skip') {
|
|
14064
|
+
<tr class="strct-diff__skip-row">
|
|
14065
|
+
<td colspan="6">
|
|
14066
|
+
<button type="button" class="strct-diff__skip" (click)="expand(row.startIndex)">
|
|
14067
|
+
· {{ row.count }} {{ unchangedLabel() }} ·
|
|
14068
|
+
</button>
|
|
14069
|
+
</td>
|
|
14070
|
+
</tr>
|
|
14071
|
+
} @else {
|
|
14072
|
+
<tr>
|
|
14073
|
+
<td class="strct-diff__no strct-diff__cell--{{ row.left.type }}">
|
|
14074
|
+
{{ row.left.no ?? '' }}
|
|
14075
|
+
</td>
|
|
14076
|
+
<td class="strct-diff__sign strct-diff__cell--{{ row.left.type }}">
|
|
14077
|
+
{{ sign(row.left.type) }}
|
|
14078
|
+
</td>
|
|
14079
|
+
<td
|
|
14080
|
+
class="strct-diff__text strct-diff__cell--{{ row.left.type }}"
|
|
14081
|
+
[textContent]="row.left.text"
|
|
14082
|
+
></td>
|
|
14083
|
+
<td class="strct-diff__no strct-diff__cell--{{ row.right.type }}">
|
|
14084
|
+
{{ row.right.no ?? '' }}
|
|
14085
|
+
</td>
|
|
14086
|
+
<td class="strct-diff__sign strct-diff__cell--{{ row.right.type }}">
|
|
14087
|
+
{{ sign(row.right.type) }}
|
|
14088
|
+
</td>
|
|
14089
|
+
<td
|
|
14090
|
+
class="strct-diff__text strct-diff__cell--{{ row.right.type }}"
|
|
14091
|
+
[textContent]="row.right.text"
|
|
14092
|
+
></td>
|
|
14093
|
+
</tr>
|
|
14094
|
+
}
|
|
14095
|
+
}
|
|
14096
|
+
</tbody>
|
|
14097
|
+
</table>
|
|
14098
|
+
}
|
|
14099
|
+
</div>
|
|
14100
|
+
`, isInline: true, styles: [".strct-diff{display:block;border:1px solid var(--b2);border-radius:9px;background:var(--bg-2);overflow:hidden}.strct-diff__head{display:flex;align-items:center;gap:8px;padding:6px 10px;border-bottom:1px solid var(--b1)}.strct-diff__title{font-size:12.5px;font-weight:600;color:var(--t1)}.strct-diff__lang{font-family:var(--mono);font-size:10px;letter-spacing:.6px;text-transform:uppercase;color:var(--t3);border:1px solid var(--b2);border-radius:4px;padding:2px 6px}.strct-diff__stat{font-family:var(--mono);font-size:11px;font-weight:600;font-variant-numeric:tabular-nums}.strct-diff__stat--add{color:var(--success)}.strct-diff__stat--del{color:var(--critical)}.strct-diff__grow{flex:1}.strct-diff__cols{display:grid;grid-template-columns:1fr 1fr;padding:4px 10px;border-bottom:1px solid var(--b1);font-size:11px;font-weight:600;color:var(--t3)}.strct-diff__scroll{overflow:auto}.strct-diff__scroll:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-diff__table{width:100%;border-collapse:collapse;font-family:var(--mono);font-size:12px;line-height:1.55}.strct-diff__no{width:1%;min-width:3.5ch;padding:0 6px;text-align:end;color:var(--t4);-webkit-user-select:none;user-select:none;font-variant-numeric:tabular-nums;vertical-align:top}.strct-diff__sign{width:1%;padding:0 2px;text-align:center;-webkit-user-select:none;user-select:none;vertical-align:top}.strct-diff__text{padding:0 10px 0 4px;white-space:pre-wrap;overflow-wrap:anywhere;color:var(--t1)}.strct-diff__row--add,.strct-diff__cell--add{background:var(--success-bg)}.strct-diff__row--add .strct-diff__sign,.strct-diff__cell--add.strct-diff__sign{color:var(--success);font-weight:700}.strct-diff__row--del,.strct-diff__cell--del{background:var(--critical-bg)}.strct-diff__row--del .strct-diff__sign,.strct-diff__cell--del.strct-diff__sign{color:var(--critical);font-weight:700}.strct-diff__table--split .strct-diff__text{width:50%}.strct-diff__skip-row td{padding:0}.strct-diff__skip{display:block;width:100%;padding:3px 10px;border:0;border-block:1px dashed var(--b2);background:var(--bg-3);color:var(--t3);font-family:var(--mono);font-size:11px;cursor:pointer}.strct-diff__skip:hover{color:var(--t1)}.strct-diff__skip:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}\n"], dependencies: [{ kind: "component", type: StrctCopy, selector: "strct-copy", inputs: ["text", "label", "ariaLabel", "copyLabel", "copiedLabel"], outputs: ["copied"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
14101
|
+
}
|
|
14102
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctDiff, decorators: [{
|
|
14103
|
+
type: Component,
|
|
14104
|
+
args: [{ selector: 'strct-diff', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [StrctCopy], template: `
|
|
14105
|
+
<div class="strct-diff__head">
|
|
14106
|
+
<span class="strct-diff__title">{{ title() }}</span>
|
|
14107
|
+
@if (language()) {
|
|
14108
|
+
<span class="strct-diff__lang">{{ language() }}</span>
|
|
14109
|
+
}
|
|
14110
|
+
<span class="strct-diff__stat strct-diff__stat--add">+{{ addCount() }}</span>
|
|
14111
|
+
<span class="strct-diff__stat strct-diff__stat--del">−{{ delCount() }}</span>
|
|
14112
|
+
<span class="strct-diff__grow"></span>
|
|
14113
|
+
@if (copyable()) {
|
|
14114
|
+
<strct-copy [text]="after()" [copyLabel]="copyAfterLabel()" />
|
|
14115
|
+
}
|
|
14116
|
+
</div>
|
|
14117
|
+
@if (mode() === 'split') {
|
|
14118
|
+
<div class="strct-diff__cols" aria-hidden="true">
|
|
14119
|
+
<span>{{ beforeLabel() }}</span>
|
|
14120
|
+
<span>{{ afterLabel() }}</span>
|
|
14121
|
+
</div>
|
|
14122
|
+
}
|
|
14123
|
+
<div
|
|
14124
|
+
class="strct-diff__scroll"
|
|
14125
|
+
tabindex="0"
|
|
14126
|
+
role="region"
|
|
14127
|
+
[attr.aria-label]="title()"
|
|
14128
|
+
[style.max-height.px]="maxHeight()"
|
|
14129
|
+
>
|
|
14130
|
+
@if (mode() === 'unified') {
|
|
14131
|
+
<table class="strct-diff__table">
|
|
14132
|
+
<tbody>
|
|
14133
|
+
@for (row of unifiedRows(); track $index) {
|
|
14134
|
+
@if (row.kind === 'skip') {
|
|
14135
|
+
<tr class="strct-diff__skip-row">
|
|
14136
|
+
<td colspan="4">
|
|
14137
|
+
<button type="button" class="strct-diff__skip" (click)="expand(row.startIndex)">
|
|
14138
|
+
· {{ row.count }} {{ unchangedLabel() }} ·
|
|
14139
|
+
</button>
|
|
14140
|
+
</td>
|
|
14141
|
+
</tr>
|
|
14142
|
+
} @else {
|
|
14143
|
+
<tr class="strct-diff__row strct-diff__row--{{ row.type }}">
|
|
14144
|
+
<td class="strct-diff__no">{{ row.oldNo ?? '' }}</td>
|
|
14145
|
+
<td class="strct-diff__no">{{ row.newNo ?? '' }}</td>
|
|
14146
|
+
<td class="strct-diff__sign">{{ sign(row.type) }}</td>
|
|
14147
|
+
<td class="strct-diff__text" [textContent]="row.text"></td>
|
|
14148
|
+
</tr>
|
|
14149
|
+
}
|
|
14150
|
+
}
|
|
14151
|
+
</tbody>
|
|
14152
|
+
</table>
|
|
14153
|
+
} @else {
|
|
14154
|
+
<table class="strct-diff__table strct-diff__table--split">
|
|
14155
|
+
<tbody>
|
|
14156
|
+
@for (row of splitRows(); track $index) {
|
|
14157
|
+
@if (row.kind === 'skip') {
|
|
14158
|
+
<tr class="strct-diff__skip-row">
|
|
14159
|
+
<td colspan="6">
|
|
14160
|
+
<button type="button" class="strct-diff__skip" (click)="expand(row.startIndex)">
|
|
14161
|
+
· {{ row.count }} {{ unchangedLabel() }} ·
|
|
14162
|
+
</button>
|
|
14163
|
+
</td>
|
|
14164
|
+
</tr>
|
|
14165
|
+
} @else {
|
|
14166
|
+
<tr>
|
|
14167
|
+
<td class="strct-diff__no strct-diff__cell--{{ row.left.type }}">
|
|
14168
|
+
{{ row.left.no ?? '' }}
|
|
14169
|
+
</td>
|
|
14170
|
+
<td class="strct-diff__sign strct-diff__cell--{{ row.left.type }}">
|
|
14171
|
+
{{ sign(row.left.type) }}
|
|
14172
|
+
</td>
|
|
14173
|
+
<td
|
|
14174
|
+
class="strct-diff__text strct-diff__cell--{{ row.left.type }}"
|
|
14175
|
+
[textContent]="row.left.text"
|
|
14176
|
+
></td>
|
|
14177
|
+
<td class="strct-diff__no strct-diff__cell--{{ row.right.type }}">
|
|
14178
|
+
{{ row.right.no ?? '' }}
|
|
14179
|
+
</td>
|
|
14180
|
+
<td class="strct-diff__sign strct-diff__cell--{{ row.right.type }}">
|
|
14181
|
+
{{ sign(row.right.type) }}
|
|
14182
|
+
</td>
|
|
14183
|
+
<td
|
|
14184
|
+
class="strct-diff__text strct-diff__cell--{{ row.right.type }}"
|
|
14185
|
+
[textContent]="row.right.text"
|
|
14186
|
+
></td>
|
|
14187
|
+
</tr>
|
|
14188
|
+
}
|
|
14189
|
+
}
|
|
14190
|
+
</tbody>
|
|
14191
|
+
</table>
|
|
14192
|
+
}
|
|
14193
|
+
</div>
|
|
14194
|
+
`, host: { class: 'strct-diff' }, styles: [".strct-diff{display:block;border:1px solid var(--b2);border-radius:9px;background:var(--bg-2);overflow:hidden}.strct-diff__head{display:flex;align-items:center;gap:8px;padding:6px 10px;border-bottom:1px solid var(--b1)}.strct-diff__title{font-size:12.5px;font-weight:600;color:var(--t1)}.strct-diff__lang{font-family:var(--mono);font-size:10px;letter-spacing:.6px;text-transform:uppercase;color:var(--t3);border:1px solid var(--b2);border-radius:4px;padding:2px 6px}.strct-diff__stat{font-family:var(--mono);font-size:11px;font-weight:600;font-variant-numeric:tabular-nums}.strct-diff__stat--add{color:var(--success)}.strct-diff__stat--del{color:var(--critical)}.strct-diff__grow{flex:1}.strct-diff__cols{display:grid;grid-template-columns:1fr 1fr;padding:4px 10px;border-bottom:1px solid var(--b1);font-size:11px;font-weight:600;color:var(--t3)}.strct-diff__scroll{overflow:auto}.strct-diff__scroll:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-diff__table{width:100%;border-collapse:collapse;font-family:var(--mono);font-size:12px;line-height:1.55}.strct-diff__no{width:1%;min-width:3.5ch;padding:0 6px;text-align:end;color:var(--t4);-webkit-user-select:none;user-select:none;font-variant-numeric:tabular-nums;vertical-align:top}.strct-diff__sign{width:1%;padding:0 2px;text-align:center;-webkit-user-select:none;user-select:none;vertical-align:top}.strct-diff__text{padding:0 10px 0 4px;white-space:pre-wrap;overflow-wrap:anywhere;color:var(--t1)}.strct-diff__row--add,.strct-diff__cell--add{background:var(--success-bg)}.strct-diff__row--add .strct-diff__sign,.strct-diff__cell--add.strct-diff__sign{color:var(--success);font-weight:700}.strct-diff__row--del,.strct-diff__cell--del{background:var(--critical-bg)}.strct-diff__row--del .strct-diff__sign,.strct-diff__cell--del.strct-diff__sign{color:var(--critical);font-weight:700}.strct-diff__table--split .strct-diff__text{width:50%}.strct-diff__skip-row td{padding:0}.strct-diff__skip{display:block;width:100%;padding:3px 10px;border:0;border-block:1px dashed var(--b2);background:var(--bg-3);color:var(--t3);font-family:var(--mono);font-size:11px;cursor:pointer}.strct-diff__skip:hover{color:var(--t1)}.strct-diff__skip:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}\n"] }]
|
|
14195
|
+
}], propDecorators: { before: [{ type: i0.Input, args: [{ isSignal: true, alias: "before", required: true }] }], after: [{ type: i0.Input, args: [{ isSignal: true, alias: "after", required: true }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], context: [{ type: i0.Input, args: [{ isSignal: true, alias: "context", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], language: [{ type: i0.Input, args: [{ isSignal: true, alias: "language", required: false }] }], beforeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "beforeLabel", required: false }] }], afterLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "afterLabel", required: false }] }], copyable: [{ type: i0.Input, args: [{ isSignal: true, alias: "copyable", required: false }] }], copyAfterLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "copyAfterLabel", required: false }] }], unchangedLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "unchangedLabel", required: false }] }], maxHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxHeight", required: false }] }] } });
|
|
14196
|
+
function startOfRun(visible, i) {
|
|
14197
|
+
let s = i;
|
|
14198
|
+
while (s > 0 && visible[s - 1] === visible[i])
|
|
14199
|
+
s--;
|
|
14200
|
+
return s;
|
|
14201
|
+
}
|
|
14202
|
+
|
|
14203
|
+
/**
|
|
14204
|
+
* Two resizable panes with a draggable gutter — master/detail layouts where
|
|
14205
|
+
* the split is the user's to own:
|
|
14206
|
+
*
|
|
14207
|
+
* <strct-splitter [(split)]="pct" [min]="20" [max]="80">
|
|
14208
|
+
* <div strctPaneStart>…list…</div>
|
|
14209
|
+
* <div strctPaneEnd>…detail…</div>
|
|
14210
|
+
* </strct-splitter>
|
|
14211
|
+
*
|
|
14212
|
+
* `split` is the start pane's share in percent (two-way, persistable).
|
|
14213
|
+
* The gutter is a keyboard separator: arrows nudge, Home/End jump to min/max.
|
|
14214
|
+
*/
|
|
14215
|
+
class StrctSplitter {
|
|
14216
|
+
host = inject(ElementRef);
|
|
14217
|
+
/** Start pane share in percent (two-way). */
|
|
14218
|
+
split = model(50, ...(ngDevMode ? [{ debugName: "split" }] : /* istanbul ignore next */ []));
|
|
14219
|
+
/** Clamp bounds for the split (percent). */
|
|
14220
|
+
min = input(15, ...(ngDevMode ? [{ debugName: "min" }] : /* istanbul ignore next */ []));
|
|
14221
|
+
max = input(85, ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
|
|
14222
|
+
/** Stack panes vertically (gutter drags up/down). */
|
|
14223
|
+
vertical = input(false, ...(ngDevMode ? [{ debugName: "vertical" }] : /* istanbul ignore next */ []));
|
|
14224
|
+
/** Accessible name of the separator (localizable). */
|
|
14225
|
+
gutterLabel = input('Resize panes', ...(ngDevMode ? [{ debugName: "gutterLabel" }] : /* istanbul ignore next */ []));
|
|
14226
|
+
/** Keyboard nudge step in percent. */
|
|
14227
|
+
step = input(3, ...(ngDevMode ? [{ debugName: "step" }] : /* istanbul ignore next */ []));
|
|
14228
|
+
clamped = computed(() => Math.min(this.max(), Math.max(this.min(), this.split())), ...(ngDevMode ? [{ debugName: "clamped" }] : /* istanbul ignore next */ []));
|
|
14229
|
+
dragging = false;
|
|
14230
|
+
moveHandler = (e) => this.onDragMove(e);
|
|
14231
|
+
upHandler = () => this.onDragEnd();
|
|
14232
|
+
onDragStart(event) {
|
|
14233
|
+
event.preventDefault();
|
|
14234
|
+
this.dragging = true;
|
|
14235
|
+
document.addEventListener('mousemove', this.moveHandler);
|
|
14236
|
+
document.addEventListener('mouseup', this.upHandler);
|
|
14237
|
+
}
|
|
14238
|
+
onDragMove(event) {
|
|
14239
|
+
const rect = this.host.nativeElement.getBoundingClientRect();
|
|
14240
|
+
const ratio = this.vertical()
|
|
14241
|
+
? (event.clientY - rect.top) / rect.height
|
|
14242
|
+
: (event.clientX - rect.left) / rect.width;
|
|
14243
|
+
this.split.set(Math.min(this.max(), Math.max(this.min(), Math.round(ratio * 100))));
|
|
14244
|
+
}
|
|
14245
|
+
onDragEnd() {
|
|
14246
|
+
this.dragging = false;
|
|
14247
|
+
document.removeEventListener('mousemove', this.moveHandler);
|
|
14248
|
+
document.removeEventListener('mouseup', this.upHandler);
|
|
14249
|
+
}
|
|
14250
|
+
onKeydown(event) {
|
|
14251
|
+
const dec = this.vertical() ? 'ArrowUp' : 'ArrowLeft';
|
|
14252
|
+
const inc = this.vertical() ? 'ArrowDown' : 'ArrowRight';
|
|
14253
|
+
let next = null;
|
|
14254
|
+
if (event.key === dec)
|
|
14255
|
+
next = this.clamped() - this.step();
|
|
14256
|
+
else if (event.key === inc)
|
|
14257
|
+
next = this.clamped() + this.step();
|
|
14258
|
+
else if (event.key === 'Home')
|
|
14259
|
+
next = this.min();
|
|
14260
|
+
else if (event.key === 'End')
|
|
14261
|
+
next = this.max();
|
|
14262
|
+
if (next == null)
|
|
14263
|
+
return;
|
|
14264
|
+
event.preventDefault();
|
|
14265
|
+
this.split.set(Math.min(this.max(), Math.max(this.min(), next)));
|
|
14266
|
+
}
|
|
14267
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctSplitter, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
14268
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.16", type: StrctSplitter, isStandalone: true, selector: "strct-splitter", inputs: { split: { classPropertyName: "split", publicName: "split", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, vertical: { classPropertyName: "vertical", publicName: "vertical", isSignal: true, isRequired: false, transformFunction: null }, gutterLabel: { classPropertyName: "gutterLabel", publicName: "gutterLabel", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { split: "splitChange" }, host: { properties: { "class.strct-split--vertical": "vertical()", "class.strct-split--dragging": "dragging" }, classAttribute: "strct-split" }, ngImport: i0, template: `
|
|
14269
|
+
<div class="strct-split__pane" [style.flex-basis.%]="clamped()">
|
|
14270
|
+
<ng-content select="[strctPaneStart]" />
|
|
14271
|
+
</div>
|
|
14272
|
+
<div
|
|
14273
|
+
class="strct-split__gutter"
|
|
14274
|
+
role="separator"
|
|
14275
|
+
tabindex="0"
|
|
14276
|
+
[attr.aria-label]="gutterLabel()"
|
|
14277
|
+
[attr.aria-orientation]="vertical() ? 'horizontal' : 'vertical'"
|
|
14278
|
+
[attr.aria-valuenow]="clamped()"
|
|
14279
|
+
[attr.aria-valuemin]="min()"
|
|
14280
|
+
[attr.aria-valuemax]="max()"
|
|
14281
|
+
(mousedown)="onDragStart($event)"
|
|
14282
|
+
(keydown)="onKeydown($event)"
|
|
14283
|
+
>
|
|
14284
|
+
<span class="strct-split__grip" aria-hidden="true"></span>
|
|
14285
|
+
</div>
|
|
14286
|
+
<div class="strct-split__pane strct-split__pane--end">
|
|
14287
|
+
<ng-content select="[strctPaneEnd]" />
|
|
14288
|
+
</div>
|
|
14289
|
+
`, isInline: true, styles: [".strct-split{display:flex;width:100%;min-height:0}.strct-split--vertical{flex-direction:column}.strct-split__pane{flex-grow:0;flex-shrink:0;overflow:auto;min-width:0;min-height:0}.strct-split__pane--end{flex:1 1 0}.strct-split__gutter{flex:0 0 auto;display:flex;align-items:center;justify-content:center;width:7px;cursor:col-resize;background:transparent;touch-action:none}.strct-split--vertical>.strct-split__gutter{width:auto;height:7px;cursor:row-resize}.strct-split__gutter:hover,.strct-split--dragging>.strct-split__gutter{background:var(--acc18)}.strct-split__gutter:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-split__grip{width:1.5px;height:26px;border-radius:1px;background:var(--b2)}.strct-split--vertical .strct-split__grip{width:26px;height:1.5px}.strct-split--dragging{-webkit-user-select:none;user-select:none}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
14290
|
+
}
|
|
14291
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctSplitter, decorators: [{
|
|
14292
|
+
type: Component,
|
|
14293
|
+
args: [{ selector: 'strct-splitter', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: `
|
|
14294
|
+
<div class="strct-split__pane" [style.flex-basis.%]="clamped()">
|
|
14295
|
+
<ng-content select="[strctPaneStart]" />
|
|
14296
|
+
</div>
|
|
14297
|
+
<div
|
|
14298
|
+
class="strct-split__gutter"
|
|
14299
|
+
role="separator"
|
|
14300
|
+
tabindex="0"
|
|
14301
|
+
[attr.aria-label]="gutterLabel()"
|
|
14302
|
+
[attr.aria-orientation]="vertical() ? 'horizontal' : 'vertical'"
|
|
14303
|
+
[attr.aria-valuenow]="clamped()"
|
|
14304
|
+
[attr.aria-valuemin]="min()"
|
|
14305
|
+
[attr.aria-valuemax]="max()"
|
|
14306
|
+
(mousedown)="onDragStart($event)"
|
|
14307
|
+
(keydown)="onKeydown($event)"
|
|
14308
|
+
>
|
|
14309
|
+
<span class="strct-split__grip" aria-hidden="true"></span>
|
|
14310
|
+
</div>
|
|
14311
|
+
<div class="strct-split__pane strct-split__pane--end">
|
|
14312
|
+
<ng-content select="[strctPaneEnd]" />
|
|
14313
|
+
</div>
|
|
14314
|
+
`, host: {
|
|
14315
|
+
class: 'strct-split',
|
|
14316
|
+
'[class.strct-split--vertical]': 'vertical()',
|
|
14317
|
+
'[class.strct-split--dragging]': 'dragging',
|
|
14318
|
+
}, styles: [".strct-split{display:flex;width:100%;min-height:0}.strct-split--vertical{flex-direction:column}.strct-split__pane{flex-grow:0;flex-shrink:0;overflow:auto;min-width:0;min-height:0}.strct-split__pane--end{flex:1 1 0}.strct-split__gutter{flex:0 0 auto;display:flex;align-items:center;justify-content:center;width:7px;cursor:col-resize;background:transparent;touch-action:none}.strct-split--vertical>.strct-split__gutter{width:auto;height:7px;cursor:row-resize}.strct-split__gutter:hover,.strct-split--dragging>.strct-split__gutter{background:var(--acc18)}.strct-split__gutter:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-split__grip{width:1.5px;height:26px;border-radius:1px;background:var(--b2)}.strct-split--vertical .strct-split__grip{width:26px;height:1.5px}.strct-split--dragging{-webkit-user-select:none;user-select:none}\n"] }]
|
|
14319
|
+
}], propDecorators: { split: [{ type: i0.Input, args: [{ isSignal: true, alias: "split", required: false }] }, { type: i0.Output, args: ["splitChange"] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], vertical: [{ type: i0.Input, args: [{ isSignal: true, alias: "vertical", required: false }] }], gutterLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "gutterLabel", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }] } });
|
|
14320
|
+
|
|
14321
|
+
/**
|
|
14322
|
+
* Dual-list transfer (picklist) — "assign hosts to the cluster": all items on
|
|
14323
|
+
* the left, the chosen set on the right, checkbox multi-select and move
|
|
14324
|
+
* buttons in between, with a searchbox per side.
|
|
14325
|
+
*
|
|
14326
|
+
* <strct-transfer [items]="hosts" [(assigned)]="clusterHosts" />
|
|
14327
|
+
*
|
|
14328
|
+
* `assigned` is the two-way id set (string[]); `items` stays the single
|
|
14329
|
+
* source-of-truth list.
|
|
14330
|
+
*/
|
|
14331
|
+
class StrctTransfer {
|
|
14332
|
+
/** The full item universe. */
|
|
14333
|
+
items = input.required(...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
|
|
14334
|
+
/** Assigned item ids (two-way). */
|
|
14335
|
+
assigned = model([], ...(ngDevMode ? [{ debugName: "assigned" }] : /* istanbul ignore next */ []));
|
|
14336
|
+
/** Localizable strings. */
|
|
14337
|
+
sourceLabel = input('Available', ...(ngDevMode ? [{ debugName: "sourceLabel" }] : /* istanbul ignore next */ []));
|
|
14338
|
+
targetLabel = input('Assigned', ...(ngDevMode ? [{ debugName: "targetLabel" }] : /* istanbul ignore next */ []));
|
|
14339
|
+
searchPlaceholder = input('Search', ...(ngDevMode ? [{ debugName: "searchPlaceholder" }] : /* istanbul ignore next */ []));
|
|
14340
|
+
emptyLabel = input('No items', ...(ngDevMode ? [{ debugName: "emptyLabel" }] : /* istanbul ignore next */ []));
|
|
14341
|
+
assignLabel = input('Assign selected', ...(ngDevMode ? [{ debugName: "assignLabel" }] : /* istanbul ignore next */ []));
|
|
14342
|
+
unassignLabel = input('Unassign selected', ...(ngDevMode ? [{ debugName: "unassignLabel" }] : /* istanbul ignore next */ []));
|
|
14343
|
+
/** A move happened (either direction) with the moved ids. */
|
|
14344
|
+
moved = output();
|
|
14345
|
+
sourceQuery = signal('', ...(ngDevMode ? [{ debugName: "sourceQuery" }] : /* istanbul ignore next */ []));
|
|
14346
|
+
targetQuery = signal('', ...(ngDevMode ? [{ debugName: "targetQuery" }] : /* istanbul ignore next */ []));
|
|
14347
|
+
sourceChecked = signal(new Set(), ...(ngDevMode ? [{ debugName: "sourceChecked" }] : /* istanbul ignore next */ []));
|
|
14348
|
+
targetChecked = signal(new Set(), ...(ngDevMode ? [{ debugName: "targetChecked" }] : /* istanbul ignore next */ []));
|
|
14349
|
+
assignedSet = computed(() => new Set(this.assigned()), ...(ngDevMode ? [{ debugName: "assignedSet" }] : /* istanbul ignore next */ []));
|
|
14350
|
+
sourceItems = computed(() => this.items().filter((i) => !this.assignedSet().has(i.id) && this.matches(i, this.sourceQuery())), ...(ngDevMode ? [{ debugName: "sourceItems" }] : /* istanbul ignore next */ []));
|
|
14351
|
+
targetItems = computed(() => this.items().filter((i) => this.assignedSet().has(i.id) && this.matches(i, this.targetQuery())), ...(ngDevMode ? [{ debugName: "targetItems" }] : /* istanbul ignore next */ []));
|
|
14352
|
+
matches(item, q) {
|
|
14353
|
+
return !q.trim() || item.label.toLowerCase().includes(q.trim().toLowerCase());
|
|
14354
|
+
}
|
|
14355
|
+
toggle(which, id) {
|
|
14356
|
+
const next = new Set(which());
|
|
14357
|
+
if (next.has(id))
|
|
14358
|
+
next.delete(id);
|
|
14359
|
+
else
|
|
14360
|
+
next.add(id);
|
|
14361
|
+
which.set(next);
|
|
14362
|
+
}
|
|
14363
|
+
assign() {
|
|
14364
|
+
const ids = [...this.sourceChecked()];
|
|
14365
|
+
if (!ids.length)
|
|
14366
|
+
return;
|
|
14367
|
+
this.assigned.set([...this.assigned(), ...ids]);
|
|
14368
|
+
this.sourceChecked.set(new Set());
|
|
14369
|
+
this.moved.emit({ direction: 'assign', ids });
|
|
14370
|
+
}
|
|
14371
|
+
unassign() {
|
|
14372
|
+
const ids = new Set(this.targetChecked());
|
|
14373
|
+
if (!ids.size)
|
|
14374
|
+
return;
|
|
14375
|
+
this.assigned.set(this.assigned().filter((id) => !ids.has(id)));
|
|
14376
|
+
this.targetChecked.set(new Set());
|
|
14377
|
+
this.moved.emit({ direction: 'unassign', ids: [...ids] });
|
|
14378
|
+
}
|
|
14379
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctTransfer, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
14380
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: StrctTransfer, isStandalone: true, selector: "strct-transfer", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, assigned: { classPropertyName: "assigned", publicName: "assigned", isSignal: true, isRequired: false, transformFunction: null }, sourceLabel: { classPropertyName: "sourceLabel", publicName: "sourceLabel", isSignal: true, isRequired: false, transformFunction: null }, targetLabel: { classPropertyName: "targetLabel", publicName: "targetLabel", isSignal: true, isRequired: false, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, emptyLabel: { classPropertyName: "emptyLabel", publicName: "emptyLabel", isSignal: true, isRequired: false, transformFunction: null }, assignLabel: { classPropertyName: "assignLabel", publicName: "assignLabel", isSignal: true, isRequired: false, transformFunction: null }, unassignLabel: { classPropertyName: "unassignLabel", publicName: "unassignLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { assigned: "assignedChange", moved: "moved" }, host: { classAttribute: "strct-tf" }, ngImport: i0, template: `
|
|
14381
|
+
<div class="strct-tf__col">
|
|
14382
|
+
<div class="strct-tf__head">
|
|
14383
|
+
<span class="strct-tf__title">{{ sourceLabel() }}</span>
|
|
14384
|
+
<span class="strct-tf__count">{{ sourceItems().length }}</span>
|
|
14385
|
+
</div>
|
|
14386
|
+
<strct-searchbox
|
|
14387
|
+
class="strct-tf__search"
|
|
14388
|
+
[placeholder]="searchPlaceholder()"
|
|
14389
|
+
[value]="sourceQuery()"
|
|
14390
|
+
(valueChange)="sourceQuery.set($event)"
|
|
14391
|
+
/>
|
|
14392
|
+
<ul class="strct-tf__list" role="listbox" [attr.aria-label]="sourceLabel()">
|
|
14393
|
+
@for (item of sourceItems(); track item.id) {
|
|
14394
|
+
<li class="strct-tf__item" [class.strct-tf__item--disabled]="item.disabled">
|
|
14395
|
+
<strct-checkbox
|
|
14396
|
+
[checked]="sourceChecked().has(item.id)"
|
|
14397
|
+
[disabled]="item.disabled ?? false"
|
|
14398
|
+
(checkedChange)="toggle(sourceChecked, item.id)"
|
|
14399
|
+
>
|
|
14400
|
+
@if (item.icon) {
|
|
14401
|
+
<strct-icon [name]="item.icon" [size]="14" />
|
|
14402
|
+
}
|
|
14403
|
+
<span class="strct-tf__label">{{ item.label }}</span>
|
|
14404
|
+
</strct-checkbox>
|
|
14405
|
+
</li>
|
|
14406
|
+
} @empty {
|
|
14407
|
+
<li class="strct-tf__empty">{{ emptyLabel() }}</li>
|
|
14408
|
+
}
|
|
14409
|
+
</ul>
|
|
14410
|
+
</div>
|
|
14411
|
+
|
|
14412
|
+
<div class="strct-tf__mid">
|
|
14413
|
+
<button
|
|
14414
|
+
strct-button
|
|
14415
|
+
size="sm"
|
|
14416
|
+
variant="neutral"
|
|
14417
|
+
[disabled]="!sourceChecked().size"
|
|
14418
|
+
[attr.aria-label]="assignLabel()"
|
|
14419
|
+
(click)="assign()"
|
|
14420
|
+
>
|
|
14421
|
+
<strct-icon name="chevronRight" [size]="14" />
|
|
14422
|
+
</button>
|
|
14423
|
+
<button
|
|
14424
|
+
strct-button
|
|
14425
|
+
size="sm"
|
|
14426
|
+
variant="neutral"
|
|
14427
|
+
[disabled]="!targetChecked().size"
|
|
14428
|
+
[attr.aria-label]="unassignLabel()"
|
|
14429
|
+
(click)="unassign()"
|
|
14430
|
+
>
|
|
14431
|
+
<strct-icon name="chevronLeft" [size]="14" />
|
|
14432
|
+
</button>
|
|
14433
|
+
</div>
|
|
14434
|
+
|
|
14435
|
+
<div class="strct-tf__col">
|
|
14436
|
+
<div class="strct-tf__head">
|
|
14437
|
+
<span class="strct-tf__title">{{ targetLabel() }}</span>
|
|
14438
|
+
<span class="strct-tf__count">{{ targetItems().length }}</span>
|
|
14439
|
+
</div>
|
|
14440
|
+
<strct-searchbox
|
|
14441
|
+
class="strct-tf__search"
|
|
14442
|
+
[placeholder]="searchPlaceholder()"
|
|
14443
|
+
[value]="targetQuery()"
|
|
14444
|
+
(valueChange)="targetQuery.set($event)"
|
|
14445
|
+
/>
|
|
14446
|
+
<ul class="strct-tf__list" role="listbox" [attr.aria-label]="targetLabel()">
|
|
14447
|
+
@for (item of targetItems(); track item.id) {
|
|
14448
|
+
<li class="strct-tf__item" [class.strct-tf__item--disabled]="item.disabled">
|
|
14449
|
+
<strct-checkbox
|
|
14450
|
+
[checked]="targetChecked().has(item.id)"
|
|
14451
|
+
[disabled]="item.disabled ?? false"
|
|
14452
|
+
(checkedChange)="toggle(targetChecked, item.id)"
|
|
14453
|
+
>
|
|
14454
|
+
@if (item.icon) {
|
|
14455
|
+
<strct-icon [name]="item.icon" [size]="14" />
|
|
14456
|
+
}
|
|
14457
|
+
<span class="strct-tf__label">{{ item.label }}</span>
|
|
14458
|
+
</strct-checkbox>
|
|
14459
|
+
</li>
|
|
14460
|
+
} @empty {
|
|
14461
|
+
<li class="strct-tf__empty">{{ emptyLabel() }}</li>
|
|
14462
|
+
}
|
|
14463
|
+
</ul>
|
|
14464
|
+
</div>
|
|
14465
|
+
`, isInline: true, styles: [".strct-tf{display:flex;align-items:stretch;gap:10px;width:100%}.strct-tf__col{flex:1 1 0;min-width:0;display:flex;flex-direction:column;gap:8px;padding:10px;background:var(--bg-2);border:1px solid var(--b2);border-radius:9px}.strct-tf__head{display:flex;align-items:baseline;gap:8px}.strct-tf__title{font-size:12.5px;font-weight:600;color:var(--t1)}.strct-tf__count{font-size:11px;color:var(--t3);font-variant-numeric:tabular-nums}.strct-tf__search{width:100%}.strct-tf__list{list-style:none;margin:0;padding:0;min-height:120px;max-height:240px;overflow:auto;display:flex;flex-direction:column;gap:1px}.strct-tf__item{display:flex;align-items:center;padding:2px 4px;border-radius:5px;color:var(--t1);font-size:12.5px}.strct-tf__item strct-icon{margin-inline-end:6px}.strct-tf__item:hover{background:var(--bg-3)}.strct-tf__item--disabled{color:var(--t4);cursor:default}.strct-tf__label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.strct-tf__empty{padding:12px 8px;font-size:12px;color:var(--t3)}.strct-tf__mid{display:flex;flex-direction:column;justify-content:center;gap:8px}\n"], dependencies: [{ kind: "component", type: StrctButton, selector: "button[strct-button], a[strct-button]", inputs: ["variant", "size", "solid", "block", "iconOnly"] }, { kind: "component", type: StrctCheckbox, selector: "strct-checkbox", inputs: ["checked", "isDisabled", "disabled", "indeterminate", "ariaLabel"], outputs: ["checkedChange", "isDisabledChange"] }, { kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "size", "strokeWidth", "badge", "ariaLabel"] }, { kind: "component", type: StrctSearchbox, selector: "strct-searchbox", inputs: ["placeholder", "hint", "trigger", "clearable", "ariaLabel", "clearLabel", "value"], outputs: ["valueChange", "search", "activated"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
14466
|
+
}
|
|
14467
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctTransfer, decorators: [{
|
|
14468
|
+
type: Component,
|
|
14469
|
+
args: [{ selector: 'strct-transfer', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [StrctButton, StrctCheckbox, StrctIcon, StrctSearchbox], template: `
|
|
14470
|
+
<div class="strct-tf__col">
|
|
14471
|
+
<div class="strct-tf__head">
|
|
14472
|
+
<span class="strct-tf__title">{{ sourceLabel() }}</span>
|
|
14473
|
+
<span class="strct-tf__count">{{ sourceItems().length }}</span>
|
|
14474
|
+
</div>
|
|
14475
|
+
<strct-searchbox
|
|
14476
|
+
class="strct-tf__search"
|
|
14477
|
+
[placeholder]="searchPlaceholder()"
|
|
14478
|
+
[value]="sourceQuery()"
|
|
14479
|
+
(valueChange)="sourceQuery.set($event)"
|
|
14480
|
+
/>
|
|
14481
|
+
<ul class="strct-tf__list" role="listbox" [attr.aria-label]="sourceLabel()">
|
|
14482
|
+
@for (item of sourceItems(); track item.id) {
|
|
14483
|
+
<li class="strct-tf__item" [class.strct-tf__item--disabled]="item.disabled">
|
|
14484
|
+
<strct-checkbox
|
|
14485
|
+
[checked]="sourceChecked().has(item.id)"
|
|
14486
|
+
[disabled]="item.disabled ?? false"
|
|
14487
|
+
(checkedChange)="toggle(sourceChecked, item.id)"
|
|
14488
|
+
>
|
|
14489
|
+
@if (item.icon) {
|
|
14490
|
+
<strct-icon [name]="item.icon" [size]="14" />
|
|
14491
|
+
}
|
|
14492
|
+
<span class="strct-tf__label">{{ item.label }}</span>
|
|
14493
|
+
</strct-checkbox>
|
|
14494
|
+
</li>
|
|
14495
|
+
} @empty {
|
|
14496
|
+
<li class="strct-tf__empty">{{ emptyLabel() }}</li>
|
|
14497
|
+
}
|
|
14498
|
+
</ul>
|
|
14499
|
+
</div>
|
|
14500
|
+
|
|
14501
|
+
<div class="strct-tf__mid">
|
|
14502
|
+
<button
|
|
14503
|
+
strct-button
|
|
14504
|
+
size="sm"
|
|
14505
|
+
variant="neutral"
|
|
14506
|
+
[disabled]="!sourceChecked().size"
|
|
14507
|
+
[attr.aria-label]="assignLabel()"
|
|
14508
|
+
(click)="assign()"
|
|
14509
|
+
>
|
|
14510
|
+
<strct-icon name="chevronRight" [size]="14" />
|
|
14511
|
+
</button>
|
|
14512
|
+
<button
|
|
14513
|
+
strct-button
|
|
14514
|
+
size="sm"
|
|
14515
|
+
variant="neutral"
|
|
14516
|
+
[disabled]="!targetChecked().size"
|
|
14517
|
+
[attr.aria-label]="unassignLabel()"
|
|
14518
|
+
(click)="unassign()"
|
|
14519
|
+
>
|
|
14520
|
+
<strct-icon name="chevronLeft" [size]="14" />
|
|
14521
|
+
</button>
|
|
14522
|
+
</div>
|
|
14523
|
+
|
|
14524
|
+
<div class="strct-tf__col">
|
|
14525
|
+
<div class="strct-tf__head">
|
|
14526
|
+
<span class="strct-tf__title">{{ targetLabel() }}</span>
|
|
14527
|
+
<span class="strct-tf__count">{{ targetItems().length }}</span>
|
|
14528
|
+
</div>
|
|
14529
|
+
<strct-searchbox
|
|
14530
|
+
class="strct-tf__search"
|
|
14531
|
+
[placeholder]="searchPlaceholder()"
|
|
14532
|
+
[value]="targetQuery()"
|
|
14533
|
+
(valueChange)="targetQuery.set($event)"
|
|
14534
|
+
/>
|
|
14535
|
+
<ul class="strct-tf__list" role="listbox" [attr.aria-label]="targetLabel()">
|
|
14536
|
+
@for (item of targetItems(); track item.id) {
|
|
14537
|
+
<li class="strct-tf__item" [class.strct-tf__item--disabled]="item.disabled">
|
|
14538
|
+
<strct-checkbox
|
|
14539
|
+
[checked]="targetChecked().has(item.id)"
|
|
14540
|
+
[disabled]="item.disabled ?? false"
|
|
14541
|
+
(checkedChange)="toggle(targetChecked, item.id)"
|
|
14542
|
+
>
|
|
14543
|
+
@if (item.icon) {
|
|
14544
|
+
<strct-icon [name]="item.icon" [size]="14" />
|
|
14545
|
+
}
|
|
14546
|
+
<span class="strct-tf__label">{{ item.label }}</span>
|
|
14547
|
+
</strct-checkbox>
|
|
14548
|
+
</li>
|
|
14549
|
+
} @empty {
|
|
14550
|
+
<li class="strct-tf__empty">{{ emptyLabel() }}</li>
|
|
14551
|
+
}
|
|
14552
|
+
</ul>
|
|
14553
|
+
</div>
|
|
14554
|
+
`, host: { class: 'strct-tf' }, styles: [".strct-tf{display:flex;align-items:stretch;gap:10px;width:100%}.strct-tf__col{flex:1 1 0;min-width:0;display:flex;flex-direction:column;gap:8px;padding:10px;background:var(--bg-2);border:1px solid var(--b2);border-radius:9px}.strct-tf__head{display:flex;align-items:baseline;gap:8px}.strct-tf__title{font-size:12.5px;font-weight:600;color:var(--t1)}.strct-tf__count{font-size:11px;color:var(--t3);font-variant-numeric:tabular-nums}.strct-tf__search{width:100%}.strct-tf__list{list-style:none;margin:0;padding:0;min-height:120px;max-height:240px;overflow:auto;display:flex;flex-direction:column;gap:1px}.strct-tf__item{display:flex;align-items:center;padding:2px 4px;border-radius:5px;color:var(--t1);font-size:12.5px}.strct-tf__item strct-icon{margin-inline-end:6px}.strct-tf__item:hover{background:var(--bg-3)}.strct-tf__item--disabled{color:var(--t4);cursor:default}.strct-tf__label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.strct-tf__empty{padding:12px 8px;font-size:12px;color:var(--t3)}.strct-tf__mid{display:flex;flex-direction:column;justify-content:center;gap:8px}\n"] }]
|
|
14555
|
+
}], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }], assigned: [{ type: i0.Input, args: [{ isSignal: true, alias: "assigned", required: false }] }, { type: i0.Output, args: ["assignedChange"] }], sourceLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "sourceLabel", required: false }] }], targetLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "targetLabel", required: false }] }], searchPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPlaceholder", required: false }] }], emptyLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyLabel", required: false }] }], assignLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "assignLabel", required: false }] }], unassignLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "unassignLabel", required: false }] }], moved: [{ type: i0.Output, args: ["moved"] }] } });
|
|
14556
|
+
|
|
14557
|
+
/**
|
|
14558
|
+
* Split button — a primary action plus a chevron opening its variants
|
|
14559
|
+
* (Fluent's pattern): "Deploy ▾ → Deploy with snapshot / Deploy paused".
|
|
14560
|
+
*
|
|
14561
|
+
* <strct-split-button label="Deploy" [items]="variants"
|
|
14562
|
+
* (action)="deploy()" (picked)="run($event)" />
|
|
14563
|
+
*
|
|
14564
|
+
* The main segment fires `(action)`; menu entries fire `(picked)`.
|
|
14565
|
+
*/
|
|
14566
|
+
class StrctSplitButton {
|
|
14567
|
+
/** Main action label. */
|
|
14568
|
+
label = input.required(...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
14569
|
+
/** Menu entries (StrctMenuItem: id, label, icon?, critical?, disabled?). */
|
|
14570
|
+
items = input([], ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
|
|
14571
|
+
/** Optional leading icon of the main segment. */
|
|
14572
|
+
icon = input('', ...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
|
|
14573
|
+
/** Filled (solid accent) look. */
|
|
14574
|
+
solid = input(false, { ...(ngDevMode ? { debugName: "solid" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
14575
|
+
/** Disable both segments. */
|
|
14576
|
+
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
14577
|
+
/** Accessible name of the chevron (localizable). */
|
|
14578
|
+
menuLabel = input('More actions', ...(ngDevMode ? [{ debugName: "menuLabel" }] : /* istanbul ignore next */ []));
|
|
14579
|
+
/** The main segment was clicked. */
|
|
14580
|
+
action = output();
|
|
14581
|
+
/** A menu entry was chosen. */
|
|
14582
|
+
picked = output();
|
|
14583
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctSplitButton, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
14584
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: StrctSplitButton, isStandalone: true, selector: "strct-split-button", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, solid: { classPropertyName: "solid", publicName: "solid", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, menuLabel: { classPropertyName: "menuLabel", publicName: "menuLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { action: "action", picked: "picked" }, ngImport: i0, template: `
|
|
14585
|
+
<div class="strct-sbt" [class.strct-sbt--solid]="solid()">
|
|
14586
|
+
<button
|
|
14587
|
+
type="button"
|
|
14588
|
+
class="strct-sbt__main"
|
|
14589
|
+
[disabled]="disabled() || null"
|
|
14590
|
+
(click)="action.emit()"
|
|
14591
|
+
>
|
|
14592
|
+
@if (icon()) {
|
|
14593
|
+
<strct-icon [name]="icon()" [size]="13" />
|
|
14594
|
+
}
|
|
14595
|
+
{{ label() }}
|
|
14596
|
+
</button>
|
|
14597
|
+
<strct-dropdown align="end">
|
|
14598
|
+
<button
|
|
14599
|
+
type="button"
|
|
14600
|
+
class="strct-sbt__chev"
|
|
14601
|
+
strctDropdownTrigger
|
|
14602
|
+
[disabled]="disabled() || null"
|
|
14603
|
+
[attr.aria-label]="menuLabel() + ': ' + label()"
|
|
14604
|
+
>
|
|
14605
|
+
<strct-icon name="chevronDown" [size]="12" />
|
|
14606
|
+
</button>
|
|
14607
|
+
@for (item of items(); track $index) {
|
|
14608
|
+
@if (item.divider) {
|
|
14609
|
+
<strct-dropdown-divider />
|
|
14610
|
+
} @else {
|
|
14611
|
+
<strct-dropdown-item
|
|
14612
|
+
[critical]="item.critical ?? false"
|
|
14613
|
+
[disabled]="item.disabled ?? false"
|
|
14614
|
+
(click)="!item.disabled && picked.emit(item)"
|
|
14615
|
+
>
|
|
14616
|
+
@if (item.icon) {
|
|
14617
|
+
<strct-icon [name]="item.icon" [size]="13" />
|
|
14618
|
+
}
|
|
14619
|
+
{{ item.label }}
|
|
14620
|
+
</strct-dropdown-item>
|
|
14621
|
+
}
|
|
14622
|
+
}
|
|
14623
|
+
</strct-dropdown>
|
|
14624
|
+
</div>
|
|
14625
|
+
`, isInline: true, styles: [".strct-sbt{display:inline-flex;align-items:stretch}.strct-sbt .strct-dd{display:inline-flex;align-self:stretch}.strct-sbt .strct-dd__trigger{display:inline-flex;align-items:stretch}.strct-sbt__chev{align-self:stretch;height:100%}.strct-sbt__main,.strct-sbt__chev{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--acc);background:transparent;color:var(--acc);font-family:var(--font);font-size:12.5px;font-weight:600;cursor:pointer;padding:5px 12px}.strct-sbt__main{border-start-start-radius:7px;border-end-start-radius:7px;border-inline-end:0}.strct-sbt__chev{padding:5px 6px;border-start-end-radius:7px;border-end-end-radius:7px;border-inline-start:1px solid var(--acc50)}.strct-sbt--solid .strct-sbt__main,.strct-sbt--solid .strct-sbt__chev{background:var(--acc);color:var(--inv)}.strct-sbt--solid .strct-sbt__chev{border-inline-start-color:color-mix(in srgb,var(--inv) 30%,var(--acc))}.strct-sbt__main:hover:not(:disabled),.strct-sbt__chev:hover:not(:disabled){background:var(--acc18)}.strct-sbt--solid .strct-sbt__main:hover:not(:disabled),.strct-sbt--solid .strct-sbt__chev:hover:not(:disabled){filter:brightness(1.08);background:var(--acc)}.strct-sbt__main:disabled,.strct-sbt__chev:disabled{opacity:.5;cursor:default}.strct-sbt__main:focus-visible,.strct-sbt__chev:focus-visible{outline:2px solid var(--acc50);outline-offset:1px;position:relative;z-index:1}\n"], dependencies: [{ kind: "component", type: StrctDropdown, selector: "strct-dropdown", inputs: ["align", "popover", "popoverLabel"] }, { kind: "component", type: StrctDropdownDivider, selector: "strct-dropdown-divider" }, { kind: "component", type: StrctDropdownItem, selector: "strct-dropdown-item", inputs: ["critical", "disabled"] }, { kind: "directive", type: StrctDropdownTrigger, selector: "[strctDropdownTrigger]" }, { kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "size", "strokeWidth", "badge", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
14626
|
+
}
|
|
14627
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctSplitButton, decorators: [{
|
|
14628
|
+
type: Component,
|
|
14629
|
+
args: [{ selector: 'strct-split-button', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [
|
|
14630
|
+
StrctDropdown,
|
|
14631
|
+
StrctDropdownDivider,
|
|
14632
|
+
StrctDropdownItem,
|
|
14633
|
+
StrctDropdownTrigger,
|
|
14634
|
+
StrctIcon,
|
|
14635
|
+
], template: `
|
|
14636
|
+
<div class="strct-sbt" [class.strct-sbt--solid]="solid()">
|
|
14637
|
+
<button
|
|
14638
|
+
type="button"
|
|
14639
|
+
class="strct-sbt__main"
|
|
14640
|
+
[disabled]="disabled() || null"
|
|
14641
|
+
(click)="action.emit()"
|
|
14642
|
+
>
|
|
14643
|
+
@if (icon()) {
|
|
14644
|
+
<strct-icon [name]="icon()" [size]="13" />
|
|
14645
|
+
}
|
|
14646
|
+
{{ label() }}
|
|
14647
|
+
</button>
|
|
14648
|
+
<strct-dropdown align="end">
|
|
14649
|
+
<button
|
|
14650
|
+
type="button"
|
|
14651
|
+
class="strct-sbt__chev"
|
|
14652
|
+
strctDropdownTrigger
|
|
14653
|
+
[disabled]="disabled() || null"
|
|
14654
|
+
[attr.aria-label]="menuLabel() + ': ' + label()"
|
|
14655
|
+
>
|
|
14656
|
+
<strct-icon name="chevronDown" [size]="12" />
|
|
14657
|
+
</button>
|
|
14658
|
+
@for (item of items(); track $index) {
|
|
14659
|
+
@if (item.divider) {
|
|
14660
|
+
<strct-dropdown-divider />
|
|
14661
|
+
} @else {
|
|
14662
|
+
<strct-dropdown-item
|
|
14663
|
+
[critical]="item.critical ?? false"
|
|
14664
|
+
[disabled]="item.disabled ?? false"
|
|
14665
|
+
(click)="!item.disabled && picked.emit(item)"
|
|
14666
|
+
>
|
|
14667
|
+
@if (item.icon) {
|
|
14668
|
+
<strct-icon [name]="item.icon" [size]="13" />
|
|
14669
|
+
}
|
|
14670
|
+
{{ item.label }}
|
|
14671
|
+
</strct-dropdown-item>
|
|
14672
|
+
}
|
|
14673
|
+
}
|
|
14674
|
+
</strct-dropdown>
|
|
14675
|
+
</div>
|
|
14676
|
+
`, styles: [".strct-sbt{display:inline-flex;align-items:stretch}.strct-sbt .strct-dd{display:inline-flex;align-self:stretch}.strct-sbt .strct-dd__trigger{display:inline-flex;align-items:stretch}.strct-sbt__chev{align-self:stretch;height:100%}.strct-sbt__main,.strct-sbt__chev{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--acc);background:transparent;color:var(--acc);font-family:var(--font);font-size:12.5px;font-weight:600;cursor:pointer;padding:5px 12px}.strct-sbt__main{border-start-start-radius:7px;border-end-start-radius:7px;border-inline-end:0}.strct-sbt__chev{padding:5px 6px;border-start-end-radius:7px;border-end-end-radius:7px;border-inline-start:1px solid var(--acc50)}.strct-sbt--solid .strct-sbt__main,.strct-sbt--solid .strct-sbt__chev{background:var(--acc);color:var(--inv)}.strct-sbt--solid .strct-sbt__chev{border-inline-start-color:color-mix(in srgb,var(--inv) 30%,var(--acc))}.strct-sbt__main:hover:not(:disabled),.strct-sbt__chev:hover:not(:disabled){background:var(--acc18)}.strct-sbt--solid .strct-sbt__main:hover:not(:disabled),.strct-sbt--solid .strct-sbt__chev:hover:not(:disabled){filter:brightness(1.08);background:var(--acc)}.strct-sbt__main:disabled,.strct-sbt__chev:disabled{opacity:.5;cursor:default}.strct-sbt__main:focus-visible,.strct-sbt__chev:focus-visible{outline:2px solid var(--acc50);outline-offset:1px;position:relative;z-index:1}\n"] }]
|
|
14677
|
+
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], solid: [{ type: i0.Input, args: [{ isSignal: true, alias: "solid", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], menuLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "menuLabel", required: false }] }], action: [{ type: i0.Output, args: ["action"] }], picked: [{ type: i0.Output, args: ["picked"] }] } });
|
|
14678
|
+
|
|
14679
|
+
/**
|
|
14680
|
+
* Repeating diagonal text watermark over its content — compliance overlays
|
|
14681
|
+
* for classified/internal consoles ("CONFIDENTIAL", "user@corp · 2026-07-21"):
|
|
14682
|
+
*
|
|
14683
|
+
* <strct-watermark text="CONFIDENTIAL">…page…</strct-watermark>
|
|
14684
|
+
*
|
|
14685
|
+
* Rendered as a pointer-transparent tiled SVG layer, so the content below
|
|
14686
|
+
* stays fully interactive and selectable. Purely visual — `aria-hidden`.
|
|
14687
|
+
*/
|
|
14688
|
+
class StrctWatermark {
|
|
14689
|
+
sanitizer = inject(DomSanitizer);
|
|
14690
|
+
/** The repeated watermark text. */
|
|
14691
|
+
text = input.required(...(ngDevMode ? [{ debugName: "text" }] : /* istanbul ignore next */ []));
|
|
14692
|
+
/** Layer opacity (0–1). */
|
|
14693
|
+
opacity = input(0.06, ...(ngDevMode ? [{ debugName: "opacity" }] : /* istanbul ignore next */ []));
|
|
14694
|
+
/** Rotation in degrees (negative = up-slope). */
|
|
14695
|
+
angle = input(-24, ...(ngDevMode ? [{ debugName: "angle" }] : /* istanbul ignore next */ []));
|
|
14696
|
+
/** Tile size in px — larger spreads the marks out. */
|
|
14697
|
+
gap = input(220, ...(ngDevMode ? [{ debugName: "gap" }] : /* istanbul ignore next */ []));
|
|
14698
|
+
/** Font size in px. */
|
|
14699
|
+
fontSize = input(15, ...(ngDevMode ? [{ debugName: "fontSize" }] : /* istanbul ignore next */ []));
|
|
14700
|
+
tile = computed(() => {
|
|
14701
|
+
const size = this.gap();
|
|
14702
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}">` +
|
|
14703
|
+
`<text x="50%" y="50%" text-anchor="middle" dominant-baseline="middle" ` +
|
|
14704
|
+
`transform="rotate(${this.angle()} ${size / 2} ${size / 2})" ` +
|
|
14705
|
+
`font-family="sans-serif" font-size="${this.fontSize()}" font-weight="600" ` +
|
|
14706
|
+
`fill="currentColor" fill-opacity="${this.opacity()}">${escapeXml(this.text())}</text></svg>`;
|
|
14707
|
+
return this.sanitizer.bypassSecurityTrustStyle(`url("data:image/svg+xml;utf8,${encodeURIComponent(svg)}")`);
|
|
14708
|
+
}, ...(ngDevMode ? [{ debugName: "tile" }] : /* istanbul ignore next */ []));
|
|
14709
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctWatermark, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
14710
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.16", type: StrctWatermark, isStandalone: true, selector: "strct-watermark", inputs: { text: { classPropertyName: "text", publicName: "text", isSignal: true, isRequired: true, transformFunction: null }, opacity: { classPropertyName: "opacity", publicName: "opacity", isSignal: true, isRequired: false, transformFunction: null }, angle: { classPropertyName: "angle", publicName: "angle", isSignal: true, isRequired: false, transformFunction: null }, gap: { classPropertyName: "gap", publicName: "gap", isSignal: true, isRequired: false, transformFunction: null }, fontSize: { classPropertyName: "fontSize", publicName: "fontSize", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "strct-wm" }, ngImport: i0, template: `
|
|
14711
|
+
<ng-content />
|
|
14712
|
+
<div class="strct-wm__layer" aria-hidden="true" [style.background-image]="tile()"></div>
|
|
14713
|
+
`, isInline: true, styles: [".strct-wm{position:relative;display:block}.strct-wm__layer{position:absolute;inset:0;pointer-events:none;background-repeat:repeat;z-index:5}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
14714
|
+
}
|
|
14715
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctWatermark, decorators: [{
|
|
14716
|
+
type: Component,
|
|
14717
|
+
args: [{ selector: 'strct-watermark', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: `
|
|
14718
|
+
<ng-content />
|
|
14719
|
+
<div class="strct-wm__layer" aria-hidden="true" [style.background-image]="tile()"></div>
|
|
14720
|
+
`, host: { class: 'strct-wm' }, styles: [".strct-wm{position:relative;display:block}.strct-wm__layer{position:absolute;inset:0;pointer-events:none;background-repeat:repeat;z-index:5}\n"] }]
|
|
14721
|
+
}], propDecorators: { text: [{ type: i0.Input, args: [{ isSignal: true, alias: "text", required: true }] }], opacity: [{ type: i0.Input, args: [{ isSignal: true, alias: "opacity", required: false }] }], angle: [{ type: i0.Input, args: [{ isSignal: true, alias: "angle", required: false }] }], gap: [{ type: i0.Input, args: [{ isSignal: true, alias: "gap", required: false }] }], fontSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "fontSize", required: false }] }] } });
|
|
14722
|
+
function escapeXml(s) {
|
|
14723
|
+
return s
|
|
14724
|
+
.replace(/&/g, '&')
|
|
14725
|
+
.replace(/</g, '<')
|
|
14726
|
+
.replace(/>/g, '>')
|
|
14727
|
+
.replace(/"/g, '"');
|
|
14728
|
+
}
|
|
14729
|
+
|
|
14730
|
+
/**
|
|
14731
|
+
* Feature tour (coach marks) — "what's new" onboarding over live UI:
|
|
14732
|
+
*
|
|
14733
|
+
* <strct-tour [(open)]="tourOpen" [steps]="steps" (finished)="done()" />
|
|
14734
|
+
*
|
|
14735
|
+
* Each step spotlights its `target` selector with a ring cut out of a dimmed
|
|
14736
|
+
* backdrop and anchors a card next to it. Arrows / buttons step, Escape
|
|
14737
|
+
* closes, and the card is a labeled dialog. Purely DOM-driven: targets are
|
|
14738
|
+
* looked up when the step shows, so it works over any page.
|
|
14739
|
+
*/
|
|
14740
|
+
class StrctTour {
|
|
14741
|
+
/** Whether the tour is showing (two-way). */
|
|
14742
|
+
open = model(false, ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
|
|
14743
|
+
/** The steps, in order. */
|
|
14744
|
+
steps = input.required(...(ngDevMode ? [{ debugName: "steps" }] : /* istanbul ignore next */ []));
|
|
14745
|
+
/** Localizable strings. */
|
|
14746
|
+
nextLabel = input('Next', ...(ngDevMode ? [{ debugName: "nextLabel" }] : /* istanbul ignore next */ []));
|
|
14747
|
+
backLabel = input('Back', ...(ngDevMode ? [{ debugName: "backLabel" }] : /* istanbul ignore next */ []));
|
|
14748
|
+
skipLabel = input('Skip', ...(ngDevMode ? [{ debugName: "skipLabel" }] : /* istanbul ignore next */ []));
|
|
14749
|
+
doneLabel = input('Done', ...(ngDevMode ? [{ debugName: "doneLabel" }] : /* istanbul ignore next */ []));
|
|
14750
|
+
/** The last step's Done was clicked (Escape / Skip emit `dismissed`). */
|
|
14751
|
+
finished = output();
|
|
14752
|
+
dismissed = output();
|
|
14753
|
+
index = signal(0, ...(ngDevMode ? [{ debugName: "index" }] : /* istanbul ignore next */ []));
|
|
14754
|
+
/** Bumped on open/step/resize to re-measure the target. */
|
|
14755
|
+
measureTick = signal(0, ...(ngDevMode ? [{ debugName: "measureTick" }] : /* istanbul ignore next */ []));
|
|
14756
|
+
current = computed(() => this.steps()[this.index()] ?? null, ...(ngDevMode ? [{ debugName: "current" }] : /* istanbul ignore next */ []));
|
|
14757
|
+
spot = computed(() => {
|
|
14758
|
+
this.measureTick();
|
|
14759
|
+
const step = this.current();
|
|
14760
|
+
if (!this.open() || !step?.target)
|
|
14761
|
+
return null;
|
|
14762
|
+
const el = document.querySelector(step.target);
|
|
14763
|
+
if (!el)
|
|
14764
|
+
return null;
|
|
14765
|
+
const r = el.getBoundingClientRect();
|
|
14766
|
+
return { top: r.top, left: r.left, width: r.width, height: r.height };
|
|
14767
|
+
}, ...(ngDevMode ? [{ debugName: "spot" }] : /* istanbul ignore next */ []));
|
|
14768
|
+
cardPos = computed(() => {
|
|
14769
|
+
const r = this.spot();
|
|
14770
|
+
const w = 300;
|
|
14771
|
+
const h = 170;
|
|
14772
|
+
if (!r) {
|
|
14773
|
+
return {
|
|
14774
|
+
top: Math.max(20, window.innerHeight / 2 - h / 2),
|
|
14775
|
+
left: Math.max(20, window.innerWidth / 2 - w / 2),
|
|
14776
|
+
};
|
|
14777
|
+
}
|
|
14778
|
+
// Below the target if it fits, else above; clamped into the viewport.
|
|
14779
|
+
let top = r.top + r.height + 14;
|
|
14780
|
+
if (top + h > window.innerHeight - 12)
|
|
14781
|
+
top = Math.max(12, r.top - h - 14);
|
|
14782
|
+
const left = Math.min(Math.max(12, r.left), window.innerWidth - w - 12);
|
|
14783
|
+
return { top, left };
|
|
14784
|
+
}, ...(ngDevMode ? [{ debugName: "cardPos" }] : /* istanbul ignore next */ []));
|
|
14785
|
+
constructor() {
|
|
14786
|
+
// Restart from step 0 on open; scroll the first target into view.
|
|
14787
|
+
effect(() => {
|
|
14788
|
+
if (this.open()) {
|
|
14789
|
+
this.index.set(0);
|
|
14790
|
+
this.scrollToTarget();
|
|
14791
|
+
}
|
|
14792
|
+
});
|
|
14793
|
+
}
|
|
14794
|
+
next() {
|
|
14795
|
+
if (this.index() >= this.steps().length - 1) {
|
|
14796
|
+
this.open.set(false);
|
|
14797
|
+
this.finished.emit();
|
|
14798
|
+
return;
|
|
14799
|
+
}
|
|
14800
|
+
this.index.update((i) => i + 1);
|
|
14801
|
+
this.scrollToTarget();
|
|
14802
|
+
}
|
|
14803
|
+
prev() {
|
|
14804
|
+
this.index.update((i) => Math.max(0, i - 1));
|
|
14805
|
+
this.scrollToTarget();
|
|
14806
|
+
}
|
|
14807
|
+
close() {
|
|
14808
|
+
this.open.set(false);
|
|
14809
|
+
this.dismissed.emit();
|
|
14810
|
+
}
|
|
14811
|
+
scrollToTarget() {
|
|
14812
|
+
setTimeout(() => {
|
|
14813
|
+
const sel = this.steps()[this.index()]?.target;
|
|
14814
|
+
if (sel)
|
|
14815
|
+
document.querySelector(sel)?.scrollIntoView?.({ block: 'center' });
|
|
14816
|
+
this.measureTick.update((n) => n + 1);
|
|
14817
|
+
});
|
|
14818
|
+
}
|
|
14819
|
+
onEscape() {
|
|
14820
|
+
if (this.open())
|
|
14821
|
+
this.close();
|
|
14822
|
+
}
|
|
14823
|
+
onReflow() {
|
|
14824
|
+
if (this.open())
|
|
14825
|
+
this.measureTick.update((n) => n + 1);
|
|
14826
|
+
}
|
|
14827
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctTour, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
14828
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: StrctTour, isStandalone: true, selector: "strct-tour", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, steps: { classPropertyName: "steps", publicName: "steps", isSignal: true, isRequired: true, transformFunction: null }, nextLabel: { classPropertyName: "nextLabel", publicName: "nextLabel", isSignal: true, isRequired: false, transformFunction: null }, backLabel: { classPropertyName: "backLabel", publicName: "backLabel", isSignal: true, isRequired: false, transformFunction: null }, skipLabel: { classPropertyName: "skipLabel", publicName: "skipLabel", isSignal: true, isRequired: false, transformFunction: null }, doneLabel: { classPropertyName: "doneLabel", publicName: "doneLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange", finished: "finished", dismissed: "dismissed" }, host: { listeners: { "document:keydown.escape": "onEscape()", "window:resize": "onReflow()", "window:scroll": "onReflow()" } }, ngImport: i0, template: `
|
|
14829
|
+
@if (open() && current(); as step) {
|
|
14830
|
+
<!-- Always the click-catcher; dims only when no ring (its shadow dims). -->
|
|
14831
|
+
<div
|
|
14832
|
+
class="strct-tour__backdrop"
|
|
14833
|
+
[class.strct-tour__backdrop--dim]="!spot()"
|
|
14834
|
+
(click)="close()"
|
|
14835
|
+
></div>
|
|
14836
|
+
@if (spot(); as r) {
|
|
14837
|
+
<div
|
|
14838
|
+
class="strct-tour__ring"
|
|
14839
|
+
[style.top.px]="r.top - 6"
|
|
14840
|
+
[style.left.px]="r.left - 6"
|
|
14841
|
+
[style.width.px]="r.width + 12"
|
|
14842
|
+
[style.height.px]="r.height + 12"
|
|
14843
|
+
></div>
|
|
14844
|
+
}
|
|
14845
|
+
<div
|
|
14846
|
+
class="strct-tour__card"
|
|
14847
|
+
role="dialog"
|
|
14848
|
+
[attr.aria-label]="step.title"
|
|
14849
|
+
[style.top.px]="cardPos().top"
|
|
14850
|
+
[style.left.px]="cardPos().left"
|
|
14851
|
+
>
|
|
14852
|
+
<div class="strct-tour__step">{{ index() + 1 }} / {{ steps().length }}</div>
|
|
14853
|
+
<div class="strct-tour__title">{{ step.title }}</div>
|
|
14854
|
+
<div class="strct-tour__body">{{ step.body }}</div>
|
|
14855
|
+
<div class="strct-tour__nav">
|
|
14856
|
+
<button strct-button size="sm" variant="flat" (click)="close()">
|
|
14857
|
+
{{ skipLabel() }}
|
|
14858
|
+
</button>
|
|
14859
|
+
<span class="strct-tour__grow"></span>
|
|
14860
|
+
@if (index() > 0) {
|
|
14861
|
+
<button strct-button size="sm" variant="neutral" (click)="prev()">
|
|
14862
|
+
{{ backLabel() }}
|
|
14863
|
+
</button>
|
|
14864
|
+
}
|
|
14865
|
+
<button strct-button size="sm" variant="primary" solid (click)="next()">
|
|
14866
|
+
{{ index() === steps().length - 1 ? doneLabel() : nextLabel() }}
|
|
14867
|
+
</button>
|
|
14868
|
+
</div>
|
|
14869
|
+
</div>
|
|
14870
|
+
}
|
|
14871
|
+
`, isInline: true, styles: [".strct-tour__backdrop{position:fixed;inset:0;z-index:400;background:transparent}.strct-tour__backdrop--dim{background:#00000073}.strct-tour__ring{position:fixed;z-index:401;border:2px solid var(--acc);border-radius:9px;box-shadow:0 0 0 4px var(--acc30),0 0 0 9999px #00000073;pointer-events:none}.strct-tour__card{position:fixed;z-index:402;width:300px;padding:14px 16px;background:var(--bg-1);border:1px solid var(--b2);border-radius:9px;box-shadow:var(--shh)}.strct-tour__step{font-size:10.5px;font-weight:600;letter-spacing:.6px;color:var(--t3);margin-bottom:4px;font-variant-numeric:tabular-nums}.strct-tour__title{font-size:13.5px;font-weight:650;color:var(--t1);margin-bottom:6px}.strct-tour__body{font-size:12.5px;line-height:1.5;color:var(--t2);margin-bottom:12px}.strct-tour__nav{display:flex;align-items:center;gap:8px}.strct-tour__grow{flex:1}\n"], dependencies: [{ kind: "component", type: StrctButton, selector: "button[strct-button], a[strct-button]", inputs: ["variant", "size", "solid", "block", "iconOnly"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
14872
|
+
}
|
|
14873
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctTour, decorators: [{
|
|
14874
|
+
type: Component,
|
|
14875
|
+
args: [{ selector: 'strct-tour', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [StrctButton], template: `
|
|
14876
|
+
@if (open() && current(); as step) {
|
|
14877
|
+
<!-- Always the click-catcher; dims only when no ring (its shadow dims). -->
|
|
14878
|
+
<div
|
|
14879
|
+
class="strct-tour__backdrop"
|
|
14880
|
+
[class.strct-tour__backdrop--dim]="!spot()"
|
|
14881
|
+
(click)="close()"
|
|
14882
|
+
></div>
|
|
14883
|
+
@if (spot(); as r) {
|
|
14884
|
+
<div
|
|
14885
|
+
class="strct-tour__ring"
|
|
14886
|
+
[style.top.px]="r.top - 6"
|
|
14887
|
+
[style.left.px]="r.left - 6"
|
|
14888
|
+
[style.width.px]="r.width + 12"
|
|
14889
|
+
[style.height.px]="r.height + 12"
|
|
14890
|
+
></div>
|
|
14891
|
+
}
|
|
14892
|
+
<div
|
|
14893
|
+
class="strct-tour__card"
|
|
14894
|
+
role="dialog"
|
|
14895
|
+
[attr.aria-label]="step.title"
|
|
14896
|
+
[style.top.px]="cardPos().top"
|
|
14897
|
+
[style.left.px]="cardPos().left"
|
|
14898
|
+
>
|
|
14899
|
+
<div class="strct-tour__step">{{ index() + 1 }} / {{ steps().length }}</div>
|
|
14900
|
+
<div class="strct-tour__title">{{ step.title }}</div>
|
|
14901
|
+
<div class="strct-tour__body">{{ step.body }}</div>
|
|
14902
|
+
<div class="strct-tour__nav">
|
|
14903
|
+
<button strct-button size="sm" variant="flat" (click)="close()">
|
|
14904
|
+
{{ skipLabel() }}
|
|
14905
|
+
</button>
|
|
14906
|
+
<span class="strct-tour__grow"></span>
|
|
14907
|
+
@if (index() > 0) {
|
|
14908
|
+
<button strct-button size="sm" variant="neutral" (click)="prev()">
|
|
14909
|
+
{{ backLabel() }}
|
|
14910
|
+
</button>
|
|
14911
|
+
}
|
|
14912
|
+
<button strct-button size="sm" variant="primary" solid (click)="next()">
|
|
14913
|
+
{{ index() === steps().length - 1 ? doneLabel() : nextLabel() }}
|
|
14914
|
+
</button>
|
|
14915
|
+
</div>
|
|
14916
|
+
</div>
|
|
14917
|
+
}
|
|
14918
|
+
`, styles: [".strct-tour__backdrop{position:fixed;inset:0;z-index:400;background:transparent}.strct-tour__backdrop--dim{background:#00000073}.strct-tour__ring{position:fixed;z-index:401;border:2px solid var(--acc);border-radius:9px;box-shadow:0 0 0 4px var(--acc30),0 0 0 9999px #00000073;pointer-events:none}.strct-tour__card{position:fixed;z-index:402;width:300px;padding:14px 16px;background:var(--bg-1);border:1px solid var(--b2);border-radius:9px;box-shadow:var(--shh)}.strct-tour__step{font-size:10.5px;font-weight:600;letter-spacing:.6px;color:var(--t3);margin-bottom:4px;font-variant-numeric:tabular-nums}.strct-tour__title{font-size:13.5px;font-weight:650;color:var(--t1);margin-bottom:6px}.strct-tour__body{font-size:12.5px;line-height:1.5;color:var(--t2);margin-bottom:12px}.strct-tour__nav{display:flex;align-items:center;gap:8px}.strct-tour__grow{flex:1}\n"] }]
|
|
14919
|
+
}], ctorParameters: () => [], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], steps: [{ type: i0.Input, args: [{ isSignal: true, alias: "steps", required: true }] }], nextLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "nextLabel", required: false }] }], backLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "backLabel", required: false }] }], skipLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "skipLabel", required: false }] }], doneLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "doneLabel", required: false }] }], finished: [{ type: i0.Output, args: ["finished"] }], dismissed: [{ type: i0.Output, args: ["dismissed"] }], onEscape: [{
|
|
14920
|
+
type: HostListener,
|
|
14921
|
+
args: ['document:keydown.escape']
|
|
14922
|
+
}], onReflow: [{
|
|
14923
|
+
type: HostListener,
|
|
14924
|
+
args: ['window:resize']
|
|
14925
|
+
}, {
|
|
14926
|
+
type: HostListener,
|
|
14927
|
+
args: ['window:scroll']
|
|
14928
|
+
}] } });
|
|
14929
|
+
|
|
14930
|
+
/**
|
|
14931
|
+
* Horizontal menubar — the application-menu strip ("File · Edit · View") for
|
|
14932
|
+
* dense tool-style consoles:
|
|
14933
|
+
*
|
|
14934
|
+
* <strct-menubar [menus]="menus" (picked)="run($event)" />
|
|
14935
|
+
*
|
|
14936
|
+
* Click (or Enter/Down) opens a menu; Left/Right move across the bar and,
|
|
14937
|
+
* while open, switch the open menu (the APG menubar convention). Escape and
|
|
14938
|
+
* outside click close. `(picked)` carries `{ menu, item }`.
|
|
14939
|
+
*/
|
|
14940
|
+
class StrctMenubar {
|
|
14941
|
+
host = inject(ElementRef);
|
|
14942
|
+
/** Top-level menus. */
|
|
14943
|
+
menus = input.required(...(ngDevMode ? [{ debugName: "menus" }] : /* istanbul ignore next */ []));
|
|
14944
|
+
/** Accessible name of the bar (localizable). */
|
|
14945
|
+
ariaLabel = input('Application menu', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
14946
|
+
/** A menu entry was chosen. */
|
|
14947
|
+
picked = output();
|
|
14948
|
+
openId = signal(null, ...(ngDevMode ? [{ debugName: "openId" }] : /* istanbul ignore next */ []));
|
|
14949
|
+
focusIdx = signal(0, ...(ngDevMode ? [{ debugName: "focusIdx" }] : /* istanbul ignore next */ []));
|
|
14950
|
+
toggleMenu(id, i) {
|
|
14951
|
+
this.focusIdx.set(i);
|
|
14952
|
+
this.openId.set(this.openId() === id ? null : id);
|
|
14953
|
+
}
|
|
14954
|
+
pick(menu, item) {
|
|
14955
|
+
if (item.disabled)
|
|
14956
|
+
return;
|
|
14957
|
+
this.openId.set(null);
|
|
14958
|
+
this.picked.emit({ menu, item });
|
|
14959
|
+
}
|
|
14960
|
+
onTopKey(event, i) {
|
|
14961
|
+
const menus = this.menus();
|
|
14962
|
+
if (event.key === 'ArrowRight' || event.key === 'ArrowLeft') {
|
|
14963
|
+
event.preventDefault();
|
|
14964
|
+
const dir = event.key === 'ArrowRight' ? 1 : -1;
|
|
14965
|
+
const next = (i + dir + menus.length) % menus.length;
|
|
14966
|
+
this.focusIdx.set(next);
|
|
14967
|
+
// While a menu is open, moving across the bar switches the open menu.
|
|
14968
|
+
if (this.openId())
|
|
14969
|
+
this.openId.set(menus[next].id);
|
|
14970
|
+
setTimeout(() => {
|
|
14971
|
+
const tops = this.host.nativeElement.querySelectorAll('.strct-mb__top');
|
|
14972
|
+
tops[next]?.focus();
|
|
14973
|
+
});
|
|
14974
|
+
}
|
|
14975
|
+
else if (event.key === 'ArrowDown' || event.key === 'Enter') {
|
|
14976
|
+
event.preventDefault();
|
|
14977
|
+
this.openId.set(menus[i].id);
|
|
14978
|
+
}
|
|
14979
|
+
}
|
|
14980
|
+
onDocClick(event) {
|
|
14981
|
+
if (this.openId() && !this.host.nativeElement.contains(event.target)) {
|
|
14982
|
+
this.openId.set(null);
|
|
14983
|
+
}
|
|
14984
|
+
}
|
|
14985
|
+
onEscape() {
|
|
14986
|
+
this.openId.set(null);
|
|
14987
|
+
}
|
|
14988
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctMenubar, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
14989
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: StrctMenubar, isStandalone: true, selector: "strct-menubar", inputs: { menus: { classPropertyName: "menus", publicName: "menus", isSignal: true, isRequired: true, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { picked: "picked" }, host: { listeners: { "document:click": "onDocClick($event)", "document:keydown.escape": "onEscape()" } }, ngImport: i0, template: `
|
|
14990
|
+
<div class="strct-mb" role="menubar" [attr.aria-label]="ariaLabel()">
|
|
14991
|
+
@for (menu of menus(); track menu.id; let i = $index) {
|
|
14992
|
+
<div class="strct-mb__wrap">
|
|
14993
|
+
<button
|
|
14994
|
+
type="button"
|
|
14995
|
+
class="strct-mb__top"
|
|
14996
|
+
role="menuitem"
|
|
14997
|
+
[class.strct-mb__top--open]="openId() === menu.id"
|
|
14998
|
+
[attr.aria-haspopup]="'menu'"
|
|
14999
|
+
[attr.aria-expanded]="openId() === menu.id"
|
|
15000
|
+
[attr.tabindex]="i === focusIdx() ? 0 : -1"
|
|
15001
|
+
(click)="toggleMenu(menu.id, i)"
|
|
15002
|
+
(keydown)="onTopKey($event, i)"
|
|
15003
|
+
>
|
|
15004
|
+
{{ menu.label }}
|
|
15005
|
+
</button>
|
|
15006
|
+
@if (openId() === menu.id) {
|
|
15007
|
+
<div class="strct-mb__menu" role="menu" [attr.aria-label]="menu.label">
|
|
15008
|
+
@for (item of menu.items; track $index) {
|
|
15009
|
+
@if (item.divider) {
|
|
15010
|
+
<div class="strct-mb__divider" role="separator"></div>
|
|
15011
|
+
} @else {
|
|
15012
|
+
<button
|
|
15013
|
+
type="button"
|
|
15014
|
+
class="strct-mb__item"
|
|
15015
|
+
role="menuitem"
|
|
15016
|
+
[class.strct-mb__item--critical]="item.critical"
|
|
15017
|
+
[disabled]="item.disabled || null"
|
|
15018
|
+
(click)="pick(menu, item)"
|
|
15019
|
+
>
|
|
15020
|
+
@if (item.icon) {
|
|
15021
|
+
<strct-icon [name]="item.icon" [size]="13" />
|
|
15022
|
+
}
|
|
15023
|
+
{{ item.label }}
|
|
15024
|
+
</button>
|
|
15025
|
+
}
|
|
15026
|
+
}
|
|
15027
|
+
</div>
|
|
15028
|
+
}
|
|
15029
|
+
</div>
|
|
15030
|
+
}
|
|
15031
|
+
</div>
|
|
15032
|
+
`, isInline: true, styles: [".strct-mb{display:inline-flex;gap:2px;padding:2px;background:var(--bg-2);border:1px solid var(--b1);border-radius:7px}.strct-mb__wrap{position:relative}.strct-mb__top{padding:4px 11px;border:0;border-radius:5px;background:transparent;color:var(--t1);font-family:var(--font);font-size:12.5px;cursor:pointer;white-space:nowrap}.strct-mb__top:hover,.strct-mb__top--open{background:var(--bg-3)}.strct-mb__top:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}.strct-mb__menu{position:absolute;top:calc(100% + 4px);inset-inline-start:0;z-index:200;min-width:180px;padding:4px;background:var(--bg-1);border:1px solid var(--b2);border-radius:7px;box-shadow:var(--shh);display:flex;flex-direction:column}.strct-mb__item{display:flex;align-items:center;gap:8px;width:100%;padding:6px 10px;border:0;border-radius:5px;background:transparent;color:var(--t1);font-family:var(--font);font-size:12.5px;text-align:start;cursor:pointer;white-space:nowrap}.strct-mb__item:hover:not(:disabled){background:var(--bg-3)}.strct-mb__item--critical{color:var(--critical)}.strct-mb__item--critical:hover:not(:disabled){background:var(--critical-bg)}.strct-mb__item:disabled{color:var(--t4);cursor:default}.strct-mb__item:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-mb__divider{height:1px;margin:4px 6px;background:var(--b2)}\n"], dependencies: [{ kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "size", "strokeWidth", "badge", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
15033
|
+
}
|
|
15034
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctMenubar, decorators: [{
|
|
15035
|
+
type: Component,
|
|
15036
|
+
args: [{ selector: 'strct-menubar', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [StrctIcon], template: `
|
|
15037
|
+
<div class="strct-mb" role="menubar" [attr.aria-label]="ariaLabel()">
|
|
15038
|
+
@for (menu of menus(); track menu.id; let i = $index) {
|
|
15039
|
+
<div class="strct-mb__wrap">
|
|
15040
|
+
<button
|
|
15041
|
+
type="button"
|
|
15042
|
+
class="strct-mb__top"
|
|
15043
|
+
role="menuitem"
|
|
15044
|
+
[class.strct-mb__top--open]="openId() === menu.id"
|
|
15045
|
+
[attr.aria-haspopup]="'menu'"
|
|
15046
|
+
[attr.aria-expanded]="openId() === menu.id"
|
|
15047
|
+
[attr.tabindex]="i === focusIdx() ? 0 : -1"
|
|
15048
|
+
(click)="toggleMenu(menu.id, i)"
|
|
15049
|
+
(keydown)="onTopKey($event, i)"
|
|
15050
|
+
>
|
|
15051
|
+
{{ menu.label }}
|
|
15052
|
+
</button>
|
|
15053
|
+
@if (openId() === menu.id) {
|
|
15054
|
+
<div class="strct-mb__menu" role="menu" [attr.aria-label]="menu.label">
|
|
15055
|
+
@for (item of menu.items; track $index) {
|
|
15056
|
+
@if (item.divider) {
|
|
15057
|
+
<div class="strct-mb__divider" role="separator"></div>
|
|
15058
|
+
} @else {
|
|
15059
|
+
<button
|
|
15060
|
+
type="button"
|
|
15061
|
+
class="strct-mb__item"
|
|
15062
|
+
role="menuitem"
|
|
15063
|
+
[class.strct-mb__item--critical]="item.critical"
|
|
15064
|
+
[disabled]="item.disabled || null"
|
|
15065
|
+
(click)="pick(menu, item)"
|
|
15066
|
+
>
|
|
15067
|
+
@if (item.icon) {
|
|
15068
|
+
<strct-icon [name]="item.icon" [size]="13" />
|
|
15069
|
+
}
|
|
15070
|
+
{{ item.label }}
|
|
15071
|
+
</button>
|
|
15072
|
+
}
|
|
15073
|
+
}
|
|
15074
|
+
</div>
|
|
15075
|
+
}
|
|
15076
|
+
</div>
|
|
15077
|
+
}
|
|
15078
|
+
</div>
|
|
15079
|
+
`, styles: [".strct-mb{display:inline-flex;gap:2px;padding:2px;background:var(--bg-2);border:1px solid var(--b1);border-radius:7px}.strct-mb__wrap{position:relative}.strct-mb__top{padding:4px 11px;border:0;border-radius:5px;background:transparent;color:var(--t1);font-family:var(--font);font-size:12.5px;cursor:pointer;white-space:nowrap}.strct-mb__top:hover,.strct-mb__top--open{background:var(--bg-3)}.strct-mb__top:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}.strct-mb__menu{position:absolute;top:calc(100% + 4px);inset-inline-start:0;z-index:200;min-width:180px;padding:4px;background:var(--bg-1);border:1px solid var(--b2);border-radius:7px;box-shadow:var(--shh);display:flex;flex-direction:column}.strct-mb__item{display:flex;align-items:center;gap:8px;width:100%;padding:6px 10px;border:0;border-radius:5px;background:transparent;color:var(--t1);font-family:var(--font);font-size:12.5px;text-align:start;cursor:pointer;white-space:nowrap}.strct-mb__item:hover:not(:disabled){background:var(--bg-3)}.strct-mb__item--critical{color:var(--critical)}.strct-mb__item--critical:hover:not(:disabled){background:var(--critical-bg)}.strct-mb__item:disabled{color:var(--t4);cursor:default}.strct-mb__item:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-mb__divider{height:1px;margin:4px 6px;background:var(--b2)}\n"] }]
|
|
15080
|
+
}], propDecorators: { menus: [{ type: i0.Input, args: [{ isSignal: true, alias: "menus", required: true }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], picked: [{ type: i0.Output, args: ["picked"] }], onDocClick: [{
|
|
15081
|
+
type: HostListener,
|
|
15082
|
+
args: ['document:click', ['$event']]
|
|
15083
|
+
}], onEscape: [{
|
|
15084
|
+
type: HostListener,
|
|
15085
|
+
args: ['document:keydown.escape']
|
|
15086
|
+
}] } });
|
|
15087
|
+
|
|
15088
|
+
/**
|
|
15089
|
+
* Screen-reader announcements for state changes that have no visible text —
|
|
15090
|
+
* "12 rows loaded", "Snapshot created" (Material's LiveAnnouncer, strct-sized):
|
|
15091
|
+
*
|
|
15092
|
+
* private announcer = inject(StrctAnnouncer);
|
|
15093
|
+
* this.announcer.announce('12 rows loaded');
|
|
15094
|
+
*
|
|
15095
|
+
* Maintains one visually-hidden live region per politeness level; repeated
|
|
15096
|
+
* identical messages are re-announced (the region is cleared first).
|
|
15097
|
+
*/
|
|
15098
|
+
class StrctAnnouncer {
|
|
15099
|
+
regions = new Map();
|
|
15100
|
+
clearTimer;
|
|
15101
|
+
announce(message, politeness = 'polite') {
|
|
15102
|
+
const region = this.regionFor(politeness);
|
|
15103
|
+
// Clear first so identical consecutive messages still fire.
|
|
15104
|
+
region.textContent = '';
|
|
15105
|
+
setTimeout(() => (region.textContent = message));
|
|
15106
|
+
clearTimeout(this.clearTimer);
|
|
15107
|
+
// Stale announcements should not linger for the next SR user to stumble on.
|
|
15108
|
+
this.clearTimer = setTimeout(() => (region.textContent = ''), 10_000);
|
|
15109
|
+
}
|
|
15110
|
+
regionFor(politeness) {
|
|
15111
|
+
let region = this.regions.get(politeness);
|
|
15112
|
+
if (!region) {
|
|
15113
|
+
region = document.createElement('div');
|
|
15114
|
+
region.setAttribute('aria-live', politeness);
|
|
15115
|
+
region.setAttribute('aria-atomic', 'true');
|
|
15116
|
+
region.className = 'strct-announcer';
|
|
15117
|
+
region.style.cssText =
|
|
15118
|
+
'position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;';
|
|
15119
|
+
document.body.appendChild(region);
|
|
15120
|
+
this.regions.set(politeness, region);
|
|
15121
|
+
}
|
|
15122
|
+
return region;
|
|
15123
|
+
}
|
|
15124
|
+
ngOnDestroy() {
|
|
15125
|
+
clearTimeout(this.clearTimer);
|
|
15126
|
+
for (const region of this.regions.values())
|
|
15127
|
+
region.remove();
|
|
15128
|
+
this.regions.clear();
|
|
15129
|
+
}
|
|
15130
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctAnnouncer, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
15131
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctAnnouncer, providedIn: 'root' });
|
|
15132
|
+
}
|
|
15133
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctAnnouncer, decorators: [{
|
|
15134
|
+
type: Injectable,
|
|
15135
|
+
args: [{ providedIn: 'root' }]
|
|
15136
|
+
}] });
|
|
15137
|
+
|
|
15138
|
+
function normalize(combo) {
|
|
15139
|
+
return combo
|
|
15140
|
+
.toLowerCase()
|
|
15141
|
+
.split('+')
|
|
15142
|
+
.map((p) => p.trim())
|
|
15143
|
+
.sort()
|
|
15144
|
+
.join('+');
|
|
15145
|
+
}
|
|
15146
|
+
function comboOf(event) {
|
|
15147
|
+
const parts = [];
|
|
15148
|
+
if (event.ctrlKey)
|
|
15149
|
+
parts.push('ctrl');
|
|
15150
|
+
if (event.metaKey)
|
|
15151
|
+
parts.push('meta');
|
|
15152
|
+
if (event.altKey)
|
|
15153
|
+
parts.push('alt');
|
|
15154
|
+
if (event.shiftKey && event.key.length > 1)
|
|
15155
|
+
parts.push('shift');
|
|
15156
|
+
parts.push(event.key.toLowerCase());
|
|
15157
|
+
return parts.sort().join('+');
|
|
15158
|
+
}
|
|
15159
|
+
/** `mod` matches Ctrl (and ⌘ on mac keyboards) — expand to both. */
|
|
15160
|
+
function expand(combo) {
|
|
15161
|
+
const n = combo.toLowerCase();
|
|
15162
|
+
if (!n.includes('mod'))
|
|
15163
|
+
return [normalize(n)];
|
|
15164
|
+
return [normalize(n.replace('mod', 'ctrl')), normalize(n.replace('mod', 'meta'))];
|
|
15165
|
+
}
|
|
15166
|
+
/**
|
|
15167
|
+
* Application hotkeys, centrally registered so they are discoverable — the
|
|
15168
|
+
* Blueprint pattern:
|
|
15169
|
+
*
|
|
15170
|
+
* private hotkeys = inject(StrctHotkeysService);
|
|
15171
|
+
* ngOnInit() { this.dispose = this.hotkeys.register(
|
|
15172
|
+
* { combo: 'mod+k', description: 'Open command palette', group: 'Global',
|
|
15173
|
+
* handler: () => this.palette.open() }); }
|
|
15174
|
+
*
|
|
15175
|
+
* Combos: `'a'`, `'?'`, `'ctrl+k'`, `'meta+shift+p'`, `'mod+k'` (mod = Ctrl/⌘).
|
|
15176
|
+
* Plain-key combos are suppressed while typing in inputs / textareas /
|
|
15177
|
+
* contenteditable; modifier combos always fire. Pair with
|
|
15178
|
+
* `<strct-hotkeys-help/>` for the ⌘/ ("?") cheatsheet overlay.
|
|
15179
|
+
*/
|
|
15180
|
+
class StrctHotkeysService {
|
|
15181
|
+
registry = signal([], ...(ngDevMode ? [{ debugName: "registry" }] : /* istanbul ignore next */ []));
|
|
15182
|
+
/** All registered hotkeys (for the help overlay). */
|
|
15183
|
+
hotkeys = this.registry.asReadonly();
|
|
15184
|
+
listener = (event) => this.onKeydown(event);
|
|
15185
|
+
constructor() {
|
|
15186
|
+
document.addEventListener('keydown', this.listener);
|
|
15187
|
+
}
|
|
15188
|
+
/** Register a hotkey; the returned function unregisters it. */
|
|
15189
|
+
register(hotkey) {
|
|
15190
|
+
this.registry.update((list) => [...list, hotkey]);
|
|
15191
|
+
return () => this.registry.update((list) => list.filter((h) => h !== hotkey));
|
|
15192
|
+
}
|
|
15193
|
+
onKeydown(event) {
|
|
15194
|
+
const combo = comboOf(event);
|
|
15195
|
+
const typing = isTyping(event);
|
|
15196
|
+
for (const hk of this.registry()) {
|
|
15197
|
+
if (!expand(hk.combo).includes(combo))
|
|
15198
|
+
continue;
|
|
15199
|
+
// While typing, only modifier combos may fire.
|
|
15200
|
+
if (typing && !(event.ctrlKey || event.metaKey || event.altKey))
|
|
15201
|
+
continue;
|
|
15202
|
+
event.preventDefault();
|
|
15203
|
+
hk.handler(event);
|
|
15204
|
+
return;
|
|
15205
|
+
}
|
|
15206
|
+
}
|
|
15207
|
+
ngOnDestroy() {
|
|
15208
|
+
document.removeEventListener('keydown', this.listener);
|
|
15209
|
+
}
|
|
15210
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctHotkeysService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
15211
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctHotkeysService, providedIn: 'root' });
|
|
15212
|
+
}
|
|
15213
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctHotkeysService, decorators: [{
|
|
15214
|
+
type: Injectable,
|
|
15215
|
+
args: [{ providedIn: 'root' }]
|
|
15216
|
+
}], ctorParameters: () => [] });
|
|
15217
|
+
function isTyping(event) {
|
|
15218
|
+
const el = event.target;
|
|
15219
|
+
if (!el)
|
|
15220
|
+
return false;
|
|
15221
|
+
return (el.tagName === 'INPUT' ||
|
|
15222
|
+
el.tagName === 'TEXTAREA' ||
|
|
15223
|
+
el.tagName === 'SELECT' ||
|
|
15224
|
+
el.isContentEditable);
|
|
15225
|
+
}
|
|
15226
|
+
/**
|
|
15227
|
+
* The hotkey cheatsheet overlay. Mount once near the app root; it registers
|
|
15228
|
+
* `?` itself and lists everything in the service, grouped:
|
|
15229
|
+
*
|
|
15230
|
+
* <strct-hotkeys-help />
|
|
15231
|
+
*/
|
|
15232
|
+
class StrctHotkeysHelp {
|
|
15233
|
+
service = inject(StrctHotkeysService);
|
|
15234
|
+
/** Overlay visibility (two-way; `?` toggles it too). */
|
|
15235
|
+
open = model(false, ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
|
|
15236
|
+
/** Localizable strings. */
|
|
15237
|
+
title = input('Keyboard shortcuts', ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
15238
|
+
emptyText = input('No shortcuts registered.', ...(ngDevMode ? [{ debugName: "emptyText" }] : /* istanbul ignore next */ []));
|
|
15239
|
+
grouped = computed(() => {
|
|
15240
|
+
const groups = new Map();
|
|
15241
|
+
for (const hk of this.service.hotkeys()) {
|
|
15242
|
+
const name = hk.group ?? 'General';
|
|
15243
|
+
const list = groups.get(name);
|
|
15244
|
+
if (list)
|
|
15245
|
+
list.push(hk);
|
|
15246
|
+
else
|
|
15247
|
+
groups.set(name, [hk]);
|
|
15248
|
+
}
|
|
15249
|
+
return [...groups.entries()].map(([name, keys]) => ({ name, keys }));
|
|
15250
|
+
}, ...(ngDevMode ? [{ debugName: "grouped" }] : /* istanbul ignore next */ []));
|
|
15251
|
+
constructor() {
|
|
15252
|
+
const dispose = this.service.register({
|
|
15253
|
+
combo: '?',
|
|
15254
|
+
description: 'Show this help',
|
|
15255
|
+
group: 'General',
|
|
15256
|
+
handler: () => this.open.update((v) => !v),
|
|
15257
|
+
});
|
|
15258
|
+
inject(DestroyRef).onDestroy(dispose);
|
|
15259
|
+
}
|
|
15260
|
+
// Escape is handled locally — registering it in the service would swallow
|
|
15261
|
+
// every Escape in the app (the service preventDefaults on match).
|
|
15262
|
+
onEscape() {
|
|
15263
|
+
if (this.open())
|
|
15264
|
+
this.open.set(false);
|
|
15265
|
+
}
|
|
15266
|
+
parts(combo) {
|
|
15267
|
+
return combo.split('+').map((p) => {
|
|
15268
|
+
const map = {
|
|
15269
|
+
mod: '⌘/Ctrl',
|
|
15270
|
+
meta: '⌘',
|
|
15271
|
+
ctrl: 'Ctrl',
|
|
15272
|
+
alt: 'Alt',
|
|
15273
|
+
shift: 'Shift',
|
|
15274
|
+
escape: 'Esc',
|
|
15275
|
+
};
|
|
15276
|
+
return map[p.trim().toLowerCase()] ?? p.trim().toUpperCase();
|
|
15277
|
+
});
|
|
15278
|
+
}
|
|
15279
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctHotkeysHelp, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
15280
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: StrctHotkeysHelp, isStandalone: true, selector: "strct-hotkeys-help", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange" }, host: { listeners: { "document:keydown.escape": "onEscape()" } }, ngImport: i0, template: `
|
|
15281
|
+
@if (open()) {
|
|
15282
|
+
<div class="strct-hkh__backdrop" (click)="open.set(false)"></div>
|
|
15283
|
+
<div class="strct-hkh" role="dialog" [attr.aria-label]="title()">
|
|
15284
|
+
<div class="strct-hkh__head">
|
|
15285
|
+
<span class="strct-hkh__title">{{ title() }}</span>
|
|
15286
|
+
<strct-kbd>?</strct-kbd>
|
|
15287
|
+
</div>
|
|
15288
|
+
@for (group of grouped(); track group.name) {
|
|
15289
|
+
<div class="strct-hkh__group">
|
|
15290
|
+
<div class="strct-hkh__groupname">{{ group.name }}</div>
|
|
15291
|
+
@for (hk of group.keys; track hk.combo + hk.description) {
|
|
15292
|
+
<div class="strct-hkh__row">
|
|
15293
|
+
<span class="strct-hkh__desc">{{ hk.description }}</span>
|
|
15294
|
+
<span class="strct-hkh__keys">
|
|
15295
|
+
@for (part of parts(hk.combo); track $index) {
|
|
15296
|
+
<strct-kbd>{{ part }}</strct-kbd>
|
|
15297
|
+
}
|
|
15298
|
+
</span>
|
|
15299
|
+
</div>
|
|
15300
|
+
}
|
|
15301
|
+
</div>
|
|
15302
|
+
} @empty {
|
|
15303
|
+
<p class="strct-hkh__empty">{{ emptyText() }}</p>
|
|
15304
|
+
}
|
|
15305
|
+
</div>
|
|
15306
|
+
}
|
|
15307
|
+
`, isInline: true, styles: [".strct-hkh__backdrop{position:fixed;inset:0;z-index:400;background:#00000073}.strct-hkh{position:fixed;z-index:401;top:50%;left:50%;transform:translate(-50%,-50%);width:min(420px,calc(100vw - 32px));max-height:min(70vh,520px);overflow:auto;padding:16px 18px;background:var(--bg-1);border:1px solid var(--b2);border-radius:10px;box-shadow:var(--shh)}.strct-hkh__head{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}.strct-hkh__title{font-size:13.5px;font-weight:650;color:var(--t1)}.strct-hkh__group{margin-bottom:12px}.strct-hkh__groupname{font-size:10.5px;font-weight:600;letter-spacing:.7px;text-transform:uppercase;color:var(--t3);margin-bottom:5px}.strct-hkh__row{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:4px 0}.strct-hkh__desc{font-size:12.5px;color:var(--t1)}.strct-hkh__keys{display:inline-flex;gap:4px}.strct-hkh__empty{font-size:12.5px;color:var(--t3)}\n"], dependencies: [{ kind: "component", type: StrctKbd, selector: "strct-kbd" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
15308
|
+
}
|
|
15309
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctHotkeysHelp, decorators: [{
|
|
15310
|
+
type: Component,
|
|
15311
|
+
args: [{ selector: 'strct-hotkeys-help', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [StrctKbd], template: `
|
|
15312
|
+
@if (open()) {
|
|
15313
|
+
<div class="strct-hkh__backdrop" (click)="open.set(false)"></div>
|
|
15314
|
+
<div class="strct-hkh" role="dialog" [attr.aria-label]="title()">
|
|
15315
|
+
<div class="strct-hkh__head">
|
|
15316
|
+
<span class="strct-hkh__title">{{ title() }}</span>
|
|
15317
|
+
<strct-kbd>?</strct-kbd>
|
|
15318
|
+
</div>
|
|
15319
|
+
@for (group of grouped(); track group.name) {
|
|
15320
|
+
<div class="strct-hkh__group">
|
|
15321
|
+
<div class="strct-hkh__groupname">{{ group.name }}</div>
|
|
15322
|
+
@for (hk of group.keys; track hk.combo + hk.description) {
|
|
15323
|
+
<div class="strct-hkh__row">
|
|
15324
|
+
<span class="strct-hkh__desc">{{ hk.description }}</span>
|
|
15325
|
+
<span class="strct-hkh__keys">
|
|
15326
|
+
@for (part of parts(hk.combo); track $index) {
|
|
15327
|
+
<strct-kbd>{{ part }}</strct-kbd>
|
|
15328
|
+
}
|
|
15329
|
+
</span>
|
|
15330
|
+
</div>
|
|
15331
|
+
}
|
|
15332
|
+
</div>
|
|
15333
|
+
} @empty {
|
|
15334
|
+
<p class="strct-hkh__empty">{{ emptyText() }}</p>
|
|
15335
|
+
}
|
|
15336
|
+
</div>
|
|
15337
|
+
}
|
|
15338
|
+
`, styles: [".strct-hkh__backdrop{position:fixed;inset:0;z-index:400;background:#00000073}.strct-hkh{position:fixed;z-index:401;top:50%;left:50%;transform:translate(-50%,-50%);width:min(420px,calc(100vw - 32px));max-height:min(70vh,520px);overflow:auto;padding:16px 18px;background:var(--bg-1);border:1px solid var(--b2);border-radius:10px;box-shadow:var(--shh)}.strct-hkh__head{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}.strct-hkh__title{font-size:13.5px;font-weight:650;color:var(--t1)}.strct-hkh__group{margin-bottom:12px}.strct-hkh__groupname{font-size:10.5px;font-weight:600;letter-spacing:.7px;text-transform:uppercase;color:var(--t3);margin-bottom:5px}.strct-hkh__row{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:4px 0}.strct-hkh__desc{font-size:12.5px;color:var(--t1)}.strct-hkh__keys{display:inline-flex;gap:4px}.strct-hkh__empty{font-size:12.5px;color:var(--t3)}\n"] }]
|
|
15339
|
+
}], ctorParameters: () => [], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], onEscape: [{
|
|
15340
|
+
type: HostListener,
|
|
15341
|
+
args: ['document:keydown.escape']
|
|
15342
|
+
}] } });
|
|
15343
|
+
|
|
15344
|
+
/**
|
|
15345
|
+
* List drag-reorder primitive — the consumer owns the array:
|
|
15346
|
+
*
|
|
15347
|
+
* <ul strctReorder (reordered)="move($event)">
|
|
15348
|
+
* @for (s of steps(); track s.id) {
|
|
15349
|
+
* <li strctReorderItem>{{ s.label }}</li>
|
|
15350
|
+
* }
|
|
15351
|
+
* </ul>
|
|
15352
|
+
*
|
|
15353
|
+
* move({ from, to }: StrctReorderEvent) {
|
|
15354
|
+
* this.steps.update((s) => { const c = [...s]; c.splice(to, 0, ...c.splice(from, 1)); return c; });
|
|
15355
|
+
* }
|
|
15356
|
+
*
|
|
15357
|
+
* Items are HTML5-draggable; keyboard reorder is Alt+ArrowUp / Alt+ArrowDown
|
|
15358
|
+
* on the focused item (items get `tabindex="0"` unless they already manage
|
|
15359
|
+
* focus). Indexes are positions among the `strctReorderItem` siblings.
|
|
15360
|
+
*/
|
|
15361
|
+
class StrctReorder {
|
|
15362
|
+
host = inject(ElementRef);
|
|
15363
|
+
/** Emits when a drag or keyboard move completes. */
|
|
15364
|
+
reordered = output();
|
|
15365
|
+
/** Disable all reordering (display-only mode). */
|
|
15366
|
+
reorderDisabled = input(false, ...(ngDevMode ? [{ debugName: "reorderDisabled" }] : /* istanbul ignore next */ []));
|
|
15367
|
+
dragIndex = signal(null, ...(ngDevMode ? [{ debugName: "dragIndex" }] : /* istanbul ignore next */ []));
|
|
15368
|
+
overIndex = signal(null, ...(ngDevMode ? [{ debugName: "overIndex" }] : /* istanbul ignore next */ []));
|
|
15369
|
+
items() {
|
|
15370
|
+
return [...this.host.nativeElement.querySelectorAll('[strctReorderItem]')];
|
|
15371
|
+
}
|
|
15372
|
+
indexOf(el) {
|
|
15373
|
+
return this.items().indexOf(el);
|
|
15374
|
+
}
|
|
15375
|
+
commit(from, to) {
|
|
15376
|
+
this.dragIndex.set(null);
|
|
15377
|
+
this.overIndex.set(null);
|
|
15378
|
+
if (from < 0 || to < 0 || from === to)
|
|
15379
|
+
return;
|
|
15380
|
+
this.reordered.emit({ from, to });
|
|
15381
|
+
}
|
|
15382
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctReorder, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
15383
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.16", type: StrctReorder, isStandalone: true, selector: "[strctReorder]", inputs: { reorderDisabled: { classPropertyName: "reorderDisabled", publicName: "reorderDisabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { reordered: "reordered" }, ngImport: i0 });
|
|
15384
|
+
}
|
|
15385
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctReorder, decorators: [{
|
|
15386
|
+
type: Directive,
|
|
15387
|
+
args: [{ selector: '[strctReorder]' }]
|
|
15388
|
+
}], propDecorators: { reordered: [{ type: i0.Output, args: ["reordered"] }], reorderDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "reorderDisabled", required: false }] }] } });
|
|
15389
|
+
/** One draggable row inside a `[strctReorder]` container. */
|
|
15390
|
+
class StrctReorderItem {
|
|
15391
|
+
list = inject(StrctReorder);
|
|
15392
|
+
el = inject(ElementRef);
|
|
15393
|
+
// Captured before our own binding ever writes tabindex, so it never flips.
|
|
15394
|
+
hadOwnTabindex = this.el.nativeElement.hasAttribute('tabindex');
|
|
15395
|
+
hostTabindex() {
|
|
15396
|
+
return this.hadOwnTabindex ? null : 0;
|
|
15397
|
+
}
|
|
15398
|
+
isDragging() {
|
|
15399
|
+
return this.list.dragIndex() === this.index();
|
|
15400
|
+
}
|
|
15401
|
+
isOver() {
|
|
15402
|
+
return this.list.overIndex() === this.index() && this.list.dragIndex() !== this.index();
|
|
15403
|
+
}
|
|
15404
|
+
index() {
|
|
15405
|
+
return this.list.indexOf(this.el.nativeElement);
|
|
15406
|
+
}
|
|
15407
|
+
onDragStart(event) {
|
|
15408
|
+
if (this.list.reorderDisabled())
|
|
15409
|
+
return;
|
|
15410
|
+
this.list.dragIndex.set(this.index());
|
|
15411
|
+
event.dataTransfer?.setData('text/plain', String(this.index()));
|
|
15412
|
+
if (event.dataTransfer)
|
|
15413
|
+
event.dataTransfer.effectAllowed = 'move';
|
|
15414
|
+
}
|
|
15415
|
+
onDragOver(event) {
|
|
15416
|
+
if (this.list.dragIndex() == null)
|
|
15417
|
+
return;
|
|
15418
|
+
event.preventDefault();
|
|
15419
|
+
this.list.overIndex.set(this.index());
|
|
15420
|
+
}
|
|
15421
|
+
onDrop(event) {
|
|
15422
|
+
event.preventDefault();
|
|
15423
|
+
const from = this.list.dragIndex();
|
|
15424
|
+
if (from == null)
|
|
15425
|
+
return;
|
|
15426
|
+
this.list.commit(from, this.index());
|
|
15427
|
+
}
|
|
15428
|
+
onDragEnd() {
|
|
15429
|
+
this.list.dragIndex.set(null);
|
|
15430
|
+
this.list.overIndex.set(null);
|
|
15431
|
+
}
|
|
15432
|
+
onKeydown(event) {
|
|
15433
|
+
if (this.list.reorderDisabled() || !event.altKey)
|
|
15434
|
+
return;
|
|
15435
|
+
if (event.key !== 'ArrowUp' && event.key !== 'ArrowDown')
|
|
15436
|
+
return;
|
|
15437
|
+
event.preventDefault();
|
|
15438
|
+
const from = this.index();
|
|
15439
|
+
const to = event.key === 'ArrowUp' ? from - 1 : from + 1;
|
|
15440
|
+
if (to < 0 || to >= this.list.items().length)
|
|
15441
|
+
return;
|
|
15442
|
+
this.list.commit(from, to);
|
|
15443
|
+
// Keep focus on the moved item after the consumer re-renders.
|
|
15444
|
+
setTimeout(() => this.list.items()[to]?.focus());
|
|
15445
|
+
}
|
|
15446
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctReorderItem, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
15447
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.16", type: StrctReorderItem, isStandalone: true, selector: "[strctReorderItem]", host: { listeners: { "dragstart": "onDragStart($event)", "dragover": "onDragOver($event)", "drop": "onDrop($event)", "dragend": "onDragEnd()", "keydown": "onKeydown($event)" }, properties: { "attr.draggable": "!list.reorderDisabled()", "attr.tabindex": "hostTabindex()", "class.strct-reorder--dragging": "isDragging()", "class.strct-reorder--over": "isOver()", "attr.aria-roledescription": "'sortable'" } }, ngImport: i0 });
|
|
15448
|
+
}
|
|
15449
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctReorderItem, decorators: [{
|
|
15450
|
+
type: Directive,
|
|
15451
|
+
args: [{
|
|
15452
|
+
selector: '[strctReorderItem]',
|
|
15453
|
+
host: {
|
|
15454
|
+
'[attr.draggable]': '!list.reorderDisabled()',
|
|
15455
|
+
'[attr.tabindex]': 'hostTabindex()',
|
|
15456
|
+
'[class.strct-reorder--dragging]': 'isDragging()',
|
|
15457
|
+
'[class.strct-reorder--over]': 'isOver()',
|
|
15458
|
+
'[attr.aria-roledescription]': "'sortable'",
|
|
15459
|
+
},
|
|
15460
|
+
}]
|
|
15461
|
+
}], propDecorators: { onDragStart: [{
|
|
15462
|
+
type: HostListener,
|
|
15463
|
+
args: ['dragstart', ['$event']]
|
|
15464
|
+
}], onDragOver: [{
|
|
15465
|
+
type: HostListener,
|
|
15466
|
+
args: ['dragover', ['$event']]
|
|
15467
|
+
}], onDrop: [{
|
|
15468
|
+
type: HostListener,
|
|
15469
|
+
args: ['drop', ['$event']]
|
|
15470
|
+
}], onDragEnd: [{
|
|
15471
|
+
type: HostListener,
|
|
15472
|
+
args: ['dragend']
|
|
15473
|
+
}], onKeydown: [{
|
|
15474
|
+
type: HostListener,
|
|
15475
|
+
args: ['keydown', ['$event']]
|
|
15476
|
+
}] } });
|
|
15477
|
+
|
|
12762
15478
|
/**
|
|
12763
15479
|
* Public API surface of the strct UI library.
|
|
12764
15480
|
*/
|
|
@@ -12768,5 +15484,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
|
|
|
12768
15484
|
* Generated bundle index. Do not edit.
|
|
12769
15485
|
*/
|
|
12770
15486
|
|
|
12771
|
-
export { STRCT_ICONS, STRCT_ICON_GROUPS, STRCT_ICON_NAMES, STRCT_MASKS, STRCT_PALETTES, STRCT_RAW_ICONS, StrctAccordion, StrctAccordionPanel, StrctAlert, StrctAvatar, StrctBadge, StrctBreadcrumb, StrctBreadcrumbItem, StrctButton, StrctButtonGroup, StrctCard, StrctCardBlock, StrctCardFooter, StrctCardHeader, StrctCascadeHost, StrctCascadeNode, StrctCascadeSelect, StrctCellDef, StrctCellStatus, StrctChart, StrctCheckbox, StrctChips, StrctCode, StrctColorPicker, StrctCombobox, StrctCommandPalette, StrctContextMenu, StrctContextMenuTrigger, StrctCopy, StrctDatagrid, StrctDatagridActionBar, StrctDatepicker, StrctDesc, StrctDescriptionList, StrctDivider, StrctDonut, StrctDrawer, StrctDrawerFooter, StrctDropdown, StrctDropdownDivider, StrctDropdownItem, StrctEmptyState, StrctField, StrctFile, StrctFilterBar, StrctFlow, StrctFooter, StrctGauge, StrctHeader, StrctHero, StrctIcon, StrctInput, StrctInputMask, StrctInputOtp, StrctKbd, StrctKnob, StrctLogin, StrctMenuPanel, StrctMenuService, StrctMetricTile, StrctModal, StrctNav, StrctNavItem, StrctOverlay, StrctPageHeader, StrctPageHeaderActions, StrctPageHeaderCrumbs, StrctPagination, StrctPassword, StrctProgress, StrctRadio, StrctRadioGroup, StrctRail, StrctRange, StrctRating, StrctRowDetailDef, StrctSearchbox, StrctSectionMenu, StrctSegmented, StrctShell, StrctShellService, StrctSignpost, StrctSkeleton, StrctSparkline, StrctSpeedDial, StrctSpinner, StrctStack, StrctStackItem, StrctStep, StrctSubmenu, StrctTab, StrctTable, StrctTabs, StrctTag, StrctThemeService, StrctThemeSwitcher, StrctTimeline, StrctTimelineItem, StrctToastOutlet, StrctToastService, StrctToggle, StrctTooltip, StrctTree, StrctTreeNode, StrctVerticalNav, StrctWizard, registerStrctIcon, strctValidationIcon, strctValidationTone };
|
|
15487
|
+
export { STRCT_ICONS, STRCT_ICON_GROUPS, STRCT_ICON_NAMES, STRCT_MASKS, STRCT_PALETTES, STRCT_RAW_ICONS, STRCT_TIME_RANGE_PRESETS, StrctAccordion, StrctAccordionPanel, StrctAlert, StrctAnnouncer, StrctAvatar, StrctBadge, StrctBreadcrumb, StrctBreadcrumbItem, StrctButton, StrctButtonGroup, StrctBytesPipe, StrctCard, StrctCardBlock, StrctCardFooter, StrctCardHeader, StrctCascadeHost, StrctCascadeNode, StrctCascadeSelect, StrctCellDef, StrctCellStatus, StrctChart, StrctCheckbox, StrctChips, StrctCode, StrctColorPicker, StrctCombobox, StrctCommandPalette, StrctContextMenu, StrctContextMenuTrigger, StrctCopy, StrctDatagrid, StrctDatagridActionBar, StrctDatepicker, StrctDesc, StrctDescriptionList, StrctDiff, StrctDivider, StrctDonut, StrctDrawer, StrctDrawerFooter, StrctDropdown, StrctDropdownDivider, StrctDropdownItem, StrctDropdownTrigger, StrctDurationPipe, StrctEmptyState, StrctField, StrctFile, StrctFilterBar, StrctFlow, StrctFooter, StrctGauge, StrctHeader, StrctHero, StrctHotkeysHelp, StrctHotkeysService, StrctIcon, StrctInput, StrctInputMask, StrctInputOtp, StrctKbd, StrctKnob, StrctLogViewer, StrctLogin, StrctMenuPanel, StrctMenuService, StrctMenubar, StrctMetricTile, StrctModal, StrctNav, StrctNavItem, StrctOverlay, StrctPageHeader, StrctPageHeaderActions, StrctPageHeaderCrumbs, StrctPagination, StrctPassword, StrctProgress, StrctRadio, StrctRadioGroup, StrctRail, StrctRange, StrctRatePipe, StrctRating, StrctReorder, StrctReorderItem, StrctRowDetailDef, StrctSearchbox, StrctSectionMenu, StrctSegmented, StrctShell, StrctShellService, StrctSiPipe, StrctSignpost, StrctSkeleton, StrctSparkline, StrctSpeedDial, StrctSpinner, StrctSplitButton, StrctSplitter, StrctStack, StrctStackItem, StrctStep, StrctSubmenu, StrctTab, StrctTable, StrctTabs, StrctTag, StrctThemeService, StrctThemeSwitcher, StrctTimeRangePicker, StrctTimeline, StrctTimelineItem, StrctToastOutlet, StrctToastService, StrctToggle, StrctTooltip, StrctTour, StrctTransfer, StrctTree, StrctTreeNode, StrctVerticalNav, StrctWatermark, StrctWizard, parseAnsi, registerStrctIcon, strctComputeDiff, strctFormatBytes, strctFormatDuration, strctFormatRate, strctFormatSi, strctValidationIcon, strctValidationTone };
|
|
12772
15488
|
//# sourceMappingURL=akcelik-strct.mjs.map
|