@akcelik/strct 1.6.0 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/akcelik-strct.mjs +2830 -40
- 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 +736 -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,12 @@ 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',
|
|
8291
|
+
quickFilter: 'Quick filter',
|
|
8265
8292
|
};
|
|
8266
8293
|
/** Utility-column widths used when sticky columns are active (px). */
|
|
8267
8294
|
const UTIL_W = { detail: 36, expand: 36, sel: 40 };
|
|
@@ -8389,6 +8416,30 @@ class StrctDatagrid {
|
|
|
8389
8416
|
* groups. Paging is bypassed while grouped; not combinable with `virtual`.
|
|
8390
8417
|
*/
|
|
8391
8418
|
groupBy = input(null, ...(ngDevMode ? [{ debugName: "groupBy" }] : /* istanbul ignore next */ []));
|
|
8419
|
+
/**
|
|
8420
|
+
* Two-way per-column filter state: `key → contains-text` (text filter) or
|
|
8421
|
+
* `key → unknown[]` (checked value set). Applied client-side; in `lazy`
|
|
8422
|
+
* mode it is passed through on `lazyLoad` instead.
|
|
8423
|
+
*/
|
|
8424
|
+
filters = model({}, ...(ngDevMode ? [{ debugName: "filters" }] : /* istanbul ignore next */ []));
|
|
8425
|
+
/**
|
|
8426
|
+
* Global quick filter (two-way): one term substring-matched (OR) across
|
|
8427
|
+
* `quickFilterFields` — the console-standard "filter this list fast" box.
|
|
8428
|
+
* Complementary to the per-column `filters` (which AND). Applied before
|
|
8429
|
+
* paging client-side; passed through on `lazyLoad` in server mode.
|
|
8430
|
+
*/
|
|
8431
|
+
quickFilter = model('', ...(ngDevMode ? [{ debugName: "quickFilter" }] : /* istanbul ignore next */ []));
|
|
8432
|
+
/** Columns the quick term scans. Defaults to every column's `key`. */
|
|
8433
|
+
quickFilterFields = input(null, ...(ngDevMode ? [{ debugName: "quickFilterFields" }] : /* istanbul ignore next */ []));
|
|
8434
|
+
/** Render the built-in quick-filter searchbox in the toolbar. */
|
|
8435
|
+
quickFilterable = input(false, { ...(ngDevMode ? { debugName: "quickFilterable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
8436
|
+
/**
|
|
8437
|
+
* Tree-grid: the row property holding a row's children array. Rows render
|
|
8438
|
+
* hierarchically with indent + carets; sorting applies per sibling level;
|
|
8439
|
+
* an active filter shows matches with their ancestors, force-expanded.
|
|
8440
|
+
* Not combinable with `groupBy` or `lazy`.
|
|
8441
|
+
*/
|
|
8442
|
+
childrenKey = input(null, ...(ngDevMode ? [{ debugName: "childrenKey" }] : /* istanbul ignore next */ []));
|
|
8392
8443
|
/** Emitted when the selection changes. */
|
|
8393
8444
|
selectionChange = output();
|
|
8394
8445
|
/** Emitted when the refresh button is clicked. */
|
|
@@ -8397,6 +8448,9 @@ class StrctDatagrid {
|
|
|
8397
8448
|
rowAction = output();
|
|
8398
8449
|
/** Server-side data request (lazy mode): load this page / sort and set `rows`. */
|
|
8399
8450
|
lazyLoad = output();
|
|
8451
|
+
/** An editable cell was committed (Enter / blur). The grid does not mutate
|
|
8452
|
+
* rows — apply `value` to your data and pass the updated array back in. */
|
|
8453
|
+
cellEdit = output();
|
|
8400
8454
|
menu = inject(StrctMenuService);
|
|
8401
8455
|
detailDef = contentChild(StrctRowDetailDef, ...(ngDevMode ? [{ debugName: "detailDef" }] : /* istanbul ignore next */ []));
|
|
8402
8456
|
actionBarDef = contentChild(StrctDatagridActionBar, ...(ngDevMode ? [{ debugName: "actionBarDef" }] : /* istanbul ignore next */ []));
|
|
@@ -8493,6 +8547,11 @@ class StrctDatagrid {
|
|
|
8493
8547
|
dragKey = signal(null, ...(ngDevMode ? [{ debugName: "dragKey" }] : /* istanbul ignore next */ []));
|
|
8494
8548
|
dropKey = signal(null, ...(ngDevMode ? [{ debugName: "dropKey" }] : /* istanbul ignore next */ []));
|
|
8495
8549
|
onColDragStart(key, event) {
|
|
8550
|
+
// Text selection inside the filter panel must not start a column drag.
|
|
8551
|
+
if (event.target?.closest?.('.strct-dg__filterpanel')) {
|
|
8552
|
+
event.preventDefault();
|
|
8553
|
+
return;
|
|
8554
|
+
}
|
|
8496
8555
|
if (!this.reorderable())
|
|
8497
8556
|
return;
|
|
8498
8557
|
this.dragKey.set(key);
|
|
@@ -8562,12 +8621,171 @@ class StrctDatagrid {
|
|
|
8562
8621
|
itemKey(it) {
|
|
8563
8622
|
return it.group ? `strct-group:${String(it.group.key)}` : this.rowKey(it.row);
|
|
8564
8623
|
}
|
|
8624
|
+
// ── Column filter popover ──────────────────────────────────────
|
|
8625
|
+
filterOpenKey = signal(null, ...(ngDevMode ? [{ debugName: "filterOpenKey" }] : /* istanbul ignore next */ []));
|
|
8626
|
+
toggleFilterPanel(key, event) {
|
|
8627
|
+
event.stopPropagation();
|
|
8628
|
+
this.filterOpenKey.set(this.filterOpenKey() === key ? null : key);
|
|
8629
|
+
}
|
|
8630
|
+
hasFilter(key) {
|
|
8631
|
+
const v = this.filters()[key];
|
|
8632
|
+
return Array.isArray(v) ? v.length > 0 : v != null && String(v).trim() !== '';
|
|
8633
|
+
}
|
|
8634
|
+
textFilter(key) {
|
|
8635
|
+
const v = this.filters()[key];
|
|
8636
|
+
return typeof v === 'string' ? v : '';
|
|
8637
|
+
}
|
|
8638
|
+
setTextFilter(key, value) {
|
|
8639
|
+
this.filters.update((f) => ({ ...f, [key]: value }));
|
|
8640
|
+
}
|
|
8641
|
+
optionChecked(key, opt) {
|
|
8642
|
+
const v = this.filters()[key];
|
|
8643
|
+
return Array.isArray(v) && v.includes(opt);
|
|
8644
|
+
}
|
|
8645
|
+
toggleFilterOption(key, opt) {
|
|
8646
|
+
this.filters.update((f) => {
|
|
8647
|
+
const cur = Array.isArray(f[key]) ? [...f[key]] : [];
|
|
8648
|
+
const i = cur.indexOf(opt);
|
|
8649
|
+
if (i >= 0)
|
|
8650
|
+
cur.splice(i, 1);
|
|
8651
|
+
else
|
|
8652
|
+
cur.push(opt);
|
|
8653
|
+
return { ...f, [key]: cur };
|
|
8654
|
+
});
|
|
8655
|
+
}
|
|
8656
|
+
clearFilter(key) {
|
|
8657
|
+
this.filters.update((f) => {
|
|
8658
|
+
const next = { ...f };
|
|
8659
|
+
delete next[key];
|
|
8660
|
+
return next;
|
|
8661
|
+
});
|
|
8662
|
+
}
|
|
8663
|
+
onDocClickFilter(event) {
|
|
8664
|
+
if (!this.filterOpenKey())
|
|
8665
|
+
return;
|
|
8666
|
+
const t = event.target;
|
|
8667
|
+
if (t.closest('.strct-dg__filterpanel') || t.closest('.strct-dg__filterbtn'))
|
|
8668
|
+
return;
|
|
8669
|
+
this.filterOpenKey.set(null);
|
|
8670
|
+
}
|
|
8671
|
+
onEscapeFilter() {
|
|
8672
|
+
this.filterOpenKey.set(null);
|
|
8673
|
+
}
|
|
8674
|
+
// ── Tree-grid state ────────────────────────────────────────────
|
|
8675
|
+
expandedTree = signal(new Set(), ...(ngDevMode ? [{ debugName: "expandedTree" }] : /* istanbul ignore next */ []));
|
|
8676
|
+
treeDepth(row) {
|
|
8677
|
+
return this.treeFlat().meta.get(this.rowKey(row))?.depth ?? 0;
|
|
8678
|
+
}
|
|
8679
|
+
treeHasChildren(row) {
|
|
8680
|
+
return this.treeFlat().meta.get(this.rowKey(row))?.hasChildren ?? false;
|
|
8681
|
+
}
|
|
8682
|
+
treeExpanded(row) {
|
|
8683
|
+
return this.treeFlat().meta.get(this.rowKey(row))?.expanded ?? false;
|
|
8684
|
+
}
|
|
8685
|
+
/** Expand / collapse one tree row. */
|
|
8686
|
+
toggleTreeRow(row) {
|
|
8687
|
+
const key = this.rowKey(row);
|
|
8688
|
+
const next = new Set(this.expandedTree());
|
|
8689
|
+
if (next.has(key))
|
|
8690
|
+
next.delete(key);
|
|
8691
|
+
else
|
|
8692
|
+
next.add(key);
|
|
8693
|
+
this.expandedTree.set(next);
|
|
8694
|
+
}
|
|
8695
|
+
// ── Inline cell editing ────────────────────────────────────────
|
|
8696
|
+
hostRef = inject(ElementRef);
|
|
8697
|
+
editing = signal(null, ...(ngDevMode ? [{ debugName: "editing" }] : /* istanbul ignore next */ []));
|
|
8698
|
+
isEditingCell(row, col) {
|
|
8699
|
+
const e = this.editing();
|
|
8700
|
+
return !!e && e.rowKey === this.rowKey(row) && e.colKey === col.key;
|
|
8701
|
+
}
|
|
8702
|
+
startEdit(row, col) {
|
|
8703
|
+
this.editing.set({ rowKey: this.rowKey(row), colKey: col.key });
|
|
8704
|
+
setTimeout(() => this.hostRef.nativeElement.querySelector('.strct-dg__editinput')?.focus());
|
|
8705
|
+
}
|
|
8706
|
+
commitEdit(row, col, event) {
|
|
8707
|
+
if (!this.isEditingCell(row, col))
|
|
8708
|
+
return; // Escape/Enter already closed it
|
|
8709
|
+
const value = event.target.value;
|
|
8710
|
+
this.editing.set(null);
|
|
8711
|
+
const previous = row[col.key];
|
|
8712
|
+
if (String(previous ?? '') !== value)
|
|
8713
|
+
this.cellEdit.emit({ row, column: col, value, previous });
|
|
8714
|
+
}
|
|
8715
|
+
cancelEdit(event) {
|
|
8716
|
+
event?.stopPropagation();
|
|
8717
|
+
this.editing.set(null);
|
|
8718
|
+
}
|
|
8719
|
+
/** Active (non-empty) filter entries. */
|
|
8720
|
+
activeFilters = computed(() => Object.entries(this.filters()).filter(([, v]) => Array.isArray(v) ? v.length > 0 : String(v).trim() !== ''), ...(ngDevMode ? [{ debugName: "activeFilters" }] : /* istanbul ignore next */ []));
|
|
8721
|
+
quickTerm = computed(() => this.quickFilter().trim().toLowerCase(), ...(ngDevMode ? [{ debugName: "quickTerm" }] : /* istanbul ignore next */ []));
|
|
8722
|
+
matchesQuick(row) {
|
|
8723
|
+
const term = this.quickTerm();
|
|
8724
|
+
if (!term)
|
|
8725
|
+
return true;
|
|
8726
|
+
const fields = this.quickFilterFields() ?? this.columns().map((c) => c.key);
|
|
8727
|
+
return fields.some((key) => String(row[key] ?? '')
|
|
8728
|
+
.toLowerCase()
|
|
8729
|
+
.includes(term));
|
|
8730
|
+
}
|
|
8731
|
+
matchesFilters(row) {
|
|
8732
|
+
return (this.matchesQuick(row) &&
|
|
8733
|
+
this.activeFilters().every(([key, v]) => Array.isArray(v)
|
|
8734
|
+
? v.some((opt) => opt === row[key])
|
|
8735
|
+
: String(row[key] ?? '')
|
|
8736
|
+
.toLowerCase()
|
|
8737
|
+
.includes(String(v).trim().toLowerCase())));
|
|
8738
|
+
}
|
|
8739
|
+
/** Rows surviving the quick + per-column filters (client-side modes only). */
|
|
8740
|
+
filteredRows = computed(() => {
|
|
8741
|
+
if (this.lazy() || (!this.activeFilters().length && !this.quickTerm()))
|
|
8742
|
+
return this.rows();
|
|
8743
|
+
return this.rows().filter((r) => this.matchesFilters(r));
|
|
8744
|
+
}, ...(ngDevMode ? [{ debugName: "filteredRows" }] : /* istanbul ignore next */ []));
|
|
8745
|
+
/** "12 / 480" — shown while any client-side filtering narrows the set. */
|
|
8746
|
+
filterNote = computed(() => {
|
|
8747
|
+
if (this.lazy() || (!this.activeFilters().length && !this.quickTerm()))
|
|
8748
|
+
return null;
|
|
8749
|
+
return `${this.sorted().length} / ${this.rows().length}`;
|
|
8750
|
+
}, ...(ngDevMode ? [{ debugName: "filterNote" }] : /* istanbul ignore next */ []));
|
|
8751
|
+
/** Tree mode: flattened visible rows + per-row depth/children/expanded meta. */
|
|
8752
|
+
treeFlat = computed(() => {
|
|
8753
|
+
const ck = this.childrenKey();
|
|
8754
|
+
const { key, dir } = this.sort();
|
|
8755
|
+
const sign = dir === 'asc' ? 1 : -1;
|
|
8756
|
+
const sortRows = (rs) => {
|
|
8757
|
+
const c = [...rs];
|
|
8758
|
+
if (key)
|
|
8759
|
+
c.sort((a, b) => sign * this.compare(a[key], b[key]));
|
|
8760
|
+
return c;
|
|
8761
|
+
};
|
|
8762
|
+
const filtering = this.activeFilters().length > 0 || this.quickTerm() !== '';
|
|
8763
|
+
const kidsOf = (r) => r[ck] ?? [];
|
|
8764
|
+
const survives = (r) => this.matchesFilters(r) || kidsOf(r).some(survives);
|
|
8765
|
+
const meta = new Map();
|
|
8766
|
+
const rowsOut = [];
|
|
8767
|
+
const walk = (rs, depth) => {
|
|
8768
|
+
for (const r of sortRows(rs)) {
|
|
8769
|
+
const kids = filtering ? kidsOf(r).filter(survives) : kidsOf(r);
|
|
8770
|
+
// Filtering force-expands so matches are never hidden under a fold.
|
|
8771
|
+
const expanded = filtering ? true : this.expandedTree().has(this.rowKey(r));
|
|
8772
|
+
meta.set(this.rowKey(r), { depth, hasChildren: kids.length > 0, expanded });
|
|
8773
|
+
rowsOut.push(r);
|
|
8774
|
+
if (kids.length && expanded)
|
|
8775
|
+
walk(kids, depth + 1);
|
|
8776
|
+
}
|
|
8777
|
+
};
|
|
8778
|
+
walk(filtering ? this.rows().filter(survives) : this.rows(), 0);
|
|
8779
|
+
return { rows: rowsOut, meta };
|
|
8780
|
+
}, ...(ngDevMode ? [{ debugName: "treeFlat" }] : /* istanbul ignore next */ []));
|
|
8565
8781
|
sorted = computed(() => {
|
|
8566
8782
|
// Server-side mode: rows arrive already ordered / sliced — never touch them.
|
|
8567
8783
|
if (this.lazy())
|
|
8568
8784
|
return this.rows();
|
|
8785
|
+
if (this.childrenKey())
|
|
8786
|
+
return this.treeFlat().rows;
|
|
8569
8787
|
const { key, dir } = this.sort();
|
|
8570
|
-
const data = [...this.
|
|
8788
|
+
const data = [...this.filteredRows()];
|
|
8571
8789
|
if (!key)
|
|
8572
8790
|
return data;
|
|
8573
8791
|
const sign = dir === 'asc' ? 1 : -1;
|
|
@@ -8689,6 +8907,12 @@ class StrctDatagrid {
|
|
|
8689
8907
|
if (this.page() > pageCount)
|
|
8690
8908
|
this.page.set(pageCount);
|
|
8691
8909
|
});
|
|
8910
|
+
// A changed filter always restarts from page 1.
|
|
8911
|
+
effect(() => {
|
|
8912
|
+
this.filters();
|
|
8913
|
+
this.quickTerm();
|
|
8914
|
+
untracked(() => this.page.set(1));
|
|
8915
|
+
});
|
|
8692
8916
|
// Seed the selection from initialSelection (re-runs only when the input changes,
|
|
8693
8917
|
// so a user's own toggles afterward are untouched).
|
|
8694
8918
|
effect(() => {
|
|
@@ -8707,6 +8931,8 @@ class StrctDatagrid {
|
|
|
8707
8931
|
pageSize: this.pageSize(),
|
|
8708
8932
|
sortKey: this.sort().key,
|
|
8709
8933
|
sortDir: this.sort().dir,
|
|
8934
|
+
filters: this.filters(),
|
|
8935
|
+
quickFilter: this.quickTerm(),
|
|
8710
8936
|
};
|
|
8711
8937
|
const key = JSON.stringify(state);
|
|
8712
8938
|
untracked(() => {
|
|
@@ -8953,9 +9179,23 @@ class StrctDatagrid {
|
|
|
8953
9179
|
return String(a ?? '').localeCompare(String(b ?? ''), undefined, { numeric: true });
|
|
8954
9180
|
}
|
|
8955
9181
|
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: `
|
|
8957
|
-
@if (actionBarDef()) {
|
|
8958
|
-
<div class="strct-dg__toolbar"
|
|
9182
|
+
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 }, quickFilter: { classPropertyName: "quickFilter", publicName: "quickFilter", isSignal: true, isRequired: false, transformFunction: null }, quickFilterFields: { classPropertyName: "quickFilterFields", publicName: "quickFilterFields", isSignal: true, isRequired: false, transformFunction: null }, quickFilterable: { classPropertyName: "quickFilterable", publicName: "quickFilterable", isSignal: true, isRequired: false, transformFunction: null }, childrenKey: { classPropertyName: "childrenKey", publicName: "childrenKey", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { columnState: "columnStateChange", filters: "filtersChange", quickFilter: "quickFilterChange", 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: `
|
|
9183
|
+
@if (actionBarDef() || quickFilterable()) {
|
|
9184
|
+
<div class="strct-dg__toolbar">
|
|
9185
|
+
@if (quickFilterable()) {
|
|
9186
|
+
<strct-searchbox
|
|
9187
|
+
class="strct-dg__quickfilter"
|
|
9188
|
+
[placeholder]="L().quickFilter"
|
|
9189
|
+
[ariaLabel]="L().quickFilter"
|
|
9190
|
+
[value]="quickFilter()"
|
|
9191
|
+
(valueChange)="quickFilter.set($event)"
|
|
9192
|
+
/>
|
|
9193
|
+
@if (filterNote(); as note) {
|
|
9194
|
+
<span class="strct-dg__filternote">{{ note }}</span>
|
|
9195
|
+
}
|
|
9196
|
+
}
|
|
9197
|
+
<ng-content select="[strctDatagridActionBar]" />
|
|
9198
|
+
</div>
|
|
8959
9199
|
}
|
|
8960
9200
|
|
|
8961
9201
|
<div class="strct-dg__layout" [class.strct-dg__layout--paned]="paneOpen()">
|
|
@@ -8967,7 +9207,7 @@ class StrctDatagrid {
|
|
|
8967
9207
|
[style.max-height.px]="virtual() ? viewportHeight() : null"
|
|
8968
9208
|
(scroll)="virtual() && onScroll($event)"
|
|
8969
9209
|
>
|
|
8970
|
-
<table class="strct-dg">
|
|
9210
|
+
<table class="strct-dg" [attr.role]="childrenKey() ? 'treegrid' : null">
|
|
8971
9211
|
<thead>
|
|
8972
9212
|
<tr>
|
|
8973
9213
|
@if (canDetail()) {
|
|
@@ -9027,6 +9267,62 @@ class StrctDatagrid {
|
|
|
9027
9267
|
[size]="13"
|
|
9028
9268
|
/>
|
|
9029
9269
|
}
|
|
9270
|
+
@if (col.filterable || col.filterOptions) {
|
|
9271
|
+
<button
|
|
9272
|
+
#fbtn
|
|
9273
|
+
type="button"
|
|
9274
|
+
class="strct-dg__filterbtn"
|
|
9275
|
+
[class.strct-dg__filterbtn--active]="hasFilter(col.key)"
|
|
9276
|
+
[attr.aria-expanded]="filterOpenKey() === col.key"
|
|
9277
|
+
[attr.aria-label]="L().filterColumn + ': ' + col.label"
|
|
9278
|
+
(click)="toggleFilterPanel(col.key, $event)"
|
|
9279
|
+
(keydown.enter)="$event.stopPropagation()"
|
|
9280
|
+
(keydown.space)="$event.stopPropagation()"
|
|
9281
|
+
>
|
|
9282
|
+
<strct-icon name="filter" [size]="12" />
|
|
9283
|
+
</button>
|
|
9284
|
+
@if (filterOpenKey() === col.key) {
|
|
9285
|
+
<div
|
|
9286
|
+
class="strct-dg__filterpanel"
|
|
9287
|
+
role="dialog"
|
|
9288
|
+
[attr.aria-label]="L().filterColumn + ': ' + col.label"
|
|
9289
|
+
[strctOverlay]="fbtn"
|
|
9290
|
+
strctOverlayPlacement="bottom-start"
|
|
9291
|
+
(click)="$event.stopPropagation()"
|
|
9292
|
+
(keydown.enter)="$event.stopPropagation()"
|
|
9293
|
+
(keydown.space)="$event.stopPropagation()"
|
|
9294
|
+
>
|
|
9295
|
+
@if (col.filterOptions; as opts) {
|
|
9296
|
+
@for (opt of opts; track $index) {
|
|
9297
|
+
<label class="strct-dg__filteropt">
|
|
9298
|
+
<input
|
|
9299
|
+
type="checkbox"
|
|
9300
|
+
[checked]="optionChecked(col.key, opt)"
|
|
9301
|
+
(change)="toggleFilterOption(col.key, opt)"
|
|
9302
|
+
/>
|
|
9303
|
+
<span>{{ opt }}</span>
|
|
9304
|
+
</label>
|
|
9305
|
+
}
|
|
9306
|
+
} @else {
|
|
9307
|
+
<input
|
|
9308
|
+
class="strct-dg__filterinput"
|
|
9309
|
+
type="text"
|
|
9310
|
+
[attr.placeholder]="L().filterPlaceholder"
|
|
9311
|
+
[value]="textFilter(col.key)"
|
|
9312
|
+
(input)="setTextFilter(col.key, $any($event.target).value)"
|
|
9313
|
+
/>
|
|
9314
|
+
}
|
|
9315
|
+
<button
|
|
9316
|
+
type="button"
|
|
9317
|
+
class="strct-dg__filterclear"
|
|
9318
|
+
[disabled]="!hasFilter(col.key)"
|
|
9319
|
+
(click)="clearFilter(col.key)"
|
|
9320
|
+
>
|
|
9321
|
+
{{ L().clearFilter }}
|
|
9322
|
+
</button>
|
|
9323
|
+
</div>
|
|
9324
|
+
}
|
|
9325
|
+
}
|
|
9030
9326
|
</span>
|
|
9031
9327
|
@if (resizable() && col.resizable !== false) {
|
|
9032
9328
|
<div
|
|
@@ -9097,6 +9393,10 @@ class StrctDatagrid {
|
|
|
9097
9393
|
<tr
|
|
9098
9394
|
[class.strct-dg__row--selected]="isSelected(row)"
|
|
9099
9395
|
[class.strct-dg__row--active]="paneOpen() && row === activeRow()"
|
|
9396
|
+
[attr.aria-level]="childrenKey() ? treeDepth(row) + 1 : null"
|
|
9397
|
+
[attr.aria-expanded]="
|
|
9398
|
+
childrenKey() && treeHasChildren(row) ? treeExpanded(row) : null
|
|
9399
|
+
"
|
|
9100
9400
|
>
|
|
9101
9401
|
@if (canDetail()) {
|
|
9102
9402
|
<td
|
|
@@ -9147,14 +9447,51 @@ class StrctDatagrid {
|
|
|
9147
9447
|
/>
|
|
9148
9448
|
</td>
|
|
9149
9449
|
}
|
|
9150
|
-
@for (col of visibleColumns(); track col.key) {
|
|
9450
|
+
@for (col of visibleColumns(); track col.key; let colIdx = $index) {
|
|
9151
9451
|
<td
|
|
9152
9452
|
[style.text-align]="col.align ?? 'start'"
|
|
9153
9453
|
[class.strct-dg__cell--sticky]="isSticky(col)"
|
|
9154
9454
|
[class.strct-dg__cell--sticky-last]="col.key === lastStickyKey()"
|
|
9455
|
+
[class.strct-dg__cell--editable]="col.editable"
|
|
9155
9456
|
[style.insetInlineStart.px]="stickyLeft(col.key)"
|
|
9457
|
+
(dblclick)="col.editable && startEdit(row, col)"
|
|
9156
9458
|
>
|
|
9157
|
-
@if (
|
|
9459
|
+
@if (childrenKey() && colIdx === 0) {
|
|
9460
|
+
<span
|
|
9461
|
+
class="strct-dg__treepad"
|
|
9462
|
+
[style.width.px]="treeDepth(row) * 18"
|
|
9463
|
+
aria-hidden="true"
|
|
9464
|
+
></span>
|
|
9465
|
+
@if (treeHasChildren(row)) {
|
|
9466
|
+
<button
|
|
9467
|
+
type="button"
|
|
9468
|
+
class="strct-dg__treebtn"
|
|
9469
|
+
[class.strct-dg__treebtn--open]="treeExpanded(row)"
|
|
9470
|
+
[attr.aria-expanded]="treeExpanded(row)"
|
|
9471
|
+
[attr.aria-label]="L().toggleChildren"
|
|
9472
|
+
(click)="toggleTreeRow(row)"
|
|
9473
|
+
>
|
|
9474
|
+
<strct-icon name="chevronRight" [size]="12" [strokeWidth]="1.7" />
|
|
9475
|
+
</button>
|
|
9476
|
+
} @else {
|
|
9477
|
+
<span
|
|
9478
|
+
class="strct-dg__treebtn strct-dg__treebtn--leaf"
|
|
9479
|
+
aria-hidden="true"
|
|
9480
|
+
></span>
|
|
9481
|
+
}
|
|
9482
|
+
}
|
|
9483
|
+
@if (isEditingCell(row, col)) {
|
|
9484
|
+
<input
|
|
9485
|
+
class="strct-dg__editinput"
|
|
9486
|
+
type="text"
|
|
9487
|
+
[value]="row[col.key] ?? ''"
|
|
9488
|
+
[attr.aria-label]="L().editCell + ': ' + col.label"
|
|
9489
|
+
(keydown.enter)="commitEdit(row, col, $event)"
|
|
9490
|
+
(keydown.escape)="cancelEdit($event)"
|
|
9491
|
+
(blur)="commitEdit(row, col, $event)"
|
|
9492
|
+
(click)="$event.stopPropagation()"
|
|
9493
|
+
/>
|
|
9494
|
+
} @else if (cellTemplate(col.key); as tpl) {
|
|
9158
9495
|
<ng-container
|
|
9159
9496
|
[ngTemplateOutlet]="tpl"
|
|
9160
9497
|
[ngTemplateOutletContext]="{
|
|
@@ -9292,7 +9629,7 @@ class StrctDatagrid {
|
|
|
9292
9629
|
</div>
|
|
9293
9630
|
</div>
|
|
9294
9631
|
}
|
|
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 });
|
|
9632
|
+
`, 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__quickfilter{min-width:180px;max-width:260px}.strct-dg__filternote{font-size:11.5px;color:var(--t3);font-variant-numeric:tabular-nums;white-space:nowrap}.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"] }, { 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 });
|
|
9296
9633
|
}
|
|
9297
9634
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctDatagrid, decorators: [{
|
|
9298
9635
|
type: Component,
|
|
@@ -9303,9 +9640,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
|
|
|
9303
9640
|
StrctCheckbox,
|
|
9304
9641
|
StrctButton,
|
|
9305
9642
|
StrctButtonGroup,
|
|
9643
|
+
StrctOverlay,
|
|
9644
|
+
StrctSearchbox,
|
|
9306
9645
|
], template: `
|
|
9307
|
-
@if (actionBarDef()) {
|
|
9308
|
-
<div class="strct-dg__toolbar"
|
|
9646
|
+
@if (actionBarDef() || quickFilterable()) {
|
|
9647
|
+
<div class="strct-dg__toolbar">
|
|
9648
|
+
@if (quickFilterable()) {
|
|
9649
|
+
<strct-searchbox
|
|
9650
|
+
class="strct-dg__quickfilter"
|
|
9651
|
+
[placeholder]="L().quickFilter"
|
|
9652
|
+
[ariaLabel]="L().quickFilter"
|
|
9653
|
+
[value]="quickFilter()"
|
|
9654
|
+
(valueChange)="quickFilter.set($event)"
|
|
9655
|
+
/>
|
|
9656
|
+
@if (filterNote(); as note) {
|
|
9657
|
+
<span class="strct-dg__filternote">{{ note }}</span>
|
|
9658
|
+
}
|
|
9659
|
+
}
|
|
9660
|
+
<ng-content select="[strctDatagridActionBar]" />
|
|
9661
|
+
</div>
|
|
9309
9662
|
}
|
|
9310
9663
|
|
|
9311
9664
|
<div class="strct-dg__layout" [class.strct-dg__layout--paned]="paneOpen()">
|
|
@@ -9317,7 +9670,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
|
|
|
9317
9670
|
[style.max-height.px]="virtual() ? viewportHeight() : null"
|
|
9318
9671
|
(scroll)="virtual() && onScroll($event)"
|
|
9319
9672
|
>
|
|
9320
|
-
<table class="strct-dg">
|
|
9673
|
+
<table class="strct-dg" [attr.role]="childrenKey() ? 'treegrid' : null">
|
|
9321
9674
|
<thead>
|
|
9322
9675
|
<tr>
|
|
9323
9676
|
@if (canDetail()) {
|
|
@@ -9377,6 +9730,62 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
|
|
|
9377
9730
|
[size]="13"
|
|
9378
9731
|
/>
|
|
9379
9732
|
}
|
|
9733
|
+
@if (col.filterable || col.filterOptions) {
|
|
9734
|
+
<button
|
|
9735
|
+
#fbtn
|
|
9736
|
+
type="button"
|
|
9737
|
+
class="strct-dg__filterbtn"
|
|
9738
|
+
[class.strct-dg__filterbtn--active]="hasFilter(col.key)"
|
|
9739
|
+
[attr.aria-expanded]="filterOpenKey() === col.key"
|
|
9740
|
+
[attr.aria-label]="L().filterColumn + ': ' + col.label"
|
|
9741
|
+
(click)="toggleFilterPanel(col.key, $event)"
|
|
9742
|
+
(keydown.enter)="$event.stopPropagation()"
|
|
9743
|
+
(keydown.space)="$event.stopPropagation()"
|
|
9744
|
+
>
|
|
9745
|
+
<strct-icon name="filter" [size]="12" />
|
|
9746
|
+
</button>
|
|
9747
|
+
@if (filterOpenKey() === col.key) {
|
|
9748
|
+
<div
|
|
9749
|
+
class="strct-dg__filterpanel"
|
|
9750
|
+
role="dialog"
|
|
9751
|
+
[attr.aria-label]="L().filterColumn + ': ' + col.label"
|
|
9752
|
+
[strctOverlay]="fbtn"
|
|
9753
|
+
strctOverlayPlacement="bottom-start"
|
|
9754
|
+
(click)="$event.stopPropagation()"
|
|
9755
|
+
(keydown.enter)="$event.stopPropagation()"
|
|
9756
|
+
(keydown.space)="$event.stopPropagation()"
|
|
9757
|
+
>
|
|
9758
|
+
@if (col.filterOptions; as opts) {
|
|
9759
|
+
@for (opt of opts; track $index) {
|
|
9760
|
+
<label class="strct-dg__filteropt">
|
|
9761
|
+
<input
|
|
9762
|
+
type="checkbox"
|
|
9763
|
+
[checked]="optionChecked(col.key, opt)"
|
|
9764
|
+
(change)="toggleFilterOption(col.key, opt)"
|
|
9765
|
+
/>
|
|
9766
|
+
<span>{{ opt }}</span>
|
|
9767
|
+
</label>
|
|
9768
|
+
}
|
|
9769
|
+
} @else {
|
|
9770
|
+
<input
|
|
9771
|
+
class="strct-dg__filterinput"
|
|
9772
|
+
type="text"
|
|
9773
|
+
[attr.placeholder]="L().filterPlaceholder"
|
|
9774
|
+
[value]="textFilter(col.key)"
|
|
9775
|
+
(input)="setTextFilter(col.key, $any($event.target).value)"
|
|
9776
|
+
/>
|
|
9777
|
+
}
|
|
9778
|
+
<button
|
|
9779
|
+
type="button"
|
|
9780
|
+
class="strct-dg__filterclear"
|
|
9781
|
+
[disabled]="!hasFilter(col.key)"
|
|
9782
|
+
(click)="clearFilter(col.key)"
|
|
9783
|
+
>
|
|
9784
|
+
{{ L().clearFilter }}
|
|
9785
|
+
</button>
|
|
9786
|
+
</div>
|
|
9787
|
+
}
|
|
9788
|
+
}
|
|
9380
9789
|
</span>
|
|
9381
9790
|
@if (resizable() && col.resizable !== false) {
|
|
9382
9791
|
<div
|
|
@@ -9447,6 +9856,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
|
|
|
9447
9856
|
<tr
|
|
9448
9857
|
[class.strct-dg__row--selected]="isSelected(row)"
|
|
9449
9858
|
[class.strct-dg__row--active]="paneOpen() && row === activeRow()"
|
|
9859
|
+
[attr.aria-level]="childrenKey() ? treeDepth(row) + 1 : null"
|
|
9860
|
+
[attr.aria-expanded]="
|
|
9861
|
+
childrenKey() && treeHasChildren(row) ? treeExpanded(row) : null
|
|
9862
|
+
"
|
|
9450
9863
|
>
|
|
9451
9864
|
@if (canDetail()) {
|
|
9452
9865
|
<td
|
|
@@ -9497,14 +9910,51 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
|
|
|
9497
9910
|
/>
|
|
9498
9911
|
</td>
|
|
9499
9912
|
}
|
|
9500
|
-
@for (col of visibleColumns(); track col.key) {
|
|
9913
|
+
@for (col of visibleColumns(); track col.key; let colIdx = $index) {
|
|
9501
9914
|
<td
|
|
9502
9915
|
[style.text-align]="col.align ?? 'start'"
|
|
9503
9916
|
[class.strct-dg__cell--sticky]="isSticky(col)"
|
|
9504
9917
|
[class.strct-dg__cell--sticky-last]="col.key === lastStickyKey()"
|
|
9918
|
+
[class.strct-dg__cell--editable]="col.editable"
|
|
9505
9919
|
[style.insetInlineStart.px]="stickyLeft(col.key)"
|
|
9920
|
+
(dblclick)="col.editable && startEdit(row, col)"
|
|
9506
9921
|
>
|
|
9507
|
-
@if (
|
|
9922
|
+
@if (childrenKey() && colIdx === 0) {
|
|
9923
|
+
<span
|
|
9924
|
+
class="strct-dg__treepad"
|
|
9925
|
+
[style.width.px]="treeDepth(row) * 18"
|
|
9926
|
+
aria-hidden="true"
|
|
9927
|
+
></span>
|
|
9928
|
+
@if (treeHasChildren(row)) {
|
|
9929
|
+
<button
|
|
9930
|
+
type="button"
|
|
9931
|
+
class="strct-dg__treebtn"
|
|
9932
|
+
[class.strct-dg__treebtn--open]="treeExpanded(row)"
|
|
9933
|
+
[attr.aria-expanded]="treeExpanded(row)"
|
|
9934
|
+
[attr.aria-label]="L().toggleChildren"
|
|
9935
|
+
(click)="toggleTreeRow(row)"
|
|
9936
|
+
>
|
|
9937
|
+
<strct-icon name="chevronRight" [size]="12" [strokeWidth]="1.7" />
|
|
9938
|
+
</button>
|
|
9939
|
+
} @else {
|
|
9940
|
+
<span
|
|
9941
|
+
class="strct-dg__treebtn strct-dg__treebtn--leaf"
|
|
9942
|
+
aria-hidden="true"
|
|
9943
|
+
></span>
|
|
9944
|
+
}
|
|
9945
|
+
}
|
|
9946
|
+
@if (isEditingCell(row, col)) {
|
|
9947
|
+
<input
|
|
9948
|
+
class="strct-dg__editinput"
|
|
9949
|
+
type="text"
|
|
9950
|
+
[value]="row[col.key] ?? ''"
|
|
9951
|
+
[attr.aria-label]="L().editCell + ': ' + col.label"
|
|
9952
|
+
(keydown.enter)="commitEdit(row, col, $event)"
|
|
9953
|
+
(keydown.escape)="cancelEdit($event)"
|
|
9954
|
+
(blur)="commitEdit(row, col, $event)"
|
|
9955
|
+
(click)="$event.stopPropagation()"
|
|
9956
|
+
/>
|
|
9957
|
+
} @else if (cellTemplate(col.key); as tpl) {
|
|
9508
9958
|
<ng-container
|
|
9509
9959
|
[ngTemplateOutlet]="tpl"
|
|
9510
9960
|
[ngTemplateOutletContext]="{
|
|
@@ -9648,8 +10098,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
|
|
|
9648
10098
|
'[class.strct-dg-host--singleline]': 'singleLine()',
|
|
9649
10099
|
'[class.strct-dg-host--virtual]': 'virtual()',
|
|
9650
10100
|
'[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 }] }]
|
|
10101
|
+
}, 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__quickfilter{min-width:180px;max-width:260px}.strct-dg__filternote{font-size:11.5px;color:var(--t3);font-variant-numeric:tabular-nums;white-space:nowrap}.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"] }]
|
|
10102
|
+
}], 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"] }], quickFilter: [{ type: i0.Input, args: [{ isSignal: true, alias: "quickFilter", required: false }] }, { type: i0.Output, args: ["quickFilterChange"] }], quickFilterFields: [{ type: i0.Input, args: [{ isSignal: true, alias: "quickFilterFields", required: false }] }], quickFilterable: [{ type: i0.Input, args: [{ isSignal: true, alias: "quickFilterable", required: false }] }], 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: [{
|
|
10103
|
+
type: HostListener,
|
|
10104
|
+
args: ['document:click', ['$event']]
|
|
10105
|
+
}], onEscapeFilter: [{
|
|
10106
|
+
type: HostListener,
|
|
10107
|
+
args: ['document:keydown.escape']
|
|
10108
|
+
}] } });
|
|
9653
10109
|
|
|
9654
10110
|
/** Vertical timeline container. Wraps `<strct-timeline-item>` children. */
|
|
9655
10111
|
class StrctTimeline {
|
|
@@ -12759,6 +13215,2340 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
|
|
|
12759
13215
|
`, 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
13216
|
}], 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
13217
|
|
|
13218
|
+
/**
|
|
13219
|
+
* Unit formatting for infrastructure consoles — bytes, rates, durations and
|
|
13220
|
+
* SI magnitudes, as pure functions plus template pipes. The functions are the
|
|
13221
|
+
* API charts / grids can call from TS; the pipes are the template sugar:
|
|
13222
|
+
*
|
|
13223
|
+
* {{ vm.memBytes | strctBytes }} → 1.5 GiB
|
|
13224
|
+
* {{ nic.rxBps | strctRate }} → 2.4 Gbit/s
|
|
13225
|
+
* {{ task.elapsedMs | strctDuration }} → 2h 14m
|
|
13226
|
+
* {{ cluster.iops | strctSi : 'IOPS' }} → 12.4k IOPS
|
|
13227
|
+
*/
|
|
13228
|
+
const BINARY_UNITS = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB'];
|
|
13229
|
+
const DECIMAL_UNITS = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB'];
|
|
13230
|
+
const SI_SUFFIXES = ['', 'k', 'M', 'G', 'T', 'P'];
|
|
13231
|
+
function trimZeros(v) {
|
|
13232
|
+
return v.includes('.') ? v.replace(/\.?0+$/, '') : v;
|
|
13233
|
+
}
|
|
13234
|
+
/**
|
|
13235
|
+
* Format a byte count. Binary (KiB, 1024) by default — the storage/memory
|
|
13236
|
+
* convention consoles actually mean; pass `binary: false` for decimal (kB).
|
|
13237
|
+
*/
|
|
13238
|
+
function strctFormatBytes(bytes, options = {}) {
|
|
13239
|
+
if (!Number.isFinite(bytes))
|
|
13240
|
+
return '—';
|
|
13241
|
+
const { binary = true, digits = 1 } = options;
|
|
13242
|
+
const base = binary ? 1024 : 1000;
|
|
13243
|
+
const units = binary ? BINARY_UNITS : DECIMAL_UNITS;
|
|
13244
|
+
const neg = bytes < 0 ? '-' : '';
|
|
13245
|
+
let v = Math.abs(bytes);
|
|
13246
|
+
let i = 0;
|
|
13247
|
+
while (v >= base && i < units.length - 1) {
|
|
13248
|
+
v /= base;
|
|
13249
|
+
i++;
|
|
13250
|
+
}
|
|
13251
|
+
return `${neg}${trimZeros(v.toFixed(i === 0 ? 0 : digits))} ${units[i]}`;
|
|
13252
|
+
}
|
|
13253
|
+
/**
|
|
13254
|
+
* Format a data rate from bits per second (network convention). Pass bytes/s
|
|
13255
|
+
* through `strctFormatBytes(...) + '/s'` instead if you mean throughput.
|
|
13256
|
+
*/
|
|
13257
|
+
function strctFormatRate(bitsPerSecond, options = {}) {
|
|
13258
|
+
if (!Number.isFinite(bitsPerSecond))
|
|
13259
|
+
return '—';
|
|
13260
|
+
const { digits = 1 } = options;
|
|
13261
|
+
const units = ['bit/s', 'kbit/s', 'Mbit/s', 'Gbit/s', 'Tbit/s'];
|
|
13262
|
+
const neg = bitsPerSecond < 0 ? '-' : '';
|
|
13263
|
+
let v = Math.abs(bitsPerSecond);
|
|
13264
|
+
let i = 0;
|
|
13265
|
+
while (v >= 1000 && i < units.length - 1) {
|
|
13266
|
+
v /= 1000;
|
|
13267
|
+
i++;
|
|
13268
|
+
}
|
|
13269
|
+
return `${neg}${trimZeros(v.toFixed(i === 0 ? 0 : digits))} ${units[i]}`;
|
|
13270
|
+
}
|
|
13271
|
+
/**
|
|
13272
|
+
* Format a duration in ms as the two most significant units: "2h 14m",
|
|
13273
|
+
* "3d 6h", "45s", "820ms". Sub-second stays in ms; `maxUnits` widens it.
|
|
13274
|
+
*/
|
|
13275
|
+
function strctFormatDuration(ms, options = {}) {
|
|
13276
|
+
if (!Number.isFinite(ms))
|
|
13277
|
+
return '—';
|
|
13278
|
+
const { maxUnits = 2 } = options;
|
|
13279
|
+
const neg = ms < 0 ? '-' : '';
|
|
13280
|
+
let v = Math.abs(ms);
|
|
13281
|
+
if (v < 1000)
|
|
13282
|
+
return `${neg}${Math.round(v)}ms`;
|
|
13283
|
+
const units = [
|
|
13284
|
+
['d', 86_400_000],
|
|
13285
|
+
['h', 3_600_000],
|
|
13286
|
+
['m', 60_000],
|
|
13287
|
+
['s', 1000],
|
|
13288
|
+
];
|
|
13289
|
+
const parts = [];
|
|
13290
|
+
for (const [label, size] of units) {
|
|
13291
|
+
if (parts.length >= maxUnits)
|
|
13292
|
+
break;
|
|
13293
|
+
const n = Math.floor(v / size);
|
|
13294
|
+
if (n > 0 || parts.length > 0) {
|
|
13295
|
+
if (n > 0)
|
|
13296
|
+
parts.push(`${n}${label}`);
|
|
13297
|
+
v -= n * size;
|
|
13298
|
+
}
|
|
13299
|
+
}
|
|
13300
|
+
return neg + (parts.length ? parts.join(' ') : '0s');
|
|
13301
|
+
}
|
|
13302
|
+
/** Format a count with SI magnitude suffixes: 12400 → "12.4k". */
|
|
13303
|
+
function strctFormatSi(value, options = {}) {
|
|
13304
|
+
if (!Number.isFinite(value))
|
|
13305
|
+
return '—';
|
|
13306
|
+
const { digits = 1, unit = '' } = options;
|
|
13307
|
+
const neg = value < 0 ? '-' : '';
|
|
13308
|
+
let v = Math.abs(value);
|
|
13309
|
+
let i = 0;
|
|
13310
|
+
while (v >= 1000 && i < SI_SUFFIXES.length - 1) {
|
|
13311
|
+
v /= 1000;
|
|
13312
|
+
i++;
|
|
13313
|
+
}
|
|
13314
|
+
const num = `${neg}${trimZeros(v.toFixed(i === 0 ? 0 : digits))}${SI_SUFFIXES[i]}`;
|
|
13315
|
+
return unit ? `${num} ${unit}` : num;
|
|
13316
|
+
}
|
|
13317
|
+
/** `{{ bytes | strctBytes }}` / `{{ bytes | strctBytes : false }}` (decimal). */
|
|
13318
|
+
class StrctBytesPipe {
|
|
13319
|
+
transform(value, binary = true, digits = 1) {
|
|
13320
|
+
return strctFormatBytes(value, { binary, digits });
|
|
13321
|
+
}
|
|
13322
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctBytesPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
13323
|
+
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.16", ngImport: i0, type: StrctBytesPipe, isStandalone: true, name: "strctBytes" });
|
|
13324
|
+
}
|
|
13325
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctBytesPipe, decorators: [{
|
|
13326
|
+
type: Pipe,
|
|
13327
|
+
args: [{ name: 'strctBytes' }]
|
|
13328
|
+
}] });
|
|
13329
|
+
/** `{{ bps | strctRate }}` — bits per second with kbit/Mbit/Gbit steps. */
|
|
13330
|
+
class StrctRatePipe {
|
|
13331
|
+
transform(value, digits = 1) {
|
|
13332
|
+
return strctFormatRate(value, { digits });
|
|
13333
|
+
}
|
|
13334
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctRatePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
13335
|
+
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.16", ngImport: i0, type: StrctRatePipe, isStandalone: true, name: "strctRate" });
|
|
13336
|
+
}
|
|
13337
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctRatePipe, decorators: [{
|
|
13338
|
+
type: Pipe,
|
|
13339
|
+
args: [{ name: 'strctRate' }]
|
|
13340
|
+
}] });
|
|
13341
|
+
/** `{{ ms | strctDuration }}` — "2h 14m"; optional max unit count. */
|
|
13342
|
+
class StrctDurationPipe {
|
|
13343
|
+
transform(value, maxUnits = 2) {
|
|
13344
|
+
return strctFormatDuration(value, { maxUnits });
|
|
13345
|
+
}
|
|
13346
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctDurationPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
13347
|
+
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.16", ngImport: i0, type: StrctDurationPipe, isStandalone: true, name: "strctDuration" });
|
|
13348
|
+
}
|
|
13349
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctDurationPipe, decorators: [{
|
|
13350
|
+
type: Pipe,
|
|
13351
|
+
args: [{ name: 'strctDuration' }]
|
|
13352
|
+
}] });
|
|
13353
|
+
/** `{{ n | strctSi : 'IOPS' }}` — SI magnitude with an optional unit label. */
|
|
13354
|
+
class StrctSiPipe {
|
|
13355
|
+
transform(value, unit = '', digits = 1) {
|
|
13356
|
+
return strctFormatSi(value, { unit, digits });
|
|
13357
|
+
}
|
|
13358
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctSiPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
13359
|
+
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.16", ngImport: i0, type: StrctSiPipe, isStandalone: true, name: "strctSi" });
|
|
13360
|
+
}
|
|
13361
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctSiPipe, decorators: [{
|
|
13362
|
+
type: Pipe,
|
|
13363
|
+
args: [{ name: 'strctSi' }]
|
|
13364
|
+
}] });
|
|
13365
|
+
|
|
13366
|
+
/** The Grafana-conventional quick ranges consoles expect. */
|
|
13367
|
+
const STRCT_TIME_RANGE_PRESETS = [
|
|
13368
|
+
{ id: '15m', label: 'Last 15 minutes', ms: 15 * 60_000 },
|
|
13369
|
+
{ id: '1h', label: 'Last 1 hour', ms: 3_600_000 },
|
|
13370
|
+
{ id: '6h', label: 'Last 6 hours', ms: 6 * 3_600_000 },
|
|
13371
|
+
{ id: '24h', label: 'Last 24 hours', ms: 24 * 3_600_000 },
|
|
13372
|
+
{ id: '7d', label: 'Last 7 days', ms: 7 * 86_400_000 },
|
|
13373
|
+
{ id: '30d', label: 'Last 30 days', ms: 30 * 86_400_000 },
|
|
13374
|
+
];
|
|
13375
|
+
/**
|
|
13376
|
+
* Monitoring time-range picker — the "Last 1 hour ▾" control charts hang off:
|
|
13377
|
+
* quick relative ranges plus an absolute from/to editor, in one popover.
|
|
13378
|
+
*
|
|
13379
|
+
* <strct-time-range [(range)]="range" />
|
|
13380
|
+
*
|
|
13381
|
+
* Picking a preset resolves it against "now" immediately and stamps
|
|
13382
|
+
* `presetId`, so a consumer can re-resolve the same preset on its own refresh
|
|
13383
|
+
* tick. The popover reuses `strct-dropdown`'s dialog semantics: inner clicks
|
|
13384
|
+
* keep it open, outside click / Escape dismiss.
|
|
13385
|
+
*/
|
|
13386
|
+
class StrctTimeRangePicker {
|
|
13387
|
+
/** The selected window, two-way. Null until the user (or you) picks one. */
|
|
13388
|
+
range = model(null, ...(ngDevMode ? [{ debugName: "range" }] : /* istanbul ignore next */ []));
|
|
13389
|
+
/** Quick ranges shown in the left column. */
|
|
13390
|
+
presets = input(STRCT_TIME_RANGE_PRESETS, ...(ngDevMode ? [{ debugName: "presets" }] : /* istanbul ignore next */ []));
|
|
13391
|
+
/** Popover alignment, as in strct-dropdown. */
|
|
13392
|
+
align = input('start', ...(ngDevMode ? [{ debugName: "align" }] : /* istanbul ignore next */ []));
|
|
13393
|
+
/** Trigger size — match it to the toolbar's buttons (sm strips). */
|
|
13394
|
+
size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
13395
|
+
/** Localizable strings. */
|
|
13396
|
+
dialogLabel = input('Time range', ...(ngDevMode ? [{ debugName: "dialogLabel" }] : /* istanbul ignore next */ []));
|
|
13397
|
+
quickLabel = input('Quick ranges', ...(ngDevMode ? [{ debugName: "quickLabel" }] : /* istanbul ignore next */ []));
|
|
13398
|
+
absoluteLabel = input('Absolute range', ...(ngDevMode ? [{ debugName: "absoluteLabel" }] : /* istanbul ignore next */ []));
|
|
13399
|
+
fromLabel = input('From', ...(ngDevMode ? [{ debugName: "fromLabel" }] : /* istanbul ignore next */ []));
|
|
13400
|
+
toLabel = input('To', ...(ngDevMode ? [{ debugName: "toLabel" }] : /* istanbul ignore next */ []));
|
|
13401
|
+
applyLabel = input('Apply', ...(ngDevMode ? [{ debugName: "applyLabel" }] : /* istanbul ignore next */ []));
|
|
13402
|
+
invalidLabel = input('"From" must be before "To".', ...(ngDevMode ? [{ debugName: "invalidLabel" }] : /* istanbul ignore next */ []));
|
|
13403
|
+
placeholderLabel = input('Select time range', ...(ngDevMode ? [{ debugName: "placeholderLabel" }] : /* istanbul ignore next */ []));
|
|
13404
|
+
/** Fires on every commit (preset pick or absolute apply). */
|
|
13405
|
+
applied = output();
|
|
13406
|
+
dd = viewChild.required(StrctDropdown);
|
|
13407
|
+
// Absolute-editor draft (datetime-local strings), seeded from the range.
|
|
13408
|
+
draftFrom = signal('', ...(ngDevMode ? [{ debugName: "draftFrom" }] : /* istanbul ignore next */ []));
|
|
13409
|
+
draftTo = signal('', ...(ngDevMode ? [{ debugName: "draftTo" }] : /* istanbul ignore next */ []));
|
|
13410
|
+
draftError = computed(() => {
|
|
13411
|
+
const f = this.draftFrom();
|
|
13412
|
+
const t = this.draftTo();
|
|
13413
|
+
if (!f || !t)
|
|
13414
|
+
return false;
|
|
13415
|
+
return new Date(f).getTime() >= new Date(t).getTime();
|
|
13416
|
+
}, ...(ngDevMode ? [{ debugName: "draftError" }] : /* istanbul ignore next */ []));
|
|
13417
|
+
triggerLabel = computed(() => {
|
|
13418
|
+
const r = this.range();
|
|
13419
|
+
if (!r)
|
|
13420
|
+
return this.placeholderLabel();
|
|
13421
|
+
const preset = this.presets().find((p) => p.id === r.presetId);
|
|
13422
|
+
if (preset)
|
|
13423
|
+
return preset.label;
|
|
13424
|
+
return `${this.formatStamp(r.from)} → ${this.formatStamp(r.to)}`;
|
|
13425
|
+
}, ...(ngDevMode ? [{ debugName: "triggerLabel" }] : /* istanbul ignore next */ []));
|
|
13426
|
+
pickPreset(p) {
|
|
13427
|
+
const to = new Date();
|
|
13428
|
+
const next = { from: new Date(to.getTime() - p.ms), to, presetId: p.id };
|
|
13429
|
+
this.range.set(next);
|
|
13430
|
+
this.applied.emit(next);
|
|
13431
|
+
this.seedDraft(next);
|
|
13432
|
+
}
|
|
13433
|
+
applyAbsolute() {
|
|
13434
|
+
const f = this.draftFrom();
|
|
13435
|
+
const t = this.draftTo();
|
|
13436
|
+
if (!f || !t || this.draftError())
|
|
13437
|
+
return false;
|
|
13438
|
+
const next = { from: new Date(f), to: new Date(t) };
|
|
13439
|
+
this.range.set(next);
|
|
13440
|
+
this.applied.emit(next);
|
|
13441
|
+
return true;
|
|
13442
|
+
}
|
|
13443
|
+
/** Re-resolve a preset range against "now" — call from your refresh tick. */
|
|
13444
|
+
refresh() {
|
|
13445
|
+
const r = this.range();
|
|
13446
|
+
const preset = r && this.presets().find((p) => p.id === r.presetId);
|
|
13447
|
+
if (!preset)
|
|
13448
|
+
return;
|
|
13449
|
+
this.pickPreset(preset);
|
|
13450
|
+
}
|
|
13451
|
+
seedDraft(r) {
|
|
13452
|
+
this.draftFrom.set(this.toLocal(r.from));
|
|
13453
|
+
this.draftTo.set(this.toLocal(r.to));
|
|
13454
|
+
}
|
|
13455
|
+
toLocal(d) {
|
|
13456
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
13457
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
|
13458
|
+
}
|
|
13459
|
+
formatStamp(d) {
|
|
13460
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
13461
|
+
return `${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
|
13462
|
+
}
|
|
13463
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctTimeRangePicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
13464
|
+
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 }, size: { classPropertyName: "size", publicName: "size", 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: `
|
|
13465
|
+
<strct-dropdown #dd popover [popoverLabel]="dialogLabel()" [align]="align()">
|
|
13466
|
+
<button
|
|
13467
|
+
strct-button
|
|
13468
|
+
variant="neutral"
|
|
13469
|
+
[size]="size()"
|
|
13470
|
+
strctDropdownTrigger
|
|
13471
|
+
class="strct-tr__trigger"
|
|
13472
|
+
>
|
|
13473
|
+
<strct-icon name="clock" [size]="13" />
|
|
13474
|
+
<span class="strct-tr__label">{{ triggerLabel() }}</span>
|
|
13475
|
+
<strct-icon name="chevronDown" [size]="12" />
|
|
13476
|
+
</button>
|
|
13477
|
+
|
|
13478
|
+
<div class="strct-tr__panel">
|
|
13479
|
+
<div class="strct-tr__group" role="group" [attr.aria-label]="quickLabel()">
|
|
13480
|
+
<div class="strct-tr__head">{{ quickLabel() }}</div>
|
|
13481
|
+
@for (p of presets(); track p.id) {
|
|
13482
|
+
<button
|
|
13483
|
+
type="button"
|
|
13484
|
+
class="strct-tr__preset"
|
|
13485
|
+
[class.strct-tr__preset--active]="range()?.presetId === p.id"
|
|
13486
|
+
[attr.aria-pressed]="range()?.presetId === p.id"
|
|
13487
|
+
(click)="pickPreset(p); dd.close()"
|
|
13488
|
+
>
|
|
13489
|
+
{{ p.label }}
|
|
13490
|
+
</button>
|
|
13491
|
+
}
|
|
13492
|
+
</div>
|
|
13493
|
+
|
|
13494
|
+
<div class="strct-tr__group" role="group" [attr.aria-label]="absoluteLabel()">
|
|
13495
|
+
<div class="strct-tr__head">{{ absoluteLabel() }}</div>
|
|
13496
|
+
<label class="strct-tr__field">
|
|
13497
|
+
<span>{{ fromLabel() }}</span>
|
|
13498
|
+
<input
|
|
13499
|
+
type="datetime-local"
|
|
13500
|
+
class="strct-tr__input"
|
|
13501
|
+
[value]="draftFrom()"
|
|
13502
|
+
(input)="draftFrom.set($any($event.target).value)"
|
|
13503
|
+
/>
|
|
13504
|
+
</label>
|
|
13505
|
+
<label class="strct-tr__field">
|
|
13506
|
+
<span>{{ toLabel() }}</span>
|
|
13507
|
+
<input
|
|
13508
|
+
type="datetime-local"
|
|
13509
|
+
class="strct-tr__input"
|
|
13510
|
+
[value]="draftTo()"
|
|
13511
|
+
(input)="draftTo.set($any($event.target).value)"
|
|
13512
|
+
/>
|
|
13513
|
+
</label>
|
|
13514
|
+
@if (draftError()) {
|
|
13515
|
+
<div class="strct-tr__error" role="alert">{{ invalidLabel() }}</div>
|
|
13516
|
+
}
|
|
13517
|
+
<button
|
|
13518
|
+
strct-button
|
|
13519
|
+
variant="primary"
|
|
13520
|
+
size="sm"
|
|
13521
|
+
class="strct-tr__apply"
|
|
13522
|
+
[disabled]="draftError()"
|
|
13523
|
+
(click)="applyAbsolute() && dd.close()"
|
|
13524
|
+
>
|
|
13525
|
+
{{ applyLabel() }}
|
|
13526
|
+
</button>
|
|
13527
|
+
</div>
|
|
13528
|
+
</div>
|
|
13529
|
+
</strct-dropdown>
|
|
13530
|
+
`, 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 });
|
|
13531
|
+
}
|
|
13532
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctTimeRangePicker, decorators: [{
|
|
13533
|
+
type: Component,
|
|
13534
|
+
args: [{ selector: 'strct-time-range', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [StrctButton, StrctDropdown, StrctDropdownTrigger, StrctIcon], template: `
|
|
13535
|
+
<strct-dropdown #dd popover [popoverLabel]="dialogLabel()" [align]="align()">
|
|
13536
|
+
<button
|
|
13537
|
+
strct-button
|
|
13538
|
+
variant="neutral"
|
|
13539
|
+
[size]="size()"
|
|
13540
|
+
strctDropdownTrigger
|
|
13541
|
+
class="strct-tr__trigger"
|
|
13542
|
+
>
|
|
13543
|
+
<strct-icon name="clock" [size]="13" />
|
|
13544
|
+
<span class="strct-tr__label">{{ triggerLabel() }}</span>
|
|
13545
|
+
<strct-icon name="chevronDown" [size]="12" />
|
|
13546
|
+
</button>
|
|
13547
|
+
|
|
13548
|
+
<div class="strct-tr__panel">
|
|
13549
|
+
<div class="strct-tr__group" role="group" [attr.aria-label]="quickLabel()">
|
|
13550
|
+
<div class="strct-tr__head">{{ quickLabel() }}</div>
|
|
13551
|
+
@for (p of presets(); track p.id) {
|
|
13552
|
+
<button
|
|
13553
|
+
type="button"
|
|
13554
|
+
class="strct-tr__preset"
|
|
13555
|
+
[class.strct-tr__preset--active]="range()?.presetId === p.id"
|
|
13556
|
+
[attr.aria-pressed]="range()?.presetId === p.id"
|
|
13557
|
+
(click)="pickPreset(p); dd.close()"
|
|
13558
|
+
>
|
|
13559
|
+
{{ p.label }}
|
|
13560
|
+
</button>
|
|
13561
|
+
}
|
|
13562
|
+
</div>
|
|
13563
|
+
|
|
13564
|
+
<div class="strct-tr__group" role="group" [attr.aria-label]="absoluteLabel()">
|
|
13565
|
+
<div class="strct-tr__head">{{ absoluteLabel() }}</div>
|
|
13566
|
+
<label class="strct-tr__field">
|
|
13567
|
+
<span>{{ fromLabel() }}</span>
|
|
13568
|
+
<input
|
|
13569
|
+
type="datetime-local"
|
|
13570
|
+
class="strct-tr__input"
|
|
13571
|
+
[value]="draftFrom()"
|
|
13572
|
+
(input)="draftFrom.set($any($event.target).value)"
|
|
13573
|
+
/>
|
|
13574
|
+
</label>
|
|
13575
|
+
<label class="strct-tr__field">
|
|
13576
|
+
<span>{{ toLabel() }}</span>
|
|
13577
|
+
<input
|
|
13578
|
+
type="datetime-local"
|
|
13579
|
+
class="strct-tr__input"
|
|
13580
|
+
[value]="draftTo()"
|
|
13581
|
+
(input)="draftTo.set($any($event.target).value)"
|
|
13582
|
+
/>
|
|
13583
|
+
</label>
|
|
13584
|
+
@if (draftError()) {
|
|
13585
|
+
<div class="strct-tr__error" role="alert">{{ invalidLabel() }}</div>
|
|
13586
|
+
}
|
|
13587
|
+
<button
|
|
13588
|
+
strct-button
|
|
13589
|
+
variant="primary"
|
|
13590
|
+
size="sm"
|
|
13591
|
+
class="strct-tr__apply"
|
|
13592
|
+
[disabled]="draftError()"
|
|
13593
|
+
(click)="applyAbsolute() && dd.close()"
|
|
13594
|
+
>
|
|
13595
|
+
{{ applyLabel() }}
|
|
13596
|
+
</button>
|
|
13597
|
+
</div>
|
|
13598
|
+
</div>
|
|
13599
|
+
</strct-dropdown>
|
|
13600
|
+
`, 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"] }]
|
|
13601
|
+
}], 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 }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", 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 }] }] } });
|
|
13602
|
+
|
|
13603
|
+
// SGR code → segment class. Colors map onto chart/status tokens, not raw hues.
|
|
13604
|
+
const ANSI_CLASSES = {
|
|
13605
|
+
1: 'b',
|
|
13606
|
+
30: 'c30',
|
|
13607
|
+
31: 'c31',
|
|
13608
|
+
32: 'c32',
|
|
13609
|
+
33: 'c33',
|
|
13610
|
+
34: 'c34',
|
|
13611
|
+
35: 'c35',
|
|
13612
|
+
36: 'c36',
|
|
13613
|
+
37: 'c37',
|
|
13614
|
+
90: 'c90',
|
|
13615
|
+
91: 'c31',
|
|
13616
|
+
92: 'c32',
|
|
13617
|
+
93: 'c33',
|
|
13618
|
+
94: 'c34',
|
|
13619
|
+
95: 'c35',
|
|
13620
|
+
96: 'c36',
|
|
13621
|
+
97: 'c37',
|
|
13622
|
+
};
|
|
13623
|
+
const LEVEL_RE = /\b(ERROR|ERR|FATAL|WARN(?:ING)?|INFO|DEBUG|TRACE)\b/;
|
|
13624
|
+
/**
|
|
13625
|
+
* Virtualized log tail — the `kubectl logs -f` surface as a component:
|
|
13626
|
+
*
|
|
13627
|
+
* <strct-log-viewer [lines]="lines()" [(follow)]="follow" [height]="360" />
|
|
13628
|
+
*
|
|
13629
|
+
* - **Virtualized**: only the visible window plus overscan is in the DOM, so
|
|
13630
|
+
* 100k lines scroll flat.
|
|
13631
|
+
* - **Follow mode** (two-way): sticks to the tail as lines stream in;
|
|
13632
|
+
* scrolling up pauses it, the button (or scrolling back to the bottom)
|
|
13633
|
+
* resumes it — the Grafana/Loki convention.
|
|
13634
|
+
* - **ANSI SGR colors** (16-color + bold) parsed into safe spans — no
|
|
13635
|
+
* innerHTML — and severity tinting from `StrctLogLine.level` or, for plain
|
|
13636
|
+
* strings, auto-detected ERROR/WARN/INFO/DEBUG tokens.
|
|
13637
|
+
*/
|
|
13638
|
+
class StrctLogViewer {
|
|
13639
|
+
/** Log lines — plain strings or `{ text, level }` objects. */
|
|
13640
|
+
lines = input([], ...(ngDevMode ? [{ debugName: "lines" }] : /* istanbul ignore next */ []));
|
|
13641
|
+
/** Viewport height in px. */
|
|
13642
|
+
height = input(320, ...(ngDevMode ? [{ debugName: "height" }] : /* istanbul ignore next */ []));
|
|
13643
|
+
/** Follow the tail as new lines arrive (two-way; pauses on scroll-up). */
|
|
13644
|
+
follow = model(true, ...(ngDevMode ? [{ debugName: "follow" }] : /* istanbul ignore next */ []));
|
|
13645
|
+
/** Soft-wrap long lines instead of horizontal scrolling (two-way). */
|
|
13646
|
+
wrapMode = model(false, ...(ngDevMode ? [{ debugName: "wrapMode" }] : /* istanbul ignore next */ []));
|
|
13647
|
+
/** Auto-detect ERROR/WARN/INFO/DEBUG tokens on plain-string lines. */
|
|
13648
|
+
autoLevel = input(true, { ...(ngDevMode ? { debugName: "autoLevel" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
13649
|
+
/** Localizable strings. */
|
|
13650
|
+
title = input('Logs', ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
13651
|
+
followLabel = input('Follow', ...(ngDevMode ? [{ debugName: "followLabel" }] : /* istanbul ignore next */ []));
|
|
13652
|
+
wrapLabel = input('Wrap', ...(ngDevMode ? [{ debugName: "wrapLabel" }] : /* istanbul ignore next */ []));
|
|
13653
|
+
linesLabel = input('lines', ...(ngDevMode ? [{ debugName: "linesLabel" }] : /* istanbul ignore next */ []));
|
|
13654
|
+
emptyLabel = input('No log lines.', ...(ngDevMode ? [{ debugName: "emptyLabel" }] : /* istanbul ignore next */ []));
|
|
13655
|
+
scroller = viewChild.required('scroller');
|
|
13656
|
+
scrollTop = signal(0, ...(ngDevMode ? [{ debugName: "scrollTop" }] : /* istanbul ignore next */ []));
|
|
13657
|
+
suppressScrollEvent = false;
|
|
13658
|
+
static LINE_H = 20;
|
|
13659
|
+
static OVERSCAN = 10;
|
|
13660
|
+
normalized = computed(() => this.lines().map((l) => (typeof l === 'string' ? { text: l } : l)), ...(ngDevMode ? [{ debugName: "normalized" }] : /* istanbul ignore next */ []));
|
|
13661
|
+
totalHeight = computed(() => this.normalized().length * StrctLogViewer.LINE_H || StrctLogViewer.LINE_H, ...(ngDevMode ? [{ debugName: "totalHeight" }] : /* istanbul ignore next */ []));
|
|
13662
|
+
windowStart = computed(() => {
|
|
13663
|
+
const start = Math.floor(this.scrollTop() / StrctLogViewer.LINE_H) - StrctLogViewer.OVERSCAN;
|
|
13664
|
+
return Math.max(0, start);
|
|
13665
|
+
}, ...(ngDevMode ? [{ debugName: "windowStart" }] : /* istanbul ignore next */ []));
|
|
13666
|
+
offsetY = computed(() => this.windowStart() * StrctLogViewer.LINE_H, ...(ngDevMode ? [{ debugName: "offsetY" }] : /* istanbul ignore next */ []));
|
|
13667
|
+
window = computed(() => {
|
|
13668
|
+
const all = this.normalized();
|
|
13669
|
+
const start = this.windowStart();
|
|
13670
|
+
const visible = Math.ceil(this.height() / StrctLogViewer.LINE_H) + StrctLogViewer.OVERSCAN * 2;
|
|
13671
|
+
return all.slice(start, start + visible).map((line, i) => ({
|
|
13672
|
+
index: start + i,
|
|
13673
|
+
level: line.level ?? (this.autoLevel() ? detectLevel(line.text) : 'none'),
|
|
13674
|
+
segments: parseAnsi(line.text),
|
|
13675
|
+
}));
|
|
13676
|
+
}, ...(ngDevMode ? [{ debugName: "window" }] : /* istanbul ignore next */ []));
|
|
13677
|
+
constructor() {
|
|
13678
|
+
// Stick to the tail whenever content grows while following.
|
|
13679
|
+
effect(() => {
|
|
13680
|
+
this.totalHeight();
|
|
13681
|
+
if (!this.follow())
|
|
13682
|
+
return;
|
|
13683
|
+
const el = this.scroller().nativeElement;
|
|
13684
|
+
// After the DOM height settles.
|
|
13685
|
+
requestAnimationFrame(() => {
|
|
13686
|
+
this.suppressScrollEvent = true;
|
|
13687
|
+
el.scrollTop = el.scrollHeight;
|
|
13688
|
+
this.scrollTop.set(el.scrollTop);
|
|
13689
|
+
});
|
|
13690
|
+
});
|
|
13691
|
+
}
|
|
13692
|
+
onScroll() {
|
|
13693
|
+
const el = this.scroller().nativeElement;
|
|
13694
|
+
this.scrollTop.set(el.scrollTop);
|
|
13695
|
+
if (this.suppressScrollEvent) {
|
|
13696
|
+
this.suppressScrollEvent = false;
|
|
13697
|
+
return;
|
|
13698
|
+
}
|
|
13699
|
+
const atBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - StrctLogViewer.LINE_H;
|
|
13700
|
+
// Manual scroll: leaving the tail pauses follow, reaching it resumes.
|
|
13701
|
+
if (this.follow() && !atBottom)
|
|
13702
|
+
this.follow.set(false);
|
|
13703
|
+
else if (!this.follow() && atBottom)
|
|
13704
|
+
this.follow.set(true);
|
|
13705
|
+
}
|
|
13706
|
+
toggleFollow() {
|
|
13707
|
+
const next = !this.follow();
|
|
13708
|
+
this.follow.set(next);
|
|
13709
|
+
if (next) {
|
|
13710
|
+
const el = this.scroller().nativeElement;
|
|
13711
|
+
this.suppressScrollEvent = true;
|
|
13712
|
+
el.scrollTop = el.scrollHeight;
|
|
13713
|
+
this.scrollTop.set(el.scrollTop);
|
|
13714
|
+
}
|
|
13715
|
+
}
|
|
13716
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctLogViewer, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
13717
|
+
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: `
|
|
13718
|
+
<div class="strct-lv__bar">
|
|
13719
|
+
<span class="strct-lv__title">{{ title() }}</span>
|
|
13720
|
+
<span class="strct-lv__count">{{ normalized().length }} {{ linesLabel() }}</span>
|
|
13721
|
+
<span class="strct-lv__grow"></span>
|
|
13722
|
+
<button
|
|
13723
|
+
strct-button
|
|
13724
|
+
size="sm"
|
|
13725
|
+
[variant]="wrapMode() ? 'primary' : 'flat'"
|
|
13726
|
+
[attr.aria-pressed]="wrapMode()"
|
|
13727
|
+
(click)="wrapMode.set(!wrapMode())"
|
|
13728
|
+
>
|
|
13729
|
+
{{ wrapLabel() }}
|
|
13730
|
+
</button>
|
|
13731
|
+
<button
|
|
13732
|
+
strct-button
|
|
13733
|
+
size="sm"
|
|
13734
|
+
[variant]="follow() ? 'primary' : 'flat'"
|
|
13735
|
+
[attr.aria-pressed]="follow()"
|
|
13736
|
+
(click)="toggleFollow()"
|
|
13737
|
+
>
|
|
13738
|
+
<strct-icon name="chevronDoubleDown" [size]="12" />
|
|
13739
|
+
{{ followLabel() }}
|
|
13740
|
+
</button>
|
|
13741
|
+
</div>
|
|
13742
|
+
<div
|
|
13743
|
+
#scroller
|
|
13744
|
+
class="strct-lv__scroll"
|
|
13745
|
+
role="log"
|
|
13746
|
+
tabindex="0"
|
|
13747
|
+
[attr.aria-label]="title()"
|
|
13748
|
+
[style.height.px]="height()"
|
|
13749
|
+
(scroll)="onScroll()"
|
|
13750
|
+
>
|
|
13751
|
+
<div class="strct-lv__spacer" [style.height.px]="totalHeight()">
|
|
13752
|
+
<div
|
|
13753
|
+
class="strct-lv__window"
|
|
13754
|
+
[class.strct-lv__window--wrap]="wrapMode()"
|
|
13755
|
+
[style.transform]="'translateY(' + offsetY() + 'px)'"
|
|
13756
|
+
>
|
|
13757
|
+
@for (line of window(); track line.index) {
|
|
13758
|
+
<div class="strct-lv__line strct-lv__line--{{ line.level }}">
|
|
13759
|
+
<span class="strct-lv__no" aria-hidden="true">{{ line.index + 1 }}</span>
|
|
13760
|
+
<span class="strct-lv__text">
|
|
13761
|
+
@for (seg of line.segments; track $index) {
|
|
13762
|
+
<span class="strct-lv__seg-{{ seg.cls }}">{{ seg.text }}</span>
|
|
13763
|
+
}
|
|
13764
|
+
</span>
|
|
13765
|
+
</div>
|
|
13766
|
+
} @empty {
|
|
13767
|
+
<div class="strct-lv__empty">{{ emptyLabel() }}</div>
|
|
13768
|
+
}
|
|
13769
|
+
</div>
|
|
13770
|
+
</div>
|
|
13771
|
+
</div>
|
|
13772
|
+
`, 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 });
|
|
13773
|
+
}
|
|
13774
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctLogViewer, decorators: [{
|
|
13775
|
+
type: Component,
|
|
13776
|
+
args: [{ selector: 'strct-log-viewer', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [StrctButton, StrctIcon], template: `
|
|
13777
|
+
<div class="strct-lv__bar">
|
|
13778
|
+
<span class="strct-lv__title">{{ title() }}</span>
|
|
13779
|
+
<span class="strct-lv__count">{{ normalized().length }} {{ linesLabel() }}</span>
|
|
13780
|
+
<span class="strct-lv__grow"></span>
|
|
13781
|
+
<button
|
|
13782
|
+
strct-button
|
|
13783
|
+
size="sm"
|
|
13784
|
+
[variant]="wrapMode() ? 'primary' : 'flat'"
|
|
13785
|
+
[attr.aria-pressed]="wrapMode()"
|
|
13786
|
+
(click)="wrapMode.set(!wrapMode())"
|
|
13787
|
+
>
|
|
13788
|
+
{{ wrapLabel() }}
|
|
13789
|
+
</button>
|
|
13790
|
+
<button
|
|
13791
|
+
strct-button
|
|
13792
|
+
size="sm"
|
|
13793
|
+
[variant]="follow() ? 'primary' : 'flat'"
|
|
13794
|
+
[attr.aria-pressed]="follow()"
|
|
13795
|
+
(click)="toggleFollow()"
|
|
13796
|
+
>
|
|
13797
|
+
<strct-icon name="chevronDoubleDown" [size]="12" />
|
|
13798
|
+
{{ followLabel() }}
|
|
13799
|
+
</button>
|
|
13800
|
+
</div>
|
|
13801
|
+
<div
|
|
13802
|
+
#scroller
|
|
13803
|
+
class="strct-lv__scroll"
|
|
13804
|
+
role="log"
|
|
13805
|
+
tabindex="0"
|
|
13806
|
+
[attr.aria-label]="title()"
|
|
13807
|
+
[style.height.px]="height()"
|
|
13808
|
+
(scroll)="onScroll()"
|
|
13809
|
+
>
|
|
13810
|
+
<div class="strct-lv__spacer" [style.height.px]="totalHeight()">
|
|
13811
|
+
<div
|
|
13812
|
+
class="strct-lv__window"
|
|
13813
|
+
[class.strct-lv__window--wrap]="wrapMode()"
|
|
13814
|
+
[style.transform]="'translateY(' + offsetY() + 'px)'"
|
|
13815
|
+
>
|
|
13816
|
+
@for (line of window(); track line.index) {
|
|
13817
|
+
<div class="strct-lv__line strct-lv__line--{{ line.level }}">
|
|
13818
|
+
<span class="strct-lv__no" aria-hidden="true">{{ line.index + 1 }}</span>
|
|
13819
|
+
<span class="strct-lv__text">
|
|
13820
|
+
@for (seg of line.segments; track $index) {
|
|
13821
|
+
<span class="strct-lv__seg-{{ seg.cls }}">{{ seg.text }}</span>
|
|
13822
|
+
}
|
|
13823
|
+
</span>
|
|
13824
|
+
</div>
|
|
13825
|
+
} @empty {
|
|
13826
|
+
<div class="strct-lv__empty">{{ emptyLabel() }}</div>
|
|
13827
|
+
}
|
|
13828
|
+
</div>
|
|
13829
|
+
</div>
|
|
13830
|
+
</div>
|
|
13831
|
+
`, 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"] }]
|
|
13832
|
+
}], 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 }] }] } });
|
|
13833
|
+
function detectLevel(text) {
|
|
13834
|
+
const m = LEVEL_RE.exec(text);
|
|
13835
|
+
if (!m)
|
|
13836
|
+
return 'none';
|
|
13837
|
+
const tok = m[1];
|
|
13838
|
+
if (tok.startsWith('ERR') || tok === 'FATAL')
|
|
13839
|
+
return 'error';
|
|
13840
|
+
if (tok.startsWith('WARN'))
|
|
13841
|
+
return 'warn';
|
|
13842
|
+
if (tok === 'DEBUG' || tok === 'TRACE')
|
|
13843
|
+
return 'debug';
|
|
13844
|
+
return 'info';
|
|
13845
|
+
}
|
|
13846
|
+
/** Parse ANSI SGR sequences into class-tagged segments (16-color + bold). */
|
|
13847
|
+
function parseAnsi(text) {
|
|
13848
|
+
if (!text.includes('\u001b['))
|
|
13849
|
+
return [{ text, cls: 'c37' }];
|
|
13850
|
+
const segments = [];
|
|
13851
|
+
const re = /\u001b\[([0-9;]*)m/g;
|
|
13852
|
+
let last = 0;
|
|
13853
|
+
let current = new Set();
|
|
13854
|
+
let m;
|
|
13855
|
+
while ((m = re.exec(text))) {
|
|
13856
|
+
if (m.index > last) {
|
|
13857
|
+
segments.push({ text: text.slice(last, m.index), cls: clsOf(current) });
|
|
13858
|
+
}
|
|
13859
|
+
for (const raw of (m[1] || '0').split(';')) {
|
|
13860
|
+
const code = Number(raw || '0');
|
|
13861
|
+
if (code === 0)
|
|
13862
|
+
current = new Set();
|
|
13863
|
+
else if (ANSI_CLASSES[code]) {
|
|
13864
|
+
// A new foreground color replaces the previous one; bold stacks.
|
|
13865
|
+
if (code !== 1)
|
|
13866
|
+
for (const c of [...current])
|
|
13867
|
+
if (c !== 'b')
|
|
13868
|
+
current.delete(c);
|
|
13869
|
+
current.add(ANSI_CLASSES[code]);
|
|
13870
|
+
}
|
|
13871
|
+
}
|
|
13872
|
+
last = re.lastIndex;
|
|
13873
|
+
}
|
|
13874
|
+
if (last < text.length)
|
|
13875
|
+
segments.push({ text: text.slice(last), cls: clsOf(current) });
|
|
13876
|
+
return segments.length ? segments : [{ text: '', cls: 'c37' }];
|
|
13877
|
+
}
|
|
13878
|
+
function clsOf(set) {
|
|
13879
|
+
return set.size ? [...set].join(' strct-lv__seg-') : 'c37';
|
|
13880
|
+
}
|
|
13881
|
+
|
|
13882
|
+
/**
|
|
13883
|
+
* Compute a line diff (LCS-based). Exported for programmatic use — change
|
|
13884
|
+
* counts, gating "anything changed?" logic, custom renderings.
|
|
13885
|
+
*/
|
|
13886
|
+
function strctComputeDiff(before, after) {
|
|
13887
|
+
const a = before.split('\n');
|
|
13888
|
+
const b = after.split('\n');
|
|
13889
|
+
// Trim common prefix/suffix first — configs mostly match, keeping DP small.
|
|
13890
|
+
let start = 0;
|
|
13891
|
+
while (start < a.length && start < b.length && a[start] === b[start])
|
|
13892
|
+
start++;
|
|
13893
|
+
let endA = a.length;
|
|
13894
|
+
let endB = b.length;
|
|
13895
|
+
while (endA > start && endB > start && a[endA - 1] === b[endB - 1]) {
|
|
13896
|
+
endA--;
|
|
13897
|
+
endB--;
|
|
13898
|
+
}
|
|
13899
|
+
const midA = a.slice(start, endA);
|
|
13900
|
+
const midB = b.slice(start, endB);
|
|
13901
|
+
const rows = [];
|
|
13902
|
+
for (let i = 0; i < start; i++)
|
|
13903
|
+
rows.push({ type: 'ctx', oldNo: i + 1, newNo: i + 1, text: a[i] });
|
|
13904
|
+
// LCS DP on the changed middle; fall back to replace-all when it is huge.
|
|
13905
|
+
if (midA.length * midB.length <= 4_000_000) {
|
|
13906
|
+
const n = midA.length;
|
|
13907
|
+
const m = midB.length;
|
|
13908
|
+
const dp = new Uint32Array((n + 1) * (m + 1));
|
|
13909
|
+
for (let i = n - 1; i >= 0; i--) {
|
|
13910
|
+
for (let j = m - 1; j >= 0; j--) {
|
|
13911
|
+
dp[i * (m + 1) + j] =
|
|
13912
|
+
midA[i] === midB[j]
|
|
13913
|
+
? dp[(i + 1) * (m + 1) + j + 1] + 1
|
|
13914
|
+
: Math.max(dp[(i + 1) * (m + 1) + j], dp[i * (m + 1) + j + 1]);
|
|
13915
|
+
}
|
|
13916
|
+
}
|
|
13917
|
+
let i = 0;
|
|
13918
|
+
let j = 0;
|
|
13919
|
+
while (i < n && j < m) {
|
|
13920
|
+
if (midA[i] === midB[j]) {
|
|
13921
|
+
rows.push({ type: 'ctx', oldNo: start + i + 1, newNo: start + j + 1, text: midA[i] });
|
|
13922
|
+
i++;
|
|
13923
|
+
j++;
|
|
13924
|
+
}
|
|
13925
|
+
else if (dp[(i + 1) * (m + 1) + j] >= dp[i * (m + 1) + j + 1]) {
|
|
13926
|
+
rows.push({ type: 'del', oldNo: start + i + 1, newNo: null, text: midA[i] });
|
|
13927
|
+
i++;
|
|
13928
|
+
}
|
|
13929
|
+
else {
|
|
13930
|
+
rows.push({ type: 'add', oldNo: null, newNo: start + j + 1, text: midB[j] });
|
|
13931
|
+
j++;
|
|
13932
|
+
}
|
|
13933
|
+
}
|
|
13934
|
+
while (i < n) {
|
|
13935
|
+
rows.push({ type: 'del', oldNo: start + i + 1, newNo: null, text: midA[i] });
|
|
13936
|
+
i++;
|
|
13937
|
+
}
|
|
13938
|
+
while (j < m) {
|
|
13939
|
+
rows.push({ type: 'add', oldNo: null, newNo: start + j + 1, text: midB[j] });
|
|
13940
|
+
j++;
|
|
13941
|
+
}
|
|
13942
|
+
}
|
|
13943
|
+
else {
|
|
13944
|
+
for (let i = 0; i < midA.length; i++)
|
|
13945
|
+
rows.push({ type: 'del', oldNo: start + i + 1, newNo: null, text: midA[i] });
|
|
13946
|
+
for (let j = 0; j < midB.length; j++)
|
|
13947
|
+
rows.push({ type: 'add', oldNo: null, newNo: start + j + 1, text: midB[j] });
|
|
13948
|
+
}
|
|
13949
|
+
for (let i = endA, j = endB; i < a.length; i++, j++) {
|
|
13950
|
+
rows.push({ type: 'ctx', oldNo: i + 1, newNo: j + 1, text: a[i] });
|
|
13951
|
+
}
|
|
13952
|
+
return rows;
|
|
13953
|
+
}
|
|
13954
|
+
/**
|
|
13955
|
+
* Config / YAML diff viewer — change-approval screens without a hand-rolled
|
|
13956
|
+
* diff. Unified by default, `split` for side-by-side; long unchanged runs
|
|
13957
|
+
* collapse to an expandable "· N unchanged lines ·" row.
|
|
13958
|
+
*
|
|
13959
|
+
* <strct-diff [before]="saved" [after]="draft" beforeLabel="Running" afterLabel="Draft" />
|
|
13960
|
+
*
|
|
13961
|
+
* Rows are marked by symbol (+/−) as well as tint, so the diff reads without
|
|
13962
|
+
* color — the same CVD rule the icon badges follow.
|
|
13963
|
+
*/
|
|
13964
|
+
class StrctDiff {
|
|
13965
|
+
/** The two texts to compare. */
|
|
13966
|
+
before = input.required(...(ngDevMode ? [{ debugName: "before" }] : /* istanbul ignore next */ []));
|
|
13967
|
+
after = input.required(...(ngDevMode ? [{ debugName: "after" }] : /* istanbul ignore next */ []));
|
|
13968
|
+
/** Unified (default) or side-by-side split view. */
|
|
13969
|
+
mode = input('unified', ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
|
|
13970
|
+
/** Context lines kept around changes before collapsing (0 disables collapsing). */
|
|
13971
|
+
context = input(3, ...(ngDevMode ? [{ debugName: "context" }] : /* istanbul ignore next */ []));
|
|
13972
|
+
/** Header bits. */
|
|
13973
|
+
title = input('Diff', ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
13974
|
+
language = input('', ...(ngDevMode ? [{ debugName: "language" }] : /* istanbul ignore next */ []));
|
|
13975
|
+
beforeLabel = input('Before', ...(ngDevMode ? [{ debugName: "beforeLabel" }] : /* istanbul ignore next */ []));
|
|
13976
|
+
afterLabel = input('After', ...(ngDevMode ? [{ debugName: "afterLabel" }] : /* istanbul ignore next */ []));
|
|
13977
|
+
copyable = input(true, { ...(ngDevMode ? { debugName: "copyable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
13978
|
+
copyAfterLabel = input('Copy new version', ...(ngDevMode ? [{ debugName: "copyAfterLabel" }] : /* istanbul ignore next */ []));
|
|
13979
|
+
unchangedLabel = input('unchanged lines', ...(ngDevMode ? [{ debugName: "unchangedLabel" }] : /* istanbul ignore next */ []));
|
|
13980
|
+
/** Scroll the body past this height (px); null grows freely. */
|
|
13981
|
+
maxHeight = input(null, ...(ngDevMode ? [{ debugName: "maxHeight" }] : /* istanbul ignore next */ []));
|
|
13982
|
+
// Indices of collapsed runs the user has expanded.
|
|
13983
|
+
expanded = signal(new Set(), ...(ngDevMode ? [{ debugName: "expanded" }] : /* istanbul ignore next */ []));
|
|
13984
|
+
rows = computed(() => strctComputeDiff(this.before(), this.after()), ...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
|
|
13985
|
+
addCount = computed(() => this.rows().filter((r) => r.type === 'add').length, ...(ngDevMode ? [{ debugName: "addCount" }] : /* istanbul ignore next */ []));
|
|
13986
|
+
delCount = computed(() => this.rows().filter((r) => r.type === 'del').length, ...(ngDevMode ? [{ debugName: "delCount" }] : /* istanbul ignore next */ []));
|
|
13987
|
+
unifiedRows = computed(() => {
|
|
13988
|
+
const out = [];
|
|
13989
|
+
for (const chunk of this.chunks()) {
|
|
13990
|
+
if (chunk.skip)
|
|
13991
|
+
out.push({ kind: 'skip', count: chunk.rows.length, startIndex: chunk.start });
|
|
13992
|
+
else
|
|
13993
|
+
for (const r of chunk.rows)
|
|
13994
|
+
out.push({ kind: 'row', ...r });
|
|
13995
|
+
}
|
|
13996
|
+
return out;
|
|
13997
|
+
}, ...(ngDevMode ? [{ debugName: "unifiedRows" }] : /* istanbul ignore next */ []));
|
|
13998
|
+
splitRows = computed(() => {
|
|
13999
|
+
const out = [];
|
|
14000
|
+
for (const chunk of this.chunks()) {
|
|
14001
|
+
if (chunk.skip) {
|
|
14002
|
+
out.push({ kind: 'skip', count: chunk.rows.length, startIndex: chunk.start });
|
|
14003
|
+
continue;
|
|
14004
|
+
}
|
|
14005
|
+
// Pair del-runs with the add-run that follows them (classic split view).
|
|
14006
|
+
let i = 0;
|
|
14007
|
+
const rows = chunk.rows;
|
|
14008
|
+
while (i < rows.length) {
|
|
14009
|
+
const r = rows[i];
|
|
14010
|
+
if (r.type === 'ctx') {
|
|
14011
|
+
out.push({
|
|
14012
|
+
kind: 'row',
|
|
14013
|
+
left: { type: 'ctx', no: r.oldNo, text: r.text },
|
|
14014
|
+
right: { type: 'ctx', no: r.newNo, text: r.text },
|
|
14015
|
+
});
|
|
14016
|
+
i++;
|
|
14017
|
+
continue;
|
|
14018
|
+
}
|
|
14019
|
+
const dels = [];
|
|
14020
|
+
const adds = [];
|
|
14021
|
+
while (i < rows.length && rows[i].type === 'del')
|
|
14022
|
+
dels.push(rows[i++]);
|
|
14023
|
+
while (i < rows.length && rows[i].type === 'add')
|
|
14024
|
+
adds.push(rows[i++]);
|
|
14025
|
+
const len = Math.max(dels.length, adds.length);
|
|
14026
|
+
for (let k = 0; k < len; k++) {
|
|
14027
|
+
const d = dels[k];
|
|
14028
|
+
const a = adds[k];
|
|
14029
|
+
out.push({
|
|
14030
|
+
kind: 'row',
|
|
14031
|
+
left: d
|
|
14032
|
+
? { type: 'del', no: d.oldNo, text: d.text }
|
|
14033
|
+
: { type: 'empty', no: null, text: '' },
|
|
14034
|
+
right: a
|
|
14035
|
+
? { type: 'add', no: a.newNo, text: a.text }
|
|
14036
|
+
: { type: 'empty', no: null, text: '' },
|
|
14037
|
+
});
|
|
14038
|
+
}
|
|
14039
|
+
}
|
|
14040
|
+
}
|
|
14041
|
+
return out;
|
|
14042
|
+
}, ...(ngDevMode ? [{ debugName: "splitRows" }] : /* istanbul ignore next */ []));
|
|
14043
|
+
/** Group rows into visible chunks and collapsible unchanged runs. */
|
|
14044
|
+
chunks = computed(() => {
|
|
14045
|
+
const rows = this.rows();
|
|
14046
|
+
const ctx = this.context();
|
|
14047
|
+
const expanded = this.expanded();
|
|
14048
|
+
if (ctx <= 0)
|
|
14049
|
+
return [{ skip: false, start: 0, rows }];
|
|
14050
|
+
// Mark rows within `ctx` of any change as visible.
|
|
14051
|
+
const visible = new Array(rows.length).fill(false);
|
|
14052
|
+
rows.forEach((r, i) => {
|
|
14053
|
+
if (r.type !== 'ctx') {
|
|
14054
|
+
for (let k = Math.max(0, i - ctx); k <= Math.min(rows.length - 1, i + ctx); k++) {
|
|
14055
|
+
visible[k] = true;
|
|
14056
|
+
}
|
|
14057
|
+
}
|
|
14058
|
+
});
|
|
14059
|
+
const out = [];
|
|
14060
|
+
let i = 0;
|
|
14061
|
+
while (i < rows.length) {
|
|
14062
|
+
const vis = visible[i] || expanded.has(startOfRun(visible, i));
|
|
14063
|
+
const start = i;
|
|
14064
|
+
while (i < rows.length && (visible[i] || expanded.has(startOfRun(visible, i))) === vis)
|
|
14065
|
+
i++;
|
|
14066
|
+
const slice = rows.slice(start, i);
|
|
14067
|
+
// Tiny hidden runs are not worth a fold row.
|
|
14068
|
+
if (!vis && slice.length <= 2)
|
|
14069
|
+
out.push({ skip: false, start, rows: slice });
|
|
14070
|
+
else
|
|
14071
|
+
out.push({ skip: !vis, start, rows: slice });
|
|
14072
|
+
}
|
|
14073
|
+
return out;
|
|
14074
|
+
}, ...(ngDevMode ? [{ debugName: "chunks" }] : /* istanbul ignore next */ []));
|
|
14075
|
+
expand(startIndex) {
|
|
14076
|
+
const next = new Set(this.expanded());
|
|
14077
|
+
next.add(startIndex);
|
|
14078
|
+
this.expanded.set(next);
|
|
14079
|
+
}
|
|
14080
|
+
sign(type) {
|
|
14081
|
+
return type === 'add' ? '+' : type === 'del' ? '−' : '';
|
|
14082
|
+
}
|
|
14083
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctDiff, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
14084
|
+
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: `
|
|
14085
|
+
<div class="strct-diff__head">
|
|
14086
|
+
<span class="strct-diff__title">{{ title() }}</span>
|
|
14087
|
+
@if (language()) {
|
|
14088
|
+
<span class="strct-diff__lang">{{ language() }}</span>
|
|
14089
|
+
}
|
|
14090
|
+
<span class="strct-diff__stat strct-diff__stat--add">+{{ addCount() }}</span>
|
|
14091
|
+
<span class="strct-diff__stat strct-diff__stat--del">−{{ delCount() }}</span>
|
|
14092
|
+
<span class="strct-diff__grow"></span>
|
|
14093
|
+
@if (copyable()) {
|
|
14094
|
+
<strct-copy [text]="after()" [copyLabel]="copyAfterLabel()" />
|
|
14095
|
+
}
|
|
14096
|
+
</div>
|
|
14097
|
+
@if (mode() === 'split') {
|
|
14098
|
+
<div class="strct-diff__cols" aria-hidden="true">
|
|
14099
|
+
<span>{{ beforeLabel() }}</span>
|
|
14100
|
+
<span>{{ afterLabel() }}</span>
|
|
14101
|
+
</div>
|
|
14102
|
+
}
|
|
14103
|
+
<div
|
|
14104
|
+
class="strct-diff__scroll"
|
|
14105
|
+
tabindex="0"
|
|
14106
|
+
role="region"
|
|
14107
|
+
[attr.aria-label]="title()"
|
|
14108
|
+
[style.max-height.px]="maxHeight()"
|
|
14109
|
+
>
|
|
14110
|
+
@if (mode() === 'unified') {
|
|
14111
|
+
<table class="strct-diff__table">
|
|
14112
|
+
<tbody>
|
|
14113
|
+
@for (row of unifiedRows(); track $index) {
|
|
14114
|
+
@if (row.kind === 'skip') {
|
|
14115
|
+
<tr class="strct-diff__skip-row">
|
|
14116
|
+
<td colspan="4">
|
|
14117
|
+
<button type="button" class="strct-diff__skip" (click)="expand(row.startIndex)">
|
|
14118
|
+
· {{ row.count }} {{ unchangedLabel() }} ·
|
|
14119
|
+
</button>
|
|
14120
|
+
</td>
|
|
14121
|
+
</tr>
|
|
14122
|
+
} @else {
|
|
14123
|
+
<tr class="strct-diff__row strct-diff__row--{{ row.type }}">
|
|
14124
|
+
<td class="strct-diff__no">{{ row.oldNo ?? '' }}</td>
|
|
14125
|
+
<td class="strct-diff__no">{{ row.newNo ?? '' }}</td>
|
|
14126
|
+
<td class="strct-diff__sign">{{ sign(row.type) }}</td>
|
|
14127
|
+
<td class="strct-diff__text" [textContent]="row.text"></td>
|
|
14128
|
+
</tr>
|
|
14129
|
+
}
|
|
14130
|
+
}
|
|
14131
|
+
</tbody>
|
|
14132
|
+
</table>
|
|
14133
|
+
} @else {
|
|
14134
|
+
<table class="strct-diff__table strct-diff__table--split">
|
|
14135
|
+
<tbody>
|
|
14136
|
+
@for (row of splitRows(); track $index) {
|
|
14137
|
+
@if (row.kind === 'skip') {
|
|
14138
|
+
<tr class="strct-diff__skip-row">
|
|
14139
|
+
<td colspan="6">
|
|
14140
|
+
<button type="button" class="strct-diff__skip" (click)="expand(row.startIndex)">
|
|
14141
|
+
· {{ row.count }} {{ unchangedLabel() }} ·
|
|
14142
|
+
</button>
|
|
14143
|
+
</td>
|
|
14144
|
+
</tr>
|
|
14145
|
+
} @else {
|
|
14146
|
+
<tr>
|
|
14147
|
+
<td class="strct-diff__no strct-diff__cell--{{ row.left.type }}">
|
|
14148
|
+
{{ row.left.no ?? '' }}
|
|
14149
|
+
</td>
|
|
14150
|
+
<td class="strct-diff__sign strct-diff__cell--{{ row.left.type }}">
|
|
14151
|
+
{{ sign(row.left.type) }}
|
|
14152
|
+
</td>
|
|
14153
|
+
<td
|
|
14154
|
+
class="strct-diff__text strct-diff__cell--{{ row.left.type }}"
|
|
14155
|
+
[textContent]="row.left.text"
|
|
14156
|
+
></td>
|
|
14157
|
+
<td class="strct-diff__no strct-diff__cell--{{ row.right.type }}">
|
|
14158
|
+
{{ row.right.no ?? '' }}
|
|
14159
|
+
</td>
|
|
14160
|
+
<td class="strct-diff__sign strct-diff__cell--{{ row.right.type }}">
|
|
14161
|
+
{{ sign(row.right.type) }}
|
|
14162
|
+
</td>
|
|
14163
|
+
<td
|
|
14164
|
+
class="strct-diff__text strct-diff__cell--{{ row.right.type }}"
|
|
14165
|
+
[textContent]="row.right.text"
|
|
14166
|
+
></td>
|
|
14167
|
+
</tr>
|
|
14168
|
+
}
|
|
14169
|
+
}
|
|
14170
|
+
</tbody>
|
|
14171
|
+
</table>
|
|
14172
|
+
}
|
|
14173
|
+
</div>
|
|
14174
|
+
`, 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 });
|
|
14175
|
+
}
|
|
14176
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctDiff, decorators: [{
|
|
14177
|
+
type: Component,
|
|
14178
|
+
args: [{ selector: 'strct-diff', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [StrctCopy], template: `
|
|
14179
|
+
<div class="strct-diff__head">
|
|
14180
|
+
<span class="strct-diff__title">{{ title() }}</span>
|
|
14181
|
+
@if (language()) {
|
|
14182
|
+
<span class="strct-diff__lang">{{ language() }}</span>
|
|
14183
|
+
}
|
|
14184
|
+
<span class="strct-diff__stat strct-diff__stat--add">+{{ addCount() }}</span>
|
|
14185
|
+
<span class="strct-diff__stat strct-diff__stat--del">−{{ delCount() }}</span>
|
|
14186
|
+
<span class="strct-diff__grow"></span>
|
|
14187
|
+
@if (copyable()) {
|
|
14188
|
+
<strct-copy [text]="after()" [copyLabel]="copyAfterLabel()" />
|
|
14189
|
+
}
|
|
14190
|
+
</div>
|
|
14191
|
+
@if (mode() === 'split') {
|
|
14192
|
+
<div class="strct-diff__cols" aria-hidden="true">
|
|
14193
|
+
<span>{{ beforeLabel() }}</span>
|
|
14194
|
+
<span>{{ afterLabel() }}</span>
|
|
14195
|
+
</div>
|
|
14196
|
+
}
|
|
14197
|
+
<div
|
|
14198
|
+
class="strct-diff__scroll"
|
|
14199
|
+
tabindex="0"
|
|
14200
|
+
role="region"
|
|
14201
|
+
[attr.aria-label]="title()"
|
|
14202
|
+
[style.max-height.px]="maxHeight()"
|
|
14203
|
+
>
|
|
14204
|
+
@if (mode() === 'unified') {
|
|
14205
|
+
<table class="strct-diff__table">
|
|
14206
|
+
<tbody>
|
|
14207
|
+
@for (row of unifiedRows(); track $index) {
|
|
14208
|
+
@if (row.kind === 'skip') {
|
|
14209
|
+
<tr class="strct-diff__skip-row">
|
|
14210
|
+
<td colspan="4">
|
|
14211
|
+
<button type="button" class="strct-diff__skip" (click)="expand(row.startIndex)">
|
|
14212
|
+
· {{ row.count }} {{ unchangedLabel() }} ·
|
|
14213
|
+
</button>
|
|
14214
|
+
</td>
|
|
14215
|
+
</tr>
|
|
14216
|
+
} @else {
|
|
14217
|
+
<tr class="strct-diff__row strct-diff__row--{{ row.type }}">
|
|
14218
|
+
<td class="strct-diff__no">{{ row.oldNo ?? '' }}</td>
|
|
14219
|
+
<td class="strct-diff__no">{{ row.newNo ?? '' }}</td>
|
|
14220
|
+
<td class="strct-diff__sign">{{ sign(row.type) }}</td>
|
|
14221
|
+
<td class="strct-diff__text" [textContent]="row.text"></td>
|
|
14222
|
+
</tr>
|
|
14223
|
+
}
|
|
14224
|
+
}
|
|
14225
|
+
</tbody>
|
|
14226
|
+
</table>
|
|
14227
|
+
} @else {
|
|
14228
|
+
<table class="strct-diff__table strct-diff__table--split">
|
|
14229
|
+
<tbody>
|
|
14230
|
+
@for (row of splitRows(); track $index) {
|
|
14231
|
+
@if (row.kind === 'skip') {
|
|
14232
|
+
<tr class="strct-diff__skip-row">
|
|
14233
|
+
<td colspan="6">
|
|
14234
|
+
<button type="button" class="strct-diff__skip" (click)="expand(row.startIndex)">
|
|
14235
|
+
· {{ row.count }} {{ unchangedLabel() }} ·
|
|
14236
|
+
</button>
|
|
14237
|
+
</td>
|
|
14238
|
+
</tr>
|
|
14239
|
+
} @else {
|
|
14240
|
+
<tr>
|
|
14241
|
+
<td class="strct-diff__no strct-diff__cell--{{ row.left.type }}">
|
|
14242
|
+
{{ row.left.no ?? '' }}
|
|
14243
|
+
</td>
|
|
14244
|
+
<td class="strct-diff__sign strct-diff__cell--{{ row.left.type }}">
|
|
14245
|
+
{{ sign(row.left.type) }}
|
|
14246
|
+
</td>
|
|
14247
|
+
<td
|
|
14248
|
+
class="strct-diff__text strct-diff__cell--{{ row.left.type }}"
|
|
14249
|
+
[textContent]="row.left.text"
|
|
14250
|
+
></td>
|
|
14251
|
+
<td class="strct-diff__no strct-diff__cell--{{ row.right.type }}">
|
|
14252
|
+
{{ row.right.no ?? '' }}
|
|
14253
|
+
</td>
|
|
14254
|
+
<td class="strct-diff__sign strct-diff__cell--{{ row.right.type }}">
|
|
14255
|
+
{{ sign(row.right.type) }}
|
|
14256
|
+
</td>
|
|
14257
|
+
<td
|
|
14258
|
+
class="strct-diff__text strct-diff__cell--{{ row.right.type }}"
|
|
14259
|
+
[textContent]="row.right.text"
|
|
14260
|
+
></td>
|
|
14261
|
+
</tr>
|
|
14262
|
+
}
|
|
14263
|
+
}
|
|
14264
|
+
</tbody>
|
|
14265
|
+
</table>
|
|
14266
|
+
}
|
|
14267
|
+
</div>
|
|
14268
|
+
`, 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"] }]
|
|
14269
|
+
}], 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 }] }] } });
|
|
14270
|
+
function startOfRun(visible, i) {
|
|
14271
|
+
let s = i;
|
|
14272
|
+
while (s > 0 && visible[s - 1] === visible[i])
|
|
14273
|
+
s--;
|
|
14274
|
+
return s;
|
|
14275
|
+
}
|
|
14276
|
+
|
|
14277
|
+
/**
|
|
14278
|
+
* Two resizable panes with a draggable gutter — master/detail layouts where
|
|
14279
|
+
* the split is the user's to own:
|
|
14280
|
+
*
|
|
14281
|
+
* <strct-splitter [(split)]="pct" [min]="20" [max]="80">
|
|
14282
|
+
* <div strctPaneStart>…list…</div>
|
|
14283
|
+
* <div strctPaneEnd>…detail…</div>
|
|
14284
|
+
* </strct-splitter>
|
|
14285
|
+
*
|
|
14286
|
+
* `split` is the start pane's share in percent (two-way, persistable).
|
|
14287
|
+
* The gutter is a keyboard separator: arrows nudge, Home/End jump to min/max.
|
|
14288
|
+
*/
|
|
14289
|
+
class StrctSplitter {
|
|
14290
|
+
host = inject(ElementRef);
|
|
14291
|
+
/** Start pane share in percent (two-way). */
|
|
14292
|
+
split = model(50, ...(ngDevMode ? [{ debugName: "split" }] : /* istanbul ignore next */ []));
|
|
14293
|
+
/** Clamp bounds for the split (percent). */
|
|
14294
|
+
min = input(15, ...(ngDevMode ? [{ debugName: "min" }] : /* istanbul ignore next */ []));
|
|
14295
|
+
max = input(85, ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
|
|
14296
|
+
/** Stack panes vertically (gutter drags up/down). */
|
|
14297
|
+
vertical = input(false, ...(ngDevMode ? [{ debugName: "vertical" }] : /* istanbul ignore next */ []));
|
|
14298
|
+
/** Accessible name of the separator (localizable). */
|
|
14299
|
+
gutterLabel = input('Resize panes', ...(ngDevMode ? [{ debugName: "gutterLabel" }] : /* istanbul ignore next */ []));
|
|
14300
|
+
/** Keyboard nudge step in percent. */
|
|
14301
|
+
step = input(3, ...(ngDevMode ? [{ debugName: "step" }] : /* istanbul ignore next */ []));
|
|
14302
|
+
clamped = computed(() => Math.min(this.max(), Math.max(this.min(), this.split())), ...(ngDevMode ? [{ debugName: "clamped" }] : /* istanbul ignore next */ []));
|
|
14303
|
+
dragging = false;
|
|
14304
|
+
moveHandler = (e) => this.onDragMove(e);
|
|
14305
|
+
upHandler = () => this.onDragEnd();
|
|
14306
|
+
onDragStart(event) {
|
|
14307
|
+
event.preventDefault();
|
|
14308
|
+
this.dragging = true;
|
|
14309
|
+
document.addEventListener('mousemove', this.moveHandler);
|
|
14310
|
+
document.addEventListener('mouseup', this.upHandler);
|
|
14311
|
+
}
|
|
14312
|
+
onDragMove(event) {
|
|
14313
|
+
const rect = this.host.nativeElement.getBoundingClientRect();
|
|
14314
|
+
const ratio = this.vertical()
|
|
14315
|
+
? (event.clientY - rect.top) / rect.height
|
|
14316
|
+
: (event.clientX - rect.left) / rect.width;
|
|
14317
|
+
this.split.set(Math.min(this.max(), Math.max(this.min(), Math.round(ratio * 100))));
|
|
14318
|
+
}
|
|
14319
|
+
onDragEnd() {
|
|
14320
|
+
this.dragging = false;
|
|
14321
|
+
document.removeEventListener('mousemove', this.moveHandler);
|
|
14322
|
+
document.removeEventListener('mouseup', this.upHandler);
|
|
14323
|
+
}
|
|
14324
|
+
onKeydown(event) {
|
|
14325
|
+
const dec = this.vertical() ? 'ArrowUp' : 'ArrowLeft';
|
|
14326
|
+
const inc = this.vertical() ? 'ArrowDown' : 'ArrowRight';
|
|
14327
|
+
let next = null;
|
|
14328
|
+
if (event.key === dec)
|
|
14329
|
+
next = this.clamped() - this.step();
|
|
14330
|
+
else if (event.key === inc)
|
|
14331
|
+
next = this.clamped() + this.step();
|
|
14332
|
+
else if (event.key === 'Home')
|
|
14333
|
+
next = this.min();
|
|
14334
|
+
else if (event.key === 'End')
|
|
14335
|
+
next = this.max();
|
|
14336
|
+
if (next == null)
|
|
14337
|
+
return;
|
|
14338
|
+
event.preventDefault();
|
|
14339
|
+
this.split.set(Math.min(this.max(), Math.max(this.min(), next)));
|
|
14340
|
+
}
|
|
14341
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctSplitter, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
14342
|
+
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: `
|
|
14343
|
+
<div class="strct-split__pane" [style.flex-basis.%]="clamped()">
|
|
14344
|
+
<ng-content select="[strctPaneStart]" />
|
|
14345
|
+
</div>
|
|
14346
|
+
<div
|
|
14347
|
+
class="strct-split__gutter"
|
|
14348
|
+
role="separator"
|
|
14349
|
+
tabindex="0"
|
|
14350
|
+
[attr.aria-label]="gutterLabel()"
|
|
14351
|
+
[attr.aria-orientation]="vertical() ? 'horizontal' : 'vertical'"
|
|
14352
|
+
[attr.aria-valuenow]="clamped()"
|
|
14353
|
+
[attr.aria-valuemin]="min()"
|
|
14354
|
+
[attr.aria-valuemax]="max()"
|
|
14355
|
+
(mousedown)="onDragStart($event)"
|
|
14356
|
+
(keydown)="onKeydown($event)"
|
|
14357
|
+
>
|
|
14358
|
+
<span class="strct-split__grip" aria-hidden="true"></span>
|
|
14359
|
+
</div>
|
|
14360
|
+
<div class="strct-split__pane strct-split__pane--end">
|
|
14361
|
+
<ng-content select="[strctPaneEnd]" />
|
|
14362
|
+
</div>
|
|
14363
|
+
`, 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 });
|
|
14364
|
+
}
|
|
14365
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctSplitter, decorators: [{
|
|
14366
|
+
type: Component,
|
|
14367
|
+
args: [{ selector: 'strct-splitter', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: `
|
|
14368
|
+
<div class="strct-split__pane" [style.flex-basis.%]="clamped()">
|
|
14369
|
+
<ng-content select="[strctPaneStart]" />
|
|
14370
|
+
</div>
|
|
14371
|
+
<div
|
|
14372
|
+
class="strct-split__gutter"
|
|
14373
|
+
role="separator"
|
|
14374
|
+
tabindex="0"
|
|
14375
|
+
[attr.aria-label]="gutterLabel()"
|
|
14376
|
+
[attr.aria-orientation]="vertical() ? 'horizontal' : 'vertical'"
|
|
14377
|
+
[attr.aria-valuenow]="clamped()"
|
|
14378
|
+
[attr.aria-valuemin]="min()"
|
|
14379
|
+
[attr.aria-valuemax]="max()"
|
|
14380
|
+
(mousedown)="onDragStart($event)"
|
|
14381
|
+
(keydown)="onKeydown($event)"
|
|
14382
|
+
>
|
|
14383
|
+
<span class="strct-split__grip" aria-hidden="true"></span>
|
|
14384
|
+
</div>
|
|
14385
|
+
<div class="strct-split__pane strct-split__pane--end">
|
|
14386
|
+
<ng-content select="[strctPaneEnd]" />
|
|
14387
|
+
</div>
|
|
14388
|
+
`, host: {
|
|
14389
|
+
class: 'strct-split',
|
|
14390
|
+
'[class.strct-split--vertical]': 'vertical()',
|
|
14391
|
+
'[class.strct-split--dragging]': 'dragging',
|
|
14392
|
+
}, 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"] }]
|
|
14393
|
+
}], 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 }] }] } });
|
|
14394
|
+
|
|
14395
|
+
/**
|
|
14396
|
+
* Dual-list transfer (picklist) — "assign hosts to the cluster": all items on
|
|
14397
|
+
* the left, the chosen set on the right, checkbox multi-select and move
|
|
14398
|
+
* buttons in between, with a searchbox per side.
|
|
14399
|
+
*
|
|
14400
|
+
* <strct-transfer [items]="hosts" [(assigned)]="clusterHosts" />
|
|
14401
|
+
*
|
|
14402
|
+
* `assigned` is the two-way id set (string[]); `items` stays the single
|
|
14403
|
+
* source-of-truth list.
|
|
14404
|
+
*/
|
|
14405
|
+
class StrctTransfer {
|
|
14406
|
+
/** The full item universe. */
|
|
14407
|
+
items = input.required(...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
|
|
14408
|
+
/** Assigned item ids (two-way). */
|
|
14409
|
+
assigned = model([], ...(ngDevMode ? [{ debugName: "assigned" }] : /* istanbul ignore next */ []));
|
|
14410
|
+
/** Localizable strings. */
|
|
14411
|
+
sourceLabel = input('Available', ...(ngDevMode ? [{ debugName: "sourceLabel" }] : /* istanbul ignore next */ []));
|
|
14412
|
+
targetLabel = input('Assigned', ...(ngDevMode ? [{ debugName: "targetLabel" }] : /* istanbul ignore next */ []));
|
|
14413
|
+
searchPlaceholder = input('Search', ...(ngDevMode ? [{ debugName: "searchPlaceholder" }] : /* istanbul ignore next */ []));
|
|
14414
|
+
emptyLabel = input('No items', ...(ngDevMode ? [{ debugName: "emptyLabel" }] : /* istanbul ignore next */ []));
|
|
14415
|
+
assignLabel = input('Assign selected', ...(ngDevMode ? [{ debugName: "assignLabel" }] : /* istanbul ignore next */ []));
|
|
14416
|
+
unassignLabel = input('Unassign selected', ...(ngDevMode ? [{ debugName: "unassignLabel" }] : /* istanbul ignore next */ []));
|
|
14417
|
+
/** A move happened (either direction) with the moved ids. */
|
|
14418
|
+
moved = output();
|
|
14419
|
+
sourceQuery = signal('', ...(ngDevMode ? [{ debugName: "sourceQuery" }] : /* istanbul ignore next */ []));
|
|
14420
|
+
targetQuery = signal('', ...(ngDevMode ? [{ debugName: "targetQuery" }] : /* istanbul ignore next */ []));
|
|
14421
|
+
sourceChecked = signal(new Set(), ...(ngDevMode ? [{ debugName: "sourceChecked" }] : /* istanbul ignore next */ []));
|
|
14422
|
+
targetChecked = signal(new Set(), ...(ngDevMode ? [{ debugName: "targetChecked" }] : /* istanbul ignore next */ []));
|
|
14423
|
+
assignedSet = computed(() => new Set(this.assigned()), ...(ngDevMode ? [{ debugName: "assignedSet" }] : /* istanbul ignore next */ []));
|
|
14424
|
+
sourceItems = computed(() => this.items().filter((i) => !this.assignedSet().has(i.id) && this.matches(i, this.sourceQuery())), ...(ngDevMode ? [{ debugName: "sourceItems" }] : /* istanbul ignore next */ []));
|
|
14425
|
+
targetItems = computed(() => this.items().filter((i) => this.assignedSet().has(i.id) && this.matches(i, this.targetQuery())), ...(ngDevMode ? [{ debugName: "targetItems" }] : /* istanbul ignore next */ []));
|
|
14426
|
+
matches(item, q) {
|
|
14427
|
+
return !q.trim() || item.label.toLowerCase().includes(q.trim().toLowerCase());
|
|
14428
|
+
}
|
|
14429
|
+
toggle(which, id) {
|
|
14430
|
+
const next = new Set(which());
|
|
14431
|
+
if (next.has(id))
|
|
14432
|
+
next.delete(id);
|
|
14433
|
+
else
|
|
14434
|
+
next.add(id);
|
|
14435
|
+
which.set(next);
|
|
14436
|
+
}
|
|
14437
|
+
assign() {
|
|
14438
|
+
const ids = [...this.sourceChecked()];
|
|
14439
|
+
if (!ids.length)
|
|
14440
|
+
return;
|
|
14441
|
+
this.assigned.set([...this.assigned(), ...ids]);
|
|
14442
|
+
this.sourceChecked.set(new Set());
|
|
14443
|
+
this.moved.emit({ direction: 'assign', ids });
|
|
14444
|
+
}
|
|
14445
|
+
unassign() {
|
|
14446
|
+
const ids = new Set(this.targetChecked());
|
|
14447
|
+
if (!ids.size)
|
|
14448
|
+
return;
|
|
14449
|
+
this.assigned.set(this.assigned().filter((id) => !ids.has(id)));
|
|
14450
|
+
this.targetChecked.set(new Set());
|
|
14451
|
+
this.moved.emit({ direction: 'unassign', ids: [...ids] });
|
|
14452
|
+
}
|
|
14453
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctTransfer, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
14454
|
+
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: `
|
|
14455
|
+
<div class="strct-tf__col">
|
|
14456
|
+
<div class="strct-tf__head">
|
|
14457
|
+
<span class="strct-tf__title">{{ sourceLabel() }}</span>
|
|
14458
|
+
<span class="strct-tf__count">{{ sourceItems().length }}</span>
|
|
14459
|
+
</div>
|
|
14460
|
+
<strct-searchbox
|
|
14461
|
+
class="strct-tf__search"
|
|
14462
|
+
[placeholder]="searchPlaceholder()"
|
|
14463
|
+
[value]="sourceQuery()"
|
|
14464
|
+
(valueChange)="sourceQuery.set($event)"
|
|
14465
|
+
/>
|
|
14466
|
+
<ul class="strct-tf__list" role="listbox" [attr.aria-label]="sourceLabel()">
|
|
14467
|
+
@for (item of sourceItems(); track item.id) {
|
|
14468
|
+
<li class="strct-tf__item" [class.strct-tf__item--disabled]="item.disabled">
|
|
14469
|
+
<strct-checkbox
|
|
14470
|
+
[checked]="sourceChecked().has(item.id)"
|
|
14471
|
+
[disabled]="item.disabled ?? false"
|
|
14472
|
+
(checkedChange)="toggle(sourceChecked, item.id)"
|
|
14473
|
+
>
|
|
14474
|
+
@if (item.icon) {
|
|
14475
|
+
<strct-icon [name]="item.icon" [size]="14" />
|
|
14476
|
+
}
|
|
14477
|
+
<span class="strct-tf__label">{{ item.label }}</span>
|
|
14478
|
+
</strct-checkbox>
|
|
14479
|
+
</li>
|
|
14480
|
+
} @empty {
|
|
14481
|
+
<li class="strct-tf__empty">{{ emptyLabel() }}</li>
|
|
14482
|
+
}
|
|
14483
|
+
</ul>
|
|
14484
|
+
</div>
|
|
14485
|
+
|
|
14486
|
+
<div class="strct-tf__mid">
|
|
14487
|
+
<button
|
|
14488
|
+
strct-button
|
|
14489
|
+
size="sm"
|
|
14490
|
+
variant="neutral"
|
|
14491
|
+
[disabled]="!sourceChecked().size"
|
|
14492
|
+
[attr.aria-label]="assignLabel()"
|
|
14493
|
+
(click)="assign()"
|
|
14494
|
+
>
|
|
14495
|
+
<strct-icon name="chevronRight" [size]="14" />
|
|
14496
|
+
</button>
|
|
14497
|
+
<button
|
|
14498
|
+
strct-button
|
|
14499
|
+
size="sm"
|
|
14500
|
+
variant="neutral"
|
|
14501
|
+
[disabled]="!targetChecked().size"
|
|
14502
|
+
[attr.aria-label]="unassignLabel()"
|
|
14503
|
+
(click)="unassign()"
|
|
14504
|
+
>
|
|
14505
|
+
<strct-icon name="chevronLeft" [size]="14" />
|
|
14506
|
+
</button>
|
|
14507
|
+
</div>
|
|
14508
|
+
|
|
14509
|
+
<div class="strct-tf__col">
|
|
14510
|
+
<div class="strct-tf__head">
|
|
14511
|
+
<span class="strct-tf__title">{{ targetLabel() }}</span>
|
|
14512
|
+
<span class="strct-tf__count">{{ targetItems().length }}</span>
|
|
14513
|
+
</div>
|
|
14514
|
+
<strct-searchbox
|
|
14515
|
+
class="strct-tf__search"
|
|
14516
|
+
[placeholder]="searchPlaceholder()"
|
|
14517
|
+
[value]="targetQuery()"
|
|
14518
|
+
(valueChange)="targetQuery.set($event)"
|
|
14519
|
+
/>
|
|
14520
|
+
<ul class="strct-tf__list" role="listbox" [attr.aria-label]="targetLabel()">
|
|
14521
|
+
@for (item of targetItems(); track item.id) {
|
|
14522
|
+
<li class="strct-tf__item" [class.strct-tf__item--disabled]="item.disabled">
|
|
14523
|
+
<strct-checkbox
|
|
14524
|
+
[checked]="targetChecked().has(item.id)"
|
|
14525
|
+
[disabled]="item.disabled ?? false"
|
|
14526
|
+
(checkedChange)="toggle(targetChecked, item.id)"
|
|
14527
|
+
>
|
|
14528
|
+
@if (item.icon) {
|
|
14529
|
+
<strct-icon [name]="item.icon" [size]="14" />
|
|
14530
|
+
}
|
|
14531
|
+
<span class="strct-tf__label">{{ item.label }}</span>
|
|
14532
|
+
</strct-checkbox>
|
|
14533
|
+
</li>
|
|
14534
|
+
} @empty {
|
|
14535
|
+
<li class="strct-tf__empty">{{ emptyLabel() }}</li>
|
|
14536
|
+
}
|
|
14537
|
+
</ul>
|
|
14538
|
+
</div>
|
|
14539
|
+
`, 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 });
|
|
14540
|
+
}
|
|
14541
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctTransfer, decorators: [{
|
|
14542
|
+
type: Component,
|
|
14543
|
+
args: [{ selector: 'strct-transfer', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [StrctButton, StrctCheckbox, StrctIcon, StrctSearchbox], template: `
|
|
14544
|
+
<div class="strct-tf__col">
|
|
14545
|
+
<div class="strct-tf__head">
|
|
14546
|
+
<span class="strct-tf__title">{{ sourceLabel() }}</span>
|
|
14547
|
+
<span class="strct-tf__count">{{ sourceItems().length }}</span>
|
|
14548
|
+
</div>
|
|
14549
|
+
<strct-searchbox
|
|
14550
|
+
class="strct-tf__search"
|
|
14551
|
+
[placeholder]="searchPlaceholder()"
|
|
14552
|
+
[value]="sourceQuery()"
|
|
14553
|
+
(valueChange)="sourceQuery.set($event)"
|
|
14554
|
+
/>
|
|
14555
|
+
<ul class="strct-tf__list" role="listbox" [attr.aria-label]="sourceLabel()">
|
|
14556
|
+
@for (item of sourceItems(); track item.id) {
|
|
14557
|
+
<li class="strct-tf__item" [class.strct-tf__item--disabled]="item.disabled">
|
|
14558
|
+
<strct-checkbox
|
|
14559
|
+
[checked]="sourceChecked().has(item.id)"
|
|
14560
|
+
[disabled]="item.disabled ?? false"
|
|
14561
|
+
(checkedChange)="toggle(sourceChecked, item.id)"
|
|
14562
|
+
>
|
|
14563
|
+
@if (item.icon) {
|
|
14564
|
+
<strct-icon [name]="item.icon" [size]="14" />
|
|
14565
|
+
}
|
|
14566
|
+
<span class="strct-tf__label">{{ item.label }}</span>
|
|
14567
|
+
</strct-checkbox>
|
|
14568
|
+
</li>
|
|
14569
|
+
} @empty {
|
|
14570
|
+
<li class="strct-tf__empty">{{ emptyLabel() }}</li>
|
|
14571
|
+
}
|
|
14572
|
+
</ul>
|
|
14573
|
+
</div>
|
|
14574
|
+
|
|
14575
|
+
<div class="strct-tf__mid">
|
|
14576
|
+
<button
|
|
14577
|
+
strct-button
|
|
14578
|
+
size="sm"
|
|
14579
|
+
variant="neutral"
|
|
14580
|
+
[disabled]="!sourceChecked().size"
|
|
14581
|
+
[attr.aria-label]="assignLabel()"
|
|
14582
|
+
(click)="assign()"
|
|
14583
|
+
>
|
|
14584
|
+
<strct-icon name="chevronRight" [size]="14" />
|
|
14585
|
+
</button>
|
|
14586
|
+
<button
|
|
14587
|
+
strct-button
|
|
14588
|
+
size="sm"
|
|
14589
|
+
variant="neutral"
|
|
14590
|
+
[disabled]="!targetChecked().size"
|
|
14591
|
+
[attr.aria-label]="unassignLabel()"
|
|
14592
|
+
(click)="unassign()"
|
|
14593
|
+
>
|
|
14594
|
+
<strct-icon name="chevronLeft" [size]="14" />
|
|
14595
|
+
</button>
|
|
14596
|
+
</div>
|
|
14597
|
+
|
|
14598
|
+
<div class="strct-tf__col">
|
|
14599
|
+
<div class="strct-tf__head">
|
|
14600
|
+
<span class="strct-tf__title">{{ targetLabel() }}</span>
|
|
14601
|
+
<span class="strct-tf__count">{{ targetItems().length }}</span>
|
|
14602
|
+
</div>
|
|
14603
|
+
<strct-searchbox
|
|
14604
|
+
class="strct-tf__search"
|
|
14605
|
+
[placeholder]="searchPlaceholder()"
|
|
14606
|
+
[value]="targetQuery()"
|
|
14607
|
+
(valueChange)="targetQuery.set($event)"
|
|
14608
|
+
/>
|
|
14609
|
+
<ul class="strct-tf__list" role="listbox" [attr.aria-label]="targetLabel()">
|
|
14610
|
+
@for (item of targetItems(); track item.id) {
|
|
14611
|
+
<li class="strct-tf__item" [class.strct-tf__item--disabled]="item.disabled">
|
|
14612
|
+
<strct-checkbox
|
|
14613
|
+
[checked]="targetChecked().has(item.id)"
|
|
14614
|
+
[disabled]="item.disabled ?? false"
|
|
14615
|
+
(checkedChange)="toggle(targetChecked, item.id)"
|
|
14616
|
+
>
|
|
14617
|
+
@if (item.icon) {
|
|
14618
|
+
<strct-icon [name]="item.icon" [size]="14" />
|
|
14619
|
+
}
|
|
14620
|
+
<span class="strct-tf__label">{{ item.label }}</span>
|
|
14621
|
+
</strct-checkbox>
|
|
14622
|
+
</li>
|
|
14623
|
+
} @empty {
|
|
14624
|
+
<li class="strct-tf__empty">{{ emptyLabel() }}</li>
|
|
14625
|
+
}
|
|
14626
|
+
</ul>
|
|
14627
|
+
</div>
|
|
14628
|
+
`, 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"] }]
|
|
14629
|
+
}], 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"] }] } });
|
|
14630
|
+
|
|
14631
|
+
/**
|
|
14632
|
+
* Split button — a primary action plus a chevron opening its variants
|
|
14633
|
+
* (Fluent's pattern): "Deploy ▾ → Deploy with snapshot / Deploy paused".
|
|
14634
|
+
*
|
|
14635
|
+
* <strct-split-button label="Deploy" [items]="variants"
|
|
14636
|
+
* (action)="deploy()" (picked)="run($event)" />
|
|
14637
|
+
*
|
|
14638
|
+
* The main segment fires `(action)`; menu entries fire `(picked)`.
|
|
14639
|
+
*/
|
|
14640
|
+
class StrctSplitButton {
|
|
14641
|
+
/** Main action label. */
|
|
14642
|
+
label = input.required(...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
14643
|
+
/** Menu entries (StrctMenuItem: id, label, icon?, critical?, disabled?). */
|
|
14644
|
+
items = input([], ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
|
|
14645
|
+
/** Optional leading icon of the main segment. */
|
|
14646
|
+
icon = input('', ...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
|
|
14647
|
+
/** Filled (solid accent) look. */
|
|
14648
|
+
solid = input(false, { ...(ngDevMode ? { debugName: "solid" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
14649
|
+
/** Disable both segments. */
|
|
14650
|
+
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
14651
|
+
/** Accessible name of the chevron (localizable). */
|
|
14652
|
+
menuLabel = input('More actions', ...(ngDevMode ? [{ debugName: "menuLabel" }] : /* istanbul ignore next */ []));
|
|
14653
|
+
/** The main segment was clicked. */
|
|
14654
|
+
action = output();
|
|
14655
|
+
/** A menu entry was chosen. */
|
|
14656
|
+
picked = output();
|
|
14657
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctSplitButton, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
14658
|
+
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: `
|
|
14659
|
+
<div class="strct-sbt" [class.strct-sbt--solid]="solid()">
|
|
14660
|
+
<button
|
|
14661
|
+
type="button"
|
|
14662
|
+
class="strct-sbt__main"
|
|
14663
|
+
[disabled]="disabled() || null"
|
|
14664
|
+
(click)="action.emit()"
|
|
14665
|
+
>
|
|
14666
|
+
@if (icon()) {
|
|
14667
|
+
<strct-icon [name]="icon()" [size]="13" />
|
|
14668
|
+
}
|
|
14669
|
+
{{ label() }}
|
|
14670
|
+
</button>
|
|
14671
|
+
<strct-dropdown align="end">
|
|
14672
|
+
<button
|
|
14673
|
+
type="button"
|
|
14674
|
+
class="strct-sbt__chev"
|
|
14675
|
+
strctDropdownTrigger
|
|
14676
|
+
[disabled]="disabled() || null"
|
|
14677
|
+
[attr.aria-label]="menuLabel() + ': ' + label()"
|
|
14678
|
+
>
|
|
14679
|
+
<strct-icon name="chevronDown" [size]="12" />
|
|
14680
|
+
</button>
|
|
14681
|
+
@for (item of items(); track $index) {
|
|
14682
|
+
@if (item.divider) {
|
|
14683
|
+
<strct-dropdown-divider />
|
|
14684
|
+
} @else {
|
|
14685
|
+
<strct-dropdown-item
|
|
14686
|
+
[critical]="item.critical ?? false"
|
|
14687
|
+
[disabled]="item.disabled ?? false"
|
|
14688
|
+
(click)="!item.disabled && picked.emit(item)"
|
|
14689
|
+
>
|
|
14690
|
+
@if (item.icon) {
|
|
14691
|
+
<strct-icon [name]="item.icon" [size]="13" />
|
|
14692
|
+
}
|
|
14693
|
+
{{ item.label }}
|
|
14694
|
+
</strct-dropdown-item>
|
|
14695
|
+
}
|
|
14696
|
+
}
|
|
14697
|
+
</strct-dropdown>
|
|
14698
|
+
</div>
|
|
14699
|
+
`, 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 });
|
|
14700
|
+
}
|
|
14701
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctSplitButton, decorators: [{
|
|
14702
|
+
type: Component,
|
|
14703
|
+
args: [{ selector: 'strct-split-button', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [
|
|
14704
|
+
StrctDropdown,
|
|
14705
|
+
StrctDropdownDivider,
|
|
14706
|
+
StrctDropdownItem,
|
|
14707
|
+
StrctDropdownTrigger,
|
|
14708
|
+
StrctIcon,
|
|
14709
|
+
], template: `
|
|
14710
|
+
<div class="strct-sbt" [class.strct-sbt--solid]="solid()">
|
|
14711
|
+
<button
|
|
14712
|
+
type="button"
|
|
14713
|
+
class="strct-sbt__main"
|
|
14714
|
+
[disabled]="disabled() || null"
|
|
14715
|
+
(click)="action.emit()"
|
|
14716
|
+
>
|
|
14717
|
+
@if (icon()) {
|
|
14718
|
+
<strct-icon [name]="icon()" [size]="13" />
|
|
14719
|
+
}
|
|
14720
|
+
{{ label() }}
|
|
14721
|
+
</button>
|
|
14722
|
+
<strct-dropdown align="end">
|
|
14723
|
+
<button
|
|
14724
|
+
type="button"
|
|
14725
|
+
class="strct-sbt__chev"
|
|
14726
|
+
strctDropdownTrigger
|
|
14727
|
+
[disabled]="disabled() || null"
|
|
14728
|
+
[attr.aria-label]="menuLabel() + ': ' + label()"
|
|
14729
|
+
>
|
|
14730
|
+
<strct-icon name="chevronDown" [size]="12" />
|
|
14731
|
+
</button>
|
|
14732
|
+
@for (item of items(); track $index) {
|
|
14733
|
+
@if (item.divider) {
|
|
14734
|
+
<strct-dropdown-divider />
|
|
14735
|
+
} @else {
|
|
14736
|
+
<strct-dropdown-item
|
|
14737
|
+
[critical]="item.critical ?? false"
|
|
14738
|
+
[disabled]="item.disabled ?? false"
|
|
14739
|
+
(click)="!item.disabled && picked.emit(item)"
|
|
14740
|
+
>
|
|
14741
|
+
@if (item.icon) {
|
|
14742
|
+
<strct-icon [name]="item.icon" [size]="13" />
|
|
14743
|
+
}
|
|
14744
|
+
{{ item.label }}
|
|
14745
|
+
</strct-dropdown-item>
|
|
14746
|
+
}
|
|
14747
|
+
}
|
|
14748
|
+
</strct-dropdown>
|
|
14749
|
+
</div>
|
|
14750
|
+
`, 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"] }]
|
|
14751
|
+
}], 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"] }] } });
|
|
14752
|
+
|
|
14753
|
+
/**
|
|
14754
|
+
* Repeating diagonal text watermark over its content — compliance overlays
|
|
14755
|
+
* for classified/internal consoles ("CONFIDENTIAL", "user@corp · 2026-07-21"):
|
|
14756
|
+
*
|
|
14757
|
+
* <strct-watermark text="CONFIDENTIAL">…page…</strct-watermark>
|
|
14758
|
+
*
|
|
14759
|
+
* Rendered as a pointer-transparent tiled SVG layer, so the content below
|
|
14760
|
+
* stays fully interactive and selectable. Purely visual — `aria-hidden`.
|
|
14761
|
+
*/
|
|
14762
|
+
class StrctWatermark {
|
|
14763
|
+
sanitizer = inject(DomSanitizer);
|
|
14764
|
+
/** The repeated watermark text. */
|
|
14765
|
+
text = input.required(...(ngDevMode ? [{ debugName: "text" }] : /* istanbul ignore next */ []));
|
|
14766
|
+
/** Layer opacity (0–1). */
|
|
14767
|
+
opacity = input(0.06, ...(ngDevMode ? [{ debugName: "opacity" }] : /* istanbul ignore next */ []));
|
|
14768
|
+
/** Rotation in degrees (negative = up-slope). */
|
|
14769
|
+
angle = input(-24, ...(ngDevMode ? [{ debugName: "angle" }] : /* istanbul ignore next */ []));
|
|
14770
|
+
/** Tile size in px — larger spreads the marks out. */
|
|
14771
|
+
gap = input(220, ...(ngDevMode ? [{ debugName: "gap" }] : /* istanbul ignore next */ []));
|
|
14772
|
+
/** Font size in px. */
|
|
14773
|
+
fontSize = input(15, ...(ngDevMode ? [{ debugName: "fontSize" }] : /* istanbul ignore next */ []));
|
|
14774
|
+
tile = computed(() => {
|
|
14775
|
+
const size = this.gap();
|
|
14776
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}">` +
|
|
14777
|
+
`<text x="50%" y="50%" text-anchor="middle" dominant-baseline="middle" ` +
|
|
14778
|
+
`transform="rotate(${this.angle()} ${size / 2} ${size / 2})" ` +
|
|
14779
|
+
`font-family="sans-serif" font-size="${this.fontSize()}" font-weight="600" ` +
|
|
14780
|
+
`fill="currentColor" fill-opacity="${this.opacity()}">${escapeXml(this.text())}</text></svg>`;
|
|
14781
|
+
return this.sanitizer.bypassSecurityTrustStyle(`url("data:image/svg+xml;utf8,${encodeURIComponent(svg)}")`);
|
|
14782
|
+
}, ...(ngDevMode ? [{ debugName: "tile" }] : /* istanbul ignore next */ []));
|
|
14783
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctWatermark, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
14784
|
+
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: `
|
|
14785
|
+
<ng-content />
|
|
14786
|
+
<div class="strct-wm__layer" aria-hidden="true" [style.background-image]="tile()"></div>
|
|
14787
|
+
`, 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 });
|
|
14788
|
+
}
|
|
14789
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctWatermark, decorators: [{
|
|
14790
|
+
type: Component,
|
|
14791
|
+
args: [{ selector: 'strct-watermark', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: `
|
|
14792
|
+
<ng-content />
|
|
14793
|
+
<div class="strct-wm__layer" aria-hidden="true" [style.background-image]="tile()"></div>
|
|
14794
|
+
`, 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"] }]
|
|
14795
|
+
}], 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 }] }] } });
|
|
14796
|
+
function escapeXml(s) {
|
|
14797
|
+
return s
|
|
14798
|
+
.replace(/&/g, '&')
|
|
14799
|
+
.replace(/</g, '<')
|
|
14800
|
+
.replace(/>/g, '>')
|
|
14801
|
+
.replace(/"/g, '"');
|
|
14802
|
+
}
|
|
14803
|
+
|
|
14804
|
+
/**
|
|
14805
|
+
* Feature tour (coach marks) — "what's new" onboarding over live UI:
|
|
14806
|
+
*
|
|
14807
|
+
* <strct-tour [(open)]="tourOpen" [steps]="steps" (finished)="done()" />
|
|
14808
|
+
*
|
|
14809
|
+
* Each step spotlights its `target` selector with a ring cut out of a dimmed
|
|
14810
|
+
* backdrop and anchors a card next to it. Arrows / buttons step, Escape
|
|
14811
|
+
* closes, and the card is a labeled dialog. Purely DOM-driven: targets are
|
|
14812
|
+
* looked up when the step shows, so it works over any page.
|
|
14813
|
+
*/
|
|
14814
|
+
class StrctTour {
|
|
14815
|
+
/** Whether the tour is showing (two-way). */
|
|
14816
|
+
open = model(false, ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
|
|
14817
|
+
/** The steps, in order. */
|
|
14818
|
+
steps = input.required(...(ngDevMode ? [{ debugName: "steps" }] : /* istanbul ignore next */ []));
|
|
14819
|
+
/** Localizable strings. */
|
|
14820
|
+
nextLabel = input('Next', ...(ngDevMode ? [{ debugName: "nextLabel" }] : /* istanbul ignore next */ []));
|
|
14821
|
+
backLabel = input('Back', ...(ngDevMode ? [{ debugName: "backLabel" }] : /* istanbul ignore next */ []));
|
|
14822
|
+
skipLabel = input('Skip', ...(ngDevMode ? [{ debugName: "skipLabel" }] : /* istanbul ignore next */ []));
|
|
14823
|
+
doneLabel = input('Done', ...(ngDevMode ? [{ debugName: "doneLabel" }] : /* istanbul ignore next */ []));
|
|
14824
|
+
/** The last step's Done was clicked (Escape / Skip emit `dismissed`). */
|
|
14825
|
+
finished = output();
|
|
14826
|
+
dismissed = output();
|
|
14827
|
+
index = signal(0, ...(ngDevMode ? [{ debugName: "index" }] : /* istanbul ignore next */ []));
|
|
14828
|
+
/** Bumped on open/step/resize to re-measure the target. */
|
|
14829
|
+
measureTick = signal(0, ...(ngDevMode ? [{ debugName: "measureTick" }] : /* istanbul ignore next */ []));
|
|
14830
|
+
current = computed(() => this.steps()[this.index()] ?? null, ...(ngDevMode ? [{ debugName: "current" }] : /* istanbul ignore next */ []));
|
|
14831
|
+
spot = computed(() => {
|
|
14832
|
+
this.measureTick();
|
|
14833
|
+
const step = this.current();
|
|
14834
|
+
if (!this.open() || !step?.target)
|
|
14835
|
+
return null;
|
|
14836
|
+
const el = document.querySelector(step.target);
|
|
14837
|
+
if (!el)
|
|
14838
|
+
return null;
|
|
14839
|
+
const r = el.getBoundingClientRect();
|
|
14840
|
+
return { top: r.top, left: r.left, width: r.width, height: r.height };
|
|
14841
|
+
}, ...(ngDevMode ? [{ debugName: "spot" }] : /* istanbul ignore next */ []));
|
|
14842
|
+
cardPos = computed(() => {
|
|
14843
|
+
const r = this.spot();
|
|
14844
|
+
const w = 300;
|
|
14845
|
+
const h = 170;
|
|
14846
|
+
if (!r) {
|
|
14847
|
+
return {
|
|
14848
|
+
top: Math.max(20, window.innerHeight / 2 - h / 2),
|
|
14849
|
+
left: Math.max(20, window.innerWidth / 2 - w / 2),
|
|
14850
|
+
};
|
|
14851
|
+
}
|
|
14852
|
+
// Below the target if it fits, else above; clamped into the viewport.
|
|
14853
|
+
let top = r.top + r.height + 14;
|
|
14854
|
+
if (top + h > window.innerHeight - 12)
|
|
14855
|
+
top = Math.max(12, r.top - h - 14);
|
|
14856
|
+
const left = Math.min(Math.max(12, r.left), window.innerWidth - w - 12);
|
|
14857
|
+
return { top, left };
|
|
14858
|
+
}, ...(ngDevMode ? [{ debugName: "cardPos" }] : /* istanbul ignore next */ []));
|
|
14859
|
+
constructor() {
|
|
14860
|
+
// Restart from step 0 on open; scroll the first target into view.
|
|
14861
|
+
effect(() => {
|
|
14862
|
+
if (this.open()) {
|
|
14863
|
+
this.index.set(0);
|
|
14864
|
+
this.scrollToTarget();
|
|
14865
|
+
}
|
|
14866
|
+
});
|
|
14867
|
+
}
|
|
14868
|
+
next() {
|
|
14869
|
+
if (this.index() >= this.steps().length - 1) {
|
|
14870
|
+
this.open.set(false);
|
|
14871
|
+
this.finished.emit();
|
|
14872
|
+
return;
|
|
14873
|
+
}
|
|
14874
|
+
this.index.update((i) => i + 1);
|
|
14875
|
+
this.scrollToTarget();
|
|
14876
|
+
}
|
|
14877
|
+
prev() {
|
|
14878
|
+
this.index.update((i) => Math.max(0, i - 1));
|
|
14879
|
+
this.scrollToTarget();
|
|
14880
|
+
}
|
|
14881
|
+
close() {
|
|
14882
|
+
this.open.set(false);
|
|
14883
|
+
this.dismissed.emit();
|
|
14884
|
+
}
|
|
14885
|
+
scrollToTarget() {
|
|
14886
|
+
setTimeout(() => {
|
|
14887
|
+
const sel = this.steps()[this.index()]?.target;
|
|
14888
|
+
if (sel)
|
|
14889
|
+
document.querySelector(sel)?.scrollIntoView?.({ block: 'center' });
|
|
14890
|
+
this.measureTick.update((n) => n + 1);
|
|
14891
|
+
});
|
|
14892
|
+
}
|
|
14893
|
+
onEscape() {
|
|
14894
|
+
if (this.open())
|
|
14895
|
+
this.close();
|
|
14896
|
+
}
|
|
14897
|
+
onReflow() {
|
|
14898
|
+
if (this.open())
|
|
14899
|
+
this.measureTick.update((n) => n + 1);
|
|
14900
|
+
}
|
|
14901
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctTour, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
14902
|
+
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: `
|
|
14903
|
+
@if (open() && current(); as step) {
|
|
14904
|
+
<!-- Always the click-catcher; dims only when no ring (its shadow dims). -->
|
|
14905
|
+
<div
|
|
14906
|
+
class="strct-tour__backdrop"
|
|
14907
|
+
[class.strct-tour__backdrop--dim]="!spot()"
|
|
14908
|
+
(click)="close()"
|
|
14909
|
+
></div>
|
|
14910
|
+
@if (spot(); as r) {
|
|
14911
|
+
<div
|
|
14912
|
+
class="strct-tour__ring"
|
|
14913
|
+
[style.top.px]="r.top - 6"
|
|
14914
|
+
[style.left.px]="r.left - 6"
|
|
14915
|
+
[style.width.px]="r.width + 12"
|
|
14916
|
+
[style.height.px]="r.height + 12"
|
|
14917
|
+
></div>
|
|
14918
|
+
}
|
|
14919
|
+
<div
|
|
14920
|
+
class="strct-tour__card"
|
|
14921
|
+
role="dialog"
|
|
14922
|
+
[attr.aria-label]="step.title"
|
|
14923
|
+
[style.top.px]="cardPos().top"
|
|
14924
|
+
[style.left.px]="cardPos().left"
|
|
14925
|
+
>
|
|
14926
|
+
<div class="strct-tour__step">{{ index() + 1 }} / {{ steps().length }}</div>
|
|
14927
|
+
<div class="strct-tour__title">{{ step.title }}</div>
|
|
14928
|
+
<div class="strct-tour__body">{{ step.body }}</div>
|
|
14929
|
+
<div class="strct-tour__nav">
|
|
14930
|
+
<button strct-button size="sm" variant="flat" (click)="close()">
|
|
14931
|
+
{{ skipLabel() }}
|
|
14932
|
+
</button>
|
|
14933
|
+
<span class="strct-tour__grow"></span>
|
|
14934
|
+
@if (index() > 0) {
|
|
14935
|
+
<button strct-button size="sm" variant="neutral" (click)="prev()">
|
|
14936
|
+
{{ backLabel() }}
|
|
14937
|
+
</button>
|
|
14938
|
+
}
|
|
14939
|
+
<button strct-button size="sm" variant="primary" solid (click)="next()">
|
|
14940
|
+
{{ index() === steps().length - 1 ? doneLabel() : nextLabel() }}
|
|
14941
|
+
</button>
|
|
14942
|
+
</div>
|
|
14943
|
+
</div>
|
|
14944
|
+
}
|
|
14945
|
+
`, 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 });
|
|
14946
|
+
}
|
|
14947
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctTour, decorators: [{
|
|
14948
|
+
type: Component,
|
|
14949
|
+
args: [{ selector: 'strct-tour', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [StrctButton], template: `
|
|
14950
|
+
@if (open() && current(); as step) {
|
|
14951
|
+
<!-- Always the click-catcher; dims only when no ring (its shadow dims). -->
|
|
14952
|
+
<div
|
|
14953
|
+
class="strct-tour__backdrop"
|
|
14954
|
+
[class.strct-tour__backdrop--dim]="!spot()"
|
|
14955
|
+
(click)="close()"
|
|
14956
|
+
></div>
|
|
14957
|
+
@if (spot(); as r) {
|
|
14958
|
+
<div
|
|
14959
|
+
class="strct-tour__ring"
|
|
14960
|
+
[style.top.px]="r.top - 6"
|
|
14961
|
+
[style.left.px]="r.left - 6"
|
|
14962
|
+
[style.width.px]="r.width + 12"
|
|
14963
|
+
[style.height.px]="r.height + 12"
|
|
14964
|
+
></div>
|
|
14965
|
+
}
|
|
14966
|
+
<div
|
|
14967
|
+
class="strct-tour__card"
|
|
14968
|
+
role="dialog"
|
|
14969
|
+
[attr.aria-label]="step.title"
|
|
14970
|
+
[style.top.px]="cardPos().top"
|
|
14971
|
+
[style.left.px]="cardPos().left"
|
|
14972
|
+
>
|
|
14973
|
+
<div class="strct-tour__step">{{ index() + 1 }} / {{ steps().length }}</div>
|
|
14974
|
+
<div class="strct-tour__title">{{ step.title }}</div>
|
|
14975
|
+
<div class="strct-tour__body">{{ step.body }}</div>
|
|
14976
|
+
<div class="strct-tour__nav">
|
|
14977
|
+
<button strct-button size="sm" variant="flat" (click)="close()">
|
|
14978
|
+
{{ skipLabel() }}
|
|
14979
|
+
</button>
|
|
14980
|
+
<span class="strct-tour__grow"></span>
|
|
14981
|
+
@if (index() > 0) {
|
|
14982
|
+
<button strct-button size="sm" variant="neutral" (click)="prev()">
|
|
14983
|
+
{{ backLabel() }}
|
|
14984
|
+
</button>
|
|
14985
|
+
}
|
|
14986
|
+
<button strct-button size="sm" variant="primary" solid (click)="next()">
|
|
14987
|
+
{{ index() === steps().length - 1 ? doneLabel() : nextLabel() }}
|
|
14988
|
+
</button>
|
|
14989
|
+
</div>
|
|
14990
|
+
</div>
|
|
14991
|
+
}
|
|
14992
|
+
`, 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"] }]
|
|
14993
|
+
}], 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: [{
|
|
14994
|
+
type: HostListener,
|
|
14995
|
+
args: ['document:keydown.escape']
|
|
14996
|
+
}], onReflow: [{
|
|
14997
|
+
type: HostListener,
|
|
14998
|
+
args: ['window:resize']
|
|
14999
|
+
}, {
|
|
15000
|
+
type: HostListener,
|
|
15001
|
+
args: ['window:scroll']
|
|
15002
|
+
}] } });
|
|
15003
|
+
|
|
15004
|
+
/**
|
|
15005
|
+
* Horizontal menubar — the application-menu strip ("File · Edit · View") for
|
|
15006
|
+
* dense tool-style consoles:
|
|
15007
|
+
*
|
|
15008
|
+
* <strct-menubar [menus]="menus" (picked)="run($event)" />
|
|
15009
|
+
*
|
|
15010
|
+
* Click (or Enter/Down) opens a menu; Left/Right move across the bar and,
|
|
15011
|
+
* while open, switch the open menu (the APG menubar convention). Escape and
|
|
15012
|
+
* outside click close. `(picked)` carries `{ menu, item }`.
|
|
15013
|
+
*/
|
|
15014
|
+
class StrctMenubar {
|
|
15015
|
+
host = inject(ElementRef);
|
|
15016
|
+
/** Top-level menus. */
|
|
15017
|
+
menus = input.required(...(ngDevMode ? [{ debugName: "menus" }] : /* istanbul ignore next */ []));
|
|
15018
|
+
/** Accessible name of the bar (localizable). */
|
|
15019
|
+
ariaLabel = input('Application menu', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
15020
|
+
/** A menu entry was chosen. */
|
|
15021
|
+
picked = output();
|
|
15022
|
+
openId = signal(null, ...(ngDevMode ? [{ debugName: "openId" }] : /* istanbul ignore next */ []));
|
|
15023
|
+
focusIdx = signal(0, ...(ngDevMode ? [{ debugName: "focusIdx" }] : /* istanbul ignore next */ []));
|
|
15024
|
+
toggleMenu(id, i) {
|
|
15025
|
+
this.focusIdx.set(i);
|
|
15026
|
+
this.openId.set(this.openId() === id ? null : id);
|
|
15027
|
+
}
|
|
15028
|
+
pick(menu, item) {
|
|
15029
|
+
if (item.disabled)
|
|
15030
|
+
return;
|
|
15031
|
+
this.openId.set(null);
|
|
15032
|
+
this.picked.emit({ menu, item });
|
|
15033
|
+
}
|
|
15034
|
+
onTopKey(event, i) {
|
|
15035
|
+
const menus = this.menus();
|
|
15036
|
+
if (event.key === 'ArrowRight' || event.key === 'ArrowLeft') {
|
|
15037
|
+
event.preventDefault();
|
|
15038
|
+
const dir = event.key === 'ArrowRight' ? 1 : -1;
|
|
15039
|
+
const next = (i + dir + menus.length) % menus.length;
|
|
15040
|
+
this.focusIdx.set(next);
|
|
15041
|
+
// While a menu is open, moving across the bar switches the open menu.
|
|
15042
|
+
if (this.openId())
|
|
15043
|
+
this.openId.set(menus[next].id);
|
|
15044
|
+
setTimeout(() => {
|
|
15045
|
+
const tops = this.host.nativeElement.querySelectorAll('.strct-mb__top');
|
|
15046
|
+
tops[next]?.focus();
|
|
15047
|
+
});
|
|
15048
|
+
}
|
|
15049
|
+
else if (event.key === 'ArrowDown' || event.key === 'Enter') {
|
|
15050
|
+
event.preventDefault();
|
|
15051
|
+
this.openId.set(menus[i].id);
|
|
15052
|
+
}
|
|
15053
|
+
}
|
|
15054
|
+
onDocClick(event) {
|
|
15055
|
+
if (this.openId() && !this.host.nativeElement.contains(event.target)) {
|
|
15056
|
+
this.openId.set(null);
|
|
15057
|
+
}
|
|
15058
|
+
}
|
|
15059
|
+
onEscape() {
|
|
15060
|
+
this.openId.set(null);
|
|
15061
|
+
}
|
|
15062
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctMenubar, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
15063
|
+
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: `
|
|
15064
|
+
<div class="strct-mb" role="menubar" [attr.aria-label]="ariaLabel()">
|
|
15065
|
+
@for (menu of menus(); track menu.id; let i = $index) {
|
|
15066
|
+
<div class="strct-mb__wrap">
|
|
15067
|
+
<button
|
|
15068
|
+
type="button"
|
|
15069
|
+
class="strct-mb__top"
|
|
15070
|
+
role="menuitem"
|
|
15071
|
+
[class.strct-mb__top--open]="openId() === menu.id"
|
|
15072
|
+
[attr.aria-haspopup]="'menu'"
|
|
15073
|
+
[attr.aria-expanded]="openId() === menu.id"
|
|
15074
|
+
[attr.tabindex]="i === focusIdx() ? 0 : -1"
|
|
15075
|
+
(click)="toggleMenu(menu.id, i)"
|
|
15076
|
+
(keydown)="onTopKey($event, i)"
|
|
15077
|
+
>
|
|
15078
|
+
{{ menu.label }}
|
|
15079
|
+
</button>
|
|
15080
|
+
@if (openId() === menu.id) {
|
|
15081
|
+
<div class="strct-mb__menu" role="menu" [attr.aria-label]="menu.label">
|
|
15082
|
+
@for (item of menu.items; track $index) {
|
|
15083
|
+
@if (item.divider) {
|
|
15084
|
+
<div class="strct-mb__divider" role="separator"></div>
|
|
15085
|
+
} @else {
|
|
15086
|
+
<button
|
|
15087
|
+
type="button"
|
|
15088
|
+
class="strct-mb__item"
|
|
15089
|
+
role="menuitem"
|
|
15090
|
+
[class.strct-mb__item--critical]="item.critical"
|
|
15091
|
+
[disabled]="item.disabled || null"
|
|
15092
|
+
(click)="pick(menu, item)"
|
|
15093
|
+
>
|
|
15094
|
+
@if (item.icon) {
|
|
15095
|
+
<strct-icon [name]="item.icon" [size]="13" />
|
|
15096
|
+
}
|
|
15097
|
+
{{ item.label }}
|
|
15098
|
+
</button>
|
|
15099
|
+
}
|
|
15100
|
+
}
|
|
15101
|
+
</div>
|
|
15102
|
+
}
|
|
15103
|
+
</div>
|
|
15104
|
+
}
|
|
15105
|
+
</div>
|
|
15106
|
+
`, 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 });
|
|
15107
|
+
}
|
|
15108
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctMenubar, decorators: [{
|
|
15109
|
+
type: Component,
|
|
15110
|
+
args: [{ selector: 'strct-menubar', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [StrctIcon], template: `
|
|
15111
|
+
<div class="strct-mb" role="menubar" [attr.aria-label]="ariaLabel()">
|
|
15112
|
+
@for (menu of menus(); track menu.id; let i = $index) {
|
|
15113
|
+
<div class="strct-mb__wrap">
|
|
15114
|
+
<button
|
|
15115
|
+
type="button"
|
|
15116
|
+
class="strct-mb__top"
|
|
15117
|
+
role="menuitem"
|
|
15118
|
+
[class.strct-mb__top--open]="openId() === menu.id"
|
|
15119
|
+
[attr.aria-haspopup]="'menu'"
|
|
15120
|
+
[attr.aria-expanded]="openId() === menu.id"
|
|
15121
|
+
[attr.tabindex]="i === focusIdx() ? 0 : -1"
|
|
15122
|
+
(click)="toggleMenu(menu.id, i)"
|
|
15123
|
+
(keydown)="onTopKey($event, i)"
|
|
15124
|
+
>
|
|
15125
|
+
{{ menu.label }}
|
|
15126
|
+
</button>
|
|
15127
|
+
@if (openId() === menu.id) {
|
|
15128
|
+
<div class="strct-mb__menu" role="menu" [attr.aria-label]="menu.label">
|
|
15129
|
+
@for (item of menu.items; track $index) {
|
|
15130
|
+
@if (item.divider) {
|
|
15131
|
+
<div class="strct-mb__divider" role="separator"></div>
|
|
15132
|
+
} @else {
|
|
15133
|
+
<button
|
|
15134
|
+
type="button"
|
|
15135
|
+
class="strct-mb__item"
|
|
15136
|
+
role="menuitem"
|
|
15137
|
+
[class.strct-mb__item--critical]="item.critical"
|
|
15138
|
+
[disabled]="item.disabled || null"
|
|
15139
|
+
(click)="pick(menu, item)"
|
|
15140
|
+
>
|
|
15141
|
+
@if (item.icon) {
|
|
15142
|
+
<strct-icon [name]="item.icon" [size]="13" />
|
|
15143
|
+
}
|
|
15144
|
+
{{ item.label }}
|
|
15145
|
+
</button>
|
|
15146
|
+
}
|
|
15147
|
+
}
|
|
15148
|
+
</div>
|
|
15149
|
+
}
|
|
15150
|
+
</div>
|
|
15151
|
+
}
|
|
15152
|
+
</div>
|
|
15153
|
+
`, 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"] }]
|
|
15154
|
+
}], 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: [{
|
|
15155
|
+
type: HostListener,
|
|
15156
|
+
args: ['document:click', ['$event']]
|
|
15157
|
+
}], onEscape: [{
|
|
15158
|
+
type: HostListener,
|
|
15159
|
+
args: ['document:keydown.escape']
|
|
15160
|
+
}] } });
|
|
15161
|
+
|
|
15162
|
+
/**
|
|
15163
|
+
* Screen-reader announcements for state changes that have no visible text —
|
|
15164
|
+
* "12 rows loaded", "Snapshot created" (Material's LiveAnnouncer, strct-sized):
|
|
15165
|
+
*
|
|
15166
|
+
* private announcer = inject(StrctAnnouncer);
|
|
15167
|
+
* this.announcer.announce('12 rows loaded');
|
|
15168
|
+
*
|
|
15169
|
+
* Maintains one visually-hidden live region per politeness level; repeated
|
|
15170
|
+
* identical messages are re-announced (the region is cleared first).
|
|
15171
|
+
*/
|
|
15172
|
+
class StrctAnnouncer {
|
|
15173
|
+
regions = new Map();
|
|
15174
|
+
clearTimer;
|
|
15175
|
+
announce(message, politeness = 'polite') {
|
|
15176
|
+
const region = this.regionFor(politeness);
|
|
15177
|
+
// Clear first so identical consecutive messages still fire.
|
|
15178
|
+
region.textContent = '';
|
|
15179
|
+
setTimeout(() => (region.textContent = message));
|
|
15180
|
+
clearTimeout(this.clearTimer);
|
|
15181
|
+
// Stale announcements should not linger for the next SR user to stumble on.
|
|
15182
|
+
this.clearTimer = setTimeout(() => (region.textContent = ''), 10_000);
|
|
15183
|
+
}
|
|
15184
|
+
regionFor(politeness) {
|
|
15185
|
+
let region = this.regions.get(politeness);
|
|
15186
|
+
if (!region) {
|
|
15187
|
+
region = document.createElement('div');
|
|
15188
|
+
region.setAttribute('aria-live', politeness);
|
|
15189
|
+
region.setAttribute('aria-atomic', 'true');
|
|
15190
|
+
region.className = 'strct-announcer';
|
|
15191
|
+
region.style.cssText =
|
|
15192
|
+
'position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;';
|
|
15193
|
+
document.body.appendChild(region);
|
|
15194
|
+
this.regions.set(politeness, region);
|
|
15195
|
+
}
|
|
15196
|
+
return region;
|
|
15197
|
+
}
|
|
15198
|
+
ngOnDestroy() {
|
|
15199
|
+
clearTimeout(this.clearTimer);
|
|
15200
|
+
for (const region of this.regions.values())
|
|
15201
|
+
region.remove();
|
|
15202
|
+
this.regions.clear();
|
|
15203
|
+
}
|
|
15204
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctAnnouncer, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
15205
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctAnnouncer, providedIn: 'root' });
|
|
15206
|
+
}
|
|
15207
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctAnnouncer, decorators: [{
|
|
15208
|
+
type: Injectable,
|
|
15209
|
+
args: [{ providedIn: 'root' }]
|
|
15210
|
+
}] });
|
|
15211
|
+
|
|
15212
|
+
function normalize(combo) {
|
|
15213
|
+
return combo
|
|
15214
|
+
.toLowerCase()
|
|
15215
|
+
.split('+')
|
|
15216
|
+
.map((p) => p.trim())
|
|
15217
|
+
.sort()
|
|
15218
|
+
.join('+');
|
|
15219
|
+
}
|
|
15220
|
+
function comboOf(event) {
|
|
15221
|
+
const parts = [];
|
|
15222
|
+
if (event.ctrlKey)
|
|
15223
|
+
parts.push('ctrl');
|
|
15224
|
+
if (event.metaKey)
|
|
15225
|
+
parts.push('meta');
|
|
15226
|
+
if (event.altKey)
|
|
15227
|
+
parts.push('alt');
|
|
15228
|
+
if (event.shiftKey && event.key.length > 1)
|
|
15229
|
+
parts.push('shift');
|
|
15230
|
+
parts.push(event.key.toLowerCase());
|
|
15231
|
+
return parts.sort().join('+');
|
|
15232
|
+
}
|
|
15233
|
+
/** `mod` matches Ctrl (and ⌘ on mac keyboards) — expand to both. */
|
|
15234
|
+
function expand(combo) {
|
|
15235
|
+
const n = combo.toLowerCase();
|
|
15236
|
+
if (!n.includes('mod'))
|
|
15237
|
+
return [normalize(n)];
|
|
15238
|
+
return [normalize(n.replace('mod', 'ctrl')), normalize(n.replace('mod', 'meta'))];
|
|
15239
|
+
}
|
|
15240
|
+
/**
|
|
15241
|
+
* Application hotkeys, centrally registered so they are discoverable — the
|
|
15242
|
+
* Blueprint pattern:
|
|
15243
|
+
*
|
|
15244
|
+
* private hotkeys = inject(StrctHotkeysService);
|
|
15245
|
+
* ngOnInit() { this.dispose = this.hotkeys.register(
|
|
15246
|
+
* { combo: 'mod+k', description: 'Open command palette', group: 'Global',
|
|
15247
|
+
* handler: () => this.palette.open() }); }
|
|
15248
|
+
*
|
|
15249
|
+
* Combos: `'a'`, `'?'`, `'ctrl+k'`, `'meta+shift+p'`, `'mod+k'` (mod = Ctrl/⌘).
|
|
15250
|
+
* Plain-key combos are suppressed while typing in inputs / textareas /
|
|
15251
|
+
* contenteditable; modifier combos always fire. Pair with
|
|
15252
|
+
* `<strct-hotkeys-help/>` for the ⌘/ ("?") cheatsheet overlay.
|
|
15253
|
+
*/
|
|
15254
|
+
class StrctHotkeysService {
|
|
15255
|
+
registry = signal([], ...(ngDevMode ? [{ debugName: "registry" }] : /* istanbul ignore next */ []));
|
|
15256
|
+
/** All registered hotkeys (for the help overlay). */
|
|
15257
|
+
hotkeys = this.registry.asReadonly();
|
|
15258
|
+
listener = (event) => this.onKeydown(event);
|
|
15259
|
+
constructor() {
|
|
15260
|
+
document.addEventListener('keydown', this.listener);
|
|
15261
|
+
}
|
|
15262
|
+
/** Register a hotkey; the returned function unregisters it. */
|
|
15263
|
+
register(hotkey) {
|
|
15264
|
+
this.registry.update((list) => [...list, hotkey]);
|
|
15265
|
+
return () => this.registry.update((list) => list.filter((h) => h !== hotkey));
|
|
15266
|
+
}
|
|
15267
|
+
onKeydown(event) {
|
|
15268
|
+
const combo = comboOf(event);
|
|
15269
|
+
const typing = isTyping(event);
|
|
15270
|
+
for (const hk of this.registry()) {
|
|
15271
|
+
if (!expand(hk.combo).includes(combo))
|
|
15272
|
+
continue;
|
|
15273
|
+
// While typing, only modifier combos may fire.
|
|
15274
|
+
if (typing && !(event.ctrlKey || event.metaKey || event.altKey))
|
|
15275
|
+
continue;
|
|
15276
|
+
event.preventDefault();
|
|
15277
|
+
hk.handler(event);
|
|
15278
|
+
return;
|
|
15279
|
+
}
|
|
15280
|
+
}
|
|
15281
|
+
ngOnDestroy() {
|
|
15282
|
+
document.removeEventListener('keydown', this.listener);
|
|
15283
|
+
}
|
|
15284
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctHotkeysService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
15285
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctHotkeysService, providedIn: 'root' });
|
|
15286
|
+
}
|
|
15287
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctHotkeysService, decorators: [{
|
|
15288
|
+
type: Injectable,
|
|
15289
|
+
args: [{ providedIn: 'root' }]
|
|
15290
|
+
}], ctorParameters: () => [] });
|
|
15291
|
+
function isTyping(event) {
|
|
15292
|
+
const el = event.target;
|
|
15293
|
+
if (!el)
|
|
15294
|
+
return false;
|
|
15295
|
+
return (el.tagName === 'INPUT' ||
|
|
15296
|
+
el.tagName === 'TEXTAREA' ||
|
|
15297
|
+
el.tagName === 'SELECT' ||
|
|
15298
|
+
el.isContentEditable);
|
|
15299
|
+
}
|
|
15300
|
+
/**
|
|
15301
|
+
* The hotkey cheatsheet overlay. Mount once near the app root; it registers
|
|
15302
|
+
* `?` itself and lists everything in the service, grouped:
|
|
15303
|
+
*
|
|
15304
|
+
* <strct-hotkeys-help />
|
|
15305
|
+
*/
|
|
15306
|
+
class StrctHotkeysHelp {
|
|
15307
|
+
service = inject(StrctHotkeysService);
|
|
15308
|
+
/** Overlay visibility (two-way; `?` toggles it too). */
|
|
15309
|
+
open = model(false, ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
|
|
15310
|
+
/** Localizable strings. */
|
|
15311
|
+
title = input('Keyboard shortcuts', ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
15312
|
+
emptyText = input('No shortcuts registered.', ...(ngDevMode ? [{ debugName: "emptyText" }] : /* istanbul ignore next */ []));
|
|
15313
|
+
grouped = computed(() => {
|
|
15314
|
+
const groups = new Map();
|
|
15315
|
+
for (const hk of this.service.hotkeys()) {
|
|
15316
|
+
const name = hk.group ?? 'General';
|
|
15317
|
+
const list = groups.get(name);
|
|
15318
|
+
if (list)
|
|
15319
|
+
list.push(hk);
|
|
15320
|
+
else
|
|
15321
|
+
groups.set(name, [hk]);
|
|
15322
|
+
}
|
|
15323
|
+
return [...groups.entries()].map(([name, keys]) => ({ name, keys }));
|
|
15324
|
+
}, ...(ngDevMode ? [{ debugName: "grouped" }] : /* istanbul ignore next */ []));
|
|
15325
|
+
constructor() {
|
|
15326
|
+
const dispose = this.service.register({
|
|
15327
|
+
combo: '?',
|
|
15328
|
+
description: 'Show this help',
|
|
15329
|
+
group: 'General',
|
|
15330
|
+
handler: () => this.open.update((v) => !v),
|
|
15331
|
+
});
|
|
15332
|
+
inject(DestroyRef).onDestroy(dispose);
|
|
15333
|
+
}
|
|
15334
|
+
// Escape is handled locally — registering it in the service would swallow
|
|
15335
|
+
// every Escape in the app (the service preventDefaults on match).
|
|
15336
|
+
onEscape() {
|
|
15337
|
+
if (this.open())
|
|
15338
|
+
this.open.set(false);
|
|
15339
|
+
}
|
|
15340
|
+
parts(combo) {
|
|
15341
|
+
return combo.split('+').map((p) => {
|
|
15342
|
+
const map = {
|
|
15343
|
+
mod: '⌘/Ctrl',
|
|
15344
|
+
meta: '⌘',
|
|
15345
|
+
ctrl: 'Ctrl',
|
|
15346
|
+
alt: 'Alt',
|
|
15347
|
+
shift: 'Shift',
|
|
15348
|
+
escape: 'Esc',
|
|
15349
|
+
};
|
|
15350
|
+
return map[p.trim().toLowerCase()] ?? p.trim().toUpperCase();
|
|
15351
|
+
});
|
|
15352
|
+
}
|
|
15353
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctHotkeysHelp, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
15354
|
+
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: `
|
|
15355
|
+
@if (open()) {
|
|
15356
|
+
<div class="strct-hkh__backdrop" (click)="open.set(false)"></div>
|
|
15357
|
+
<div class="strct-hkh" role="dialog" [attr.aria-label]="title()">
|
|
15358
|
+
<div class="strct-hkh__head">
|
|
15359
|
+
<span class="strct-hkh__title">{{ title() }}</span>
|
|
15360
|
+
<strct-kbd>?</strct-kbd>
|
|
15361
|
+
</div>
|
|
15362
|
+
@for (group of grouped(); track group.name) {
|
|
15363
|
+
<div class="strct-hkh__group">
|
|
15364
|
+
<div class="strct-hkh__groupname">{{ group.name }}</div>
|
|
15365
|
+
@for (hk of group.keys; track hk.combo + hk.description) {
|
|
15366
|
+
<div class="strct-hkh__row">
|
|
15367
|
+
<span class="strct-hkh__desc">{{ hk.description }}</span>
|
|
15368
|
+
<span class="strct-hkh__keys">
|
|
15369
|
+
@for (part of parts(hk.combo); track $index) {
|
|
15370
|
+
<strct-kbd>{{ part }}</strct-kbd>
|
|
15371
|
+
}
|
|
15372
|
+
</span>
|
|
15373
|
+
</div>
|
|
15374
|
+
}
|
|
15375
|
+
</div>
|
|
15376
|
+
} @empty {
|
|
15377
|
+
<p class="strct-hkh__empty">{{ emptyText() }}</p>
|
|
15378
|
+
}
|
|
15379
|
+
</div>
|
|
15380
|
+
}
|
|
15381
|
+
`, 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 });
|
|
15382
|
+
}
|
|
15383
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctHotkeysHelp, decorators: [{
|
|
15384
|
+
type: Component,
|
|
15385
|
+
args: [{ selector: 'strct-hotkeys-help', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [StrctKbd], template: `
|
|
15386
|
+
@if (open()) {
|
|
15387
|
+
<div class="strct-hkh__backdrop" (click)="open.set(false)"></div>
|
|
15388
|
+
<div class="strct-hkh" role="dialog" [attr.aria-label]="title()">
|
|
15389
|
+
<div class="strct-hkh__head">
|
|
15390
|
+
<span class="strct-hkh__title">{{ title() }}</span>
|
|
15391
|
+
<strct-kbd>?</strct-kbd>
|
|
15392
|
+
</div>
|
|
15393
|
+
@for (group of grouped(); track group.name) {
|
|
15394
|
+
<div class="strct-hkh__group">
|
|
15395
|
+
<div class="strct-hkh__groupname">{{ group.name }}</div>
|
|
15396
|
+
@for (hk of group.keys; track hk.combo + hk.description) {
|
|
15397
|
+
<div class="strct-hkh__row">
|
|
15398
|
+
<span class="strct-hkh__desc">{{ hk.description }}</span>
|
|
15399
|
+
<span class="strct-hkh__keys">
|
|
15400
|
+
@for (part of parts(hk.combo); track $index) {
|
|
15401
|
+
<strct-kbd>{{ part }}</strct-kbd>
|
|
15402
|
+
}
|
|
15403
|
+
</span>
|
|
15404
|
+
</div>
|
|
15405
|
+
}
|
|
15406
|
+
</div>
|
|
15407
|
+
} @empty {
|
|
15408
|
+
<p class="strct-hkh__empty">{{ emptyText() }}</p>
|
|
15409
|
+
}
|
|
15410
|
+
</div>
|
|
15411
|
+
}
|
|
15412
|
+
`, 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"] }]
|
|
15413
|
+
}], 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: [{
|
|
15414
|
+
type: HostListener,
|
|
15415
|
+
args: ['document:keydown.escape']
|
|
15416
|
+
}] } });
|
|
15417
|
+
|
|
15418
|
+
/**
|
|
15419
|
+
* List drag-reorder primitive — the consumer owns the array:
|
|
15420
|
+
*
|
|
15421
|
+
* <ul strctReorder (reordered)="move($event)">
|
|
15422
|
+
* @for (s of steps(); track s.id) {
|
|
15423
|
+
* <li strctReorderItem>{{ s.label }}</li>
|
|
15424
|
+
* }
|
|
15425
|
+
* </ul>
|
|
15426
|
+
*
|
|
15427
|
+
* move({ from, to }: StrctReorderEvent) {
|
|
15428
|
+
* this.steps.update((s) => { const c = [...s]; c.splice(to, 0, ...c.splice(from, 1)); return c; });
|
|
15429
|
+
* }
|
|
15430
|
+
*
|
|
15431
|
+
* Items are HTML5-draggable; keyboard reorder is Alt+ArrowUp / Alt+ArrowDown
|
|
15432
|
+
* on the focused item (items get `tabindex="0"` unless they already manage
|
|
15433
|
+
* focus). Indexes are positions among the `strctReorderItem` siblings.
|
|
15434
|
+
*/
|
|
15435
|
+
class StrctReorder {
|
|
15436
|
+
host = inject(ElementRef);
|
|
15437
|
+
/** Emits when a drag or keyboard move completes. */
|
|
15438
|
+
reordered = output();
|
|
15439
|
+
/** Disable all reordering (display-only mode). */
|
|
15440
|
+
reorderDisabled = input(false, ...(ngDevMode ? [{ debugName: "reorderDisabled" }] : /* istanbul ignore next */ []));
|
|
15441
|
+
dragIndex = signal(null, ...(ngDevMode ? [{ debugName: "dragIndex" }] : /* istanbul ignore next */ []));
|
|
15442
|
+
overIndex = signal(null, ...(ngDevMode ? [{ debugName: "overIndex" }] : /* istanbul ignore next */ []));
|
|
15443
|
+
items() {
|
|
15444
|
+
return [...this.host.nativeElement.querySelectorAll('[strctReorderItem]')];
|
|
15445
|
+
}
|
|
15446
|
+
indexOf(el) {
|
|
15447
|
+
return this.items().indexOf(el);
|
|
15448
|
+
}
|
|
15449
|
+
commit(from, to) {
|
|
15450
|
+
this.dragIndex.set(null);
|
|
15451
|
+
this.overIndex.set(null);
|
|
15452
|
+
if (from < 0 || to < 0 || from === to)
|
|
15453
|
+
return;
|
|
15454
|
+
this.reordered.emit({ from, to });
|
|
15455
|
+
}
|
|
15456
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctReorder, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
15457
|
+
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 });
|
|
15458
|
+
}
|
|
15459
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctReorder, decorators: [{
|
|
15460
|
+
type: Directive,
|
|
15461
|
+
args: [{ selector: '[strctReorder]' }]
|
|
15462
|
+
}], propDecorators: { reordered: [{ type: i0.Output, args: ["reordered"] }], reorderDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "reorderDisabled", required: false }] }] } });
|
|
15463
|
+
/** One draggable row inside a `[strctReorder]` container. */
|
|
15464
|
+
class StrctReorderItem {
|
|
15465
|
+
list = inject(StrctReorder);
|
|
15466
|
+
el = inject(ElementRef);
|
|
15467
|
+
// Captured before our own binding ever writes tabindex, so it never flips.
|
|
15468
|
+
hadOwnTabindex = this.el.nativeElement.hasAttribute('tabindex');
|
|
15469
|
+
hostTabindex() {
|
|
15470
|
+
return this.hadOwnTabindex ? null : 0;
|
|
15471
|
+
}
|
|
15472
|
+
isDragging() {
|
|
15473
|
+
return this.list.dragIndex() === this.index();
|
|
15474
|
+
}
|
|
15475
|
+
isOver() {
|
|
15476
|
+
return this.list.overIndex() === this.index() && this.list.dragIndex() !== this.index();
|
|
15477
|
+
}
|
|
15478
|
+
index() {
|
|
15479
|
+
return this.list.indexOf(this.el.nativeElement);
|
|
15480
|
+
}
|
|
15481
|
+
onDragStart(event) {
|
|
15482
|
+
if (this.list.reorderDisabled())
|
|
15483
|
+
return;
|
|
15484
|
+
this.list.dragIndex.set(this.index());
|
|
15485
|
+
event.dataTransfer?.setData('text/plain', String(this.index()));
|
|
15486
|
+
if (event.dataTransfer)
|
|
15487
|
+
event.dataTransfer.effectAllowed = 'move';
|
|
15488
|
+
}
|
|
15489
|
+
onDragOver(event) {
|
|
15490
|
+
if (this.list.dragIndex() == null)
|
|
15491
|
+
return;
|
|
15492
|
+
event.preventDefault();
|
|
15493
|
+
this.list.overIndex.set(this.index());
|
|
15494
|
+
}
|
|
15495
|
+
onDrop(event) {
|
|
15496
|
+
event.preventDefault();
|
|
15497
|
+
const from = this.list.dragIndex();
|
|
15498
|
+
if (from == null)
|
|
15499
|
+
return;
|
|
15500
|
+
this.list.commit(from, this.index());
|
|
15501
|
+
}
|
|
15502
|
+
onDragEnd() {
|
|
15503
|
+
this.list.dragIndex.set(null);
|
|
15504
|
+
this.list.overIndex.set(null);
|
|
15505
|
+
}
|
|
15506
|
+
onKeydown(event) {
|
|
15507
|
+
if (this.list.reorderDisabled() || !event.altKey)
|
|
15508
|
+
return;
|
|
15509
|
+
if (event.key !== 'ArrowUp' && event.key !== 'ArrowDown')
|
|
15510
|
+
return;
|
|
15511
|
+
event.preventDefault();
|
|
15512
|
+
const from = this.index();
|
|
15513
|
+
const to = event.key === 'ArrowUp' ? from - 1 : from + 1;
|
|
15514
|
+
if (to < 0 || to >= this.list.items().length)
|
|
15515
|
+
return;
|
|
15516
|
+
this.list.commit(from, to);
|
|
15517
|
+
// Keep focus on the moved item after the consumer re-renders.
|
|
15518
|
+
setTimeout(() => this.list.items()[to]?.focus());
|
|
15519
|
+
}
|
|
15520
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctReorderItem, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
15521
|
+
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 });
|
|
15522
|
+
}
|
|
15523
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctReorderItem, decorators: [{
|
|
15524
|
+
type: Directive,
|
|
15525
|
+
args: [{
|
|
15526
|
+
selector: '[strctReorderItem]',
|
|
15527
|
+
host: {
|
|
15528
|
+
'[attr.draggable]': '!list.reorderDisabled()',
|
|
15529
|
+
'[attr.tabindex]': 'hostTabindex()',
|
|
15530
|
+
'[class.strct-reorder--dragging]': 'isDragging()',
|
|
15531
|
+
'[class.strct-reorder--over]': 'isOver()',
|
|
15532
|
+
'[attr.aria-roledescription]': "'sortable'",
|
|
15533
|
+
},
|
|
15534
|
+
}]
|
|
15535
|
+
}], propDecorators: { onDragStart: [{
|
|
15536
|
+
type: HostListener,
|
|
15537
|
+
args: ['dragstart', ['$event']]
|
|
15538
|
+
}], onDragOver: [{
|
|
15539
|
+
type: HostListener,
|
|
15540
|
+
args: ['dragover', ['$event']]
|
|
15541
|
+
}], onDrop: [{
|
|
15542
|
+
type: HostListener,
|
|
15543
|
+
args: ['drop', ['$event']]
|
|
15544
|
+
}], onDragEnd: [{
|
|
15545
|
+
type: HostListener,
|
|
15546
|
+
args: ['dragend']
|
|
15547
|
+
}], onKeydown: [{
|
|
15548
|
+
type: HostListener,
|
|
15549
|
+
args: ['keydown', ['$event']]
|
|
15550
|
+
}] } });
|
|
15551
|
+
|
|
12762
15552
|
/**
|
|
12763
15553
|
* Public API surface of the strct UI library.
|
|
12764
15554
|
*/
|
|
@@ -12768,5 +15558,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
|
|
|
12768
15558
|
* Generated bundle index. Do not edit.
|
|
12769
15559
|
*/
|
|
12770
15560
|
|
|
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 };
|
|
15561
|
+
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
15562
|
//# sourceMappingURL=akcelik-strct.mjs.map
|