@akcelik/strct 1.3.0 → 1.6.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.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, DOCUMENT, signal, computed, Injectable, input, 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 } 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';
@@ -135,7 +135,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
135
135
  * storage, VMs …). Object state — running / stopped / maintenance — is layered
136
136
  * on with the `badge` input rather than a separate icon per state.
137
137
  */
138
- const STRCT_ICONS = {
138
+ // Built-in stroke glyphs. Keys stay inferred so \`StrctIconName\` below is a
139
+ // compile-time union of every name.
140
+ const STRCT_ICON_DEFS = {
139
141
  // ── General UI ───────────────────────────────────────────────
140
142
  hexagon: '<path d="M8 1.6l5.5 3.2v6.4L8 14.4 2.5 11.2V4.8L8 1.6z"/>',
141
143
  chevronRight: '<path d="M6 3.5L10.5 8 6 12.5"/>',
@@ -254,6 +256,7 @@ const STRCT_ICONS = {
254
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"/>',
255
257
  flag: '<path d="M4 13.5V3M4 3.6h7.4L9.8 6.2l1.6 2.6H4"/>',
256
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
+ 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"/>',
257
260
  // ── State / action (also usable as badges via the icon `badge` input) ─
258
261
  running: '<path d="M5.5 4l6 4-6 4z"/>',
259
262
  stopped: '<rect x="4.5" y="4.5" width="7" height="7" rx="1"/>',
@@ -365,15 +368,26 @@ const STRCT_ICONS = {
365
368
  // Printer — paper feed, body, output sheet and an LED.
366
369
  printer: '<path d="M5 6.4V3.2h6v3.2"/><rect x="2.8" y="6.4" width="10.4" height="4.8" rx="1"/><path d="M5 9.2h6v3.6H5z"/><circle cx="11" cy="8.4" r=".5" fill="currentColor" stroke="none"/>',
367
370
  };
368
- // Composite "off" variants — re-use base glyph + diagonal slash.
369
- STRCT_ICONS['eyeOff'] = `${STRCT_ICONS['eye']}<path d="M2.5 2.5l11 11"/>`;
370
- STRCT_ICONS['bellOff'] = `${STRCT_ICONS['bell']}<path d="M2.6 2.6l10.8 10.8"/>`;
371
- STRCT_ICONS['wifiOff'] = `${STRCT_ICONS['wifi']}<path d="M2.5 2.5l11 11"/>`;
372
- STRCT_ICONS['cloudOff'] = `${STRCT_ICONS['cloud']}<path d="M2.5 2.5l11 11"/>`;
373
- STRCT_ICONS['linkOff'] = `${STRCT_ICONS['link']}<path d="M2.5 2.5l11 11"/>`;
374
- // Semantic aliases.
375
- STRCT_ICONS['unlink'] = STRCT_ICONS['linkOff'];
376
- STRCT_ICONS['webhook'] = STRCT_ICONS['bolt'];
371
+ // Composite "off" variants — re-use base glyph + diagonal slash — and aliases.
372
+ const STRCT_ICON_DERIVED_DEFS = {
373
+ eyeOff: `${STRCT_ICON_DEFS.eye}<path d="M2.5 2.5l11 11"/>`,
374
+ bellOff: `${STRCT_ICON_DEFS.bell}<path d="M2.6 2.6l10.8 10.8"/>`,
375
+ wifiOff: `${STRCT_ICON_DEFS.wifi}<path d="M2.5 2.5l11 11"/>`,
376
+ cloudOff: `${STRCT_ICON_DEFS.cloud}<path d="M2.5 2.5l11 11"/>`,
377
+ linkOff: `${STRCT_ICON_DEFS.link}<path d="M2.5 2.5l11 11"/>`,
378
+ unlink: `${STRCT_ICON_DEFS.link}<path d="M2.5 2.5l11 11"/>`,
379
+ webhook: STRCT_ICON_DEFS.bolt,
380
+ };
381
+ /**
382
+ * Icon name → glyph markup. Open (`Record<string, string>`) on purpose so
383
+ * `registerStrctIcon` can add app-specific names at runtime.
384
+ */
385
+ const STRCT_ICONS = {
386
+ ...STRCT_ICON_DEFS,
387
+ ...STRCT_ICON_DERIVED_DEFS,
388
+ };
389
+ /** All built-in icon names — greppable, iterable (galleries, docs, tests). */
390
+ const STRCT_ICON_NAMES = Object.keys(STRCT_ICONS).sort();
377
391
  /** Icon names grouped for galleries / documentation. */
378
392
  const STRCT_ICON_GROUPS = [
379
393
  {
@@ -514,6 +528,7 @@ const STRCT_ICON_GROUPS = [
514
528
  'container',
515
529
  'firewall',
516
530
  'shield',
531
+ 'shieldCheck',
517
532
  'certificate',
518
533
  'key',
519
534
  'metrics',
@@ -585,9 +600,15 @@ function registerStrctIcon(name, content, options = {}) {
585
600
  * glyph with a green status dot (a "running host"). Unknown names render
586
601
  * nothing rather than throwing.
587
602
  */
603
+ // One warning per unknown name per app run — enough to surface the bug
604
+ // without flooding the console from a list render.
605
+ const warnedUnknownIcons = new Set();
588
606
  class StrctIcon {
589
607
  sanitizer = inject(DomSanitizer);
590
- /** Icon name from the STRCT_ICONS registry. */
608
+ /**
609
+ * Icon name. Typed to autocomplete the built-in set while still accepting
610
+ * any string, since `registerStrctIcon` can add names at runtime.
611
+ */
591
612
  name = input.required(...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
592
613
  /** Icon size in pixels. */
593
614
  size = input(16, ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
@@ -599,7 +620,17 @@ class StrctIcon {
599
620
  ariaLabel = input('', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
600
621
  /** True when the named icon is a full-SVG (raw) icon registered by the app. */
601
622
  isRaw = computed(() => Object.prototype.hasOwnProperty.call(STRCT_RAW_ICONS, this.name()), ...(ngDevMode ? [{ debugName: "isRaw" }] : /* istanbul ignore next */ []));
602
- svg = computed(() => this.sanitizer.bypassSecurityTrustHtml(STRCT_ICONS[this.name()] ?? ''), ...(ngDevMode ? [{ debugName: "svg" }] : /* istanbul ignore next */ []));
623
+ svg = computed(() => {
624
+ const name = this.name();
625
+ const glyph = STRCT_ICONS[name];
626
+ if (glyph === undefined && !this.isRaw() && isDevMode() && !warnedUnknownIcons.has(name)) {
627
+ warnedUnknownIcons.add(name);
628
+ console.warn(`[strct-icon] Unknown icon name "${name}" — it will render empty. ` +
629
+ `Built-in names are exported as STRCT_ICON_NAMES (type StrctIconName); ` +
630
+ `custom icons must be added with registerStrctIcon().`);
631
+ }
632
+ return this.sanitizer.bypassSecurityTrustHtml(glyph ?? '');
633
+ }, ...(ngDevMode ? [{ debugName: "svg" }] : /* istanbul ignore next */ []));
603
634
  rawSvg = computed(() => this.sanitizer.bypassSecurityTrustHtml(STRCT_RAW_ICONS[this.name()] ?? ''), ...(ngDevMode ? [{ debugName: "rawSvg" }] : /* istanbul ignore next */ []));
604
635
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctIcon, deps: [], target: i0.ɵɵFactoryTarget.Component });
605
636
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: StrctIcon, isStandalone: true, selector: "strct-icon", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, strokeWidth: { classPropertyName: "strokeWidth", publicName: "strokeWidth", isSignal: true, isRequired: false, transformFunction: null }, badge: { classPropertyName: "badge", publicName: "badge", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "strct-icon" }, ngImport: i0, template: `@if (isRaw()) {
@@ -1326,6 +1357,76 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
1326
1357
  `, host: { class: 'strct-login' }, styles: [".strct-login{display:flex;align-items:center;justify-content:center;width:100%;min-height:100%;padding:32px}.strct-login__inner{width:100%}.strct-login__card{display:grid;grid-template-columns:1.05fr 1fr;width:100%;overflow:hidden}.strct-login__main{padding:40px 38px;display:flex;flex-direction:column;justify-content:center}.strct-login__aside{position:relative;overflow:hidden;min-height:420px;color:var(--t1);border-inline-end:1px solid var(--b2)}.strct-login__aside:before,.strct-login__aside:after{display:none}.strct-login__aside-inner{position:relative;z-index:1;height:100%;display:flex;flex-direction:column;justify-content:space-between;padding:34px 36px}@media(max-width:720px){.strct-login__card{grid-template-columns:1fr}.strct-login__aside{min-height:220px}.strct-login__main{padding:30px 26px}}\n"] }]
1327
1358
  }], propDecorators: { maxWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxWidth", required: false }] }], split: [{ type: i0.Input, args: [{ isSignal: true, alias: "split", required: false }] }] } });
1328
1359
 
1360
+ /** Marks the breadcrumb row projected above the title. */
1361
+ class StrctPageHeaderCrumbs {
1362
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctPageHeaderCrumbs, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1363
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.16", type: StrctPageHeaderCrumbs, isStandalone: true, selector: "[strctPageHeaderCrumbs]", ngImport: i0 });
1364
+ }
1365
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctPageHeaderCrumbs, decorators: [{
1366
+ type: Directive,
1367
+ args: [{ selector: '[strctPageHeaderCrumbs]' }]
1368
+ }] });
1369
+ /** Marks the action buttons aligned to the end of the title row. */
1370
+ class StrctPageHeaderActions {
1371
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctPageHeaderActions, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1372
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.16", type: StrctPageHeaderActions, isStandalone: true, selector: "[strctPageHeaderActions]", ngImport: i0 });
1373
+ }
1374
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctPageHeaderActions, decorators: [{
1375
+ type: Directive,
1376
+ args: [{ selector: '[strctPageHeaderActions]' }]
1377
+ }] });
1378
+ /**
1379
+ * Page header — the top of every console object page: an optional breadcrumb
1380
+ * row, a title + subtitle, and actions aligned to the end. Default content
1381
+ * projects below the title row (status badges, meta strips).
1382
+ *
1383
+ * <strct-page-header title="hv-02" subtitle="Hypervisor · cluster-01">
1384
+ * <strct-breadcrumb strctPageHeaderCrumbs>…</strct-breadcrumb>
1385
+ * <button strct-button strctPageHeaderActions variant="primary">Migrate</button>
1386
+ * </strct-page-header>
1387
+ */
1388
+ class StrctPageHeader {
1389
+ /** Page title (an h1). */
1390
+ title = input.required(...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
1391
+ /** One-line description under the title. */
1392
+ subtitle = input('', ...(ngDevMode ? [{ debugName: "subtitle" }] : /* istanbul ignore next */ []));
1393
+ /** Draw a hairline divider under the header. */
1394
+ divider = input(false, { ...(ngDevMode ? { debugName: "divider" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1395
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctPageHeader, deps: [], target: i0.ɵɵFactoryTarget.Component });
1396
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: StrctPageHeader, isStandalone: true, selector: "strct-page-header", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null }, divider: { classPropertyName: "divider", publicName: "divider", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.strct-ph--divider": "divider()" }, classAttribute: "strct-ph" }, ngImport: i0, template: `
1397
+ <div class="strct-ph__crumbs"><ng-content select="[strctPageHeaderCrumbs]" /></div>
1398
+ <div class="strct-ph__row">
1399
+ <div class="strct-ph__text">
1400
+ <h1 class="strct-ph__title">{{ title() }}</h1>
1401
+ @if (subtitle()) {
1402
+ <p class="strct-ph__subtitle">{{ subtitle() }}</p>
1403
+ }
1404
+ </div>
1405
+ <div class="strct-ph__actions"><ng-content select="[strctPageHeaderActions]" /></div>
1406
+ </div>
1407
+ <ng-content />
1408
+ `, isInline: true, styles: [".strct-ph{display:block}.strct-ph--divider{padding-bottom:16px;border-bottom:1px solid var(--b1)}.strct-ph__crumbs:empty{display:none}.strct-ph__crumbs{margin-bottom:6px}.strct-ph__row{display:flex;align-items:flex-start;gap:16px;flex-wrap:wrap}.strct-ph__text{flex:1;min-width:0}.strct-ph__title{margin:0;font-size:var(--text-2xl);font-weight:700;letter-spacing:-.01em;color:var(--t1);text-wrap:balance}.strct-ph__subtitle{margin:6px 0 0;font-size:13px;line-height:1.55;color:var(--t2);max-width:72ch}.strct-ph__actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.strct-ph__actions:empty{display:none}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
1409
+ }
1410
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctPageHeader, decorators: [{
1411
+ type: Component,
1412
+ args: [{ selector: 'strct-page-header', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: `
1413
+ <div class="strct-ph__crumbs"><ng-content select="[strctPageHeaderCrumbs]" /></div>
1414
+ <div class="strct-ph__row">
1415
+ <div class="strct-ph__text">
1416
+ <h1 class="strct-ph__title">{{ title() }}</h1>
1417
+ @if (subtitle()) {
1418
+ <p class="strct-ph__subtitle">{{ subtitle() }}</p>
1419
+ }
1420
+ </div>
1421
+ <div class="strct-ph__actions"><ng-content select="[strctPageHeaderActions]" /></div>
1422
+ </div>
1423
+ <ng-content />
1424
+ `, host: {
1425
+ class: 'strct-ph',
1426
+ '[class.strct-ph--divider]': 'divider()',
1427
+ }, styles: [".strct-ph{display:block}.strct-ph--divider{padding-bottom:16px;border-bottom:1px solid var(--b1)}.strct-ph__crumbs:empty{display:none}.strct-ph__crumbs{margin-bottom:6px}.strct-ph__row{display:flex;align-items:flex-start;gap:16px;flex-wrap:wrap}.strct-ph__text{flex:1;min-width:0}.strct-ph__title{margin:0;font-size:var(--text-2xl);font-weight:700;letter-spacing:-.01em;color:var(--t1);text-wrap:balance}.strct-ph__subtitle{margin:6px 0 0;font-size:13px;line-height:1.55;color:var(--t2);max-width:72ch}.strct-ph__actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.strct-ph__actions:empty{display:none}\n"] }]
1428
+ }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], subtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitle", required: false }] }], divider: [{ type: i0.Input, args: [{ isSignal: true, alias: "divider", required: false }] }] } });
1429
+
1329
1430
  /**
1330
1431
  * Styles a native `<button>` / `<a>` so it stays fully accessible and form-aware.
1331
1432
  *
@@ -3256,11 +3357,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
3256
3357
  * <strct-dropdown-item>Rename</strct-dropdown-item>
3257
3358
  * <strct-dropdown-item critical>Delete</strct-dropdown-item>
3258
3359
  * </strct-dropdown>
3360
+ *
3361
+ * With `popover`, the panel holds *form controls* instead of menu items: an
3362
+ * inner click no longer closes it (only outside click / Escape do), and the
3363
+ * semantics switch from `role="menu"` to a labeled `role="dialog"` — a panel
3364
+ * of selects is not a menu to a screen reader either:
3365
+ *
3366
+ * <strct-dropdown popover popoverLabel="Filters">
3367
+ * <button strct-button strctDropdownTrigger>Filters</button>
3368
+ * <strct-field label="Severity">…</strct-field>
3369
+ * </strct-dropdown>
3259
3370
  */
3260
3371
  class StrctDropdown {
3261
3372
  host = inject((ElementRef));
3262
3373
  /** Horizontal alignment of the menu. */
3263
3374
  align = input('start', ...(ngDevMode ? [{ debugName: "align" }] : /* istanbul ignore next */ []));
3375
+ /**
3376
+ * Popover mode for panels holding form controls (filter/settings panels):
3377
+ * inner clicks never close the panel — only outside click / Escape do —
3378
+ * and it renders as a labeled `role="dialog"` instead of a menu.
3379
+ */
3380
+ popover = input(false, { ...(ngDevMode ? { debugName: "popover" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
3381
+ /** Accessible name of the popover dialog (localizable). */
3382
+ popoverLabel = input('Filters', ...(ngDevMode ? [{ debugName: "popoverLabel" }] : /* istanbul ignore next */ []));
3264
3383
  open = signal(false, ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
3265
3384
  toggle() {
3266
3385
  this.open.update((v) => !v);
@@ -3268,6 +3387,11 @@ class StrctDropdown {
3268
3387
  close() {
3269
3388
  this.open.set(false);
3270
3389
  }
3390
+ /** Menu items close on activation; popover form controls never do. */
3391
+ onInnerActivate() {
3392
+ if (!this.popover())
3393
+ this.close();
3394
+ }
3271
3395
  onDocClick(event) {
3272
3396
  if (this.open() && !this.host.nativeElement.contains(event.target)) {
3273
3397
  this.close();
@@ -3277,33 +3401,41 @@ class StrctDropdown {
3277
3401
  this.close();
3278
3402
  }
3279
3403
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctDropdown, deps: [], target: i0.ɵɵFactoryTarget.Component });
3280
- 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 } }, host: { listeners: { "document:click": "onDocClick($event)", "document:keydown.escape": "onEscape()" }, classAttribute: "strct-dd" }, ngImport: i0, template: `
3404
+ 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: `
3281
3405
  <div
3282
3406
  #trigger
3283
3407
  class="strct-dd__trigger"
3284
3408
  role="button"
3285
3409
  tabindex="0"
3410
+ [attr.aria-haspopup]="popover() ? 'dialog' : 'menu'"
3411
+ [attr.aria-expanded]="open()"
3286
3412
  (click)="toggle()"
3287
3413
  (keydown.enter)="toggle()"
3288
3414
  (keydown.space)="toggle()"
3289
3415
  >
3290
3416
  <ng-content select="[strctDropdownTrigger]" />
3291
3417
  </div>
3418
+ <!--
3419
+ One panel for both modes (a default ng-content per @if branch would strand
3420
+ the projected content in the inactive branch) — mode picks the semantics.
3421
+ -->
3292
3422
  @if (open()) {
3293
3423
  <div
3294
3424
  class="strct-dd__menu"
3425
+ [class.strct-dd__menu--popover]="popover()"
3295
3426
  [strctOverlay]="trigger"
3296
3427
  [strctOverlayPlacement]="align() === 'end' ? 'bottom-end' : 'bottom-start'"
3297
- role="menu"
3298
- tabindex="0"
3299
- (click)="close()"
3300
- (keydown.enter)="close()"
3301
- (keydown.space)="close()"
3428
+ [attr.role]="popover() ? 'dialog' : 'menu'"
3429
+ [attr.aria-label]="popover() ? popoverLabel() : null"
3430
+ [attr.tabindex]="popover() ? -1 : 0"
3431
+ (click)="onInnerActivate()"
3432
+ (keydown.enter)="onInnerActivate()"
3433
+ (keydown.space)="onInnerActivate()"
3302
3434
  >
3303
3435
  <ng-content />
3304
3436
  </div>
3305
3437
  }
3306
- `, isInline: true, 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}@keyframes strct-dd-in{0%{opacity:0;transform:translateY(-4px)}}\n"], dependencies: [{ kind: "directive", type: StrctOverlay, selector: "[strctOverlay]", inputs: ["strctOverlay", "strctOverlayPlacement", "strctOverlayMatchWidth", "strctOverlayGap"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
3438
+ `, isInline: true, 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"], dependencies: [{ kind: "directive", type: StrctOverlay, selector: "[strctOverlay]", inputs: ["strctOverlay", "strctOverlayPlacement", "strctOverlayMatchWidth", "strctOverlayGap"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
3307
3439
  }
3308
3440
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctDropdown, decorators: [{
3309
3441
  type: Component,
@@ -3313,28 +3445,36 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
3313
3445
  class="strct-dd__trigger"
3314
3446
  role="button"
3315
3447
  tabindex="0"
3448
+ [attr.aria-haspopup]="popover() ? 'dialog' : 'menu'"
3449
+ [attr.aria-expanded]="open()"
3316
3450
  (click)="toggle()"
3317
3451
  (keydown.enter)="toggle()"
3318
3452
  (keydown.space)="toggle()"
3319
3453
  >
3320
3454
  <ng-content select="[strctDropdownTrigger]" />
3321
3455
  </div>
3456
+ <!--
3457
+ One panel for both modes (a default ng-content per @if branch would strand
3458
+ the projected content in the inactive branch) — mode picks the semantics.
3459
+ -->
3322
3460
  @if (open()) {
3323
3461
  <div
3324
3462
  class="strct-dd__menu"
3463
+ [class.strct-dd__menu--popover]="popover()"
3325
3464
  [strctOverlay]="trigger"
3326
3465
  [strctOverlayPlacement]="align() === 'end' ? 'bottom-end' : 'bottom-start'"
3327
- role="menu"
3328
- tabindex="0"
3329
- (click)="close()"
3330
- (keydown.enter)="close()"
3331
- (keydown.space)="close()"
3466
+ [attr.role]="popover() ? 'dialog' : 'menu'"
3467
+ [attr.aria-label]="popover() ? popoverLabel() : null"
3468
+ [attr.tabindex]="popover() ? -1 : 0"
3469
+ (click)="onInnerActivate()"
3470
+ (keydown.enter)="onInnerActivate()"
3471
+ (keydown.space)="onInnerActivate()"
3332
3472
  >
3333
3473
  <ng-content />
3334
3474
  </div>
3335
3475
  }
3336
- `, 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}@keyframes strct-dd-in{0%{opacity:0;transform:translateY(-4px)}}\n"] }]
3337
- }], propDecorators: { align: [{ type: i0.Input, args: [{ isSignal: true, alias: "align", required: false }] }], onDocClick: [{
3476
+ `, 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: [{
3338
3478
  type: HostListener,
3339
3479
  args: ['document:click', ['$event']]
3340
3480
  }], onEscape: [{
@@ -4139,9 +4279,24 @@ class StrctCommandPalette {
4139
4279
  emptyText = input('No matches for', ...(ngDevMode ? [{ debugName: "emptyText" }] : /* istanbul ignore next */ []));
4140
4280
  /** Maximum rendered results. */
4141
4281
  maxResults = input(50, ...(ngDevMode ? [{ debugName: "maxResults" }] : /* istanbul ignore next */ []));
4282
+ /**
4283
+ * Internal ranked filtering. Set `false` for a **server-backed** palette:
4284
+ * items render in the order given (you already filtered them) and the query
4285
+ * reaches you through the two-way `query` model. `maxResults` still caps
4286
+ * rendering.
4287
+ */
4288
+ filter = input(true, { ...(ngDevMode ? { debugName: "filter" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4289
+ /** Show a "searching" row while server results are in flight. */
4290
+ loading = input(false, { ...(ngDevMode ? { debugName: "loading" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4291
+ /** Localizable text of the loading row. */
4292
+ loadingText = input('Searching…', ...(ngDevMode ? [{ debugName: "loadingText" }] : /* istanbul ignore next */ []));
4142
4293
  /** Emitted when a command is chosen (Enter or click); the palette closes. */
4143
4294
  picked = output();
4144
- query = signal('', ...(ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []));
4295
+ /**
4296
+ * The typed query (two-way). The palette still owns typing and keyboard
4297
+ * behaviour; consumers observe it to serve results asynchronously.
4298
+ */
4299
+ query = model('', ...(ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []));
4145
4300
  activeIndex = signal(0, ...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
4146
4301
  listId = `strct-cmdp-list-${++paletteCounter}`;
4147
4302
  previousActive = null;
@@ -4150,8 +4305,11 @@ class StrctCommandPalette {
4150
4305
  }
4151
4306
  /** Rank: label prefix > word-start > substring; group/keyword matches last. */
4152
4307
  filtered = computed(() => {
4153
- const q = this.query().trim().toLowerCase();
4154
4308
  const items = this.items();
4309
+ // Server-backed mode: the caller already filtered — render as given.
4310
+ if (!this.filter())
4311
+ return items.slice(0, this.maxResults());
4312
+ const q = this.query().trim().toLowerCase();
4155
4313
  if (!q)
4156
4314
  return items.slice(0, this.maxResults());
4157
4315
  const scored = [];
@@ -4176,6 +4334,12 @@ class StrctCommandPalette {
4176
4334
  .map((s) => s.item);
4177
4335
  }, ...(ngDevMode ? [{ debugName: "filtered" }] : /* istanbul ignore next */ []));
4178
4336
  constructor() {
4337
+ // Async results can shrink under the cursor — keep the active option in range.
4338
+ effect(() => {
4339
+ const len = this.filtered().length;
4340
+ if (this.activeIndex() >= len)
4341
+ this.activeIndex.set(Math.max(0, len - 1));
4342
+ });
4179
4343
  // Focus the input on open; restore focus on close.
4180
4344
  effect(() => {
4181
4345
  if (this.open()) {
@@ -4247,7 +4411,7 @@ class StrctCommandPalette {
4247
4411
  this.open.set(false);
4248
4412
  }
4249
4413
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctCommandPalette, deps: [], target: i0.ɵɵFactoryTarget.Component });
4250
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: StrctCommandPalette, isStandalone: true, selector: "strct-command-palette", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, hotkey: { classPropertyName: "hotkey", publicName: "hotkey", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null }, maxResults: { classPropertyName: "maxResults", publicName: "maxResults", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange", picked: "picked" }, host: { listeners: { "document:keydown": "onGlobalKey($event)" } }, viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["input"], descendants: true, isSignal: true }], ngImport: i0, template: `
4414
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: StrctCommandPalette, isStandalone: true, selector: "strct-command-palette", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, hotkey: { classPropertyName: "hotkey", publicName: "hotkey", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null }, maxResults: { classPropertyName: "maxResults", publicName: "maxResults", isSignal: true, isRequired: false, transformFunction: null }, filter: { classPropertyName: "filter", publicName: "filter", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, loadingText: { classPropertyName: "loadingText", publicName: "loadingText", isSignal: true, isRequired: false, transformFunction: null }, query: { classPropertyName: "query", publicName: "query", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange", picked: "picked", query: "queryChange" }, host: { listeners: { "document:keydown": "onGlobalKey($event)" } }, viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["input"], descendants: true, isSignal: true }], ngImport: i0, template: `
4251
4415
  @if (open()) {
4252
4416
  <!-- Backdrop: pointer-only dismiss (Escape covers keyboard). -->
4253
4417
  <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->
@@ -4278,6 +4442,11 @@ class StrctCommandPalette {
4278
4442
  <strct-kbd>Esc</strct-kbd>
4279
4443
  </div>
4280
4444
  <div class="strct-cmdp__list" role="listbox" [id]="listId">
4445
+ @if (loading()) {
4446
+ <div class="strct-cmdp__loading" aria-live="polite">
4447
+ <span class="strct-cmdp__spin" aria-hidden="true"></span>{{ loadingText() }}
4448
+ </div>
4449
+ }
4281
4450
  @for (item of filtered(); track item.id; let i = $index) {
4282
4451
  <button
4283
4452
  type="button"
@@ -4307,13 +4476,15 @@ class StrctCommandPalette {
4307
4476
  }
4308
4477
  </button>
4309
4478
  } @empty {
4310
- <div class="strct-cmdp__empty">{{ emptyText() }} “{{ query() }}”</div>
4479
+ @if (!loading()) {
4480
+ <div class="strct-cmdp__empty">{{ emptyText() }} “{{ query() }}”</div>
4481
+ }
4311
4482
  }
4312
4483
  </div>
4313
4484
  </div>
4314
4485
  </div>
4315
4486
  }
4316
- `, isInline: true, styles: [".strct-cmdp__backdrop{position:fixed;inset:0;z-index:1200;display:flex;justify-content:center;align-items:flex-start;padding:14vh 16px 16px;background:#00000080;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);animation:strct-cmdp-fade .12s ease}.strct-cmdp{width:100%;max-width:560px;max-height:60vh;display:flex;flex-direction:column;background:var(--bg-1);border:1px solid var(--b2);border-radius:var(--radius-xl);box-shadow:var(--shadow-floating);overflow:hidden;animation:strct-cmdp-rise .14s ease}.strct-cmdp__search{display:flex;align-items:center;gap:10px;padding:13px 16px;border-bottom:1px solid var(--b1);color:var(--t3)}.strct-cmdp__input{flex:1;min-width:0;border:0;background:transparent;color:var(--t1);font-size:15px;font-family:var(--font);outline:none}.strct-cmdp__list{overflow-y:auto;padding:6px}.strct-cmdp__item{display:flex;align-items:center;gap:11px;width:100%;padding:9px 11px;border:0;border-radius:var(--radius-md);background:transparent;cursor:pointer;text-align:start;font-family:var(--font)}.strct-cmdp__item.is-active{background:var(--acc-m)}.strct-cmdp__icon{color:var(--t2);flex-shrink:0}.strct-cmdp__item.is-active .strct-cmdp__icon{color:var(--acc)}.strct-cmdp__label{flex:1;min-width:0;font-size:var(--text-lg);color:var(--t1);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.strct-cmdp__group{font-size:var(--text-sm);color:var(--t3);flex-shrink:0}.strct-cmdp__empty{padding:20px;text-align:center;font-size:var(--text-md);color:var(--t3)}@keyframes strct-cmdp-fade{0%{opacity:0}}@keyframes strct-cmdp-rise{0%{opacity:0;transform:translateY(-8px)}}@media(prefers-reduced-motion:reduce){.strct-cmdp__backdrop,.strct-cmdp{animation:none}}\n"], dependencies: [{ kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "size", "strokeWidth", "badge", "ariaLabel"] }, { kind: "component", type: StrctKbd, selector: "strct-kbd" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
4487
+ `, isInline: true, styles: [".strct-cmdp__backdrop{position:fixed;inset:0;z-index:1200;display:flex;justify-content:center;align-items:flex-start;padding:14vh 16px 16px;background:#00000080;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);animation:strct-cmdp-fade .12s ease}.strct-cmdp{width:100%;max-width:560px;max-height:60vh;display:flex;flex-direction:column;background:var(--bg-1);border:1px solid var(--b2);border-radius:var(--radius-xl);box-shadow:var(--shadow-floating);overflow:hidden;animation:strct-cmdp-rise .14s ease}.strct-cmdp__search{display:flex;align-items:center;gap:10px;padding:13px 16px;border-bottom:1px solid var(--b1);color:var(--t3)}.strct-cmdp__input{flex:1;min-width:0;border:0;background:transparent;color:var(--t1);font-size:15px;font-family:var(--font);outline:none}.strct-cmdp__list{overflow-y:auto;padding:6px}.strct-cmdp__item{display:flex;align-items:center;gap:11px;width:100%;padding:9px 11px;border:0;border-radius:var(--radius-md);background:transparent;cursor:pointer;text-align:start;font-family:var(--font)}.strct-cmdp__item.is-active{background:var(--acc-m)}.strct-cmdp__icon{color:var(--t2);flex-shrink:0}.strct-cmdp__item.is-active .strct-cmdp__icon{color:var(--acc)}.strct-cmdp__label{flex:1;min-width:0;font-size:var(--text-lg);color:var(--t1);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.strct-cmdp__group{font-size:var(--text-sm);color:var(--t3);flex-shrink:0}.strct-cmdp__loading{display:flex;align-items:center;gap:9px;padding:10px 12px;font-size:var(--text-md);color:var(--t3)}.strct-cmdp__spin{width:13px;height:13px;border:2px solid var(--acc30);border-top-color:var(--acc);border-radius:50%;flex-shrink:0}@media(prefers-reduced-motion:no-preference){.strct-cmdp__spin{animation:strct-cmdp-spin .8s linear infinite}}@keyframes strct-cmdp-spin{to{transform:rotate(360deg)}}.strct-cmdp__empty{padding:20px;text-align:center;font-size:var(--text-md);color:var(--t3)}@keyframes strct-cmdp-fade{0%{opacity:0}}@keyframes strct-cmdp-rise{0%{opacity:0;transform:translateY(-8px)}}@media(prefers-reduced-motion:reduce){.strct-cmdp__backdrop,.strct-cmdp{animation:none}}\n"], dependencies: [{ kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "size", "strokeWidth", "badge", "ariaLabel"] }, { kind: "component", type: StrctKbd, selector: "strct-kbd" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
4317
4488
  }
4318
4489
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctCommandPalette, decorators: [{
4319
4490
  type: Component,
@@ -4348,6 +4519,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
4348
4519
  <strct-kbd>Esc</strct-kbd>
4349
4520
  </div>
4350
4521
  <div class="strct-cmdp__list" role="listbox" [id]="listId">
4522
+ @if (loading()) {
4523
+ <div class="strct-cmdp__loading" aria-live="polite">
4524
+ <span class="strct-cmdp__spin" aria-hidden="true"></span>{{ loadingText() }}
4525
+ </div>
4526
+ }
4351
4527
  @for (item of filtered(); track item.id; let i = $index) {
4352
4528
  <button
4353
4529
  type="button"
@@ -4377,14 +4553,509 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
4377
4553
  }
4378
4554
  </button>
4379
4555
  } @empty {
4380
- <div class="strct-cmdp__empty">{{ emptyText() }} “{{ query() }}”</div>
4556
+ @if (!loading()) {
4557
+ <div class="strct-cmdp__empty">{{ emptyText() }} “{{ query() }}”</div>
4558
+ }
4381
4559
  }
4382
4560
  </div>
4383
4561
  </div>
4384
4562
  </div>
4385
4563
  }
4386
- `, styles: [".strct-cmdp__backdrop{position:fixed;inset:0;z-index:1200;display:flex;justify-content:center;align-items:flex-start;padding:14vh 16px 16px;background:#00000080;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);animation:strct-cmdp-fade .12s ease}.strct-cmdp{width:100%;max-width:560px;max-height:60vh;display:flex;flex-direction:column;background:var(--bg-1);border:1px solid var(--b2);border-radius:var(--radius-xl);box-shadow:var(--shadow-floating);overflow:hidden;animation:strct-cmdp-rise .14s ease}.strct-cmdp__search{display:flex;align-items:center;gap:10px;padding:13px 16px;border-bottom:1px solid var(--b1);color:var(--t3)}.strct-cmdp__input{flex:1;min-width:0;border:0;background:transparent;color:var(--t1);font-size:15px;font-family:var(--font);outline:none}.strct-cmdp__list{overflow-y:auto;padding:6px}.strct-cmdp__item{display:flex;align-items:center;gap:11px;width:100%;padding:9px 11px;border:0;border-radius:var(--radius-md);background:transparent;cursor:pointer;text-align:start;font-family:var(--font)}.strct-cmdp__item.is-active{background:var(--acc-m)}.strct-cmdp__icon{color:var(--t2);flex-shrink:0}.strct-cmdp__item.is-active .strct-cmdp__icon{color:var(--acc)}.strct-cmdp__label{flex:1;min-width:0;font-size:var(--text-lg);color:var(--t1);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.strct-cmdp__group{font-size:var(--text-sm);color:var(--t3);flex-shrink:0}.strct-cmdp__empty{padding:20px;text-align:center;font-size:var(--text-md);color:var(--t3)}@keyframes strct-cmdp-fade{0%{opacity:0}}@keyframes strct-cmdp-rise{0%{opacity:0;transform:translateY(-8px)}}@media(prefers-reduced-motion:reduce){.strct-cmdp__backdrop,.strct-cmdp{animation:none}}\n"] }]
4387
- }], ctorParameters: () => [], propDecorators: { inputRef: [{ type: i0.ViewChild, args: ['input', { isSignal: true }] }], items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], hotkey: [{ type: i0.Input, args: [{ isSignal: true, alias: "hotkey", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], maxResults: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxResults", required: false }] }], picked: [{ type: i0.Output, args: ["picked"] }] } });
4564
+ `, styles: [".strct-cmdp__backdrop{position:fixed;inset:0;z-index:1200;display:flex;justify-content:center;align-items:flex-start;padding:14vh 16px 16px;background:#00000080;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);animation:strct-cmdp-fade .12s ease}.strct-cmdp{width:100%;max-width:560px;max-height:60vh;display:flex;flex-direction:column;background:var(--bg-1);border:1px solid var(--b2);border-radius:var(--radius-xl);box-shadow:var(--shadow-floating);overflow:hidden;animation:strct-cmdp-rise .14s ease}.strct-cmdp__search{display:flex;align-items:center;gap:10px;padding:13px 16px;border-bottom:1px solid var(--b1);color:var(--t3)}.strct-cmdp__input{flex:1;min-width:0;border:0;background:transparent;color:var(--t1);font-size:15px;font-family:var(--font);outline:none}.strct-cmdp__list{overflow-y:auto;padding:6px}.strct-cmdp__item{display:flex;align-items:center;gap:11px;width:100%;padding:9px 11px;border:0;border-radius:var(--radius-md);background:transparent;cursor:pointer;text-align:start;font-family:var(--font)}.strct-cmdp__item.is-active{background:var(--acc-m)}.strct-cmdp__icon{color:var(--t2);flex-shrink:0}.strct-cmdp__item.is-active .strct-cmdp__icon{color:var(--acc)}.strct-cmdp__label{flex:1;min-width:0;font-size:var(--text-lg);color:var(--t1);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.strct-cmdp__group{font-size:var(--text-sm);color:var(--t3);flex-shrink:0}.strct-cmdp__loading{display:flex;align-items:center;gap:9px;padding:10px 12px;font-size:var(--text-md);color:var(--t3)}.strct-cmdp__spin{width:13px;height:13px;border:2px solid var(--acc30);border-top-color:var(--acc);border-radius:50%;flex-shrink:0}@media(prefers-reduced-motion:no-preference){.strct-cmdp__spin{animation:strct-cmdp-spin .8s linear infinite}}@keyframes strct-cmdp-spin{to{transform:rotate(360deg)}}.strct-cmdp__empty{padding:20px;text-align:center;font-size:var(--text-md);color:var(--t3)}@keyframes strct-cmdp-fade{0%{opacity:0}}@keyframes strct-cmdp-rise{0%{opacity:0;transform:translateY(-8px)}}@media(prefers-reduced-motion:reduce){.strct-cmdp__backdrop,.strct-cmdp{animation:none}}\n"] }]
4565
+ }], ctorParameters: () => [], propDecorators: { inputRef: [{ type: i0.ViewChild, args: ['input', { isSignal: true }] }], items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], hotkey: [{ type: i0.Input, args: [{ isSignal: true, alias: "hotkey", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], maxResults: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxResults", required: false }] }], filter: [{ type: i0.Input, args: [{ isSignal: true, alias: "filter", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], loadingText: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadingText", required: false }] }], picked: [{ type: i0.Output, args: ["picked"] }], query: [{ type: i0.Input, args: [{ isSignal: true, alias: "query", required: false }] }, { type: i0.Output, args: ["queryChange"] }] } });
4566
+
4567
+ /**
4568
+ * Compact search pill — the pattern from the docs header: a leading search
4569
+ * icon, a label / input and an optional keyboard-hint chip. Two modes:
4570
+ *
4571
+ * - **input** (default): a real search field. Two-way `value` (also
4572
+ * CVA-compatible), `(search)` emits on Enter, Escape / the × clears.
4573
+ *
4574
+ * <strct-searchbox [(value)]="q" (search)="run($event)" />
4575
+ *
4576
+ * - **`trigger`**: a button that only announces activation — the classic
4577
+ * "fake search that opens the command palette" header pattern:
4578
+ *
4579
+ * <strct-searchbox trigger hint="⌘K" (activated)="palette.open.set(true)" />
4580
+ */
4581
+ class StrctSearchbox {
4582
+ host = inject(ElementRef);
4583
+ /** Placeholder text (and the trigger mode's label). */
4584
+ placeholder = input('Search', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
4585
+ /** Keyboard-hint chip (e.g. "⌘K"); hidden while typing. */
4586
+ hint = input('', ...(ngDevMode ? [{ debugName: "hint" }] : /* istanbul ignore next */ []));
4587
+ /** Render as an activation button instead of a real input (palette opener). */
4588
+ trigger = input(false, { ...(ngDevMode ? { debugName: "trigger" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4589
+ /** Show the × clear button when there is a value. */
4590
+ clearable = input(true, { ...(ngDevMode ? { debugName: "clearable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4591
+ /** Accessible label; falls back to `placeholder`. */
4592
+ ariaLabel = input('', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
4593
+ /** Accessible label of the clear button (localizable). */
4594
+ clearLabel = input('Clear search', ...(ngDevMode ? [{ debugName: "clearLabel" }] : /* istanbul ignore next */ []));
4595
+ /** Current text (two-way; also drives the CVA value). */
4596
+ value = model('', ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
4597
+ /** Enter pressed in input mode — run the search with the current text. */
4598
+ search = output();
4599
+ /** Trigger mode clicked (open your command palette / search surface). */
4600
+ activated = output();
4601
+ focused = signal(false, ...(ngDevMode ? [{ debugName: "focused" }] : /* istanbul ignore next */ []));
4602
+ isDisabled = signal(false, ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
4603
+ onChange = () => { };
4604
+ onTouched = () => { };
4605
+ onInput(event) {
4606
+ const next = event.target.value;
4607
+ this.value.set(next);
4608
+ this.onChange(next);
4609
+ }
4610
+ /** Clear the text (Escape / the × button). */
4611
+ clear() {
4612
+ if (!this.value())
4613
+ return;
4614
+ this.value.set('');
4615
+ this.onChange('');
4616
+ }
4617
+ /** Focus the input programmatically (e.g. from a global shortcut). */
4618
+ focus() {
4619
+ this.host.nativeElement.querySelector('.strct-sb__input')?.focus();
4620
+ }
4621
+ writeValue(value) {
4622
+ this.value.set(value ?? '');
4623
+ }
4624
+ registerOnChange(fn) {
4625
+ this.onChange = fn;
4626
+ }
4627
+ registerOnTouched(fn) {
4628
+ this.onTouched = fn;
4629
+ }
4630
+ setDisabledState(isDisabled) {
4631
+ this.isDisabled.set(isDisabled);
4632
+ }
4633
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctSearchbox, deps: [], target: i0.ɵɵFactoryTarget.Component });
4634
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: StrctSearchbox, isStandalone: true, selector: "strct-searchbox", inputs: { placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, trigger: { classPropertyName: "trigger", publicName: "trigger", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, clearLabel: { classPropertyName: "clearLabel", publicName: "clearLabel", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", search: "search", activated: "activated" }, host: { classAttribute: "strct-sb-host" }, providers: [
4635
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => StrctSearchbox), multi: true },
4636
+ ], ngImport: i0, template: `
4637
+ @if (trigger()) {
4638
+ <button
4639
+ type="button"
4640
+ class="strct-sb strct-sb--trigger"
4641
+ [disabled]="isDisabled()"
4642
+ [attr.aria-label]="ariaLabel() || placeholder()"
4643
+ (click)="activated.emit()"
4644
+ >
4645
+ <strct-icon class="strct-sb__icon" name="search" [size]="14" [strokeWidth]="1.6" />
4646
+ <span class="strct-sb__label">{{ placeholder() }}</span>
4647
+ @if (hint()) {
4648
+ <kbd class="strct-sb__kbd">{{ hint() }}</kbd>
4649
+ }
4650
+ </button>
4651
+ } @else {
4652
+ <div class="strct-sb" [class.strct-sb--disabled]="isDisabled()">
4653
+ <strct-icon class="strct-sb__icon" name="search" [size]="14" [strokeWidth]="1.6" />
4654
+ <input
4655
+ #field
4656
+ class="strct-sb__input"
4657
+ type="text"
4658
+ role="searchbox"
4659
+ [attr.aria-label]="ariaLabel() || placeholder()"
4660
+ [placeholder]="placeholder()"
4661
+ [disabled]="isDisabled()"
4662
+ [value]="value()"
4663
+ (input)="onInput($event)"
4664
+ (keydown.enter)="search.emit(value())"
4665
+ (keydown.escape)="clear()"
4666
+ (focus)="focused.set(true)"
4667
+ (blur)="focused.set(false); onTouched()"
4668
+ />
4669
+ @if (clearable() && value()) {
4670
+ <button
4671
+ type="button"
4672
+ class="strct-sb__clear"
4673
+ [attr.aria-label]="clearLabel()"
4674
+ (click)="clear(); field.focus()"
4675
+ >
4676
+ <strct-icon name="close" [size]="11" [strokeWidth]="1.8" />
4677
+ </button>
4678
+ } @else if (hint() && !focused()) {
4679
+ <kbd class="strct-sb__kbd">{{ hint() }}</kbd>
4680
+ }
4681
+ </div>
4682
+ }
4683
+ `, isInline: true, styles: [".strct-sb-host{display:inline-flex;max-width:100%}.strct-sb{display:inline-flex;align-items:center;gap:8px;width:100%;min-width:0;padding:5px 8px 5px 10px;border:1px solid var(--strct-sb-border, var(--b2));border-radius:7px;background:var(--strct-sb-bg, var(--bg-2));color:var(--strct-sb-fg, var(--t2));font-family:var(--font);font-size:var(--strct-sb-font-size, 12.5px);transition:background .14s ease,border-color .14s ease,color .14s ease}.strct-sb--trigger{cursor:pointer;text-align:start}.strct-sb--trigger:hover:not(:disabled){color:var(--strct-sb-fg-hover, var(--t1));background:var(--strct-sb-bg-hover, var(--bg-3))}.strct-sb--trigger:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}.strct-sb:focus-within:not(.strct-sb--trigger){border-color:var(--acc50);color:var(--t1)}.strct-sb--disabled,.strct-sb--trigger:disabled{opacity:.55;cursor:not-allowed}.strct-sb__icon{flex-shrink:0;color:var(--strct-sb-muted, var(--t3))}.strct-sb__label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.strct-sb__input{flex:1;min-width:0;border:0;padding:0;background:transparent;color:var(--t1);font-family:var(--font);font-size:12.5px;outline:none}.strct-sb__input::placeholder{color:var(--t3)}.strct-sb__kbd{flex-shrink:0;font-family:var(--mono);font-size:10px;line-height:1;padding:3px 5px;border:1px solid var(--strct-sb-kbd-border, var(--b2));border-radius:4px;background:var(--strct-sb-kbd-bg, var(--bg-2));color:var(--strct-sb-kbd-fg, var(--t3))}.strct-sb__clear{flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;margin:-2px;padding:0;border:0;border-radius:5px;background:transparent;color:var(--t3);cursor:pointer}.strct-sb__clear:hover{color:var(--t1);background:var(--bg-3)}.strct-sb__clear:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}@media(prefers-reduced-motion:reduce){.strct-sb{transition:none}}\n"], dependencies: [{ kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "size", "strokeWidth", "badge", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
4684
+ }
4685
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctSearchbox, decorators: [{
4686
+ type: Component,
4687
+ args: [{ selector: 'strct-searchbox', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [StrctIcon], providers: [
4688
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => StrctSearchbox), multi: true },
4689
+ ], template: `
4690
+ @if (trigger()) {
4691
+ <button
4692
+ type="button"
4693
+ class="strct-sb strct-sb--trigger"
4694
+ [disabled]="isDisabled()"
4695
+ [attr.aria-label]="ariaLabel() || placeholder()"
4696
+ (click)="activated.emit()"
4697
+ >
4698
+ <strct-icon class="strct-sb__icon" name="search" [size]="14" [strokeWidth]="1.6" />
4699
+ <span class="strct-sb__label">{{ placeholder() }}</span>
4700
+ @if (hint()) {
4701
+ <kbd class="strct-sb__kbd">{{ hint() }}</kbd>
4702
+ }
4703
+ </button>
4704
+ } @else {
4705
+ <div class="strct-sb" [class.strct-sb--disabled]="isDisabled()">
4706
+ <strct-icon class="strct-sb__icon" name="search" [size]="14" [strokeWidth]="1.6" />
4707
+ <input
4708
+ #field
4709
+ class="strct-sb__input"
4710
+ type="text"
4711
+ role="searchbox"
4712
+ [attr.aria-label]="ariaLabel() || placeholder()"
4713
+ [placeholder]="placeholder()"
4714
+ [disabled]="isDisabled()"
4715
+ [value]="value()"
4716
+ (input)="onInput($event)"
4717
+ (keydown.enter)="search.emit(value())"
4718
+ (keydown.escape)="clear()"
4719
+ (focus)="focused.set(true)"
4720
+ (blur)="focused.set(false); onTouched()"
4721
+ />
4722
+ @if (clearable() && value()) {
4723
+ <button
4724
+ type="button"
4725
+ class="strct-sb__clear"
4726
+ [attr.aria-label]="clearLabel()"
4727
+ (click)="clear(); field.focus()"
4728
+ >
4729
+ <strct-icon name="close" [size]="11" [strokeWidth]="1.8" />
4730
+ </button>
4731
+ } @else if (hint() && !focused()) {
4732
+ <kbd class="strct-sb__kbd">{{ hint() }}</kbd>
4733
+ }
4734
+ </div>
4735
+ }
4736
+ `, host: { class: 'strct-sb-host' }, styles: [".strct-sb-host{display:inline-flex;max-width:100%}.strct-sb{display:inline-flex;align-items:center;gap:8px;width:100%;min-width:0;padding:5px 8px 5px 10px;border:1px solid var(--strct-sb-border, var(--b2));border-radius:7px;background:var(--strct-sb-bg, var(--bg-2));color:var(--strct-sb-fg, var(--t2));font-family:var(--font);font-size:var(--strct-sb-font-size, 12.5px);transition:background .14s ease,border-color .14s ease,color .14s ease}.strct-sb--trigger{cursor:pointer;text-align:start}.strct-sb--trigger:hover:not(:disabled){color:var(--strct-sb-fg-hover, var(--t1));background:var(--strct-sb-bg-hover, var(--bg-3))}.strct-sb--trigger:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}.strct-sb:focus-within:not(.strct-sb--trigger){border-color:var(--acc50);color:var(--t1)}.strct-sb--disabled,.strct-sb--trigger:disabled{opacity:.55;cursor:not-allowed}.strct-sb__icon{flex-shrink:0;color:var(--strct-sb-muted, var(--t3))}.strct-sb__label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.strct-sb__input{flex:1;min-width:0;border:0;padding:0;background:transparent;color:var(--t1);font-family:var(--font);font-size:12.5px;outline:none}.strct-sb__input::placeholder{color:var(--t3)}.strct-sb__kbd{flex-shrink:0;font-family:var(--mono);font-size:10px;line-height:1;padding:3px 5px;border:1px solid var(--strct-sb-kbd-border, var(--b2));border-radius:4px;background:var(--strct-sb-kbd-bg, var(--bg-2));color:var(--strct-sb-kbd-fg, var(--t3))}.strct-sb__clear{flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;margin:-2px;padding:0;border:0;border-radius:5px;background:transparent;color:var(--t3);cursor:pointer}.strct-sb__clear:hover{color:var(--t1);background:var(--bg-3)}.strct-sb__clear:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}@media(prefers-reduced-motion:reduce){.strct-sb{transition:none}}\n"] }]
4737
+ }], propDecorators: { placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], trigger: [{ type: i0.Input, args: [{ isSignal: true, alias: "trigger", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], clearLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearLabel", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], search: [{ type: i0.Output, args: ["search"] }], activated: [{ type: i0.Output, args: ["activated"] }] } });
4738
+
4739
+ /**
4740
+ * Click-to-copy chip with built-in feedback: an icon (or icon + label) button
4741
+ * that writes `text` to the clipboard and flips to a ✓ "Copied" state for a
4742
+ * moment. The console workhorse for UUIDs, IPs, serials and snippets:
4743
+ *
4744
+ * <strct-copy text="172.16.75.100" />
4745
+ * <strct-copy [text]="host.id" label="Copy ID" />
4746
+ */
4747
+ class StrctCopy {
4748
+ destroyRef = inject(DestroyRef);
4749
+ /** The text written to the clipboard. */
4750
+ text = input.required(...(ngDevMode ? [{ debugName: "text" }] : /* istanbul ignore next */ []));
4751
+ /** Optional visible label next to the icon (icon-only by default). */
4752
+ label = input('', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
4753
+ /** Accessible label; falls back to `copyLabel`. */
4754
+ ariaLabel = input('', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
4755
+ /** Localizable strings. */
4756
+ copyLabel = input('Copy', ...(ngDevMode ? [{ debugName: "copyLabel" }] : /* istanbul ignore next */ []));
4757
+ copiedLabel = input('Copied', ...(ngDevMode ? [{ debugName: "copiedLabel" }] : /* istanbul ignore next */ []));
4758
+ /** Emitted after a successful copy, with the copied text. */
4759
+ copied = output();
4760
+ done = signal(false, ...(ngDevMode ? [{ debugName: "done" }] : /* istanbul ignore next */ []));
4761
+ timer = null;
4762
+ constructor() {
4763
+ this.destroyRef.onDestroy(() => {
4764
+ if (this.timer)
4765
+ clearTimeout(this.timer);
4766
+ });
4767
+ }
4768
+ /** Write `text` to the clipboard and show the ✓ feedback. */
4769
+ copy() {
4770
+ const value = this.text();
4771
+ void navigator.clipboard?.writeText(value).then(() => {
4772
+ this.copied.emit(value);
4773
+ this.done.set(true);
4774
+ if (this.timer)
4775
+ clearTimeout(this.timer);
4776
+ this.timer = setTimeout(() => this.done.set(false), 1600);
4777
+ });
4778
+ }
4779
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctCopy, deps: [], target: i0.ɵɵFactoryTarget.Component });
4780
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: StrctCopy, isStandalone: true, selector: "strct-copy", inputs: { text: { classPropertyName: "text", publicName: "text", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, copyLabel: { classPropertyName: "copyLabel", publicName: "copyLabel", isSignal: true, isRequired: false, transformFunction: null }, copiedLabel: { classPropertyName: "copiedLabel", publicName: "copiedLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { copied: "copied" }, host: { classAttribute: "strct-copy-host" }, ngImport: i0, template: `
4781
+ <button
4782
+ type="button"
4783
+ class="strct-copy"
4784
+ [class.strct-copy--done]="done()"
4785
+ [attr.aria-label]="done() ? copiedLabel() : ariaLabel() || copyLabel()"
4786
+ (click)="copy()"
4787
+ >
4788
+ <strct-icon [name]="done() ? 'check' : 'copy'" [size]="13" [strokeWidth]="1.6" />
4789
+ @if (label()) {
4790
+ <span class="strct-copy__label">{{ done() ? copiedLabel() : label() }}</span>
4791
+ }
4792
+ <span class="strct-copy__sr" aria-live="polite">{{ done() ? copiedLabel() : '' }}</span>
4793
+ </button>
4794
+ `, isInline: true, styles: [".strct-copy-host{display:inline-flex}.strct-copy{display:inline-flex;align-items:center;gap:5px;padding:3px 6px;border:0;border-radius:5px;background:transparent;color:var(--t3);font-family:var(--font);font-size:12px;cursor:pointer;transition:color .13s ease,background .13s ease}.strct-copy:hover{color:var(--t1);background:var(--bg-3)}.strct-copy:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}.strct-copy--done,.strct-copy--done:hover{color:var(--success)}.strct-copy__sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}@media(prefers-reduced-motion:reduce){.strct-copy{transition:none}}\n"], dependencies: [{ kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "size", "strokeWidth", "badge", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
4795
+ }
4796
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctCopy, decorators: [{
4797
+ type: Component,
4798
+ args: [{ selector: 'strct-copy', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [StrctIcon], template: `
4799
+ <button
4800
+ type="button"
4801
+ class="strct-copy"
4802
+ [class.strct-copy--done]="done()"
4803
+ [attr.aria-label]="done() ? copiedLabel() : ariaLabel() || copyLabel()"
4804
+ (click)="copy()"
4805
+ >
4806
+ <strct-icon [name]="done() ? 'check' : 'copy'" [size]="13" [strokeWidth]="1.6" />
4807
+ @if (label()) {
4808
+ <span class="strct-copy__label">{{ done() ? copiedLabel() : label() }}</span>
4809
+ }
4810
+ <span class="strct-copy__sr" aria-live="polite">{{ done() ? copiedLabel() : '' }}</span>
4811
+ </button>
4812
+ `, host: { class: 'strct-copy-host' }, styles: [".strct-copy-host{display:inline-flex}.strct-copy{display:inline-flex;align-items:center;gap:5px;padding:3px 6px;border:0;border-radius:5px;background:transparent;color:var(--t3);font-family:var(--font);font-size:12px;cursor:pointer;transition:color .13s ease,background .13s ease}.strct-copy:hover{color:var(--t1);background:var(--bg-3)}.strct-copy:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}.strct-copy--done,.strct-copy--done:hover{color:var(--success)}.strct-copy__sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}@media(prefers-reduced-motion:reduce){.strct-copy{transition:none}}\n"] }]
4813
+ }], ctorParameters: () => [], propDecorators: { text: [{ type: i0.Input, args: [{ isSignal: true, alias: "text", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], copyLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "copyLabel", required: false }] }], copiedLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "copiedLabel", required: false }] }], copied: [{ type: i0.Output, args: ["copied"] }] } });
4814
+
4815
+ /**
4816
+ * Copyable mono code / rendered-config block — the component form of the
4817
+ * `<details><pre>` pattern consoles keep hand-rolling. A header row carries an
4818
+ * optional title, a language tag, a copy button and (when `collapsible`) a
4819
+ * chevron; the body is a scrollable, keyboard-focusable `<pre>`.
4820
+ *
4821
+ * <strct-code [code]="yaml" language="yaml" title="cloud-init" collapsible />
4822
+ */
4823
+ class StrctCode {
4824
+ /** The code / rendered config text (copied verbatim). */
4825
+ code = input.required(...(ngDevMode ? [{ debugName: "code" }] : /* istanbul ignore next */ []));
4826
+ /** Header title (e.g. a filename). */
4827
+ title = input('', ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
4828
+ /** Small uppercase language tag (yaml, json, bash …). */
4829
+ language = input('', ...(ngDevMode ? [{ debugName: "language" }] : /* istanbul ignore next */ []));
4830
+ /** Show the copy button. */
4831
+ copyable = input(true, { ...(ngDevMode ? { debugName: "copyable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4832
+ /** Collapse to just the header (the `<details><pre>` pattern, done right). */
4833
+ collapsible = input(false, { ...(ngDevMode ? { debugName: "collapsible" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4834
+ /** Collapsed state (two-way; only meaningful with `collapsible`). */
4835
+ collapsed = model(false, ...(ngDevMode ? [{ debugName: "collapsed" }] : /* istanbul ignore next */ []));
4836
+ /** Show a line-number gutter (numbers are never copied — `code` is). */
4837
+ lineNumbers = input(false, { ...(ngDevMode ? { debugName: "lineNumbers" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4838
+ /**
4839
+ * Soft-wrap long unbroken text (PEM/CSR blocks, base64, long commands) so a
4840
+ * dialog never scrolls horizontally. Wrapping breaks the gutter's visual
4841
+ * alignment, so `wrap` takes precedence and hides `lineNumbers`.
4842
+ */
4843
+ wrap = input(false, { ...(ngDevMode ? { debugName: "wrap" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
4844
+ /** Scroll the body past this height (px); null grows freely. */
4845
+ maxHeight = input(null, ...(ngDevMode ? [{ debugName: "maxHeight" }] : /* istanbul ignore next */ []));
4846
+ /** Localizable labels for the collapse toggle. */
4847
+ collapseLabel = input('Collapse', ...(ngDevMode ? [{ debugName: "collapseLabel" }] : /* istanbul ignore next */ []));
4848
+ expandLabel = input('Expand', ...(ngDevMode ? [{ debugName: "expandLabel" }] : /* istanbul ignore next */ []));
4849
+ lineNos = computed(() => {
4850
+ const n = this.code().split('\n').length;
4851
+ return Array.from({ length: n }, (_, i) => i + 1);
4852
+ }, ...(ngDevMode ? [{ debugName: "lineNos" }] : /* istanbul ignore next */ []));
4853
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctCode, deps: [], target: i0.ɵɵFactoryTarget.Component });
4854
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: StrctCode, isStandalone: true, selector: "strct-code", inputs: { code: { classPropertyName: "code", publicName: "code", isSignal: true, isRequired: true, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, language: { classPropertyName: "language", publicName: "language", isSignal: true, isRequired: false, transformFunction: null }, copyable: { classPropertyName: "copyable", publicName: "copyable", isSignal: true, isRequired: false, transformFunction: null }, collapsible: { classPropertyName: "collapsible", publicName: "collapsible", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, lineNumbers: { classPropertyName: "lineNumbers", publicName: "lineNumbers", isSignal: true, isRequired: false, transformFunction: null }, wrap: { classPropertyName: "wrap", publicName: "wrap", isSignal: true, isRequired: false, transformFunction: null }, maxHeight: { classPropertyName: "maxHeight", publicName: "maxHeight", isSignal: true, isRequired: false, transformFunction: null }, collapseLabel: { classPropertyName: "collapseLabel", publicName: "collapseLabel", isSignal: true, isRequired: false, transformFunction: null }, expandLabel: { classPropertyName: "expandLabel", publicName: "expandLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { collapsed: "collapsedChange" }, host: { properties: { "class.strct-code--collapsed": "collapsible() && collapsed()" }, classAttribute: "strct-code" }, ngImport: i0, template: `
4855
+ @if (title() || language() || copyable() || collapsible()) {
4856
+ <div class="strct-code__head">
4857
+ @if (collapsible()) {
4858
+ <button
4859
+ type="button"
4860
+ class="strct-code__toggle"
4861
+ [attr.aria-expanded]="!collapsed()"
4862
+ [attr.aria-label]="
4863
+ (collapsed() ? expandLabel() : collapseLabel()) +
4864
+ ' ' +
4865
+ (title() || language() || 'code')
4866
+ "
4867
+ (click)="collapsed.set(!collapsed())"
4868
+ >
4869
+ <strct-icon
4870
+ name="chevronRight"
4871
+ [size]="12"
4872
+ [strokeWidth]="1.7"
4873
+ [class.strct-code__chev--open]="!collapsed()"
4874
+ />
4875
+ </button>
4876
+ }
4877
+ @if (title()) {
4878
+ <span class="strct-code__title">{{ title() }}</span>
4879
+ }
4880
+ @if (language()) {
4881
+ <span class="strct-code__lang">{{ language() }}</span>
4882
+ }
4883
+ <span class="strct-code__grow"></span>
4884
+ @if (copyable()) {
4885
+ <strct-copy [text]="code()" />
4886
+ }
4887
+ </div>
4888
+ }
4889
+ @if (!collapsible() || !collapsed()) {
4890
+ <div
4891
+ class="strct-code__scroll"
4892
+ [class.strct-code__scroll--wrap]="wrap()"
4893
+ tabindex="0"
4894
+ role="region"
4895
+ [attr.aria-label]="title() || language() || 'code'"
4896
+ [style.max-height.px]="maxHeight()"
4897
+ >
4898
+ <pre class="strct-code__pre"><!--
4899
+ -->@if (lineNumbers() && !wrap()) {<!--
4900
+ --><span class="strct-code__gutter" aria-hidden="true">@for (n of lineNos(); track n) {<span class="strct-code__ln">{{ n }}</span>}</span><!--
4901
+ -->}<!--
4902
+ --><code class="strct-code__code">{{ code() }}</code></pre>
4903
+ </div>
4904
+ }
4905
+ `, isInline: true, styles: [".strct-code{display:block;border:1px solid var(--b2);border-radius:9px;background:var(--bg-2);overflow:hidden}.strct-code__head{display:flex;align-items:center;gap:8px;padding:6px 10px;border-bottom:1px solid var(--b1)}.strct-code--collapsed .strct-code__head{border-bottom:0}.strct-code__toggle{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;margin:-3px 0 -3px -4px;padding:0;border:0;border-radius:5px;background:transparent;color:var(--t3);cursor:pointer}.strct-code__toggle:hover{color:var(--t1);background:var(--bg-3)}.strct-code__toggle:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}.strct-code__toggle strct-icon{transition:transform .15s ease}.strct-code__chev--open{transform:rotate(90deg)}@media(prefers-reduced-motion:reduce){.strct-code__toggle strct-icon{transition:none}}.strct-code__title{font-size:12.5px;font-weight:600;color:var(--t1)}.strct-code__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-code__grow{flex:1}.strct-code__scroll{overflow:auto}.strct-code__scroll:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-code__pre{display:flex;margin:0;padding:10px 12px;font-family:var(--mono);font-size:12px;line-height:1.55;color:var(--t1)}.strct-code__gutter{display:flex;flex-direction:column;flex-shrink:0;margin-inline-end:12px;padding-inline-end:10px;border-inline-end:1px solid var(--b1);color:var(--t3);text-align:end;-webkit-user-select:none;user-select:none;font-variant-numeric:tabular-nums}.strct-code__code{display:block;white-space:pre}.strct-code__scroll--wrap .strct-code__code{white-space:pre-wrap;overflow-wrap:anywhere}\n"], dependencies: [{ kind: "component", type: StrctCopy, selector: "strct-copy", inputs: ["text", "label", "ariaLabel", "copyLabel", "copiedLabel"], outputs: ["copied"] }, { kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "size", "strokeWidth", "badge", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
4906
+ }
4907
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctCode, decorators: [{
4908
+ type: Component,
4909
+ args: [{ selector: 'strct-code', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [StrctCopy, StrctIcon], template: `
4910
+ @if (title() || language() || copyable() || collapsible()) {
4911
+ <div class="strct-code__head">
4912
+ @if (collapsible()) {
4913
+ <button
4914
+ type="button"
4915
+ class="strct-code__toggle"
4916
+ [attr.aria-expanded]="!collapsed()"
4917
+ [attr.aria-label]="
4918
+ (collapsed() ? expandLabel() : collapseLabel()) +
4919
+ ' ' +
4920
+ (title() || language() || 'code')
4921
+ "
4922
+ (click)="collapsed.set(!collapsed())"
4923
+ >
4924
+ <strct-icon
4925
+ name="chevronRight"
4926
+ [size]="12"
4927
+ [strokeWidth]="1.7"
4928
+ [class.strct-code__chev--open]="!collapsed()"
4929
+ />
4930
+ </button>
4931
+ }
4932
+ @if (title()) {
4933
+ <span class="strct-code__title">{{ title() }}</span>
4934
+ }
4935
+ @if (language()) {
4936
+ <span class="strct-code__lang">{{ language() }}</span>
4937
+ }
4938
+ <span class="strct-code__grow"></span>
4939
+ @if (copyable()) {
4940
+ <strct-copy [text]="code()" />
4941
+ }
4942
+ </div>
4943
+ }
4944
+ @if (!collapsible() || !collapsed()) {
4945
+ <div
4946
+ class="strct-code__scroll"
4947
+ [class.strct-code__scroll--wrap]="wrap()"
4948
+ tabindex="0"
4949
+ role="region"
4950
+ [attr.aria-label]="title() || language() || 'code'"
4951
+ [style.max-height.px]="maxHeight()"
4952
+ >
4953
+ <pre class="strct-code__pre"><!--
4954
+ -->@if (lineNumbers() && !wrap()) {<!--
4955
+ --><span class="strct-code__gutter" aria-hidden="true">@for (n of lineNos(); track n) {<span class="strct-code__ln">{{ n }}</span>}</span><!--
4956
+ -->}<!--
4957
+ --><code class="strct-code__code">{{ code() }}</code></pre>
4958
+ </div>
4959
+ }
4960
+ `, host: {
4961
+ class: 'strct-code',
4962
+ '[class.strct-code--collapsed]': 'collapsible() && collapsed()',
4963
+ }, styles: [".strct-code{display:block;border:1px solid var(--b2);border-radius:9px;background:var(--bg-2);overflow:hidden}.strct-code__head{display:flex;align-items:center;gap:8px;padding:6px 10px;border-bottom:1px solid var(--b1)}.strct-code--collapsed .strct-code__head{border-bottom:0}.strct-code__toggle{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;margin:-3px 0 -3px -4px;padding:0;border:0;border-radius:5px;background:transparent;color:var(--t3);cursor:pointer}.strct-code__toggle:hover{color:var(--t1);background:var(--bg-3)}.strct-code__toggle:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}.strct-code__toggle strct-icon{transition:transform .15s ease}.strct-code__chev--open{transform:rotate(90deg)}@media(prefers-reduced-motion:reduce){.strct-code__toggle strct-icon{transition:none}}.strct-code__title{font-size:12.5px;font-weight:600;color:var(--t1)}.strct-code__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-code__grow{flex:1}.strct-code__scroll{overflow:auto}.strct-code__scroll:focus-visible{outline:2px solid var(--acc50);outline-offset:-2px}.strct-code__pre{display:flex;margin:0;padding:10px 12px;font-family:var(--mono);font-size:12px;line-height:1.55;color:var(--t1)}.strct-code__gutter{display:flex;flex-direction:column;flex-shrink:0;margin-inline-end:12px;padding-inline-end:10px;border-inline-end:1px solid var(--b1);color:var(--t3);text-align:end;-webkit-user-select:none;user-select:none;font-variant-numeric:tabular-nums}.strct-code__code{display:block;white-space:pre}.strct-code__scroll--wrap .strct-code__code{white-space:pre-wrap;overflow-wrap:anywhere}\n"] }]
4964
+ }], propDecorators: { code: [{ type: i0.Input, args: [{ isSignal: true, alias: "code", required: true }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], language: [{ type: i0.Input, args: [{ isSignal: true, alias: "language", required: false }] }], copyable: [{ type: i0.Input, args: [{ isSignal: true, alias: "copyable", required: false }] }], collapsible: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsible", required: false }] }], collapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapsed", required: false }] }, { type: i0.Output, args: ["collapsedChange"] }], lineNumbers: [{ type: i0.Input, args: [{ isSignal: true, alias: "lineNumbers", required: false }] }], wrap: [{ type: i0.Input, args: [{ isSignal: true, alias: "wrap", required: false }] }], maxHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxHeight", required: false }] }], collapseLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapseLabel", required: false }] }], expandLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandLabel", required: false }] }] } });
4965
+
4966
+ /**
4967
+ * The standard strip above a data grid: a search field, the active filter
4968
+ * chips (each removable), a result count and a clear-all action. The bar owns
4969
+ * no filtering logic — it renders state and announces intent:
4970
+ *
4971
+ * <strct-filter-bar [(query)]="q" [filters]="chips" [count]="rows.length"
4972
+ * (removed)="drop($event)" (cleared)="reset()" />
4973
+ */
4974
+ class StrctFilterBar {
4975
+ /** Search text (two-way; forwards to the embedded searchbox). */
4976
+ query = model('', ...(ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []));
4977
+ /** Active filter chips. */
4978
+ filters = input([], ...(ngDevMode ? [{ debugName: "filters" }] : /* istanbul ignore next */ []));
4979
+ /** Result count shown at the end; null hides it. */
4980
+ count = input(null, ...(ngDevMode ? [{ debugName: "count" }] : /* istanbul ignore next */ []));
4981
+ /** Localizable strings. */
4982
+ placeholder = input('Search', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
4983
+ countLabel = input('results', ...(ngDevMode ? [{ debugName: "countLabel" }] : /* istanbul ignore next */ []));
4984
+ clearLabel = input('Clear filters', ...(ngDevMode ? [{ debugName: "clearLabel" }] : /* istanbul ignore next */ []));
4985
+ removeLabel = input('Remove filter', ...(ngDevMode ? [{ debugName: "removeLabel" }] : /* istanbul ignore next */ []));
4986
+ /** Enter pressed in the search field. */
4987
+ search = output();
4988
+ /** A chip's × was clicked. */
4989
+ removed = output();
4990
+ /** "Clear filters" was clicked. */
4991
+ cleared = output();
4992
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctFilterBar, deps: [], target: i0.ɵɵFactoryTarget.Component });
4993
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: StrctFilterBar, isStandalone: true, selector: "strct-filter-bar", inputs: { query: { classPropertyName: "query", publicName: "query", isSignal: true, isRequired: false, transformFunction: null }, filters: { classPropertyName: "filters", publicName: "filters", isSignal: true, isRequired: false, transformFunction: null }, count: { classPropertyName: "count", publicName: "count", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, countLabel: { classPropertyName: "countLabel", publicName: "countLabel", isSignal: true, isRequired: false, transformFunction: null }, clearLabel: { classPropertyName: "clearLabel", publicName: "clearLabel", isSignal: true, isRequired: false, transformFunction: null }, removeLabel: { classPropertyName: "removeLabel", publicName: "removeLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { query: "queryChange", search: "search", removed: "removed", cleared: "cleared" }, host: { classAttribute: "strct-fb" }, ngImport: i0, template: `
4994
+ <strct-searchbox
4995
+ class="strct-fb__search"
4996
+ [placeholder]="placeholder()"
4997
+ [(value)]="query"
4998
+ (search)="search.emit($event)"
4999
+ />
5000
+ @for (chip of filters(); track chip.id) {
5001
+ <span class="strct-fb__chip">
5002
+ <span class="strct-fb__chip-label">{{ chip.label }}</span>
5003
+ <button
5004
+ type="button"
5005
+ class="strct-fb__chip-x"
5006
+ [attr.aria-label]="removeLabel() + ' ' + chip.label"
5007
+ (click)="removed.emit(chip)"
5008
+ >
5009
+ <strct-icon name="close" [size]="10" [strokeWidth]="1.9" />
5010
+ </button>
5011
+ </span>
5012
+ }
5013
+ @if (filters().length > 1) {
5014
+ <button type="button" class="strct-fb__clear" (click)="cleared.emit()">
5015
+ {{ clearLabel() }}
5016
+ </button>
5017
+ }
5018
+ <ng-content />
5019
+ <span class="strct-fb__grow"></span>
5020
+ @if (count() !== null) {
5021
+ <span class="strct-fb__count" aria-live="polite">{{ count() }} {{ countLabel() }}</span>
5022
+ }
5023
+ `, isInline: true, styles: [".strct-fb{display:flex;align-items:center;gap:8px;flex-wrap:wrap;width:100%}.strct-fb__search{width:240px;max-width:100%}.strct-fb__chip{display:inline-flex;align-items:center;gap:5px;padding:3px 4px 3px 9px;border:1px solid var(--b2);border-radius:99px;background:var(--acc-m);color:var(--acc);font-size:12px;font-weight:500}.strct-fb__chip-x{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;padding:0;border:0;border-radius:50%;background:transparent;color:inherit;cursor:pointer}.strct-fb__chip-x:hover{background:var(--acc30)}.strct-fb__chip-x:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}.strct-fb__clear{border:0;padding:3px 6px;border-radius:5px;background:transparent;color:var(--t3);font-family:var(--font);font-size:12px;cursor:pointer}.strct-fb__clear:hover{color:var(--t1);background:var(--bg-3)}.strct-fb__clear:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}.strct-fb__grow{flex:1}.strct-fb__count{font-size:12px;color:var(--t3);font-variant-numeric:tabular-nums;white-space:nowrap}\n"], dependencies: [{ 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 });
5024
+ }
5025
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: StrctFilterBar, decorators: [{
5026
+ type: Component,
5027
+ args: [{ selector: 'strct-filter-bar', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [StrctIcon, StrctSearchbox], template: `
5028
+ <strct-searchbox
5029
+ class="strct-fb__search"
5030
+ [placeholder]="placeholder()"
5031
+ [(value)]="query"
5032
+ (search)="search.emit($event)"
5033
+ />
5034
+ @for (chip of filters(); track chip.id) {
5035
+ <span class="strct-fb__chip">
5036
+ <span class="strct-fb__chip-label">{{ chip.label }}</span>
5037
+ <button
5038
+ type="button"
5039
+ class="strct-fb__chip-x"
5040
+ [attr.aria-label]="removeLabel() + ' ' + chip.label"
5041
+ (click)="removed.emit(chip)"
5042
+ >
5043
+ <strct-icon name="close" [size]="10" [strokeWidth]="1.9" />
5044
+ </button>
5045
+ </span>
5046
+ }
5047
+ @if (filters().length > 1) {
5048
+ <button type="button" class="strct-fb__clear" (click)="cleared.emit()">
5049
+ {{ clearLabel() }}
5050
+ </button>
5051
+ }
5052
+ <ng-content />
5053
+ <span class="strct-fb__grow"></span>
5054
+ @if (count() !== null) {
5055
+ <span class="strct-fb__count" aria-live="polite">{{ count() }} {{ countLabel() }}</span>
5056
+ }
5057
+ `, host: { class: 'strct-fb' }, styles: [".strct-fb{display:flex;align-items:center;gap:8px;flex-wrap:wrap;width:100%}.strct-fb__search{width:240px;max-width:100%}.strct-fb__chip{display:inline-flex;align-items:center;gap:5px;padding:3px 4px 3px 9px;border:1px solid var(--b2);border-radius:99px;background:var(--acc-m);color:var(--acc);font-size:12px;font-weight:500}.strct-fb__chip-x{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;padding:0;border:0;border-radius:50%;background:transparent;color:inherit;cursor:pointer}.strct-fb__chip-x:hover{background:var(--acc30)}.strct-fb__chip-x:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}.strct-fb__clear{border:0;padding:3px 6px;border-radius:5px;background:transparent;color:var(--t3);font-family:var(--font);font-size:12px;cursor:pointer}.strct-fb__clear:hover{color:var(--t1);background:var(--bg-3)}.strct-fb__clear:focus-visible{outline:2px solid var(--acc50);outline-offset:1px}.strct-fb__grow{flex:1}.strct-fb__count{font-size:12px;color:var(--t3);font-variant-numeric:tabular-nums;white-space:nowrap}\n"] }]
5058
+ }], propDecorators: { query: [{ type: i0.Input, args: [{ isSignal: true, alias: "query", required: false }] }, { type: i0.Output, args: ["queryChange"] }], filters: [{ type: i0.Input, args: [{ isSignal: true, alias: "filters", required: false }] }], count: [{ type: i0.Input, args: [{ isSignal: true, alias: "count", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], countLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "countLabel", required: false }] }], clearLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearLabel", required: false }] }], removeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "removeLabel", required: false }] }], search: [{ type: i0.Output, args: ["search"] }], removed: [{ type: i0.Output, args: ["removed"] }], cleared: [{ type: i0.Output, args: ["cleared"] }] } });
4388
5059
 
4389
5060
  /** Tone class suffix per validation status (used by field + cell helper styles). */
4390
5061
  function strctValidationTone(status) {
@@ -12097,5 +12768,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImpo
12097
12768
  * Generated bundle index. Do not edit.
12098
12769
  */
12099
12770
 
12100
- export { STRCT_ICONS, STRCT_ICON_GROUPS, 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, StrctColorPicker, StrctCombobox, StrctCommandPalette, StrctContextMenu, StrctContextMenuTrigger, StrctDatagrid, StrctDatagridActionBar, StrctDatepicker, StrctDesc, StrctDescriptionList, StrctDivider, StrctDonut, StrctDrawer, StrctDrawerFooter, StrctDropdown, StrctDropdownDivider, StrctDropdownItem, StrctEmptyState, StrctField, StrctFile, StrctFlow, StrctFooter, StrctGauge, StrctHeader, StrctHero, StrctIcon, StrctInput, StrctInputMask, StrctInputOtp, StrctKbd, StrctKnob, StrctLogin, StrctMenuPanel, StrctMenuService, StrctMetricTile, StrctModal, StrctNav, StrctNavItem, StrctOverlay, StrctPagination, StrctPassword, StrctProgress, StrctRadio, StrctRadioGroup, StrctRail, StrctRange, StrctRating, StrctRowDetailDef, 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 };
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 };
12101
12772
  //# sourceMappingURL=akcelik-strct.mjs.map