@akcelik/strct 5.0.0 → 5.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1749,7 +1749,7 @@ class StrctPageHeader {
1749
1749
  <div class="strct-ph__actions"><ng-content select="[strctPageHeaderActions]" /></div>
1750
1750
  </div>
1751
1751
  <ng-content />
1752
- `, 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__titlerow{display:flex;align-items:baseline;gap:var(--space-2);min-width:0;flex-wrap:wrap}.strct-ph__icon{align-self:center;color:var(--t2);flex:none}.strct-ph__titlemeta:empty{display:none}.strct-ph__titlemeta{display:inline-flex;align-items:center;gap:var(--space-2)}.strct-ph--pane .strct-ph__title{font-size:var(--text-lg);font-weight:600;letter-spacing: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"], 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 });
1752
+ `, 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__titlerow{display:flex;align-items:baseline;gap:var(--space-2);min-width:0;flex-wrap:wrap}.strct-ph__icon{align-self:center;color:var(--t2);flex:none}.strct-ph__titlemeta:empty{display:none}.strct-ph__titlemeta{display:inline-flex;align-items:center;gap:var(--space-2)}.strct-ph--pane .strct-ph__title{font-size:18px;font-weight:600;letter-spacing: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"], 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 });
1753
1753
  }
1754
1754
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctPageHeader, decorators: [{
1755
1755
  type: Component,
@@ -1794,7 +1794,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
1794
1794
  class: 'strct-ph',
1795
1795
  '[class.strct-ph--divider]': 'divider()',
1796
1796
  '[class.strct-ph--pane]': "size() === 'pane'",
1797
- }, 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__titlerow{display:flex;align-items:baseline;gap:var(--space-2);min-width:0;flex-wrap:wrap}.strct-ph__icon{align-self:center;color:var(--t2);flex:none}.strct-ph__titlemeta:empty{display:none}.strct-ph__titlemeta{display:inline-flex;align-items:center;gap:var(--space-2)}.strct-ph--pane .strct-ph__title{font-size:var(--text-lg);font-weight:600;letter-spacing: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"] }]
1797
+ }, 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__titlerow{display:flex;align-items:baseline;gap:var(--space-2);min-width:0;flex-wrap:wrap}.strct-ph__icon{align-self:center;color:var(--t2);flex:none}.strct-ph__titlemeta:empty{display:none}.strct-ph__titlemeta{display:inline-flex;align-items:center;gap:var(--space-2)}.strct-ph--pane .strct-ph__title{font-size:18px;font-weight:600;letter-spacing: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"] }]
1798
1798
  }], 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 }] }], level: [{ type: i0.Input, args: [{ isSignal: true, alias: "level", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }] } });
1799
1799
 
1800
1800
  /** Leading marker of a row — a badge, a status dot, an icon. */
@@ -2216,8 +2216,25 @@ function strctCheckHostInputs(host, selector, owns) {
2216
2216
  * way to break a flex host.
2217
2217
  */
2218
2218
  function strctCheckHostDisplay(host, selector, expected) {
2219
+ // A host inside a closed modal or an unshown tab is not in the document yet,
2220
+ // so getComputedStyle reports '' — which is not an override. Warning there
2221
+ // would be wrong twice over: the message is nonsense, and because the warning
2222
+ // deduplicates by key it would mask a real override later on the same page.
2223
+ // Wait for the element to be laid out instead.
2224
+ if (!host.isConnected) {
2225
+ if (typeof ResizeObserver === 'undefined')
2226
+ return;
2227
+ const ro = new ResizeObserver(() => {
2228
+ if (!host.isConnected)
2229
+ return;
2230
+ ro.disconnect();
2231
+ strctCheckHostDisplay(host, selector, expected);
2232
+ });
2233
+ ro.observe(host);
2234
+ return;
2235
+ }
2219
2236
  const actual = getComputedStyle(host).display;
2220
- if (actual === expected)
2237
+ if (!actual || actual === expected)
2221
2238
  return;
2222
2239
  strctDevWarn(`host-display:${selector}`, `[strct] <${selector}> needs display: ${expected}, but it computes to ${actual}. ` +
2223
2240
  `Wrap it, or set the margin on a wrapper, rather than changing its display.`);
@@ -2660,7 +2677,7 @@ class StrctSpinner {
2660
2677
  <span class="strct-spinner__ring"></span>
2661
2678
  <span class="strct-spinner__caption">{{ caption() }}</span>
2662
2679
  }
2663
- `, isInline: true, styles: [".strct-spinner{display:inline-block;width:22px;height:22px;border:2.5px solid var(--b3);border-top-color:var(--acc);border-radius:50%;animation:strct-spin .7s linear infinite}.strct-spinner--sm{width:14px;height:14px;border-width:2px}.strct-spinner--lg{width:34px;height:34px;border-width:3px}@keyframes strct-spin{to{transform:rotate(360deg)}}@media(prefers-reduced-motion:reduce){.strct-spinner{animation-duration:1.6s}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
2680
+ `, isInline: true, styles: [".strct-spinner{display:inline-block;width:22px;height:22px;border:2.5px solid var(--b3);border-top-color:var(--acc);border-radius:50%;animation:strct-spin .7s linear infinite}.strct-spinner--sm{width:14px;height:14px;border-width:2px}.strct-spinner--lg{width:34px;height:34px;border-width:3px}@keyframes strct-spin{to{transform:rotate(360deg)}}@media(prefers-reduced-motion:reduce){.strct-spinner{animation-duration:1.6s}}.strct-spinner--captioned{display:inline-flex;align-items:center;gap:var(--space-2);width:auto;height:auto;border:0;border-radius:0;animation:none}.strct-spinner__ring{flex:none;width:22px;height:22px;border:2.5px solid var(--b3);border-top-color:var(--acc);border-radius:50%;animation:strct-spin .7s linear infinite}.strct-spinner--sm .strct-spinner__ring{width:14px;height:14px;border-width:2px}.strct-spinner--lg .strct-spinner__ring{width:34px;height:34px;border-width:3px}@media(prefers-reduced-motion:reduce){.strct-spinner__ring{animation-duration:1.6s}}.strct-spinner__caption{font-size:var(--text-sm);color:var(--t3);line-height:1.3}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
2664
2681
  }
2665
2682
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctSpinner, decorators: [{
2666
2683
  type: Component,
@@ -2676,7 +2693,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
2676
2693
  '[attr.aria-label]': 'caption() || label()',
2677
2694
  '[class.strct-spinner--sm]': "size() === 'sm'",
2678
2695
  '[class.strct-spinner--lg]': "size() === 'lg'",
2679
- }, styles: [".strct-spinner{display:inline-block;width:22px;height:22px;border:2.5px solid var(--b3);border-top-color:var(--acc);border-radius:50%;animation:strct-spin .7s linear infinite}.strct-spinner--sm{width:14px;height:14px;border-width:2px}.strct-spinner--lg{width:34px;height:34px;border-width:3px}@keyframes strct-spin{to{transform:rotate(360deg)}}@media(prefers-reduced-motion:reduce){.strct-spinner{animation-duration:1.6s}}\n"] }]
2696
+ }, styles: [".strct-spinner{display:inline-block;width:22px;height:22px;border:2.5px solid var(--b3);border-top-color:var(--acc);border-radius:50%;animation:strct-spin .7s linear infinite}.strct-spinner--sm{width:14px;height:14px;border-width:2px}.strct-spinner--lg{width:34px;height:34px;border-width:3px}@keyframes strct-spin{to{transform:rotate(360deg)}}@media(prefers-reduced-motion:reduce){.strct-spinner{animation-duration:1.6s}}.strct-spinner--captioned{display:inline-flex;align-items:center;gap:var(--space-2);width:auto;height:auto;border:0;border-radius:0;animation:none}.strct-spinner__ring{flex:none;width:22px;height:22px;border:2.5px solid var(--b3);border-top-color:var(--acc);border-radius:50%;animation:strct-spin .7s linear infinite}.strct-spinner--sm .strct-spinner__ring{width:14px;height:14px;border-width:2px}.strct-spinner--lg .strct-spinner__ring{width:34px;height:34px;border-width:3px}@media(prefers-reduced-motion:reduce){.strct-spinner__ring{animation-duration:1.6s}}.strct-spinner__caption{font-size:var(--text-sm);color:var(--t3);line-height:1.3}\n"] }]
2680
2697
  }], propDecorators: { size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], caption: [{ type: i0.Input, args: [{ isSignal: true, alias: "caption", required: false }] }] } });
2681
2698
 
2682
2699
  /**
@@ -7793,22 +7810,60 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
7793
7810
  class StrctCodeInline {
7794
7811
  /** Append a copy button that copies this element's text. */
7795
7812
  copyable = input(false, { ...(ngDevMode ? { debugName: "copyable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
7813
+ /**
7814
+ * What the copy button puts on the clipboard, when that is not simply what
7815
+ * the element says — a span showing a shortened thumbprint copies the whole
7816
+ * one. Empty follows the element's own text, including later changes.
7817
+ */
7818
+ value = input('', /* @ts-ignore */
7819
+ ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
7820
+ /** Long ids wrap instead of overflowing a narrow card. */
7821
+ wrap = input(false, { ...(ngDevMode ? { debugName: "wrap" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
7796
7822
  host = inject(ElementRef);
7797
7823
  vcr = inject(ViewContainerRef);
7824
+ destroyRef = inject(DestroyRef);
7825
+ /** The button, once it exists; and a counter the observer bumps. */
7826
+ copyRef = signal(null, /* @ts-ignore */
7827
+ ...(ngDevMode ? [{ debugName: "copyRef" }] : /* istanbul ignore next */ []));
7828
+ revision = signal(0, /* @ts-ignore */
7829
+ ...(ngDevMode ? [{ debugName: "revision" }] : /* istanbul ignore next */ []));
7798
7830
  constructor() {
7799
7831
  afterNextRender(() => {
7800
7832
  if (!this.copyable())
7801
7833
  return;
7802
7834
  const el = this.host.nativeElement;
7803
7835
  const ref = this.vcr.createComponent(StrctCopy);
7804
- // The text to copy is what the element says, before the button joins it.
7805
- ref.setInput('text', el.textContent?.trim() ?? '');
7806
- ref.changeDetectorRef.detectChanges();
7807
7836
  el.appendChild(ref.location.nativeElement);
7837
+ this.copyRef.set(ref);
7838
+ // The element's text is not a signal, so watch it: a path or a thumbprint
7839
+ // that loads later must be the text that gets copied, not the first one.
7840
+ if (typeof MutationObserver !== 'undefined') {
7841
+ const mo = new MutationObserver(() => this.revision.update((n) => n + 1));
7842
+ mo.observe(el, { childList: true, characterData: true, subtree: true });
7843
+ this.destroyRef.onDestroy(() => mo.disconnect());
7844
+ }
7845
+ });
7846
+ // Keeps the button's text in step with `value`, or with the element.
7847
+ effect(() => {
7848
+ const ref = this.copyRef();
7849
+ if (!ref)
7850
+ return;
7851
+ this.revision(); // re-read the element whenever its text changed
7852
+ const text = this.value() || this.ownText();
7853
+ untracked(() => {
7854
+ ref.setInput('text', text);
7855
+ ref.changeDetectorRef.detectChanges();
7856
+ });
7808
7857
  });
7809
7858
  }
7859
+ /** What the element says, without the copy button's own content. */
7860
+ ownText() {
7861
+ const clone = this.host.nativeElement.cloneNode(true);
7862
+ clone.querySelector('strct-copy')?.remove();
7863
+ return clone.textContent?.trim() ?? '';
7864
+ }
7810
7865
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctCodeInline, deps: [], target: i0.ɵɵFactoryTarget.Directive });
7811
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.4", type: StrctCodeInline, isStandalone: true, selector: "[strctCode]", inputs: { copyable: { classPropertyName: "copyable", publicName: "copyable", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.strct-code-inline--copyable": "copyable()" }, classAttribute: "strct-code-inline" }, ngImport: i0 });
7866
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.4", type: StrctCodeInline, isStandalone: true, selector: "[strctCode]", inputs: { copyable: { classPropertyName: "copyable", publicName: "copyable", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, wrap: { classPropertyName: "wrap", publicName: "wrap", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.strct-code-inline--copyable": "copyable()", "class.strct-code-inline--wrap": "wrap()" }, classAttribute: "strct-code-inline" }, ngImport: i0 });
7812
7867
  }
7813
7868
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctCodeInline, decorators: [{
7814
7869
  type: Directive,
@@ -7817,9 +7872,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
7817
7872
  host: {
7818
7873
  class: 'strct-code-inline',
7819
7874
  '[class.strct-code-inline--copyable]': 'copyable()',
7875
+ '[class.strct-code-inline--wrap]': 'wrap()',
7820
7876
  },
7821
7877
  }]
7822
- }], ctorParameters: () => [], propDecorators: { copyable: [{ type: i0.Input, args: [{ isSignal: true, alias: "copyable", required: false }] }] } });
7878
+ }], ctorParameters: () => [], propDecorators: { copyable: [{ type: i0.Input, args: [{ isSignal: true, alias: "copyable", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], wrap: [{ type: i0.Input, args: [{ isSignal: true, alias: "wrap", required: false }] }] } });
7823
7879
 
7824
7880
  /**
7825
7881
  * The standard strip above a data grid: a search field, the active filter
@@ -12522,6 +12578,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
12522
12578
  /**
12523
12579
  * One projected `label → value` row inside a `<strct-description-list>`. The
12524
12580
  * value is whatever you project, so it can host a badge, icon or formatted text.
12581
+ *
12582
+ * Use **`<div strctDesc>`** inside a list: `<dl>` allows only `dt` / `dd` pairs
12583
+ * or `<div>` wrappers as children, so a `<strct-desc>` element between them is
12584
+ * invalid and assistive tech loses the pairing. `<strct-desc>` still works for a
12585
+ * row rendered outside a list.
12525
12586
  */
12526
12587
  class StrctDesc {
12527
12588
  /** The row's label. */
@@ -12544,7 +12605,7 @@ class StrctDesc {
12544
12605
  note = input('', /* @ts-ignore */
12545
12606
  ...(ngDevMode ? [{ debugName: "note" }] : /* istanbul ignore next */ []));
12546
12607
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDesc, deps: [], target: i0.ɵɵFactoryTarget.Component });
12547
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: StrctDesc, isStandalone: true, selector: "strct-desc", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, mono: { classPropertyName: "mono", publicName: "mono", isSignal: true, isRequired: false, transformFunction: null }, muted: { classPropertyName: "muted", publicName: "muted", isSignal: true, isRequired: false, transformFunction: null }, status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: false, transformFunction: null }, statusLabel: { classPropertyName: "statusLabel", publicName: "statusLabel", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, note: { classPropertyName: "note", publicName: "note", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.strct-desc--mono": "mono()", "class.strct-desc--muted": "muted()" }, classAttribute: "strct-desc" }, ngImport: i0, template: `
12608
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: StrctDesc, isStandalone: true, selector: "strct-desc, div[strctDesc]", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, mono: { classPropertyName: "mono", publicName: "mono", isSignal: true, isRequired: false, transformFunction: null }, muted: { classPropertyName: "muted", publicName: "muted", isSignal: true, isRequired: false, transformFunction: null }, status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: false, transformFunction: null }, statusLabel: { classPropertyName: "statusLabel", publicName: "statusLabel", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, note: { classPropertyName: "note", publicName: "note", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.strct-desc--mono": "mono()", "class.strct-desc--muted": "muted()" }, classAttribute: "strct-desc" }, ngImport: i0, template: `
12548
12609
  <dt class="strct-desc__label">
12549
12610
  @if (status()) {
12550
12611
  <strct-status-dot size="sm" [status]="status()!" [label]="statusLabel()" />
@@ -12565,7 +12626,11 @@ class StrctDesc {
12565
12626
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctDesc, decorators: [{
12566
12627
  type: Component,
12567
12628
  args: [{
12568
- selector: 'strct-desc',
12629
+ // Both spellings. `<strct-desc>` is the readable one, but a custom element
12630
+ // between `<dl>` and its `dt` / `dd` breaks the content model — axe reports
12631
+ // definition-list and dlitem — so inside a list, use `<div strctDesc>`, which
12632
+ // the model does allow. (`StrctInput` gained `strct-input` the same way.)
12633
+ selector: 'strct-desc, div[strctDesc]',
12569
12634
  changeDetection: ChangeDetectionStrategy.OnPush,
12570
12635
  encapsulation: ViewEncapsulation.None,
12571
12636
  imports: [StrctIcon, StrctStatusDot],
@@ -14283,6 +14348,51 @@ class StrctDatagrid {
14283
14348
  return;
14284
14349
  untracked(() => this.selected.set(new Set(init)));
14285
14350
  });
14351
+ // A selection must not outlive its rows: after a delete the footer said
14352
+ // "2 selected" and selectionChange consumers held ids for rows that are
14353
+ // gone. Prune to the ids the current rows can account for, and emit only
14354
+ // when the pruning actually removed something. A lazy grid cannot prove a
14355
+ // row is gone rather than on another page, so it is left alone.
14356
+ effect(() => {
14357
+ const rows = this.rows();
14358
+ if (this.lazy())
14359
+ return;
14360
+ untracked(() => {
14361
+ const selected = this.selected();
14362
+ if (!selected.size)
14363
+ return;
14364
+ const present = new Set(rows.map((r) => this.idOf(r)));
14365
+ const kept = new Set([...selected].filter((id) => present.has(id)));
14366
+ if (kept.size === selected.size)
14367
+ return;
14368
+ this.commitSelection(kept);
14369
+ });
14370
+ });
14371
+ // Single mode: the checked row follows `selectedId`, whoever wrote it — the
14372
+ // user picking, or a consumer restoring a pick from an earlier step. It
14373
+ // writes `selected` directly rather than through commitSelection, so an
14374
+ // external write never emits selectionChange (a user's own pick already
14375
+ // emitted one).
14376
+ effect(() => {
14377
+ if (this.mode() !== 'single')
14378
+ return;
14379
+ const id = this.selectedId();
14380
+ untracked(() => {
14381
+ const next = id == null ? new Set() : new Set([id]);
14382
+ const current = this.selected();
14383
+ if (current.size === next.size && [...next].every((v) => current.has(v)))
14384
+ return;
14385
+ this.selected.set(next);
14386
+ // Keep the row snapshot in step when the row is loaded; a pick set
14387
+ // before the rows arrive is cached when it is toggled later.
14388
+ this.selectedRowCache.clear();
14389
+ if (id != null) {
14390
+ const row = this.rows().find((r) => this.idOf(r) === id);
14391
+ if (row)
14392
+ this.selectedRowCache.set(id, row);
14393
+ }
14394
+ });
14395
+ });
14286
14396
  // Dev mode only: say so when rowId or initialSelection cannot do what they
14287
14397
  // were given to do. In production the effect is never registered.
14288
14398
  if (typeof ngDevMode !== 'undefined' && ngDevMode) {
@@ -19737,7 +19847,7 @@ class StrctEmptyState {
19737
19847
  <p class="strct-empty__desc">{{ description() }}</p>
19738
19848
  }
19739
19849
  <div class="strct-empty__actions"><ng-content /></div>
19740
- `, isInline: true, styles: [".strct-empty{display:flex;flex-direction:column;align-items:center;text-align:center;gap:10px;padding:40px 24px}.strct-empty__icon{display:inline-flex;align-items:center;justify-content:center;width:56px;height:56px;border-radius:14px;margin-bottom:2px}.strct-empty__icon--neutral{background:var(--bg-3);color:var(--t2)}.strct-empty__icon--warning{background:var(--warning-bg);color:var(--warning)}.strct-empty__icon--critical{background:var(--critical-bg);color:var(--critical)}.strct-empty__icon--accent{background:var(--acc-m);color:var(--acc)}.strct-empty__title{margin:0;font-size:15px;font-weight:600;color:var(--t1)}.strct-empty__desc{margin:0;max-width:360px;font-size:13px;line-height:1.5;color:var(--t3)}.strct-empty__actions{display:flex;align-items:center;gap:8px;margin-top:6px}.strct-empty__actions:empty{display:none}\n"], dependencies: [{ kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "strictName", "size", "strokeWidth", "badge", "ariaLabel", "count", "countMax", "countStatus", "countLabel"] }, { kind: "component", type: StrctSpinner, selector: "strct-spinner", inputs: ["size", "label", "caption"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
19850
+ `, isInline: true, styles: [".strct-empty{display:flex;flex-direction:column;align-items:center;text-align:center;gap:10px;padding:40px 24px}.strct-empty__icon{display:inline-flex;align-items:center;justify-content:center;width:56px;height:56px;border-radius:14px;margin-bottom:2px}.strct-empty__icon--neutral{background:var(--bg-3);color:var(--t2)}.strct-empty__icon--warning{background:var(--warning-bg);color:var(--warning)}.strct-empty__icon--critical{background:var(--critical-bg);color:var(--critical)}.strct-empty__icon--accent{background:var(--acc-m);color:var(--acc)}.strct-empty__title{margin:0;font-size:15px;font-weight:600;color:var(--t1)}.strct-empty__desc{margin:0;max-width:360px;font-size:13px;line-height:1.5;color:var(--t3)}.strct-empty--sm{display:grid;grid-template-columns:auto minmax(0,1fr);align-items:center;gap:2px var(--space-2);text-align:start;padding:var(--space-3)}.strct-empty--sm .strct-empty__icon{width:16px;height:16px;border-radius:0;margin-bottom:0;background:none;grid-row:1}.strct-empty--sm .strct-empty__title{font-size:13px;grid-row:1}.strct-empty--sm .strct-empty__desc,.strct-empty--sm .strct-empty__actions{grid-column:2}.strct-empty--sm .strct-empty__desc{font-size:var(--text-sm);max-width:none}.strct-empty--sm .strct-empty__actions:empty{display:none}.strct-empty__actions{display:flex;align-items:center;gap:8px;margin-top:6px}.strct-empty__actions:empty{display:none}\n"], dependencies: [{ kind: "component", type: StrctIcon, selector: "strct-icon", inputs: ["name", "strictName", "size", "strokeWidth", "badge", "ariaLabel", "count", "countMax", "countStatus", "countLabel"] }, { kind: "component", type: StrctSpinner, selector: "strct-spinner", inputs: ["size", "label", "caption"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
19741
19851
  }
19742
19852
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctEmptyState, decorators: [{
19743
19853
  type: Component,
@@ -19762,7 +19872,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
19762
19872
  class: 'strct-empty',
19763
19873
  '[class.strct-empty--sm]': "size() === 'sm'",
19764
19874
  '[attr.aria-busy]': "variant() === 'loading' ? 'true' : null",
19765
- }, styles: [".strct-empty{display:flex;flex-direction:column;align-items:center;text-align:center;gap:10px;padding:40px 24px}.strct-empty__icon{display:inline-flex;align-items:center;justify-content:center;width:56px;height:56px;border-radius:14px;margin-bottom:2px}.strct-empty__icon--neutral{background:var(--bg-3);color:var(--t2)}.strct-empty__icon--warning{background:var(--warning-bg);color:var(--warning)}.strct-empty__icon--critical{background:var(--critical-bg);color:var(--critical)}.strct-empty__icon--accent{background:var(--acc-m);color:var(--acc)}.strct-empty__title{margin:0;font-size:15px;font-weight:600;color:var(--t1)}.strct-empty__desc{margin:0;max-width:360px;font-size:13px;line-height:1.5;color:var(--t3)}.strct-empty__actions{display:flex;align-items:center;gap:8px;margin-top:6px}.strct-empty__actions:empty{display:none}\n"] }]
19875
+ }, styles: [".strct-empty{display:flex;flex-direction:column;align-items:center;text-align:center;gap:10px;padding:40px 24px}.strct-empty__icon{display:inline-flex;align-items:center;justify-content:center;width:56px;height:56px;border-radius:14px;margin-bottom:2px}.strct-empty__icon--neutral{background:var(--bg-3);color:var(--t2)}.strct-empty__icon--warning{background:var(--warning-bg);color:var(--warning)}.strct-empty__icon--critical{background:var(--critical-bg);color:var(--critical)}.strct-empty__icon--accent{background:var(--acc-m);color:var(--acc)}.strct-empty__title{margin:0;font-size:15px;font-weight:600;color:var(--t1)}.strct-empty__desc{margin:0;max-width:360px;font-size:13px;line-height:1.5;color:var(--t3)}.strct-empty--sm{display:grid;grid-template-columns:auto minmax(0,1fr);align-items:center;gap:2px var(--space-2);text-align:start;padding:var(--space-3)}.strct-empty--sm .strct-empty__icon{width:16px;height:16px;border-radius:0;margin-bottom:0;background:none;grid-row:1}.strct-empty--sm .strct-empty__title{font-size:13px;grid-row:1}.strct-empty--sm .strct-empty__desc,.strct-empty--sm .strct-empty__actions{grid-column:2}.strct-empty--sm .strct-empty__desc{font-size:var(--text-sm);max-width:none}.strct-empty--sm .strct-empty__actions:empty{display:none}.strct-empty__actions{display:flex;align-items:center;gap:8px;margin-top:6px}.strct-empty__actions:empty{display:none}\n"] }]
19766
19876
  }], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], description: [{ type: i0.Input, args: [{ isSignal: true, alias: "description", required: false }] }] } });
19767
19877
 
19768
19878
  /**
@@ -22922,7 +23032,9 @@ class StrctReorderItem {
22922
23032
  // Captured before our own binding ever writes tabindex, so it never flips.
22923
23033
  hadOwnTabindex = this.el.nativeElement.hasAttribute('tabindex');
22924
23034
  hostTabindex() {
22925
- return this.hadOwnTabindex ? null : 0;
23035
+ if (this.hadOwnTabindex)
23036
+ return null;
23037
+ return this.list.reorderDisabled() ? null : 0;
22926
23038
  }
22927
23039
  /** The keys that apply: the sideways pair only when there is somewhere to go. */
22928
23040
  shortcuts() {
@@ -23008,7 +23120,7 @@ class StrctReorderItem {
23008
23120
  setTimeout(() => this.list.items()[to]?.focus());
23009
23121
  }
23010
23122
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctReorderItem, deps: [], target: i0.ɵɵFactoryTarget.Directive });
23011
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.4", type: StrctReorderItem, isStandalone: true, selector: "[strctReorderItem]", host: { listeners: { "pointerdown": "onPointerDown($event)", "dragstart": "onDragStart($event)", "dragover": "onDragOver($event)", "drop": "onDrop($event)", "dragend": "onDragEnd()", "keydown": "onKeydown($event)" }, properties: { "attr.draggable": "!list.reorderDisabled()", "attr.tabindex": "hostTabindex()", "class.strct-reorder--dragging": "isDragging()", "class.strct-reorder--over": "isOver()", "attr.aria-roledescription": "'sortable'", "attr.aria-keyshortcuts": "shortcuts()", "attr.aria-posinset": "index() + 1", "attr.aria-setsize": "list.items().length", "attr.aria-describedby": "list.instructions() ? list.instructionsId : null" } }, ngImport: i0 });
23123
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.4", type: StrctReorderItem, isStandalone: true, selector: "[strctReorderItem]", host: { listeners: { "pointerdown": "onPointerDown($event)", "dragstart": "onDragStart($event)", "dragover": "onDragOver($event)", "drop": "onDrop($event)", "dragend": "onDragEnd()", "keydown": "onKeydown($event)" }, properties: { "attr.draggable": "!list.reorderDisabled()", "attr.tabindex": "hostTabindex()", "class.strct-reorder--dragging": "isDragging()", "class.strct-reorder--over": "isOver()", "attr.aria-roledescription": "list.reorderDisabled() ? null : 'sortable'", "attr.aria-keyshortcuts": "list.reorderDisabled() ? null : shortcuts()", "attr.aria-posinset": "list.reorderDisabled() ? null : index() + 1", "attr.aria-setsize": "list.reorderDisabled() ? null : list.items().length", "attr.aria-describedby": "list.reorderDisabled() || !list.instructions() ? null : list.instructionsId" } }, ngImport: i0 });
23012
23124
  }
23013
23125
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctReorderItem, decorators: [{
23014
23126
  type: Directive,
@@ -23019,11 +23131,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
23019
23131
  '[attr.tabindex]': 'hostTabindex()',
23020
23132
  '[class.strct-reorder--dragging]': 'isDragging()',
23021
23133
  '[class.strct-reorder--over]': 'isOver()',
23022
- '[attr.aria-roledescription]': "'sortable'",
23023
- '[attr.aria-keyshortcuts]': 'shortcuts()',
23024
- '[attr.aria-posinset]': 'index() + 1',
23025
- '[attr.aria-setsize]': 'list.items().length',
23026
- '[attr.aria-describedby]': 'list.instructions() ? list.instructionsId : null',
23134
+ // A display-only list says nothing about sorting: no roledescription, no
23135
+ // shortcuts, no tab stop of its own, and no description pointing at
23136
+ // instructions that do not apply.
23137
+ '[attr.aria-roledescription]': "list.reorderDisabled() ? null : 'sortable'",
23138
+ '[attr.aria-keyshortcuts]': 'list.reorderDisabled() ? null : shortcuts()',
23139
+ '[attr.aria-posinset]': 'list.reorderDisabled() ? null : index() + 1',
23140
+ '[attr.aria-setsize]': 'list.reorderDisabled() ? null : list.items().length',
23141
+ '[attr.aria-describedby]': 'list.reorderDisabled() || !list.instructions() ? null : list.instructionsId',
23027
23142
  },
23028
23143
  }]
23029
23144
  }], propDecorators: { onPointerDown: [{
@@ -23598,6 +23713,7 @@ class StrctWindow {
23598
23713
  ...(ngDevMode ? [{ debugName: "L" }] : /* istanbul ignore next */ []));
23599
23714
  service = inject(StrctWindowService);
23600
23715
  doc = inject(DOCUMENT$1);
23716
+ zone = inject(NgZone);
23601
23717
  host = inject(ElementRef);
23602
23718
  zOffset = signal(0, /* @ts-ignore */
23603
23719
  ...(ngDevMode ? [{ debugName: "zOffset" }] : /* istanbul ignore next */ []));
@@ -23612,6 +23728,16 @@ class StrctWindow {
23612
23728
  rect = computed(() => this.bounds() ?? this.fallback(), /* @ts-ignore */
23613
23729
  ...(ngDevMode ? [{ debugName: "rect" }] : /* istanbul ignore next */ []));
23614
23730
  restoreTo = null;
23731
+ /** A click beside the window, when `closeOnOutside` asks for one. */
23732
+ onOutside = (event) => {
23733
+ if (this.closeOnOutside() !== 'minimize' || !this.open() || this.minimized())
23734
+ return;
23735
+ const frame = this.host.nativeElement.querySelector('.strct-window__frame');
23736
+ const target = event.target;
23737
+ if (frame && target instanceof Node && frame.contains(target))
23738
+ return;
23739
+ this.zone.run(() => this.minimize());
23740
+ };
23615
23741
  constructor() {
23616
23742
  this.service.register(this);
23617
23743
  effect(() => {
@@ -23621,6 +23747,19 @@ class StrctWindow {
23621
23747
  queueMicrotask(() => this.focusTitle());
23622
23748
  }
23623
23749
  });
23750
+ // `closeOnOutside` was declared and never read. The listener is added only
23751
+ // while a window asks for it, and outside the zone — a click anywhere on
23752
+ // the page must not cost a change-detection pass for every open window.
23753
+ effect((onCleanup) => {
23754
+ if (this.closeOnOutside() !== 'minimize' || !this.open() || this.minimized())
23755
+ return;
23756
+ // Deferred, so the click that opened the window does not close it.
23757
+ const timer = setTimeout(() => this.zone.runOutsideAngular(() => this.doc.addEventListener('pointerdown', this.onOutside, true)));
23758
+ onCleanup(() => {
23759
+ clearTimeout(timer);
23760
+ this.doc.removeEventListener('pointerdown', this.onOutside, true);
23761
+ });
23762
+ });
23624
23763
  }
23625
23764
  /** Raise this window above the others when it is clicked. */
23626
23765
  focusWindow() {
@@ -24011,8 +24150,25 @@ class StrctChatThread {
24011
24150
  /** False once the reader scrolls away from the end; true again at the end. */
24012
24151
  atEnd = signal(true, /* @ts-ignore */
24013
24152
  ...(ngDevMode ? [{ debugName: "atEnd" }] : /* istanbul ignore next */ []));
24153
+ destroyRef = inject(DestroyRef);
24014
24154
  constructor() {
24015
- afterNextRender(() => this.scrollToEnd());
24155
+ afterNextRender(() => {
24156
+ this.scrollToEnd();
24157
+ // A new message, or a streaming one growing, changes the list's height —
24158
+ // and nothing else tells the thread that. Following `busy` alone left the
24159
+ // reader looking at the message before last.
24160
+ if (typeof ResizeObserver === 'undefined')
24161
+ return;
24162
+ const list = this.host.nativeElement.querySelector('.strct-chat__list');
24163
+ if (!list)
24164
+ return;
24165
+ const ro = new ResizeObserver(() => {
24166
+ if (this.autoScroll() && this.atEnd())
24167
+ this.scrollToEnd();
24168
+ });
24169
+ ro.observe(list);
24170
+ this.destroyRef.onDestroy(() => ro.disconnect());
24171
+ });
24016
24172
  effect(() => {
24017
24173
  this.busy();
24018
24174
  if (this.autoScroll() && this.atEnd())