@akcelik/strct 5.0.1 → 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. */
@@ -7810,22 +7810,60 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
7810
7810
  class StrctCodeInline {
7811
7811
  /** Append a copy button that copies this element's text. */
7812
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 });
7813
7822
  host = inject(ElementRef);
7814
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 */ []));
7815
7830
  constructor() {
7816
7831
  afterNextRender(() => {
7817
7832
  if (!this.copyable())
7818
7833
  return;
7819
7834
  const el = this.host.nativeElement;
7820
7835
  const ref = this.vcr.createComponent(StrctCopy);
7821
- // The text to copy is what the element says, before the button joins it.
7822
- ref.setInput('text', el.textContent?.trim() ?? '');
7823
- ref.changeDetectorRef.detectChanges();
7824
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
+ });
7825
7857
  });
7826
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
+ }
7827
7865
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctCodeInline, deps: [], target: i0.ɵɵFactoryTarget.Directive });
7828
- 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 });
7829
7867
  }
7830
7868
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctCodeInline, decorators: [{
7831
7869
  type: Directive,
@@ -7834,9 +7872,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
7834
7872
  host: {
7835
7873
  class: 'strct-code-inline',
7836
7874
  '[class.strct-code-inline--copyable]': 'copyable()',
7875
+ '[class.strct-code-inline--wrap]': 'wrap()',
7837
7876
  },
7838
7877
  }]
7839
- }], 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 }] }] } });
7840
7879
 
7841
7880
  /**
7842
7881
  * The standard strip above a data grid: a search field, the active filter
@@ -14309,6 +14348,26 @@ class StrctDatagrid {
14309
14348
  return;
14310
14349
  untracked(() => this.selected.set(new Set(init)));
14311
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
+ });
14312
14371
  // Single mode: the checked row follows `selectedId`, whoever wrote it — the
14313
14372
  // user picking, or a consumer restoring a pick from an earlier step. It
14314
14373
  // writes `selected` directly rather than through commitSelection, so an
@@ -22973,7 +23032,9 @@ class StrctReorderItem {
22973
23032
  // Captured before our own binding ever writes tabindex, so it never flips.
22974
23033
  hadOwnTabindex = this.el.nativeElement.hasAttribute('tabindex');
22975
23034
  hostTabindex() {
22976
- return this.hadOwnTabindex ? null : 0;
23035
+ if (this.hadOwnTabindex)
23036
+ return null;
23037
+ return this.list.reorderDisabled() ? null : 0;
22977
23038
  }
22978
23039
  /** The keys that apply: the sideways pair only when there is somewhere to go. */
22979
23040
  shortcuts() {
@@ -23059,7 +23120,7 @@ class StrctReorderItem {
23059
23120
  setTimeout(() => this.list.items()[to]?.focus());
23060
23121
  }
23061
23122
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctReorderItem, deps: [], target: i0.ɵɵFactoryTarget.Directive });
23062
- 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 });
23063
23124
  }
23064
23125
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: StrctReorderItem, decorators: [{
23065
23126
  type: Directive,
@@ -23070,11 +23131,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
23070
23131
  '[attr.tabindex]': 'hostTabindex()',
23071
23132
  '[class.strct-reorder--dragging]': 'isDragging()',
23072
23133
  '[class.strct-reorder--over]': 'isOver()',
23073
- '[attr.aria-roledescription]': "'sortable'",
23074
- '[attr.aria-keyshortcuts]': 'shortcuts()',
23075
- '[attr.aria-posinset]': 'index() + 1',
23076
- '[attr.aria-setsize]': 'list.items().length',
23077
- '[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',
23078
23142
  },
23079
23143
  }]
23080
23144
  }], propDecorators: { onPointerDown: [{
@@ -23649,6 +23713,7 @@ class StrctWindow {
23649
23713
  ...(ngDevMode ? [{ debugName: "L" }] : /* istanbul ignore next */ []));
23650
23714
  service = inject(StrctWindowService);
23651
23715
  doc = inject(DOCUMENT$1);
23716
+ zone = inject(NgZone);
23652
23717
  host = inject(ElementRef);
23653
23718
  zOffset = signal(0, /* @ts-ignore */
23654
23719
  ...(ngDevMode ? [{ debugName: "zOffset" }] : /* istanbul ignore next */ []));
@@ -23663,6 +23728,16 @@ class StrctWindow {
23663
23728
  rect = computed(() => this.bounds() ?? this.fallback(), /* @ts-ignore */
23664
23729
  ...(ngDevMode ? [{ debugName: "rect" }] : /* istanbul ignore next */ []));
23665
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
+ };
23666
23741
  constructor() {
23667
23742
  this.service.register(this);
23668
23743
  effect(() => {
@@ -23672,6 +23747,19 @@ class StrctWindow {
23672
23747
  queueMicrotask(() => this.focusTitle());
23673
23748
  }
23674
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
+ });
23675
23763
  }
23676
23764
  /** Raise this window above the others when it is clicked. */
23677
23765
  focusWindow() {
@@ -24062,8 +24150,25 @@ class StrctChatThread {
24062
24150
  /** False once the reader scrolls away from the end; true again at the end. */
24063
24151
  atEnd = signal(true, /* @ts-ignore */
24064
24152
  ...(ngDevMode ? [{ debugName: "atEnd" }] : /* istanbul ignore next */ []));
24153
+ destroyRef = inject(DestroyRef);
24065
24154
  constructor() {
24066
- 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
+ });
24067
24172
  effect(() => {
24068
24173
  this.busy();
24069
24174
  if (this.autoScroll() && this.atEnd())