@akcelik/strct 4.12.0 → 4.14.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.
@@ -2998,6 +2998,15 @@ class StrctMenuPanel {
2998
2998
  ...(ngDevMode ? [{ debugName: "y" }] : /* istanbul ignore next */ []));
2999
2999
  /** Render as a nested submenu panel. */
3000
3000
  submenu = input(false, { ...(ngDevMode ? { debugName: "submenu" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
3001
+ /** The anchor's viewport rect, when the menu is placed against a control. */
3002
+ anchorRect = input(null, /* @ts-ignore */
3003
+ ...(ngDevMode ? [{ debugName: "anchorRect" }] : /* istanbul ignore next */ []));
3004
+ /** Preferred side and alignment against `anchorRect`. */
3005
+ placement = input('bottom-start', /* @ts-ignore */
3006
+ ...(ngDevMode ? [{ debugName: "placement" }] : /* istanbul ignore next */ []));
3007
+ /** Gap between the anchor and the menu. */
3008
+ offset = input(4, /* @ts-ignore */
3009
+ ...(ngDevMode ? [{ debugName: "offset" }] : /* istanbul ignore next */ []));
3001
3010
  /** Emitted when an item is selected. */
3002
3011
  select = output();
3003
3012
  /** Emitted when the menu requests closing. */
@@ -3008,6 +3017,13 @@ class StrctMenuPanel {
3008
3017
  ...(ngDevMode ? [{ debugName: "posX" }] : /* istanbul ignore next */ []));
3009
3018
  posY = signal(0, /* @ts-ignore */
3010
3019
  ...(ngDevMode ? [{ debugName: "posY" }] : /* istanbul ignore next */ []));
3020
+ /**
3021
+ * An anchored menu is placed from its real size, which is known only after it
3022
+ * renders — so it stays hidden for that one frame rather than appearing in
3023
+ * the wrong place first.
3024
+ */
3025
+ placed = signal(true, /* @ts-ignore */
3026
+ ...(ngDevMode ? [{ debugName: "placed" }] : /* istanbul ignore next */ []));
3011
3027
  /** Vertical offset of a submenu fly-out (matches the CSS `top: -5px` default). */
3012
3028
  subTop = signal(-5, /* @ts-ignore */
3013
3029
  ...(ngDevMode ? [{ debugName: "subTop" }] : /* istanbul ignore next */ []));
@@ -3032,15 +3048,60 @@ class StrctMenuPanel {
3032
3048
  constructor() {
3033
3049
  this.posX.set(this.x());
3034
3050
  this.posY.set(this.y());
3051
+ if (this.anchorRect())
3052
+ this.placed.set(false);
3035
3053
  afterNextRender(() => {
3036
3054
  // Open on the first entry that can act; a disabled-but-hinted one is
3037
3055
  // reachable by arrow keys, not the landing spot.
3038
3056
  const nav = this.navIndices();
3039
3057
  this.activeIndex.set(nav.find((i) => !this.items()[i].disabled) ?? nav[0] ?? 0);
3040
- this.clampToViewport();
3058
+ if (this.anchorRect())
3059
+ this.placeAgainstAnchor();
3060
+ else
3061
+ this.clampToViewport();
3062
+ this.placed.set(true);
3041
3063
  this.focusItem(this.activeIndex());
3042
3064
  });
3043
3065
  }
3066
+ /**
3067
+ * Place the panel against its anchor from the panel's measured size: the
3068
+ * preferred side when it fits, the opposite side when it does not and the
3069
+ * opposite side does, and clamped on the cross axis either way.
3070
+ */
3071
+ placeAgainstAnchor() {
3072
+ const host = this.host.nativeElement;
3073
+ const r = this.anchorRect();
3074
+ const w = host.offsetWidth;
3075
+ const h = host.offsetHeight;
3076
+ const vw = window.innerWidth;
3077
+ const vh = window.innerHeight;
3078
+ const gap = this.offset();
3079
+ const m = 6;
3080
+ const [preferred, align] = this.placement().split('-');
3081
+ let side = preferred;
3082
+ if (side === 'bottom' && r.bottom + gap + h > vh - m && r.top - gap - h >= m)
3083
+ side = 'top';
3084
+ else if (side === 'top' && r.top - gap - h < m && r.bottom + gap + h <= vh - m)
3085
+ side = 'bottom';
3086
+ else if (side === 'right' && r.right + gap + w > vw - m && r.left - gap - w >= m)
3087
+ side = 'left';
3088
+ else if (side === 'left' && r.left - gap - w < m && r.right + gap + w <= vw - m)
3089
+ side = 'right';
3090
+ let x;
3091
+ let y;
3092
+ if (side === 'bottom' || side === 'top') {
3093
+ x = align === 'end' ? r.right - w : r.left;
3094
+ y = side === 'bottom' ? r.bottom + gap : r.top - gap - h;
3095
+ }
3096
+ else {
3097
+ x = side === 'right' ? r.right + gap : r.left - gap - w;
3098
+ y = align === 'end' ? r.bottom - h : r.top;
3099
+ }
3100
+ this.posX.set(Math.max(m, Math.min(x, vw - w - m)));
3101
+ this.posY.set(Math.max(m, Math.min(y, vh - h - m)));
3102
+ const rtl = getComputedStyle(host).direction === 'rtl';
3103
+ this.flipLeft.set(rtl ? this.posX() < 220 : this.posX() + w > vw - 220);
3104
+ }
3044
3105
  clampToViewport() {
3045
3106
  const host = this.host.nativeElement;
3046
3107
  const vh = window.innerHeight;
@@ -3172,7 +3233,7 @@ class StrctMenuPanel {
3172
3233
  }
3173
3234
  }
3174
3235
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctMenuPanel, deps: [], target: i0.ɵɵFactoryTarget.Component });
3175
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: StrctMenuPanel, isStandalone: true, selector: "strct-menu-panel", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, x: { classPropertyName: "x", publicName: "x", isSignal: true, isRequired: false, transformFunction: null }, y: { classPropertyName: "y", publicName: "y", isSignal: true, isRequired: false, transformFunction: null }, submenu: { classPropertyName: "submenu", publicName: "submenu", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { select: "select", close: "close", back: "back" }, host: { properties: { "style.position": "submenu() ? null : 'fixed'", "style.left.px": "submenu() ? null : posX()", "style.top.px": "submenu() ? subTop() : posY()", "style.zIndex": "submenu() ? null : 1100" }, classAttribute: "strct-menu-host" }, ngImport: i0, template: `
3236
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: StrctMenuPanel, isStandalone: true, selector: "strct-menu-panel", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, x: { classPropertyName: "x", publicName: "x", isSignal: true, isRequired: false, transformFunction: null }, y: { classPropertyName: "y", publicName: "y", isSignal: true, isRequired: false, transformFunction: null }, submenu: { classPropertyName: "submenu", publicName: "submenu", isSignal: true, isRequired: false, transformFunction: null }, anchorRect: { classPropertyName: "anchorRect", publicName: "anchorRect", isSignal: true, isRequired: false, transformFunction: null }, placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null }, offset: { classPropertyName: "offset", publicName: "offset", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { select: "select", close: "close", back: "back" }, host: { properties: { "style.position": "submenu() ? null : 'fixed'", "style.visibility": "placed() ? null : 'hidden'", "style.left.px": "submenu() ? null : posX()", "style.top.px": "submenu() ? subTop() : posY()", "style.zIndex": "submenu() ? null : 1100" }, classAttribute: "strct-menu-host" }, ngImport: i0, template: `
3176
3237
  <div class="strct-menu" role="menu" tabindex="-1" (keydown)="onKeydown($event)">
3177
3238
  @for (item of items(); track $index; let i = $index) {
3178
3239
  @if (item.divider) {
@@ -3241,7 +3302,7 @@ class StrctMenuPanel {
3241
3302
  }
3242
3303
  }
3243
3304
  </div>
3244
- `, isInline: true, styles: [".strct-menu-host{display:block}.strct-menu{min-width:180px;padding:4px;background:var(--bg-1);border:1px solid var(--b2);border-radius:var(--radius-md);box-shadow:var(--shh);animation:strct-menu-in .1s ease}.strct-menu:focus{outline:none}.strct-menu__wrap{position:relative}.strct-menu__item{display:flex;align-items:center;gap:8px;width:100%;padding:7px 8px 7px 10px;border:0;border-radius:5px;cursor:pointer;background:transparent;color:var(--t1);font-size:13px;font-family:var(--font);text-align:start}.strct-menu__item:hover:not([aria-disabled=true]),.strct-menu__item--active:not([aria-disabled=true]){background:var(--bg-3)}.strct-menu__item:focus-visible{outline:none;background:var(--bg-3)}.strct-menu__item--critical{color:var(--critical)}.strct-menu__item--critical:hover:not([aria-disabled=true]),.strct-menu__item--critical.strct-menu__item--active:not([aria-disabled=true]){background:var(--critical-bg)}.strct-menu__item[aria-disabled=true]{opacity:.45;cursor:not-allowed}.strct-menu__icon{color:var(--t2);flex-shrink:0}.strct-menu__item--critical .strct-menu__icon{color:var(--critical)}.strct-menu__icon-spacer{width:14px;flex-shrink:0}.strct-menu__label{flex:1;white-space:nowrap}.strct-menu__arrow{color:var(--t3);flex-shrink:0}.strct-menu__sep{height:1px;margin:4px 6px;background:var(--b1)}.strct-menu__subpanel{position:absolute;top:-5px;inset-inline-start:100%;margin-inline-start:2px;z-index:var(--z-base)}.strct-menu__subpanel--flip{inset-inline-start:auto;inset-inline-end:100%;margin-inline-start:0;margin-inline-end:2px}[dir=rtl] .strct-menu__arrow{transform:rotate(180deg)}@keyframes strct-menu-in{0%{opacity:0;transform:scale(.97)}}\n"], dependencies: [{ kind: "component", type: StrctMenuPanel, selector: "strct-menu-panel", inputs: ["items", "data", "x", "y", "submenu"], outputs: ["select", "close", "back"] }, { kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "strictName", "size", "strokeWidth", "badge", "ariaLabel", "count", "countMax", "countStatus", "countLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
3305
+ `, isInline: true, styles: [".strct-menu-host{display:block}.strct-menu{min-width:180px;padding:4px;background:var(--bg-1);border:1px solid var(--b2);border-radius:var(--radius-md);box-shadow:var(--shh);animation:strct-menu-in .1s ease}.strct-menu:focus{outline:none}.strct-menu__wrap{position:relative}.strct-menu__item{display:flex;align-items:center;gap:8px;width:100%;padding:7px 8px 7px 10px;border:0;border-radius:5px;cursor:pointer;background:transparent;color:var(--t1);font-size:13px;font-family:var(--font);text-align:start}.strct-menu__item:hover:not([aria-disabled=true]),.strct-menu__item--active:not([aria-disabled=true]){background:var(--bg-3)}.strct-menu__item:focus-visible{outline:none;background:var(--bg-3)}.strct-menu__item--critical{color:var(--critical)}.strct-menu__item--critical:hover:not([aria-disabled=true]),.strct-menu__item--critical.strct-menu__item--active:not([aria-disabled=true]){background:var(--critical-bg)}.strct-menu__item[aria-disabled=true]{opacity:.45;cursor:not-allowed}.strct-menu__icon{color:var(--t2);flex-shrink:0}.strct-menu__item--critical .strct-menu__icon{color:var(--critical)}.strct-menu__icon-spacer{width:14px;flex-shrink:0}.strct-menu__label{flex:1;white-space:nowrap}.strct-menu__arrow{color:var(--t3);flex-shrink:0}.strct-menu__sep{height:1px;margin:4px 6px;background:var(--b1)}.strct-menu__subpanel{position:absolute;top:-5px;inset-inline-start:100%;margin-inline-start:2px;z-index:var(--z-base)}.strct-menu__subpanel--flip{inset-inline-start:auto;inset-inline-end:100%;margin-inline-start:0;margin-inline-end:2px}[dir=rtl] .strct-menu__arrow{transform:rotate(180deg)}@keyframes strct-menu-in{0%{opacity:0;transform:scale(.97)}}\n"], dependencies: [{ kind: "component", type: StrctMenuPanel, selector: "strct-menu-panel", inputs: ["items", "data", "x", "y", "submenu", "anchorRect", "placement", "offset"], outputs: ["select", "close", "back"] }, { kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "strictName", "size", "strokeWidth", "badge", "ariaLabel", "count", "countMax", "countStatus", "countLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
3245
3306
  }
3246
3307
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctMenuPanel, decorators: [{
3247
3308
  type: Component,
@@ -3317,11 +3378,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
3317
3378
  `, host: {
3318
3379
  class: 'strct-menu-host',
3319
3380
  '[style.position]': "submenu() ? null : 'fixed'",
3381
+ '[style.visibility]': "placed() ? null : 'hidden'",
3320
3382
  '[style.left.px]': 'submenu() ? null : posX()',
3321
3383
  '[style.top.px]': 'submenu() ? subTop() : posY()',
3322
3384
  '[style.zIndex]': 'submenu() ? null : 1100',
3323
3385
  }, styles: [".strct-menu-host{display:block}.strct-menu{min-width:180px;padding:4px;background:var(--bg-1);border:1px solid var(--b2);border-radius:var(--radius-md);box-shadow:var(--shh);animation:strct-menu-in .1s ease}.strct-menu:focus{outline:none}.strct-menu__wrap{position:relative}.strct-menu__item{display:flex;align-items:center;gap:8px;width:100%;padding:7px 8px 7px 10px;border:0;border-radius:5px;cursor:pointer;background:transparent;color:var(--t1);font-size:13px;font-family:var(--font);text-align:start}.strct-menu__item:hover:not([aria-disabled=true]),.strct-menu__item--active:not([aria-disabled=true]){background:var(--bg-3)}.strct-menu__item:focus-visible{outline:none;background:var(--bg-3)}.strct-menu__item--critical{color:var(--critical)}.strct-menu__item--critical:hover:not([aria-disabled=true]),.strct-menu__item--critical.strct-menu__item--active:not([aria-disabled=true]){background:var(--critical-bg)}.strct-menu__item[aria-disabled=true]{opacity:.45;cursor:not-allowed}.strct-menu__icon{color:var(--t2);flex-shrink:0}.strct-menu__item--critical .strct-menu__icon{color:var(--critical)}.strct-menu__icon-spacer{width:14px;flex-shrink:0}.strct-menu__label{flex:1;white-space:nowrap}.strct-menu__arrow{color:var(--t3);flex-shrink:0}.strct-menu__sep{height:1px;margin:4px 6px;background:var(--b1)}.strct-menu__subpanel{position:absolute;top:-5px;inset-inline-start:100%;margin-inline-start:2px;z-index:var(--z-base)}.strct-menu__subpanel--flip{inset-inline-start:auto;inset-inline-end:100%;margin-inline-start:0;margin-inline-end:2px}[dir=rtl] .strct-menu__arrow{transform:rotate(180deg)}@keyframes strct-menu-in{0%{opacity:0;transform:scale(.97)}}\n"] }]
3324
- }], ctorParameters: () => [], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], x: [{ type: i0.Input, args: [{ isSignal: true, alias: "x", required: false }] }], y: [{ type: i0.Input, args: [{ isSignal: true, alias: "y", required: false }] }], submenu: [{ type: i0.Input, args: [{ isSignal: true, alias: "submenu", required: false }] }], select: [{ type: i0.Output, args: ["select"] }], close: [{ type: i0.Output, args: ["close"] }], back: [{ type: i0.Output, args: ["back"] }] } });
3386
+ }], ctorParameters: () => [], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], x: [{ type: i0.Input, args: [{ isSignal: true, alias: "x", required: false }] }], y: [{ type: i0.Input, args: [{ isSignal: true, alias: "y", required: false }] }], submenu: [{ type: i0.Input, args: [{ isSignal: true, alias: "submenu", required: false }] }], anchorRect: [{ type: i0.Input, args: [{ isSignal: true, alias: "anchorRect", required: false }] }], placement: [{ type: i0.Input, args: [{ isSignal: true, alias: "placement", required: false }] }], offset: [{ type: i0.Input, args: [{ isSignal: true, alias: "offset", required: false }] }], select: [{ type: i0.Output, args: ["select"] }], close: [{ type: i0.Output, args: ["close"] }], back: [{ type: i0.Output, args: ["back"] }] } });
3325
3387
  /**
3326
3388
  * Imperatively opens the data-driven menu panel, portaled into `<body>`. Shared
3327
3389
  * by the `[strctContextMenu]` directive (right-click) and any click trigger such
@@ -3343,8 +3405,20 @@ class StrctMenuService {
3343
3405
  const ref = createComponent(StrctMenuPanel, { environmentInjector: this.envInjector });
3344
3406
  ref.setInput('items', opts.items);
3345
3407
  ref.setInput('data', opts.data);
3346
- ref.setInput('x', opts.x);
3347
- ref.setInput('y', opts.y);
3408
+ ref.setInput('x', opts.x ?? 0);
3409
+ ref.setInput('y', opts.y ?? 0);
3410
+ if (opts.anchor) {
3411
+ const rect = opts.anchor instanceof Element ? opts.anchor.getBoundingClientRect() : opts.anchor;
3412
+ ref.setInput('anchorRect', rect);
3413
+ ref.setInput('placement', opts.placement ?? 'bottom-start');
3414
+ ref.setInput('offset', opts.offset ?? 4);
3415
+ // Focus goes back to the control the menu belongs to, when that control
3416
+ // can take it — otherwise to whatever had focus before.
3417
+ if (opts.anchor instanceof HTMLElement &&
3418
+ opts.anchor.matches('button, a[href], input, select, textarea, [tabindex]')) {
3419
+ this.restoreTo = opts.anchor;
3420
+ }
3421
+ }
3348
3422
  ref.instance.select.subscribe((item) => {
3349
3423
  item.action?.(opts.data);
3350
3424
  opts.onSelect?.(item);
@@ -5003,7 +5077,10 @@ class StrctDropdown {
5003
5077
  onInnerActivate(event) {
5004
5078
  if (this.popover())
5005
5079
  return;
5006
- const item = event.target?.closest('strct-dropdown-item');
5080
+ const target = event.target;
5081
+ if (target?.closest('.strct-dd__itemaction'))
5082
+ return; // the second action is not the item
5083
+ const item = target?.closest('strct-dropdown-item');
5007
5084
  if (item && item.getAttribute('aria-disabled') !== 'true')
5008
5085
  this.close(true);
5009
5086
  }
@@ -5035,10 +5112,39 @@ class StrctDropdown {
5035
5112
  if (target.getAttribute('aria-disabled') !== 'true')
5036
5113
  target.click();
5037
5114
  }
5115
+ else if (key === 'ArrowRight' || key === 'ArrowLeft' || key === 'Delete') {
5116
+ this.onItemActionKey(event, key);
5117
+ }
5038
5118
  else if (key === 'Tab') {
5039
5119
  this.close();
5040
5120
  }
5041
5121
  }
5122
+ /**
5123
+ * An item with a trailing action has two targets: the right arrow reaches the
5124
+ * action from its item, the left arrow returns, and Delete on the item
5125
+ * triggers it (the WAI-ARIA pattern for a secondary action). Keys that do not
5126
+ * apply are left alone, so submenus keep their own arrow behaviour.
5127
+ */
5128
+ onItemActionKey(event, key) {
5129
+ const target = event.target;
5130
+ if (key === 'ArrowLeft') {
5131
+ const item = target.closest('strct-dropdown-item');
5132
+ if (!target.classList.contains('strct-dd__itemaction') || !item)
5133
+ return;
5134
+ event.preventDefault();
5135
+ item.focus();
5136
+ return;
5137
+ }
5138
+ const item = target.closest('strct-dropdown-item');
5139
+ const action = item?.querySelector('.strct-dd__itemaction');
5140
+ if (!item || item !== target || !action)
5141
+ return;
5142
+ event.preventDefault();
5143
+ if (key === 'ArrowRight')
5144
+ action.focus();
5145
+ else
5146
+ action.click();
5147
+ }
5042
5148
  enabledItems() {
5043
5149
  return [
5044
5150
  ...this.host.nativeElement.querySelectorAll('strct-dropdown-item:not([aria-disabled="true"]), strct-submenu .strct-submenu__trigger'),
@@ -5174,6 +5280,31 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
5174
5280
  }] });
5175
5281
  let dropdownItemCounter = 0;
5176
5282
  /** A selectable row inside a `<strct-dropdown>`. */
5283
+ /**
5284
+ * A second action at the end of a menu item — "delete this saved view" beside
5285
+ * "open it". It stops its own click from reaching the item, so the item is not
5286
+ * activated and the menu stays open, and it stays out of the Tab order: the
5287
+ * right arrow reaches it from its item, the left arrow returns, and Delete on
5288
+ * the item triggers it.
5289
+ */
5290
+ class StrctDropdownItemAction {
5291
+ onClick(event) {
5292
+ event.stopPropagation();
5293
+ }
5294
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDropdownItemAction, deps: [], target: i0.ɵɵFactoryTarget.Directive });
5295
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.4", type: StrctDropdownItemAction, isStandalone: true, selector: "[strctDropdownItemAction]", host: { attributes: { "tabindex": "-1" }, listeners: { "click": "onClick($event)" }, classAttribute: "strct-dd__itemaction" }, ngImport: i0 });
5296
+ }
5297
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDropdownItemAction, decorators: [{
5298
+ type: Directive,
5299
+ args: [{
5300
+ selector: '[strctDropdownItemAction]',
5301
+ host: {
5302
+ class: 'strct-dd__itemaction',
5303
+ tabindex: '-1',
5304
+ '(click)': 'onClick($event)',
5305
+ },
5306
+ }]
5307
+ }] });
5177
5308
  class StrctDropdownItem {
5178
5309
  /**
5179
5310
  * Select-like usage: bind `selected` and the item becomes a
@@ -5217,12 +5348,15 @@ class StrctDropdownItem {
5217
5348
  }
5218
5349
  </span>
5219
5350
  }
5351
+ <!-- Declared before the catch-all so its selector wins; CSS order and an
5352
+ auto margin put it at the item's end. -->
5353
+ <ng-content select="[strctDropdownItemAction]" />
5220
5354
  <ng-content />
5221
5355
  @if (hint()) {
5222
5356
  <!-- hidden: kept out of the item's accessible NAME, still read as its
5223
5357
  description through aria-describedby. -->
5224
5358
  <span [id]="hintId" hidden>{{ hint() }}</span>
5225
- }`, isInline: true, styles: [".strct-dd__item{display:flex;align-items:center;gap:8px;padding:9px 10px;border-radius:5px;cursor:pointer;font-size:13px;color:var(--t1)}.strct-dd__item:hover,.strct-dd__item:focus-visible{background:var(--bg-3);outline:none}.strct-dd__check{display:inline-flex;width:14px;flex:none;color:var(--acc)}.strct-dd__item--selected{color:var(--t1);font-weight:600;background:var(--acc-s)}.strct-dd__item--critical{color:var(--critical)}.strct-dd__item--critical:hover{background:var(--critical-bg)}.strct-dd__item[aria-disabled=true]{color:var(--t4);pointer-events:none}.strct-dd__item--hinted[aria-disabled=true]{pointer-events:auto;cursor:not-allowed}.strct-dd__item--hinted[aria-disabled=true]:hover{background:transparent}\n"], dependencies: [{ kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "strictName", "size", "strokeWidth", "badge", "ariaLabel", "count", "countMax", "countStatus", "countLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
5359
+ }`, isInline: true, styles: [".strct-dd__item{display:flex;align-items:center;gap:8px;padding:9px 10px;border-radius:5px;cursor:pointer;font-size:13px;color:var(--t1)}.strct-dd__item:hover,.strct-dd__item:focus-visible{background:var(--bg-3);outline:none}.strct-dd__itemaction{order:1;margin-inline-start:auto;flex:none}.strct-dd__check{display:inline-flex;width:14px;flex:none;color:var(--acc)}.strct-dd__item--selected{color:var(--t1);font-weight:600;background:var(--acc-s)}.strct-dd__item--critical{color:var(--critical)}.strct-dd__item--critical:hover{background:var(--critical-bg)}.strct-dd__item[aria-disabled=true]{color:var(--t4);pointer-events:none}.strct-dd__item--hinted[aria-disabled=true]{pointer-events:auto;cursor:not-allowed}.strct-dd__item--hinted[aria-disabled=true]:hover{background:transparent}\n"], dependencies: [{ kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "strictName", "size", "strokeWidth", "badge", "ariaLabel", "count", "countMax", "countStatus", "countLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
5226
5360
  }
5227
5361
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDropdownItem, decorators: [{
5228
5362
  type: Component,
@@ -5233,6 +5367,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
5233
5367
  }
5234
5368
  </span>
5235
5369
  }
5370
+ <!-- Declared before the catch-all so its selector wins; CSS order and an
5371
+ auto margin put it at the item's end. -->
5372
+ <ng-content select="[strctDropdownItemAction]" />
5236
5373
  <ng-content />
5237
5374
  @if (hint()) {
5238
5375
  <!-- hidden: kept out of the item's accessible NAME, still read as its
@@ -5250,7 +5387,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
5250
5387
  '[class.strct-dd__item--critical]': 'critical()',
5251
5388
  '[class.strct-dd__item--selected]': 'selected() === true',
5252
5389
  '[attr.aria-disabled]': 'disabled() || null',
5253
- }, styles: [".strct-dd__item{display:flex;align-items:center;gap:8px;padding:9px 10px;border-radius:5px;cursor:pointer;font-size:13px;color:var(--t1)}.strct-dd__item:hover,.strct-dd__item:focus-visible{background:var(--bg-3);outline:none}.strct-dd__check{display:inline-flex;width:14px;flex:none;color:var(--acc)}.strct-dd__item--selected{color:var(--t1);font-weight:600;background:var(--acc-s)}.strct-dd__item--critical{color:var(--critical)}.strct-dd__item--critical:hover{background:var(--critical-bg)}.strct-dd__item[aria-disabled=true]{color:var(--t4);pointer-events:none}.strct-dd__item--hinted[aria-disabled=true]{pointer-events:auto;cursor:not-allowed}.strct-dd__item--hinted[aria-disabled=true]:hover{background:transparent}\n"] }]
5390
+ }, styles: [".strct-dd__item{display:flex;align-items:center;gap:8px;padding:9px 10px;border-radius:5px;cursor:pointer;font-size:13px;color:var(--t1)}.strct-dd__item:hover,.strct-dd__item:focus-visible{background:var(--bg-3);outline:none}.strct-dd__itemaction{order:1;margin-inline-start:auto;flex:none}.strct-dd__check{display:inline-flex;width:14px;flex:none;color:var(--acc)}.strct-dd__item--selected{color:var(--t1);font-weight:600;background:var(--acc-s)}.strct-dd__item--critical{color:var(--critical)}.strct-dd__item--critical:hover{background:var(--critical-bg)}.strct-dd__item[aria-disabled=true]{color:var(--t4);pointer-events:none}.strct-dd__item--hinted[aria-disabled=true]{pointer-events:auto;cursor:not-allowed}.strct-dd__item--hinted[aria-disabled=true]:hover{background:transparent}\n"] }]
5254
5391
  }], ctorParameters: () => [], propDecorators: { selected: [{ type: i0.Input, args: [{ isSignal: true, alias: "selected", required: false }] }], critical: [{ type: i0.Input, args: [{ isSignal: true, alias: "critical", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }] } });
5255
5392
  /** Thin separator between groups of menu items. */
5256
5393
  class StrctDropdownDivider {
@@ -17884,6 +18021,22 @@ class StrctMetricTile {
17884
18021
  /** Tints the value (defaults to neutral primary text). */
17885
18022
  status = input('neutral', /* @ts-ignore */
17886
18023
  ...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
18024
+ /**
18025
+ * Tints the caption instead of the value — "2 down" is the warning, and the
18026
+ * 14 nodes above it are not. `null` keeps the caption muted.
18027
+ */
18028
+ captionStatus = input(null, /* @ts-ignore */
18029
+ ...(ngDevMode ? [{ debugName: "captionStatus" }] : /* istanbul ignore next */ []));
18030
+ /** Renders the tile as a link: the whole tile is the target. */
18031
+ href = input(null, /* @ts-ignore */
18032
+ ...(ngDevMode ? [{ debugName: "href" }] : /* istanbul ignore next */ []));
18033
+ /** Makes the whole tile a control that emits `activated`. */
18034
+ interactive = input(false, { ...(ngDevMode ? { debugName: "interactive" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
18035
+ /** Emitted when an `interactive` tile is activated. */
18036
+ activated = output();
18037
+ /** The link / button's name: the KPI and its value, since the tile is the target. */
18038
+ hitLabel = computed(() => [this.label(), `${this.value()}${this.unit()}`].filter(Boolean).join(': '), /* @ts-ignore */
18039
+ ...(ngDevMode ? [{ debugName: "hitLabel" }] : /* istanbul ignore next */ []));
17887
18040
  /** Change indicator; sign drives the arrow + colour. Null hides it. */
17888
18041
  delta = input(null, /* @ts-ignore */
17889
18042
  ...(ngDevMode ? [{ debugName: "delta" }] : /* istanbul ignore next */ []));
@@ -17935,7 +18088,7 @@ class StrctMetricTile {
17935
18088
  }, /* @ts-ignore */
17936
18089
  ...(ngDevMode ? [{ debugName: "sparkStatus" }] : /* istanbul ignore next */ []));
17937
18090
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctMetricTile, deps: [], target: i0.ɵɵFactoryTarget.Component });
17938
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: StrctMetricTile, isStandalone: true, selector: "strct-metric-tile", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, unit: { classPropertyName: "unit", publicName: "unit", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: false, transformFunction: null }, delta: { classPropertyName: "delta", publicName: "delta", isSignal: true, isRequired: false, transformFunction: null }, deltaSuffix: { classPropertyName: "deltaSuffix", publicName: "deltaSuffix", isSignal: true, isRequired: false, transformFunction: null }, deltaAriaLabel: { classPropertyName: "deltaAriaLabel", publicName: "deltaAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, invertDelta: { classPropertyName: "invertDelta", publicName: "invertDelta", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, caption: { classPropertyName: "caption", publicName: "caption", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.strct-mt--loading": "loading()", "attr.aria-busy": "loading() ? \"true\" : null" }, classAttribute: "strct-mt" }, ngImport: i0, template: `
18091
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: StrctMetricTile, isStandalone: true, selector: "strct-metric-tile", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, unit: { classPropertyName: "unit", publicName: "unit", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: false, transformFunction: null }, captionStatus: { classPropertyName: "captionStatus", publicName: "captionStatus", isSignal: true, isRequired: false, transformFunction: null }, href: { classPropertyName: "href", publicName: "href", isSignal: true, isRequired: false, transformFunction: null }, interactive: { classPropertyName: "interactive", publicName: "interactive", isSignal: true, isRequired: false, transformFunction: null }, delta: { classPropertyName: "delta", publicName: "delta", isSignal: true, isRequired: false, transformFunction: null }, deltaSuffix: { classPropertyName: "deltaSuffix", publicName: "deltaSuffix", isSignal: true, isRequired: false, transformFunction: null }, deltaAriaLabel: { classPropertyName: "deltaAriaLabel", publicName: "deltaAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, invertDelta: { classPropertyName: "invertDelta", publicName: "invertDelta", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, caption: { classPropertyName: "caption", publicName: "caption", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { activated: "activated" }, host: { properties: { "class.strct-mt--actionable": "href() || interactive()", "class.strct-mt--loading": "loading()", "attr.aria-busy": "loading() ? \"true\" : null" }, classAttribute: "strct-mt" }, ngImport: i0, template: `
17939
18092
  @if (loading()) {
17940
18093
  <strct-skeleton width="45%" height="12px" />
17941
18094
  <strct-skeleton width="65%" height="28px" />
@@ -17961,8 +18114,14 @@ class StrctMetricTile {
17961
18114
  }
17962
18115
  </div>
17963
18116
 
18117
+ <!-- A number over its bar: the meter sits under the value and above the
18118
+ caption, so "62 %" and "of 80 GB" read as one thing. -->
18119
+ <div class="strct-mt__meter"><ng-content select="[strctMetricMeter]" /></div>
18120
+
17964
18121
  @if (caption()) {
17965
- <div class="strct-mt__caption">{{ caption() }}</div>
18122
+ <div class="strct-mt__caption strct-mt__caption--{{ captionStatus() ?? 'none' }}">
18123
+ {{ caption() }}
18124
+ </div>
17966
18125
  }
17967
18126
 
17968
18127
  @if (data().length) {
@@ -17971,7 +18130,19 @@ class StrctMetricTile {
17971
18130
  </div>
17972
18131
  }
17973
18132
  }
17974
- `, isInline: true, styles: [".strct-mt{display:flex;flex-direction:column;gap:6px;padding:var(--space-3) var(--space-4);border:1px solid var(--b2);border-radius:var(--radius-lg);background:var(--bg-1);min-width:0}.strct-mt__top{display:flex;align-items:center;gap:7px}.strct-mt__icon{display:inline-flex;color:var(--t3);flex-shrink:0}.strct-mt__label{font-size:12px;font-weight:500;color:var(--t3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.strct-mt__delta{display:inline-flex;align-items:center;gap:2px;margin-inline-start:auto;font-size:12px;font-weight:600;font-variant-numeric:tabular-nums;flex-shrink:0}.strct-mt__delta--up{color:var(--success)}.strct-mt__delta--down{color:var(--critical)}.strct-mt__delta--flat{color:var(--t3)}.strct-mt__sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.strct-mt__value{display:flex;align-items:baseline;gap:4px;font-size:var(--text-3xl);font-weight:650;letter-spacing:-.01em;color:var(--t1);line-height:1.1}.strct-mt__value--accent{color:var(--acc)}.strct-mt__value--success{color:var(--success)}.strct-mt__value--warning{color:var(--warning)}.strct-mt__value--critical{color:var(--critical)}.strct-mt__unit{font-size:14px;font-weight:500;color:var(--t3);letter-spacing:0}.strct-mt__caption{font-size:12px;color:var(--t3)}.strct-mt__spark{margin-top:2px;line-height:0}.strct-mt__spark strct-sparkline,.strct-mt__spark .strct-spark__svg{width:100%}\n"], dependencies: [{ kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "strictName", "size", "strokeWidth", "badge", "ariaLabel", "count", "countMax", "countStatus", "countLabel"] }, { kind: "component", type: StrctSkeleton, selector: "strct-skeleton", inputs: ["width", "height", "circle"] }, { kind: "component", type: StrctSparkline, selector: "strct-sparkline", inputs: ["data", "status", "area", "width", "height", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
18133
+ <!-- A KPI you can drill into is a link. The hit area covers the tile, so
18134
+ the whole tile is the target and the focus ring is the tile's own. -->
18135
+ @if (href()) {
18136
+ <a class="strct-mt__hit" [href]="href()" [attr.aria-label]="hitLabel()"></a>
18137
+ } @else if (interactive()) {
18138
+ <button
18139
+ type="button"
18140
+ class="strct-mt__hit"
18141
+ [attr.aria-label]="hitLabel()"
18142
+ (click)="activated.emit()"
18143
+ ></button>
18144
+ }
18145
+ `, isInline: true, styles: [".strct-mt{position:relative;display:flex;flex-direction:column;gap:6px;padding:var(--space-3) var(--space-4);border:1px solid var(--b2);border-radius:var(--radius-lg);background:var(--bg-1);min-width:0}.strct-mt__top{display:flex;align-items:center;gap:7px}.strct-mt__icon{display:inline-flex;color:var(--t3);flex-shrink:0}.strct-mt__label{font-size:12px;font-weight:500;color:var(--t3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.strct-mt__delta{display:inline-flex;align-items:center;gap:2px;margin-inline-start:auto;font-size:12px;font-weight:600;font-variant-numeric:tabular-nums;flex-shrink:0}.strct-mt__delta--up{color:var(--success)}.strct-mt__delta--down{color:var(--critical)}.strct-mt__delta--flat{color:var(--t3)}.strct-mt__meter:empty{display:none}.strct-mt__hit{position:absolute;inset:-1px;padding:0;border:0;background:none;border-radius:inherit;cursor:pointer}.strct-mt__hit:focus-visible{outline:2px solid var(--acc50);outline-offset:2px}.strct-mt--actionable:hover{border-color:var(--b3);background:var(--bg-2)}.strct-mt__sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.strct-mt__value{display:flex;align-items:baseline;gap:4px;font-size:var(--text-3xl);font-weight:650;letter-spacing:-.01em;color:var(--t1);line-height:1.1}.strct-mt__value--accent{color:var(--acc)}.strct-mt__value--success{color:var(--success)}.strct-mt__value--warning{color:var(--warning)}.strct-mt__value--critical{color:var(--critical)}.strct-mt__unit{font-size:14px;font-weight:500;color:var(--t3);letter-spacing:0}.strct-mt__caption{font-size:12px;color:var(--t3)}.strct-mt__caption--accent{color:var(--acc)}.strct-mt__caption--success{color:var(--success)}.strct-mt__caption--warning{color:var(--warning)}.strct-mt__caption--critical{color:var(--critical)}.strct-mt__spark{margin-top:2px;line-height:0}.strct-mt__spark strct-sparkline,.strct-mt__spark .strct-spark__svg{width:100%}\n"], dependencies: [{ kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "strictName", "size", "strokeWidth", "badge", "ariaLabel", "count", "countMax", "countStatus", "countLabel"] }, { kind: "component", type: StrctSkeleton, selector: "strct-skeleton", inputs: ["width", "height", "circle"] }, { kind: "component", type: StrctSparkline, selector: "strct-sparkline", inputs: ["data", "status", "area", "width", "height", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
17975
18146
  }
17976
18147
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctMetricTile, decorators: [{
17977
18148
  type: Component,
@@ -18001,8 +18172,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
18001
18172
  }
18002
18173
  </div>
18003
18174
 
18175
+ <!-- A number over its bar: the meter sits under the value and above the
18176
+ caption, so "62 %" and "of 80 GB" read as one thing. -->
18177
+ <div class="strct-mt__meter"><ng-content select="[strctMetricMeter]" /></div>
18178
+
18004
18179
  @if (caption()) {
18005
- <div class="strct-mt__caption">{{ caption() }}</div>
18180
+ <div class="strct-mt__caption strct-mt__caption--{{ captionStatus() ?? 'none' }}">
18181
+ {{ caption() }}
18182
+ </div>
18006
18183
  }
18007
18184
 
18008
18185
  @if (data().length) {
@@ -18011,12 +18188,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
18011
18188
  </div>
18012
18189
  }
18013
18190
  }
18191
+ <!-- A KPI you can drill into is a link. The hit area covers the tile, so
18192
+ the whole tile is the target and the focus ring is the tile's own. -->
18193
+ @if (href()) {
18194
+ <a class="strct-mt__hit" [href]="href()" [attr.aria-label]="hitLabel()"></a>
18195
+ } @else if (interactive()) {
18196
+ <button
18197
+ type="button"
18198
+ class="strct-mt__hit"
18199
+ [attr.aria-label]="hitLabel()"
18200
+ (click)="activated.emit()"
18201
+ ></button>
18202
+ }
18014
18203
  `, host: {
18015
18204
  class: 'strct-mt',
18205
+ '[class.strct-mt--actionable]': 'href() || interactive()',
18016
18206
  '[class.strct-mt--loading]': 'loading()',
18017
18207
  '[attr.aria-busy]': 'loading() ? "true" : null',
18018
- }, styles: [".strct-mt{display:flex;flex-direction:column;gap:6px;padding:var(--space-3) var(--space-4);border:1px solid var(--b2);border-radius:var(--radius-lg);background:var(--bg-1);min-width:0}.strct-mt__top{display:flex;align-items:center;gap:7px}.strct-mt__icon{display:inline-flex;color:var(--t3);flex-shrink:0}.strct-mt__label{font-size:12px;font-weight:500;color:var(--t3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.strct-mt__delta{display:inline-flex;align-items:center;gap:2px;margin-inline-start:auto;font-size:12px;font-weight:600;font-variant-numeric:tabular-nums;flex-shrink:0}.strct-mt__delta--up{color:var(--success)}.strct-mt__delta--down{color:var(--critical)}.strct-mt__delta--flat{color:var(--t3)}.strct-mt__sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.strct-mt__value{display:flex;align-items:baseline;gap:4px;font-size:var(--text-3xl);font-weight:650;letter-spacing:-.01em;color:var(--t1);line-height:1.1}.strct-mt__value--accent{color:var(--acc)}.strct-mt__value--success{color:var(--success)}.strct-mt__value--warning{color:var(--warning)}.strct-mt__value--critical{color:var(--critical)}.strct-mt__unit{font-size:14px;font-weight:500;color:var(--t3);letter-spacing:0}.strct-mt__caption{font-size:12px;color:var(--t3)}.strct-mt__spark{margin-top:2px;line-height:0}.strct-mt__spark strct-sparkline,.strct-mt__spark .strct-spark__svg{width:100%}\n"] }]
18019
- }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], unit: [{ type: i0.Input, args: [{ isSignal: true, alias: "unit", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: false }] }], delta: [{ type: i0.Input, args: [{ isSignal: true, alias: "delta", required: false }] }], deltaSuffix: [{ type: i0.Input, args: [{ isSignal: true, alias: "deltaSuffix", required: false }] }], deltaAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "deltaAriaLabel", required: false }] }], invertDelta: [{ type: i0.Input, args: [{ isSignal: true, alias: "invertDelta", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], caption: [{ type: i0.Input, args: [{ isSignal: true, alias: "caption", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }] } });
18208
+ }, styles: [".strct-mt{position:relative;display:flex;flex-direction:column;gap:6px;padding:var(--space-3) var(--space-4);border:1px solid var(--b2);border-radius:var(--radius-lg);background:var(--bg-1);min-width:0}.strct-mt__top{display:flex;align-items:center;gap:7px}.strct-mt__icon{display:inline-flex;color:var(--t3);flex-shrink:0}.strct-mt__label{font-size:12px;font-weight:500;color:var(--t3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.strct-mt__delta{display:inline-flex;align-items:center;gap:2px;margin-inline-start:auto;font-size:12px;font-weight:600;font-variant-numeric:tabular-nums;flex-shrink:0}.strct-mt__delta--up{color:var(--success)}.strct-mt__delta--down{color:var(--critical)}.strct-mt__delta--flat{color:var(--t3)}.strct-mt__meter:empty{display:none}.strct-mt__hit{position:absolute;inset:-1px;padding:0;border:0;background:none;border-radius:inherit;cursor:pointer}.strct-mt__hit:focus-visible{outline:2px solid var(--acc50);outline-offset:2px}.strct-mt--actionable:hover{border-color:var(--b3);background:var(--bg-2)}.strct-mt__sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.strct-mt__value{display:flex;align-items:baseline;gap:4px;font-size:var(--text-3xl);font-weight:650;letter-spacing:-.01em;color:var(--t1);line-height:1.1}.strct-mt__value--accent{color:var(--acc)}.strct-mt__value--success{color:var(--success)}.strct-mt__value--warning{color:var(--warning)}.strct-mt__value--critical{color:var(--critical)}.strct-mt__unit{font-size:14px;font-weight:500;color:var(--t3);letter-spacing:0}.strct-mt__caption{font-size:12px;color:var(--t3)}.strct-mt__caption--accent{color:var(--acc)}.strct-mt__caption--success{color:var(--success)}.strct-mt__caption--warning{color:var(--warning)}.strct-mt__caption--critical{color:var(--critical)}.strct-mt__spark{margin-top:2px;line-height:0}.strct-mt__spark strct-sparkline,.strct-mt__spark .strct-spark__svg{width:100%}\n"] }]
18209
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], unit: [{ type: i0.Input, args: [{ isSignal: true, alias: "unit", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: false }] }], captionStatus: [{ type: i0.Input, args: [{ isSignal: true, alias: "captionStatus", required: false }] }], href: [{ type: i0.Input, args: [{ isSignal: true, alias: "href", required: false }] }], interactive: [{ type: i0.Input, args: [{ isSignal: true, alias: "interactive", required: false }] }], activated: [{ type: i0.Output, args: ["activated"] }], delta: [{ type: i0.Input, args: [{ isSignal: true, alias: "delta", required: false }] }], deltaSuffix: [{ type: i0.Input, args: [{ isSignal: true, alias: "deltaSuffix", required: false }] }], deltaAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "deltaAriaLabel", required: false }] }], invertDelta: [{ type: i0.Input, args: [{ isSignal: true, alias: "invertDelta", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], caption: [{ type: i0.Input, args: [{ isSignal: true, alias: "caption", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }] } });
18020
18210
 
18021
18211
  let heroCounter = 0;
18022
18212
  /** Default leading icon per status, when `icon` is not supplied. */
@@ -18119,9 +18309,18 @@ class StrctAlert {
18119
18309
  /** Accessible label of the dismiss button (localizable). */
18120
18310
  dismissLabel = input('Dismiss', /* @ts-ignore */
18121
18311
  ...(ngDevMode ? [{ debugName: "dismissLabel" }] : /* istanbul ignore next */ []));
18312
+ /**
18313
+ * Overrides the icon derived from `type` — a lock for a locked setting, a
18314
+ * shield for a security note, when the tone alone does not say what kind of
18315
+ * note this is. `null` keeps the derived one.
18316
+ */
18317
+ icon = input(null, /* @ts-ignore */
18318
+ ...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
18122
18319
  /** Emitted when the alert is dismissed. */
18123
18320
  closed = output();
18124
- icon = computed(() => {
18321
+ iconName = computed(() => this.icon() ?? this.derivedIcon(), /* @ts-ignore */
18322
+ ...(ngDevMode ? [{ debugName: "iconName" }] : /* istanbul ignore next */ []));
18323
+ derivedIcon = computed(() => {
18125
18324
  switch (this.type()) {
18126
18325
  case 'success':
18127
18326
  return 'success';
@@ -18133,38 +18332,46 @@ class StrctAlert {
18133
18332
  return 'info';
18134
18333
  }
18135
18334
  }, /* @ts-ignore */
18136
- ...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
18335
+ ...(ngDevMode ? [{ debugName: "derivedIcon" }] : /* istanbul ignore next */ []));
18137
18336
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctAlert, deps: [], target: i0.ɵɵFactoryTarget.Component });
18138
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: StrctAlert, isStandalone: true, selector: "strct-alert", inputs: { type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, closable: { classPropertyName: "closable", publicName: "closable", isSignal: true, isRequired: false, transformFunction: null }, dismissLabel: { classPropertyName: "dismissLabel", publicName: "dismissLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed" }, host: { properties: { "attr.role": "type() === 'critical' ? 'alert' : 'status'", "class.strct-alert--success": "type() === 'success'", "class.strct-alert--warning": "type() === 'warning'", "class.strct-alert--critical": "type() === 'critical'" }, classAttribute: "strct-alert" }, ngImport: i0, template: `
18139
- <strct-icon [name]="icon()" [size]="16" />
18140
- <div class="strct-alert__body"><ng-content /></div>
18141
- @if (closable()) {
18142
- <button
18143
- type="button"
18144
- class="strct-alert__close"
18145
- [attr.aria-label]="dismissLabel()"
18146
- (click)="closed.emit()"
18147
- >
18148
- <strct-icon name="close" [size]="13" />
18149
- </button>
18150
- }
18151
- `, isInline: true, styles: [".strct-alert{display:flex;align-items:flex-start;gap:var(--space-2);padding:var(--space-2) var(--space-3);border-radius:var(--radius-lg);font-size:13px;color:var(--t1);background:var(--bg-1);border:1px solid var(--b2);border-inline-start:3px solid var(--acc)}.strct-alert strct-icon{color:var(--acc);margin-top:1px;flex-shrink:0}.strct-alert__body{flex:1;color:var(--t1)}.strct-alert__close{flex-shrink:0;display:inline-flex;padding:2px;margin-block-start:-2px;margin-inline-end:-2px;border:0;background:transparent;color:var(--t3);cursor:pointer;border-radius:var(--radius-sm)}.strct-alert__close:hover{color:var(--t1);background:var(--bg-3)}.strct-alert--success{border-inline-start-color:var(--success)}.strct-alert--success strct-icon{color:var(--success)}.strct-alert--warning{border-inline-start-color:var(--warning)}.strct-alert--warning strct-icon{color:var(--warning)}.strct-alert--critical{border-inline-start-color:var(--critical)}.strct-alert--critical strct-icon{color:var(--critical)}\n"], dependencies: [{ kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "strictName", "size", "strokeWidth", "badge", "ariaLabel", "count", "countMax", "countStatus", "countLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
18337
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: StrctAlert, isStandalone: true, selector: "strct-alert", inputs: { type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, closable: { classPropertyName: "closable", publicName: "closable", isSignal: true, isRequired: false, transformFunction: null }, dismissLabel: { classPropertyName: "dismissLabel", publicName: "dismissLabel", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed" }, host: { properties: { "attr.role": "type() === 'critical' ? 'alert' : 'status'", "class.strct-alert--success": "type() === 'success'", "class.strct-alert--warning": "type() === 'warning'", "class.strct-alert--critical": "type() === 'critical'" }, classAttribute: "strct-alert" }, ngImport: i0, template: `
18338
+ <!-- The row carries the layout, not the host: a consumer setting
18339
+ display:block for spacing must not put the icon on its own line. -->
18340
+ <div class="strct-alert__row">
18341
+ <strct-icon class="strct-alert__icon" [name]="iconName()" [size]="16" />
18342
+ <div class="strct-alert__body"><ng-content /></div>
18343
+ @if (closable()) {
18344
+ <button
18345
+ type="button"
18346
+ class="strct-alert__close"
18347
+ [attr.aria-label]="dismissLabel()"
18348
+ (click)="closed.emit()"
18349
+ >
18350
+ <strct-icon name="close" [size]="13" />
18351
+ </button>
18352
+ }
18353
+ </div>
18354
+ `, isInline: true, styles: [".strct-alert{display:block;padding:var(--space-2) var(--space-3);border-radius:var(--radius-lg);font-size:13px;color:var(--t1);background:var(--bg-1);border:1px solid var(--b2);border-inline-start:3px solid var(--acc)}.strct-alert__row{display:flex;align-items:flex-start;gap:var(--space-2)}.strct-alert strct-icon{color:var(--acc);margin-top:1px;flex-shrink:0}.strct-alert__body{flex:1;color:var(--t1)}.strct-alert__close{flex-shrink:0;display:inline-flex;padding:2px;margin-block-start:-2px;margin-inline-end:-2px;border:0;background:transparent;color:var(--t3);cursor:pointer;border-radius:var(--radius-sm)}.strct-alert__close:hover{color:var(--t1);background:var(--bg-3)}.strct-alert--success{border-inline-start-color:var(--success)}.strct-alert--success strct-icon{color:var(--success)}.strct-alert--warning{border-inline-start-color:var(--warning)}.strct-alert--warning strct-icon{color:var(--warning)}.strct-alert--critical{border-inline-start-color:var(--critical)}.strct-alert--critical strct-icon{color:var(--critical)}\n"], dependencies: [{ kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "strictName", "size", "strokeWidth", "badge", "ariaLabel", "count", "countMax", "countStatus", "countLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
18152
18355
  }
18153
18356
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctAlert, decorators: [{
18154
18357
  type: Component,
18155
18358
  args: [{ selector: 'strct-alert', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [StrctIcon], template: `
18156
- <strct-icon [name]="icon()" [size]="16" />
18157
- <div class="strct-alert__body"><ng-content /></div>
18158
- @if (closable()) {
18159
- <button
18160
- type="button"
18161
- class="strct-alert__close"
18162
- [attr.aria-label]="dismissLabel()"
18163
- (click)="closed.emit()"
18164
- >
18165
- <strct-icon name="close" [size]="13" />
18166
- </button>
18167
- }
18359
+ <!-- The row carries the layout, not the host: a consumer setting
18360
+ display:block for spacing must not put the icon on its own line. -->
18361
+ <div class="strct-alert__row">
18362
+ <strct-icon class="strct-alert__icon" [name]="iconName()" [size]="16" />
18363
+ <div class="strct-alert__body"><ng-content /></div>
18364
+ @if (closable()) {
18365
+ <button
18366
+ type="button"
18367
+ class="strct-alert__close"
18368
+ [attr.aria-label]="dismissLabel()"
18369
+ (click)="closed.emit()"
18370
+ >
18371
+ <strct-icon name="close" [size]="13" />
18372
+ </button>
18373
+ }
18374
+ </div>
18168
18375
  `, host: {
18169
18376
  class: 'strct-alert',
18170
18377
  // role="alert" implies aria-live="assertive" for critical alerts.
@@ -18172,8 +18379,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
18172
18379
  '[class.strct-alert--success]': "type() === 'success'",
18173
18380
  '[class.strct-alert--warning]': "type() === 'warning'",
18174
18381
  '[class.strct-alert--critical]': "type() === 'critical'",
18175
- }, styles: [".strct-alert{display:flex;align-items:flex-start;gap:var(--space-2);padding:var(--space-2) var(--space-3);border-radius:var(--radius-lg);font-size:13px;color:var(--t1);background:var(--bg-1);border:1px solid var(--b2);border-inline-start:3px solid var(--acc)}.strct-alert strct-icon{color:var(--acc);margin-top:1px;flex-shrink:0}.strct-alert__body{flex:1;color:var(--t1)}.strct-alert__close{flex-shrink:0;display:inline-flex;padding:2px;margin-block-start:-2px;margin-inline-end:-2px;border:0;background:transparent;color:var(--t3);cursor:pointer;border-radius:var(--radius-sm)}.strct-alert__close:hover{color:var(--t1);background:var(--bg-3)}.strct-alert--success{border-inline-start-color:var(--success)}.strct-alert--success strct-icon{color:var(--success)}.strct-alert--warning{border-inline-start-color:var(--warning)}.strct-alert--warning strct-icon{color:var(--warning)}.strct-alert--critical{border-inline-start-color:var(--critical)}.strct-alert--critical strct-icon{color:var(--critical)}\n"] }]
18176
- }], propDecorators: { type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], closable: [{ type: i0.Input, args: [{ isSignal: true, alias: "closable", required: false }] }], dismissLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "dismissLabel", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }] } });
18382
+ }, styles: [".strct-alert{display:block;padding:var(--space-2) var(--space-3);border-radius:var(--radius-lg);font-size:13px;color:var(--t1);background:var(--bg-1);border:1px solid var(--b2);border-inline-start:3px solid var(--acc)}.strct-alert__row{display:flex;align-items:flex-start;gap:var(--space-2)}.strct-alert strct-icon{color:var(--acc);margin-top:1px;flex-shrink:0}.strct-alert__body{flex:1;color:var(--t1)}.strct-alert__close{flex-shrink:0;display:inline-flex;padding:2px;margin-block-start:-2px;margin-inline-end:-2px;border:0;background:transparent;color:var(--t3);cursor:pointer;border-radius:var(--radius-sm)}.strct-alert__close:hover{color:var(--t1);background:var(--bg-3)}.strct-alert--success{border-inline-start-color:var(--success)}.strct-alert--success strct-icon{color:var(--success)}.strct-alert--warning{border-inline-start-color:var(--warning)}.strct-alert--warning strct-icon{color:var(--warning)}.strct-alert--critical{border-inline-start-color:var(--critical)}.strct-alert--critical strct-icon{color:var(--critical)}\n"] }]
18383
+ }], propDecorators: { type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], closable: [{ type: i0.Input, args: [{ isSignal: true, alias: "closable", required: false }] }], dismissLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "dismissLabel", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }] } });
18177
18384
 
18178
18385
  let tooltipCounter = 0;
18179
18386
  /**
@@ -21721,13 +21928,19 @@ class StrctStatusDot {
21721
21928
  /** Dot size: `sm` for dense rows, `md` standalone. */
21722
21929
  size = input('md', /* @ts-ignore */
21723
21930
  ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
21931
+ /**
21932
+ * Something happening *now* — a task running, a live chart. The halo pulses
21933
+ * once a second; the dot itself never changes size, so a column of dots does
21934
+ * not jitter. Under `prefers-reduced-motion` the halo is a static ring.
21935
+ */
21936
+ pulse = input(false, { ...(ngDevMode ? { debugName: "pulse" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
21724
21937
  text = computed(() => this.label() || DEFAULT_LABEL[this.status()], /* @ts-ignore */
21725
21938
  ...(ngDevMode ? [{ debugName: "text" }] : /* istanbul ignore next */ []));
21726
21939
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctStatusDot, deps: [], target: i0.ɵɵFactoryTarget.Component });
21727
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.4", type: StrctStatusDot, isStandalone: true, selector: "strct-status-dot", inputs: { status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.strct-dot--accent": "status() === 'accent'", "class.strct-dot--success": "status() === 'success'", "class.strct-dot--warning": "status() === 'warning'", "class.strct-dot--critical": "status() === 'critical'", "class.strct-dot--sm": "size() === 'sm'" }, classAttribute: "strct-dot" }, ngImport: i0, template: `
21940
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.4", type: StrctStatusDot, isStandalone: true, selector: "strct-status-dot", inputs: { status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, pulse: { classPropertyName: "pulse", publicName: "pulse", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.strct-dot--accent": "status() === 'accent'", "class.strct-dot--success": "status() === 'success'", "class.strct-dot--warning": "status() === 'warning'", "class.strct-dot--critical": "status() === 'critical'", "class.strct-dot--sm": "size() === 'sm'", "class.strct-dot--pulse": "pulse()" }, classAttribute: "strct-dot" }, ngImport: i0, template: `
21728
21941
  <span class="strct-dot__dot" aria-hidden="true"></span>
21729
21942
  <span class="strct-dot__sr">{{ text() }}</span>
21730
- `, isInline: true, styles: [".strct-dot{display:inline-flex;align-items:center;flex:none;line-height:0}.strct-dot__dot{width:10px;height:10px;border-radius:50%;background:var(--t3)}.strct-dot--sm .strct-dot__dot{width:8px;height:8px}.strct-dot--accent .strct-dot__dot{background:var(--acc)}.strct-dot--success .strct-dot__dot{background:var(--success)}.strct-dot--warning .strct-dot__dot{background:var(--warning)}.strct-dot--critical .strct-dot__dot{background:var(--critical)}.strct-dot__sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
21943
+ `, isInline: true, styles: [".strct-dot{display:inline-flex;align-items:center;flex:none;line-height:0}.strct-dot__dot{width:10px;height:10px;border-radius:50%;background:var(--t3)}.strct-dot--sm .strct-dot__dot{width:8px;height:8px}.strct-dot--accent .strct-dot__dot{background:var(--acc)}.strct-dot--success .strct-dot__dot{background:var(--success)}.strct-dot--warning .strct-dot__dot{background:var(--warning)}.strct-dot--critical .strct-dot__dot{background:var(--critical)}.strct-dot--pulse .strct-dot__dot{position:relative}.strct-dot--pulse .strct-dot__dot:after{content:\"\";position:absolute;inset:0;border-radius:50%;background:inherit;animation:strct-dot-pulse 1s ease-out infinite}@keyframes strct-dot-pulse{0%{box-shadow:0 0 0 0 currentColor;opacity:.55}to{box-shadow:0 0 0 5px transparent;opacity:0}}.strct-dot--pulse .strct-dot__dot{color:var(--t3)}.strct-dot--pulse.strct-dot--accent .strct-dot__dot{color:var(--acc)}.strct-dot--pulse.strct-dot--success .strct-dot__dot{color:var(--success)}.strct-dot--pulse.strct-dot--warning .strct-dot__dot{color:var(--warning)}.strct-dot--pulse.strct-dot--critical .strct-dot__dot{color:var(--critical)}@media(prefers-reduced-motion:reduce){.strct-dot--pulse .strct-dot__dot:after{animation:none;box-shadow:0 0 0 3px currentColor;opacity:.25}}.strct-dot__sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
21731
21944
  }
21732
21945
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctStatusDot, decorators: [{
21733
21946
  type: Component,
@@ -21741,8 +21954,115 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
21741
21954
  '[class.strct-dot--warning]': "status() === 'warning'",
21742
21955
  '[class.strct-dot--critical]': "status() === 'critical'",
21743
21956
  '[class.strct-dot--sm]': "size() === 'sm'",
21744
- }, styles: [".strct-dot{display:inline-flex;align-items:center;flex:none;line-height:0}.strct-dot__dot{width:10px;height:10px;border-radius:50%;background:var(--t3)}.strct-dot--sm .strct-dot__dot{width:8px;height:8px}.strct-dot--accent .strct-dot__dot{background:var(--acc)}.strct-dot--success .strct-dot__dot{background:var(--success)}.strct-dot--warning .strct-dot__dot{background:var(--warning)}.strct-dot--critical .strct-dot__dot{background:var(--critical)}.strct-dot__sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}\n"] }]
21745
- }], propDecorators: { status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }] } });
21957
+ '[class.strct-dot--pulse]': 'pulse()',
21958
+ }, styles: [".strct-dot{display:inline-flex;align-items:center;flex:none;line-height:0}.strct-dot__dot{width:10px;height:10px;border-radius:50%;background:var(--t3)}.strct-dot--sm .strct-dot__dot{width:8px;height:8px}.strct-dot--accent .strct-dot__dot{background:var(--acc)}.strct-dot--success .strct-dot__dot{background:var(--success)}.strct-dot--warning .strct-dot__dot{background:var(--warning)}.strct-dot--critical .strct-dot__dot{background:var(--critical)}.strct-dot--pulse .strct-dot__dot{position:relative}.strct-dot--pulse .strct-dot__dot:after{content:\"\";position:absolute;inset:0;border-radius:50%;background:inherit;animation:strct-dot-pulse 1s ease-out infinite}@keyframes strct-dot-pulse{0%{box-shadow:0 0 0 0 currentColor;opacity:.55}to{box-shadow:0 0 0 5px transparent;opacity:0}}.strct-dot--pulse .strct-dot__dot{color:var(--t3)}.strct-dot--pulse.strct-dot--accent .strct-dot__dot{color:var(--acc)}.strct-dot--pulse.strct-dot--success .strct-dot__dot{color:var(--success)}.strct-dot--pulse.strct-dot--warning .strct-dot__dot{color:var(--warning)}.strct-dot--pulse.strct-dot--critical .strct-dot__dot{color:var(--critical)}@media(prefers-reduced-motion:reduce){.strct-dot--pulse .strct-dot__dot:after{animation:none;box-shadow:0 0 0 3px currentColor;opacity:.25}}.strct-dot__sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}\n"] }]
21959
+ }], propDecorators: { status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], pulse: [{ type: i0.Input, args: [{ isSignal: true, alias: "pulse", required: false }] }] } });
21960
+
21961
+ const DEFAULT_LABELS = {
21962
+ live: 'Live',
21963
+ liveEvery: (s) => `Live · updates every ${s} s`,
21964
+ connecting: 'Connecting…',
21965
+ reconnecting: 'Reconnecting…',
21966
+ paused: 'Paused',
21967
+ stale: (relative) => `Last updated ${relative}`,
21968
+ justNow: 'just now',
21969
+ minutesAgo: (n) => `${n} min ago`,
21970
+ hoursAgo: (n) => `${n} h ago`,
21971
+ };
21972
+ const TONE = {
21973
+ live: 'success',
21974
+ connecting: 'accent',
21975
+ reconnecting: 'warning',
21976
+ paused: 'neutral',
21977
+ stale: 'warning',
21978
+ };
21979
+ /**
21980
+ * "Live · updates every 5 s" and "Reconnecting…" are states of one indicator,
21981
+ * not three captions written by hand next to three hand-rolled dots. A dot that
21982
+ * is live and a dot that is merely green are different facts.
21983
+ *
21984
+ * <strct-live-indicator state="live" [interval]="5000" />
21985
+ * <strct-live-indicator state="stale" [updatedAt]="lastRead" />
21986
+ *
21987
+ * It is a `role="status"`, so a change of state is announced politely — and the
21988
+ * relative time re-renders on its own every 30 s without announcing each tick.
21989
+ */
21990
+ class StrctLiveIndicator {
21991
+ /** What the view is doing. */
21992
+ state = input('live', /* @ts-ignore */
21993
+ ...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
21994
+ /** Refresh period in ms — turns "Live" into "Live · updates every 5 s". */
21995
+ interval = input(null, /* @ts-ignore */
21996
+ ...(ngDevMode ? [{ debugName: "interval" }] : /* istanbul ignore next */ []));
21997
+ /** When the data last arrived; drives the relative time of `stale`. */
21998
+ updatedAt = input(null, /* @ts-ignore */
21999
+ ...(ngDevMode ? [{ debugName: "updatedAt" }] : /* istanbul ignore next */ []));
22000
+ /** Every string, for localisation. */
22001
+ labels = input({}, /* @ts-ignore */
22002
+ ...(ngDevMode ? [{ debugName: "labels" }] : /* istanbul ignore next */ []));
22003
+ tone = computed(() => TONE[this.state()], /* @ts-ignore */
22004
+ ...(ngDevMode ? [{ debugName: "tone" }] : /* istanbul ignore next */ []));
22005
+ /** Only a view that is actually receiving data pulses. */
22006
+ pulsing = computed(() => this.state() === 'live', /* @ts-ignore */
22007
+ ...(ngDevMode ? [{ debugName: "pulsing" }] : /* istanbul ignore next */ []));
22008
+ L = computed(() => ({ ...DEFAULT_LABELS, ...this.labels() }), /* @ts-ignore */
22009
+ ...(ngDevMode ? [{ debugName: "L" }] : /* istanbul ignore next */ []));
22010
+ /** Re-read every 30 s, so "3 min ago" does not go stale on screen. */
22011
+ now = signal(Date.now(), /* @ts-ignore */
22012
+ ...(ngDevMode ? [{ debugName: "now" }] : /* istanbul ignore next */ []));
22013
+ constructor() {
22014
+ const timer = setInterval(() => this.now.set(Date.now()), 30_000);
22015
+ inject(DestroyRef).onDestroy(() => clearInterval(timer));
22016
+ }
22017
+ relative = computed(() => {
22018
+ const at = this.updatedAt();
22019
+ if (at == null)
22020
+ return '';
22021
+ const ms = this.now() - (at instanceof Date ? at.getTime() : at);
22022
+ const mins = Math.floor(ms / 60_000);
22023
+ if (mins < 1)
22024
+ return this.L().justNow;
22025
+ if (mins < 60)
22026
+ return this.L().minutesAgo(mins);
22027
+ return this.L().hoursAgo(Math.floor(mins / 60));
22028
+ }, /* @ts-ignore */
22029
+ ...(ngDevMode ? [{ debugName: "relative" }] : /* istanbul ignore next */ []));
22030
+ text = computed(() => {
22031
+ const l = this.L();
22032
+ switch (this.state()) {
22033
+ case 'live': {
22034
+ const ms = this.interval();
22035
+ return ms ? l.liveEvery(Math.round(ms / 1000)) : l.live;
22036
+ }
22037
+ case 'connecting':
22038
+ return l.connecting;
22039
+ case 'reconnecting':
22040
+ return l.reconnecting;
22041
+ case 'paused':
22042
+ return l.paused;
22043
+ case 'stale':
22044
+ return this.relative() ? l.stale(this.relative()) : l.stale('');
22045
+ }
22046
+ }, /* @ts-ignore */
22047
+ ...(ngDevMode ? [{ debugName: "text" }] : /* istanbul ignore next */ []));
22048
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctLiveIndicator, deps: [], target: i0.ɵɵFactoryTarget.Component });
22049
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.4", type: StrctLiveIndicator, isStandalone: true, selector: "strct-live-indicator", inputs: { state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: false, transformFunction: null }, interval: { classPropertyName: "interval", publicName: "interval", isSignal: true, isRequired: false, transformFunction: null }, updatedAt: { classPropertyName: "updatedAt", publicName: "updatedAt", isSignal: true, isRequired: false, transformFunction: null }, labels: { classPropertyName: "labels", publicName: "labels", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "status", "aria-live": "polite" }, properties: { "attr.data-state": "state()" }, classAttribute: "strct-live" }, ngImport: i0, template: `
22050
+ <strct-status-dot size="sm" [status]="tone()" [pulse]="pulsing()" [label]="text()" />
22051
+ <span class="strct-live__text">{{ text() }}</span>
22052
+ `, isInline: true, styles: [".strct-live{display:inline-flex;align-items:center;gap:6px;font-size:var(--text-sm);color:var(--t3)}.strct-live[data-state=reconnecting] .strct-live__text,.strct-live[data-state=stale] .strct-live__text{color:var(--warning)}.strct-live .strct-dot__sr{display:none}\n"], dependencies: [{ kind: "component", type: StrctStatusDot, selector: "strct-status-dot", inputs: ["status", "label", "size", "pulse"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
22053
+ }
22054
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctLiveIndicator, decorators: [{
22055
+ type: Component,
22056
+ args: [{ selector: 'strct-live-indicator', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [StrctStatusDot], template: `
22057
+ <strct-status-dot size="sm" [status]="tone()" [pulse]="pulsing()" [label]="text()" />
22058
+ <span class="strct-live__text">{{ text() }}</span>
22059
+ `, host: {
22060
+ class: 'strct-live',
22061
+ role: 'status',
22062
+ 'aria-live': 'polite',
22063
+ '[attr.data-state]': 'state()',
22064
+ }, styles: [".strct-live{display:inline-flex;align-items:center;gap:6px;font-size:var(--text-sm);color:var(--t3)}.strct-live[data-state=reconnecting] .strct-live__text,.strct-live[data-state=stale] .strct-live__text{color:var(--warning)}.strct-live .strct-dot__sr{display:none}\n"] }]
22065
+ }], ctorParameters: () => [], propDecorators: { state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: false }] }], interval: [{ type: i0.Input, args: [{ isSignal: true, alias: "interval", required: false }] }], updatedAt: [{ type: i0.Input, args: [{ isSignal: true, alias: "updatedAt", required: false }] }], labels: [{ type: i0.Input, args: [{ isSignal: true, alias: "labels", required: false }] }] } });
21746
22066
 
21747
22067
  /**
21748
22068
  * A generic anchored overlay panel for arbitrary projected content — the
@@ -23610,5 +23930,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
23610
23930
  * Generated bundle index. Do not edit.
23611
23931
  */
23612
23932
 
23613
- export { FOCUSABLE_SELECTOR, STRCT_DP_DOW, STRCT_DP_DOW_FULL, STRCT_DP_MONTHS, STRCT_ICONS, STRCT_ICON_GROUPS, STRCT_ICON_NAMES, STRCT_MASKS, STRCT_NOTIFICATION_HISTORY_LIMIT, STRCT_PALETTES, STRCT_RAW_ICONS, STRCT_TIME_RANGE_PRESETS, STRCT_WIZARD_DEFAULTS, StrctAccordion, StrctAccordionPanel, StrctAlert, StrctAnnouncer, StrctAvatar, StrctBadge, StrctBreadcrumb, StrctBreadcrumbItem, StrctButton, StrctButtonGroup, StrctBytesPipe, StrctCard, StrctCardBlock, StrctCardFooter, StrctCardHeader, StrctCardHeaderActions, StrctCardHeaderMeta, StrctCardHeaderNote, StrctCascadeColumn, StrctCascadeHost, StrctCascadeSelect, StrctCellDef, StrctCellStatus, StrctChart, StrctCheckbox, StrctChips, StrctCode, StrctCodeInline, StrctColorPicker, StrctCombobox, StrctCommandPalette, StrctConfirmOutlet, StrctConfirmService, StrctContextMenu, StrctContextMenuTrigger, StrctControlDescription, StrctCopy, StrctDatagrid, StrctDatagridActionBar, StrctDatagridActionBarCaption, StrctDatepicker, StrctDatetimePicker, StrctDesc, StrctDescriptionList, StrctDiff, StrctDivider, StrctDonut, StrctDrawer, StrctDrawerFooter, StrctDropdown, StrctDropdownDivider, StrctDropdownItem, StrctDropdownTrigger, StrctDurationPipe, StrctEmptyState, StrctField, StrctFieldGroup, StrctFieldHint, StrctFieldPrefix, StrctFieldSuffix, StrctFile, StrctFilterBar, StrctFlow, StrctFooter, StrctGauge, StrctHeader, StrctHeatmap, StrctHero, StrctHotkeysHelp, StrctHotkeysService, StrctIcon, StrctInlineEdit, StrctInput, StrctInputMask, StrctInputOtp, StrctKbd, StrctKnob, StrctList, StrctListItem, StrctListItemDescription, StrctListItemLeading, StrctListItemMeta, StrctListItemTrailing, StrctLogViewer, StrctLogin, StrctMenuPanel, StrctMenuService, StrctMenubar, StrctMetricTile, StrctModal, StrctModalContent, StrctNav, StrctNavItem, StrctNotificationCenter, StrctNumber, StrctOverlay, StrctPageHeader, StrctPageHeaderActions, StrctPageHeaderCrumbs, StrctPageHeaderTitleMeta, StrctPagination, StrctPassword, StrctPopover, StrctPopoverTrigger, StrctProgress, StrctRadio, StrctRadioGroup, StrctRail, StrctRange, StrctRatePipe, StrctRating, StrctReorder, StrctReorderItem, StrctRowDetailDef, StrctSearchbox, StrctSectionHeader, StrctSectionHeaderActions, StrctSectionHeaderMeta, StrctSectionMenu, StrctSegmented, StrctSelect, StrctShell, StrctShellService, StrctSiPipe, StrctSignpost, StrctSignpostTrigger, StrctSkeleton, StrctSparkline, StrctSpeedDial, StrctSpinner, StrctSplitButton, StrctSplitter, StrctStack, StrctStackItem, StrctStatusDot, StrctStep, StrctSubmenu, StrctTab, StrctTable, StrctTabs, StrctTag, StrctTagLeading, StrctThemeService, StrctThemeSwitcher, StrctTimeRangePicker, StrctTimeline, StrctTimelineItem, StrctToastOutlet, StrctToastService, StrctToggle, StrctToolbar, StrctToolbarCaption, StrctToolbarSpacer, StrctTooltip, StrctTour, StrctTransfer, StrctTree, StrctTreeNode, StrctTreeSelect, StrctVerticalNav, StrctWatermark, StrctWizard, StrctWizardAside, focusFirstIn, keepTabInside, lockBodyScroll, parseAnsi, provideStrctWizardDefaults, registerStrctIcon, restoreFocus, saveFocusedElement, strctComputeDiff, strctDpPad, strctFormatBytes, strctFormatDuration, strctFormatRate, strctFormatSi, strctValidationIcon, strctValidationTone, unlockBodyScroll };
23933
+ export { FOCUSABLE_SELECTOR, STRCT_DP_DOW, STRCT_DP_DOW_FULL, STRCT_DP_MONTHS, STRCT_ICONS, STRCT_ICON_GROUPS, STRCT_ICON_NAMES, STRCT_MASKS, STRCT_NOTIFICATION_HISTORY_LIMIT, STRCT_PALETTES, STRCT_RAW_ICONS, STRCT_TIME_RANGE_PRESETS, STRCT_WIZARD_DEFAULTS, StrctAccordion, StrctAccordionPanel, StrctAlert, StrctAnnouncer, StrctAvatar, StrctBadge, StrctBreadcrumb, StrctBreadcrumbItem, StrctButton, StrctButtonGroup, StrctBytesPipe, StrctCard, StrctCardBlock, StrctCardFooter, StrctCardHeader, StrctCardHeaderActions, StrctCardHeaderMeta, StrctCardHeaderNote, StrctCascadeColumn, StrctCascadeHost, StrctCascadeSelect, StrctCellDef, StrctCellStatus, StrctChart, StrctCheckbox, StrctChips, StrctCode, StrctCodeInline, StrctColorPicker, StrctCombobox, StrctCommandPalette, StrctConfirmOutlet, StrctConfirmService, StrctContextMenu, StrctContextMenuTrigger, StrctControlDescription, StrctCopy, StrctDatagrid, StrctDatagridActionBar, StrctDatagridActionBarCaption, StrctDatepicker, StrctDatetimePicker, StrctDesc, StrctDescriptionList, StrctDiff, StrctDivider, StrctDonut, StrctDrawer, StrctDrawerFooter, StrctDropdown, StrctDropdownDivider, StrctDropdownItem, StrctDropdownItemAction, StrctDropdownTrigger, StrctDurationPipe, StrctEmptyState, StrctField, StrctFieldGroup, StrctFieldHint, StrctFieldPrefix, StrctFieldSuffix, StrctFile, StrctFilterBar, StrctFlow, StrctFooter, StrctGauge, StrctHeader, StrctHeatmap, StrctHero, StrctHotkeysHelp, StrctHotkeysService, StrctIcon, StrctInlineEdit, StrctInput, StrctInputMask, StrctInputOtp, StrctKbd, StrctKnob, StrctList, StrctListItem, StrctListItemDescription, StrctListItemLeading, StrctListItemMeta, StrctListItemTrailing, StrctLiveIndicator, StrctLogViewer, StrctLogin, StrctMenuPanel, StrctMenuService, StrctMenubar, StrctMetricTile, StrctModal, StrctModalContent, StrctNav, StrctNavItem, StrctNotificationCenter, StrctNumber, StrctOverlay, StrctPageHeader, StrctPageHeaderActions, StrctPageHeaderCrumbs, StrctPageHeaderTitleMeta, StrctPagination, StrctPassword, StrctPopover, StrctPopoverTrigger, StrctProgress, StrctRadio, StrctRadioGroup, StrctRail, StrctRange, StrctRatePipe, StrctRating, StrctReorder, StrctReorderItem, StrctRowDetailDef, StrctSearchbox, StrctSectionHeader, StrctSectionHeaderActions, StrctSectionHeaderMeta, StrctSectionMenu, StrctSegmented, StrctSelect, StrctShell, StrctShellService, StrctSiPipe, StrctSignpost, StrctSignpostTrigger, StrctSkeleton, StrctSparkline, StrctSpeedDial, StrctSpinner, StrctSplitButton, StrctSplitter, StrctStack, StrctStackItem, StrctStatusDot, StrctStep, StrctSubmenu, StrctTab, StrctTable, StrctTabs, StrctTag, StrctTagLeading, StrctThemeService, StrctThemeSwitcher, StrctTimeRangePicker, StrctTimeline, StrctTimelineItem, StrctToastOutlet, StrctToastService, StrctToggle, StrctToolbar, StrctToolbarCaption, StrctToolbarSpacer, StrctTooltip, StrctTour, StrctTransfer, StrctTree, StrctTreeNode, StrctTreeSelect, StrctVerticalNav, StrctWatermark, StrctWizard, StrctWizardAside, focusFirstIn, keepTabInside, lockBodyScroll, parseAnsi, provideStrctWizardDefaults, registerStrctIcon, restoreFocus, saveFocusedElement, strctComputeDiff, strctDpPad, strctFormatBytes, strctFormatDuration, strctFormatRate, strctFormatSi, strctValidationIcon, strctValidationTone, unlockBodyScroll };
23614
23934
  //# sourceMappingURL=akcelik-strct.mjs.map